lion/packages/input-amount/src/LionInputAmount.js
2020-11-09 13:09:23 +01:00

143 lines
4.2 KiB
JavaScript

import { css } from '@lion/core';
import { LionInput } from '@lion/input';
import { getCurrencyName, localize, LocalizeMixin } from '@lion/localize';
import { IsNumber } from '@lion/form-core';
import { formatAmount, formatCurrencyLabel } from './formatters.js';
import { parseAmount } from './parsers.js';
/**
* `LionInputAmount` is a class for an amount custom form element (`<lion-input-amount>`).
*
* @customElement lion-input-amount
*/
// @ts-expect-error false positive for incompatible static get properties. Lit-element merges super properties already for you.
export class LionInputAmount extends LocalizeMixin(LionInput) {
static get properties() {
return {
/**
* @desc an iso code like 'EUR' or 'USD' that will be displayed next to the input
* and from which an accessible label (like 'euros') is computed for screen
* reader users
*/
currency: String,
/**
* @desc the modelValue of the input-amount has the 'Number' type. This allows
* Application Developers to easily read from and write to this input or write custom
* validators.
*/
modelValue: Number,
};
}
get slots() {
return {
...super.slots,
after: () => {
if (this.currency) {
const el = document.createElement('span');
// The data-label attribute will make sure that FormControl adds this to
// input[aria-labelledby]
el.setAttribute('data-label', '');
el.textContent = this.__currencyLabel;
return el;
}
return undefined;
},
};
}
get _currencyDisplayNode() {
return Array.from(this.children).find(child => child.slot === 'after');
}
static get styles() {
return [
super.styles,
css`
.input-group__container > .input-group__input ::slotted(.form-control) {
text-align: right;
}
`,
];
}
constructor() {
super();
this.parser = parseAmount;
this.formatter = formatAmount;
/** @type {string | undefined} */
this.currency = undefined;
this.__isPasting = false;
this.addEventListener('paste', () => {
this.__isPasting = true;
this.__parserCallcountSincePaste = 0;
});
this.defaultValidators.push(new IsNumber());
}
connectedCallback() {
// eslint-disable-next-line wc/guard-super-call
super.connectedCallback();
this.type = 'text';
this._inputNode.setAttribute('inputmode', 'decimal');
if (this.currency) {
this.__setCurrencyDisplayLabel();
}
}
/** @param {import('lit-element').PropertyValues } changedProperties */
updated(changedProperties) {
super.updated(changedProperties);
if (changedProperties.has('currency') && this.currency) {
this._onCurrencyChanged({ currency: this.currency });
}
}
/**
* @override of FormatMixin
*/
__callParser(value = this.formattedValue) {
// TODO: (@daKmor) input and change events both trigger parsing therefore we need to handle the second parse
this.__parserCallcountSincePaste += 1;
this.__isPasting = this.__parserCallcountSincePaste === 2;
this.formatOptions.mode = this.__isPasting === true ? 'pasted' : 'auto';
return super.__callParser(value);
}
/**
* @override of FormatMixin
*/
_reflectBackOn() {
return super._reflectBackOn() || this.__isPasting;
}
/**
* @param {Object} opts
* @param {string} opts.currency
*/
_onCurrencyChanged({ currency }) {
if (this._isPrivateSlot('after') && this._currencyDisplayNode) {
this._currencyDisplayNode.textContent = this.__currencyLabel;
}
this.formatOptions.currency = currency;
this._calculateValues({ source: null });
this.__setCurrencyDisplayLabel();
}
__setCurrencyDisplayLabel() {
// TODO: (@erikkroes) for optimal a11y, abbreviations should be part of aria-label
// example, for a language switch with text 'en', an aria-label of 'english' is not
// sufficient, it should also contain the abbreviation.
if (this.currency && this._currencyDisplayNode) {
this._currencyDisplayNode.setAttribute('aria-label', getCurrencyName(this.currency, {}));
}
}
get __currencyLabel() {
return this.currency ? formatCurrencyLabel(this.currency, localize.locale) : '';
}
}