places-autocomplete-svelte 1.0.1 → 2.0.0

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
@@ -3,32 +3,34 @@
3
3
  This Svelte component leverages the [Google Maps Places Autocomplete API](https://developers.google.com/maps/documentation/javascript/place-autocomplete-overview) to provide a user-friendly way to search for and retrieve detailed address information within your [SvelteKit](https://kit.svelte.dev) applications.
4
4
 
5
5
  ## Features:
6
- - Seamless Integration: Easily integrate the component into your SvelteKit projects.
7
- - Autocomplete Suggestions: Provide users with real-time suggestions as they type, enhancing the search experience.
8
- - Detailed Address Retrieval: Retrieve comprehensive address information, including street address, city, region, postal code, and country.
9
- - Formatted and Unformatted Responses: Access both formatted address strings and raw address component data for flexible use cases.
10
- - Country Selection: Allow users to specify a region for more targeted results.
11
-
12
6
 
7
+ - **Seamless Integration:** Easily integrate the component into your SvelteKit projects.
8
+ - **Autocomplete Suggestions:** Provide users with real-time suggestions as they type, enhancing the search experience.
9
+ - **Detailed Address Retrieval:** Retrieve comprehensive address information, including street address, city, region, postal code, and country.
10
+ - **Formatted and Unformatted Responses:** Access both formatted address strings and raw address component data for flexible use cases.
11
+ - **Country/Region Selection:** Allow users to specify a region for more targeted results.
12
+ - **Customizable:** Tailor the component's appearance and behavior using language settings and placeholder text.
13
13
 
14
14
  ![Places Autocomplete Svelte](places-autocomplete-svelte.gif)
15
15
 
16
16
 
17
+
17
18
  ## Requirements
18
19
 
19
- Before using this component, you'll need:
20
- - Google Maps API Key: Create an 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.
20
+ - **Google Maps API Key:** Create an 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.
21
21
 
22
+ ## Installation Svelte 5
22
23
 
23
- ## Installation
24
+ ```bash
25
+ npm i places-autocomplete-svelte
26
+ ```
24
27
 
25
- Using npm:
28
+ ## Installation Svelte 4
26
29
 
27
30
  ```bash
28
- $ npm i places-autocomplete-svelte
31
+ npm i places-autocomplete-svelte@1.0.1
29
32
  ```
30
33
 
31
-
32
34
  ## Import Component
33
35
 
34
36
  ```javascript
@@ -37,168 +39,143 @@ import PlaceAutocomplete from 'places-autocomplete-svelte';
37
39
 
38
40
  ## Basic Usage
39
41
 
40
- Initiate the component with your public Google Maps API key `PUBLIC_GOOGLE_MAPS_API_KEY` and `formattedAddress` properties. Enter your search query in the search box, then click to select the results. The `formattedAddress` property poulated with the place name and address.
42
+ 1. **Provide your Google Maps API Key:** Replace `'--YOUR_API_KEY--'` with your actual Google Maps API key.
43
+ 2. **Bind to Address Variables:** Use `bind:formattedAddress` to capture the selected address as string.
41
44
 
42
- The request default `language: 'en-GB'` and `region: 'GB'`, see below to specify different.
43
-
44
- Use keyboard `up` and `down` key to navigate the suggested results. `esc` will clear the search query.
45
-
46
- ```js
45
+ ```svelte
47
46
  <script>
48
- import {PlaceAutocomplete} from 'places-autocomplete-svelte';
49
-
50
- //the business name and address.
51
- let formattedAddress = '';
52
- // Your Google Maps Public API Key
53
- let PUBLIC_GOOGLE_MAPS_API_KEY = '--YOUR_API_KEY--';
54
- </script>
47
+ import { PlaceAutocomplete } from 'places-autocomplete-svelte';
55
48
 
56
- <PlaceAutocomplete bind:PUBLIC_GOOGLE_MAPS_API_KEY bind:formattedAddress/>
49
+ let formattedAddress = '';
50
+ let PUBLIC_GOOGLE_MAPS_API_KEY = '--YOUR_API_KEY--';
51
+ </script>
57
52
 
58
- // Etihad Stadium, Etihad Campus, Manchester M11 3FF
59
- <p>{formattedAddress}</p>
53
+ <PlaceAutocomplete
54
+ bind:PUBLIC_GOOGLE_MAPS_API_KEY
55
+ bind:formattedAddress />
60
56
 
57
+ <p>Formatted Address: {formattedAddress}</p>
61
58
  ```
62
- - Replace `'--YOUR_API_KEY--'` with your actual Google Maps API key.
63
- - The `formattedAddress` variable will be populated with the selected place's formatted address string.
64
59
 
65
- ## Accessing Address Components
66
60
 
67
- ```js
61
+ ## Specifying Countries/Regions
62
+
63
+ Use the `countries` property to refine search by region:
64
+
65
+ ```svelte
68
66
  <script>
69
- import {PlaceAutocomplete} from 'places-autocomplete-svelte';
70
- // formatted address object
71
- let formattedAddressObj = {
72
- street_number: '',
73
- street: '',
74
- town: '',
75
- county: '',
76
- country_iso2: '',
77
- postcode: ''
78
- };
79
- // Your Google Maps Public API Key
80
- let PUBLIC_GOOGLE_MAPS_API_KEY = '--YOUR_API_KEY--';
67
+ // ... other imports
81
68
 
69
+ let countries = [
70
+ { name: 'United Kingdom', region: 'GB' , language:'en-GB'},
71
+ { name: 'United States', region: 'US',language:'en-US' },
72
+ // ... more countries
73
+ ];
82
74
  </script>
83
75
 
84
76
  <PlaceAutocomplete
85
77
  bind:PUBLIC_GOOGLE_MAPS_API_KEY
86
- bind:formattedAddressObj />
87
- /**
88
- * Formatted address
89
- * {
90
- * "street_number":"Etihad Stadium",
91
- * "street":"Etihad Campus",
92
- * "town":"Manchester",
93
- * "county":"Greater Manchester",
94
- * "country_iso2":"GB",
95
- * "postcode":"M11 3FF"
96
- * }
97
- */
98
- <p>{JSON.stringify(formattedAddressObj)}</p>
99
-
78
+ bind:formattedAddress
79
+ bind:countries />
100
80
  ```
101
81
 
102
- - The `formattedAddressObj` will contain parsed address components, allowing you to access individual elements like street number, town, and postcode.
103
-
104
- Address Parsing:
82
+ - The `region` code follows the [CLDR two-character format](https://developers.google.com/maps/documentation/javascript/reference/autocomplete-data#AutocompleteRequest).
83
+ - The `language` in which to return results. [See details](https://developers.google.com/maps/documentation/javascript/reference/autocomplete-data#AutocompleteRequest.language)
105
84
 
106
- The Google Mps API `fetchFields()` method retrieves detailed place information. This information is then mapped to the `formattedAddressObj` based on address component types. The following mapping corresponds to the following component types:
85
+ ## Accessing Full Response Data
107
86
 
108
- - `street_number`: longText property of the `street_number`
109
- - `street`: longText property of the `route`
110
- - `town`: longText property of the `postal_town`
111
- - `county`: longText property of the `administrative_area_level_2`
112
- - `country_iso2`: shortText property of the `country`
113
- - `postcode`: longText property of the `postal_code`
87
+ For maximum flexibility, access the complete unformatted response from the Google Maps API:
114
88
 
115
- Benefits:
116
-
117
- By using `formattedAddressObj`, you can easily access individual address components like street number, town, and postcode, simplifying address manipulation in your application.
89
+ ```svelte
90
+ <script>
91
+ // ... other imports
92
+ let fullResponse = {};
93
+ </script>
118
94
 
119
- ## Specifying Countries
95
+ <PlaceAutocomplete bind:PUBLIC_GOOGLE_MAPS_API_KEY bind:fullResponse />
120
96
 
97
+ <pre>{JSON.stringify(fullResponse, null, 2)}</pre>
98
+ ```
121
99
 
122
- ```js
100
+ ## Example
101
+ ```svelte
123
102
  <script>
124
- import {PlaceAutocomplete} from 'places-autocomplete-svelte';
125
-
126
- //the business name and address.
127
- let formattedAddress = '';
128
- // Your Google Maps Public API Key
129
- let PUBLIC_GOOGLE_MAPS_API_KEY = '--YOUR_API_KEY--';
130
- // countries
131
- let countries = [
132
- { name: 'United Kingdom', region: 'GB' },
133
- { name: 'United States', region: 'US' },
134
- { name: 'Switzerland', region: 'CH' },
135
- { name: 'Greece', region: 'GR' },
136
- { name: 'Russia', region: 'RU' },
137
- { name: 'Japan', region: 'JP' }
138
- ];
103
+ import { PlaceAutocomplete } from 'places-autocomplete-svelte';
104
+
105
+ let PUBLIC_GOOGLE_MAPS_API_KEY = '--YOUR_API_KEY--';
106
+ let formattedAddress = '';
107
+ let fullResponse = {};
108
+ let formattedAddressObj = {};
109
+ let countries = [
110
+ { name: 'United Kingdom', region: 'GB' , language:'en-GB'},
111
+ { name: 'United States', region: 'US',language:'en-US' },
112
+ // ... more countries
113
+ ];
139
114
  </script>
140
115
 
141
116
  <PlaceAutocomplete
142
117
  bind:PUBLIC_GOOGLE_MAPS_API_KEY
143
- bind:formattedAddress
144
- bind:countries/>
145
-
146
- // Etihad Stadium, Etihad Campus, Manchester M11 3FF
147
- <p>{formattedAddress}</p>
118
+ bind:formattedAddress
119
+ bind:fullResponse
120
+ bind:formattedAddressObj
121
+ bind:countries
122
+ placeholder="Enter your address...">
148
123
 
149
124
  ```
125
+ - The `formattedAddress` - selected address as string.
126
+ - The `fullResponse` - the complete unformatted response from the Google Maps API.
127
+ - The `formattedAddressObj` - parsed address components, containing individual elements like street number, town, and postcode.
128
+
129
+ The `formattedAddressObj` mapping corresponds to the following component types:
130
+
131
+ - `formattedAddressObj.street_number`: longText property of the street_number
132
+ - `formattedAddressObj.street`: longText property of the route
133
+ - `formattedAddressObj.town`: longText property of the postal_town
134
+ - `formattedAddressObj.county`: longText property of the administrative_area_level_2
135
+ - `formattedAddressObj.country_iso2`: shortText property of the country
136
+ - `formattedAddressObj.postcode`: longText property of the postal_code
150
137
 
151
- - The `countries` array allows users to select a specific region for their search.
152
138
 
153
- An optional property `countries` to allow country selection. As per [Google Maps documentation](https://developers.google.com/maps/documentation/javascript/reference/autocomplete-data#AutocompleteRequest):
154
139
 
155
- `The region code, specified as a CLDR two-character region code. This affects address formatting, result ranking, and may influence what results are returned. This does not restrict results to the specified region.`
140
+ ## Error Handling
156
141
 
142
+ The component will throw an error if:
143
+ - The Google Maps API key is invalid or missing.
144
+ - There are network issues connecting to the Google Maps service.
157
145
 
158
- ## Formatted and Unformatted reponses
146
+ Handle these errors gracefully in your application:
159
147
 
160
- ```js
148
+ ```svelte
161
149
  <script>
162
- import {PlaceAutocomplete} from 'places-autocomplete-svelte';
163
-
164
- //the business name and address.
165
- let formattedAddress = '';
166
- // full unformatted response
167
- let fullResponse = [];
168
- // Your Google Maps Public API Key
169
- let PUBLIC_GOOGLE_MAPS_API_KEY = '--YOUR_API_KEY--';
170
- // countries
171
- let countries = [
172
- { name: 'United Kingdom', region: 'GB' },
173
- { name: 'United States', region: 'US' },
174
- { name: 'Switzerland', region: 'CH' },
175
- { name: 'Greece', region: 'GR' },
176
- { name: 'Russia', region: 'RU' },
177
- { name: 'Japan', region: 'JP' }
178
- ];
150
+ // ... other imports
151
+
152
+ // Error handler
153
+ let pacError = '';
154
+ let onError = (error:string) => {
155
+ console.error(error);
156
+ pacError = error;
157
+ };
179
158
  </script>
180
159
 
181
- <PlaceAutocomplete
182
- bind:formattedAddress
183
- bind:formattedAddressObj
184
- bind:fullResponse
185
- bind:PUBLIC_GOOGLE_MAPS_API_KEY
186
- bind:countries/>
160
+ <PlaceAutocomplete bind:PUBLIC_GOOGLE_MAPS_API_KEY {onError} />
187
161
 
162
+ {#if pacError}
163
+ <p class="error">{pacError}</p>
164
+ {/if}
188
165
  ```
189
- Depending on your application requirements you can bind the component properties as needed.
190
- Below example binds all three responsones:
191
- - `formattedAddress` - place name and address as string
192
- - `formattedAddressObj` - populated with the parsed address components
193
- - `fullResponse` - populated address components response as it retruned from `fetchFilds()` method
194
166
 
195
- ## Customization:
196
167
 
197
- - **Language:** The component defaults to `language: 'en-GB'` and `region: 'GB'`. You can customize these settings as needed.
198
168
 
199
169
 
200
170
 
171
+
172
+
173
+ ## Contributing
174
+
175
+ Contributions are welcome! Please open an issue or submit a pull request on the [GitHub repository](https://github.com/alexpechkarev/places-autocomplete-svelte/).
176
+
201
177
  ## License
202
178
 
203
179
  [MIT](LICENSE)
204
180
 
181
+
@@ -1,162 +1,233 @@
1
- <script>import { onMount } from "svelte";
2
- import * as GMaps from "@googlemaps/js-api-loader";
3
- const { Loader } = GMaps;
4
- export let PUBLIC_GOOGLE_MAPS_API_KEY = "";
5
- export let fetchFields = ["displayName", "formattedAddress", "addressComponents"];
6
- export let countries = [];
7
- let hasCountries = countries.length > 0;
8
- export let formattedAddress = "";
9
- export let fullResponse = [];
10
- export let formattedAddressObj = {
11
- street_number: "",
12
- street: "",
13
- town: "",
14
- county: "",
15
- country_iso2: "",
16
- postcode: ""
17
- };
18
- let placesError = "";
19
- let inputRef;
20
- let currentSuggestion = -1;
21
- let title = "";
22
- let results = [];
23
- let token;
24
- let loader;
25
- let placesApi = {};
26
- let request = {
27
- input: "",
28
- language: "en-GB",
29
- region: "GB",
30
- sessionToken: ""
31
- };
32
- $: {
33
- if (request.input == "") {
34
- results = [];
35
- }
36
- }
37
- const reset = () => {
38
- currentSuggestion = -1;
39
- request.input = "";
40
- results = [];
41
- refreshToken(request);
42
- };
43
- const makeAcRequest = async (event) => {
44
- const target = event.currentTarget;
45
- if (target?.value == "") {
46
- title = "";
47
- request.input = "";
48
- results = [];
49
- return;
50
- }
51
- request.input = target.value;
52
- const { suggestions } = await placesApi.AutocompleteSuggestion.fetchAutocompleteSuggestions(request);
53
- results = [];
54
- for (const suggestion of suggestions) {
55
- results.push({
56
- to_pace: suggestion.placePrediction.toPlace(),
57
- text: suggestion.placePrediction.text.toString()
58
- });
59
- }
60
- };
61
- const onPlaceSelected = async (place) => {
62
- await place.fetchFields({
63
- fields: fetchFields
64
- });
65
- let placeData = place.toJSON();
66
- formattedAddressObj = {
67
- street_number: "",
68
- street: "",
69
- town: "",
70
- county: "",
71
- country_iso2: "",
72
- postcode: ""
73
- };
74
- title = "Selected address: " + placeData.formattedAddress;
75
- formattedAddress = placeData.formattedAddress;
76
- fullResponse = placeData.addressComponents;
77
- for (const component of placeData.addressComponents) {
78
- switch (component.types[0]) {
79
- case "street_number":
80
- case "point_of_interest":
81
- case "establishment":
82
- formattedAddressObj.street_number = component.longText;
83
- break;
84
- case "route":
85
- case "premise":
86
- formattedAddressObj.street = component.longText;
87
- break;
88
- case "postal_town":
89
- formattedAddressObj.town = component.longText;
90
- break;
91
- case "administrative_area_level_2":
92
- formattedAddressObj.county = component.longText;
93
- break;
94
- case "country":
95
- formattedAddressObj.country_iso2 = component.shortText;
96
- break;
97
- case "postal_code":
98
- formattedAddressObj.postcode = component.longText;
99
- break;
100
- }
101
- }
102
- reset();
103
- };
104
- const refreshToken = async (request2) => {
105
- token = new placesApi.AutocompleteSessionToken();
106
- request2.sessionToken = token;
107
- return request2;
108
- };
109
- onMount(async () => {
110
- inputRef.focus();
111
- try {
112
- loader = new Loader({
113
- apiKey: PUBLIC_GOOGLE_MAPS_API_KEY,
114
- version: "weekly",
115
- libraries: ["places"]
116
- });
117
- const { AutocompleteSessionToken, AutocompleteSuggestion } = await loader.importLibrary("places");
118
- placesApi.AutocompleteSessionToken = AutocompleteSessionToken;
119
- placesApi.AutocompleteSuggestion = AutocompleteSuggestion;
120
- token = new placesApi.AutocompleteSessionToken();
121
- request.sessionToken = token;
122
- } catch (e) {
123
- console.error(e);
124
- placesError = "An error occurred - see console for details.";
125
- }
126
- });
127
- function onKeyDown(e) {
128
- if (e.key === "ArrowDown") {
129
- currentSuggestion = Math.min(currentSuggestion + 1, results.length - 1);
130
- } else if (e.key === "ArrowUp") {
131
- currentSuggestion = Math.max(currentSuggestion - 1, 0);
132
- } else if (e.key === "Enter") {
133
- e.preventDefault();
134
- if (currentSuggestion >= 0) {
135
- onPlaceSelected(results[currentSuggestion].to_pace);
136
- }
137
- } else if (e.key === "Escape") {
138
- reset();
139
- }
140
- }
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import * as GMaps from '@googlemaps/js-api-loader';
4
+ const { Loader } = GMaps;
5
+
6
+
7
+
8
+ export let PUBLIC_GOOGLE_MAPS_API_KEY: string = '';
9
+ export let fetchFields: string[] = ['displayName', 'formattedAddress', 'addressComponents'];
10
+ export let countries: { name: string; region: string }[] = [];
11
+ let hasCountries = countries.length > 0;
12
+
13
+ export let formattedAddress: string = '';
14
+ export let fullResponse: { longText: string; shortText: string; types: Array<string> }[] = [];
15
+ export let formattedAddressObj: {
16
+ street_number: string;
17
+ street: string;
18
+ town: string;
19
+ county: string;
20
+ country_iso2: string;
21
+ postcode: string;
22
+ } = {
23
+ street_number: '',
24
+ street: '',
25
+ town: '',
26
+ county: '',
27
+ country_iso2: '',
28
+ postcode: ''
29
+ };
30
+ export let onError: (error: string) => void = (error: string) => {};
31
+
32
+ let inputRef: HTMLInputElement;
33
+ let currentSuggestion = -1;
34
+ let title: string = '';
35
+ let results: any[] = [];
36
+ let token;
37
+ let loader: GMaps.Loader;
38
+ let placesApi: { [key: string]: any } = {};
39
+ //https://developers.google.com/maps/documentation/javascript/reference/autocomplete-data#AutocompleteRequest.includedPrimaryTypes
40
+ let request = {
41
+ input: '',
42
+ language: 'en-GB',
43
+ region: 'GB',
44
+ sessionToken: ''
45
+ };
46
+
47
+ $: {
48
+ if (request.input == '') {
49
+ results = [];
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Reset search input and results.
55
+ */
56
+ const reset = () => {
57
+ currentSuggestion = -1;
58
+ request.input = '';
59
+ results = [];
60
+ refreshToken(request);
61
+ };
62
+
63
+ /**
64
+ * Make request and get autocomplete suggestions.
65
+ * @param event
66
+ */
67
+ const makeAcRequest = async (
68
+ event: Event & { currentTarget: HTMLInputElement }
69
+ ): Promise<void> => {
70
+ const target = event.currentTarget as HTMLInputElement;
71
+ if (target?.value == '') {
72
+ title = '';
73
+ request.input = '';
74
+ results = [];
75
+ return;
76
+ }
77
+
78
+ request.input = target.value;
79
+ try {
80
+ const { suggestions } =
81
+ await placesApi.AutocompleteSuggestion.fetchAutocompleteSuggestions(request);
82
+ results = [];
83
+ // iterate suggestions and add results to an array
84
+ for (const suggestion of suggestions) {
85
+ // add suggexstions to results
86
+ results.push({
87
+ to_pace: suggestion.placePrediction.toPlace(),
88
+ text: suggestion.placePrediction.text.toString()
89
+ });
90
+ }
91
+ } catch (e) {
92
+ onError((e.name || 'An error occurred') + ' - ' + (e.message || 'see console for details.'));
93
+ }
94
+ };
95
+ /**
96
+ * Event handler for clicking on a suggested place.
97
+ //https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/reference/autocomplete-data#AutocompleteSuggestion
98
+ * @param place
99
+ */
100
+ const onPlaceSelected = async (place: {
101
+ [x: string]: any;
102
+ fetchFields: (arg0: { fields: string[] }) => any;
103
+ addressComponents: any;
104
+ formattedAddress: string;
105
+ }): Promise<void> => {
106
+ try {
107
+
108
+ await place.fetchFields({
109
+ fields: fetchFields
110
+ });
111
+ let placeData = place.toJSON();
112
+ formattedAddressObj = {
113
+ street_number: '',
114
+ street: '',
115
+ town: '',
116
+ county: '',
117
+ country_iso2: '',
118
+ postcode: ''
119
+ };
120
+
121
+ title = 'Selected address: ' + placeData.formattedAddress;
122
+ formattedAddress = placeData.formattedAddress;
123
+ fullResponse = placeData.addressComponents;
124
+
125
+ for (const component of placeData.addressComponents) {
126
+ switch (component.types[0]) {
127
+ case 'street_number':
128
+ case 'point_of_interest':
129
+ case 'establishment':
130
+ formattedAddressObj.street_number = component.longText;
131
+ break;
132
+ case 'route':
133
+ case 'premise':
134
+ formattedAddressObj.street = component.longText;
135
+ break;
136
+ case 'postal_town':
137
+ formattedAddressObj.town = component.longText;
138
+ break;
139
+ case 'administrative_area_level_2':
140
+ formattedAddressObj.county = component.longText;
141
+ break;
142
+ case 'country':
143
+ formattedAddressObj.country_iso2 = component.shortText;
144
+ break;
145
+ case 'postal_code':
146
+ formattedAddressObj.postcode = component.longText;
147
+ break;
148
+ }
149
+ }
150
+ } catch (e) {
151
+ onError((e.name || 'An error occurred') + ' - ' + (e.message || 'error fetching place details'));
152
+ }
153
+
154
+ // reset srarch input and results
155
+ reset();
156
+ };
157
+
158
+ /**
159
+ * Helper function to refresh the session token.
160
+ * @param request
161
+ */
162
+ const refreshToken = async (request: {
163
+ input?: string;
164
+ language?: string;
165
+ region?: string;
166
+ sessionToken?: any;
167
+ }): Promise<{ input?: string; language?: string; region?: string; sessionToken?: any }> => {
168
+ try{
169
+ token = new placesApi.AutocompleteSessionToken();
170
+ request.sessionToken = token;
171
+ return request;
172
+
173
+ }catch(e){
174
+ onError((e.name || 'An error occurred') + ' - ' + (e.message || 'error fetch token'));
175
+ return request;
176
+ }
177
+ };
178
+ /**
179
+ * Initialize the Google Maps JavaScript API Loader.
180
+ */
181
+ onMount(async (): Promise<void> => {
182
+ inputRef.focus();
183
+
184
+ try {
185
+ loader = new Loader({
186
+ apiKey: PUBLIC_GOOGLE_MAPS_API_KEY,
187
+ version: 'weekly',
188
+ libraries: ['places']
189
+ });
190
+
191
+ const { AutocompleteSessionToken, AutocompleteSuggestion } =
192
+ await loader.importLibrary('places');
193
+ placesApi.AutocompleteSessionToken = AutocompleteSessionToken;
194
+ placesApi.AutocompleteSuggestion = AutocompleteSuggestion;
195
+ token = new placesApi.AutocompleteSessionToken();
196
+ request.sessionToken = token;
197
+ } catch (e) {
198
+ onError(
199
+ (e.name || 'An error occurred') + ' - ' + (e.message || 'Error loading Google Maps API')
200
+ );
201
+ }
202
+ });
203
+ /**
204
+ * Handles keyboard events for navigating the suggestions.
205
+ */
206
+ function onKeyDown(e: KeyboardEvent) {
207
+ if (e.key === 'ArrowDown') {
208
+ currentSuggestion = Math.min(currentSuggestion + 1, results.length - 1);
209
+ } else if (e.key === 'ArrowUp') {
210
+ currentSuggestion = Math.max(currentSuggestion - 1, 0);
211
+ } else if (e.key === 'Enter') {
212
+ e.preventDefault();
213
+ if (currentSuggestion >= 0) {
214
+ onPlaceSelected(results[currentSuggestion].to_pace);
215
+ }
216
+ } else if (e.key === 'Escape') {
217
+ // reset srarch input and results
218
+ reset();
219
+ }
220
+ }
141
221
  </script>
142
222
 
143
223
  <svelte:window on:keydown={onKeyDown} />
144
224
 
145
225
  <section class="my-10">
146
- {#if placesError}
147
- <div
148
- class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative"
149
- role="alert"
150
- >
151
- <strong class="font-bold">Error!</strong>
152
- <span class="block">Check your PUBLIC_GOOGLE_MAPS_API_KEY and API restrictions.</span>
153
- <span class="block sm:inline">{placesError}</span>
154
- </div>
155
- {/if}
156
-
157
226
  <div class="grid grid-cols-1 lg:grid-cols-6 gap-x-4">
158
227
  <div class:lg:col-span-4={hasCountries} class:lg:col-span-6={!hasCountries}>
159
- <label class="mt-1 text-sm leading-6 text-gray-600" for="search">Start typing your address</label>
228
+ <label class="mt-1 text-sm leading-6 text-gray-600" for="search"
229
+ >Start typing your address</label
230
+ >
160
231
  <div class="relative z-50 transform rounded-xl mt-4">
161
232
  <div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
162
233
  <svg
@@ -170,11 +241,12 @@ function onKeyDown(e) {
170
241
  stroke-linejoin="round"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg
171
242
  >
172
243
  </div>
244
+
173
245
  <input
174
246
  type="text"
175
247
  name="search"
176
248
  bind:this={inputRef}
177
- class="border-1 w-full rounded-md border-gray-300 bg-gray-100 px-4 py-2.5 pl-10 pr-20 text-gray-900 focus:ring-1 sm:text-sm"
249
+ class="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"
178
250
  placeholder="Search..."
179
251
  aria-controls="options"
180
252
  bind:value={request.input}
@@ -228,7 +300,7 @@ function onKeyDown(e) {
228
300
  <select
229
301
  id="country"
230
302
  name="country"
231
- class="h-10 w-full rounded-md border-1 bg-transparent py-0 pl-2 pr-7 text-gray-500 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm"
303
+ class="h-10 w-full rounded-md border-0 bg-transparent py-0 pl-2 pr-7 text-gray-500 focus:ring-2 ring-1 ring-inset ring-gray-300 focus:ring-inset focus:ring-indigo-600 sm:text-sm"
232
304
  bind:value={request.region}
233
305
  >
234
306
  {#each countries as country}
@@ -242,8 +314,4 @@ function onKeyDown(e) {
242
314
  <div class="text-sm font-medium leading-6 text-gray-500">{title}</div>
243
315
  </div>
244
316
  </div>
245
- </section>
246
-
247
- <style>
248
- input,select,ul{margin:0;padding:0}.absolute,.sr-only{position:absolute}.block,svg{display:block}*,.border-gray-300{--tw-border-opacity:1}.text-gray-500,.text-gray-900,.text-red-700,.text-white{--tw-text-opacity:1}*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb;--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / 0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }:after,:before{--tw-content:''}:host,section{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:system-ui,sans-serif,apple color emoji,segoe ui emoji,Segoe UI Symbol,noto color emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}strong{font-weight:bolder}kbd{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}input,select{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit}input:where([type=button]),input:where([type=reset]),input:where([type=submit]){background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}ul{list-style:none}.cursor-default,:disabled{cursor:default}svg{vertical-align:middle}[type=text],input:where(:not([type])),select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow:0 0 #0000}[type=text]:focus,input:where(:not([type])):focus,select:focus{outline:transparent solid 2px;outline-offset:2px;--tw-ring-inset:var(--tw-empty,);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}input::-moz-placeholder{color:#6b7280;opacity:1}input::placeholder{color:#6b7280;opacity:1}select{text-transform:none;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIGZpbGw9J25vbmUnIHZpZXdCb3g9JzAgMCAyMCAyMCc+PHBhdGggc3Ryb2tlPScjNmI3MjgwJyBzdHJva2UtbGluZWNhcD0ncm91bmQnIHN0cm9rZS1saW5lam9pbj0ncm91bmQnIHN0cm9rZS13aWR0aD0nMS41JyBkPSdNNiA4bDQgNCA0LTQnLz48L3N2Zz4=);background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}:root{--background:0 0% 100%;--foreground:222.2 84% 4.9%;--muted:210 40% 96.1%;--muted-foreground:215.4 16.3% 46.9%;--popover:0 0% 100%;--popover-foreground:222.2 84% 4.9%;--card:0 0% 100%;--card-foreground:222.2 84% 4.9%;--border:214.3 31.8% 91.4%;--input:214.3 31.8% 91.4%;--primary:222.2 47.4% 11.2%;--primary-foreground:210 40% 98%;--secondary:210 40% 96.1%;--secondary-foreground:222.2 47.4% 11.2%;--accent:210 40% 96.1%;--accent-foreground:222.2 47.4% 11.2%;--destructive:0 72.2% 50.6%;--destructive-foreground:210 40% 98%;--ring:222.2 84% 4.9%;--radius:0.5rem}*{border-color:hsl(var(--border) / var(--tw-border-opacity))}::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / 0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.sr-only{width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.relative{position:relative}.inset-y-0{top:0;bottom:0}.left-0{left:0}.right-0{right:0}.z-50{z-index:50}.-mb-2{margin-bottom:-.5rem}.mr-1{margin-right:.25rem}.mt-4{margin-top:1rem}.flex{display:flex}.inline-flex{display:inline-flex}.hidden{display:none}.h-10{height:2.5rem}.h-5{height:1.25rem}.max-h-60{max-height:15rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr));grid-template-columns:1fr}.items-center{align-items:center}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem;row-gap:1rem}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-gray-300{border-color:rgb(209 213 219 / var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.bg-indigo-500,.hover\:bg-indigo-500:hover{--tw-bg-opacity:1;background-color:rgb(99 102 241 / var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.px-1{padding-left:.25rem;padding-right:.25rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0{padding-top:0;padding-bottom:0}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.pl-10{padding-left:2.5rem}.pl-2{padding-left:.5rem}.pl-3{padding-left:.75rem}.pl-4{padding-left:1rem}.pr-1\.5{padding-right:.375rem}.pr-20{padding-right:5rem}.pr-7{padding-right:1.75rem}.text-base{font-size:1rem;line-height:1.5rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.text-gray-500{color:rgb(107 114 128 / var(--tw-text-opacity))}.text-gray-900{color:rgb(17 24 39 / var(--tw-text-opacity))}.text-red-700{color:rgb(185 28 28 / var(--tw-text-opacity))}.text-white{color:rgb(255 255 255 / var(--tw-text-opacity))}.shadow-lg{--tw-shadow:0 10px 15px -3px rgb(0 0 0 / 0.1),0 4px 6px -4px rgb(0 0 0 / 0.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.focus\:ring-1:focus,.focus\:ring-2:focus,.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-1:focus,.ring-1{--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-black{--tw-ring-opacity:1;--tw-ring-color:rgb(0 0 0 / var(--tw-ring-opacity))}.focus\:outline-none:focus{outline:transparent solid 2px;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-inset:focus{--tw-ring-inset:inset}@media (min-width:640px){.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}@media (min-width:1024px){.lg\:col-span-2{grid-column:span 2/span 2}}@keyframes swipe-out{0%{transform:translateY(calc(var(--lift) * var(--offset) + var(--swipe-amount)));opacity:1}to{transform:translateY(calc(var(--lift) * var(--offset) + var(--swipe-amount) + var(--lift) * -100%));opacity:0}}.grid{display:grid}@media (min-width:768px){.lg\:grid-cols-6{grid-template-columns:repeat(6,1fr)}.lg\:col-span-6{grid-column-start:span 6}.lg\:col-span-4{grid-column-start:span 4}.lg\:col-span-2{grid-column-start:span 2}}.my-10{margin-top:2.5rem;margin-bottom:2.5rem} .justify-center {justify-content: center;} .hover\:text-white:hover{color:rgb(255 255 255 / var(--tw-text-opacity))}
249
- </style>
317
+ </section>
@@ -1,37 +1,40 @@
1
- import { SvelteComponent } from "svelte";
2
- declare const __propDef: {
3
- props: {
4
- PUBLIC_GOOGLE_MAPS_API_KEY?: string;
5
- fetchFields?: string[];
6
- countries?: {
7
- name: string;
8
- region: string;
9
- }[];
10
- formattedAddress?: string;
11
- fullResponse?: {
12
- longText: string;
13
- shortText: string;
14
- types: Array<string>;
15
- }[];
16
- formattedAddressObj?: {
17
- street_number: string;
18
- street: string;
19
- town: string;
20
- county: string;
21
- country_iso2: string;
22
- postcode: string;
23
- };
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: Props & {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
24
11
  };
25
- events: {
26
- [evt: string]: CustomEvent<any>;
27
- };
28
- slots: {};
29
- exports?: {} | undefined;
30
- bindings?: string | undefined;
31
- };
32
- export type PlaceAutocompleteProps = typeof __propDef.props;
33
- export type PlaceAutocompleteEvents = typeof __propDef.events;
34
- export type PlaceAutocompleteSlots = typeof __propDef.slots;
35
- export default class PlaceAutocomplete extends SvelteComponent<PlaceAutocompleteProps, PlaceAutocompleteEvents, PlaceAutocompleteSlots> {
12
+ z_$$bindings?: Bindings;
36
13
  }
37
- export {};
14
+ declare const PlaceAutocomplete: $$__sveltets_2_IsomorphicComponent<{
15
+ PUBLIC_GOOGLE_MAPS_API_KEY?: string;
16
+ fetchFields?: string[];
17
+ countries?: {
18
+ name: string;
19
+ region: string;
20
+ }[];
21
+ formattedAddress?: string;
22
+ fullResponse?: {
23
+ longText: string;
24
+ shortText: string;
25
+ types: Array<string>;
26
+ }[];
27
+ formattedAddressObj?: {
28
+ street_number: string;
29
+ street: string;
30
+ town: string;
31
+ county: string;
32
+ country_iso2: string;
33
+ postcode: string;
34
+ };
35
+ onError?: (error: string) => void;
36
+ }, {
37
+ [evt: string]: CustomEvent<any>;
38
+ }, {}, {}, string>;
39
+ type PlaceAutocomplete = InstanceType<typeof PlaceAutocomplete>;
40
+ export default PlaceAutocomplete;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "places-autocomplete-svelte",
3
3
  "license": "MIT",
4
- "version": "1.0.1",
4
+ "version": "2.0.0",
5
5
  "description": "A Svelte component that leverages the power of Google Places New API to provide a user-friendly autocomplete experience for location searches.",
6
6
  "keywords": [
7
7
  "svelte",
@@ -55,34 +55,40 @@
55
55
  "!dist/**/*.test.*",
56
56
  "!dist/**/*.spec.*"
57
57
  ],
58
+ "publishConfig": {
59
+ "@alexpechkarev:registry": "https://npm.pkg.github.com"
60
+ },
58
61
  "peerDependencies": {
59
62
  "svelte": "^4.0.0"
60
63
  },
61
64
  "devDependencies": {
62
- "@sveltejs/adapter-auto": "^3.2.2",
63
- "@sveltejs/adapter-cloudflare": "^4.6.1",
64
- "@sveltejs/kit": "^2.5.18",
65
- "@sveltejs/package": "^2.3.2",
66
- "@sveltejs/vite-plugin-svelte": "^3.1.1",
67
- "@types/eslint": "^8.56.10",
68
- "eslint": "^9.7.0",
65
+ "@sveltejs/adapter-auto": "^3.3.1",
66
+ "@sveltejs/adapter-cloudflare": "^4.7.4",
67
+ "@sveltejs/kit": "^2.7.3",
68
+ "@sveltejs/package": "^2.3.7",
69
+ "@sveltejs/vite-plugin-svelte": "^4.0.0",
70
+ "@types/eslint": "^9.6.1",
71
+ "autoprefixer": "^10.4.20",
72
+ "eslint": "^9.13.0",
69
73
  "eslint-config-prettier": "^9.1.0",
70
- "eslint-plugin-svelte": "^2.43.0",
71
- "globals": "^15.8.0",
74
+ "eslint-plugin-svelte": "^2.46.0",
75
+ "globals": "^15.11.0",
76
+ "postcss": "^8.4.47",
72
77
  "prettier": "^3.3.3",
73
- "prettier-plugin-svelte": "^3.2.6",
74
- "publint": "^0.2.9",
75
- "svelte": "^4.2.18",
76
- "svelte-check": "^3.8.4",
77
- "tslib": "^2.6.3",
78
- "typescript": "^5.5.3",
79
- "typescript-eslint": "^8.0.0-alpha.47",
80
- "vite": "^5.3.4"
78
+ "prettier-plugin-svelte": "^3.2.7",
79
+ "publint": "^0.2.12",
80
+ "svelte": "^5.1.3",
81
+ "svelte-check": "^4.0.5",
82
+ "tailwindcss": "^3.4.14",
83
+ "tslib": "^2.8.0",
84
+ "typescript": "^5.6.3",
85
+ "typescript-eslint": "^8.11.0",
86
+ "vite": "^5.4.10"
81
87
  },
82
88
  "svelte": "./dist/index.js",
83
89
  "types": "./dist/PlaceAutocomplete.svelte.d.ts",
84
90
  "type": "module",
85
91
  "dependencies": {
86
- "@googlemaps/js-api-loader": "^1.16.6"
92
+ "@googlemaps/js-api-loader": "^1.16.8"
87
93
  }
88
- }
94
+ }