@mailwoman/react 7.5.0 → 7.7.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.
Files changed (128) hide show
  1. package/common/PresetChips.tsx +4 -1
  2. package/index.ts +28 -0
  3. package/map/BackendControl.tsx +39 -0
  4. package/map/CompareToggle.tsx +81 -0
  5. package/map/DemoControls.tsx +173 -0
  6. package/map/DemoMap.tsx +98 -0
  7. package/map/GeocoderDemo.tsx +178 -0
  8. package/map/OverlayLayers.tsx +46 -0
  9. package/map/PlaceAutocomplete.tsx +69 -0
  10. package/map/PlaceMarker.tsx +40 -0
  11. package/map/ResolvedPlaceLayers.tsx +63 -0
  12. package/map/ResultCamera.tsx +60 -0
  13. package/map/ResultOverlay.tsx +61 -0
  14. package/map/ResultPanel.tsx +84 -0
  15. package/map/VersionPicker.tsx +62 -0
  16. package/map/geometry.ts +135 -0
  17. package/map/index.ts +73 -0
  18. package/map/place-render.ts +157 -0
  19. package/map/types.ts +207 -0
  20. package/map/useCompareState.ts +45 -0
  21. package/map/useDemoGeocode.ts +67 -0
  22. package/map/useMapPlaceRender.ts +21 -0
  23. package/map/usePlaceAutocomplete.ts +195 -0
  24. package/out/common/PresetChips.d.ts +3 -1
  25. package/out/common/PresetChips.d.ts.map +1 -1
  26. package/out/common/PresetChips.js +2 -2
  27. package/out/common/PresetChips.js.map +1 -1
  28. package/out/index.d.ts +3 -0
  29. package/out/index.d.ts.map +1 -1
  30. package/out/index.js +2 -0
  31. package/out/index.js.map +1 -1
  32. package/out/map/BackendControl.d.ts +24 -0
  33. package/out/map/BackendControl.d.ts.map +1 -0
  34. package/out/map/BackendControl.js +6 -0
  35. package/out/map/BackendControl.js.map +1 -0
  36. package/out/map/CompareToggle.d.ts +35 -0
  37. package/out/map/CompareToggle.d.ts.map +1 -0
  38. package/out/map/CompareToggle.js +9 -0
  39. package/out/map/CompareToggle.js.map +1 -0
  40. package/out/map/DemoControls.d.ts +44 -0
  41. package/out/map/DemoControls.d.ts.map +1 -0
  42. package/out/map/DemoControls.js +28 -0
  43. package/out/map/DemoControls.js.map +1 -0
  44. package/out/map/DemoMap.d.ts +59 -0
  45. package/out/map/DemoMap.d.ts.map +1 -0
  46. package/out/map/DemoMap.js +12 -0
  47. package/out/map/DemoMap.js.map +1 -0
  48. package/out/map/GeocoderDemo.d.ts +47 -0
  49. package/out/map/GeocoderDemo.d.ts.map +1 -0
  50. package/out/map/GeocoderDemo.js +73 -0
  51. package/out/map/GeocoderDemo.js.map +1 -0
  52. package/out/map/OverlayLayers.d.ts +23 -0
  53. package/out/map/OverlayLayers.d.ts.map +1 -0
  54. package/out/map/OverlayLayers.js +27 -0
  55. package/out/map/OverlayLayers.js.map +1 -0
  56. package/out/map/PlaceAutocomplete.d.ts +34 -0
  57. package/out/map/PlaceAutocomplete.d.ts.map +1 -0
  58. package/out/map/PlaceAutocomplete.js +9 -0
  59. package/out/map/PlaceAutocomplete.js.map +1 -0
  60. package/out/map/PlaceMarker.d.ts +27 -0
  61. package/out/map/PlaceMarker.d.ts.map +1 -0
  62. package/out/map/PlaceMarker.js +9 -0
  63. package/out/map/PlaceMarker.js.map +1 -0
  64. package/out/map/ResolvedPlaceLayers.d.ts +34 -0
  65. package/out/map/ResolvedPlaceLayers.d.ts.map +1 -0
  66. package/out/map/ResolvedPlaceLayers.js +11 -0
  67. package/out/map/ResolvedPlaceLayers.js.map +1 -0
  68. package/out/map/ResultCamera.d.ts +34 -0
  69. package/out/map/ResultCamera.d.ts.map +1 -0
  70. package/out/map/ResultCamera.js +43 -0
  71. package/out/map/ResultCamera.js.map +1 -0
  72. package/out/map/ResultOverlay.d.ts +34 -0
  73. package/out/map/ResultOverlay.d.ts.map +1 -0
  74. package/out/map/ResultOverlay.js +18 -0
  75. package/out/map/ResultOverlay.js.map +1 -0
  76. package/out/map/ResultPanel.d.ts +34 -0
  77. package/out/map/ResultPanel.d.ts.map +1 -0
  78. package/out/map/ResultPanel.js +12 -0
  79. package/out/map/ResultPanel.js.map +1 -0
  80. package/out/map/VersionPicker.d.ts +30 -0
  81. package/out/map/VersionPicker.d.ts.map +1 -0
  82. package/out/map/VersionPicker.js +8 -0
  83. package/out/map/VersionPicker.js.map +1 -0
  84. package/out/map/geometry.d.ts +55 -0
  85. package/out/map/geometry.d.ts.map +1 -0
  86. package/out/map/geometry.js +99 -0
  87. package/out/map/geometry.js.map +1 -0
  88. package/out/map/index.d.ts +52 -0
  89. package/out/map/index.d.ts.map +1 -0
  90. package/out/map/index.js +36 -0
  91. package/out/map/index.js.map +1 -0
  92. package/out/map/place-render.d.ts +85 -0
  93. package/out/map/place-render.d.ts.map +1 -0
  94. package/out/map/place-render.js +98 -0
  95. package/out/map/place-render.js.map +1 -0
  96. package/out/map/types.d.ts +193 -0
  97. package/out/map/types.d.ts.map +1 -0
  98. package/out/map/types.js +18 -0
  99. package/out/map/types.js.map +1 -0
  100. package/out/map/useCompareState.d.ts +25 -0
  101. package/out/map/useCompareState.d.ts.map +1 -0
  102. package/out/map/useCompareState.js +27 -0
  103. package/out/map/useCompareState.js.map +1 -0
  104. package/out/map/useDemoGeocode.d.ts +36 -0
  105. package/out/map/useDemoGeocode.d.ts.map +1 -0
  106. package/out/map/useDemoGeocode.js +44 -0
  107. package/out/map/useDemoGeocode.js.map +1 -0
  108. package/out/map/useMapPlaceRender.d.ts +15 -0
  109. package/out/map/useMapPlaceRender.d.ts.map +1 -0
  110. package/out/map/useMapPlaceRender.js +18 -0
  111. package/out/map/useMapPlaceRender.js.map +1 -0
  112. package/out/map/usePlaceAutocomplete.d.ts +61 -0
  113. package/out/map/usePlaceAutocomplete.d.ts.map +1 -0
  114. package/out/map/usePlaceAutocomplete.js +122 -0
  115. package/out/map/usePlaceAutocomplete.js.map +1 -0
  116. package/out/pipeline/QueryForm.d.ts +6 -2
  117. package/out/pipeline/QueryForm.d.ts.map +1 -1
  118. package/out/pipeline/QueryForm.js +2 -2
  119. package/out/pipeline/QueryForm.js.map +1 -1
  120. package/out/runtime/useDemoRuntime.d.ts +113 -0
  121. package/out/runtime/useDemoRuntime.d.ts.map +1 -0
  122. package/out/runtime/useDemoRuntime.js +135 -0
  123. package/out/runtime/useDemoRuntime.js.map +1 -0
  124. package/package.json +27 -6
  125. package/pipeline/QueryForm.tsx +17 -2
  126. package/runtime/useDemoRuntime.ts +245 -0
  127. package/styles.css +145 -0
  128. package/vitest.node.config.ts +20 -0
@@ -0,0 +1,193 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Types for the geocoder-demo map surface. Mirrors the pipeline seam: the package owns the UI state
7
+ * machine + the declarative map, while the host injects a {@link DemoRuntime} that owns ONNX / httpvfs
8
+ * / R2 and the composed map style. {@link DemoRuntime} EXTENDS {@link PipelineRuntime} so the shared
9
+ * `runParse` / `parseStageLabels` / `loading` contract is reused, and adds the map-specific surface
10
+ * (style, overlays, initial center, viewport bias, backend/version selection). Phase 4 adds the
11
+ * `resolveMapPlace` enricher, the {@link DemoPanels} injection bag, and the {@link DemoCompareContext}.
12
+ *
13
+ * The map-spec types are imported type-only from `react-map-gl/maplibre`; nothing here loads maplibre at
14
+ * runtime, so this module stays node-safe (its concrete-value CONSUMERS — `DemoMap`, `GeocoderDemo` —
15
+ * are the ones gated behind the `@mailwoman/react/map` subpath).
16
+ */
17
+ import type { ReactNode } from "react";
18
+ import type { LayerSpecification, SourceSpecification } from "react-map-gl/maplibre";
19
+ import type { ParseResult, PipelineRuntime, ResolvedPlaceView } from "../pipeline/types.ts";
20
+ import type { DemoMapStyle } from "./DemoMap.tsx";
21
+ import type { ResolvedMapPlace } from "./place-render.ts";
22
+ /** `[longitude, latitude]`. */
23
+ export type LngLatTuple = [number, number];
24
+ /** A viewport bias handed to `runParse` — the map's current center (and optionally zoom) as a soft prior. */
25
+ export interface MapBias {
26
+ /** Map center as `[lon, lat]`, typically read from `map.getCenter()`. */
27
+ center: LngLatTuple;
28
+ /** Current zoom, if the host wants distance-aware biasing. */
29
+ zoom?: number;
30
+ }
31
+ /**
32
+ * A host-supplied overlay: one map `<Source>` plus one or more `<Layer>`s laid over the basemap (coverage "fog of war",
33
+ * race-dots, …). The host composes the specs; the package renders them declaratively in a later phase.
34
+ */
35
+ export interface OverlaySpec {
36
+ /** Stable id — used as the `<Source>` id and the layer-id prefix. */
37
+ id: string;
38
+ /** The map source spec (vector/geojson/raster). */
39
+ source: SourceSpecification;
40
+ /** The layers drawn from that source. */
41
+ layers: LayerSpecification[];
42
+ /** Whether the overlay is visible initially. @default true */
43
+ visible?: boolean;
44
+ /** Human label for a layer-toggle control. */
45
+ label?: string;
46
+ }
47
+ /** One autocomplete suggestion produced by the host's FST prefix-walk. */
48
+ export interface Suggestion {
49
+ /** The text inserted when the suggestion is picked. */
50
+ value: string;
51
+ /** Optional display label if it differs from `value`. */
52
+ label?: string;
53
+ /** Optional place kind for badge/icon rendering. */
54
+ placetype?: string;
55
+ }
56
+ /** A selectable model bundle (version tag + a display label the picker shows). */
57
+ export interface DemoVersionOption {
58
+ /** The version tag (e.g. a git tag or model-card version). */
59
+ version: string;
60
+ /** Display label; falls back to `version`. */
61
+ label?: string;
62
+ }
63
+ /** Which neural backend the demo is currently running on. */
64
+ export type DemoBackend = "webgpu" | "wasm";
65
+ /**
66
+ * The injected demo runtime. Extends {@link PipelineRuntime} (shared `runParse` / `parseStageLabels` / `loading` /
67
+ * `ready`) with the map + version/backend surface the demo needs. The host composes `mapStyle` (via cartographer's
68
+ * `StyleSpecificationComposer` + the tile-worker TileJSON), supplies the overlay specs, the initial center (from
69
+ * geolocation), the FST autocomplete, and the calibrator — nothing in the package imports `@mailwoman/cartographer`,
70
+ * `@mailwoman/neural-web`, httpvfs, or Docusaurus.
71
+ */
72
+ export interface DemoRuntime extends PipelineRuntime {
73
+ /** The composed basemap style (URL or `StyleSpecification`). */
74
+ mapStyle: DemoMapStyle;
75
+ /** Host-supplied overlays (coverage, race-dots, …). */
76
+ overlays?: OverlaySpec[];
77
+ /** Initial map center as `[lon, lat]` (the host's browser-geolocation result). */
78
+ initialCenter: LngLatTuple;
79
+ /** Initial zoom for the first camera. */
80
+ initialZoom?: number;
81
+ /**
82
+ * A bias-aware parse. The map demo feeds the current viewport center as a soft prior; when absent the host falls back
83
+ * to the base {@link PipelineRuntime.runParse}. Kept separate so the shared `runParse` contract is unchanged.
84
+ */
85
+ runParseWithBias?: (input: string, bias: MapBias | null, hooks: {
86
+ onStage: (stage: number) => void;
87
+ }) => ReturnType<PipelineRuntime["runParse"]>;
88
+ /** FST prefix-walk autocomplete, wrapped by the host. */
89
+ autocomplete?: (query: string) => Promise<Suggestion[]>;
90
+ /** Maps a raw model score to a calibrated one; `null` when no calibration table is loaded. */
91
+ calibrator?: (raw: number) => number | null;
92
+ /**
93
+ * Enrich the selected candidate into the richer {@link ResolvedMapPlace} the declarative map render consumes (bbox,
94
+ * street tier + uncertainty, a pre-fetched crisp polygon) — the fields that live on the demo's `ResolvedHit` but not
95
+ * on the shared {@link ResolvedPlaceView}. The host owns this because those extras (and the async polygon fetch in the
96
+ * real demo) are host/gazetteer concerns; the package keeps {@link ParseResult} unpolluted. Absent → the candidate
97
+ * renders as a bare point (marker + a mid-zoom fly-to). Returning `null` also renders nothing.
98
+ */
99
+ resolveMapPlace?: (candidate: ResolvedPlaceView, result: ParseResult) => ResolvedMapPlace | null;
100
+ /** The selectable model bundles the version picker offers. */
101
+ availableVersions?: DemoVersionOption[];
102
+ /** The currently-selected model version. */
103
+ selectedVersion?: string;
104
+ /** Switch the active model bundle (re-loads weights/tokenizer/gazetteer). */
105
+ selectVersion?: (version: string) => void;
106
+ /** The backend the neural runtime resolved to (e.g. `webgpu (28 MB int8)`); free-form for the label. */
107
+ activeBackend?: string;
108
+ /** Whether the CPU/WASM backend is currently forced (the controlled value for the backend toggle). */
109
+ forceWASM?: boolean;
110
+ /** Force the WASM backend (opt out of WebGPU), for the backend toggle. */
111
+ setForceWASM?: (forceWASM: boolean) => void;
112
+ }
113
+ /** The compare-mode state a {@link DemoPanels.compare} render-prop receives (the second parse itself stays host-side). */
114
+ export interface DemoCompareContext {
115
+ /** The current primary parse result, or `null` before the first submit. */
116
+ result: ParseResult | null;
117
+ /** Whether the compare toggle is on. */
118
+ compareMode: boolean;
119
+ /** The version selected to compare against, or `null` when none is chosen. */
120
+ compareVersion: string | null;
121
+ }
122
+ /**
123
+ * The state a {@link DemoPanels.result} render-prop receives, so a host can render its OWN result block (the docs
124
+ * `<ResultPanel>` with its span-highlight / timing / hierarchy / precision detail) in place of the package's default
125
+ * {@link ResultPanel}. Everything the default panel needs is passed through; the candidate-selection state stays owned
126
+ * by the package (`useDemoGeocode`).
127
+ */
128
+ export interface DemoResultContext {
129
+ /** The current parse+resolve result. */
130
+ result: ParseResult;
131
+ /** The selected candidate (falls back to the first), enriched for the resolved-place detail. */
132
+ selectedCandidate: ResolvedPlaceView | null;
133
+ /** The selected candidate index, for the picker's active state. */
134
+ selectedCandidateIndex: number;
135
+ /** Fired when a candidate in the picker is chosen. */
136
+ onSelectCandidate: (index: number) => void;
137
+ }
138
+ /**
139
+ * Host-injected panels for {@link GeocoderDemo}, the map analogue of `PipelinePanels`. Each is an already-rendered
140
+ * `ReactNode` (or a thunk of the parse result / compare state) so the package needs neither the heavy docs visualizers
141
+ * (ModelVisualizer, VersionCompare, AboutDemo, PermalinkButton) nor their data types. Every field is optional — the
142
+ * fake-runtime Storybook stories pass none and still render the whole demo.
143
+ */
144
+ export interface DemoPanels {
145
+ /** Rendered at the top of the control panel (e.g. the docs "About this demo"). */
146
+ header?: ReactNode;
147
+ /** One-line release blurb for the selected version. */
148
+ releaseInfo?: ReactNode;
149
+ /** Rendered at the bottom of the control panel (e.g. a guided tour). */
150
+ footer?: ReactNode;
151
+ /**
152
+ * A device-location / proximity-bias control, rendered between the query form and the autocomplete list (the demo's
153
+ * "📍 Use my location" row). Host-owned so the geolocation permission + the bias it feeds into the host's
154
+ * {@link DemoRuntime.runParseWithBias} stay a host concern.
155
+ */
156
+ bias?: ReactNode;
157
+ /** Heavy visualizers (span highlight, tree, timing, BIO, …), rendered from the result. */
158
+ extras?: (result: ParseResult) => ReactNode;
159
+ /**
160
+ * Rendered just above the result block (present or empty). The demo's opt-in display toggles live here — calibrated
161
+ * confidence + dev-mode — because the host owns both the toggle state AND the {@link result} / {@link debugDrawer}
162
+ * renderers those toggles drive.
163
+ */
164
+ aboveResult?: (context: {
165
+ result: ParseResult | null;
166
+ }) => ReactNode;
167
+ /**
168
+ * Replace the package's default {@link ResultPanel} entirely. When provided, the host renders its own result block
169
+ * (the docs `<ResultPanel>` — span highlight, timing, hierarchy, precision detail, calibrated confidences) from the
170
+ * {@link DemoResultContext}. Absent → the built-in panel renders.
171
+ */
172
+ result?: (context: DemoResultContext) => ReactNode;
173
+ /**
174
+ * Rendered in place of the resolved-place panel when nothing resolved (host's FailureDiagnostic). Ignored when
175
+ * {@link result} is set.
176
+ */
177
+ failure?: (result: ParseResult) => ReactNode;
178
+ /** The version-compare view — the host renders its own diff from the compare state it owns. */
179
+ compare?: (context: DemoCompareContext) => ReactNode;
180
+ /**
181
+ * The model-visualizer / debug drawer, mounted beside the map (host's ModelVisualizer). A render-prop so the host can
182
+ * trace the CURRENT result (its input) — the package passes the live parse result; the host gates on its own dev-mode
183
+ * state and returns `null` when the drawer is closed.
184
+ */
185
+ debugDrawer?: (context: {
186
+ result: ParseResult | null;
187
+ }) => ReactNode;
188
+ /** Extra map controls mounted as `<DemoMap>` children (host's DebugControl / LayerToggle via `useControl`). */
189
+ mapControls?: ReactNode;
190
+ /** A permalink control for the current address (host's PermalinkButton). */
191
+ permalink?: (text: string) => ReactNode;
192
+ }
193
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../map/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Types for the geocoder-demo map surface. Mirrors the pipeline seam: the package owns the UI state
7
+ * machine + the declarative map, while the host injects a {@link DemoRuntime} that owns ONNX / httpvfs
8
+ * / R2 and the composed map style. {@link DemoRuntime} EXTENDS {@link PipelineRuntime} so the shared
9
+ * `runParse` / `parseStageLabels` / `loading` contract is reused, and adds the map-specific surface
10
+ * (style, overlays, initial center, viewport bias, backend/version selection). Phase 4 adds the
11
+ * `resolveMapPlace` enricher, the {@link DemoPanels} injection bag, and the {@link DemoCompareContext}.
12
+ *
13
+ * The map-spec types are imported type-only from `react-map-gl/maplibre`; nothing here loads maplibre at
14
+ * runtime, so this module stays node-safe (its concrete-value CONSUMERS — `DemoMap`, `GeocoderDemo` —
15
+ * are the ones gated behind the `@mailwoman/react/map` subpath).
16
+ */
17
+ export {};
18
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../map/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * `useCompareState` — the small headless state machine behind the demo's compare toggle
7
+ * (`_app.tsx:142-143, 1238-1305`): whether compare mode is on, and which version to compare against.
8
+ * Turning compare off clears the chosen version; selecting a compare version equal to the primary is
9
+ * rejected (the picker filters it out, this guards the programmatic path). The SECOND parse itself is a
10
+ * host concern surfaced through `panels.compare` — this hook owns only the toggle + selection.
11
+ */
12
+ export interface UseCompareState {
13
+ /** Whether compare mode is on. */
14
+ compareMode: boolean;
15
+ /** The version chosen to compare against, or `null`. */
16
+ compareVersion: string | null;
17
+ /** Flip compare mode; turning it off clears the chosen version. */
18
+ setCompareMode: (compareMode: boolean) => void;
19
+ /** Choose a compare version (`null` clears it). */
20
+ setCompareVersion: (version: string | null) => void;
21
+ /** Keep the compare selection distinct from the primary — call when the primary version changes. */
22
+ clearIfPrimary: (primaryVersion: string) => void;
23
+ }
24
+ export declare function useCompareState(): UseCompareState;
25
+ //# sourceMappingURL=useCompareState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCompareState.d.ts","sourceRoot":"","sources":["../../map/useCompareState.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,MAAM,WAAW,eAAe;IAC/B,kCAAkC;IAClC,WAAW,EAAE,OAAO,CAAA;IACpB,wDAAwD;IACxD,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,mEAAmE;IACnE,cAAc,EAAE,CAAC,WAAW,EAAE,OAAO,KAAK,IAAI,CAAA;IAC9C,mDAAmD;IACnD,iBAAiB,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACnD,oGAAoG;IACpG,cAAc,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAA;CAChD;AAED,wBAAgB,eAAe,IAAI,eAAe,CAiBjD"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * `useCompareState` — the small headless state machine behind the demo's compare toggle
7
+ * (`_app.tsx:142-143, 1238-1305`): whether compare mode is on, and which version to compare against.
8
+ * Turning compare off clears the chosen version; selecting a compare version equal to the primary is
9
+ * rejected (the picker filters it out, this guards the programmatic path). The SECOND parse itself is a
10
+ * host concern surfaced through `panels.compare` — this hook owns only the toggle + selection.
11
+ */
12
+ import { useCallback, useState } from "react";
13
+ export function useCompareState() {
14
+ const [compareMode, setCompareModeState] = useState(false);
15
+ const [compareVersion, setCompareVersion] = useState(null);
16
+ const setCompareMode = useCallback((next) => {
17
+ setCompareModeState(next);
18
+ if (!next) {
19
+ setCompareVersion(null);
20
+ }
21
+ }, []);
22
+ const clearIfPrimary = useCallback((primaryVersion) => {
23
+ setCompareVersion((prev) => (prev === primaryVersion ? null : prev));
24
+ }, []);
25
+ return { compareMode, compareVersion, setCompareMode, setCompareVersion, clearIfPrimary };
26
+ }
27
+ //# sourceMappingURL=useCompareState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCompareState.js","sourceRoot":"","sources":["../../map/useCompareState.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAe7C,MAAM,UAAU,eAAe;IAC9B,MAAM,CAAC,WAAW,EAAE,mBAAmB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC1D,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IAEzE,MAAM,cAAc,GAAG,WAAW,CAAC,CAAC,IAAa,EAAE,EAAE;QACpD,mBAAmB,CAAC,IAAI,CAAC,CAAA;QAEzB,IAAI,CAAC,IAAI,EAAE,CAAC;YACX,iBAAiB,CAAC,IAAI,CAAC,CAAA;QACxB,CAAC;IACF,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,cAAc,GAAG,WAAW,CAAC,CAAC,cAAsB,EAAE,EAAE;QAC7D,iBAAiB,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,KAAK,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;IACrE,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,cAAc,EAAE,iBAAiB,EAAE,cAAc,EAAE,CAAA;AAC1F,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * `useDemoGeocode` — the parse+resolve state machine for the geocoder map demo. It REUSES the pipeline's
7
+ * `useParsePipeline` (text / busy / stage / result / candidate selection) and layers on the two map-only
8
+ * concerns the demo adds over the base explorer:
9
+ *
10
+ * 1. Viewport bias — when the runtime exposes `runParseWithBias`, the current map center (read through
11
+ * an injected `getBias`, itself reading the `MapRef`) rides along as a soft proximity prior. The
12
+ * bias is injected by deriving a `runParse` that binds it, so `useParsePipeline` is reused verbatim.
13
+ * 2. The map render place — the selected candidate, enriched by the host's `resolveMapPlace` into the
14
+ * richer {@link ResolvedMapPlace} the declarative overlays consume (bbox / tier / polygon). Absent
15
+ * an enricher, the candidate renders as a bare point.
16
+ *
17
+ * No maplibre import at module scope — the map handle is reached only through the injected `getBias`
18
+ * callback, so this hook stays independent of the map binding.
19
+ */
20
+ import { type UseParsePipeline } from "../pipeline/useParsePipeline.ts";
21
+ import type { ResolvedMapPlace } from "./place-render.ts";
22
+ import type { DemoRuntime, MapBias } from "./types.ts";
23
+ export interface UseDemoGeocodeOptions {
24
+ /** The injected demo runtime (extends `PipelineRuntime` with the map + bias surface). */
25
+ runtime: DemoRuntime;
26
+ /** Address to pre-fill. */
27
+ defaultText: string;
28
+ /** Read the current viewport bias (the map center) at submit time. Absent → no bias. */
29
+ getBias?: () => MapBias | null;
30
+ }
31
+ export interface UseDemoGeocode extends UseParsePipeline {
32
+ /** The selected candidate enriched into the map-render shape (bbox / tier / polygon), or `null`. */
33
+ mapPlace: ResolvedMapPlace | null;
34
+ }
35
+ export declare function useDemoGeocode({ runtime, defaultText, getBias }: UseDemoGeocodeOptions): UseDemoGeocode;
36
+ //# sourceMappingURL=useDemoGeocode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDemoGeocode.d.ts","sourceRoot":"","sources":["../../map/useDemoGeocode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,iCAAiC,CAAA;AACzF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAEtD,MAAM,WAAW,qBAAqB;IACrC,yFAAyF;IACzF,OAAO,EAAE,WAAW,CAAA;IACpB,2BAA2B;IAC3B,WAAW,EAAE,MAAM,CAAA;IACnB,wFAAwF;IACxF,OAAO,CAAC,EAAE,MAAM,OAAO,GAAG,IAAI,CAAA;CAC9B;AAED,MAAM,WAAW,cAAe,SAAQ,gBAAgB;IACvD,oGAAoG;IACpG,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAA;CACjC;AAED,wBAAgB,cAAc,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,qBAAqB,GAAG,cAAc,CAyBvG"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * `useDemoGeocode` — the parse+resolve state machine for the geocoder map demo. It REUSES the pipeline's
7
+ * `useParsePipeline` (text / busy / stage / result / candidate selection) and layers on the two map-only
8
+ * concerns the demo adds over the base explorer:
9
+ *
10
+ * 1. Viewport bias — when the runtime exposes `runParseWithBias`, the current map center (read through
11
+ * an injected `getBias`, itself reading the `MapRef`) rides along as a soft proximity prior. The
12
+ * bias is injected by deriving a `runParse` that binds it, so `useParsePipeline` is reused verbatim.
13
+ * 2. The map render place — the selected candidate, enriched by the host's `resolveMapPlace` into the
14
+ * richer {@link ResolvedMapPlace} the declarative overlays consume (bbox / tier / polygon). Absent
15
+ * an enricher, the candidate renders as a bare point.
16
+ *
17
+ * No maplibre import at module scope — the map handle is reached only through the injected `getBias`
18
+ * callback, so this hook stays independent of the map binding.
19
+ */
20
+ import { useMemo } from "react";
21
+ import { useParsePipeline } from "../pipeline/useParsePipeline.js";
22
+ export function useDemoGeocode({ runtime, defaultText, getBias }) {
23
+ // Bind the viewport bias into a derived `runParse` so `useParsePipeline` is reused unchanged. When the runtime has no
24
+ // bias-aware parse, pass it straight through.
25
+ const geoRuntime = useMemo(() => {
26
+ const withBias = runtime.runParseWithBias;
27
+ if (!withBias)
28
+ return runtime;
29
+ return {
30
+ ...runtime,
31
+ runParse: (input, hooks) => withBias(input, getBias?.() ?? null, hooks),
32
+ };
33
+ }, [runtime, getBias]);
34
+ const pipeline = useParsePipeline({ runtime: geoRuntime, defaultText });
35
+ const mapPlace = useMemo(() => {
36
+ if (!pipeline.selectedCandidate || !pipeline.result)
37
+ return null;
38
+ return runtime.resolveMapPlace
39
+ ? runtime.resolveMapPlace(pipeline.selectedCandidate, pipeline.result)
40
+ : pipeline.selectedCandidate;
41
+ }, [pipeline.selectedCandidate, pipeline.result, runtime]);
42
+ return { ...pipeline, mapPlace };
43
+ }
44
+ //# sourceMappingURL=useDemoGeocode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDemoGeocode.js","sourceRoot":"","sources":["../../map/useDemoGeocode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAG/B,OAAO,EAAE,gBAAgB,EAAyB,MAAM,iCAAiC,CAAA;AAkBzF,MAAM,UAAU,cAAc,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAyB;IACtF,sHAAsH;IACtH,8CAA8C;IAC9C,MAAM,UAAU,GAAG,OAAO,CAAkB,GAAG,EAAE;QAChD,MAAM,QAAQ,GAAG,OAAO,CAAC,gBAAgB,CAAA;QAEzC,IAAI,CAAC,QAAQ;YAAE,OAAO,OAAO,CAAA;QAE7B,OAAO;YACN,GAAG,OAAO;YACV,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,IAAI,IAAI,EAAE,KAAK,CAAC;SACvE,CAAA;IACF,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;IAEtB,MAAM,QAAQ,GAAG,gBAAgB,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC,CAAA;IAEvE,MAAM,QAAQ,GAAG,OAAO,CAA0B,GAAG,EAAE;QACtD,IAAI,CAAC,QAAQ,CAAC,iBAAiB,IAAI,CAAC,QAAQ,CAAC,MAAM;YAAE,OAAO,IAAI,CAAA;QAEhE,OAAO,OAAO,CAAC,eAAe;YAC7B,CAAC,CAAC,OAAO,CAAC,eAAe,CAAC,QAAQ,CAAC,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC;YACtE,CAAC,CAAC,QAAQ,CAAC,iBAAiB,CAAA;IAC9B,CAAC,EAAE,CAAC,QAAQ,CAAC,iBAAiB,EAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IAE1D,OAAO,EAAE,GAAG,QAAQ,EAAE,QAAQ,EAAE,CAAA;AACjC,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * `useMapPlaceRender` — the thin React memo wrapper over the pure {@link computeMapPlaceRenderSpec}. It
7
+ * recomputes the render spec only when the resolved place changes, so the declarative overlays render a
8
+ * stable spec object. The MATH lives in `place-render.ts` (pure, node-tested); this file adds nothing
9
+ * but memoization, so it stays trivially correct. A `null` place (no result yet, or a result with no
10
+ * candidate) yields `null` — the overlays render nothing.
11
+ */
12
+ import type { MapPlaceRenderSpec, ResolvedMapPlace } from "./place-render.ts";
13
+ /** Memoize the render spec for a resolved place; `null` in → `null` out (nothing to draw). */
14
+ export declare function useMapPlaceRender(place: ResolvedMapPlace | null | undefined): MapPlaceRenderSpec | null;
15
+ //# sourceMappingURL=useMapPlaceRender.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMapPlaceRender.d.ts","sourceRoot":"","sources":["../../map/useMapPlaceRender.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAE7E,8FAA8F;AAC9F,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,GAAG,kBAAkB,GAAG,IAAI,CAEvG"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * `useMapPlaceRender` — the thin React memo wrapper over the pure {@link computeMapPlaceRenderSpec}. It
7
+ * recomputes the render spec only when the resolved place changes, so the declarative overlays render a
8
+ * stable spec object. The MATH lives in `place-render.ts` (pure, node-tested); this file adds nothing
9
+ * but memoization, so it stays trivially correct. A `null` place (no result yet, or a result with no
10
+ * candidate) yields `null` — the overlays render nothing.
11
+ */
12
+ import { useMemo } from "react";
13
+ import { computeMapPlaceRenderSpec } from "./place-render.js";
14
+ /** Memoize the render spec for a resolved place; `null` in → `null` out (nothing to draw). */
15
+ export function useMapPlaceRender(place) {
16
+ return useMemo(() => (place ? computeMapPlaceRenderSpec(place) : null), [place]);
17
+ }
18
+ //# sourceMappingURL=useMapPlaceRender.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMapPlaceRender.js","sourceRoot":"","sources":["../../map/useMapPlaceRender.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAE/B,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAA;AAG7D,8FAA8F;AAC9F,MAAM,UAAU,iBAAiB,CAAC,KAA0C;IAC3E,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,yBAAyB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;AACjF,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * `usePlaceAutocomplete` — the headless "Did you mean" combobox lifted from the demo god component
7
+ * (`_app.tsx:820-900`). It walks the host-supplied `autocomplete` over the locality segment the visitor
8
+ * is typing (the text after the last comma), owns the suggestion list + keyboard-highlighted active
9
+ * descendant, and rewrites the input on pick (replacing just that segment). No FST, no fetch of its own
10
+ * — the `autocomplete` fetcher is INJECTED (the package never imports `@mailwoman/resolver-wof-sqlite`),
11
+ * so this stays node-safe and testable with a synchronous fake.
12
+ *
13
+ * Returns the `<input>` aria/combobox props to spread onto the reused {@link QueryForm} input plus an
14
+ * `onInputKeyDown` (↑/↓ move the highlight, Enter accepts it AND suppresses submit, Esc dismisses), and
15
+ * the presentational {@link PlaceAutocomplete} listbox renders `suggestions` / `activeIndex`.
16
+ */
17
+ import { type KeyboardEvent } from "react";
18
+ import type { Suggestion } from "./types.ts";
19
+ export interface UsePlaceAutocompleteOptions {
20
+ /** The current input text. */
21
+ text: string;
22
+ /** Setter for the input text (a pick rewrites the last-comma segment). */
23
+ setText: (text: string) => void;
24
+ /** The host's autocomplete fetcher (FST prefix-walk). Absent → the combobox is inert. */
25
+ autocomplete?: (query: string) => Promise<Suggestion[]>;
26
+ /** Minimum query length before suggesting. @default 2 */
27
+ minChars?: number;
28
+ /** Debounce before firing the fetcher. @default 150 */
29
+ debounceMs?: number;
30
+ }
31
+ /** The combobox aria props to spread onto the input the suggestions describe. */
32
+ export interface AutocompleteInputProps {
33
+ role: "combobox";
34
+ "aria-expanded": boolean;
35
+ "aria-controls": string;
36
+ "aria-autocomplete": "list";
37
+ "aria-activedescendant": string | undefined;
38
+ autoComplete: "off";
39
+ }
40
+ export interface UsePlaceAutocomplete {
41
+ /** The current suggestions (empty when nothing matches — the listbox then hides). */
42
+ suggestions: Suggestion[];
43
+ /** The keyboard-highlighted suggestion index; `-1` when none is highlighted. */
44
+ activeIndex: number;
45
+ /** Set the highlighted index (the listbox calls this on mouse-enter). */
46
+ setActiveIndex: (index: number) => void;
47
+ /** Keydown handler for the input: ↑/↓ highlight, Enter accepts (+ suppresses submit), Esc dismisses. */
48
+ onInputKeyDown: (event: KeyboardEvent<HTMLInputElement>) => void;
49
+ /** Accept a suggestion by value (rewrites the last-comma segment, closes the list). */
50
+ pick: (value: string) => void;
51
+ /** Close the list without picking. */
52
+ dismiss: () => void;
53
+ /** Aria/combobox props for the input the suggestions describe. */
54
+ inputProps: AutocompleteInputProps;
55
+ /** The listbox element id (matches `inputProps["aria-controls"]`). */
56
+ listboxId: string;
57
+ /** Build the option element id for suggestion `index`. */
58
+ optionId: (index: number) => string;
59
+ }
60
+ export declare function usePlaceAutocomplete({ text, setText, autocomplete, minChars, debounceMs, }: UsePlaceAutocompleteOptions): UsePlaceAutocomplete;
61
+ //# sourceMappingURL=usePlaceAutocomplete.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePlaceAutocomplete.d.ts","sourceRoot":"","sources":["../../map/usePlaceAutocomplete.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,KAAK,aAAa,EAA4C,MAAM,OAAO,CAAA;AAGpF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C,MAAM,WAAW,2BAA2B;IAC3C,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,0EAA0E;IAC1E,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,yFAAyF;IACzF,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC,CAAA;IACvD,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,iFAAiF;AACjF,MAAM,WAAW,sBAAsB;IACtC,IAAI,EAAE,UAAU,CAAA;IAChB,eAAe,EAAE,OAAO,CAAA;IACxB,eAAe,EAAE,MAAM,CAAA;IACvB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,uBAAuB,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3C,YAAY,EAAE,KAAK,CAAA;CACnB;AAED,MAAM,WAAW,oBAAoB;IACpC,qFAAqF;IACrF,WAAW,EAAE,UAAU,EAAE,CAAA;IACzB,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,wGAAwG;IACxG,cAAc,EAAE,CAAC,KAAK,EAAE,aAAa,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAA;IAChE,uFAAuF;IACvF,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7B,sCAAsC;IACtC,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,kEAAkE;IAClE,UAAU,EAAE,sBAAsB,CAAA;IAClC,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAA;IACjB,0DAA0D;IAC1D,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;CACnC;AAeD,wBAAgB,oBAAoB,CAAC,EACpC,IAAI,EACJ,OAAO,EACP,YAAY,EACZ,QAAY,EACZ,UAAgB,GAChB,EAAE,2BAA2B,GAAG,oBAAoB,CA6GpD"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * `usePlaceAutocomplete` — the headless "Did you mean" combobox lifted from the demo god component
7
+ * (`_app.tsx:820-900`). It walks the host-supplied `autocomplete` over the locality segment the visitor
8
+ * is typing (the text after the last comma), owns the suggestion list + keyboard-highlighted active
9
+ * descendant, and rewrites the input on pick (replacing just that segment). No FST, no fetch of its own
10
+ * — the `autocomplete` fetcher is INJECTED (the package never imports `@mailwoman/resolver-wof-sqlite`),
11
+ * so this stays node-safe and testable with a synchronous fake.
12
+ *
13
+ * Returns the `<input>` aria/combobox props to spread onto the reused {@link QueryForm} input plus an
14
+ * `onInputKeyDown` (↑/↓ move the highlight, Enter accepts it AND suppresses submit, Esc dismisses), and
15
+ * the presentational {@link PlaceAutocomplete} listbox renders `suggestions` / `activeIndex`.
16
+ */
17
+ import { useCallback, useEffect, useRef, useState } from "react";
18
+ import { useDebouncedValue } from "../common/useDebouncedValue.js";
19
+ const LISTBOX_ID = "mw-demo-suggest-list";
20
+ const optionId = (index) => `mw-demo-suggest-${index}`;
21
+ /** Extract the locality segment being typed — the text after the last comma, trimmed. */
22
+ function localitySegment(text) {
23
+ return (text.includes(",") ? text.slice(text.lastIndexOf(",") + 1) : text).trim();
24
+ }
25
+ /** Replace the locality segment (after the last comma) with `name`, preserving the address prefix. */
26
+ function replaceSegment(current, name) {
27
+ return current.includes(",") ? `${current.slice(0, current.lastIndexOf(",") + 1)} ${name}` : name;
28
+ }
29
+ export function usePlaceAutocomplete({ text, setText, autocomplete, minChars = 2, debounceMs = 150, }) {
30
+ const [suggestions, setSuggestions] = useState([]);
31
+ const [activeIndex, setActiveIndex] = useState(-1);
32
+ // One-shot guard: a pick rewrites `text` to the chosen name, which would otherwise re-trigger the fetch and
33
+ // immediately re-suggest the place just chosen. Set on pick, consumed by the next effect run.
34
+ const suppressRef = useRef(false);
35
+ const query = localitySegment(text);
36
+ const debouncedQuery = useDebouncedValue(query, debounceMs);
37
+ useEffect(() => {
38
+ if (suppressRef.current) {
39
+ suppressRef.current = false;
40
+ setSuggestions([]);
41
+ setActiveIndex(-1);
42
+ return;
43
+ }
44
+ if (!autocomplete || debouncedQuery.length < minChars || /^\d/.test(debouncedQuery)) {
45
+ setSuggestions([]);
46
+ setActiveIndex(-1);
47
+ return;
48
+ }
49
+ let cancelled = false;
50
+ void (async () => {
51
+ try {
52
+ const next = await autocomplete(debouncedQuery);
53
+ if (cancelled)
54
+ return;
55
+ setSuggestions(next);
56
+ setActiveIndex(-1);
57
+ }
58
+ catch {
59
+ if (cancelled)
60
+ return;
61
+ setSuggestions([]);
62
+ setActiveIndex(-1);
63
+ }
64
+ })();
65
+ return () => {
66
+ cancelled = true;
67
+ };
68
+ }, [debouncedQuery, autocomplete, minChars]);
69
+ const pick = useCallback((value) => {
70
+ suppressRef.current = true;
71
+ setText(replaceSegment(text, value));
72
+ setSuggestions([]);
73
+ setActiveIndex(-1);
74
+ }, [text, setText]);
75
+ const dismiss = useCallback(() => {
76
+ setSuggestions([]);
77
+ setActiveIndex(-1);
78
+ }, []);
79
+ const onInputKeyDown = useCallback((event) => {
80
+ if (suggestions.length === 0)
81
+ return;
82
+ switch (event.key) {
83
+ case "ArrowDown":
84
+ event.preventDefault();
85
+ setActiveIndex((i) => Math.min(i + 1, suggestions.length - 1));
86
+ break;
87
+ case "ArrowUp":
88
+ event.preventDefault();
89
+ setActiveIndex((i) => Math.max(i - 1, 0));
90
+ break;
91
+ case "Enter":
92
+ if (activeIndex >= 0 && activeIndex < suggestions.length) {
93
+ event.preventDefault();
94
+ pick(suggestions[activeIndex].value);
95
+ }
96
+ break;
97
+ case "Escape":
98
+ event.preventDefault();
99
+ dismiss();
100
+ break;
101
+ }
102
+ }, [suggestions, activeIndex, pick, dismiss]);
103
+ return {
104
+ suggestions,
105
+ activeIndex,
106
+ setActiveIndex,
107
+ onInputKeyDown,
108
+ pick,
109
+ dismiss,
110
+ inputProps: {
111
+ role: "combobox",
112
+ "aria-expanded": suggestions.length > 0,
113
+ "aria-controls": LISTBOX_ID,
114
+ "aria-autocomplete": "list",
115
+ "aria-activedescendant": activeIndex >= 0 ? optionId(activeIndex) : undefined,
116
+ autoComplete: "off",
117
+ },
118
+ listboxId: LISTBOX_ID,
119
+ optionId,
120
+ };
121
+ }
122
+ //# sourceMappingURL=usePlaceAutocomplete.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePlaceAutocomplete.js","sourceRoot":"","sources":["../../map/usePlaceAutocomplete.ts"],"names":[],"mappings":"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"}