From 23f29173709fb8e2e302759f7426cc24316882c2 Mon Sep 17 00:00:00 2001 From: shwetadeshmukh Date: Wed, 28 Feb 2024 16:23:52 +0530 Subject: [PATCH 1/5] Feat : Localities api autocomplete endpoint samples --- .../index.njk | 22 ++ .../index.ts | 198 ++++++++++++++++++ .../localities-api-autocomplete-advanced.json | 12 ++ .../style.scss | 15 ++ samples/localities-api-autocomplete/index.njk | 22 ++ samples/localities-api-autocomplete/index.ts | 197 +++++++++++++++++ .../localities-api-autocomplete.json | 12 ++ .../localities-api-autocomplete/style.scss | 15 ++ 8 files changed, 493 insertions(+) create mode 100644 samples/localities-api-autocomplete-advanced/index.njk create mode 100644 samples/localities-api-autocomplete-advanced/index.ts create mode 100644 samples/localities-api-autocomplete-advanced/localities-api-autocomplete-advanced.json create mode 100644 samples/localities-api-autocomplete-advanced/style.scss create mode 100644 samples/localities-api-autocomplete/index.njk create mode 100644 samples/localities-api-autocomplete/index.ts create mode 100644 samples/localities-api-autocomplete/localities-api-autocomplete.json create mode 100644 samples/localities-api-autocomplete/style.scss diff --git a/samples/localities-api-autocomplete-advanced/index.njk b/samples/localities-api-autocomplete-advanced/index.njk new file mode 100644 index 00000000..ab5b0b9c --- /dev/null +++ b/samples/localities-api-autocomplete-advanced/index.njk @@ -0,0 +1,22 @@ +{% extends '../../src/_includes/layout.njk' %} +{% block html %} + +
+
+ {% svgIcon 'search.svg' %} + + +
    +
    +
    
    +    
    + +{% endblock %} +{% block api %}{% endblock %} \ No newline at end of file diff --git a/samples/localities-api-autocomplete-advanced/index.ts b/samples/localities-api-autocomplete-advanced/index.ts new file mode 100644 index 00000000..eb47f2e3 --- /dev/null +++ b/samples/localities-api-autocomplete-advanced/index.ts @@ -0,0 +1,198 @@ +// [START woosmap_localities_api_autocomplete_advanced] +const componentsRestriction = []; +const woosmap_key = "YOUR_API_KEY"; + +const inputElement = document.getElementById( + "autocomplete-input", +) as HTMLInputElement; +const suggestionsList = document.getElementById( + "suggestions-list", +) as HTMLUListElement; +const clearSearchBtn = document.getElementsByClassName( + "clear-searchButton", +)[0] as HTMLButtonElement; +const responseElement = document.getElementById( + "selected-locality", +) as HTMLElement; + +function init(): void { + if (inputElement && suggestionsList) { + inputElement.addEventListener("input", handleAutocomplete); + inputElement.addEventListener("keydown", (event) => { + if (event.key === "Enter") { + const firstLi = suggestionsList.querySelector("li"); + if (firstLi) { + firstLi.click(); + } + } + }); + } + clearSearchBtn.addEventListener("click", () => { + inputElement.value = ""; + suggestionsList.style.display = "none"; + clearSearchBtn.style.display = "none"; + responseElement.innerHTML = ""; + inputElement.focus(); + }); +} + +function handleAutocomplete(): void { + if (inputElement && suggestionsList) { + const input = inputElement.value; + input.replace('"', '\\"').replace(/^\s+|\s+$/g, ""); + const components: string[] = componentsRestriction.map( + ({ id }) => `country:${id}`, + ); + const componentsArgs: string = components.join("|"); + if (input !== "") { + const debounceRequest = debounce(() => { + autocompleteAddress(input, componentsArgs, woosmap_key) + .then(({ localities }) => displaySuggestions(localities)) + .catch((error) => + console.error("Error autocomplete localities:", error), + ); + }, 0); + debounceRequest(); + } + } +} +function displaySuggestions( + localitiesPredictions: woosmap.map.localities.LocalitiesPredictions[], +) { + if (inputElement && suggestionsList) { + suggestionsList.innerHTML = ""; + if (localitiesPredictions.length > 0) { + localitiesPredictions.forEach((locality) => { + const li = document.createElement("li"); + li.innerHTML = formatPredictionList(locality) ?? ""; + li.addEventListener("click", () => { + inputElement.value = locality.description ?? ""; + suggestionsList.style.display = "none"; + displayLocalitiesResponse(locality); + }); + suggestionsList.appendChild(li); + }); + suggestionsList.style.display = "block"; + clearSearchBtn.style.display = "block"; + } else { + suggestionsList.style.display = "none"; + } + } +} +function formatPredictionList(locality): string { + const prediction = locality; + const predictionClass = "no-viewpoint"; + const matched_substrings = prediction.matched_substrings; + let formatted_name = ""; + if ( + prediction.matched_substrings && + prediction.matched_substrings.description + ) { + formatted_name = bold_matched_substring( + prediction["description"], + matched_substrings.description, + ); + } else { + formatted_name = prediction["description"]; + } + + let html = ""; + html += `
    ${formatted_name}
    `; + + return html; +} +function displayLocalitiesResponse( + selectedLocality: woosmap.map.localities.LocalitiesPredictions, +) { + if (responseElement) { + responseElement.innerHTML = `${JSON.stringify(selectedLocality, null, 2)}`; + } +} +function bold_matched_substring(string: string, matched_substrings: string[]) { + matched_substrings = matched_substrings.reverse(); + for (const substring of matched_substrings) { + const char = string.substring( + substring["offset"], + substring["offset"] + substring["length"], + ); + string = `${string.substring( + 0, + substring["offset"], + )}${char}${string.substring( + substring["offset"] + substring["length"], + )}`; + } + return string; +} +function autocompleteAddress( + input: string, + components: string, + woosmap_key: string, +): Promise { + const args = { + key: woosmap_key, + input, + types: "locality|postal_code|address", + components: "country:fr|country:gb|country:it|country:es|country:de", + }; + + if (components !== "") { + if (args["components"]) { + args["components"] = components; + } + } + return fetch( + `https://api.woosmap.com/localities/autocomplete/?${buildQueryString(args)}`, + ).then((response) => response.json()); +} +function buildQueryString(params: object) { + const queryStringParts = []; + + for (const key in params) { + if (params[key]) { + const value = params[key]; + queryStringParts.push( + `${encodeURIComponent(key)}=${encodeURIComponent(value)}` as never, + ); + } + } + return queryStringParts.join("&"); +} +type FuncType = (...args: any[]) => any; + +function debounce(func: FuncType, wait: number, immediate?: boolean): FuncType { + let timeout: NodeJS.Timeout | null; + + return function (this: any, ...args: any[]) { + const later = () => { + timeout = null; + if (!immediate) { + func.apply(this, args); + } + }; + + const callNow = immediate && !timeout; + + if (timeout) { + clearTimeout(timeout); + } + + timeout = setTimeout(later, wait); + + if (callNow) { + func.apply(this, args); + } + }; +} + +init(); + +declare global { + interface Window { + init: () => void; + } +} +window.init = init; +// [END woosmap_localities_api_autocomplete_advanced] + +export {}; diff --git a/samples/localities-api-autocomplete-advanced/localities-api-autocomplete-advanced.json b/samples/localities-api-autocomplete-advanced/localities-api-autocomplete-advanced.json new file mode 100644 index 00000000..33e3937f --- /dev/null +++ b/samples/localities-api-autocomplete-advanced/localities-api-autocomplete-advanced.json @@ -0,0 +1,12 @@ +{ + "title": "Localities Api - Autocomplete (Country restrictions, address type in suggestions)", + "description": "Localities Autocomplete requests on locality, postal code and address types in UK, FR, IT, SP and DE", + "tag": "localities_api_autocomplete_advanced", + "name": "localities-api-autocomplete-advanced", + "pagination": { + "data": "mode", + "size": 1, + "alias": "mode" + }, + "permalink": "samples/{{ page.fileSlug }}/{{mode}}/{% if mode == 'jsfiddle' %}demo{% else %}index{% endif %}.{{ page.outputFileExtension }}" +} \ No newline at end of file diff --git a/samples/localities-api-autocomplete-advanced/style.scss b/samples/localities-api-autocomplete-advanced/style.scss new file mode 100644 index 00000000..529ddd50 --- /dev/null +++ b/samples/localities-api-autocomplete-advanced/style.scss @@ -0,0 +1,15 @@ +@use 'sass:meta'; // To enable @use via meta.load-css and keep comments in order + +/* [START woosmap_localities_api_autocomplete_advanced] */ +@include meta.load-css("../../shared/scss/_default.scss"); +@include meta.load-css("../../shared/scss/_autocomplete_input.scss"); +@include meta.load-css("../../shared/scss/_autocomplete_list.scss"); + +#selected-locality { + margin-top: 70px; + padding: 10px; +} +.bold { + font-weight: 700; +} +/* [END woosmap_localities_api_autocomplete_advanced] */ \ No newline at end of file diff --git a/samples/localities-api-autocomplete/index.njk b/samples/localities-api-autocomplete/index.njk new file mode 100644 index 00000000..ab5b0b9c --- /dev/null +++ b/samples/localities-api-autocomplete/index.njk @@ -0,0 +1,22 @@ +{% extends '../../src/_includes/layout.njk' %} +{% block html %} + +
    +
    + {% svgIcon 'search.svg' %} + + +
      +
      +
      
      +    
      + +{% endblock %} +{% block api %}{% endblock %} \ No newline at end of file diff --git a/samples/localities-api-autocomplete/index.ts b/samples/localities-api-autocomplete/index.ts new file mode 100644 index 00000000..2b99767d --- /dev/null +++ b/samples/localities-api-autocomplete/index.ts @@ -0,0 +1,197 @@ +// [START woosmap_localities_api_autocomplete] +const componentsRestriction = []; +const woosmap_key = "YOUR_API_KEY"; + +const inputElement = document.getElementById( + "autocomplete-input", +) as HTMLInputElement; +const suggestionsList = document.getElementById( + "suggestions-list", +) as HTMLUListElement; +const clearSearchBtn = document.getElementsByClassName( + "clear-searchButton", +)[0] as HTMLButtonElement; +const responseElement = document.getElementById( + "selected-locality", +) as HTMLElement; + +function init(): void { + if (inputElement && suggestionsList) { + inputElement.addEventListener("input", handleAutocomplete); + inputElement.addEventListener("keydown", (event) => { + if (event.key === "Enter") { + const firstLi = suggestionsList.querySelector("li"); + if (firstLi) { + firstLi.click(); + } + } + }); + } + clearSearchBtn.addEventListener("click", () => { + inputElement.value = ""; + suggestionsList.style.display = "none"; + clearSearchBtn.style.display = "none"; + responseElement.innerHTML = ""; + inputElement.focus(); + }); +} + +function handleAutocomplete(): void { + if (inputElement && suggestionsList) { + const input = inputElement.value; + input.replace('"', '\\"').replace(/^\s+|\s+$/g, ""); + const components: string[] = componentsRestriction.map( + ({ id }) => `country:${id}`, + ); + const componentsArgs: string = components.join("|"); + if (input !== "") { + const debounceRequest = debounce(() => { + autocompleteAddress(input, componentsArgs, woosmap_key) + .then(({ localities }) => displaySuggestions(localities)) + .catch((error) => + console.error("Error autocomplete localities:", error), + ); + }, 0); + debounceRequest(); + } + } +} +function displaySuggestions( + localitiesPredictions: woosmap.map.localities.LocalitiesPredictions[], +) { + if (inputElement && suggestionsList) { + suggestionsList.innerHTML = ""; + if (localitiesPredictions.length > 0) { + localitiesPredictions.forEach((locality) => { + const li = document.createElement("li"); + li.innerHTML = formatPredictionList(locality) ?? ""; + li.addEventListener("click", () => { + inputElement.value = locality.description ?? ""; + suggestionsList.style.display = "none"; + displayLocalitiesResponse(locality); + }); + suggestionsList.appendChild(li); + }); + suggestionsList.style.display = "block"; + clearSearchBtn.style.display = "block"; + } else { + suggestionsList.style.display = "none"; + } + } +} +function formatPredictionList(locality): string { + const prediction = locality; + const predictionClass = "no-viewpoint"; + const matched_substrings = prediction.matched_substrings; + let formatted_name = ""; + if ( + prediction.matched_substrings && + prediction.matched_substrings.description + ) { + formatted_name = bold_matched_substring( + prediction["description"], + matched_substrings.description, + ); + } else { + formatted_name = prediction["description"]; + } + + let html = ""; + html += `
      ${formatted_name}
      `; + + return html; +} +function displayLocalitiesResponse( + selectedLocality: woosmap.map.localities.LocalitiesPredictions, +) { + if (responseElement) { + responseElement.innerHTML = `${JSON.stringify(selectedLocality, null, 2)}`; + } +} +function bold_matched_substring(string: string, matched_substrings: string[]) { + matched_substrings = matched_substrings.reverse(); + for (const substring of matched_substrings) { + const char = string.substring( + substring["offset"], + substring["offset"] + substring["length"], + ); + string = `${string.substring( + 0, + substring["offset"], + )}${char}${string.substring( + substring["offset"] + substring["length"], + )}`; + } + return string; +} +function autocompleteAddress( + input: string, + components: string, + woosmap_key: string, +): Promise { + const args = { + key: woosmap_key, + input, + types: "locality|postal_code", + }; + + if (components !== "") { + if (args["components"]) { + args["components"] = components; + } + } + return fetch( + `https://api.woosmap.com/localities/autocomplete/?${buildQueryString(args)}`, + ).then((response) => response.json()); +} +function buildQueryString(params: object) { + const queryStringParts = []; + + for (const key in params) { + if (params[key]) { + const value = params[key]; + queryStringParts.push( + `${encodeURIComponent(key)}=${encodeURIComponent(value)}` as never, + ); + } + } + return queryStringParts.join("&"); +} +type FuncType = (...args: any[]) => any; + +function debounce(func: FuncType, wait: number, immediate?: boolean): FuncType { + let timeout: NodeJS.Timeout | null; + + return function (this: any, ...args: any[]) { + const later = () => { + timeout = null; + if (!immediate) { + func.apply(this, args); + } + }; + + const callNow = immediate && !timeout; + + if (timeout) { + clearTimeout(timeout); + } + + timeout = setTimeout(later, wait); + + if (callNow) { + func.apply(this, args); + } + }; +} + +init(); + +declare global { + interface Window { + init: () => void; + } +} +window.init = init; +// [END woosmap_localities_api_autocomplete] + +export {}; diff --git a/samples/localities-api-autocomplete/localities-api-autocomplete.json b/samples/localities-api-autocomplete/localities-api-autocomplete.json new file mode 100644 index 00000000..e404d7e7 --- /dev/null +++ b/samples/localities-api-autocomplete/localities-api-autocomplete.json @@ -0,0 +1,12 @@ +{ + "title": "Localities Api - Autocomplete Requests", + "description": "The Localities Autocomplete endpoint provides worldwide suggestions for text-based geographic searches. ", + "tag": "localities_api_autocomplete", + "name": "localities-api-autocomplete", + "pagination": { + "data": "mode", + "size": 1, + "alias": "mode" + }, + "permalink": "samples/{{ page.fileSlug }}/{{mode}}/{% if mode == 'jsfiddle' %}demo{% else %}index{% endif %}.{{ page.outputFileExtension }}" +} \ No newline at end of file diff --git a/samples/localities-api-autocomplete/style.scss b/samples/localities-api-autocomplete/style.scss new file mode 100644 index 00000000..9f1ff4a3 --- /dev/null +++ b/samples/localities-api-autocomplete/style.scss @@ -0,0 +1,15 @@ +@use 'sass:meta'; // To enable @use via meta.load-css and keep comments in order + +/* [START woosmap_localities_api_autocomplete] */ +@include meta.load-css("../../shared/scss/_default.scss"); +@include meta.load-css("../../shared/scss/_autocomplete_input.scss"); +@include meta.load-css("../../shared/scss/_autocomplete_list.scss"); + +#selected-locality { + margin-top: 70px; + padding: 10px; +} +.bold { + font-weight: 700; +} +/* [END woosmap_localities_api_autocomplete] */ \ No newline at end of file From 410be5f40ad2eb98367e0b9b44f2150a85b3b1b7 Mon Sep 17 00:00:00 2001 From: galela Date: Wed, 28 Feb 2024 12:14:41 +0100 Subject: [PATCH 2/5] test: added e2e tests for Autocomplete without map --- e2e/samples/app.spec.ts | 22 ++++++++++++++++------ e2e/utils.ts | 7 +++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/e2e/samples/app.spec.ts b/e2e/samples/app.spec.ts index 617a8f12..b4bda1fb 100644 --- a/e2e/samples/app.spec.ts +++ b/e2e/samples/app.spec.ts @@ -1,10 +1,18 @@ import { test } from "@playwright/test"; -import { waitForWoosmapToLoad, failOnPageError } from "../utils"; +import { + waitForWoosmapToLoad, + waitForAutocompleteFetch, + failOnPageError, +} from "../utils"; import fs from "fs"; export const BROKEN_APP_SAMPLES = [ "store-locator-widget-baidu", // too long to load ]; +export const AUTOCOMPOLETE_WITHOUT_MAP_SAMPLES = [ + "localities-api-autocomplete", + "localities-api-autocomplete-advanced", +]; const samples = fs .readdirSync("samples", { withFileTypes: true }) @@ -24,12 +32,14 @@ test.describe.parallel("sample applications", () => { waitUntil: "networkidle", }); - if (sample === "programmatic-load-button") { - await page.locator("button").click(); + if (AUTOCOMPOLETE_WITHOUT_MAP_SAMPLES.includes(sample)) { + // wait for #suggestions-list li elements to be visible + await waitForAutocompleteFetch(page); + } else { + console.log(sample, "search with map"); + // wait for woosmap.map to be loaded + await waitForWoosmapToLoad(page); } - - // wait for woosmap.map to be loaded - await waitForWoosmapToLoad(page); }); }); }); diff --git a/e2e/utils.ts b/e2e/utils.ts index f6c152fc..42ced39c 100644 --- a/e2e/utils.ts +++ b/e2e/utils.ts @@ -7,6 +7,13 @@ export async function waitForWoosmapToLoad(page: Page): Promise { await page.waitForTimeout(100); } +export async function waitForAutocompleteFetch(page: Page): Promise { + await page.fill("#autocomplete-input", "Paris"); + await page.waitForSelector(`//ul[@id='suggestions-list']/li`, { + state: "visible", + }); +} + export const failOnPageError = (page: Page): void => { page.on("pageerror", (e) => { console.error(e.message); From 9754e821f2ab0c4b1310e5a2e80cc2420c823ffe Mon Sep 17 00:00:00 2001 From: galela Date: Wed, 28 Feb 2024 12:21:18 +0100 Subject: [PATCH 3/5] test: fix typo --- e2e/samples/app.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/samples/app.spec.ts b/e2e/samples/app.spec.ts index b4bda1fb..eed57a6a 100644 --- a/e2e/samples/app.spec.ts +++ b/e2e/samples/app.spec.ts @@ -9,7 +9,7 @@ import fs from "fs"; export const BROKEN_APP_SAMPLES = [ "store-locator-widget-baidu", // too long to load ]; -export const AUTOCOMPOLETE_WITHOUT_MAP_SAMPLES = [ +export const AUTOCOMPLETE_WITHOUT_MAP_SAMPLES = [ "localities-api-autocomplete", "localities-api-autocomplete-advanced", ]; @@ -32,7 +32,7 @@ test.describe.parallel("sample applications", () => { waitUntil: "networkidle", }); - if (AUTOCOMPOLETE_WITHOUT_MAP_SAMPLES.includes(sample)) { + if (AUTOCOMPLETE_WITHOUT_MAP_SAMPLES.includes(sample)) { // wait for #suggestions-list li elements to be visible await waitForAutocompleteFetch(page); } else { From b599dc35de6a4900d0def66ddae6259ea775ecdf Mon Sep 17 00:00:00 2001 From: galela Date: Wed, 28 Feb 2024 12:21:31 +0100 Subject: [PATCH 4/5] test: fix typo --- e2e/samples/app.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/e2e/samples/app.spec.ts b/e2e/samples/app.spec.ts index eed57a6a..a56b6e02 100644 --- a/e2e/samples/app.spec.ts +++ b/e2e/samples/app.spec.ts @@ -36,7 +36,6 @@ test.describe.parallel("sample applications", () => { // wait for #suggestions-list li elements to be visible await waitForAutocompleteFetch(page); } else { - console.log(sample, "search with map"); // wait for woosmap.map to be loaded await waitForWoosmapToLoad(page); } From 988b38b3015d0049b9fd3cdc76cf8289a6707d1e Mon Sep 17 00:00:00 2001 From: shwetadeshmukh Date: Wed, 28 Feb 2024 18:35:42 +0530 Subject: [PATCH 5/5] Fixed - debouce function - minor html fixes --- .../index.njk | 2 +- .../index.ts | 74 +++++++++-------- .../style.scss | 2 +- samples/localities-api-autocomplete/index.njk | 2 +- samples/localities-api-autocomplete/index.ts | 82 ++++++++++--------- .../localities-api-autocomplete/style.scss | 2 +- 6 files changed, 89 insertions(+), 75 deletions(-) diff --git a/samples/localities-api-autocomplete-advanced/index.njk b/samples/localities-api-autocomplete-advanced/index.njk index ab5b0b9c..1f7af6ae 100644 --- a/samples/localities-api-autocomplete-advanced/index.njk +++ b/samples/localities-api-autocomplete-advanced/index.njk @@ -15,7 +15,7 @@
        -
        
        +        
        
             
             
         {% endblock %}
        diff --git a/samples/localities-api-autocomplete-advanced/index.ts b/samples/localities-api-autocomplete-advanced/index.ts
        index eb47f2e3..f6a9a988 100644
        --- a/samples/localities-api-autocomplete-advanced/index.ts
        +++ b/samples/localities-api-autocomplete-advanced/index.ts
        @@ -1,6 +1,9 @@
         // [START woosmap_localities_api_autocomplete_advanced]
         const componentsRestriction = [];
         const woosmap_key = "YOUR_API_KEY";
        +let debouncedAutocomplete: (
        +  ...args: any[]
        +) => Promise;
         
         const inputElement = document.getElementById(
           "autocomplete-input",
        @@ -12,7 +15,7 @@ const clearSearchBtn = document.getElementsByClassName(
           "clear-searchButton",
         )[0] as HTMLButtonElement;
         const responseElement = document.getElementById(
        -  "selected-locality",
        +  "response-container",
         ) as HTMLElement;
         
         function init(): void {
        @@ -34,6 +37,7 @@ function init(): void {
             responseElement.innerHTML = "";
             inputElement.focus();
           });
        +  debouncedAutocomplete = debouncePromise(autocompleteAddress, 0);
         }
         
         function handleAutocomplete(): void {
        @@ -45,14 +49,11 @@ function handleAutocomplete(): void {
             );
             const componentsArgs: string = components.join("|");
             if (input !== "") {
        -      const debounceRequest = debounce(() => {
        -        autocompleteAddress(input, componentsArgs, woosmap_key)
        -          .then(({ localities }) => displaySuggestions(localities))
        -          .catch((error) =>
        -            console.error("Error autocomplete localities:", error),
        -          );
        -      }, 0);
        -      debounceRequest();
        +      debouncedAutocomplete(input, componentsArgs, woosmap_key)
        +        .then(({ localities }) => displaySuggestions(localities))
        +        .catch((error) =>
        +          console.error("Error autocomplete localities:", error),
        +        );
             }
           }
         }
        @@ -158,30 +159,39 @@ function buildQueryString(params: object) {
           }
           return queryStringParts.join("&");
         }
        -type FuncType = (...args: any[]) => any;
        -
        -function debounce(func: FuncType, wait: number, immediate?: boolean): FuncType {
        -  let timeout: NodeJS.Timeout | null;
        -
        -  return function (this: any, ...args: any[]) {
        -    const later = () => {
        -      timeout = null;
        -      if (!immediate) {
        -        func.apply(this, args);
        +type DebouncePromiseFunction = (
        +  ...args: Args
        +) => Promise;
        +
        +function debouncePromise(
        +  fn: (...args: Args) => Promise,
        +  delay: number,
        +): DebouncePromiseFunction {
        +  let timeoutId: ReturnType | null = null;
        +  let latestResolve: ((value: T | PromiseLike) => void) | null = null;
        +  let latestReject: ((reason?: any) => void) | null = null;
        +
        +  return function (...args: Args): Promise {
        +    return new Promise((resolve, reject) => {
        +      if (timeoutId !== null) {
        +        clearTimeout(timeoutId);
               }
        -    };
        -
        -    const callNow = immediate && !timeout;
        -
        -    if (timeout) {
        -      clearTimeout(timeout);
        -    }
        -
        -    timeout = setTimeout(later, wait);
        -
        -    if (callNow) {
        -      func.apply(this, args);
        -    }
        +      latestResolve = resolve;
        +      latestReject = reject;
        +      timeoutId = setTimeout(() => {
        +        fn(...args)
        +          .then((result) => {
        +            if (latestResolve === resolve && latestReject === reject) {
        +              resolve(result);
        +            }
        +          })
        +          .catch((error) => {
        +            if (latestResolve === resolve && latestReject === reject) {
        +              reject(error);
        +            }
        +          });
        +      }, delay);
        +    });
           };
         }
         
        diff --git a/samples/localities-api-autocomplete-advanced/style.scss b/samples/localities-api-autocomplete-advanced/style.scss
        index 529ddd50..aee6cd79 100644
        --- a/samples/localities-api-autocomplete-advanced/style.scss
        +++ b/samples/localities-api-autocomplete-advanced/style.scss
        @@ -5,7 +5,7 @@
         @include meta.load-css("../../shared/scss/_autocomplete_input.scss");
         @include meta.load-css("../../shared/scss/_autocomplete_list.scss");
         
        -#selected-locality {
        +#response-container {
           margin-top: 70px;
           padding: 10px;
         }
        diff --git a/samples/localities-api-autocomplete/index.njk b/samples/localities-api-autocomplete/index.njk
        index ab5b0b9c..1f7af6ae 100644
        --- a/samples/localities-api-autocomplete/index.njk
        +++ b/samples/localities-api-autocomplete/index.njk
        @@ -15,7 +15,7 @@
                     
                     
          -
          
          +        
          
               
               
           {% endblock %}
          diff --git a/samples/localities-api-autocomplete/index.ts b/samples/localities-api-autocomplete/index.ts
          index 2b99767d..d53f1e38 100644
          --- a/samples/localities-api-autocomplete/index.ts
          +++ b/samples/localities-api-autocomplete/index.ts
          @@ -1,6 +1,9 @@
           // [START woosmap_localities_api_autocomplete]
           const componentsRestriction = [];
           const woosmap_key = "YOUR_API_KEY";
          +let debouncedAutocomplete: (
          +  ...args: any[]
          +) => Promise;
           
           const inputElement = document.getElementById(
             "autocomplete-input",
          @@ -12,7 +15,7 @@ const clearSearchBtn = document.getElementsByClassName(
             "clear-searchButton",
           )[0] as HTMLButtonElement;
           const responseElement = document.getElementById(
          -  "selected-locality",
          +  "response-container",
           ) as HTMLElement;
           
           function init(): void {
          @@ -34,6 +37,8 @@ function init(): void {
               responseElement.innerHTML = "";
               inputElement.focus();
             });
          +
          +  debouncedAutocomplete = debouncePromise(autocompleteAddress, 0);
           }
           
           function handleAutocomplete(): void {
          @@ -45,14 +50,11 @@ function handleAutocomplete(): void {
               );
               const componentsArgs: string = components.join("|");
               if (input !== "") {
          -      const debounceRequest = debounce(() => {
          -        autocompleteAddress(input, componentsArgs, woosmap_key)
          -          .then(({ localities }) => displaySuggestions(localities))
          -          .catch((error) =>
          -            console.error("Error autocomplete localities:", error),
          -          );
          -      }, 0);
          -      debounceRequest();
          +      debouncedAutocomplete(input, componentsArgs, woosmap_key)
          +        .then(({ localities }) => displaySuggestions(localities))
          +        .catch((error) =>
          +          console.error("Error autocomplete localities:", error),
          +        );
               }
             }
           }
          @@ -157,41 +159,43 @@ function buildQueryString(params: object) {
             }
             return queryStringParts.join("&");
           }
          -type FuncType = (...args: any[]) => any;
          -
          -function debounce(func: FuncType, wait: number, immediate?: boolean): FuncType {
          -  let timeout: NodeJS.Timeout | null;
          -
          -  return function (this: any, ...args: any[]) {
          -    const later = () => {
          -      timeout = null;
          -      if (!immediate) {
          -        func.apply(this, args);
          +type DebouncePromiseFunction = (
          +  ...args: Args
          +) => Promise;
          +
          +function debouncePromise(
          +  fn: (...args: Args) => Promise,
          +  delay: number,
          +): DebouncePromiseFunction {
          +  let timeoutId: ReturnType | null = null;
          +  let latestResolve: ((value: T | PromiseLike) => void) | null = null;
          +  let latestReject: ((reason?: any) => void) | null = null;
          +
          +  return function (...args: Args): Promise {
          +    return new Promise((resolve, reject) => {
          +      if (timeoutId !== null) {
          +        clearTimeout(timeoutId);
                 }
          -    };
          -
          -    const callNow = immediate && !timeout;
          -
          -    if (timeout) {
          -      clearTimeout(timeout);
          -    }
          -
          -    timeout = setTimeout(later, wait);
          -
          -    if (callNow) {
          -      func.apply(this, args);
          -    }
          +      latestResolve = resolve;
          +      latestReject = reject;
          +      timeoutId = setTimeout(() => {
          +        fn(...args)
          +          .then((result) => {
          +            if (latestResolve === resolve && latestReject === reject) {
          +              resolve(result);
          +            }
          +          })
          +          .catch((error) => {
          +            if (latestResolve === resolve && latestReject === reject) {
          +              reject(error);
          +            }
          +          });
          +      }, delay);
          +    });
             };
           }
           
           init();
          -
          -declare global {
          -  interface Window {
          -    init: () => void;
          -  }
          -}
          -window.init = init;
           // [END woosmap_localities_api_autocomplete]
           
           export {};
          diff --git a/samples/localities-api-autocomplete/style.scss b/samples/localities-api-autocomplete/style.scss
          index 9f1ff4a3..2cc5aa8e 100644
          --- a/samples/localities-api-autocomplete/style.scss
          +++ b/samples/localities-api-autocomplete/style.scss
          @@ -5,7 +5,7 @@
           @include meta.load-css("../../shared/scss/_autocomplete_input.scss");
           @include meta.load-css("../../shared/scss/_autocomplete_list.scss");
           
          -#selected-locality {
          +#response-container {
             margin-top: 70px;
             padding: 10px;
           }