From f3ce934f29e77b15cababe01d1d5a86ed813598f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=B8egh?= Date: Tue, 9 Jan 2024 08:00:26 +0100 Subject: [PATCH] fix(MultiInputMask): enhance handling of right, left and backspace key usage (#3192) --- .../__tests__/MultiInputMask.test.tsx | 100 ++++++++++++------ .../hooks/useHandleCursorPosition.ts | 16 ++- 2 files changed, 76 insertions(+), 40 deletions(-) diff --git a/packages/dnb-eufemia/src/components/input-masked/__tests__/MultiInputMask.test.tsx b/packages/dnb-eufemia/src/components/input-masked/__tests__/MultiInputMask.test.tsx index 9e0c4caa43a..e01a81715c8 100644 --- a/packages/dnb-eufemia/src/components/input-masked/__tests__/MultiInputMask.test.tsx +++ b/packages/dnb-eufemia/src/components/input-masked/__tests__/MultiInputMask.test.tsx @@ -4,7 +4,7 @@ */ import React from 'react' -import { act, render } from '@testing-library/react' +import { fireEvent, render } from '@testing-library/react' import userEvent from '@testing-library/user-event' import MultiInputMask, { MultiInputMaskInput, @@ -54,9 +54,7 @@ describe('MultiInputMask', () => { '.dnb-multi-input-mask__input' )[0] as HTMLInputElement - act(() => { - firstInput.focus() - }) + fireEvent.focus(firstInput) await userEvent.keyboard('08122023') @@ -238,9 +236,7 @@ describe('MultiInputMask', () => { document.querySelectorAll('.dnb-multi-input-mask__input') ) as HTMLInputElement[] - act(() => { - first.focus() - }) + fireEvent.focus(first) await userEvent.keyboard('11223333') @@ -294,10 +290,8 @@ describe('MultiInputMask', () => { document.querySelectorAll('.dnb-multi-input-mask__input') ) as HTMLInputElement[] - act(() => { - first.focus() - first.setSelectionRange(0, 0) - }) + fireEvent.focus(first) + first.setSelectionRange(0, 0) await userEvent.keyboard('fst') @@ -357,10 +351,8 @@ describe('MultiInputMask', () => { document.querySelectorAll('.dnb-multi-input-mask__input') ) as HTMLInputElement[] - act(() => { - first.focus() - first.setSelectionRange(0, 0) - }) + fireEvent.focus(first) + first.setSelectionRange(0, 0) expect(first.selectionStart).toBe(0) expect(first.selectionEnd).toBe(0) @@ -528,10 +520,8 @@ describe('MultiInputMask', () => { document.querySelectorAll('.dnb-multi-input-mask__input') ) as HTMLInputElement[] - act(() => { - first.focus() - first.setSelectionRange(0, 0) - }) + fireEvent.focus(first) + first.setSelectionRange(0, 0) expect(first.selectionStart).toBe(0) expect(first.selectionEnd).toBe(0) @@ -563,9 +553,7 @@ describe('MultiInputMask', () => { document.querySelectorAll('.dnb-multi-input-mask__input') ) as HTMLInputElement[] - act(() => { - first.focus() - }) + fireEvent.focus(first) await userEvent.keyboard('11223333') @@ -595,22 +583,20 @@ describe('MultiInputMask', () => { document.querySelectorAll('.dnb-multi-input-mask__input') ) as HTMLInputElement[] - act(() => { - first.focus() - first.setSelectionRange(0, 0) - }) + fireEvent.focus(first) + first.setSelectionRange(0, 0) expect(first.selectionStart).toBe(0) expect(first.selectionEnd).toBe(0) expect(document.activeElement).toBe(first) - await userEvent.keyboard('{ArrowRight}{ArrowRight}{ArrowRight}') + await userEvent.keyboard('{ArrowRight>3}') expect(second.selectionStart).toBe(0) expect(second.selectionEnd).toBe(0) expect(document.activeElement).toBe(second) - await userEvent.keyboard('{ArrowRight}{ArrowRight}{ArrowRight}') + await userEvent.keyboard('{ArrowRight>3}') expect(third.selectionStart).toBe(0) expect(third.selectionEnd).toBe(0) @@ -622,21 +608,67 @@ describe('MultiInputMask', () => { expect(second.selectionEnd).toBe(2) expect(document.activeElement).toBe(second) - await userEvent.keyboard('{ArrowLeft}{ArrowLeft}{ArrowLeft}') + await userEvent.keyboard('{ArrowLeft>3}') expect(first.selectionStart).toBe(2) expect(first.selectionEnd).toBe(2) expect(document.activeElement).toBe(first) - await userEvent.keyboard( - '{ArrowRight}{ArrowRight}{ArrowRight}{ArrowRight}' - ) + await userEvent.keyboard('{ArrowRight>3}{ArrowRight}') expect(third.selectionStart).toBe(0) expect(third.selectionEnd).toBe(0) expect(document.activeElement).toBe(third) }) + it('should set cursor at the start or end of the input when value is selected', async () => { + render() + + const [first, second, third] = Array.from( + document.querySelectorAll('.dnb-multi-input-mask__input') + ) as HTMLInputElement[] + + // 1. Test the ArrowRight + + fireEvent.focus(first) + + expect(document.activeElement).toBe(first) + expect(first.selectionStart).toBe(0) + expect(first.selectionEnd).toBe(2) + + await userEvent.keyboard('{ArrowRight}') + + expect(document.activeElement).toBe(first) + expect(first.selectionStart).toBe(2) + expect(first.selectionEnd).toBe(2) + + await userEvent.keyboard('{ArrowRight}') + + expect(document.activeElement).toBe(second) + expect(second.selectionStart).toBe(0) + expect(second.selectionEnd).toBe(0) + + // 2. Test the same but with the last input and backspace + + fireEvent.focus(third) + + expect(document.activeElement).toBe(third) + expect(third.selectionStart).toBe(0) + expect(third.selectionEnd).toBe(4) + + await userEvent.keyboard('{Backspace}') + + expect(document.activeElement).toBe(third) + expect(third.selectionStart).toBe(0) + expect(third.selectionEnd).toBe(0) + + await userEvent.keyboard('{ArrowLeft}') + + expect(document.activeElement).toBe(second) + expect(second.selectionStart).toBe(2) + expect(second.selectionEnd).toBe(2) + }) + it('should be able to tab between inputs', async () => { render() @@ -644,9 +676,7 @@ describe('MultiInputMask', () => { document.querySelectorAll('.dnb-multi-input-mask__input') ) as HTMLInputElement[] - act(() => { - first.focus() - }) + fireEvent.focus(first) expect(document.activeElement).toBe(first) diff --git a/packages/dnb-eufemia/src/components/input-masked/hooks/useHandleCursorPosition.ts b/packages/dnb-eufemia/src/components/input-masked/hooks/useHandleCursorPosition.ts index 908dda2afc4..544994e6f68 100644 --- a/packages/dnb-eufemia/src/components/input-masked/hooks/useHandleCursorPosition.ts +++ b/packages/dnb-eufemia/src/components/input-masked/hooks/useHandleCursorPosition.ts @@ -21,17 +21,19 @@ function useHandleCursorPosition( getKeysToHandle({ keysToHandle, input })?.test(pressedKey) || /(ArrowRight|ArrowLeft|Backspace)/.test(pressedKey) - const initialSelectionStart = input.selectionStart + const hasSelection = hasSelectedValue(input) - const inputPosition = getInputPosition(input, inputs) + const inputPosition = !hasSelection && getInputPosition(input, inputs) - window.requestAnimationFrame(() => { - const caretPosition = getCaretPosition(input) + const initialSelectionStart = input.selectionStart - if (!hasPressedKeysToHandle) { + window.requestAnimationFrame(() => { + if (!hasPressedKeysToHandle || hasSelection) { return // stop here } + const caretPosition = getCaretPosition(input) + if ( caretPosition === 'last' && inputPosition !== 'last' && @@ -113,6 +115,10 @@ function getSelectionPositions(input: HTMLInputElement) { return { start: 0, end: Number(input.size) } } +function hasSelectedValue(input: HTMLInputElement) { + return input.selectionEnd > input.selectionStart +} + function getCaretPosition(input: HTMLInputElement) { const { start, end } = getSelectionPositions(input)