@pro6pp/infer-react 0.0.2-beta.7 → 0.0.2-beta.8

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
@@ -42,6 +42,7 @@ You can customize the appearance of the component via the following props:
42
42
  | `noResultsText` | The text to display when no suggestions are found. |
43
43
  | `renderItem` | A custom render function for suggestion items, receiving the `item` and `isActive` state. |
44
44
  | `renderNoResults` | A custom render function for the empty state, receiving the current `state`. |
45
+ | `debounceMs` | Delay in ms before API search. Defaults to `150` (min `50`). |
45
46
 
46
47
  ---
47
48
 
package/dist/index.cjs CHANGED
@@ -42,7 +42,8 @@ var import_react = __toESM(require("react"), 1);
42
42
  var DEFAULTS = {
43
43
  API_URL: "https://api.pro6pp.nl/v2",
44
44
  LIMIT: 1e3,
45
- DEBOUNCE_MS: 300
45
+ DEBOUNCE_MS: 150,
46
+ MIN_DEBOUNCE_MS: 50
46
47
  };
47
48
  var PATTERNS = {
48
49
  DIGITS_1_3: /^[0-9]{1,3}$/
@@ -59,6 +60,10 @@ var INITIAL_STATE = {
59
60
  selectedSuggestionIndex: -1
60
61
  };
61
62
  var InferCore = class {
63
+ /**
64
+ * Initializes a new instance of the Infer engine.
65
+ * @param config The configuration object including API keys and callbacks.
66
+ */
62
67
  constructor(config) {
63
68
  __publicField(this, "country");
64
69
  __publicField(this, "authKey");
@@ -67,6 +72,10 @@ var InferCore = class {
67
72
  __publicField(this, "fetcher");
68
73
  __publicField(this, "onStateChange");
69
74
  __publicField(this, "onSelect");
75
+ /**
76
+ * The current read-only state of the engine.
77
+ * Use `onStateChange` to react to updates.
78
+ */
70
79
  __publicField(this, "state");
71
80
  __publicField(this, "abortController", null);
72
81
  __publicField(this, "debouncedFetch");
@@ -81,11 +90,15 @@ var InferCore = class {
81
90
  this.onSelect = config.onSelect || (() => {
82
91
  });
83
92
  this.state = { ...INITIAL_STATE };
84
- this.debouncedFetch = this.debounce(
85
- (val) => this.executeFetch(val),
86
- DEFAULTS.DEBOUNCE_MS
87
- );
93
+ const configDebounce = config.debounceMs !== void 0 ? config.debounceMs : DEFAULTS.DEBOUNCE_MS;
94
+ const debounceTime = Math.max(configDebounce, DEFAULTS.MIN_DEBOUNCE_MS);
95
+ this.debouncedFetch = this.debounce((val) => this.executeFetch(val), debounceTime);
88
96
  }
97
+ /**
98
+ * Processes new text input from the user.
99
+ * Triggers a debounced API request and updates the internal state.
100
+ * @param value The raw string from the input field.
101
+ */
89
102
  handleInput(value) {
90
103
  if (this.isSelecting) {
91
104
  this.isSelecting = false;
@@ -103,6 +116,14 @@ var InferCore = class {
103
116
  }
104
117
  this.debouncedFetch(value);
105
118
  }
119
+ /**
120
+ * Handles keyboard events for the input field.
121
+ * Supports:
122
+ * - `ArrowUp`/`ArrowDown`: Navigate through the suggestion list.
123
+ * - `Enter`: Select the currently highlighted suggestion.
124
+ * - `Space`: Automatically inserts a comma if a numeric house number is detected.
125
+ * @param event The keyboard event from the input element.
126
+ */
106
127
  handleKeyDown(event) {
107
128
  const target = event.target;
108
129
  if (!target) return;
@@ -144,6 +165,11 @@ var InferCore = class {
144
165
  this.updateQueryAndFetch(next);
145
166
  }
146
167
  }
168
+ /**
169
+ * Manually selects a suggestion or a string value.
170
+ * This is typically called when a user clicks a suggestion in the UI.
171
+ * @param item The suggestion object or string to select.
172
+ */
147
173
  selectItem(item) {
148
174
  this.debouncedFetch.cancel();
149
175
  if (this.abortController) {
@@ -379,21 +405,18 @@ var DEFAULT_STYLES = `
379
405
  list-style: none !important;
380
406
  padding: 0 !important;
381
407
  margin: 0 !important;
382
- overflow: hidden;
383
408
  }
384
409
  .pro6pp-item {
385
- padding: 12px 12px 9px 12px;
410
+ padding: 10px 16px;
386
411
  cursor: pointer;
387
412
  display: flex;
388
413
  flex-direction: row;
389
414
  align-items: center;
390
415
  color: #000000;
391
416
  font-size: 14px;
392
- line-height: 1;
417
+ line-height: 1.2;
393
418
  white-space: nowrap;
394
419
  overflow: hidden;
395
- border-radius: 0 !important;
396
- margin: 0 !important;
397
420
  }
398
421
  .pro6pp-item:hover, .pro6pp-item--active {
399
422
  background-color: #f5f5f5;
@@ -455,15 +478,19 @@ function useInfer(config) {
455
478
  }
456
479
  }
457
480
  });
458
- }, [config.country, config.authKey, config.limit]);
481
+ }, [config.country, config.authKey, config.limit, config.debounceMs]);
459
482
  return {
483
+ /** The current UI state (suggestions, loading status, query, etc.). */
460
484
  state,
485
+ /** The raw InferCore instance for manual control. */
461
486
  core,
487
+ /** Pre-configured event handlers to spread onto an <input /> element. */
462
488
  inputProps: {
463
489
  value: state.query,
464
490
  onChange: (e) => core.handleInput(e.target.value),
465
491
  onKeyDown: (e) => core.handleKeyDown(e)
466
492
  },
493
+ /** Function to manually select a specific suggestion. */
467
494
  selectItem: (item) => core.selectItem(item)
468
495
  };
469
496
  }
@@ -479,7 +506,9 @@ var Pro6PPInfer = ({
479
506
  ...config
480
507
  }) => {
481
508
  const { state, selectItem, inputProps: coreInputProps } = useInfer(config);
509
+ const [isOpen, setIsOpen] = (0, import_react.useState)(false);
482
510
  const inputRef = (0, import_react.useRef)(null);
511
+ const wrapperRef = (0, import_react.useRef)(null);
483
512
  (0, import_react.useEffect)(() => {
484
513
  if (disableDefaultStyles) return;
485
514
  const styleId = "pro6pp-styles";
@@ -490,6 +519,15 @@ var Pro6PPInfer = ({
490
519
  document.head.appendChild(styleEl);
491
520
  }
492
521
  }, [disableDefaultStyles]);
522
+ (0, import_react.useEffect)(() => {
523
+ const handleClickOutside = (event) => {
524
+ if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
525
+ setIsOpen(false);
526
+ }
527
+ };
528
+ document.addEventListener("mousedown", handleClickOutside);
529
+ return () => document.removeEventListener("mousedown", handleClickOutside);
530
+ }, []);
493
531
  const items = (0, import_react.useMemo)(() => {
494
532
  return [
495
533
  ...state.cities.map((c) => ({ ...c, type: "city" })),
@@ -499,14 +537,15 @@ var Pro6PPInfer = ({
499
537
  }, [state.cities, state.streets, state.suggestions]);
500
538
  const handleSelect = (item) => {
501
539
  selectItem(item);
540
+ setIsOpen(false);
502
541
  if (!state.isValid && inputRef.current) {
503
542
  inputRef.current.focus();
504
543
  }
505
544
  };
506
545
  const hasResults = items.length > 0;
507
546
  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(
547
+ const showDropdown = isOpen && (hasResults || showNoResults);
548
+ 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(
510
549
  "input",
511
550
  {
512
551
  ref: inputRef,
@@ -515,11 +554,15 @@ var Pro6PPInfer = ({
515
554
  placeholder,
516
555
  autoComplete: "off",
517
556
  ...inputProps,
518
- ...coreInputProps
557
+ ...coreInputProps,
558
+ onFocus: (e) => {
559
+ setIsOpen(true);
560
+ inputProps?.onFocus?.(e);
561
+ }
519
562
  }
520
563
  ), 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
564
  const isActive = index === state.selectedSuggestionIndex;
522
- const secondaryText = item.subtitle || item.count;
565
+ const secondaryText = item.subtitle || (item.count !== void 0 ? item.count : "");
523
566
  const showChevron = item.value === void 0 || item.value === null;
524
567
  return /* @__PURE__ */ import_react.default.createElement(
525
568
  "li",
package/dist/index.d.cts CHANGED
@@ -2,26 +2,56 @@ import React from 'react';
2
2
  import { InferConfig, InferState, InferCore, InferResult } from '@pro6pp/infer-core';
3
3
  export { AddressValue, CountryCode, Fetcher, InferConfig, InferResult, InferState, Stage } from '@pro6pp/infer-core';
4
4
 
5
+ /**
6
+ * A headless React hook that provides the logic for address search using the Infer API.
7
+ * @param config The engine configuration (authKey, country, etc.).
8
+ * @returns An object containing the current state, the core instance, and pre-bound input props.
9
+ */
5
10
  declare function useInfer(config: InferConfig): {
11
+ /** The current UI state (suggestions, loading status, query, etc.). */
6
12
  state: InferState;
13
+ /** The raw InferCore instance for manual control. */
7
14
  core: InferCore;
15
+ /** Pre-configured event handlers to spread onto an <input /> element. */
8
16
  inputProps: {
9
17
  value: string;
10
18
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
11
19
  onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void;
12
20
  };
21
+ /** Function to manually select a specific suggestion. */
13
22
  selectItem: (item: InferResult | string) => void;
14
23
  };
24
+ /**
25
+ * Props for the Pro6PPInfer component.
26
+ */
15
27
  interface Pro6PPInferProps extends InferConfig {
28
+ /** Optional CSS class for the wrapper div. */
16
29
  className?: string;
30
+ /** Optional inline styles for the wrapper div. */
17
31
  style?: React.CSSProperties;
32
+ /** Attributes to pass directly to the underlying input element. */
18
33
  inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
34
+ /** * Custom placeholder text.
35
+ * @default 'Start typing an address...'
36
+ */
19
37
  placeholder?: string;
38
+ /** A custom render function for individual suggestion items. */
20
39
  renderItem?: (item: InferResult, isActive: boolean) => React.ReactNode;
40
+ /** * If true, prevents the default CSS theme from being injected.
41
+ * @default false
42
+ */
21
43
  disableDefaultStyles?: boolean;
44
+ /** * The text to show when no results are found.
45
+ * @default 'No results found'
46
+ */
22
47
  noResultsText?: string;
48
+ /** A custom render function for the "no results" state. */
23
49
  renderNoResults?: (state: InferState) => React.ReactNode;
24
50
  }
51
+ /**
52
+ * A styled React component for Pro6PP Infer API.
53
+ * Includes styling, keyboard navigation, and loading states.
54
+ */
25
55
  declare const Pro6PPInfer: React.FC<Pro6PPInferProps>;
26
56
 
27
57
  export { Pro6PPInfer, type Pro6PPInferProps, useInfer };
package/dist/index.d.ts CHANGED
@@ -2,26 +2,56 @@ import React from 'react';
2
2
  import { InferConfig, InferState, InferCore, InferResult } from '@pro6pp/infer-core';
3
3
  export { AddressValue, CountryCode, Fetcher, InferConfig, InferResult, InferState, Stage } from '@pro6pp/infer-core';
4
4
 
5
+ /**
6
+ * A headless React hook that provides the logic for address search using the Infer API.
7
+ * @param config The engine configuration (authKey, country, etc.).
8
+ * @returns An object containing the current state, the core instance, and pre-bound input props.
9
+ */
5
10
  declare function useInfer(config: InferConfig): {
11
+ /** The current UI state (suggestions, loading status, query, etc.). */
6
12
  state: InferState;
13
+ /** The raw InferCore instance for manual control. */
7
14
  core: InferCore;
15
+ /** Pre-configured event handlers to spread onto an <input /> element. */
8
16
  inputProps: {
9
17
  value: string;
10
18
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
11
19
  onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void;
12
20
  };
21
+ /** Function to manually select a specific suggestion. */
13
22
  selectItem: (item: InferResult | string) => void;
14
23
  };
24
+ /**
25
+ * Props for the Pro6PPInfer component.
26
+ */
15
27
  interface Pro6PPInferProps extends InferConfig {
28
+ /** Optional CSS class for the wrapper div. */
16
29
  className?: string;
30
+ /** Optional inline styles for the wrapper div. */
17
31
  style?: React.CSSProperties;
32
+ /** Attributes to pass directly to the underlying input element. */
18
33
  inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
34
+ /** * Custom placeholder text.
35
+ * @default 'Start typing an address...'
36
+ */
19
37
  placeholder?: string;
38
+ /** A custom render function for individual suggestion items. */
20
39
  renderItem?: (item: InferResult, isActive: boolean) => React.ReactNode;
40
+ /** * If true, prevents the default CSS theme from being injected.
41
+ * @default false
42
+ */
21
43
  disableDefaultStyles?: boolean;
44
+ /** * The text to show when no results are found.
45
+ * @default 'No results found'
46
+ */
22
47
  noResultsText?: string;
48
+ /** A custom render function for the "no results" state. */
23
49
  renderNoResults?: (state: InferState) => React.ReactNode;
24
50
  }
51
+ /**
52
+ * A styled React component for Pro6PP Infer API.
53
+ * Includes styling, keyboard navigation, and loading states.
54
+ */
25
55
  declare const Pro6PPInfer: React.FC<Pro6PPInferProps>;
26
56
 
27
57
  export { Pro6PPInfer, type Pro6PPInferProps, useInfer };
package/dist/index.js CHANGED
@@ -9,7 +9,8 @@ import React, { useState, useMemo, useEffect, useRef } from "react";
9
9
  var DEFAULTS = {
10
10
  API_URL: "https://api.pro6pp.nl/v2",
11
11
  LIMIT: 1e3,
12
- DEBOUNCE_MS: 300
12
+ DEBOUNCE_MS: 150,
13
+ MIN_DEBOUNCE_MS: 50
13
14
  };
14
15
  var PATTERNS = {
15
16
  DIGITS_1_3: /^[0-9]{1,3}$/
@@ -26,6 +27,10 @@ var INITIAL_STATE = {
26
27
  selectedSuggestionIndex: -1
27
28
  };
28
29
  var InferCore = class {
30
+ /**
31
+ * Initializes a new instance of the Infer engine.
32
+ * @param config The configuration object including API keys and callbacks.
33
+ */
29
34
  constructor(config) {
30
35
  __publicField(this, "country");
31
36
  __publicField(this, "authKey");
@@ -34,6 +39,10 @@ var InferCore = class {
34
39
  __publicField(this, "fetcher");
35
40
  __publicField(this, "onStateChange");
36
41
  __publicField(this, "onSelect");
42
+ /**
43
+ * The current read-only state of the engine.
44
+ * Use `onStateChange` to react to updates.
45
+ */
37
46
  __publicField(this, "state");
38
47
  __publicField(this, "abortController", null);
39
48
  __publicField(this, "debouncedFetch");
@@ -48,11 +57,15 @@ var InferCore = class {
48
57
  this.onSelect = config.onSelect || (() => {
49
58
  });
50
59
  this.state = { ...INITIAL_STATE };
51
- this.debouncedFetch = this.debounce(
52
- (val) => this.executeFetch(val),
53
- DEFAULTS.DEBOUNCE_MS
54
- );
60
+ const configDebounce = config.debounceMs !== void 0 ? config.debounceMs : DEFAULTS.DEBOUNCE_MS;
61
+ const debounceTime = Math.max(configDebounce, DEFAULTS.MIN_DEBOUNCE_MS);
62
+ this.debouncedFetch = this.debounce((val) => this.executeFetch(val), debounceTime);
55
63
  }
64
+ /**
65
+ * Processes new text input from the user.
66
+ * Triggers a debounced API request and updates the internal state.
67
+ * @param value The raw string from the input field.
68
+ */
56
69
  handleInput(value) {
57
70
  if (this.isSelecting) {
58
71
  this.isSelecting = false;
@@ -70,6 +83,14 @@ var InferCore = class {
70
83
  }
71
84
  this.debouncedFetch(value);
72
85
  }
86
+ /**
87
+ * Handles keyboard events for the input field.
88
+ * Supports:
89
+ * - `ArrowUp`/`ArrowDown`: Navigate through the suggestion list.
90
+ * - `Enter`: Select the currently highlighted suggestion.
91
+ * - `Space`: Automatically inserts a comma if a numeric house number is detected.
92
+ * @param event The keyboard event from the input element.
93
+ */
73
94
  handleKeyDown(event) {
74
95
  const target = event.target;
75
96
  if (!target) return;
@@ -111,6 +132,11 @@ var InferCore = class {
111
132
  this.updateQueryAndFetch(next);
112
133
  }
113
134
  }
135
+ /**
136
+ * Manually selects a suggestion or a string value.
137
+ * This is typically called when a user clicks a suggestion in the UI.
138
+ * @param item The suggestion object or string to select.
139
+ */
114
140
  selectItem(item) {
115
141
  this.debouncedFetch.cancel();
116
142
  if (this.abortController) {
@@ -346,21 +372,18 @@ var DEFAULT_STYLES = `
346
372
  list-style: none !important;
347
373
  padding: 0 !important;
348
374
  margin: 0 !important;
349
- overflow: hidden;
350
375
  }
351
376
  .pro6pp-item {
352
- padding: 12px 12px 9px 12px;
377
+ padding: 10px 16px;
353
378
  cursor: pointer;
354
379
  display: flex;
355
380
  flex-direction: row;
356
381
  align-items: center;
357
382
  color: #000000;
358
383
  font-size: 14px;
359
- line-height: 1;
384
+ line-height: 1.2;
360
385
  white-space: nowrap;
361
386
  overflow: hidden;
362
- border-radius: 0 !important;
363
- margin: 0 !important;
364
387
  }
365
388
  .pro6pp-item:hover, .pro6pp-item--active {
366
389
  background-color: #f5f5f5;
@@ -422,15 +445,19 @@ function useInfer(config) {
422
445
  }
423
446
  }
424
447
  });
425
- }, [config.country, config.authKey, config.limit]);
448
+ }, [config.country, config.authKey, config.limit, config.debounceMs]);
426
449
  return {
450
+ /** The current UI state (suggestions, loading status, query, etc.). */
427
451
  state,
452
+ /** The raw InferCore instance for manual control. */
428
453
  core,
454
+ /** Pre-configured event handlers to spread onto an <input /> element. */
429
455
  inputProps: {
430
456
  value: state.query,
431
457
  onChange: (e) => core.handleInput(e.target.value),
432
458
  onKeyDown: (e) => core.handleKeyDown(e)
433
459
  },
460
+ /** Function to manually select a specific suggestion. */
434
461
  selectItem: (item) => core.selectItem(item)
435
462
  };
436
463
  }
@@ -446,7 +473,9 @@ var Pro6PPInfer = ({
446
473
  ...config
447
474
  }) => {
448
475
  const { state, selectItem, inputProps: coreInputProps } = useInfer(config);
476
+ const [isOpen, setIsOpen] = useState(false);
449
477
  const inputRef = useRef(null);
478
+ const wrapperRef = useRef(null);
450
479
  useEffect(() => {
451
480
  if (disableDefaultStyles) return;
452
481
  const styleId = "pro6pp-styles";
@@ -457,6 +486,15 @@ var Pro6PPInfer = ({
457
486
  document.head.appendChild(styleEl);
458
487
  }
459
488
  }, [disableDefaultStyles]);
489
+ useEffect(() => {
490
+ const handleClickOutside = (event) => {
491
+ if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
492
+ setIsOpen(false);
493
+ }
494
+ };
495
+ document.addEventListener("mousedown", handleClickOutside);
496
+ return () => document.removeEventListener("mousedown", handleClickOutside);
497
+ }, []);
460
498
  const items = useMemo(() => {
461
499
  return [
462
500
  ...state.cities.map((c) => ({ ...c, type: "city" })),
@@ -466,14 +504,15 @@ var Pro6PPInfer = ({
466
504
  }, [state.cities, state.streets, state.suggestions]);
467
505
  const handleSelect = (item) => {
468
506
  selectItem(item);
507
+ setIsOpen(false);
469
508
  if (!state.isValid && inputRef.current) {
470
509
  inputRef.current.focus();
471
510
  }
472
511
  };
473
512
  const hasResults = items.length > 0;
474
513
  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(
514
+ const showDropdown = isOpen && (hasResults || showNoResults);
515
+ return /* @__PURE__ */ React.createElement("div", { ref: wrapperRef, className: `pro6pp-wrapper ${className || ""}`, style }, /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(
477
516
  "input",
478
517
  {
479
518
  ref: inputRef,
@@ -482,11 +521,15 @@ var Pro6PPInfer = ({
482
521
  placeholder,
483
522
  autoComplete: "off",
484
523
  ...inputProps,
485
- ...coreInputProps
524
+ ...coreInputProps,
525
+ onFocus: (e) => {
526
+ setIsOpen(true);
527
+ inputProps?.onFocus?.(e);
528
+ }
486
529
  }
487
530
  ), 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
531
  const isActive = index === state.selectedSuggestionIndex;
489
- const secondaryText = item.subtitle || item.count;
532
+ const secondaryText = item.subtitle || (item.count !== void 0 ? item.count : "");
490
533
  const showChevron = item.value === void 0 || item.value === null;
491
534
  return /* @__PURE__ */ React.createElement(
492
535
  "li",
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.7",
23
+ "version": "0.0.2-beta.8",
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.5"
49
+ "@pro6pp/infer-core": "0.0.2-beta.6"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@testing-library/dom": "^10.4.1",