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 {} /** 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 void }> = ( node ) => { this._primary = node; this._configurePrimary(); }; /** registers line 2 address field */ line2: Action = (node) => { this._line2 = node; }; /** registers city field */ city: Action = (node) => { this._city = node; }; /** registers province field */ province: Action = (node) => { this._province = node; }; /** registers postal code field */ postalCode: Action = (node) => { this._postalCode = node; }; /** registers country field */ country: Action = (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); }); }; }