imask
Version:
vanilla javascript input mask
193 lines (151 loc) • 5.84 kB
JavaScript
import {escapeRegExp, DIRECTION, indexInDirection} from '../core/utils.js';
import Masked from './base.js';
export default
class MaskedNumber extends Masked {
constructor (opts) {
opts.postFormat = Object.assign({}, MaskedNumber.DEFAULTS.postFormat, opts.postFormat);
super({
...MaskedNumber.DEFAULTS,
...opts
});
}
_update (opts) {
opts.postFormat = Object.assign({}, this.postFormat, opts.postFormat);
super._update(opts);
this._updateRegExps();
}
_updateRegExps () {
// TODO refactor?
let regExpStrSoft = '^';
let regExpStr = '^';
if (this.allowNegative) {
regExpStrSoft += '([+|\\-]?|([+|\\-]?(0|([1-9]+\\d*))))';
regExpStr += '[+|\\-]?';
} else {
regExpStrSoft += '(0|([1-9]+\\d*))';
}
regExpStr += '\\d*';
if (this.scale) {
regExpStrSoft += '(' + this.radix + '\\d{0,' + this.scale + '})?';
regExpStr += '(' + this.radix + '\\d{0,' + this.scale + '})?';
}
regExpStrSoft += '$';
regExpStr += '$';
this._numberRegExpSoft = new RegExp(regExpStrSoft);
this._numberRegExp = new RegExp(regExpStr);
this._mapToRadixRegExp = new RegExp('[' +
this.mapToRadix.map(escapeRegExp).join('') +
']', 'g');
this._thousandsSeparatorRegExp = new RegExp(escapeRegExp(this.thousandsSeparator), 'g');
}
extractTail (fromPos=0, toPos=this.value.length) {
return this._removeThousandsSeparators(super.extractTail(fromPos, toPos));
}
_removeThousandsSeparators (value) {
return value.replace(this._thousandsSeparatorRegExp, '');
}
_insertThousandsSeparators (value) {
// https://stackoverflow.com/questions/2901102/how-to-print-a-number-with-commas-as-thousands-separators-in-javascript
const parts = value.split(this.radix);
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandsSeparator);
return parts.join(this.radix);
}
doPrepare (str, soft) {
return super.doPrepare(this._removeThousandsSeparators(str.replace(this._mapToRadixRegExp, this.radix)), soft);
}
appendWithTail (str, tail) {
const oldValueLength = this.value.length;
this._value = this._removeThousandsSeparators(this.value);
let removedSeparatorsCount = oldValueLength - this.value.length;
const appended = super.appendWithTail(str, tail);
this._value = this._insertThousandsSeparators(this.value);
let beforeTailPos = oldValueLength + appended - removedSeparatorsCount;
let insertedSeparatorsBeforeTailCount = 0;
for (let pos = 0; pos <= beforeTailPos; ++pos) {
if (this.value[pos] === this.thousandsSeparator) {
++insertedSeparatorsBeforeTailCount;
++beforeTailPos;
}
}
return appended - removedSeparatorsCount + insertedSeparatorsBeforeTailCount;
}
nearestInputPos (cursorPos, direction=DIRECTION.LEFT) {
if (!direction) return cursorPos;
const nextPos = indexInDirection(cursorPos, direction);
if (this.value[nextPos] === this.thousandsSeparator) cursorPos += direction;
return cursorPos;
}
doValidate (soft) {
const regexp = soft ? this._numberRegExpSoft : this._numberRegExp;
// validate as string
let valid = regexp.test(this._removeThousandsSeparators(this.value));
if (valid) {
// validate as number
const number = this.number;
valid = valid && !isNaN(number) &&
// check min bound for negative values
(this.min == null || this.min >= 0 || this.min <= this.number) &&
// check max bound for positive values
(this.max == null || this.max <= 0 || this.number <= this.max);
}
return valid && super.doValidate(soft);
}
doCommit () {
const number = this.number;
let validnum = number;
// check bounds
if (this.min != null) validnum = Math.max(validnum, this.min);
if (this.max != null) validnum = Math.min(validnum, this.max);
if (validnum !== number) this.unmaskedValue = '' + validnum;
let formatted = this.value;
if (this.postFormat.normalizeZeros) formatted = this._normalizeZeros(formatted);
if (this.postFormat.padFractionalZeros) formatted = this._padFractionalZeros(formatted);
this._value = formatted;
super.doCommit();
}
_normalizeZeros (value) {
const parts = this._removeThousandsSeparators(value).split(this.radix);
// remove leading zeros
parts[0] = parts[0].replace(/^(\D*)(0*)(\d*)/, (match, sign, zeros, num) => sign + num);
// add leading zero
if (value.length && !/\d$/.test(parts[0])) parts[0] = parts[0] + '0';
if (parts.length > 1) {
parts[1] = parts[1].replace(/0*$/, ''); // remove trailing zeros
if (!parts[1].length) parts.length = 1; // remove fractional
}
return this._insertThousandsSeparators(parts.join(this.radix));
}
_padFractionalZeros (value) {
const parts = value.split(this.radix);
if (parts.length < 2) parts.push('');
parts[1] = parts[1].padEnd(this.scale, '0');
return parts.join(this.radix);
}
get number () {
let numstr =
this._removeThousandsSeparators(
this._normalizeZeros(
this.unmaskedValue))
.replace(this.radix, '.');
return Number(numstr);
}
set number (number) {
this.unmaskedValue = ('' + number).replace('.', this.radix);
}
get allowNegative () {
return this.signed ||
(this.min != null && this.min < 0) ||
(this.max != null && this.max < 0);
}
}
MaskedNumber.DEFAULTS = {
radix: ',',
thousandsSeparator: '',
mapToRadix: ['.'],
scale: 2,
signed: false,
postFormat: {
normalizeZeros: true,
padFractionalZeros: false
}
};