Skip to content

Commit

Permalink
fix(core): fix scroll for masked narrow textfields (#1645)
Browse files Browse the repository at this point in the history
  • Loading branch information
nsbarsukov authored Sep 19, 2024
1 parent d7bbf27 commit c6d2828
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 28 deletions.
66 changes: 39 additions & 27 deletions projects/core/src/lib/mask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export class Maskito extends MaskHistory {
...this.maskitoOptions,
};

private upcomingElementState: ElementState | null = null;

private readonly preprocessor = maskitoPipe(this.options.preprocessors);

private readonly postprocessor = maskitoPipe(this.options.postprocessors);
Expand Down Expand Up @@ -132,6 +134,17 @@ export class Maskito extends MaskHistory {
}
});

this.eventListener.listen(
'input',
() => {
if (this.upcomingElementState) {
this.updateElementState(this.upcomingElementState);
this.upcomingElementState = null;
}
},
{capture: true},
);

this.eventListener.listen('input', ({inputType}) => {
if (inputType === 'insertCompositionText') {
return; // will be handled inside `compositionend` event
Expand All @@ -154,17 +167,14 @@ export class Maskito extends MaskHistory {

protected updateElementState(
{value, selection}: ElementState,
eventInit: Pick<TypedInputEvent, 'data' | 'inputType'> = {
inputType: 'insertText',
data: null,
},
eventInit?: Pick<TypedInputEvent, 'data' | 'inputType'>,
): void {
const initialValue = this.elementState.value;

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

if (initialValue !== value) {
if (eventInit && initialValue !== value) {
this.dispatchInputEvent(eventInit);
}
}
Expand Down Expand Up @@ -200,7 +210,10 @@ export class Maskito extends MaskHistory {
}

private ensureValueFitsMask(): void {
this.updateElementState(maskitoTransform(this.elementState, this.options));
this.updateElementState(maskitoTransform(this.elementState, this.options), {
inputType: 'insertText',
data: null,
});
}

private dispatchInputEvent(
Expand Down Expand Up @@ -261,32 +274,28 @@ export class Maskito extends MaskHistory {
return;
}

event.preventDefault();

if (
areElementValuesEqual(initialState, elementState, maskModel, newElementState)
) {
event.preventDefault();

// User presses Backspace/Delete for the fixed value
return this.updateSelectionRange(isForward ? [to, to] : [from, from]);
}

this.updateElementState(newElementState, {
inputType: event.inputType,
data: null,
});
this.updateHistory(newElementState);
this.upcomingElementState = newElementState;
}

private handleInsert(event: TypedInputEvent, data: string): void {
const initialElementState = this.elementState;
const {options, maxLength, element, elementState: initialElementState} = this;
const {elementState, data: insertedText = data} = this.preprocessor(
{
data,
elementState: initialElementState,
},
'insert',
);
const maskModel = new MaskModel(elementState, this.options);
const maskModel = new MaskModel(elementState, options);

try {
maskModel.addCharacters(elementState.selection, insertedText);
Expand All @@ -301,21 +310,24 @@ export class Maskito extends MaskHistory {
initialElementState.value.slice(to);
const newElementState = this.postprocessor(maskModel, initialElementState);

if (newElementState.value.length > this.maxLength) {
if (newElementState.value.length > maxLength) {
return event.preventDefault();
}

if (
newPossibleValue !== newElementState.value ||
this.element.isContentEditable
) {
event.preventDefault();

this.updateElementState(newElementState, {
data,
inputType: event.inputType,
});
this.updateHistory(newElementState);
if (newPossibleValue !== newElementState.value || element.isContentEditable) {
this.upcomingElementState = newElementState;

if (
options.overwriteMode === 'replace' &&
newPossibleValue.length > maxLength
) {
/**
* Browsers know nothing about Maskito and its `overwriteMode`.
* When textfield value length is already equal to attribute `maxlength`,
* pressing any key (even with valid value) does not emit `input` event.
*/
this.dispatchInputEvent({inputType: 'insertText', data});
}
}
}

Expand Down
4 changes: 3 additions & 1 deletion projects/core/src/lib/utils/dom/event-listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ export class EventListener {
const untypedFn = fn as (event: HTMLElementEventMap[E]) => unknown;

this.element.addEventListener<E>(eventType, untypedFn, options);
this.listeners.push(() => this.element.removeEventListener(eventType, untypedFn));
this.listeners.push(() =>
this.element.removeEventListener(eventType, untypedFn, options),
);
}

public destroy(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ describe('DateRange | Basic', () => {
it('Type `deleteSoftLineBackward` of `InputEvent` works', () => {
cy.get('@input')
.trigger('beforeinput', {inputType: 'deleteSoftLineBackward'})
.trigger('input', {inputType: 'deleteSoftLineBackward'})
.should('have.value', '')
.should('have.prop', 'selectionStart', ''.length)
.should('have.prop', 'selectionEnd', ''.length);
Expand All @@ -161,6 +162,7 @@ describe('DateRange | Basic', () => {
cy.get('@input')
.type('{moveToStart}')
.trigger('beforeinput', {inputType: 'deleteSoftLineForward'})
.trigger('input', {inputType: 'deleteSoftLineForward'})
.should('have.value', '')
.should('have.prop', 'selectionStart', ''.length)
.should('have.prop', 'selectionEnd', ''.length);
Expand All @@ -170,6 +172,7 @@ describe('DateRange | Basic', () => {
cy.get('@input')
.type('{leftArrow}'.repeat(' 31.12.2022'.length))
.trigger('beforeinput', {inputType: 'deleteSoftLineBackward'})
.trigger('input', {inputType: 'deleteSoftLineBackward'})
.should('have.value', '01.01.0001 – 31.12.2022')
.should('have.prop', 'selectionStart', ''.length)
.should('have.prop', 'selectionEnd', ''.length);
Expand All @@ -179,6 +182,7 @@ describe('DateRange | Basic', () => {
cy.get('@input')
.type('{leftArrow}'.repeat(' 31.12.2022'.length))
.trigger('beforeinput', {inputType: 'deleteSoftLineForward'})
.trigger('input', {inputType: 'deleteSoftLineForward'})
.should('have.value', '20.01.1990')
.should('have.prop', 'selectionStart', '20.01.1990'.length)
.should('have.prop', 'selectionEnd', '20.01.1990'.length);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ describe('Date', () => {
cy.get('@input')
.type('{moveToStart}')
.trigger('beforeinput', {inputType: 'deleteSoftLineForward'})
.trigger('input', {inputType: 'deleteSoftLineForward'})
.should('have.value', '')
.should('have.prop', 'selectionStart', ''.length)
.should('have.prop', 'selectionEnd', ''.length);
Expand All @@ -135,6 +136,7 @@ describe('Date', () => {
it('Type `deleteSoftLineBackward` of `InputEvent` works', () => {
cy.get('@input')
.trigger('beforeinput', {inputType: 'deleteSoftLineBackward'})
.trigger('input', {inputType: 'deleteSoftLineBackward'})
.should('have.value', '')
.should('have.prop', 'selectionStart', ''.length)
.should('have.prop', 'selectionEnd', ''.length);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ describe('Textarea (mask latin letters + digits)', () => {
.type('{enter}')
.type('UI and Maskito')
.trigger('beforeinput', {inputType: 'deleteSoftLineBackward'})
.trigger('input', {inputType: 'deleteSoftLineBackward'})
.should('have.value', 'Taiga\n');
});

Expand All @@ -58,6 +59,7 @@ describe('Textarea (mask latin letters + digits)', () => {
.type('UI and Maskito')
.type('{moveToStart}')
.trigger('beforeinput', {inputType: 'deleteSoftLineForward'})
.trigger('input', {inputType: 'deleteSoftLineForward'})
.should('have.value', 'UI and Maskito');
});
});
Expand Down

0 comments on commit c6d2828

Please sign in to comment.