@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.
- package/cdn/radar-autocomplete.js +114 -22
- package/cdn/radar-autocomplete.min.js +1 -1
- package/dist/autocomplete.d.ts +94 -6
- package/dist/errors.d.ts +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +114 -22
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +32 -5
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/autocomplete.ts +114 -32
- package/src/errors.ts +1 -0
- package/src/index.ts +11 -0
- package/src/types.ts +40 -13
- package/src/version.ts +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
(function (Radar) {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
/** error thrown when the autocomplete container element is not found in the DOM */
|
|
4
5
|
class RadarAutocompleteContainerNotFound extends Radar.RadarError {
|
|
5
6
|
constructor(message) {
|
|
6
7
|
super(message);
|
|
@@ -65,6 +66,7 @@
|
|
|
65
66
|
</svg>`.trim();
|
|
66
67
|
return `data:image/svg+xml;charset=utf-8,${svg}`;
|
|
67
68
|
};
|
|
69
|
+
/** address autocomplete UI widget with keyboard navigation and result display */
|
|
68
70
|
class AutocompleteUI {
|
|
69
71
|
constructor(options, ctx) {
|
|
70
72
|
this.ctx = ctx;
|
|
@@ -72,7 +74,7 @@
|
|
|
72
74
|
this.config = Object.assign({}, defaultAutocompleteOptions, options);
|
|
73
75
|
// setup state
|
|
74
76
|
this.isOpen = false;
|
|
75
|
-
this.debouncedFetchResults = this.debounce(this.fetchResults, this.config.debounceMS);
|
|
77
|
+
this.debouncedFetchResults = this.debounce(this.fetchResults.bind(this), this.config.debounceMS);
|
|
76
78
|
this.results = [];
|
|
77
79
|
this.highlightedIndex = -1;
|
|
78
80
|
// set threshold alias
|
|
@@ -119,7 +121,7 @@
|
|
|
119
121
|
this.inputField = containerEL;
|
|
120
122
|
// append to dom
|
|
121
123
|
this.wrapper.appendChild(this.resultsList);
|
|
122
|
-
containerEL.parentNode
|
|
124
|
+
containerEL.parentNode?.appendChild(this.wrapper);
|
|
123
125
|
}
|
|
124
126
|
else {
|
|
125
127
|
// if container is not an input, create new input and append to container
|
|
@@ -155,6 +157,7 @@
|
|
|
155
157
|
}
|
|
156
158
|
Logger.debug('AutocompleteUI initialized with options', this.config);
|
|
157
159
|
}
|
|
160
|
+
/** handle input field changes and trigger debounced search */
|
|
158
161
|
handleInput() {
|
|
159
162
|
const { Logger } = this.ctx;
|
|
160
163
|
// Fetch autocomplete results and display them
|
|
@@ -180,25 +183,18 @@
|
|
|
180
183
|
}
|
|
181
184
|
debounce(fn, delay) {
|
|
182
185
|
let timeoutId;
|
|
183
|
-
let resolveFn;
|
|
184
|
-
let rejectFn;
|
|
186
|
+
let resolveFn = null;
|
|
187
|
+
let rejectFn = null;
|
|
185
188
|
return (...args) => {
|
|
186
189
|
clearTimeout(timeoutId);
|
|
187
190
|
timeoutId = setTimeout(() => {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
})
|
|
196
|
-
.catch((error) => {
|
|
197
|
-
if (rejectFn) {
|
|
198
|
-
rejectFn(error);
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
}
|
|
191
|
+
fn(...args)
|
|
192
|
+
.then((value) => {
|
|
193
|
+
resolveFn?.(value);
|
|
194
|
+
})
|
|
195
|
+
.catch((error) => {
|
|
196
|
+
rejectFn?.(error);
|
|
197
|
+
});
|
|
202
198
|
}, delay);
|
|
203
199
|
return new Promise((resolve, reject) => {
|
|
204
200
|
resolveFn = resolve;
|
|
@@ -206,6 +202,11 @@
|
|
|
206
202
|
});
|
|
207
203
|
};
|
|
208
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* fetch autocomplete results from the Radar API
|
|
207
|
+
* @param query - the search query string
|
|
208
|
+
* @returns matching addresses
|
|
209
|
+
*/
|
|
209
210
|
async fetchResults(query) {
|
|
210
211
|
const { apis } = this.ctx;
|
|
211
212
|
const { limit, layers, countryCode, expandUnits, mailable, lang, postalCode, onRequest } = this.config;
|
|
@@ -228,6 +229,10 @@
|
|
|
228
229
|
const { addresses } = await apis.Search.autocomplete(params, 'autocomplete-ui');
|
|
229
230
|
return addresses;
|
|
230
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* render autocomplete results in the dropdown
|
|
234
|
+
* @param results - array of address results to display
|
|
235
|
+
*/
|
|
231
236
|
displayResults(results) {
|
|
232
237
|
// Clear the previous results
|
|
233
238
|
this.clearResultsList();
|
|
@@ -246,7 +251,7 @@
|
|
|
246
251
|
li.setAttribute('id', `${CLASSNAMES.RESULTS_ITEM}}-${index}`);
|
|
247
252
|
// construct result with bolded label
|
|
248
253
|
let listContent;
|
|
249
|
-
if (result.formattedAddress
|
|
254
|
+
if (result.formattedAddress?.includes(result.addressLabel) && result.layer !== 'postalCode') {
|
|
250
255
|
// if addressLabel is contained in the formatted address, bold the address label
|
|
251
256
|
const regex = new RegExp(`(${result.addressLabel}),?`);
|
|
252
257
|
listContent = result.formattedAddress.replace(regex, '<b>$1</b>');
|
|
@@ -292,6 +297,7 @@
|
|
|
292
297
|
this.resultsList.appendChild(noResultsText);
|
|
293
298
|
}
|
|
294
299
|
}
|
|
300
|
+
/** open the results dropdown */
|
|
295
301
|
open() {
|
|
296
302
|
if (this.isOpen) {
|
|
297
303
|
return;
|
|
@@ -300,6 +306,7 @@
|
|
|
300
306
|
this.resultsList.removeAttribute('hidden');
|
|
301
307
|
this.isOpen = true;
|
|
302
308
|
}
|
|
309
|
+
/** close the results dropdown and clear highlighted state */
|
|
303
310
|
close(e) {
|
|
304
311
|
if (!this.isOpen) {
|
|
305
312
|
return;
|
|
@@ -316,6 +323,10 @@
|
|
|
316
323
|
this.clearResultsList();
|
|
317
324
|
}, linkClick ? 100 : 0);
|
|
318
325
|
}
|
|
326
|
+
/**
|
|
327
|
+
* highlight a result by index with wrap-around navigation
|
|
328
|
+
* @param index - the result index to highlight
|
|
329
|
+
*/
|
|
319
330
|
goTo(index) {
|
|
320
331
|
if (!this.isOpen || !this.results.length) {
|
|
321
332
|
return;
|
|
@@ -370,6 +381,10 @@
|
|
|
370
381
|
break;
|
|
371
382
|
}
|
|
372
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* select a result by index and populate the input field
|
|
386
|
+
* @param index - the result index to select
|
|
387
|
+
*/
|
|
373
388
|
select(index) {
|
|
374
389
|
const { Logger } = this.ctx;
|
|
375
390
|
const result = this.results[index];
|
|
@@ -378,7 +393,7 @@
|
|
|
378
393
|
return;
|
|
379
394
|
}
|
|
380
395
|
let inputValue;
|
|
381
|
-
if (result.formattedAddress
|
|
396
|
+
if (result.formattedAddress?.includes(result.addressLabel)) {
|
|
382
397
|
inputValue = result.formattedAddress;
|
|
383
398
|
}
|
|
384
399
|
else {
|
|
@@ -393,11 +408,12 @@
|
|
|
393
408
|
// clear results list
|
|
394
409
|
this.close();
|
|
395
410
|
}
|
|
411
|
+
/** clear the results list DOM and reset results array */
|
|
396
412
|
clearResultsList() {
|
|
397
413
|
this.resultsList.innerHTML = '';
|
|
398
414
|
this.results = [];
|
|
399
415
|
}
|
|
400
|
-
|
|
416
|
+
/** remove the autocomplete widget from the DOM */
|
|
401
417
|
remove() {
|
|
402
418
|
const { Logger } = this.ctx;
|
|
403
419
|
Logger.debug('AutocompleteUI removed.');
|
|
@@ -405,6 +421,11 @@
|
|
|
405
421
|
this.resultsList.remove();
|
|
406
422
|
this.wrapper.remove();
|
|
407
423
|
}
|
|
424
|
+
/**
|
|
425
|
+
* set the `near` location bias for autocomplete requests
|
|
426
|
+
* @param near - location string, Location object, or null to clear
|
|
427
|
+
* @returns this instance for chaining
|
|
428
|
+
*/
|
|
408
429
|
setNear(near) {
|
|
409
430
|
if (near === undefined || near === null) {
|
|
410
431
|
this.near = undefined;
|
|
@@ -417,21 +438,41 @@
|
|
|
417
438
|
}
|
|
418
439
|
return this;
|
|
419
440
|
}
|
|
441
|
+
/**
|
|
442
|
+
* set the input placeholder text
|
|
443
|
+
* @param placeholder - new placeholder string
|
|
444
|
+
* @returns this instance for chaining
|
|
445
|
+
*/
|
|
420
446
|
setPlaceholder(placeholder) {
|
|
421
447
|
this.config.placeholder = placeholder;
|
|
422
448
|
this.inputField.placeholder = placeholder;
|
|
423
449
|
return this;
|
|
424
450
|
}
|
|
451
|
+
/**
|
|
452
|
+
* set the disabled state of the input
|
|
453
|
+
* @param disabled - whether to disable the input
|
|
454
|
+
* @returns this instance for chaining
|
|
455
|
+
*/
|
|
425
456
|
setDisabled(disabled) {
|
|
426
457
|
this.config.disabled = disabled;
|
|
427
458
|
this.inputField.disabled = disabled;
|
|
428
459
|
return this;
|
|
429
460
|
}
|
|
461
|
+
/**
|
|
462
|
+
* toggle responsive width mode
|
|
463
|
+
* @param responsive - whether to use responsive layout
|
|
464
|
+
* @returns this instance for chaining
|
|
465
|
+
*/
|
|
430
466
|
setResponsive(responsive) {
|
|
431
467
|
this.config.responsive = responsive;
|
|
432
468
|
setWidth(this.wrapper, this.config);
|
|
433
469
|
return this;
|
|
434
470
|
}
|
|
471
|
+
/**
|
|
472
|
+
* set the widget width
|
|
473
|
+
* @param width - width in px, CSS string, or null to reset
|
|
474
|
+
* @returns this instance for chaining
|
|
475
|
+
*/
|
|
435
476
|
setWidth(width) {
|
|
436
477
|
if (width === null) {
|
|
437
478
|
this.config.width = undefined;
|
|
@@ -442,6 +483,11 @@
|
|
|
442
483
|
setWidth(this.wrapper, this.config);
|
|
443
484
|
return this;
|
|
444
485
|
}
|
|
486
|
+
/**
|
|
487
|
+
* set the max height of the results dropdown
|
|
488
|
+
* @param height - height in px, CSS string, or null to reset
|
|
489
|
+
* @returns this instance for chaining
|
|
490
|
+
*/
|
|
445
491
|
setMaxHeight(height) {
|
|
446
492
|
if (height === null) {
|
|
447
493
|
this.config.maxHeight = undefined;
|
|
@@ -452,15 +498,30 @@
|
|
|
452
498
|
setHeight(this.resultsList, this.config);
|
|
453
499
|
return this;
|
|
454
500
|
}
|
|
501
|
+
/**
|
|
502
|
+
* set the minimum character count to trigger autocomplete
|
|
503
|
+
* @param minCharacters - character threshold
|
|
504
|
+
* @returns this instance for chaining
|
|
505
|
+
*/
|
|
455
506
|
setMinCharacters(minCharacters) {
|
|
456
507
|
this.config.minCharacters = minCharacters;
|
|
457
508
|
this.config.threshold = minCharacters;
|
|
458
509
|
return this;
|
|
459
510
|
}
|
|
511
|
+
/**
|
|
512
|
+
* set the maximum number of results
|
|
513
|
+
* @param limit - result count limit
|
|
514
|
+
* @returns this instance for chaining
|
|
515
|
+
*/
|
|
460
516
|
setLimit(limit) {
|
|
461
517
|
this.config.limit = limit;
|
|
462
518
|
return this;
|
|
463
519
|
}
|
|
520
|
+
/**
|
|
521
|
+
* set the language for autocomplete results
|
|
522
|
+
* @param lang - language code or null to clear
|
|
523
|
+
* @returns this instance for chaining
|
|
524
|
+
*/
|
|
464
525
|
setLang(lang) {
|
|
465
526
|
if (lang === null) {
|
|
466
527
|
this.config.lang = undefined;
|
|
@@ -470,6 +531,11 @@
|
|
|
470
531
|
}
|
|
471
532
|
return this;
|
|
472
533
|
}
|
|
534
|
+
/**
|
|
535
|
+
* set a postal code bias for autocomplete requests
|
|
536
|
+
* @param postalCode - postal code string or null to clear
|
|
537
|
+
* @returns this instance for chaining
|
|
538
|
+
*/
|
|
473
539
|
setPostalCode(postalCode) {
|
|
474
540
|
if (postalCode === null) {
|
|
475
541
|
this.config.postalCode = undefined;
|
|
@@ -479,6 +545,11 @@
|
|
|
479
545
|
}
|
|
480
546
|
return this;
|
|
481
547
|
}
|
|
548
|
+
/**
|
|
549
|
+
* toggle marker icons in result items
|
|
550
|
+
* @param showMarkers - whether to show markers
|
|
551
|
+
* @returns this instance for chaining
|
|
552
|
+
*/
|
|
482
553
|
setShowMarkers(showMarkers) {
|
|
483
554
|
this.config.showMarkers = showMarkers;
|
|
484
555
|
if (showMarkers) {
|
|
@@ -504,6 +575,11 @@
|
|
|
504
575
|
}
|
|
505
576
|
return this;
|
|
506
577
|
}
|
|
578
|
+
/**
|
|
579
|
+
* set the color of marker icons in result items
|
|
580
|
+
* @param color - CSS color string
|
|
581
|
+
* @returns this instance for chaining
|
|
582
|
+
*/
|
|
507
583
|
setMarkerColor(color) {
|
|
508
584
|
this.config.markerColor = color;
|
|
509
585
|
const marker = this.resultsList.getElementsByClassName(CLASSNAMES.RESULTS_MARKER);
|
|
@@ -512,6 +588,11 @@
|
|
|
512
588
|
}
|
|
513
589
|
return this;
|
|
514
590
|
}
|
|
591
|
+
/**
|
|
592
|
+
* toggle hiding results when input loses focus
|
|
593
|
+
* @param hideResultsOnBlur - whether to hide on blur
|
|
594
|
+
* @returns this instance for chaining
|
|
595
|
+
*/
|
|
515
596
|
setHideResultsOnBlur(hideResultsOnBlur) {
|
|
516
597
|
this.config.hideResultsOnBlur = hideResultsOnBlur;
|
|
517
598
|
if (hideResultsOnBlur) {
|
|
@@ -524,8 +605,19 @@
|
|
|
524
605
|
}
|
|
525
606
|
}
|
|
526
607
|
|
|
527
|
-
var version = '5.0.0-beta.
|
|
608
|
+
var version = '5.0.0-beta.6';
|
|
528
609
|
|
|
610
|
+
/**
|
|
611
|
+
* create the Radar autocomplete plugin
|
|
612
|
+
*
|
|
613
|
+
* @returns a plugin that adds `Radar.ui.autocomplete()` method
|
|
614
|
+
*
|
|
615
|
+
* @example
|
|
616
|
+
* ```ts
|
|
617
|
+
* import { createAutocompletePlugin } from '@radarlabs/plugin-autocomplete';
|
|
618
|
+
* Radar.registerPlugin(createAutocompletePlugin());
|
|
619
|
+
* ```
|
|
620
|
+
*/
|
|
529
621
|
function createAutocompletePlugin() {
|
|
530
622
|
return {
|
|
531
623
|
name: 'autocomplete',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(t){"use strict";class e extends t.RadarError{constructor(t){super(t),this.name="RadarAutocompleteContainerNotFound",this.status="CONTAINER_NOT_FOUND"}}const s="radar-autocomplete-wrapper",i="radar-autocomplete-input",r="radar-autocomplete-search-icon",n="radar-autocomplete-results-list",a="radar-autocomplete-results-item",l="radar-autocomplete-results-marker",o="radar-autocomplete-results-item-selected",h="radar-powered",d="radar-no-results",c={container:"autocomplete",debounceMS:200,minCharacters:3,limit:8,placeholder:"Search address",responsive:!0,disabled:!1,showMarkers:!0,hideResultsOnBlur:!0},u=t=>"number"==typeof t?`${t}px`:t,p=(t,e)=>{if(e.responsive)return t.style.width="100%",void(e.width&&(t.style.maxWidth=u(e.width)));t.style.width=u(e.width||400),t.style.removeProperty("max-width")},g=(t,e)=>{e.maxHeight&&(t.style.maxHeight=u(e.maxHeight),t.style.overflowY="auto")},m=(t="#ACBDC8")=>`data:image/svg+xml;charset=utf-8,${`<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">\n <path d="M12.5704 6.57036C12.5704 4.11632 10.6342 2.11257 8.21016 2C8.14262 2 8.06757 2 8.00003 2C7.93249 2 7.85744 2 7.7899 2C5.35838 2.11257 3.42967 4.11632 3.42967 6.57036C3.42967 6.60037 3.42967 6.6379 3.42967 6.66792C3.42967 6.69794 3.42967 6.73546 3.42967 6.76548C3.42967 9.46717 7.09196 13.3621 7.4672 13.7598C7.61729 13.9174 7.84994 14 8.00003 14C8.15012 14 8.38277 13.9174 8.53286 13.7598C8.9156 13.3621 12.5704 9.46717 12.5704 6.76548C12.5704 6.72795 12.5704 6.69794 12.5704 6.66792C12.5704 6.6379 12.5704 6.60037 12.5704 6.57036ZM7.99252 8.28893C7.04693 8.28893 6.27395 7.52345 6.27395 6.57036C6.27395 5.61726 7.03943 4.85178 7.99252 4.85178C8.94562 4.85178 9.7111 5.61726 9.7111 6.57036C9.7111 7.52345 8.94562 8.28893 7.99252 8.28893Z" fill="${t.replace("#","%23")}"/>\n </svg>`.trim()}`;class f{constructor(t,a){this.ctx=a;const{Logger:l}=this.ctx;let o;if(this.config=Object.assign({},c,t),this.isOpen=!1,this.debouncedFetchResults=this.debounce(this.fetchResults,this.config.debounceMS),this.results=[],this.highlightedIndex=-1,void 0!==this.config.threshold&&(this.config.minCharacters=this.config.threshold,l.warn('AutocompleteUI option "threshold" is deprecated, use "minCharacters" instead.')),t.near&&("string"==typeof t.near?this.near=t.near:this.near=`${t.near.latitude},${t.near.longitude}`),o="string"==typeof this.config.container?document.getElementById(this.config.container):this.config.container,!o)throw new e(`Could not find container element: ${this.config.container}`);if(this.container=o,this.wrapper=document.createElement("div"),this.wrapper.classList.add(s),this.wrapper.style.display=this.config.responsive?"block":"inline-block",p(this.wrapper,this.config),this.resultsList=document.createElement("ul"),this.resultsList.classList.add(n),this.resultsList.setAttribute("id",n),this.resultsList.setAttribute("role","listbox"),this.resultsList.setAttribute("aria-live","polite"),this.resultsList.setAttribute("aria-label","Search results"),g(this.resultsList,this.config),"INPUT"===o.nodeName)this.inputField=o,this.wrapper.appendChild(this.resultsList),o.parentNode.appendChild(this.wrapper);else{this.inputField=document.createElement("input"),this.inputField.classList.add(i),this.inputField.placeholder=this.config.placeholder,this.inputField.type="text",this.inputField.disabled=this.config.disabled;const t=document.createElement("div");t.classList.add(r),this.wrapper.appendChild(this.inputField),this.wrapper.appendChild(this.resultsList),this.wrapper.appendChild(t),this.container.appendChild(this.wrapper)}this.inputField.setAttribute("autocomplete","off"),this.inputField.setAttribute("role","combobox"),this.inputField.setAttribute("aria-controls",n),this.inputField.setAttribute("aria-expanded","false"),this.inputField.setAttribute("aria-haspopup","listbox"),this.inputField.setAttribute("aria-autocomplete","list"),this.inputField.setAttribute("aria-activedescendant",""),this.inputField.addEventListener("input",this.handleInput.bind(this)),this.inputField.addEventListener("keydown",this.handleKeyboardNavigation.bind(this)),this.config.hideResultsOnBlur&&this.inputField.addEventListener("blur",this.close.bind(this),!0),l.debug("AutocompleteUI initialized with options",this.config)}handleInput(){const{Logger:t}=this.ctx,e=this.inputField.value;e.length<this.config.minCharacters||this.debouncedFetchResults(e).then((t=>{const e=this.config.onResults;e&&e(t),this.displayResults(t)})).catch((e=>{t.warn(`Autocomplete ui error: ${e.message}`);const s=this.config.onError;s&&s(e)}))}debounce(t,e){let s,i,r;return(...n)=>(clearTimeout(s),s=setTimeout((()=>{const e=t.apply(this,n);e instanceof Promise&&e.then((t=>{i&&i(t)})).catch((t=>{r&&r(t)}))}),e),new Promise(((t,e)=>{i=t,r=e})))}async fetchResults(t){const{apis:e}=this.ctx,{limit:s,layers:i,countryCode:r,expandUnits:n,mailable:a,lang:l,postalCode:o,onRequest:h}=this.config,d={query:t,limit:s,layers:i,countryCode:r,expandUnits:n,mailable:a,lang:l,postalCode:o};this.near&&(d.near=this.near),h&&h(d);const{addresses:c}=await e.Search.autocomplete(d,"autocomplete-ui");return c}displayResults(t){let e;if(this.clearResultsList(),this.results=t,this.config.showMarkers&&(e=document.createElement("img"),e.classList.add(l),e.setAttribute("src",m(this.config.markerColor))),t.forEach(((t,s)=>{const i=document.createElement("li");let r;if(i.classList.add(a),i.setAttribute("role","option"),i.setAttribute("id",`${a}}-${s}`),t.formattedAddress.includes(t.addressLabel)&&"postalCode"!==t.layer){const e=new RegExp(`(${t.addressLabel}),?`);r=t.formattedAddress.replace(e,"<b>$1</b>")}else{r=`<b>${t.placeLabel||t.addressLabel}</b> ${t.formattedAddress}`}i.innerHTML=r,e&&i.prepend(e.cloneNode()),i.addEventListener("mousedown",(()=>{this.select(s)})),this.resultsList.appendChild(i)})),this.open(),t.length>0){const t=document.createElement("a");t.href="https://radar.com?ref=powered_by_radar",t.target="_blank",this.poweredByLink=t;const e=document.createElement("span");e.textContent="Powered by",t.appendChild(e);const s=document.createElement("span");s.id="radar-powered-logo",s.textContent="Radar",t.appendChild(s);const i=document.createElement("div");i.classList.add(h),i.appendChild(t),this.resultsList.appendChild(i)}else{const t=document.createElement("div");t.classList.add(d),t.textContent="No results",this.resultsList.appendChild(t)}}open(){this.isOpen||(this.inputField.setAttribute("aria-expanded","true"),this.resultsList.removeAttribute("hidden"),this.isOpen=!0)}close(t){if(!this.isOpen)return;const e=t&&t.relatedTarget===this.poweredByLink;setTimeout((()=>{this.inputField.setAttribute("aria-expanded","false"),this.inputField.setAttribute("aria-activedescendant",""),this.resultsList.setAttribute("hidden",""),this.highlightedIndex=-1,this.isOpen=!1,this.clearResultsList()}),e?100:0)}goTo(t){if(!this.isOpen||!this.results.length)return;t<0?t=this.results.length-1:t>=this.results.length&&(t=0);const e=this.resultsList.getElementsByTagName("li");this.highlightedIndex>-1&&e[this.highlightedIndex].classList.remove(o),e[t].classList.add(o),this.inputField.setAttribute("aria-activedescendant",`${a}-${t}`),this.highlightedIndex=t}handleKeyboardNavigation(t){let e=t.key;if(this.isOpen)switch("Tab"===e&&t.shiftKey&&(e="ArrowUp"),e){case"Tab":case"ArrowDown":t.preventDefault(),this.goTo(this.highlightedIndex+1);break;case"ArrowUp":t.preventDefault(),this.goTo(this.highlightedIndex-1);break;case"Enter":this.select(this.highlightedIndex);break;case"Esc":this.close()}}select(t){const{Logger:e}=this.ctx,s=this.results[t];if(!s)return void e.warn(`No autocomplete result found at index: ${t}`);let i;if(s.formattedAddress.includes(s.addressLabel))i=s.formattedAddress;else{i=`${s.placeLabel||s.addressLabel}, ${s.formattedAddress}`}this.inputField.value=i;const r=this.config.onSelection;r&&r(s),this.close()}clearResultsList(){this.resultsList.innerHTML="",this.results=[]}remove(){const{Logger:t}=this.ctx;t.debug("AutocompleteUI removed."),this.inputField.remove(),this.resultsList.remove(),this.wrapper.remove()}setNear(t){return this.near=null==t?void 0:"string"==typeof t?t:`${t.latitude},${t.longitude}`,this}setPlaceholder(t){return this.config.placeholder=t,this.inputField.placeholder=t,this}setDisabled(t){return this.config.disabled=t,this.inputField.disabled=t,this}setResponsive(t){return this.config.responsive=t,p(this.wrapper,this.config),this}setWidth(t){return null===t?this.config.width=void 0:"string"!=typeof t&&"number"!=typeof t||(this.config.width=t),p(this.wrapper,this.config),this}setMaxHeight(t){return null===t?this.config.maxHeight=void 0:"string"!=typeof t&&"number"!=typeof t||(this.config.maxHeight=t),g(this.resultsList,this.config),this}setMinCharacters(t){return this.config.minCharacters=t,this.config.threshold=t,this}setLimit(t){return this.config.limit=t,this}setLang(t){return null===t?this.config.lang=void 0:"string"==typeof t&&(this.config.lang=t),this}setPostalCode(t){return null===t?this.config.postalCode=void 0:"string"==typeof t&&(this.config.postalCode=t),this}setShowMarkers(t){if(this.config.showMarkers=t,t){const t=document.createElement("img");t.classList.add(l),t.setAttribute("src",m(this.config.markerColor));const e=this.resultsList.getElementsByTagName("li");for(let s=0;s<e.length;s++){e[s].getElementsByClassName(l)[0]||e[s].prepend(t.cloneNode())}}else{const t=this.resultsList.getElementsByTagName("li");for(let e=0;e<t.length;e++){const s=t[e].getElementsByClassName(l)[0];s&&s.remove()}}return this}setMarkerColor(t){this.config.markerColor=t;const e=this.resultsList.getElementsByClassName(l);for(let s=0;s<e.length;s++)e[s].setAttribute("src",m(t));return this}setHideResultsOnBlur(t){return this.config.hideResultsOnBlur=t,t?this.inputField.addEventListener("blur",this.close.bind(this),!0):this.inputField.removeEventListener("blur",this.close.bind(this),!0),this}}t.registerPlugin({name:"autocomplete",version:"5.0.0-beta.5",install(t){const e=t.Radar.ui||{};t.Radar.ui={...e,autocomplete:e=>new f(e,t)}}})}(Radar);
|
|
1
|
+
!function(t){"use strict";class e extends t.RadarError{constructor(t){super(t),this.name="RadarAutocompleteContainerNotFound",this.status="CONTAINER_NOT_FOUND"}}const s="radar-autocomplete-wrapper",i="radar-autocomplete-input",r="radar-autocomplete-search-icon",n="radar-autocomplete-results-list",a="radar-autocomplete-results-item",l="radar-autocomplete-results-marker",o="radar-autocomplete-results-item-selected",h="radar-powered",d="radar-no-results",c={container:"autocomplete",debounceMS:200,minCharacters:3,limit:8,placeholder:"Search address",responsive:!0,disabled:!1,showMarkers:!0,hideResultsOnBlur:!0},u=t=>"number"==typeof t?`${t}px`:t,p=(t,e)=>{if(e.responsive)return t.style.width="100%",void(e.width&&(t.style.maxWidth=u(e.width)));t.style.width=u(e.width||400),t.style.removeProperty("max-width")},g=(t,e)=>{e.maxHeight&&(t.style.maxHeight=u(e.maxHeight),t.style.overflowY="auto")},m=(t="#ACBDC8")=>`data:image/svg+xml;charset=utf-8,${`<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">\n <path d="M12.5704 6.57036C12.5704 4.11632 10.6342 2.11257 8.21016 2C8.14262 2 8.06757 2 8.00003 2C7.93249 2 7.85744 2 7.7899 2C5.35838 2.11257 3.42967 4.11632 3.42967 6.57036C3.42967 6.60037 3.42967 6.6379 3.42967 6.66792C3.42967 6.69794 3.42967 6.73546 3.42967 6.76548C3.42967 9.46717 7.09196 13.3621 7.4672 13.7598C7.61729 13.9174 7.84994 14 8.00003 14C8.15012 14 8.38277 13.9174 8.53286 13.7598C8.9156 13.3621 12.5704 9.46717 12.5704 6.76548C12.5704 6.72795 12.5704 6.69794 12.5704 6.66792C12.5704 6.6379 12.5704 6.60037 12.5704 6.57036ZM7.99252 8.28893C7.04693 8.28893 6.27395 7.52345 6.27395 6.57036C6.27395 5.61726 7.03943 4.85178 7.99252 4.85178C8.94562 4.85178 9.7111 5.61726 9.7111 6.57036C9.7111 7.52345 8.94562 8.28893 7.99252 8.28893Z" fill="${t.replace("#","%23")}"/>\n </svg>`.trim()}`;class f{constructor(t,a){this.ctx=a;const{Logger:l}=this.ctx;let o;if(this.config=Object.assign({},c,t),this.isOpen=!1,this.debouncedFetchResults=this.debounce(this.fetchResults.bind(this),this.config.debounceMS),this.results=[],this.highlightedIndex=-1,void 0!==this.config.threshold&&(this.config.minCharacters=this.config.threshold,l.warn('AutocompleteUI option "threshold" is deprecated, use "minCharacters" instead.')),t.near&&("string"==typeof t.near?this.near=t.near:this.near=`${t.near.latitude},${t.near.longitude}`),o="string"==typeof this.config.container?document.getElementById(this.config.container):this.config.container,!o)throw new e(`Could not find container element: ${this.config.container}`);if(this.container=o,this.wrapper=document.createElement("div"),this.wrapper.classList.add(s),this.wrapper.style.display=this.config.responsive?"block":"inline-block",p(this.wrapper,this.config),this.resultsList=document.createElement("ul"),this.resultsList.classList.add(n),this.resultsList.setAttribute("id",n),this.resultsList.setAttribute("role","listbox"),this.resultsList.setAttribute("aria-live","polite"),this.resultsList.setAttribute("aria-label","Search results"),g(this.resultsList,this.config),"INPUT"===o.nodeName)this.inputField=o,this.wrapper.appendChild(this.resultsList),o.parentNode?.appendChild(this.wrapper);else{this.inputField=document.createElement("input"),this.inputField.classList.add(i),this.inputField.placeholder=this.config.placeholder,this.inputField.type="text",this.inputField.disabled=this.config.disabled;const t=document.createElement("div");t.classList.add(r),this.wrapper.appendChild(this.inputField),this.wrapper.appendChild(this.resultsList),this.wrapper.appendChild(t),this.container.appendChild(this.wrapper)}this.inputField.setAttribute("autocomplete","off"),this.inputField.setAttribute("role","combobox"),this.inputField.setAttribute("aria-controls",n),this.inputField.setAttribute("aria-expanded","false"),this.inputField.setAttribute("aria-haspopup","listbox"),this.inputField.setAttribute("aria-autocomplete","list"),this.inputField.setAttribute("aria-activedescendant",""),this.inputField.addEventListener("input",this.handleInput.bind(this)),this.inputField.addEventListener("keydown",this.handleKeyboardNavigation.bind(this)),this.config.hideResultsOnBlur&&this.inputField.addEventListener("blur",this.close.bind(this),!0),l.debug("AutocompleteUI initialized with options",this.config)}handleInput(){const{Logger:t}=this.ctx,e=this.inputField.value;e.length<this.config.minCharacters||this.debouncedFetchResults(e).then((t=>{const e=this.config.onResults;e&&e(t),this.displayResults(t)})).catch((e=>{t.warn(`Autocomplete ui error: ${e.message}`);const s=this.config.onError;s&&s(e)}))}debounce(t,e){let s,i=null,r=null;return(...n)=>(clearTimeout(s),s=setTimeout((()=>{t(...n).then((t=>{i?.(t)})).catch((t=>{r?.(t)}))}),e),new Promise(((t,e)=>{i=t,r=e})))}async fetchResults(t){const{apis:e}=this.ctx,{limit:s,layers:i,countryCode:r,expandUnits:n,mailable:a,lang:l,postalCode:o,onRequest:h}=this.config,d={query:t,limit:s,layers:i,countryCode:r,expandUnits:n,mailable:a,lang:l,postalCode:o};this.near&&(d.near=this.near),h&&h(d);const{addresses:c}=await e.Search.autocomplete(d,"autocomplete-ui");return c}displayResults(t){let e;if(this.clearResultsList(),this.results=t,this.config.showMarkers&&(e=document.createElement("img"),e.classList.add(l),e.setAttribute("src",m(this.config.markerColor))),t.forEach(((t,s)=>{const i=document.createElement("li");let r;if(i.classList.add(a),i.setAttribute("role","option"),i.setAttribute("id",`${a}}-${s}`),t.formattedAddress?.includes(t.addressLabel)&&"postalCode"!==t.layer){const e=new RegExp(`(${t.addressLabel}),?`);r=t.formattedAddress.replace(e,"<b>$1</b>")}else{r=`<b>${t.placeLabel||t.addressLabel}</b> ${t.formattedAddress}`}i.innerHTML=r,e&&i.prepend(e.cloneNode()),i.addEventListener("mousedown",(()=>{this.select(s)})),this.resultsList.appendChild(i)})),this.open(),t.length>0){const t=document.createElement("a");t.href="https://radar.com?ref=powered_by_radar",t.target="_blank",this.poweredByLink=t;const e=document.createElement("span");e.textContent="Powered by",t.appendChild(e);const s=document.createElement("span");s.id="radar-powered-logo",s.textContent="Radar",t.appendChild(s);const i=document.createElement("div");i.classList.add(h),i.appendChild(t),this.resultsList.appendChild(i)}else{const t=document.createElement("div");t.classList.add(d),t.textContent="No results",this.resultsList.appendChild(t)}}open(){this.isOpen||(this.inputField.setAttribute("aria-expanded","true"),this.resultsList.removeAttribute("hidden"),this.isOpen=!0)}close(t){if(!this.isOpen)return;const e=t&&t.relatedTarget===this.poweredByLink;setTimeout((()=>{this.inputField.setAttribute("aria-expanded","false"),this.inputField.setAttribute("aria-activedescendant",""),this.resultsList.setAttribute("hidden",""),this.highlightedIndex=-1,this.isOpen=!1,this.clearResultsList()}),e?100:0)}goTo(t){if(!this.isOpen||!this.results.length)return;t<0?t=this.results.length-1:t>=this.results.length&&(t=0);const e=this.resultsList.getElementsByTagName("li");this.highlightedIndex>-1&&e[this.highlightedIndex].classList.remove(o),e[t].classList.add(o),this.inputField.setAttribute("aria-activedescendant",`${a}-${t}`),this.highlightedIndex=t}handleKeyboardNavigation(t){let e=t.key;if(this.isOpen)switch("Tab"===e&&t.shiftKey&&(e="ArrowUp"),e){case"Tab":case"ArrowDown":t.preventDefault(),this.goTo(this.highlightedIndex+1);break;case"ArrowUp":t.preventDefault(),this.goTo(this.highlightedIndex-1);break;case"Enter":this.select(this.highlightedIndex);break;case"Esc":this.close()}}select(t){const{Logger:e}=this.ctx,s=this.results[t];if(!s)return void e.warn(`No autocomplete result found at index: ${t}`);let i;if(s.formattedAddress?.includes(s.addressLabel))i=s.formattedAddress;else{i=`${s.placeLabel||s.addressLabel}, ${s.formattedAddress}`}this.inputField.value=i;const r=this.config.onSelection;r&&r(s),this.close()}clearResultsList(){this.resultsList.innerHTML="",this.results=[]}remove(){const{Logger:t}=this.ctx;t.debug("AutocompleteUI removed."),this.inputField.remove(),this.resultsList.remove(),this.wrapper.remove()}setNear(t){return this.near=null==t?void 0:"string"==typeof t?t:`${t.latitude},${t.longitude}`,this}setPlaceholder(t){return this.config.placeholder=t,this.inputField.placeholder=t,this}setDisabled(t){return this.config.disabled=t,this.inputField.disabled=t,this}setResponsive(t){return this.config.responsive=t,p(this.wrapper,this.config),this}setWidth(t){return null===t?this.config.width=void 0:"string"!=typeof t&&"number"!=typeof t||(this.config.width=t),p(this.wrapper,this.config),this}setMaxHeight(t){return null===t?this.config.maxHeight=void 0:"string"!=typeof t&&"number"!=typeof t||(this.config.maxHeight=t),g(this.resultsList,this.config),this}setMinCharacters(t){return this.config.minCharacters=t,this.config.threshold=t,this}setLimit(t){return this.config.limit=t,this}setLang(t){return null===t?this.config.lang=void 0:"string"==typeof t&&(this.config.lang=t),this}setPostalCode(t){return null===t?this.config.postalCode=void 0:"string"==typeof t&&(this.config.postalCode=t),this}setShowMarkers(t){if(this.config.showMarkers=t,t){const t=document.createElement("img");t.classList.add(l),t.setAttribute("src",m(this.config.markerColor));const e=this.resultsList.getElementsByTagName("li");for(let s=0;s<e.length;s++){e[s].getElementsByClassName(l)[0]||e[s].prepend(t.cloneNode())}}else{const t=this.resultsList.getElementsByTagName("li");for(let e=0;e<t.length;e++){const s=t[e].getElementsByClassName(l)[0];s&&s.remove()}}return this}setMarkerColor(t){this.config.markerColor=t;const e=this.resultsList.getElementsByClassName(l);for(let s=0;s<e.length;s++)e[s].setAttribute("src",m(t));return this}setHideResultsOnBlur(t){return this.config.hideResultsOnBlur=t,t?this.inputField.addEventListener("blur",this.close.bind(this),!0):this.inputField.removeEventListener("blur",this.close.bind(this),!0),this}}t.registerPlugin({name:"autocomplete",version:"5.0.0-beta.6",install(t){const e=t.Radar.ui||{};t.Radar.ui={...e,autocomplete:e=>new f(e,t)}}})}(Radar);
|
package/dist/autocomplete.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { RadarAutocompleteUIOptions, RadarAutocompleteConfig } from './types';
|
|
2
|
-
import type { Location, RadarPluginContext } from 'radar-sdk-js';
|
|
2
|
+
import type { RadarAutocompleteAddress, Location, RadarPluginContext } from 'radar-sdk-js';
|
|
3
|
+
/** address autocomplete UI widget with keyboard navigation and result display */
|
|
3
4
|
declare class AutocompleteUI {
|
|
4
5
|
private ctx;
|
|
5
6
|
config: RadarAutocompleteConfig;
|
|
6
7
|
isOpen: boolean;
|
|
7
|
-
results:
|
|
8
|
+
results: RadarAutocompleteAddress[];
|
|
8
9
|
highlightedIndex: number;
|
|
9
|
-
debouncedFetchResults: (
|
|
10
|
+
debouncedFetchResults: (query: string) => Promise<RadarAutocompleteAddress[]>;
|
|
10
11
|
near?: string;
|
|
11
12
|
container: HTMLElement;
|
|
12
13
|
inputField: HTMLInputElement;
|
|
@@ -14,29 +15,116 @@ declare class AutocompleteUI {
|
|
|
14
15
|
wrapper: HTMLElement;
|
|
15
16
|
poweredByLink?: HTMLElement;
|
|
16
17
|
constructor(options: Partial<RadarAutocompleteUIOptions>, ctx: RadarPluginContext);
|
|
18
|
+
/** handle input field changes and trigger debounced search */
|
|
17
19
|
handleInput(): void;
|
|
18
|
-
debounce(fn:
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
debounce<TArgs extends unknown[], TReturn>(fn: (...args: TArgs) => Promise<TReturn>, delay: number): (...args: TArgs) => Promise<TReturn>;
|
|
21
|
+
/**
|
|
22
|
+
* fetch autocomplete results from the Radar API
|
|
23
|
+
* @param query - the search query string
|
|
24
|
+
* @returns matching addresses
|
|
25
|
+
*/
|
|
26
|
+
fetchResults(query: string): Promise<RadarAutocompleteAddress[]>;
|
|
27
|
+
/**
|
|
28
|
+
* render autocomplete results in the dropdown
|
|
29
|
+
* @param results - array of address results to display
|
|
30
|
+
*/
|
|
31
|
+
displayResults(results: RadarAutocompleteAddress[]): void;
|
|
32
|
+
/** open the results dropdown */
|
|
21
33
|
open(): void;
|
|
34
|
+
/** close the results dropdown and clear highlighted state */
|
|
22
35
|
close(e?: FocusEvent): void;
|
|
36
|
+
/**
|
|
37
|
+
* highlight a result by index with wrap-around navigation
|
|
38
|
+
* @param index - the result index to highlight
|
|
39
|
+
*/
|
|
23
40
|
goTo(index: number): void;
|
|
24
41
|
handleKeyboardNavigation(event: KeyboardEvent): void;
|
|
42
|
+
/**
|
|
43
|
+
* select a result by index and populate the input field
|
|
44
|
+
* @param index - the result index to select
|
|
45
|
+
*/
|
|
25
46
|
select(index: number): void;
|
|
47
|
+
/** clear the results list DOM and reset results array */
|
|
26
48
|
clearResultsList(): void;
|
|
49
|
+
/** remove the autocomplete widget from the DOM */
|
|
27
50
|
remove(): void;
|
|
51
|
+
/**
|
|
52
|
+
* set the `near` location bias for autocomplete requests
|
|
53
|
+
* @param near - location string, Location object, or null to clear
|
|
54
|
+
* @returns this instance for chaining
|
|
55
|
+
*/
|
|
28
56
|
setNear(near: string | Location | undefined | null): this;
|
|
57
|
+
/**
|
|
58
|
+
* set the input placeholder text
|
|
59
|
+
* @param placeholder - new placeholder string
|
|
60
|
+
* @returns this instance for chaining
|
|
61
|
+
*/
|
|
29
62
|
setPlaceholder(placeholder: string): this;
|
|
63
|
+
/**
|
|
64
|
+
* set the disabled state of the input
|
|
65
|
+
* @param disabled - whether to disable the input
|
|
66
|
+
* @returns this instance for chaining
|
|
67
|
+
*/
|
|
30
68
|
setDisabled(disabled: boolean): this;
|
|
69
|
+
/**
|
|
70
|
+
* toggle responsive width mode
|
|
71
|
+
* @param responsive - whether to use responsive layout
|
|
72
|
+
* @returns this instance for chaining
|
|
73
|
+
*/
|
|
31
74
|
setResponsive(responsive: boolean): this;
|
|
75
|
+
/**
|
|
76
|
+
* set the widget width
|
|
77
|
+
* @param width - width in px, CSS string, or null to reset
|
|
78
|
+
* @returns this instance for chaining
|
|
79
|
+
*/
|
|
32
80
|
setWidth(width: number | string | null): this;
|
|
81
|
+
/**
|
|
82
|
+
* set the max height of the results dropdown
|
|
83
|
+
* @param height - height in px, CSS string, or null to reset
|
|
84
|
+
* @returns this instance for chaining
|
|
85
|
+
*/
|
|
33
86
|
setMaxHeight(height: number | string | null): this;
|
|
87
|
+
/**
|
|
88
|
+
* set the minimum character count to trigger autocomplete
|
|
89
|
+
* @param minCharacters - character threshold
|
|
90
|
+
* @returns this instance for chaining
|
|
91
|
+
*/
|
|
34
92
|
setMinCharacters(minCharacters: number): this;
|
|
93
|
+
/**
|
|
94
|
+
* set the maximum number of results
|
|
95
|
+
* @param limit - result count limit
|
|
96
|
+
* @returns this instance for chaining
|
|
97
|
+
*/
|
|
35
98
|
setLimit(limit: number): this;
|
|
99
|
+
/**
|
|
100
|
+
* set the language for autocomplete results
|
|
101
|
+
* @param lang - language code or null to clear
|
|
102
|
+
* @returns this instance for chaining
|
|
103
|
+
*/
|
|
36
104
|
setLang(lang: string | null): this;
|
|
105
|
+
/**
|
|
106
|
+
* set a postal code bias for autocomplete requests
|
|
107
|
+
* @param postalCode - postal code string or null to clear
|
|
108
|
+
* @returns this instance for chaining
|
|
109
|
+
*/
|
|
37
110
|
setPostalCode(postalCode: string | null): this;
|
|
111
|
+
/**
|
|
112
|
+
* toggle marker icons in result items
|
|
113
|
+
* @param showMarkers - whether to show markers
|
|
114
|
+
* @returns this instance for chaining
|
|
115
|
+
*/
|
|
38
116
|
setShowMarkers(showMarkers: boolean): this;
|
|
117
|
+
/**
|
|
118
|
+
* set the color of marker icons in result items
|
|
119
|
+
* @param color - CSS color string
|
|
120
|
+
* @returns this instance for chaining
|
|
121
|
+
*/
|
|
39
122
|
setMarkerColor(color: string): this;
|
|
123
|
+
/**
|
|
124
|
+
* toggle hiding results when input loses focus
|
|
125
|
+
* @param hideResultsOnBlur - whether to hide on blur
|
|
126
|
+
* @returns this instance for chaining
|
|
127
|
+
*/
|
|
40
128
|
setHideResultsOnBlur(hideResultsOnBlur: boolean): this;
|
|
41
129
|
}
|
|
42
130
|
export default AutocompleteUI;
|
package/dist/errors.d.ts
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -11,4 +11,15 @@ declare module 'radar-sdk-js' {
|
|
|
11
11
|
let ui: RadarUI;
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* create the Radar autocomplete plugin
|
|
16
|
+
*
|
|
17
|
+
* @returns a plugin that adds `Radar.ui.autocomplete()` method
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { createAutocompletePlugin } from '@radarlabs/plugin-autocomplete';
|
|
22
|
+
* Radar.registerPlugin(createAutocompletePlugin());
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
14
25
|
export declare function createAutocompletePlugin(): RadarPlugin;
|