places-autocomplete-svelte 2.2.14 → 2.2.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,31 +3,28 @@
3
3
  [![npm version](https://badge.fury.io/js/places-autocomplete-svelte.svg)](https://badge.fury.io/js/places-autocomplete-svelte)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- A flexible and customizable [Svelte](https://kit.svelte.dev) component leveraging the [Google Maps Places Autocomplete API (New)](https://developers.google.com/maps/documentation/javascript/place-autocomplete-overview) to provide a user-friendly way to search for and retrieve detailed address information within your [SvelteKit](https://kit.svelte.dev) applications.
7
-
8
- This component handles API loading, session tokens, fetching suggestions, and requesting place details, allowing you to focus on integrating the results into your application. Includes features like debounced input, highlighting of matched suggestions, extensive customization via CSS classes, and full TypeScript support.
6
+ A flexible, accessible, and secure [Svelte](https://kit.svelte.dev) component leveraging the [Google Maps Places Autocomplete API (New)](https://developers.google.com/maps/documentation/javascript/place-autocomplete-overview) to provide a user-friendly way to search for and retrieve detailed address information.
9
7
 
8
+ This component handles API loading, session tokens, fetching suggestions, and requesting place details, allowing you to focus on integrating the results into your application. It is built with accessibility and security in mind.
10
9
 
11
10
  ## Available: Standalone JavaScript Library
12
11
 
13
12
  Need this functionality for a non-Svelte project? Check out our companion vanilla JavaScript library, `places-autocomplete-js`, which offers the same core Google Places (New) Autocomplete features.
14
13
  [View `places-autocomplete-js` on GitHub](https://github.com/alexpechkarev/places-autocomplete-js)
15
14
 
16
-
17
15
  ## Features
18
16
 
19
- * Integrates with the modern **"Google Maps Places Autocomplete API (New)**.
17
+ * Integrates with the modern **Google Maps Places Autocomplete API (New)**.
18
+ * **Highly Accessible:** Follows WAI-ARIA patterns for comboboxes, with full keyboard navigation and screen reader support.
19
+ * **Secure:** Safely renders suggestions to protect against XSS attacks.
20
20
  * Automatically handles **session tokens** for cost management per Google's guidelines.
21
21
  * **Debounced Input:** Limits API calls while the user is typing (configurable).
22
- * **Suggestion Highlighting:** Automatically highlights the portion of text matching the user's input in the suggestions list.
23
- * **Imperative API:** Exposes `clear()`, `focus()`, and `getRequestParams()` methods for direct control over the component.
24
- * **Customizable Styling:** Easily override default styles or apply your own using the `options.classes` prop. Built with [Tailwind CSS](https://tailwindcss.com/) utility classes by default.
22
+ * **Suggestion Highlighting:** Automatically highlights the portion of text matching the user's input.
23
+ * **Imperative API:** Exposes `clear()`, `focus()`, and `getRequestParams()` methods for direct control.
24
+ * **Customisable Styling:** Easily override default styles using the `options.classes` prop.
25
25
  * **TypeScript Support:** Fully written in TypeScript with included type definitions.
26
26
  * **Event Handling:** Provides `onResponse` and `onError` callbacks.
27
- * **Configurable:** Control API parameters (`requestParams`), requested data fields (`fetchFields`), and component behavior/appearance (`options`).
28
- * **Prop Validation:** Sensible defaults and validation for configuration props.
29
-
30
-
27
+ * **Configurable:** Control API parameters (`requestParams`), requested data fields (`fetchFields`), and component behavior (`options`).
31
28
 
32
29
  ## Demo
33
30
 
@@ -38,21 +35,13 @@ See a live demo of the component in action: [Basic Example](https://places-autoc
38
35
  [Customise request parameters](https://places-autocomplete-demo.pages.dev/examples/customise-request-parameters) - construct a `requestParams` object and control various aspects of the search, including language, region, and more.
39
36
 
40
37
  [Retain Input Value After Selection](https://places-autocomplete-demo.pages.dev/examples/retain-input-value) -
41
- This example demonstrates how to configure the Places (New) Autocomplete Svelte component to keep the selected address visible in the input field after a suggestion is chosen. It utilises the `options.clear_input = false` setting.
42
-
38
+ This example demonstrates how to configure the component to keep the selected address visible in the input field after a suggestion is chosen.
43
39
 
44
40
  <img src="places-autocomplete-svelte.gif" alt="A video demonstrating the Places Autocomplete Svelte component in action, showing address suggestions and selection.">
45
41
 
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
42
  ## Requirements
54
43
 
55
- - **Google Maps API Key** with the Google Maps Places API (New) enabled. Refer to [Use API Keys](https://developers.google.com/maps/documentation/javascript/get-api-key) for detailed instructions.
44
+ * **Google Maps API Key** with the "Places API" enabled. Refer to [Use API Keys](https://developers.google.com/maps/documentation/javascript/get-api-key) for detailed instructions.
56
45
 
57
46
  ## Installation
58
47
 
@@ -62,25 +51,44 @@ npm install places-autocomplete-svelte
62
51
  yarn add places-autocomplete-svelte
63
52
  ```
64
53
 
54
+ ## Security
55
+
56
+ ### API Key Security
57
+
58
+ Your Google Maps API Key is a sensitive credential. To prevent unauthorised use and unexpected charges, you **must** restrict it.
59
+
60
+ 1. Go to the [Google Cloud Console](https://console.cloud.google.com/google/maps-apis/credentials).
61
+ 2. Select your API key.
62
+ 3. Under **Application restrictions**, select **HTTP referrers (web sites)** and add your application's domain(s) (e.g., `your-domain.com/*`).
63
+ 4. Under **API restrictions**, select **Restrict key** and choose only the **Places API**.
65
64
 
65
+ ### XSS Protection
66
+
67
+ This component is designed to be secure out-of-the-box. It safely renders user-input and API responses to prevent Cross-Site Scripting (XSS) vulnerabilities.
68
+
69
+ ## Accessibility
70
+
71
+ This component is built to be accessible and follows the [WAI-ARIA Authoring Practices for a Combobox](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/).
72
+
73
+ * **Keyboard Navigation:** Users can navigate suggestions using `ArrowUp`, `ArrowDown`, select with `Enter`, and close the list with `Escape`.
74
+ * **Screen Reader Support:** Uses `role="combobox"`, `aria-autocomplete`, `aria-expanded`, and `aria-activedescendant` to provide a clear experience for screen reader users.
75
+ * **Focus Management:** Focus remains on the input field while navigating the suggestion list, creating a predictable experience.
66
76
 
67
77
  ## Basic Usage
68
78
 
69
- 1. Replace `'___YOUR_API_KEY___'` with your actual **Google Maps API Key**.
70
- 2. Use the `onResponse` callback to **handle the response**.
79
+ 1. Replace `___YOUR_API_KEY___` with your actual **Google Maps API Key**.
80
+ 2. Use the `onResponse` callback to **handle the response**.
71
81
 
72
- ```svelte
82
+ ```javascript
73
83
  <script>
74
84
  import { PlaceAutocomplete } from 'places-autocomplete-svelte';
75
- import type { PlaceResult, ComponentOptions, RequestParams } from 'places-autocomplete-svelte/interfaces'; // Adjust path if needed
76
-
85
+ import type { PlaceResult, ComponentOptions, RequestParams } from 'places-autocomplete-svelte/interfaces';
77
86
 
78
87
  // Get API Key securely (e.g., from environment variables)
79
88
  const PUBLIC_GOOGLE_MAPS_API_KEY = import.meta.env.VITE_PUBLIC_GOOGLE_MAPS_API_KEY;
80
89
  let fullResponse: PlaceResult | null = $state(null);
81
90
  let placesError = $state('');
82
91
 
83
-
84
92
  // --- Event Handlers ---
85
93
  const handleResponse = (response: PlaceResult) => {
86
94
  console.log('Place Selected:', response);
@@ -95,31 +103,20 @@ const handleError = (error: string) => {
95
103
  };
96
104
 
97
105
  // --- Configuration (Optional) ---
98
-
99
- // Control API request parameters
100
106
  const requestParams: Partial<RequestParams> = $state({
101
- region: 'GB', // Example: Bias results to Great Britain
107
+ region: 'GB',
102
108
  language: 'en-GB',
103
- // includedRegionCodes: ['GB'], // Example: Only show results in the specified regions,
104
- // includedPrimaryTypes: ['address'], // Example: Only show addresses
105
109
  });
106
-
107
- // Control which data fields are fetched for Place Details (affects cost!)
108
110
  const fetchFields: string[] = $state(['formattedAddress', 'addressComponents', 'displayName']);
109
-
110
- // Control component appearance and behavior
111
111
  const options: Partial<ComponentOptions> = $state({
112
112
  placeholder: 'Start typing your address...',
113
- debounce: 200, // Debounce input by 200ms (default is 100ms)
114
- distance: true, // Show distance if origin is provided in requestParams
113
+ debounce: 200,
115
114
  classes: {
116
- // Example: Override input styling and highlight class
117
115
  input: 'my-custom-input-class border-blue-500',
118
- highlight: 'bg-yellow-200 text-black', // Customize suggestion highlighting
116
+ highlight: 'bg-yellow-200 text-black',
119
117
  },
120
- clear_input: false, // Overriding the default value to keep the input value after selecting a suggestion. The value of the input will be the value of `formattedAddress`
118
+ clear_input: false,
121
119
  });
122
-
123
120
  </script>
124
121
 
125
122
  {#if placesError}
@@ -143,12 +140,10 @@ const options: Partial<ComponentOptions> = $state({
143
140
  {/if}
144
141
 
145
142
  <style>
146
- /* Example of styling an overridden class */
147
143
  :global(.my-custom-input-class) {
148
144
  padding: 0.75rem;
149
145
  border-radius: 0.25rem;
150
146
  width: 100%;
151
- /* Add other styles */
152
147
  }
153
148
  .error-message {
154
149
  color: red;
@@ -156,131 +151,110 @@ const options: Partial<ComponentOptions> = $state({
156
151
  }
157
152
  </style>
158
153
  ```
154
+
159
155
  ## Props
160
- | Prop | Type | Required | Default | Description |
161
- |----------------------------|---------------------------------|----------|-------------------------------------------|-----------------------------------------------------------------------------------------------------------------|
162
- | PUBLIC_GOOGLE_MAPS_API_KEY | string | Yes | - | Your Google Maps API Key with Places API enabled. |
163
- | fetchFields | string[] | No | ['formattedAddress', 'addressComponents'] | Array of Place Data Fields to request when a place is selected. Affects API cost. |
164
- | requestParams | Partial<RequestParams> | No | { inputOffset: 3, ... } | Parameters for the Autocomplete request. See AutocompletionRequest options. |
165
- | options | Partial<ComponentOptions> | No | { debounce: 100, ... } | Options to control component behavior and appearance. See details below. |
166
- | onResponse | (response: PlaceResult) => void | Yes | - | Callback function triggered with the selected place details (PlaceResult object) after fetchFields is complete. |
167
- | onError | (error: string) => void | Yes | - | Callback function triggered when an error occurs (API loading, fetching suggestions, fetching details). |
168
156
 
169
- Component Methods (Imperative API)
170
- ----------------------------------
157
+ | Prop | Type | Required | Default | Description |
158
+ | :--- | :--- | :--- | :--- | :--- |
159
+ | `PUBLIC_GOOGLE_MAPS_API_KEY` | `string` | Yes | - | Your restricted Google Maps API Key. |
160
+ | `fetchFields` | `string[]` | No | `['formattedAddress', 'addressComponents']` | Place Data Fields to request. **Affects API cost.** |
161
+ | `requestParams` | `Partial<RequestParams>` | No | `{ inputOffset: 3, ... }` | Parameters for the Autocomplete API request. |
162
+ | `options` | `Partial<ComponentOptions>` | No | `{ debounce: 100, ... }` | Options to control component behavior and appearance. |
163
+ | `onResponse` | `(response: PlaceResult) => void` | Yes | - | Callback triggered with the selected place details. |
164
+ | `onError` | `(error: string) => void` | Yes | - | Callback triggered when an error occurs. |
165
+
166
+ ## Component Methods (Imperative API)
171
167
 
172
- For advanced use cases, you can get a reference to the component instance using `bind:this` and call its methods directly. This is useful for controlling the component from a parent, such as clearing the input from an external button.
168
+ Get a reference to the component instance using `bind:this` to call its methods directly.
173
169
 
174
- **Example Setup:**
170
+ **Example:**
175
171
 
176
- ```svelte
172
+ ```javascript
177
173
  <script lang="ts">
178
174
  import PlaceAutocomplete from 'places-autocomplete-svelte';
179
- let autocompleteComponent = $state(null); // This will hold the component instance
175
+ let autocompleteComponent: PlaceAutocomplete | undefined = $state(undefined);
180
176
  </script>
181
177
 
182
178
  <PlaceAutocomplete bind:this={autocompleteComponent} ... />
183
179
 
184
180
  <button onclick={() => autocompleteComponent.clear()}>Clear</button>
185
- <button onclick={() => autocompleteComponent.focus()}>Focus Input</button>
186
- <button onclick={() => console.log(JSON.stringify(autocompleteComponent.getRequestParams()))}>Get Request Params</button>
187
181
  ```
188
182
 
189
- **Available Methods:**
190
-
191
183
  | Method | Signature | Description |
192
- | :-- | :-- | :-- |
193
- | `clear()` | `() => void` | Clears the input field, removes all suggestions, and resets the Google Places session token. |
194
- | `focus()` | `() => void` | Programmatically sets focus on the text input field. |
195
- | `getRequestParams()` | `() => RequestParams` | Returns the component's current internal `requestParams` object. Useful for debugging or state inspection. |
184
+ | :--- | :--- | :--- |
185
+ | `clear()` | `() => void` | Clears the input, removes suggestions, and resets the session token. |
186
+ | `focus()` | `() => void` | Sets focus on the text input field. |
187
+ | `getRequestParams()` | `() => RequestParams` | Returns the current internal `requestParams` object. |
196
188
 
197
- ### Options
189
+ ## Options
198
190
 
191
+ | Option | Type | Default | Description |
192
+ | :--- | :--- | :--- | :--- |
193
+ | `placeholder` | `string` | `''` | Placeholder text for the input field. |
194
+ | `debounce` | `number` | `100` | Delay in ms before firing API request. Set to `0` to disable. |
195
+ | `distance` | `boolean` | `true` | Show distance from `requestParams.origin` (if provided). |
196
+ | `distance_units` | `'km' \| 'miles'` | `'km'` | Units for displaying distance. |
197
+ | `label` | `string` | `''` | Optional label text displayed above the input. |
198
+ | `autofocus` | `boolean` | `false` | Automatically focus the input on mount. |
199
+ | `autocomplete` | `string` | `'off'` | The `autocomplete` attribute for the input field. |
200
+ | `classes` | `Partial<ComponentClasses>` | `{}` | Object to override default CSS classes. See Styling section. |
201
+ | `clear_input` | `boolean` | `true` | If `false`, retains the `formattedAddress` in the input after selection. |
199
202
 
200
- | Option | Type | Default | Description |
201
- |----------------|---------------------------|---------|---------------------------------------------------------------------------------------------------------------------------------|
202
- | placeholder | string | '' | Placeholder text for the input field. |
203
- | debounce | number | 100 | Delay in milliseconds before triggering autocomplete API request after user stops typing. Set to 0 to disable debouncing. |
204
- | distance | boolean | true | Show distance from requestParams.origin in suggestions (if origin is provided). |
205
- | distance_units | 'km' \| 'miles' | 'km' | Units to display distance in. |
206
- | label | string | '' | Optional label text displayed above the input field. |
207
- | autofocus | boolean | false | Automatically focus the input field on mount. |
208
- | autocomplete | string | 'off' | Standard HTML autocomplete attribute for the input field. |
209
- | classes | Partial<ComponentClasses> | {} | Object to override default CSS classes for various component parts. See Styling (options.classes) section for keys. | |
210
- | clear_input | Boolean | true | If `true` (default), clears the input field after a suggestion is selected. If `false`, the input field retains the `formattedAddress` of the selected place. |
211
-
212
-
213
-
214
-
215
- ### Styling (`options.classes`)
216
- ---------------------------
217
-
218
- You can customize the appearance of the component by providing your own CSS classes via the `options.classes` prop. The component uses Tailwind CSS utility classes by default. Provide an object where keys are the component parts and values are the class strings you want to apply. See [styling](https://places-autocomplete-demo.pages.dev/examples/styling) for details.
203
+ ## Styling (`options.classes`)
219
204
 
205
+ Customise the component by providing your own CSS classes via `options.classes`.
220
206
 
221
207
  **Available Class Keys:**
222
- The following keys can be used within the `options.classes object to target specific parts of the component:
223
-
224
- - `section`: The main container section.
225
- - `container`: The div containing the input and suggestions list.
226
- - `label`: The label element (if `options.label` is provided).
227
- - `input`: The main text input element.
228
- - `icon_container`: Container for the optional icon.
229
- - `icon`: SVG string for the icon.
230
- - `ul`: The `<ul>` element for the suggestions list.
231
- - `li`: Each `<li>` suggestion item.
232
- - `li_current`: Class added to the currently highlighted/selected `<li>` (keyboard/mouse).
233
- - `li_a`: The inner `<a>` or `<button>` element within each `<li>`.
234
- - `li_a_current`: Class added to the inner element when its `<li>` is current.
235
- - `li_div_container`: Container div within the `<a>`/`<button>`.
236
- - `li_div_one`: First inner div (usually contains the main text).
237
- - `li_div_one_p`: The `<p>` tag containing the main suggestion text (`@html` is used).
238
- - `li_div_two`: Second inner div (usually contains the distance).
239
- - `li_div_two_p`: The `<p>` tag containing the distance text.
240
- - `kbd_container`: Container for the keyboard hint keys (Esc, Up, Down).
241
- - `kbd_escape`: The `<kbd>` tag for the 'Esc' hint.
242
- - `kbd_up`: The `<kbd>` tag for the 'Up Arrow' hint.
243
- - `kbd_down`: The `<kbd>` tag for the 'Down Arrow' hint.
244
- - `highlight`: The class applied to the `<span>` wrapping the matched text within suggestions. Defaults to `'font-bold'`.
245
-
246
- ### Example:
208
+
209
+ * `section`: The main container `section`.
210
+ * `container`: The `div` containing the input and suggestions list.
211
+ * `label`: The `label` element.
212
+ * `input`: The main text `input` element.
213
+ * `icon_container`: Container for the optional icon.
214
+ * `icon`: SVG string for the icon.
215
+ * `ul`: The `<ul>` element for the suggestions list.
216
+ * `li`: Each `<li>` suggestion item.
217
+ * `li_current`: Class added to the currently highlighted `<li>`.
218
+ * `li_div_container`: Container `div` within each list item.
219
+ * `li_div_one`: First inner `div` (contains the main text).
220
+ * `li_div_one_p`: The `<p>` tag containing the main suggestion text.
221
+ * `li_div_two`: Second inner `div` (contains the distance).
222
+ * `li_div_two_p`: The `<p>` tag containing the distance text.
223
+ * `kbd_container`: Container for the keyboard hint keys.
224
+ * `kbd_escape`: The `<kbd>` tag for the 'Esc' hint.
225
+ * `kbd_up`: The `<kbd>` tag for the 'Up Arrow' hint.
226
+ * `kbd_down`: The `<kbd>` tag for the 'Down Arrow' hint.
227
+ * `highlight`: The class applied to the `<span>` wrapping the matched text. Defaults to `'font-bold'`.
228
+
229
+ **Example:**
247
230
 
248
231
  ```javascript
249
232
  const options = {
250
233
  classes: {
251
- input: 'form-input w-full rounded-md shadow-sm', // Replace default input style
252
- ul: 'absolute bg-white shadow-lg rounded-md mt-1 w-full z-10', // Custom dropdown style
253
- li_current: 'bg-blue-500 text-white', // Custom highlight style for selected item
254
- highlight: 'text-blue-700 font-semibold' // Custom style for matched text
234
+ input: 'form-input w-full rounded-md shadow-sm',
235
+ ul: 'absolute bg-white shadow-lg rounded-md mt-1 w-full z-10',
236
+ li_current: 'bg-blue-500 text-white',
237
+ highlight: 'text-blue-700 font-semibold'
255
238
  }
256
239
  };
257
240
  ```
258
241
 
259
- Events
260
- ------
261
-
262
- - **`onResponse`**: `(response: PlaceResult) => void`
263
- - Fired after a user selects a suggestion and the requested `fetchFields` have been successfully retrieved.
264
- - The `response` argument is an object containing the place details based on the `fetchFields` requested. Its structure mirrors the [PlaceResult](https://developers.google.com/maps/documentation/javascript/reference/places-service#PlaceResult) but includes only the requested fields.
265
- - **`onError`**: `(error: string) => void`
266
- - Fired if there's an error loading the Google Maps API, fetching autocomplete suggestions, or fetching place details.
267
- - The `error` argument is a string describing the error.
268
-
269
-
270
- TypeScript
271
- ----------
242
+ ## Events
272
243
 
273
- This component is written in TypeScript and includes type definitions for props (`Props`, `ComponentOptions`, `RequestParams`, `ComponentClasses`) and the response (`PlaceResult`, `AddressComponent`). You can import these types from `places-autocomplete-svelte/interfaces` (adjust path if needed based on your setup).
244
+ * **`onResponse`**: `(response: PlaceResult) => void`
245
+ * Fired after a user selects a suggestion and `fetchFields` are retrieved.
246
+ * **`onError`**: `(error: string) => void`
247
+ * Fired on any error (API loading, fetching suggestions, etc.).
274
248
 
249
+ ## TypeScript
275
250
 
251
+ This component is written in TypeScript. Import types from `places-autocomplete-svelte/interfaces`.
276
252
 
277
- Google Places API & Billing
278
- ---------------------------
253
+ ## Google Places API & Billing
279
254
 
280
- - This component uses the Google Maps JavaScript API (specifically the Places library). Usage is subject to Google's terms and pricing.
281
- - An API key enabled for the "Places API" is required.
282
- - The component uses **Session Tokens** automatically to group Autocomplete requests, which can lead to significant cost savings compared to per-request billing. See [Google's Session Token Pricing](https://developers.google.com/maps/documentation/places/web-service/usage-and-billing#session-pricing).
283
- - Place Details requests (made via `fetchFields` when a suggestion is selected) are billed separately. Carefully select only the `fetchFields` you need to manage costs. See [Place Data Fields Pricing](https://developers.google.com/maps/documentation/javascript/usage-and-billing#data-pricing).
255
+ * This component uses the Google Maps JavaScript API (Places library). Usage is subject to Google's terms and pricing.
256
+ * It uses **Session Tokens** automatically to group Autocomplete requests, which can reduce costs.
257
+ * Place Details requests (via `fetchFields`) are billed separately. Only request the fields you need to manage costs.
284
258
 
285
259
  ## Contributing
286
260
 
@@ -7,6 +7,8 @@
7
7
  validateRequestParams,
8
8
  formatDistance,
9
9
  validateFetchFields,
10
+ createHighlightedSegments,
11
+ debounce
10
12
  } from './helpers.js';
11
13
  const { Loader } = GMaps;
12
14
 
@@ -33,6 +35,9 @@
33
35
  fetchFields = validateFetchFields(fetchFields);
34
36
  //console.log(fetchFields);
35
37
 
38
+ // variable to hold a reference to the component's root element
39
+ //let componentRoot: HTMLElement;
40
+
36
41
  let kbdAction = $state(''); // 'up', 'down', or 'escape'
37
42
 
38
43
  // Local variables
@@ -102,27 +107,6 @@
102
107
  return request;
103
108
  }
104
109
 
105
- /**
106
- * Debounce function to limit the rate at which a function can fire.
107
- * @param func
108
- * @param wait
109
- */
110
- function debounce<T extends (...args: any[]) => any>(
111
- func: T,
112
- wait: number
113
- ): (...args: Parameters<T>) => void {
114
- let timeout: ReturnType<typeof setTimeout> | null = null;
115
- return function executedFunction(...args: Parameters<T>) {
116
- const later = () => {
117
- timeout = null;
118
- func(...args);
119
- };
120
- if (timeout !== null) {
121
- clearTimeout(timeout);
122
- }
123
- timeout = setTimeout(later, wait);
124
- };
125
- }
126
110
 
127
111
  /**
128
112
  * Make request and get autocomplete suggestions.
@@ -164,30 +148,15 @@
164
148
  const matches = predictionText.matches;
165
149
 
166
150
  //Highlighting Logic
167
- let highlightedText = '';
168
- let lastIndex = 0;
151
+ let highlightedText: { text: string; highlighted: boolean }[] = [];
169
152
 
170
153
  // Sort matches just in case they aren't ordered (though they usually are)
171
154
  matches.sort(
172
155
  (a: { startOffset: number }, b: { startOffset: number }) => a.startOffset - b.startOffset
173
156
  );
174
- for (const match of matches) {
175
- // Append text before the current match
176
- highlightedText += originalText.substring(lastIndex, match.startOffset);
177
-
178
- // Append the highlighted match segment
179
- // Choose your highlighting class (e.g., 'font-bold' or a custom one)
180
- highlightedText += `<span class="${options.classes?.highlight ?? 'font-bold'}">`;
181
- highlightedText += originalText.substring(match.startOffset, match.endOffset);
182
- highlightedText += `</span>`;
183
157
 
184
- // Update the last index processed
185
- lastIndex = match.endOffset;
186
- }
187
-
188
- // Append any remaining text after the last match
189
- highlightedText += originalText.substring(lastIndex);
190
- // --- End Highlighting Logic ---
158
+ // Create highlighted segments
159
+ highlightedText = createHighlightedSegments(originalText, matches);
191
160
 
192
161
  results.push({
193
162
  place: suggestion.placePrediction.toPlace(),
@@ -318,11 +287,19 @@
318
287
  }
319
288
  </script>
320
289
 
321
- <svelte:window onkeydown={onKeyDown} onclick={handleClickOutside} />
322
-
323
- <section class={options.classes?.section}>
290
+ <svelte:window onclick={handleClickOutside} />
291
+
292
+ <section
293
+ class={options.classes?.section}
294
+ role="combobox"
295
+ aria-controls="autocomplete-listbox"
296
+ tabindex="0"
297
+ aria-haspopup="listbox"
298
+ aria-expanded={results.length > 0}
299
+ aria-owns="autocomplete-listbox"
300
+ >
324
301
  {#if options?.label ?? ''}
325
- <label for="search" class={options.classes?.label ?? ''}>
302
+ <label for="places-autocomplete-input" class={options.classes?.label ?? ''}>
326
303
  {options.label}
327
304
  </label>
328
305
  {/if}
@@ -334,6 +311,7 @@
334
311
  {/if}
335
312
 
336
313
  <input
314
+ id="places-autocomplete-input"
337
315
  type="text"
338
316
  name="search"
339
317
  bind:this={inputRef}
@@ -346,7 +324,9 @@
346
324
  aria-labelledby="search"
347
325
  aria-label="Search"
348
326
  aria-haspopup="listbox"
327
+ aria-activedescendant={currentSuggestion > -1 ? `option-${currentSuggestion + 1}` : undefined}
349
328
  bind:value={request.input}
329
+ onkeydown={onKeyDown}
350
330
  oninput={(event) => debouncedMakeAcRequest(event.currentTarget.value)}
351
331
  />
352
332
  <!-- oninput={makeAcRequest} -->
@@ -362,7 +342,7 @@
362
342
  >&dArr;</kbd
363
343
  >
364
344
  </div>
365
- <ul class={options.classes?.ul ?? ''} id="options" role="listbox">
345
+ <ul class={options.classes?.ul ?? ''} id="autocomplete-listbox" role="listbox">
366
346
  {#each results as p, i}
367
347
  <li
368
348
  role="option"
@@ -373,8 +353,8 @@
373
353
  ]}
374
354
  onmouseenter={() => (currentSuggestion = i)}
375
355
  id="option-{i + 1}"
356
+
376
357
  >
377
- <!-- svelte-ignore a11y_invalid_attribute -->
378
358
  <button
379
359
  type="button"
380
360
  class={[
@@ -396,8 +376,14 @@
396
376
  options.classes?.li_div_one_p
397
377
  ]}
398
378
  >
399
- <!-- {p.text} -->
400
- {@html p.text}
379
+ {#each p.text as segment}
380
+ {#if segment.highlighted}
381
+ <span class={options.classes?.highlight ?? 'font-bold'}>{segment.text}</span
382
+ >
383
+ {:else}
384
+ {segment.text}
385
+ {/if}
386
+ {/each}
401
387
  </p>
402
388
  </div>
403
389
  </div>
package/dist/helpers.d.ts CHANGED
@@ -41,3 +41,25 @@ export declare const validateOptions: (options: ComponentOptions | undefined) =>
41
41
  * @returns
42
42
  */
43
43
  export declare const formatDistance: (distance: number, units: DistanceUnits) => string | null;
44
+ /**
45
+ * Create highlighted segments from the original text based on the provided matches.
46
+ * @param originalText The original text to segment.
47
+ * @param matches An array of match objects containing start and end offsets.
48
+ * @returns An array of text segments with highlighting information.
49
+ */
50
+ export declare function createHighlightedSegments(originalText: string, matches: {
51
+ startOffset: number;
52
+ endOffset: number;
53
+ }[]): {
54
+ text: string;
55
+ highlighted: boolean;
56
+ }[];
57
+ /**
58
+ * Debounce function that takes a function and a delay
59
+ * and returns a new function that will only execute
60
+ * after the delay has passed without any new calls.
61
+ * This version is generic and preserves the types of the original function.
62
+ * @param func The function to debounce.
63
+ * @param delay The debounce delay in milliseconds.
64
+ */
65
+ export declare const debounce: <T extends (...args: any[]) => void>(func: T, delay: number) => (...args: Parameters<T>) => void;
package/dist/helpers.js CHANGED
@@ -363,3 +363,49 @@ export const formatDistance = function (distance, units) {
363
363
  return `${(distance / 1609.34).toFixed(2)} miles`;
364
364
  }
365
365
  };
366
+ /**
367
+ * Create highlighted segments from the original text based on the provided matches.
368
+ * @param originalText The original text to segment.
369
+ * @param matches An array of match objects containing start and end offsets.
370
+ * @returns An array of text segments with highlighting information.
371
+ */
372
+ export function createHighlightedSegments(originalText, matches) {
373
+ const segments = [];
374
+ if (!originalText || !matches)
375
+ return segments;
376
+ let lastIndex = 0;
377
+ // Sort matches just in case they aren't ordered
378
+ matches.sort((a, b) => a.startOffset - b.startOffset);
379
+ for (const match of matches) {
380
+ // Add text before the match
381
+ if (match.startOffset > lastIndex) {
382
+ segments.push({ text: originalText.substring(lastIndex, match.startOffset), highlighted: false });
383
+ }
384
+ // Add the matched text
385
+ segments.push({ text: originalText.substring(match.startOffset, match.endOffset), highlighted: true });
386
+ lastIndex = match.endOffset;
387
+ }
388
+ // Add any remaining text after the last match
389
+ if (lastIndex < originalText.length) {
390
+ segments.push({ text: originalText.substring(lastIndex), highlighted: false });
391
+ }
392
+ return segments;
393
+ }
394
+ /**
395
+ * Debounce function that takes a function and a delay
396
+ * and returns a new function that will only execute
397
+ * after the delay has passed without any new calls.
398
+ * This version is generic and preserves the types of the original function.
399
+ * @param func The function to debounce.
400
+ * @param delay The debounce delay in milliseconds.
401
+ */
402
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
403
+ export const debounce = (func, delay) => {
404
+ let timeout;
405
+ return (...args) => {
406
+ clearTimeout(timeout);
407
+ timeout = setTimeout(() => {
408
+ func(...args);
409
+ }, delay);
410
+ };
411
+ };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "places-autocomplete-svelte",
3
3
  "license": "MIT",
4
- "version": "2.2.14",
5
- "description": "A flexible and customisable Svelte component leveraging the Google Maps Places (New) Autocomplete API to provide a user-friendly way to search for and retrieve detailed address information within your SvelteKit applications.",
4
+ "version": "2.2.15",
5
+ "description": "A flexible, accessible, and secure Svelte component leveraging the Google Maps Places Autocomplete API (New) to provide a user-friendly way to search for and retrieve detailed address information.",
6
6
  "keywords": [
7
7
  "svelte",
8
8
  "sveltekit",
@@ -71,36 +71,37 @@
71
71
  "svelte": "^5.0.0"
72
72
  },
73
73
  "devDependencies": {
74
- "@sveltejs/adapter-auto": "^6.0.1",
75
- "@sveltejs/adapter-cloudflare": "^7.0.4",
76
- "@sveltejs/kit": "^2.22.2",
77
- "@sveltejs/package": "^2.3.12",
78
- "@sveltejs/vite-plugin-svelte": "^5.1.0",
79
- "@tailwindcss/postcss": "^4.1.11",
74
+ "@sveltejs/adapter-auto": "^6.1.0",
75
+ "@sveltejs/adapter-cloudflare": "^7.2.2",
76
+ "@sveltejs/kit": "^2.36.1",
77
+ "@sveltejs/package": "^2.5.0",
78
+ "@sveltejs/vite-plugin-svelte": "^6.1.3",
79
+ "@tailwindcss/postcss": "^4.1.12",
80
80
  "@tailwindcss/typography": "^0.5.16",
81
- "@tailwindcss/vite": "^4.1.11",
81
+ "@tailwindcss/vite": "^4.1.12",
82
82
  "@types/eslint": "^9.6.1",
83
+ "@types/node": "^24.3.0",
83
84
  "autoprefixer": "^10.4.21",
84
- "eslint": "^9.30.1",
85
- "eslint-config-prettier": "^10.1.5",
86
- "eslint-plugin-svelte": "^3.10.1",
85
+ "eslint": "^9.34.0",
86
+ "eslint-config-prettier": "^10.1.8",
87
+ "eslint-plugin-svelte": "^3.11.0",
87
88
  "globals": "^16.3.0",
88
89
  "postcss": "^8.5.6",
89
90
  "prettier": "^3.6.2",
90
91
  "prettier-plugin-svelte": "^3.4.0",
91
92
  "publint": "^0.3.12",
92
- "svelte": "^5.35.2",
93
- "svelte-check": "^4.2.2",
94
- "tailwindcss": "^4.1.11",
93
+ "svelte": "^5.38.2",
94
+ "svelte-check": "^4.3.1",
95
+ "tailwindcss": "^4.1.12",
95
96
  "tslib": "^2.8.1",
96
- "typescript": "^5.8.3",
97
- "typescript-eslint": "^8.35.1",
98
- "vite": "^6.3.5"
97
+ "typescript": "^5.9.2",
98
+ "typescript-eslint": "^8.40.0",
99
+ "vite": "^7.1.3"
99
100
  },
100
101
  "svelte": "./dist/index.js",
101
102
  "types": "./dist/PlaceAutocomplete.svelte.d.ts",
102
103
  "type": "module",
103
104
  "dependencies": {
104
- "@googlemaps/js-api-loader": "^1.16.8"
105
+ "@googlemaps/js-api-loader": "^1.16.10"
105
106
  }
106
107
  }