@pro6pp/infer-react 0.0.2-beta.14 → 0.0.2-beta.16

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
@@ -47,7 +47,8 @@ var DEFAULTS = {
47
47
  MAX_RETRIES: 0
48
48
  };
49
49
  var PATTERNS = {
50
- DIGITS_1_3: /^[0-9]{1,3}$/
50
+ DIGITS_1_3: /^[0-9]{1,3}$/,
51
+ STREET_NUMBER_PREFIX: /^(\d+)\s*,\s*$/
51
52
  };
52
53
  var INITIAL_STATE = {
53
54
  query: "",
@@ -115,7 +116,8 @@ var InferCore = class {
115
116
  value: null,
116
117
  isLoading: !!value.trim(),
117
118
  selectedSuggestionIndex: -1,
118
- hasMore: false
119
+ hasMore: false,
120
+ stage: isEditingFinal ? null : this.state.stage
119
121
  });
120
122
  if (isEditingFinal) {
121
123
  this.onSelect(null);
@@ -246,10 +248,20 @@ var InferCore = class {
246
248
  } else {
247
249
  const prefix = this.getQueryPrefix(query);
248
250
  const shouldAddSubtitle = !prefix || !prefix.includes(subtitle);
251
+ let effectivePrefix = prefix;
252
+ if (prefix && subtitle) {
253
+ const prefixNumMatch = prefix.match(PATTERNS.STREET_NUMBER_PREFIX);
254
+ if (prefixNumMatch) {
255
+ const num = prefixNumMatch[1];
256
+ if (subtitle.startsWith(num)) {
257
+ effectivePrefix = "";
258
+ }
259
+ }
260
+ }
249
261
  if (shouldAddSubtitle) {
250
- nextQuery = prefix ? `${prefix} ${text}, ${subtitle}, ` : `${text}, ${subtitle}, `;
262
+ nextQuery = effectivePrefix ? `${effectivePrefix} ${text}, ${subtitle}, ` : `${text}, ${subtitle}, `;
251
263
  } else {
252
- nextQuery = prefix ? `${prefix} ${text}, ` : `${text}, `;
264
+ nextQuery = effectivePrefix ? `${effectivePrefix} ${text}, ` : `${text}, `;
253
265
  }
254
266
  }
255
267
  this.updateQueryAndFetch(nextQuery);
@@ -344,7 +356,11 @@ var InferCore = class {
344
356
  if (data.stage === "mixed") {
345
357
  newState.cities = data.cities || [];
346
358
  newState.streets = data.streets || [];
347
- newState.suggestions = [];
359
+ if (newState.cities?.length === 0 && newState.streets?.length === 0) {
360
+ newState.suggestions = uniqueSuggestions;
361
+ } else {
362
+ newState.suggestions = [];
363
+ }
348
364
  } else {
349
365
  newState.suggestions = uniqueSuggestions;
350
366
  newState.cities = [];
@@ -508,17 +524,6 @@ var DEFAULT_STYLES = `
508
524
  background-color: #f3f4f6;
509
525
  }
510
526
 
511
- .pro6pp-loader {
512
- width: 20px;
513
- height: 20px;
514
- margin: 0 8px;
515
- border: 2px solid #e0e0e0;
516
- border-top-color: #6b7280;
517
- border-radius: 50%;
518
- animation: pro6pp-spin 0.6s linear infinite;
519
- flex-shrink: 0;
520
- }
521
-
522
527
  .pro6pp-dropdown {
523
528
  position: absolute;
524
529
  top: 100%;
@@ -620,6 +625,27 @@ var DEFAULT_STYLES = `
620
625
  touch-action: manipulation;
621
626
  }
622
627
 
628
+ .pro6pp-loader-item {
629
+ padding: 10px 12px;
630
+ color: #6b7280;
631
+ font-size: 0.875rem;
632
+ display: flex;
633
+ align-items: center;
634
+ justify-content: center;
635
+ gap: 8px;
636
+ background-color: #f9fafb;
637
+ border-top: 1px solid #f3f4f6;
638
+ }
639
+
640
+ .pro6pp-mini-spinner {
641
+ width: 14px;
642
+ height: 14px;
643
+ border: 2px solid #e5e7eb;
644
+ border-top-color: #6b7280;
645
+ border-radius: 50%;
646
+ animation: pro6pp-spin 0.6s linear infinite;
647
+ }
648
+
623
649
  @media (max-width: 640px) {
624
650
  .pro6pp-input {
625
651
  font-size: 16px;
@@ -737,7 +763,7 @@ var Pro6PPInfer = (0, import_react.forwardRef)(
737
763
  renderItem,
738
764
  disableDefaultStyles = false,
739
765
  noResultsText = "No results found",
740
- loadMoreText = "Show more results...",
766
+ loadingText = "Loading more...",
741
767
  renderNoResults,
742
768
  showClearButton = true,
743
769
  ...config
@@ -746,6 +772,7 @@ var Pro6PPInfer = (0, import_react.forwardRef)(
746
772
  const [isOpen, setIsOpen] = (0, import_react.useState)(false);
747
773
  const internalInputRef = (0, import_react.useRef)(null);
748
774
  const wrapperRef = (0, import_react.useRef)(null);
775
+ const observerTarget = (0, import_react.useRef)(null);
749
776
  (0, import_react.useImperativeHandle)(ref, () => internalInputRef.current);
750
777
  (0, import_react.useEffect)(() => {
751
778
  if (disableDefaultStyles) return;
@@ -766,6 +793,22 @@ var Pro6PPInfer = (0, import_react.forwardRef)(
766
793
  document.addEventListener("mousedown", handleClickOutside);
767
794
  return () => document.removeEventListener("mousedown", handleClickOutside);
768
795
  }, []);
796
+ (0, import_react.useEffect)(() => {
797
+ const currentTarget = observerTarget.current;
798
+ if (!currentTarget) return;
799
+ const observer = new IntersectionObserver(
800
+ (entries) => {
801
+ if (entries[0].isIntersecting && state.hasMore && !state.isLoading) {
802
+ loadMore();
803
+ }
804
+ },
805
+ { threshold: 0.1 }
806
+ );
807
+ observer.observe(currentTarget);
808
+ return () => {
809
+ if (currentTarget) observer.unobserve(currentTarget);
810
+ };
811
+ }, [state.hasMore, state.isLoading, loadMore, isOpen]);
769
812
  const items = (0, import_react.useMemo)(() => {
770
813
  return [
771
814
  ...state.cities.map((c) => ({ ...c, type: "city" })),
@@ -788,6 +831,7 @@ var Pro6PPInfer = (0, import_react.forwardRef)(
788
831
  const hasResults = items.length > 0;
789
832
  const showNoResults = !state.isLoading && !state.isError && state.query.length > 0 && !hasResults && !state.isValid;
790
833
  const showDropdown = isOpen && (hasResults || state.isLoading || showNoResults);
834
+ const isInfiniteLoading = state.isLoading && items.length > 0;
791
835
  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(
792
836
  "input",
793
837
  {
@@ -808,7 +852,7 @@ var Pro6PPInfer = (0, import_react.forwardRef)(
808
852
  inputProps?.onFocus?.(e);
809
853
  }
810
854
  }
811
- ), /* @__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(
855
+ ), /* @__PURE__ */ import_react.default.createElement("div", { className: "pro6pp-input-addons" }, showClearButton && state.query.length > 0 && /* @__PURE__ */ import_react.default.createElement(
812
856
  "button",
813
857
  {
814
858
  type: "button",
@@ -838,7 +882,7 @@ var Pro6PPInfer = (0, import_react.forwardRef)(
838
882
  onWheel: (e) => e.stopPropagation(),
839
883
  onMouseDown: (e) => e.stopPropagation()
840
884
  },
841
- /* @__PURE__ */ import_react.default.createElement("ul", { className: "pro6pp-list", role: "listbox" }, hasResults ? items.map((item, index) => {
885
+ /* @__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) => {
842
886
  const isActive = index === state.selectedSuggestionIndex;
843
887
  const secondaryText = item.subtitle || (item.count !== void 0 ? item.count : "");
844
888
  const showChevron = item.value === void 0 || item.value === null;
@@ -867,19 +911,7 @@ var Pro6PPInfer = (0, import_react.forwardRef)(
867
911
  /* @__PURE__ */ import_react.default.createElement("polyline", { points: "9 18 15 12 9 6" })
868
912
  )))
869
913
  );
870
- }) : 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)),
871
- state.hasMore && /* @__PURE__ */ import_react.default.createElement(
872
- "button",
873
- {
874
- type: "button",
875
- className: "pro6pp-load-more",
876
- onClick: (e) => {
877
- e.preventDefault();
878
- loadMore();
879
- }
880
- },
881
- loadMoreText
882
- )
914
+ }), 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))
883
915
  ));
884
916
  }
885
917
  );
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
@@ -21,7 +21,8 @@ var DEFAULTS = {
21
21
  MAX_RETRIES: 0
22
22
  };
23
23
  var PATTERNS = {
24
- DIGITS_1_3: /^[0-9]{1,3}$/
24
+ DIGITS_1_3: /^[0-9]{1,3}$/,
25
+ STREET_NUMBER_PREFIX: /^(\d+)\s*,\s*$/
25
26
  };
26
27
  var INITIAL_STATE = {
27
28
  query: "",
@@ -89,7 +90,8 @@ var InferCore = class {
89
90
  value: null,
90
91
  isLoading: !!value.trim(),
91
92
  selectedSuggestionIndex: -1,
92
- hasMore: false
93
+ hasMore: false,
94
+ stage: isEditingFinal ? null : this.state.stage
93
95
  });
94
96
  if (isEditingFinal) {
95
97
  this.onSelect(null);
@@ -220,10 +222,20 @@ var InferCore = class {
220
222
  } else {
221
223
  const prefix = this.getQueryPrefix(query);
222
224
  const shouldAddSubtitle = !prefix || !prefix.includes(subtitle);
225
+ let effectivePrefix = prefix;
226
+ if (prefix && subtitle) {
227
+ const prefixNumMatch = prefix.match(PATTERNS.STREET_NUMBER_PREFIX);
228
+ if (prefixNumMatch) {
229
+ const num = prefixNumMatch[1];
230
+ if (subtitle.startsWith(num)) {
231
+ effectivePrefix = "";
232
+ }
233
+ }
234
+ }
223
235
  if (shouldAddSubtitle) {
224
- nextQuery = prefix ? `${prefix} ${text}, ${subtitle}, ` : `${text}, ${subtitle}, `;
236
+ nextQuery = effectivePrefix ? `${effectivePrefix} ${text}, ${subtitle}, ` : `${text}, ${subtitle}, `;
225
237
  } else {
226
- nextQuery = prefix ? `${prefix} ${text}, ` : `${text}, `;
238
+ nextQuery = effectivePrefix ? `${effectivePrefix} ${text}, ` : `${text}, `;
227
239
  }
228
240
  }
229
241
  this.updateQueryAndFetch(nextQuery);
@@ -318,7 +330,11 @@ var InferCore = class {
318
330
  if (data.stage === "mixed") {
319
331
  newState.cities = data.cities || [];
320
332
  newState.streets = data.streets || [];
321
- newState.suggestions = [];
333
+ if (newState.cities?.length === 0 && newState.streets?.length === 0) {
334
+ newState.suggestions = uniqueSuggestions;
335
+ } else {
336
+ newState.suggestions = [];
337
+ }
322
338
  } else {
323
339
  newState.suggestions = uniqueSuggestions;
324
340
  newState.cities = [];
@@ -482,17 +498,6 @@ var DEFAULT_STYLES = `
482
498
  background-color: #f3f4f6;
483
499
  }
484
500
 
485
- .pro6pp-loader {
486
- width: 20px;
487
- height: 20px;
488
- margin: 0 8px;
489
- border: 2px solid #e0e0e0;
490
- border-top-color: #6b7280;
491
- border-radius: 50%;
492
- animation: pro6pp-spin 0.6s linear infinite;
493
- flex-shrink: 0;
494
- }
495
-
496
501
  .pro6pp-dropdown {
497
502
  position: absolute;
498
503
  top: 100%;
@@ -594,6 +599,27 @@ var DEFAULT_STYLES = `
594
599
  touch-action: manipulation;
595
600
  }
596
601
 
602
+ .pro6pp-loader-item {
603
+ padding: 10px 12px;
604
+ color: #6b7280;
605
+ font-size: 0.875rem;
606
+ display: flex;
607
+ align-items: center;
608
+ justify-content: center;
609
+ gap: 8px;
610
+ background-color: #f9fafb;
611
+ border-top: 1px solid #f3f4f6;
612
+ }
613
+
614
+ .pro6pp-mini-spinner {
615
+ width: 14px;
616
+ height: 14px;
617
+ border: 2px solid #e5e7eb;
618
+ border-top-color: #6b7280;
619
+ border-radius: 50%;
620
+ animation: pro6pp-spin 0.6s linear infinite;
621
+ }
622
+
597
623
  @media (max-width: 640px) {
598
624
  .pro6pp-input {
599
625
  font-size: 16px;
@@ -711,7 +737,7 @@ var Pro6PPInfer = forwardRef(
711
737
  renderItem,
712
738
  disableDefaultStyles = false,
713
739
  noResultsText = "No results found",
714
- loadMoreText = "Show more results...",
740
+ loadingText = "Loading more...",
715
741
  renderNoResults,
716
742
  showClearButton = true,
717
743
  ...config
@@ -720,6 +746,7 @@ var Pro6PPInfer = forwardRef(
720
746
  const [isOpen, setIsOpen] = useState(false);
721
747
  const internalInputRef = useRef(null);
722
748
  const wrapperRef = useRef(null);
749
+ const observerTarget = useRef(null);
723
750
  useImperativeHandle(ref, () => internalInputRef.current);
724
751
  useEffect(() => {
725
752
  if (disableDefaultStyles) return;
@@ -740,6 +767,22 @@ var Pro6PPInfer = forwardRef(
740
767
  document.addEventListener("mousedown", handleClickOutside);
741
768
  return () => document.removeEventListener("mousedown", handleClickOutside);
742
769
  }, []);
770
+ useEffect(() => {
771
+ const currentTarget = observerTarget.current;
772
+ if (!currentTarget) return;
773
+ const observer = new IntersectionObserver(
774
+ (entries) => {
775
+ if (entries[0].isIntersecting && state.hasMore && !state.isLoading) {
776
+ loadMore();
777
+ }
778
+ },
779
+ { threshold: 0.1 }
780
+ );
781
+ observer.observe(currentTarget);
782
+ return () => {
783
+ if (currentTarget) observer.unobserve(currentTarget);
784
+ };
785
+ }, [state.hasMore, state.isLoading, loadMore, isOpen]);
743
786
  const items = useMemo(() => {
744
787
  return [
745
788
  ...state.cities.map((c) => ({ ...c, type: "city" })),
@@ -762,6 +805,7 @@ var Pro6PPInfer = forwardRef(
762
805
  const hasResults = items.length > 0;
763
806
  const showNoResults = !state.isLoading && !state.isError && state.query.length > 0 && !hasResults && !state.isValid;
764
807
  const showDropdown = isOpen && (hasResults || state.isLoading || showNoResults);
808
+ const isInfiniteLoading = state.isLoading && items.length > 0;
765
809
  return /* @__PURE__ */ React.createElement("div", { ref: wrapperRef, className: `pro6pp-wrapper ${className || ""}`, style }, /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(
766
810
  "input",
767
811
  {
@@ -782,7 +826,7 @@ var Pro6PPInfer = forwardRef(
782
826
  inputProps?.onFocus?.(e);
783
827
  }
784
828
  }
785
- ), /* @__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(
829
+ ), /* @__PURE__ */ React.createElement("div", { className: "pro6pp-input-addons" }, showClearButton && state.query.length > 0 && /* @__PURE__ */ React.createElement(
786
830
  "button",
787
831
  {
788
832
  type: "button",
@@ -812,7 +856,7 @@ var Pro6PPInfer = forwardRef(
812
856
  onWheel: (e) => e.stopPropagation(),
813
857
  onMouseDown: (e) => e.stopPropagation()
814
858
  },
815
- /* @__PURE__ */ React.createElement("ul", { className: "pro6pp-list", role: "listbox" }, hasResults ? items.map((item, index) => {
859
+ /* @__PURE__ */ React.createElement("ul", { className: "pro6pp-list", role: "listbox" }, hasResults ? /* @__PURE__ */ React.createElement(React.Fragment, null, items.map((item, index) => {
816
860
  const isActive = index === state.selectedSuggestionIndex;
817
861
  const secondaryText = item.subtitle || (item.count !== void 0 ? item.count : "");
818
862
  const showChevron = item.value === void 0 || item.value === null;
@@ -841,19 +885,7 @@ var Pro6PPInfer = forwardRef(
841
885
  /* @__PURE__ */ React.createElement("polyline", { points: "9 18 15 12 9 6" })
842
886
  )))
843
887
  );
844
- }) : state.isLoading ? /* @__PURE__ */ React.createElement("li", { className: "pro6pp-no-results" }, "Loading suggestions...") : /* @__PURE__ */ React.createElement("li", { className: "pro6pp-no-results" }, renderNoResults ? renderNoResults(state) : noResultsText)),
845
- state.hasMore && /* @__PURE__ */ React.createElement(
846
- "button",
847
- {
848
- type: "button",
849
- className: "pro6pp-load-more",
850
- onClick: (e) => {
851
- e.preventDefault();
852
- loadMore();
853
- }
854
- },
855
- loadMoreText
856
- )
888
+ }), 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))
857
889
  ));
858
890
  }
859
891
  );
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.14",
23
+ "version": "0.0.2-beta.16",
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.12"
49
+ "@pro6pp/infer-core": "0.0.2-beta.14"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@testing-library/dom": "^10.4.1",