UNPKG

@ideal-postcodes/address-finder

Version:

Address Finder JS library backed by the Ideal Postcodes UK address search API

787 lines (786 loc) 21.9 kB
/** * @module Controller */ import { Announce } from "./announcer"; import { Callbacks, Listener } from "./callbacks"; import { ContextStore, ContextDetails } from "./contexts"; import { DebouncedFunc } from "lodash"; import { ApiCache, QueryOptions, ResolveOptions } from "./cache"; import { AddressSuggestion, AnyAddress } from "@ideal-postcodes/jsutil"; import { Client } from "@ideal-postcodes/core-axios"; import { Config } from "@ideal-postcodes/core-axios/dist/client"; import { ViewService, CloseReason } from "./state"; import { SelectorNode, CSSStyle, OutputFields, NamedFields, IdGen } from "@ideal-postcodes/jsutil"; export type AddressFormat = "gbr" | "usa"; /** * Configuration options for an Address Finder instance */ export interface ControllerOptions extends Partial<Callbacks>, Partial<Omit<Config, "api_key">> { /** * CSS selector or HTML Element which specifies the `<input>` field which the * Address Finder View should bind. */ inputField?: SelectorNode; /** * API Key from your Ideal Postcodes account. Typically begins `ak_` */ apiKey: string; /** * Scopes the operable area of the DOM * * @default * * `window.document` */ scope?: Document | HTMLElement | string; /** * Specify the Document to operate on * * @default * * `window.document` */ document?: Document; /** * Default Country * * @default * * `"GBR"` */ defaultCountry?: string; /** * Narrow the countries you wish to support * * Setting this to an empty array (default) will enable all countries * * Setting this to a single country will disable country selection and hide the country selection toolbar * * @default * * `[]` */ restrictCountries?: string[]; /** * Provide a custom list of possible contexts to select a new country or context from */ contexts?: Record<string, ContextDetails>; /** * Specify parent element for output fields to looking for them to narrow search area */ outputScope?: string | HTMLElement | Document | null; /** * An object specifying where address field data points should be piped. * * The attribute of the document should be the same as the address attribute * as found in the documentation. E.g. `line_1`, `post_town`, `postcode`. * * You may use a CSS selector `string` or a `HTMLElement`. E.g. * `{ line_1: "#line_1" }` or `{ line_1: document.getElementById("line_1") }` * * Using an `HTMLElement` as an `outputField` selector has the effect of eagerly binding the Address Finder instance to your output fields. When using `string` selectors, Address Finder will bind to your ouput fields when when an address is selected. */ outputFields?: OutputFields; /** * Specify the format to receive the resolved address in. * * @default "gbr" */ format?: AddressFormat; /** * An object specifying the `name`s of HTML Input Elements to target for address population * * This will fallback to `aria-name` if a name cannot be detected */ names?: NamedFields; /** * An object specifying the labels associated with HTML Input Elements to target for address population */ labels?: NamedFields; /** * Optional. An optional field to remove organisation name from address lines. * * This is `false` by default. */ removeOrganisation?: boolean; /** * An optional field to check whether the key is usable against the Ideal * Postcodes API. This should be used in conjunction with the * `onFailedCheck` callback to specify the necessary behaviour when the API * Key is not in a usable state. This is `true` by default. */ checkKey?: boolean; /** * Configures which WAI-ARIA specification version Address Finder should target. * * - `"1.1"` will target the most recent spec * - `"1.0"` will enable some regressions to support the 1.0 spec. * * Although 1.1 was released in 2017, this currently defaults to "1.0" as it receives the widest support among screen readers. VoiceOver (for MacOS and iOS) and NVDA in particular benefit from this. * * Defaults to "1.0" */ aria?: "1.0" | "1.1"; /** * Automatically aligns address finder * * @default true */ alignToInput?: boolean; /** * Offset of AddressFinder from input in pixels * * @default 2 */ offset?: number; /** * An optional field to convert the case of the Post Town from upper case * into title case. E.g. `"LONDON"` becomes `"London".` Default is `true` */ titleizePostTown?: boolean; /** * Optional configuration object to apply to address resolve queries */ resolveOptions?: ResolveOptions; /** * Optional configuration object to apply to address queries */ queryOptions?: QueryOptions; /** * Sets the `autocomplete=` attribute of the input element. Setting this attribute aims to prevent some browsers (particularly Chrome) from providing a clashing autofill overlay. * * The best practice for this attribute breaks over time (see https://stackoverflow.com/questions/15738259/disabling-chrome-autofill) and is specific to different forms. If you are observing chrome's autofill clashing on your form, update this attribute to the best practice du jour. * * @default `"none"` */ autocomplete?: string; /** * Hides the toolbar. Users are unable to change the country * @default false */ hideToolbar?: boolean; /** * Detects country based on IP address. * @default true */ detectCountry?: boolean; /** * Inject stylesheet into DOM to style Address Finder with default theme. Default is `true` * * Styling of the Address Finder can be achieved using a CSS file. Set this to `false` if you wish to do this * * - `true` Injects the default styles into the DOM * - `string` e.g. `https://cdn.jsdelivr.net/npm/@ideal-postcodes/address-finder@1.1.1/css/address-finder.min.css` will include a CSS Stylesheet in the DOM with the src set as the string */ injectStyle?: boolean | string; /** * Message in input placeholder when address results are suggested * * Defaults to `"Try the first line or postal code of your address"` */ msgPlaceholder?: string; /** * Message in input placeholder when country suggestions are presented * * Defaults to `"Select your country"` */ msgPlaceholderCountry?: string; /** * Fallback message in case communication message with API fails * * Defaults to `"Please enter your address manually"` */ msgFallback?: string; /** * Initial message when Address Finder opens an no query is available * * Defaults to `"Start typing to find address"` */ msgInitial?: string; /** * Aria-label attached to country select bytton * * Defaults to `"Click to change your country"` */ msgCountryToggle?: string; /** * Message presented when no matches found for a particular query * * Defaults to `"No matches found"` */ msgNoMatch?: string; /** * Aria-label attached to the suggestion list. Prompts screen reader user on how to operate list * * Defaults to `"Select your address"` * * @default `"Select your address"` */ msgList?: string; /** * CSS class assigned to Address Finder element. This element is the main visible element containing address suggestions, messages and toolbar underneath the address finder * * Defaults to `"idpc_af"` * * @default `"idpc_af"` */ mainClass?: string; /** * CSS class assigned to message box * * Defaults to `"idpc_error"` * * @default `"idpc_error"` */ messageClass?: string; /** * CSS class assigned to the AddressFinder container/wrapper * * Defaults to `"idpc_autocomplete"` * * @default `"idpc_autocomplete"` */ containerClass?: string; /** * CSS class assigned to suggestion list * * Defaults to `"idpc_ul"` * * @default `"idpc_ul"` */ listClass?: string; /** * CSS class assigned to toolbar at bottom of Address Finder * * @default `"idpc_toolbar"` */ toolbarClass?: string; /** * CSS class assigned to country toggle button * * @default `"idpc_country"` */ countryToggleClass?: string; /** * Suppresses `county` from being populated if set to `false` * * @default true */ populateCounty?: boolean; /** * Suppresses `organisation_name` from being populated if set to `false` * * @default true */ populateOrganisation?: boolean; /** * Applies additional styling to the input field. Ideal for quick tweaks. Accepts CSSStyleDeclaration object * Input styles are restored to original when controller is detached from DOM * * @default * * `{}` * * @example * * ```javascript * { * inputStyle: { * backgroundColor: "#000", * }, * } * ``` */ inputStyle?: CSSStyle; /** * Applies additional styling to the the suggestion list. Accepts CSSStyleDeclaration object * * `style` encapsulates all visible elements of Address Finder. This element is actively shown/hidden when AddressFinder is toggled * * @default * * `{}` * * @example * * ```javascript * { * listStyle: { * backgroundColor: "#000", * }, * } * ``` */ listStyle?: CSSStyle; /** * Applies additional styling to the the Address Finder container element. Accepts CSSStyleDeclaration object * `containerStyle encapsulates all elements of Address Finder including the input, ARIA controls * * @default * * `{}` * * @example * * ```javascript * { * containerStyle: { * backgroundColor: "#000", * }, * } * ``` */ containerStyle?: CSSStyle; /** * Applies additional styling to the the Address Finder Main Component. The Main Component contains the visible elements of the Address Finder such as the address suggestion list, toolbar and messages which appears underneath the input field. * * Accepts CSSStyleDeclaration object * * @default * * `{}` * * @example * * ```javascript * { * mainStyle: { * backgroundColor: "#000", * }, * } * ``` */ mainStyle?: CSSStyle; /** * Applies additional styling to the the Address Finder list element. Accepts CSSStyleDeclaration object * * @default * * `{}` * * @example * * ```javascript * { * liStyle: { * backgroundColor: "#000", * }, * } * ``` */ liStyle?: CSSStyle; /** * Hide a list of HTML elements when Postcode Lookup is instantiated * * Specify these elements using query selectors or direct HTMLElement references * * @default [] */ hide?: (string | HTMLElement)[]; /** * Message shown to user to unhide address fields if `hide` attribute is configured * * @default "Enter address manually" */ msgUnhide?: string; /** * Specify a clickable element to unhide elements hidden with `hide` * * @default null */ unhide?: string | HTMLElement | null; /** * Class of clickable unhide element * * @default "idpc-unhide" */ unhideClass?: string; /** * Applies additional styling to the unhide element. Accepts CSSStyleDeclaration object * * @default * * `{}` * * @example * * ```javascript * { * unhideStyle: { * color: "#0066cc", * cursor: "pointer", * }, * } * ``` */ unhideStyle?: CSSStyle; /** * Set CSS display attribute to `fixed` on the Address Finder list element * * @default false */ fixed?: boolean; } /** * @hidden */ export declare const NOOP: () => void; /** * @hidden */ interface RetrieveSuggestions { (event: Event): Promise<Controller>; } /** * @hidden */ export interface StoredOptions extends Required<Omit<ControllerOptions, keyof Config>>, Omit<Config, "api_key"> { } /** * Default options assigned to controller instances */ export declare const defaults: Omit<StoredOptions, "scope" | "document">; /** * # Controller * * The Autocomplete Controller class acts as the public class which you may * wield to enable address autocomplete on your HTML address forms * * When instantiated, the controller will serve as a bridge beteen the * address suggestion view presented on the DOM and the Ideal * Postcodes Address resolution HTTP APIs * * The role of the controller is to bind to events produced by the user * interface and take appropriate action including querying the API, * modifying other aspects of the DOM. */ export declare class Controller { /** * Ideal Postcodes API Client */ client: Client; /** * Scopes the DOM for the entire controller */ scope: HTMLElement | Document; /** * Scopes the DOM for fields which should receive address inputs */ outputScope: HTMLElement | Document; /** * Reference to the DOM, to which this.scope belongs */ document: Document; /** * Address Finder API cache */ cache: ApiCache; /** * Caches options */ options: StoredOptions; /** * Caches previous placeholder value for input */ placeholderCache: string | undefined; /** * Reference to input DOM element */ input: HTMLInputElement; /** * Reference to input DOM element */ countryInput: HTMLInputElement | HTMLSelectElement | null; /** * Reference to Address Finder message DOM element */ message: HTMLLIElement; /** * Reference to container wrapping AddressFinder elements DOM element. This includes the main component, input fields and WAI-ARIA controls */ container: HTMLDivElement; /** * Reference to inner container wrapping list and toolbar */ mainComponent: HTMLDivElement; /** * Reference to Address Suggestion list DOM element */ list: HTMLUListElement; /** * Reference to toolbar at bottom of finder list */ toolbar: HTMLDivElement; /** * Reference to country select toggle button */ countryToggle: HTMLSpanElement; /** * Reference to country icon */ countryIcon: HTMLSpanElement; /** * Reference to country toggle message */ countryMessage: HTMLSpanElement; /** * Reference to clickable Unhide link */ unhide: HTMLElement; /** * Input element input event listener */ inputListener: Listener<"input">; /** * Input blur event listener */ blurListener: Listener<"blur">; /** * Input focus event listener */ focusListener: Listener<"focus">; /** * Input keydown event listener */ keydownListener: Listener<"keydown">; /** * Unhide click event listener */ unhideEvent: Listener<"click">; /** * Country form input change listener */ countryListener: Listener<"change">; /** * Address Finder state machine */ fsm: ViewService; /** * ID generation method */ ids: IdGen; /** * Reference to accessibility announcer */ announce: Announce; /** * Reference to alerts container */ alerts: HTMLDivElement; /** * Caches input style prior to Address Finder attachment */ inputStyle: string | null; /** * Debounced method used to retrieve suggestions */ retrieveSuggestions: DebouncedFunc<RetrieveSuggestions>; /** * Current search context */ context: string; /** * Current list of address suggestions */ suggestions: AddressSuggestion[]; /** * Current list of context suggestions */ contextSuggestions: ContextDetails[]; /** * Current notification to be shown to user */ notification: string; /** * Index of current elem in list selected */ current: number; constructor(options: ControllerOptions); /** * Sets placeholder and caches previous result * @hidden */ setPlaceholder(msg: string): void; /** * Unsets any placeholder value to original * @hidden */ unsetPlaceholder(): void; /** * Returns current highlighted context * @hidden */ currentContext(): ContextDetails; /** * Binds to DOM and begin DOM mutations * @hidden */ load(): void; /** * Attaches Controller to the DOM. * * If `checkKey` is enabled, a key check will be performed prioer to binding. Use the `onLoaded` and `onFailedCheck` callbacks to define follow up behaviour if the key check succeeds or fails */ init(): Promise<void>; updateContexts(contexts: ContextStore): void; filteredContexts(): ContextDetails[]; /** * Render available country options */ renderContexts(): void; /** * Render current address suggestions */ renderSuggestions(): void; /** * Updates current li in list to active descendant */ goToCurrent(): void; /** * Marks aria component as opened */ ariaExpand(): void; /** * Marks aria component as closed */ ariaContract(): void; /** * Resolves a suggestion to full address and apply results to form */ applySuggestion(suggestion: AddressSuggestion): Promise<Controller>; /** * Writes a selected to the input fields specified in the controller config */ populateAddress(address: AnyAddress): void; /** * Applies new query options to search. This process clears the existing * cache to prevent stale searches */ setQueryOptions(options: QueryOptions): void; /** * Applies new query options to search. This process clears the existing * cache to prevent stale searches */ setResolveOptions(options: ResolveOptions): void; /** * Adds Address Finder to DOM * - Wraps input with container * - Appends suggestion list to container * - Enables listeners * - Starts FSM */ attach(): Controller; /** * Removes Address Finder from DOM * - Disable listeners * - Removes sugestion list from container * - Appends suggestion list to container * - Enables listeners * - Stops FSM */ detach(): Controller; /** * Sets message as a list item, no or empty string removes any message */ setMessage(notification: string): Controller; /** * Returns HTML Element which recevies key aria attributes * * @hidden */ ariaAnchor(): HTMLElement; /** * Returns current address query */ query(): string; clearInput(): void; /** * Set address finder suggestions */ setSuggestions(suggestions: AddressSuggestion[], query: string): Controller; /** * Close address finder */ close(reason?: CloseReason): void; /** * Updates suggestions and resets current selection * @hidden */ updateSuggestions(s: AddressSuggestion[]): void; /** * Applies context to API cache * @hidden */ applyContext(details: ContextDetails, announce?: boolean): void; /** * Renders notification box * @hidden */ renderNotice(): void; /** * Open address finder * @hidden */ open(): void; /** * Sets next suggestion as current * @hidden */ next(): Controller; /** * Sets previous suggestion as current * @hidden */ previous(): Controller; /** * Given a HTMLLiElement, scroll parent until it is in view * @hidden */ scrollToView(li: HTMLElement): Controller; /** * Moves currently selected li into view * @hidden */ goto(i: number): Controller; /** * Returns true if address finder is open */ opened(): boolean; /** * Returs false if address finder is closed */ closed(): boolean; /** * Creates a clickable element that can trigger unhiding of fields */ createUnhide(): HTMLElement; /** * Removes unhide elem from DOM */ unmountUnhide(): void; hiddenFields(): HTMLElement[]; /** * Hides fields marked for hiding */ hideFields(): void; /** * Unhides fields marked for hiding */ unhideFields(): void; } /** * Event handler: Fires on "keyDown" event of search field * @hidden */ export declare const _onKeyDown: (c: Controller) => Listener<"keydown">; export declare const _onCountryChange: (c: Controller) => Listener<"change">; /** * Retrieve Element * - If string, assumes is valid and returns first match within scope * - If null, invokes the create method to return a default * - If HTMLElement returns instance * @hidden */ export declare const findOrCreate: <T>(scope: HTMLElement | Document, q: string | T | null, create?: (() => T) | undefined) => T; export declare const setPositionFixed: (mainComponent: HTMLElement, container: HTMLElement, document: Document) => void; export {};