UNPKG

imask

Version:

vanilla javascript input mask

465 lines (442 loc) 15.1 kB
import ChangeDetails from '../core/change-details.js'; import IMask from '../core/holder.js'; import { DIRECTION } from '../core/utils.js'; import Masked from './base.js'; import createMask, { normalizeOpts } from './factory.js'; import ChunksTailDetails from './pattern/chunk-tail-details.js'; import PatternCursor from './pattern/cursor.js'; import PatternFixedDefinition from './pattern/fixed-definition.js'; import PatternInputDefinition from './pattern/input-definition.js'; import './regexp.js'; import '../core/continuous-tail-details.js'; /** Pattern mask */ class MaskedPattern extends Masked { /** */ /** */ /** Single char for empty input */ /** Single char for filled input */ /** Show placeholder only when needed */ /** Enable characters overwriting */ /** */ /** */ /** */ constructor(opts) { super({ ...MaskedPattern.DEFAULTS, ...opts, definitions: Object.assign({}, PatternInputDefinition.DEFAULT_DEFINITIONS, opts == null ? void 0 : opts.definitions) }); } updateOptions(opts) { super.updateOptions(opts); } _update(opts) { opts.definitions = Object.assign({}, this.definitions, opts.definitions); super._update(opts); this._rebuildMask(); } _rebuildMask() { const defs = this.definitions; this._blocks = []; this.exposeBlock = undefined; this._stops = []; this._maskedBlocks = {}; const pattern = this.mask; if (!pattern || !defs) return; let unmaskingBlock = false; let optionalBlock = false; for (let i = 0; i < pattern.length; ++i) { if (this.blocks) { const p = pattern.slice(i); const bNames = Object.keys(this.blocks).filter(bName => p.indexOf(bName) === 0); // order by key length bNames.sort((a, b) => b.length - a.length); // use block name with max length const bName = bNames[0]; if (bName) { const { expose, repeat, ...bOpts } = normalizeOpts(this.blocks[bName]); // TODO type Opts<Arg & Extra> const blockOpts = { lazy: this.lazy, eager: this.eager, placeholderChar: this.placeholderChar, displayChar: this.displayChar, overwrite: this.overwrite, autofix: this.autofix, ...bOpts, repeat, parent: this }; const maskedBlock = repeat != null ? new IMask.RepeatBlock(blockOpts /* TODO */) : createMask(blockOpts); if (maskedBlock) { this._blocks.push(maskedBlock); if (expose) this.exposeBlock = maskedBlock; // store block index if (!this._maskedBlocks[bName]) this._maskedBlocks[bName] = []; this._maskedBlocks[bName].push(this._blocks.length - 1); } i += bName.length - 1; continue; } } let char = pattern[i]; let isInput = (char in defs); if (char === MaskedPattern.STOP_CHAR) { this._stops.push(this._blocks.length); continue; } if (char === '{' || char === '}') { unmaskingBlock = !unmaskingBlock; continue; } if (char === '[' || char === ']') { optionalBlock = !optionalBlock; continue; } if (char === MaskedPattern.ESCAPE_CHAR) { ++i; char = pattern[i]; if (!char) break; isInput = false; } const def = isInput ? new PatternInputDefinition({ isOptional: optionalBlock, lazy: this.lazy, eager: this.eager, placeholderChar: this.placeholderChar, displayChar: this.displayChar, ...normalizeOpts(defs[char]), parent: this }) : new PatternFixedDefinition({ char, eager: this.eager, isUnmasking: unmaskingBlock }); this._blocks.push(def); } } get state() { return { ...super.state, _blocks: this._blocks.map(b => b.state) }; } set state(state) { if (!state) { this.reset(); return; } const { _blocks, ...maskedState } = state; this._blocks.forEach((b, bi) => b.state = _blocks[bi]); super.state = maskedState; } reset() { super.reset(); this._blocks.forEach(b => b.reset()); } get isComplete() { return this.exposeBlock ? this.exposeBlock.isComplete : this._blocks.every(b => b.isComplete); } get isFilled() { return this._blocks.every(b => b.isFilled); } get isFixed() { return this._blocks.every(b => b.isFixed); } get isOptional() { return this._blocks.every(b => b.isOptional); } doCommit() { this._blocks.forEach(b => b.doCommit()); super.doCommit(); } get unmaskedValue() { return this.exposeBlock ? this.exposeBlock.unmaskedValue : this._blocks.reduce((str, b) => str += b.unmaskedValue, ''); } set unmaskedValue(unmaskedValue) { if (this.exposeBlock) { const tail = this.extractTail(this._blockStartPos(this._blocks.indexOf(this.exposeBlock)) + this.exposeBlock.displayValue.length); this.exposeBlock.unmaskedValue = unmaskedValue; this.appendTail(tail); this.doCommit(); } else super.unmaskedValue = unmaskedValue; } get value() { return this.exposeBlock ? this.exposeBlock.value : // TODO return _value when not in change? this._blocks.reduce((str, b) => str += b.value, ''); } set value(value) { if (this.exposeBlock) { const tail = this.extractTail(this._blockStartPos(this._blocks.indexOf(this.exposeBlock)) + this.exposeBlock.displayValue.length); this.exposeBlock.value = value; this.appendTail(tail); this.doCommit(); } else super.value = value; } get typedValue() { return this.exposeBlock ? this.exposeBlock.typedValue : super.typedValue; } set typedValue(value) { if (this.exposeBlock) { const tail = this.extractTail(this._blockStartPos(this._blocks.indexOf(this.exposeBlock)) + this.exposeBlock.displayValue.length); this.exposeBlock.typedValue = value; this.appendTail(tail); this.doCommit(); } else super.typedValue = value; } get displayValue() { return this._blocks.reduce((str, b) => str += b.displayValue, ''); } appendTail(tail) { return super.appendTail(tail).aggregate(this._appendPlaceholder()); } _appendEager() { var _this$_mapPosToBlock; const details = new ChangeDetails(); let startBlockIndex = (_this$_mapPosToBlock = this._mapPosToBlock(this.displayValue.length)) == null ? void 0 : _this$_mapPosToBlock.index; if (startBlockIndex == null) return details; // TODO test if it works for nested pattern masks if (this._blocks[startBlockIndex].isFilled) ++startBlockIndex; for (let bi = startBlockIndex; bi < this._blocks.length; ++bi) { const d = this._blocks[bi]._appendEager(); if (!d.inserted) break; details.aggregate(d); } return details; } _appendCharRaw(ch, flags) { if (flags === void 0) { flags = {}; } const blockIter = this._mapPosToBlock(this.displayValue.length); const details = new ChangeDetails(); if (!blockIter) return details; for (let bi = blockIter.index, block; block = this._blocks[bi]; ++bi) { var _flags$_beforeTailSta; const blockDetails = block._appendChar(ch, { ...flags, _beforeTailState: (_flags$_beforeTailSta = flags._beforeTailState) == null || (_flags$_beforeTailSta = _flags$_beforeTailSta._blocks) == null ? void 0 : _flags$_beforeTailSta[bi] }); details.aggregate(blockDetails); if (blockDetails.consumed) break; // go next char } return details; } extractTail(fromPos, toPos) { if (fromPos === void 0) { fromPos = 0; } if (toPos === void 0) { toPos = this.displayValue.length; } const chunkTail = new ChunksTailDetails(); if (fromPos === toPos) return chunkTail; this._forEachBlocksInRange(fromPos, toPos, (b, bi, bFromPos, bToPos) => { const blockChunk = b.extractTail(bFromPos, bToPos); blockChunk.stop = this._findStopBefore(bi); blockChunk.from = this._blockStartPos(bi); if (blockChunk instanceof ChunksTailDetails) blockChunk.blockIndex = bi; chunkTail.extend(blockChunk); }); return chunkTail; } extractInput(fromPos, toPos, flags) { if (fromPos === void 0) { fromPos = 0; } if (toPos === void 0) { toPos = this.displayValue.length; } if (flags === void 0) { flags = {}; } if (fromPos === toPos) return ''; let input = ''; this._forEachBlocksInRange(fromPos, toPos, (b, _, fromPos, toPos) => { input += b.extractInput(fromPos, toPos, flags); }); return input; } _findStopBefore(blockIndex) { let stopBefore; for (let si = 0; si < this._stops.length; ++si) { const stop = this._stops[si]; if (stop <= blockIndex) stopBefore = stop;else break; } return stopBefore; } /** Appends placeholder depending on laziness */ _appendPlaceholder(toBlockIndex) { const details = new ChangeDetails(); if (this.lazy && toBlockIndex == null) return details; const startBlockIter = this._mapPosToBlock(this.displayValue.length); if (!startBlockIter) return details; const startBlockIndex = startBlockIter.index; const endBlockIndex = toBlockIndex != null ? toBlockIndex : this._blocks.length; this._blocks.slice(startBlockIndex, endBlockIndex).forEach(b => { if (!b.lazy || toBlockIndex != null) { var _blocks2; details.aggregate(b._appendPlaceholder((_blocks2 = b._blocks) == null ? void 0 : _blocks2.length)); } }); return details; } /** Finds block in pos */ _mapPosToBlock(pos) { let accVal = ''; for (let bi = 0; bi < this._blocks.length; ++bi) { const block = this._blocks[bi]; const blockStartPos = accVal.length; accVal += block.displayValue; if (pos <= accVal.length) { return { index: bi, offset: pos - blockStartPos }; } } } _blockStartPos(blockIndex) { return this._blocks.slice(0, blockIndex).reduce((pos, b) => pos += b.displayValue.length, 0); } _forEachBlocksInRange(fromPos, toPos, fn) { if (toPos === void 0) { toPos = this.displayValue.length; } const fromBlockIter = this._mapPosToBlock(fromPos); if (fromBlockIter) { const toBlockIter = this._mapPosToBlock(toPos); // process first block const isSameBlock = toBlockIter && fromBlockIter.index === toBlockIter.index; const fromBlockStartPos = fromBlockIter.offset; const fromBlockEndPos = toBlockIter && isSameBlock ? toBlockIter.offset : this._blocks[fromBlockIter.index].displayValue.length; fn(this._blocks[fromBlockIter.index], fromBlockIter.index, fromBlockStartPos, fromBlockEndPos); if (toBlockIter && !isSameBlock) { // process intermediate blocks for (let bi = fromBlockIter.index + 1; bi < toBlockIter.index; ++bi) { fn(this._blocks[bi], bi, 0, this._blocks[bi].displayValue.length); } // process last block fn(this._blocks[toBlockIter.index], toBlockIter.index, 0, toBlockIter.offset); } } } remove(fromPos, toPos) { if (fromPos === void 0) { fromPos = 0; } if (toPos === void 0) { toPos = this.displayValue.length; } const removeDetails = super.remove(fromPos, toPos); this._forEachBlocksInRange(fromPos, toPos, (b, _, bFromPos, bToPos) => { removeDetails.aggregate(b.remove(bFromPos, bToPos)); }); return removeDetails; } nearestInputPos(cursorPos, direction) { if (direction === void 0) { direction = DIRECTION.NONE; } if (!this._blocks.length) return 0; const cursor = new PatternCursor(this, cursorPos); if (direction === DIRECTION.NONE) { // ------------------------------------------------- // NONE should only go out from fixed to the right! // ------------------------------------------------- if (cursor.pushRightBeforeInput()) return cursor.pos; cursor.popState(); if (cursor.pushLeftBeforeInput()) return cursor.pos; return this.displayValue.length; } // FORCE is only about a|* otherwise is 0 if (direction === DIRECTION.LEFT || direction === DIRECTION.FORCE_LEFT) { // try to break fast when *|a if (direction === DIRECTION.LEFT) { cursor.pushRightBeforeFilled(); if (cursor.ok && cursor.pos === cursorPos) return cursorPos; cursor.popState(); } // forward flow cursor.pushLeftBeforeInput(); cursor.pushLeftBeforeRequired(); cursor.pushLeftBeforeFilled(); // backward flow if (direction === DIRECTION.LEFT) { cursor.pushRightBeforeInput(); cursor.pushRightBeforeRequired(); if (cursor.ok && cursor.pos <= cursorPos) return cursor.pos; cursor.popState(); if (cursor.ok && cursor.pos <= cursorPos) return cursor.pos; cursor.popState(); } if (cursor.ok) return cursor.pos; if (direction === DIRECTION.FORCE_LEFT) return 0; cursor.popState(); if (cursor.ok) return cursor.pos; cursor.popState(); if (cursor.ok) return cursor.pos; return 0; } if (direction === DIRECTION.RIGHT || direction === DIRECTION.FORCE_RIGHT) { // forward flow cursor.pushRightBeforeInput(); cursor.pushRightBeforeRequired(); if (cursor.pushRightBeforeFilled()) return cursor.pos; if (direction === DIRECTION.FORCE_RIGHT) return this.displayValue.length; // backward flow cursor.popState(); if (cursor.ok) return cursor.pos; cursor.popState(); if (cursor.ok) return cursor.pos; return this.nearestInputPos(cursorPos, DIRECTION.LEFT); } return cursorPos; } totalInputPositions(fromPos, toPos) { if (fromPos === void 0) { fromPos = 0; } if (toPos === void 0) { toPos = this.displayValue.length; } let total = 0; this._forEachBlocksInRange(fromPos, toPos, (b, _, bFromPos, bToPos) => { total += b.totalInputPositions(bFromPos, bToPos); }); return total; } /** Get block by name */ maskedBlock(name) { return this.maskedBlocks(name)[0]; } /** Get all blocks by name */ maskedBlocks(name) { const indices = this._maskedBlocks[name]; if (!indices) return []; return indices.map(gi => this._blocks[gi]); } pad(flags) { const details = new ChangeDetails(); this._forEachBlocksInRange(0, this.displayValue.length, b => details.aggregate(b.pad(flags))); return details; } } MaskedPattern.DEFAULTS = { ...Masked.DEFAULTS, lazy: true, placeholderChar: '_' }; MaskedPattern.STOP_CHAR = '`'; MaskedPattern.ESCAPE_CHAR = '\\'; MaskedPattern.InputDefinition = PatternInputDefinition; MaskedPattern.FixedDefinition = PatternFixedDefinition; IMask.MaskedPattern = MaskedPattern; export { MaskedPattern as default };