places-autocomplete-js
Version:
Google Maps Platform Awards 2025 Winner - A flexible, accessible, and secure vanilla JavaScript library that brings the power of Google's address search to your application in minutes.
1 lines • 60 kB
Source Map (JSON)
{"version":3,"file":"places-autocomplete.umd.cjs","sources":["../PlacesAutocomplete.js"],"sourcesContent":["import \"./places-autocomplete.css\";\n\n/**\n * A flexible and customisable Places Autocomplete widget powered by the Google Maps Places API.\n *\n * Features:\n * - Dynamic Google Maps API loading\n * - Debounced input for optimized API requests\n * - Keyboard navigation (Arrow keys, Enter, Escape)\n * - Session token management for cost-effective API usage\n * - Customizable styling and behavior\n *\n * @author Alexander Pechkarev <alexpechkarev@gmail.com>\n * @license MIT\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: \"pac-container\", // \"relative z-10 transform rounded-xl mt-4\", // Main container div.\n icon_container: \"pac-icon-container\", //\"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=\"pac-w-5 pac-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: \"pac-input\", //\"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: \"pac-kbd-container\", //\"absolute inset-y-0 right-0 flex py-1.5 pr-1.5\", // Container for keyboard hints.\n kbd_escape: \"pac-kbd-escape\", //\"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: \"pac-kbd-up\", //\"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: \"pac-kbd-down\", //\"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: \"pac-kbd-active\", //\"bg-indigo-500 text-white\", // Class for active keyboard hint.\n ul: \"pac-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: \"pac-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: \"pac-li-current\", //\"bg-indigo-500\", // Class for the currently selected suggestion item.\n li_a: \"pac-li-a\", //\"block w-full flex justify-between\", // Link element within a suggestion item.\n li_button: \"pac-li-button\", //\"block w-full flex justify-between\", // Link element within a suggestion item.\n li_button_current: \"pac-li-button-current\", //\"text-white\", // Class for the link in the currently selected suggestion item.\n li_div_container: \"pac-li-div-container\", //\"flex min-w-0 gap-x-4\", // Container div within the suggestion link.\n li_div_p_container: \"pac-li-div-p-container\",\n li_div_one: \"pac-li-div-one\", //\"min-w-0 flex-auto\", // First inner div (for place name).\n li_div_one_p: \"pac-li-div-one-p\", //\"text-sm/6\", // Paragraph for the place name.\n map_icon_svg: \"pac-map-icon-svg\",\n map_pin_icon:\n '<path d=\"M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0\"/><circle cx=\"12\" cy=\"10\" r=\"3\"/>',\n li_div_one_p_secondaryText: \"pac-li-div-one-p-secondaryText\",\n li_div_two: \"pac-li-div-two\", //\"shrink-0 flex flex-col items-end min-w-16\", // Second inner div (for distance).\n li_div_two_p: \"pac-li-div-two-p\", //\"mt-1 text-xs/5\", // Paragraph for the distance.\n highlight: \"pac-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 * This function is called on initialization and when options are updated.\n *\n * @private\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 const suggestionElements = await Promise.all(\n this._createSuggestionElements(suggestions)\n );\n this.#ul.replaceChildren(...suggestionElements);\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 *\n * @private\n * @param {Function} func - The function to debounce\n * @param {number} wait - Delay in milliseconds\n * @returns {Function} Debounced function\n */\n _debounce(func, wait) {\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 value from meters to the specified unit.\n *\n * @private\n * @param {number|null|undefined} distance - Distance in meters\n * @param {('km'|'miles')} units - Target unit for conversion\n * @returns {string|null} Formatted distance string or null if 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(\"input\", this.#debouncedMakeAcRequest);\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(\"button\");\n if (link) {\n this.#options.classes.li_button_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 currentButton = currentLi.querySelector(\"button\");\n this.#options.classes.li_current\n .split(\" \")\n .forEach((cl) => currentLi.classList.add(cl));\n if (currentButton) {\n this.#options.classes.li_button_current\n .split(\" \")\n .forEach((cl) => currentButton.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 currentButton = currentLi.querySelector(\"button\");\n this.#options.classes.li_current\n .split(\" \")\n .forEach((cl) => currentLi.classList.add(cl));\n if (currentButton) {\n this.#options.classes.li_button_current\n .split(\" \")\n .forEach((cl) => currentButton.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 // Helper function to find a specific address component\n _getAddressComponent(response, type) {\n return (\n response.addressComponents?.find((c) => c.types.includes(type))\n ?.longText || \"\"\n );\n }\n\n // Helper function to get secondary text from address components\n _getSecondaryText(place) {\n const locality = this._getAddressComponent(place, \"locality\");\n const adminArea = this._getAddressComponent(\n place,\n \"administrative_area_level_1\"\n );\n const postalCode = this._getAddressComponent(place, \"postal_code\");\n const country = this._getAddressComponent(place, \"country\");\n let components = [locality, adminArea, country, postalCode].filter(Boolean);\n return components.join(\", \");\n }\n\n /**\n * Creates a button element for a suggestion item.\n * @returns {HTMLButtonElement} The created button element.\n */\n _createButtonElement(index) {\n const button = document.createElement(\"button\");\n button.tabIndex = index + 1;\n button.className = this.#options.classes.li_button; // block w-full flex justify-between\n return button;\n }\n\n /**\n * Creates a div container for suggestion item.\n * @returns {HTMLDivElement} The created div container element.\n */\n _createDivElement(className) {\n // create div elements pac-li-div-container\n const divContainer = document.createElement(\"div\");\n if (className) {\n divContainer.className = className;\n }\n return divContainer;\n }\n\n /**\n * Creates an SVG element representing a map pin icon.\n * @returns {SVGSVGElement} The created SVG element.\n */\n _createMapPinIconElement() {\n const svgIcon = document.createElementNS(\n \"http://www.w3.org/2000/svg\",\n \"svg\"\n );\n svgIcon.setAttribute(\"xmlns\", \"http://www.w3.org/2000/svg\");\n svgIcon.setAttribute(\"width\", \"24\");\n svgIcon.setAttribute(\"height\", \"24\");\n svgIcon.setAttribute(\"viewBox\", \"0 0 24 24\");\n svgIcon.setAttribute(\"fill\", \"none\");\n svgIcon.setAttribute(\"stroke\", \"currentColor\");\n svgIcon.setAttribute(\"stroke-width\", \"2\");\n svgIcon.setAttribute(\"stroke-linecap\", \"round\");\n svgIcon.setAttribute(\"stroke-linejoin\", \"round\");\n svgIcon.setAttribute(\"class\", this.#options.classes.map_icon_svg);\n // insert map pin icon path\n svgIcon.innerHTML = this.#options.classes.map_pin_icon;\n return svgIcon;\n }\n\n /**\n * Creates a paragraph (P) element with an optional class name.\n * @param {string} className\n * @returns {HTMLParagraphElement}\n */\n _createPElement(className) {\n const p = document.createElement(\"p\");\n if (className) {\n p.className = className;\n }\n return p;\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 // Map suggestions to LI elements\n return suggestions.map(async (suggestion, index) => {\n // Fetch place details for each suggestion\n let place = suggestion.placePrediction.toPlace();\n // Fetch address components for the place\n await place.fetchFields({ fields: [\"addressComponents\"] });\n\n const li = document.createElement(\"li\");\n li.id = `option-${index + 1}`;\n li.className = this.#options.classes.li;\n\n // create button element\n const button = this._createButtonElement(index);\n // Add click event listener to handle selection\n button.addEventListener(\"click\", (e) => {\n e.preventDefault();\n this._onPlaceSelected(suggestion.placePrediction.toPlace());\n });\n\n // create div elements pac-li-div-container\n const divContainer = this._createDivElement(\n this.#options.classes.li_div_container\n );\n\n // create li div one element - place name\n const liDivOne = this._createDivElement(this.#options.classes.li_div_one);\n const liDivTwo = this._createDivElement(this.#options.classes.li_div_two);\n // create map pin icon element\n const mapPinIcon = this._createMapPinIconElement();\n // append map pin icon to liDivOne\n liDivOne.appendChild(mapPinIcon);\n\n // create div p container\n const divPContainer = this._createDivElement(\n this.#options.classes.li_div_p_container\n );\n // append div p container to liDivOne\n liDivOne.appendChild(divPContainer);\n\n // create p element - place name\n const pOne = this._createPElement(this.#options.classes.li_div_one_p);\n // create p element - secondary text\n const pTwo = this._createPElement(\n this.#options.classes.li_div_one_p_secondaryText\n );\n // create p element - distance\n const pThree = this._createPElement(this.#options.classes.li_div_two_p);\n pThree.textContent = this._formatDistance(\n suggestion.placePrediction.distanceMeters,\n this.#options.distance_units ?? \"km\"\n );\n // append p element to div p container\n divPContainer.appendChild(pOne);\n divPContainer.appendChild(pTwo);\n liDivTwo.appendChild(pThree);\n\n // append liDivOne to divContainer\n divContainer.appendChild(liDivOne);\n divContainer.appendChild(liDivTwo);\n // append divContainer to button\n button.appendChild(divContainer);\n // append button to li\n li.appendChild(button);\n\n // get prediction text\n const predictionText = suggestion.placePrediction.mainText;\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 pOne.appendChild(outerSpan);\n\n // set secondary text\n const secondaryText = this._getSecondaryText(place);\n\n if (secondaryText) {\n pTwo.textContent = secondaryText;\n }\n\n this.#allSuggestions.push({\n id: index + 1,\n place: place,\n mainText: suggestion.placePrediction.mainText.text,\n secondaryText: this._getSecondaryText(place),\n description: suggestion.placePrediction.toString(),\n });\n\n li.appendChild(button);\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 }\n}\n"],"names":["PlacesAutocomplete","#containerId","#pacEl","#googleMapsApiKey","#googleMapsApiVersion","#options","#request","#inputElement","#container","#ul","#kbdEscape","#kbdUp","#kbdDown","#allSuggestions","#currentSuggestion","#onDataCallback","#onErrorCallback","#debouncedMakeAcRequest","#defaultOptions","#defaultClasses","#defaultRequestParams","#fetchFields","#defaultFetchFields","config","place","error","suggestions","suggestionElements","fields","func","wait","timeout","args","later","distance","units","value","unitLabel","g","h","a","k","p","c","l","q","m","b","d","r","e","u","f","n","t","section","iconContainer","icon","label","kbdContainer","refresh","placeData","li","cl","link","currentLi","currentButton","response","type","locality","adminArea","postalCode","country","index","button","className","divContainer","svgIcon","suggestion","liDivOne","liDivTwo","mapPinIcon","divPContainer","pOne","pTwo","pThree","predictionText","originalText","matches","lastIndex","outerSpan","innerSpan","match","remainingText","secondaryText","data","params","options","tmpClasses"],"mappings":"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"}