@wherabouts/react 0.1.2 → 0.2.1
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/LICENSE +12 -18
- package/README.md +104 -15
- package/dist/index.cjs +408 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +152 -2
- package/dist/index.d.ts +152 -2
- package/dist/index.js +399 -39
- package/dist/index.js.map +1 -1
- package/package.json +9 -8
package/LICENSE
CHANGED
|
@@ -1,21 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
Copyright (c) 2026 Wherabouts. All rights reserved.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This software and associated documentation files (the "SDK") are the proprietary
|
|
4
|
+
property of Wherabouts. The SDK is licensed, not sold, and is made available solely
|
|
5
|
+
for use in connection with the Wherabouts location API by parties holding valid
|
|
6
|
+
Wherabouts API credentials, subject to the Wherabouts Terms of Service.
|
|
4
7
|
|
|
5
|
-
Permission is
|
|
6
|
-
of
|
|
7
|
-
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
8
|
+
Permission is NOT granted to use, copy, modify, merge, publish, distribute,
|
|
9
|
+
sublicense, or sell copies of the SDK except as expressly permitted in writing by
|
|
10
|
+
Wherabouts or as necessary to integrate with the Wherabouts API under a valid account.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
12
|
+
THE SDK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
13
|
+
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
14
|
+
PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL WHERABOUTS BE LIABLE FOR
|
|
15
|
+
ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM THE USE OF THE SDK.
|
package/README.md
CHANGED
|
@@ -36,15 +36,24 @@ yarn add @wherabouts/react @wherabouts/sdk
|
|
|
36
36
|
All hooks accept a `WheraboutsClient` instance as their first argument. Create one with your publishable API key from the [Wherabouts dashboard](https://wherabouts.com/dashboard).
|
|
37
37
|
|
|
38
38
|
```ts
|
|
39
|
-
import {
|
|
39
|
+
import { createWheraboutsClient } from "@wherabouts/sdk";
|
|
40
40
|
|
|
41
|
-
const client =
|
|
41
|
+
const client = createWheraboutsClient({
|
|
42
42
|
apiKey: "pk_live_...", // publishable key — safe in browser
|
|
43
43
|
});
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
> Use a **publishable key** (`pk_live_…`) in browser code. Secret keys must stay server-side only.
|
|
47
47
|
|
|
48
|
+
> **Keep a stable client reference.** Create the client once — at module scope or
|
|
49
|
+
> with `useMemo` — and reuse it. The hooks list `client` in their effect
|
|
50
|
+
> dependencies, so passing a freshly-created client on every render restarts the
|
|
51
|
+
> request (and debounce) each render.
|
|
52
|
+
>
|
|
53
|
+
> ```tsx
|
|
54
|
+
> const client = useMemo(() => createWheraboutsClient({ apiKey }), [apiKey]);
|
|
55
|
+
> ```
|
|
56
|
+
|
|
48
57
|
---
|
|
49
58
|
|
|
50
59
|
## Hooks
|
|
@@ -94,30 +103,84 @@ function useAutocomplete(
|
|
|
94
103
|
|
|
95
104
|
#### `UseAutocompleteOptions`
|
|
96
105
|
|
|
97
|
-
| Option
|
|
98
|
-
|
|
99
|
-
| `debounceMs`
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
106
|
+
| Option | Type | Default | Description |
|
|
107
|
+
|--------------------|---------------------------------|---------|------------------------------------------------------|
|
|
108
|
+
| `debounceMs` | `number` | `300` | Milliseconds to wait after last keystroke |
|
|
109
|
+
| `minLength` | `number` | `2` | Minimum trimmed query length before a request fires (the API requires `q` ≥ 2) |
|
|
110
|
+
| `limit` | `number` | — | Maximum number of suggestions to return |
|
|
111
|
+
| `country` | `string` | — | ISO 3166-1 alpha-2 country code filter (e.g. `"AU"`) |
|
|
112
|
+
| `state` | `string` | — | State or region name filter |
|
|
113
|
+
| `lat` / `lng` | `number` | — | Bias results toward a coordinate (proximity) |
|
|
114
|
+
| `sessionToken` | `string` | — | Groups a run of keystrokes into one billable search (see `newSessionToken()`) |
|
|
115
|
+
| `keepPreviousData` | `boolean` | `false` | Keep previous results visible while the next search loads (avoids dropdown flicker) |
|
|
116
|
+
| `cache` | `{ storage; ttlMs?: number }` | — | Opt-in client cache, e.g. `{ storage: sessionStorage, ttlMs: 60_000 }` |
|
|
103
117
|
|
|
104
118
|
#### `UseAutocompleteResult`
|
|
105
119
|
|
|
106
|
-
| Field
|
|
107
|
-
|
|
108
|
-
| `query`
|
|
109
|
-
| `setQuery`
|
|
110
|
-
| `results`
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
120
|
+
| Field | Type | Description |
|
|
121
|
+
|---------------|------------------------|---------------------------------------------------|
|
|
122
|
+
| `query` | `string` | Current search string |
|
|
123
|
+
| `setQuery` | `(q: string) => void` | Stable setter — safe to pass directly to `onChange` |
|
|
124
|
+
| `results` | `AddressSuggestion[]` | Matching address suggestions |
|
|
125
|
+
| `status` | `"idle" \| "loading" \| "success" \| "empty" \| "error"` | Coarse state machine for rendering |
|
|
126
|
+
| `loading` | `boolean` | `true` while a request is in flight |
|
|
127
|
+
| `rateLimited` | `boolean` | `true` when the last request was rejected with HTTP 429 |
|
|
128
|
+
| `error` | `Error \| null` | Last error, or `null` |
|
|
129
|
+
| `reset` | `() => void` | Clear the query, results, and any in-flight request |
|
|
113
130
|
|
|
114
131
|
**Behaviour notes:**
|
|
132
|
+
- Queries shorter than `minLength` (default 2) never fire a request.
|
|
115
133
|
- Empty or whitespace-only query immediately clears results without firing a request.
|
|
116
134
|
- Each new keystroke cancels the previous pending request via `AbortController`.
|
|
117
135
|
- `setQuery` is memoised with `useCallback` — it will not cause unnecessary re-renders.
|
|
118
136
|
|
|
119
137
|
---
|
|
120
138
|
|
|
139
|
+
### `useCombobox`
|
|
140
|
+
|
|
141
|
+
Headless [WAI-ARIA combobox](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/)
|
|
142
|
+
helpers for an accessible autocomplete dropdown — keyboard navigation
|
|
143
|
+
(↑/↓/Home/End/Enter/Esc, with wrapping) and ARIA wiring. Bring your own markup;
|
|
144
|
+
pair it with `useAutocomplete`.
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
import { useAutocomplete, useCombobox } from "@wherabouts/react";
|
|
148
|
+
|
|
149
|
+
function AddressCombobox({ client }) {
|
|
150
|
+
const { query, setQuery, results } = useAutocomplete(client);
|
|
151
|
+
const { getInputProps, getListboxProps, getItemProps, activeIndex } =
|
|
152
|
+
useCombobox({
|
|
153
|
+
id: "address",
|
|
154
|
+
count: results.length,
|
|
155
|
+
onSelect: (i) => setQuery(results[i]?.formattedAddress ?? ""),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<>
|
|
160
|
+
<input
|
|
161
|
+
{...getInputProps()}
|
|
162
|
+
value={query}
|
|
163
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
164
|
+
/>
|
|
165
|
+
<ul {...getListboxProps()}>
|
|
166
|
+
{results.map((r, i) => (
|
|
167
|
+
<li key={r.id} {...getItemProps(i)} data-active={i === activeIndex}>
|
|
168
|
+
{r.formattedAddress}
|
|
169
|
+
</li>
|
|
170
|
+
))}
|
|
171
|
+
</ul>
|
|
172
|
+
</>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`getInputProps()` / `getListboxProps()` / `getItemProps(index)` return the
|
|
178
|
+
`role`, `aria-*`, and event handlers for each element. The pure building blocks
|
|
179
|
+
(`comboboxReducer`, `keyToAction`, `buildInputProps`/`buildListboxProps`/`buildItemProps`)
|
|
180
|
+
are also exported for advanced use.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
121
184
|
### `useReverseGeocode`
|
|
122
185
|
|
|
123
186
|
Resolves a latitude/longitude coordinate to the nearest address. Pass `null` to reset.
|
|
@@ -229,6 +292,32 @@ function useZoneContains(
|
|
|
229
292
|
|
|
230
293
|
---
|
|
231
294
|
|
|
295
|
+
### Routing hooks — `useDirections` / `useMatrix` / `useIsochrone`
|
|
296
|
+
|
|
297
|
+
Fetch routing results that re-run when their params change and abort the previous
|
|
298
|
+
request. Pass `null` params to stay idle. Each returns `{ data, loading, error }`.
|
|
299
|
+
|
|
300
|
+
```tsx
|
|
301
|
+
import { useDirections } from "@wherabouts/react";
|
|
302
|
+
|
|
303
|
+
function Route({ client }) {
|
|
304
|
+
const { data, loading, error } = useDirections(client, {
|
|
305
|
+
from: "-33.865,151.209",
|
|
306
|
+
to: "-33.8,151.0",
|
|
307
|
+
profile: "driving", // "driving" | "walking" | "cycling"
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
if (loading) return <p>Routing…</p>;
|
|
311
|
+
if (error) return <p>Error: {error.message}</p>;
|
|
312
|
+
return <p>{data ? `${data.distance_m} m, ${data.duration_s} s` : null}</p>;
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
- `useMatrix(client, { sources, destinations, profile? } | null)` — duration/distance matrix.
|
|
317
|
+
- `useIsochrone(client, { origin, durationSeconds | distanceMeters, profile? } | null)` — reachability polygon.
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
232
321
|
## Using the browser Geolocation API
|
|
233
322
|
|
|
234
323
|
A common pattern — combine the browser's `navigator.geolocation` with these hooks:
|
package/dist/index.cjs
CHANGED
|
@@ -1,66 +1,361 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var react = require('react');
|
|
4
|
+
var sdk = require('@wherabouts/sdk');
|
|
5
|
+
|
|
6
|
+
// src/combobox.ts
|
|
7
|
+
var INITIAL_COMBOBOX_STATE = {
|
|
8
|
+
isOpen: false,
|
|
9
|
+
activeIndex: -1
|
|
10
|
+
};
|
|
11
|
+
function comboboxReducer(state, action) {
|
|
12
|
+
switch (action.type) {
|
|
13
|
+
case "next": {
|
|
14
|
+
if (action.count === 0) {
|
|
15
|
+
return state;
|
|
16
|
+
}
|
|
17
|
+
const activeIndex = state.activeIndex >= action.count - 1 ? 0 : state.activeIndex + 1;
|
|
18
|
+
return { isOpen: true, activeIndex };
|
|
19
|
+
}
|
|
20
|
+
case "prev": {
|
|
21
|
+
if (action.count === 0) {
|
|
22
|
+
return state;
|
|
23
|
+
}
|
|
24
|
+
const activeIndex = state.activeIndex <= 0 ? action.count - 1 : state.activeIndex - 1;
|
|
25
|
+
return { isOpen: true, activeIndex };
|
|
26
|
+
}
|
|
27
|
+
case "first":
|
|
28
|
+
return action.count === 0 ? state : { isOpen: true, activeIndex: 0 };
|
|
29
|
+
case "last":
|
|
30
|
+
return action.count === 0 ? state : { isOpen: true, activeIndex: action.count - 1 };
|
|
31
|
+
case "set":
|
|
32
|
+
return { isOpen: true, activeIndex: action.index };
|
|
33
|
+
case "open":
|
|
34
|
+
return { ...state, isOpen: true };
|
|
35
|
+
case "close":
|
|
36
|
+
case "select":
|
|
37
|
+
return INITIAL_COMBOBOX_STATE;
|
|
38
|
+
default:
|
|
39
|
+
return INITIAL_COMBOBOX_STATE;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function keyToAction(key, count) {
|
|
43
|
+
switch (key) {
|
|
44
|
+
case "ArrowDown":
|
|
45
|
+
return { type: "next", count };
|
|
46
|
+
case "ArrowUp":
|
|
47
|
+
return { type: "prev", count };
|
|
48
|
+
case "Home":
|
|
49
|
+
return { type: "first", count };
|
|
50
|
+
case "End":
|
|
51
|
+
return { type: "last", count };
|
|
52
|
+
case "Escape":
|
|
53
|
+
return { type: "close" };
|
|
54
|
+
case "Enter":
|
|
55
|
+
return { type: "select" };
|
|
56
|
+
default:
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
var listboxId = (id) => `${id}-listbox`;
|
|
61
|
+
var optionId = (id, index) => `${id}-option-${index}`;
|
|
62
|
+
function buildInputProps(id, state) {
|
|
63
|
+
return {
|
|
64
|
+
role: "combobox",
|
|
65
|
+
"aria-autocomplete": "list",
|
|
66
|
+
"aria-expanded": state.isOpen,
|
|
67
|
+
"aria-controls": listboxId(id),
|
|
68
|
+
"aria-activedescendant": state.isOpen && state.activeIndex >= 0 ? optionId(id, state.activeIndex) : void 0
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function buildListboxProps(id) {
|
|
72
|
+
return { id: listboxId(id), role: "listbox" };
|
|
73
|
+
}
|
|
74
|
+
function buildItemProps(id, index, state) {
|
|
75
|
+
return {
|
|
76
|
+
role: "option",
|
|
77
|
+
id: optionId(id, index),
|
|
78
|
+
"aria-selected": state.activeIndex === index
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function useCombobox(options) {
|
|
82
|
+
const { id, count, onSelect } = options;
|
|
83
|
+
const [state, dispatch] = react.useReducer(comboboxReducer, INITIAL_COMBOBOX_STATE);
|
|
84
|
+
const open = react.useCallback(() => dispatch({ type: "open" }), []);
|
|
85
|
+
const close = react.useCallback(() => dispatch({ type: "close" }), []);
|
|
86
|
+
const reset = react.useCallback(() => dispatch({ type: "reset" }), []);
|
|
87
|
+
const setActiveIndex = react.useCallback(
|
|
88
|
+
(index) => dispatch({ type: "set", index }),
|
|
89
|
+
[]
|
|
90
|
+
);
|
|
91
|
+
const getInputProps = react.useCallback(
|
|
92
|
+
() => ({
|
|
93
|
+
...buildInputProps(id, state),
|
|
94
|
+
onFocus: () => dispatch({ type: "open" }),
|
|
95
|
+
onBlur: () => dispatch({ type: "close" }),
|
|
96
|
+
onKeyDown: (event) => {
|
|
97
|
+
const action = keyToAction(event.key, count);
|
|
98
|
+
if (!action) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (action.type === "select") {
|
|
102
|
+
if (state.activeIndex >= 0) {
|
|
103
|
+
event.preventDefault();
|
|
104
|
+
onSelect?.(state.activeIndex);
|
|
105
|
+
}
|
|
106
|
+
dispatch({ type: "select" });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
event.preventDefault();
|
|
110
|
+
dispatch(action);
|
|
111
|
+
}
|
|
112
|
+
}),
|
|
113
|
+
[id, state, count, onSelect]
|
|
114
|
+
);
|
|
115
|
+
const getListboxProps = react.useCallback(() => buildListboxProps(id), [id]);
|
|
116
|
+
const getItemProps = react.useCallback(
|
|
117
|
+
(index) => ({
|
|
118
|
+
...buildItemProps(id, index, state),
|
|
119
|
+
onMouseEnter: () => dispatch({ type: "set", index }),
|
|
120
|
+
onMouseDown: (event) => {
|
|
121
|
+
event.preventDefault();
|
|
122
|
+
onSelect?.(index);
|
|
123
|
+
dispatch({ type: "select" });
|
|
124
|
+
}
|
|
125
|
+
}),
|
|
126
|
+
[id, state, onSelect]
|
|
127
|
+
);
|
|
128
|
+
return {
|
|
129
|
+
isOpen: state.isOpen,
|
|
130
|
+
activeIndex: state.activeIndex,
|
|
131
|
+
open,
|
|
132
|
+
close,
|
|
133
|
+
reset,
|
|
134
|
+
setActiveIndex,
|
|
135
|
+
getInputProps,
|
|
136
|
+
getListboxProps,
|
|
137
|
+
getItemProps
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/autocomplete-cache.ts
|
|
142
|
+
var KEY_PREFIX = "wh:ac:";
|
|
143
|
+
function buildCacheKey(parts) {
|
|
144
|
+
const q = parts.q.trim().toLowerCase();
|
|
145
|
+
return JSON.stringify([
|
|
146
|
+
q,
|
|
147
|
+
parts.country ?? "",
|
|
148
|
+
parts.state ?? "",
|
|
149
|
+
parts.limit ?? "",
|
|
150
|
+
parts.lat ?? "",
|
|
151
|
+
parts.lng ?? ""
|
|
152
|
+
]);
|
|
153
|
+
}
|
|
154
|
+
function readCache(storage, key, ttlMs, now) {
|
|
155
|
+
const raw = storage.getItem(KEY_PREFIX + key);
|
|
156
|
+
if (raw === null) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
let entry;
|
|
160
|
+
try {
|
|
161
|
+
entry = JSON.parse(raw);
|
|
162
|
+
} catch {
|
|
163
|
+
storage.removeItem(KEY_PREFIX + key);
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
if (typeof entry?.t !== "number" || !Array.isArray(entry.results) || now - entry.t > ttlMs) {
|
|
167
|
+
storage.removeItem(KEY_PREFIX + key);
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
return entry.results;
|
|
171
|
+
}
|
|
172
|
+
function writeCache(storage, key, results, now) {
|
|
173
|
+
const entry = { results, t: now };
|
|
174
|
+
try {
|
|
175
|
+
storage.setItem(KEY_PREFIX + key, JSON.stringify(entry));
|
|
176
|
+
} catch {
|
|
177
|
+
}
|
|
178
|
+
}
|
|
4
179
|
|
|
5
180
|
// src/use-autocomplete.ts
|
|
181
|
+
var DEFAULT_DEBOUNCE_MS = 300;
|
|
182
|
+
var DEFAULT_MIN_LENGTH = 2;
|
|
183
|
+
var DEFAULT_CACHE_TTL_MS = 6e4;
|
|
184
|
+
function deriveStatus(s) {
|
|
185
|
+
if (s.query.trim().length < s.minLength) {
|
|
186
|
+
return "idle";
|
|
187
|
+
}
|
|
188
|
+
if (s.loading) {
|
|
189
|
+
return "loading";
|
|
190
|
+
}
|
|
191
|
+
if (s.error) {
|
|
192
|
+
return "error";
|
|
193
|
+
}
|
|
194
|
+
return s.results.length === 0 ? "empty" : "success";
|
|
195
|
+
}
|
|
196
|
+
function planSearch(args) {
|
|
197
|
+
if (args.trimmed.length < args.minLength) {
|
|
198
|
+
return { kind: "idle" };
|
|
199
|
+
}
|
|
200
|
+
if (args.cacheStorage) {
|
|
201
|
+
const hit = readCache(
|
|
202
|
+
args.cacheStorage,
|
|
203
|
+
buildCacheKey(args.keyParts),
|
|
204
|
+
args.cacheTtl,
|
|
205
|
+
Date.now()
|
|
206
|
+
);
|
|
207
|
+
if (hit) {
|
|
208
|
+
return { kind: "cache", results: hit };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return { kind: "fetch" };
|
|
212
|
+
}
|
|
213
|
+
async function executeAutocomplete(client, params, controller, actions) {
|
|
214
|
+
try {
|
|
215
|
+
const res = await client.addresses.autocomplete(params, {
|
|
216
|
+
signal: controller.signal
|
|
217
|
+
});
|
|
218
|
+
if (!controller.signal.aborted) {
|
|
219
|
+
actions.onSuccess(res.results);
|
|
220
|
+
}
|
|
221
|
+
} catch (e) {
|
|
222
|
+
if (!controller.signal.aborted) {
|
|
223
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
224
|
+
actions.onError(err, sdk.isRateLimitError(e));
|
|
225
|
+
}
|
|
226
|
+
} finally {
|
|
227
|
+
if (!controller.signal.aborted) {
|
|
228
|
+
actions.onSettled();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function cancelPending(timer, controller) {
|
|
233
|
+
if (timer) {
|
|
234
|
+
clearTimeout(timer);
|
|
235
|
+
}
|
|
236
|
+
controller?.abort();
|
|
237
|
+
}
|
|
6
238
|
function useAutocomplete(client, options = {}) {
|
|
7
|
-
const {
|
|
239
|
+
const {
|
|
240
|
+
debounceMs = DEFAULT_DEBOUNCE_MS,
|
|
241
|
+
minLength = DEFAULT_MIN_LENGTH,
|
|
242
|
+
limit,
|
|
243
|
+
country,
|
|
244
|
+
state,
|
|
245
|
+
lat,
|
|
246
|
+
lng,
|
|
247
|
+
sessionToken,
|
|
248
|
+
keepPreviousData = false,
|
|
249
|
+
cache
|
|
250
|
+
} = options;
|
|
8
251
|
const [query, setQuery] = react.useState("");
|
|
9
252
|
const [results, setResults] = react.useState([]);
|
|
10
253
|
const [loading, setLoading] = react.useState(false);
|
|
11
254
|
const [error, setError] = react.useState(null);
|
|
255
|
+
const [rateLimited, setRateLimited] = react.useState(false);
|
|
12
256
|
const abortRef = react.useRef(null);
|
|
13
257
|
const timerRef = react.useRef(null);
|
|
14
258
|
const handleSetQuery = react.useCallback((q) => {
|
|
15
259
|
setQuery(q);
|
|
16
260
|
}, []);
|
|
261
|
+
const reset = react.useCallback(() => {
|
|
262
|
+
cancelPending(timerRef.current, abortRef.current);
|
|
263
|
+
setQuery("");
|
|
264
|
+
setResults([]);
|
|
265
|
+
setLoading(false);
|
|
266
|
+
setError(null);
|
|
267
|
+
setRateLimited(false);
|
|
268
|
+
}, []);
|
|
269
|
+
const cacheStorage = cache?.storage;
|
|
270
|
+
const cacheTtl = cache?.ttlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
17
271
|
react.useEffect(() => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
272
|
+
cancelPending(timerRef.current, abortRef.current);
|
|
273
|
+
const trimmed = query.trim();
|
|
274
|
+
const keyParts = {
|
|
275
|
+
q: trimmed,
|
|
276
|
+
country,
|
|
277
|
+
state,
|
|
278
|
+
limit,
|
|
279
|
+
lat,
|
|
280
|
+
lng
|
|
281
|
+
};
|
|
282
|
+
const plan = planSearch({
|
|
283
|
+
trimmed,
|
|
284
|
+
minLength,
|
|
285
|
+
cacheStorage,
|
|
286
|
+
cacheTtl,
|
|
287
|
+
keyParts
|
|
288
|
+
});
|
|
289
|
+
setError(null);
|
|
290
|
+
setRateLimited(false);
|
|
291
|
+
if (plan.kind === "idle") {
|
|
292
|
+
if (!keepPreviousData) {
|
|
293
|
+
setResults([]);
|
|
294
|
+
}
|
|
295
|
+
setLoading(false);
|
|
296
|
+
return;
|
|
23
297
|
}
|
|
24
|
-
if (
|
|
25
|
-
setResults(
|
|
298
|
+
if (plan.kind === "cache") {
|
|
299
|
+
setResults(plan.results);
|
|
26
300
|
setLoading(false);
|
|
27
|
-
setError(null);
|
|
28
301
|
return;
|
|
29
302
|
}
|
|
303
|
+
if (!keepPreviousData) {
|
|
304
|
+
setResults([]);
|
|
305
|
+
}
|
|
30
306
|
setLoading(true);
|
|
31
307
|
timerRef.current = setTimeout(async () => {
|
|
32
308
|
const controller = new AbortController();
|
|
33
309
|
abortRef.current = controller;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
310
|
+
await executeAutocomplete(
|
|
311
|
+
client,
|
|
312
|
+
{ q: trimmed, limit, country, state, lat, lng, sessionToken },
|
|
313
|
+
controller,
|
|
314
|
+
{
|
|
315
|
+
onSuccess: (r) => {
|
|
316
|
+
setResults(r);
|
|
317
|
+
if (cacheStorage) {
|
|
318
|
+
writeCache(cacheStorage, buildCacheKey(keyParts), r, Date.now());
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
onError: (err, limited) => {
|
|
322
|
+
setError(err);
|
|
323
|
+
setRateLimited(limited);
|
|
324
|
+
if (!keepPreviousData) {
|
|
325
|
+
setResults([]);
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
onSettled: () => setLoading(false)
|
|
42
329
|
}
|
|
43
|
-
|
|
44
|
-
if (!controller.signal.aborted) {
|
|
45
|
-
setError(e instanceof Error ? e : new Error(String(e)));
|
|
46
|
-
setResults([]);
|
|
47
|
-
}
|
|
48
|
-
} finally {
|
|
49
|
-
if (!controller.signal.aborted) {
|
|
50
|
-
setLoading(false);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
330
|
+
);
|
|
53
331
|
}, debounceMs);
|
|
54
|
-
return () =>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
332
|
+
return () => cancelPending(timerRef.current, abortRef.current);
|
|
333
|
+
}, [
|
|
334
|
+
query,
|
|
335
|
+
debounceMs,
|
|
336
|
+
minLength,
|
|
337
|
+
limit,
|
|
338
|
+
country,
|
|
339
|
+
state,
|
|
340
|
+
lat,
|
|
341
|
+
lng,
|
|
342
|
+
sessionToken,
|
|
343
|
+
keepPreviousData,
|
|
344
|
+
cacheStorage,
|
|
345
|
+
cacheTtl,
|
|
346
|
+
client
|
|
347
|
+
]);
|
|
348
|
+
const status = deriveStatus({ query, minLength, loading, error, results });
|
|
349
|
+
return {
|
|
350
|
+
query,
|
|
351
|
+
setQuery: handleSetQuery,
|
|
352
|
+
results,
|
|
353
|
+
loading,
|
|
354
|
+
error,
|
|
355
|
+
rateLimited,
|
|
356
|
+
reset,
|
|
357
|
+
status
|
|
358
|
+
};
|
|
64
359
|
}
|
|
65
360
|
function useReverseGeocode(client, coords) {
|
|
66
361
|
const [address, setAddress] = react.useState(null);
|
|
@@ -103,6 +398,71 @@ function useReverseGeocode(client, coords) {
|
|
|
103
398
|
}, [coords?.lat, coords?.lng, client]);
|
|
104
399
|
return { address, distance, loading, error };
|
|
105
400
|
}
|
|
401
|
+
|
|
402
|
+
// src/async-resource.ts
|
|
403
|
+
async function runResource(promise, signal, actions) {
|
|
404
|
+
try {
|
|
405
|
+
const data = await promise;
|
|
406
|
+
if (!signal.aborted) {
|
|
407
|
+
actions.onSuccess(data);
|
|
408
|
+
}
|
|
409
|
+
} catch (e) {
|
|
410
|
+
if (!signal.aborted) {
|
|
411
|
+
actions.onError(e instanceof Error ? e : new Error(String(e)));
|
|
412
|
+
}
|
|
413
|
+
} finally {
|
|
414
|
+
if (!signal.aborted) {
|
|
415
|
+
actions.onSettled();
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// src/use-routing.ts
|
|
421
|
+
function useRoutingResource(client, params, call) {
|
|
422
|
+
const [data, setData] = react.useState(null);
|
|
423
|
+
const [loading, setLoading] = react.useState(false);
|
|
424
|
+
const [error, setError] = react.useState(null);
|
|
425
|
+
const abortRef = react.useRef(null);
|
|
426
|
+
const key = params ? JSON.stringify(params) : null;
|
|
427
|
+
react.useEffect(() => {
|
|
428
|
+
if (abortRef.current) {
|
|
429
|
+
abortRef.current.abort();
|
|
430
|
+
}
|
|
431
|
+
if (!params) {
|
|
432
|
+
setData(null);
|
|
433
|
+
setLoading(false);
|
|
434
|
+
setError(null);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
const controller = new AbortController();
|
|
438
|
+
abortRef.current = controller;
|
|
439
|
+
setLoading(true);
|
|
440
|
+
setError(null);
|
|
441
|
+
runResource(
|
|
442
|
+
call(client, params, { signal: controller.signal }),
|
|
443
|
+
controller.signal,
|
|
444
|
+
{
|
|
445
|
+
onSuccess: setData,
|
|
446
|
+
onError: setError,
|
|
447
|
+
onSettled: () => setLoading(false)
|
|
448
|
+
}
|
|
449
|
+
);
|
|
450
|
+
return () => controller.abort();
|
|
451
|
+
}, [key, client, call]);
|
|
452
|
+
return { data, loading, error };
|
|
453
|
+
}
|
|
454
|
+
var directionsCall = (client, params, options) => client.routing.directions(params, options);
|
|
455
|
+
var matrixCall = (client, params, options) => client.routing.matrix(params, options);
|
|
456
|
+
var isochroneCall = (client, params, options) => client.routing.isochrone(params, options);
|
|
457
|
+
function useDirections(client, params) {
|
|
458
|
+
return useRoutingResource(client, params, directionsCall);
|
|
459
|
+
}
|
|
460
|
+
function useMatrix(client, params) {
|
|
461
|
+
return useRoutingResource(client, params, matrixCall);
|
|
462
|
+
}
|
|
463
|
+
function useIsochrone(client, params) {
|
|
464
|
+
return useRoutingResource(client, params, isochroneCall);
|
|
465
|
+
}
|
|
106
466
|
function useZoneContains(client, coords) {
|
|
107
467
|
const [zones, setZones] = react.useState([]);
|
|
108
468
|
const [loading, setLoading] = react.useState(false);
|
|
@@ -142,7 +502,18 @@ function useZoneContains(client, coords) {
|
|
|
142
502
|
return { zones, loading, error };
|
|
143
503
|
}
|
|
144
504
|
|
|
505
|
+
exports.INITIAL_COMBOBOX_STATE = INITIAL_COMBOBOX_STATE;
|
|
506
|
+
exports.buildInputProps = buildInputProps;
|
|
507
|
+
exports.buildItemProps = buildItemProps;
|
|
508
|
+
exports.buildListboxProps = buildListboxProps;
|
|
509
|
+
exports.comboboxReducer = comboboxReducer;
|
|
510
|
+
exports.deriveStatus = deriveStatus;
|
|
511
|
+
exports.keyToAction = keyToAction;
|
|
145
512
|
exports.useAutocomplete = useAutocomplete;
|
|
513
|
+
exports.useCombobox = useCombobox;
|
|
514
|
+
exports.useDirections = useDirections;
|
|
515
|
+
exports.useIsochrone = useIsochrone;
|
|
516
|
+
exports.useMatrix = useMatrix;
|
|
146
517
|
exports.useReverseGeocode = useReverseGeocode;
|
|
147
518
|
exports.useZoneContains = useZoneContains;
|
|
148
519
|
//# sourceMappingURL=index.cjs.map
|