@mailwoman/react 8.0.0 → 8.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/common/ClientOnly.tsx +6 -2
- package/common/CopyButton.tsx +9 -3
- package/common/KindBadge.tsx +1 -1
- package/common/LoadingIndicator.tsx +22 -7
- package/common/PresetChips.tsx +6 -2
- package/common/cx.ts +3 -1
- package/common/useClipboard.ts +7 -2
- package/index.ts +5 -0
- package/map/BackendControl.tsx +12 -4
- package/map/CompareToggle.tsx +27 -9
- package/map/DemoControls.tsx +31 -11
- package/map/DemoMap.tsx +33 -11
- package/map/GeocoderDemo.tsx +25 -8
- package/map/OverlayLayers.tsx +7 -3
- package/map/PlaceAutocomplete.tsx +25 -9
- package/map/PlaceMarker.tsx +18 -6
- package/map/ResolvedPlaceLayers.tsx +15 -5
- package/map/ResultCamera.tsx +6 -2
- package/map/ResultOverlay.tsx +18 -6
- package/map/ResultPanel.tsx +21 -7
- package/map/VersionPicker.tsx +18 -6
- package/map/geometry.ts +28 -8
- package/map/index.ts +3 -0
- package/map/place-render.ts +36 -12
- package/map/types.ts +132 -44
- package/map/useCompareState.ts +15 -5
- package/map/useDemoGeocode.ts +12 -4
- package/map/useMapPlaceRender.ts +3 -1
- package/map/usePlaceAutocomplete.ts +52 -18
- package/out/common/ClientOnly.d.ts +6 -2
- package/out/common/ClientOnly.d.ts.map +1 -1
- package/out/common/ClientOnly.js.map +1 -1
- package/out/common/CopyButton.d.ts +9 -3
- package/out/common/CopyButton.d.ts.map +1 -1
- package/out/common/CopyButton.js.map +1 -1
- package/out/common/KindBadge.js +1 -1
- package/out/common/KindBadge.js.map +1 -1
- package/out/common/LoadingIndicator.d.ts +21 -6
- package/out/common/LoadingIndicator.d.ts.map +1 -1
- package/out/common/LoadingIndicator.js +4 -1
- package/out/common/LoadingIndicator.js.map +1 -1
- package/out/common/PresetChips.d.ts +6 -2
- package/out/common/PresetChips.d.ts.map +1 -1
- package/out/common/PresetChips.js.map +1 -1
- package/out/common/cx.d.ts +3 -1
- package/out/common/cx.d.ts.map +1 -1
- package/out/common/cx.js +3 -1
- package/out/common/cx.js.map +1 -1
- package/out/common/useClipboard.d.ts +3 -1
- package/out/common/useClipboard.d.ts.map +1 -1
- package/out/common/useClipboard.js +6 -2
- package/out/common/useClipboard.js.map +1 -1
- package/out/index.d.ts.map +1 -1
- package/out/index.js.map +1 -1
- package/out/map/BackendControl.d.ts +12 -4
- package/out/map/BackendControl.d.ts.map +1 -1
- package/out/map/BackendControl.js +3 -1
- package/out/map/BackendControl.js.map +1 -1
- package/out/map/CompareToggle.d.ts +27 -9
- package/out/map/CompareToggle.d.ts.map +1 -1
- package/out/map/CompareToggle.js +3 -1
- package/out/map/CompareToggle.js.map +1 -1
- package/out/map/DemoControls.d.ts +30 -10
- package/out/map/DemoControls.d.ts.map +1 -1
- package/out/map/DemoControls.js +4 -2
- package/out/map/DemoControls.js.map +1 -1
- package/out/map/DemoMap.d.ts +33 -11
- package/out/map/DemoMap.d.ts.map +1 -1
- package/out/map/DemoMap.js.map +1 -1
- package/out/map/GeocoderDemo.d.ts +16 -6
- package/out/map/GeocoderDemo.d.ts.map +1 -1
- package/out/map/GeocoderDemo.js +9 -3
- package/out/map/GeocoderDemo.js.map +1 -1
- package/out/map/OverlayLayers.d.ts +6 -2
- package/out/map/OverlayLayers.d.ts.map +1 -1
- package/out/map/OverlayLayers.js +4 -2
- package/out/map/OverlayLayers.js.map +1 -1
- package/out/map/PlaceAutocomplete.d.ts +24 -8
- package/out/map/PlaceAutocomplete.d.ts.map +1 -1
- package/out/map/PlaceAutocomplete.js +4 -2
- package/out/map/PlaceAutocomplete.js.map +1 -1
- package/out/map/PlaceMarker.d.ts +15 -5
- package/out/map/PlaceMarker.d.ts.map +1 -1
- package/out/map/PlaceMarker.js +6 -2
- package/out/map/PlaceMarker.js.map +1 -1
- package/out/map/ResolvedPlaceLayers.d.ts +15 -5
- package/out/map/ResolvedPlaceLayers.d.ts.map +1 -1
- package/out/map/ResolvedPlaceLayers.js +3 -1
- package/out/map/ResolvedPlaceLayers.js.map +1 -1
- package/out/map/ResultCamera.d.ts +6 -2
- package/out/map/ResultCamera.d.ts.map +1 -1
- package/out/map/ResultCamera.js +3 -1
- package/out/map/ResultCamera.js.map +1 -1
- package/out/map/ResultOverlay.d.ts +15 -5
- package/out/map/ResultOverlay.d.ts.map +1 -1
- package/out/map/ResultOverlay.js +3 -1
- package/out/map/ResultOverlay.js.map +1 -1
- package/out/map/ResultPanel.d.ts +21 -7
- package/out/map/ResultPanel.d.ts.map +1 -1
- package/out/map/ResultPanel.js +3 -1
- package/out/map/ResultPanel.js.map +1 -1
- package/out/map/VersionPicker.d.ts +18 -6
- package/out/map/VersionPicker.d.ts.map +1 -1
- package/out/map/VersionPicker.js +3 -1
- package/out/map/VersionPicker.js.map +1 -1
- package/out/map/geometry.d.ts +12 -4
- package/out/map/geometry.d.ts.map +1 -1
- package/out/map/geometry.js +17 -5
- package/out/map/geometry.js.map +1 -1
- package/out/map/index.d.ts.map +1 -1
- package/out/map/index.js.map +1 -1
- package/out/map/place-render.d.ts +27 -9
- package/out/map/place-render.d.ts.map +1 -1
- package/out/map/place-render.js +9 -3
- package/out/map/place-render.js.map +1 -1
- package/out/map/types.d.ts +132 -44
- package/out/map/types.d.ts.map +1 -1
- package/out/map/useCompareState.d.ts +15 -5
- package/out/map/useCompareState.d.ts.map +1 -1
- package/out/map/useCompareState.js.map +1 -1
- package/out/map/useDemoGeocode.d.ts +12 -4
- package/out/map/useDemoGeocode.d.ts.map +1 -1
- package/out/map/useDemoGeocode.js.map +1 -1
- package/out/map/useMapPlaceRender.d.ts +3 -1
- package/out/map/useMapPlaceRender.d.ts.map +1 -1
- package/out/map/useMapPlaceRender.js +3 -1
- package/out/map/useMapPlaceRender.js.map +1 -1
- package/out/map/usePlaceAutocomplete.d.ts +45 -15
- package/out/map/usePlaceAutocomplete.d.ts.map +1 -1
- package/out/map/usePlaceAutocomplete.js +7 -3
- package/out/map/usePlaceAutocomplete.js.map +1 -1
- package/out/pipeline/ConfidenceCell.d.ts.map +1 -1
- package/out/pipeline/ConfidenceCell.js +9 -1
- package/out/pipeline/ConfidenceCell.js.map +1 -1
- package/out/pipeline/PipelineExplorer.d.ts +12 -4
- package/out/pipeline/PipelineExplorer.d.ts.map +1 -1
- package/out/pipeline/PipelineExplorer.js +6 -2
- package/out/pipeline/PipelineExplorer.js.map +1 -1
- package/out/pipeline/QueryForm.d.ts +6 -2
- package/out/pipeline/QueryForm.d.ts.map +1 -1
- package/out/pipeline/QueryForm.js +1 -1
- package/out/pipeline/QueryForm.js.map +1 -1
- package/out/pipeline/ResolvedPlace.js +1 -1
- package/out/pipeline/ResolvedPlace.js.map +1 -1
- package/out/pipeline/presets.d.ts +7 -0
- package/out/pipeline/presets.d.ts.map +1 -1
- package/out/pipeline/presets.js +7 -0
- package/out/pipeline/presets.js.map +1 -1
- package/out/pipeline/types.d.ts +60 -20
- package/out/pipeline/types.d.ts.map +1 -1
- package/out/pipeline/useParsePipeline.d.ts +12 -4
- package/out/pipeline/useParsePipeline.d.ts.map +1 -1
- package/out/pipeline/useParsePipeline.js +2 -2
- package/out/pipeline/useParsePipeline.js.map +1 -1
- package/out/poi/LiveResultsBlock.d.ts +6 -2
- package/out/poi/LiveResultsBlock.d.ts.map +1 -1
- package/out/poi/LiveResultsBlock.js +1 -1
- package/out/poi/LiveResultsBlock.js.map +1 -1
- package/out/poi/POIExplorer.d.ts +12 -4
- package/out/poi/POIExplorer.d.ts.map +1 -1
- package/out/poi/POIExplorer.js.map +1 -1
- package/out/poi/SubjectPanel.d.ts.map +1 -1
- package/out/poi/SubjectPanel.js +4 -2
- package/out/poi/SubjectPanel.js.map +1 -1
- package/out/poi/runtime.d.ts +9 -2
- package/out/poi/runtime.d.ts.map +1 -1
- package/out/poi/runtime.js +10 -3
- package/out/poi/runtime.js.map +1 -1
- package/out/poi/types.d.ts +40 -15
- package/out/poi/types.d.ts.map +1 -1
- package/out/poi/usePOISearch.d.ts +27 -9
- package/out/poi/usePOISearch.d.ts.map +1 -1
- package/out/poi/usePOISearch.js +5 -3
- package/out/poi/usePOISearch.js.map +1 -1
- package/out/runtime/useDemoRuntime.d.ts +81 -27
- package/out/runtime/useDemoRuntime.d.ts.map +1 -1
- package/out/runtime/useDemoRuntime.js.map +1 -1
- package/package.json +4 -4
- package/pipeline/ConfidenceCell.tsx +11 -1
- package/pipeline/PipelineExplorer.tsx +19 -6
- package/pipeline/QueryForm.tsx +7 -3
- package/pipeline/ResolvedPlace.tsx +2 -2
- package/pipeline/presets.ts +7 -0
- package/pipeline/types.ts +60 -20
- package/pipeline/useParsePipeline.ts +14 -6
- package/poi/LiveResultsBlock.tsx +7 -3
- package/poi/POIExplorer.tsx +14 -4
- package/poi/SubjectPanel.tsx +4 -2
- package/poi/runtime.ts +11 -3
- package/poi/types.ts +41 -15
- package/poi/usePOISearch.ts +34 -12
- package/runtime/useDemoRuntime.ts +82 -27
package/pipeline/types.ts
CHANGED
|
@@ -13,7 +13,9 @@ import type { ReactNode } from "react"
|
|
|
13
13
|
|
|
14
14
|
import type { KindBadgeResult } from "../common/KindBadge.tsx"
|
|
15
15
|
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* One decoded component, as the table + span views render it. Offsets index into `ParseResult.input`.
|
|
18
|
+
*/
|
|
17
19
|
export interface ParsedComponent {
|
|
18
20
|
tag: string
|
|
19
21
|
value?: unknown
|
|
@@ -22,7 +24,9 @@ export interface ParsedComponent {
|
|
|
22
24
|
end?: number
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* A resolved gazetteer place, projected to what the "Resolved place" panel + copy payload need.
|
|
29
|
+
*/
|
|
26
30
|
export interface ResolvedPlaceView {
|
|
27
31
|
id: number
|
|
28
32
|
name: string
|
|
@@ -32,7 +36,9 @@ export interface ResolvedPlaceView {
|
|
|
32
36
|
score: number
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* An additional admin role a resolved place also fulfils (the dual-role / city-state relation, #402).
|
|
41
|
+
*/
|
|
36
42
|
export interface DualRoleView {
|
|
37
43
|
id: number
|
|
38
44
|
name: string
|
|
@@ -41,14 +47,18 @@ export interface DualRoleView {
|
|
|
41
47
|
role: string
|
|
42
48
|
}
|
|
43
49
|
|
|
44
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* Per-stage wall-clock (ms) for one parse. `resolve` is absent when the lookup is skipped.
|
|
52
|
+
*/
|
|
45
53
|
export interface StageTiming {
|
|
46
54
|
shape: number
|
|
47
55
|
classify: number
|
|
48
56
|
resolve?: number
|
|
49
57
|
}
|
|
50
58
|
|
|
51
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* FST gazetteer-prior provenance, as the "FST prior" disclosure renders it.
|
|
61
|
+
*/
|
|
52
62
|
export interface FSTProvenance {
|
|
53
63
|
builtAt: string
|
|
54
64
|
stateCount: number
|
|
@@ -56,10 +66,14 @@ export interface FSTProvenance {
|
|
|
56
66
|
importanceMatches: number
|
|
57
67
|
}
|
|
58
68
|
|
|
59
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* The presentational projection of one parse+resolve. The host's `runParse` produces it.
|
|
71
|
+
*/
|
|
60
72
|
export interface ParseResult {
|
|
61
73
|
input: string
|
|
62
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* Opaque hierarchy — handed straight to a host-injected tree/visualizer panel.
|
|
76
|
+
*/
|
|
63
77
|
tree: unknown
|
|
64
78
|
nodes: ParsedComponent[]
|
|
65
79
|
kindResult?: KindBadgeResult
|
|
@@ -71,7 +85,9 @@ export interface ParseResult {
|
|
|
71
85
|
dualRoles?: DualRoleView[]
|
|
72
86
|
}
|
|
73
87
|
|
|
74
|
-
/**
|
|
88
|
+
/**
|
|
89
|
+
* Bundle-load progress surfaced before the runtime is `ready`.
|
|
90
|
+
*/
|
|
75
91
|
export interface PipelineLoadingState {
|
|
76
92
|
progress?: string
|
|
77
93
|
stepLabels: string[]
|
|
@@ -83,15 +99,25 @@ export interface PipelineLoadingState {
|
|
|
83
99
|
* progress + errors. This package never imports the model or gazetteer — it only calls this contract.
|
|
84
100
|
*/
|
|
85
101
|
export interface PipelineRuntime {
|
|
86
|
-
/**
|
|
102
|
+
/**
|
|
103
|
+
* Whether the model/gazetteer bundle is ready to parse.
|
|
104
|
+
*/
|
|
87
105
|
ready: boolean
|
|
88
|
-
/**
|
|
106
|
+
/**
|
|
107
|
+
* Execute a full parse+resolve, reporting stage progress via `onStage` (0-based index into `parseStageLabels`).
|
|
108
|
+
*/
|
|
89
109
|
runParse: (input: string, hooks: { onStage: (stage: number) => void }) => Promise<ParseResult>
|
|
90
|
-
/**
|
|
110
|
+
/**
|
|
111
|
+
* Per-parse progress labels (differ by whether a gazetteer lookup is wired).
|
|
112
|
+
*/
|
|
91
113
|
parseStageLabels: string[]
|
|
92
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* Bundle-load progress (before `ready`).
|
|
116
|
+
*/
|
|
93
117
|
loading?: PipelineLoadingState | null
|
|
94
|
-
/**
|
|
118
|
+
/**
|
|
119
|
+
* Error surfaced by the bundle load, distinct from a per-parse error.
|
|
120
|
+
*/
|
|
95
121
|
errorMessage?: string | null
|
|
96
122
|
}
|
|
97
123
|
|
|
@@ -101,18 +127,32 @@ export interface PipelineRuntime {
|
|
|
101
127
|
* components nor their heavy data types.
|
|
102
128
|
*/
|
|
103
129
|
export interface PipelinePanels {
|
|
104
|
-
/**
|
|
130
|
+
/**
|
|
131
|
+
* Rendered above the form (e.g. the docs "About this demo").
|
|
132
|
+
*/
|
|
105
133
|
header?: ReactNode
|
|
106
|
-
/**
|
|
134
|
+
/**
|
|
135
|
+
* Rendered below everything (e.g. a guided tour).
|
|
136
|
+
*/
|
|
107
137
|
footer?: ReactNode
|
|
108
|
-
/**
|
|
138
|
+
/**
|
|
139
|
+
* Model-version selector, wired to the host's bundle state.
|
|
140
|
+
*/
|
|
109
141
|
versionControl?: ReactNode
|
|
110
|
-
/**
|
|
142
|
+
/**
|
|
143
|
+
* Backend indicator + WASM toggle.
|
|
144
|
+
*/
|
|
111
145
|
backendControl?: ReactNode
|
|
112
|
-
/**
|
|
146
|
+
/**
|
|
147
|
+
* One-line release blurb.
|
|
148
|
+
*/
|
|
113
149
|
releaseInfo?: ReactNode
|
|
114
|
-
/**
|
|
150
|
+
/**
|
|
151
|
+
* Heavy visualizers (span highlight, tree, timing, BIO, …), rendered from the result.
|
|
152
|
+
*/
|
|
115
153
|
extras?: (result: ParseResult) => ReactNode
|
|
116
|
-
/**
|
|
154
|
+
/**
|
|
155
|
+
* Rendered in place of the resolved-place panel when nothing resolved (host's FailureDiagnostic).
|
|
156
|
+
*/
|
|
117
157
|
failure?: (result: ParseResult) => ReactNode
|
|
118
158
|
}
|
|
@@ -22,17 +22,25 @@ export interface UseParsePipeline {
|
|
|
22
22
|
text: string
|
|
23
23
|
setText: (text: string) => void
|
|
24
24
|
busy: boolean
|
|
25
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* 0-based index into `runtime.parseStageLabels`; -1 when idle.
|
|
27
|
+
*/
|
|
26
28
|
parseStage: number
|
|
27
29
|
result: ParseResult | null
|
|
28
30
|
parseError: string | null
|
|
29
31
|
selectedCandidateIndex: number
|
|
30
32
|
selectCandidate: (index: number) => void
|
|
31
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* The currently-selected candidate (falls back to the first), or null.
|
|
35
|
+
*/
|
|
32
36
|
selectedCandidate: ResolvedPlaceView | null
|
|
33
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Run a parse for the current text. Safe to bind to a form's `onSubmit`.
|
|
39
|
+
*/
|
|
34
40
|
submit: () => Promise<void>
|
|
35
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Clear the result (used when a preset replaces the input).
|
|
43
|
+
*/
|
|
36
44
|
reset: () => void
|
|
37
45
|
}
|
|
38
46
|
|
|
@@ -55,8 +63,8 @@ export function useParsePipeline({ runtime, defaultText }: UseParsePipelineOptio
|
|
|
55
63
|
const parsed = await runtime.runParse(text, { onStage: setParseStage })
|
|
56
64
|
setSelectedCandidateIndex(0)
|
|
57
65
|
setResult(parsed)
|
|
58
|
-
} catch (
|
|
59
|
-
setParseError(
|
|
66
|
+
} catch (error) {
|
|
67
|
+
setParseError(error instanceof Error ? error.message : String(error))
|
|
60
68
|
} finally {
|
|
61
69
|
setBusy(false)
|
|
62
70
|
setParseStage(-1)
|
package/poi/LiveResultsBlock.tsx
CHANGED
|
@@ -14,9 +14,13 @@ import { formatDistance } from "./runtime.ts"
|
|
|
14
14
|
import type { LiveSearchState } from "./types.ts"
|
|
15
15
|
|
|
16
16
|
export interface LiveResultsBlockProps {
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Subject label for empty-state copy — a category ("No hospital results near …") or a brand ("No chevron results …").
|
|
19
|
+
*/
|
|
18
20
|
subjectLabel: string
|
|
19
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* The location anchor the search runs against (empty ⇒ the button is disabled with a hint).
|
|
23
|
+
*/
|
|
20
24
|
anchor: string
|
|
21
25
|
state: LiveSearchState
|
|
22
26
|
onSearch: () => void
|
|
@@ -45,7 +49,7 @@ export function LiveResultsBlock({ subjectLabel, anchor, state, onSearch }: Live
|
|
|
45
49
|
) : state.status === "error" ? (
|
|
46
50
|
<p className="mw-error">{state.message}</p>
|
|
47
51
|
) : state.status === "success" ? (
|
|
48
|
-
state.hits.length
|
|
52
|
+
!state.hits.length ? (
|
|
49
53
|
<p className="mw-muted">
|
|
50
54
|
No {subjectLabel.toLowerCase()} results near {state.centerName}.
|
|
51
55
|
</p>
|
package/poi/POIExplorer.tsx
CHANGED
|
@@ -24,13 +24,21 @@ import type { LoadPOIRuntime, POILiveSearch } from "./types.ts"
|
|
|
24
24
|
import { usePOISearch } from "./usePOISearch.ts"
|
|
25
25
|
|
|
26
26
|
export interface POIExplorerProps {
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Query to pre-fill in the input.
|
|
29
|
+
*/
|
|
28
30
|
defaultText?: string
|
|
29
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Example chips. @default the built-in POI presets
|
|
33
|
+
*/
|
|
30
34
|
presets?: ReadonlyArray<Preset>
|
|
31
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* Override the taxonomy-runtime loader (stories/tests inject a mock).
|
|
37
|
+
*/
|
|
32
38
|
loadRuntime?: LoadPOIRuntime
|
|
33
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Live poi.db probe. Absent ⇒ intent-only (no live-results affordance).
|
|
41
|
+
*/
|
|
34
42
|
runLiveSearch?: POILiveSearch
|
|
35
43
|
/**
|
|
36
44
|
* Whether {@link runLiveSearch} can serve BRAND subjects (fetch by QID). Default false: a brand subject shows the
|
|
@@ -53,6 +61,7 @@ function POIExplorerInner({
|
|
|
53
61
|
brandLiveSearch,
|
|
54
62
|
}: POIExplorerInnerProps): ReactNode {
|
|
55
63
|
const [text, setText] = useState(defaultText)
|
|
64
|
+
|
|
56
65
|
const { result, liveSearch, searchLive } = usePOISearch({
|
|
57
66
|
text,
|
|
58
67
|
loadRuntime,
|
|
@@ -61,6 +70,7 @@ function POIExplorerInner({
|
|
|
61
70
|
})
|
|
62
71
|
|
|
63
72
|
const subject = result?.subject
|
|
73
|
+
|
|
64
74
|
// The live block appears whenever the subject COULD be live-searched with an anchor — i.e. capable minus the
|
|
65
75
|
// anchor-present requirement (`canSearchLive` also requires an anchor; the block itself renders the "add an anchor"
|
|
66
76
|
// hint, so it must show one step earlier). Category: not build-local. Brand: brand-capable probe + a QID.
|
package/poi/SubjectPanel.tsx
CHANGED
|
@@ -16,7 +16,9 @@ export interface SubjectPanelProps {
|
|
|
16
16
|
subject: POISubject
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* The shared match-detail list (matched phrase, confidence, anchor) — identical for category + brand subjects.
|
|
21
|
+
*/
|
|
20
22
|
function MatchDetail({ subject }: SubjectPanelProps): ReactNode {
|
|
21
23
|
return (
|
|
22
24
|
<dl className="mw-subject__detail">
|
|
@@ -27,7 +29,7 @@ function MatchDetail({ subject }: SubjectPanelProps): ReactNode {
|
|
|
27
29
|
<dt>confidence</dt>
|
|
28
30
|
<dd>{Math.round(subject.confidence * 100)}%</dd>
|
|
29
31
|
<dt>anchor</dt>
|
|
30
|
-
<dd>{subject.remainder
|
|
32
|
+
<dd>{subject.remainder || <em>none — global query</em>}</dd>
|
|
31
33
|
</dl>
|
|
32
34
|
)
|
|
33
35
|
}
|
package/poi/runtime.ts
CHANGED
|
@@ -30,6 +30,7 @@ export async function loadPOIRuntime(): Promise<POIRuntime> {
|
|
|
30
30
|
import("@mailwoman/poi-taxonomy/data/taxonomy.json").then((m) => m.default),
|
|
31
31
|
import("@mailwoman/poi-taxonomy/data/brands.json").then((m) => m.default),
|
|
32
32
|
])
|
|
33
|
+
|
|
33
34
|
const lookup = createPOITaxonomyLookup(table as unknown as Parameters<typeof createPOITaxonomyLookup>[0])
|
|
34
35
|
const brands = createPOIBrandLookup(brandTable as unknown as Parameters<typeof createPOIBrandLookup>[0])
|
|
35
36
|
|
|
@@ -38,7 +39,7 @@ export async function loadPOIRuntime(): Promise<POIRuntime> {
|
|
|
38
39
|
const lexicon: POIPhraseLookup = (phrase, locale) => {
|
|
39
40
|
const categoryHits = lookup.lookupPOICategory(phrase, locale)
|
|
40
41
|
|
|
41
|
-
if (categoryHits.length
|
|
42
|
+
if (categoryHits.length) {
|
|
42
43
|
return categoryHits.map((match) => ({
|
|
43
44
|
kind: "category",
|
|
44
45
|
categoryID: match.category.id,
|
|
@@ -59,7 +60,9 @@ export async function loadPOIRuntime(): Promise<POIRuntime> {
|
|
|
59
60
|
return { lookup, lexicon, classify: createKindClassifier({ poiLexicon: lexicon }) }
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* Default example queries for the POI explorer — a mix of category, build-local, and chain-brand subjects.
|
|
65
|
+
*/
|
|
63
66
|
export const POI_PRESETS = [
|
|
64
67
|
{ label: "Drinking fountain", value: "drinking fountain near Springfield" },
|
|
65
68
|
{ label: "Fire hydrant", value: "fire hydrant" },
|
|
@@ -68,9 +71,14 @@ export const POI_PRESETS = [
|
|
|
68
71
|
{ label: "Applebee's (brand)", value: "applebee's near Chicago" },
|
|
69
72
|
] as const
|
|
70
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Query the POI explorer opens on — the first preset, so the two stay in step.
|
|
76
|
+
*/
|
|
71
77
|
export const POI_DEFAULT_TEXT = POI_PRESETS[0].value
|
|
72
78
|
|
|
73
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* `742 m` under 1 km, `1.9 km` past it — matches the demo's distance captions.
|
|
81
|
+
*/
|
|
74
82
|
export function formatDistance(distanceM: number): string {
|
|
75
83
|
if (distanceM < 1000) return `${Math.round(distanceM)} m`
|
|
76
84
|
|
package/poi/types.ts
CHANGED
|
@@ -9,33 +9,43 @@
|
|
|
9
9
|
* package's browser graph (it lives in the docs site, which knows where the layer is served).
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import type { POIPhraseLookup, QueryKindResult } from "@mailwoman/kind-classifier"
|
|
13
|
-
import type { createKindClassifier } from "@mailwoman/kind-classifier"
|
|
12
|
+
import type { POIPhraseLookup, QueryKindResult, createKindClassifier } from "@mailwoman/kind-classifier"
|
|
14
13
|
import type { createPOITaxonomyLookup } from "@mailwoman/poi-taxonomy/table"
|
|
15
14
|
|
|
16
15
|
export type TaxonomyLookup = ReturnType<typeof createPOITaxonomyLookup>
|
|
16
|
+
|
|
17
17
|
export type CategoryRecord = NonNullable<ReturnType<TaxonomyLookup["getPOICategory"]>>
|
|
18
18
|
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* The lazily-loaded POI runtime: the taxonomy lookup, the adapted lexicon, and the kind classifier over it.
|
|
21
|
+
*/
|
|
20
22
|
export interface POIRuntime {
|
|
21
23
|
lookup: TaxonomyLookup
|
|
22
24
|
lexicon: POIPhraseLookup
|
|
23
25
|
classify: ReturnType<typeof createKindClassifier>
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Fields shared by every resolved POI subject, category or brand.
|
|
30
|
+
*/
|
|
27
31
|
export interface POISubjectBase {
|
|
28
32
|
matchedPhrase: string
|
|
29
33
|
confidence: number
|
|
30
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* The non-subject remainder of the query (the location anchor, e.g. "near Springfield").
|
|
36
|
+
*/
|
|
31
37
|
remainder: string
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* A resolved POI subject that names a taxonomy CATEGORY (`cafe`, `hospital`, `drinking fountain`).
|
|
42
|
+
*/
|
|
35
43
|
export interface POICategorySubject extends POISubjectBase {
|
|
36
44
|
kind: "category"
|
|
37
45
|
category: CategoryRecord
|
|
38
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* Whether this category needs the locally-built OSM (ODbL) layer — precomputed off the runtime.
|
|
48
|
+
*/
|
|
39
49
|
buildLocal: boolean
|
|
40
50
|
}
|
|
41
51
|
|
|
@@ -46,16 +56,24 @@ export interface POICategorySubject extends POISubjectBase {
|
|
|
46
56
|
*/
|
|
47
57
|
export interface POIBrandSubject extends POISubjectBase {
|
|
48
58
|
kind: "brand"
|
|
49
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* The brand's canonical display name.
|
|
61
|
+
*/
|
|
50
62
|
name: string
|
|
51
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* Wikidata QID, when the lexicon carried one (`Q319642` = Chevron). Absent ⇒ matched by name alone.
|
|
65
|
+
*/
|
|
52
66
|
wikidata?: string
|
|
53
67
|
}
|
|
54
68
|
|
|
55
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* A resolved POI subject — a taxonomy category or a chain brand — plus its match metadata.
|
|
71
|
+
*/
|
|
56
72
|
export type POISubject = POICategorySubject | POIBrandSubject
|
|
57
73
|
|
|
58
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* The intent-only result: the kind verdict plus (when a subject was detected) its OverpassQL export (category only).
|
|
76
|
+
*/
|
|
59
77
|
export interface POIExplorerResult {
|
|
60
78
|
kindResult: QueryKindResult
|
|
61
79
|
subject?: POISubject
|
|
@@ -63,7 +81,9 @@ export interface POIExplorerResult {
|
|
|
63
81
|
overpassError?: string
|
|
64
82
|
}
|
|
65
83
|
|
|
66
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* One live poi.db hit, as the results list renders it.
|
|
86
|
+
*/
|
|
67
87
|
export interface POISearchHit {
|
|
68
88
|
name: string
|
|
69
89
|
lat: number
|
|
@@ -97,16 +117,22 @@ export type POILiveSearch = (params: {
|
|
|
97
117
|
categoryID: string
|
|
98
118
|
overtureCategoryIDs: string[]
|
|
99
119
|
anchor: string
|
|
100
|
-
/**
|
|
120
|
+
/**
|
|
121
|
+
* Present when the subject is a chain brand — the probe fetches by this QID, not a category k-ring.
|
|
122
|
+
*/
|
|
101
123
|
brandWikidata?: string
|
|
102
124
|
}) => Promise<POILiveSearchResult>
|
|
103
125
|
|
|
104
|
-
/**
|
|
126
|
+
/**
|
|
127
|
+
* "Search live" state machine.
|
|
128
|
+
*/
|
|
105
129
|
export type LiveSearchState =
|
|
106
130
|
| { status: "idle" }
|
|
107
131
|
| { status: "loading" }
|
|
108
132
|
| { status: "error"; message: string }
|
|
109
133
|
| { status: "success"; hits: POISearchHit[]; centerName: string }
|
|
110
134
|
|
|
111
|
-
/**
|
|
135
|
+
/**
|
|
136
|
+
* A single-argument runtime loader — injectable so stories/tests can substitute a mock taxonomy.
|
|
137
|
+
*/
|
|
112
138
|
export type LoadPOIRuntime = () => Promise<POIRuntime>
|
package/poi/usePOISearch.ts
CHANGED
|
@@ -20,11 +20,17 @@ import { loadPOIRuntime } from "./runtime.ts"
|
|
|
20
20
|
import type { LiveSearchState, LoadPOIRuntime, POIExplorerResult, POILiveSearch, POIRuntime } from "./types.ts"
|
|
21
21
|
|
|
22
22
|
export interface UsePOISearchOptions {
|
|
23
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* The current query text (controlled by the caller).
|
|
25
|
+
*/
|
|
24
26
|
text: string
|
|
25
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Runtime loader. @default loadPOIRuntime
|
|
29
|
+
*/
|
|
26
30
|
loadRuntime?: LoadPOIRuntime
|
|
27
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Injected live-search probe. Absent ⇒ the live-results affordance is disabled.
|
|
33
|
+
*/
|
|
28
34
|
runLiveSearch?: POILiveSearch
|
|
29
35
|
/**
|
|
30
36
|
* Whether the injected probe can serve BRAND subjects (fetch by Wikidata QID). Default false: brand subjects show the
|
|
@@ -33,24 +39,38 @@ export interface UsePOISearchOptions {
|
|
|
33
39
|
* live search is unaffected either way.
|
|
34
40
|
*/
|
|
35
41
|
brandLiveSearch?: boolean
|
|
36
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Debounce before (re)classifying. @default 250
|
|
44
|
+
*/
|
|
37
45
|
debounceMs?: number
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
export interface UsePOISearch {
|
|
41
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* True once the taxonomy runtime has loaded.
|
|
51
|
+
*/
|
|
42
52
|
runtimeReady: boolean
|
|
43
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* The intent result for the current (debounced) query, or null for empty input.
|
|
55
|
+
*/
|
|
44
56
|
result: POIExplorerResult | null
|
|
45
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* State of the on-demand live poi.db search.
|
|
59
|
+
*/
|
|
46
60
|
liveSearch: LiveSearchState
|
|
47
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* Whether a live search can run right now (a probe is wired + there's a resolved subject with an anchor).
|
|
63
|
+
*/
|
|
48
64
|
canSearchLive: boolean
|
|
49
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* Kick off a live search for the current subject. No-op when {@link canSearchLive} is false.
|
|
67
|
+
*/
|
|
50
68
|
searchLive: () => Promise<void>
|
|
51
69
|
}
|
|
52
70
|
|
|
53
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* Compute the OverpassQL export for a matched subject, capturing any emitter error rather than throwing.
|
|
73
|
+
*/
|
|
54
74
|
function buildOverpass(
|
|
55
75
|
runtime: POIRuntime,
|
|
56
76
|
categoryID: string,
|
|
@@ -70,8 +90,8 @@ function buildOverpass(
|
|
|
70
90
|
|
|
71
91
|
try {
|
|
72
92
|
return { overpassQL: emitOverpassQL(intent, category.osmTag ? { osmTag: category.osmTag } : {}) }
|
|
73
|
-
} catch (
|
|
74
|
-
return { overpassError:
|
|
93
|
+
} catch (error) {
|
|
94
|
+
return { overpassError: error instanceof Error ? error.message : String(error) }
|
|
75
95
|
}
|
|
76
96
|
}
|
|
77
97
|
|
|
@@ -184,12 +204,14 @@ export function usePOISearch({
|
|
|
184
204
|
}, [debouncedText, runtime])
|
|
185
205
|
|
|
186
206
|
const subject = result?.subject
|
|
207
|
+
|
|
187
208
|
// A subject is live-searchable when a probe is wired, it has an anchor, and: a CATEGORY that isn't build-local, or a
|
|
188
209
|
// BRAND with a QID AND a brand-capable probe (`brandLiveSearch`). Brands without a QID / without a brand probe show
|
|
189
210
|
// the intent + QID chip but no live affordance.
|
|
190
211
|
const subjectLiveCapable =
|
|
191
212
|
subject !== undefined &&
|
|
192
213
|
(subject.kind === "brand" ? brandLiveSearch && subject.wikidata !== undefined : !subject.buildLocal)
|
|
214
|
+
|
|
193
215
|
const canSearchLive = Boolean(
|
|
194
216
|
runLiveSearch && runtime && subject && subjectLiveCapable && subject.remainder.trim().length > 0
|
|
195
217
|
)
|