places-autocomplete-svelte 2.2.16 → 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,115 +57,122 @@ yarn add places-autocomplete-svelte
53
57
 
54
58
  ## Usage
55
59
 
56
- Provide your Google Maps API key to the component. It will automatically handle loading the required `places` library.
60
+ ### Basic Usage (Automatic Initialisation)
57
61
 
58
- ```javascript
59
- <script lang="ts">
60
- import { PlaceAutocomplete } from 'places-autocomplete-svelte';
61
- import type { PlaceResult, ComponentOptions, RequestParams } from 'places-autocomplete-svelte/interfaces';
62
+ For simple use cases, just pass your API key to the component. It will automatically handle the Google Maps loader initialisation:
62
63
 
63
- // Get API Key securely (e.g., from environment variables)
64
- const PUBLIC_GOOGLE_MAPS_API_KEY = import.meta.env.VITE_PUBLIC_GOOGLE_MAPS_API_KEY;
65
64
 
66
- // --- Event Handlers ---
67
- let fullResponse: PlaceResult | null = $state(null);
68
- let placesError = $state('');
65
+ ```javascript
66
+ <script lang="ts">
67
+ import { PlaceAutocomplete } from 'places-autocomplete-svelte';
68
+ import type { PlaceResult } from 'places-autocomplete-svelte/interfaces';
69
69
 
70
- const handleResponse = (response: PlaceResult) => {
71
- console.log('Place Selected:', response);
72
- fullResponse = response;
73
- placesError = ''; // Clear previous errors
74
- };
70
+ // Get API Key securely (e.g., from environment variables)
71
+ const PUBLIC_GOOGLE_MAPS_API_KEY = import.meta.env.VITE_PUBLIC_GOOGLE_MAPS_API_KEY;
75
72
 
76
- const handleError = (error: string) => {
77
- console.error('Places Autocomplete Error:', error);
78
- placesError = error;
79
- fullResponse = null; // Clear previous results
80
- };
73
+ const handleResponse = (response: PlaceResult) => {
74
+ console.log('Selected:', response.formattedAddress);
75
+ };
81
76
 
82
- // --- Configuration (Optional) ---
83
- const requestParams: Partial<RequestParams> = $state({
84
- region: 'GB',
85
- language: 'en-GB',
86
- });
87
- const fetchFields: string[] = $state(['formattedAddress', 'addressComponents', 'displayName']);
88
- const options: Partial<ComponentOptions> = $state({
89
- placeholder: 'Start typing your address...',
90
- debounce: 200,
91
- classes: {
92
- input: 'my-custom-input-class border-blue-500',
93
- highlight: 'bg-yellow-200 text-black',
94
- },
95
- clear_input: false,
96
- });
77
+ const handleError = (error: string) => {
78
+ console.error('Error:', error);
79
+ };
97
80
  </script>
98
81
 
99
- {#if placesError}
100
- <div class="error-message" role="alert">
101
- Error: {placesError}
102
- </div>
103
- {/if}
104
-
105
- <PlaceAutocomplete
82
+ <PlaceAutocomplete
106
83
  {PUBLIC_GOOGLE_MAPS_API_KEY}
107
- {requestParams}
108
- {fetchFields}
109
- {options}
110
- onResponse={handleResponse}
111
- onError={handleError}
84
+ onResponse={handleResponse}
85
+ onError={handleError}
112
86
  />
87
+ ```
113
88
 
114
- {#if fullResponse}
115
- <h2>Selected Place Details:</h2>
116
- <pre>{JSON.stringify(fullResponse, null, 2)}</pre>
117
- {/if}
89
+ ### Advanced Usage (Manual Initialisation)
118
90
 
119
- <style>
120
- :global(.my-custom-input-class) {
121
- padding: 0.75rem;
122
- border-radius: 0.25rem;
123
- width: 100%;
124
- }
125
- .error-message {
126
- color: red;
127
- margin-bottom: 1rem;
128
- }
129
- </style>
130
- ```
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:
131
92
 
132
- ### Advanced: Using with other Google Maps Libraries
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)
133
97
 
134
- You can reuse the shared Google Maps loader created by the `PlaceAutocomplete` component to load other libraries (like `maps`). Because the loader instance is shared, you can access it from any other component to load additional libraries without causing conflicts.
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
135
103
 
136
- The `PlaceAutocomplete` component only loads the `places` library by default.
137
104
  ```javascript
138
- // In a parent component, e.g., src/routes/+page.svelte
105
+ // In +layout.svelte or +page.svelte
139
106
  <script lang="ts">
140
- import { onMount } from 'svelte';
141
- import { getGMapsLoader } from 'places-autocomplete-svelte/gmaps';
142
- import PlaceAutocomplete from '$lib/PlaceAutocomplete.svelte';
143
-
144
- const PUBLIC_GOOGLE_MAPS_API_KEY = import.meta.env.VITE_PUBLIC_GOOGLE_MAPS_API_KEY;
145
-
146
- // Pre-initialise the loader with all libraries needed for this page.
147
- onMount(async () => {
148
- const loader = getGMapsLoader(PUBLIC_GOOGLE_MAPS_API_KEY);
149
- const { Map } = await loader.importLibrary('maps');
150
- ...
151
- });
107
+ import { browser } from '$app/environment';
108
+ import { PlaceAutocomplete } from 'places-autocomplete-svelte';
109
+ import { setGMapsContext, initialiseGMaps, importLibrary } from 'places-autocomplete-svelte/gmaps';
110
+ import { onMount } from 'svelte';
111
+
112
+ // 1. Set the context at the top level (must be synchronous)
113
+ setGMapsContext();
114
+
115
+ // 2. Initialise the loader in the browser
116
+ if (browser) {
117
+ 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);
122
+ });
123
+ }
124
+
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
+ };
152
155
  </script>
153
156
 
154
- <!-- The component will now use the loader you created above -->
155
- <PlaceAutocomplete
156
- {PUBLIC_GOOGLE_MAPS_API_KEY}
157
- onResponse={...}
158
- 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}
159
162
  />
160
163
 
161
- <!-- You can now use other Google Maps services, e.g., a map -->
162
- <div id="map"></div>
164
+ <div id="map" class="h-96 w-full"></div>
163
165
  ```
164
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
+
165
176
  ## Security
166
177
 
167
178
  ### API Key Security
@@ -189,12 +200,14 @@ This component is built to be accessible and follows the [WAI-ARIA Authoring Pra
189
200
 
190
201
  | Prop | Type | Required | Default | Description |
191
202
  | :--- | :--- | :--- | :--- | :--- |
192
- | `PUBLIC_GOOGLE_MAPS_API_KEY` | `string` | Yes | - | Your restricted Google Maps API Key. |
193
- | `fetchFields` | `string[]` | No | `['formattedAddress', 'addressComponents']` | Place Data Fields to request. **Affects API cost.** |
194
- | `requestParams` | `Partial<RequestParams>` | No | `{ inputOffset: 3, ... }` | Parameters for the Autocomplete API request. |
195
- | `options` | `Partial<ComponentOptions>` | No | `{ debounce: 100, ... }` | Options to control component behavior and appearance. |
196
- | `onResponse` | `(response: PlaceResult) => void` | Yes | - | Callback triggered with the selected place details. |
197
- | `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.
198
211
 
199
212
  ## Component Methods (Imperative API)
200
213
 
@@ -282,7 +295,36 @@ const options = {
282
295
 
283
296
  ## TypeScript
284
297
 
285
- 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
+ ```
286
328
 
287
329
  ## Google Places API & Billing
288
330
 
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { onMount } from 'svelte';
3
3
  import type { PlaceResult, Props } from './interfaces.js';
4
+ import { getGMapsContext, hasGMapsContext, importLibrary, initialiseGMapsNoContext, type GMapsContext } from './gmaps.js';
4
5
  import {
5
6
  validateOptions,
6
7
  validateRequestParams,
@@ -10,7 +11,13 @@
10
11
  debounce
11
12
  } from './helpers.js';
12
13
 
13
- import { getGMapsLoader, type GMapsLoaderType } from './gmaps.js';
14
+
15
+ let gmaps: GMapsContext | undefined;
16
+ // Get the context synchronously. This is safe.
17
+ if (hasGMapsContext()) {
18
+ gmaps = getGMapsContext();
19
+ }
20
+
14
21
 
15
22
 
16
23
  let {
@@ -18,7 +25,7 @@
18
25
  * By default using SKU: Place Detals (Location Only) - 0.005 USD per each
19
26
  * @see https://developers.google.com/maps/documentation/javascript/usage-and-billing#location-placedetails
20
27
  */
21
- PUBLIC_GOOGLE_MAPS_API_KEY,
28
+ PUBLIC_GOOGLE_MAPS_API_KEY='',
22
29
  fetchFields,
23
30
  options,
24
31
  onResponse = $bindable((response: PlaceResult) => {}),
@@ -36,7 +43,6 @@
36
43
  fetchFields = validateFetchFields(fetchFields);
37
44
  //console.log(fetchFields);
38
45
 
39
-
40
46
  let kbdAction = $state(''); // 'up', 'down', or 'escape'
41
47
 
42
48
  // Local variables
@@ -44,7 +50,6 @@
44
50
  let currentSuggestion = $state(-1);
45
51
  let results: any[] = $state([]);
46
52
  let placesApi: { [key: string]: any } = {};
47
- let loader: GMapsLoaderType;
48
53
 
49
54
  //https://developers.google.com/maps/documentation/javascript/reference/autocomplete-data
50
55
  // validate requestParams
@@ -106,6 +111,21 @@
106
111
  return request;
107
112
  }
108
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
+
109
129
  /**
110
130
  * Make request and get autocomplete suggestions.
111
131
  * @param event
@@ -137,10 +157,16 @@
137
157
  // Clear previous results
138
158
  results = [];
139
159
 
160
+
161
+
140
162
  // ieterate over suggestions and add results to an array
141
163
  for (const suggestion of suggestions) {
142
164
  // get prediction text
143
- 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;
144
170
  const originalText = predictionText.text;
145
171
  // Array of objects with startOffset, endOffset
146
172
  const matches = predictionText.matches;
@@ -157,14 +183,16 @@
157
183
  highlightedText = createHighlightedSegments(originalText, matches);
158
184
 
159
185
  results.push({
160
- place: suggestion.placePrediction.toPlace(),
161
- text: highlightedText,
186
+ place: place,
187
+ mainText: highlightedText,
188
+ secondaryText: getSecondaryText(place),
162
189
  distance: formatDistance(
163
190
  suggestion.placePrediction.distanceMeters,
164
191
  options.distance_units ?? 'km'
165
192
  )
166
193
  });
167
194
  }
195
+ //console.log('Autocomplete suggestions:', results);
168
196
  } catch (e: any) {
169
197
  onError((e.name || 'An error occurred') + ' - ' + (e.message || 'see console for details.'));
170
198
  }
@@ -213,6 +241,8 @@
213
241
  * Initialize the Google Maps JavaScript API Loader.
214
242
  */
215
243
  onMount(async (): Promise<void> => {
244
+
245
+
216
246
  if (isDefaultOnResponse) {
217
247
  console.warn(
218
248
  'PlaceAutocomplete: The `onResponse` callback has not been provided. Selected place data will not be handled. See documentation for usage.'
@@ -224,16 +254,26 @@
224
254
  }
225
255
 
226
256
  try {
227
- loader = getGMapsLoader(PUBLIC_GOOGLE_MAPS_API_KEY);
228
- } catch (e: any) {
229
- onError(
230
- (e.name || 'An error occurred') + ' - ' + (e.message || 'Error loading Google Maps API')
231
- );
232
- }
257
+ // Await the promise that was stored in the context by the parent.
258
+ // If the parent has already finished, this resolves immediately.
259
+ // If the parent is still loading, this will wait.
260
+ if(typeof gmaps !== 'undefined' && gmaps) {
261
+ await gmaps?.initializationPromise;
262
+ }else{
263
+
264
+ // Check if the API key is provided
265
+ if(PUBLIC_GOOGLE_MAPS_API_KEY === '' || !PUBLIC_GOOGLE_MAPS_API_KEY) {
266
+ throw new Error('Google Maps API key is required. Please provide a valid API key.');
267
+ }
268
+
269
+ // No context available, initialize without context
270
+ // This will load the Google Maps script
271
+ // and set up the necessary objects
272
+ // for places API usage.
273
+ await initialiseGMapsNoContext({key: PUBLIC_GOOGLE_MAPS_API_KEY, 'v': 'weekly'});
274
+ }
233
275
 
234
- try {
235
- const { AutocompleteSessionToken, AutocompleteSuggestion } =
236
- await loader.importLibrary('places');
276
+ const { AutocompleteSessionToken, AutocompleteSuggestion } = await importLibrary('places');
237
277
 
238
278
  placesApi.AutocompleteSessionToken = AutocompleteSessionToken;
239
279
  placesApi.AutocompleteSuggestion = AutocompleteSuggestion;
@@ -366,21 +406,47 @@
366
406
  i === currentSuggestion && options.classes?.li_div_current
367
407
  ]}
368
408
  >
369
- <p
370
- class={[
371
- i === currentSuggestion && options.classes?.li_current,
372
- options.classes?.li_div_one_p
373
- ]}
374
- >
375
- {#each p.text as segment}
376
- {#if segment.highlighted}
377
- <span class={options.classes?.highlight ?? 'font-bold'}>{segment.text}</span
378
- >
379
- {:else}
380
- {segment.text}
381
- {/if}
382
- {/each}
383
- </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>
384
450
  </div>
385
451
  </div>
386
452
  {#if options.distance && p.distance}
package/dist/gmaps.d.ts CHANGED
@@ -1,11 +1,34 @@
1
- import * as GMaps from '@googlemaps/js-api-loader';
1
+ import { type Writable } from 'svelte/store';
2
+ import { importLibrary, type APIOptions } from "@googlemaps/js-api-loader";
3
+ interface GMapsContext {
4
+ isInitialized: Writable<boolean>;
5
+ error: Writable<Error | null>;
6
+ initializationPromise: Promise<void> | null;
7
+ }
8
+ export type { GMapsContext, APIOptions };
2
9
  /**
3
- * Defines the shape of the context object that will be shared.
4
- * This can be expanded if you need to share more than just the loader.
10
+ * Creates and sets the Google Maps context with writable stores.
11
+ * This is synchronous and should be called once in a top-level component's script.
5
12
  */
6
- export type GMapsLoaderType = GMaps.Loader;
13
+ export declare function setGMapsContext(): void;
7
14
  /**
8
- * Gets the Google Maps Loader instance from Svelte's context.
9
- * @returns {typeof Loader}
15
+ * Retrieves the shared Google Maps context.
16
+ * @returns {GMapsContext} The stores for initialization status and errors.
10
17
  */
11
- export declare const getGMapsLoader: (PUBLIC_GOOGLE_MAPS_API_KEY: string, version?: string | undefined) => GMaps.Loader;
18
+ export declare function getGMapsContext(): GMapsContext;
19
+ export declare function hasGMapsContext(): boolean;
20
+ /**
21
+ * Asynchronously initializes the Google Maps loader using the provided context.
22
+ * This function is idempotent and safe to be called multiple times.
23
+ * @param context - The GMapsContext object.
24
+ * @param options - The options for the JS API loader, including your API key.
25
+ * @returns {Promise<void>}
26
+ */
27
+ export declare function initialiseGMaps(options: APIOptions): Promise<void>;
28
+ /**
29
+ * Initializes the Google Maps API without using the context.
30
+ * @param options The options for the JS API loader, including your API key.
31
+ * @returns A promise that resolves when the API is initialized.
32
+ */
33
+ export declare function initialiseGMapsNoContext(options: APIOptions): Promise<void>;
34
+ export { importLibrary };
package/dist/gmaps.js CHANGED
@@ -1,23 +1,87 @@
1
- import { getContext, setContext, hasContext } from 'svelte';
2
- import * as GMaps from '@googlemaps/js-api-loader';
3
- const { Loader } = GMaps;
1
+ import { getContext, setContext } from 'svelte';
2
+ import { writable, get } from 'svelte/store';
3
+ import { setOptions, importLibrary } from "@googlemaps/js-api-loader";
4
+ const LOADER_CONTEXT_KEY = Symbol('gmaps-loader');
4
5
  /**
5
- * A unique key for setting and getting the Google Maps Loader instance from Svelte's context.
6
- * This allows multiple components to share a single loader instance.
6
+ * Creates and sets the Google Maps context with writable stores.
7
+ * This is synchronous and should be called once in a top-level component's script.
7
8
  */
8
- const gmapsContextKey = Symbol('pacgmaps');
9
+ export function setGMapsContext() {
10
+ // Only set the context if it doesn't already exist.
11
+ if (getContext(LOADER_CONTEXT_KEY))
12
+ return;
13
+ setContext(LOADER_CONTEXT_KEY, {
14
+ isInitialized: writable(false),
15
+ error: writable(null),
16
+ initializationPromise: null, // Will be set by the loader function
17
+ });
18
+ }
9
19
  /**
10
- * Gets the Google Maps Loader instance from Svelte's context.
11
- * @returns {typeof Loader}
20
+ * Retrieves the shared Google Maps context.
21
+ * @returns {GMapsContext} The stores for initialization status and errors.
12
22
  */
13
- export const getGMapsLoader = (PUBLIC_GOOGLE_MAPS_API_KEY, version) => {
14
- version = version || 'weekly';
15
- if (!hasContext(gmapsContextKey)) {
16
- const loader = new Loader({
17
- apiKey: PUBLIC_GOOGLE_MAPS_API_KEY,
18
- version: version,
19
- });
20
- setContext(gmapsContextKey, loader);
23
+ export function getGMapsContext() {
24
+ const context = getContext(LOADER_CONTEXT_KEY);
25
+ if (!context) {
26
+ throw new Error('Google Maps context not found. Call setGMapsContext in a parent component.');
21
27
  }
22
- return getContext(gmapsContextKey);
23
- };
28
+ return context;
29
+ }
30
+ export function hasGMapsContext() {
31
+ const context = getContext(LOADER_CONTEXT_KEY);
32
+ return !!context;
33
+ }
34
+ /**
35
+ * Asynchronously initializes the Google Maps loader using the provided context.
36
+ * This function is idempotent and safe to be called multiple times.
37
+ * @param context - The GMapsContext object.
38
+ * @param options - The options for the JS API loader, including your API key.
39
+ * @returns {Promise<void>}
40
+ */
41
+ export async function initialiseGMaps(options) {
42
+ // Get the context internally
43
+ const context = getGMapsContext();
44
+ // If the promise already exists, just await it. Don't re-initialize.
45
+ if (context.initializationPromise) {
46
+ return context.initializationPromise;
47
+ }
48
+ // If it's already marked as initialized (e.g., from a previous page navigation), resolve immediately.
49
+ if (get(context.isInitialized)) {
50
+ return Promise.resolve();
51
+ }
52
+ // Create the promise and store it in the context object.
53
+ context.initializationPromise = new Promise((resolve, reject) => {
54
+ try {
55
+ setOptions(options); // Await the setOptions which returns a promise
56
+ context.isInitialized.set(true);
57
+ resolve();
58
+ }
59
+ catch (e) {
60
+ const error = e instanceof Error ? e : new Error(String(e));
61
+ context.error.set(error);
62
+ console.error("Failed to set Google Maps API options.", error);
63
+ reject(error);
64
+ }
65
+ });
66
+ return context.initializationPromise;
67
+ }
68
+ /**
69
+ * Initializes the Google Maps API without using the context.
70
+ * @param options The options for the JS API loader, including your API key.
71
+ * @returns A promise that resolves when the API is initialized.
72
+ */
73
+ export function initialiseGMapsNoContext(options) {
74
+ return new Promise((resolve, reject) => {
75
+ try {
76
+ setOptions(options);
77
+ resolve();
78
+ }
79
+ catch (e) {
80
+ const error = e instanceof Error ? e : new Error(String(e));
81
+ console.error("Failed to set Google Maps API options.", error);
82
+ reject(error);
83
+ }
84
+ });
85
+ }
86
+ // Re-export importLibrary for components to use.
87
+ export { importLibrary };
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',
@@ -63,7 +63,7 @@ export interface FormattedAddress {
63
63
  postcode: string;
64
64
  }
65
65
  export interface Props {
66
- PUBLIC_GOOGLE_MAPS_API_KEY: string;
66
+ PUBLIC_GOOGLE_MAPS_API_KEY?: string;
67
67
  options?: ComponentOptions;
68
68
  fetchFields?: string[];
69
69
  libraries?: string[];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "places-autocomplete-svelte",
3
3
  "license": "MIT",
4
- "version": "2.2.16",
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",
@@ -55,7 +55,7 @@
55
55
  "./gmaps": {
56
56
  "types": "./dist/gmaps.d.ts",
57
57
  "svelte": "./dist/gmaps.js"
58
- },
58
+ },
59
59
  ".": {
60
60
  "types": "./dist/index.d.ts",
61
61
  "svelte": "./dist/index.js",
@@ -75,38 +75,38 @@
75
75
  "svelte": "^5.0.0"
76
76
  },
77
77
  "devDependencies": {
78
- "@sveltejs/adapter-auto": "^6.1.0",
79
- "@sveltejs/adapter-cloudflare": "^7.2.2",
80
- "@sveltejs/kit": "^2.36.1",
81
- "@sveltejs/package": "^2.5.0",
82
- "@sveltejs/vite-plugin-svelte": "^6.1.3",
83
- "@tailwindcss/postcss": "^4.1.12",
84
- "@tailwindcss/typography": "^0.5.16",
85
- "@tailwindcss/vite": "^4.1.12",
78
+ "@sveltejs/adapter-auto": "^7.0.0",
79
+ "@sveltejs/adapter-cloudflare": "^7.2.4",
80
+ "@sveltejs/kit": "^2.49.0",
81
+ "@sveltejs/package": "^2.5.7",
82
+ "@sveltejs/vite-plugin-svelte": "^6.2.1",
83
+ "@tailwindcss/postcss": "^4.1.17",
84
+ "@tailwindcss/typography": "^0.5.19",
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.3.0",
89
- "autoprefixer": "^10.4.21",
90
- "eslint": "^9.34.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.11.0",
93
- "globals": "^16.3.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.12",
98
- "svelte": "^5.38.2",
99
- "svelte-check": "^4.3.1",
100
- "tailwindcss": "^4.1.12",
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
- "typescript": "^5.9.2",
103
- "typescript-eslint": "^8.40.0",
104
- "vite": "^7.1.3"
102
+ "typescript": "^5.9.3",
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": "^1.16.10"
110
+ "@googlemaps/js-api-loader": "^2.0.2"
111
111
  }
112
112
  }