places-autocomplete-svelte 2.1.7 → 2.1.9

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
@@ -1,6 +1,6 @@
1
1
  # Places (New) Autocomplete Svelte
2
2
 
3
- This Svelte component provides a user-friendly way to search for and retrieve detailed address information within your [SvelteKit](https://kit.svelte.dev) applications, leveraging the power of the [Google Maps Places (New) Autocomplete API](https://developers.google.com/maps/documentation/javascript/place-autocomplete-overview). It comes with default styling using [Tailwind CSS](https://tailwindcss.com/), which you can fully customize.
3
+ This Svelte component provides a user-friendly way to search for and retrieve detailed address information within your [SvelteKit](https://kit.svelte.dev) applications, leveraging the power of the [Google Maps Places (New) Autocomplete API](https://developers.google.com/maps/documentation/javascript/place-autocomplete-overview). It comes with default styling using [Tailwind CSS](https://tailwindcss.com/), which you can fully customise.
4
4
 
5
5
 
6
6
 
@@ -69,21 +69,31 @@ let onResponse = (response) => {
69
69
 
70
70
 
71
71
  ## Component Properties
72
- | Property | Type | Description | Required | Default Value |
73
- |--------------------------|--------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-----------------------------------------------|
74
- | `PUBLIC_GOOGLE_MAPS_API_KEY` | `String` | Your Google Maps Places API Key. | Yes | |
75
- | `onResponse` | `CustomEvent` | Dispatched when a place is selected, containing the place details. | Yes | |
76
- | `onError` | `CustomEvent` | Dispatched when an error occurs. | No | |
77
- | `placeholder` | `String` | Placeholder text for the input field. | No | `"Search..."` |
78
- | `autocomplete` | `string` | HTML `autocomplete` attribute for the input field. Set to "off" to disable browser autocomplete.
79
- | `autofocus` | `boolean` | The attribute indicating that an element should be focused on page load. | No | `false` |
80
- | `requestParams` | `Object` | Object for additional request parameters (e.g., `types`, `bounds`). See [AutocompleteRequest](https://developers.google.com/maps/documentation/javascript/reference/autocomplete-data#AutocompleteRequest). | No | `{}` |
81
- | `fetchFields` | `Array` | Array of place data fields to return. See [Supported Fields](https://developers.google.com/maps/documentation/javascript/reference/places-service#PlaceResult) | No | `['formattedAddress', 'addressComponents']` |
82
- | `classes` | `Object` | Object to override default Tailwind CSS classes applied to the component's elements (input, list, etc.). See the "Basic Usage" section for structure and default class names. | No | *Default Tailwind classes* |
72
+
73
+ | Property | Type | Description | Required | Default Value |
74
+ |--------------------------|-----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-----------------------------------------------|
75
+ | `PUBLIC_GOOGLE_MAPS_API_KEY` | `String` | Your Google Maps Places API Key. | Yes | |
76
+ | `onResponse` | `CustomEvent` | Dispatched when a place is selected, containing the place details in `event.detail`. | Yes | |
77
+ | `onError` | `CustomEvent` | Dispatched when an error occurs, with the error message in `event.detail`. | No | |
78
+ | `requestParams` | `Object` | Object for additional request parameters (e.g., `types`, `bounds`, `origin`, `region`, `language`). See [AutocompleteRequest](https://developers.google.com/maps/documentation/javascript/reference/autocomplete-data#AutocompleteRequest). | No | `{}` |
79
+ | `fetchFields` | `Array` | Array of place data fields to return. See [Supported Fields](https://developers.google.com/maps/documentation/javascript/reference/places-service#PlaceResult) | No | `['formattedAddress', 'addressComponents']` |
80
+ | `options` | `Object` | Options for customizing the component's behavior and appearance. See "Customization" below. | No | See default values in "Customization" |
81
+
82
+
83
83
 
84
84
  ## Customization
85
+ ### Options
86
+
87
+ | Property | Type | Description | Default Value |
88
+ |----------------|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|
89
+ | `autofocus` | `boolean` | If `true`, the input field will be focused automatically when the component mounts. | `false` |
90
+ | `placeholder` | `String` | Placeholder text for the input field. | `"Search..."` |
91
+ | `autocomplete`| `string` | HTML `autocomplete` attribute for the input field. Set to `"off"` to disable browser autocomplete. | `"off"` |
92
+ | `show_distance`| `boolean` | If `true`, and if an `origin` is specified in `requestParams`, displays the distance to each suggestion. The distance is calculated as a geodesic in meters. | `false` |
93
+ | `classes` | `Object` | Object to override default Tailwind CSS classes. | See [styling](https://places-autocomplete-demo.pages.dev/examples/styling) |
94
+
85
95
  ### Styling
86
- Customize the component's appearance by providing an object to the classes property. This object should contain key-value pairs, where the keys correspond to the component's elements and the values are your custom CSS class names. See [styling](https://places-autocomplete-demo.pages.dev/examples/styling) for details.
96
+ Customise the component's appearance by providing an object to the classes property. This object should contain key-value pairs, where the keys correspond to the component's elements and the values are your custom CSS class names. See [styling](https://places-autocomplete-demo.pages.dev/examples/styling) for details.
87
97
 
88
98
 
89
99
  ### Request Parameters (requestParams)
@@ -93,23 +103,6 @@ Fine-tune the autocomplete search with the requestParams property. This property
93
103
  <script>
94
104
  // ... other imports
95
105
 
96
- /**
97
- * @type string optional
98
- */
99
- const placeholder = 'Search...';
100
- /**
101
- * @type string optional
102
- * The <input> HTML autocomplete attribute.
103
- * default: 'off'
104
- * */
105
- const autocompete = 'off';
106
-
107
- /**
108
- * @type boolean optional
109
- * Boolean attribute indicating that an element should be focused on page load.
110
- * default: false
111
- * */
112
- const autofocus = false;
113
106
  /**
114
107
  * @type object optional
115
108
  * AutocompleteRequest properties
@@ -127,27 +120,14 @@ const requestParams = {
127
120
 
128
121
  /**
129
122
  * @type object optional
130
- * Component default Tailwind CSS classes
123
+ * Options
131
124
  */
132
- const classes = {
133
- section: '',
134
- container: 'relative z-10 transform rounded-xl mt-4',
135
- icon_container: 'pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3',
136
- icon: '<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>',
137
- input:
138
- 'border-1 w-full rounded-md border-0 shadow-sm bg-gray-100 px-4 py-2.5 pl-10 pr-20 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 sm:text-sm',
139
- kbd_container: 'absolute inset-y-0 right-0 flex py-1.5 pr-1.5',
140
- kbd_escape:
141
- 'inline-flex items-center rounded border border-gray-300 px-1 font-sans text-xs text-gray-500 w-8 mr-1',
142
- kbd_up:
143
- 'inline-flex items-center justify-center rounded border border-gray-300 px-1 font-sans text-xs text-gray-500 w-6',
144
- kbd_down:
145
- 'inline-flex items-center rounded border border-gray-400 px-1 font-sans text-xs text-gray-500 justify-center w-6',
146
- 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',
147
- li: 'z-50 cursor-default select-none py-2 pl-4 text-gray-900 hover:bg-indigo-500 hover:text-white',
148
- li_current: 'bg-indigo-500 text-white',
149
- li_a: 'block w-full'
150
- },
125
+ const options = {
126
+ autofocus: false,
127
+ autocompete: 'off',
128
+ placeholder: 'Start typing your address',
129
+ show_distance: true,
130
+ };
151
131
 
152
132
  /**
153
133
  * @type array optional
@@ -160,11 +140,9 @@ const fetchFields = ['formattedAddress', 'addressComponents'];
160
140
  {onResponse}
161
141
  {PUBLIC_GOOGLE_MAPS_API_KEY}
162
142
  {requestParams}
163
- {placeholder}
164
- {autocompete}
165
- {autofocus}
143
+ {options}
166
144
  {fetchFields}
167
- {classes}
145
+
168
146
  />
169
147
 
170
148
  ```
@@ -1,8 +1,8 @@
1
1
  <script lang="ts">
2
2
  import { onMount } from 'svelte';
3
3
  import * as GMaps from '@googlemaps/js-api-loader';
4
- import type { Props } from './interfaces.js';
5
- import { validateRequestParams } from './helpers.js';
4
+ import type { ComponentOptions, Props } from './interfaces.js';
5
+ import { validateOptions, validateRequestParams } from './helpers.js';
6
6
  const { Loader } = GMaps;
7
7
 
8
8
  let {
@@ -12,42 +12,38 @@
12
12
  */
13
13
  PUBLIC_GOOGLE_MAPS_API_KEY,
14
14
  fetchFields = $bindable(['formattedAddress', 'addressComponents']),
15
- placeholder = 'Search...',
16
- autocompete = 'off',
17
- autofocus = false,
18
- classes = {
19
- section: '',
20
- container: 'relative z-10 transform rounded-xl mt-4',
21
- icon_container: 'pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3',
22
- icon: '<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>',
23
- input:
24
- 'border-1 w-full rounded-md border-0 shadow-sm bg-gray-100 px-4 py-2.5 pl-10 pr-20 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 sm:text-sm',
25
- kbd_container: 'absolute inset-y-0 right-0 flex py-1.5 pr-1.5',
26
- kbd_escape:
27
- 'inline-flex items-center rounded border border-gray-300 px-1 font-sans text-xs text-gray-500 w-8 mr-1',
28
- kbd_up:
29
- 'inline-flex items-center justify-center rounded border border-gray-300 px-1 font-sans text-xs text-gray-500 w-6',
30
- kbd_down:
31
- 'inline-flex items-center rounded border border-gray-400 px-1 font-sans text-xs text-gray-500 justify-center w-6',
32
- 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',
33
- li: 'z-50 cursor-default select-none py-2 pl-4 text-gray-900 hover:bg-indigo-500 hover:text-white',
34
- li_current: 'bg-indigo-500 text-white',
35
- li_a: 'block w-full'
36
- },
15
+ options,
37
16
  onResponse = $bindable((e: Event) => {}),
38
17
  onError = $bindable((error: string) => {}),
39
18
  requestParams = {}
40
19
  }: Props = $props();
41
20
 
21
+ // validate options
22
+ options = validateOptions(options);
23
+
42
24
  // set classes as state
43
- let cl = $state(classes);
25
+ let cl = $state(options.classes);
44
26
 
45
- // reset keyboard classes
46
- const resetKbdClasses = () => {
47
- cl.kbd_down = classes.kbd_down;
48
- cl.kbd_up = classes.kbd_up;
49
- }
27
+ // format meters to km and meters
28
+ const formatMeters = function (meters: number): string|null {
29
+ if(typeof meters !== 'number') {
30
+ return null;
31
+ }
32
+ const km = Math.floor(meters / 1000);
33
+ const remainingMeters = meters % 1000;
34
+ let formattedString = '';
35
+ if (km > 0) {
36
+ formattedString += km + 'km ';
37
+ }
38
+ formattedString += remainingMeters + 'm';
39
+ return formattedString;
40
+ };
50
41
 
42
+ // reset keyboard classes
43
+ const resetKbdClasses = () => {
44
+ cl.kbd_down = options.classes.kbd_down;
45
+ cl.kbd_up = options.classes.kbd_up;
46
+ };
51
47
 
52
48
  // Local variables
53
49
  let inputRef: HTMLInputElement;
@@ -55,12 +51,14 @@
55
51
  let results: any[] = $state([]);
56
52
  let loader: GMaps.Loader;
57
53
  let placesApi: { [key: string]: any } = {};
54
+
55
+
58
56
  //https://developers.google.com/maps/documentation/javascript/reference/autocomplete-data
59
57
  // validate and merge requestParams with requestParamsDefault
60
58
  //let request = $state(validateRequestParams(Object.assign(requestParamsDefault, requestParams)));
61
59
  requestParams = validateRequestParams(requestParams);
62
60
  let request = $state(requestParams);
63
-
61
+ //$inspect(request);
64
62
  // clear result when input is empty
65
63
  $effect(() => {
66
64
  if (request.input == '') {
@@ -108,12 +106,15 @@
108
106
  const { suggestions } =
109
107
  await placesApi.AutocompleteSuggestion.fetchAutocompleteSuggestions(request);
110
108
  results = [];
109
+ const formatter = new Intl.NumberFormat('en');
111
110
  // iterate suggestions and add results to an array
112
111
  for (const suggestion of suggestions) {
112
+
113
113
  // add suggestions to results
114
114
  results.push({
115
115
  to_pace: suggestion.placePrediction.toPlace(),
116
- text: suggestion.placePrediction.text.toString()
116
+ text: suggestion.placePrediction.text.toString(),
117
+ distance: formatMeters(suggestion.placePrediction.distanceMeters)
117
118
  });
118
119
  }
119
120
  } catch (e: any) {
@@ -162,7 +163,7 @@
162
163
  * Initialize the Google Maps JavaScript API Loader.
163
164
  */
164
165
  onMount(async (): Promise<void> => {
165
- if(autofocus) {
166
+ if (options.autofocus) {
166
167
  // focus on the input
167
168
  inputRef.focus();
168
169
  }
@@ -193,11 +194,11 @@
193
194
  if (e.key === 'ArrowDown') {
194
195
  currentSuggestion = Math.min(currentSuggestion + 1, results.length - 1);
195
196
  resetKbdClasses();
196
- cl.kbd_down += ' bg-indigo-500 text-white';
197
+ cl.kbd_down += ' bg-indigo-500 text-white';
197
198
  } else if (e.key === 'ArrowUp') {
198
199
  currentSuggestion = Math.max(currentSuggestion - 1, 0);
199
200
  resetKbdClasses();
200
- cl.kbd_up += ' bg-indigo-500 text-white';
201
+ cl.kbd_up += ' bg-indigo-500 text-white';
201
202
  } else if (e.key === 'Enter') {
202
203
  e.preventDefault();
203
204
  if (currentSuggestion >= 0) {
@@ -209,28 +210,29 @@
209
210
  }
210
211
 
211
212
  setTimeout(() => {
212
- resetKbdClasses();
213
- }, 300);
213
+ resetKbdClasses();
214
+ }, 300);
214
215
  }
215
216
  </script>
216
217
 
217
218
  <svelte:window onkeydown={onKeyDown} />
218
219
 
219
- <section class="{classes?.section}">
220
- <div class="{classes.container}">
221
- {#if classes.icon}
222
- <div class="{classes.icon_container}">
223
- {@html classes.icon}
224
- </div>
225
- {/if}
220
+ <section class={options.classes?.section}>
221
+ <div class={options.classes.container}>
222
+ {#if options.classes.icon}
223
+ <div class={options.classes.icon_container}>
224
+ {@html options.classes.icon}
225
+ </div>
226
+ {/if}
227
+
226
228
 
227
229
  <input
228
230
  type="text"
229
231
  name="search"
230
232
  bind:this={inputRef}
231
- class="{classes.input}"
232
- {placeholder}
233
- autocomplete={autocompete}
233
+ class={options.classes.input}
234
+ placeholder={options.placeholder}
235
+ autocomplete={options.autocomplete}
234
236
  aria-controls="options"
235
237
  aria-autocomplete="list"
236
238
  aria-owns="options"
@@ -242,37 +244,43 @@
242
244
  />
243
245
 
244
246
  {#if results.length > 0}
245
- <div class="{classes.kbd_container}">
246
- <kbd
247
- class="{classes.kbd_escape}"
248
- >Esc</kbd
249
- >
250
- <kbd
251
- class="{cl.kbd_up}"
252
- >&uArr;</kbd
253
- >
254
- <kbd
255
- class="{cl.kbd_down}"
256
- >&dArr;</kbd
257
- >
258
- </div>
259
- <ul
260
- class="{classes.ul}"
261
- id="options"
262
- >
247
+ <div class={options.classes.kbd_container}>
248
+ <kbd class={options.classes.kbd_escape}>Esc</kbd>
249
+ <kbd class={cl.kbd_up}>&uArr;</kbd>
250
+ <kbd class={cl.kbd_down}>&dArr;</kbd>
251
+ </div>
252
+
253
+ <ul class={options.classes.ul} id="options">
263
254
  {#each results as place, i}
264
255
  <li
265
- class={[ classes.li, i === currentSuggestion && classes.li_current ]}
256
+ class={[options.classes.li, i === currentSuggestion && options.classes.li_current]}
266
257
  id="option-{i + 1}"
267
258
  >
268
259
  <!-- svelte-ignore a11y_invalid_attribute -->
269
260
  <a
270
261
  href="javascript:void(0)"
271
- class="{classes?.li_a}"
262
+ class={[options.classes?.li_a, 'flex justify-between']}
272
263
  tabindex={i + 1}
273
264
  onclick={() => onPlaceSelected(place.to_pace)}
274
265
  >
275
- {place.text}
266
+ <div class="flex min-w-0 gap-x-4">
267
+ <!-- <img
268
+ class="size-12 flex-none rounded-full bg-gray-50"
269
+ src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
270
+ alt=""
271
+ /> -->
272
+ <div class="min-w-0 flex-auto">
273
+ <p class={[i === currentSuggestion && options.classes.li_current,'text-sm/6 font-semibold text-gray-900']}>{place.text}</p>
274
+ <!-- <p class="mt-1 truncate text-xs/5 text-gray-500">leslie.alexander@example.com</p> -->
275
+ </div>
276
+ </div>
277
+ {#if options.show_distance && place.distance}
278
+ <div class="shrink-0 flex flex-col items-end min-w-16">
279
+ <p class={[i === currentSuggestion && options.classes.li_current,'mt-1 text-xs/5 text-gray-500']}>
280
+ {place.distance}
281
+ </p>
282
+ </div>
283
+ {/if}
276
284
  </a>
277
285
  </li>
278
286
  {/each}
package/dist/helpers.d.ts CHANGED
@@ -1,7 +1,17 @@
1
- import type { RequestParams } from './interfaces.js';
1
+ import type { RequestParams, ComponentOptions, ComponentClasses } from './interfaces.js';
2
2
  export declare const requestParamsDefault: RequestParams;
3
3
  /**
4
4
  * Validate and cast request parameters
5
5
  * @param requestParams
6
6
  */
7
7
  export declare const validateRequestParams: (requestParams: RequestParams | undefined) => RequestParams;
8
+ /**
9
+ * Default component classes
10
+ */
11
+ export declare const componentClasses: ComponentClasses;
12
+ export declare const componentOptions: ComponentOptions;
13
+ /**
14
+ * Validate and cast component options
15
+ * @param options
16
+ */
17
+ export declare const validateOptions: (options: ComponentOptions | undefined) => ComponentOptions;
package/dist/helpers.js CHANGED
@@ -43,7 +43,10 @@ export const requestParamsDefault = {
43
43
  * If neither are set, the results will be biased by IP address, meaning the IP address
44
44
  * will be mapped to an imprecise location and used as a biasing signal.
45
45
  */
46
- locationBias: null,
46
+ locationBias: {
47
+ lat: 0,
48
+ lng: 0
49
+ },
47
50
  /**
48
51
  * @param LocationRestriction optional
49
52
  * Restrict results to a specified location.
@@ -118,7 +121,7 @@ export const validateRequestParams = (requestParams) => {
118
121
  }
119
122
  }
120
123
  // merge requestParams with requestParamsDefault
121
- requestParams = Object.assign(requestParamsDefault, requestParams);
124
+ //requestParams = Object.assign(requestParamsDefault, requestParams);
122
125
  // Reset sessionToken to empty string if passed to the component
123
126
  if (requestParams.sessionToken) {
124
127
  requestParams.sessionToken = String('');
@@ -145,7 +148,7 @@ export const validateRequestParams = (requestParams) => {
145
148
  * If requestParams.includedRegionCodes is an array and has more than 15 items, slice it to 15
146
149
  */
147
150
  if (!Array.isArray(requestParams.includedRegionCodes)
148
- || (Array.isArray(requestParams.includedPrimaryTypes) && requestParams.includedPrimaryTypes.length === 0)) {
151
+ || (Array.isArray(requestParams.includedRegionCodes) && requestParams.includedRegionCodes.length === 0)) {
149
152
  delete requestParams.includedRegionCodes;
150
153
  }
151
154
  else if (Array.isArray(requestParams.includedRegionCodes) && requestParams.includedRegionCodes.length > 15) {
@@ -163,26 +166,92 @@ export const validateRequestParams = (requestParams) => {
163
166
  delete requestParams.language;
164
167
  }
165
168
  // If locationBias is not a string, remove it
166
- if (typeof requestParams.locationBias !== 'string') {
169
+ if (typeof requestParams.locationBias !== 'undefined'
170
+ && (!Object.keys(requestParams.locationBias).includes('lat') || !Object.keys(requestParams.locationBias).includes('lng'))
171
+ || requestParams.locationBias?.lat === 0
172
+ || requestParams.locationBias?.lng === 0) {
167
173
  delete requestParams.locationBias;
168
174
  }
169
175
  /**
170
176
  * If locationRestriction is not set, remove it
171
177
  */
172
- if (requestParams.locationRestriction?.east === 0
173
- && requestParams.locationRestriction?.north === 0
174
- && requestParams.locationRestriction?.south === 0
175
- && requestParams.locationRestriction?.west === 0) {
178
+ if (typeof requestParams.locationRestriction !== 'undefined'
179
+ && (!Object.keys(requestParams.locationRestriction).includes('east')
180
+ || !Object.keys(requestParams.locationRestriction).includes('north')
181
+ || !Object.keys(requestParams.locationRestriction).includes('south')
182
+ || !Object.keys(requestParams.locationRestriction).includes('west'))
183
+ || requestParams.locationRestriction?.east === 0
184
+ || requestParams.locationRestriction?.north === 0
185
+ || requestParams.locationRestriction?.south === 0
186
+ || requestParams.locationRestriction?.west === 0) {
176
187
  delete requestParams.locationRestriction;
177
188
  }
178
189
  // If origin is not set, remove it
179
- if (requestParams.origin?.lat === 0 && requestParams.origin?.lng === 0) {
190
+ if (typeof requestParams.origin !== 'undefined'
191
+ && (!Object.keys(requestParams.origin).includes('lat') || !Object.keys(requestParams.origin).includes('lng'))
192
+ || requestParams.origin?.lat === 0 || requestParams.origin?.lng === 0) {
180
193
  delete requestParams.origin;
181
194
  }
182
195
  // If region is not a string, remove it
183
196
  if (typeof requestParams.region !== 'string') {
184
197
  delete requestParams.region;
185
198
  }
186
- //console.log('requestParams:', Object.keys(requestParams));
199
+ // console.log('requestParams:', Object.keys(requestParams));
200
+ // console.log('requestParams:', requestParams);
187
201
  return requestParams;
188
202
  };
203
+ /**
204
+ * Default component classes
205
+ */
206
+ export const componentClasses = {
207
+ section: '',
208
+ container: 'relative z-10 transform rounded-xl mt-4',
209
+ icon_container: 'pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3',
210
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>',
211
+ input: 'border-1 w-full rounded-md border-0 shadow-sm bg-gray-100 px-4 py-2.5 pl-10 pr-20 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 sm:text-sm',
212
+ kbd_container: 'absolute inset-y-0 right-0 flex py-1.5 pr-1.5',
213
+ kbd_escape: 'inline-flex items-center rounded border border-gray-300 px-1 font-sans text-xs text-gray-500 w-8 mr-1',
214
+ kbd_up: 'inline-flex items-center justify-center rounded border border-gray-300 px-1 font-sans text-xs text-gray-500 w-6',
215
+ kbd_down: 'inline-flex items-center rounded border border-gray-400 px-1 font-sans text-xs text-gray-500 justify-center w-6',
216
+ 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',
217
+ li: 'z-50 cursor-default select-none py-2 px-2 lg:px-4 text-gray-900 hover:bg-indigo-500 hover:text-white',
218
+ li_current: 'bg-indigo-500 text-white',
219
+ li_a: 'block w-full',
220
+ };
221
+ export const componentOptions = {
222
+ autofocus: false,
223
+ autocomplete: 'off',
224
+ classes: componentClasses,
225
+ placeholder: '',
226
+ show_distance: false
227
+ };
228
+ /**
229
+ * Validate and cast component options
230
+ * @param options
231
+ */
232
+ export const validateOptions = (options) => {
233
+ // If options is not an object, set it to an empty object
234
+ if (typeof options !== 'object' || Object.keys(options).length === 0) {
235
+ options = {
236
+ autofocus: false,
237
+ autocomplete: 'off',
238
+ classes: componentClasses,
239
+ placeholder: 'Start typing...',
240
+ show_distance: false
241
+ };
242
+ return options;
243
+ }
244
+ // Find the missing options properties
245
+ for (const key in componentOptions) {
246
+ if (!(key in options)) {
247
+ options[key] = componentOptions[key];
248
+ }
249
+ }
250
+ // Find the missing classes properties
251
+ for (const key in componentClasses) {
252
+ if (!(key in options.classes)) {
253
+ options.classes[key] = componentClasses[key];
254
+ }
255
+ }
256
+ return options;
257
+ };
@@ -8,7 +8,10 @@ export interface RequestParams {
8
8
  includedRegionCodes?: string[];
9
9
  inputOffset?: number;
10
10
  language?: string;
11
- locationBias?: unknown | string;
11
+ locationBias?: {
12
+ lat: number;
13
+ lng: number;
14
+ };
12
15
  locationRestriction?: {
13
16
  west: number;
14
17
  south: number;
@@ -23,22 +26,18 @@ export interface RequestParams {
23
26
  sessionToken?: string;
24
27
  }
25
28
  export interface ComponentClasses {
26
- section?: string;
27
- container?: string;
28
- icon_container?: string;
29
- icon?: string;
30
- input?: string;
31
- kbd_container?: string;
32
- kbd_escape?: string;
33
- kbd_up?: string;
34
- kbd_down?: string;
35
- ul?: string;
36
- li?: string;
37
- li_current?: string;
38
- li_a?: string;
29
+ [key: string]: string;
30
+ }
31
+ export interface ComponentOptions {
32
+ autofocus: boolean;
33
+ autocomplete: AutoFill;
34
+ classes: ComponentClasses;
35
+ placeholder: string;
36
+ show_distance: boolean;
39
37
  }
40
38
  export interface Props {
41
39
  PUBLIC_GOOGLE_MAPS_API_KEY: string;
40
+ options?: ComponentOptions;
42
41
  fetchFields?: string[];
43
42
  placeholder?: string;
44
43
  autofocus?: boolean;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "places-autocomplete-svelte",
3
3
  "license": "MIT",
4
- "version": "2.1.7",
4
+ "version": "2.1.9",
5
5
  "description": "A lightweight and customizable Svelte component for easy integration of Google Maps Places (New) Autocomplete in your Svelte/SvelteKit applications. Provides accessible autocomplete suggestions and detailed address retrieval.",
6
6
  "keywords": [
7
7
  "svelte",
@@ -68,30 +68,30 @@
68
68
  },
69
69
  "devDependencies": {
70
70
  "@sveltejs/adapter-auto": "^4.0.0",
71
- "@sveltejs/adapter-cloudflare": "^5.0.1",
72
- "@sveltejs/kit": "^2.16.1",
73
- "@sveltejs/package": "^2.3.9",
71
+ "@sveltejs/adapter-cloudflare": "^5.0.2",
72
+ "@sveltejs/kit": "^2.17.1",
73
+ "@sveltejs/package": "^2.3.10",
74
74
  "@sveltejs/vite-plugin-svelte": "^5.0.3",
75
- "@tailwindcss/postcss": "^4.0.0",
75
+ "@tailwindcss/postcss": "^4.0.5",
76
76
  "@tailwindcss/typography": "^0.5.16",
77
- "@tailwindcss/vite": "^4.0.0",
77
+ "@tailwindcss/vite": "^4.0.5",
78
78
  "@types/eslint": "^9.6.1",
79
79
  "autoprefixer": "^10.4.20",
80
- "eslint": "^9.19.0",
80
+ "eslint": "^9.20.0",
81
81
  "eslint-config-prettier": "^10.0.1",
82
82
  "eslint-plugin-svelte": "^2.46.1",
83
83
  "globals": "^15.14.0",
84
84
  "postcss": "^8.5.1",
85
85
  "prettier": "^3.4.2",
86
86
  "prettier-plugin-svelte": "^3.3.3",
87
- "publint": "^0.3.2",
88
- "svelte": "^5.19.4",
87
+ "publint": "^0.3.4",
88
+ "svelte": "^5.19.9",
89
89
  "svelte-check": "^4.1.4",
90
- "tailwindcss": "^4.0.0",
90
+ "tailwindcss": "^4.0.5",
91
91
  "tslib": "^2.8.1",
92
92
  "typescript": "^5.7.3",
93
- "typescript-eslint": "^8.22.0",
94
- "vite": "^6.0.11"
93
+ "typescript-eslint": "^8.23.0",
94
+ "vite": "^6.1.0"
95
95
  },
96
96
  "svelte": "./dist/index.js",
97
97
  "types": "./dist/PlaceAutocomplete.svelte.d.ts",