Skip to content

Commit

Permalink
fix(core): incorrect order of actions during update of native element (
Browse files Browse the repository at this point in the history
  • Loading branch information
nsbarsukov authored Mar 23, 2023
1 parent a996ac4 commit 394d5d9
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 43 deletions.
11 changes: 4 additions & 7 deletions projects/core/src/lib/classes/mask-history.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import type {ElementState, SelectionRange, TypedInputEvent} from '../types';
import type {ElementState, TypedInputEvent} from '../types';

export abstract class MaskHistory {
private now: ElementState | null = null;
private readonly past: ElementState[] = [];
private future: ElementState[] = [];

protected abstract updateValue(
value: string,
protected abstract updateElementState(
state: ElementState,
eventInit: Pick<TypedInputEvent, 'data' | 'inputType'>,
): void;

protected abstract updateSelectionRange(selection: SelectionRange): void;

protected undo(): void {
const state = this.past.pop();

Expand Down Expand Up @@ -59,7 +57,6 @@ export abstract class MaskHistory {
inputType: TypedInputEvent['inputType'],
): void {
this.now = state;
this.updateValue(state.value, {inputType, data: null});
this.updateSelectionRange(state.selection);
this.updateElementState(state, {inputType, data: null});
}
}
85 changes: 49 additions & 36 deletions projects/core/src/lib/mask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class Maskito extends MaskHistory {
private readonly maskitoOptions: MaskitoOptions,
) {
super();
this.conformValueToMask();
this.ensureValueFitsMask();
this.updateHistory(this.elementState);

this.eventListener.listen('keydown', event => {
Expand Down Expand Up @@ -113,7 +113,7 @@ export class Maskito extends MaskHistory {
}

this.eventListener.listen('input', () => {
this.conformValueToMask();
this.ensureValueFitsMask();
this.updateHistory(this.elementState);
});
}
Expand All @@ -135,35 +135,20 @@ export class Maskito extends MaskHistory {
this.eventListener.destroy();
}

protected updateSelectionRange([from, to]: SelectionRange): void {
if (this.element.selectionStart !== from || this.element.selectionEnd !== to) {
this.element.setSelectionRange?.(from, to);
}
}

protected updateValue(
newValue: string,
protected updateElementState(
{value, selection}: ElementState,
eventInit: Pick<TypedInputEvent, 'data' | 'inputType'> = {
inputType: 'insertText',
data: null,
},
): void {
if (this.element.value !== newValue) {
const globalObject = typeof window !== 'undefined' ? window : globalThis;
const initialValue = this.elementState.value;

this.element.value = newValue;
this.updateValue(value);
this.updateSelectionRange(selection);

// TODO: replace `globalObject` with `globalThis` after bumping Firefox to 65+
// @see https://caniuse.com/?search=globalThis
if (globalObject?.InputEvent) {
this.element.dispatchEvent(
new InputEvent('input', {
...eventInit,
bubbles: true,
cancelable: true,
}),
);
}
if (initialValue !== value) {
this.dispatchInputEvent(eventInit);
}
}

Expand All @@ -190,11 +175,8 @@ export class Maskito extends MaskHistory {
this.handleInsert(event, pressedKey);
}

private conformValueToMask(): void {
const {value, selection} = maskitoTransform(this.elementState, this.options);

this.updateValue(value);
this.updateSelectionRange(selection);
private ensureValueFitsMask(): void {
this.updateElementState(maskitoTransform(this.elementState, this.options));
}

private handleDelete({
Expand Down Expand Up @@ -248,11 +230,10 @@ export class Maskito extends MaskHistory {
? 'deleteContentForward'
: 'deleteContentBackward';

this.updateValue(newElementState.value, {
this.updateElementState(newElementState, {
inputType: 'inputType' in event ? event.inputType : inputTypeFallback,
data: null,
});
this.updateSelectionRange(newElementState.selection);
this.updateHistory(newElementState);
}

Expand All @@ -276,20 +257,19 @@ export class Maskito extends MaskHistory {
const [from, to] = elementState.selection;
const newPossibleValue =
elementState.value.slice(0, from) + data + elementState.value.slice(to);
const {value, selection} = this.options.postprocessor(
const newElementState = this.options.postprocessor(
maskModel,
initialElementState,
);

if (newPossibleValue !== value) {
if (newPossibleValue !== newElementState.value) {
event.preventDefault();

this.updateValue(value, {
this.updateElementState(newElementState, {
data,
inputType: 'inputType' in event ? event.inputType : 'insertText',
});
this.updateSelectionRange(selection);
this.updateHistory({value, selection});
this.updateHistory(newElementState);
}
}

Expand All @@ -298,4 +278,37 @@ export class Maskito extends MaskHistory {
this.handleInsert(event, '\n');
}
}

private updateSelectionRange([from, to]: SelectionRange): void {
if (this.element.selectionStart !== from || this.element.selectionEnd !== to) {
this.element.setSelectionRange?.(from, to);
}
}

private updateValue(newValue: string): void {
if (this.element.value !== newValue) {
this.element.value = newValue;
}
}

private dispatchInputEvent(
eventInit: Pick<TypedInputEvent, 'data' | 'inputType'> = {
inputType: 'insertText',
data: null,
},
): void {
const globalObject = typeof window !== 'undefined' ? window : globalThis;

// TODO: replace `globalObject` with `globalThis` after bumping Firefox to 65+
// @see https://caniuse.com/?search=globalThis
if (globalObject?.InputEvent) {
this.element.dispatchEvent(
new InputEvent('input', {
...eventInit,
bubbles: true,
cancelable: false,
}),
);
}
}
}

0 comments on commit 394d5d9

Please sign in to comment.