feat(input): mask deletion

This commit is contained in:
Sean Perkins
2023-05-09 22:46:04 -04:00
parent 2dc4ae57dc
commit a29fd7ef90
4 changed files with 142 additions and 7 deletions

View File

@@ -2,7 +2,7 @@ import { MaskHistory, MaskModel } from './classes';
import { MASK_DEFAULT_OPTIONS } from './constants';
import { isBeforeInputEventSupported, isEventProducingCharacter, EventListener } from './dom';
import type { ElementState, MaskOptions, SelectionRange, TypedInputEvent } from './types/mask-interface';
import { getNotEmptySelection } from './utils';
import { areElementValuesEqual, getLineSelection, getNotEmptySelection, getWordSelection } from './utils';
import { maskTransform } from './utils/transform';
/**
@@ -33,7 +33,37 @@ export class MaskController extends MaskHistory {
if (isBeforeInputEventSupported(element)) {
this.eventListener.listen('beforeinput', (event) => {
switch (event.type) {
const isForward = event.inputType.includes('Forward');
this.updateHistory(this.elementState);
switch (event.inputType) {
case 'deleteByCut':
case 'deleteContentBackward':
case 'deleteContentForward':
return this.handleDelete({
event,
isForward,
selection: getNotEmptySelection(this.elementState, isForward),
});
case 'deleteWordForward':
case 'deleteWordBackward':
return this.handleDelete({
event,
isForward,
selection: getWordSelection(this.elementState, isForward),
force: true,
});
case 'deleteSoftLineBackward':
case 'deleteSoftLineForward':
case 'deleteHardLineBackward':
case 'deleteHardLineForward':
return this.handleDelete({
event,
isForward,
selection: getLineSelection(this.elementState, isForward),
force: true,
});
case 'insertText':
default:
return this.handleInsert(event, event.data || '');
@@ -124,13 +154,49 @@ export class MaskController extends MaskHistory {
isForward: boolean;
force?: boolean;
}): void {
// TODO implementation
console.debug('handleDelete', {
event,
const initialState: ElementState = {
value: this.elementState.value,
selection,
isForward,
force,
};
const [initialFrom, initialTo] = initialState.selection;
const { elementState } = this.options.preprocessor(
{
elementState: initialState,
data: '',
},
isForward ? 'deleteForward' : 'deleteBackward',
);
const maskModel = new MaskModel(elementState, this.options);
const [from, to] = elementState.selection;
maskModel.deleteCharacters([from, to]);
const newElementState = this.options.postprocessor(maskModel, initialState);
const newPossibleValue =
initialState.value.slice(0, initialFrom) +
initialState.value.slice(initialTo);
if (newPossibleValue === newElementState.value && !force) {
return;
}
event.preventDefault();
if (areElementValuesEqual(initialState, elementState, maskModel, newElementState)) {
// User presses Backspace/Delete for the fixed value
return this.updateSelectionRange(isForward ? [to, to] : [from, from]);
}
// TODO: drop it when `event: Event | TypedInputEvent` => `event: TypedInputEvent`
const inputTypeFallback = isForward
? 'deleteContentForward'
: 'deleteContentBackward';
this.updateElementState(newElementState, {
inputType: 'inputType' in event ? event.inputType : inputTypeFallback,
data: null,
});
this.updateHistory(newElementState);
}
private handleInsert(event: Event | TypedInputEvent, data: string): void {

View File

@@ -0,0 +1,21 @@
import type { ElementState, SelectionRange } from "../types/mask-interface";
export function getLineSelection(
{ value, selection }: ElementState,
isForward: boolean,
): SelectionRange {
const [from, to] = selection;
if (from !== to) {
return [from, to];
}
const nearestBreak = isForward
? value.slice(from).indexOf('\n') + 1 || value.length
: value.slice(0, to).lastIndexOf('\n') + 1;
const selectFrom = isForward ? from : nearestBreak;
const selectTo = isForward ? nearestBreak : to;
return [selectFrom, selectTo];
}

View File

@@ -0,0 +1,46 @@
import type { ElementState, SelectionRange } from "../types/mask-interface";
const TRAILING_SPACES_REG = /\s+$/g;
const LEADING_SPACES_REG = /^\s+/g;
const SPACE_REG = /\s/;
export function getWordSelection(
{ value, selection }: ElementState,
isForward: boolean,
): SelectionRange {
const [from, to] = selection;
if (from !== to) {
return [from, to];
}
if (isForward) {
const valueAfterSelectionStart = value.slice(from);
const [leadingSpaces] = valueAfterSelectionStart.match(LEADING_SPACES_REG) || [
'',
];
const nearestWordEndIndex = valueAfterSelectionStart
.trimStart()
.search(SPACE_REG);
return [
from,
nearestWordEndIndex !== -1
? from + leadingSpaces.length + nearestWordEndIndex
: value.length,
];
}
const valueBeforeSelectionEnd = value.slice(0, to);
const [trailingSpaces] = valueBeforeSelectionEnd.match(TRAILING_SPACES_REG) || [''];
const selectedWordLength = valueBeforeSelectionEnd
.trimEnd()
.split('')
.reverse()
.findIndex(char => char.match(SPACE_REG));
return [
selectedWordLength !== -1 ? to - trailingSpaces.length - selectedWordLength : 0,
to,
];
}

View File

@@ -2,3 +2,5 @@ export * from './get-not-empty-selection';
export * from './identity';
export * from './element-states-equality';
export * from './format-mask';
export * from './get-word-selection';
export * from './get-line-selection';