UNPKG

places-autocomplete-js

Version:

A flexible and customisable vanilla JavaScript library leveraging the Google Maps Places (New) Autocomplete API. This library provides a user-friendly way to search for and retrieve detailed address information in any web application.

1 lines 54.4 kB
{"version":3,"file":"places-autocomplete.umd.cjs","sources":["../PlacesAutocomplete.js"],"sourcesContent":["/**\n * Initializes a Places Autocomplete widget.\n * This script dynamically loads the Google Maps JavaScript API, creates the UI elements\n * for the autocomplete input and suggestions list, handles user input with debouncing,\n * fetches suggestions, manages keyboard navigation, and calls user-defined callbacks\n * on place selection or error.\n *\n * @author Alexander Pechkarev <alexpechkarev@gmail.com>\n * @license MIT\n *\n */\n\n/**\n * PacAutocomplete class\n * This class provides a Places Autocomplete widget.\n */\nexport class PlacesAutocomplete {\n  // --- Private Properties (using # or _ prefix by convention) ---\n  #containerId; // Container ID where the autocomplete widget will be rendered.\n  #pacEl;\n  #googleMapsApiKey;\n  #googleMapsApiVersion;\n  #options;\n  #request;\n  #inputElement;\n  #container;\n  #ul;\n  #kbdEscape;\n  #kbdUp;\n  #kbdDown;\n  #allSuggestions = [];\n  #currentSuggestion = -1;\n  #onDataCallback; // For user-provided data callback\n  #onErrorCallback; // For user-provided error callback\n  _debouncedMakeAcRequest; // Declare without initializing here\n  #defaultOptions = {\n    // Default options for the autocomplete widget.\n    autofocus: false, // Automatically focus the input on load.\n    autocomplete: \"off\", // HTML autocomplete attribute for the input.\n    placeholder: \"Start typing your address ...\", // Placeholder text for the input.\n    distance: true, // Show distance in suggestions (requires origin in request).\n    distance_units: \"km\", // Units for distance ('km' or 'miles').\n    label: \"\", // Optional label text above the input.\n    debounce: 100, // Debounce delay (ms) for API requests.\n    clear_input: true, // Clear input button (not implemented in this version).\n    debug: false, // Enable debug mode (not implemented in this version).\n  };\n  #defaultClasses = {\n    // CSS classes for various parts of the widget.\n    section: \"\", // Outer section container.\n    container: \"relative z-10 transform rounded-xl mt-4\", // Main container div.\n    icon_container:\n      \"pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3\", // Container for the search icon.\n    icon: '<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"w-5 h-5\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"11\" cy=\"11\" r=\"8\" /><path d=\"m21 21-4.3-4.3\" /></svg>', // SVG for the search icon.\n    input:\n      \"border-1 w-full rounded-md border-0 shadow-sm bg-gray-100 px-4 py-2.5 pl-10 pr-20 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 sm:text-sm\", // Input field.\n    kbd_container: \"absolute inset-y-0 right-0 flex py-1.5 pr-1.5\", // Container for keyboard hints.\n    kbd_escape:\n      \"inline-flex items-center rounded border border-gray-300 px-1 font-sans text-xs text-gray-500 w-8 mr-1\", // Escape key hint.\n    kbd_up:\n      \"inline-flex items-center justify-center rounded border border-gray-300 px-1 font-sans text-xs text-gray-500 w-6\", // Up arrow key hint.\n    kbd_down:\n      \"inline-flex items-center rounded border border-gray-400 px-1 font-sans text-xs text-gray-500 justify-center w-6\", // Down arrow key hint.\n    kbd_active: \"bg-indigo-500 text-white\", // Class for active keyboard hint.\n    ul: \"absolute z-50 -mb-2 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm divide-y divide-gray-100\", // Suggestions list (ul).\n    li: \"z-50 cursor-default select-none py-2 px-2 lg:px-4 text-gray-900 hover:bg-indigo-500 hover:text-white\", // Suggestion item (li).\n    li_current: \"bg-indigo-500\", // Class for the currently selected suggestion item.\n    li_a: \"block w-full flex justify-between\", // Link element within a suggestion item.\n    li_a_current: \"text-white\", // Class for the link in the currently selected suggestion item.\n    li_div_container: \"flex min-w-0 gap-x-4\", // Container div within the suggestion link.\n    li_div_one: \"min-w-0 flex-auto\", // First inner div (for place name).\n    li_div_one_p: \"text-sm/6\", // Paragraph for the place name.\n    li_div_two: \"shrink-0 flex flex-col items-end min-w-16\", // Second inner div (for distance).\n    li_div_two_p: \"mt-1 text-xs/5\", // Paragraph for the distance.\n    highlight: \"font-bold\", // Class for highlighting matched text in suggestions.\n  };\n  #defaultRequestParams = {\n    // Default parameters for the autocomplete request.\n    input: \"\", // Initial input value (empty).\n    includedRegionCodes: [\"GB\"], // Default region codes to include in suggestions.\n    language: \"en-gb\",\n    region: \"GB\",\n  };\n  #fetchFields = [\"formattedAddress\", \"addressComponents\"];\n  #defaultFetchFields = [\"formattedAddress\", \"addressComponents\"]; // Fields to fetch for the selected place (can be extended).\n\n  /**\n   * Class constructor for PacAutocomplete.\n   * Initializes the autocomplete widget with the provided configuration.\n   * @param {Object} config - Configuration object for the autocomplete widget.\n   * @param {string} config.containerId - ID of the container element for the widget.\n   * @param {string} config.googleMapsApiKey - Google Maps API key.\n   * @param {string} [config.googleMapsApiVersion] - Version of the Google Maps API to use (default: \"weekly\").\n   * @param {Object} [config.options] - Additional options for the widget (e.g., classes, callbacks).\n   * @param {Object} [config.requestParams] - Parameters for the autocomplete request (e.g., input, region).\n   * @param {*} config\n   */\n  constructor(config) {\n    if (!config || !config.containerId || !config.googleMapsApiKey) {\n      throw new Error(\n        \"PacAutocomplete: Missing required configuration (containerId, googleMapsApiKey).\"\n      );\n    }\n\n    this.#containerId = config.containerId; // Store the configuration object\n    this.#pacEl = document.getElementById(config.containerId);\n    if (!this.#pacEl) {\n      throw new Error(\n        `PacAutocomplete: Container element with ID \"${config.containerId}\" not found.`\n      );\n    }\n\n    this.#googleMapsApiKey = config.googleMapsApiKey;\n    this.#googleMapsApiVersion = config.googleMapsApiVersion || \"weekly\";\n\n    // Merge user options with defaults\n    this.#options = {\n      ...this.#defaultOptions, // Default options\n      ...config.options, // User-defined options override defaults\n    };\n    // Ensure classes are deeply merged if user provides partial classes\n    if (config.options && config.options.classes) {\n      this.#options.classes = {\n        ...this.#defaultClasses,\n        ...config.options.classes,\n      };\n    } else {\n      this.#options.classes = this.#defaultClasses; // Use default classes if none provided\n    }\n\n    if (this.#options.debug) {\n      console.log(\"___debug constructor options:\");\n      console.log(this.#options);\n    }\n\n    if (config.fetchFields && Array.isArray(config.fetchFields)) {\n      this._setFetchFields(config.fetchFields); // Set fetch fields from user config\n    }\n\n    // Set default response and error callbacks if not provided\n    this.#onDataCallback =\n      config.onResponse ||\n      ((place) => {\n        console.info(\"---------Default onResponse not provided---------\");\n        console.info(\"Selected Place:\", JSON.stringify(place, null, 2));\n      });\n    this.#onErrorCallback =\n      config.onError ||\n      ((error) => {\n        console.error(\"---------Default onError not provided---------\");\n        console.error(\"PAC Error:\", error);\n      });\n\n    if (config.requestParams && Object.keys(config.requestParams).length > 0) {\n      this.#request = {\n        ...this.#defaultRequestParams,\n        ...config.requestParams,\n      };\n    } else {\n      this.#request = { ...this.#defaultRequestParams }; // Use defaults if no requestParams provided\n    }\n\n    if (this.#options.debug) {\n      console.log(\"___debug constructor requestParams:\", this.#request);\n    }\n\n    this._initialiseDebouncedRequest(); // Initialize the debounced request function\n\n    this._init(); // Underscore prefix for internal initialization method\n  }\n\n  // --- Private Initialization Method ---\n  async _init() {\n    try {\n      // check if google maps api is already loaded\n      if (typeof google === \"undefined\" || !google.maps) {\n        // Load the Google Maps API dynamically\n        await this._loadGoogleMapsApi({\n          key: this.#googleMapsApiKey,\n          v: this.#googleMapsApiVersion,\n        });\n      }\n      this._createPACStructure();\n      await this._initializeAutocomplete();\n    } catch (error) {\n      this.#onErrorCallback(error);\n    }\n  }\n\n  /**\n   * Initializes the debounced request function for fetching autocomplete suggestions.\n   *\n   * Debounced function to fetch autocomplete suggestions from the Google Places API.\n   * Triggered by the 'input' event on the input element.\n   *\n   *\n   */\n  _initialiseDebouncedRequest() {\n    this._debouncedMakeAcRequest = this._debounce(async () => {\n      if (!this.#inputElement || !this.#inputElement.value) {\n        this._reset();\n        if (this.#inputElement)\n          this.#inputElement.setAttribute(\"aria-expanded\", \"false\");\n        return;\n      }\n\n      this.#request.input = this.#inputElement.value;\n\n      try {\n        const { suggestions } =\n          // eslint-disable-next-line no-undef\n          await google.maps.places.AutocompleteSuggestion.fetchAutocompleteSuggestions(\n            this.#request\n          );\n\n        // Display suggestions\n        if (suggestions && suggestions.length > 0) {\n          this.#ul.replaceChildren(\n            ...this._createSuggestionElements(suggestions)\n          );\n          this.#ul.style.display = \"block\";\n          this.#inputElement.setAttribute(\"aria-expanded\", \"true\");\n        } else {\n          // No suggestions found\n          this._reset(); // Clear any old suggestions\n          this.#inputElement.setAttribute(\"aria-expanded\", \"false\");\n          // Optionally display a \"no results\" message in the 'ul'\n        }\n      } catch (error) {\n        this.#onErrorCallback(error);\n        this._reset();\n      }\n    }, this.#options.debounce); // Default debounce to 100ms if not set\n  }\n  /**\n   * Sets the fields to fetch for the selected place.\n   * @param {Array<string>} fields - The fields to fetch.\n   */\n  _setFetchFields(fields) {\n    if (Array.isArray(fields) && fields.length > 0) {\n      this.#fetchFields = [\n        ...new Set([...this.#defaultFetchFields, ...fields]),\n      ].filter((e) => e); // Ensure unique and non-empty fields\n    }\n  }\n\n  /**\n   * Creates a debounced version of a function.\n   * The debounced function delays invoking `func` until after `wait` milliseconds have\n   * elapsed since the last time the debounced function was invoked.\n   * @param {Function} func - The function to debounce.\n   * @param {number} wait - The number of milliseconds to delay.\n   * @returns {Function} The new debounced function.\n   */\n  _debounce(func, wait) {\n    if (this.#options.debug) {\n      console.log(\"___debug debounce function called with wait:\", wait);\n    }\n\n    let timeout = null;\n    return function executedFunction(...args) {\n      const later = () => {\n        timeout = null;\n        func(...args); // Call original function\n      };\n      if (timeout !== null) {\n        clearTimeout(timeout); // Clear the previous timeout\n      }\n      timeout = setTimeout(later, wait ?? 100); // Set the new timeout\n    };\n  }\n  /**\n   * Formats a distance in meters into kilometers or miles.\n   * @param {number | null | undefined} distance - Distance in meters.\n   * @param {'km' | 'miles'} units - The desired output units.\n   * @returns {string | null} Formatted distance string (e.g., \"1.23 km\") or null if input is invalid.\n   */\n  _formatDistance(distance, units) {\n    if (typeof distance !== \"number\" || !this.#options.distance) {\n      return null; // Return null if distance isn't shown or invalid\n    }\n    let value;\n    let unitLabel;\n    if (units === \"km\") {\n      value = (distance / 1000).toFixed(2);\n      unitLabel = \"km\";\n    } else {\n      // Default to miles if not 'km'\n      value = (distance / 1609.34).toFixed(2);\n      unitLabel = \"miles\";\n    }\n    // Avoid showing \".00\"\n    value = value.replace(/\\.00$/, \"\");\n    return `${value} ${unitLabel}`;\n  }\n\n  /**\n   * Dynamically loads the Google Maps JavaScript API using the importLibrary method.\n   * This is the standard approach recommended by Google.\n   * @see https://developers.google.com/maps/documentation/javascript/load-maps-js-api\n   * @param {object} g - Configuration object for the API loader (key, v, libraries, etc.).\n   */\n  async _loadGoogleMapsApi(g) {\n    var h, // Promise tracking API load\n      a, // Script element\n      k, // Loop variable for config keys\n      p = \"The Google Maps JavaScript API\", // Error message prefix\n      c = \"google\", // Global namespace\n      l = \"importLibrary\", // Loader function name\n      q = \"__ib__\", // Internal callback name\n      m = document, // Document reference\n      b = window; // Window reference\n    b = b[c] || (b[c] = {}); // Ensure google namespace exists\n    var d = b.maps || (b.maps = {}), // Ensure google.maps namespace exists\n      r = new Set(), // Set to track requested libraries\n      e = new URLSearchParams(), // URL parameters for the API script\n      u = () =>\n        // Function to initiate API loading (if not already started)\n        h ||\n        // eslint-disable-next-line no-async-promise-executor\n        (h = new Promise(async (f, n) => {\n          // Create script element (done async to potentially wait for nonce)\n          // await (a = m.createElement('script')); // Original Google code had await here, might not be needed\n          a = m.createElement(\"script\"); // Create script tag\n          e.set(\"libraries\", [...r].join(\",\")); // Add accumulated libraries\n          // Add other parameters from the config object 'g'\n          for (k in g)\n            e.set(\n              k.replace(/[A-Z]/g, (t) => \"_\" + t[0].toLowerCase()), // Convert camelCase to snake_case\n              g[k]\n            );\n          e.set(\"callback\", c + \".maps.\" + q); // Set the internal callback function name\n          a.src = `https://maps.${c}apis.com/maps/api/js?` + e; // Construct the API URL\n          d[q] = f; // Assign the promise resolver to the callback name on google.maps\n          // Error handling for script loading failure\n          a.onerror = () =>\n            (h = n(\n              new Error(\n                `${p} could not load. Check your API key and network connection.`\n              )\n            )); // Use onerror for load failures\n          // Nonce for Content Security Policy\n          a.nonce = m.querySelector(\"script[nonce]\")?.nonce || \"\";\n          m.head.append(a); // Append the script to the document head\n        }));\n    // Define or reuse the importLibrary function on google.maps\n    // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n    d[l]\n      ? console.warn(p + \" only loads once. Ignoring:\", g) // Warn if called again\n      : (d[l] = (f, ...n) => r.add(f) && u().then(() => d[l](f, ...n))); // The actual importLibrary implementation\n  }\n\n  // --- UI Creation ---\n  _createPACStructure() {\n    const section = document.createElement(\"section\");\n    section.className = this.#options.classes.section;\n\n    // Main container\n    this.#container = document.createElement(\"div\");\n    this.#container.className = this.#options.classes.container;\n    this.#container.setAttribute(\"id\", this.#containerId + \"-div\");\n    section.appendChild(this.#container);\n\n    // Icon\n    const iconContainer = document.createElement(\"div\");\n    iconContainer.className = this.#options.classes.icon_container;\n    this.#container.appendChild(iconContainer);\n    const icon = document.createElement(\"div\");\n    icon.innerHTML = this.#options.classes.icon;\n    iconContainer.appendChild(icon.firstElementChild); // Append the actual SVG element\n\n    // Input field\n    this.#inputElement = document.createElement(\"input\");\n    this.#inputElement.id = this.#containerId + \"-input\"; // Assign ID for label association\n    this.#inputElement.type = \"text\";\n    this.#inputElement.name = \"search\"; // Consider making name configurable\n    this.#inputElement.placeholder = this.#options.placeholder;\n    this.#inputElement.autocomplete = this.#options.autocomplete;\n    this.#inputElement.className = this.#options.classes.input;\n    this.#inputElement.setAttribute(\"role\", \"combobox\");\n    this.#inputElement.setAttribute(\"aria-autocomplete\", \"list\");\n    this.#inputElement.setAttribute(\"aria-expanded\", \"false\"); // Will be updated dynamically\n    this.#inputElement.setAttribute(\"aria-controls\", \"pacSuggestions\"); // Links to the suggestions list\n    this.#inputElement.setAttribute(\"aria-activedescendant\", \"\"); // Will be updated dynamically\n\n    if (this.#options.autofocus) {\n      this.#inputElement.autofocus = true;\n    }\n    if (this.#options.label) {\n      const label = document.createElement(\"label\");\n      label.htmlFor = this.#inputElement.id; // Correctly link label to input by ID\n      label.textContent = this.#options.label;\n      // Add label classes if needed from opts.classes\n      section.prepend(label); // Append label before input or adjust structure\n    }\n    this.#container.appendChild(this.#inputElement);\n\n    // Keyboard hints container\n    const kbdContainer = document.createElement(\"div\");\n    kbdContainer.className = this.#options.classes.kbd_container;\n    this.#kbdEscape = document.createElement(\"kbd\");\n    this.#kbdEscape.className = this.#options.classes.kbd_escape;\n    this.#kbdEscape.textContent = \"Esc\";\n    kbdContainer.appendChild(this.#kbdEscape);\n    this.#kbdUp = document.createElement(\"kbd\");\n    this.#kbdUp.className = this.#options.classes.kbd_up;\n    this.#kbdUp.innerHTML = \"&#8593;\"; // Up arrow HTML entity\n    kbdContainer.appendChild(this.#kbdUp);\n    this.#kbdDown = document.createElement(\"kbd\");\n    this.#kbdDown.className = this.#options.classes.kbd_down;\n    this.#kbdDown.innerHTML = \"&#8595;\"; // Down arrow HTML entity\n    kbdContainer.appendChild(this.#kbdDown);\n    this.#container.appendChild(kbdContainer);\n\n    // Suggestions list (initially hidden)\n    this.#ul = document.createElement(\"ul\");\n    this.#ul.id = \"pacSuggestions\"; // Must match aria-controls\n    this.#ul.className = this.#options.classes.ul;\n    this.#ul.style.display = \"none\";\n    this.#ul.setAttribute(\"role\", \"listbox\");\n    this.#ul.setAttribute(\"aria-labelledby\", this.#inputElement.id); // Link listbox to input for accessibility\n    this.#container.appendChild(this.#ul);\n\n    this.#pacEl.appendChild(section);\n    section.addEventListener(\"keydown\", this._onKeyDown.bind(this)); // Bind 'this'\n  }\n\n  /**\n   * Attaches event listeners to the input element for handling user input.\n   * This includes debounced input handling, focus/blur events, and keyboard navigation.\n   */\n  _attachedEventListeners() {\n    this.#inputElement.addEventListener(\n      \"input\",\n      this._debouncedMakeAcRequest\n    );\n    // Add focus/blur listeners if needed to manage suggestion visibility\n    this.#inputElement.addEventListener(\"blur\", () => {\n      // Delay hiding suggestions to allow click events on them\n      setTimeout(() => {\n        if (this.#ul && !this.#ul.contains(document.activeElement)) {\n          // Check if focus moved outside suggestions\n          this.#ul.style.display = \"none\";\n          this.#inputElement.setAttribute(\"aria-expanded\", \"false\");\n        }\n      }, 200); // Adjust delay as needed\n    });\n    this.#inputElement.addEventListener(\"focus\", () => {\n      // Potentially show suggestions again if input has value\n      if (this.#inputElement.value && this.#allSuggestions.length > 0) {\n        this.#ul.style.display = \"block\";\n        this.#inputElement.setAttribute(\"aria-expanded\", \"true\");\n      }\n    });\n  }\n\n  _detachEventListeners() {\n    // Remove event listeners, remove elements from DOM\n    if (this.#inputElement) {\n      this.#inputElement.removeEventListener(\n        \"input\",\n        this._debouncedMakeAcRequest\n      );\n      // remove other listeners\n    }\n    if (this.#pacEl && this.#container) {\n      this.#pacEl.removeChild(this.#container.parentElement); // remove the whole section\n    }\n  }\n\n  /**\n   * Initializes the core autocomplete functionality after the API is loaded.\n   * Imports necessary libraries and sets up the input event listener.\n   */\n  async _initializeAutocomplete() {\n    try {\n      // Ensure the 'places' library is available via the dynamic loader\n      // eslint-disable-next-line no-undef\n      await google.maps.importLibrary(\"places\");\n      // console.log('Places library imported successfully.'); // For debugging\n\n      // Initial token generation\n      this._refreshToken();\n\n      // Attach the debounced request function to the input element's 'input' event\n      if (this.#inputElement) {\n        this._attachedEventListeners();\n      } else {\n        this.#onErrorCallback(\n          new Error(\"Input element not found during initialization.\")\n        );\n      }\n    } catch (error) {\n      console.error(\"Error initializing Google Places Autocomplete:\", error);\n      // eslint-disable-next-line no-undef\n      this.#onErrorCallback(\n        new Error(\"Google Maps Places library not available.\")\n      );\n    }\n  }\n\n  /**\n   * Resets the autocomplete input field, clears suggestions, and optionally refreshes the session token.\n   * @param {boolean} [refresh=false] - Whether to refresh the Google Places session token.\n   */\n  _reset(refresh = false, placeData = null) {\n    this.#currentSuggestion = -1;\n\n    /**\n     * If the input element exists and clear_input is false, set its value to the formatted address\n     * from placeData. Otherwise, clear the input value.\n     * This allows the user to keep the input value if they selected a place,\n     * but still allows clearing it if clear_input is true or placeData is not provided.\n     * @type {HTMLInputElement}\n     * @property {string} value - The value of the input element.\n     * @property {boolean} clear_input - Whether to clear the input value after selection.\n     * @property {Object} placeData - The data of the selected place, containing formattedAddress.\n     */\n    if (\n      this.#inputElement &&\n      this.#options.clear_input == false &&\n      placeData &&\n      placeData.formattedAddress\n    ) {\n      this.#inputElement.value = placeData.formattedAddress; // Set input value to formatted address\n    } else if (this.#inputElement) {\n      this.#inputElement.value = \"\";\n    }\n\n    if (this.#inputElement) {\n      this.#inputElement.setAttribute(\"aria-expanded\", \"false\");\n      this.#inputElement.setAttribute(\"aria-activedescendant\", \"\"); // Clear aria-activedescendant\n      this.#inputElement.blur();\n    }\n\n    this.#allSuggestions = [];\n    this.#currentSuggestion = -1;\n    if (this.#ul) {\n      this.#ul.innerHTML = \"\"; // Clear existing suggestions\n      this.#ul.style.display = \"none\";\n    }\n    if (refresh) {\n      this._refreshToken();\n    }\n  }\n  /**\n   * Removes the 'current' highlighting classes from all suggestion list items (li) and their links (a).\n   */\n  _resetLiClasses() {\n    if (!this.#ul) return;\n    Array.from(this.#ul.children).forEach((li) => {\n      this.#options.classes.li_current\n        .split(\" \")\n        .forEach((cl) => li.classList.remove(cl));\n      const link = li.querySelector(\"a\");\n      if (link) {\n        this.#options.classes.li_a_current\n          .split(\" \")\n          .forEach((cl) => link.classList.remove(cl));\n      }\n    });\n  }\n\n  /**\n   * Handles keyboard events (ArrowDown, ArrowUp, Enter, Escape) for navigating\n   * and selecting suggestions or closing the list.\n   * @param {KeyboardEvent} e - The keyboard event object.\n   */\n  _onKeyDown(e) {\n    this._resetLiClasses(); // Reset classes on any key press within the suggestions\n\n    if (e.key === \"Escape\") {\n      e.preventDefault();\n      // Visual feedback for key press\n      this.#options.classes.kbd_active\n        .split(\" \")\n        .forEach((cl) => this.#kbdEscape?.classList.add(cl));\n      setTimeout(\n        () =>\n          this.#options.classes.kbd_active\n            .split(\" \")\n            .forEach((cl) => this.#kbdEscape?.classList.remove(cl)),\n        300\n      );\n\n      this._reset(true); // Reset search input and results, refresh token\n    }\n\n    if (\n      !this.#allSuggestions.length ||\n      !this.#ul ||\n      this.#ul.style.display === \"none\"\n    )\n      return;\n\n    if (e.key === \"ArrowDown\") {\n      e.preventDefault(); // Prevent cursor movement in input\n      this.#currentSuggestion = Math.min(\n        this.#currentSuggestion + 1,\n        this.#allSuggestions.length - 1\n      );\n      if (this.#currentSuggestion < 0) this.#currentSuggestion = 0; // Handle case where it was -1\n\n      const currentLi = this.#ul.children.item(this.#currentSuggestion);\n      if (currentLi) {\n        const currentA = currentLi.querySelector(\"a\");\n        this.#options.classes.li_current\n          .split(\" \")\n          .forEach((cl) => currentLi.classList.add(cl));\n        if (currentA) {\n          this.#options.classes.li_a_current\n            .split(\" \")\n            .forEach((cl) => currentA.classList.add(cl));\n        }\n        currentLi.scrollIntoView({ block: \"nearest\" }); // Ensure visible\n        this.#inputElement.setAttribute(\"aria-activedescendant\", currentLi.id); // Update aria-activedescendant\n      }\n\n      // Visual feedback for key press\n      this.#options.classes.kbd_active\n        .split(\" \")\n        .forEach((cl) => this.#kbdDown?.classList.add(cl));\n      setTimeout(\n        () =>\n          this.#options.classes.kbd_active\n            .split(\" \")\n            .forEach((cl) => this.#kbdDown?.classList.remove(cl)),\n        300\n      );\n    } else if (e.key === \"ArrowUp\") {\n      e.preventDefault(); // Prevent cursor movement in input\n      this.#currentSuggestion = Math.max(this.#currentSuggestion - 1, 0); // Stay at 0 if already there\n\n      const currentLi = this.#ul.children.item(this.#currentSuggestion);\n      if (currentLi) {\n        const currentA = currentLi.querySelector(\"a\");\n        this.#options.classes.li_current\n          .split(\" \")\n          .forEach((cl) => currentLi.classList.add(cl));\n        if (currentA) {\n          this.#options.classes.li_a_current\n            .split(\" \")\n            .forEach((cl) => currentA.classList.add(cl));\n        }\n        currentLi.scrollIntoView({ block: \"nearest\" }); // Ensure visible\n      }\n\n      // Visual feedback for key press\n      this.#options.classes.kbd_active\n        .split(\" \")\n        .forEach((cl) => this.#kbdUp?.classList.add(cl));\n      setTimeout(\n        () =>\n          this.#options.classes.kbd_active\n            .split(\" \")\n            .forEach((cl) => this.#kbdUp?.classList.remove(cl)),\n        300\n      );\n    } else if (e.key === \"Enter\") {\n      e.preventDefault(); // Prevent form submission if applicable\n      if (\n        this.#currentSuggestion >= 0 &&\n        this.#currentSuggestion < this.#allSuggestions.length\n      ) {\n        this._onPlaceSelected(\n          this.#allSuggestions[this.#currentSuggestion].place\n        );\n        // Reset is handled within onPlaceSelected via reset(true)\n      }\n    }\n  }\n\n  /**\n   * Creates an array of list item (LI) elements for the suggestions dropdown.\n   * Each LI contains a link (A) with the place prediction details and distance.\n   * Handles highlighting the matched parts of the suggestion text.\n   * @param {Array<google.maps.places.AutocompleteSuggestion>} suggestions - Array of suggestion objects from the API.\n   * @returns {Array<HTMLLIElement>} An array of LI elements to be added to the suggestions UL.\n   */\n  _createSuggestionElements(suggestions) {\n    this.#allSuggestions = []; // Reset before populating\n    return suggestions.map((suggestion, index) => {\n      this.#allSuggestions.push({\n        id: index + 1,\n        description: suggestion.placePrediction.toString(),\n        place: suggestion.placePrediction.toPlace(),\n      });\n\n      // create div elements\n      const divContainer = document.createElement(\"div\");\n      divContainer.className = this.#options.classes.li_div_container; // flex min-w-0 gap-x-4\n      // create inner div element - place name\n      const divInner = document.createElement(\"div\");\n      divInner.className = this.#options.classes.li_div_one; // min-w-0 flex-auto\n      divContainer.appendChild(divInner);\n      // create p element - place name\n      const p = document.createElement(\"p\");\n      p.className = this.#options.classes.li_div_one_p; // text-sm/6\n\n      // get prediction text\n      const predictionText = suggestion.placePrediction.text;\n      const originalText = predictionText.text;\n      // Array of objects with startOffset, endOffset\n      const matches = predictionText.matches;\n\n      //Highlighting Logic\n      let lastIndex = 0;\n\n      // Sort matches just in case they aren't ordered (though they usually are)\n      matches.sort((a, b) => a.startOffset - b.startOffset);\n\n      // 1. Create the outer span\n      const outerSpan = document.createElement(\"span\");\n\n      // 2. Create the inner span for the bold part\n      const innerSpan = document.createElement(\"span\");\n      innerSpan.classList = this.#options.classes.highlight ?? \"font-bold\"; // Use the highlight class from options\n\n      for (const match of matches) {\n        // Append text before the current match\n        outerSpan.textContent += originalText.substring(\n          lastIndex,\n          match.startOffset\n        );\n\n        // Append the highlighted match segment\n        if (match.startOffset > 0) {\n          // check previous charter is space\n          const prevChar = originalText.charAt(match.startOffset - 1);\n          if (prevChar == \" \") {\n            innerSpan.textContent += \" \";\n          }\n        }\n        innerSpan.textContent += originalText.substring(\n          match.startOffset,\n          match.endOffset\n        );\n\n        // Update the last index processed\n        lastIndex = match.endOffset;\n      }\n\n      // 3. Create a text node for the remaining text\n      const remainingText = document.createTextNode(\n        originalText.substring(lastIndex)\n      );\n\n      // 4. Append the inner span and the text node to the outer span\n      outerSpan.appendChild(innerSpan);\n      outerSpan.appendChild(remainingText);\n\n      // 5. Append the outer span to the paragraph element\n      p.appendChild(outerSpan);\n\n      divInner.appendChild(p);\n      divContainer.appendChild(divInner);\n\n      // create inner div element - distance\n      const divInner2 = document.createElement(\"div\");\n      divInner2.className = this.#options.classes.li_div_two; //'shrink-0 flex flex-col items-end min-w-16';\n      divContainer.appendChild(divInner2);\n      // create p element - distance\n      const p2 = document.createElement(\"p\");\n      p2.className = this.#options.classes.li_div_two_p; //'mt-1 text-xs/5 ';\n      p2.textContent = this._formatDistance(\n        suggestion.placePrediction.distanceMeters,\n        this.#options.distance_units ?? \"km\"\n      );\n      divInner2.appendChild(p2);\n\n      // // create a link element\n      const a = document.createElement(\"a\");\n      a.href = \"javascript:void(0)\";\n      a.tabIndex = index + 1;\n      a.className = this.#options.classes.li_a; // block w-full flex justify-between\n\n      a.addEventListener(\"click\", () => {\n        this._onPlaceSelected(suggestion.placePrediction.toPlace());\n      });\n\n      // ...\n      // a.addEventListener(\"click\", () => {\n      //   this._fetchPlaceDetails(prediction.place_id);\n      // });\n      a.appendChild(divContainer);\n      a.appendChild(divInner2);\n\n      const li = document.createElement(\"li\");\n      li.id = `option-${index + 1}`;\n      li.className = this.#options.classes.li;\n\n      li.appendChild(a);\n      return li;\n    });\n  }\n\n  /**\n   * Handles the selection of a place. Fetches required fields\n   * (displayName, formattedAddress, addressComponents) and calls the\n   * user-defined `onPacData` callback.\n   * @param {google.maps.places.Place} place - The selected Place object.\n   */\n  async _onPlaceSelected(place) {\n    let data = null;\n    try {\n      // Fetch necessary details for the selected place\n      await place.fetchFields({\n        fields: this.#fetchFields, //[\"displayName\", \"formattedAddress\", \"addressComponents\"], // Add more fields as needed\n      });\n      // Call the user-provided callback with the place data\n      // eslint-disable-next-line no-undef\n      data = place.toJSON(); // Convert to plain JSON object\n      this.#onDataCallback(data); // Convert to plain JSON object for the callback\n    } catch (error) {\n      console.error(\"Error fetching place details:\", error);\n      // eslint-disable-next-line no-undef\n      this.#onErrorCallback(error);\n    } finally {\n      // Reset the input and suggestions regardless of success/error\n      this._reset(true, data); // Refresh token after selection\n    }\n  }\n\n  /**\n   * Creates a new Google Places Autocomplete Session Token.\n   * This should be called before starting a new series of autocomplete requests.\n   */\n  _refreshToken() {\n    try {\n      // eslint-disable-next-line no-undef\n      this.#request.sessionToken =\n        new google.maps.places.AutocompleteSessionToken();\n    } catch (error) {\n      console.error(\"Error creating session token:\", error);\n      // eslint-disable-next-line no-undef\n      this.#onErrorCallback(error);\n    }\n  }\n\n  /**\n   * Sets the fields to be fetched for the selected place.\n   * This allows you to specify which details you want to retrieve\n   * when a place is selected, such as displayName, formattedAddress,\n   * addressComponents, etc.\n   * @param {Array<string>} fields - Array of field names to fetch.\n   */\n  setFetchFields(fields) {\n    this._setFetchFields(fields);\n  }\n  /**\n   * Gets the current fields that will be fetched for the selected place.\n   * This is useful for understanding what details will be available\n   * when a place is selected, such as displayName, formattedAddress,\n   * addressComponents, etc.\n   * @returns {Array<string>} The current fetch fields.\n   */\n  getFetchFields() {\n    return this.#fetchFields;\n  }\n\n  /**\n   * Sets the request parameters for the Places Autocomplete instance.\n   * This method allows you to update the request parameters dynamically,\n   * such as the input value, region codes, language, etc.\n   * It merges the provided parameters with the default request parameters,\n   * allowing you to override specific values while keeping the defaults intact.\n   * This is useful for updating the search criteria without needing to recreate the instance.\n   * @param {*} params\n   */\n  setRequestParams(params) {\n    if (\n      typeof params === \"object\" &&\n      !Array.isArray(params) &&\n      params !== null\n    ) {\n      // if params.input is provided, set it to the input element\n      if (params.input && typeof params.input === \"string\") {\n        this.#inputElement.value = params.input; // Set input value if provided\n      }\n      // Merge provided params with default request parameters\n      // This allows overriding specific request parameters while keeping defaults\n      this.#request = {\n        ...this.#defaultRequestParams,\n        ...params,\n      };\n    }\n  }\n\n  /**\n   * Returns the current request parameters used by the Places Autocomplete instance.\n   * This includes the input value, included region codes, language, and other settings.\n   * It is useful for debugging or when you need to know the current search criteria.\n   * @returns {Object} The current request parameters.\n   */\n  getRequestParams() {\n    return this.#request;\n  }\n\n  /**\n   * Sets the options for the Places Autocomplete instance.\n   * This method allows you to change the appearance and behavior of the autocomplete widget,\n   * such as classes, placeholder text, debounce time, etc.\n   * It merges the provided options with the default options, allowing you to override specific values\n   * while keeping the defaults intact.\n   * This is useful for updating the widget's configuration without needing to recreate the instance.\n   * @param {*} options\n   */\n  setOptions(options) {\n    if (\n      typeof options === \"object\" &&\n      !Array.isArray(options) &&\n      options !== null\n    ) {\n      this._detachEventListeners(); // Detach event listeners\n\n      // // Ensure classes are deeply merged if user provides partial classes\n      const tmpClasses = options.classes || {};\n      delete options.classes; // Remove classes from options to avoid overwriting\n\n      // Merge provided options with default options\n      // This allows overriding specific options while keeping defaults\n      this.#options = {\n        ...this.#defaultOptions,\n        ...options,\n      };\n\n      // Merge classes with defaults\n      // This allows overriding specific classes while keeping defaults\n      if (\n        tmpClasses &&\n        typeof tmpClasses === \"object\" &&\n        Object.keys(tmpClasses).length > 0\n      ) {\n        this.#options.classes = {\n          ...this.#defaultClasses,\n          ...tmpClasses,\n        };\n      } else {\n        this.#options.classes = { ...this.#defaultClasses }; // Use defaults if no classes provided\n      }\n\n      this._initialiseDebouncedRequest(); // Reinitialize the debounced request function\n      this._createPACStructure(); // Recreate the structure with new options\n      // Reattach the input event listener to the input element\n      // This is necessary to ensure the new input element is ready for events\n      if (this.#inputElement) {\n        this._attachedEventListeners();\n      } // Reinitialize the autocomplete functionality\n    }\n  }\n\n  /**\n   * Gets the current options used by the Places Autocomplete instance.\n   * @returns {Object} The current options.\n   */\n  getOptions() {\n    /**\n     * Returns the current options used by the Places Autocomplete instance.\n     * This includes classes, placeholder text, debounce time, and other settings.\n     * It is useful for debugging or when you need to know the current configuration of the widget.\n     * @returns {Object} The current options.\n     */\n    return this.#options;\n  }\n\n  /**\n   * Clears the autocomplete input field and suggestions list.\n   * This method resets the state of the autocomplete widget,\n   * allowing the user to start a new search without any previous input or suggestions.\n   * It also refreshes the Google Places session token to ensure a new session.\n   * @returns {void}\n   */\n  clear() {\n    this._reset(true);\n  }\n\n  /**\n   * Destroys the PacAutocomplete instance.\n   * Removes event listeners, clears the DOM, and nullifies properties.\n   * This method should be called when the autocomplete widget is no longer needed.\n   * It ensures that all resources are cleaned up to prevent memory leaks.\n   * @returns {void}\n   */\n  destroy() {\n    // Remove event listeners, remove elements from DOM\n    this._detachEventListeners(); // Detach event listeners\n    // Nullify properties\n    this.#containerId = null;\n    this.#pacEl = null;\n    this.#googleMapsApiKey = null;\n    this.#googleMapsApiVersion = null;\n    this.#options = null;\n    this.#request = null;\n    this.#inputElement = null;\n    this.#container = null;\n    this.#ul = null;\n    this.#kbdEscape = null;\n    this.#kbdUp = null;\n    this.#kbdDown = null;\n    this.#allSuggestions = null;\n    this.#currentSuggestion = -1;\n    this.#onDataCallback = null;\n    this.#onErrorCallback = null;\n    this._debouncedMakeAcRequest = null;\n    console.log(\"PacAutocomplete instance destroyed.\");\n  }\n}\n"],"names":["PlacesAutocomplete","#containerId","#pacEl","#googleMapsApiKey","#googleMapsApiVersion","#options","#request","#inputElement","#container","#ul","#kbdEscape","#kbdUp","#kbdDown","#allSuggestions","#currentSuggestion","#onDataCallback","#onErrorCallback","#defaultOptions","#defaultClasses","#defaultRequestParams","#fetchFields","#defaultFetchFields","config","place","error","suggestions","fields","e","func","wait","timeout","args","later","distance","units","value","unitLabel","g","h","a","k","p","c","l","q","m","b","d","r","u","f","n","t","section","iconContainer","icon","label","kbdContainer","refresh","placeData","li","cl","link","currentLi","currentA","suggestion","index","divContainer","divInner","predictionText","originalText","matches","lastIndex","outerSpan","innerSpan","match","remainingText","divInner2","p2","data","params","options","tmpClasses"],"mappings":"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"}