@ideal-postcodes/address-finder
Advanced tools
Comparing version 1.6.0 to 1.7.0
@@ -0,1 +1,8 @@ | ||
# [1.7.0](https://github.com/ideal-postcodes/address-finder/compare/1.6.0...1.7.0) (2021-03-01) | ||
### Features | ||
* **Hide:** Add ability to hide/unhide fields ([5c3c05e](https://github.com/ideal-postcodes/address-finder/commit/5c3c05e0d47c9bfcc02a160e121521589901ec62)) | ||
# [1.6.0](https://github.com/ideal-postcodes/address-finder/compare/1.5.4...1.6.0) (2021-03-01) | ||
@@ -2,0 +9,0 @@ |
@@ -62,11 +62,2 @@ import { View } from "./view"; | ||
/** | ||
* 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 | ||
*/ | ||
findOrCreate<T>(q: string | null | T, create?: () => T): T; | ||
/** | ||
* Binds to DOM and begin DOM mutations | ||
@@ -73,0 +64,0 @@ * |
@@ -50,2 +50,7 @@ "use strict"; | ||
containerStyle: {}, | ||
// Hide / unhide | ||
unhide: null, | ||
unhideClass: "idpc-unhide", | ||
msgUnhide: "Enter address manually", | ||
hide: [], | ||
// Callbacks | ||
@@ -70,2 +75,3 @@ onOpen: NOOP, | ||
onKeyDown: NOOP, | ||
onUnhide: NOOP | ||
}; | ||
@@ -98,3 +104,3 @@ /** | ||
// Assign a document or DOM subtree to scope outputs. Defaults to controller scope | ||
this.outputScope = this.findOrCreate(this.options.outputScope, () => this.scope); | ||
this.outputScope = view_1.findOrCreate(this.scope, this.options.outputScope, () => this.scope); | ||
this.client = new core_axios_1.Client({ ...this.options, api_key: this.options.apiKey }); | ||
@@ -111,3 +117,3 @@ this.cache = new cache_1.ApiCache(this.client); | ||
}), | ||
onSelect: this._onSelect(), | ||
onSelect: this._onSelect() | ||
}); | ||
@@ -117,17 +123,2 @@ this.init(); | ||
/** | ||
* 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 | ||
*/ | ||
findOrCreate(q, create) { | ||
if (jsutil_1.isString(q)) | ||
return this.scope.querySelector(q); | ||
if (create && q === null) | ||
return create(); | ||
return q; | ||
} | ||
/** | ||
* Binds to DOM and begin DOM mutations | ||
@@ -227,2 +218,3 @@ * | ||
populateAddress(address) { | ||
this.view.unhideFields(); | ||
jsutil_1.populateAddress({ | ||
@@ -229,0 +221,0 @@ address, |
@@ -10,3 +10,3 @@ "use strict"; | ||
*/ | ||
const d = 'div.idpc_autocomplete{position:relative;margin:0;padding:0;border:0}div.idpc_autocomplete > input{display:block}div.idpc_autocomplete > ul{position:absolute;left:0;z-index:1;min-width:100%;box-sizing:border-box;list-style:none;padding:0;border-radius:0.3em;margin:0.2em 0 0;background:#fff;border:1px solid rgba(0,0,0,0.3);box-shadow:0.05em 0.2em 0.6em rgba(0,0,0,0.2);text-shadow:none;max-height:250px;overflow-y:scroll}div.idpc_autocomplete > ul > li{position:relative;padding:0.2em 0.5em;cursor:pointer}div.idpc_autocomplete > ul > li:hover{background:#b8d3e0;color:black}div.idpc_autocomplete > ul > li.idpc_error{font-style:italic;background-color:#eee;cursor:default !important}div.idpc_autocomplete > ul > li[aria-selected="true"]{background:#3d6d8f;color:white} @supports(transform:scale(0)){div.idpc_autocomplete > ul{transition:0.3s cubic-bezier(0.4,0.2,0.5,1.4);transform-origin:1.43em -0.43em}div.idpc_autocomplete > ul[hidden],div.idpc_autocomplete > ul:empty{opacity:0;transform:scale(0);display:block;transition-timing-function:ease}}'; | ||
const d = 'div.idpc_autocomplete{position:relative;margin:0;padding:0;border:0}div.idpc_autocomplete > input{display:block}div.idpc_autocomplete > ul{position:absolute;left:0;z-index:1;min-width:100%;box-sizing:border-box;list-style:none;padding:0;border-radius:0.3em;margin:0.2em 0 0;background:#fff;border:1px solid rgba(0,0,0,0.3);box-shadow:0.05em 0.2em 0.6em rgba(0,0,0,0.2);text-shadow:none;max-height:250px;overflow-y:scroll}div.idpc_autocomplete > ul > li{position:relative;padding:0.2em 0.5em;cursor:pointer}div.idpc_autocomplete > ul > li:hover{background:#b8d3e0;color:black}div.idpc_autocomplete > ul > li.idpc_error{font-style:italic;background-color:#eee;cursor:default !important}div.idpc_autocomplete > ul > li[aria-selected="true"]{background:#3d6d8f;color:white}div.idpc_autocomplete>.idpc-unhide{font-size:90%;text-decoration:underline;cursor:pointer}@supports(transform:scale(0)){div.idpc_autocomplete > ul{transition:0.3s cubic-bezier(0.4,0.2,0.5,1.4);transform-origin:1.43em -0.43em}div.idpc_autocomplete > ul[hidden],div.idpc_autocomplete > ul:empty{opacity:0;transform:scale(0);display:block;transition-timing-function:ease}}'; | ||
/** | ||
@@ -13,0 +13,0 @@ * Injects CSS style into DOM |
@@ -5,3 +5,3 @@ import { Address, AddressSuggestion } from "@ideal-postcodes/api-typings"; | ||
import { SelectorNode, OutputFields } from "@ideal-postcodes/jsutil"; | ||
import { View, ViewOptions, OnOpen, OnBlur, OnClose, OnFocus, OnInput, OnSelect } from "./view"; | ||
import { View, ViewOptions, OnOpen, OnBlur, OnClose, OnFocus, OnInput, OnSelect, OnUnhide } from "./view"; | ||
export interface OnLoaded { | ||
@@ -243,2 +243,6 @@ (this: Controller): void; | ||
onSelect?: OnSelect; | ||
/** | ||
* Invoked when hidden fields are unhidden (i.e. user selects an address or opts for manual input) | ||
*/ | ||
onUnhide?: OnUnhide; | ||
} | ||
@@ -245,0 +249,0 @@ export declare const setup: (config: ControllerOptions) => Controller; |
@@ -17,7 +17,11 @@ "use strict"; | ||
*/ | ||
const SUGGEST = { SUGGEST: "suggesting" }; | ||
const SUGGEST = { | ||
SUGGEST: { target: "suggesting", actions: ["updateSuggestions"] }, | ||
}; | ||
/** | ||
* @hidden | ||
*/ | ||
const NOTIFY = { NOTIFY: "notifying" }; | ||
const NOTIFY = { | ||
NOTIFY: { target: "notifying", actions: ["updateMessage"] }, | ||
}; | ||
/** | ||
@@ -36,3 +40,3 @@ * Creates a finite state machine that drives Address Finder UI | ||
closed: { | ||
entry: ["resetCurrent", "gotoCurrent", "close", "expandOff"], | ||
entry: ["close"], | ||
exit: ["open"], | ||
@@ -52,3 +56,3 @@ on: { | ||
notifying: { | ||
entry: ["resetCurrent", "updateMessage", "showMessage", "expandOff"], | ||
entry: ["renderNotice"], | ||
exit: ["clearAnnouncement"], | ||
@@ -63,8 +67,4 @@ on: { | ||
suggesting: { | ||
entry: [ | ||
"updateSuggestions", | ||
"renderSuggestions", | ||
"gotoCurrent", | ||
"expand", | ||
], | ||
entry: ["renderSuggestions", "gotoCurrent", "expand"], | ||
exit: ["resetCurrent", "gotoCurrent", "contract"], | ||
on: { | ||
@@ -77,4 +77,4 @@ ...CLOSE, | ||
PREVIOUS: { actions: ["previous", "gotoCurrent"] }, | ||
RESET: { actions: ["resetCurrent", "gotoCurrent"] }, | ||
SELECT: { target: "closed", actions: ["select"] }, | ||
RESET: { target: "suggesting", actions: ["resetCurrent"] }, | ||
}, | ||
@@ -114,2 +114,5 @@ }, | ||
}, | ||
/** | ||
* Clears ARIA announcement fields | ||
*/ | ||
clearAnnouncement: () => view.announce(""), | ||
@@ -119,3 +122,3 @@ /** | ||
*/ | ||
renderSuggestions: (_, e) => { | ||
renderSuggestions: (c, e) => { | ||
if (e.type !== "SUGGEST") | ||
@@ -125,3 +128,3 @@ return; | ||
const id = view.list.id; | ||
const s = view.suggestions(); | ||
const s = c.suggestions; | ||
s.forEach(({ suggestion }, i) => { | ||
@@ -149,7 +152,3 @@ const li = view.options.document.createElement("li"); | ||
}, | ||
current: (c, e) => { | ||
if (e.type !== "SUGGEST") | ||
return c.current; | ||
return -1; | ||
}, | ||
current: () => -1, | ||
}), | ||
@@ -175,6 +174,12 @@ /** | ||
}, | ||
/** | ||
* Marks aria component as expanded | ||
*/ | ||
expand: () => { | ||
view.ariaAnchor().setAttribute("aria-expanded", "true"); | ||
}, | ||
expandOff: () => { | ||
/** | ||
* Marks aria component as closed | ||
*/ | ||
contract: () => { | ||
view.ariaAnchor().setAttribute("aria-expanded", "false"); | ||
@@ -195,3 +200,3 @@ }, | ||
*/ | ||
showMessage: (c) => { | ||
renderNotice: (c) => { | ||
view.list.innerHTML = ""; | ||
@@ -198,0 +203,0 @@ view.input.setAttribute("aria-activedescendant", ""); |
import { AddressSuggestion } from "@ideal-postcodes/api-typings"; | ||
import { SelectorNode, CSSStyle } from "@ideal-postcodes/jsutil"; | ||
import { SelectorNode, IdGen, CSSStyle } from "@ideal-postcodes/jsutil"; | ||
import { ViewService } from "./state"; | ||
import { IdGen } from "@ideal-postcodes/jsutil"; | ||
import { Announce } from "./announcer"; | ||
@@ -63,2 +62,8 @@ import { OnMounted, OnRemove } from "./index"; | ||
/** | ||
* Invoked when an address suggestion in suggestion box is selected | ||
*/ | ||
export interface OnUnhide { | ||
(this: View): void; | ||
} | ||
/** | ||
* Options supported by `View` | ||
@@ -176,2 +181,6 @@ */ | ||
/** | ||
* Invoked when hidden fields are unhidden (i.e. user selects an address or options to manually insert address) | ||
*/ | ||
onUnhide: OnUnhide; | ||
/** | ||
* Applies additional styling to the input field. Ideal for quick tweaks. Accepts CSSStyleDeclaration object | ||
@@ -231,2 +240,28 @@ * Input styles are restored to original when controller is detached from DOM | ||
containerStyle: 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; | ||
} | ||
@@ -253,2 +288,3 @@ /** | ||
list: HTMLUListElement; | ||
unhide: HTMLElement; | ||
inputListener: Listener<"input">; | ||
@@ -259,2 +295,3 @@ blurListener: Listener<"blur">; | ||
mousedownListener: Listener<"mousedown">; | ||
unhideEvent: Listener<"click">; | ||
fsm: ViewService; | ||
@@ -264,3 +301,3 @@ ids: IdGen; | ||
alerts: HTMLDivElement; | ||
private inputStyle; | ||
inputStyle: string | null; | ||
/** | ||
@@ -353,2 +390,19 @@ * Creates an View instance | ||
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; | ||
} | ||
@@ -367,2 +421,11 @@ /** | ||
export declare const _onKeyDown: (view: View) => Listener<"keydown">; | ||
/** | ||
* 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 {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports._onKeyDown = exports.View = void 0; | ||
exports.findOrCreate = exports._onKeyDown = exports.View = void 0; | ||
const jsutil_1 = require("@ideal-postcodes/jsutil"); | ||
const state_1 = require("./state"); | ||
const fsm_1 = require("@xstate/fsm"); | ||
const jsutil_2 = require("@ideal-postcodes/jsutil"); | ||
const announcer_1 = require("./announcer"); | ||
@@ -29,3 +28,3 @@ /** | ||
this.options = options; | ||
this.ids = jsutil_2.idGen("idpcaf"); | ||
this.ids = jsutil_1.idGen("idpcaf"); | ||
const { inputField } = options; | ||
@@ -47,3 +46,6 @@ // Configure container | ||
this.list.setAttribute("role", "listbox"); | ||
jsutil_2.hide(this.list); | ||
jsutil_1.hide(this.list); | ||
//configure unhide | ||
this.unhideEvent = this.unhideFields.bind(this); | ||
this.unhide = this.createUnhide(); | ||
// Configure input | ||
@@ -68,2 +70,3 @@ if (jsutil_1.isString(inputField)) { | ||
this.ariaAnchor().setAttribute("role", "combobox"); | ||
this.ariaAnchor().setAttribute("aria-expanded", "false"); | ||
this.ariaAnchor().setAttribute("aria-owns", this.list.id); | ||
@@ -109,5 +112,8 @@ this.inputListener = _onInput(this); | ||
this.container.appendChild(this.alerts); | ||
if (this.options.hide.length > 0 && this.options.unhide == null) | ||
this.container.appendChild(this.unhide); | ||
} | ||
this.fsm.start(); | ||
this.options.onMounted.call(this); | ||
this.hideFields(); | ||
return this; | ||
@@ -138,2 +144,4 @@ } | ||
} | ||
this.unmountUnhide(); | ||
this.unhideFields(); | ||
this.fsm.stop(); | ||
@@ -283,2 +291,48 @@ jsutil_1.restoreStyle(this.input, this.inputStyle); | ||
} | ||
/** | ||
* Creates a clickable element that can trigger unhiding of fields | ||
*/ | ||
createUnhide() { | ||
const e = exports.findOrCreate(this.options.scope, this.options.unhide, () => { | ||
const e = this.options.document.createElement("p"); | ||
e.innerText = this.options.msgUnhide; | ||
e.setAttribute("role", "button"); | ||
e.setAttribute("tabindex", "0"); | ||
if (this.options.unhideClass) | ||
e.className = this.options.unhideClass; | ||
return e; | ||
}); | ||
e.addEventListener("click", this.unhideEvent); | ||
return e; | ||
} | ||
/** | ||
* Removes unhide elem from DOM | ||
*/ | ||
unmountUnhide() { | ||
this.unhide.removeEventListener("click", this.unhideEvent); | ||
if (this.options.unhide == null && this.options.hide.length) | ||
jsutil_1.remove(this.unhide); | ||
} | ||
hiddenFields() { | ||
return this.options.hide | ||
.map((e) => { | ||
if (jsutil_1.isString(e)) | ||
return jsutil_1.toHtmlElem(this.options.scope, e); | ||
return e; | ||
}) | ||
.filter((e) => e !== null); | ||
} | ||
/** | ||
* Hides fields marked for hiding | ||
*/ | ||
hideFields() { | ||
this.hiddenFields().forEach(jsutil_1.hide); | ||
} | ||
/** | ||
* Unhides fields marked for hiding | ||
*/ | ||
unhideFields() { | ||
this.hiddenFields().forEach(jsutil_1.show); | ||
this.options.onUnhide.call(this); | ||
} | ||
} | ||
@@ -384,1 +438,17 @@ exports.View = View; | ||
exports._onKeyDown = _onKeyDown; | ||
/** | ||
* 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 | ||
*/ | ||
const findOrCreate = (scope, q, create) => { | ||
if (jsutil_1.isString(q)) | ||
return scope.querySelector(q); | ||
if (create && q === null) | ||
return create(); | ||
return q; | ||
}; | ||
exports.findOrCreate = findOrCreate; |
@@ -62,11 +62,2 @@ import { View } from "./view"; | ||
/** | ||
* 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 | ||
*/ | ||
findOrCreate<T>(q: string | null | T, create?: () => T): T; | ||
/** | ||
* Binds to DOM and begin DOM mutations | ||
@@ -73,0 +64,0 @@ * |
/* eslint-disable no-invalid-this */ | ||
import { View } from "./view"; | ||
import { View, findOrCreate } from "./view"; | ||
import debounce from "lodash/debounce"; | ||
@@ -7,3 +7,3 @@ import { ApiCache } from "./cache"; | ||
import { Client } from "@ideal-postcodes/core-axios"; | ||
import { getScope, getDocument, populateAddress, isString, } from "@ideal-postcodes/jsutil"; | ||
import { getScope, getDocument, populateAddress, } from "@ideal-postcodes/jsutil"; | ||
/** | ||
@@ -45,2 +45,7 @@ * @hidden | ||
containerStyle: {}, | ||
// Hide / unhide | ||
unhide: null, | ||
unhideClass: "idpc-unhide", | ||
msgUnhide: "Enter address manually", | ||
hide: [], | ||
// Callbacks | ||
@@ -65,2 +70,3 @@ onOpen: NOOP, | ||
onKeyDown: NOOP, | ||
onUnhide: NOOP | ||
}; | ||
@@ -93,3 +99,3 @@ /** | ||
// Assign a document or DOM subtree to scope outputs. Defaults to controller scope | ||
this.outputScope = this.findOrCreate(this.options.outputScope, () => this.scope); | ||
this.outputScope = findOrCreate(this.scope, this.options.outputScope, () => this.scope); | ||
this.client = new Client({ ...this.options, api_key: this.options.apiKey }); | ||
@@ -106,3 +112,3 @@ this.cache = new ApiCache(this.client); | ||
}), | ||
onSelect: this._onSelect(), | ||
onSelect: this._onSelect() | ||
}); | ||
@@ -112,17 +118,2 @@ this.init(); | ||
/** | ||
* 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 | ||
*/ | ||
findOrCreate(q, create) { | ||
if (isString(q)) | ||
return this.scope.querySelector(q); | ||
if (create && q === null) | ||
return create(); | ||
return q; | ||
} | ||
/** | ||
* Binds to DOM and begin DOM mutations | ||
@@ -222,2 +213,3 @@ * | ||
populateAddress(address) { | ||
this.view.unhideFields(); | ||
populateAddress({ | ||
@@ -224,0 +216,0 @@ address, |
@@ -7,3 +7,3 @@ import { idpcState, loadStyle, isString, injectStyle, } from "@ideal-postcodes/jsutil"; | ||
*/ | ||
const d = 'div.idpc_autocomplete{position:relative;margin:0;padding:0;border:0}div.idpc_autocomplete > input{display:block}div.idpc_autocomplete > ul{position:absolute;left:0;z-index:1;min-width:100%;box-sizing:border-box;list-style:none;padding:0;border-radius:0.3em;margin:0.2em 0 0;background:#fff;border:1px solid rgba(0,0,0,0.3);box-shadow:0.05em 0.2em 0.6em rgba(0,0,0,0.2);text-shadow:none;max-height:250px;overflow-y:scroll}div.idpc_autocomplete > ul > li{position:relative;padding:0.2em 0.5em;cursor:pointer}div.idpc_autocomplete > ul > li:hover{background:#b8d3e0;color:black}div.idpc_autocomplete > ul > li.idpc_error{font-style:italic;background-color:#eee;cursor:default !important}div.idpc_autocomplete > ul > li[aria-selected="true"]{background:#3d6d8f;color:white} @supports(transform:scale(0)){div.idpc_autocomplete > ul{transition:0.3s cubic-bezier(0.4,0.2,0.5,1.4);transform-origin:1.43em -0.43em}div.idpc_autocomplete > ul[hidden],div.idpc_autocomplete > ul:empty{opacity:0;transform:scale(0);display:block;transition-timing-function:ease}}'; | ||
const d = 'div.idpc_autocomplete{position:relative;margin:0;padding:0;border:0}div.idpc_autocomplete > input{display:block}div.idpc_autocomplete > ul{position:absolute;left:0;z-index:1;min-width:100%;box-sizing:border-box;list-style:none;padding:0;border-radius:0.3em;margin:0.2em 0 0;background:#fff;border:1px solid rgba(0,0,0,0.3);box-shadow:0.05em 0.2em 0.6em rgba(0,0,0,0.2);text-shadow:none;max-height:250px;overflow-y:scroll}div.idpc_autocomplete > ul > li{position:relative;padding:0.2em 0.5em;cursor:pointer}div.idpc_autocomplete > ul > li:hover{background:#b8d3e0;color:black}div.idpc_autocomplete > ul > li.idpc_error{font-style:italic;background-color:#eee;cursor:default !important}div.idpc_autocomplete > ul > li[aria-selected="true"]{background:#3d6d8f;color:white}div.idpc_autocomplete>.idpc-unhide{font-size:90%;text-decoration:underline;cursor:pointer}@supports(transform:scale(0)){div.idpc_autocomplete > ul{transition:0.3s cubic-bezier(0.4,0.2,0.5,1.4);transform-origin:1.43em -0.43em}div.idpc_autocomplete > ul[hidden],div.idpc_autocomplete > ul:empty{opacity:0;transform:scale(0);display:block;transition-timing-function:ease}}'; | ||
/** | ||
@@ -10,0 +10,0 @@ * Injects CSS style into DOM |
@@ -5,3 +5,3 @@ import { Address, AddressSuggestion } from "@ideal-postcodes/api-typings"; | ||
import { SelectorNode, OutputFields } from "@ideal-postcodes/jsutil"; | ||
import { View, ViewOptions, OnOpen, OnBlur, OnClose, OnFocus, OnInput, OnSelect } from "./view"; | ||
import { View, ViewOptions, OnOpen, OnBlur, OnClose, OnFocus, OnInput, OnSelect, OnUnhide } from "./view"; | ||
export interface OnLoaded { | ||
@@ -243,2 +243,6 @@ (this: Controller): void; | ||
onSelect?: OnSelect; | ||
/** | ||
* Invoked when hidden fields are unhidden (i.e. user selects an address or opts for manual input) | ||
*/ | ||
onUnhide?: OnUnhide; | ||
} | ||
@@ -245,0 +249,0 @@ export declare const setup: (config: ControllerOptions) => Controller; |
@@ -14,7 +14,11 @@ import { createMachine, interpret, assign } from "@xstate/fsm"; | ||
*/ | ||
const SUGGEST = { SUGGEST: "suggesting" }; | ||
const SUGGEST = { | ||
SUGGEST: { target: "suggesting", actions: ["updateSuggestions"] }, | ||
}; | ||
/** | ||
* @hidden | ||
*/ | ||
const NOTIFY = { NOTIFY: "notifying" }; | ||
const NOTIFY = { | ||
NOTIFY: { target: "notifying", actions: ["updateMessage"] }, | ||
}; | ||
/** | ||
@@ -33,3 +37,3 @@ * Creates a finite state machine that drives Address Finder UI | ||
closed: { | ||
entry: ["resetCurrent", "gotoCurrent", "close", "expandOff"], | ||
entry: ["close"], | ||
exit: ["open"], | ||
@@ -49,3 +53,3 @@ on: { | ||
notifying: { | ||
entry: ["resetCurrent", "updateMessage", "showMessage", "expandOff"], | ||
entry: ["renderNotice"], | ||
exit: ["clearAnnouncement"], | ||
@@ -60,8 +64,4 @@ on: { | ||
suggesting: { | ||
entry: [ | ||
"updateSuggestions", | ||
"renderSuggestions", | ||
"gotoCurrent", | ||
"expand", | ||
], | ||
entry: ["renderSuggestions", "gotoCurrent", "expand"], | ||
exit: ["resetCurrent", "gotoCurrent", "contract"], | ||
on: { | ||
@@ -74,4 +74,4 @@ ...CLOSE, | ||
PREVIOUS: { actions: ["previous", "gotoCurrent"] }, | ||
RESET: { actions: ["resetCurrent", "gotoCurrent"] }, | ||
SELECT: { target: "closed", actions: ["select"] }, | ||
RESET: { target: "suggesting", actions: ["resetCurrent"] }, | ||
}, | ||
@@ -111,2 +111,5 @@ }, | ||
}, | ||
/** | ||
* Clears ARIA announcement fields | ||
*/ | ||
clearAnnouncement: () => view.announce(""), | ||
@@ -116,3 +119,3 @@ /** | ||
*/ | ||
renderSuggestions: (_, e) => { | ||
renderSuggestions: (c, e) => { | ||
if (e.type !== "SUGGEST") | ||
@@ -122,3 +125,3 @@ return; | ||
const id = view.list.id; | ||
const s = view.suggestions(); | ||
const s = c.suggestions; | ||
s.forEach(({ suggestion }, i) => { | ||
@@ -146,7 +149,3 @@ const li = view.options.document.createElement("li"); | ||
}, | ||
current: (c, e) => { | ||
if (e.type !== "SUGGEST") | ||
return c.current; | ||
return -1; | ||
}, | ||
current: () => -1, | ||
}), | ||
@@ -172,6 +171,12 @@ /** | ||
}, | ||
/** | ||
* Marks aria component as expanded | ||
*/ | ||
expand: () => { | ||
view.ariaAnchor().setAttribute("aria-expanded", "true"); | ||
}, | ||
expandOff: () => { | ||
/** | ||
* Marks aria component as closed | ||
*/ | ||
contract: () => { | ||
view.ariaAnchor().setAttribute("aria-expanded", "false"); | ||
@@ -192,3 +197,3 @@ }, | ||
*/ | ||
showMessage: (c) => { | ||
renderNotice: (c) => { | ||
view.list.innerHTML = ""; | ||
@@ -195,0 +200,0 @@ view.input.setAttribute("aria-activedescendant", ""); |
import { AddressSuggestion } from "@ideal-postcodes/api-typings"; | ||
import { SelectorNode, CSSStyle } from "@ideal-postcodes/jsutil"; | ||
import { SelectorNode, IdGen, CSSStyle } from "@ideal-postcodes/jsutil"; | ||
import { ViewService } from "./state"; | ||
import { IdGen } from "@ideal-postcodes/jsutil"; | ||
import { Announce } from "./announcer"; | ||
@@ -63,2 +62,8 @@ import { OnMounted, OnRemove } from "./index"; | ||
/** | ||
* Invoked when an address suggestion in suggestion box is selected | ||
*/ | ||
export interface OnUnhide { | ||
(this: View): void; | ||
} | ||
/** | ||
* Options supported by `View` | ||
@@ -176,2 +181,6 @@ */ | ||
/** | ||
* Invoked when hidden fields are unhidden (i.e. user selects an address or options to manually insert address) | ||
*/ | ||
onUnhide: OnUnhide; | ||
/** | ||
* Applies additional styling to the input field. Ideal for quick tweaks. Accepts CSSStyleDeclaration object | ||
@@ -231,2 +240,28 @@ * Input styles are restored to original when controller is detached from DOM | ||
containerStyle: 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; | ||
} | ||
@@ -253,2 +288,3 @@ /** | ||
list: HTMLUListElement; | ||
unhide: HTMLElement; | ||
inputListener: Listener<"input">; | ||
@@ -259,2 +295,3 @@ blurListener: Listener<"blur">; | ||
mousedownListener: Listener<"mousedown">; | ||
unhideEvent: Listener<"click">; | ||
fsm: ViewService; | ||
@@ -264,3 +301,3 @@ ids: IdGen; | ||
alerts: HTMLDivElement; | ||
private inputStyle; | ||
inputStyle: string | null; | ||
/** | ||
@@ -353,2 +390,19 @@ * Creates an View instance | ||
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; | ||
} | ||
@@ -367,2 +421,11 @@ /** | ||
export declare const _onKeyDown: (view: View) => Listener<"keydown">; | ||
/** | ||
* 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 {}; |
@@ -1,5 +0,4 @@ | ||
import { isString, toKey, setStyle, restoreStyle, } from "@ideal-postcodes/jsutil"; | ||
import { isString, toKey, setStyle, restoreStyle, idGen, show, hide, remove, toHtmlElem, } from "@ideal-postcodes/jsutil"; | ||
import { create } from "./state"; | ||
import { InterpreterStatus } from "@xstate/fsm"; | ||
import { idGen, hide } from "@ideal-postcodes/jsutil"; | ||
import { announcer } from "./announcer"; | ||
@@ -44,2 +43,5 @@ /** | ||
hide(this.list); | ||
//configure unhide | ||
this.unhideEvent = this.unhideFields.bind(this); | ||
this.unhide = this.createUnhide(); | ||
// Configure input | ||
@@ -64,2 +66,3 @@ if (isString(inputField)) { | ||
this.ariaAnchor().setAttribute("role", "combobox"); | ||
this.ariaAnchor().setAttribute("aria-expanded", "false"); | ||
this.ariaAnchor().setAttribute("aria-owns", this.list.id); | ||
@@ -105,5 +108,8 @@ this.inputListener = _onInput(this); | ||
this.container.appendChild(this.alerts); | ||
if (this.options.hide.length > 0 && this.options.unhide == null) | ||
this.container.appendChild(this.unhide); | ||
} | ||
this.fsm.start(); | ||
this.options.onMounted.call(this); | ||
this.hideFields(); | ||
return this; | ||
@@ -134,2 +140,4 @@ } | ||
} | ||
this.unmountUnhide(); | ||
this.unhideFields(); | ||
this.fsm.stop(); | ||
@@ -279,2 +287,48 @@ restoreStyle(this.input, this.inputStyle); | ||
} | ||
/** | ||
* Creates a clickable element that can trigger unhiding of fields | ||
*/ | ||
createUnhide() { | ||
const e = findOrCreate(this.options.scope, this.options.unhide, () => { | ||
const e = this.options.document.createElement("p"); | ||
e.innerText = this.options.msgUnhide; | ||
e.setAttribute("role", "button"); | ||
e.setAttribute("tabindex", "0"); | ||
if (this.options.unhideClass) | ||
e.className = this.options.unhideClass; | ||
return e; | ||
}); | ||
e.addEventListener("click", this.unhideEvent); | ||
return e; | ||
} | ||
/** | ||
* Removes unhide elem from DOM | ||
*/ | ||
unmountUnhide() { | ||
this.unhide.removeEventListener("click", this.unhideEvent); | ||
if (this.options.unhide == null && this.options.hide.length) | ||
remove(this.unhide); | ||
} | ||
hiddenFields() { | ||
return this.options.hide | ||
.map((e) => { | ||
if (isString(e)) | ||
return toHtmlElem(this.options.scope, e); | ||
return e; | ||
}) | ||
.filter((e) => e !== null); | ||
} | ||
/** | ||
* Hides fields marked for hiding | ||
*/ | ||
hideFields() { | ||
this.hiddenFields().forEach(hide); | ||
} | ||
/** | ||
* Unhides fields marked for hiding | ||
*/ | ||
unhideFields() { | ||
this.hiddenFields().forEach(show); | ||
this.options.onUnhide.call(this); | ||
} | ||
} | ||
@@ -378,1 +432,16 @@ /** | ||
}; | ||
/** | ||
* 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 const findOrCreate = (scope, q, create) => { | ||
if (isString(q)) | ||
return scope.querySelector(q); | ||
if (create && q === null) | ||
return create(); | ||
return q; | ||
}; |
{ | ||
"name": "@ideal-postcodes/address-finder", | ||
"version": "1.6.0", | ||
"version": "1.7.0", | ||
"description": "Address Finder JS library backed by the Ideal Postcodes UK address search API", | ||
@@ -103,3 +103,3 @@ "main": "dist/index.js", | ||
"@ideal-postcodes/jsutil": "~4.3.2", | ||
"@xstate/fsm": "~1.5.2", | ||
"@xstate/fsm": "~1.6.0", | ||
"lodash": "~4.17.20" | ||
@@ -144,3 +144,3 @@ }, | ||
"karma-typescript": "~5.4.0", | ||
"karma-typescript-es6-transform": "~5.2.0", | ||
"karma-typescript-es6-transform": "~5.4.0", | ||
"minify": "~7.0.0", | ||
@@ -147,0 +147,0 @@ "mocha": "~8.3.0", |
@@ -173,2 +173,3 @@ <h1 align="center"> | ||
- [msgNoMatch](https://address-finder.ideal-postcodes.dev/interfaces/controlleroptions.html#msgnomatch) | ||
- [msgUnhide](https://address-finder.ideal-postcodes.dev/interfaces/controlleroptions.html#msgunhide) | ||
@@ -240,2 +241,26 @@ ### Setup Options | ||
#### [`hide`](https://address-finder.ideal-postcodes.dev/interfaces/controllerconfig.html#hide) | ||
Accepts an array of HTMLElements or CSS selectors. E.g. | ||
```javascript | ||
{ | ||
hide: [ | ||
"#line_1", | ||
document.getElementById("line_2"), | ||
document.querySelector("#line_3")], | ||
], | ||
} | ||
``` | ||
When enabled, the HTMLElements supplied in `hide` are hidden with `display: none;` when Address Finder initialises and attaches to the DOM. These elements will be subsequently unhidden if an address is selected or the user opts to manually input an address. | ||
Enabling this feature will also create a clickable element in the Address Finder, which provides users the option to manually input an address. You may provide your own clickable element with `unhide` or customise the clickable element with `msgUnhide` and `unhideClass`. | ||
Defaults to `[]`. | ||
#### [`unhide`](https://address-finder.ideal-postcodes.dev/interfaces/controllerconfig.html#unhide) | ||
Specify a query selector (`string`) or (`HTMLElement`) as a custom clickable element to unhide and form fields configured with `hide`. This will prevent the default unhide element from being rendered. | ||
### Lifecycle Callbacks | ||
@@ -242,0 +267,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
184389
4015
339
+ Added@xstate/fsm@1.6.5(transitive)
- Removed@xstate/fsm@1.5.2(transitive)
Updated@xstate/fsm@~1.6.0