@pro6pp/infer-react 0.0.2-beta.6 → 0.0.2-beta.7
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/README.md +45 -7
- package/dist/index.cjs +295 -21
- package/dist/index.d.cts +13 -11
- package/dist/index.d.ts +13 -11
- package/dist/index.js +284 -21
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -11,13 +11,48 @@ npm install @pro6pp/infer-react
|
|
|
11
11
|
|
|
12
12
|
## Usage
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
The `Pro6PPInfer` component provides a styled address autocomplete input.
|
|
15
15
|
|
|
16
16
|
```tsx
|
|
17
17
|
import React from 'react';
|
|
18
|
-
import {
|
|
18
|
+
import { Pro6PPInfer } from '@pro6pp/infer-react';
|
|
19
19
|
|
|
20
20
|
const AddressForm = () => {
|
|
21
|
+
return (
|
|
22
|
+
<div className="form-group">
|
|
23
|
+
<label>Search Address</label>
|
|
24
|
+
<Pro6PPInfer
|
|
25
|
+
authKey="YOUR_AUTH_KEY"
|
|
26
|
+
country="NL"
|
|
27
|
+
onSelect={(selection) => console.log('Selected:', selection)}
|
|
28
|
+
placeholder="Type a Dutch address..."
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
You can customize the appearance of the component via the following props:
|
|
36
|
+
|
|
37
|
+
| Prop | Description |
|
|
38
|
+
| :--------------------- | :---------------------------------------------------------------------------------------- |
|
|
39
|
+
| `className` | Optional CSS class name for the wrapper `div`. |
|
|
40
|
+
| `disableDefaultStyles` | If `true`, prevents the automatic injection of the default CSS theme. |
|
|
41
|
+
| `placeholder` | Custom placeholder text for the input field. |
|
|
42
|
+
| `noResultsText` | The text to display when no suggestions are found. |
|
|
43
|
+
| `renderItem` | A custom render function for suggestion items, receiving the `item` and `isActive` state. |
|
|
44
|
+
| `renderNoResults` | A custom render function for the empty state, receiving the current `state`. |
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
Alternatively, you can use the headless `useInfer` hook.
|
|
49
|
+
This hook handles all the logic (state, API calls, debouncing, keyboard navigation), but allows you to build your own custom UI.
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
import React from 'react';
|
|
53
|
+
import { useInfer } from '@pro6pp/infer-react';
|
|
54
|
+
|
|
55
|
+
const CustomAddressForm = () => {
|
|
21
56
|
const { state, inputProps, selectItem } = useInfer({
|
|
22
57
|
authKey: 'YOUR_AUTH_KEY',
|
|
23
58
|
country: 'NL',
|
|
@@ -28,19 +63,22 @@ const AddressForm = () => {
|
|
|
28
63
|
<div className="address-autocomplete">
|
|
29
64
|
<label>Address</label>
|
|
30
65
|
|
|
31
|
-
|
|
66
|
+
{/* inputProps contains value, onChange, and onKeyDown */}
|
|
67
|
+
<input {...inputProps} placeholder="Type an address..." className="my-input-class" />
|
|
68
|
+
|
|
69
|
+
{state.isLoading && <div className="spinner">Loading...</div>}
|
|
32
70
|
|
|
33
71
|
{/* render the dropdown */}
|
|
34
72
|
{(state.suggestions.length > 0 || state.cities.length > 0) && (
|
|
35
73
|
<ul className="my-dropdown-class">
|
|
36
|
-
{/* render cities
|
|
74
|
+
{/* render cities */}
|
|
37
75
|
{state.cities.map((city, i) => (
|
|
38
76
|
<li key={`city-${i}`} onClick={() => selectItem(city)}>
|
|
39
77
|
<strong>{city.label}</strong> (City)
|
|
40
78
|
</li>
|
|
41
79
|
))}
|
|
42
80
|
|
|
43
|
-
{/* render streets
|
|
81
|
+
{/* render streets */}
|
|
44
82
|
{state.streets.map((street, i) => (
|
|
45
83
|
<li key={`street-${i}`} onClick={() => selectItem(street)}>
|
|
46
84
|
<strong>{street.label}</strong> (Street)
|
|
@@ -49,14 +87,14 @@ const AddressForm = () => {
|
|
|
49
87
|
|
|
50
88
|
{/* render general suggestions */}
|
|
51
89
|
{state.suggestions.map((item, i) => (
|
|
52
|
-
<li key={`
|
|
90
|
+
<li key={`suggestion-${i}`} onClick={() => selectItem(item)}>
|
|
53
91
|
{item.label}
|
|
54
92
|
</li>
|
|
55
93
|
))}
|
|
56
94
|
</ul>
|
|
57
95
|
)}
|
|
58
96
|
|
|
59
|
-
{state.isValid && <p>Valid address selected
|
|
97
|
+
{state.isValid && <p>Valid address selected.</p>}
|
|
60
98
|
</div>
|
|
61
99
|
);
|
|
62
100
|
};
|
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
7
9
|
var __export = (target, all) => {
|
|
@@ -16,16 +18,25 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
16
18
|
}
|
|
17
19
|
return to;
|
|
18
20
|
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
19
29
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
30
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
21
31
|
|
|
22
|
-
// src/index.
|
|
32
|
+
// src/index.tsx
|
|
23
33
|
var index_exports = {};
|
|
24
34
|
__export(index_exports, {
|
|
35
|
+
Pro6PPInfer: () => Pro6PPInfer,
|
|
25
36
|
useInfer: () => useInfer
|
|
26
37
|
});
|
|
27
38
|
module.exports = __toCommonJS(index_exports);
|
|
28
|
-
var import_react = require("react");
|
|
39
|
+
var import_react = __toESM(require("react"), 1);
|
|
29
40
|
|
|
30
41
|
// ../core/src/core.ts
|
|
31
42
|
var DEFAULTS = {
|
|
@@ -59,6 +70,7 @@ var InferCore = class {
|
|
|
59
70
|
__publicField(this, "state");
|
|
60
71
|
__publicField(this, "abortController", null);
|
|
61
72
|
__publicField(this, "debouncedFetch");
|
|
73
|
+
__publicField(this, "isSelecting", false);
|
|
62
74
|
this.country = config.country;
|
|
63
75
|
this.authKey = config.authKey;
|
|
64
76
|
this.apiUrl = config.apiUrl || DEFAULTS.API_URL;
|
|
@@ -75,13 +87,18 @@ var InferCore = class {
|
|
|
75
87
|
);
|
|
76
88
|
}
|
|
77
89
|
handleInput(value) {
|
|
90
|
+
if (this.isSelecting) {
|
|
91
|
+
this.isSelecting = false;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const isEditingFinal = this.state.stage === "final" && value !== this.state.query;
|
|
78
95
|
this.updateState({
|
|
79
96
|
query: value,
|
|
80
97
|
isValid: false,
|
|
81
98
|
isLoading: !!value.trim(),
|
|
82
99
|
selectedSuggestionIndex: -1
|
|
83
100
|
});
|
|
84
|
-
if (
|
|
101
|
+
if (isEditingFinal) {
|
|
85
102
|
this.onSelect(null);
|
|
86
103
|
}
|
|
87
104
|
this.debouncedFetch(value);
|
|
@@ -128,14 +145,32 @@ var InferCore = class {
|
|
|
128
145
|
}
|
|
129
146
|
}
|
|
130
147
|
selectItem(item) {
|
|
148
|
+
this.debouncedFetch.cancel();
|
|
149
|
+
if (this.abortController) {
|
|
150
|
+
this.abortController.abort();
|
|
151
|
+
}
|
|
131
152
|
const label = typeof item === "string" ? item : item.label;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
153
|
+
let logicValue = label;
|
|
154
|
+
if (typeof item !== "string" && typeof item.value === "string") {
|
|
155
|
+
logicValue = item.value;
|
|
156
|
+
}
|
|
157
|
+
const valueObj = typeof item !== "string" && typeof item.value === "object" ? item.value : void 0;
|
|
158
|
+
const isFullResult = !!valueObj && Object.keys(valueObj).length > 0;
|
|
159
|
+
this.isSelecting = true;
|
|
160
|
+
if (this.state.stage === "final" || isFullResult) {
|
|
161
|
+
let finalQuery = label;
|
|
162
|
+
if (valueObj && Object.keys(valueObj).length > 0) {
|
|
163
|
+
const { street, street_number, house_number, city } = valueObj;
|
|
164
|
+
const number = street_number || house_number;
|
|
165
|
+
if (street && number && city) {
|
|
166
|
+
finalQuery = `${street} ${number}, ${city}`;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
this.finishSelection(finalQuery, valueObj);
|
|
136
170
|
return;
|
|
137
171
|
}
|
|
138
|
-
|
|
172
|
+
const subtitle = typeof item !== "string" ? item.subtitle : null;
|
|
173
|
+
this.processSelection(logicValue, subtitle);
|
|
139
174
|
}
|
|
140
175
|
shouldAutoInsertComma(currentVal) {
|
|
141
176
|
const isStartOfSegmentAndNumeric = !currentVal.includes(",") && PATTERNS.DIGITS_1_3.test(currentVal.trim());
|
|
@@ -147,33 +182,43 @@ var InferCore = class {
|
|
|
147
182
|
return false;
|
|
148
183
|
}
|
|
149
184
|
finishSelection(label, value) {
|
|
150
|
-
this.updateState({
|
|
185
|
+
this.updateState({
|
|
186
|
+
query: label,
|
|
187
|
+
suggestions: [],
|
|
188
|
+
cities: [],
|
|
189
|
+
streets: [],
|
|
190
|
+
isValid: true,
|
|
191
|
+
stage: "final"
|
|
192
|
+
});
|
|
151
193
|
this.onSelect(value || label);
|
|
194
|
+
setTimeout(() => {
|
|
195
|
+
this.isSelecting = false;
|
|
196
|
+
}, 0);
|
|
152
197
|
}
|
|
153
|
-
processSelection(
|
|
198
|
+
processSelection(text, subtitle) {
|
|
154
199
|
const { stage, query } = this.state;
|
|
155
200
|
let nextQuery = query;
|
|
156
201
|
const isContextualSelection = subtitle && (stage === "city" || stage === "street" || stage === "mixed");
|
|
157
202
|
if (isContextualSelection) {
|
|
158
203
|
if (stage === "city") {
|
|
159
|
-
nextQuery = `${subtitle}, ${
|
|
204
|
+
nextQuery = `${subtitle}, ${text}, `;
|
|
160
205
|
} else {
|
|
161
206
|
const prefix = this.getQueryPrefix(query);
|
|
162
|
-
nextQuery = prefix ? `${prefix} ${
|
|
207
|
+
nextQuery = prefix ? `${prefix} ${text}, ${subtitle}, ` : `${text}, ${subtitle}, `;
|
|
163
208
|
}
|
|
164
209
|
this.updateQueryAndFetch(nextQuery);
|
|
165
210
|
return;
|
|
166
211
|
}
|
|
167
212
|
if (stage === "direct" || stage === "addition") {
|
|
168
|
-
this.finishSelection(
|
|
213
|
+
this.finishSelection(text);
|
|
169
214
|
return;
|
|
170
215
|
}
|
|
171
216
|
const hasComma = query.includes(",");
|
|
172
217
|
const isFirstSegment = !hasComma && (stage === "city" || stage === "street" || stage === "house_number_first");
|
|
173
218
|
if (isFirstSegment) {
|
|
174
|
-
nextQuery = `${
|
|
219
|
+
nextQuery = `${text}, `;
|
|
175
220
|
} else {
|
|
176
|
-
nextQuery = this.replaceLastSegment(query,
|
|
221
|
+
nextQuery = this.replaceLastSegment(query, text);
|
|
177
222
|
if (stage !== "house_number") {
|
|
178
223
|
nextQuery += ", ";
|
|
179
224
|
}
|
|
@@ -211,21 +256,52 @@ var InferCore = class {
|
|
|
211
256
|
stage: data.stage,
|
|
212
257
|
isLoading: false
|
|
213
258
|
};
|
|
259
|
+
let autoSelect = false;
|
|
260
|
+
let autoSelectItem = null;
|
|
261
|
+
const rawSuggestions = data.suggestions || [];
|
|
262
|
+
const uniqueSuggestions = [];
|
|
263
|
+
const seen = /* @__PURE__ */ new Set();
|
|
264
|
+
for (const item of rawSuggestions) {
|
|
265
|
+
const key = `${item.label}|${item.subtitle || ""}|${JSON.stringify(item.value || {})}`;
|
|
266
|
+
if (!seen.has(key)) {
|
|
267
|
+
seen.add(key);
|
|
268
|
+
uniqueSuggestions.push(item);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
214
271
|
if (data.stage === "mixed") {
|
|
215
272
|
newState.cities = data.cities || [];
|
|
216
273
|
newState.streets = data.streets || [];
|
|
217
274
|
newState.suggestions = [];
|
|
218
275
|
} else {
|
|
219
|
-
newState.suggestions =
|
|
276
|
+
newState.suggestions = uniqueSuggestions;
|
|
220
277
|
newState.cities = [];
|
|
221
278
|
newState.streets = [];
|
|
279
|
+
if (data.stage === "final" && uniqueSuggestions.length === 1) {
|
|
280
|
+
autoSelect = true;
|
|
281
|
+
autoSelectItem = uniqueSuggestions[0];
|
|
282
|
+
}
|
|
222
283
|
}
|
|
223
284
|
newState.isValid = data.stage === "final";
|
|
224
|
-
|
|
285
|
+
if (autoSelect && autoSelectItem) {
|
|
286
|
+
newState.query = autoSelectItem.label;
|
|
287
|
+
newState.suggestions = [];
|
|
288
|
+
newState.cities = [];
|
|
289
|
+
newState.streets = [];
|
|
290
|
+
newState.isValid = true;
|
|
291
|
+
this.updateState(newState);
|
|
292
|
+
const val = typeof autoSelectItem.value === "object" ? autoSelectItem.value : autoSelectItem.label;
|
|
293
|
+
this.onSelect(val);
|
|
294
|
+
} else {
|
|
295
|
+
this.updateState(newState);
|
|
296
|
+
}
|
|
225
297
|
}
|
|
226
298
|
updateQueryAndFetch(nextQuery) {
|
|
227
299
|
this.updateState({ query: nextQuery, suggestions: [], cities: [], streets: [] });
|
|
228
|
-
this.
|
|
300
|
+
this.updateState({ isLoading: true, isValid: false });
|
|
301
|
+
this.debouncedFetch(nextQuery);
|
|
302
|
+
setTimeout(() => {
|
|
303
|
+
this.isSelecting = false;
|
|
304
|
+
}, 0);
|
|
229
305
|
}
|
|
230
306
|
replaceLastSegment(fullText, newSegment) {
|
|
231
307
|
const lastCommaIndex = fullText.lastIndexOf(",");
|
|
@@ -248,20 +324,136 @@ var InferCore = class {
|
|
|
248
324
|
}
|
|
249
325
|
debounce(func, wait) {
|
|
250
326
|
let timeout;
|
|
251
|
-
|
|
327
|
+
const debounced = (...args) => {
|
|
252
328
|
if (timeout) clearTimeout(timeout);
|
|
253
329
|
timeout = setTimeout(() => func.apply(this, args), wait);
|
|
254
330
|
};
|
|
331
|
+
debounced.cancel = () => {
|
|
332
|
+
if (timeout) {
|
|
333
|
+
clearTimeout(timeout);
|
|
334
|
+
timeout = void 0;
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
return debounced;
|
|
255
338
|
}
|
|
256
339
|
};
|
|
257
340
|
|
|
258
|
-
// src/
|
|
341
|
+
// ../core/src/default-styles.ts
|
|
342
|
+
var DEFAULT_STYLES = `
|
|
343
|
+
.pro6pp-wrapper {
|
|
344
|
+
position: relative;
|
|
345
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
346
|
+
box-sizing: border-box;
|
|
347
|
+
width: 100%;
|
|
348
|
+
}
|
|
349
|
+
.pro6pp-wrapper * {
|
|
350
|
+
box-sizing: border-box;
|
|
351
|
+
}
|
|
352
|
+
.pro6pp-input {
|
|
353
|
+
width: 100%;
|
|
354
|
+
padding: 10px 12px;
|
|
355
|
+
border: 1px solid #e0e0e0;
|
|
356
|
+
border-radius: 4px;
|
|
357
|
+
font-size: 16px;
|
|
358
|
+
line-height: 1.5;
|
|
359
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
360
|
+
}
|
|
361
|
+
.pro6pp-input:focus {
|
|
362
|
+
outline: none;
|
|
363
|
+
border-color: #3b82f6;
|
|
364
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
365
|
+
}
|
|
366
|
+
.pro6pp-dropdown {
|
|
367
|
+
position: absolute;
|
|
368
|
+
top: 100%;
|
|
369
|
+
left: 0;
|
|
370
|
+
right: 0;
|
|
371
|
+
z-index: 9999;
|
|
372
|
+
margin-top: 4px;
|
|
373
|
+
background: white;
|
|
374
|
+
border: 1px solid #e0e0e0;
|
|
375
|
+
border-radius: 4px;
|
|
376
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
377
|
+
max-height: 300px;
|
|
378
|
+
overflow-y: auto;
|
|
379
|
+
list-style: none !important;
|
|
380
|
+
padding: 0 !important;
|
|
381
|
+
margin: 0 !important;
|
|
382
|
+
overflow: hidden;
|
|
383
|
+
}
|
|
384
|
+
.pro6pp-item {
|
|
385
|
+
padding: 12px 12px 9px 12px;
|
|
386
|
+
cursor: pointer;
|
|
387
|
+
display: flex;
|
|
388
|
+
flex-direction: row;
|
|
389
|
+
align-items: center;
|
|
390
|
+
color: #000000;
|
|
391
|
+
font-size: 14px;
|
|
392
|
+
line-height: 1;
|
|
393
|
+
white-space: nowrap;
|
|
394
|
+
overflow: hidden;
|
|
395
|
+
border-radius: 0 !important;
|
|
396
|
+
margin: 0 !important;
|
|
397
|
+
}
|
|
398
|
+
.pro6pp-item:hover, .pro6pp-item--active {
|
|
399
|
+
background-color: #f5f5f5;
|
|
400
|
+
}
|
|
401
|
+
.pro6pp-item__label {
|
|
402
|
+
font-weight: 500;
|
|
403
|
+
flex-shrink: 0;
|
|
404
|
+
}
|
|
405
|
+
.pro6pp-item__subtitle {
|
|
406
|
+
font-size: 14px;
|
|
407
|
+
color: #404040;
|
|
408
|
+
overflow: hidden;
|
|
409
|
+
text-overflow: ellipsis;
|
|
410
|
+
flex-shrink: 1;
|
|
411
|
+
}
|
|
412
|
+
.pro6pp-item__chevron {
|
|
413
|
+
margin-left: auto;
|
|
414
|
+
display: flex;
|
|
415
|
+
align-items: center;
|
|
416
|
+
color: #a3a3a3;
|
|
417
|
+
padding-left: 8px;
|
|
418
|
+
}
|
|
419
|
+
.pro6pp-no-results {
|
|
420
|
+
padding: 12px;
|
|
421
|
+
color: #555555;
|
|
422
|
+
font-size: 14px;
|
|
423
|
+
text-align: center;
|
|
424
|
+
user-select: none;
|
|
425
|
+
pointer-events: none;
|
|
426
|
+
}
|
|
427
|
+
.pro6pp-loader {
|
|
428
|
+
position: absolute;
|
|
429
|
+
right: 12px;
|
|
430
|
+
top: 50%;
|
|
431
|
+
transform: translateY(-50%);
|
|
432
|
+
width: 16px;
|
|
433
|
+
height: 16px;
|
|
434
|
+
border: 2px solid #e0e0e0;
|
|
435
|
+
border-top-color: #404040;
|
|
436
|
+
border-radius: 50%;
|
|
437
|
+
animation: pro6pp-spin 0.6s linear infinite;
|
|
438
|
+
pointer-events: none;
|
|
439
|
+
}
|
|
440
|
+
@keyframes pro6pp-spin {
|
|
441
|
+
to { transform: translateY(-50%) rotate(360deg); }
|
|
442
|
+
}
|
|
443
|
+
`;
|
|
444
|
+
|
|
445
|
+
// src/index.tsx
|
|
259
446
|
function useInfer(config) {
|
|
260
447
|
const [state, setState] = (0, import_react.useState)(INITIAL_STATE);
|
|
261
448
|
const core = (0, import_react.useMemo)(() => {
|
|
262
449
|
return new InferCore({
|
|
263
450
|
...config,
|
|
264
|
-
onStateChange: (newState) =>
|
|
451
|
+
onStateChange: (newState) => {
|
|
452
|
+
setState({ ...newState });
|
|
453
|
+
if (config.onStateChange) {
|
|
454
|
+
config.onStateChange(newState);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
265
457
|
});
|
|
266
458
|
}, [config.country, config.authKey, config.limit]);
|
|
267
459
|
return {
|
|
@@ -275,7 +467,89 @@ function useInfer(config) {
|
|
|
275
467
|
selectItem: (item) => core.selectItem(item)
|
|
276
468
|
};
|
|
277
469
|
}
|
|
470
|
+
var Pro6PPInfer = ({
|
|
471
|
+
className,
|
|
472
|
+
style,
|
|
473
|
+
inputProps,
|
|
474
|
+
placeholder = "Start typing an address...",
|
|
475
|
+
renderItem,
|
|
476
|
+
disableDefaultStyles = false,
|
|
477
|
+
noResultsText = "No results found",
|
|
478
|
+
renderNoResults,
|
|
479
|
+
...config
|
|
480
|
+
}) => {
|
|
481
|
+
const { state, selectItem, inputProps: coreInputProps } = useInfer(config);
|
|
482
|
+
const inputRef = (0, import_react.useRef)(null);
|
|
483
|
+
(0, import_react.useEffect)(() => {
|
|
484
|
+
if (disableDefaultStyles) return;
|
|
485
|
+
const styleId = "pro6pp-styles";
|
|
486
|
+
if (!document.getElementById(styleId)) {
|
|
487
|
+
const styleEl = document.createElement("style");
|
|
488
|
+
styleEl.id = styleId;
|
|
489
|
+
styleEl.textContent = DEFAULT_STYLES;
|
|
490
|
+
document.head.appendChild(styleEl);
|
|
491
|
+
}
|
|
492
|
+
}, [disableDefaultStyles]);
|
|
493
|
+
const items = (0, import_react.useMemo)(() => {
|
|
494
|
+
return [
|
|
495
|
+
...state.cities.map((c) => ({ ...c, type: "city" })),
|
|
496
|
+
...state.streets.map((s) => ({ ...s, type: "street" })),
|
|
497
|
+
...state.suggestions.map((s) => ({ ...s, type: "suggestion" }))
|
|
498
|
+
];
|
|
499
|
+
}, [state.cities, state.streets, state.suggestions]);
|
|
500
|
+
const handleSelect = (item) => {
|
|
501
|
+
selectItem(item);
|
|
502
|
+
if (!state.isValid && inputRef.current) {
|
|
503
|
+
inputRef.current.focus();
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
const hasResults = items.length > 0;
|
|
507
|
+
const showNoResults = !state.isLoading && !state.isError && state.query.length > 0 && !hasResults && !state.isValid;
|
|
508
|
+
const showDropdown = hasResults || showNoResults;
|
|
509
|
+
return /* @__PURE__ */ import_react.default.createElement("div", { className: `pro6pp-wrapper ${className || ""}`, style }, /* @__PURE__ */ import_react.default.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ import_react.default.createElement(
|
|
510
|
+
"input",
|
|
511
|
+
{
|
|
512
|
+
ref: inputRef,
|
|
513
|
+
type: "text",
|
|
514
|
+
className: "pro6pp-input",
|
|
515
|
+
placeholder,
|
|
516
|
+
autoComplete: "off",
|
|
517
|
+
...inputProps,
|
|
518
|
+
...coreInputProps
|
|
519
|
+
}
|
|
520
|
+
), state.isLoading && /* @__PURE__ */ import_react.default.createElement("div", { className: "pro6pp-loader" })), showDropdown && /* @__PURE__ */ import_react.default.createElement("ul", { className: "pro6pp-dropdown", role: "listbox" }, hasResults ? items.map((item, index) => {
|
|
521
|
+
const isActive = index === state.selectedSuggestionIndex;
|
|
522
|
+
const secondaryText = item.subtitle || item.count;
|
|
523
|
+
const showChevron = item.value === void 0 || item.value === null;
|
|
524
|
+
return /* @__PURE__ */ import_react.default.createElement(
|
|
525
|
+
"li",
|
|
526
|
+
{
|
|
527
|
+
key: `${item.label}-${index}`,
|
|
528
|
+
role: "option",
|
|
529
|
+
"aria-selected": isActive,
|
|
530
|
+
className: `pro6pp-item ${isActive ? "pro6pp-item--active" : ""}`,
|
|
531
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
532
|
+
onClick: () => handleSelect(item)
|
|
533
|
+
},
|
|
534
|
+
renderItem ? renderItem(item, isActive) : /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("span", { className: "pro6pp-item__label" }, item.label), secondaryText && /* @__PURE__ */ import_react.default.createElement("span", { className: "pro6pp-item__subtitle" }, ", ", secondaryText), showChevron && /* @__PURE__ */ import_react.default.createElement("div", { className: "pro6pp-item__chevron" }, /* @__PURE__ */ import_react.default.createElement(
|
|
535
|
+
"svg",
|
|
536
|
+
{
|
|
537
|
+
width: "16",
|
|
538
|
+
height: "16",
|
|
539
|
+
viewBox: "0 0 24 24",
|
|
540
|
+
fill: "none",
|
|
541
|
+
stroke: "currentColor",
|
|
542
|
+
strokeWidth: "2",
|
|
543
|
+
strokeLinecap: "round",
|
|
544
|
+
strokeLinejoin: "round"
|
|
545
|
+
},
|
|
546
|
+
/* @__PURE__ */ import_react.default.createElement("polyline", { points: "9 18 15 12 9 6" })
|
|
547
|
+
)))
|
|
548
|
+
);
|
|
549
|
+
}) : /* @__PURE__ */ import_react.default.createElement("li", { className: "pro6pp-no-results" }, renderNoResults ? renderNoResults(state) : noResultsText)));
|
|
550
|
+
};
|
|
278
551
|
// Annotate the CommonJS export names for ESM import in node:
|
|
279
552
|
0 && (module.exports = {
|
|
553
|
+
Pro6PPInfer,
|
|
280
554
|
useInfer
|
|
281
555
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,16 +1,7 @@
|
|
|
1
|
+
import React from 'react';
|
|
1
2
|
import { InferConfig, InferState, InferCore, InferResult } from '@pro6pp/infer-core';
|
|
2
3
|
export { AddressValue, CountryCode, Fetcher, InferConfig, InferResult, InferState, Stage } from '@pro6pp/infer-core';
|
|
3
4
|
|
|
4
|
-
/**
|
|
5
|
-
* Hook for the Pro6PP Infer API.
|
|
6
|
-
* @param config - The API configuration.
|
|
7
|
-
* @returns An object containing the current state, input helpers, and selection handler.
|
|
8
|
-
* @example
|
|
9
|
-
* const { state, inputProps, selectItem } = useInfer({
|
|
10
|
-
* authKey: 'YOUR_KEY',
|
|
11
|
-
* country: 'NL'
|
|
12
|
-
* });
|
|
13
|
-
*/
|
|
14
5
|
declare function useInfer(config: InferConfig): {
|
|
15
6
|
state: InferState;
|
|
16
7
|
core: InferCore;
|
|
@@ -21,5 +12,16 @@ declare function useInfer(config: InferConfig): {
|
|
|
21
12
|
};
|
|
22
13
|
selectItem: (item: InferResult | string) => void;
|
|
23
14
|
};
|
|
15
|
+
interface Pro6PPInferProps extends InferConfig {
|
|
16
|
+
className?: string;
|
|
17
|
+
style?: React.CSSProperties;
|
|
18
|
+
inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
|
|
19
|
+
placeholder?: string;
|
|
20
|
+
renderItem?: (item: InferResult, isActive: boolean) => React.ReactNode;
|
|
21
|
+
disableDefaultStyles?: boolean;
|
|
22
|
+
noResultsText?: string;
|
|
23
|
+
renderNoResults?: (state: InferState) => React.ReactNode;
|
|
24
|
+
}
|
|
25
|
+
declare const Pro6PPInfer: React.FC<Pro6PPInferProps>;
|
|
24
26
|
|
|
25
|
-
export { useInfer };
|
|
27
|
+
export { Pro6PPInfer, type Pro6PPInferProps, useInfer };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,7 @@
|
|
|
1
|
+
import React from 'react';
|
|
1
2
|
import { InferConfig, InferState, InferCore, InferResult } from '@pro6pp/infer-core';
|
|
2
3
|
export { AddressValue, CountryCode, Fetcher, InferConfig, InferResult, InferState, Stage } from '@pro6pp/infer-core';
|
|
3
4
|
|
|
4
|
-
/**
|
|
5
|
-
* Hook for the Pro6PP Infer API.
|
|
6
|
-
* @param config - The API configuration.
|
|
7
|
-
* @returns An object containing the current state, input helpers, and selection handler.
|
|
8
|
-
* @example
|
|
9
|
-
* const { state, inputProps, selectItem } = useInfer({
|
|
10
|
-
* authKey: 'YOUR_KEY',
|
|
11
|
-
* country: 'NL'
|
|
12
|
-
* });
|
|
13
|
-
*/
|
|
14
5
|
declare function useInfer(config: InferConfig): {
|
|
15
6
|
state: InferState;
|
|
16
7
|
core: InferCore;
|
|
@@ -21,5 +12,16 @@ declare function useInfer(config: InferConfig): {
|
|
|
21
12
|
};
|
|
22
13
|
selectItem: (item: InferResult | string) => void;
|
|
23
14
|
};
|
|
15
|
+
interface Pro6PPInferProps extends InferConfig {
|
|
16
|
+
className?: string;
|
|
17
|
+
style?: React.CSSProperties;
|
|
18
|
+
inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
|
|
19
|
+
placeholder?: string;
|
|
20
|
+
renderItem?: (item: InferResult, isActive: boolean) => React.ReactNode;
|
|
21
|
+
disableDefaultStyles?: boolean;
|
|
22
|
+
noResultsText?: string;
|
|
23
|
+
renderNoResults?: (state: InferState) => React.ReactNode;
|
|
24
|
+
}
|
|
25
|
+
declare const Pro6PPInfer: React.FC<Pro6PPInferProps>;
|
|
24
26
|
|
|
25
|
-
export { useInfer };
|
|
27
|
+
export { Pro6PPInfer, type Pro6PPInferProps, useInfer };
|
package/dist/index.js
CHANGED
|
@@ -2,8 +2,8 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
|
|
5
|
-
// src/index.
|
|
6
|
-
import { useState, useMemo } from "react";
|
|
5
|
+
// src/index.tsx
|
|
6
|
+
import React, { useState, useMemo, useEffect, useRef } from "react";
|
|
7
7
|
|
|
8
8
|
// ../core/src/core.ts
|
|
9
9
|
var DEFAULTS = {
|
|
@@ -37,6 +37,7 @@ var InferCore = class {
|
|
|
37
37
|
__publicField(this, "state");
|
|
38
38
|
__publicField(this, "abortController", null);
|
|
39
39
|
__publicField(this, "debouncedFetch");
|
|
40
|
+
__publicField(this, "isSelecting", false);
|
|
40
41
|
this.country = config.country;
|
|
41
42
|
this.authKey = config.authKey;
|
|
42
43
|
this.apiUrl = config.apiUrl || DEFAULTS.API_URL;
|
|
@@ -53,13 +54,18 @@ var InferCore = class {
|
|
|
53
54
|
);
|
|
54
55
|
}
|
|
55
56
|
handleInput(value) {
|
|
57
|
+
if (this.isSelecting) {
|
|
58
|
+
this.isSelecting = false;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const isEditingFinal = this.state.stage === "final" && value !== this.state.query;
|
|
56
62
|
this.updateState({
|
|
57
63
|
query: value,
|
|
58
64
|
isValid: false,
|
|
59
65
|
isLoading: !!value.trim(),
|
|
60
66
|
selectedSuggestionIndex: -1
|
|
61
67
|
});
|
|
62
|
-
if (
|
|
68
|
+
if (isEditingFinal) {
|
|
63
69
|
this.onSelect(null);
|
|
64
70
|
}
|
|
65
71
|
this.debouncedFetch(value);
|
|
@@ -106,14 +112,32 @@ var InferCore = class {
|
|
|
106
112
|
}
|
|
107
113
|
}
|
|
108
114
|
selectItem(item) {
|
|
115
|
+
this.debouncedFetch.cancel();
|
|
116
|
+
if (this.abortController) {
|
|
117
|
+
this.abortController.abort();
|
|
118
|
+
}
|
|
109
119
|
const label = typeof item === "string" ? item : item.label;
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
120
|
+
let logicValue = label;
|
|
121
|
+
if (typeof item !== "string" && typeof item.value === "string") {
|
|
122
|
+
logicValue = item.value;
|
|
123
|
+
}
|
|
124
|
+
const valueObj = typeof item !== "string" && typeof item.value === "object" ? item.value : void 0;
|
|
125
|
+
const isFullResult = !!valueObj && Object.keys(valueObj).length > 0;
|
|
126
|
+
this.isSelecting = true;
|
|
127
|
+
if (this.state.stage === "final" || isFullResult) {
|
|
128
|
+
let finalQuery = label;
|
|
129
|
+
if (valueObj && Object.keys(valueObj).length > 0) {
|
|
130
|
+
const { street, street_number, house_number, city } = valueObj;
|
|
131
|
+
const number = street_number || house_number;
|
|
132
|
+
if (street && number && city) {
|
|
133
|
+
finalQuery = `${street} ${number}, ${city}`;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
this.finishSelection(finalQuery, valueObj);
|
|
114
137
|
return;
|
|
115
138
|
}
|
|
116
|
-
|
|
139
|
+
const subtitle = typeof item !== "string" ? item.subtitle : null;
|
|
140
|
+
this.processSelection(logicValue, subtitle);
|
|
117
141
|
}
|
|
118
142
|
shouldAutoInsertComma(currentVal) {
|
|
119
143
|
const isStartOfSegmentAndNumeric = !currentVal.includes(",") && PATTERNS.DIGITS_1_3.test(currentVal.trim());
|
|
@@ -125,33 +149,43 @@ var InferCore = class {
|
|
|
125
149
|
return false;
|
|
126
150
|
}
|
|
127
151
|
finishSelection(label, value) {
|
|
128
|
-
this.updateState({
|
|
152
|
+
this.updateState({
|
|
153
|
+
query: label,
|
|
154
|
+
suggestions: [],
|
|
155
|
+
cities: [],
|
|
156
|
+
streets: [],
|
|
157
|
+
isValid: true,
|
|
158
|
+
stage: "final"
|
|
159
|
+
});
|
|
129
160
|
this.onSelect(value || label);
|
|
161
|
+
setTimeout(() => {
|
|
162
|
+
this.isSelecting = false;
|
|
163
|
+
}, 0);
|
|
130
164
|
}
|
|
131
|
-
processSelection(
|
|
165
|
+
processSelection(text, subtitle) {
|
|
132
166
|
const { stage, query } = this.state;
|
|
133
167
|
let nextQuery = query;
|
|
134
168
|
const isContextualSelection = subtitle && (stage === "city" || stage === "street" || stage === "mixed");
|
|
135
169
|
if (isContextualSelection) {
|
|
136
170
|
if (stage === "city") {
|
|
137
|
-
nextQuery = `${subtitle}, ${
|
|
171
|
+
nextQuery = `${subtitle}, ${text}, `;
|
|
138
172
|
} else {
|
|
139
173
|
const prefix = this.getQueryPrefix(query);
|
|
140
|
-
nextQuery = prefix ? `${prefix} ${
|
|
174
|
+
nextQuery = prefix ? `${prefix} ${text}, ${subtitle}, ` : `${text}, ${subtitle}, `;
|
|
141
175
|
}
|
|
142
176
|
this.updateQueryAndFetch(nextQuery);
|
|
143
177
|
return;
|
|
144
178
|
}
|
|
145
179
|
if (stage === "direct" || stage === "addition") {
|
|
146
|
-
this.finishSelection(
|
|
180
|
+
this.finishSelection(text);
|
|
147
181
|
return;
|
|
148
182
|
}
|
|
149
183
|
const hasComma = query.includes(",");
|
|
150
184
|
const isFirstSegment = !hasComma && (stage === "city" || stage === "street" || stage === "house_number_first");
|
|
151
185
|
if (isFirstSegment) {
|
|
152
|
-
nextQuery = `${
|
|
186
|
+
nextQuery = `${text}, `;
|
|
153
187
|
} else {
|
|
154
|
-
nextQuery = this.replaceLastSegment(query,
|
|
188
|
+
nextQuery = this.replaceLastSegment(query, text);
|
|
155
189
|
if (stage !== "house_number") {
|
|
156
190
|
nextQuery += ", ";
|
|
157
191
|
}
|
|
@@ -189,21 +223,52 @@ var InferCore = class {
|
|
|
189
223
|
stage: data.stage,
|
|
190
224
|
isLoading: false
|
|
191
225
|
};
|
|
226
|
+
let autoSelect = false;
|
|
227
|
+
let autoSelectItem = null;
|
|
228
|
+
const rawSuggestions = data.suggestions || [];
|
|
229
|
+
const uniqueSuggestions = [];
|
|
230
|
+
const seen = /* @__PURE__ */ new Set();
|
|
231
|
+
for (const item of rawSuggestions) {
|
|
232
|
+
const key = `${item.label}|${item.subtitle || ""}|${JSON.stringify(item.value || {})}`;
|
|
233
|
+
if (!seen.has(key)) {
|
|
234
|
+
seen.add(key);
|
|
235
|
+
uniqueSuggestions.push(item);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
192
238
|
if (data.stage === "mixed") {
|
|
193
239
|
newState.cities = data.cities || [];
|
|
194
240
|
newState.streets = data.streets || [];
|
|
195
241
|
newState.suggestions = [];
|
|
196
242
|
} else {
|
|
197
|
-
newState.suggestions =
|
|
243
|
+
newState.suggestions = uniqueSuggestions;
|
|
198
244
|
newState.cities = [];
|
|
199
245
|
newState.streets = [];
|
|
246
|
+
if (data.stage === "final" && uniqueSuggestions.length === 1) {
|
|
247
|
+
autoSelect = true;
|
|
248
|
+
autoSelectItem = uniqueSuggestions[0];
|
|
249
|
+
}
|
|
200
250
|
}
|
|
201
251
|
newState.isValid = data.stage === "final";
|
|
202
|
-
|
|
252
|
+
if (autoSelect && autoSelectItem) {
|
|
253
|
+
newState.query = autoSelectItem.label;
|
|
254
|
+
newState.suggestions = [];
|
|
255
|
+
newState.cities = [];
|
|
256
|
+
newState.streets = [];
|
|
257
|
+
newState.isValid = true;
|
|
258
|
+
this.updateState(newState);
|
|
259
|
+
const val = typeof autoSelectItem.value === "object" ? autoSelectItem.value : autoSelectItem.label;
|
|
260
|
+
this.onSelect(val);
|
|
261
|
+
} else {
|
|
262
|
+
this.updateState(newState);
|
|
263
|
+
}
|
|
203
264
|
}
|
|
204
265
|
updateQueryAndFetch(nextQuery) {
|
|
205
266
|
this.updateState({ query: nextQuery, suggestions: [], cities: [], streets: [] });
|
|
206
|
-
this.
|
|
267
|
+
this.updateState({ isLoading: true, isValid: false });
|
|
268
|
+
this.debouncedFetch(nextQuery);
|
|
269
|
+
setTimeout(() => {
|
|
270
|
+
this.isSelecting = false;
|
|
271
|
+
}, 0);
|
|
207
272
|
}
|
|
208
273
|
replaceLastSegment(fullText, newSegment) {
|
|
209
274
|
const lastCommaIndex = fullText.lastIndexOf(",");
|
|
@@ -226,20 +291,136 @@ var InferCore = class {
|
|
|
226
291
|
}
|
|
227
292
|
debounce(func, wait) {
|
|
228
293
|
let timeout;
|
|
229
|
-
|
|
294
|
+
const debounced = (...args) => {
|
|
230
295
|
if (timeout) clearTimeout(timeout);
|
|
231
296
|
timeout = setTimeout(() => func.apply(this, args), wait);
|
|
232
297
|
};
|
|
298
|
+
debounced.cancel = () => {
|
|
299
|
+
if (timeout) {
|
|
300
|
+
clearTimeout(timeout);
|
|
301
|
+
timeout = void 0;
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
return debounced;
|
|
233
305
|
}
|
|
234
306
|
};
|
|
235
307
|
|
|
236
|
-
// src/
|
|
308
|
+
// ../core/src/default-styles.ts
|
|
309
|
+
var DEFAULT_STYLES = `
|
|
310
|
+
.pro6pp-wrapper {
|
|
311
|
+
position: relative;
|
|
312
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
313
|
+
box-sizing: border-box;
|
|
314
|
+
width: 100%;
|
|
315
|
+
}
|
|
316
|
+
.pro6pp-wrapper * {
|
|
317
|
+
box-sizing: border-box;
|
|
318
|
+
}
|
|
319
|
+
.pro6pp-input {
|
|
320
|
+
width: 100%;
|
|
321
|
+
padding: 10px 12px;
|
|
322
|
+
border: 1px solid #e0e0e0;
|
|
323
|
+
border-radius: 4px;
|
|
324
|
+
font-size: 16px;
|
|
325
|
+
line-height: 1.5;
|
|
326
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
327
|
+
}
|
|
328
|
+
.pro6pp-input:focus {
|
|
329
|
+
outline: none;
|
|
330
|
+
border-color: #3b82f6;
|
|
331
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
332
|
+
}
|
|
333
|
+
.pro6pp-dropdown {
|
|
334
|
+
position: absolute;
|
|
335
|
+
top: 100%;
|
|
336
|
+
left: 0;
|
|
337
|
+
right: 0;
|
|
338
|
+
z-index: 9999;
|
|
339
|
+
margin-top: 4px;
|
|
340
|
+
background: white;
|
|
341
|
+
border: 1px solid #e0e0e0;
|
|
342
|
+
border-radius: 4px;
|
|
343
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
344
|
+
max-height: 300px;
|
|
345
|
+
overflow-y: auto;
|
|
346
|
+
list-style: none !important;
|
|
347
|
+
padding: 0 !important;
|
|
348
|
+
margin: 0 !important;
|
|
349
|
+
overflow: hidden;
|
|
350
|
+
}
|
|
351
|
+
.pro6pp-item {
|
|
352
|
+
padding: 12px 12px 9px 12px;
|
|
353
|
+
cursor: pointer;
|
|
354
|
+
display: flex;
|
|
355
|
+
flex-direction: row;
|
|
356
|
+
align-items: center;
|
|
357
|
+
color: #000000;
|
|
358
|
+
font-size: 14px;
|
|
359
|
+
line-height: 1;
|
|
360
|
+
white-space: nowrap;
|
|
361
|
+
overflow: hidden;
|
|
362
|
+
border-radius: 0 !important;
|
|
363
|
+
margin: 0 !important;
|
|
364
|
+
}
|
|
365
|
+
.pro6pp-item:hover, .pro6pp-item--active {
|
|
366
|
+
background-color: #f5f5f5;
|
|
367
|
+
}
|
|
368
|
+
.pro6pp-item__label {
|
|
369
|
+
font-weight: 500;
|
|
370
|
+
flex-shrink: 0;
|
|
371
|
+
}
|
|
372
|
+
.pro6pp-item__subtitle {
|
|
373
|
+
font-size: 14px;
|
|
374
|
+
color: #404040;
|
|
375
|
+
overflow: hidden;
|
|
376
|
+
text-overflow: ellipsis;
|
|
377
|
+
flex-shrink: 1;
|
|
378
|
+
}
|
|
379
|
+
.pro6pp-item__chevron {
|
|
380
|
+
margin-left: auto;
|
|
381
|
+
display: flex;
|
|
382
|
+
align-items: center;
|
|
383
|
+
color: #a3a3a3;
|
|
384
|
+
padding-left: 8px;
|
|
385
|
+
}
|
|
386
|
+
.pro6pp-no-results {
|
|
387
|
+
padding: 12px;
|
|
388
|
+
color: #555555;
|
|
389
|
+
font-size: 14px;
|
|
390
|
+
text-align: center;
|
|
391
|
+
user-select: none;
|
|
392
|
+
pointer-events: none;
|
|
393
|
+
}
|
|
394
|
+
.pro6pp-loader {
|
|
395
|
+
position: absolute;
|
|
396
|
+
right: 12px;
|
|
397
|
+
top: 50%;
|
|
398
|
+
transform: translateY(-50%);
|
|
399
|
+
width: 16px;
|
|
400
|
+
height: 16px;
|
|
401
|
+
border: 2px solid #e0e0e0;
|
|
402
|
+
border-top-color: #404040;
|
|
403
|
+
border-radius: 50%;
|
|
404
|
+
animation: pro6pp-spin 0.6s linear infinite;
|
|
405
|
+
pointer-events: none;
|
|
406
|
+
}
|
|
407
|
+
@keyframes pro6pp-spin {
|
|
408
|
+
to { transform: translateY(-50%) rotate(360deg); }
|
|
409
|
+
}
|
|
410
|
+
`;
|
|
411
|
+
|
|
412
|
+
// src/index.tsx
|
|
237
413
|
function useInfer(config) {
|
|
238
414
|
const [state, setState] = useState(INITIAL_STATE);
|
|
239
415
|
const core = useMemo(() => {
|
|
240
416
|
return new InferCore({
|
|
241
417
|
...config,
|
|
242
|
-
onStateChange: (newState) =>
|
|
418
|
+
onStateChange: (newState) => {
|
|
419
|
+
setState({ ...newState });
|
|
420
|
+
if (config.onStateChange) {
|
|
421
|
+
config.onStateChange(newState);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
243
424
|
});
|
|
244
425
|
}, [config.country, config.authKey, config.limit]);
|
|
245
426
|
return {
|
|
@@ -253,6 +434,88 @@ function useInfer(config) {
|
|
|
253
434
|
selectItem: (item) => core.selectItem(item)
|
|
254
435
|
};
|
|
255
436
|
}
|
|
437
|
+
var Pro6PPInfer = ({
|
|
438
|
+
className,
|
|
439
|
+
style,
|
|
440
|
+
inputProps,
|
|
441
|
+
placeholder = "Start typing an address...",
|
|
442
|
+
renderItem,
|
|
443
|
+
disableDefaultStyles = false,
|
|
444
|
+
noResultsText = "No results found",
|
|
445
|
+
renderNoResults,
|
|
446
|
+
...config
|
|
447
|
+
}) => {
|
|
448
|
+
const { state, selectItem, inputProps: coreInputProps } = useInfer(config);
|
|
449
|
+
const inputRef = useRef(null);
|
|
450
|
+
useEffect(() => {
|
|
451
|
+
if (disableDefaultStyles) return;
|
|
452
|
+
const styleId = "pro6pp-styles";
|
|
453
|
+
if (!document.getElementById(styleId)) {
|
|
454
|
+
const styleEl = document.createElement("style");
|
|
455
|
+
styleEl.id = styleId;
|
|
456
|
+
styleEl.textContent = DEFAULT_STYLES;
|
|
457
|
+
document.head.appendChild(styleEl);
|
|
458
|
+
}
|
|
459
|
+
}, [disableDefaultStyles]);
|
|
460
|
+
const items = useMemo(() => {
|
|
461
|
+
return [
|
|
462
|
+
...state.cities.map((c) => ({ ...c, type: "city" })),
|
|
463
|
+
...state.streets.map((s) => ({ ...s, type: "street" })),
|
|
464
|
+
...state.suggestions.map((s) => ({ ...s, type: "suggestion" }))
|
|
465
|
+
];
|
|
466
|
+
}, [state.cities, state.streets, state.suggestions]);
|
|
467
|
+
const handleSelect = (item) => {
|
|
468
|
+
selectItem(item);
|
|
469
|
+
if (!state.isValid && inputRef.current) {
|
|
470
|
+
inputRef.current.focus();
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
const hasResults = items.length > 0;
|
|
474
|
+
const showNoResults = !state.isLoading && !state.isError && state.query.length > 0 && !hasResults && !state.isValid;
|
|
475
|
+
const showDropdown = hasResults || showNoResults;
|
|
476
|
+
return /* @__PURE__ */ React.createElement("div", { className: `pro6pp-wrapper ${className || ""}`, style }, /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(
|
|
477
|
+
"input",
|
|
478
|
+
{
|
|
479
|
+
ref: inputRef,
|
|
480
|
+
type: "text",
|
|
481
|
+
className: "pro6pp-input",
|
|
482
|
+
placeholder,
|
|
483
|
+
autoComplete: "off",
|
|
484
|
+
...inputProps,
|
|
485
|
+
...coreInputProps
|
|
486
|
+
}
|
|
487
|
+
), state.isLoading && /* @__PURE__ */ React.createElement("div", { className: "pro6pp-loader" })), showDropdown && /* @__PURE__ */ React.createElement("ul", { className: "pro6pp-dropdown", role: "listbox" }, hasResults ? items.map((item, index) => {
|
|
488
|
+
const isActive = index === state.selectedSuggestionIndex;
|
|
489
|
+
const secondaryText = item.subtitle || item.count;
|
|
490
|
+
const showChevron = item.value === void 0 || item.value === null;
|
|
491
|
+
return /* @__PURE__ */ React.createElement(
|
|
492
|
+
"li",
|
|
493
|
+
{
|
|
494
|
+
key: `${item.label}-${index}`,
|
|
495
|
+
role: "option",
|
|
496
|
+
"aria-selected": isActive,
|
|
497
|
+
className: `pro6pp-item ${isActive ? "pro6pp-item--active" : ""}`,
|
|
498
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
499
|
+
onClick: () => handleSelect(item)
|
|
500
|
+
},
|
|
501
|
+
renderItem ? renderItem(item, isActive) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { className: "pro6pp-item__label" }, item.label), secondaryText && /* @__PURE__ */ React.createElement("span", { className: "pro6pp-item__subtitle" }, ", ", secondaryText), showChevron && /* @__PURE__ */ React.createElement("div", { className: "pro6pp-item__chevron" }, /* @__PURE__ */ React.createElement(
|
|
502
|
+
"svg",
|
|
503
|
+
{
|
|
504
|
+
width: "16",
|
|
505
|
+
height: "16",
|
|
506
|
+
viewBox: "0 0 24 24",
|
|
507
|
+
fill: "none",
|
|
508
|
+
stroke: "currentColor",
|
|
509
|
+
strokeWidth: "2",
|
|
510
|
+
strokeLinecap: "round",
|
|
511
|
+
strokeLinejoin: "round"
|
|
512
|
+
},
|
|
513
|
+
/* @__PURE__ */ React.createElement("polyline", { points: "9 18 15 12 9 6" })
|
|
514
|
+
)))
|
|
515
|
+
);
|
|
516
|
+
}) : /* @__PURE__ */ React.createElement("li", { className: "pro6pp-no-results" }, renderNoResults ? renderNoResults(state) : noResultsText)));
|
|
517
|
+
};
|
|
256
518
|
export {
|
|
519
|
+
Pro6PPInfer,
|
|
257
520
|
useInfer
|
|
258
521
|
};
|
package/package.json
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"url": "https://github.com/pro6pp/infer-sdk/issues"
|
|
21
21
|
},
|
|
22
22
|
"sideEffects": false,
|
|
23
|
-
"version": "0.0.2-beta.
|
|
23
|
+
"version": "0.0.2-beta.7",
|
|
24
24
|
"main": "./dist/index.cjs",
|
|
25
25
|
"module": "./dist/index.js",
|
|
26
26
|
"types": "./dist/index.d.ts",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"react": ">=16"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@pro6pp/infer-core": "0.0.2-beta.
|
|
49
|
+
"@pro6pp/infer-core": "0.0.2-beta.5"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@testing-library/dom": "^10.4.1",
|