@pro6pp/infer-react 0.0.2-beta.0 → 0.0.2-beta.10

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/dist/index.js CHANGED
@@ -1,50 +1,750 @@
1
- "use strict";
2
1
  var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+
5
+ // src/index.tsx
6
+ import React, {
7
+ useState,
8
+ useMemo,
9
+ useEffect,
10
+ useRef,
11
+ forwardRef,
12
+ useImperativeHandle
13
+ } from "react";
14
+
15
+ // ../core/src/core.ts
16
+ var DEFAULTS = {
17
+ API_URL: "https://api.pro6pp.nl/v2",
18
+ LIMIT: 20,
19
+ DEBOUNCE_MS: 150,
20
+ MIN_DEBOUNCE_MS: 50,
21
+ MAX_RETRIES: 0
22
+ };
23
+ var PATTERNS = {
24
+ DIGITS_1_3: /^[0-9]{1,3}$/
25
+ };
26
+ var INITIAL_STATE = {
27
+ query: "",
28
+ stage: null,
29
+ cities: [],
30
+ streets: [],
31
+ suggestions: [],
32
+ isValid: false,
33
+ isError: false,
34
+ isLoading: false,
35
+ hasMore: false,
36
+ selectedSuggestionIndex: -1
9
37
  };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
38
+ var InferCore = class {
39
+ /**
40
+ * Initializes a new instance of the Infer engine.
41
+ * @param config The configuration object including API keys and callbacks.
42
+ */
43
+ constructor(config) {
44
+ __publicField(this, "country");
45
+ __publicField(this, "authKey");
46
+ __publicField(this, "explicitApiUrl");
47
+ __publicField(this, "baseLimit");
48
+ __publicField(this, "currentLimit");
49
+ __publicField(this, "maxRetries");
50
+ __publicField(this, "fetcher");
51
+ __publicField(this, "onStateChange");
52
+ __publicField(this, "onSelect");
53
+ /**
54
+ * The current read-only state of the engine.
55
+ * Use `onStateChange` to react to updates.
56
+ */
57
+ __publicField(this, "state");
58
+ __publicField(this, "abortController", null);
59
+ __publicField(this, "debouncedFetch");
60
+ __publicField(this, "isSelecting", false);
61
+ this.country = config.country;
62
+ this.authKey = config.authKey;
63
+ this.explicitApiUrl = config.apiUrl;
64
+ this.baseLimit = config.limit || DEFAULTS.LIMIT;
65
+ this.currentLimit = this.baseLimit;
66
+ const configRetries = config.maxRetries !== void 0 ? config.maxRetries : DEFAULTS.MAX_RETRIES;
67
+ this.maxRetries = Math.max(0, Math.min(configRetries, 10));
68
+ this.fetcher = config.fetcher || ((url, init) => fetch(url, init));
69
+ this.onStateChange = config.onStateChange || (() => {
70
+ });
71
+ this.onSelect = config.onSelect || (() => {
72
+ });
73
+ this.state = { ...INITIAL_STATE };
74
+ const configDebounce = config.debounceMs !== void 0 ? config.debounceMs : DEFAULTS.DEBOUNCE_MS;
75
+ const debounceTime = Math.max(configDebounce, DEFAULTS.MIN_DEBOUNCE_MS);
76
+ this.debouncedFetch = this.debounce((val) => this.executeFetch(val), debounceTime);
77
+ }
78
+ /**
79
+ * Processes new text input from the user.
80
+ * Triggers a debounced API request and updates the internal state.
81
+ * @param value The raw string from the input field.
82
+ */
83
+ handleInput(value) {
84
+ if (this.isSelecting) {
85
+ this.isSelecting = false;
86
+ return;
87
+ }
88
+ this.currentLimit = this.baseLimit;
89
+ const isEditingFinal = this.state.stage === "final" && value !== this.state.query;
90
+ this.updateState({
91
+ query: value,
92
+ isValid: false,
93
+ isLoading: !!value.trim(),
94
+ selectedSuggestionIndex: -1,
95
+ hasMore: false
96
+ });
97
+ if (isEditingFinal) {
98
+ this.onSelect(null);
99
+ }
100
+ this.debouncedFetch(value);
101
+ }
102
+ /**
103
+ * Increases the current limit and re-fetches the query to show more results.
104
+ */
105
+ loadMore() {
106
+ if (this.state.isLoading) return;
107
+ this.currentLimit += this.baseLimit;
108
+ this.updateState({ isLoading: true });
109
+ this.executeFetch(this.state.query);
110
+ }
111
+ /**
112
+ * Handles keyboard events for the input field.
113
+ * Supports:
114
+ * - `ArrowUp`/`ArrowDown`: Navigate through the suggestion list.
115
+ * - `Enter`: Select the currently highlighted suggestion.
116
+ * - `Space`: Automatically inserts a comma if a numeric house number is detected.
117
+ * @param event The keyboard event from the input element.
118
+ */
119
+ handleKeyDown(event) {
120
+ const target = event.target;
121
+ if (!target) return;
122
+ const totalItems = this.state.cities.length + this.state.streets.length + this.state.suggestions.length;
123
+ if (totalItems > 0) {
124
+ if (event.key === "ArrowDown") {
125
+ event.preventDefault();
126
+ let nextIndex = this.state.selectedSuggestionIndex + 1;
127
+ if (nextIndex >= totalItems) {
128
+ nextIndex = 0;
129
+ }
130
+ this.updateState({ selectedSuggestionIndex: nextIndex });
131
+ return;
132
+ }
133
+ if (event.key === "ArrowUp") {
134
+ event.preventDefault();
135
+ let nextIndex = this.state.selectedSuggestionIndex - 1;
136
+ if (nextIndex < 0) {
137
+ nextIndex = totalItems - 1;
138
+ }
139
+ this.updateState({ selectedSuggestionIndex: nextIndex });
140
+ return;
141
+ }
142
+ if (event.key === "Enter" && this.state.selectedSuggestionIndex >= 0) {
143
+ event.preventDefault();
144
+ const allItems = [...this.state.cities, ...this.state.streets, ...this.state.suggestions];
145
+ const item = allItems[this.state.selectedSuggestionIndex];
146
+ if (item) {
147
+ this.selectItem(item);
148
+ this.updateState({ selectedSuggestionIndex: -1 });
149
+ }
150
+ return;
151
+ }
152
+ }
153
+ const val = target.value;
154
+ if (event.key === " " && this.shouldAutoInsertComma(val)) {
155
+ event.preventDefault();
156
+ const next = `${val.trim()}, `;
157
+ this.updateQueryAndFetch(next);
158
+ }
159
+ }
160
+ /**
161
+ * Manually selects a suggestion or a string value.
162
+ * This is typically called when a user clicks a suggestion in the UI.
163
+ * @param item The suggestion object or string to select.
164
+ * @returns boolean True if the selection is a final address.
165
+ */
166
+ selectItem(item) {
167
+ this.debouncedFetch.cancel();
168
+ if (this.abortController) {
169
+ this.abortController.abort();
170
+ }
171
+ const label = typeof item === "string" ? item : item.label;
172
+ let logicValue = label;
173
+ if (typeof item !== "string" && typeof item.value === "string") {
174
+ logicValue = item.value;
175
+ }
176
+ const valueObj = typeof item !== "string" && typeof item.value === "object" ? item.value : void 0;
177
+ const isFullResult = !!valueObj && Object.keys(valueObj).length > 0;
178
+ this.isSelecting = true;
179
+ if (this.state.stage === "final" || isFullResult) {
180
+ let finalQuery = label;
181
+ if (valueObj && Object.keys(valueObj).length > 0) {
182
+ const { street, street_number, house_number, city } = valueObj;
183
+ const number = street_number || house_number;
184
+ if (street && number && city) {
185
+ finalQuery = `${street} ${number}, ${city}`;
186
+ }
187
+ }
188
+ this.finishSelection(finalQuery, valueObj);
189
+ return true;
190
+ }
191
+ const subtitle = typeof item !== "string" ? item.subtitle : null;
192
+ this.processSelection(logicValue, subtitle);
193
+ return false;
194
+ }
195
+ shouldAutoInsertComma(currentVal) {
196
+ const isStartOfSegmentAndNumeric = !currentVal.includes(",") && PATTERNS.DIGITS_1_3.test(currentVal.trim());
197
+ if (isStartOfSegmentAndNumeric) return true;
198
+ if (this.state.stage === "house_number") {
199
+ const currentFragment = this.getCurrentFragment(currentVal);
200
+ return PATTERNS.DIGITS_1_3.test(currentFragment);
201
+ }
202
+ return false;
203
+ }
204
+ finishSelection(label, value) {
205
+ this.updateState({
206
+ query: label,
207
+ suggestions: [],
208
+ cities: [],
209
+ streets: [],
210
+ isValid: true,
211
+ stage: "final",
212
+ hasMore: false
213
+ });
214
+ this.onSelect(value || label);
215
+ }
216
+ processSelection(text, subtitle) {
217
+ const { stage, query } = this.state;
218
+ let nextQuery = query;
219
+ const isContextualSelection = subtitle && (stage === "city" || stage === "street" || stage === "mixed");
220
+ if (isContextualSelection) {
221
+ if (stage === "city") {
222
+ nextQuery = `${subtitle}, ${text}, `;
223
+ } else {
224
+ const prefix = this.getQueryPrefix(query);
225
+ nextQuery = prefix ? `${prefix} ${text}, ${subtitle}, ` : `${text}, ${subtitle}, `;
226
+ }
227
+ this.updateQueryAndFetch(nextQuery);
228
+ return;
229
+ }
230
+ if (stage === "direct" || stage === "addition") {
231
+ this.finishSelection(text);
232
+ return;
233
+ }
234
+ const hasComma = query.includes(",");
235
+ const isFirstSegment = !hasComma && (stage === "city" || stage === "street" || stage === "house_number_first");
236
+ if (isFirstSegment) {
237
+ nextQuery = `${text}, `;
238
+ } else {
239
+ nextQuery = this.replaceLastSegment(query, text);
240
+ if (stage !== "house_number") {
241
+ nextQuery += ", ";
242
+ }
243
+ }
244
+ this.updateQueryAndFetch(nextQuery);
245
+ }
246
+ executeFetch(val, attempt = 0) {
247
+ const text = (val || "").toString();
248
+ if (!text.trim()) {
249
+ this.abortController?.abort();
250
+ this.resetState();
251
+ return;
252
+ }
253
+ if (attempt === 0) {
254
+ this.updateState({ isError: false });
255
+ if (this.abortController) this.abortController.abort();
256
+ this.abortController = new AbortController();
257
+ }
258
+ const currentSignal = this.abortController?.signal;
259
+ const baseUrl = this.explicitApiUrl ? this.explicitApiUrl : `${DEFAULTS.API_URL}/infer/${this.country.toLowerCase()}`;
260
+ const params = new URLSearchParams({
261
+ country: this.country.toLowerCase(),
262
+ query: text,
263
+ limit: this.currentLimit.toString()
264
+ });
265
+ if (this.authKey) {
266
+ params.set("authKey", this.authKey);
267
+ }
268
+ const separator = baseUrl.includes("?") ? "&" : "?";
269
+ const finalUrl = `${baseUrl}${separator}${params.toString()}`;
270
+ this.fetcher(finalUrl, { signal: currentSignal }).then((res) => {
271
+ if (!res.ok) {
272
+ if (attempt < this.maxRetries && (res.status >= 500 || res.status === 429)) {
273
+ return this.retry(val, attempt, currentSignal);
274
+ }
275
+ throw new Error("Network error");
276
+ }
277
+ return res.json();
278
+ }).then((data) => {
279
+ if (data) this.mapResponseToState(data);
280
+ }).catch((e) => {
281
+ if (e.name === "AbortError") return;
282
+ if (attempt < this.maxRetries) {
283
+ return this.retry(val, attempt, currentSignal);
284
+ }
285
+ this.updateState({ isError: true, isLoading: false });
286
+ });
287
+ }
288
+ retry(val, attempt, signal) {
289
+ if (signal?.aborted) return;
290
+ const delay = Math.pow(2, attempt) * 200;
291
+ setTimeout(() => {
292
+ if (!signal?.aborted) {
293
+ this.executeFetch(val, attempt + 1);
294
+ }
295
+ }, delay);
296
+ }
297
+ mapResponseToState(data) {
298
+ const newState = {
299
+ stage: data.stage,
300
+ isLoading: false
301
+ };
302
+ let autoSelect = false;
303
+ let autoSelectItem = null;
304
+ const rawSuggestions = data.suggestions || [];
305
+ const uniqueSuggestions = [];
306
+ const seen = /* @__PURE__ */ new Set();
307
+ for (const item of rawSuggestions) {
308
+ const key = `${item.label}|${item.subtitle || ""}|${JSON.stringify(item.value || {})}`;
309
+ if (!seen.has(key)) {
310
+ seen.add(key);
311
+ uniqueSuggestions.push(item);
312
+ }
313
+ }
314
+ const totalCount = uniqueSuggestions.length + (data.cities?.length || 0) + (data.streets?.length || 0);
315
+ newState.hasMore = totalCount >= this.currentLimit;
316
+ if (data.stage === "mixed") {
317
+ newState.cities = data.cities || [];
318
+ newState.streets = data.streets || [];
319
+ newState.suggestions = [];
320
+ } else {
321
+ newState.suggestions = uniqueSuggestions;
322
+ newState.cities = [];
323
+ newState.streets = [];
324
+ const firstItem = uniqueSuggestions[0];
325
+ const hasFullValue = firstItem && typeof firstItem.value === "object" && firstItem.value !== null && Object.keys(firstItem.value).length > 0;
326
+ if ((data.stage === "final" || hasFullValue) && uniqueSuggestions.length === 1) {
327
+ autoSelect = true;
328
+ autoSelectItem = firstItem;
329
+ }
330
+ }
331
+ newState.isValid = data.stage === "final";
332
+ if (autoSelect && autoSelectItem) {
333
+ newState.query = autoSelectItem.label;
334
+ newState.suggestions = [];
335
+ newState.cities = [];
336
+ newState.streets = [];
337
+ newState.isValid = true;
338
+ newState.hasMore = false;
339
+ this.isSelecting = true;
340
+ this.updateState(newState);
341
+ const val = typeof autoSelectItem.value === "object" ? autoSelectItem.value : autoSelectItem.label;
342
+ this.onSelect(val);
343
+ } else {
344
+ this.updateState(newState);
345
+ }
346
+ }
347
+ updateQueryAndFetch(nextQuery) {
348
+ this.updateState({ query: nextQuery, suggestions: [], cities: [], streets: [] });
349
+ this.updateState({ isLoading: true, isValid: false, hasMore: false });
350
+ this.debouncedFetch(nextQuery);
351
+ }
352
+ replaceLastSegment(fullText, newSegment) {
353
+ const lastCommaIndex = fullText.lastIndexOf(",");
354
+ if (lastCommaIndex === -1) return newSegment;
355
+ return `${fullText.slice(0, lastCommaIndex + 1)} ${newSegment}`.trim();
356
+ }
357
+ getQueryPrefix(q) {
358
+ const lastComma = q.lastIndexOf(",");
359
+ return lastComma === -1 ? "" : q.slice(0, lastComma + 1).trimEnd();
360
+ }
361
+ getCurrentFragment(q) {
362
+ return (q.split(",").slice(-1)[0] ?? "").trim();
363
+ }
364
+ resetState() {
365
+ this.updateState({ ...INITIAL_STATE, query: this.state.query });
366
+ }
367
+ updateState(updates) {
368
+ this.state = { ...this.state, ...updates };
369
+ this.onStateChange(this.state);
370
+ }
371
+ debounce(func, wait) {
372
+ let timeout;
373
+ const debounced = (...args) => {
374
+ if (timeout) clearTimeout(timeout);
375
+ timeout = setTimeout(() => func.apply(this, args), wait);
376
+ };
377
+ debounced.cancel = () => {
378
+ if (timeout) {
379
+ clearTimeout(timeout);
380
+ timeout = void 0;
381
+ }
382
+ };
383
+ return debounced;
15
384
  }
16
- return to;
17
385
  };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- useInfer: () => useInfer
24
- });
25
- module.exports = __toCommonJS(index_exports);
26
- var import_react = require("react");
27
- var import_infer_core = require("@pro6pp/infer-core");
386
+
387
+ // ../core/src/default-styles.ts
388
+ var DEFAULT_STYLES = `
389
+ .pro6pp-wrapper {
390
+ position: relative;
391
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
392
+ box-sizing: border-box;
393
+ width: 100%;
394
+ }
395
+ .pro6pp-wrapper * {
396
+ box-sizing: border-box;
397
+ }
398
+ .pro6pp-input {
399
+ width: 100%;
400
+ padding: 10px 12px;
401
+ padding-right: 48px;
402
+ border: 1px solid #e0e0e0;
403
+ border-radius: 4px;
404
+ font-size: 16px;
405
+ line-height: 1.5;
406
+ transition: border-color 0.2s, box-shadow 0.2s;
407
+ }
408
+ .pro6pp-input:focus {
409
+ outline: none;
410
+ border-color: #3b82f6;
411
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
412
+ }
413
+
414
+ .pro6pp-input-addons {
415
+ position: absolute;
416
+ right: 6px;
417
+ top: 0;
418
+ bottom: 0;
419
+ display: flex;
420
+ align-items: center;
421
+ gap: 2px;
422
+ pointer-events: none;
423
+ }
424
+ .pro6pp-input-addons > * {
425
+ pointer-events: auto;
426
+ }
427
+
428
+ .pro6pp-clear-button {
429
+ background: none;
430
+ border: none;
431
+ width: 28px;
432
+ height: 28px;
433
+ cursor: pointer;
434
+ color: #a3a3a3;
435
+ display: flex;
436
+ align-items: center;
437
+ justify-content: center;
438
+ border-radius: 50%;
439
+ transition: color 0.2s, background-color 0.2s, transform 0.1s;
440
+ }
441
+ .pro6pp-clear-button:hover {
442
+ color: #1f2937;
443
+ background-color: #f3f4f6;
444
+ }
445
+ .pro6pp-clear-button:active {
446
+ transform: scale(0.92);
447
+ }
448
+ .pro6pp-clear-button svg {
449
+ width: 18px;
450
+ height: 18px;
451
+ }
452
+
453
+ .pro6pp-loader {
454
+ width: 18px;
455
+ height: 18px;
456
+ margin: 0 4px;
457
+ border: 2px solid #e0e0e0;
458
+ border-top-color: #6b7280;
459
+ border-radius: 50%;
460
+ animation: pro6pp-spin 0.6s linear infinite;
461
+ flex-shrink: 0;
462
+ }
463
+
464
+ .pro6pp-dropdown {
465
+ position: absolute;
466
+ top: 100%;
467
+ left: 0;
468
+ right: 0;
469
+ z-index: 9999;
470
+ margin-top: 4px;
471
+ background: white;
472
+ border: 1px solid #e0e0e0;
473
+ border-radius: 4px;
474
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
475
+ max-height: 300px;
476
+ overflow-y: auto;
477
+ display: flex;
478
+ flex-direction: column;
479
+ }
480
+ .pro6pp-list {
481
+ list-style: none !important;
482
+ padding: 0 !important;
483
+ margin: 0 !important;
484
+ flex-grow: 1;
485
+ }
486
+ .pro6pp-item {
487
+ padding: 12px 16px;
488
+ cursor: pointer;
489
+ display: flex;
490
+ flex-direction: row;
491
+ align-items: center;
492
+ color: #111827;
493
+ font-size: 14px;
494
+ line-height: 1.2;
495
+ white-space: nowrap;
496
+ overflow: hidden;
497
+ }
498
+ .pro6pp-item:hover, .pro6pp-item--active {
499
+ background-color: #f9fafb;
500
+ }
501
+ .pro6pp-item__label {
502
+ font-weight: 500;
503
+ flex-shrink: 0;
504
+ }
505
+ .pro6pp-item__subtitle {
506
+ font-size: 14px;
507
+ color: #6b7280;
508
+ overflow: hidden;
509
+ text-overflow: ellipsis;
510
+ flex-shrink: 1;
511
+ }
512
+ .pro6pp-item__chevron {
513
+ margin-left: auto;
514
+ display: flex;
515
+ align-items: center;
516
+ color: #9ca3af;
517
+ padding-left: 8px;
518
+ }
519
+ .pro6pp-no-results {
520
+ padding: 16px;
521
+ color: #6b7280;
522
+ font-size: 14px;
523
+ text-align: center;
524
+ }
525
+ .pro6pp-load-more {
526
+ width: 100%;
527
+ padding: 10px;
528
+ background: #f9fafb;
529
+ border: none;
530
+ border-top: 1px solid #e0e0e0;
531
+ color: #3b82f6;
532
+ font-size: 13px;
533
+ font-weight: 600;
534
+ cursor: pointer;
535
+ transition: background-color 0.2s;
536
+ flex-shrink: 0;
537
+ }
538
+ .pro6pp-load-more:hover {
539
+ background-color: #f3f4f6;
540
+ }
541
+
542
+ @keyframes pro6pp-spin {
543
+ to { transform: rotate(360deg); }
544
+ }
545
+ `;
546
+
547
+ // src/index.tsx
28
548
  function useInfer(config) {
29
- const [state, setState] = (0, import_react.useState)(import_infer_core.INITIAL_STATE);
30
- const core = (0, import_react.useMemo)(() => {
31
- return new import_infer_core.InferCore({
549
+ const [state, setState] = useState(INITIAL_STATE);
550
+ const callbacksRef = useRef({
551
+ onStateChange: config.onStateChange,
552
+ onSelect: config.onSelect
553
+ });
554
+ useEffect(() => {
555
+ callbacksRef.current = {
556
+ onStateChange: config.onStateChange,
557
+ onSelect: config.onSelect
558
+ };
559
+ }, [config.onStateChange, config.onSelect]);
560
+ const core = useMemo(() => {
561
+ return new InferCore({
32
562
  ...config,
33
- onStateChange: (newState) => setState({ ...newState })
563
+ onStateChange: (newState) => {
564
+ setState({ ...newState });
565
+ callbacksRef.current.onStateChange?.(newState);
566
+ },
567
+ onSelect: (selection) => {
568
+ callbacksRef.current.onSelect?.(selection);
569
+ }
34
570
  });
35
- }, [config.country, config.authKey, config.limit]);
571
+ }, [
572
+ config.country,
573
+ config.authKey,
574
+ config.apiUrl,
575
+ config.fetcher,
576
+ config.limit,
577
+ config.debounceMs,
578
+ config.maxRetries
579
+ ]);
36
580
  return {
581
+ /** The current UI state (suggestions, loading status, query, etc.). */
37
582
  state,
583
+ /** The raw InferCore instance for manual control. */
38
584
  core,
585
+ /** Pre-configured event handlers to spread onto an <input /> element. */
39
586
  inputProps: {
40
587
  value: state.query,
41
588
  onChange: (e) => core.handleInput(e.target.value),
42
589
  onKeyDown: (e) => core.handleKeyDown(e)
43
590
  },
44
- selectItem: (item) => core.selectItem(item)
591
+ /** Function to manually select a specific suggestion. */
592
+ selectItem: (item) => core.selectItem(item),
593
+ /** Function to load more results. */
594
+ loadMore: () => core.loadMore()
45
595
  };
46
596
  }
47
- // Annotate the CommonJS export names for ESM import in node:
48
- 0 && (module.exports = {
597
+ var Pro6PPInfer = forwardRef(
598
+ ({
599
+ className,
600
+ style,
601
+ inputProps,
602
+ placeholder = "Start typing an address...",
603
+ renderItem,
604
+ disableDefaultStyles = false,
605
+ noResultsText = "No results found",
606
+ loadMoreText = "Show more results...",
607
+ renderNoResults,
608
+ showClearButton = true,
609
+ ...config
610
+ }, ref) => {
611
+ const { state, selectItem, loadMore, inputProps: coreInputProps, core } = useInfer(config);
612
+ const [isOpen, setIsOpen] = useState(false);
613
+ const internalInputRef = useRef(null);
614
+ const wrapperRef = useRef(null);
615
+ useImperativeHandle(ref, () => internalInputRef.current);
616
+ useEffect(() => {
617
+ if (disableDefaultStyles) return;
618
+ const styleId = "pro6pp-styles";
619
+ if (!document.getElementById(styleId)) {
620
+ const styleEl = document.createElement("style");
621
+ styleEl.id = styleId;
622
+ styleEl.textContent = DEFAULT_STYLES;
623
+ document.head.appendChild(styleEl);
624
+ }
625
+ }, [disableDefaultStyles]);
626
+ useEffect(() => {
627
+ const handleClickOutside = (event) => {
628
+ if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
629
+ setIsOpen(false);
630
+ }
631
+ };
632
+ document.addEventListener("mousedown", handleClickOutside);
633
+ return () => document.removeEventListener("mousedown", handleClickOutside);
634
+ }, []);
635
+ const items = useMemo(() => {
636
+ return [
637
+ ...state.cities.map((c) => ({ ...c, type: "city" })),
638
+ ...state.streets.map((s) => ({ ...s, type: "street" })),
639
+ ...state.suggestions.map((s) => ({ ...s, type: "suggestion" }))
640
+ ];
641
+ }, [state.cities, state.streets, state.suggestions]);
642
+ const handleSelect = (item) => {
643
+ const isFinal = selectItem(item);
644
+ if (!isFinal) {
645
+ setTimeout(() => internalInputRef.current?.focus(), 0);
646
+ } else {
647
+ setIsOpen(false);
648
+ }
649
+ };
650
+ const handleClear = () => {
651
+ core.handleInput("");
652
+ internalInputRef.current?.focus();
653
+ };
654
+ const hasResults = items.length > 0;
655
+ const showNoResults = !state.isLoading && !state.isError && state.query.length > 0 && !hasResults && !state.isValid;
656
+ const showDropdown = isOpen && (hasResults || state.isLoading || showNoResults);
657
+ return /* @__PURE__ */ React.createElement("div", { ref: wrapperRef, className: `pro6pp-wrapper ${className || ""}`, style }, /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(
658
+ "input",
659
+ {
660
+ ref: internalInputRef,
661
+ type: "text",
662
+ className: "pro6pp-input",
663
+ placeholder,
664
+ autoComplete: "off",
665
+ ...inputProps,
666
+ ...coreInputProps,
667
+ onFocus: (e) => {
668
+ setIsOpen(true);
669
+ inputProps?.onFocus?.(e);
670
+ }
671
+ }
672
+ ), /* @__PURE__ */ React.createElement("div", { className: "pro6pp-input-addons" }, state.isLoading && /* @__PURE__ */ React.createElement("div", { className: "pro6pp-loader" }), showClearButton && state.query.length > 0 && /* @__PURE__ */ React.createElement(
673
+ "button",
674
+ {
675
+ type: "button",
676
+ className: "pro6pp-clear-button",
677
+ onClick: handleClear,
678
+ "aria-label": "Clear input"
679
+ },
680
+ /* @__PURE__ */ React.createElement(
681
+ "svg",
682
+ {
683
+ width: "14",
684
+ height: "14",
685
+ viewBox: "0 0 24 24",
686
+ fill: "none",
687
+ stroke: "currentColor",
688
+ strokeWidth: "2",
689
+ strokeLinecap: "round",
690
+ strokeLinejoin: "round"
691
+ },
692
+ /* @__PURE__ */ React.createElement("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
693
+ /* @__PURE__ */ React.createElement("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
694
+ )
695
+ ))), showDropdown && /* @__PURE__ */ React.createElement(
696
+ "div",
697
+ {
698
+ className: "pro6pp-dropdown",
699
+ onWheel: (e) => e.stopPropagation(),
700
+ onMouseDown: (e) => e.stopPropagation()
701
+ },
702
+ /* @__PURE__ */ React.createElement("ul", { className: "pro6pp-list", role: "listbox" }, hasResults ? items.map((item, index) => {
703
+ const isActive = index === state.selectedSuggestionIndex;
704
+ const secondaryText = item.subtitle || (item.count !== void 0 ? item.count : "");
705
+ const showChevron = item.value === void 0 || item.value === null;
706
+ return /* @__PURE__ */ React.createElement(
707
+ "li",
708
+ {
709
+ key: `${item.label}-${index}`,
710
+ role: "option",
711
+ "aria-selected": isActive,
712
+ className: `pro6pp-item ${isActive ? "pro6pp-item--active" : ""}`,
713
+ onMouseDown: (e) => e.preventDefault(),
714
+ onClick: () => handleSelect(item)
715
+ },
716
+ 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(
717
+ "svg",
718
+ {
719
+ width: "16",
720
+ height: "16",
721
+ viewBox: "0 0 24 24",
722
+ fill: "none",
723
+ stroke: "currentColor",
724
+ strokeWidth: "2",
725
+ strokeLinecap: "round",
726
+ strokeLinejoin: "round"
727
+ },
728
+ /* @__PURE__ */ React.createElement("polyline", { points: "9 18 15 12 9 6" })
729
+ )))
730
+ );
731
+ }) : state.isLoading ? /* @__PURE__ */ React.createElement("li", { className: "pro6pp-no-results" }, "Loading suggestions...") : /* @__PURE__ */ React.createElement("li", { className: "pro6pp-no-results" }, renderNoResults ? renderNoResults(state) : noResultsText)),
732
+ state.hasMore && /* @__PURE__ */ React.createElement(
733
+ "button",
734
+ {
735
+ type: "button",
736
+ className: "pro6pp-load-more",
737
+ onClick: (e) => {
738
+ e.preventDefault();
739
+ loadMore();
740
+ }
741
+ },
742
+ loadMoreText
743
+ )
744
+ ));
745
+ }
746
+ );
747
+ export {
748
+ Pro6PPInfer,
49
749
  useInfer
50
- });
750
+ };