@kahwee/sf-map-svg 4.3.1 → 4.4.0
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 +26 -0
- package/README.md +134 -1
- package/dist/src/cluster-renderer.d.ts +34 -0
- package/dist/src/cluster-renderer.js +159 -0
- package/dist/src/controller-types.d.ts +2 -2
- package/dist/src/dom.d.ts +2 -0
- package/dist/src/dom.js +6 -0
- package/dist/src/explorer-core.js +241 -265
- package/dist/src/guide-data.js +1 -0
- package/dist/src/guide-static.js +1 -0
- package/dist/src/label-candidates.d.ts +24 -0
- package/dist/src/label-candidates.js +29 -0
- package/dist/src/label-renderer.d.ts +1 -10
- package/dist/src/label-renderer.js +16 -8
- package/dist/src/map-core.d.ts +3 -1
- package/dist/src/map-core.js +7 -5
- package/dist/src/marker-catalog.d.ts +11 -0
- package/dist/src/marker-catalog.js +67 -0
- package/dist/src/marker-chooser.d.ts +6 -0
- package/dist/src/marker-chooser.js +59 -0
- package/dist/src/marker-layer.d.ts +13 -8
- package/dist/src/marker-layer.js +41 -23
- package/dist/src/marker-navigation.d.ts +5 -0
- package/dist/src/marker-navigation.js +52 -0
- package/dist/src/static-labels.d.ts +7 -0
- package/dist/src/static-labels.js +100 -0
- package/dist/src/static.js +12 -3
- package/dist/src/types.d.ts +8 -2
- package/docs/API.md +14 -4
- package/docs/EXAMPLES.md +1 -0
- package/docs/api-audit.md +5 -4
- package/docs/consumer-integration.md +43 -1
- package/docs/guide-bundle-report.md +4 -4
- package/llms-full.txt +251 -31
- package/llms.txt +1 -1
- package/package.json +14 -11
package/llms-full.txt
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
> Offline San Francisco SVG maps with explicit geographic imports, no runtime dependencies, static rendering, and a browser controller.
|
|
4
4
|
|
|
5
|
-
@kahwee/sf-map-svg 4.
|
|
5
|
+
@kahwee/sf-map-svg 4.4.0 · local working tree. Node >=24.0.0 for server rendering; browsers need a DOM and a JSON-capable bundler.
|
|
6
6
|
|
|
7
7
|
These are working-tree docs. Unreleased changelog entries may describe APIs absent from npm; verify the installed version before copying them.
|
|
8
8
|
|
|
9
9
|
Use renderMap(data, options) for SVG strings; createMap(data, options) returns a controller. Mount map.element and destroy it on unmount. Choose mode explicitly: createMap defaults to basemap. Geography is separate; staticMapData and fullMapData have different shapes.
|
|
10
10
|
|
|
11
|
-
<!-- Generated from README.md; @kahwee/sf-map-svg 4.
|
|
11
|
+
<!-- Generated from README.md; @kahwee/sf-map-svg 4.4.0 · local working tree. -->
|
|
12
12
|
|
|
13
13
|
# SF Map SVG
|
|
14
14
|
|
|
@@ -16,6 +16,8 @@ Offline, self-contained San Francisco SVG maps. The package has no runtime depen
|
|
|
16
16
|
|
|
17
17
|
[Getting started](https://github.com/kahwee/sf-map-svg/blob/main/docs/developer-guide.md) · [API reference](https://github.com/kahwee/sf-map-svg/blob/main/docs/API.md) · [Design playground](https://kahwee.github.io/sf-map-svg/playground.html) · [Live examples](https://kahwee.github.io/sf-map-svg/examples.html) · [Storybook source](https://github.com/kahwee/sf-map-svg/blob/main/stories/) · [Geographic sources](https://github.com/kahwee/sf-map-svg/blob/main/SOURCES.md) · [v3 migration](https://github.com/kahwee/sf-map-svg/blob/main/docs/migration-v3.md)
|
|
18
18
|
|
|
19
|
+
[Map preview](https://github.com/kahwee/sf-map-svg/blob/main/docs/map-preview.png) · 2,000-marker screencasts: [desktop](https://github.com/kahwee/sf-map-svg/blob/main/docs/media/markers-2000-desktop.webm), [390 px phone](https://github.com/kahwee/sf-map-svg/blob/main/docs/media/markers-2000-phone.webm). [Screenshots and capture instructions](https://github.com/kahwee/sf-map-svg/blob/main/docs/media/README.md) show zoom, viewport culling, clustering, label updates, and selection from the complete catalog. These recordings illustrate behavior; frame timings are reported below.
|
|
20
|
+
|
|
19
21
|
## Install
|
|
20
22
|
|
|
21
23
|
```sh
|
|
@@ -43,6 +45,23 @@ const { svg, project, viewBox } = renderMap(staticMapData, {
|
|
|
43
45
|
|
|
44
46
|
See [map options and animations](https://github.com/kahwee/sf-map-svg/blob/main/docs/API.md#static-maps) for optional layers and static animation.
|
|
45
47
|
|
|
48
|
+
## Labels before JavaScript loads
|
|
49
|
+
|
|
50
|
+
Version 4.4 adds an opt-in overview layout for server-rendered guides:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { createGuideShell } from '@kahwee/sf-map-svg/guide/static';
|
|
54
|
+
|
|
55
|
+
const guideMarkup = createGuideShell({
|
|
56
|
+
title: 'Explore San Francisco',
|
|
57
|
+
labelLayout: { width: 390 },
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`width` is the expected canvas width in CSS pixels. The SVG keeps its 800 × 800 projection while labels share the interactive map's priorities, anchors, sizes and collision rules. Guide shells include neighborhood names in this layout; calls without `labelLayout` keep their previous output.
|
|
62
|
+
|
|
63
|
+
Enhance the shell with `mountGuideController()` using the same data, colors, markers and layers. Static font widths are estimated, so browser fonts and resize can change which labels fit. See the [complete integration recipe and width guidance](https://github.com/kahwee/sf-map-svg/blob/main/docs/consumer-integration.md#keep-overview-labels-consistent). No migration is required from 4.3.
|
|
64
|
+
|
|
46
65
|
## Interactive map
|
|
47
66
|
|
|
48
67
|
```ts
|
|
@@ -83,11 +102,125 @@ Call `destroy()` when removing an interactive map.
|
|
|
83
102
|
|
|
84
103
|
Canonical geography is in [`data/`](https://github.com/kahwee/sf-map-svg/blob/main/data/README.md), with provenance in [`SOURCES.md`](https://github.com/kahwee/sf-map-svg/blob/main/SOURCES.md). The public `/data` entry exposes lookup, catalog, district maps, and source-specific neighborhood collections. These are deeply frozen; clone before editing.
|
|
85
104
|
|
|
86
|
-
Install with `pnpm install --frozen-lockfile`. See the [contributing guide](https://github.com/kahwee/sf-map-svg/blob/main/CONTRIBUTING.md) for architecture and the [validation and release runbook](https://github.com/kahwee/sf-map-svg/blob/main/docs/maintenance.md) for checks, browser inspection, and publishing. If you are upgrading from v2, follow the [v3 migration guide](https://github.com/kahwee/sf-map-svg/blob/main/docs/migration-v3.md).
|
|
105
|
+
Install with `pnpm install --frozen-lockfile`. Bun 1.4.3 is pinned in `.bun-version`; `pnpm test:bun` checks compiled-package imports and static rendering. Strict TypeScript 7.0.2 declarations and packed consumers remain part of the Node/pnpm checks. See the [contributing guide](https://github.com/kahwee/sf-map-svg/blob/main/CONTRIBUTING.md) for architecture and the [validation and release runbook](https://github.com/kahwee/sf-map-svg/blob/main/docs/maintenance.md) for checks, browser inspection, and publishing. If you are upgrading from v2, follow the [v3 migration guide](https://github.com/kahwee/sf-map-svg/blob/main/docs/migration-v3.md).
|
|
106
|
+
|
|
107
|
+
## Browser checks and marker performance
|
|
108
|
+
|
|
109
|
+
The focused interaction suite checks a production bundle in Chromium, Firefox, and WebKit at 1440 px and 390 px. It covers 2,000-marker viewport culling and reattachment, overlapping-place selection, marker keyboard navigation and focus recovery, camera input, resizing, touch-mode scroll policy, reduced motion, and disposal. Phone widths check responsive layout in these engines; they do not replace physical-device gesture testing. The full Storybook and screenshot suites continue to use Chromium.
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
pnpm exec playwright install --with-deps chromium firefox webkit
|
|
113
|
+
pnpm test:browsers
|
|
114
|
+
pnpm benchmark:markers --browsers=chromium,firefox,webkit
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The benchmark uses headless engines and a deterministic city grid with 500 and 2,000 labeled markers at 800 px and 390 px viewport widths. It loads the production bundle and fonts before timing, disables motion, and starts each operation from a consistent camera and clustering state. Each operation has three warmups and 20 measured samples. Results include mount, pan, zoom, clustering changes, retained-ID updates, replacement, and identical updates. Run benchmarks alone on an otherwise idle machine. Use `--labels=false` to compare label cost and `--output=test-results/another-run.json` to keep separate results.
|
|
118
|
+
|
|
119
|
+
`test-results/marker-benchmark.json` records browser versions, machine details, and p50/p95/max timings. Synchronous times include the API call and forced layout; settled times include two animation frames and are not GPU-paint measurements. Six continuous workloads record 60 frame intervals each: pan, rapid zoom, and resize with clustering on and off. Four initial frame callbacks precede the measurements. An untimed allocation probe also counts newly created pin groups, including detached nodes. These measurements describe the test machine, rather than a universal performance limit or a mobile-device guarantee. The report also records mounted pin/cluster counts at overview and two zoom levels, with clustering on and off; the picker retains all records. CI saves a shorter 2,000-marker Chromium benchmark for comparison without timing thresholds.
|
|
120
|
+
|
|
121
|
+
Measured October 10–11, 2026 against renderer commit `1d523d9`, in a Linux x64 container on an Intel Xeon Platinum 8573C with a four-CPU quota and 16 GiB memory limit, using Node 26.11.1 and Playwright 1.63.0. All 36 focused browser checks passed. The 2,000-marker results below use clustering and labels. Mount, update (retained IDs), and replacement (all new IDs) show median synchronous milliseconds; pan shows the 95th-percentile frame interval in milliseconds.
|
|
122
|
+
|
|
123
|
+
| Engine | Viewport | Mount | Update | Replace | Pan frame p95 |
|
|
124
|
+
| --- | ---: | ---: | ---: | ---: | ---: |
|
|
125
|
+
| Chromium 153 | 800 px | 147.2 | 28.3 | 92.0 | 166.7 |
|
|
126
|
+
| Chromium 153 | 390 px | 180.6 | 24.4 | 104.4 | 183.3 |
|
|
127
|
+
| Firefox 155 | 800 px | 351.0 | 55.0 | 222.0 | 200.3 |
|
|
128
|
+
| Firefox 155 | 390 px | 386.0 | 211.0 | 161.0 | 116.8 |
|
|
129
|
+
| WebKit 26.6 | 800 px | 468.0 | 105.0 | 896.0 | 302.0 |
|
|
130
|
+
| WebKit 26.6 | 390 px | 235.0 | 107.0 | 889.0 | 118.0 |
|
|
131
|
+
|
|
132
|
+
The baseline retained all supplied pin nodes in the SVG, including clustered pins. These frame intervals exceeded the 16.7 ms budget for 60 Hz interaction. Prefer smaller marker lists and stable IDs, and avoid repeatedly replacing every marker during interaction. Identical 2,000-marker updates took 0.7–2.0 ms at the median across these runs; full replacement was especially costly in WebKit. Keep camera-call timings separate from frame timings: a quick synchronous call does not imply a smooth rendered frame.
|
|
133
|
+
|
|
134
|
+
In that baseline, a separate 20-sample Chromium run with 2,000 markers at 800 px and `labels: false` reduced the pan frame p95 from 166.7 ms to 50.0 ms and median mount time from 147.2 ms to 115.6 ms. Disabling labels helps this workload but still does not establish 60 Hz performance. Reproduce the comparison with `pnpm benchmark:markers --counts=2000 --widths=800 --labels=false --output=test-results/marker-benchmark-no-labels.json`.
|
|
135
|
+
|
|
136
|
+
### Viewport renderer results
|
|
137
|
+
|
|
138
|
+
The viewport renderer detaches offscreen pins and clustered members, reuses their nodes when they return, caches cluster groups during pan, and skips unchanged SVG writes. Selected and focused pins stay mounted; every supplied record remains selectable. Cluster membership uses the full catalog, so panning does not change counts at the edges.
|
|
139
|
+
|
|
140
|
+
The same 20-sample, 2,000-marker benchmark was rerun October 11, 2026 on the same host, browser versions, and label settings. Columns use the same units as the baseline above.
|
|
141
|
+
|
|
142
|
+
| Engine | Viewport | Mount | Update | Replace | Pan frame p95 |
|
|
143
|
+
| --- | ---: | ---: | ---: | ---: | ---: |
|
|
144
|
+
| Chromium 153 | 800 px | 92.3 | 17.0 | 52.8 | 16.8 |
|
|
145
|
+
| Chromium 153 | 390 px | 97.6 | 12.9 | 63.6 | 16.7 |
|
|
146
|
+
| Firefox 155 | 800 px | 106.0 | 21.0 | 54.0 | 49.4 |
|
|
147
|
+
| Firefox 155 | 390 px | 123.0 | 45.0 | 78.0 | 17.1 |
|
|
148
|
+
| WebKit 26.6 | 800 px | 160.0 | 32.0 | 805.0 | 49.0 |
|
|
149
|
+
| WebKit 26.6 | 390 px | 164.0 | 20.0 | 781.0 | 36.0 |
|
|
150
|
+
|
|
151
|
+
Desktop pan frame p95 improved from 166.7 → 16.8 ms in Chromium, 200.3 → 49.4 ms in Firefox, and 302.0 → 49.0 ms in WebKit. Firefox and WebKit still exceed a 60 Hz frame budget in this run. Prefer stable IDs, clustering, and fewer enabled label layers for dense interactive views; full replacement still creates all new records and picker options. These headless measurements do not establish smoothness on a physical phone.
|
|
152
|
+
|
|
153
|
+
With clustering off at 800 px, the mounted pin count fell from 2,000 in city view to 228 at the sampled 4× zoom; all 2,000 records stayed in the picker. **Checks / 2000 markers / Desktop** and **Phone** in Storybook show live counts and test zoom, pan, retained-ID updates, offscreen selection, and reset. The [plan and storyboard](https://github.com/kahwee/sf-map-svg/blob/main/docs/marker-performance-plan.md) describe the regression contract. [Saved baseline and updated JSON](https://github.com/kahwee/sf-map-svg/blob/main/docs/benchmarks/markers-2026-10-11.json) include all timing percentiles and viewport counts.
|
|
154
|
+
|
|
155
|
+
```sh
|
|
156
|
+
pnpm benchmark:markers --counts=2000 --browsers=chromium,firefox,webkit \
|
|
157
|
+
--output=test-results/marker-benchmark-viewport.json
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Lazy pin creation and expanded workloads
|
|
161
|
+
|
|
162
|
+
Individual SVG pins now allocate on first visibility or selection, then retain their nodes for reuse. With 2,000 clustered records, an untimed probe counted zero pin groups at mount, zero after replacing every ID, and one after selecting a pin. The previous renderer created 2,000 groups at mount and another 2,000 on replacement. The complete picker, selection, keyboard focus, and deferred entrance animations remain available.
|
|
163
|
+
|
|
164
|
+
October 11, 2026 results with the same engines, labeled grid, and 20 operation samples (median synchronous milliseconds):
|
|
165
|
+
|
|
166
|
+
| Engine | Viewport | Mount | Replace all IDs |
|
|
167
|
+
| --- | ---: | ---: | ---: |
|
|
168
|
+
| chromium | 800 px | 78.1 | 39.3 |
|
|
169
|
+
| chromium | 390 px | 87.9 | 47.9 |
|
|
170
|
+
| firefox | 800 px | 88.0 | 24.0 |
|
|
171
|
+
| firefox | 390 px | 95.0 | 46.0 |
|
|
172
|
+
| webkit | 800 px | 128.0 | 717.0 |
|
|
173
|
+
| webkit | 390 px | 132.0 | 710.0 |
|
|
174
|
+
|
|
175
|
+
A separate run of the same expanded harness against `e41aa2b` measured Chromium mount medians of 115.1/123.6 ms and replacement medians of 57.1/89.8 ms at 800/390 px, versus 78.1/87.9 ms and 39.3/47.9 ms after lazy allocation. This paired control covers Chromium only. WebKit replacement remains expensive at 717/710 ms; stable IDs remain preferable.
|
|
176
|
+
|
|
177
|
+
The expanded stress cases reveal work that culling alone does not remove. Unclustered frame interval p95 values, in milliseconds:
|
|
178
|
+
|
|
179
|
+
| Engine | Viewport | Pan | Rapid zoom | Resize |
|
|
180
|
+
| --- | ---: | ---: | ---: | ---: |
|
|
181
|
+
| chromium | 800 px | 16.7 | 133.4 | 83.3 |
|
|
182
|
+
| chromium | 390 px | 33.3 | 33.4 | 50.0 |
|
|
183
|
+
| firefox | 800 px | 33.8 | 34.2 | 100.2 |
|
|
184
|
+
| firefox | 390 px | 34.2 | 49.4 | 50.7 |
|
|
185
|
+
| webkit | 800 px | 60.0 | 106.0 | 342.0 |
|
|
186
|
+
| webkit | 390 px | 114.0 | 124.0 | 192.0 |
|
|
187
|
+
|
|
188
|
+
These runs do not demonstrate a consistent frame-rate gain: desktop Chromium clustered pan p95 was 50.1 ms in the control and 66.8 ms after lazy allocation, compared with 16.8 ms in the earlier run. Rapid zoom alternates reciprocal 1.2× factors; resize alternates host widths by 80 px. Each workload runs with clustering both on and off, samples counts outside the timed loop, and retains 60 measured intervals. Headless frame timings vary and do not establish physical-device smoothness. [Saved lazy and control results](https://github.com/kahwee/sf-map-svg/blob/main/docs/benchmarks/markers-lazy-2026-10-11.json) preserve every scenario and allocation count.
|
|
189
|
+
|
|
190
|
+
```sh
|
|
191
|
+
pnpm benchmark:markers --counts=2000 --browsers=chromium,firefox,webkit \
|
|
192
|
+
--output=test-results/marker-benchmark-lazy.json
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Cached label preparation and renderer components
|
|
196
|
+
|
|
197
|
+
Neighborhood priority is sorted once per source. Styled label candidates are reused until selection, catalog, source, layers, appearance, or zoom/width eligibility changes. Overlay bounds are measured before marker and cluster SVG updates; secondary-road visibility and scale furniture skip unchanged writes. Marker catalog reconciliation and cluster rendering now have separate modules, reducing the explorer controller from 2,316 to 2,151 lines. At the 4.3.2 marker-performance baseline, the renderer was 34.6 KiB gzip and static rendering was 7.9 KiB. See the [current bundle report](https://github.com/kahwee/sf-map-svg/blob/main/docs/guide-bundle-report.md) for measurements including the opt-in static label layout.
|
|
198
|
+
|
|
199
|
+
A fresh Chromium comparison against `e774fbd` used 2,000 labeled markers, 20 operation samples, both widths, and all six frame workloads in basemap and neighborhood modes. Neighborhood-mode frame interval p95 values (milliseconds):
|
|
200
|
+
|
|
201
|
+
| Workload | 800 px before | 800 px after | 390 px before | 390 px after |
|
|
202
|
+
| --- | ---: | ---: | ---: | ---: |
|
|
203
|
+
| clustered pan | 66.7 | 50.0 | 16.7 | 16.8 |
|
|
204
|
+
| clustered rapid zoom | 66.7 | 83.4 | 33.4 | 16.8 |
|
|
205
|
+
| clustered resize | 16.7 | 66.7 | 66.6 | 16.8 |
|
|
206
|
+
| unclustered pan | 16.7 | 49.9 | 33.3 | 66.6 |
|
|
207
|
+
| unclustered rapid zoom | 116.6 | 33.4 | 66.7 | 83.4 |
|
|
208
|
+
| unclustered resize | 66.7 | 33.3 | 50.0 | 50.1 |
|
|
209
|
+
|
|
210
|
+
Results are mixed rather than a consistent frame-rate gain. Basemap desktop clustered pan p95 changed from 50.0 to 16.8 ms, while phone unclustered pan changed from 16.8 to 50.0 ms. Basemap mount medians were 72.1 → 88.8 ms at 800 px and 88.3 → 89.1 ms at 390 px; neighborhood-mode mount medians were 81.5 → 73.3 ms and 93.9 → 89.0 ms. Viewport counts and allocation probes match before and after. [Saved comparisons](https://github.com/kahwee/sf-map-svg/blob/main/docs/benchmarks/markers-refactor-2026-10-11.json) include every operation and scenario; these headless runs do not establish physical-device smoothness.
|
|
211
|
+
|
|
212
|
+
```sh
|
|
213
|
+
pnpm benchmark:markers --counts=2000 --mode=neighborhoods \
|
|
214
|
+
--output=test-results/marker-benchmark-neighborhoods.json
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## CI maintenance
|
|
218
|
+
|
|
219
|
+
[GitHub Actions maintenance](https://github.com/kahwee/sf-map-svg/blob/main/.github/ACTIONS.md) covers workflows, parallel checks, action versions, and weekly updates.
|
|
87
220
|
|
|
88
221
|
---
|
|
89
222
|
|
|
90
|
-
<!-- Generated from docs/developer-guide.md; @kahwee/sf-map-svg 4.
|
|
223
|
+
<!-- Generated from docs/developer-guide.md; @kahwee/sf-map-svg 4.4.0 · local working tree. -->
|
|
91
224
|
|
|
92
225
|
# Developer guide
|
|
93
226
|
|
|
@@ -231,7 +364,7 @@ See [API options](https://github.com/kahwee/sf-map-svg/blob/main/docs/API.md), [
|
|
|
231
364
|
|
|
232
365
|
---
|
|
233
366
|
|
|
234
|
-
<!-- Generated from docs/API.md; @kahwee/sf-map-svg 4.
|
|
367
|
+
<!-- Generated from docs/API.md; @kahwee/sf-map-svg 4.4.0 · local working tree. -->
|
|
235
368
|
|
|
236
369
|
# Map options and lifecycle
|
|
237
370
|
|
|
@@ -254,9 +387,9 @@ const { svg } = renderMap(staticMapData, presentation);
|
|
|
254
387
|
const map = createMap(fullMapData, presentation);
|
|
255
388
|
```
|
|
256
389
|
|
|
257
|
-
Static `layers` supports every interactive layer switch
|
|
390
|
+
Static `layers` supports every interactive layer switch. `neighborhoodLabels` applies to the opt-in `labelLayout` described below. Static `appearance` supports `theme`, `colors`, and `districtStyle`; browser typography, area interaction styles, and screen-space marker sizing remain interactive settings. Unknown keys and invalid values are rejected for both flat and grouped options. Supplied grouped keys override flat compatibility keys; omitted or `undefined` grouped keys preserve the flat value or renderer default. Undefined color tokens use the theme default instead of entering SVG attributes. The two renderers retain their existing defaults. Marker arrays accept readonly inputs.
|
|
258
391
|
|
|
259
|
-
Flat compatibility options include `theme`, `width`, `height`, `padding`, `year` (2002, 2012, 2022), `districtLines`, `districtFills`, `districtStyle`, `districtLabels`, `neighborhoodLines`, `labels`, `highways`, `keyRoads`, `roadLabels`, `landmarks`, `bartStations`, `markers`, `overlays`, `title`, `idPrefix`, `colors`, and `animation`. Each optional layer is independent. User-supplied text and attributes are escaped in SVG output.
|
|
392
|
+
Flat compatibility options include `theme`, `width`, `height`, `padding`, `year` (2002, 2012, 2022), `districtLines`, `districtFills`, `districtStyle`, `districtLabels`, `neighborhoodLines`, `neighborhoodLabels`, `labels`, `labelLayout`, `highways`, `keyRoads`, `roadLabels`, `landmarks`, `bartStations`, `markers`, `overlays`, `title`, `idPrefix`, `colors`, and `animation`. Each optional layer is independent. User-supplied text and attributes are escaped in SVG output.
|
|
260
393
|
|
|
261
394
|
Custom district IDs must be finite numbers. An empty `labelPoints` array uses the district's primary `label` position.
|
|
262
395
|
|
|
@@ -264,6 +397,10 @@ The SVG description names the selected neighborhood source for source-aware data
|
|
|
264
397
|
|
|
265
398
|
`animation: true` (or `{ duration, delay }`) makes a static map draw itself with self-contained, scoped CSS: land fades in, the coast and lines draw, districts grow, then labels and points appear. It plays when the SVG is inserted into a page or loaded as an image, needs no JavaScript, and stays still under `prefers-reduced-motion`. Re-insert the markup to replay it. Default output is unchanged.
|
|
266
399
|
|
|
400
|
+
Static `labelLayout: { width: 390 }` opts into shared interactive **overview** label priorities, sizes, anchors and collision rules. `width` must be a positive finite CSS canvas width; it does not alter the SVG viewBox. Set `neighborhoodLabels: true` (flat or under `layers`) to add area names in this layout. Guide SVGs and shells enable neighborhood names by default only when opting into `labelLayout`. `labels: false` remains the master switch. Default static output is unchanged.
|
|
401
|
+
|
|
402
|
+
Static placement uses estimated system-font metrics without browser globals. Interactive placement measures actual fonts and includes live selection and HTML furniture, so exact glyph positions and the visible label set can differ. See the [integration recipe](https://github.com/kahwee/sf-map-svg/blob/main/docs/consumer-integration.md#keep-overview-labels-consistent) for width and progressive-enhancement guidance.
|
|
403
|
+
|
|
267
404
|
## Interactive maps
|
|
268
405
|
|
|
269
406
|
`createMap` defaults to `mode: 'basemap'` and `appearance.theme: 'transit'`. Set the mode explicitly for neighborhood or district exploration. District and neighborhood layer defaults follow the mode; other supplied layers default on. The [developer guide](https://github.com/kahwee/sf-map-svg/blob/main/docs/developer-guide.md) compares static and interactive defaults and explains configuration resets, coordinates, and cleanup.
|
|
@@ -274,7 +411,13 @@ The controller also supports marker, neighborhood, and district selection; distr
|
|
|
274
411
|
|
|
275
412
|
If a district callback destroys the map or selects a newer district, the interrupted activation stops without moving keyboard focus or sending a stale event.
|
|
276
413
|
|
|
277
|
-
`setMarkers()` reconciles by stable marker `id`: retained markers keep
|
|
414
|
+
`setMarkers()` reconciles by stable marker `id`: retained markers keep any previously created nodes, focus, and in-progress entrance animations. New pin entrances start when the pin first becomes visible; a cached pin does not animate again merely because it returns to the viewport. Disabling entrances, reduced-motion changes, and destruction cancel pending and active entrances. Identical ordered marker updates are a visual no-op. Camera state and `viewportchange` events remain synchronous, while animated camera steps and their dependent label/marker layout commit in the same browser frame.
|
|
415
|
+
|
|
416
|
+
Interactive maps create individual pin nodes only on first visibility or selection, then cache them for reuse. Record and style updates before first visibility apply to the eventual node. They mount only pins and cluster symbols intersecting a padded viewport; clustered member pins detach. All records remain in the native picker and selectable through the API. Selected and keyboard-focused pins stay mounted, even outside the viewport, and retained nodes return when panning back. Cluster membership uses the complete catalog and stays stable during pan at the same zoom. Static SVG exports are unaffected.
|
|
417
|
+
|
|
418
|
+
Visible markers and clusters share one tab stop. Use `[` and `]` to move focus, Enter or Space to activate, and Tab to leave the group. Arrow keys retain map panning. Marker replacement preserves the focused ID when available and moves focus to a surviving marker when it is removed.
|
|
419
|
+
|
|
420
|
+
Activating coincident pins opens a place chooser, including when compact embeds hide the native marker picker. Clusters zoom when their members can separate at maximum zoom; otherwise they open the chooser. Escape or Close returns focus to the originating pin or cluster when it remains visible. Choosing a place selects its marker; replacing markers or destroying the map closes the chooser. `clusteractivate` still reports the activated cluster's markers.
|
|
278
421
|
|
|
279
422
|
For a small guide, import `guideMapData` from `/guide/data` and pass it to `createMap`. The optional `/guide` entry also provides `createGuideMap`, `mountGuideMap`, and detailed-data loading for existing guide layouts. `/transit` provides the standalone schematic transit animation. See [examples](https://github.com/kahwee/sf-map-svg/blob/main/docs/EXAMPLES.md) and [consumer integration](https://github.com/kahwee/sf-map-svg/blob/main/docs/consumer-integration.md).
|
|
280
423
|
|
|
@@ -331,13 +474,13 @@ Neighborhood IDs are exact source-scoped feature IDs, rather than display names
|
|
|
331
474
|
|
|
332
475
|
## Coordinates and units
|
|
333
476
|
|
|
334
|
-
GeoJSON and static projection inputs use `[longitude, latitude]` in WGS84; markers use `{ lng, lat }`. Static `project` returns SVG units. `projectToScreen(lng, lat)` returns pixels relative to the mounted canvas. Camera viewports
|
|
477
|
+
GeoJSON and static projection inputs use `[longitude, latitude]` in WGS84; markers use `{ lng, lat }`. Static `project` returns SVG units. `projectToScreen(lng, lat)` returns pixels relative to the mounted canvas. Camera viewports use the fixed 800 × 800 projected map space, independent of canvas size. Pan deltas use screen pixels, converted using the mounted canvas width. Marker radius uses screen pixels in browser maps and SVG units in static output.
|
|
335
478
|
|
|
336
479
|
Explore presentation options in the [live map design playground](https://kahwee.github.io/sf-map-svg/playground.html). The page identifies its package version; released builds use compatible options for that installed version.
|
|
337
480
|
|
|
338
481
|
---
|
|
339
482
|
|
|
340
|
-
<!-- Generated from docs/EXAMPLES.md; @kahwee/sf-map-svg 4.
|
|
483
|
+
<!-- Generated from docs/EXAMPLES.md; @kahwee/sf-map-svg 4.4.0 · local working tree. -->
|
|
341
484
|
|
|
342
485
|
# Examples
|
|
343
486
|
|
|
@@ -348,6 +491,7 @@ Choose by task. All snippets use the public package API; browser examples need a
|
|
|
348
491
|
| I want to… | Start here |
|
|
349
492
|
| --- | --- |
|
|
350
493
|
| Render an SVG on a server | [Static map](#static-svg) |
|
|
494
|
+
| Show labels before JavaScript loads | [Static-to-interactive guide](https://github.com/kahwee/sf-map-svg/blob/main/docs/consumer-integration.md#keep-overview-labels-consistent) |
|
|
351
495
|
| Add a small map to a browser | [Lightweight guide](#lightweight-interactive-guide) |
|
|
352
496
|
| Bundle only selected geography | [Selected data](#selected-geography) |
|
|
353
497
|
| Draw a route over the city | [Route overlay](#route-overlay) |
|
|
@@ -448,7 +592,7 @@ For the controller and explicit data imports, see [the v3 migration guide](https
|
|
|
448
592
|
|
|
449
593
|
---
|
|
450
594
|
|
|
451
|
-
<!-- Generated from docs/consumer-integration.md; @kahwee/sf-map-svg 4.
|
|
595
|
+
<!-- Generated from docs/consumer-integration.md; @kahwee/sf-map-svg 4.4.0 · local working tree. -->
|
|
452
596
|
|
|
453
597
|
# Embedding and progressive enhancement
|
|
454
598
|
|
|
@@ -467,6 +611,46 @@ const markup = createGuideShell({ title: 'Explore San Francisco' });
|
|
|
467
611
|
|
|
468
612
|
The shell uses an 800 × 800 projection with 28 units of padding, matching the interactive map. Use `createGuideSVG()` for custom dimensions. Rendering is offline, with zero runtime dependencies.
|
|
469
613
|
|
|
614
|
+
## Keep overview labels consistent
|
|
615
|
+
|
|
616
|
+
Opt into the browser's overview label priorities and collision rules with a target **canvas width in CSS pixels**:
|
|
617
|
+
|
|
618
|
+
```ts
|
|
619
|
+
const labeledMarkup = createGuideShell({
|
|
620
|
+
title: 'Explore San Francisco',
|
|
621
|
+
labelLayout: { width: 390 },
|
|
622
|
+
});
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
This keeps the 800 × 800 projection. It sizes and places text for a 390 px canvas, prioritizes major parks and primary roads, includes neighborhood names, and avoids label, marker, and BART-symbol collisions. BART names and smaller parks remain deferred until interactive zoom. Use the same geography, colors, markers and layer switches when mounting. Set `layers.neighborhoodLabels: false` on both renderers to omit neighborhood names.
|
|
626
|
+
|
|
627
|
+
The width is a rendering hint, not a responsive script. Generate an appropriate variant for your known embed width; an SVG displayed at another width scales its labels with the artwork. Static layout uses conservative estimated system-font widths, while the browser measures actual fonts and recalculates on resize. Font metrics, custom browser label styles, selection, clustering and HTML overlays can therefore change the final label set or position. This reduces the transition without promising pixel-identical hydration. Existing calls without `labelLayout` keep their original fixed labels.
|
|
628
|
+
|
|
629
|
+
For a fixed-width embed, use shared inputs on the server and in the browser:
|
|
630
|
+
|
|
631
|
+
```ts
|
|
632
|
+
// Shared inputs: coordinates, IDs, radii, colors and layer choices must match.
|
|
633
|
+
const guideMarkers = [
|
|
634
|
+
{ id: 'ferry', label: 'Ferry Building', lng: -122.3937, lat: 37.7955, radius: 7 },
|
|
635
|
+
];
|
|
636
|
+
const guideLayers = { neighborhoodLabels: true, roadLabels: true };
|
|
637
|
+
const guideColors = { label: '#183e58', landmark: '#426641' };
|
|
638
|
+
|
|
639
|
+
// Server: place this HTML in a canvas that is 390 CSS pixels wide.
|
|
640
|
+
const labeledPlacesMarkup = createGuideShell({
|
|
641
|
+
labelLayout: { width: 390 },
|
|
642
|
+
markers: guideMarkers,
|
|
643
|
+
layers: guideLayers,
|
|
644
|
+
appearance: { colors: guideColors },
|
|
645
|
+
});
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
After inserting that markup, pass `markers: guideMarkers`, `layers: guideLayers`, and `appearance: { colors: guideColors }` to `mountGuideController()` as shown below. The static-only `labelLayout` option is not a browser option: the controller measures the actual canvas and font. The shell border is outside the canvas, so a default shell with a 390 px outer width has a 388 px canvas.
|
|
649
|
+
|
|
650
|
+
For responsive layouts whose canvas width is unknown at build time, choose a representative width and accept text scaling until enhancement, or serve independently rendered SVG variants through your application's responsive markup. The package does not detect a device on the server. Keep `labels: false` if your design requires no visible text change at enhancement.
|
|
651
|
+
|
|
652
|
+
For `renderMap()`, also enable `neighborhoodLabels` explicitly; it defaults off outside the guide preset. `labels: false` hides all text in either layout while preserving symbols and accessible titles.
|
|
653
|
+
|
|
470
654
|
## Enhance the existing frame
|
|
471
655
|
|
|
472
656
|
In your browser entry, mount the controller into that shell:
|
|
@@ -479,7 +663,9 @@ if (!shell) throw new Error('Missing guide shell');
|
|
|
479
663
|
const guide = mountGuideController(shell, {
|
|
480
664
|
attribution: 'compact',
|
|
481
665
|
features: { motion: { duration: 400 }, markerEntrance: true },
|
|
482
|
-
|
|
666
|
+
markers: guideMarkers,
|
|
667
|
+
layers: guideLayers,
|
|
668
|
+
appearance: { theme: 'transit', colors: guideColors },
|
|
483
669
|
});
|
|
484
670
|
|
|
485
671
|
// Call before your application removes the containing view.
|
|
@@ -500,7 +686,7 @@ See the [exact API contract](https://github.com/kahwee/sf-map-svg/blob/main/docs
|
|
|
500
686
|
|
|
501
687
|
---
|
|
502
688
|
|
|
503
|
-
<!-- Generated from docs/migration-v3.md; @kahwee/sf-map-svg 4.
|
|
689
|
+
<!-- Generated from docs/migration-v3.md; @kahwee/sf-map-svg 4.4.0 · local working tree. -->
|
|
504
690
|
|
|
505
691
|
# Migrate to version 3
|
|
506
692
|
|
|
@@ -541,7 +727,7 @@ District styles are now prepared once per district on construction and style/yea
|
|
|
541
727
|
|
|
542
728
|
---
|
|
543
729
|
|
|
544
|
-
<!-- Generated from SOURCES.md; @kahwee/sf-map-svg 4.
|
|
730
|
+
<!-- Generated from SOURCES.md; @kahwee/sf-map-svg 4.4.0 · local working tree. -->
|
|
545
731
|
|
|
546
732
|
# Geometry and provenance
|
|
547
733
|
|
|
@@ -725,7 +911,7 @@ display geometry, and no precinct polygons or raw workbook data are shipped.
|
|
|
725
911
|
|
|
726
912
|
---
|
|
727
913
|
|
|
728
|
-
<!-- Generated from CHANGELOG.md; @kahwee/sf-map-svg 4.
|
|
914
|
+
<!-- Generated from CHANGELOG.md; @kahwee/sf-map-svg 4.4.0 · local working tree. -->
|
|
729
915
|
|
|
730
916
|
# Changelog
|
|
731
917
|
|
|
@@ -733,6 +919,32 @@ User-visible changes are recorded here. Unreleased entries describe changes on `
|
|
|
733
919
|
|
|
734
920
|
## Unreleased
|
|
735
921
|
|
|
922
|
+
## 4.4.0 — 2026-10-12
|
|
923
|
+
|
|
924
|
+
- Add opt-in static `labelLayout: { width }` to `renderMap`, `createGuideSVG`, and `createGuideShell`. Static and interactive maps now share overview priorities, neighborhood anchors, font sizes, and collision rules; centered labels retain their anchors during enhancement.
|
|
925
|
+
- Add static `neighborhoodLabels`, available flat or under `layers`, for the new layout. Guide SVGs and shells enable neighborhood names when opting in. Existing fixed-label output and interactive defaults remain unchanged; `labels: false` still hides text while preserving symbols and accessible titles.
|
|
926
|
+
- Keep static layout server-safe and dependency-free. The width is a CSS canvas-width hint, not the SVG projection width; portable font estimates can produce a different visible label set from browser-measured fonts. Document matching data, markers, colors, and layers, plus responsive-width and custom-style limitations.
|
|
927
|
+
- Keep server font estimation out of interactive-only imports and browser runtime code out of static imports. Refresh bundle reports and enforce bounded size budgets for the combined renderer and static-capable Pages tools.
|
|
928
|
+
- Add desktop and phone enhancement stories, collision and input-validation regressions, an SVG example, and copyable integration documentation. Existing screenshot baselines remain unchanged.
|
|
929
|
+
|
|
930
|
+
## 4.3.2 — 2026-10-10
|
|
931
|
+
|
|
932
|
+
- Update compatible development tooling and refresh dependency security fixes.
|
|
933
|
+
|
|
934
|
+
- Cache label candidates across camera frames, prepare neighborhood priority once per source, and measure overlays before SVG updates.
|
|
935
|
+
- Extract marker catalog reconciliation and cluster rendering with explicit cache and listener ownership; add label refresh checks across catalog, appearance, layers, and source changes.
|
|
936
|
+
|
|
937
|
+
- Create individual marker visuals only on first visibility or selection, keeping the full catalog and retained nodes. Defer new pin entrances and cancel pending entrances with active animations.
|
|
938
|
+
- Benchmark clustered and unclustered continuous pan, rapid zoom, and resize, with separate allocation probes.
|
|
939
|
+
|
|
940
|
+
- Cull offscreen pins and cluster symbols while preserving the full catalog, cached nodes, selection, and keyboard focus. Cache cluster groups during pan and skip unchanged SVG attributes.
|
|
941
|
+
- Add interactive 2,000-marker desktop and phone stories, viewport count benchmarks, and a Bun 1.4.3 compatibility gate.
|
|
942
|
+
|
|
943
|
+
- Add production-bundle interaction checks in Chromium, Firefox, and WebKit, plus reproducible marker benchmarks and CI performance artifacts.
|
|
944
|
+
- Add optimized desktop and phone marker screencasts to the Pages examples, with captions and playback size checks.
|
|
945
|
+
- Add a place chooser for coincident pins and clusters that cannot separate at maximum zoom, including compact embeds with hidden native pickers.
|
|
946
|
+
- Give visible markers and clusters one tab stop with bracket-key navigation, preserving focus across marker reordering and recovering focus when a marker is removed.
|
|
947
|
+
|
|
736
948
|
## 4.3.1 — 2026-10-04
|
|
737
949
|
|
|
738
950
|
- Reject unknown flat static options and invalid values before rendering or invoking district styling callbacks, matching grouped-option validation.
|
|
@@ -1051,7 +1263,7 @@ consistent home, and makes animation and subscription ownership predictable.
|
|
|
1051
1263
|
|
|
1052
1264
|
# Published type contracts
|
|
1053
1265
|
|
|
1054
|
-
@kahwee/sf-map-svg 4.
|
|
1266
|
+
@kahwee/sf-map-svg 4.4.0 · local working tree. These declarations are generated by TypeScript. Use package export paths, not the internal filenames below. Imported relative contracts are included for context, not as additional public entry points.
|
|
1055
1267
|
|
|
1056
1268
|
| Public import | Declaration |
|
|
1057
1269
|
| --- | --- |
|
|
@@ -1232,7 +1444,7 @@ export interface MapCamera {
|
|
|
1232
1444
|
fit(geometry: Geometry, options?: CameraOptions & {
|
|
1233
1445
|
padding?: number | MapPadding;
|
|
1234
1446
|
}): void;
|
|
1235
|
-
/** Delta in
|
|
1447
|
+
/** Delta in screen pixels, converted using the mounted canvas width. */
|
|
1236
1448
|
pan(x: number, y: number, options?: CameraOptions): void;
|
|
1237
1449
|
zoom(factor: number, options?: CameraOptions): void;
|
|
1238
1450
|
reset(options?: CameraOptions): void;
|
|
@@ -1249,7 +1461,7 @@ export interface MapController {
|
|
|
1249
1461
|
getResolvedConfiguration(): ResolvedMapConfiguration;
|
|
1250
1462
|
getCapabilities(): MapCapabilities;
|
|
1251
1463
|
on<K extends keyof MapEvents>(type: K, listener: (detail: MapEvents[K]) => void): () => void;
|
|
1252
|
-
/** Reconcile stable IDs;
|
|
1464
|
+
/** Reconcile stable IDs; preserve existing nodes and focus. Pin visuals allocate on first visibility/selection. */
|
|
1253
1465
|
setMarkers(markers: readonly MapMarker[]): void;
|
|
1254
1466
|
setOverlays(overlays: readonly MapOverlay[]): void;
|
|
1255
1467
|
/** False for missing IDs or a source/year other than the current geography. */
|
|
@@ -1468,12 +1680,18 @@ export interface SFMapOptions {
|
|
|
1468
1680
|
source?: NeighborhoodSource;
|
|
1469
1681
|
districtLines?: boolean;
|
|
1470
1682
|
neighborhoodLines?: boolean;
|
|
1683
|
+
/** Neighborhood names in the opt-in collision layout. */
|
|
1684
|
+
neighborhoodLabels?: boolean;
|
|
1471
1685
|
districtFills?: boolean;
|
|
1472
1686
|
/** Evaluated once per district at construction/style/year updates; call setDistrictStyle again when external data changes. */
|
|
1473
1687
|
districtStyle?: (district: import('./map-core.js').DistrictRowData) => DistrictStyle;
|
|
1474
1688
|
districtLabels?: boolean;
|
|
1475
1689
|
/** Hide all visible text labels while retaining geographic symbols and accessible titles. */
|
|
1476
1690
|
labels?: boolean;
|
|
1691
|
+
/** Opt into interactive overview label priorities/collisions at this CSS display width. Uses estimated system-font metrics without a DOM. */
|
|
1692
|
+
labelLayout?: {
|
|
1693
|
+
width: number;
|
|
1694
|
+
};
|
|
1477
1695
|
highways?: boolean;
|
|
1478
1696
|
keyRoads?: boolean;
|
|
1479
1697
|
roadLabels?: boolean;
|
|
@@ -1496,7 +1714,7 @@ export interface SFMapOptions {
|
|
|
1496
1714
|
colors?: Partial<Record<'water' | 'land' | 'district' | 'neighborhood' | 'highway' | 'road' | 'park' | 'landmark' | 'bart' | 'label' | 'marker' | 'selected', string>>;
|
|
1497
1715
|
}
|
|
1498
1716
|
/** Presentation supported by both the static and interactive renderers. */
|
|
1499
|
-
export type StaticMapLayers =
|
|
1717
|
+
export type StaticMapLayers = InteractiveLayers;
|
|
1500
1718
|
export type StaticMapAppearance = Pick<SFMapOptions, 'theme' | 'colors' | 'districtStyle'>;
|
|
1501
1719
|
/** Serializable viewport in the interactive map's fixed 800 × 800 projected space. */
|
|
1502
1720
|
export type MapViewport = readonly [x: number, y: number, size: number];
|
|
@@ -1663,7 +1881,7 @@ export interface NeighborhoodExplorerElement extends HTMLElement {
|
|
|
1663
1881
|
fitGeometry(geometry: Geometry, padding?: number | MapPadding, options?: CameraOptions): void;
|
|
1664
1882
|
/** Explicitly engage map touch gestures; false restores page gestures. */
|
|
1665
1883
|
setTouchNavigation(enabled: boolean): void;
|
|
1666
|
-
/** Reconcile stable IDs;
|
|
1884
|
+
/** Reconcile stable IDs; preserve existing nodes and focus. Pin visuals allocate on first visibility/selection. */
|
|
1667
1885
|
setMarkers(markers: readonly MapMarker[]): void;
|
|
1668
1886
|
setOverlays(overlays: readonly MapOverlay[]): void;
|
|
1669
1887
|
selectMarker(id: string | null, options?: {
|
|
@@ -1699,10 +1917,12 @@ export interface InteractiveSFMapData {
|
|
|
1699
1917
|
import type { Geometry, Position } from '../data/types.js';
|
|
1700
1918
|
import type { DistrictYear, SFMapOptions } from './types.js';
|
|
1701
1919
|
export type { DistrictYear, MapMarker, MapOverlay, SFMapOptions } from './types.js';
|
|
1920
|
+
import { type Project } from './geometry.js';
|
|
1702
1921
|
export interface SFMapData {
|
|
1703
1922
|
coast: Geometry;
|
|
1704
1923
|
districts?: Readonly<Partial<Record<DistrictYear, readonly DistrictRowData[]>>>;
|
|
1705
1924
|
neighborhoods?: readonly {
|
|
1925
|
+
id?: string;
|
|
1706
1926
|
name: string;
|
|
1707
1927
|
geometry: Geometry;
|
|
1708
1928
|
}[];
|
|
@@ -1793,13 +2013,23 @@ export declare function resolveMapColors(theme: SFMapOptions['theme'], colors?:
|
|
|
1793
2013
|
selected: string;
|
|
1794
2014
|
};
|
|
1795
2015
|
/** Make an offline SVG and the matching longitude/latitude projection. */
|
|
1796
|
-
export declare function createSFMapWithData(options: SFMapOptions, data: SFMapData): {
|
|
2016
|
+
export declare function createSFMapWithData(options: SFMapOptions, data: SFMapData, renderLabels?: (project: Project) => string): {
|
|
1797
2017
|
svg: string;
|
|
1798
2018
|
project: (coordinates: Position) => [number, number];
|
|
1799
2019
|
viewBox: [number, number, number, number];
|
|
1800
2020
|
};
|
|
1801
2021
|
```
|
|
1802
2022
|
|
|
2023
|
+
### dist/src/geometry.d.ts
|
|
2024
|
+
|
|
2025
|
+
```ts
|
|
2026
|
+
import type { Geometry, Position } from '../data/types.js';
|
|
2027
|
+
export type Project = (position: Position) => [number, number];
|
|
2028
|
+
export declare function rawProject([longitude, latitude]: Position): [number, number];
|
|
2029
|
+
export declare function positions(geometry: Geometry | null | undefined): Position[];
|
|
2030
|
+
export declare function geometryPath(geometry: Geometry | null | undefined, project: Project): string;
|
|
2031
|
+
```
|
|
2032
|
+
|
|
1803
2033
|
### dist/src/static.d.ts
|
|
1804
2034
|
|
|
1805
2035
|
```ts
|
|
@@ -1976,16 +2206,6 @@ import type { FeatureCollection, NeighborhoodProperties } from './types.js';
|
|
|
1976
2206
|
export declare const sfFindNeighborhoods: FeatureCollection<NeighborhoodProperties>;
|
|
1977
2207
|
```
|
|
1978
2208
|
|
|
1979
|
-
### ./dist/src/geometry.d.ts
|
|
1980
|
-
|
|
1981
|
-
```ts
|
|
1982
|
-
import type { Geometry, Position } from '../data/types.js';
|
|
1983
|
-
export type Project = (position: Position) => [number, number];
|
|
1984
|
-
export declare function rawProject([longitude, latitude]: Position): [number, number];
|
|
1985
|
-
export declare function positions(geometry: Geometry | null | undefined): Position[];
|
|
1986
|
-
export declare function geometryPath(geometry: Geometry | null | undefined, project: Project): string;
|
|
1987
|
-
```
|
|
1988
|
-
|
|
1989
2209
|
### ./dist/src/guide.d.ts
|
|
1990
2210
|
|
|
1991
2211
|
```ts
|
package/llms.txt
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Offline San Francisco SVG maps with explicit geographic imports, no runtime dependencies, static rendering, and a browser controller.
|
|
4
4
|
|
|
5
|
-
@kahwee/sf-map-svg 4.
|
|
5
|
+
@kahwee/sf-map-svg 4.4.0 · local working tree. Node >=24.0.0 for server rendering; browsers need a DOM and a JSON-capable bundler.
|
|
6
6
|
|
|
7
7
|
These are working-tree docs. Unreleased changelog entries may describe APIs absent from npm; verify the installed version before copying them.
|
|
8
8
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kahwee/sf-map-svg",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
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",
|
|
@@ -135,22 +135,22 @@
|
|
|
135
135
|
"node": ">=24.0.0"
|
|
136
136
|
},
|
|
137
137
|
"devDependencies": {
|
|
138
|
-
"@biomejs/biome": "2.5.
|
|
139
|
-
"@storybook/addon-a11y": "10.6.
|
|
140
|
-
"@storybook/addon-docs": "^10.6.
|
|
141
|
-
"@storybook/addon-vitest": "10.6.
|
|
142
|
-
"@storybook/html-vite": "^10.6.
|
|
143
|
-
"@vitest/browser-playwright": "^5.0.
|
|
144
|
-
"@vitest/coverage-v8": "5.0.
|
|
138
|
+
"@biomejs/biome": "2.5.15",
|
|
139
|
+
"@storybook/addon-a11y": "10.6.1",
|
|
140
|
+
"@storybook/addon-docs": "^10.6.1",
|
|
141
|
+
"@storybook/addon-vitest": "10.6.1",
|
|
142
|
+
"@storybook/html-vite": "^10.6.1",
|
|
143
|
+
"@vitest/browser-playwright": "^5.0.3",
|
|
144
|
+
"@vitest/coverage-v8": "5.0.3",
|
|
145
145
|
"@xmldom/xmldom": "^0.9.12",
|
|
146
146
|
"playwright": "^1.63.0",
|
|
147
147
|
"polygon-clipping": "^0.15.7",
|
|
148
148
|
"react": "^19.3.0",
|
|
149
149
|
"react-dom": "^19.3.0",
|
|
150
|
-
"storybook": "^10.6.
|
|
150
|
+
"storybook": "^10.6.1",
|
|
151
151
|
"typescript": "^7.0.2",
|
|
152
|
-
"vite": "^8.3.
|
|
153
|
-
"vitest": "^5.0.
|
|
152
|
+
"vite": "^8.3.3",
|
|
153
|
+
"vitest": "^5.0.3"
|
|
154
154
|
},
|
|
155
155
|
"homepage": "https://kahwee.github.io/sf-map-svg/",
|
|
156
156
|
"bugs": {
|
|
@@ -172,6 +172,9 @@
|
|
|
172
172
|
"test:stories": "pnpm build && vitest run --project=storybook",
|
|
173
173
|
"test:stories:coverage": "pnpm build && vitest run --project=storybook --coverage",
|
|
174
174
|
"test:visual": "playwright test --config=playwright.visual.config.mjs",
|
|
175
|
+
"test:browsers": "playwright test --config=playwright.browser.config.mjs",
|
|
176
|
+
"test:bun": "pnpm build && bun scripts/smoke-bun.mjs",
|
|
177
|
+
"benchmark:markers": "node scripts/benchmark-markers.mjs",
|
|
175
178
|
"demo": "pnpm build && node examples/build.mjs",
|
|
176
179
|
"format": "biome check --write .",
|
|
177
180
|
"format:check": "biome format .",
|