UNPKG

@postnord/web-components

Version:
794 lines (793 loc) 30.7 kB
/*! * Built with Stencil * By PostNord. */ import { Host, h } from "@stencil/core"; import { uuidv4, en, awaitTopbar } from "../../../globals/helpers"; import { minus, plus } from "pn-design-assets/pn-assets/icons.js"; import { translations } from "./translations"; /** * The `pn-counter` is an input with a button on each side that can be used to increase and decrease the input value. * * @slot helpertext - Use this slot to add custom HTML inside of the helpertext element. Recommended if you need a link. {@since v7.25.0} * * @nativeInput Use the `input` event to listen to content being modified by the user. It is emitted everytime a user writes or removes content in the input. * @nativeChange The `change` event is emitted when the input loses focus, the user clicks `Enter` or makes a selection (such as auto complete or suggestions). */ export class PnCounter { id = `pn-counter-${uuidv4()}`; idLabel = `${this.id}-label`; idText = `${this.id}-text`; idAmount = `${this.id}-count`; hostElement; clearAriaTimer; displaySrValue = false; showMinMaxMessage = false; /** Prevent double events when reaching min/max values. */ lastDispatchedValue; interactType; /** Label for the counter. This is required for the counter to be 100% accessible out of the box. */ label; /** Set a helpertext for the counter. */ helpertext; /** Set the language manually for the built in translations. */ language = undefined; /** Set a predefined value. @category Native attributes */ value = 0; /** HTML input name. @category Native attributes */ name; /** Minimum value. @category Native attributes */ min; /** Maximum value. @category Native attributes */ max; /** Increase/decrease the value in steps, default is 1. @category Native attributes */ step = 1; /** Suggest values for the counter input. @category Native attributes */ list; /** Set the counter as required. @category Native attributes */ required = false; /** Set the counter as readonly. @category Native attributes */ readonly = false; /** Set the counter as disabled. @category Native attributes */ disabled = false; /** Use the compact label variant. @since v7.21.0 @category Features */ compact = false; /** Text for the decrease button. Default is "Decrease". @category Features */ labelDecrease; /** Text for the increase button. Default is "Increase". @category Features */ labelIncrease; /** Hide the tooltips for the increase/decrease button. @since v7.25.0 @category Features */ hideTooltips = false; /** Set a message that shows below input when user wants to set a value that is below `min`. Default is "Minimum value is x" @category Features */ minMessage; /** Set a message that shows below input when user wants to set a value that is above `max`. Default is "Maximum value is x" @category Features */ maxMessage; /** Set a unique HTML ID for the counter. @since v7.25.0 @category HTML attributes */ pnId; /** * Provide the label via an aria attribute. * We strongly recommend you use the `label` prop instead. * @since v7.25.0 * @category HTML attributes */ pnAriaLabel; /** * Provide the label from another element via its ID. * We strongly recommend you use the `label` prop instead. * @since v7.25.0 * @category HTML attributes */ pnAriaLabelledby; /** Set a unique HTML ID for the counter. @deprecated Use `pn-id` instead. @category HTML attributes */ counterid; /** * Instead of listening to multiple input, change and/or click events, we bundled them into one. * - `value` is the current counter value. * - `input` is true if the user changed the value with the HTML input. * - `decrease` is true if the user clicked on the decrease button. * - `increase` is true if the user clicked on the increase button. */ counterInput; watchValue() { this.showSrContent(); this.showMinMaxMessage = false; if (this.hasMin() && this.value < this.min) { this.setMin(); this.showMinMaxMessage = true; } if (this.hasMax() && this.value > this.max) { this.setMax(); this.showMinMaxMessage = true; } if (this.lastDispatchedValue !== this.value) this.counterInput.emit({ value: this.value, input: this.interactType === 'input', decrease: this.interactType === 'decrease', increase: this.interactType === 'increase', }); this.lastDispatchedValue = this.value; } handleId() { this.idLabel = `${this.getId()}-label`; this.idText = `${this.getId()}-text`; this.idAmount = `${this.getId()}-count`; } async componentWillLoad() { if (this.language === undefined) await awaitTopbar(this.hostElement); } getId() { return this.pnId || this.counterid || this.id; } setVal(event) { const { valueAsNumber } = event.target; if (isNaN(valueAsNumber)) return; this.interactType = 'input'; this.value = valueAsNumber; } setMin() { if (this.hasMin()) this.value = this.min; } setMax() { if (this.hasMax()) this.value = this.max; } hasMin() { return typeof this.min === 'number'; } hasMax() { return typeof this.max === 'number'; } getAriaLabel() { return !this.label && !this.pnAriaLabelledby ? this.pnAriaLabel : null; } getAriaLabelledby() { return !this.label && !this.pnAriaLabel ? this.pnAriaLabelledby : null; } decreaseAmount() { if (this.hasMin() && this.value <= this.min) this.showMinMaxMessage = true; this.interactType = 'decrease'; this.value = this.value - this.step; } increaseAmount() { if (this.hasMax() && this.value >= this.max) this.showMinMaxMessage = true; this.interactType = 'increase'; this.value = this.value + this.step; } keyBoardInput(event) { event.stopImmediatePropagation(); if (!/^(Home|End)$/.test(event.key)) return; event.preventDefault(); this.interactType = 'input'; if (event.key === 'Home') this.setMin(); if (event.key === 'End') this.setMax(); } showSrContent() { if (this.clearAriaTimer) { clearTimeout(this.clearAriaTimer); } this.displaySrValue = true; this.clearAriaTimer = setTimeout(() => { this.displaySrValue = false; }, 4000); } toggleMinMaxMessage() { const isMax = this.value === this.max; const i18ntxt = `${isMax ? 'MAX' : 'MIN'}_VALUE_MESSAGE`; const message = this.translate(i18ntxt) + `${isMax ? this.max : this.min}`.toString(); return isMax ? this.maxMessage || message : this.minMessage || message; } getTextMessage(showValue = false) { if (this.showMinMaxMessage) return this.toggleMinMaxMessage(); return showValue ? this.value.toString() : this.helpertext; } hideHelpertext() { const slotHelpertext = this.hostElement.querySelector('[slot="helpertext"]'); return !(this.getTextMessage() || !!slotHelpertext?.textContent); } describedbyIds() { const list = []; if (!this.hideHelpertext()) list.push(this.idText); if (this.displaySrValue) list.push(this.idAmount); if (list.length === 0) return null; return list.join(' '); } translate(prop) { return translations?.[prop]?.[this.language || en]; } noButtons() { return this.disabled || this.readonly; } renderLabel() { if (!this.label) return null; return (h("label", { htmlFor: this.getId(), class: "pn-counter-label", id: this.idLabel, "data-compact": this.compact }, h("span", null, this.label))); } renderButton(increase = false) { const label = increase ? this.labelIncrease || this.translate('INCREASE') : this.labelDecrease || this.translate('DECREASE'); return (h("pn-button", { "data-increase": increase ? true : null, "data-decrease": increase ? null : true, appearance: "light", variant: "outlined", "no-tab": this.noButtons(), icon: increase ? plus : minus, iconOnly: true, tooltip: this.hideTooltips ? null : label, arialabel: this.hideTooltips ? label : null, onPnClick: () => (increase ? this.increaseAmount() : this.decreaseAmount()) })); } render() { return (h(Host, { key: 'f1ff6cf51a77b28c5375f80c686106f82026eda8' }, h("div", { key: 'a6080106e0bf5ca0246e5a47a114c5ba967be4ef', class: "pn-counter", role: "group", "aria-labelledby": this.idLabel, "aria-describedby": this.describedbyIds() }, !this.compact && this.renderLabel(), h("div", { key: 'ef3c419277ca2f2a2f8fe8cefad5633c893b4736', class: "pn-counter-items", "data-hidebuttons": this.noButtons() }, this.renderButton(), h("div", { key: 'c130e8c0d812eaad1cf794e9417ab62d3c12e1a7', class: "pn-counter-input" }, h("input", { key: '908746fc89d2d70fb6f26f0f13bbe99961d66222', id: this.getId(), class: "pn-counter-element", type: "number", min: this.min, max: this.max, step: this.step, value: this.value, name: this.name, placeholder: this.compact ? ' ' : null, required: this.required, readonly: this.readonly, disabled: this.disabled, "aria-label": this.getAriaLabel(), "aria-labelledby": this.getAriaLabelledby(), "aria-describedby": this.describedbyIds(), "data-compact": this.compact, onInput: e => this.setVal(e), onKeyDown: (e) => this.keyBoardInput(e) }), this.compact && this.renderLabel()), this.renderButton(true)), h("p", { key: 'a25cd6e9abdb9fe8077e345cef828e187a11d260', id: this.idText, class: "pn-counter-helpertext", hidden: this.hideHelpertext() }, h("span", { key: '533d9836452f3b21a176eb594c225ad44c21956e' }, this.getTextMessage()), h("slot", { key: '35f10d13c8a1c65e6fc7b1a75b864e1cfd268837', name: "helpertext" })), h("p", { key: '1009ba0ea75131d6c19e3fb8960c39fc9d565d16', id: this.idAmount, class: "pn-counter-sr-only", "aria-live": "assertive" }, this.displaySrValue ? this.getTextMessage(true) : '')))); } static get is() { return "pn-counter"; } static get originalStyleUrls() { return { "$": ["pn-counter.scss"] }; } static get styleUrls() { return { "$": ["pn-counter.css"] }; } static get properties() { return { "label": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "Label for the counter. This is required for the counter to be 100% accessible out of the box." }, "getter": false, "setter": false, "reflect": false, "attribute": "label" }, "helpertext": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "Set a helpertext for the counter." }, "getter": false, "setter": false, "reflect": false, "attribute": "helpertext" }, "language": { "type": "string", "mutable": false, "complexType": { "original": "PnLanguages", "resolved": "\"\" | \"da\" | \"en\" | \"fi\" | \"no\" | \"sv\"", "references": { "PnLanguages": { "location": "import", "path": "@/globals/types", "id": "src/globals/types.ts::PnLanguages", "referenceLocation": "PnLanguages" } } }, "required": false, "optional": true, "docs": { "tags": [], "text": "Set the language manually for the built in translations." }, "getter": false, "setter": false, "reflect": false, "attribute": "language", "defaultValue": "undefined" }, "value": { "type": "number", "mutable": true, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": false, "docs": { "tags": [{ "name": "category", "text": "Native attributes" }], "text": "Set a predefined value." }, "getter": false, "setter": false, "reflect": true, "attribute": "value", "defaultValue": "0" }, "name": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": true, "docs": { "tags": [{ "name": "category", "text": "Native attributes" }], "text": "HTML input name." }, "getter": false, "setter": false, "reflect": false, "attribute": "name" }, "min": { "type": "number", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": true, "docs": { "tags": [{ "name": "category", "text": "Native attributes" }], "text": "Minimum value." }, "getter": false, "setter": false, "reflect": false, "attribute": "min" }, "max": { "type": "number", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": true, "docs": { "tags": [{ "name": "category", "text": "Native attributes" }], "text": "Maximum value." }, "getter": false, "setter": false, "reflect": false, "attribute": "max" }, "step": { "type": "number", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": false, "docs": { "tags": [{ "name": "category", "text": "Native attributes" }], "text": "Increase/decrease the value in steps, default is 1." }, "getter": false, "setter": false, "reflect": false, "attribute": "step", "defaultValue": "1" }, "list": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": true, "docs": { "tags": [{ "name": "category", "text": "Native attributes" }], "text": "Suggest values for the counter input." }, "getter": false, "setter": false, "reflect": false, "attribute": "list" }, "required": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [{ "name": "category", "text": "Native attributes" }], "text": "Set the counter as required." }, "getter": false, "setter": false, "reflect": false, "attribute": "required", "defaultValue": "false" }, "readonly": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [{ "name": "category", "text": "Native attributes" }], "text": "Set the counter as readonly." }, "getter": false, "setter": false, "reflect": false, "attribute": "readonly", "defaultValue": "false" }, "disabled": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [{ "name": "category", "text": "Native attributes" }], "text": "Set the counter as disabled." }, "getter": false, "setter": false, "reflect": false, "attribute": "disabled", "defaultValue": "false" }, "compact": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": true, "docs": { "tags": [{ "name": "since", "text": "v7.21.0" }, { "name": "category", "text": "Features" }], "text": "Use the compact label variant." }, "getter": false, "setter": false, "reflect": false, "attribute": "compact", "defaultValue": "false" }, "labelDecrease": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": true, "docs": { "tags": [{ "name": "category", "text": "Features" }], "text": "Text for the decrease button. Default is \"Decrease\"." }, "getter": false, "setter": false, "reflect": false, "attribute": "label-decrease" }, "labelIncrease": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": true, "docs": { "tags": [{ "name": "category", "text": "Features" }], "text": "Text for the increase button. Default is \"Increase\"." }, "getter": false, "setter": false, "reflect": false, "attribute": "label-increase" }, "hideTooltips": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": true, "docs": { "tags": [{ "name": "since", "text": "v7.25.0" }, { "name": "category", "text": "Features" }], "text": "Hide the tooltips for the increase/decrease button." }, "getter": false, "setter": false, "reflect": false, "attribute": "hide-tooltips", "defaultValue": "false" }, "minMessage": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": true, "docs": { "tags": [{ "name": "category", "text": "Features" }], "text": "Set a message that shows below input when user wants to set a value that is below `min`. Default is \"Minimum value is x\"" }, "getter": false, "setter": false, "reflect": false, "attribute": "min-message" }, "maxMessage": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": true, "docs": { "tags": [{ "name": "category", "text": "Features" }], "text": "Set a message that shows below input when user wants to set a value that is above `max`. Default is \"Maximum value is x\"" }, "getter": false, "setter": false, "reflect": false, "attribute": "max-message" }, "pnId": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": true, "docs": { "tags": [{ "name": "since", "text": "v7.25.0" }, { "name": "category", "text": "HTML attributes" }], "text": "Set a unique HTML ID for the counter." }, "getter": false, "setter": false, "reflect": false, "attribute": "pn-id" }, "pnAriaLabel": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": true, "docs": { "tags": [{ "name": "since", "text": "v7.25.0" }, { "name": "category", "text": "HTML attributes" }], "text": "Provide the label via an aria attribute.\nWe strongly recommend you use the `label` prop instead." }, "getter": false, "setter": false, "reflect": false, "attribute": "pn-aria-label" }, "pnAriaLabelledby": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": true, "docs": { "tags": [{ "name": "since", "text": "v7.25.0" }, { "name": "category", "text": "HTML attributes" }], "text": "Provide the label from another element via its ID.\nWe strongly recommend you use the `label` prop instead." }, "getter": false, "setter": false, "reflect": false, "attribute": "pn-aria-labelledby" }, "counterid": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": true, "docs": { "tags": [{ "name": "deprecated", "text": "Use `pn-id` instead." }, { "name": "category", "text": "HTML attributes" }], "text": "Set a unique HTML ID for the counter." }, "getter": false, "setter": false, "reflect": false, "attribute": "counterid" } }; } static get states() { return { "clearAriaTimer": {}, "displaySrValue": {}, "showMinMaxMessage": {}, "lastDispatchedValue": {}, "interactType": {} }; } static get events() { return [{ "method": "counterInput", "name": "counterInput", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Instead of listening to multiple input, change and/or click events, we bundled them into one.\n- `value` is the current counter value.\n- `input` is true if the user changed the value with the HTML input.\n- `decrease` is true if the user clicked on the decrease button.\n- `increase` is true if the user clicked on the increase button." }, "complexType": { "original": "{ value: number; input?: boolean; decrease?: boolean; increase?: boolean }", "resolved": "{ value: number; input?: boolean; decrease?: boolean; increase?: boolean; }", "references": {} } }]; } static get elementRef() { return "hostElement"; } static get watchers() { return [{ "propName": "value", "methodName": "watchValue" }, { "propName": "counterid", "methodName": "handleId" }, { "propName": "pnId", "methodName": "handleId", "handlerOptions": { "immediate": true } }]; } }