@pro6pp/infer-react 0.0.2-beta.15 → 0.0.2-beta.17

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 CHANGED
@@ -45,7 +45,7 @@ You can customize the appearance of the component via the following props:
45
45
  | `debounceMs` | Delay in ms before API search. Defaults to `150` (min `50`). |
46
46
  | `maxRetries` | Maximum retry attempts for transient network errors. Valid range: `0` to `10`. |
47
47
  | `showClearButton` | If `true`, displays a button to empty the input field. Defaults to `true`. |
48
- | `loadMoreText` | The text to display on the pagination button. |
48
+ | `loadingText` | The text displayed at the bottom of the list when fetching more results. |
49
49
 
50
50
  ---
51
51
 
package/dist/index.cjs CHANGED
@@ -38,6 +38,100 @@ __export(index_exports, {
38
38
  module.exports = __toCommonJS(index_exports);
39
39
  var import_react = __toESM(require("react"), 1);
40
40
 
41
+ // ../core/src/label-formatter.ts
42
+ function normalize(str) {
43
+ return str.toLowerCase().trim();
44
+ }
45
+ function escapeRegex(str) {
46
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
47
+ }
48
+ function findWordPosition(query, value) {
49
+ const normalizedQuery = normalize(query);
50
+ const normalizedValue = normalize(value);
51
+ if (normalizedValue.includes(" ")) {
52
+ return normalizedQuery.indexOf(normalizedValue);
53
+ }
54
+ const pattern = new RegExp(`(?:^|[,\\s])${escapeRegex(normalizedValue)}(?:$|[,\\s])`, "g");
55
+ const match = pattern.exec(normalizedQuery);
56
+ if (match) {
57
+ const matchStart = match.index;
58
+ const firstChar = normalizedQuery[matchStart];
59
+ if (firstChar === "," || firstChar === " ") {
60
+ return matchStart + 1;
61
+ }
62
+ return matchStart;
63
+ }
64
+ return -1;
65
+ }
66
+ function detectComponentOrder(query, value) {
67
+ const detected = [];
68
+ const componentMap = [];
69
+ if (value.street) {
70
+ componentMap.push({ value: value.street, type: "street" });
71
+ }
72
+ if (value.city) {
73
+ componentMap.push({ value: value.city, type: "city" });
74
+ }
75
+ if (value.postcode) {
76
+ componentMap.push({ value: value.postcode, type: "postcode" });
77
+ }
78
+ if (value.street_number !== void 0 && value.street_number !== null) {
79
+ componentMap.push({ value: String(value.street_number), type: "street_number" });
80
+ }
81
+ if (value.addition) {
82
+ componentMap.push({ value: value.addition, type: "addition" });
83
+ }
84
+ for (const comp of componentMap) {
85
+ const position = findWordPosition(query, comp.value);
86
+ if (position !== -1) {
87
+ detected.push({
88
+ type: comp.type,
89
+ value: comp.value,
90
+ position
91
+ });
92
+ }
93
+ }
94
+ detected.sort((a, b) => a.position - b.position);
95
+ return detected;
96
+ }
97
+ function formatLabelByInputOrder(query, value) {
98
+ if (!value || !query) {
99
+ return "";
100
+ }
101
+ const detectedOrder = detectComponentOrder(query, value);
102
+ const detectedTypes = new Set(detectedOrder.map((d) => d.type));
103
+ const parts = [];
104
+ for (const detected of detectedOrder) {
105
+ parts.push(detected.value);
106
+ }
107
+ const defaultOrder = ["street", "street_number", "addition", "postcode", "city"];
108
+ for (const type of defaultOrder) {
109
+ if (detectedTypes.has(type)) continue;
110
+ let val;
111
+ switch (type) {
112
+ case "street":
113
+ val = value.street;
114
+ break;
115
+ case "city":
116
+ val = value.city;
117
+ break;
118
+ case "street_number":
119
+ val = value.street_number !== void 0 ? String(value.street_number) : void 0;
120
+ break;
121
+ case "postcode":
122
+ val = value.postcode;
123
+ break;
124
+ case "addition":
125
+ val = value.addition;
126
+ break;
127
+ }
128
+ if (val) {
129
+ parts.push(val);
130
+ }
131
+ }
132
+ return parts.join(", ");
133
+ }
134
+
41
135
  // ../core/src/core.ts
42
136
  var DEFAULTS = {
43
137
  API_URL: "https://api.pro6pp.nl/v2",
@@ -203,10 +297,11 @@ var InferCore = class {
203
297
  if (this.state.stage === "final" || isFullResult) {
204
298
  let finalQuery = label;
205
299
  if (valueObj && Object.keys(valueObj).length > 0) {
206
- const { street, street_number, city, addition } = valueObj;
300
+ const { street, street_number, postcode, city, addition } = valueObj;
207
301
  if (street && street_number && city) {
208
302
  const suffix = addition ? ` ${addition}` : "";
209
- finalQuery = `${street} ${street_number}${suffix}, ${city}`;
303
+ const postcodeStr = postcode ? `${postcode}, ` : "";
304
+ finalQuery = `${street} ${street_number}${suffix}, ${postcodeStr}${city}`;
210
305
  }
211
306
  }
212
307
  this.finishSelection(finalQuery, valueObj);
@@ -348,7 +443,8 @@ var InferCore = class {
348
443
  const key = `${item.label}|${item.subtitle || ""}|${JSON.stringify(item.value || {})}`;
349
444
  if (!seen.has(key)) {
350
445
  seen.add(key);
351
- uniqueSuggestions.push(item);
446
+ const reformattedItem = this.reformatSuggestionLabel(item);
447
+ uniqueSuggestions.push(reformattedItem);
352
448
  }
353
449
  }
354
450
  const totalCount = uniqueSuggestions.length + (data.cities?.length || 0) + (data.streets?.length || 0);
@@ -372,6 +468,25 @@ var InferCore = class {
372
468
  this.selectItem(uniqueSuggestions[0]);
373
469
  }
374
470
  }
471
+ /**
472
+ * Reformats a suggestion's label based on the user's input order.
473
+ * If the suggestion has a structured value object, we reorder the label
474
+ * to match how the user typed the components.
475
+ */
476
+ reformatSuggestionLabel(item) {
477
+ if (!item.value || typeof item.value === "string") {
478
+ return item;
479
+ }
480
+ const addressValue = item.value;
481
+ if (!addressValue.street || !addressValue.city) {
482
+ return item;
483
+ }
484
+ const reformattedLabel = formatLabelByInputOrder(this.state.query, addressValue);
485
+ if (reformattedLabel) {
486
+ return { ...item, label: reformattedLabel };
487
+ }
488
+ return item;
489
+ }
375
490
  updateQueryAndFetch(nextQuery) {
376
491
  this.updateState({
377
492
  query: nextQuery,
@@ -421,6 +536,19 @@ var InferCore = class {
421
536
  };
422
537
 
423
538
  // ../core/src/highlight.ts
539
+ function mergeSegments(segments) {
540
+ if (segments.length === 0) return segments;
541
+ const merged = [];
542
+ for (const seg of segments) {
543
+ const last = merged[merged.length - 1];
544
+ if (last && last.match === seg.match) {
545
+ last.text += seg.text;
546
+ } else {
547
+ merged.push({ text: seg.text, match: seg.match });
548
+ }
549
+ }
550
+ return merged;
551
+ }
424
552
  function getHighlightSegments(text, query) {
425
553
  if (!query || !text) return [{ text, match: false }];
426
554
  const segments = [];
@@ -447,7 +575,7 @@ function getHighlightSegments(text, query) {
447
575
  if (!isFullMatch) {
448
576
  return [{ text, match: false }];
449
577
  }
450
- return segments;
578
+ return mergeSegments(segments);
451
579
  }
452
580
 
453
581
  // ../core/src/default-styles.ts
@@ -524,17 +652,6 @@ var DEFAULT_STYLES = `
524
652
  background-color: #f3f4f6;
525
653
  }
526
654
 
527
- .pro6pp-loader {
528
- width: 20px;
529
- height: 20px;
530
- margin: 0 8px;
531
- border: 2px solid #e0e0e0;
532
- border-top-color: #6b7280;
533
- border-radius: 50%;
534
- animation: pro6pp-spin 0.6s linear infinite;
535
- flex-shrink: 0;
536
- }
537
-
538
655
  .pro6pp-dropdown {
539
656
  position: absolute;
540
657
  top: 100%;
@@ -594,15 +711,23 @@ var DEFAULT_STYLES = `
594
711
  }
595
712
 
596
713
  .pro6pp-item__label {
597
- font-weight: 500;
714
+ font-weight: 400;
598
715
  flex-shrink: 1;
599
716
  overflow: hidden;
600
717
  text-overflow: ellipsis;
601
718
  white-space: nowrap;
602
719
  }
603
720
 
721
+ .pro6pp-item__label--match {
722
+ font-weight: 520;
723
+ }
724
+
725
+ .pro6pp-item__label--unmatched {
726
+ font-weight: 400;
727
+ color: #4b5563;
728
+ }
729
+
604
730
  .pro6pp-item__subtitle {
605
- font-size: 13px;
606
731
  color: #6b7280;
607
732
  flex-shrink: 0;
608
733
  }
@@ -622,18 +747,25 @@ var DEFAULT_STYLES = `
622
747
  text-align: center;
623
748
  }
624
749
 
625
- .pro6pp-load-more {
626
- width: 100%;
627
- padding: 14px;
628
- background: #f9fafb;
629
- border: none;
630
- border-top: 1px solid #e0e0e0;
631
- color: #3b82f6;
632
- font-size: 14px;
633
- font-weight: 600;
634
- cursor: pointer;
635
- flex-shrink: 0;
636
- touch-action: manipulation;
750
+ .pro6pp-loader-item {
751
+ padding: 10px 12px;
752
+ color: #6b7280;
753
+ font-size: 0.875rem;
754
+ display: flex;
755
+ align-items: center;
756
+ justify-content: center;
757
+ gap: 8px;
758
+ background-color: #f9fafb;
759
+ border-top: 1px solid #f3f4f6;
760
+ }
761
+
762
+ .pro6pp-mini-spinner {
763
+ width: 14px;
764
+ height: 14px;
765
+ border: 2px solid #e5e7eb;
766
+ border-top-color: #6b7280;
767
+ border-radius: 50%;
768
+ animation: pro6pp-spin 0.6s linear infinite;
637
769
  }
638
770
 
639
771
  @media (max-width: 640px) {
@@ -645,17 +777,6 @@ var DEFAULT_STYLES = `
645
777
  padding: 10px 12px;
646
778
  font-size: 14px;
647
779
  }
648
- .pro6pp-item__subtitle {
649
- font-size: 12px;
650
- }
651
- .pro6pp-load-more {
652
- padding: 12px;
653
- font-size: 13px;
654
- }
655
- }
656
-
657
- .pro6pp-load-more:active {
658
- background-color: #f3f4f6;
659
780
  }
660
781
 
661
782
  @keyframes pro6pp-spin {
@@ -667,16 +788,17 @@ var DEFAULT_STYLES = `
667
788
  var HighlightedText = ({ text, query }) => {
668
789
  const segments = (0, import_react.useMemo)(() => getHighlightSegments(text, query), [text, query]);
669
790
  return /* @__PURE__ */ import_react.default.createElement("span", { className: "pro6pp-item__label" }, segments.map(
670
- (seg, i) => seg.match ? /* @__PURE__ */ import_react.default.createElement("strong", { key: i, className: "pro6pp-item__label--match" }, seg.text) : seg.text
791
+ (seg, i) => seg.match ? /* @__PURE__ */ import_react.default.createElement("span", { key: i, className: "pro6pp-item__label--match" }, seg.text) : /* @__PURE__ */ import_react.default.createElement("span", { key: i, className: "pro6pp-item__label--unmatched" }, seg.text)
671
792
  ));
672
793
  };
673
794
  function useInfer(config) {
674
795
  const [state, setState] = (0, import_react.useState)(() => {
675
796
  if (config.initialValue) {
797
+ const postcodeStr = config.initialValue.postcode ? `${config.initialValue.postcode}, ` : "";
676
798
  return {
677
799
  ...INITIAL_STATE,
678
800
  value: config.initialValue,
679
- query: `${config.initialValue.street} ${config.initialValue.street_number}, ${config.initialValue.city}`,
801
+ query: `${config.initialValue.street} ${config.initialValue.street_number}, ${postcodeStr}${config.initialValue.city}`,
680
802
  isValid: true,
681
803
  stage: "final"
682
804
  };
@@ -706,7 +828,8 @@ function useInfer(config) {
706
828
  });
707
829
  if (config.initialValue) {
708
830
  const address = config.initialValue;
709
- const label = `${address.street} ${address.street_number}, ${address.city}`;
831
+ const postcodeStr = address.postcode ? `${address.postcode}, ` : "";
832
+ const label = `${address.street} ${address.street_number}, ${postcodeStr}${address.city}`;
710
833
  instance.selectItem({ label, value: address });
711
834
  }
712
835
  return instance;
@@ -722,7 +845,8 @@ function useInfer(config) {
722
845
  ]);
723
846
  const setValue = (address) => {
724
847
  if (!address) return;
725
- const label = `${address.street} ${address.street_number}, ${address.city}`;
848
+ const postcodeStr = address.postcode ? `${address.postcode}, ` : "";
849
+ const label = `${address.street} ${address.street_number}, ${postcodeStr}${address.city}`;
726
850
  core.selectItem({ label, value: address });
727
851
  };
728
852
  return {
@@ -753,7 +877,7 @@ var Pro6PPInfer = (0, import_react.forwardRef)(
753
877
  renderItem,
754
878
  disableDefaultStyles = false,
755
879
  noResultsText = "No results found",
756
- loadMoreText = "Show more results...",
880
+ loadingText = "Loading more...",
757
881
  renderNoResults,
758
882
  showClearButton = true,
759
883
  ...config
@@ -762,6 +886,7 @@ var Pro6PPInfer = (0, import_react.forwardRef)(
762
886
  const [isOpen, setIsOpen] = (0, import_react.useState)(false);
763
887
  const internalInputRef = (0, import_react.useRef)(null);
764
888
  const wrapperRef = (0, import_react.useRef)(null);
889
+ const observerTarget = (0, import_react.useRef)(null);
765
890
  (0, import_react.useImperativeHandle)(ref, () => internalInputRef.current);
766
891
  (0, import_react.useEffect)(() => {
767
892
  if (disableDefaultStyles) return;
@@ -782,6 +907,22 @@ var Pro6PPInfer = (0, import_react.forwardRef)(
782
907
  document.addEventListener("mousedown", handleClickOutside);
783
908
  return () => document.removeEventListener("mousedown", handleClickOutside);
784
909
  }, []);
910
+ (0, import_react.useEffect)(() => {
911
+ const currentTarget = observerTarget.current;
912
+ if (!currentTarget) return;
913
+ const observer = new IntersectionObserver(
914
+ (entries) => {
915
+ if (entries[0].isIntersecting && state.hasMore && !state.isLoading) {
916
+ loadMore();
917
+ }
918
+ },
919
+ { threshold: 0.1 }
920
+ );
921
+ observer.observe(currentTarget);
922
+ return () => {
923
+ if (currentTarget) observer.unobserve(currentTarget);
924
+ };
925
+ }, [state.hasMore, state.isLoading, loadMore, isOpen]);
785
926
  const items = (0, import_react.useMemo)(() => {
786
927
  return [
787
928
  ...state.cities.map((c) => ({ ...c, type: "city" })),
@@ -804,6 +945,7 @@ var Pro6PPInfer = (0, import_react.forwardRef)(
804
945
  const hasResults = items.length > 0;
805
946
  const showNoResults = !state.isLoading && !state.isError && state.query.length > 0 && !hasResults && !state.isValid;
806
947
  const showDropdown = isOpen && (hasResults || state.isLoading || showNoResults);
948
+ const isInfiniteLoading = state.isLoading && items.length > 0;
807
949
  return /* @__PURE__ */ import_react.default.createElement("div", { ref: wrapperRef, className: `pro6pp-wrapper ${className || ""}`, style }, /* @__PURE__ */ import_react.default.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ import_react.default.createElement(
808
950
  "input",
809
951
  {
@@ -824,7 +966,7 @@ var Pro6PPInfer = (0, import_react.forwardRef)(
824
966
  inputProps?.onFocus?.(e);
825
967
  }
826
968
  }
827
- ), /* @__PURE__ */ import_react.default.createElement("div", { className: "pro6pp-input-addons" }, state.isLoading && /* @__PURE__ */ import_react.default.createElement("div", { className: "pro6pp-loader" }), showClearButton && state.query.length > 0 && /* @__PURE__ */ import_react.default.createElement(
969
+ ), /* @__PURE__ */ import_react.default.createElement("div", { className: "pro6pp-input-addons" }, showClearButton && state.query.length > 0 && /* @__PURE__ */ import_react.default.createElement(
828
970
  "button",
829
971
  {
830
972
  type: "button",
@@ -854,7 +996,7 @@ var Pro6PPInfer = (0, import_react.forwardRef)(
854
996
  onWheel: (e) => e.stopPropagation(),
855
997
  onMouseDown: (e) => e.stopPropagation()
856
998
  },
857
- /* @__PURE__ */ import_react.default.createElement("ul", { className: "pro6pp-list", role: "listbox" }, hasResults ? items.map((item, index) => {
999
+ /* @__PURE__ */ import_react.default.createElement("ul", { className: "pro6pp-list", role: "listbox" }, hasResults ? /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, items.map((item, index) => {
858
1000
  const isActive = index === state.selectedSuggestionIndex;
859
1001
  const secondaryText = item.subtitle || (item.count !== void 0 ? item.count : "");
860
1002
  const showChevron = item.value === void 0 || item.value === null;
@@ -883,19 +1025,7 @@ var Pro6PPInfer = (0, import_react.forwardRef)(
883
1025
  /* @__PURE__ */ import_react.default.createElement("polyline", { points: "9 18 15 12 9 6" })
884
1026
  )))
885
1027
  );
886
- }) : state.isLoading ? /* @__PURE__ */ import_react.default.createElement("li", { className: "pro6pp-no-results" }, "Loading suggestions...") : /* @__PURE__ */ import_react.default.createElement("li", { className: "pro6pp-no-results" }, renderNoResults ? renderNoResults(state) : noResultsText)),
887
- state.hasMore && /* @__PURE__ */ import_react.default.createElement(
888
- "button",
889
- {
890
- type: "button",
891
- className: "pro6pp-load-more",
892
- onClick: (e) => {
893
- e.preventDefault();
894
- loadMore();
895
- }
896
- },
897
- loadMoreText
898
- )
1028
+ }), state.hasMore && !state.isLoading && /* @__PURE__ */ import_react.default.createElement("li", { key: "sentinel", ref: observerTarget, style: { height: "1px", opacity: 0 } }), isInfiniteLoading && /* @__PURE__ */ import_react.default.createElement("li", { key: "loader", className: "pro6pp-loader-item" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "pro6pp-mini-spinner" }), /* @__PURE__ */ import_react.default.createElement("span", null, loadingText))) : state.isLoading ? /* @__PURE__ */ import_react.default.createElement("li", { className: "pro6pp-no-results" }, "Searching...") : /* @__PURE__ */ import_react.default.createElement("li", { className: "pro6pp-no-results" }, renderNoResults ? renderNoResults(state) : noResultsText))
899
1029
  ));
900
1030
  }
901
1031
  );
package/dist/index.d.cts CHANGED
@@ -58,10 +58,10 @@ interface Pro6PPInferProps extends UseInferConfig {
58
58
  * @default 'No results found'
59
59
  */
60
60
  noResultsText?: string;
61
- /** * The text to show on the load more button.
62
- * @default 'Show more results...'
61
+ /** * The text to show on the bottom loading indicator.
62
+ * @default 'Loading more...'
63
63
  */
64
- loadMoreText?: string;
64
+ loadingText?: string;
65
65
  /** A custom render function for the "no results" state. */
66
66
  renderNoResults?: (state: InferState) => React.ReactNode;
67
67
  /**
package/dist/index.d.ts CHANGED
@@ -58,10 +58,10 @@ interface Pro6PPInferProps extends UseInferConfig {
58
58
  * @default 'No results found'
59
59
  */
60
60
  noResultsText?: string;
61
- /** * The text to show on the load more button.
62
- * @default 'Show more results...'
61
+ /** * The text to show on the bottom loading indicator.
62
+ * @default 'Loading more...'
63
63
  */
64
- loadMoreText?: string;
64
+ loadingText?: string;
65
65
  /** A custom render function for the "no results" state. */
66
66
  renderNoResults?: (state: InferState) => React.ReactNode;
67
67
  /**
package/dist/index.js CHANGED
@@ -12,6 +12,100 @@ import React, {
12
12
  useImperativeHandle
13
13
  } from "react";
14
14
 
15
+ // ../core/src/label-formatter.ts
16
+ function normalize(str) {
17
+ return str.toLowerCase().trim();
18
+ }
19
+ function escapeRegex(str) {
20
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
21
+ }
22
+ function findWordPosition(query, value) {
23
+ const normalizedQuery = normalize(query);
24
+ const normalizedValue = normalize(value);
25
+ if (normalizedValue.includes(" ")) {
26
+ return normalizedQuery.indexOf(normalizedValue);
27
+ }
28
+ const pattern = new RegExp(`(?:^|[,\\s])${escapeRegex(normalizedValue)}(?:$|[,\\s])`, "g");
29
+ const match = pattern.exec(normalizedQuery);
30
+ if (match) {
31
+ const matchStart = match.index;
32
+ const firstChar = normalizedQuery[matchStart];
33
+ if (firstChar === "," || firstChar === " ") {
34
+ return matchStart + 1;
35
+ }
36
+ return matchStart;
37
+ }
38
+ return -1;
39
+ }
40
+ function detectComponentOrder(query, value) {
41
+ const detected = [];
42
+ const componentMap = [];
43
+ if (value.street) {
44
+ componentMap.push({ value: value.street, type: "street" });
45
+ }
46
+ if (value.city) {
47
+ componentMap.push({ value: value.city, type: "city" });
48
+ }
49
+ if (value.postcode) {
50
+ componentMap.push({ value: value.postcode, type: "postcode" });
51
+ }
52
+ if (value.street_number !== void 0 && value.street_number !== null) {
53
+ componentMap.push({ value: String(value.street_number), type: "street_number" });
54
+ }
55
+ if (value.addition) {
56
+ componentMap.push({ value: value.addition, type: "addition" });
57
+ }
58
+ for (const comp of componentMap) {
59
+ const position = findWordPosition(query, comp.value);
60
+ if (position !== -1) {
61
+ detected.push({
62
+ type: comp.type,
63
+ value: comp.value,
64
+ position
65
+ });
66
+ }
67
+ }
68
+ detected.sort((a, b) => a.position - b.position);
69
+ return detected;
70
+ }
71
+ function formatLabelByInputOrder(query, value) {
72
+ if (!value || !query) {
73
+ return "";
74
+ }
75
+ const detectedOrder = detectComponentOrder(query, value);
76
+ const detectedTypes = new Set(detectedOrder.map((d) => d.type));
77
+ const parts = [];
78
+ for (const detected of detectedOrder) {
79
+ parts.push(detected.value);
80
+ }
81
+ const defaultOrder = ["street", "street_number", "addition", "postcode", "city"];
82
+ for (const type of defaultOrder) {
83
+ if (detectedTypes.has(type)) continue;
84
+ let val;
85
+ switch (type) {
86
+ case "street":
87
+ val = value.street;
88
+ break;
89
+ case "city":
90
+ val = value.city;
91
+ break;
92
+ case "street_number":
93
+ val = value.street_number !== void 0 ? String(value.street_number) : void 0;
94
+ break;
95
+ case "postcode":
96
+ val = value.postcode;
97
+ break;
98
+ case "addition":
99
+ val = value.addition;
100
+ break;
101
+ }
102
+ if (val) {
103
+ parts.push(val);
104
+ }
105
+ }
106
+ return parts.join(", ");
107
+ }
108
+
15
109
  // ../core/src/core.ts
16
110
  var DEFAULTS = {
17
111
  API_URL: "https://api.pro6pp.nl/v2",
@@ -177,10 +271,11 @@ var InferCore = class {
177
271
  if (this.state.stage === "final" || isFullResult) {
178
272
  let finalQuery = label;
179
273
  if (valueObj && Object.keys(valueObj).length > 0) {
180
- const { street, street_number, city, addition } = valueObj;
274
+ const { street, street_number, postcode, city, addition } = valueObj;
181
275
  if (street && street_number && city) {
182
276
  const suffix = addition ? ` ${addition}` : "";
183
- finalQuery = `${street} ${street_number}${suffix}, ${city}`;
277
+ const postcodeStr = postcode ? `${postcode}, ` : "";
278
+ finalQuery = `${street} ${street_number}${suffix}, ${postcodeStr}${city}`;
184
279
  }
185
280
  }
186
281
  this.finishSelection(finalQuery, valueObj);
@@ -322,7 +417,8 @@ var InferCore = class {
322
417
  const key = `${item.label}|${item.subtitle || ""}|${JSON.stringify(item.value || {})}`;
323
418
  if (!seen.has(key)) {
324
419
  seen.add(key);
325
- uniqueSuggestions.push(item);
420
+ const reformattedItem = this.reformatSuggestionLabel(item);
421
+ uniqueSuggestions.push(reformattedItem);
326
422
  }
327
423
  }
328
424
  const totalCount = uniqueSuggestions.length + (data.cities?.length || 0) + (data.streets?.length || 0);
@@ -346,6 +442,25 @@ var InferCore = class {
346
442
  this.selectItem(uniqueSuggestions[0]);
347
443
  }
348
444
  }
445
+ /**
446
+ * Reformats a suggestion's label based on the user's input order.
447
+ * If the suggestion has a structured value object, we reorder the label
448
+ * to match how the user typed the components.
449
+ */
450
+ reformatSuggestionLabel(item) {
451
+ if (!item.value || typeof item.value === "string") {
452
+ return item;
453
+ }
454
+ const addressValue = item.value;
455
+ if (!addressValue.street || !addressValue.city) {
456
+ return item;
457
+ }
458
+ const reformattedLabel = formatLabelByInputOrder(this.state.query, addressValue);
459
+ if (reformattedLabel) {
460
+ return { ...item, label: reformattedLabel };
461
+ }
462
+ return item;
463
+ }
349
464
  updateQueryAndFetch(nextQuery) {
350
465
  this.updateState({
351
466
  query: nextQuery,
@@ -395,6 +510,19 @@ var InferCore = class {
395
510
  };
396
511
 
397
512
  // ../core/src/highlight.ts
513
+ function mergeSegments(segments) {
514
+ if (segments.length === 0) return segments;
515
+ const merged = [];
516
+ for (const seg of segments) {
517
+ const last = merged[merged.length - 1];
518
+ if (last && last.match === seg.match) {
519
+ last.text += seg.text;
520
+ } else {
521
+ merged.push({ text: seg.text, match: seg.match });
522
+ }
523
+ }
524
+ return merged;
525
+ }
398
526
  function getHighlightSegments(text, query) {
399
527
  if (!query || !text) return [{ text, match: false }];
400
528
  const segments = [];
@@ -421,7 +549,7 @@ function getHighlightSegments(text, query) {
421
549
  if (!isFullMatch) {
422
550
  return [{ text, match: false }];
423
551
  }
424
- return segments;
552
+ return mergeSegments(segments);
425
553
  }
426
554
 
427
555
  // ../core/src/default-styles.ts
@@ -498,17 +626,6 @@ var DEFAULT_STYLES = `
498
626
  background-color: #f3f4f6;
499
627
  }
500
628
 
501
- .pro6pp-loader {
502
- width: 20px;
503
- height: 20px;
504
- margin: 0 8px;
505
- border: 2px solid #e0e0e0;
506
- border-top-color: #6b7280;
507
- border-radius: 50%;
508
- animation: pro6pp-spin 0.6s linear infinite;
509
- flex-shrink: 0;
510
- }
511
-
512
629
  .pro6pp-dropdown {
513
630
  position: absolute;
514
631
  top: 100%;
@@ -568,15 +685,23 @@ var DEFAULT_STYLES = `
568
685
  }
569
686
 
570
687
  .pro6pp-item__label {
571
- font-weight: 500;
688
+ font-weight: 400;
572
689
  flex-shrink: 1;
573
690
  overflow: hidden;
574
691
  text-overflow: ellipsis;
575
692
  white-space: nowrap;
576
693
  }
577
694
 
695
+ .pro6pp-item__label--match {
696
+ font-weight: 520;
697
+ }
698
+
699
+ .pro6pp-item__label--unmatched {
700
+ font-weight: 400;
701
+ color: #4b5563;
702
+ }
703
+
578
704
  .pro6pp-item__subtitle {
579
- font-size: 13px;
580
705
  color: #6b7280;
581
706
  flex-shrink: 0;
582
707
  }
@@ -596,18 +721,25 @@ var DEFAULT_STYLES = `
596
721
  text-align: center;
597
722
  }
598
723
 
599
- .pro6pp-load-more {
600
- width: 100%;
601
- padding: 14px;
602
- background: #f9fafb;
603
- border: none;
604
- border-top: 1px solid #e0e0e0;
605
- color: #3b82f6;
606
- font-size: 14px;
607
- font-weight: 600;
608
- cursor: pointer;
609
- flex-shrink: 0;
610
- touch-action: manipulation;
724
+ .pro6pp-loader-item {
725
+ padding: 10px 12px;
726
+ color: #6b7280;
727
+ font-size: 0.875rem;
728
+ display: flex;
729
+ align-items: center;
730
+ justify-content: center;
731
+ gap: 8px;
732
+ background-color: #f9fafb;
733
+ border-top: 1px solid #f3f4f6;
734
+ }
735
+
736
+ .pro6pp-mini-spinner {
737
+ width: 14px;
738
+ height: 14px;
739
+ border: 2px solid #e5e7eb;
740
+ border-top-color: #6b7280;
741
+ border-radius: 50%;
742
+ animation: pro6pp-spin 0.6s linear infinite;
611
743
  }
612
744
 
613
745
  @media (max-width: 640px) {
@@ -619,17 +751,6 @@ var DEFAULT_STYLES = `
619
751
  padding: 10px 12px;
620
752
  font-size: 14px;
621
753
  }
622
- .pro6pp-item__subtitle {
623
- font-size: 12px;
624
- }
625
- .pro6pp-load-more {
626
- padding: 12px;
627
- font-size: 13px;
628
- }
629
- }
630
-
631
- .pro6pp-load-more:active {
632
- background-color: #f3f4f6;
633
754
  }
634
755
 
635
756
  @keyframes pro6pp-spin {
@@ -641,16 +762,17 @@ var DEFAULT_STYLES = `
641
762
  var HighlightedText = ({ text, query }) => {
642
763
  const segments = useMemo(() => getHighlightSegments(text, query), [text, query]);
643
764
  return /* @__PURE__ */ React.createElement("span", { className: "pro6pp-item__label" }, segments.map(
644
- (seg, i) => seg.match ? /* @__PURE__ */ React.createElement("strong", { key: i, className: "pro6pp-item__label--match" }, seg.text) : seg.text
765
+ (seg, i) => seg.match ? /* @__PURE__ */ React.createElement("span", { key: i, className: "pro6pp-item__label--match" }, seg.text) : /* @__PURE__ */ React.createElement("span", { key: i, className: "pro6pp-item__label--unmatched" }, seg.text)
645
766
  ));
646
767
  };
647
768
  function useInfer(config) {
648
769
  const [state, setState] = useState(() => {
649
770
  if (config.initialValue) {
771
+ const postcodeStr = config.initialValue.postcode ? `${config.initialValue.postcode}, ` : "";
650
772
  return {
651
773
  ...INITIAL_STATE,
652
774
  value: config.initialValue,
653
- query: `${config.initialValue.street} ${config.initialValue.street_number}, ${config.initialValue.city}`,
775
+ query: `${config.initialValue.street} ${config.initialValue.street_number}, ${postcodeStr}${config.initialValue.city}`,
654
776
  isValid: true,
655
777
  stage: "final"
656
778
  };
@@ -680,7 +802,8 @@ function useInfer(config) {
680
802
  });
681
803
  if (config.initialValue) {
682
804
  const address = config.initialValue;
683
- const label = `${address.street} ${address.street_number}, ${address.city}`;
805
+ const postcodeStr = address.postcode ? `${address.postcode}, ` : "";
806
+ const label = `${address.street} ${address.street_number}, ${postcodeStr}${address.city}`;
684
807
  instance.selectItem({ label, value: address });
685
808
  }
686
809
  return instance;
@@ -696,7 +819,8 @@ function useInfer(config) {
696
819
  ]);
697
820
  const setValue = (address) => {
698
821
  if (!address) return;
699
- const label = `${address.street} ${address.street_number}, ${address.city}`;
822
+ const postcodeStr = address.postcode ? `${address.postcode}, ` : "";
823
+ const label = `${address.street} ${address.street_number}, ${postcodeStr}${address.city}`;
700
824
  core.selectItem({ label, value: address });
701
825
  };
702
826
  return {
@@ -727,7 +851,7 @@ var Pro6PPInfer = forwardRef(
727
851
  renderItem,
728
852
  disableDefaultStyles = false,
729
853
  noResultsText = "No results found",
730
- loadMoreText = "Show more results...",
854
+ loadingText = "Loading more...",
731
855
  renderNoResults,
732
856
  showClearButton = true,
733
857
  ...config
@@ -736,6 +860,7 @@ var Pro6PPInfer = forwardRef(
736
860
  const [isOpen, setIsOpen] = useState(false);
737
861
  const internalInputRef = useRef(null);
738
862
  const wrapperRef = useRef(null);
863
+ const observerTarget = useRef(null);
739
864
  useImperativeHandle(ref, () => internalInputRef.current);
740
865
  useEffect(() => {
741
866
  if (disableDefaultStyles) return;
@@ -756,6 +881,22 @@ var Pro6PPInfer = forwardRef(
756
881
  document.addEventListener("mousedown", handleClickOutside);
757
882
  return () => document.removeEventListener("mousedown", handleClickOutside);
758
883
  }, []);
884
+ useEffect(() => {
885
+ const currentTarget = observerTarget.current;
886
+ if (!currentTarget) return;
887
+ const observer = new IntersectionObserver(
888
+ (entries) => {
889
+ if (entries[0].isIntersecting && state.hasMore && !state.isLoading) {
890
+ loadMore();
891
+ }
892
+ },
893
+ { threshold: 0.1 }
894
+ );
895
+ observer.observe(currentTarget);
896
+ return () => {
897
+ if (currentTarget) observer.unobserve(currentTarget);
898
+ };
899
+ }, [state.hasMore, state.isLoading, loadMore, isOpen]);
759
900
  const items = useMemo(() => {
760
901
  return [
761
902
  ...state.cities.map((c) => ({ ...c, type: "city" })),
@@ -778,6 +919,7 @@ var Pro6PPInfer = forwardRef(
778
919
  const hasResults = items.length > 0;
779
920
  const showNoResults = !state.isLoading && !state.isError && state.query.length > 0 && !hasResults && !state.isValid;
780
921
  const showDropdown = isOpen && (hasResults || state.isLoading || showNoResults);
922
+ const isInfiniteLoading = state.isLoading && items.length > 0;
781
923
  return /* @__PURE__ */ React.createElement("div", { ref: wrapperRef, className: `pro6pp-wrapper ${className || ""}`, style }, /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(
782
924
  "input",
783
925
  {
@@ -798,7 +940,7 @@ var Pro6PPInfer = forwardRef(
798
940
  inputProps?.onFocus?.(e);
799
941
  }
800
942
  }
801
- ), /* @__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(
943
+ ), /* @__PURE__ */ React.createElement("div", { className: "pro6pp-input-addons" }, showClearButton && state.query.length > 0 && /* @__PURE__ */ React.createElement(
802
944
  "button",
803
945
  {
804
946
  type: "button",
@@ -828,7 +970,7 @@ var Pro6PPInfer = forwardRef(
828
970
  onWheel: (e) => e.stopPropagation(),
829
971
  onMouseDown: (e) => e.stopPropagation()
830
972
  },
831
- /* @__PURE__ */ React.createElement("ul", { className: "pro6pp-list", role: "listbox" }, hasResults ? items.map((item, index) => {
973
+ /* @__PURE__ */ React.createElement("ul", { className: "pro6pp-list", role: "listbox" }, hasResults ? /* @__PURE__ */ React.createElement(React.Fragment, null, items.map((item, index) => {
832
974
  const isActive = index === state.selectedSuggestionIndex;
833
975
  const secondaryText = item.subtitle || (item.count !== void 0 ? item.count : "");
834
976
  const showChevron = item.value === void 0 || item.value === null;
@@ -857,19 +999,7 @@ var Pro6PPInfer = forwardRef(
857
999
  /* @__PURE__ */ React.createElement("polyline", { points: "9 18 15 12 9 6" })
858
1000
  )))
859
1001
  );
860
- }) : state.isLoading ? /* @__PURE__ */ React.createElement("li", { className: "pro6pp-no-results" }, "Loading suggestions...") : /* @__PURE__ */ React.createElement("li", { className: "pro6pp-no-results" }, renderNoResults ? renderNoResults(state) : noResultsText)),
861
- state.hasMore && /* @__PURE__ */ React.createElement(
862
- "button",
863
- {
864
- type: "button",
865
- className: "pro6pp-load-more",
866
- onClick: (e) => {
867
- e.preventDefault();
868
- loadMore();
869
- }
870
- },
871
- loadMoreText
872
- )
1002
+ }), state.hasMore && !state.isLoading && /* @__PURE__ */ React.createElement("li", { key: "sentinel", ref: observerTarget, style: { height: "1px", opacity: 0 } }), isInfiniteLoading && /* @__PURE__ */ React.createElement("li", { key: "loader", className: "pro6pp-loader-item" }, /* @__PURE__ */ React.createElement("div", { className: "pro6pp-mini-spinner" }), /* @__PURE__ */ React.createElement("span", null, loadingText))) : state.isLoading ? /* @__PURE__ */ React.createElement("li", { className: "pro6pp-no-results" }, "Searching...") : /* @__PURE__ */ React.createElement("li", { className: "pro6pp-no-results" }, renderNoResults ? renderNoResults(state) : noResultsText))
873
1003
  ));
874
1004
  }
875
1005
  );
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.15",
23
+ "version": "0.0.2-beta.17",
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.13"
49
+ "@pro6pp/infer-core": "0.0.2-beta.15"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@testing-library/dom": "^10.4.1",