@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.
@@ -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 { createSFMap } from '@kahwee/sf-map-svg/legacy';
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 = createSFMap({ districtFills: false });
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,3 @@
1
+ import type { MapData } from './map.js';
2
+ /** Complete, explicit geographic preset. Import this only when all datasets are needed. */
3
+ export declare const fullMapData: MapData;
@@ -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
+ });
@@ -1,4 +1,4 @@
1
- import type { InteractiveSFMapElement, InteractiveSFMapOptions } from './interactive-data.js';
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.
@@ -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 createInteractiveSFMapWithData(guideMapData, {
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.
@@ -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 './interactive-data.js';
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 { type InteractiveSFMapData } from './interactive-data.js';
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 = createInteractiveSFMapWithData(data, {
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,3 @@
1
+ import type { StaticMapData } from './static.js';
2
+ /** Complete data for static rendering, without interactive lookup collections. */
3
+ export declare const staticMapData: StaticMapData;
@@ -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 { renderSFMap } from '@kahwee/sf-map-svg/legacy';
19
+ import { renderMap } from '@kahwee/sf-map-svg';
20
+ import { staticMapData } from '@kahwee/sf-map-svg/data/static';
20
21
 
21
- const svg = renderSFMap({
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 legacy entry includes built-in geography for convenience. The modern root and `/static` accept explicit geography. See the [full option list](../README.md#static-options).
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 [complete election recipe](../README.md#election-district-api) and the Storybook
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](../README.md#render-a-static-map).
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 { createInteractiveSFMapWithData } from '@kahwee/sf-map-svg/interactive-data';
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 = createInteractiveSFMapWithData(
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 `custom-map` 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
+ [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 v2 controller and explicit data imports, see [the migration guide](migration-v2.md).
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 this change
66
+ ## Verification for version 3
67
67
 
68
- - `pnpm check`: 59 Node tests, types, lint, source catalog and bundle budgets passed.
69
- - `pnpm test:stories:coverage`: 60 Chromium stories passed.
70
- - `pnpm demo`, `pnpm build-storybook`, and clean-install `pnpm test:package` passed.
71
- - Production guide: 110.2 KB gzip; data-free compatibility renderer: 21.8 KB gzip, as reported by
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 and testing
1
+ # Consumer integration
2
2
 
3
- For new integrations, use the [v2 controller](migration-v2.md). The compatibility
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
- ## Choose the import graph deliberately
5
+ For a compact browser guide:
7
6
 
8
- Use `@kahwee/sf-map-svg/guide/map` for a guide using SFAR neighborhoods. It includes
9
- only overview coast, SFAR boundaries, selected parks and roads, and BART. The
10
- compatibility `interactive` import includes all supported district vintages and
11
- neighborhood collections. Hiding a layer at runtime does not remove its imported
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
- Use `interactive-data` for a renderer with **no bundled geography**. Supply the
15
- `InteractiveSFMapData` you need, optionally from a separately cached, same-origin
16
- asset produced at build time. This can improve caching and initial loading; it
17
- does not eliminate the cost of transmitting that geography. Serve local assets
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
- Browser code:
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 before measurement uses the compatibility `@kahwee/sf-map-svg/interactive` entry; the after measurement uses `@kahwee/sf-map-svg/guide` initial static imports.
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
- | v2 root (explicit data) | 76.0 KB | 22.8 KB | — |
8
- | v2 static renderer | 13.0 KB | 4.5 KB | — |
9
- | Compatibility interactive (before) | 6309.8 KB | 1813.6 KB | — |
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
- **Change in initial gzip:** 93.9% smaller. **500 KB target:** met.
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": "2.2.0",
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-v2.md",
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",
@@ -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';
@@ -1 +0,0 @@
1
- export { createSFMapWithData, districtColors, districtYears, } from './map-core.js';
@@ -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;
@@ -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
- }
@@ -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
- }