@kahwee/sf-map-svg 2.2.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +37 -624
- package/dist/data/README.md +3 -2
- package/dist/src/full-data.d.ts +3 -0
- package/dist/src/full-data.js +10 -0
- package/dist/src/guide-map.d.ts +1 -1
- package/dist/src/guide-map.js +4 -3
- package/dist/src/guide.d.ts +1 -2
- package/dist/src/map.d.ts +1 -1
- package/dist/src/map.js +5 -3
- package/dist/src/static-data.d.ts +3 -0
- package/dist/src/static-data.js +25 -0
- package/docs/EXAMPLES.md +13 -12
- package/docs/api-audit.md +5 -14
- package/docs/consumer-integration.md +12 -159
- package/docs/guide-bundle-report.md +5 -7
- package/docs/migration-v3.md +30 -0
- package/package.json +12 -23
- package/dist/src/custom-map.d.ts +0 -3
- package/dist/src/custom-map.js +0 -1
- package/dist/src/explorer.d.ts +0 -4
- package/dist/src/explorer.js +0 -30
- package/dist/src/index.d.ts +0 -12
- package/dist/src/index.js +0 -21
- package/dist/src/interactive-data.d.ts +0 -6
- package/dist/src/interactive-data.js +0 -7
- package/dist/src/interactive.d.ts +0 -4
- package/dist/src/interactive.js +0 -7
- package/docs/migration-v2.md +0 -238
package/dist/data/README.md
CHANGED
|
@@ -99,11 +99,12 @@ Lookup is exact after case/punctuation normalization, returns `undefined` for an
|
|
|
99
99
|
## Draw a neighborhood with the existing projection
|
|
100
100
|
|
|
101
101
|
```js
|
|
102
|
-
import {
|
|
102
|
+
import { renderMap } from '@kahwee/sf-map-svg';
|
|
103
|
+
import { fullMapData } from '@kahwee/sf-map-svg/data/full';
|
|
103
104
|
import { getNeighborhood } from '@kahwee/sf-map-svg/data';
|
|
104
105
|
import { geometryPath } from '@kahwee/sf-map-svg/geometry';
|
|
105
106
|
|
|
106
|
-
const map =
|
|
107
|
+
const map = renderMap(fullMapData.map, { districtFills: false });
|
|
107
108
|
const mission = getNeighborhood('Inner Mission');
|
|
108
109
|
const pathData = geometryPath(mission.geometry, map.project);
|
|
109
110
|
// Use pathData as an SVG <path d="..."> over map.svg.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { districtMaps } from '../data/districts.js';
|
|
2
|
+
import { neighborhoodCollections } from '../data/lookup.js';
|
|
3
|
+
import { deepFreeze } from './immutable.js';
|
|
4
|
+
import { staticMapData } from './static-data.js';
|
|
5
|
+
/** Complete, explicit geographic preset. Import this only when all datasets are needed. */
|
|
6
|
+
export const fullMapData = deepFreeze({
|
|
7
|
+
map: staticMapData,
|
|
8
|
+
neighborhoods: neighborhoodCollections,
|
|
9
|
+
districts: districtMaps,
|
|
10
|
+
});
|
package/dist/src/guide-map.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { InteractiveSFMapElement, InteractiveSFMapOptions } from './
|
|
1
|
+
import type { NeighborhoodExplorerElement as InteractiveSFMapElement, NeighborhoodExplorerOptions as InteractiveSFMapOptions } from './types.js';
|
|
2
2
|
/** Guide map preset: SFAR neighborhoods, major parks, BART, and curated roads. */
|
|
3
3
|
export declare function createGuideMap(options?: InteractiveSFMapOptions): InteractiveSFMapElement;
|
|
4
4
|
/** Enhance createGuideShell() in place with a fixed compact chrome layout.
|
package/dist/src/guide-map.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
+
import { createNeighborhoodExplorerCore } from './explorer-core.js';
|
|
1
2
|
import { guideMapData } from './guide-data.js';
|
|
2
|
-
import { createInteractiveSFMapWithData } from './interactive-data.js';
|
|
3
3
|
import { guideOptions } from './presets.js';
|
|
4
4
|
import { validateExplorerOptions } from './validation.js';
|
|
5
5
|
/** Guide map preset: SFAR neighborhoods, major parks, BART, and curated roads. */
|
|
6
6
|
export function createGuideMap(options = {}) {
|
|
7
7
|
validateExplorerOptions(options);
|
|
8
|
-
return
|
|
8
|
+
return createNeighborhoodExplorerCore({
|
|
9
9
|
mode: 'neighborhoods',
|
|
10
10
|
...options,
|
|
11
11
|
layers: {
|
|
12
12
|
...guideOptions.layers,
|
|
13
13
|
...options.layers,
|
|
14
14
|
},
|
|
15
|
-
|
|
15
|
+
interface: 'map',
|
|
16
|
+
}, guideMapData);
|
|
16
17
|
}
|
|
17
18
|
/** Enhance createGuideShell() in place with a fixed compact chrome layout.
|
|
18
19
|
* Keep an accessible external place list when hiding the native pickers.
|
package/dist/src/guide.d.ts
CHANGED
|
@@ -2,5 +2,4 @@ export type { InteractiveSFMapData } from './explorer-data.js';
|
|
|
2
2
|
export { guideMapData } from './guide-data.js';
|
|
3
3
|
export { loadGuideDetailedData } from './guide-detailed.js';
|
|
4
4
|
export { createGuideMap, mountGuideMap } from './guide-map.js';
|
|
5
|
-
export type { InteractiveSFMapElement, InteractiveSFMapOptions } from './
|
|
6
|
-
export type { CameraOptions, MapFeatures, MapMarker } from './types.js';
|
|
5
|
+
export type { CameraOptions, MapFeatures, MapMarker, NeighborhoodExplorerElement as InteractiveSFMapElement, NeighborhoodExplorerOptions as InteractiveSFMapOptions, } from './types.js';
|
package/dist/src/map.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MapController, MapOptions } from './controller-types.js';
|
|
2
|
-
import {
|
|
2
|
+
import type { InteractiveSFMapData } from './explorer-data.js';
|
|
3
3
|
export type * from './controller-types.js';
|
|
4
4
|
export type { InteractiveSFMapData as MapData } from './explorer-data.js';
|
|
5
5
|
export type { CameraOptions, DistrictSelection, DistrictStyle, DistrictYear, InteractiveLayers, MapFeatures, MapMarker, MapOverlay, MapPadding, MapViewport, NeighborhoodSelection, } from './types.js';
|
package/dist/src/map.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { validateCameraOptions } from './camera.js';
|
|
2
2
|
import { expandMapOptions, prepareConfiguration } from './configuration.js';
|
|
3
|
+
import { createNeighborhoodExplorerCore } from './explorer-core.js';
|
|
3
4
|
import { controlKeys, layerKeys } from './features.js';
|
|
4
|
-
import { createInteractiveSFMapWithData } from './interactive-data.js';
|
|
5
5
|
import { assertOptions } from './validation.js';
|
|
6
6
|
/** No geography is imported. Supply a preset or your own immutable data. */
|
|
7
7
|
export function createMap(data, options = {}) {
|
|
@@ -11,10 +11,12 @@ export function createMap(data, options = {}) {
|
|
|
11
11
|
layers: options.layers,
|
|
12
12
|
controls: options.controls,
|
|
13
13
|
});
|
|
14
|
-
const element =
|
|
14
|
+
const element = createNeighborhoodExplorerCore({
|
|
15
|
+
mode: 'basemap',
|
|
15
16
|
...expanded,
|
|
17
|
+
interface: 'map',
|
|
16
18
|
onOverlayActivate: () => { },
|
|
17
|
-
});
|
|
19
|
+
}, data);
|
|
18
20
|
const subscriptions = new Set();
|
|
19
21
|
let destroyed = false;
|
|
20
22
|
function active() {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { landmarks } from '../data/landmarks.js';
|
|
2
|
+
import { keyRoads } from '../data/roads.js';
|
|
3
|
+
import { bartStations } from '../data/stations.js';
|
|
4
|
+
import data from './data.js';
|
|
5
|
+
import { deepFreeze } from './immutable.js';
|
|
6
|
+
/** Complete data for static rendering, without interactive lookup collections. */
|
|
7
|
+
export const staticMapData = deepFreeze({
|
|
8
|
+
coast: data.coast,
|
|
9
|
+
districts: data.districts,
|
|
10
|
+
neighborhoods: data.neighborhoods,
|
|
11
|
+
highways: data.highways,
|
|
12
|
+
landmarks: landmarks.features.map(({ id, properties, geometry }) => ({
|
|
13
|
+
id,
|
|
14
|
+
...properties,
|
|
15
|
+
geometry,
|
|
16
|
+
})),
|
|
17
|
+
keyRoads: keyRoads.features.map(({ id, properties, geometry }) => ({
|
|
18
|
+
id,
|
|
19
|
+
...properties,
|
|
20
|
+
geometry,
|
|
21
|
+
})),
|
|
22
|
+
bartStations: bartStations.features.flatMap(({ id, properties, geometry }) => geometry.type === 'Point'
|
|
23
|
+
? [{ id, name: properties.name, coordinates: geometry.coordinates }]
|
|
24
|
+
: []),
|
|
25
|
+
});
|
package/docs/EXAMPLES.md
CHANGED
|
@@ -16,25 +16,25 @@ Choose by task. All snippets use the public package API; browser examples need a
|
|
|
16
16
|
|
|
17
17
|
```ts
|
|
18
18
|
import { writeFile } from 'node:fs/promises';
|
|
19
|
-
import {
|
|
19
|
+
import { renderMap } from '@kahwee/sf-map-svg';
|
|
20
|
+
import { staticMapData } from '@kahwee/sf-map-svg/data/static';
|
|
20
21
|
|
|
21
|
-
const svg =
|
|
22
|
+
const svg = renderMap(staticMapData, {
|
|
22
23
|
year: 2022,
|
|
23
24
|
landmarks: true,
|
|
24
25
|
bartStations: true,
|
|
25
26
|
idPrefix: 'example',
|
|
26
|
-
});
|
|
27
|
+
}).svg;
|
|
27
28
|
await writeFile('districts.svg', svg);
|
|
28
29
|
```
|
|
29
30
|
|
|
30
|
-
The
|
|
31
|
+
The static preset includes the packaged map layers without interactive lookup collections. For smaller bundles, pass selected data to the root or `/static` renderer. See the [static recipe](../README.md#static-svg).
|
|
31
32
|
|
|
32
33
|
For an election choropleth, use `renderMap(data, { year, districtStyle })` or
|
|
33
34
|
`createMap({ map: data, districts: districtMaps, neighborhoods: {} }, options)`.
|
|
34
35
|
The controller exposes `setDistrictYear`, `setDistrictStyle`, `selectDistrict`, and typed
|
|
35
36
|
district events; `getLayerPaths(data, { year })` returns fitted paths without SVG markup.
|
|
36
|
-
See the [
|
|
37
|
-
“Election choropleth” example.
|
|
37
|
+
See the [Storybook election choropleth](../stories/ElectionMap.stories.ts) for a working example.
|
|
38
38
|
|
|
39
39
|
## Lightweight interactive guide
|
|
40
40
|
|
|
@@ -45,23 +45,24 @@ const map = createGuideMap({ layers: { roadLabels: false } });
|
|
|
45
45
|
document.querySelector('#map')?.append(map);
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
The guide includes selected coast, SFAR neighborhoods, parks, roads, and stations. Detailed geography loads only when explicitly requested; see the [guide recipe](
|
|
48
|
+
The guide includes selected coast, SFAR neighborhoods, parks, roads, and stations. Detailed geography loads only when explicitly requested; see the [consumer guide recipe](consumer-integration.md).
|
|
49
49
|
|
|
50
50
|
## Selected geography
|
|
51
51
|
|
|
52
52
|
```ts
|
|
53
|
-
import {
|
|
53
|
+
import { createMap } from '@kahwee/sf-map-svg';
|
|
54
54
|
import coast from '@kahwee/sf-map-svg/data/coast.json' with { type: 'json' };
|
|
55
55
|
import realtor from '@kahwee/sf-map-svg/data/neighborhoods-realtor.json' with { type: 'json' };
|
|
56
56
|
|
|
57
|
-
const map =
|
|
57
|
+
const map = createMap(
|
|
58
58
|
{
|
|
59
59
|
map: { coast: coast.features[0].geometry },
|
|
60
60
|
neighborhoods: { realtor },
|
|
61
61
|
},
|
|
62
62
|
{ mode: 'neighborhoods', layers: { highways: false, keyRoads: false } },
|
|
63
63
|
);
|
|
64
|
-
document.querySelector('#map')?.append(map);
|
|
64
|
+
document.querySelector('#map')?.append(map.element);
|
|
65
|
+
// On unmount: map.destroy();
|
|
65
66
|
```
|
|
66
67
|
|
|
67
68
|
This imports one neighborhood definition source. To omit its geometry too, import catalog metadata alone from `/data/catalog`.
|
|
@@ -89,7 +90,7 @@ Coordinates are WGS84 `[longitude, latitude]`. The overlay follows pan and zoom.
|
|
|
89
90
|
|
|
90
91
|
## California propositions by SF district
|
|
91
92
|
|
|
92
|
-
[Open the interactive explorer](https://kahwee.github.io/sf-map-svg/propositions.html). It uses the public
|
|
93
|
+
[Open the interactive explorer](https://kahwee.github.io/sf-map-svg/propositions.html). It uses the public static renderer with only the 2022 district and coast datasets and colors each district from the [certified results JSON](../data/propositions/2024-11-05.json). The JSON includes all ten statewide propositions on the November 2024 ballot, with Yes and No counts for each of San Francisco's eleven supervisorial districts. Its scope is SF votes, not statewide totals or voter demographics.
|
|
93
94
|
|
|
94
95
|
The [import script](../scripts/import-2024-propositions.py) checks each district sum against the official citywide count. [Geographic and election sources](../SOURCES.md) explain the provenance.
|
|
95
96
|
|
|
@@ -101,4 +102,4 @@ compact sources, a north arrow, and a metric scale. Storybook's **Checks / Consu
|
|
|
101
102
|
API** includes executable motion, reduced-motion, and progressive-shell checks.
|
|
102
103
|
See [consumer integration](consumer-integration.md) for server and browser recipes.
|
|
103
104
|
|
|
104
|
-
For the
|
|
105
|
+
For the controller and explicit data imports, see [the v3 migration guide](migration-v3.md).
|
package/docs/api-audit.md
CHANGED
|
@@ -63,18 +63,9 @@ Exercise enable/disable round trips and callbacks that synchronously request a
|
|
|
63
63
|
second update. Add published-type and bundle regressions when the import graph or
|
|
64
64
|
public types change. Update this matrix when introducing a new interaction.
|
|
65
65
|
|
|
66
|
-
## Verification for
|
|
66
|
+
## Verification for version 3
|
|
67
67
|
|
|
68
|
-
- `pnpm check
|
|
69
|
-
- `pnpm test:stories:coverage
|
|
70
|
-
- `pnpm demo`, `pnpm build-storybook`, and
|
|
71
|
-
-
|
|
72
|
-
`pnpm report:guide` (the report script uses 1024-byte units).
|
|
73
|
-
- Isolated agent-browser inspection of generated static SVG examples and the
|
|
74
|
-
interactive example at desktop and 390px found no page overflow or browser
|
|
75
|
-
errors. Interactive labels remained collision-filtered at both widths. Browser
|
|
76
|
-
screenshots were kept outside the repository and the session was closed.
|
|
77
|
-
|
|
78
|
-
The independent pre-release review reproduced and closed stale cluster activation
|
|
79
|
-
and checked the v2 overlay event bridge. See the controller and robustness stories
|
|
80
|
-
for pointer/keyboard activation, reentrant replacement, and revoked old nodes.
|
|
68
|
+
- `pnpm check` covers Node regressions, types, lint, catalog topology, and bundle budgets.
|
|
69
|
+
- `pnpm test:stories:coverage` runs Chromium stories and reports browser coverage.
|
|
70
|
+
- `pnpm demo`, `pnpm build-storybook`, `pnpm test:package`, and `pnpm test:visual` cover examples, rendered stories, packed consumer imports, and reviewed map screenshots.
|
|
71
|
+
- The packed consumer asserts removed compatibility entry points are absent.
|
|
@@ -1,165 +1,18 @@
|
|
|
1
|
-
# Consumer integration
|
|
1
|
+
# Consumer integration
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
recipes below remain supported for existing guide and progressive-shell consumers.
|
|
3
|
+
Use the version 3 root controller and explicit geographic data. The [migration guide](migration-v3.md) maps removed imports to supported calls.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
For a compact browser guide:
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
geometry from a bundle.
|
|
7
|
+
```ts
|
|
8
|
+
import { createMap } from '@kahwee/sf-map-svg';
|
|
9
|
+
import { guideMapData } from '@kahwee/sf-map-svg/guide/data';
|
|
10
|
+
import { guideOptions } from '@kahwee/sf-map-svg/presets';
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
with compression and normal long-lived versioned cache headers. Avoid root or
|
|
19
|
-
`data` barrel imports in client code when only one collection is needed.
|
|
20
|
-
|
|
21
|
-
Load `guide/detailed` only after a user requests higher detail. Dynamically import
|
|
22
|
-
the browser map when its container approaches the viewport. Keep the static
|
|
23
|
-
renderer in server/build code; do not import it in a client loader. The overview
|
|
24
|
-
SVG still contains geography, so both static and interactive representations cost
|
|
25
|
-
bytes. Measure HTML and JS separately, including compressed bytes and lazy chunks.
|
|
26
|
-
Do not inline full-detail data into every page.
|
|
27
|
-
|
|
28
|
-
`pnpm report:guide` records measured raw/gzip sizes and audits included datasets.
|
|
29
|
-
`pnpm check` enforces 125 KiB initial guide and 35 KiB data-free renderer budgets.
|
|
30
|
-
These are regression ceilings, not claims about every application's final bundle.
|
|
31
|
-
Current measurements are in [the bundle report](guide-bundle-report.md).
|
|
32
|
-
|
|
33
|
-
## Progressive compact layout
|
|
34
|
-
|
|
35
|
-
Build/server code:
|
|
36
|
-
|
|
37
|
-
```js
|
|
38
|
-
import { createGuideShell } from '@kahwee/sf-map-svg/guide/static';
|
|
39
|
-
const html = createGuideShell({ markers: places, labels: false });
|
|
40
|
-
// Put html into the page alongside an accessible place list.
|
|
12
|
+
const map = createMap(guideMapData, { ...guideOptions });
|
|
13
|
+
document.querySelector('#map')?.append(map.element);
|
|
14
|
+
// Dispose when the containing view unmounts.
|
|
15
|
+
map.destroy();
|
|
41
16
|
```
|
|
42
17
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
```js
|
|
46
|
-
import { mountGuideMap } from '@kahwee/sf-map-svg/guide/map';
|
|
47
|
-
const map = mountGuideMap(document.querySelector('.sf-guide-shell'), {
|
|
48
|
-
markers: places,
|
|
49
|
-
labels: false,
|
|
50
|
-
motion: { duration: 400 },
|
|
51
|
-
markerEntrance: true,
|
|
52
|
-
clustering: true,
|
|
53
|
-
selectedMarkerRing: { color: '#163d61' },
|
|
54
|
-
strings: { touchNavigation: 'Touch pan', touchNavigationDone: 'Done' },
|
|
55
|
-
});
|
|
56
|
-
// In your place list's activation handler:
|
|
57
|
-
map.selectMarker(places[0].id);
|
|
58
|
-
// On component disposal:
|
|
59
|
-
// map.destroy();
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
Pass the same palette, markers, and layer choices to both sides. `labels:false`
|
|
63
|
-
avoids a label-placement change during enhancement: static labels and interactive
|
|
64
|
-
collision placement are different algorithms. Shell placeholders are not working
|
|
65
|
-
controls, and the SVG remains available without JavaScript. The compact shell
|
|
66
|
-
reserves its own rows at all widths; it is not a general serializer for arbitrary
|
|
67
|
-
explorer chrome. Keep external image/card aspect ratios and list heights stable as
|
|
68
|
-
well: a library shell cannot prevent shifts elsewhere in your page.
|
|
69
|
-
|
|
70
|
-
## Callouts without private DOM selectors
|
|
71
|
-
|
|
72
|
-
```js
|
|
73
|
-
const callout = document.createElement('div');
|
|
74
|
-
callout.textContent = 'Selected place';
|
|
75
|
-
callout.style.position = 'absolute';
|
|
76
|
-
map.overlayElement.append(callout);
|
|
77
|
-
const placeCallout = () => {
|
|
78
|
-
const pin = map.getSelectedMarker();
|
|
79
|
-
if (!pin) { callout.hidden = true; return; }
|
|
80
|
-
const point = map.projectToScreen(pin.lng, pin.lat);
|
|
81
|
-
callout.hidden = !point.visible;
|
|
82
|
-
callout.style.transform = `translate(${point.x}px, ${point.y}px)`;
|
|
83
|
-
};
|
|
84
|
-
for (const event of ['markerchange', 'viewportchange', 'mapresize'])
|
|
85
|
-
map.addEventListener(event, placeCallout);
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
These coordinates are relative to the canvas, not the page. Callout size,
|
|
89
|
-
collision avoidance, and focus belong to the application. Use `textContent` for
|
|
90
|
-
untrusted text. Consumer-added listeners should be removed on disposal (or use
|
|
91
|
-
an application-owned AbortSignal).
|
|
92
|
-
|
|
93
|
-
## Motion and dense places
|
|
94
|
-
|
|
95
|
-
Camera motion and marker entrances are independent and optional. Keep stable pin
|
|
96
|
-
IDs across filtering so existing places do not repeatedly drop in. A subsequent
|
|
97
|
-
camera request supersedes an earlier one. Reduced motion wins over application
|
|
98
|
-
options; changing the preference during motion stops at the current position.
|
|
99
|
-
|
|
100
|
-
Clustering uses a deterministic screen-distance grouping in stable marker-ID order using a spatial index. It is
|
|
101
|
-
intended for guide-sized lists, not millions of records. Clicking or pressing Enter
|
|
102
|
-
on a cluster fits its extent and emits `clusteractivate`. Exactly coincident places
|
|
103
|
-
remain clustered even at maximum zoom. Always provide a chooser/list; selecting a
|
|
104
|
-
place removes it from its cluster and draws it above clusters. Avoid silently
|
|
105
|
-
hiding all alternate access when setting `controls.markerPicker:false`.
|
|
106
|
-
|
|
107
|
-
## Verification expectations
|
|
108
|
-
|
|
109
|
-
Run the existing Node and browser suites plus bundle checks. The Consumer API
|
|
110
|
-
stories exercise transitions, supersession, teardown, reduced motion, clustered
|
|
111
|
-
selection, touch names, and 390px shell replacement. Also inspect the example page
|
|
112
|
-
at desktop/tablet/390px: keyboard focus, drag interruption, labels and marker
|
|
113
|
-
collisions, overflow, console errors, and sources disclosure. Test your own
|
|
114
|
-
container/font/CSS and delayed image loading before claiming page-wide zero CLS.
|
|
115
|
-
|
|
116
|
-
## Runtime configuration and failure handling
|
|
117
|
-
|
|
118
|
-
Prefer `setFeatures`, `setLayers`, and `setControls` over recreating a map for each
|
|
119
|
-
checkbox. Patches leave omitted keys alone; explicit `false` disables and
|
|
120
|
-
`undefined` resets. Feature option objects replace the previous object, so
|
|
121
|
-
`setFeatures({ motion: { duration: 200 } })` is predictable without hidden retained
|
|
122
|
-
fields. `getFeatures()` is a detached normalized snapshot, safe for consumer reads.
|
|
123
|
-
|
|
124
|
-
Invalid feature/control/layer updates preserve the previous state. Invalid marker
|
|
125
|
-
and overlay replacements preserve the previous list. Removed overlays lose their
|
|
126
|
-
listeners immediately. Repeated disposal cancels all asynchronous work and returns
|
|
127
|
-
touch gestures to the page. Focused markers stay reachable through filtering;
|
|
128
|
-
when a focused marker disappears, focus returns to the map rather than the page
|
|
129
|
-
body. A focused cluster that dissolves also returns focus to the map.
|
|
130
|
-
|
|
131
|
-
The compact mount uses defaults for its controls, then honors caller overrides.
|
|
132
|
-
Enabling native pickers deliberately adds their row. The default shell replacement
|
|
133
|
-
has stable geometry; custom controls, fonts, and application CSS still require
|
|
134
|
-
consumer verification. Shells reject custom SVG dimensions/padding because the
|
|
135
|
-
interactive projection is fixed. Use `createGuideSVG` for custom static sizes.
|
|
136
|
-
|
|
137
|
-
Adversarial checks cover reentrant camera callbacks, invalid/atomic patches,
|
|
138
|
-
feature round trips, control independence, overlay replacement/disposal, detached
|
|
139
|
-
configuration snapshots, keyboard focus, and 2,000-marker grouping. The camera,
|
|
140
|
-
feature normalization, and spatial grouping modules are separate from DOM rendering
|
|
141
|
-
so future options can extend those contracts without rebuilding their lifecycles.
|
|
142
|
-
|
|
143
|
-
### Adding another optional feature
|
|
144
|
-
|
|
145
|
-
Add its public type and default to the configuration boundary first. Validate and
|
|
146
|
-
copy the full next configuration before touching the current map. Implement enable
|
|
147
|
-
and disable symmetrically, and define what happens to in-flight work when it changes.
|
|
148
|
-
Avoid hiding unrelated controls through a shared parent. Any window/document/media
|
|
149
|
-
listener, observer, animation, or replaceable interactive subtree needs an explicit
|
|
150
|
-
owner and cleanup path. Keep camera timing and geographic grouping independently
|
|
151
|
-
testable rather than embedding more asynchronous state inside renderer callbacks.
|
|
152
|
-
|
|
153
|
-
For each addition, test disabled construction, enabling, disabling, repeated round
|
|
154
|
-
trips, invalid updates, reduced motion where relevant, and disposal. Verify that the
|
|
155
|
-
viewport, selection, keyboard focus, accessible descriptions, and layer legend stay
|
|
156
|
-
consistent. Runtime toggles are not a substitute for selective data imports or
|
|
157
|
-
production bundle measurement. New palette/font/layout choices still need rendered
|
|
158
|
-
consumer checks; passing these tests does not guarantee every arbitrary host CSS or
|
|
159
|
-
data combination.
|
|
160
|
-
|
|
161
|
-
The [maintainer audit matrix](api-audit.md) records option precedence, invalid-input
|
|
162
|
-
behavior, callback reentrancy, ownership, and explicit limits. Interactive options
|
|
163
|
-
reject unknown keys, including nested typos. Shared preset data is immutable;
|
|
164
|
-
clone it before deriving custom collections. The compact mount rejects full
|
|
165
|
-
attribution rather than silently overriding it.
|
|
18
|
+
For server rendering, import `renderMap` from the root and pass `guideMapData.map`, `staticMapData` from `/data/static`, or another explicit `StaticMapData`. Import `/data/full` when interactive lookup collections are also required. All rendering stays offline and has zero runtime dependencies. The guide's detailed geography loads only when `loadGuideDetailedData()` is called.
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
# Guide bundle size report
|
|
2
2
|
|
|
3
|
-
Generated 2026-09-27 by `pnpm report:guide` with Vite production minification and gzip compression. Each emitted JS chunk is compressed independently. The
|
|
3
|
+
Generated 2026-09-27 by `pnpm report:guide` with Vite production minification and gzip compression. Each emitted JS chunk is compressed independently. The report measures the explicit-data v3 root and the optional `@kahwee/sf-map-svg/guide` preset.
|
|
4
4
|
|
|
5
5
|
| Entry | Initial JS, raw | Initial JS, gzip | Explicit detail JS, gzip |
|
|
6
6
|
| --- | ---: | ---: | ---: |
|
|
7
|
-
|
|
|
8
|
-
|
|
|
9
|
-
|
|
|
10
|
-
| Data-free interactive renderer | 72.5 KB | 21.8 KB | — |
|
|
11
|
-
| Guide preset (after) | 461.7 KB | 110.2 KB | 473.7 KB |
|
|
7
|
+
| v3 root (explicit data) | 84.5 KB | 24.6 KB | — |
|
|
8
|
+
| v3 static renderer | 14.8 KB | 4.9 KB | — |
|
|
9
|
+
| Guide preset | 469.8 KB | 112.0 KB | 473.7 KB |
|
|
12
10
|
|
|
13
|
-
**
|
|
11
|
+
**500 KB target:** met.
|
|
14
12
|
|
|
15
13
|
The initial guide chunk graph includes only the overview coast, SFAR realtor neighborhoods, major parks, selected highways, six selected streets, and BART points. It excludes historical districts, SF Find neighborhoods, analysis neighborhoods, and the full catalog. Detailed coast, selected SFAR boundaries, parks, selected highway routes, streets, and BART data are in dynamic chunks and load only when `loadGuideDetailedData()` is called. The data inclusion assertions run as part of this report command.
|
|
16
14
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Migrate to version 3
|
|
2
|
+
|
|
3
|
+
Version 3 removes the deprecated compatibility entry points and their bundled-data factories. This is a breaking major release. Version 2 remains available if you need time to migrate; removed imports fail to resolve in version 3. The root API introduced in version 2 remains the supported API.
|
|
4
|
+
|
|
5
|
+
| Version 2 import or call | Version 3 replacement |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| `/legacy` `renderSFMap(options)` | `renderMap(staticMapData, options).svg` |
|
|
8
|
+
| `/legacy` `createSFMap(options)` | `renderMap(staticMapData, options)` |
|
|
9
|
+
| `/custom-map` `createSFMapWithData(options, data)` | `renderMap(data, options)` |
|
|
10
|
+
| `/explorer` `createNeighborhoodExplorer(options)` | `createMap(fullMapData, options)`; mount `.element` |
|
|
11
|
+
| `/interactive` `createInteractiveSFMap(options)` | `createMap(fullMapData, { mode: 'basemap', ...options })`; mount `.element` |
|
|
12
|
+
| `/interactive-data` `createInteractiveSFMapWithData(data, options)` | `createMap(data, options)`; mount `.element` |
|
|
13
|
+
|
|
14
|
+
`staticMapData` comes from `@kahwee/sf-map-svg/data/static` and includes the complete static map. `fullMapData` comes from `/data/full` and adds the interactive lookup collections. For smaller bundles, construct data from individual `/data/*` modules or use `guideMapData` from `/guide/data`. There is no implicit geographic data in the root import.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { createMap, renderMap } from '@kahwee/sf-map-svg';
|
|
18
|
+
import { fullMapData } from '@kahwee/sf-map-svg/data/full';
|
|
19
|
+
|
|
20
|
+
const svg = renderMap(fullMapData.map, { landmarks: true }).svg;
|
|
21
|
+
const map = createMap(fullMapData, { mode: 'neighborhoods' });
|
|
22
|
+
document.querySelector('#map')?.append(map.element);
|
|
23
|
+
map.camera.zoom(2);
|
|
24
|
+
map.configure({ layers: { landmarks: false } });
|
|
25
|
+
map.destroy();
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The controller owns subscriptions and cleanup. Replace old element calls with controller methods (`camera.get/set/pan/zoom/reset/fit/stop`, `selectNeighborhood`, `setSource`, `setMode`, `setLabels`, `setMarkers`, and `setOverlays`). Group feature flags under `features` and styling under `appearance`; `configure` updates runtime features, layers, and controls. For code that needs the guide shell, `/guide`, `/guide/data`, `/guide/static`, and `/guide/map` remain supported. `/transit` also remains supported.
|
|
29
|
+
|
|
30
|
+
Review tree-shaking after migrating: `/data/full` intentionally includes every packaged layer, while the root and `/static` stay data free. Test server rendering, browser mounting, and map disposal in your application before upgrading production.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kahwee/sf-map-svg",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Offline SVG maps of San Francisco with district boundaries, parks, landmarks, and BART stations.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/src/api.js",
|
|
@@ -10,10 +10,6 @@
|
|
|
10
10
|
"types": "./dist/src/api.d.ts",
|
|
11
11
|
"import": "./dist/src/api.js"
|
|
12
12
|
},
|
|
13
|
-
"./custom-map": {
|
|
14
|
-
"types": "./dist/src/custom-map.d.ts",
|
|
15
|
-
"import": "./dist/src/custom-map.js"
|
|
16
|
-
},
|
|
17
13
|
"./data": {
|
|
18
14
|
"types": "./dist/data/index.d.ts",
|
|
19
15
|
"import": "./dist/data/index.js"
|
|
@@ -26,6 +22,14 @@
|
|
|
26
22
|
"types": "./dist/data/catalog.d.ts",
|
|
27
23
|
"import": "./dist/data/catalog.js"
|
|
28
24
|
},
|
|
25
|
+
"./data/full": {
|
|
26
|
+
"types": "./dist/src/full-data.d.ts",
|
|
27
|
+
"import": "./dist/src/full-data.js"
|
|
28
|
+
},
|
|
29
|
+
"./data/static": {
|
|
30
|
+
"types": "./dist/src/static-data.d.ts",
|
|
31
|
+
"import": "./dist/src/static-data.js"
|
|
32
|
+
},
|
|
29
33
|
"./data/coast": {
|
|
30
34
|
"types": "./dist/data/coast.d.ts",
|
|
31
35
|
"import": "./dist/data/coast.js"
|
|
@@ -68,18 +72,6 @@
|
|
|
68
72
|
"types": "./dist/src/geometry.d.ts",
|
|
69
73
|
"import": "./dist/src/geometry.js"
|
|
70
74
|
},
|
|
71
|
-
"./explorer": {
|
|
72
|
-
"types": "./dist/src/explorer.d.ts",
|
|
73
|
-
"import": "./dist/src/explorer.js"
|
|
74
|
-
},
|
|
75
|
-
"./interactive": {
|
|
76
|
-
"types": "./dist/src/interactive.d.ts",
|
|
77
|
-
"import": "./dist/src/interactive.js"
|
|
78
|
-
},
|
|
79
|
-
"./interactive-data": {
|
|
80
|
-
"types": "./dist/src/interactive-data.d.ts",
|
|
81
|
-
"import": "./dist/src/interactive-data.js"
|
|
82
|
-
},
|
|
83
75
|
"./guide": {
|
|
84
76
|
"types": "./dist/src/guide.d.ts",
|
|
85
77
|
"import": "./dist/src/guide.js"
|
|
@@ -104,10 +96,6 @@
|
|
|
104
96
|
"types": "./dist/src/guide-static.d.ts",
|
|
105
97
|
"import": "./dist/src/guide-static.js"
|
|
106
98
|
},
|
|
107
|
-
"./legacy": {
|
|
108
|
-
"types": "./dist/src/index.d.ts",
|
|
109
|
-
"import": "./dist/src/index.js"
|
|
110
|
-
},
|
|
111
99
|
"./map": {
|
|
112
100
|
"types": "./dist/src/map.d.ts",
|
|
113
101
|
"import": "./dist/src/map.js"
|
|
@@ -130,7 +118,7 @@
|
|
|
130
118
|
"docs/consumer-integration.md",
|
|
131
119
|
"docs/api-audit.md",
|
|
132
120
|
"docs/guide-bundle-report.md",
|
|
133
|
-
"docs/migration-
|
|
121
|
+
"docs/migration-v3.md",
|
|
134
122
|
"CHANGELOG.md"
|
|
135
123
|
],
|
|
136
124
|
"sideEffects": false,
|
|
@@ -183,7 +171,8 @@
|
|
|
183
171
|
"demo": "pnpm build && node examples/build.mjs",
|
|
184
172
|
"format": "biome check --write .",
|
|
185
173
|
"format:check": "biome format .",
|
|
186
|
-
"check": "pnpm peers check && pnpm lint && pnpm data:check && pnpm typecheck && pnpm typecheck:stories && pnpm test && pnpm test:bundle",
|
|
174
|
+
"check": "pnpm peers check && pnpm lint && pnpm check:docs && pnpm data:check && pnpm typecheck && pnpm typecheck:stories && pnpm test && pnpm test:bundle",
|
|
175
|
+
"check:docs": "node scripts/check-docs.mjs",
|
|
187
176
|
"storybook": "pnpm build && storybook dev -p 6006 --host 127.0.0.1 --no-open",
|
|
188
177
|
"build-storybook": "pnpm build && storybook build",
|
|
189
178
|
"data:catalog": "node scripts/build-data-catalog.mjs",
|
package/dist/src/custom-map.d.ts
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
export type { BartStationData, DistrictRowData, KeyRoadData, LandmarkData, SFMapData, } from './map-core.js';
|
|
2
|
-
export { createSFMapWithData, districtColors, districtYears, } from './map-core.js';
|
|
3
|
-
export type { DistrictYear, MapMarker, MapOverlay, SFMapOptions } from './types.js';
|
package/dist/src/custom-map.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { createSFMapWithData, districtColors, districtYears, } from './map-core.js';
|
package/dist/src/explorer.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { NeighborhoodExplorerElement, NeighborhoodExplorerOptions } from './types.js';
|
|
2
|
-
export type { ExplorerMode, NeighborhoodExplorerElement, NeighborhoodExplorerOptions, } from './types.js';
|
|
3
|
-
/** Create an offline, browser-only neighborhood explorer with package datasets. */
|
|
4
|
-
export declare function createNeighborhoodExplorer(options?: NeighborhoodExplorerOptions): NeighborhoodExplorerElement;
|
package/dist/src/explorer.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { bartStations, districtMaps, keyRoads, landmarks, neighborhoodCollections, } from '../data/index.js';
|
|
2
|
-
import data from './data.js';
|
|
3
|
-
import { createNeighborhoodExplorerCore } from './explorer-core.js';
|
|
4
|
-
const packagedData = {
|
|
5
|
-
map: {
|
|
6
|
-
coast: data.coast,
|
|
7
|
-
districts: data.districts,
|
|
8
|
-
neighborhoods: data.neighborhoods,
|
|
9
|
-
highways: data.highways,
|
|
10
|
-
landmarks: landmarks.features.map(({ id, properties, geometry }) => ({
|
|
11
|
-
id,
|
|
12
|
-
...properties,
|
|
13
|
-
geometry,
|
|
14
|
-
})),
|
|
15
|
-
keyRoads: keyRoads.features.map(({ id, properties, geometry }) => ({
|
|
16
|
-
id,
|
|
17
|
-
...properties,
|
|
18
|
-
geometry,
|
|
19
|
-
})),
|
|
20
|
-
bartStations: bartStations.features.flatMap(({ id, properties, geometry }) => geometry.type === 'Point'
|
|
21
|
-
? [{ id, name: properties.name, coordinates: geometry.coordinates }]
|
|
22
|
-
: []),
|
|
23
|
-
},
|
|
24
|
-
neighborhoods: neighborhoodCollections,
|
|
25
|
-
districts: districtMaps,
|
|
26
|
-
};
|
|
27
|
-
/** Create an offline, browser-only neighborhood explorer with package datasets. */
|
|
28
|
-
export function createNeighborhoodExplorer(options = {}) {
|
|
29
|
-
return createNeighborhoodExplorerCore(options, packagedData);
|
|
30
|
-
}
|
package/dist/src/index.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { districtColors, districtYears } from './map-core.js';
|
|
2
|
-
import type { SFMapOptions } from './types.js';
|
|
3
|
-
export type { DistrictYear, MapMarker, MapOverlay, SFMapOptions } from './types.js';
|
|
4
|
-
export { districtColors, districtYears };
|
|
5
|
-
export declare const neighborhoodNames: readonly string[];
|
|
6
|
-
/** Make an offline SVG and the matching longitude/latitude projection. */
|
|
7
|
-
export declare function createSFMap(options?: SFMapOptions): {
|
|
8
|
-
svg: string;
|
|
9
|
-
project: (coordinates: import("../data/types.js").Position) => [number, number];
|
|
10
|
-
viewBox: [number, number, number, number];
|
|
11
|
-
};
|
|
12
|
-
export declare function renderSFMap(options?: SFMapOptions): string;
|
package/dist/src/index.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import data from './data.js';
|
|
2
|
-
import { createSFMapWithData, districtColors, districtYears } from './map-core.js';
|
|
3
|
-
import { bartStations, keyRoads, landmarks } from './overlays.js';
|
|
4
|
-
export { districtColors, districtYears };
|
|
5
|
-
export const neighborhoodNames = Object.freeze(data.neighborhoods.map((item) => item.name));
|
|
6
|
-
const packagedData = {
|
|
7
|
-
coast: data.coast,
|
|
8
|
-
districts: data.districts,
|
|
9
|
-
neighborhoods: data.neighborhoods,
|
|
10
|
-
highways: data.highways,
|
|
11
|
-
landmarks,
|
|
12
|
-
keyRoads,
|
|
13
|
-
bartStations,
|
|
14
|
-
};
|
|
15
|
-
/** Make an offline SVG and the matching longitude/latitude projection. */
|
|
16
|
-
export function createSFMap(options = {}) {
|
|
17
|
-
return createSFMapWithData(options, packagedData);
|
|
18
|
-
}
|
|
19
|
-
export function renderSFMap(options = {}) {
|
|
20
|
-
return createSFMap(options).svg;
|
|
21
|
-
}
|