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
Source Map (JSON)
{"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 = \"↑\"; // 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 = \"↓\"; // 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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"}