133 lines
4.1 KiB
TypeScript
133 lines
4.1 KiB
TypeScript
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> {}
|
|
/** ComplexInputElement is used to represent any input besides the basic text input. */
|
|
export interface ComplexInputElement {
|
|
setter(value: string): void;
|
|
}
|
|
|
|
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;
|
|
private _line2?: HTMLInputElement;
|
|
private _city?: HTMLInputElement;
|
|
private _province?: ComplexInputElement;
|
|
private _postalCode?: HTMLInputElement;
|
|
private _country?: ComplexInputElement;
|
|
|
|
/** registers line 1 address field and attaches an autocompleter */
|
|
primary: Action<HTMLInputElement, undefined, { oncomplete: (e: CompleteEvent) => void }> = (
|
|
node
|
|
) => {
|
|
this._primary = node;
|
|
this._configurePrimary();
|
|
};
|
|
|
|
/** 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<ComplexInputElement> = (node) => {
|
|
this._province = node;
|
|
};
|
|
/** registers postal code field */
|
|
postalCode: Action<HTMLInputElement> = (node) => {
|
|
this._postalCode = node;
|
|
};
|
|
/** registers country field */
|
|
country: Action<ComplexInputElement> = (node) => {
|
|
this._country = node;
|
|
};
|
|
|
|
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.');
|
|
}
|
|
|
|
const autocomplete = new google.maps.places.Autocomplete(this._primary, googleOptions);
|
|
|
|
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.setter(component.short_name);
|
|
break;
|
|
}
|
|
case 'country': {
|
|
// this._country?.setValStr(component.short_name);
|
|
if (this._country) this._country.setter(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);
|
|
});
|
|
};
|
|
}
|