@radarlabs/plugin-autocomplete 5.0.0-beta.5 → 5.0.0-beta.6

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.
@@ -1,6 +1,6 @@
1
1
  import { RadarAutocompleteContainerNotFound } from './errors';
2
2
  import type { RadarAutocompleteUIOptions, RadarAutocompleteConfig } from './types';
3
- import type { RadarAutocompleteParams, Location, RadarPluginContext } from 'radar-sdk-js';
3
+ import type { RadarAutocompleteAddress, RadarAutocompleteParams, Location, RadarPluginContext } from 'radar-sdk-js';
4
4
 
5
5
  const CLASSNAMES = {
6
6
  WRAPPER: 'radar-autocomplete-wrapper',
@@ -66,13 +66,14 @@ const getMarkerIcon = (color: string = "#ACBDC8") => {
66
66
  };
67
67
 
68
68
 
69
+ /** address autocomplete UI widget with keyboard navigation and result display */
69
70
  class AutocompleteUI {
70
71
  private ctx: RadarPluginContext;
71
72
  config: RadarAutocompleteConfig;
72
73
  isOpen: boolean;
73
- results: any[];
74
+ results: RadarAutocompleteAddress[];
74
75
  highlightedIndex: number;
75
- debouncedFetchResults: (...args: any[]) => Promise<any>;
76
+ debouncedFetchResults: (query: string) => Promise<RadarAutocompleteAddress[]>;
76
77
  near?: string;
77
78
 
78
79
  // DOM elements
@@ -89,7 +90,7 @@ class AutocompleteUI {
89
90
 
90
91
  // setup state
91
92
  this.isOpen = false;
92
- this.debouncedFetchResults = this.debounce(this.fetchResults, this.config.debounceMS);
93
+ this.debouncedFetchResults = this.debounce(this.fetchResults.bind(this), this.config.debounceMS);
93
94
  this.results = [];
94
95
  this.highlightedIndex = -1;
95
96
 
@@ -141,7 +142,7 @@ class AutocompleteUI {
141
142
 
142
143
  // append to dom
143
144
  this.wrapper.appendChild(this.resultsList);
144
- (containerEL.parentNode as any).appendChild(this.wrapper);
145
+ containerEL.parentNode?.appendChild(this.wrapper);
145
146
 
146
147
  } else {
147
148
  // if container is not an input, create new input and append to container
@@ -185,6 +186,7 @@ class AutocompleteUI {
185
186
  Logger.debug('AutocompleteUI initialized with options', this.config);
186
187
  }
187
188
 
189
+ /** handle input field changes and trigger debounced search */
188
190
  public handleInput() {
189
191
  const { Logger } = this.ctx;
190
192
 
@@ -195,14 +197,14 @@ class AutocompleteUI {
195
197
  }
196
198
 
197
199
  this.debouncedFetchResults(query)
198
- .then((results: any[]) => {
200
+ .then((results) => {
199
201
  const onResults = this.config.onResults;
200
202
  if (onResults) {
201
203
  onResults(results);
202
204
  }
203
205
  this.displayResults(results);
204
206
  })
205
- .catch((error) => {
207
+ .catch((error: Error) => {
206
208
  Logger.warn(`Autocomplete ui error: ${error.message}`);
207
209
  const onError = this.config.onError;
208
210
  if (onError) {
@@ -211,39 +213,39 @@ class AutocompleteUI {
211
213
  });
212
214
  }
213
215
 
214
- public debounce(fn: Function, delay: number) {
215
- let timeoutId: any;
216
- let resolveFn: any;
217
- let rejectFn: any;
216
+ public debounce<TArgs extends unknown[], TReturn>(
217
+ fn: (...args: TArgs) => Promise<TReturn>,
218
+ delay: number,
219
+ ): (...args: TArgs) => Promise<TReturn> {
220
+ let timeoutId: ReturnType<typeof setTimeout>;
221
+ let resolveFn: ((value: TReturn) => void) | null = null;
222
+ let rejectFn: ((reason: unknown) => void) | null = null;
218
223
 
219
- return (...args: any[]) => {
224
+ return (...args: TArgs) => {
220
225
  clearTimeout(timeoutId);
221
226
 
222
227
  timeoutId = setTimeout(() => {
223
- const result = fn.apply(this, args);
224
-
225
- if (result instanceof Promise) {
226
- result
227
- .then((value) => {
228
- if (resolveFn) {
229
- resolveFn(value);
230
- }
231
- })
232
- .catch((error) => {
233
- if (rejectFn) {
234
- rejectFn(error);
235
- }
236
- });
237
- }
228
+ fn(...args)
229
+ .then((value) => {
230
+ resolveFn?.(value);
231
+ })
232
+ .catch((error) => {
233
+ rejectFn?.(error);
234
+ });
238
235
  }, delay);
239
236
 
240
- return new Promise((resolve, reject) => {
237
+ return new Promise<TReturn>((resolve, reject) => {
241
238
  resolveFn = resolve;
242
239
  rejectFn = reject;
243
240
  });
244
241
  };
245
242
  }
246
243
 
244
+ /**
245
+ * fetch autocomplete results from the Radar API
246
+ * @param query - the search query string
247
+ * @returns matching addresses
248
+ */
247
249
  public async fetchResults(query: string) {
248
250
  const { apis } = this.ctx;
249
251
  const { limit, layers, countryCode, expandUnits, mailable, lang, postalCode, onRequest } = this.config;
@@ -271,7 +273,11 @@ class AutocompleteUI {
271
273
  return addresses;
272
274
  }
273
275
 
274
- public displayResults(results: any[]) {
276
+ /**
277
+ * render autocomplete results in the dropdown
278
+ * @param results - array of address results to display
279
+ */
280
+ public displayResults(results: RadarAutocompleteAddress[]) {
275
281
  // Clear the previous results
276
282
  this.clearResultsList();
277
283
  this.results = results;
@@ -292,7 +298,7 @@ class AutocompleteUI {
292
298
 
293
299
  // construct result with bolded label
294
300
  let listContent;
295
- if (result.formattedAddress.includes(result.addressLabel) && result.layer !== 'postalCode') {
301
+ if (result.formattedAddress?.includes(result.addressLabel!) && result.layer !== 'postalCode') {
296
302
  // if addressLabel is contained in the formatted address, bold the address label
297
303
  const regex = new RegExp(`(${result.addressLabel}),?`);
298
304
  listContent = result.formattedAddress.replace(regex, '<b>$1</b>');
@@ -346,6 +352,7 @@ class AutocompleteUI {
346
352
  }
347
353
  }
348
354
 
355
+ /** open the results dropdown */
349
356
  public open() {
350
357
  if (this.isOpen) {
351
358
  return;
@@ -356,6 +363,7 @@ class AutocompleteUI {
356
363
  this.isOpen = true;
357
364
  }
358
365
 
366
+ /** close the results dropdown and clear highlighted state */
359
367
  public close(e?: FocusEvent) {
360
368
  if (!this.isOpen) {
361
369
  return;
@@ -374,6 +382,10 @@ class AutocompleteUI {
374
382
  }, linkClick ? 100 : 0);
375
383
  }
376
384
 
385
+ /**
386
+ * highlight a result by index with wrap-around navigation
387
+ * @param index - the result index to highlight
388
+ */
377
389
  public goTo(index: number) {
378
390
  if (!this.isOpen || !this.results.length) {
379
391
  return;
@@ -441,6 +453,10 @@ class AutocompleteUI {
441
453
  }
442
454
  }
443
455
 
456
+ /**
457
+ * select a result by index and populate the input field
458
+ * @param index - the result index to select
459
+ */
444
460
  public select(index: number) {
445
461
  const { Logger } = this.ctx;
446
462
  const result = this.results[index];
@@ -450,7 +466,7 @@ class AutocompleteUI {
450
466
  }
451
467
 
452
468
  let inputValue;
453
- if (result.formattedAddress.includes(result.addressLabel)) {
469
+ if (result.formattedAddress?.includes(result.addressLabel!)) {
454
470
  inputValue = result.formattedAddress;
455
471
  } else {
456
472
  const label = result.placeLabel || result.addressLabel;
@@ -467,12 +483,13 @@ class AutocompleteUI {
467
483
  this.close();
468
484
  }
469
485
 
486
+ /** clear the results list DOM and reset results array */
470
487
  public clearResultsList() {
471
488
  this.resultsList.innerHTML = '';
472
489
  this.results = [];
473
490
  }
474
491
 
475
- // remove elements from DOM
492
+ /** remove the autocomplete widget from the DOM */
476
493
  public remove() {
477
494
  const { Logger } = this.ctx;
478
495
  Logger.debug('AutocompleteUI removed.');
@@ -481,6 +498,11 @@ class AutocompleteUI {
481
498
  this.wrapper.remove();
482
499
  }
483
500
 
501
+ /**
502
+ * set the `near` location bias for autocomplete requests
503
+ * @param near - location string, Location object, or null to clear
504
+ * @returns this instance for chaining
505
+ */
484
506
  public setNear(near: string | Location | undefined | null) {
485
507
  if (near === undefined || near === null) {
486
508
  this.near = undefined;
@@ -492,24 +514,44 @@ class AutocompleteUI {
492
514
  return this;
493
515
  }
494
516
 
517
+ /**
518
+ * set the input placeholder text
519
+ * @param placeholder - new placeholder string
520
+ * @returns this instance for chaining
521
+ */
495
522
  public setPlaceholder(placeholder: string) {
496
523
  this.config.placeholder = placeholder;
497
524
  this.inputField.placeholder = placeholder;
498
525
  return this;
499
526
  }
500
527
 
528
+ /**
529
+ * set the disabled state of the input
530
+ * @param disabled - whether to disable the input
531
+ * @returns this instance for chaining
532
+ */
501
533
  public setDisabled(disabled: boolean) {
502
534
  this.config.disabled = disabled;
503
535
  this.inputField.disabled = disabled;
504
536
  return this;
505
537
  }
506
538
 
539
+ /**
540
+ * toggle responsive width mode
541
+ * @param responsive - whether to use responsive layout
542
+ * @returns this instance for chaining
543
+ */
507
544
  public setResponsive(responsive: boolean) {
508
545
  this.config.responsive = responsive;
509
546
  setWidth(this.wrapper, this.config);
510
547
  return this;
511
548
  }
512
549
 
550
+ /**
551
+ * set the widget width
552
+ * @param width - width in px, CSS string, or null to reset
553
+ * @returns this instance for chaining
554
+ */
513
555
  public setWidth(width: number | string | null) {
514
556
  if (width === null) {
515
557
  this.config.width = undefined;
@@ -520,6 +562,11 @@ class AutocompleteUI {
520
562
  return this;
521
563
  }
522
564
 
565
+ /**
566
+ * set the max height of the results dropdown
567
+ * @param height - height in px, CSS string, or null to reset
568
+ * @returns this instance for chaining
569
+ */
523
570
  public setMaxHeight(height: number | string | null) {
524
571
  if (height === null) {
525
572
  this.config.maxHeight = undefined;
@@ -530,17 +577,32 @@ class AutocompleteUI {
530
577
  return this;
531
578
  }
532
579
 
580
+ /**
581
+ * set the minimum character count to trigger autocomplete
582
+ * @param minCharacters - character threshold
583
+ * @returns this instance for chaining
584
+ */
533
585
  public setMinCharacters(minCharacters: number) {
534
586
  this.config.minCharacters = minCharacters;
535
587
  this.config.threshold = minCharacters;
536
588
  return this;
537
589
  }
538
590
 
591
+ /**
592
+ * set the maximum number of results
593
+ * @param limit - result count limit
594
+ * @returns this instance for chaining
595
+ */
539
596
  public setLimit(limit: number) {
540
597
  this.config.limit = limit;
541
598
  return this;
542
599
  }
543
600
 
601
+ /**
602
+ * set the language for autocomplete results
603
+ * @param lang - language code or null to clear
604
+ * @returns this instance for chaining
605
+ */
544
606
  public setLang(lang: string | null) {
545
607
  if (lang === null) {
546
608
  this.config.lang = undefined;
@@ -550,6 +612,11 @@ class AutocompleteUI {
550
612
  return this;
551
613
  }
552
614
 
615
+ /**
616
+ * set a postal code bias for autocomplete requests
617
+ * @param postalCode - postal code string or null to clear
618
+ * @returns this instance for chaining
619
+ */
553
620
  public setPostalCode(postalCode: string | null) {
554
621
  if (postalCode === null) {
555
622
  this.config.postalCode = undefined;
@@ -559,6 +626,11 @@ class AutocompleteUI {
559
626
  return this;
560
627
  }
561
628
 
629
+ /**
630
+ * toggle marker icons in result items
631
+ * @param showMarkers - whether to show markers
632
+ * @returns this instance for chaining
633
+ */
562
634
  public setShowMarkers(showMarkers: boolean) {
563
635
  this.config.showMarkers = showMarkers;
564
636
  if (showMarkers) {
@@ -584,6 +656,11 @@ class AutocompleteUI {
584
656
  return this;
585
657
  }
586
658
 
659
+ /**
660
+ * set the color of marker icons in result items
661
+ * @param color - CSS color string
662
+ * @returns this instance for chaining
663
+ */
587
664
  public setMarkerColor(color: string) {
588
665
  this.config.markerColor = color;
589
666
  const marker = this.resultsList.getElementsByClassName(CLASSNAMES.RESULTS_MARKER);
@@ -593,6 +670,11 @@ class AutocompleteUI {
593
670
  return this;
594
671
  }
595
672
 
673
+ /**
674
+ * toggle hiding results when input loses focus
675
+ * @param hideResultsOnBlur - whether to hide on blur
676
+ * @returns this instance for chaining
677
+ */
596
678
  public setHideResultsOnBlur(hideResultsOnBlur: boolean) {
597
679
  this.config.hideResultsOnBlur = hideResultsOnBlur;
598
680
  if (hideResultsOnBlur) {
package/src/errors.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { RadarError } from 'radar-sdk-js';
2
2
 
3
+ /** error thrown when the autocomplete container element is not found in the DOM */
3
4
  export class RadarAutocompleteContainerNotFound extends RadarError {
4
5
  constructor(message: string) {
5
6
  super(message);
package/src/index.ts CHANGED
@@ -17,6 +17,17 @@ declare module 'radar-sdk-js' {
17
17
  }
18
18
  }
19
19
 
20
+ /**
21
+ * create the Radar autocomplete plugin
22
+ *
23
+ * @returns a plugin that adds `Radar.ui.autocomplete()` method
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * import { createAutocompletePlugin } from '@radarlabs/plugin-autocomplete';
28
+ * Radar.registerPlugin(createAutocompletePlugin());
29
+ * ```
30
+ */
20
31
  export function createAutocompletePlugin(): RadarPlugin {
21
32
  return {
22
33
  name: 'autocomplete',
package/src/types.ts CHANGED
@@ -1,30 +1,57 @@
1
- import type { RadarAutocompleteParams } from 'radar-sdk-js';
1
+ import type { RadarAutocompleteAddress, RadarAutocompleteParams } from 'radar-sdk-js';
2
2
 
3
+ /** configuration options for the autocomplete UI widget */
3
4
  export interface RadarAutocompleteUIOptions extends Omit<RadarAutocompleteParams, 'query'> {
5
+ /** element ID or HTMLElement to mount the widget into */
4
6
  container: string | HTMLElement;
5
- debounceMS?: number, // Debounce time in milliseconds
6
- /** @deprecated use minCharacters instead */
7
+ /** debounce delay in milliseconds before fetching results */
8
+ debounceMS?: number,
9
+ /**
10
+ * @deprecated use minCharacters instead
11
+ */
7
12
  threshold?: number,
8
- minCharacters?: number, // Minimum number of characters to trigger autocomplete
9
- placeholder?: string, // Placeholder text for the input field
10
- onSelection?: (selection: any) => void,
13
+ /** minimum number of characters to trigger autocomplete */
14
+ minCharacters?: number,
15
+ /** placeholder text for the input field */
16
+ placeholder?: string,
17
+ /** callback invoked when a result is selected */
18
+ onSelection?: (selection: RadarAutocompleteAddress) => void,
19
+ /** callback invoked before each autocomplete request */
11
20
  onRequest?: (params: RadarAutocompleteParams) => void,
12
- onResults?: (results: any[]) => void,
13
- onError?: (error: any) => void,
21
+ /** callback invoked when results are returned */
22
+ onResults?: (results: RadarAutocompleteAddress[]) => void,
23
+ /** callback invoked on autocomplete errors */
24
+ onError?: (error: Error) => void,
25
+ /** whether the input is disabled */
14
26
  disabled?: boolean,
27
+ /** whether to use responsive width (100% with optional max-width) */
15
28
  responsive?: boolean;
29
+ /** fixed width or max-width (px number or CSS string) */
16
30
  width?: string | number;
31
+ /** max height of the results dropdown (px number or CSS string) */
17
32
  maxHeight?: string | number;
33
+ /** whether to show marker icons in results */
18
34
  showMarkers?: boolean;
35
+ /** color for marker icons in results */
19
36
  markerColor?: string;
37
+ /** whether to hide results when the input loses focus */
20
38
  hideResultsOnBlur?: boolean;
21
39
  }
22
40
 
41
+ /** resolved configuration with required defaults */
23
42
  export interface RadarAutocompleteConfig extends RadarAutocompleteUIOptions {
24
- debounceMS: number, // Debounce time in milliseconds
25
- threshold: number, // DEPRECATED(use minCharacters instead)
26
- minCharacters: number, // Minimum number of characters to trigger autocomplete
27
- limit: number, // Maximum number of autocomplete results
28
- placeholder: string, // Placeholder text for the input field
43
+ /** debounce delay in milliseconds */
44
+ debounceMS: number,
45
+ /**
46
+ * @deprecated use minCharacters instead
47
+ */
48
+ threshold: number,
49
+ /** minimum characters to trigger autocomplete */
50
+ minCharacters: number,
51
+ /** maximum number of autocomplete results */
52
+ limit: number,
53
+ /** placeholder text for the input field */
54
+ placeholder: string,
55
+ /** whether the input is disabled */
29
56
  disabled: boolean,
30
57
  }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export default '5.0.0-beta.5';
1
+ export default '5.0.0-beta.6';