places-autocomplete-svelte 2.2.17 → 2.2.18

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
@@ -5,7 +5,11 @@
5
5
 
6
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).
7
7
 
8
- The component handles API loading, session tokens, debounced fetching, and accessibility, allowing you to focus on building your application. It intelligently manages the Google Maps API loader, creating a shared instance that prevents conflicts with other map components on the same page.
8
+ The component handles API loading, session tokens, debounced fetching, and accessibility, allowing you to focus on building your application. It intelligently manages the Google Maps API loader, creating a shared instance via Svelte's context that prevents conflicts with other map components on the same page.
9
+
10
+ **Two initialisation patterns:**
11
+ - **Simple/Automatic**: Pass your API key directly to the component for basic use cases
12
+ - **Advanced/Manual**: Initialise the loader once in a parent component when using multiple Google Maps libraries or components
9
13
 
10
14
  ## Available: Standalone JavaScript Library
11
15
 
@@ -53,74 +57,122 @@ yarn add places-autocomplete-svelte
53
57
 
54
58
  ## Usage
55
59
 
56
- The `PlaceAutocomplete` component is designed for simplicity. Here’s the minimum required to get it working:
60
+ ### Basic Usage (Automatic Initialisation)
57
61
 
62
+ For simple use cases, just pass your API key to the component. It will automatically handle the Google Maps loader initialisation:
58
63
 
59
- ```javascript
60
- // In your +page.svelte or a parent component
61
64
 
65
+ ```javascript
62
66
  <script lang="ts">
63
67
  import { PlaceAutocomplete } from 'places-autocomplete-svelte';
64
68
  import type { PlaceResult } from 'places-autocomplete-svelte/interfaces';
65
69
 
66
70
  // Get API Key securely (e.g., from environment variables)
67
- const PUBLIC_GOOGLE_MAPS_API_KEY = import.meta.env.VITE_PUBLIC_GOOGLE_MAPS_API_KEY;
71
+ const PUBLIC_GOOGLE_MAPS_API_KEY = import.meta.env.VITE_PUBLIC_GOOGLE_MAPS_API_KEY;
68
72
 
69
- // --- Handle Component Response ---
70
73
  const handleResponse = (response: PlaceResult) => {
71
- console.log('Place Selected:', response.formattedAddress);
74
+ console.log('Selected:', response.formattedAddress);
72
75
  };
73
76
 
74
- // --- Handle Component Errors ---
75
77
  const handleError = (error: string) => {
76
78
  console.error('Error:', error);
77
79
  };
78
80
  </script>
79
81
 
80
- <!-- Add the component to your page -->
81
- <PlaceAutocomplete {onResponse} {onError} {PUBLIC_GOOGLE_MAPS_API_KEY} />
82
+ <PlaceAutocomplete
83
+ {PUBLIC_GOOGLE_MAPS_API_KEY}
84
+ onResponse={handleResponse}
85
+ onError={handleError}
86
+ />
82
87
  ```
83
88
 
84
- ### Advanced: Using with other Google Maps Libraries
89
+ ### Advanced Usage (Manual Initialisation)
85
90
 
86
- The `PlaceAutocomplete` component intelligently manages the Google Maps loader. For simple use cases, it handles loading automatically. However, if you need to use other Google Maps libraries (like `maps` or `marker`) on the same page, you should initialise the loader once in a parent component (`+page.svelte` or `+layout.svelte`). This prevents conflicts and ensures all libraries are loaded efficiently.
91
+ For applications that need multiple Google Maps libraries (e.g., `places`, `maps`, `marker`) or multiple map components, initialise the loader once in a parent component. This approach:
87
92
 
88
- The component provides helper functions (`setGMapsContext`, `getGMapsContext`, `initialiseGMaps`) to manage this.
93
+ - Loads all required libraries in a single API call (more efficient)
94
+ - Prevents "Loader must not be called again" errors
95
+ - Shares the loader instance across all child components via Svelte context
96
+ - Works seamlessly with SvelteKit's SSR (only initialises in the browser)
89
97
 
90
- Here is how you would set it up in your `+page.svelte`:
98
+ **When to use manual initialisation:**
99
+ - Using multiple Google Maps components on the same page
100
+ - Need to load multiple libraries (`maps`, `marker`, `geometry`, etc.)
101
+ - Building a layout that shares map functionality across routes
102
+ - Want centralised error handling for the loader
91
103
 
92
104
  ```javascript
93
- // src/routes/+page.svelte
105
+ // In +layout.svelte or +page.svelte
94
106
  <script lang="ts">
95
- import { onMount } from 'svelte';
96
107
  import { browser } from '$app/environment';
97
108
  import { PlaceAutocomplete } from 'places-autocomplete-svelte';
98
- import { initialiseGMaps, setGMapsContext, getGMapsContext } from 'places-autocomplete-svelte/gmaps';
109
+ import { setGMapsContext, initialiseGMaps, importLibrary } from 'places-autocomplete-svelte/gmaps';
110
+ import { onMount } from 'svelte';
99
111
 
100
- // 1. Set the context for Google Maps. This should be done in the script's top-level scope.
112
+ // 1. Set the context at the top level (must be synchronous)
101
113
  setGMapsContext();
102
114
 
103
- // 2. If we are in the browser, trigger the asynchronous loading process.
115
+ // 2. Initialise the loader in the browser
104
116
  if (browser) {
105
117
  initialiseGMaps({
106
- key: import.meta.env.VITE_PUBLIC_GOOGLE_MAPS_API_KEY,
107
- v: 'weekly'
108
- }).catch((error: any) => {
109
- // ... handle error (already logged in initialiseGMaps)
118
+ key: import.meta.env.VITE_PUBLIC_GOOGLE_MAPS_API_KEY,
119
+ v: 'weekly'
120
+ }).catch((error) => {
121
+ console.error('Failed to initialise Google Maps:', error);
110
122
  });
111
123
  }
112
124
 
113
- // ... rest of your component logic
125
+ // 3. Load additional libraries as needed
126
+ let map: google.maps.Map;
127
+
128
+ onMount(async () => {
129
+ const { Map } = await importLibrary('maps');
130
+ const { AdvancedMarkerElement } = await importLibrary('marker');
131
+
132
+ const mapElement = document.getElementById('map');
133
+ if (mapElement) {
134
+ map = new Map(mapElement, {
135
+ center: { lat: 51.5072, lng: -0.1276 },
136
+ zoom: 10,
137
+ mapId: 'YOUR_MAP_ID'
138
+ });
139
+ }
140
+ });
141
+
142
+ // 4. Handle autocomplete responses
143
+ const handleResponse = (response: PlaceResult) => {
144
+ console.log('Selected:', response.formattedAddress);
145
+ // Update map with selected location
146
+ if (response.location && map) {
147
+ map.setCenter(response.location);
148
+ map.setZoom(15);
149
+ }
150
+ };
151
+
152
+ const handleError = (error: string) => {
153
+ console.error('Error:', error);
154
+ };
114
155
  </script>
115
156
 
116
- <!-- The PlaceAutocomplete component will automatically use the context -->
117
- <!-- You do NOT need to pass the `PUBLIC_GOOGLE_MAPS_API_KEY` prop to the component if you initialise the loader in a parent component.-->
118
- <PlaceAutocomplete onResponse={...} onError={...} />
157
+ <!-- The component automatically uses the shared context -->
158
+ <!-- No need to pass PUBLIC_GOOGLE_MAPS_API_KEY when using manual initialisation -->
159
+ <PlaceAutocomplete
160
+ onResponse={handleResponse}
161
+ onError={handleError}
162
+ />
119
163
 
120
- <!-- You can now use other Google Maps services, e.g., a map -->
121
- <div id="map"></div>
164
+ <div id="map" class="h-96 w-full"></div>
122
165
  ```
123
166
 
167
+ **Available helper functions from `places-autocomplete-svelte/gmaps`:**
168
+
169
+ - `setGMapsContext()` - Creates the shared context (call once at the top level)
170
+ - `getGMapsContext()` - Retrieves the context (returns stores for initialisation state and errors)
171
+ - `hasGMapsContext()` - Checks if context exists (useful for conditional logic)
172
+ - `initialiseGMaps(options)` - Initialises the loader with your API key and options
173
+ - `initialiseGMapsNoContext(options)` - Initialises without context (for edge cases)
174
+ - `importLibrary(library)` - Dynamically imports Google Maps libraries
175
+
124
176
  ## Security
125
177
 
126
178
  ### API Key Security
@@ -148,12 +200,14 @@ This component is built to be accessible and follows the [WAI-ARIA Authoring Pra
148
200
 
149
201
  | Prop | Type | Required | Default | Description |
150
202
  | :--- | :--- | :--- | :--- | :--- |
151
- | `PUBLIC_GOOGLE_MAPS_API_KEY` | `string` | Yes | - | Your restricted Google Maps API Key. Not required if the loader has been initialised in a parent component. |
152
- | `fetchFields` | `string[]` | No | `['formattedAddress', 'addressComponents']` | Place Data Fields to request. **Affects API cost.** |
153
- | `requestParams` | `Partial<RequestParams>` | No | `{ inputOffset: 3, ... }` | Parameters for the Autocomplete API request. |
154
- | `options` | `Partial<ComponentOptions>` | No | `{ debounce: 100, ... }` | Options to control component behavior and appearance. |
155
- | `onResponse` | `(response: PlaceResult) => void` | Yes | - | Callback triggered with the selected place details. |
156
- | `onError` | `(error: string) => void` | Yes | - | Callback triggered when an error occurs. |
203
+ | `PUBLIC_GOOGLE_MAPS_API_KEY` | `string` | No* | - | Your Google Maps API Key. **Required for automatic initialisation.** Optional if you've initialised the loader in a parent component using `initialiseGMaps()`. |
204
+ | `onResponse` | `(response: PlaceResult) => void` | Yes | - | Callback triggered when a user selects a place. Receives the full place details object. |
205
+ | `onError` | `(error: string) => void` | Yes | - | Callback triggered when an error occurs (API loading, network issues, etc.). |
206
+ | `fetchFields` | `string[]` | No | `['formattedAddress', 'addressComponents']` | Place Data Fields to request from the API. See [Place Data Fields](https://developers.google.com/maps/documentation/javascript/place-data-fields). **Affects API billing.** |
207
+ | `requestParams` | `Partial<RequestParams>` | No | `{ inputOffset: 3 }` | Parameters for the Autocomplete API request (language, region, location bias, etc.). See RequestParams interface. |
208
+ | `options` | `Partial<ComponentOptions>` | No | `{ debounce: 100 }` | UI and behavior options (placeholder, debounce delay, distance display, custom classes, etc.). See ComponentOptions interface. |
209
+
210
+ *Either `PUBLIC_GOOGLE_MAPS_API_KEY` prop OR manual initialisation with `initialiseGMaps()` is required.
157
211
 
158
212
  ## Component Methods (Imperative API)
159
213
 
@@ -241,7 +295,36 @@ const options = {
241
295
 
242
296
  ## TypeScript
243
297
 
244
- This component is written in TypeScript. Import types from `places-autocomplete-svelte/interfaces` and helpers from `places-autocomplete-svelte/gmaps`.
298
+ This component is written in TypeScript with full type definitions included.
299
+
300
+ **Available imports:**
301
+
302
+ ```typescript
303
+ // Component
304
+ import { PlaceAutocomplete } from 'places-autocomplete-svelte';
305
+
306
+ // Types and interfaces
307
+ import type {
308
+ PlaceResult,
309
+ ComponentOptions,
310
+ RequestParams,
311
+ FormattedAddress,
312
+ ComponentClasses,
313
+ Props
314
+ } from 'places-autocomplete-svelte/interfaces';
315
+
316
+ // Google Maps loader helpers
317
+ import {
318
+ setGMapsContext,
319
+ getGMapsContext,
320
+ hasGMapsContext,
321
+ initialiseGMaps,
322
+ initialiseGMapsNoContext,
323
+ importLibrary,
324
+ type GMapsContext,
325
+ type APIOptions
326
+ } from 'places-autocomplete-svelte/gmaps';
327
+ ```
245
328
 
246
329
  ## Google Places API & Billing
247
330
 
@@ -111,6 +111,21 @@
111
111
  return request;
112
112
  }
113
113
 
114
+ // Helper function to find a specific address component
115
+ const getAddressComponent = (response: { addressComponents: any[]; },type: string) =>
116
+ response.addressComponents?.find((c: { types: string | string[]; }) => c.types.includes(type))?.longText || '';
117
+
118
+ // Helper function to get secondary text from address components
119
+ const getSecondaryText = (place: { addressComponents: any[]; }) => {
120
+ const locality = getAddressComponent(place, 'locality');
121
+ const adminArea = getAddressComponent(place, 'administrative_area_level_1');
122
+ const postalCode = getAddressComponent(place, 'postal_code');
123
+ const country = getAddressComponent(place, 'country');
124
+
125
+ let components = [locality, adminArea, country, postalCode].filter(Boolean);
126
+ return components.join(', ');
127
+ };
128
+
114
129
  /**
115
130
  * Make request and get autocomplete suggestions.
116
131
  * @param event
@@ -142,10 +157,16 @@
142
157
  // Clear previous results
143
158
  results = [];
144
159
 
160
+
161
+
145
162
  // ieterate over suggestions and add results to an array
146
163
  for (const suggestion of suggestions) {
147
164
  // get prediction text
148
- const predictionText = suggestion.placePrediction.text;
165
+ //console.log(suggestion.placePrediction.toPlace());
166
+ let place = suggestions[0].placePrediction.toPlace();
167
+ await place.fetchFields({fields: ["addressComponents"]});
168
+
169
+ const predictionText = suggestion.placePrediction.mainText;
149
170
  const originalText = predictionText.text;
150
171
  // Array of objects with startOffset, endOffset
151
172
  const matches = predictionText.matches;
@@ -162,14 +183,16 @@
162
183
  highlightedText = createHighlightedSegments(originalText, matches);
163
184
 
164
185
  results.push({
165
- place: suggestion.placePrediction.toPlace(),
166
- text: highlightedText,
186
+ place: place,
187
+ mainText: highlightedText,
188
+ secondaryText: getSecondaryText(place),
167
189
  distance: formatDistance(
168
190
  suggestion.placePrediction.distanceMeters,
169
191
  options.distance_units ?? 'km'
170
192
  )
171
193
  });
172
194
  }
195
+ //console.log('Autocomplete suggestions:', results);
173
196
  } catch (e: any) {
174
197
  onError((e.name || 'An error occurred') + ' - ' + (e.message || 'see console for details.'));
175
198
  }
@@ -383,21 +406,47 @@
383
406
  i === currentSuggestion && options.classes?.li_div_current
384
407
  ]}
385
408
  >
386
- <p
387
- class={[
388
- i === currentSuggestion && options.classes?.li_current,
389
- options.classes?.li_div_one_p
390
- ]}
391
- >
392
- {#each p.text as segment}
393
- {#if segment.highlighted}
394
- <span class={options.classes?.highlight ?? 'font-bold'}>{segment.text}</span
395
- >
396
- {:else}
397
- {segment.text}
398
- {/if}
399
- {/each}
400
- </p>
409
+ {#if options.classes?.map_pin_icon}
410
+ <svg
411
+ xmlns="http://www.w3.org/2000/svg"
412
+ width="24"
413
+ height="24"
414
+ viewBox="0 0 24 24"
415
+ fill="none"
416
+ stroke="currentColor"
417
+ stroke-width="2"
418
+ stroke-linecap="round"
419
+ stroke-linejoin="round"
420
+ class="size-5">{@html options.classes.map_pin_icon}</svg
421
+ >
422
+ {/if}
423
+
424
+ <div class={[options.classes?.li_div_p_container ?? '']}>
425
+ <p
426
+ class={[
427
+ i === currentSuggestion && options.classes?.li_current,
428
+ options.classes?.li_div_one_p
429
+ ]}
430
+ >
431
+ {#each p.mainText as segment}
432
+ {#if segment.highlighted}
433
+ <span class={options.classes?.highlight ?? 'font-bold'}
434
+ >{segment.text}</span
435
+ >
436
+ {:else}
437
+ {segment.text}
438
+ {/if}
439
+ {/each}
440
+ </p>
441
+ <p
442
+ class={[
443
+ i === currentSuggestion && options.classes?.li_current,
444
+ options.classes?.li_div_one_p_secondaryText
445
+ ]}
446
+ >
447
+ {p.secondaryText}
448
+ </p>
449
+ </div>
401
450
  </div>
402
451
  </div>
403
452
  {#if options.distance && p.distance}
package/dist/helpers.js CHANGED
@@ -302,12 +302,15 @@ export const componentClasses = {
302
302
  kbd_active: 'bg-indigo-500 text-white',
303
303
  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',
304
304
  li: 'z-50 cursor-default select-none py-2 px-2 lg:px-4 text-gray-900 hover:bg-indigo-500 hover:text-white',
305
- li_current: 'bg-indigo-500',
305
+ li_current: 'bg-indigo-500 !text-white',
306
306
  li_a: 'block w-full flex justify-between',
307
- li_a_current: 'text-white',
307
+ li_a_current: '!text-white',
308
308
  li_div_container: 'flex min-w-0 gap-x-4',
309
- li_div_one: 'min-w-0 flex-auto',
310
- li_div_one_p: 'text-sm/6 ',
309
+ li_div_one: 'min-w-0 flex-auto flex gap-x-2 justify-center items-center',
310
+ li_div_p_container: 'min-w-0 flex-auto',
311
+ li_div_one_p: 'text-sm/6 text-left',
312
+ map_pin_icon: '<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"/>',
313
+ li_div_one_p_secondaryText: 'text-xs text-left text-gray-500 leading-2',
311
314
  li_div_two: 'shrink-0 flex flex-col items-end min-w-16',
312
315
  li_div_two_p: 'mt-1 text-xs/5',
313
316
  highlight: 'font-bold',
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "places-autocomplete-svelte",
3
3
  "license": "MIT",
4
- "version": "2.2.17",
4
+ "version": "2.2.18",
5
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",
@@ -75,38 +75,38 @@
75
75
  "svelte": "^5.0.0"
76
76
  },
77
77
  "devDependencies": {
78
- "@sveltejs/adapter-auto": "^6.1.1",
78
+ "@sveltejs/adapter-auto": "^7.0.0",
79
79
  "@sveltejs/adapter-cloudflare": "^7.2.4",
80
- "@sveltejs/kit": "^2.46.4",
81
- "@sveltejs/package": "^2.5.4",
80
+ "@sveltejs/kit": "^2.49.0",
81
+ "@sveltejs/package": "^2.5.7",
82
82
  "@sveltejs/vite-plugin-svelte": "^6.2.1",
83
- "@tailwindcss/postcss": "^4.1.14",
83
+ "@tailwindcss/postcss": "^4.1.17",
84
84
  "@tailwindcss/typography": "^0.5.19",
85
- "@tailwindcss/vite": "^4.1.14",
85
+ "@tailwindcss/vite": "^4.1.17",
86
86
  "@types/eslint": "^9.6.1",
87
87
  "@types/google.maps": "^3.58.1",
88
- "@types/node": "^24.7.1",
89
- "autoprefixer": "^10.4.21",
90
- "eslint": "^9.37.0",
88
+ "@types/node": "^24.10.1",
89
+ "autoprefixer": "^10.4.22",
90
+ "eslint": "^9.39.1",
91
91
  "eslint-config-prettier": "^10.1.8",
92
- "eslint-plugin-svelte": "^3.12.4",
93
- "globals": "^16.4.0",
92
+ "eslint-plugin-svelte": "^3.13.0",
93
+ "globals": "^16.5.0",
94
94
  "postcss": "^8.5.6",
95
- "prettier": "^3.6.2",
95
+ "prettier": "^3.7.3",
96
96
  "prettier-plugin-svelte": "^3.4.0",
97
- "publint": "^0.3.14",
98
- "svelte": "^5.39.11",
99
- "svelte-check": "^4.3.3",
100
- "tailwindcss": "^4.1.14",
97
+ "publint": "^0.3.15",
98
+ "svelte": "^5.45.2",
99
+ "svelte-check": "^4.3.4",
100
+ "tailwindcss": "^4.1.17",
101
101
  "tslib": "^2.8.1",
102
102
  "typescript": "^5.9.3",
103
- "typescript-eslint": "^8.46.0",
104
- "vite": "^7.1.9"
103
+ "typescript-eslint": "^8.48.0",
104
+ "vite": "^7.2.4"
105
105
  },
106
106
  "svelte": "./dist/index.js",
107
107
  "types": "./dist/PlaceAutocomplete.svelte.d.ts",
108
108
  "type": "module",
109
109
  "dependencies": {
110
- "@googlemaps/js-api-loader": "^2.0.1"
110
+ "@googlemaps/js-api-loader": "^2.0.2"
111
111
  }
112
112
  }