places-autocomplete-svelte 2.1.6 → 2.1.8
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 +59 -69
- package/dist/PlaceAutocomplete.svelte +77 -69
- package/dist/helpers.d.ts +11 -1
- package/dist/helpers.js +56 -1
- package/dist/interfaces.d.ts +9 -13
- package/package.json +17 -15
package/README.md
CHANGED
|
@@ -1,29 +1,34 @@
|
|
|
1
1
|
# Places (New) Autocomplete Svelte
|
|
2
2
|
|
|
3
|
-
This Svelte component
|
|
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.
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
## Features
|
|
7
|
+
## Features
|
|
8
8
|
|
|
9
|
-
- **Seamless Integration:** Easily
|
|
10
|
-
- **Autocomplete Suggestions:**
|
|
11
|
-
- **
|
|
12
|
-
- **Country/Region Filtering:**
|
|
13
|
-
- **Customizable
|
|
14
|
-
- **Flexible Data
|
|
15
|
-
- **
|
|
9
|
+
- **Seamless SvelteKit Integration:** Easily add the component to your SvelteKit projects.
|
|
10
|
+
- **Real-time Autocomplete Suggestions:** As the user types, address suggestions appear dynamically.
|
|
11
|
+
- **Comprehensive Address Details:** Retrieve detailed information, including street address, city, state/province, postal code, country, and more.
|
|
12
|
+
- **Country/Region Filtering:** Narrow down search results by specifying target countries or regions.
|
|
13
|
+
- **Customizable Styles:** Tailor the component's appearance to match your application's design by overriding the default Tailwind CSS classes.
|
|
14
|
+
- **Flexible Data Control:** Choose the specific data fields you want to retrieve using the `fetchFields` property.
|
|
15
|
+
- **Keyboard Navigation & Accessibility:** Use keyboard navigation for selecting suggestions, ensuring accessibility for all users.
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
## Demo
|
|
19
19
|
|
|
20
|
-
See a live demo of the component in action: [
|
|
20
|
+
See a live demo of the component in action: [Basic Example](https://places-autocomplete-demo.pages.dev/)
|
|
21
|
+
|
|
22
|
+
[Reactive parameters](https://places-autocomplete-demo.pages.dev/examples/reactive-parameters) - change the search criteria based on user input, like filtering by country or change results language.
|
|
23
|
+
|
|
24
|
+
[Customise request parameters](https://places-autocomplete-demo.pages.dev/examples/customise-request-parameters) - construct a `requestParams` object and control various aspects of the search, including language, region, and more.
|
|
25
|
+
|
|
21
26
|
|
|
22
27
|

|
|
23
28
|
|
|
24
29
|
## Requirements
|
|
25
30
|
|
|
26
|
-
- **Google Maps API Key
|
|
31
|
+
- **Google Maps API Key** with the Places API (New) enabled. Refer to [Use API Keys](https://developers.google.com/maps/documentation/javascript/get-api-key) for detailed instructions.
|
|
27
32
|
|
|
28
33
|
## Installation Svelte 5
|
|
29
34
|
|
|
@@ -40,15 +45,17 @@ npm i places-autocomplete-svelte@1.0.1
|
|
|
40
45
|
|
|
41
46
|
## Basic Usage
|
|
42
47
|
|
|
43
|
-
1.
|
|
44
|
-
2.
|
|
48
|
+
1. Replace `'___YOUR_API_KEY___'` with your actual **Google Maps API Key**.
|
|
49
|
+
2. Use the `onResponse` callback to **handle the response**.
|
|
45
50
|
|
|
46
51
|
```svelte
|
|
47
52
|
<script>
|
|
48
53
|
import { PlaceAutocomplete } from 'places-autocomplete-svelte';
|
|
49
54
|
|
|
55
|
+
//Recommended: Store your key securely as an environment variable
|
|
50
56
|
const PUBLIC_GOOGLE_MAPS_API_KEY = '___YOUR_API_KEY___';
|
|
51
57
|
|
|
58
|
+
|
|
52
59
|
let fullResponse = $state('')
|
|
53
60
|
let onResponse = (response) => {
|
|
54
61
|
fullResponse = response;
|
|
@@ -61,31 +68,41 @@ let onResponse = (response) => {
|
|
|
61
68
|
```
|
|
62
69
|
|
|
63
70
|
|
|
71
|
+
## Component Properties
|
|
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
|
+
|
|
64
83
|
|
|
65
84
|
## Customization
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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.structure. | See [styling](https://places-autocomplete-demo.pages.dev/examples/styling) |
|
|
94
|
+
|
|
95
|
+
### Styling
|
|
96
|
+
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.
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
### Request Parameters (requestParams)
|
|
100
|
+
Fine-tune the autocomplete search with the requestParams property. This property accepts an object corresponding to the AutocompleteRequest object in the Google Maps API documentation. See this [request parameters](https://places-autocomplete-demo.pages.dev/component/request-parameters) for more details. Here are some common examples:
|
|
73
101
|
|
|
74
102
|
```svelte
|
|
75
103
|
<script>
|
|
76
104
|
// ... other imports
|
|
77
105
|
|
|
78
|
-
/**
|
|
79
|
-
* @type string optional
|
|
80
|
-
*/
|
|
81
|
-
const placeholder = 'Search...';
|
|
82
|
-
/**
|
|
83
|
-
* @type string optional
|
|
84
|
-
* The <input> HTML autocomplete attribute.
|
|
85
|
-
* default: 'off'
|
|
86
|
-
* */
|
|
87
|
-
const autocompete = 'off';
|
|
88
|
-
|
|
89
106
|
/**
|
|
90
107
|
* @type boolean optional
|
|
91
108
|
* Boolean attribute indicating that an element should be focused on page load.
|
|
@@ -109,27 +126,14 @@ const requestParams = {
|
|
|
109
126
|
|
|
110
127
|
/**
|
|
111
128
|
* @type object optional
|
|
112
|
-
*
|
|
129
|
+
* Options
|
|
113
130
|
*/
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
'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',
|
|
121
|
-
kbd_container: 'absolute inset-y-0 right-0 flex py-1.5 pr-1.5',
|
|
122
|
-
kbd_escape:
|
|
123
|
-
'inline-flex items-center rounded border border-gray-300 px-1 font-sans text-xs text-gray-500 w-8 mr-1',
|
|
124
|
-
kbd_up:
|
|
125
|
-
'inline-flex items-center justify-center rounded border border-gray-300 px-1 font-sans text-xs text-gray-500 w-6',
|
|
126
|
-
kbd_down:
|
|
127
|
-
'inline-flex items-center rounded border border-gray-400 px-1 font-sans text-xs text-gray-500 justify-center w-6',
|
|
128
|
-
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',
|
|
129
|
-
li: 'z-50 cursor-default select-none py-2 pl-4 text-gray-900 hover:bg-indigo-500 hover:text-white',
|
|
130
|
-
li_current: 'bg-indigo-500 text-white',
|
|
131
|
-
li_a: 'block w-full'
|
|
132
|
-
},
|
|
131
|
+
const options = {
|
|
132
|
+
autofocus: false,
|
|
133
|
+
autocompete: 'off',
|
|
134
|
+
placeholder: 'Start typing your address',
|
|
135
|
+
show_distance: true,
|
|
136
|
+
};
|
|
133
137
|
|
|
134
138
|
/**
|
|
135
139
|
* @type array optional
|
|
@@ -142,33 +146,19 @@ const fetchFields = ['formattedAddress', 'addressComponents'];
|
|
|
142
146
|
{onResponse}
|
|
143
147
|
{PUBLIC_GOOGLE_MAPS_API_KEY}
|
|
144
148
|
{requestParams}
|
|
145
|
-
{
|
|
146
|
-
{autocompete}
|
|
147
|
-
{autofocus}
|
|
149
|
+
{options}
|
|
148
150
|
{fetchFields}
|
|
149
|
-
|
|
151
|
+
|
|
150
152
|
/>
|
|
151
153
|
|
|
152
154
|
```
|
|
153
155
|
|
|
154
156
|
|
|
155
|
-
## Component Properties
|
|
156
|
-
| Property | Type | Description | Required | Default Value |
|
|
157
|
-
|--------------------------|--------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-----------------------------------------------|
|
|
158
|
-
| `PUBLIC_GOOGLE_MAPS_API_KEY` | `String` | Your Google Maps Places API Key. | Yes | |
|
|
159
|
-
| `onResponse` | `CustomEvent` | Dispatched when a place is selected, containing the place details. | Yes | |
|
|
160
|
-
| `onError` | `CustomEvent` | Dispatched when an error occurs. | No | |
|
|
161
|
-
| `placeholder` | `String` | Placeholder text for the input field. | No | `"Search..."` |
|
|
162
|
-
| `autocomplete` | `string` | HTML `autocomplete` attribute for the input field. Set to "off" to disable browser autocomplete.
|
|
163
|
-
| `autofocus` | `boolean` | The attribute indicating that an element should be focused on page load. | No | `false` |
|
|
164
|
-
| `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 | `{}` |
|
|
165
|
-
| `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']` |
|
|
166
|
-
| `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* |
|
|
167
157
|
|
|
168
158
|
|
|
169
159
|
## Error Handling
|
|
170
160
|
|
|
171
|
-
|
|
161
|
+
Use the `onError` event handler to gracefully manage any errors that may occur during the autocomplete process:
|
|
172
162
|
|
|
173
163
|
|
|
174
164
|
```svelte
|
|
@@ -195,7 +185,7 @@ let onError = (error: string) => {
|
|
|
195
185
|
|
|
196
186
|
## Contributing
|
|
197
187
|
|
|
198
|
-
Contributions are welcome! Please open an issue or submit a pull request on the [GitHub
|
|
188
|
+
Contributions are welcome! Please open an issue or submit a pull request on the [GitHub](https://github.com/alexpechkarev/places-autocomplete-svelte/).
|
|
199
189
|
|
|
200
190
|
## License
|
|
201
191
|
|
|
@@ -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
|
-
|
|
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
|
-
requestParams
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
213
|
-
|
|
213
|
+
resetKbdClasses();
|
|
214
|
+
}, 300);
|
|
214
215
|
}
|
|
215
216
|
</script>
|
|
216
217
|
|
|
217
218
|
<svelte:window onkeydown={onKeyDown} />
|
|
218
219
|
|
|
219
|
-
<section class=
|
|
220
|
-
<div class=
|
|
221
|
-
{#if classes.icon}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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=
|
|
232
|
-
{placeholder}
|
|
233
|
-
autocomplete={
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
class=
|
|
248
|
-
>
|
|
249
|
-
>
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
>⇑</kbd
|
|
253
|
-
>
|
|
254
|
-
<kbd
|
|
255
|
-
class="{cl.kbd_down}"
|
|
256
|
-
>⇓</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}>⇑</kbd>
|
|
250
|
+
<kbd class={cl.kbd_down}>⇓</kbd>
|
|
251
|
+
</div>
|
|
252
|
+
|
|
253
|
+
<ul class={options.classes.ul} id="options">
|
|
263
254
|
{#each results as place, i}
|
|
264
255
|
<li
|
|
265
|
-
class={[
|
|
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=
|
|
262
|
+
class={[options.classes?.li_a, 'flex justify-between']}
|
|
272
263
|
tabindex={i + 1}
|
|
273
264
|
onclick={() => onPlaceSelected(place.to_pace)}
|
|
274
265
|
>
|
|
275
|
-
|
|
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
|
@@ -101,7 +101,7 @@ export const validateRequestParams = (requestParams) => {
|
|
|
101
101
|
/**
|
|
102
102
|
* If requestParams is not an object, set it to an empty object
|
|
103
103
|
*/
|
|
104
|
-
if (typeof requestParams !== 'object') {
|
|
104
|
+
if (typeof requestParams !== 'object' || Object.keys(requestParams).length === 0) {
|
|
105
105
|
requestParams = {
|
|
106
106
|
input: String(''),
|
|
107
107
|
sessionToken: String(''),
|
|
@@ -186,3 +186,58 @@ export const validateRequestParams = (requestParams) => {
|
|
|
186
186
|
//console.log('requestParams:', Object.keys(requestParams));
|
|
187
187
|
return requestParams;
|
|
188
188
|
};
|
|
189
|
+
/**
|
|
190
|
+
* Default component classes
|
|
191
|
+
*/
|
|
192
|
+
export const componentClasses = {
|
|
193
|
+
section: '',
|
|
194
|
+
container: 'relative z-10 transform rounded-xl mt-4',
|
|
195
|
+
icon_container: 'pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3',
|
|
196
|
+
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>',
|
|
197
|
+
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',
|
|
198
|
+
kbd_container: 'absolute inset-y-0 right-0 flex py-1.5 pr-1.5',
|
|
199
|
+
kbd_escape: 'inline-flex items-center rounded border border-gray-300 px-1 font-sans text-xs text-gray-500 w-8 mr-1',
|
|
200
|
+
kbd_up: 'inline-flex items-center justify-center rounded border border-gray-300 px-1 font-sans text-xs text-gray-500 w-6',
|
|
201
|
+
kbd_down: 'inline-flex items-center rounded border border-gray-400 px-1 font-sans text-xs text-gray-500 justify-center w-6',
|
|
202
|
+
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',
|
|
203
|
+
li: 'z-50 cursor-default select-none py-2 px-2 lg:px-4 text-gray-900 hover:bg-indigo-500 hover:text-white',
|
|
204
|
+
li_current: 'bg-indigo-500 text-white',
|
|
205
|
+
li_a: 'block w-full',
|
|
206
|
+
};
|
|
207
|
+
export const componentOptions = {
|
|
208
|
+
autofocus: false,
|
|
209
|
+
autocomplete: 'off',
|
|
210
|
+
classes: componentClasses,
|
|
211
|
+
placeholder: '',
|
|
212
|
+
show_distance: false
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* Validate and cast component options
|
|
216
|
+
* @param options
|
|
217
|
+
*/
|
|
218
|
+
export const validateOptions = (options) => {
|
|
219
|
+
// If options is not an object, set it to an empty object
|
|
220
|
+
if (typeof options !== 'object' || Object.keys(options).length === 0) {
|
|
221
|
+
options = {
|
|
222
|
+
autofocus: false,
|
|
223
|
+
autocomplete: 'off',
|
|
224
|
+
classes: componentClasses,
|
|
225
|
+
placeholder: 'Start typing...',
|
|
226
|
+
show_distance: false
|
|
227
|
+
};
|
|
228
|
+
return options;
|
|
229
|
+
}
|
|
230
|
+
// Find the missing options properties
|
|
231
|
+
for (const key in componentOptions) {
|
|
232
|
+
if (!(key in options)) {
|
|
233
|
+
options[key] = componentOptions[key];
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Find the missing classes properties
|
|
237
|
+
for (const key in componentClasses) {
|
|
238
|
+
if (!(key in options.classes)) {
|
|
239
|
+
options.classes[key] = componentClasses[key];
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return options;
|
|
243
|
+
};
|
package/dist/interfaces.d.ts
CHANGED
|
@@ -23,22 +23,18 @@ export interface RequestParams {
|
|
|
23
23
|
sessionToken?: string;
|
|
24
24
|
}
|
|
25
25
|
export interface ComponentClasses {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
kbd_down?: string;
|
|
35
|
-
ul?: string;
|
|
36
|
-
li?: string;
|
|
37
|
-
li_current?: string;
|
|
38
|
-
li_a?: string;
|
|
26
|
+
[key: string]: string;
|
|
27
|
+
}
|
|
28
|
+
export interface ComponentOptions {
|
|
29
|
+
autofocus: boolean;
|
|
30
|
+
autocomplete: AutoFill;
|
|
31
|
+
classes: ComponentClasses;
|
|
32
|
+
placeholder: string;
|
|
33
|
+
show_distance: boolean;
|
|
39
34
|
}
|
|
40
35
|
export interface Props {
|
|
41
36
|
PUBLIC_GOOGLE_MAPS_API_KEY: string;
|
|
37
|
+
options?: ComponentOptions;
|
|
42
38
|
fetchFields?: string[];
|
|
43
39
|
placeholder?: string;
|
|
44
40
|
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.
|
|
4
|
+
"version": "2.1.8",
|
|
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",
|
|
@@ -67,29 +67,31 @@
|
|
|
67
67
|
"svelte": "^5.1.4"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@sveltejs/adapter-auto": "^
|
|
71
|
-
"@sveltejs/adapter-cloudflare": "^5.0.
|
|
72
|
-
"@sveltejs/kit": "^2.
|
|
73
|
-
"@sveltejs/package": "^2.3.
|
|
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",
|
|
74
74
|
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
|
75
|
+
"@tailwindcss/postcss": "^4.0.3",
|
|
75
76
|
"@tailwindcss/typography": "^0.5.16",
|
|
77
|
+
"@tailwindcss/vite": "^4.0.3",
|
|
76
78
|
"@types/eslint": "^9.6.1",
|
|
77
79
|
"autoprefixer": "^10.4.20",
|
|
78
|
-
"eslint": "^9.
|
|
79
|
-
"eslint-config-prettier": "^
|
|
80
|
+
"eslint": "^9.19.0",
|
|
81
|
+
"eslint-config-prettier": "^10.0.1",
|
|
80
82
|
"eslint-plugin-svelte": "^2.46.1",
|
|
81
83
|
"globals": "^15.14.0",
|
|
82
|
-
"postcss": "^8.
|
|
84
|
+
"postcss": "^8.5.1",
|
|
83
85
|
"prettier": "^3.4.2",
|
|
84
|
-
"prettier-plugin-svelte": "^3.3.
|
|
85
|
-
"publint": "^0.3.
|
|
86
|
-
"svelte": "^5.
|
|
87
|
-
"svelte-check": "^4.1.
|
|
88
|
-
"tailwindcss": "^
|
|
86
|
+
"prettier-plugin-svelte": "^3.3.3",
|
|
87
|
+
"publint": "^0.3.2",
|
|
88
|
+
"svelte": "^5.19.6",
|
|
89
|
+
"svelte-check": "^4.1.4",
|
|
90
|
+
"tailwindcss": "^4.0.3",
|
|
89
91
|
"tslib": "^2.8.1",
|
|
90
92
|
"typescript": "^5.7.3",
|
|
91
|
-
"typescript-eslint": "^8.
|
|
92
|
-
"vite": "^6.0.
|
|
93
|
+
"typescript-eslint": "^8.22.0",
|
|
94
|
+
"vite": "^6.0.11"
|
|
93
95
|
},
|
|
94
96
|
"svelte": "./dist/index.js",
|
|
95
97
|
"types": "./dist/PlaceAutocomplete.svelte.d.ts",
|