From 9a138c98a1ec65d57f593f8f091f1cf3768db716 Mon Sep 17 00:00:00 2001 From: Nikita Barsukov Date: Mon, 17 Jun 2024 18:29:42 +0300 Subject: [PATCH] fix(kit): move caret after attempt to erase fixed character in a mask with `Placeholder` --- .../tests/recipes/placeholder/us-phone.cy.ts | 53 +++++++++++++++++++ .../src/lib/processors/with-placeholder.ts | 30 ++++++++--- 2 files changed, 75 insertions(+), 8 deletions(-) diff --git a/projects/demo-integrations/src/tests/recipes/placeholder/us-phone.cy.ts b/projects/demo-integrations/src/tests/recipes/placeholder/us-phone.cy.ts index c90ebc3ca..2a355527a 100644 --- a/projects/demo-integrations/src/tests/recipes/placeholder/us-phone.cy.ts +++ b/projects/demo-integrations/src/tests/recipes/placeholder/us-phone.cy.ts @@ -79,4 +79,57 @@ describe('Placeholder | US phone', () => { .blur() .should('have.value', ''); }); + + describe('caret navigation on attempt to erase fixed character', () => { + beforeEach(() => { + cy.get('@input') + .type('2125552') + .should('have.value', '+1 (212) 555-2___') + .should('have.prop', 'selectionStart', '+1 (212) 555-2'.length) + .should('have.prop', 'selectionEnd', '+1 (212) 555-2'.length); + }); + + it('+1 (212) 555-|2___ => Backspace => +1 (212) 555|-2___', () => { + cy.get('@input') + .type('{leftArrow}{backspace}') + .should('have.value', '+1 (212) 555-2___') + .should('have.prop', 'selectionStart', '+1 (212) 555'.length) + .should('have.prop', 'selectionEnd', '+1 (212) 555'.length); + }); + + it('+1 (212) 555|-2___ => Delete => +1 (212) 555-|2___', () => { + cy.get('@input') + .type('{leftArrow}'.repeat(2)) + .should('have.prop', 'selectionStart', '+1 (212) 555'.length) + .should('have.prop', 'selectionEnd', '+1 (212) 555'.length) + .type('{del}') + .should('have.value', '+1 (212) 555-2___') + .should('have.prop', 'selectionStart', '+1 (212) 555-'.length) + .should('have.prop', 'selectionEnd', '+1 (212) 555-'.length); + }); + + it('+1 (212) |555-2___ => Backspace x2 => +1 (212|) 555-2___', () => { + cy.get('@input') + .type('{leftArrow}'.repeat('555-2'.length)) + .type('{backspace}') + .should('have.value', '+1 (212) 555-2___') + .should('have.prop', 'selectionStart', '+1 (212)'.length) + .should('have.prop', 'selectionEnd', '+1 (212)'.length) + .type('{backspace}') + .should('have.value', '+1 (212) 555-2___') + .should('have.prop', 'selectionStart', '+1 (212'.length) + .should('have.prop', 'selectionEnd', '+1 (212'.length); + }); + + it('+1 (212|) 555-2___ => Delete => +1 (212) |555-2___', () => { + cy.get('@input') + .type('{leftArrow}'.repeat(') 555-2'.length)) + .should('have.prop', 'selectionStart', '+1 (212'.length) + .should('have.prop', 'selectionEnd', '+1 (212'.length) + .type('{del}') + .should('have.value', '+1 (212) 555-2___') + .should('have.prop', 'selectionStart', '+1 (212) '.length) + .should('have.prop', 'selectionEnd', '+1 (212) '.length); + }); + }); }); diff --git a/projects/kit/src/lib/processors/with-placeholder.ts b/projects/kit/src/lib/processors/with-placeholder.ts index 2c2dd6845..8829420ab 100644 --- a/projects/kit/src/lib/processors/with-placeholder.ts +++ b/projects/kit/src/lib/processors/with-placeholder.ts @@ -1,4 +1,4 @@ -import type {MaskitoOptions} from '@maskito/core'; +import type {MaskitoOptions, MaskitoPreprocessor} from '@maskito/core'; import {maskitoUpdateElement} from '@maskito/core'; import {maskitoCaretGuard, maskitoEventHandler} from '../plugins'; @@ -10,6 +10,7 @@ export function maskitoWithPlaceholder( removePlaceholder: (value: string) => string; } { let lastClearValue = ''; + let action: Parameters[1] = 'validation'; const removePlaceholder = (value: string): string => { for (let i = value.length - 1; i >= lastClearValue.length; i--) { if (value[i] !== placeholder[i]) { @@ -52,7 +53,8 @@ export function maskitoWithPlaceholder( plugins, removePlaceholder, preprocessors: [ - ({elementState, data}) => { + ({elementState, data}, actionType) => { + action = actionType; const {value, selection} = elementState; return { @@ -76,12 +78,24 @@ export function maskitoWithPlaceholder( * For example, developer wants to remove manually placeholder (+ do something else with value) on blur. * Without this condition, placeholder will be unexpectedly added again. */ - return value !== initialElementState.value && (focused || !focusedOnly) - ? { - value: value + placeholder.slice(value.length), - selection, - } - : {value, selection}; + const newValue = + value !== initialElementState.value && (focused || !focusedOnly) + ? value + placeholder.slice(value.length) + : value; + + if ( + newValue === initialElementState.value && + action === 'deleteBackward' + ) { + const [caretIndex] = initialElementState.selection; + + return { + value: newValue, + selection: [caretIndex, caretIndex], + }; + } + + return {value: newValue, selection}; }, ], };