@kahwee/sf-map-svg 4.4.0 → 4.5.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/dist/src/cards.d.ts +15 -0
- package/dist/src/cards.js +138 -0
- package/dist/src/cluster-layer.d.ts +2 -0
- package/dist/src/cluster-layer.js +2 -0
- package/dist/src/explorer-controls.d.ts +26 -0
- package/dist/src/explorer-controls.js +120 -0
- package/dist/src/explorer-core.d.ts +6 -1
- package/dist/src/explorer-core.js +146 -662
- package/dist/src/explorer-shell.d.ts +62 -0
- package/dist/src/explorer-shell.js +228 -0
- package/dist/src/explorer-sidebar.d.ts +20 -0
- package/dist/src/explorer-sidebar.js +79 -0
- package/dist/src/guide-map.js +2 -1
- package/dist/src/label-spread.d.ts +5 -0
- package/dist/src/label-spread.js +36 -0
- package/dist/src/map.js +2 -1
- package/dist/src/neighborhood-layer.d.ts +67 -0
- package/dist/src/neighborhood-layer.js +256 -0
- package/dist/src/overlay-layer.d.ts +13 -0
- package/dist/src/overlay-layer.js +68 -0
- package/dist/src/palettes.d.ts +48 -0
- package/dist/src/palettes.js +40 -0
- package/dist/src/presets.d.ts +1 -0
- package/dist/src/presets.js +1 -0
- package/dist/src/static-labels.js +17 -2
- package/dist/src/static.js +21 -2
- package/dist/src/types.d.ts +9 -0
- package/docs/API.md +32 -0
- package/docs/consumer-integration.md +39 -1
- package/docs/guide-bundle-report.md +4 -4
- package/llms-full.txt +179 -11
- package/llms.txt +1 -1
- package/package.json +16 -8
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,20 @@ User-visible changes are recorded here. Unreleased entries describe changes on `
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 4.5.0 — 2026-10-14
|
|
8
|
+
|
|
9
|
+
- Add the optional `/cards` entry with `bindMapCards()`: hover/focus previews a pin without changing selection or camera, native buttons select places, map selection reveals matching cards without stealing focus, and explicit cleanup restores owned DOM attributes.
|
|
10
|
+
- Spread budgeted static labels across the map while favoring nearby-pin context and retaining a selected-place label when it fits. Keep collision avoidance, all pins, unbudgeted labels, and default output unchanged.
|
|
11
|
+
|
|
12
|
+
- Add opt-in `mapPalettes.nightForest` and `mapPalettes.warmPaper` tokens for consistent static/interactive map colors, label halos, markers, and selection rings, with contrast checks.
|
|
13
|
+
|
|
14
|
+
- Port YorkSF’s editorial map-preview pattern into static rendering: opt-in compact SVG label layouts, spatial label budgets, and configurable halos for dark palettes. Add a build-time field-guide example and light/dark Storybook preview without runtime dependencies or changes to default output.
|
|
15
|
+
|
|
16
|
+
- Separate interactive neighborhood state, controls, sidebar downloads and overlays into lifecycle-owned modules; revoke neighborhood hover listeners when replacing sources and keep optional explorer sidebar code out of normal map bundles.
|
|
17
|
+
- Build the package once during `pnpm check`, while keeping standalone test and typecheck commands self-contained.
|
|
18
|
+
|
|
19
|
+
- Include compatible development-tool updates to Vite 8.3.4 and Playwright 1.64.0. Keep Storybook/addons 10.6.1 and aligned Vitest/browser/coverage 5.0.3; their newer 10.6.2 and 5.0.4 releases remain inside pnpm's release-age window. Make the consumer examples' custom touch-button accessible names include their visible text. Remove the obsolete Storybook 10.6.0 peer overrides; retain Node 24+ and zero runtime dependencies. These tooling changes were previously prepared as 4.4.1 but not published separately.
|
|
20
|
+
|
|
7
21
|
## 4.4.0 — 2026-10-12
|
|
8
22
|
|
|
9
23
|
- 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.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MapController } from './controller-types.js';
|
|
2
|
+
import type { MapMarker } from './types.js';
|
|
3
|
+
export interface MapCardsOptions {
|
|
4
|
+
markers: readonly MapMarker[];
|
|
5
|
+
highlightColor?: string;
|
|
6
|
+
/** Reveal selected cards with native scrolling, without moving keyboard focus. Default true. */
|
|
7
|
+
scrollOnSelect?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** Connect consumer-owned [data-map-marker] cards and [data-map-select] buttons to a map. */
|
|
10
|
+
export declare function bindMapCards(map: MapController, root: HTMLElement, options: MapCardsOptions): {
|
|
11
|
+
/** Call after map.setMarkers() when your place catalog changes. */
|
|
12
|
+
setMarkers(next: readonly MapMarker[]): void;
|
|
13
|
+
/** Remove owned listeners/decoration and restore card attributes. Call before map.destroy(). */
|
|
14
|
+
destroy(): void;
|
|
15
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { validateMarkers } from './validation.js';
|
|
2
|
+
/** Connect consumer-owned [data-map-marker] cards and [data-map-select] buttons to a map. */
|
|
3
|
+
export function bindMapCards(map, root, options) {
|
|
4
|
+
if (map.destroyed)
|
|
5
|
+
throw new Error('Cannot bind cards to a destroyed map.');
|
|
6
|
+
validateMarkers(options.markers);
|
|
7
|
+
let markers = new Map(options.markers.map((marker) => [marker.id, { ...marker }]));
|
|
8
|
+
let disposed = false;
|
|
9
|
+
let hovered = null;
|
|
10
|
+
let focused = null;
|
|
11
|
+
let selecting = false;
|
|
12
|
+
const document = root.ownerDocument;
|
|
13
|
+
const ring = document.createElement('span');
|
|
14
|
+
ring.dataset.mapCardHighlight = '';
|
|
15
|
+
ring.setAttribute('aria-hidden', 'true');
|
|
16
|
+
ring.style.cssText =
|
|
17
|
+
'position:absolute;width:26px;height:26px;box-sizing:border-box;border:3px solid;border-radius:50%;transform:translate(-50%,-50%);pointer-events:none';
|
|
18
|
+
ring.style.borderColor = options.highlightColor ?? '#e4531b';
|
|
19
|
+
ring.hidden = true;
|
|
20
|
+
map.overlayElement.append(ring);
|
|
21
|
+
const originals = new Map();
|
|
22
|
+
function attribute(node, key, value) {
|
|
23
|
+
let saved = originals.get(node);
|
|
24
|
+
if (!saved) {
|
|
25
|
+
saved = new Map();
|
|
26
|
+
originals.set(node, saved);
|
|
27
|
+
}
|
|
28
|
+
if (!saved.has(key))
|
|
29
|
+
saved.set(key, node.getAttribute(key));
|
|
30
|
+
if (value === null)
|
|
31
|
+
node.removeAttribute(key);
|
|
32
|
+
else
|
|
33
|
+
node.setAttribute(key, value);
|
|
34
|
+
}
|
|
35
|
+
const cardAt = (target) => {
|
|
36
|
+
const card = target instanceof Element ? target.closest('[data-map-marker]') : null;
|
|
37
|
+
return card && root.contains(card) ? card : null;
|
|
38
|
+
};
|
|
39
|
+
function highlight() {
|
|
40
|
+
ring.hidden = true;
|
|
41
|
+
if (disposed || map.destroyed || !map.overlayElement.getBoundingClientRect().width)
|
|
42
|
+
return;
|
|
43
|
+
const card = focused ?? hovered;
|
|
44
|
+
const marker = card && root.contains(card) ? markers.get(card.dataset.mapMarker ?? '') : undefined;
|
|
45
|
+
if (!marker)
|
|
46
|
+
return;
|
|
47
|
+
const point = map.projectToScreen(marker.lng, marker.lat);
|
|
48
|
+
if (!point.visible)
|
|
49
|
+
return;
|
|
50
|
+
ring.style.left = `${point.x}px`;
|
|
51
|
+
ring.style.top = `${point.y}px`;
|
|
52
|
+
ring.hidden = false;
|
|
53
|
+
}
|
|
54
|
+
function selection(id, reveal) {
|
|
55
|
+
for (const card of root.querySelectorAll('[data-map-marker]')) {
|
|
56
|
+
const selected = id !== null && card.dataset.mapMarker === id;
|
|
57
|
+
attribute(card, 'data-map-selected', selected ? '' : null);
|
|
58
|
+
for (const button of card.querySelectorAll('button[data-map-select]'))
|
|
59
|
+
attribute(button, 'aria-pressed', String(selected));
|
|
60
|
+
if (selected && reveal && !selecting && options.scrollOnSelect !== false)
|
|
61
|
+
card.scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: 'instant' });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const handlers = {
|
|
65
|
+
pointerover: (event) => {
|
|
66
|
+
if (event.pointerType !== 'touch') {
|
|
67
|
+
hovered = cardAt(event.target);
|
|
68
|
+
highlight();
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
pointerout: (event) => {
|
|
72
|
+
hovered = cardAt(event.relatedTarget);
|
|
73
|
+
highlight();
|
|
74
|
+
},
|
|
75
|
+
focusin: (event) => {
|
|
76
|
+
focused = cardAt(event.target);
|
|
77
|
+
highlight();
|
|
78
|
+
},
|
|
79
|
+
focusout: (event) => {
|
|
80
|
+
focused = cardAt(event.relatedTarget);
|
|
81
|
+
highlight();
|
|
82
|
+
},
|
|
83
|
+
click: (event) => {
|
|
84
|
+
if (disposed || map.destroyed)
|
|
85
|
+
return;
|
|
86
|
+
const target = event.target instanceof Element ? event.target.closest('button[data-map-select]') : null;
|
|
87
|
+
const card = cardAt(target);
|
|
88
|
+
const id = card?.dataset.mapMarker;
|
|
89
|
+
if (!target || !card || !id || !markers.has(id) || target.disabled)
|
|
90
|
+
return;
|
|
91
|
+
selecting = true;
|
|
92
|
+
try {
|
|
93
|
+
map.selectMarker(id, { fit: false });
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
selecting = false;
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
for (const [type, listener] of Object.entries(handlers))
|
|
101
|
+
root.addEventListener(type, listener);
|
|
102
|
+
const unsubscribers = [
|
|
103
|
+
map.on('markerchange', ({ id }) => selection(id, true)),
|
|
104
|
+
map.on('viewportchange', highlight),
|
|
105
|
+
map.on('mapresize', highlight),
|
|
106
|
+
];
|
|
107
|
+
selection(map.getSelectedMarker()?.id ?? null, false);
|
|
108
|
+
return {
|
|
109
|
+
/** Call after map.setMarkers() when your place catalog changes. */
|
|
110
|
+
setMarkers(next) {
|
|
111
|
+
if (disposed)
|
|
112
|
+
return;
|
|
113
|
+
validateMarkers(next);
|
|
114
|
+
markers = new Map(next.map((marker) => [marker.id, { ...marker }]));
|
|
115
|
+
if (!map.destroyed)
|
|
116
|
+
selection(map.getSelectedMarker()?.id ?? null, false);
|
|
117
|
+
highlight();
|
|
118
|
+
},
|
|
119
|
+
/** Remove owned listeners/decoration and restore card attributes. Call before map.destroy(). */
|
|
120
|
+
destroy() {
|
|
121
|
+
if (disposed)
|
|
122
|
+
return;
|
|
123
|
+
disposed = true;
|
|
124
|
+
for (const [type, listener] of Object.entries(handlers))
|
|
125
|
+
root.removeEventListener(type, listener);
|
|
126
|
+
for (const unsubscribe of unsubscribers)
|
|
127
|
+
unsubscribe();
|
|
128
|
+
ring.remove();
|
|
129
|
+
for (const [node, saved] of originals)
|
|
130
|
+
for (const [key, value] of saved)
|
|
131
|
+
if (value === null)
|
|
132
|
+
node.removeAttribute(key);
|
|
133
|
+
else
|
|
134
|
+
node.setAttribute(key, value);
|
|
135
|
+
originals.clear();
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { createExplorerShell } from './explorer-shell.js';
|
|
2
|
+
import type { MapViewport, NeighborhoodExplorerElement, NeighborhoodExplorerOptions } from './types.js';
|
|
3
|
+
type Controls = NonNullable<NeighborhoodExplorerOptions['controls']>;
|
|
4
|
+
type Shell = Pick<ReturnType<typeof createExplorerShell>, 'root' | 'toolbar' | 'canvas' | 'hint' | 'status' | 'modeSelect' | 'sourceSelect' | 'search' | 'results' | 'neighborhoodSelect' | 'markerSelect'>;
|
|
5
|
+
type Commands = Pick<NeighborhoodExplorerElement, 'getViewport' | 'zoomBy' | 'resetView' | 'setLabels' | 'stopAnimation' | 'setMode' | 'setSource' | 'selectNeighborhood' | 'selectMarker'> & {
|
|
6
|
+
svg: SVGSVGElement;
|
|
7
|
+
signal: AbortSignal;
|
|
8
|
+
strings: NonNullable<NeighborhoodExplorerOptions['strings']>;
|
|
9
|
+
isDestroyed(): boolean;
|
|
10
|
+
getLabels(): boolean;
|
|
11
|
+
moveView(view: MapViewport): void;
|
|
12
|
+
setView(view: MapViewport): void;
|
|
13
|
+
updateResults(): void;
|
|
14
|
+
search(query: string): string | undefined;
|
|
15
|
+
};
|
|
16
|
+
/** Own toolbar and form events, touch navigation and control visibility.
|
|
17
|
+
* Commands read current map state; all listeners share the map's disposal signal.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createExplorerControls({ root, toolbar, canvas, hint, status, modeSelect, sourceSelect, search, results, neighborhoodSelect, markerSelect, }: Shell, { svg, signal, strings, isDestroyed, getLabels, getViewport, moveView, zoomBy, resetView, setLabels, stopAnimation, setView, setMode, setSource, selectNeighborhood, selectMarker, updateResults, search: findNeighborhood, }: Commands): {
|
|
20
|
+
setTouchNavigation: (enabled: boolean) => void;
|
|
21
|
+
cancelNavigation: () => void;
|
|
22
|
+
update(controls: Controls): void;
|
|
23
|
+
updateLabels(labels: boolean): void;
|
|
24
|
+
updateZoom(zoom: number): void;
|
|
25
|
+
};
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { element } from './dom.js';
|
|
2
|
+
import { attachNavigation } from './navigation.js';
|
|
3
|
+
/** Own toolbar and form events, touch navigation and control visibility.
|
|
4
|
+
* Commands read current map state; all listeners share the map's disposal signal.
|
|
5
|
+
*/
|
|
6
|
+
export function createExplorerControls({ root, toolbar, canvas, hint, status, modeSelect, sourceSelect, search, results, neighborhoodSelect, markerSelect, }, { svg, signal, strings, isDestroyed, getLabels, getViewport, moveView, zoomBy, resetView, setLabels, stopAnimation, setView, setMode, setSource, selectNeighborhood, selectMarker, updateResults, search: findNeighborhood, }) {
|
|
7
|
+
function listen(node, event, handler) {
|
|
8
|
+
node.addEventListener(event, (incoming) => handler(incoming), { signal });
|
|
9
|
+
}
|
|
10
|
+
const zoomControls = element('div', '', 'sf-explorer-control-group');
|
|
11
|
+
const panControls = element('div', '', 'sf-explorer-control-group sf-explorer-pan-controls');
|
|
12
|
+
toolbar.append(zoomControls, panControls);
|
|
13
|
+
function button(text, label, action, group = zoomControls) {
|
|
14
|
+
const node = element('button', text);
|
|
15
|
+
node.type = 'button';
|
|
16
|
+
node.setAttribute('aria-label', label);
|
|
17
|
+
node.addEventListener('click', () => action(), { signal });
|
|
18
|
+
group.append(node);
|
|
19
|
+
return node;
|
|
20
|
+
}
|
|
21
|
+
const zoomOut = button('−', 'Zoom out', () => zoomBy(1 / 1.5));
|
|
22
|
+
const zoomText = element('span', '100%', 'sf-explorer-zoom');
|
|
23
|
+
zoomControls.append(zoomText);
|
|
24
|
+
const zoomIn = button('+', 'Zoom in', () => zoomBy(1.5));
|
|
25
|
+
const resetButton = button(strings.reset ?? 'Reset', strings.reset ?? 'Reset map to city view', resetView);
|
|
26
|
+
const labelsButton = button('Labels', 'Show map labels', () => setLabels(!getLabels()));
|
|
27
|
+
for (const [symbol, label, dx, dy] of [
|
|
28
|
+
['←', 'Pan west', -1, 0],
|
|
29
|
+
['↑', 'Pan north', 0, -1],
|
|
30
|
+
['↓', 'Pan south', 0, 1],
|
|
31
|
+
['→', 'Pan east', 1, 0],
|
|
32
|
+
])
|
|
33
|
+
button(symbol, label, () => {
|
|
34
|
+
const view = getViewport();
|
|
35
|
+
moveView([view[0] + (dx * view[2]) / 4, view[1] + (dy * view[2]) / 4, view[2]]);
|
|
36
|
+
}, panControls);
|
|
37
|
+
const touchLabel = strings.touchNavigation ?? 'Touch navigation';
|
|
38
|
+
const touchButton = button(touchLabel, strings.touchNavigationLabel ?? `Enable ${touchLabel.toLowerCase()}`, () => setTouchNavigation(!touchNavigation));
|
|
39
|
+
let touchNavigation = false;
|
|
40
|
+
const navigation = attachNavigation(svg, getViewport, (next) => {
|
|
41
|
+
stopAnimation();
|
|
42
|
+
setView(next);
|
|
43
|
+
}, signal, () => setTouchNavigation(false));
|
|
44
|
+
function setTouchNavigation(enabled) {
|
|
45
|
+
if (isDestroyed())
|
|
46
|
+
return;
|
|
47
|
+
if (typeof enabled !== 'boolean')
|
|
48
|
+
throw new TypeError('Touch navigation must be a boolean.');
|
|
49
|
+
touchNavigation = enabled;
|
|
50
|
+
navigation.setTouchNavigation(enabled);
|
|
51
|
+
canvas.style.touchAction = enabled ? 'none' : 'pan-y pinch-zoom';
|
|
52
|
+
touchButton.textContent = enabled
|
|
53
|
+
? (strings.touchNavigationDone ?? 'Done: page scrolling')
|
|
54
|
+
: touchLabel;
|
|
55
|
+
touchButton.setAttribute('aria-label', enabled
|
|
56
|
+
? (strings.touchNavigationExitLabel ?? `Exit ${touchLabel.toLowerCase()}`)
|
|
57
|
+
: (strings.touchNavigationLabel ?? `Enable ${touchLabel.toLowerCase()}`));
|
|
58
|
+
touchButton.setAttribute('aria-pressed', String(enabled));
|
|
59
|
+
root.dataset.touchNavigation = String(enabled);
|
|
60
|
+
}
|
|
61
|
+
listen(modeSelect, 'change', () => {
|
|
62
|
+
const next = modeSelect.value;
|
|
63
|
+
if (next === 'neighborhoods' || next === 'districts' || next === 'basemap')
|
|
64
|
+
setMode(next);
|
|
65
|
+
});
|
|
66
|
+
listen(search, 'input', updateResults);
|
|
67
|
+
listen(search, 'keydown', (event) => {
|
|
68
|
+
if (event.key === 'Enter') {
|
|
69
|
+
const match = findNeighborhood(search.value);
|
|
70
|
+
if (match) {
|
|
71
|
+
event.preventDefault();
|
|
72
|
+
selectNeighborhood(match);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
listen(sourceSelect, 'change', () => {
|
|
77
|
+
const next = sourceSelect.value;
|
|
78
|
+
if (next === 'realtor' || next === 'sf-find' || next === 'analysis')
|
|
79
|
+
setSource(next);
|
|
80
|
+
});
|
|
81
|
+
listen(results, 'click', (event) => {
|
|
82
|
+
const node = event.target instanceof Element
|
|
83
|
+
? event.target.closest('[data-neighborhood-result]')
|
|
84
|
+
: null;
|
|
85
|
+
if (node?.dataset.neighborhoodResult)
|
|
86
|
+
selectNeighborhood(node.dataset.neighborhoodResult);
|
|
87
|
+
});
|
|
88
|
+
listen(neighborhoodSelect, 'change', () => selectNeighborhood(neighborhoodSelect.value || null));
|
|
89
|
+
listen(markerSelect, 'change', () => selectMarker(markerSelect.value || null));
|
|
90
|
+
return {
|
|
91
|
+
setTouchNavigation,
|
|
92
|
+
cancelNavigation: navigation.cancel,
|
|
93
|
+
update(controls) {
|
|
94
|
+
for (const node of [zoomIn, zoomOut, zoomText])
|
|
95
|
+
node.hidden = controls.zoom === false;
|
|
96
|
+
panControls.hidden = controls.pan === false;
|
|
97
|
+
resetButton.hidden = controls.reset === false;
|
|
98
|
+
labelsButton.hidden = controls.labels === false;
|
|
99
|
+
touchButton.hidden = controls.touch === false;
|
|
100
|
+
if (touchButton.hidden && touchNavigation)
|
|
101
|
+
setTouchNavigation(false);
|
|
102
|
+
zoomControls.hidden = [...zoomControls.children].every((node) => node.hidden);
|
|
103
|
+
toolbar.hidden = zoomControls.hidden && panControls.hidden;
|
|
104
|
+
hint.hidden = controls.help === false;
|
|
105
|
+
if (hint.hidden)
|
|
106
|
+
svg.setAttribute('aria-describedby', hint.id);
|
|
107
|
+
else
|
|
108
|
+
svg.removeAttribute('aria-describedby');
|
|
109
|
+
status.classList.toggle('sf-explorer-visually-hidden', controls.status === false);
|
|
110
|
+
},
|
|
111
|
+
updateLabels(labels) {
|
|
112
|
+
labelsButton.setAttribute('aria-pressed', String(labels));
|
|
113
|
+
},
|
|
114
|
+
updateZoom(zoom) {
|
|
115
|
+
zoomText.textContent = `${Math.round(zoom * 100)}%`;
|
|
116
|
+
zoomIn.disabled = zoom >= 12;
|
|
117
|
+
zoomOut.disabled = zoom <= 1;
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { NeighborhoodSource } from '../data/types.js';
|
|
2
2
|
import type { MapConfiguration } from './controller-types.js';
|
|
3
3
|
import type { InteractiveSFMapData } from './explorer-data.js';
|
|
4
|
+
import type { createMapShell } from './explorer-shell.js';
|
|
5
|
+
import type { createExplorerSidebar } from './explorer-sidebar.js';
|
|
4
6
|
import type { DistrictYear, ExplorerMode, NeighborhoodExplorerElement, NeighborhoodExplorerOptions } from './types.js';
|
|
5
7
|
export type { ExplorerMode, NeighborhoodExplorerElement, NeighborhoodExplorerOptions, } from './types.js';
|
|
6
8
|
/** Private bridge between the renderer and its public lifecycle controller. */
|
|
@@ -14,4 +16,7 @@ type MapEngineElement = NeighborhoodExplorerElement & {
|
|
|
14
16
|
};
|
|
15
17
|
};
|
|
16
18
|
/** Create an offline, browser-only neighborhood explorer. Call destroy() before disposal. */
|
|
17
|
-
export declare function createNeighborhoodExplorerCore(options: NeighborhoodExplorerOptions | undefined, data: InteractiveSFMapData
|
|
19
|
+
export declare function createNeighborhoodExplorerCore(options: NeighborhoodExplorerOptions | undefined, data: InteractiveSFMapData, ui: {
|
|
20
|
+
createShell: typeof createMapShell;
|
|
21
|
+
createSidebar?: typeof createExplorerSidebar;
|
|
22
|
+
}): MapEngineElement;
|