@unicef-polymer/etools-unicef
Version:
eTools UNICEF library of reusable components
410 lines (409 loc) • 15.7 kB
JavaScript
import { __decorate } from "tslib";
import { html } from 'lit';
import { customElement, property, query, state } from 'lit/decorators.js';
import './etools-input';
import { EtoolsInputBase } from './etools-input-base';
import { addCurrencyAmountDelimiter } from '../utils/currency';
import { fireEvent } from '@unicef-polymer/etools-utils/dist/fire-event.util';
/**
* `etools-currency`
*
* A shoelace-input element that allows only currency amount values (US format). It accepts only digits, comma, a
* a single floating point (period), and allows 2 decimals. The maximum number you can have 12 digits until
* floating point.
* The value displayed it's shoelace-input's internal value. The `value` property of this element will update and format
* the internal value and when internal value is changed, element's value will be updated with current float value.
*
* ### Usage
* ```html
* <etools-currency label="Amount value"
* value="[[value]]" currency="$"></etools-currency>
* ```
*
* ### Style
*
* Use CSS properties and mixin of shoelace-input to style the element or:
*
* `--etools-currency` | Mixin applied to currency element | `{}`
*
* @customElement
* @lit
* @appliesMixin EtoolsCurrency
*/
let EtoolsCurrency = class EtoolsCurrency extends EtoolsInputBase {
constructor() {
super(...arguments);
this.value = null;
this.noOfDecimals = 2;
this.noOfSignificantDigits = 12;
this.internalValue = null;
}
render() {
// language=HTML
return html `
<etools-input
label="${this.label}"
.value="${this.internalValue}"
allowed-pattern="[0-9\\.\\,]"
placeholder="${this.placeholder}"
?disabled="${this.disabled}"
?readonly="${this.readonly}"
?required="${this.required}"
?invalid="${this.invalid}"
?auto-validate="${!this.readonly && this.autoValidate}"
error-message="${this.errorMessage}"
?no-label-float="${this.noLabelFloat}"
="${this._onKeyDown}"
="${this._onBlur}"
="${this._onFocus}"
-changed="${(e) => {
this.internalValue = e.detail.value;
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}}"
exportparts="base,input,form-control,form-control-label,form-control-help-text"
>
<div slot="prefix" class="prefix" ?hidden="${!this.currency}">${this.currency}</div>
</etools-input>
`;
}
get inputElement() {
return this.etoolsInput.slInput;
}
get nativeInput() {
var _a, _b;
return (_b = (_a = this.inputElement) === null || _a === void 0 ? void 0 : _a.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('input');
}
updated(_changedProperties) {
if (_changedProperties.has('value')) {
this._onValueChange(this.value);
}
if (_changedProperties.has('internalValue')) {
this._onInternalValueChange(this.internalValue, _changedProperties.get('internalValue'));
}
if (this.autoValidate && _changedProperties.has('internalValue') && this.internalValue !== undefined) {
setTimeout(() => (this.invalid = !this.inputElement.reportValidity()));
}
if (_changedProperties.has('readonly') && this.readonly) {
this.invalid = false;
}
}
validate() {
var _a;
this.invalid = !((_a = this.inputElement) === null || _a === void 0 ? void 0 : _a.reportValidity());
return !this.invalid;
}
_getStrValue(value) {
try {
return value === 0 ? '0' : value.toString();
}
catch (_error) {
return '0';
}
}
_onValueChange(value) {
if (value === null && this.internalValue === '') {
return;
}
let currentValue = value;
if (currentValue === null || typeof currentValue === 'undefined') {
this.internalValue = null;
}
currentValue = parseFloat(this._getValueWithoutFormat(value, this.noOfDecimals, true)).toFixed(this.noOfDecimals);
if (isNaN(currentValue)) {
currentValue = null;
}
let internalVal = this.internalValue;
if (internalVal) {
internalVal = parseFloat(this._getValueWithoutFormat(this.internalValue, this.noOfDecimals, true)).toFixed(this.noOfDecimals);
}
if (currentValue !== internalVal) {
this.internalValue = currentValue;
}
}
_restoreDamagedInternalValue(value, oldValue) {
// search for wrong delimiters and repair value's format
const formattedValue = this._formatValue(value);
const currentValCurrencyDelimitersNr = value.split(',').length;
const formattedValCurrencyDelimitersNr = formattedValue.split(',').length;
if (currentValCurrencyDelimitersNr === formattedValCurrencyDelimitersNr) {
this._updateValueAndPreserveCaretPosition(value, oldValue);
return;
}
// restore value and update cursor position
this._updateElementInternalValue(formattedValue, oldValue);
}
/**
* Internal value changed, needs to be checked and changed to US currency format
*/
_onInternalValueChange(value, oldValue) {
if (typeof value === 'undefined' || value === null) {
return;
}
value = this._getStrValue(value);
oldValue = this._getStrValue(oldValue);
if (value.substring(0, 1) === '0' && value.substring(1, 2) !== '.' && value.length > 1) {
this._updateElementInternalValue(oldValue, value);
return;
}
if (value === '.') {
this.internalValue = null;
return;
}
// floating point/period can be added just one and only at the end of the string
const floatingPointPos = value.indexOf('.');
if (floatingPointPos > -1 && floatingPointPos + 1 < value.length - (oldValue.indexOf('.') > -1 ? 4 : 3)) {
// floating point can be added only at the end of the string, starting with the last 2 digits
this._updateElementInternalValue(value.replace('.', ''), oldValue);
return;
}
let preserveFloatingPoint = false;
if (value.slice(-1) === '.') {
preserveFloatingPoint = true;
}
if (this._getValueWithoutFormat(value) === this._getValueWithoutFormat(oldValue) && !preserveFloatingPoint) {
// restore damaged internal value
this._restoreDamagedInternalValue(value, oldValue);
return;
}
const valueWithoutDelimiter = value.replace(/,/g, '');
const zeroValue = String(parseFloat('0').toFixed(this.noOfDecimals));
// prevent having invalid numbers like 000,000.00, if number have only 0s, set value to 0.00
if (!+valueWithoutDelimiter && valueWithoutDelimiter.length > zeroValue.length) {
this._updateElementInternalValue(zeroValue, value);
return;
}
if (value.substring(0, 1) === '.') {
// no integer value, only floating point and decimals
value = value.substring(0, 3);
}
else {
// format new value
value = this._formatValue(value);
if (preserveFloatingPoint) {
value = value + '.';
}
}
this._updateElementInternalValue(value, oldValue);
this._setExternalValue(value, preserveFloatingPoint);
}
/**
* Update element value with the float value of internalValue
*/
_setExternalValue(value, preserveFloatingPoint) {
let cleanValStr = this._getValueWithoutFormat(value, this.noOfDecimals);
const valuePieces = cleanValStr.split('.');
let limitExceeded = false;
if (valuePieces[0].length > this.noOfSignificantDigits) {
// limit number integer part to max 12 digits
valuePieces[0] = valuePieces[0].slice(0, this.noOfSignificantDigits);
limitExceeded = true;
}
cleanValStr = valuePieces.join('.');
if (preserveFloatingPoint) {
cleanValStr += '.';
}
if (limitExceeded) {
this.internalValue = addCurrencyAmountDelimiter(cleanValStr);
return;
}
const realFloatValue = this._getRealNumberValue(cleanValStr);
if (realFloatValue !== this.value) {
// update value only if needed
this.value = realFloatValue;
fireEvent(this, 'value-changed', { value: this.value });
}
else {
// update internal value
this.internalValue = addCurrencyAmountDelimiter(cleanValStr);
}
}
_formatValue(value) {
value = this._getValueWithoutFormat(value, this.noOfDecimals);
// re-apply format
value = this._applyCurrencyAmountFormat(value);
return value.trim();
}
_getCaretPosition(oField) {
if (!oField) {
return -1;
}
let iCaretPos = 0;
if (oField.selectionStart || oField.selectionStart == '0') {
iCaretPos = oField.selectionDirection == 'backward' ? oField.selectionStart : oField.selectionEnd;
}
return iCaretPos;
}
_getUpdatedCursorPosition(value, oldValue, cursorPos) {
const valueLength = (value || '').length;
const oldValueLength = (oldValue || '').length;
const diff = valueLength - oldValueLength;
const numberAddedWithDelimiter = diff > 1;
const numberRemovedWithDelimiter = diff < -1;
const cursorIsNotFirst = cursorPos > 1;
const cursorIsNotLast = cursorPos < valueLength;
if (numberAddedWithDelimiter && cursorIsNotFirst) {
cursorPos++;
}
else if (numberRemovedWithDelimiter && cursorIsNotLast) {
cursorPos--;
}
return cursorPos;
}
_updateElementInternalValue(value, oldValue) {
if (!this.nativeInput) {
return;
}
// Required in order for next run to have the updated oldValue
this.internalValue = value;
this._updateValueAndPreserveCaretPosition(value, oldValue);
if (!this.readonly && this.autoValidate) {
this.validate();
}
}
_updateValueAndPreserveCaretPosition(value, oldValue) {
let cursorPos = this._getCaretPosition(this.nativeInput);
// Required to be able to set correct caret position.
this.nativeInput.value = value;
try {
if (this.nativeInput && cursorPos >= 0) {
cursorPos = this._getUpdatedCursorPosition(value, oldValue, cursorPos);
this.nativeInput.selectionStart = cursorPos;
this.nativeInput.selectionEnd = cursorPos;
}
}
catch (err) {
console.log(err);
}
}
_applyCurrencyAmountFormat(value) {
value = this._getStrValue(value);
let formattedValue = '';
const _valueParts = value.split('.');
/**
* _valueParts[0] - integer part
* _valueParts[1] - decimals, if any
*/
if (_valueParts[0] !== '') {
const decimalsPart = _valueParts[1];
formattedValue = addCurrencyAmountDelimiter(_valueParts[0]);
if (!this._emptyValue(decimalsPart)) {
formattedValue += '.' + decimalsPart;
}
}
return formattedValue;
}
_getValueWithoutFormat(value, decimalsNr, needsStrValue) {
if (!decimalsNr) {
decimalsNr = 0;
}
if (needsStrValue) {
value = this._getStrValue(value);
}
let _values = value.split('.');
let _decimals = '';
const _floatingPoints = _values.length - 1;
if (_floatingPoints === 1) {
_decimals = _values[_values.length - 1];
_values = _values.slice(0, _values.length - 1);
}
value = _values.join('');
if (_decimals !== '') {
if (decimalsNr > 0) {
_decimals = _decimals.slice(0, decimalsNr);
}
value += '.' + _decimals;
}
// remove commas and spaces and return value
return value.split(',').join('').split(' ').join('');
}
_emptyValue(value) {
if (value === null || typeof value === 'undefined') {
return true;
}
value = value.toString();
return value === '' ? true : false;
}
_getRealNumberValue(value, decimals) {
if (!decimals) {
decimals = false;
}
if (this._emptyValue(value)) {
return null;
}
value = this._getValueWithoutFormat(value, this.noOfDecimals);
const floatVal = parseFloat(value);
if (isNaN(floatVal)) {
return null;
}
if (decimals) {
return parseFloat(floatVal.toFixed(decimals));
}
return floatVal;
}
_onKeyDown(e) {
if (e.key !== 'Escape' && !(e.key == 's' && e.ctrlKey)) {
e.stopImmediatePropagation();
}
if (e.key == 's' && e.ctrlKey) {
e.preventDefault();
}
if (e.which === 190) {
// do not allow more then one period char ('.')
const currentInternalValue = this.internalValue ? this.internalValue.toString() : '';
const floatingPtsNr = currentInternalValue.split('.').length - 1;
if (floatingPtsNr === 1) {
// stop, we already have a period
e.preventDefault();
}
}
}
_onBlur() {
if (this.internalValue) {
// adjust decimals on focus lost
if (this.internalValue.substring(this.internalValue.length - 1) === '.') {
this.internalValue = this.internalValue + '00';
}
const _floatingPointPos = this.internalValue.indexOf('.');
if (_floatingPointPos === -1) {
this.internalValue = this.internalValue + '.00';
}
else {
if (this.internalValue.slice(_floatingPointPos + 1).length == 1) {
// add second missing decimal
this.internalValue = this.internalValue + '0';
}
}
if (this.internalValue.substring(0, 1) === '.') {
this.internalValue = '0' + this.internalValue;
}
}
}
_onFocus(_e) {
var _a;
(_a = this.nativeInput) === null || _a === void 0 ? void 0 : _a.select();
}
};
__decorate([
property({ type: String })
], EtoolsCurrency.prototype, "value", void 0);
__decorate([
property({ type: String, reflect: true, attribute: 'currency' })
], EtoolsCurrency.prototype, "currency", void 0);
__decorate([
property({ type: Number, reflect: true, attribute: 'no-of-decimals' })
], EtoolsCurrency.prototype, "noOfDecimals", void 0);
__decorate([
property({ type: Number, reflect: true, attribute: 'no-of-significant-digits' })
], EtoolsCurrency.prototype, "noOfSignificantDigits", void 0);
__decorate([
state()
], EtoolsCurrency.prototype, "internalValue", void 0);
__decorate([
query('etools-input')
], EtoolsCurrency.prototype, "etoolsInput", void 0);
EtoolsCurrency = __decorate([
customElement('etools-currency')
], EtoolsCurrency);
export { EtoolsCurrency };