Skip to content

Commit

Permalink
feat(core): add Delete-key support (#20)
Browse files Browse the repository at this point in the history
* feat(core): add `Delete`-key support

* feat(core): new type `Range`

* chore: replace `direction` with `isForward`

* refactor: rename `Range` => `SelectionRange`
  • Loading branch information
nsbarsukov authored Nov 23, 2022
1 parent bfdcd54 commit e62cfda
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 34 deletions.
19 changes: 5 additions & 14 deletions projects/core/src/lib/classes/mask-model/mask-model.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {ElementState, MaskExpression, MaskitoOptions} from '../../types';
import {ElementState, MaskExpression, MaskitoOptions, SelectionRange} from '../../types';
import {validateValueWithMask} from './utils/validate-value-with-mask';
import {removeFixedMaskCharacters} from './utils/remove-fixed-mask-characters';
import {addFixedMaskCharacters} from './utils/add-fixed-mask-characters';

export class MaskModel {
value = '';
selection: [from: number, to: number] = [0, 0];
selection: SelectionRange = [0, 0];

private get maskExpression(): MaskExpression {
const {mask} = this.maskOptions;
Expand All @@ -26,7 +26,7 @@ export class MaskModel {
this.selection = selection;
}

addCharacters(selection: [from: number, to: number], newCharacters: string): void {
addCharacters(selection: SelectionRange, newCharacters: string): void {
const unmaskedElementState = removeFixedMaskCharacters(
{value: this.value, selection},
this.maskExpression,
Expand Down Expand Up @@ -54,7 +54,7 @@ export class MaskModel {
this.selection = maskedElementState.selection;
}

removeCharacters([from, to]: [from: number, to: number]): void {
deleteCharacters([from, to]: SelectionRange): void {
if (from === 0 && to === 0) {
return;
}
Expand All @@ -68,17 +68,8 @@ export class MaskModel {
this.maskExpression,
);
const [unmaskedFrom, unmaskedTo] = unmaskedElementState.selection;
const isOnlyFixedChars = unmaskedFrom === unmaskedTo;
const isLastChar = unmaskedTo >= unmaskedElementState.value.length;

if (isOnlyFixedChars && !isLastChar) {
this.selection = [from, from];
return;
}

const newFrom = isOnlyFixedChars && isLastChar ? unmaskedFrom - 1 : unmaskedFrom;
const newUnmaskedValue =
unmaskedElementState.value.slice(0, newFrom) +
unmaskedElementState.value.slice(0, unmaskedFrom) +
unmaskedElementState.value.slice(unmaskedTo);

const maskedElementState = addFixedMaskCharacters(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ElementState, MaskExpression} from '../../../types';
import {ElementState, MaskExpression, SelectionRange} from '../../../types';
import {isFixedCharacter} from './is-fixed-character';

export function removeFixedMaskCharacters(
Expand Down Expand Up @@ -37,6 +37,6 @@ export function removeFixedMaskCharacters(

return {
value: unmaskedValue,
selection: selection as [number, number],
selection: selection as SelectionRange,
};
}
48 changes: 32 additions & 16 deletions projects/core/src/lib/mask.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {EventListener, isEventProducingCharacter} from './utils';
import {ElementState, MaskitoOptions} from './types';
import {ElementState, MaskitoOptions, SelectionRange} from './types';
import {MaskModel} from './classes';

export class Maskito {
Expand Down Expand Up @@ -40,9 +40,8 @@ export class Maskito {
private handleKeydown(event: KeyboardEvent): void {
const pressedKey = event.key;

if (pressedKey === 'Backspace') {
// TODO: Windows OS has also wonderful button "Delete" which removes characters to the right of the caret.
return this.handleBackspace(event);
if (pressedKey === 'Backspace' || pressedKey === 'Delete') {
return this.handleDelete(event, pressedKey === 'Delete');
}

/**
Expand Down Expand Up @@ -73,32 +72,34 @@ export class Maskito {
this.updateSelectionRange(maskModel.selection);
}

private handleBackspace(event: KeyboardEvent): void {
private handleDelete(event: KeyboardEvent, isForward: boolean): void {
const {elementState, options} = this;

if (!Array.isArray(options.mask)) {
return;
}

const [selectionStart, selectionEnd] = elementState.selection;
const [from, to]: [number, number] =
selectionStart === selectionEnd
? [selectionStart - 1, selectionEnd]
: [selectionStart, selectionEnd];
const [from, to] = extendToNotEmptyRange(elementState.selection, isForward);
const maskModel = new MaskModel(elementState, options);

maskModel.removeCharacters([from, to]);
maskModel.deleteCharacters([from, to]);

const {value, selection} = maskModel;
const newPossibleValue =
elementState.value.slice(0, from) + elementState.value.slice(to);

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

this.updateValue(value);
this.updateSelectionRange(selection);
event.preventDefault();

if (value === elementState.value) {
return this.updateSelectionRange(isForward ? [to, to] : [from, from]);
}

this.updateValue(value);
this.updateSelectionRange(selection);
}

private handlePaste(event: ClipboardEvent): void {
Expand Down Expand Up @@ -133,9 +134,24 @@ export class Maskito {
}
}

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

function extendToNotEmptyRange(
[from, to]: SelectionRange,
isForward: boolean,
): SelectionRange {
if (from !== to) {
return [from, to];
}

if (isForward) {
return [from, to + 1];
}

return [from - 1, to];
}
4 changes: 3 additions & 1 deletion projects/core/src/lib/types/element-state.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {SelectionRange} from './selection-range';

export interface ElementState {
value: string;
selection: [from: number, to: number];
selection: SelectionRange;
}
1 change: 1 addition & 0 deletions projects/core/src/lib/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './element-state';
export * from './mask-options';
export * from './selection-range';
1 change: 1 addition & 0 deletions projects/core/src/lib/types/selection-range.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type SelectionRange = [from: number, to: number];
Loading

0 comments on commit e62cfda

Please sign in to comment.