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 +136 -94
- package/dist/PlaceAutocomplete.svelte +97 -31
- package/dist/gmaps.d.ts +30 -7
- package/dist/gmaps.js +82 -18
- package/dist/helpers.js +7 -4
- package/dist/interfaces.d.ts +1 -1
- package/package.json +24 -24
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
|
-
|
|
60
|
+
### Basic Usage (Automatic Initialisation)
|
|
57
61
|
|
|
58
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
71
|
-
|
|
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
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
fullResponse = null; // Clear previous results
|
|
80
|
-
};
|
|
73
|
+
const handleResponse = (response: PlaceResult) => {
|
|
74
|
+
console.log('Selected:', response.formattedAddress);
|
|
75
|
+
};
|
|
81
76
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
108
|
-
{
|
|
109
|
-
{options}
|
|
110
|
-
onResponse={handleResponse}
|
|
111
|
-
onError={handleError}
|
|
84
|
+
onResponse={handleResponse}
|
|
85
|
+
onError={handleError}
|
|
112
86
|
/>
|
|
87
|
+
```
|
|
113
88
|
|
|
114
|
-
|
|
115
|
-
<h2>Selected Place Details:</h2>
|
|
116
|
-
<pre>{JSON.stringify(fullResponse, null, 2)}</pre>
|
|
117
|
-
{/if}
|
|
89
|
+
### Advanced Usage (Manual Initialisation)
|
|
118
90
|
|
|
119
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
105
|
+
// In +layout.svelte or +page.svelte
|
|
139
106
|
<script lang="ts">
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
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` |
|
|
193
|
-
| `
|
|
194
|
-
| `
|
|
195
|
-
| `
|
|
196
|
-
| `
|
|
197
|
-
| `
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
161
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
|
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
|
-
*
|
|
4
|
-
* This
|
|
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
|
|
13
|
+
export declare function setGMapsContext(): void;
|
|
7
14
|
/**
|
|
8
|
-
*
|
|
9
|
-
* @returns {
|
|
15
|
+
* Retrieves the shared Google Maps context.
|
|
16
|
+
* @returns {GMapsContext} The stores for initialization status and errors.
|
|
10
17
|
*/
|
|
11
|
-
export declare
|
|
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
|
|
2
|
-
import
|
|
3
|
-
|
|
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
|
-
*
|
|
6
|
-
* This
|
|
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
|
-
|
|
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
|
-
*
|
|
11
|
-
* @returns {
|
|
20
|
+
* Retrieves the shared Google Maps context.
|
|
21
|
+
* @returns {GMapsContext} The stores for initialization status and errors.
|
|
12
22
|
*/
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
if (!
|
|
16
|
-
|
|
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
|
|
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
|
-
|
|
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/dist/interfaces.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "places-autocomplete-svelte",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "2.2.
|
|
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": "^
|
|
79
|
-
"@sveltejs/adapter-cloudflare": "^7.2.
|
|
80
|
-
"@sveltejs/kit": "^2.
|
|
81
|
-
"@sveltejs/package": "^2.5.
|
|
82
|
-
"@sveltejs/vite-plugin-svelte": "^6.1
|
|
83
|
-
"@tailwindcss/postcss": "^4.1.
|
|
84
|
-
"@tailwindcss/typography": "^0.5.
|
|
85
|
-
"@tailwindcss/vite": "^4.1.
|
|
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.
|
|
89
|
-
"autoprefixer": "^10.4.
|
|
90
|
-
"eslint": "^9.
|
|
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.
|
|
93
|
-
"globals": "^16.
|
|
92
|
+
"eslint-plugin-svelte": "^3.13.0",
|
|
93
|
+
"globals": "^16.5.0",
|
|
94
94
|
"postcss": "^8.5.6",
|
|
95
|
-
"prettier": "^3.
|
|
95
|
+
"prettier": "^3.7.3",
|
|
96
96
|
"prettier-plugin-svelte": "^3.4.0",
|
|
97
|
-
"publint": "^0.3.
|
|
98
|
-
"svelte": "^5.
|
|
99
|
-
"svelte-check": "^4.3.
|
|
100
|
-
"tailwindcss": "^4.1.
|
|
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.
|
|
103
|
-
"typescript-eslint": "^8.
|
|
104
|
-
"vite": "^7.
|
|
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": "^
|
|
110
|
+
"@googlemaps/js-api-loader": "^2.0.2"
|
|
111
111
|
}
|
|
112
112
|
}
|