@kahwee/sf-map-svg 5.1.3 → 5.1.4

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/src/cluster-renderer.d.ts +1 -1
  3. package/dist/src/district-layer.d.ts +1 -1
  4. package/dist/src/district-layer.js +1 -1
  5. package/dist/src/district-style.d.ts +1 -1
  6. package/dist/src/explorer-data.d.ts +1 -1
  7. package/dist/src/guide-map.js +3 -3
  8. package/dist/src/{configuration-controller.d.ts → interactive/configuration-controller.d.ts} +7 -7
  9. package/dist/src/{configuration-controller.js → interactive/configuration-controller.js} +2 -2
  10. package/dist/src/{district-controller.d.ts → interactive/district-controller.d.ts} +12 -12
  11. package/dist/src/{district-controller.js → interactive/district-controller.js} +9 -9
  12. package/dist/src/{explorer-sidebar.d.ts → interactive/explorer-sidebar.d.ts} +6 -6
  13. package/dist/src/{explorer-sidebar.js → interactive/explorer-sidebar.js} +4 -4
  14. package/dist/src/{label-coordinator.d.ts → interactive/label-coordinator.d.ts} +7 -7
  15. package/dist/src/{label-coordinator.js → interactive/label-coordinator.js} +2 -2
  16. package/dist/src/{label-renderer.d.ts → interactive/label-renderer.d.ts} +2 -2
  17. package/dist/src/{label-renderer.js → interactive/label-renderer.js} +2 -2
  18. package/dist/src/{explorer-controls.d.ts → interactive/map-controls.d.ts} +4 -4
  19. package/dist/src/{explorer-controls.js → interactive/map-controls.js} +4 -4
  20. package/dist/src/{explorer-core.d.ts → interactive/map-engine.d.ts} +7 -7
  21. package/dist/src/{explorer-core.js → interactive/map-engine.js} +40 -40
  22. package/dist/src/{explorer-shell.d.ts → interactive/map-shell.d.ts} +2 -2
  23. package/dist/src/{explorer-shell.js → interactive/map-shell.js} +2 -2
  24. package/dist/src/{marker-controller.d.ts → interactive/marker-controller.d.ts} +8 -8
  25. package/dist/src/{marker-controller.js → interactive/marker-controller.js} +3 -3
  26. package/dist/src/{marker-layer.d.ts → interactive/marker-renderer.d.ts} +3 -3
  27. package/dist/src/{marker-layer.js → interactive/marker-renderer.js} +2 -2
  28. package/dist/src/map.js +4 -4
  29. package/dist/src/marker-catalog.d.ts +2 -2
  30. package/dist/src/{layers.d.ts → rendering/svg-layers.d.ts} +5 -5
  31. package/dist/src/{layers.js → rendering/svg-layers.js} +1 -1
  32. package/dist/src/{map-core.d.ts → rendering/svg-map-renderer.d.ts} +4 -4
  33. package/dist/src/{map-core.js → rendering/svg-map-renderer.js} +6 -6
  34. package/dist/src/static-labels.d.ts +1 -1
  35. package/dist/src/static-labels.js +1 -1
  36. package/dist/src/static.d.ts +2 -2
  37. package/dist/src/static.js +1 -1
  38. package/dist/src/transit.js +1 -1
  39. package/dist/src/types.d.ts +2 -2
  40. package/llms-full.txt +25 -21
  41. package/llms.txt +1 -1
  42. package/package.json +1 -1
  43. /package/dist/src/{svg.d.ts → rendering/svg-primitives.d.ts} +0 -0
  44. /package/dist/src/{svg.js → rendering/svg-primitives.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,10 @@ User-visible changes are recorded here. Unreleased entries describe changes on `
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 5.1.4 — 2026-11-05
8
+
9
+ - Group internal browser controllers, renderers and shells under `src/interactive`, and SVG generation under `src/rendering`. Rename the general map engine, controls and marker renderer to match their responsibilities; clarify selection-detail, neighborhood-results and layer-composition operations. Keep public package entry points unchanged.
10
+
7
11
  ## 5.1.3 — 2026-11-05
8
12
 
9
13
  - Give configuration snapshots and revisions a dedicated internal owner. Split presentation preparation from a guarded, single-use commit, rejecting stale or disposed updates before mutating presentation. Preserve public configuration and reset behavior.
@@ -1,5 +1,5 @@
1
1
  import type { normalizeFeatures } from './features.js';
2
- import type { MarkerItem, MarkerVisual } from './marker-layer.js';
2
+ import type { MarkerItem, MarkerVisual } from './interactive/marker-renderer.js';
3
3
  import type { createMarkerNavigation } from './marker-navigation.js';
4
4
  import type { MapViewport } from './types.js';
5
5
  interface ClusterState {
@@ -1,4 +1,4 @@
1
- import type { DistrictRowData } from './map-core.js';
1
+ import type { DistrictRowData } from './rendering/svg-map-renderer.js';
2
2
  import type { DistrictStyle, DistrictYear, SFMapOptions } from './types.js';
3
3
  /** Apply prepared styles and interaction state without invoking consumer callbacks. */
4
4
  export declare function renderDistrictAppearance({ svg, rows, styles, selectedDistrict, hoveredDistrict, year, theme, colors, fills, lines, }: {
@@ -1,4 +1,4 @@
1
- import { districtColors } from './map-core.js';
1
+ import { districtColors } from './rendering/svg-map-renderer.js';
2
2
  /** Apply prepared styles and interaction state without invoking consumer callbacks. */
3
3
  export function renderDistrictAppearance({ svg, rows, styles, selectedDistrict, hoveredDistrict, year, theme, colors, fills, lines, }) {
4
4
  for (const row of rows) {
@@ -1,4 +1,4 @@
1
- import type { DistrictRowData } from './map-core.js';
1
+ import type { DistrictRowData } from './rendering/svg-map-renderer.js';
2
2
  import type { DistrictStyle, SFMapOptions } from './types.js';
3
3
  /** Evaluate user code once and copy its result before changing the map. */
4
4
  export declare function prepareDistrictStyles(rows: readonly DistrictRowData[], callback: SFMapOptions['districtStyle']): ReadonlyMap<number, DistrictStyle>;
@@ -1,5 +1,5 @@
1
1
  import type { DistrictProperties, FeatureCollection, NeighborhoodProperties, NeighborhoodSource } from '../data/types.js';
2
- import type { SFMapData } from './map-core.js';
2
+ import type { SFMapData } from './rendering/svg-map-renderer.js';
3
3
  import type { DistrictYear } from './types.js';
4
4
  /** Geographic inputs for the data-injected interactive map entry point. */
5
5
  export interface InteractiveSFMapData {
@@ -1,7 +1,7 @@
1
1
  import { expandMapOptions, prepareConfiguration } from './configuration.js';
2
- import { createNeighborhoodExplorerCore } from './explorer-core.js';
3
- import { createMapShell } from './explorer-shell.js';
4
2
  import { guideMapData } from './guide-data.js';
3
+ import { createMapEngine } from './interactive/map-engine.js';
4
+ import { createMapShell } from './interactive/map-shell.js';
5
5
  import { createMap } from './map.js';
6
6
  import { guideOptions } from './presets.js';
7
7
  import { validateExplorerOptions } from './validation.js';
@@ -62,7 +62,7 @@ function mountFrame(frame, map) {
62
62
  /** Element-based compatibility API. Prefer createGuideController for new integrations. */
63
63
  export function createGuideMap(options = {}) {
64
64
  validateExplorerOptions(options);
65
- return createNeighborhoodExplorerCore({
65
+ return createMapEngine({
66
66
  mode: 'neighborhoods',
67
67
  ...options,
68
68
  layers: {
@@ -1,6 +1,6 @@
1
- import { prepareConfiguration } from './configuration.js';
2
- import type { MapConfiguration } from './controller-types.js';
3
- import type { NeighborhoodExplorerElement } from './types.js';
1
+ import { prepareConfiguration } from '../configuration.js';
2
+ import type { MapConfiguration } from '../controller-types.js';
3
+ import type { NeighborhoodExplorerElement } from '../types.js';
4
4
  /** A prepared engine update may commit once, only while all revisions remain current. */
5
5
  export interface PreparedPresentation {
6
6
  commit(onCommit: () => void, isCurrent: () => boolean): boolean;
@@ -12,10 +12,10 @@ export interface ConfigurationTarget extends Pick<NeighborhoodExplorerElement, '
12
12
  export declare function createConfigurationController(initial: ReturnType<typeof prepareConfiguration>, element: ConfigurationTarget, isDestroyed: () => boolean): {
13
13
  configure: (patch: MapConfiguration) => boolean;
14
14
  readonly current: {
15
- features: import("./types.js").MapFeatures;
16
- layers: import("./types.js").InteractiveLayers;
17
- controls: import("./controller-types.js").MapControls;
18
- appearance: import("./controller-types.js").MapAppearance;
15
+ features: import("../types.js").MapFeatures;
16
+ layers: import("../types.js").InteractiveLayers;
17
+ controls: import("../controller-types.js").MapControls;
18
+ appearance: import("../controller-types.js").MapAppearance;
19
19
  };
20
20
  invalidate(): void;
21
21
  };
@@ -1,5 +1,5 @@
1
- import { prepareConfiguration } from './configuration.js';
2
- import { controlKeys, layerKeys } from './features.js';
1
+ import { prepareConfiguration } from '../configuration.js';
2
+ import { controlKeys, layerKeys } from '../features.js';
3
3
  /** Own the public configuration snapshot and invalidate stale updates before DOM mutation. */
4
4
  export function createConfigurationController(initial, element, isDestroyed) {
5
5
  let config = initial;
@@ -1,9 +1,9 @@
1
- import type { Geometry, Position } from '../data/types.js';
2
- import { prepareDistrictStyles } from './district-style.js';
3
- import type { InteractiveSFMapData } from './explorer-data.js';
4
- import type { normalizeFeatures } from './features.js';
5
- import type { LabelItem } from './label-candidates.js';
6
- import type { CameraOptions, DistrictSelection, DistrictYear, NeighborhoodExplorerOptions } from './types.js';
1
+ import type { Geometry, Position } from '../../data/types.js';
2
+ import { prepareDistrictStyles } from '../district-style.js';
3
+ import type { InteractiveSFMapData } from '../explorer-data.js';
4
+ import type { normalizeFeatures } from '../features.js';
5
+ import type { LabelItem } from '../label-candidates.js';
6
+ import type { CameraOptions, DistrictSelection, DistrictYear, NeighborhoodExplorerOptions } from '../types.js';
7
7
  type Host = {
8
8
  root: HTMLElement;
9
9
  svg: SVGSVGElement;
@@ -23,12 +23,12 @@ type Host = {
23
23
  fitGeometry(geometry: Geometry, padding: undefined, options: CameraOptions): void;
24
24
  project(point: Position): [number, number];
25
25
  setLabels(items: LabelItem[]): void;
26
- updateComposition(): void;
26
+ syncLayerComposition(): void;
27
27
  scheduleLabels(): void;
28
28
  };
29
29
  /** Owns district selection, styles, revisions and cancellable motion. */
30
30
  export declare function createDistrictController(host: Host, initialDistrictStyle: NeighborhoodExplorerOptions['districtStyle']): {
31
- districtRow: (id: number) => import("./map-core.js").DistrictRowData | undefined;
31
+ districtRow: (id: number) => import("../rendering/svg-map-renderer.js").DistrictRowData | undefined;
32
32
  districtSelection: (id: number | null) => DistrictSelection | {
33
33
  id: null;
34
34
  year: DistrictYear;
@@ -40,7 +40,7 @@ export declare function createDistrictController(host: Host, initialDistrictStyl
40
40
  fit?: boolean;
41
41
  } & CameraOptions) => boolean;
42
42
  activateDistrict: (id: number) => boolean;
43
- setDistrictStyle: (style: ((district: import('./map-core.js').DistrictRowData) => import("./types.js").DistrictStyle) | undefined) => void;
43
+ setDistrictStyle: (style: ((district: import("../rendering/svg-map-renderer.js").DistrictRowData) => import("../types.js").DistrictStyle) | undefined) => void;
44
44
  setDistrictYear: (next: DistrictYear, options?: CameraOptions, preparedStyles?: ReturnType<typeof prepareDistrictStyles>) => void;
45
45
  transition: {
46
46
  cancel(): void;
@@ -56,9 +56,9 @@ export declare function createDistrictController(host: Host, initialDistrictStyl
56
56
  };
57
57
  readonly revision: number;
58
58
  invalidate(): void;
59
- readonly style: ((district: import('./map-core.js').DistrictRowData) => import("./types.js").DistrictStyle) | undefined;
60
- readonly styles: ReadonlyMap<number, import("./types.js").DistrictStyle>;
61
- commitStyles(style: ((district: import('./map-core.js').DistrictRowData) => import("./types.js").DistrictStyle) | undefined, styles: ReadonlyMap<number, import("./types.js").DistrictStyle>): void;
59
+ readonly style: ((district: import("../rendering/svg-map-renderer.js").DistrictRowData) => import("../types.js").DistrictStyle) | undefined;
60
+ readonly styles: ReadonlyMap<number, import("../types.js").DistrictStyle>;
61
+ commitStyles(style: ((district: import("../rendering/svg-map-renderer.js").DistrictRowData) => import("../types.js").DistrictStyle) | undefined, styles: ReadonlyMap<number, import("../types.js").DistrictStyle>): void;
62
62
  hovered: number | null;
63
63
  };
64
64
  export {};
@@ -1,13 +1,13 @@
1
- import { validateCameraOptions } from './camera.js';
2
- import { renderDistrictAppearance } from './district-layer.js';
3
- import { createDistrictMorph } from './district-morph.js';
4
- import { prepareDistrictStyles } from './district-style.js';
5
- import { createDistrictTransition } from './district-transition.js';
6
- import { svgElement } from './dom.js';
7
- import { getLayerPathsWithData } from './map-core.js';
1
+ import { validateCameraOptions } from '../camera.js';
2
+ import { renderDistrictAppearance } from '../district-layer.js';
3
+ import { createDistrictMorph } from '../district-morph.js';
4
+ import { prepareDistrictStyles } from '../district-style.js';
5
+ import { createDistrictTransition } from '../district-transition.js';
6
+ import { svgElement } from '../dom.js';
7
+ import { getLayerPathsWithData } from '../rendering/svg-map-renderer.js';
8
8
  /** Owns district selection, styles, revisions and cancellable motion. */
9
9
  export function createDistrictController(host, initialDistrictStyle) {
10
- const { root, svg, status, data, districtLayers, reducedMotion, enabled, fitGeometry, updateComposition, scheduleLabels, } = host;
10
+ const { root, svg, status, data, districtLayers, reducedMotion, enabled, fitGeometry, syncLayerComposition, scheduleLabels, } = host;
11
11
  const districtMorph = createDistrictMorph();
12
12
  let selectedDistrict = null;
13
13
  let hoveredDistrict = null;
@@ -160,7 +160,7 @@ export function createDistrictController(host, initialDistrictStyle) {
160
160
  if (selectedDistrict === null)
161
161
  delete root.dataset.selectedDistrict;
162
162
  updateDistrictAppearance();
163
- updateComposition();
163
+ syncLayerComposition();
164
164
  if (morph && lineLayer && previousOutlines)
165
165
  districtMorph.start(lineLayer, previousOutlines, {
166
166
  duration: morph.duration,
@@ -1,7 +1,7 @@
1
- import type { NeighborhoodFeature, NeighborhoodSource } from '../data/types.js';
2
- import type { InteractiveSFMapData } from './explorer-data.js';
3
- import type { createExplorerShell } from './explorer-shell.js';
4
- import type { DistrictYear, ExplorerMode } from './types.js';
1
+ import type { NeighborhoodFeature, NeighborhoodSource } from '../../data/types.js';
2
+ import type { InteractiveSFMapData } from '../explorer-data.js';
3
+ import type { DistrictYear, ExplorerMode } from '../types.js';
4
+ import type { createExplorerShell } from './map-shell.js';
5
5
  interface SidebarState {
6
6
  mode: ExplorerMode;
7
7
  year: DistrictYear;
@@ -13,8 +13,8 @@ export declare function createExplorerSidebar({ detail, results, search, count,
13
13
  id: string;
14
14
  canonicalName: string;
15
15
  }[]): {
16
- updateDetail: ({ mode, year, source, selected }: SidebarState) => void;
17
- updateResults: ({ source, selected }: SidebarState) => void;
16
+ updateSelectionDetail: ({ mode, year, source, selected }: SidebarState) => void;
17
+ updateNeighborhoodResults: ({ source, selected }: SidebarState) => void;
18
18
  destroy: () => void;
19
19
  };
20
20
  export {};
@@ -1,4 +1,4 @@
1
- import { element } from './dom.js';
1
+ import { element } from '../dom.js';
2
2
  /** Render sidebar snapshots and own every download URL until replacement or disposal. */
3
3
  export function createExplorerSidebar({ detail, results, search, count, }, data, chrome, emptyResults, searchNeighborhoods) {
4
4
  const downloads = new Set();
@@ -15,7 +15,7 @@ export function createExplorerSidebar({ detail, results, search, count, }, data,
15
15
  link.download = filename;
16
16
  return link;
17
17
  }
18
- function updateDetail({ mode, year, source, selected }) {
18
+ function updateSelectionDetail({ mode, year, source, selected }) {
19
19
  releaseDownloads();
20
20
  if (chrome === 'map')
21
21
  return;
@@ -53,7 +53,7 @@ export function createExplorerSidebar({ detail, results, search, count, }, data,
53
53
  provenance.append(element('summary', 'About these boundaries'), element('p', collection.definition.description));
54
54
  detail.append(provenance);
55
55
  }
56
- function updateResults({ source, selected }) {
56
+ function updateNeighborhoodResults({ source, selected }) {
57
57
  if (chrome === 'map')
58
58
  return;
59
59
  const activeElement = document.activeElement;
@@ -75,5 +75,5 @@ export function createExplorerSidebar({ detail, results, search, count, }, data,
75
75
  if (!matches.length)
76
76
  results.append(element('p', emptyResults ?? 'No matches. Try another name or definition source.', 'sf-explorer-note'));
77
77
  }
78
- return { updateDetail, updateResults, destroy: releaseDownloads };
78
+ return { updateSelectionDetail, updateNeighborhoodResults, destroy: releaseDownloads };
79
79
  }
@@ -1,11 +1,11 @@
1
- import type { View } from './explorer-layout.js';
2
- import type { normalizeFeatures } from './features.js';
3
- import { type LabelItem } from './label-candidates.js';
4
- import type { createMarkerCatalog } from './marker-catalog.js';
1
+ import type { View } from '../explorer-layout.js';
2
+ import type { normalizeFeatures } from '../features.js';
3
+ import { type LabelItem } from '../label-candidates.js';
4
+ import type { createMarkerCatalog } from '../marker-catalog.js';
5
+ import type { createNeighborhoodLayer } from '../neighborhood-layer.js';
6
+ import type { ExplorerMode, InteractiveLayers, NeighborhoodExplorerOptions } from '../types.js';
5
7
  import type { createMarkerController } from './marker-controller.js';
6
- import type { MarkerItem, MarkerVisual } from './marker-layer.js';
7
- import type { createNeighborhoodLayer } from './neighborhood-layer.js';
8
- import type { ExplorerMode, InteractiveLayers, NeighborhoodExplorerOptions } from './types.js';
8
+ import type { MarkerItem, MarkerVisual } from './marker-renderer.js';
9
9
  interface LabelHost {
10
10
  root: HTMLElement;
11
11
  canvas: HTMLElement;
@@ -1,5 +1,5 @@
1
- import { setAttributeIfChanged } from './dom.js';
2
- import { labelCandidates, styleLabels, } from './label-candidates.js';
1
+ import { setAttributeIfChanged } from '../dom.js';
2
+ import { labelCandidates, styleLabels, } from '../label-candidates.js';
3
3
  import { createLabelRenderer } from './label-renderer.js';
4
4
  /** Own candidate caching, screen-space collision layout and label metrics invalidation. */
5
5
  export function createLabelCoordinator(host) {
@@ -1,5 +1,5 @@
1
- import type { Bounds } from '../data/types.js';
2
- import type { RenderLabel } from './label-candidates.js';
1
+ import type { Bounds } from '../../data/types.js';
2
+ import type { RenderLabel } from '../label-candidates.js';
3
3
  /** Reuse text nodes and screen-space metrics throughout a camera animation. */
4
4
  export declare function createLabelRenderer(layer: SVGGElement): {
5
5
  invalidateMetrics(): void;
@@ -1,5 +1,5 @@
1
- import { setAttributeIfChanged } from './dom.js';
2
- import { layoutLabels } from './explorer-layout.js';
1
+ import { setAttributeIfChanged } from '../dom.js';
2
+ import { layoutLabels } from '../explorer-layout.js';
3
3
  /** Reuse text nodes and screen-space metrics throughout a camera animation. */
4
4
  export function createLabelRenderer(layer) {
5
5
  const entries = new Map();
@@ -1,5 +1,5 @@
1
- import type { createExplorerShell } from './explorer-shell.js';
2
- import type { MapViewport, NeighborhoodExplorerElement, NeighborhoodExplorerOptions } from './types.js';
1
+ import type { MapViewport, NeighborhoodExplorerElement, NeighborhoodExplorerOptions } from '../types.js';
2
+ import type { createExplorerShell } from './map-shell.js';
3
3
  type Controls = NonNullable<NeighborhoodExplorerOptions['controls']>;
4
4
  type Shell = Pick<ReturnType<typeof createExplorerShell>, 'root' | 'toolbar' | 'canvas' | 'hint' | 'status' | 'modeSelect' | 'sourceSelect' | 'search' | 'results' | 'neighborhoodSelect' | 'markerSelect'>;
5
5
  type Commands = Pick<NeighborhoodExplorerElement, 'getViewport' | 'zoomBy' | 'resetView' | 'setLabels' | 'stopAnimation' | 'setMode' | 'setSource' | 'selectNeighborhood' | 'selectMarker'> & {
@@ -10,13 +10,13 @@ type Commands = Pick<NeighborhoodExplorerElement, 'getViewport' | 'zoomBy' | 're
10
10
  getLabels(): boolean;
11
11
  moveView(view: MapViewport): void;
12
12
  setView(view: MapViewport): void;
13
- updateResults(): void;
13
+ updateNeighborhoodResults(): void;
14
14
  search(query: string): string | undefined;
15
15
  };
16
16
  /** Own toolbar and form events, touch navigation and control visibility.
17
17
  * Commands read current map state; all listeners share the map's disposal signal.
18
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): {
19
+ export declare function createMapControls({ 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, updateNeighborhoodResults, search: findNeighborhood, }: Commands): {
20
20
  setTouchNavigation: (enabled: boolean) => void;
21
21
  cancelNavigation: () => void;
22
22
  update(controls: Controls): void;
@@ -1,9 +1,9 @@
1
- import { element } from './dom.js';
2
- import { attachNavigation } from './navigation.js';
1
+ import { element } from '../dom.js';
2
+ import { attachNavigation } from '../navigation.js';
3
3
  /** Own toolbar and form events, touch navigation and control visibility.
4
4
  * Commands read current map state; all listeners share the map's disposal signal.
5
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, }) {
6
+ export function createMapControls({ 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, updateNeighborhoodResults, search: findNeighborhood, }) {
7
7
  function listen(node, event, handler) {
8
8
  node.addEventListener(event, (incoming) => handler(incoming), { signal });
9
9
  }
@@ -63,7 +63,7 @@ export function createExplorerControls({ root, toolbar, canvas, hint, status, mo
63
63
  if (next === 'neighborhoods' || next === 'districts' || next === 'basemap')
64
64
  setMode(next);
65
65
  });
66
- listen(search, 'input', updateResults);
66
+ listen(search, 'input', updateNeighborhoodResults);
67
67
  listen(search, 'keydown', (event) => {
68
68
  if (event.key === 'Enter') {
69
69
  const match = findNeighborhood(search.value);
@@ -1,11 +1,11 @@
1
- import type { NeighborhoodSource } from '../data/types.js';
1
+ import type { NeighborhoodSource } from '../../data/types.js';
2
+ import type { MapConfiguration } from '../controller-types.js';
3
+ import type { InteractiveSFMapData } from '../explorer-data.js';
4
+ import type { DistrictYear, ExplorerMode, NeighborhoodExplorerElement, NeighborhoodExplorerOptions } from '../types.js';
2
5
  import type { PreparedPresentation } from './configuration-controller.js';
3
- import type { MapConfiguration } from './controller-types.js';
4
- import type { InteractiveSFMapData } from './explorer-data.js';
5
- import type { createMapShell } from './explorer-shell.js';
6
6
  import type { createExplorerSidebar } from './explorer-sidebar.js';
7
- import type { DistrictYear, ExplorerMode, NeighborhoodExplorerElement, NeighborhoodExplorerOptions } from './types.js';
8
- export type { ExplorerMode, NeighborhoodExplorerElement, NeighborhoodExplorerOptions, } from './types.js';
7
+ import type { createMapShell } from './map-shell.js';
8
+ export type { ExplorerMode, NeighborhoodExplorerElement, NeighborhoodExplorerOptions, } from '../types.js';
9
9
  /** Private bridge between the renderer and its public lifecycle controller. */
10
10
  type MapEngineElement = NeighborhoodExplorerElement & {
11
11
  preparePresentation(patch: MapConfiguration): PreparedPresentation | undefined;
@@ -17,7 +17,7 @@ type MapEngineElement = NeighborhoodExplorerElement & {
17
17
  };
18
18
  };
19
19
  /** Create an offline, browser-only neighborhood explorer. Call destroy() before disposal. */
20
- export declare function createNeighborhoodExplorerCore(options: NeighborhoodExplorerOptions | undefined, data: InteractiveSFMapData, ui: {
20
+ export declare function createMapEngine(options: NeighborhoodExplorerOptions | undefined, data: InteractiveSFMapData, ui: {
21
21
  createShell: typeof createMapShell;
22
22
  createSidebar?: typeof createExplorerSidebar;
23
23
  }): MapEngineElement;
@@ -1,28 +1,28 @@
1
- import { copyAppearance } from './appearance.js';
2
- import { createCamera, validateCameraOptions } from './camera.js';
3
- import { createClusterLayer } from './cluster-layer.js';
4
- import { clusterPoints } from './clusters.js';
1
+ import { copyAppearance } from '../appearance.js';
2
+ import { createCamera, validateCameraOptions } from '../camera.js';
3
+ import { createClusterLayer } from '../cluster-layer.js';
4
+ import { clusterPoints } from '../clusters.js';
5
+ import { prepareDistrictStyles } from '../district-style.js';
6
+ import { element, svgElement } from '../dom.js';
7
+ import { fitBounds, projectedBounds } from '../explorer-layout.js';
8
+ import { controlKeys, layerKeys, normalizeFeatures, validateSwitchPatch } from '../features.js';
9
+ import { createFrameScheduler } from '../frame-scheduler.js';
10
+ import { createLayerTransitions } from '../layer-transition.js';
11
+ import { createMarkerCatalog } from '../marker-catalog.js';
12
+ import { createMarkerChooser } from '../marker-chooser.js';
13
+ import { createMarkerNavigation } from '../marker-navigation.js';
14
+ import { createNeighborhoodLayer, createNeighborhoodLookup, formatSourceLabel, } from '../neighborhood-layer.js';
15
+ import { createOverlayLayer } from '../overlay-layer.js';
16
+ import { createSFMapWithData, districtColors, getLayerPathsWithData, resolveMapColors, } from '../rendering/svg-map-renderer.js';
17
+ import { validateExplorerOptions } from '../validation.js';
18
+ import { fitViewport, validateViewport } from '../viewport.js';
5
19
  import { createDistrictController } from './district-controller.js';
6
- import { prepareDistrictStyles } from './district-style.js';
7
- import { element, svgElement } from './dom.js';
8
- import { createExplorerControls } from './explorer-controls.js';
9
- import { fitBounds, projectedBounds } from './explorer-layout.js';
10
- import { controlKeys, layerKeys, normalizeFeatures, validateSwitchPatch } from './features.js';
11
- import { createFrameScheduler } from './frame-scheduler.js';
12
20
  import { createLabelCoordinator } from './label-coordinator.js';
13
- import { createLayerTransitions } from './layer-transition.js';
14
- import { createSFMapWithData, districtColors, getLayerPathsWithData, resolveMapColors, } from './map-core.js';
15
- import { createMarkerCatalog } from './marker-catalog.js';
16
- import { createMarkerChooser } from './marker-chooser.js';
21
+ import { createMapControls } from './map-controls.js';
17
22
  import { createMarkerController } from './marker-controller.js';
18
- import { createMarkerLayer } from './marker-layer.js';
19
- import { createMarkerNavigation } from './marker-navigation.js';
20
- import { createNeighborhoodLayer, createNeighborhoodLookup, formatSourceLabel, } from './neighborhood-layer.js';
21
- import { createOverlayLayer } from './overlay-layer.js';
22
- import { validateExplorerOptions } from './validation.js';
23
- import { fitViewport, validateViewport } from './viewport.js';
23
+ import { createMarkerRenderer } from './marker-renderer.js';
24
24
  /** Create an offline, browser-only neighborhood explorer. Call destroy() before disposal. */
25
- export function createNeighborhoodExplorerCore(options = {}, data, ui) {
25
+ export function createMapEngine(options = {}, data, ui) {
26
26
  validateExplorerOptions(options);
27
27
  let { source = 'realtor', mode = 'neighborhoods', labels = true, neighborhood, year = 2022, theme = 'transit', colors = {}, labelStyle = {}, areaStyle = {}, districtStyle: initialDistrictStyle, motion = false, markerEntrance = false, selectedMarkerRing, clustering = false, legend: legendOptions = {}, attribution: attributionMode = 'full', northArrow = false, scaleBar = false, layerTransitions = false, districtMorph: districtMorphOption = false, interface: chrome = 'explorer', layers = {}, selectableNeighborhoods = true, labelSize = {}, fitPadding = 24, markers: initialMarkers = [], markerRadius = 6, markerHitSize = 44, markerColor = colors.marker ?? '#245b61', selectedMarkerColor = colors.selected ?? '#f04f32', onMarkerActivate, overlays: initialOverlays = [], onOverlayActivate, style: styleOptions = {}, strings = {}, controls = {}, } = options;
28
28
  if (!['neighborhoods', 'districts', 'basemap'].includes(mode))
@@ -188,7 +188,7 @@ export function createNeighborhoodExplorerCore(options = {}, data, ui) {
188
188
  reducedMotion,
189
189
  enabled,
190
190
  fitGeometry,
191
- updateComposition,
191
+ syncLayerComposition,
192
192
  scheduleLabels,
193
193
  get year() {
194
194
  return year;
@@ -229,7 +229,7 @@ export function createNeighborhoodExplorerCore(options = {}, data, ui) {
229
229
  cancel: (id) => cancelAnimationFrame(id),
230
230
  render: drawLabels,
231
231
  });
232
- const markerRenderer = createMarkerLayer();
232
+ const markerRenderer = createMarkerRenderer();
233
233
  const cancelEntrances = markerRenderer.cancelEntrances;
234
234
  const camera = createCamera({
235
235
  read: getViewport,
@@ -271,13 +271,13 @@ export function createNeighborhoodExplorerCore(options = {}, data, ui) {
271
271
  refresh: {
272
272
  selection() {
273
273
  scheduleLabels();
274
- updateResults();
275
- updateDetail();
274
+ updateNeighborhoodResults();
275
+ updateSelectionDetail();
276
276
  },
277
277
  source() {
278
- updateResults();
279
- updateDetail();
280
- updateComposition();
278
+ updateNeighborhoodResults();
279
+ updateSelectionDetail();
280
+ syncLayerComposition();
281
281
  },
282
282
  },
283
283
  moveView,
@@ -466,7 +466,7 @@ export function createNeighborhoodExplorerCore(options = {}, data, ui) {
466
466
  isDestroyed: () => destroyed,
467
467
  });
468
468
  const setOverlays = overlays.set;
469
- const mapControls = createExplorerControls(shell, {
469
+ const mapControls = createMapControls(shell, {
470
470
  svg,
471
471
  signal: controller.signal,
472
472
  strings,
@@ -483,7 +483,7 @@ export function createNeighborhoodExplorerCore(options = {}, data, ui) {
483
483
  setSource,
484
484
  selectNeighborhood,
485
485
  selectMarker,
486
- updateResults,
486
+ updateNeighborhoodResults,
487
487
  search(query) {
488
488
  const exact = getNeighborhood(query, neighborhoods.source);
489
489
  const matches = searchNeighborhoods(query, neighborhoods.source);
@@ -604,16 +604,16 @@ export function createNeighborhoodExplorerCore(options = {}, data, ui) {
604
604
  labelCoordinator.draw();
605
605
  }
606
606
  const sidebar = ui.createSidebar?.(shell, data, chrome, strings.emptyResults, searchNeighborhoods);
607
- function updateDetail() {
608
- sidebar?.updateDetail({
607
+ function updateSelectionDetail() {
608
+ sidebar?.updateSelectionDetail({
609
609
  mode,
610
610
  year,
611
611
  source: neighborhoods.source,
612
612
  selected: neighborhoods.selected,
613
613
  });
614
614
  }
615
- function updateResults() {
616
- sidebar?.updateResults({
615
+ function updateNeighborhoodResults() {
616
+ sidebar?.updateNeighborhoodResults({
617
617
  mode,
618
618
  year,
619
619
  source: neighborhoods.source,
@@ -629,9 +629,9 @@ export function createNeighborhoodExplorerCore(options = {}, data, ui) {
629
629
  validateSwitchPatch(patch, layerKeys);
630
630
  const next = { ...layers, ...patch };
631
631
  layers = next;
632
- updateComposition();
632
+ syncLayerComposition();
633
633
  }
634
- function updateComposition() {
634
+ function syncLayerComposition() {
635
635
  for (const [name, key] of [
636
636
  ['landmarks', 'landmarks'],
637
637
  ['highways', 'highways'],
@@ -715,7 +715,7 @@ export function createNeighborhoodExplorerCore(options = {}, data, ui) {
715
715
  controls = next;
716
716
  mapControls.update(controls);
717
717
  markerLabel.hidden = !markerCatalog.items.length || controls.markerPicker === false;
718
- updateComposition();
718
+ syncLayerComposition();
719
719
  }
720
720
  function setMode(next, reset = true) {
721
721
  if (destroyed)
@@ -734,8 +734,8 @@ export function createNeighborhoodExplorerCore(options = {}, data, ui) {
734
734
  const districts = mode !== 'neighborhoods';
735
735
  for (const node of [sourceLabel, searchLabel, count, results])
736
736
  node.hidden = districts;
737
- updateComposition();
738
- updateDetail();
737
+ syncLayerComposition();
738
+ updateSelectionDetail();
739
739
  status.textContent =
740
740
  mode === 'basemap'
741
741
  ? 'San Francisco basemap.'
@@ -755,7 +755,7 @@ export function createNeighborhoodExplorerCore(options = {}, data, ui) {
755
755
  labels = visible;
756
756
  mapControls.updateLabels(labels);
757
757
  root.dataset.labels = String(labels);
758
- updateComposition();
758
+ syncLayerComposition();
759
759
  if (!labels) {
760
760
  labelLayer.replaceChildren();
761
761
  root.dataset.visibleLabels = '0';
@@ -1,5 +1,5 @@
1
- import type { NeighborhoodSource } from '../data/types.js';
2
- import type { NeighborhoodExplorerOptions } from './types.js';
1
+ import type { NeighborhoodSource } from '../../data/types.js';
2
+ import type { NeighborhoodExplorerOptions } from '../types.js';
3
3
  /** Construct shared map chrome; optional explorer content has its own factory below. */
4
4
  export declare function createMapShell({ chrome, styleOptions, strings, legendOptions, colors, attributionMode, }: {
5
5
  source: NeighborhoodSource;
@@ -1,5 +1,5 @@
1
- import { element } from './dom.js';
2
- import { sourceNames } from './neighborhood-layer.js';
1
+ import { element } from '../dom.js';
2
+ import { sourceNames } from '../neighborhood-layer.js';
3
3
  let explorerCount = 0;
4
4
  /** Construct shared map chrome; optional explorer content has its own factory below. */
5
5
  export function createMapShell({ chrome, styleOptions, strings, legendOptions, colors, attributionMode, }) {
@@ -1,11 +1,11 @@
1
- import type { Position } from '../data/types.js';
2
- import { type View } from './explorer-layout.js';
3
- import type { normalizeFeatures } from './features.js';
4
- import type { createMarkerCatalog } from './marker-catalog.js';
5
- import type { createMarkerChooser } from './marker-chooser.js';
6
- import type { MarkerItem, MarkerVisual } from './marker-layer.js';
7
- import type { createMarkerNavigation } from './marker-navigation.js';
8
- import type { CameraOptions, MapMarker, NeighborhoodExplorerOptions } from './types.js';
1
+ import type { Position } from '../../data/types.js';
2
+ import { type View } from '../explorer-layout.js';
3
+ import type { normalizeFeatures } from '../features.js';
4
+ import type { createMarkerCatalog } from '../marker-catalog.js';
5
+ import type { createMarkerChooser } from '../marker-chooser.js';
6
+ import type { createMarkerNavigation } from '../marker-navigation.js';
7
+ import type { CameraOptions, MapMarker, NeighborhoodExplorerOptions } from '../types.js';
8
+ import type { MarkerItem, MarkerVisual } from './marker-renderer.js';
9
9
  interface MarkerHost {
10
10
  root: HTMLElement;
11
11
  canvas: HTMLElement;
@@ -1,6 +1,6 @@
1
- import { validateCameraOptions } from './camera.js';
2
- import { fitBounds } from './explorer-layout.js';
3
- import { fitViewport } from './viewport.js';
1
+ import { validateCameraOptions } from '../camera.js';
2
+ import { fitBounds } from '../explorer-layout.js';
3
+ import { fitViewport } from '../viewport.js';
4
4
  /** Own selection revisions, activation and focus recovery independently of marker rendering. */
5
5
  export function createMarkerController(host) {
6
6
  const { root, canvas, svg, markerLayer, markerSelect, markerLabel, markerCatalog, markerNavigation, strings, fitPadding, onMarkerActivate, invalidateClusters, drawClusters, materializeMarker, scheduleLabels, syncFeatureControls, moveView, } = host;
@@ -1,5 +1,5 @@
1
- import type { normalizeFeatures } from './features.js';
2
- import type { MapMarker } from './types.js';
1
+ import type { normalizeFeatures } from '../features.js';
2
+ import type { MapMarker } from '../types.js';
3
3
  export interface MarkerVisual {
4
4
  node: SVGGElement;
5
5
  dot: SVGCircleElement;
@@ -15,7 +15,7 @@ export interface MarkerItem {
15
15
  visual: MarkerVisual | undefined;
16
16
  }
17
17
  /** Own marker visuals and entrances; selection/events remain with the controller. */
18
- export declare function createMarkerLayer(): {
18
+ export declare function createMarkerRenderer(): {
19
19
  cancelEntrances: () => void;
20
20
  remove(item: MarkerItem): void;
21
21
  update(item: MarkerItem, marker: MapMarker, point: [number, number], index: number): void;
@@ -1,6 +1,6 @@
1
- import { svgElement } from './dom.js';
1
+ import { svgElement } from '../dom.js';
2
2
  /** Own marker visuals and entrances; selection/events remain with the controller. */
3
- export function createMarkerLayer() {
3
+ export function createMarkerRenderer() {
4
4
  const records = new Set();
5
5
  const animations = new Map();
6
6
  function cancelEntrances() {
package/dist/src/map.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { copyAppearance } from './appearance.js';
2
2
  import { validateCameraOptions } from './camera.js';
3
3
  import { configurationPatch, expandMapOptions, prepareConfiguration } from './configuration.js';
4
- import { createConfigurationController } from './configuration-controller.js';
5
- import { createNeighborhoodExplorerCore } from './explorer-core.js';
6
- import { createMapShell } from './explorer-shell.js';
7
4
  import { layerKeys } from './features.js';
5
+ import { createConfigurationController } from './interactive/configuration-controller.js';
6
+ import { createMapEngine } from './interactive/map-engine.js';
7
+ import { createMapShell } from './interactive/map-shell.js';
8
8
  import { assertOptions } from './validation.js';
9
9
  /** No geography is imported. Supply a preset or your own immutable data. */
10
10
  export function createMap(data, options = {}) {
@@ -15,7 +15,7 @@ export function createMap(data, options = {}) {
15
15
  controls: options.controls,
16
16
  appearance: options.appearance,
17
17
  });
18
- const element = createNeighborhoodExplorerCore({
18
+ const element = createMapEngine({
19
19
  mode: 'basemap',
20
20
  ...expanded,
21
21
  interface: 'map',
@@ -1,7 +1,7 @@
1
- import type { createMarkerLayer, MarkerItem } from './marker-layer.js';
1
+ import type { createMarkerRenderer, MarkerItem } from './interactive/marker-renderer.js';
2
2
  import type { MapMarker } from './types.js';
3
3
  /** Own ordered records and picker reconciliation; prepare before committing DOM changes. */
4
- export declare function createMarkerCatalog(renderer: ReturnType<typeof createMarkerLayer>, picker: HTMLSelectElement, project: (point: import('../data/types.js').Position) => [number, number]): {
4
+ export declare function createMarkerCatalog(renderer: ReturnType<typeof createMarkerRenderer>, picker: HTMLSelectElement, project: (point: import('../data/types.js').Position) => [number, number]): {
5
5
  readonly items: MarkerItem[];
6
6
  prepare(markers: readonly MapMarker[], selected: string | null): {
7
7
  selection: string | null;
@@ -1,8 +1,8 @@
1
- import type { Geometry } from '../data/types.js';
2
- import type { DistrictRow } from './data.js';
3
- import type { Project } from './geometry.js';
4
- import type { BartStation, KeyRoad, Landmark } from './overlays.js';
5
- import type { MapMarker, SFMapOptions } from './types.js';
1
+ import type { Geometry } from '../../data/types.js';
2
+ import type { DistrictRow } from '../data.js';
3
+ import type { Project } from '../geometry.js';
4
+ import type { BartStation, KeyRoad, Landmark } from '../overlays.js';
5
+ import type { MapMarker, SFMapOptions } from '../types.js';
6
6
  interface LayerContext {
7
7
  labels: boolean;
8
8
  /** Animated static maps index items for staggering and measure lines for drawing. */
@@ -1,4 +1,4 @@
1
- import { escapeXml, number, overlayLabel, stroke } from './svg.js';
1
+ import { escapeXml, number, overlayLabel, stroke } from './svg-primitives.js';
2
2
  /** Custom JavaScript/JSON rows must not introduce markup or CSS through numeric IDs. */
3
3
  function districtId(id) {
4
4
  if (!Number.isFinite(id))
@@ -1,7 +1,7 @@
1
- import type { Geometry, Position } from '../data/types.js';
2
- import type { DistrictYear, SFMapOptions } from './types.js';
3
- export type { DistrictYear, MapMarker, MapOverlay, SFMapOptions } from './types.js';
4
- import { type Project } from './geometry.js';
1
+ import type { Geometry, Position } from '../../data/types.js';
2
+ import type { DistrictYear, SFMapOptions } from '../types.js';
3
+ export type { DistrictYear, MapMarker, MapOverlay, SFMapOptions } from '../types.js';
4
+ import { type Project } from '../geometry.js';
5
5
  export interface SFMapData {
6
6
  coast: Geometry;
7
7
  districts?: Readonly<Partial<Record<DistrictYear, readonly DistrictRowData[]>>>;
@@ -1,9 +1,9 @@
1
- import { validateMarkers, validateOverlays } from './validation.js';
2
- import { prepareDistrictStyles } from './district-style.js';
3
- import { geometryPath, positions, rawProject } from './geometry.js';
4
- import * as layers from './layers.js';
5
- import { normalizeStaticAnimation, staticAnimationStyle } from './static-animation.js';
6
- import { escapeXml, stroke } from './svg.js';
1
+ import { validateMarkers, validateOverlays } from '../validation.js';
2
+ import { prepareDistrictStyles } from '../district-style.js';
3
+ import { geometryPath, positions, rawProject } from '../geometry.js';
4
+ import { normalizeStaticAnimation, staticAnimationStyle } from '../static-animation.js';
5
+ import * as layers from './svg-layers.js';
6
+ import { escapeXml, stroke } from './svg-primitives.js';
7
7
  export const districtYears = Object.freeze([2002, 2012, 2022]);
8
8
  export const districtColors = Object.freeze([
9
9
  '#c8dce5',
@@ -1,5 +1,5 @@
1
1
  import type { Project } from './geometry.js';
2
- import type { SFMapData } from './map-core.js';
2
+ import type { SFMapData } from './rendering/svg-map-renderer.js';
3
3
  import type { SFMapOptions } from './types.js';
4
4
  /** Conservative portable metrics; the browser refines these with its actual font. */
5
5
  export declare function estimateLabelWidth(text: string, fontSize: number): number;
@@ -1,7 +1,7 @@
1
1
  import { interiorAnchor, layoutLabels, projectedBounds } from './explorer-layout.js';
2
2
  import { labelCandidates, styleLabels } from './label-candidates.js';
3
3
  import { spreadLabels } from './label-spread.js';
4
- import { escapeXml, number } from './svg.js';
4
+ import { escapeXml, number } from './rendering/svg-primitives.js';
5
5
  /** Conservative portable metrics; the browser refines these with its actual font. */
6
6
  export function estimateLabelWidth(text, fontSize) {
7
7
  return Array.from(text).reduce((width, char) => width +
@@ -1,9 +1,9 @@
1
1
  import type { InteractiveSFMapData } from './explorer-data.js';
2
- import { type SFMapData } from './map-core.js';
2
+ import { type SFMapData } from './rendering/svg-map-renderer.js';
3
3
  import type { SFMapOptions } from './types.js';
4
4
  /** Either compact static geography or the same source-aware geography used by createMap. */
5
5
  export type StaticMapInput = SFMapData | InteractiveSFMapData;
6
- export type { DistrictRowData, SFMapData as StaticMapData } from './map-core.js';
6
+ export type { DistrictRowData, SFMapData as StaticMapData } from './rendering/svg-map-renderer.js';
7
7
  export type { DistrictStyle, DistrictYear, SFMapOptions as StaticMapOptions, StaticMapAppearance, StaticMapLayers, } from './types.js';
8
8
  /** Server-safe SVG rendering with explicit data; returns SVG plus projection helpers. */
9
9
  export declare function renderMap(data: StaticMapInput, options?: SFMapOptions): {
@@ -1,6 +1,6 @@
1
1
  import { prepareAppearance } from './appearance.js';
2
2
  import { validateSwitchPatch } from './features.js';
3
- import { createSFMapWithData, getLayerPathsWithData } from './map-core.js';
3
+ import { createSFMapWithData, getLayerPathsWithData, } from './rendering/svg-map-renderer.js';
4
4
  import { normalizeStaticAnimation } from './static-animation.js';
5
5
  import { staticLabels } from './static-labels.js';
6
6
  import { assertOptions, validateMarkers, validateOverlays } from './validation.js';
@@ -2,7 +2,7 @@ import stations from '../data/bart-stations.json' with { type: 'json' };
2
2
  import coast from '../data/coast.json' with { type: 'json' };
3
3
  import districts from '../data/districts-2022.json' with { type: 'json' };
4
4
  import parks from '../data/landmarks.json' with { type: 'json' };
5
- import { createSFMapWithData } from './map-core.js';
5
+ import { createSFMapWithData } from './rendering/svg-map-renderer.js';
6
6
  const districtData = districts;
7
7
  const parkData = parks;
8
8
  const stationData = stations;
@@ -11,7 +11,7 @@ export type DistrictYear = 2002 | 2012 | 2022;
11
11
  export interface DistrictSelection {
12
12
  id: number;
13
13
  year: DistrictYear;
14
- district: import('./map-core.js').DistrictRowData;
14
+ district: import('./rendering/svg-map-renderer.js').DistrictRowData;
15
15
  }
16
16
  export interface DistrictStyle {
17
17
  fill?: string;
@@ -56,7 +56,7 @@ export interface SFMapOptions {
56
56
  neighborhoodLabels?: boolean;
57
57
  districtFills?: boolean;
58
58
  /** Evaluated once per district at construction/style/year updates; call setDistrictStyle again when external data changes. */
59
- districtStyle?: (district: import('./map-core.js').DistrictRowData) => DistrictStyle;
59
+ districtStyle?: (district: import('./rendering/svg-map-renderer.js').DistrictRowData) => DistrictStyle;
60
60
  districtLabels?: boolean;
61
61
  /** Hide all visible text labels while retaining geographic symbols and accessible titles. */
62
62
  labels?: boolean;
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 5.1.3 · local working tree. Node >=24.0.0 for server rendering; browsers need a DOM and a JSON-capable bundler.
5
+ @kahwee/sf-map-svg 5.1.4 · 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 5.1.3 · local working tree. -->
11
+ <!-- Generated from README.md; @kahwee/sf-map-svg 5.1.4 · local working tree. -->
12
12
 
13
13
  # SF Map SVG
14
14
 
@@ -267,7 +267,7 @@ for raw samples, output-equivalence checks, and reproducible commands.
267
267
 
268
268
  ---
269
269
 
270
- <!-- Generated from docs/developer-guide.md; @kahwee/sf-map-svg 5.1.3 · local working tree. -->
270
+ <!-- Generated from docs/developer-guide.md; @kahwee/sf-map-svg 5.1.4 · local working tree. -->
271
271
 
272
272
  # Developer guide
273
273
 
@@ -433,7 +433,7 @@ See [API options](https://github.com/kahwee/sf-map-svg/blob/main/docs/API.md), [
433
433
 
434
434
  ---
435
435
 
436
- <!-- Generated from docs/API.md; @kahwee/sf-map-svg 5.1.3 · local working tree. -->
436
+ <!-- Generated from docs/API.md; @kahwee/sf-map-svg 5.1.4 · local working tree. -->
437
437
 
438
438
  # Map options and lifecycle
439
439
 
@@ -619,7 +619,7 @@ Call `binding.setMarkers(next)` after `map.setMarkers(next)` when the catalog ch
619
619
 
620
620
  ---
621
621
 
622
- <!-- Generated from docs/EXAMPLES.md; @kahwee/sf-map-svg 5.1.3 · local working tree. -->
622
+ <!-- Generated from docs/EXAMPLES.md; @kahwee/sf-map-svg 5.1.4 · local working tree. -->
623
623
 
624
624
  # Examples
625
625
 
@@ -760,7 +760,7 @@ See [query semantics and limitations](https://github.com/kahwee/sf-map-svg/blob/
760
760
 
761
761
  ---
762
762
 
763
- <!-- Generated from docs/consumer-integration.md; @kahwee/sf-map-svg 5.1.3 · local working tree. -->
763
+ <!-- Generated from docs/consumer-integration.md; @kahwee/sf-map-svg 5.1.4 · local working tree. -->
764
764
 
765
765
  # Embedding and progressive enhancement
766
766
 
@@ -938,7 +938,7 @@ keyboard, focus, and dismissal behavior. See [coordinates and units](https://git
938
938
 
939
939
  ---
940
940
 
941
- <!-- Generated from docs/migration-v5.md; @kahwee/sf-map-svg 5.1.3 · local working tree. -->
941
+ <!-- Generated from docs/migration-v5.md; @kahwee/sf-map-svg 5.1.4 · local working tree. -->
942
942
 
943
943
  # Migrating to v5
944
944
 
@@ -1043,7 +1043,7 @@ collections. Shared edges can match more than one feature. See the
1043
1043
 
1044
1044
  ---
1045
1045
 
1046
- <!-- Generated from docs/migration-v3.md; @kahwee/sf-map-svg 5.1.3 · local working tree. -->
1046
+ <!-- Generated from docs/migration-v3.md; @kahwee/sf-map-svg 5.1.4 · local working tree. -->
1047
1047
 
1048
1048
  # Migrate to version 3
1049
1049
 
@@ -1084,7 +1084,7 @@ District styles are now prepared once per district on construction and style/yea
1084
1084
 
1085
1085
  ---
1086
1086
 
1087
- <!-- Generated from SOURCES.md; @kahwee/sf-map-svg 5.1.3 · local working tree. -->
1087
+ <!-- Generated from SOURCES.md; @kahwee/sf-map-svg 5.1.4 · local working tree. -->
1088
1088
 
1089
1089
  # Geometry and provenance
1090
1090
 
@@ -1321,7 +1321,7 @@ Report-history points preserve only actual validated source counts and their dis
1321
1321
 
1322
1322
  ---
1323
1323
 
1324
- <!-- Generated from CHANGELOG.md; @kahwee/sf-map-svg 5.1.3 · local working tree. -->
1324
+ <!-- Generated from CHANGELOG.md; @kahwee/sf-map-svg 5.1.4 · local working tree. -->
1325
1325
 
1326
1326
  # Changelog
1327
1327
 
@@ -1329,6 +1329,10 @@ User-visible changes are recorded here. Unreleased entries describe changes on `
1329
1329
 
1330
1330
  ## Unreleased
1331
1331
 
1332
+ ## 5.1.4 — 2026-11-05
1333
+
1334
+ - Group internal browser controllers, renderers and shells under `src/interactive`, and SVG generation under `src/rendering`. Rename the general map engine, controls and marker renderer to match their responsibilities; clarify selection-detail, neighborhood-results and layer-composition operations. Keep public package entry points unchanged.
1335
+
1332
1336
  ## 5.1.3 — 2026-11-05
1333
1337
 
1334
1338
  - Give configuration snapshots and revisions a dedicated internal owner. Split presentation preparation from a guarded, single-use commit, rejecting stale or disposed updates before mutating presentation. Preserve public configuration and reset behavior.
@@ -1725,7 +1729,7 @@ consistent home, and makes animation and subscription ownership predictable.
1725
1729
 
1726
1730
  # Published type contracts
1727
1731
 
1728
- @kahwee/sf-map-svg 5.1.3 · 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.
1732
+ @kahwee/sf-map-svg 5.1.4 · 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.
1729
1733
 
1730
1734
  | Public import | Declaration |
1731
1735
  | --- | --- |
@@ -2111,7 +2115,7 @@ export type DistrictYear = 2002 | 2012 | 2022;
2111
2115
  export interface DistrictSelection {
2112
2116
  id: number;
2113
2117
  year: DistrictYear;
2114
- district: import('./map-core.js').DistrictRowData;
2118
+ district: import('./rendering/svg-map-renderer.js').DistrictRowData;
2115
2119
  }
2116
2120
  export interface DistrictStyle {
2117
2121
  fill?: string;
@@ -2156,7 +2160,7 @@ export interface SFMapOptions {
2156
2160
  neighborhoodLabels?: boolean;
2157
2161
  districtFills?: boolean;
2158
2162
  /** Evaluated once per district at construction/style/year updates; call setDistrictStyle again when external data changes. */
2159
- districtStyle?: (district: import('./map-core.js').DistrictRowData) => DistrictStyle;
2163
+ districtStyle?: (district: import('./rendering/svg-map-renderer.js').DistrictRowData) => DistrictStyle;
2160
2164
  districtLabels?: boolean;
2161
2165
  /** Hide all visible text labels while retaining geographic symbols and accessible titles. */
2162
2166
  labels?: boolean;
@@ -2378,7 +2382,7 @@ export type { MapAppearance, MapCamera, MapCapabilities, MapConfiguration, MapCo
2378
2382
 
2379
2383
  ```ts
2380
2384
  import type { DistrictProperties, FeatureCollection, NeighborhoodProperties, NeighborhoodSource } from '../data/types.js';
2381
- import type { SFMapData } from './map-core.js';
2385
+ import type { SFMapData } from './rendering/svg-map-renderer.js';
2382
2386
  import type { DistrictYear } from './types.js';
2383
2387
  /** Geographic inputs for the data-injected interactive map entry point. */
2384
2388
  export interface InteractiveSFMapData {
@@ -2388,13 +2392,13 @@ export interface InteractiveSFMapData {
2388
2392
  }
2389
2393
  ```
2390
2394
 
2391
- ### dist/src/map-core.d.ts
2395
+ ### dist/src/rendering/svg-map-renderer.d.ts
2392
2396
 
2393
2397
  ```ts
2394
- import type { Geometry, Position } from '../data/types.js';
2395
- import type { DistrictYear, SFMapOptions } from './types.js';
2396
- export type { DistrictYear, MapMarker, MapOverlay, SFMapOptions } from './types.js';
2397
- import { type Project } from './geometry.js';
2398
+ import type { Geometry, Position } from '../../data/types.js';
2399
+ import type { DistrictYear, SFMapOptions } from '../types.js';
2400
+ export type { DistrictYear, MapMarker, MapOverlay, SFMapOptions } from '../types.js';
2401
+ import { type Project } from '../geometry.js';
2398
2402
  export interface SFMapData {
2399
2403
  coast: Geometry;
2400
2404
  districts?: Readonly<Partial<Record<DistrictYear, readonly DistrictRowData[]>>>;
@@ -2516,11 +2520,11 @@ export declare function geometryContainsPoint(geometry: Geometry | null | undefi
2516
2520
 
2517
2521
  ```ts
2518
2522
  import type { InteractiveSFMapData } from './explorer-data.js';
2519
- import { type SFMapData } from './map-core.js';
2523
+ import { type SFMapData } from './rendering/svg-map-renderer.js';
2520
2524
  import type { SFMapOptions } from './types.js';
2521
2525
  /** Either compact static geography or the same source-aware geography used by createMap. */
2522
2526
  export type StaticMapInput = SFMapData | InteractiveSFMapData;
2523
- export type { DistrictRowData, SFMapData as StaticMapData } from './map-core.js';
2527
+ export type { DistrictRowData, SFMapData as StaticMapData } from './rendering/svg-map-renderer.js';
2524
2528
  export type { DistrictStyle, DistrictYear, SFMapOptions as StaticMapOptions, StaticMapAppearance, StaticMapLayers, } from './types.js';
2525
2529
  /** Server-safe SVG rendering with explicit data; returns SVG plus projection helpers. */
2526
2530
  export declare function renderMap(data: StaticMapInput, options?: SFMapOptions): {
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 5.1.3 · local working tree. Node >=24.0.0 for server rendering; browsers need a DOM and a JSON-capable bundler.
5
+ @kahwee/sf-map-svg 5.1.4 · 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": "5.1.3",
3
+ "version": "5.1.4",
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",