@kahwee/sf-map-svg 4.1.0 → 4.2.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 +14 -0
- package/README.md +2 -2
- package/dist/src/appearance.d.ts +5 -0
- package/dist/src/appearance.js +54 -0
- package/dist/src/configuration.js +32 -23
- package/dist/src/controller-types.d.ts +53 -6
- package/dist/src/explorer-core.d.ts +14 -2
- package/dist/src/explorer-core.js +168 -37
- package/dist/src/guide-map.js +1 -1
- package/dist/src/guide-static.js +3 -3
- package/dist/src/map-core.d.ts +14 -0
- package/dist/src/map-core.js +4 -1
- package/dist/src/map.js +125 -20
- package/dist/src/static.d.ts +1 -1
- package/dist/src/static.js +31 -2
- package/dist/src/types.d.ts +8 -2
- package/docs/API.md +53 -1
- package/docs/api-audit.md +3 -1
- package/docs/developer-guide.md +5 -3
- package/llms-full.txt +160 -25
- package/llms.txt +1 -1
- package/package.json +3 -2
package/dist/src/map.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { copyAppearance } from './appearance.js';
|
|
1
2
|
import { validateCameraOptions } from './camera.js';
|
|
2
3
|
import { expandMapOptions, prepareConfiguration } from './configuration.js';
|
|
3
4
|
import { createNeighborhoodExplorerCore } from './explorer-core.js';
|
|
@@ -6,10 +7,11 @@ import { assertOptions } from './validation.js';
|
|
|
6
7
|
/** No geography is imported. Supply a preset or your own immutable data. */
|
|
7
8
|
export function createMap(data, options = {}) {
|
|
8
9
|
const expanded = expandMapOptions(options);
|
|
9
|
-
let config = prepareConfiguration({ features: {}, layers: {}, controls: {} }, {
|
|
10
|
+
let config = prepareConfiguration({ features: {}, layers: {}, controls: {}, appearance: {} }, {
|
|
10
11
|
features: options.features,
|
|
11
12
|
layers: options.layers,
|
|
12
13
|
controls: options.controls,
|
|
14
|
+
appearance: options.appearance,
|
|
13
15
|
});
|
|
14
16
|
const element = createNeighborhoodExplorerCore({
|
|
15
17
|
mode: 'basemap',
|
|
@@ -18,6 +20,80 @@ export function createMap(data, options = {}) {
|
|
|
18
20
|
onOverlayActivate: () => { },
|
|
19
21
|
}, data);
|
|
20
22
|
const subscriptions = new Set();
|
|
23
|
+
let configurationRevision = 0;
|
|
24
|
+
const sourceList = ['realtor', 'sf-find', 'analysis'].filter((source) => !!data.neighborhoods[source]);
|
|
25
|
+
const yearList = [2002, 2012, 2022].filter((year) => !!data.map.districts?.[year] && !!data.districts?.[year]);
|
|
26
|
+
const capabilities = () => ({
|
|
27
|
+
sources: [...sourceList],
|
|
28
|
+
years: [...yearList],
|
|
29
|
+
layers: Object.fromEntries(layerKeys.map((key) => [
|
|
30
|
+
key,
|
|
31
|
+
key.startsWith('district')
|
|
32
|
+
? !!data.map.districts?.[element.getMapState().year]?.length &&
|
|
33
|
+
yearList.includes(element.getMapState().year)
|
|
34
|
+
: key.startsWith('neighborhood')
|
|
35
|
+
? !!data.neighborhoods[element.getMapState().source]?.features.length
|
|
36
|
+
: key === 'roadLabels'
|
|
37
|
+
? !!data.map.keyRoads?.length
|
|
38
|
+
: !!data.map[key]?.length,
|
|
39
|
+
])),
|
|
40
|
+
});
|
|
41
|
+
const snapshot = () => ({
|
|
42
|
+
...structuredClone({
|
|
43
|
+
features: config.features,
|
|
44
|
+
layers: config.layers,
|
|
45
|
+
controls: config.controls,
|
|
46
|
+
}),
|
|
47
|
+
appearance: copyAppearance(config.appearance),
|
|
48
|
+
});
|
|
49
|
+
const resolved = () => {
|
|
50
|
+
const state = element.getMapState();
|
|
51
|
+
const supplied = capabilities().layers;
|
|
52
|
+
return {
|
|
53
|
+
...snapshot(),
|
|
54
|
+
...state,
|
|
55
|
+
layers: Object.fromEntries(layerKeys.map((key) => [
|
|
56
|
+
key,
|
|
57
|
+
supplied[key] &&
|
|
58
|
+
(!key.endsWith('Labels') || state.labels) &&
|
|
59
|
+
(config.layers[key] ??
|
|
60
|
+
(key.startsWith('district')
|
|
61
|
+
? state.mode === 'districts'
|
|
62
|
+
: key.startsWith('neighborhood')
|
|
63
|
+
? state.mode === 'neighborhoods'
|
|
64
|
+
: true)),
|
|
65
|
+
])),
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
let previousMarker = element.getSelectedMarker();
|
|
69
|
+
let previousNeighborhood = element.getSelection();
|
|
70
|
+
let previousDistrict = element.getSelectedDistrict();
|
|
71
|
+
const selectionListeners = [];
|
|
72
|
+
for (const type of ['markerchange', 'neighborhoodchange', 'districtchange']) {
|
|
73
|
+
const handle = (event) => {
|
|
74
|
+
if (event.target !== element)
|
|
75
|
+
return;
|
|
76
|
+
let detail;
|
|
77
|
+
if (type === 'markerchange') {
|
|
78
|
+
const current = element.getSelectedMarker();
|
|
79
|
+
detail = { kind: 'marker', current, previous: previousMarker };
|
|
80
|
+
previousMarker = current;
|
|
81
|
+
}
|
|
82
|
+
else if (type === 'neighborhoodchange') {
|
|
83
|
+
const current = element.getSelection();
|
|
84
|
+
detail = { kind: 'neighborhood', current, previous: previousNeighborhood };
|
|
85
|
+
previousNeighborhood = current;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
const current = element.getSelectedDistrict();
|
|
89
|
+
detail = { kind: 'district', current, previous: previousDistrict };
|
|
90
|
+
previousDistrict = current;
|
|
91
|
+
}
|
|
92
|
+
element.dispatchEvent(new CustomEvent('selectionchange', { detail: structuredClone(detail) }));
|
|
93
|
+
};
|
|
94
|
+
element.addEventListener(type, handle);
|
|
95
|
+
selectionListeners.push(() => element.removeEventListener(type, handle));
|
|
96
|
+
}
|
|
21
97
|
let destroyed = false;
|
|
22
98
|
function active() {
|
|
23
99
|
if (destroyed)
|
|
@@ -41,6 +117,28 @@ export function createMap(data, options = {}) {
|
|
|
41
117
|
element.fitGeometry(geometry, padding, motion);
|
|
42
118
|
}),
|
|
43
119
|
});
|
|
120
|
+
function configure(patch) {
|
|
121
|
+
const revision = ++configurationRevision;
|
|
122
|
+
const next = prepareConfiguration(config, patch);
|
|
123
|
+
const hasFeatures = 'features' in patch, hasLayers = 'layers' in patch, hasControls = 'controls' in patch;
|
|
124
|
+
const layers = Object.fromEntries(layerKeys.map((key) => [key, next.layers[key]]));
|
|
125
|
+
const controls = Object.fromEntries(controlKeys.map((key) => [key, next.controls[key]]));
|
|
126
|
+
const features = patch.features === undefined
|
|
127
|
+
? next.features
|
|
128
|
+
: Object.fromEntries(Object.keys(patch.features).map((key) => [
|
|
129
|
+
key,
|
|
130
|
+
next.features[key],
|
|
131
|
+
]));
|
|
132
|
+
element.applyPresentation({ ...patch, ...('appearance' in patch ? { appearance: next.appearance } : {}) }, () => {
|
|
133
|
+
config = next;
|
|
134
|
+
if (hasFeatures)
|
|
135
|
+
element.setFeatures(features);
|
|
136
|
+
if (hasLayers)
|
|
137
|
+
element.setLayers(layers);
|
|
138
|
+
if (hasControls)
|
|
139
|
+
element.setControls(controls);
|
|
140
|
+
}, () => !destroyed && revision === configurationRevision);
|
|
141
|
+
}
|
|
44
142
|
return Object.freeze({
|
|
45
143
|
element,
|
|
46
144
|
overlayElement: element.overlayElement,
|
|
@@ -48,22 +146,13 @@ export function createMap(data, options = {}) {
|
|
|
48
146
|
get destroyed() {
|
|
49
147
|
return destroyed;
|
|
50
148
|
},
|
|
51
|
-
configure: use(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const controls = Object.fromEntries(controlKeys.map((key) => [key, next.controls[key]]));
|
|
56
|
-
if ('features' in patch)
|
|
57
|
-
element.setFeatures(patch.features ?? next.features);
|
|
58
|
-
if ('layers' in patch)
|
|
59
|
-
element.setLayers(layers);
|
|
60
|
-
if ('controls' in patch)
|
|
61
|
-
element.setControls(controls);
|
|
62
|
-
config = next;
|
|
63
|
-
}),
|
|
64
|
-
getConfiguration: use(() => structuredClone(config)),
|
|
149
|
+
configure: use(configure),
|
|
150
|
+
getConfiguration: use(snapshot),
|
|
151
|
+
getResolvedConfiguration: use(resolved),
|
|
152
|
+
getCapabilities: use(capabilities),
|
|
65
153
|
on: use((type, listener) => {
|
|
66
154
|
if (![
|
|
155
|
+
'selectionchange',
|
|
67
156
|
'markerchange',
|
|
68
157
|
'districtchange',
|
|
69
158
|
'districthover',
|
|
@@ -97,11 +186,25 @@ export function createMap(data, options = {}) {
|
|
|
97
186
|
getSelectedNeighborhood: use(element.getSelection),
|
|
98
187
|
selectDistrict: use(element.selectDistrict),
|
|
99
188
|
getSelectedDistrict: use(element.getSelectedDistrict),
|
|
100
|
-
setDistrictYear: use(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
189
|
+
setDistrictYear: use((...args) => {
|
|
190
|
+
configurationRevision++;
|
|
191
|
+
element.setDistrictYear(...args);
|
|
192
|
+
}),
|
|
193
|
+
setDistrictStyle: use((style) => {
|
|
194
|
+
configure({ appearance: { districtStyle: style } });
|
|
195
|
+
}),
|
|
196
|
+
setSource: use((...args) => {
|
|
197
|
+
configurationRevision++;
|
|
198
|
+
element.setSource(...args);
|
|
199
|
+
}),
|
|
200
|
+
setMode: use((...args) => {
|
|
201
|
+
configurationRevision++;
|
|
202
|
+
element.setMode(...args);
|
|
203
|
+
}),
|
|
204
|
+
setLabels: use((...args) => {
|
|
205
|
+
configurationRevision++;
|
|
206
|
+
element.setLabels(...args);
|
|
207
|
+
}),
|
|
105
208
|
setTouchNavigation: use(element.setTouchNavigation),
|
|
106
209
|
projectToScreen: use(element.projectToScreen),
|
|
107
210
|
destroy() {
|
|
@@ -110,6 +213,8 @@ export function createMap(data, options = {}) {
|
|
|
110
213
|
destroyed = true;
|
|
111
214
|
for (const unsubscribe of subscriptions)
|
|
112
215
|
unsubscribe();
|
|
216
|
+
for (const unsubscribe of selectionListeners)
|
|
217
|
+
unsubscribe();
|
|
113
218
|
element.destroy();
|
|
114
219
|
},
|
|
115
220
|
});
|
package/dist/src/static.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type SFMapData } from './map-core.js';
|
|
2
2
|
import type { SFMapOptions } from './types.js';
|
|
3
3
|
export type { DistrictRowData, SFMapData as StaticMapData } from './map-core.js';
|
|
4
|
-
export type { DistrictStyle, DistrictYear, SFMapOptions as StaticMapOptions } from './types.js';
|
|
4
|
+
export type { DistrictStyle, DistrictYear, SFMapOptions as StaticMapOptions, StaticMapAppearance, StaticMapLayers, } from './types.js';
|
|
5
5
|
/** Server-safe SVG rendering with explicit data; returns SVG plus projection helpers. */
|
|
6
6
|
export declare function renderMap(data: SFMapData, options?: SFMapOptions): {
|
|
7
7
|
svg: string;
|
package/dist/src/static.js
CHANGED
|
@@ -1,9 +1,38 @@
|
|
|
1
|
+
import { prepareAppearance } from './appearance.js';
|
|
2
|
+
import { validateSwitchPatch } from './features.js';
|
|
1
3
|
import { createSFMapWithData, getLayerPathsWithData } from './map-core.js';
|
|
4
|
+
import { assertOptions } from './validation.js';
|
|
2
5
|
/** Server-safe SVG rendering with explicit data; returns SVG plus projection helpers. */
|
|
3
6
|
export function renderMap(data, options = {}) {
|
|
4
|
-
return createSFMapWithData(options, data);
|
|
7
|
+
return createSFMapWithData(expandStaticOptions(options), data);
|
|
5
8
|
}
|
|
6
9
|
/** Project canonical layer geometry without constructing an SVG string. */
|
|
7
10
|
export function getLayerPaths(data, options = {}) {
|
|
8
|
-
return getLayerPathsWithData(options, data);
|
|
11
|
+
return getLayerPathsWithData(expandStaticOptions(options), data);
|
|
12
|
+
}
|
|
13
|
+
/** Grouped keys override flat compatibility keys; omitted keys preserve renderer defaults. */
|
|
14
|
+
function expandStaticOptions(options) {
|
|
15
|
+
assertOptions(options, 'static map');
|
|
16
|
+
if (options.layers !== undefined)
|
|
17
|
+
validateSwitchPatch(options.layers, [
|
|
18
|
+
'districtFills',
|
|
19
|
+
'districtLines',
|
|
20
|
+
'districtLabels',
|
|
21
|
+
'neighborhoodLines',
|
|
22
|
+
'landmarks',
|
|
23
|
+
'bartStations',
|
|
24
|
+
'highways',
|
|
25
|
+
'keyRoads',
|
|
26
|
+
'roadLabels',
|
|
27
|
+
]);
|
|
28
|
+
if (options.appearance !== undefined) {
|
|
29
|
+
assertOptions(options.appearance, 'static appearance', ['theme', 'colors', 'districtStyle']);
|
|
30
|
+
}
|
|
31
|
+
const { layers, appearance, ...flat } = options;
|
|
32
|
+
const supplied = (group = {}) => Object.fromEntries(Object.entries(group).filter(([, value]) => value !== undefined));
|
|
33
|
+
return {
|
|
34
|
+
...flat,
|
|
35
|
+
...supplied(layers),
|
|
36
|
+
...supplied(appearance === undefined ? undefined : prepareAppearance({}, appearance)),
|
|
37
|
+
};
|
|
9
38
|
}
|
package/dist/src/types.d.ts
CHANGED
|
@@ -33,6 +33,9 @@ export interface MapOverlay {
|
|
|
33
33
|
label?: string;
|
|
34
34
|
}
|
|
35
35
|
export interface SFMapOptions {
|
|
36
|
+
/** Grouped presentation; supplied keys take precedence over flat compatibility options. */
|
|
37
|
+
layers?: StaticMapLayers;
|
|
38
|
+
appearance?: StaticMapAppearance;
|
|
36
39
|
theme?: 'districts' | 'transit';
|
|
37
40
|
width?: number;
|
|
38
41
|
height?: number;
|
|
@@ -51,7 +54,7 @@ export interface SFMapOptions {
|
|
|
51
54
|
roadLabels?: boolean;
|
|
52
55
|
landmarks?: boolean;
|
|
53
56
|
bartStations?: boolean;
|
|
54
|
-
markers?: MapMarker[];
|
|
57
|
+
markers?: readonly MapMarker[];
|
|
55
58
|
overlays?: readonly MapOverlay[];
|
|
56
59
|
title?: string;
|
|
57
60
|
idPrefix?: string;
|
|
@@ -67,6 +70,9 @@ export interface SFMapOptions {
|
|
|
67
70
|
};
|
|
68
71
|
colors?: Partial<Record<'water' | 'land' | 'district' | 'neighborhood' | 'highway' | 'road' | 'park' | 'landmark' | 'bart' | 'label' | 'marker' | 'selected', string>>;
|
|
69
72
|
}
|
|
73
|
+
/** Presentation supported by both the static and interactive renderers. */
|
|
74
|
+
export type StaticMapLayers = Omit<InteractiveLayers, 'neighborhoodLabels'>;
|
|
75
|
+
export type StaticMapAppearance = Pick<SFMapOptions, 'theme' | 'colors' | 'districtStyle'>;
|
|
70
76
|
/** Serializable viewport in the interactive map's fixed 800 × 800 projected space. */
|
|
71
77
|
export type MapViewport = readonly [x: number, y: number, size: number];
|
|
72
78
|
export type ExplorerMode = 'neighborhoods' | 'districts' | 'basemap';
|
|
@@ -245,4 +251,4 @@ export interface NeighborhoodExplorerElement extends HTMLElement {
|
|
|
245
251
|
export interface TransitAnimationElement extends HTMLElement {
|
|
246
252
|
destroy(): void;
|
|
247
253
|
}
|
|
248
|
-
export type { MapAppearance, MapCamera, MapConfiguration, MapController, MapEvents, MapOptions, } from './controller-types.js';
|
|
254
|
+
export type { MapAppearance, MapCamera, MapCapabilities, MapConfiguration, MapConfigurationSnapshot, MapController, MapControls, MapEvents, MapOptions, MapSelectionChange, ResolvedMapConfiguration, } from './controller-types.js';
|
package/docs/API.md
CHANGED
|
@@ -6,7 +6,20 @@ and event types are exported by [src/api.ts](../src/api.ts) and defined in
|
|
|
6
6
|
|
|
7
7
|
## Static maps
|
|
8
8
|
|
|
9
|
-
Static
|
|
9
|
+
Static maps accept the same grouped presentation vocabulary as interactive maps:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
const presentation = {
|
|
13
|
+
layers: { landmarks: true, bartStations: true },
|
|
14
|
+
appearance: { theme: 'districts' as const, colors: { water: '#e7f0f3' } },
|
|
15
|
+
};
|
|
16
|
+
const { svg } = renderMap(staticMapData, presentation);
|
|
17
|
+
const map = createMap(fullMapData, presentation);
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Static `layers` supports every interactive layer switch except `neighborhoodLabels`. Static `appearance` supports `theme`, `colors`, and `districtStyle`; browser typography, area interaction styles, and screen-space marker sizing remain interactive settings. Unknown grouped keys are rejected. Supplied grouped keys override flat compatibility keys; omitted or `undefined` grouped keys preserve the flat value or renderer default. The two renderers retain their existing defaults. Marker arrays accept readonly inputs.
|
|
21
|
+
|
|
22
|
+
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.
|
|
10
23
|
|
|
11
24
|
Custom district IDs must be finite numbers. An empty `labelPoints` array uses the district's primary `label` position.
|
|
12
25
|
|
|
@@ -31,3 +44,42 @@ For the guide preset with the same controller API, use `createGuideController(op
|
|
|
31
44
|
Two opt-in features animate the map's lines and layers. `features.layerTransitions` fades layers as `configure({ layers })` and `setMode()` switch them, crossfades neighborhood boundaries when `setSource()` changes the definition, and crossfades district fills on `setDistrictStyle()`. `features.districtMorph` moves district outlines from one map year to the next on `setDistrictYear()`, then settles on the exact published geometry; pass `{ animate: false }` for an instant change. Both default off, own and cancel their animations, and yield to reduced motion.
|
|
32
45
|
|
|
33
46
|
District style callbacks are evaluated once per district at construction and on style or year updates. Hover, selection, and layer toggles reuse the prepared styles; call `setDistrictStyle()` again when external styling data changes. Invalid styles leave the current map unchanged. District fades respect reduced motion and are removed on interruption or destruction.
|
|
47
|
+
|
|
48
|
+
## Live configuration and inspection
|
|
49
|
+
|
|
50
|
+
`map.configure({ appearance, mode, source, year, labels, layers, features, controls })` updates an existing map. Appearance updates preserve the camera, selections, retained marker nodes, and keyboard focus. Mode/source changes through `configure` preserve the camera; existing `setMode` and `setSource` convenience methods retain their reset behavior. Switching neighborhood source clears the neighborhood selection. Changing district year retains a selected district when it exists in the new year. Unsupported datasets, invalid appearance values, and invalid district-style callback results fail during preparation before commit. A reentrant styling callback can supersede the pending patch.
|
|
51
|
+
|
|
52
|
+
Appearance token objects (`colors`, `style`, `labelStyle`, `labelSize`, `areaStyle`) merge supplied keys; other appearance properties replace. Omitted keys retain their value, an explicit `undefined` property removes that override, and `appearance: undefined` resets the whole appearance group. Feature objects retain their existing replacement semantics. Mode/source/year/labels ignore `undefined`; reset them with an explicit value. Patch types explicitly permit these resets with TypeScript’s `exactOptionalPropertyTypes` enabled.
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
map.configure({
|
|
56
|
+
appearance: { colors: { water: '#142d45' }, labelStyle: { fontFamily: 'Georgia,serif' } },
|
|
57
|
+
layers: { bartStations: false },
|
|
58
|
+
});
|
|
59
|
+
map.configure({ appearance: undefined }); // restore construction defaults, not initial overrides
|
|
60
|
+
|
|
61
|
+
const overrides = map.getConfiguration();
|
|
62
|
+
const effective = map.getResolvedConfiguration();
|
|
63
|
+
const available = map.getCapabilities();
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`getConfiguration()` returns detached feature, layer, control, and appearance overrides; district style callbacks retain their function identity. `getResolvedConfiguration()` adds the current mode, source, year and master labels and resolves **layer switches** through mode defaults, available data, and the master label switch. Appearance/control fields remain overrides. It does not claim that every label is visible: zoom and collision filtering still apply. `getCapabilities()` returns supplied `sources`, usable district `years`, and layer data availability for the current source/year, independent of visibility overrides. These reads throw after destruction.
|
|
67
|
+
|
|
68
|
+
## Common selection event
|
|
69
|
+
|
|
70
|
+
`selectionchange` adds a consistent envelope without changing existing event payloads:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
map.on('selectionchange', (event) => {
|
|
74
|
+
// kind narrows current and previous to the corresponding selection type.
|
|
75
|
+
if (event.kind === 'marker') console.log(event.current?.id, event.previous?.id);
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`kind` is `marker`, `neighborhood`, or `district`. `current` and `previous` are detached selection snapshots or `null`. The event follows committed selection changes, including clearing and a changed district vintage; it does not represent every activation. Existing `markerchange`, `neighborhoodchange`, and `districtchange` events remain supported.
|
|
80
|
+
|
|
81
|
+
## Coordinates and units
|
|
82
|
+
|
|
83
|
+
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 and pan deltas use the fixed 800 × 800 projected map space, independent of canvas size. Marker radius uses screen pixels in browser maps and SVG units in static output.
|
|
84
|
+
|
|
85
|
+
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.
|
package/docs/api-audit.md
CHANGED
|
@@ -54,7 +54,7 @@ exceptions are not transactional validation errors and do not roll back an alrea
|
|
|
54
54
|
committed change. Direct mutation of the returned DOM can invalidate invariants;
|
|
55
55
|
use the public methods and `overlayElement` extension point.
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
Static grouped layers and appearance reject unknown keys, while flat compatibility options retain their existing surface; strict
|
|
58
58
|
interactive option-key validation is not a claim that all static options share the
|
|
59
59
|
same schema. Host layout, delayed images, fonts, and application cards need their
|
|
60
60
|
own CLS tests. The scale bar is an approximate local Mercator scale, not survey
|
|
@@ -76,3 +76,5 @@ public types change. Update this matrix when introducing a new interaction.
|
|
|
76
76
|
- `pnpm test:stories:coverage` runs Chromium stories and reports browser coverage.
|
|
77
77
|
- `pnpm demo`, `pnpm build-storybook`, `pnpm test:package`, and `pnpm test:visual` cover examples, rendered stories, packed consumer imports, and reviewed map screenshots.
|
|
78
78
|
- The packed consumer asserts removed compatibility entry points are absent.
|
|
79
|
+
|
|
80
|
+
Live appearance patches prepare values, district styles and source replacement before committing; token objects merge by key. Styling callbacks that issue a newer presentation update supersede the pending patch. Resolved layer reads distinguish visibility settings from data availability and from zoom/collision label filtering. The additive selection envelope preserves existing event contracts.
|
package/docs/developer-guide.md
CHANGED
|
@@ -88,13 +88,13 @@ SFAR realtor neighborhoods are the default when supplied. If only an alternative
|
|
|
88
88
|
|
|
89
89
|
| Change | Call |
|
|
90
90
|
| --- | --- |
|
|
91
|
-
| Layers, controls, behaviors | `map.configure({ layers, controls, features })` |
|
|
91
|
+
| Layers, controls, behaviors | `map.configure({ layers, controls, features, appearance, mode, source, year, labels })` |
|
|
92
92
|
| Map mode or neighborhood source | `map.setMode(mode)`, `map.setSource(source)` |
|
|
93
93
|
| District boundaries or choropleth | `map.setDistrictYear(year)`, `map.setDistrictStyle(callback)` |
|
|
94
94
|
| Master text visibility | `map.setLabels(visible)` |
|
|
95
95
|
| Markers or route overlays | `map.setMarkers(items)`, `map.setOverlays(items)` |
|
|
96
96
|
| Camera | `map.camera.set/pan/zoom/fit/reset(...)` |
|
|
97
|
-
| Palette, typography, control styling |
|
|
97
|
+
| Palette, typography, control styling | `map.configure({ appearance })`; retains camera, selection and focus |
|
|
98
98
|
|
|
99
99
|
`configure` merges supplied keys; omitted keys keep their existing values. Set a layer or control key to `undefined` to remove its override. Set a whole group to `undefined` to reset that group. Set a feature to `false` to disable it. Explicit layer overrides keep winning after mode changes.
|
|
100
100
|
|
|
@@ -105,7 +105,7 @@ map.configure({ features: { motion: false } });
|
|
|
105
105
|
map.configure({ layers: undefined }); // clear all explicit layer overrides
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
`getConfiguration()` returns a detached snapshot of these groups, including explicit layer overrides. It is not a full serialization of mode, source, year, camera, selection,
|
|
108
|
+
`getConfiguration()` returns a detached snapshot of these groups, including explicit layer overrides. It is not a full serialization of mode, source, year, camera, or selection. Appearance overrides are included. Use `getResolvedConfiguration()` for current mode/source/year/labels and effective layer switches, and `getCapabilities()` for supplied geography. Invalid configuration patches leave the current map unchanged.
|
|
109
109
|
|
|
110
110
|
## Markers, routes and camera coordinates
|
|
111
111
|
|
|
@@ -135,3 +135,5 @@ The package declarations are the exact type contract for your installed version.
|
|
|
135
135
|
The generated `llms.txt` index links to Markdown documentation; `llms-full.txt` combines the guides and type contracts into one file. Both are built from the same package selected for the site. Local previews are labeled as working-tree docs. Released builds use the installed npm package's docs and declarations.
|
|
136
136
|
|
|
137
137
|
See [API options](API.md), [worked examples](EXAMPLES.md), [consumer integration](consumer-integration.md), and [v3 migration](migration-v3.md).
|
|
138
|
+
|
|
139
|
+
Appearance token objects merge by key; `undefined` removes an override and `appearance: undefined` resets the group. Static maps accept grouped `layers` and the shared `appearance` keys (`theme`, `colors`, `districtStyle`) too. See [the API contract](API.md#live-configuration-and-inspection) and [the design playground](https://kahwee.github.io/sf-map-svg/playground.html).
|