imask
Version:
vanilla javascript input mask
342 lines (333 loc) • 11.2 kB
JavaScript
import { DIRECTION, objectIncludes } from '../core/utils.js';
import ChangeDetails from '../core/change-details.js';
import createMask, { normalizeOpts } from './factory.js';
import Masked from './base.js';
import IMask from '../core/holder.js';
import '../core/continuous-tail-details.js';
/** Dynamic mask for choosing appropriate mask in run-time */
class MaskedDynamic extends Masked {
constructor(opts) {
super({
...MaskedDynamic.DEFAULTS,
...opts
});
this.currentMask = undefined;
}
updateOptions(opts) {
super.updateOptions(opts);
}
_update(opts) {
super._update(opts);
if ('mask' in opts) {
this.exposeMask = undefined;
// mask could be totally dynamic with only `dispatch` option
this.compiledMasks = Array.isArray(opts.mask) ? opts.mask.map(m => {
const {
expose,
...maskOpts
} = normalizeOpts(m);
const masked = createMask({
overwrite: this._overwrite,
eager: this._eager,
skipInvalid: this._skipInvalid,
...maskOpts
});
if (expose) this.exposeMask = masked;
return masked;
}) : [];
// this.currentMask = this.doDispatch(''); // probably not needed but lets see
}
}
_appendCharRaw(ch, flags) {
if (flags === void 0) {
flags = {};
}
const details = this._applyDispatch(ch, flags);
if (this.currentMask) {
details.aggregate(this.currentMask._appendChar(ch, this.currentMaskFlags(flags)));
}
return details;
}
_applyDispatch(appended, flags, tail) {
if (appended === void 0) {
appended = '';
}
if (flags === void 0) {
flags = {};
}
if (tail === void 0) {
tail = '';
}
const prevValueBeforeTail = flags.tail && flags._beforeTailState != null ? flags._beforeTailState._value : this.value;
const inputValue = this.rawInputValue;
const insertValue = flags.tail && flags._beforeTailState != null ? flags._beforeTailState._rawInputValue : inputValue;
const tailValue = inputValue.slice(insertValue.length);
const prevMask = this.currentMask;
const details = new ChangeDetails();
const prevMaskState = prevMask == null ? void 0 : prevMask.state;
// clone flags to prevent overwriting `_beforeTailState`
this.currentMask = this.doDispatch(appended, {
...flags
}, tail);
// restore state after dispatch
if (this.currentMask) {
if (this.currentMask !== prevMask) {
// if mask changed reapply input
this.currentMask.reset();
if (insertValue) {
this.currentMask.append(insertValue, {
raw: true
});
details.tailShift = this.currentMask.value.length - prevValueBeforeTail.length;
}
if (tailValue) {
details.tailShift += this.currentMask.append(tailValue, {
raw: true,
tail: true
}).tailShift;
}
} else if (prevMaskState) {
// Dispatch can do something bad with state, so
// restore prev mask state
this.currentMask.state = prevMaskState;
}
}
return details;
}
_appendPlaceholder() {
const details = this._applyDispatch();
if (this.currentMask) {
details.aggregate(this.currentMask._appendPlaceholder());
}
return details;
}
_appendEager() {
const details = this._applyDispatch();
if (this.currentMask) {
details.aggregate(this.currentMask._appendEager());
}
return details;
}
appendTail(tail) {
const details = new ChangeDetails();
if (tail) details.aggregate(this._applyDispatch('', {}, tail));
return details.aggregate(this.currentMask ? this.currentMask.appendTail(tail) : super.appendTail(tail));
}
currentMaskFlags(flags) {
var _flags$_beforeTailSta, _flags$_beforeTailSta2;
return {
...flags,
_beforeTailState: ((_flags$_beforeTailSta = flags._beforeTailState) == null ? void 0 : _flags$_beforeTailSta.currentMaskRef) === this.currentMask && ((_flags$_beforeTailSta2 = flags._beforeTailState) == null ? void 0 : _flags$_beforeTailSta2.currentMask) || flags._beforeTailState
};
}
doDispatch(appended, flags, tail) {
if (flags === void 0) {
flags = {};
}
if (tail === void 0) {
tail = '';
}
return this.dispatch(appended, this, flags, tail);
}
doValidate(flags) {
return super.doValidate(flags) && (!this.currentMask || this.currentMask.doValidate(this.currentMaskFlags(flags)));
}
doPrepare(str, flags) {
if (flags === void 0) {
flags = {};
}
let [s, details] = super.doPrepare(str, flags);
if (this.currentMask) {
let currentDetails;
[s, currentDetails] = super.doPrepare(s, this.currentMaskFlags(flags));
details = details.aggregate(currentDetails);
}
return [s, details];
}
doPrepareChar(str, flags) {
if (flags === void 0) {
flags = {};
}
let [s, details] = super.doPrepareChar(str, flags);
if (this.currentMask) {
let currentDetails;
[s, currentDetails] = super.doPrepareChar(s, this.currentMaskFlags(flags));
details = details.aggregate(currentDetails);
}
return [s, details];
}
reset() {
var _this$currentMask;
(_this$currentMask = this.currentMask) == null || _this$currentMask.reset();
this.compiledMasks.forEach(m => m.reset());
}
get value() {
return this.exposeMask ? this.exposeMask.value : this.currentMask ? this.currentMask.value : '';
}
set value(value) {
if (this.exposeMask) {
this.exposeMask.value = value;
this.currentMask = this.exposeMask;
this._applyDispatch();
} else super.value = value;
}
get unmaskedValue() {
return this.exposeMask ? this.exposeMask.unmaskedValue : this.currentMask ? this.currentMask.unmaskedValue : '';
}
set unmaskedValue(unmaskedValue) {
if (this.exposeMask) {
this.exposeMask.unmaskedValue = unmaskedValue;
this.currentMask = this.exposeMask;
this._applyDispatch();
} else super.unmaskedValue = unmaskedValue;
}
get typedValue() {
return this.exposeMask ? this.exposeMask.typedValue : this.currentMask ? this.currentMask.typedValue : '';
}
set typedValue(typedValue) {
if (this.exposeMask) {
this.exposeMask.typedValue = typedValue;
this.currentMask = this.exposeMask;
this._applyDispatch();
return;
}
let unmaskedValue = String(typedValue);
// double check it
if (this.currentMask) {
this.currentMask.typedValue = typedValue;
unmaskedValue = this.currentMask.unmaskedValue;
}
this.unmaskedValue = unmaskedValue;
}
get displayValue() {
return this.currentMask ? this.currentMask.displayValue : '';
}
get isComplete() {
var _this$currentMask2;
return Boolean((_this$currentMask2 = this.currentMask) == null ? void 0 : _this$currentMask2.isComplete);
}
get isFilled() {
var _this$currentMask3;
return Boolean((_this$currentMask3 = this.currentMask) == null ? void 0 : _this$currentMask3.isFilled);
}
remove(fromPos, toPos) {
const details = new ChangeDetails();
if (this.currentMask) {
details.aggregate(this.currentMask.remove(fromPos, toPos))
// update with dispatch
.aggregate(this._applyDispatch());
}
return details;
}
get state() {
var _this$currentMask4;
return {
...super.state,
_rawInputValue: this.rawInputValue,
compiledMasks: this.compiledMasks.map(m => m.state),
currentMaskRef: this.currentMask,
currentMask: (_this$currentMask4 = this.currentMask) == null ? void 0 : _this$currentMask4.state
};
}
set state(state) {
const {
compiledMasks,
currentMaskRef,
currentMask,
...maskedState
} = state;
if (compiledMasks) this.compiledMasks.forEach((m, mi) => m.state = compiledMasks[mi]);
if (currentMaskRef != null) {
this.currentMask = currentMaskRef;
this.currentMask.state = currentMask;
}
super.state = maskedState;
}
extractInput(fromPos, toPos, flags) {
return this.currentMask ? this.currentMask.extractInput(fromPos, toPos, flags) : '';
}
extractTail(fromPos, toPos) {
return this.currentMask ? this.currentMask.extractTail(fromPos, toPos) : super.extractTail(fromPos, toPos);
}
doCommit() {
if (this.currentMask) this.currentMask.doCommit();
super.doCommit();
}
nearestInputPos(cursorPos, direction) {
return this.currentMask ? this.currentMask.nearestInputPos(cursorPos, direction) : super.nearestInputPos(cursorPos, direction);
}
get overwrite() {
return this.currentMask ? this.currentMask.overwrite : this._overwrite;
}
set overwrite(overwrite) {
this._overwrite = overwrite;
}
get eager() {
return this.currentMask ? this.currentMask.eager : this._eager;
}
set eager(eager) {
this._eager = eager;
}
get skipInvalid() {
return this.currentMask ? this.currentMask.skipInvalid : this._skipInvalid;
}
set skipInvalid(skipInvalid) {
this._skipInvalid = skipInvalid;
}
get autofix() {
return this.currentMask ? this.currentMask.autofix : this._autofix;
}
set autofix(autofix) {
this._autofix = autofix;
}
maskEquals(mask) {
return Array.isArray(mask) ? this.compiledMasks.every((m, mi) => {
if (!mask[mi]) return;
const {
mask: oldMask,
...restOpts
} = mask[mi];
return objectIncludes(m, restOpts) && m.maskEquals(oldMask);
}) : super.maskEquals(mask);
}
typedValueEquals(value) {
var _this$currentMask5;
return Boolean((_this$currentMask5 = this.currentMask) == null ? void 0 : _this$currentMask5.typedValueEquals(value));
}
}
/** Currently chosen mask */
/** Currently chosen mask */
/** Compliled {@link Masked} options */
/** Chooses {@link Masked} depending on input value */
MaskedDynamic.DEFAULTS = {
...Masked.DEFAULTS,
dispatch: (appended, masked, flags, tail) => {
if (!masked.compiledMasks.length) return;
const inputValue = masked.rawInputValue;
// simulate input
const inputs = masked.compiledMasks.map((m, index) => {
const isCurrent = masked.currentMask === m;
const startInputPos = isCurrent ? m.displayValue.length : m.nearestInputPos(m.displayValue.length, DIRECTION.FORCE_LEFT);
if (m.rawInputValue !== inputValue) {
m.reset();
m.append(inputValue, {
raw: true
});
} else if (!isCurrent) {
m.remove(startInputPos);
}
m.append(appended, masked.currentMaskFlags(flags));
m.appendTail(tail);
return {
index,
weight: m.rawInputValue.length,
totalInputPositions: m.totalInputPositions(0, Math.max(startInputPos, m.nearestInputPos(m.displayValue.length, DIRECTION.FORCE_LEFT)))
};
});
// pop masks with longer values first
inputs.sort((i1, i2) => i2.weight - i1.weight || i2.totalInputPositions - i1.totalInputPositions);
return masked.compiledMasks[inputs[0].index];
}
};
IMask.MaskedDynamic = MaskedDynamic;
export { MaskedDynamic as default };