places-autocomplete-svelte 2.2.12 → 2.2.14

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
@@ -8,7 +8,7 @@ A flexible and customizable [Svelte](https://kit.svelte.dev) component leveragin
8
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.
9
9
 
10
10
 
11
- ## Also Available: Standalone JavaScript Library
11
+ ## Available: Standalone JavaScript Library
12
12
 
13
13
  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
14
  [View `places-autocomplete-js` on GitHub](https://github.com/alexpechkarev/places-autocomplete-js)
@@ -20,6 +20,7 @@ Need this functionality for a non-Svelte project? Check out our companion vanill
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
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.
23
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.
24
25
  * **TypeScript Support:** Fully written in TypeScript with included type definitions.
25
26
  * **Event Handling:** Provides `onResponse` and `onError` callbacks.
@@ -73,6 +74,7 @@ yarn add places-autocomplete-svelte
73
74
  import { PlaceAutocomplete } from 'places-autocomplete-svelte';
74
75
  import type { PlaceResult, ComponentOptions, RequestParams } from 'places-autocomplete-svelte/interfaces'; // Adjust path if needed
75
76
 
77
+
76
78
  // Get API Key securely (e.g., from environment variables)
77
79
  const PUBLIC_GOOGLE_MAPS_API_KEY = import.meta.env.VITE_PUBLIC_GOOGLE_MAPS_API_KEY;
78
80
  let fullResponse: PlaceResult | null = $state(null);
@@ -164,7 +166,33 @@ const options: Partial<ComponentOptions> = $state({
164
166
  | onResponse | (response: PlaceResult) => void | Yes | - | Callback function triggered with the selected place details (PlaceResult object) after fetchFields is complete. |
165
167
  | onError | (error: string) => void | Yes | - | Callback function triggered when an error occurs (API loading, fetching suggestions, fetching details). |
166
168
 
169
+ Component Methods (Imperative API)
170
+ ----------------------------------
171
+
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.
173
+
174
+ **Example Setup:**
175
+
176
+ ```svelte
177
+ <script lang="ts">
178
+ import PlaceAutocomplete from 'places-autocomplete-svelte';
179
+ let autocompleteComponent = $state(null); // This will hold the component instance
180
+ </script>
181
+
182
+ <PlaceAutocomplete bind:this={autocompleteComponent} ... />
183
+
184
+ <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
+ ```
188
+
189
+ **Available Methods:**
167
190
 
191
+ | 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. |
168
196
 
169
197
  ### Options
170
198
 
@@ -7,7 +7,6 @@
7
7
  validateRequestParams,
8
8
  formatDistance,
9
9
  validateFetchFields,
10
- componentOptions
11
10
  } from './helpers.js';
12
11
  const { Loader } = GMaps;
13
12
 
@@ -24,6 +23,8 @@
24
23
  requestParams = {}
25
24
  }: Props = $props();
26
25
 
26
+ const isDefaultOnResponse = onResponse.toString() === ((response: PlaceResult) => {}).toString();
27
+
27
28
  // validate options
28
29
  options = validateOptions(options);
29
30
  //console.log(options);
@@ -32,13 +33,7 @@
32
33
  fetchFields = validateFetchFields(fetchFields);
33
34
  //console.log(fetchFields);
34
35
 
35
- // set classes as state
36
- let cl = $state(options.classes ?? {});
37
- // reset keyboard classes
38
- const resetKbdClasses = () => {
39
- cl.kbd_down = options.classes?.kbd_down ?? componentOptions.classes?.kbd_down ?? '';
40
- cl.kbd_up = options.classes?.kbd_up ?? componentOptions.classes?.kbd_up ?? '';
41
- };
36
+ let kbdAction = $state(''); // 'up', 'down', or 'escape'
42
37
 
43
38
  // Local variables
44
39
  let inputRef: HTMLInputElement;
@@ -64,12 +59,12 @@
64
59
  */
65
60
  const reset = (placeData?: PlaceResult) => {
66
61
  currentSuggestion = -1;
67
- if(options?.clear_input == false){
62
+ if (options?.clear_input == false) {
68
63
  if (placeData && placeData.formattedAddress) {
69
64
  // set input to formatted address
70
65
  request.input = placeData.formattedAddress;
71
66
  }
72
- }else{
67
+ } else {
73
68
  request.input = '';
74
69
  }
75
70
  results = [];
@@ -77,6 +72,36 @@
77
72
  //console.log('reset completed', results);
78
73
  };
79
74
 
75
+ /**
76
+ * Clears the autocomplete input field and suggestions list.
77
+ * Refreshes the Google Places session token for a new session.
78
+ * Can be called imperatively on the component instance.
79
+ * @example
80
+ * let autocompleteComponent;
81
+ * autocompleteComponent.clear();
82
+ */
83
+ export function clear() {
84
+ reset(); // The existing reset function already handles this
85
+ }
86
+
87
+ /**
88
+ * Programmatically focuses the input element.
89
+ * @example
90
+ * autocompleteComponent.focus();
91
+ */
92
+ export function focus() {
93
+ inputRef?.focus();
94
+ }
95
+
96
+ /**
97
+ * Returns the current internal request parameters.
98
+ * Useful for debugging or inspecting the component's state.
99
+ * @returns {RequestParams}
100
+ */
101
+ export function getRequestParams() {
102
+ return request;
103
+ }
104
+
80
105
  /**
81
106
  * Debounce function to limit the rate at which a function can fire.
82
107
  * @param func
@@ -204,8 +229,6 @@
204
229
  (e.name || 'An error occurred') + ' - ' + (e.message || 'error fetching place details')
205
230
  );
206
231
  }
207
-
208
-
209
232
  };
210
233
 
211
234
  /**
@@ -223,6 +246,11 @@
223
246
  * Initialize the Google Maps JavaScript API Loader.
224
247
  */
225
248
  onMount(async (): Promise<void> => {
249
+ if (isDefaultOnResponse) {
250
+ console.warn(
251
+ 'PlaceAutocomplete: The `onResponse` callback has not been provided. Selected place data will not be handled. See documentation for usage.'
252
+ );
253
+ }
226
254
  if (options.autofocus) {
227
255
  // focus on the input
228
256
  inputRef.focus();
@@ -236,7 +264,7 @@
236
264
  libraries: ['places']
237
265
  });
238
266
 
239
- const { AutocompleteSessionToken, AutocompleteSuggestion} =
267
+ const { AutocompleteSessionToken, AutocompleteSuggestion } =
240
268
  await loader.importLibrary('places');
241
269
 
242
270
  placesApi.AutocompleteSessionToken = AutocompleteSessionToken;
@@ -258,28 +286,27 @@
258
286
  function onKeyDown(e: KeyboardEvent) {
259
287
  if (e.key === 'ArrowDown') {
260
288
  currentSuggestion = Math.min(currentSuggestion + 1, results.length - 1);
261
- resetKbdClasses();
262
- cl.kbd_down += ' ' + (cl?.kbd_active ?? 'bg-indigo-500 text-white');
289
+ kbdAction = 'down';
263
290
  } else if (e.key === 'ArrowUp') {
264
291
  currentSuggestion = Math.max(currentSuggestion - 1, 0);
265
- resetKbdClasses();
266
- cl.kbd_up += ' ' + (cl?.kbd_active ?? 'bg-indigo-500 text-white');
292
+ kbdAction = 'up';
267
293
  } else if (e.key === 'Enter') {
268
294
  e.preventDefault();
269
295
  if (currentSuggestion >= 0) {
270
296
  onPlaceSelected(results[currentSuggestion].place);
271
297
  }
272
298
  } else if (e.key === 'Escape') {
273
- // reset srarch input and results
299
+ kbdAction = 'escape';
274
300
  request.input = '';
275
301
  reset();
276
302
  }
277
303
  // Optional: Scroll suggestion into view
278
304
  const selectedElement = document.getElementById(`option-${currentSuggestion + 1}`);
279
305
  selectedElement?.scrollIntoView({ block: 'nearest' });
306
+ // Reset the action state after a short delay to allow CSS transition to finish
280
307
  setTimeout(() => {
281
- resetKbdClasses();
282
- }, 300);
308
+ kbdAction = '';
309
+ }, 200);
283
310
  }
284
311
 
285
312
  // Handle click outside the input
@@ -291,7 +318,7 @@
291
318
  }
292
319
  </script>
293
320
 
294
- <svelte:window onkeydown={onKeyDown} onclick={handleClickOutside}/>
321
+ <svelte:window onkeydown={onKeyDown} onclick={handleClickOutside} />
295
322
 
296
323
  <section class={options.classes?.section}>
297
324
  {#if options?.label ?? ''}
@@ -326,11 +353,15 @@
326
353
 
327
354
  {#if results.length > 0}
328
355
  <div class={options.classes?.kbd_container ?? ''}>
329
- <kbd class={options.classes?.kbd_escape ?? ''}>Esc</kbd>
330
- <kbd class={cl.kbd_up}>&uArr;</kbd>
331
- <kbd class={cl.kbd_down}>&dArr;</kbd>
356
+ <kbd class={options.classes?.kbd_escape ?? ''} class:kbd-active={kbdAction === 'escape'}
357
+ >Esc</kbd
358
+ >
359
+ <kbd class={options.classes?.kbd_up ?? ''} class:kbd-active={kbdAction === 'up'}>&uArr;</kbd
360
+ >
361
+ <kbd class={options.classes?.kbd_down ?? ''} class:kbd-active={kbdAction === 'down'}
362
+ >&dArr;</kbd
363
+ >
332
364
  </div>
333
-
334
365
  <ul class={options.classes?.ul ?? ''} id="options" role="listbox">
335
366
  {#each results as p, i}
336
367
  <li
@@ -389,3 +420,15 @@
389
420
  {/if}
390
421
  </div>
391
422
  </section>
423
+
424
+ <style>
425
+ /* Combining Tailwind CSS classes bg-indigo-500 text-white */
426
+ .kbd-active {
427
+ /* Use the active class from options if available, or a default */
428
+ background-color: var(--kbd-active-bg, #4f46e5); /* Indigo 500 */
429
+ color: var(--kbd-active-color, white);
430
+ transition:
431
+ background-color 0.1s ease-in-out,
432
+ color 0.1s ease-in-out;
433
+ }
434
+ </style>
@@ -1,4 +1,8 @@
1
1
  import type { Props } from './interfaces.js';
2
- declare const PlaceAutocomplete: import("svelte").Component<Props, {}, "onResponse" | "onError">;
2
+ declare const PlaceAutocomplete: import("svelte").Component<Props, {
3
+ clear: () => void;
4
+ focus: () => void;
5
+ getRequestParams: () => import("./interfaces.js").RequestParams;
6
+ }, "onResponse" | "onError">;
3
7
  type PlaceAutocomplete = ReturnType<typeof PlaceAutocomplete>;
4
8
  export default PlaceAutocomplete;
package/dist/helpers.js CHANGED
@@ -331,56 +331,20 @@ export const componentOptions = {
331
331
  * @param options
332
332
  */
333
333
  export const validateOptions = (options) => {
334
- const validatedOptions = { ...componentOptions };
335
- if (options && typeof options === 'object') {
336
- for (const key in validatedOptions) {
337
- if (key in options) {
338
- switch (key) {
339
- case 'autofocus':
340
- validatedOptions.autofocus = Boolean(options.autofocus);
341
- break;
342
- case 'autocomplete':
343
- validatedOptions.autocomplete = String(options.autocomplete);
344
- break;
345
- case 'placeholder':
346
- validatedOptions.placeholder = String(options.placeholder);
347
- break;
348
- case 'distance':
349
- validatedOptions.distance = Boolean(options.distance);
350
- break;
351
- case 'label':
352
- validatedOptions.label = String(options.label);
353
- break;
354
- case 'distance_units':
355
- validatedOptions.distance_units = String(options.distance_units);
356
- break;
357
- case 'classes':
358
- if (options.classes && typeof options.classes === 'object') {
359
- validatedOptions.classes = {
360
- ...componentOptions.classes,
361
- ...options.classes ?? {}
362
- };
363
- }
364
- break;
365
- case 'debounce':
366
- {
367
- const debounceValue = Number(options.debounce);
368
- if (!isNaN(debounceValue) && debounceValue >= 0) { // Allow 0 for debounce
369
- validatedOptions.debounce = debounceValue;
370
- }
371
- break;
372
- }
373
- case 'clear_input':
374
- validatedOptions.clear_input = Boolean(options.clear_input);
375
- break;
376
- default:
377
- // Ignore any other keys that are not in the default options
378
- break;
379
- }
380
- }
381
- }
334
+ if (!options) {
335
+ return componentOptions;
382
336
  }
383
- return validatedOptions;
337
+ // Perform a deep merge for the 'classes' object, inspired by the JS library
338
+ const mergedClasses = {
339
+ ...componentOptions.classes,
340
+ ...(options.classes ?? {})
341
+ };
342
+ const validated = {
343
+ ...componentOptions, // Start with all defaults
344
+ ...options, // Override with user-provided options
345
+ classes: mergedClasses // Apply the specifically merged classes
346
+ };
347
+ return validated;
384
348
  };
385
349
  /**
386
350
  * Display distance in km or miles
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "places-autocomplete-svelte",
3
3
  "license": "MIT",
4
- "version": "2.2.12",
5
- "description": "A flexible and customizable 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.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.",
6
6
  "keywords": [
7
7
  "svelte",
8
8
  "sveltekit",
@@ -72,29 +72,29 @@
72
72
  },
73
73
  "devDependencies": {
74
74
  "@sveltejs/adapter-auto": "^6.0.1",
75
- "@sveltejs/adapter-cloudflare": "^7.0.3",
76
- "@sveltejs/kit": "^2.21.2",
77
- "@sveltejs/package": "^2.3.11",
75
+ "@sveltejs/adapter-cloudflare": "^7.0.4",
76
+ "@sveltejs/kit": "^2.22.2",
77
+ "@sveltejs/package": "^2.3.12",
78
78
  "@sveltejs/vite-plugin-svelte": "^5.1.0",
79
- "@tailwindcss/postcss": "^4.1.8",
79
+ "@tailwindcss/postcss": "^4.1.11",
80
80
  "@tailwindcss/typography": "^0.5.16",
81
- "@tailwindcss/vite": "^4.1.8",
81
+ "@tailwindcss/vite": "^4.1.11",
82
82
  "@types/eslint": "^9.6.1",
83
83
  "autoprefixer": "^10.4.21",
84
- "eslint": "^9.28.0",
84
+ "eslint": "^9.30.1",
85
85
  "eslint-config-prettier": "^10.1.5",
86
- "eslint-plugin-svelte": "^3.9.1",
87
- "globals": "^16.2.0",
88
- "postcss": "^8.5.4",
89
- "prettier": "^3.5.3",
86
+ "eslint-plugin-svelte": "^3.10.1",
87
+ "globals": "^16.3.0",
88
+ "postcss": "^8.5.6",
89
+ "prettier": "^3.6.2",
90
90
  "prettier-plugin-svelte": "^3.4.0",
91
91
  "publint": "^0.3.12",
92
- "svelte": "^5.33.16",
93
- "svelte-check": "^4.2.1",
94
- "tailwindcss": "^4.1.8",
92
+ "svelte": "^5.35.2",
93
+ "svelte-check": "^4.2.2",
94
+ "tailwindcss": "^4.1.11",
95
95
  "tslib": "^2.8.1",
96
96
  "typescript": "^5.8.3",
97
- "typescript-eslint": "^8.33.1",
97
+ "typescript-eslint": "^8.35.1",
98
98
  "vite": "^6.3.5"
99
99
  },
100
100
  "svelte": "./dist/index.js",