Skip to content

Commit

Permalink
Add repeat (#984)
Browse files Browse the repository at this point in the history
add repeat block feature
  • Loading branch information
uNmAnNeR committed Dec 25, 2023
1 parent 4af0274 commit 5a9e8f7
Show file tree
Hide file tree
Showing 9 changed files with 275 additions and 40 deletions.
17 changes: 9 additions & 8 deletions packages/imask/example.html
Expand Up @@ -12,24 +12,25 @@ <h1>IMask Core Demo</h1>
<!-- <script src="https://unpkg.com/imask"></script> -->
<script type="text/javascript">
const opts = {
mask: Date,
autofix: 'pad',
lazy: false,
mask: 'r0',
blocks: {
r: {
mask: 'a',
repeat: [2, 5],
},
},
};

const input = document.getElementById('input');
var result = document.getElementById('value');
var unmasked = document.getElementById('unmasked');
var imask = IMask(input, opts).on('accept', () => {
console.log('accept', imask.value, imask.unmaskedValue, imask.typedValue);
console.log('accept', imask.value, imask.mask);
result.innerHTML = imask.value;
unmasked.innerHTML = imask.unmaskedValue;
});

input.addEventListener('keydown', e => console.log('keydown', e.isComposing));
input.addEventListener('input', e => console.log('input', e.isComposing, e.inputType));
input.addEventListener('compositionstart', e => console.log('compositionstart'));
input.addEventListener('compositionend', e => console.log('compositionend'));

// document.getElementById('input').addEventListener('focus', () => {
// imask.updateOptions({
// mask: mask.map((m) => ({
Expand Down
2 changes: 2 additions & 0 deletions packages/imask/src/core/holder.ts
@@ -1,6 +1,7 @@
import type { default as _InputMask, InputMaskElement as _InputMaskElement } from '../controls/input';
import type { default as _Masked } from '../masked/base';
import type { default as _MaskedPattern } from '../masked/pattern';
import type { default as _RepeatBlock } from '../masked/repeat';
import type { default as _MaskedDate } from '../masked/date';
import type { default as _MaskedDynamic } from '../masked/dynamic';
import type { default as _MaskedEnum } from '../masked/enum';
Expand Down Expand Up @@ -36,6 +37,7 @@ declare namespace IMask {
export let createMask: typeof _createMask;
export let Masked: typeof _Masked;
export let MaskedPattern: typeof _MaskedPattern;
export let RepeatBlock: typeof _RepeatBlock;
export let MaskedDate: typeof _MaskedDate;
export let MaskedDynamic: typeof _MaskedDynamic;
export let MaskedEnum: typeof _MaskedEnum;
Expand Down
1 change: 1 addition & 0 deletions packages/imask/src/index.ts
Expand Up @@ -37,6 +37,7 @@ export { default as PatternInputDefinition, type PatternInputDefinitionOptions,
export { createPipe, pipe, PIPE_TYPE } from './masked/pipe';
export { default as MaskedRange, type MaskedRangeOptions } from './masked/range';
export { default as MaskedRegExp, type MaskedRegExpOptions } from './masked/regexp';
export { default as RepeatBlock, type RepeatBlockOptions } from './masked/repeat';

try { (globalThis as any).IMask = IMask; } catch {}
export default IMask;
2 changes: 1 addition & 1 deletion packages/imask/src/masked/number.ts
Expand Up @@ -307,7 +307,7 @@ class MaskedNumber extends Masked<number> {
return parts.join(this.radix);
}

doSkipInvalid (ch: string, flags: AppendFlags={}, checkTail?: TailDetails): boolean {
override doSkipInvalid (ch: string, flags: AppendFlags={}, checkTail?: TailDetails): boolean {
const dropFractional = this.scale === 0 && ch !== this.thousandsSeparator && (
ch === this.radix ||
ch === MaskedNumber.UNMASKED_RADIX ||
Expand Down
69 changes: 39 additions & 30 deletions packages/imask/src/masked/pattern.ts
Expand Up @@ -42,6 +42,12 @@ type BlockPosData = {
offset: number,
};

export
type BlockExtraOptions = {
expose?: boolean,
repeat?: number | [number, number],
};


/** Pattern mask */
export default
Expand All @@ -57,7 +63,7 @@ class MaskedPattern<Value=string> extends Masked<Value> {

declare mask: string;
/** */
declare blocks: { [key: string]: ExtendFactoryArgOptions<{ expose?: boolean }> };
declare blocks: { [key: string]: ExtendFactoryArgOptions<BlockExtraOptions> };
/** */
declare definitions: Definitions;
/** Single char for empty input */
Expand Down Expand Up @@ -117,16 +123,18 @@ class MaskedPattern<Value=string> extends Masked<Value> {
// use block name with max length
const bName = bNames[0];
if (bName) {
const { expose, ...blockOpts } = normalizeOpts(this.blocks[bName]) as NormalizedOpts<FactoryArg> & { expose?: boolean };
const maskedBlock = createMask({
const { expose, repeat, ...bOpts } = normalizeOpts(this.blocks[bName]) as NormalizedOpts<FactoryArg> & BlockExtraOptions; // TODO type Opts<Arg & Extra>
const blockOpts = {
lazy: this.lazy,
eager: this.eager,
placeholderChar: this.placeholderChar,
displayChar: this.displayChar,
overwrite: this.overwrite,
...blockOpts,
...bOpts,
repeat,
parent: this,
});
};
const maskedBlock = repeat != null ? new IMask.RepeatBlock(blockOpts as any /* TODO */) : createMask(blockOpts);

if (maskedBlock) {
this._blocks.push(maskedBlock);
Expand Down Expand Up @@ -187,30 +195,32 @@ class MaskedPattern<Value=string> extends Masked<Value> {
}
}

get state (): MaskedPatternState {
override get state (): MaskedPatternState {
return {
...super.state,
_blocks: this._blocks.map(b => b.state),
};
}

set state (state: MaskedPatternState) {
const {_blocks, ...maskedState} = state;
override set state (state: MaskedPatternState) {
if (!state) { this.reset(); return; }

const { _blocks, ...maskedState } = state;
this._blocks.forEach((b, bi) => b.state = _blocks[bi]);
super.state = maskedState;
}

reset () {
override reset () {
super.reset();
this._blocks.forEach(b => b.reset());
}

get isComplete (): boolean {
override get isComplete (): boolean {
return this.exposeBlock ? this.exposeBlock.isComplete :
this._blocks.every(b => b.isComplete);
}

get isFilled (): boolean {
override get isFilled (): boolean {
return this._blocks.every(b => b.isFilled);
}

Expand All @@ -222,17 +232,17 @@ class MaskedPattern<Value=string> extends Masked<Value> {
return this._blocks.every(b => b.isOptional);
}

doCommit () {
override doCommit () {
this._blocks.forEach(b => b.doCommit());
super.doCommit();
}

get unmaskedValue (): string {
override get unmaskedValue (): string {
return this.exposeBlock ? this.exposeBlock.unmaskedValue :
this._blocks.reduce((str, b) => str += b.unmaskedValue, '');
}

set unmaskedValue (unmaskedValue: string) {
override set unmaskedValue (unmaskedValue: string) {
if (this.exposeBlock) {
const tail = this.extractTail(this._blockStartPos(this._blocks.indexOf(this.exposeBlock)) + this.exposeBlock.displayValue.length);
this.exposeBlock.unmaskedValue = unmaskedValue;
Expand All @@ -242,13 +252,13 @@ class MaskedPattern<Value=string> extends Masked<Value> {
else super.unmaskedValue = unmaskedValue;
}

get value (): string {
override get value (): string {
return this.exposeBlock ? this.exposeBlock.value :
// TODO return _value when not in change?
this._blocks.reduce((str, b) => str += b.value, '');
}

set value (value: string) {
override set value (value: string) {
if (this.exposeBlock) {
const tail = this.extractTail(this._blockStartPos(this._blocks.indexOf(this.exposeBlock)) + this.exposeBlock.displayValue.length);
this.exposeBlock.value = value;
Expand All @@ -258,12 +268,12 @@ class MaskedPattern<Value=string> extends Masked<Value> {
else super.value = value;
}

get typedValue (): Value {
override get typedValue (): Value {
return this.exposeBlock ? this.exposeBlock.typedValue :
super.typedValue;
}

set typedValue (value: Value) {
override set typedValue (value: Value) {
if (this.exposeBlock) {
const tail = this.extractTail(this._blockStartPos(this._blocks.indexOf(this.exposeBlock)) + this.exposeBlock.displayValue.length);
this.exposeBlock.typedValue = value;
Expand All @@ -273,15 +283,15 @@ class MaskedPattern<Value=string> extends Masked<Value> {
else super.typedValue = value;
}

get displayValue (): string {
override get displayValue (): string {
return this._blocks.reduce((str, b) => str += b.displayValue, '');
}

appendTail (tail: string | String | TailDetails): ChangeDetails {
override appendTail (tail: string | String | TailDetails): ChangeDetails {
return super.appendTail(tail).aggregate(this._appendPlaceholder());
}

_appendEager (): ChangeDetails {
override _appendEager (): ChangeDetails {
const details = new ChangeDetails();

let startBlockIndex = this._mapPosToBlock(this.displayValue.length)?.index;
Expand All @@ -300,24 +310,23 @@ class MaskedPattern<Value=string> extends Masked<Value> {
return details;
}

_appendCharRaw (ch: string, flags: AppendFlags<MaskedPatternState>={}): ChangeDetails {
override _appendCharRaw (ch: string, flags: AppendFlags<MaskedPatternState>={}): ChangeDetails {
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) {
const blockDetails = block._appendChar(ch, { ...flags, _beforeTailState: flags._beforeTailState?._blocks?.[bi] });

const skip = blockDetails.skip;
details.aggregate(blockDetails);

if (skip || blockDetails.rawInserted) break; // go next char
if (blockDetails.skip || blockDetails.rawInserted) break; // go next char
}

return details;
}

extractTail (fromPos: number=0, toPos: number=this.displayValue.length): ChunksTailDetails {
override extractTail (fromPos: number=0, toPos: number=this.displayValue.length): ChunksTailDetails {
const chunkTail = new ChunksTailDetails();
if (fromPos === toPos) return chunkTail;

Expand All @@ -333,7 +342,7 @@ class MaskedPattern<Value=string> extends Masked<Value> {
return chunkTail;
}

extractInput (fromPos: number=0, toPos: number=this.displayValue.length, flags: ExtractFlags={}): string {
override extractInput (fromPos: number=0, toPos: number=this.displayValue.length, flags: ExtractFlags={}): string {
if (fromPos === toPos) return '';

let input = '';
Expand All @@ -356,7 +365,7 @@ class MaskedPattern<Value=string> extends Masked<Value> {
}

/** Appends placeholder depending on laziness */
_appendPlaceholder (toBlockIndex?: number): ChangeDetails {
override _appendPlaceholder (toBlockIndex?: number): ChangeDetails {
const details = new ChangeDetails();
if (this.lazy && toBlockIndex == null) return details;

Expand Down Expand Up @@ -427,15 +436,15 @@ class MaskedPattern<Value=string> extends Masked<Value> {
}
}

remove (fromPos: number=0, toPos: number=this.displayValue.length): ChangeDetails {
override remove (fromPos: number=0, toPos: number=this.displayValue.length): ChangeDetails {
const removeDetails = super.remove(fromPos, toPos);
this._forEachBlocksInRange(fromPos, toPos, (b, _, bFromPos, bToPos) => {
removeDetails.aggregate(b.remove(bFromPos, bToPos));
});
return removeDetails;
}

nearestInputPos (cursorPos: number, direction: Direction=DIRECTION.NONE): number {
override nearestInputPos (cursorPos: number, direction: Direction=DIRECTION.NONE): number {
if (!this._blocks.length) return 0;
const cursor = new PatternCursor(this, cursorPos);

Expand Down Expand Up @@ -505,7 +514,7 @@ class MaskedPattern<Value=string> extends Masked<Value> {
return cursorPos;
}

totalInputPositions (fromPos: number=0, toPos: number=this.displayValue.length): number {
override totalInputPositions (fromPos: number=0, toPos: number=this.displayValue.length): number {
let total = 0;
this._forEachBlocksInRange(fromPos, toPos, (b, _, bFromPos, bToPos) => {
total += b.totalInputPositions(bFromPos, bToPos);
Expand Down

0 comments on commit 5a9e8f7

Please sign in to comment.