@postnord/web-components
Version:
PostNord Web Components
794 lines (793 loc) • 30.7 kB
JavaScript
/*!
* 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
}
}];
}
}