refactor complete to use actions
This commit is contained in:
188
complete.ts
188
complete.ts
@@ -1,78 +1,128 @@
|
||||
import { validateInput } from '@repo/validate';
|
||||
import type { Action } from 'svelte/action';
|
||||
import { validateInput } from './index';
|
||||
|
||||
/** CompletionPayload carries the current value of an input element when it is validated. */
|
||||
export type CompletionPayload = { place: google.maps.places.PlaceResult };
|
||||
/** CompleteEvent is a custom event that is dispatched when an input is validated. */
|
||||
export class CompleteEvent extends CustomEvent<CompletionPayload> {}
|
||||
|
||||
const googleOptions: google.maps.places.AutocompleteOptions = {
|
||||
// componentRestrictions: { country: ['ca'] },
|
||||
fields: ['address_components'],
|
||||
strictBounds: false
|
||||
};
|
||||
|
||||
/**
|
||||
* AddressComplete is a utility class for handling address input completion using Google Places API.
|
||||
* It binds to an HTML input element and listens for address changes, populating related fields
|
||||
* such as address2, city, province, postalCode, and country.
|
||||
*/
|
||||
export class AddressComplete {
|
||||
private primary?: HTMLInputElement;
|
||||
address2?: HTMLInputElement;
|
||||
city?: HTMLInputElement;
|
||||
province?: HTMLInputElement;
|
||||
postalCode?: HTMLInputElement;
|
||||
country?: HTMLInputElement;
|
||||
private _primary?: HTMLInputElement;
|
||||
private _line2?: HTMLInputElement;
|
||||
private _city?: HTMLInputElement;
|
||||
private _province?: HTMLInputElement;
|
||||
private _postalCode?: HTMLInputElement;
|
||||
private _country?: HTMLInputElement;
|
||||
|
||||
bind(primary: HTMLInputElement) {
|
||||
this.primary = primary;
|
||||
addListener(this.primary, this);
|
||||
}
|
||||
}
|
||||
|
||||
const addListener = (node: HTMLInputElement, completer: AddressComplete) => {
|
||||
const googleOptions: google.maps.places.AutocompleteOptions = {
|
||||
// componentRestrictions: { country: ['ca'] },
|
||||
fields: ['address_components'],
|
||||
strictBounds: false
|
||||
/** registers line 1 address field and attaches an autocompleter */
|
||||
primary: Action<HTMLInputElement, undefined, { oncomplete: (e: CompleteEvent) => void }> = (
|
||||
node
|
||||
) => {
|
||||
this._primary = node;
|
||||
this._configurePrimary();
|
||||
};
|
||||
|
||||
const autocomplete = new google.maps.places.Autocomplete(node, googleOptions);
|
||||
/** registers line 2 address field */
|
||||
line2: Action<HTMLInputElement> = (node) => {
|
||||
this._line2 = node;
|
||||
};
|
||||
/** registers city field */
|
||||
city: Action<HTMLInputElement> = (node) => {
|
||||
this._city = node;
|
||||
};
|
||||
/** registers province field */
|
||||
province: Action<HTMLInputElement> = (node) => {
|
||||
this._province = node;
|
||||
};
|
||||
/** registers postal code field */
|
||||
postalCode: Action<HTMLInputElement> = (node) => {
|
||||
this._postalCode = node;
|
||||
};
|
||||
/** registers country field */
|
||||
country: Action<HTMLInputElement> = (node) => {
|
||||
this._country = node;
|
||||
};
|
||||
|
||||
autocomplete.addListener('place_changed', () => {
|
||||
let address1 = '';
|
||||
let postalCode = '';
|
||||
|
||||
const place = autocomplete.getPlace();
|
||||
// Sort each component
|
||||
for (const component of place.address_components as google.maps.GeocoderAddressComponent[]) {
|
||||
const componentType = component.types[0];
|
||||
|
||||
switch (componentType) {
|
||||
case 'street_number': {
|
||||
address1 = `${component.long_name} ${address1}`;
|
||||
break;
|
||||
}
|
||||
case 'route': {
|
||||
address1 += component.short_name;
|
||||
break;
|
||||
}
|
||||
case 'postal_code': {
|
||||
postalCode = `${component.long_name}${postalCode}`;
|
||||
break;
|
||||
}
|
||||
case 'postal_code_suffix': {
|
||||
postalCode = `${postalCode}-${component.long_name}`;
|
||||
break;
|
||||
}
|
||||
case 'locality': {
|
||||
if (completer.city) completer.city.value = component.long_name;
|
||||
break;
|
||||
}
|
||||
case 'administrative_area_level_1': {
|
||||
// completer.province?.setValStr(component.short_name);
|
||||
if (completer.province) completer.province.value = component.short_name;
|
||||
break;
|
||||
}
|
||||
case 'country': {
|
||||
// completer.country?.setValStr(component.short_name);
|
||||
if (completer.country) completer.country.value = component.short_name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
private _configurePrimary = () => {
|
||||
if (typeof google === 'undefined' || !google.maps || !google.maps.places) {
|
||||
throw new Error('Google Maps Places API is not loaded.');
|
||||
}
|
||||
if (!this._primary) {
|
||||
throw new Error('Primary address input is not registered.');
|
||||
}
|
||||
|
||||
node.value = address1;
|
||||
if (completer.postalCode) completer.postalCode.value = postalCode;
|
||||
completer.address2?.focus();
|
||||
const autocomplete = new google.maps.places.Autocomplete(this._primary, googleOptions);
|
||||
|
||||
// Trigger rechecks on each field
|
||||
validateInput(node);
|
||||
if (completer.city !== undefined) validateInput(completer.city);
|
||||
if (completer.postalCode !== undefined) validateInput(completer.postalCode);
|
||||
});
|
||||
};
|
||||
autocomplete.addListener('place_changed', () => {
|
||||
if (!this._primary) {
|
||||
throw new Error('Primary address input went missing.');
|
||||
}
|
||||
|
||||
let address1 = '';
|
||||
let postalCode = '';
|
||||
|
||||
const place = autocomplete.getPlace();
|
||||
// Sort each component
|
||||
for (const component of place.address_components as google.maps.GeocoderAddressComponent[]) {
|
||||
const componentType = component.types[0];
|
||||
|
||||
switch (componentType) {
|
||||
case 'street_number': {
|
||||
address1 = `${component.long_name} ${address1}`;
|
||||
break;
|
||||
}
|
||||
case 'route': {
|
||||
address1 += component.short_name;
|
||||
break;
|
||||
}
|
||||
case 'postal_code': {
|
||||
postalCode = `${component.long_name}${postalCode}`;
|
||||
break;
|
||||
}
|
||||
case 'postal_code_suffix': {
|
||||
postalCode = `${postalCode}-${component.long_name}`;
|
||||
break;
|
||||
}
|
||||
case 'locality': {
|
||||
if (this._city) this._city.value = component.long_name;
|
||||
break;
|
||||
}
|
||||
case 'administrative_area_level_1': {
|
||||
// this._province?.setValStr(component.short_name);
|
||||
if (this._province) this._province.value = component.short_name;
|
||||
break;
|
||||
}
|
||||
case 'country': {
|
||||
// this._country?.setValStr(component.short_name);
|
||||
if (this._country) this._country.value = component.short_name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._primary.value = address1;
|
||||
if (this._postalCode) this._postalCode.value = postalCode;
|
||||
this._line2?.focus();
|
||||
|
||||
// Trigger rechecks on each field
|
||||
validateInput(this._primary);
|
||||
if (this._city !== undefined) validateInput(this._city);
|
||||
if (this._postalCode !== undefined) validateInput(this._postalCode);
|
||||
|
||||
// Dispatch a custom event with the place details
|
||||
const event = new CompleteEvent('complete', { detail: { place } });
|
||||
this._primary.dispatchEvent(event);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.3.1",
|
||||
"@eslint/js": "^9.30.0",
|
||||
"@types/google.maps": "^3.58.1",
|
||||
"eslint": "^9.24.0",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"globals": "^16.2.0",
|
||||
|
||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -18,6 +18,9 @@ importers:
|
||||
'@eslint/js':
|
||||
specifier: ^9.30.0
|
||||
version: 9.30.0
|
||||
'@types/google.maps':
|
||||
specifier: ^3.58.1
|
||||
version: 3.58.1
|
||||
eslint:
|
||||
specifier: ^9.24.0
|
||||
version: 9.30.0
|
||||
@@ -141,6 +144,9 @@ packages:
|
||||
'@types/estree@1.0.8':
|
||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||
|
||||
'@types/google.maps@3.58.1':
|
||||
resolution: {integrity: sha512-X9QTSvGJ0nCfMzYOnaVs/k6/4L+7F5uCS+4iUmkLEls6J9S/Phv+m/i3mDeyc49ZBgwab3EFO1HEoBY7k98EGQ==}
|
||||
|
||||
'@types/json-schema@7.0.15':
|
||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||
|
||||
@@ -710,6 +716,8 @@ snapshots:
|
||||
|
||||
'@types/estree@1.0.8': {}
|
||||
|
||||
'@types/google.maps@3.58.1': {}
|
||||
|
||||
'@types/json-schema@7.0.15': {}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.35.1(@typescript-eslint/parser@8.35.1(eslint@9.30.0)(typescript@5.8.3))(eslint@9.30.0)(typescript@5.8.3)':
|
||||
|
||||
Reference in New Issue
Block a user