Skip to content

Commit

Permalink
fix: dispatch input events when overwriting selection (#623)
Browse files Browse the repository at this point in the history
* wip: clean up fireInputEvent helper

* fix(type): setSelectionRange on elements without selectionRange

* refactor: maxLength

* test: demonstrate #583

* fix: refactor calculateNewValue

* fix: relative imports
  • Loading branch information
ph-fritsche committed Mar 25, 2021
1 parent 5c40248 commit 394d425
Show file tree
Hide file tree
Showing 18 changed files with 320 additions and 262 deletions.
11 changes: 11 additions & 0 deletions src/__tests__/keyboard/plugin/control.ts
Expand Up @@ -46,3 +46,14 @@ test('press [End] in contenteditable', () => {
expect(selection).toHaveProperty('focusNode', element?.firstChild)
expect(selection).toHaveProperty('focusOffset', 10)
})

test('use [Delete] on number input', () => {
const {element} = setup(`<input type="number"/>`)

userEvent.type(
element as HTMLInputElement,
'1e-5[ArrowLeft][Delete]6[ArrowLeft][ArrowLeft][ArrowLeft][Delete][Delete]',
)

expect(element).toHaveValue(16)
})
9 changes: 9 additions & 0 deletions src/__tests__/type.js
Expand Up @@ -1399,3 +1399,12 @@ test('move selection with arrows', () => {
selectionStart: 1,
})
})

test('overwrite selection with same value', () => {
const {element} = setup(`<input value="1"/>`)
element.select()

userEvent.type(element, '11123')

expect(element).toHaveValue('11123')
})
77 changes: 44 additions & 33 deletions src/keyboard/plugins/character.ts
Expand Up @@ -3,12 +3,14 @@
*/

import {fireEvent} from '@testing-library/dom'
import {fireChangeForInputTimeIfValid, fireInputEventIfNeeded} from '../shared'
import {fireChangeForInputTimeIfValid, fireInputEvent} from '../shared'
import {behaviorPlugin} from '../types'
import {
buildTimeValue,
calculateNewValue,
getSpaceUntilMaxLength,
getValue,
isClickableInput,
isContentEditable,
isElementType,
isValidDateValue,
Expand All @@ -19,7 +21,7 @@ export const keypressBehavior: behaviorPlugin[] = [
{
matches: (keyDef, element) =>
keyDef.key?.length === 1 &&
isElementType(element, 'input', {type: 'time'}),
isElementType(element, 'input', {type: 'time', readOnly: false}),
handle: (keyDef, element, options, state) => {
let newEntry = keyDef.key as string

Expand All @@ -38,16 +40,20 @@ export const keypressBehavior: behaviorPlugin[] = [
newEntry,
element as HTMLElement,
)

const {prevValue} = fireInputEventIfNeeded({
newValue,
newSelectionStart,
eventOverrides: {
data: keyDef.key,
inputType: 'insertText',
},
currentElement: () => element,
})
const prevValue = getValue(element)

// this check was provided by fireInputEventIfNeeded
// TODO: verify if it is even needed by this handler
if (prevValue !== newValue) {
fireInputEvent(element as HTMLInputElement, {
newValue,
newSelectionStart,
eventOverrides: {
data: keyDef.key,
inputType: 'insertText',
},
})
}

fireChangeForInputTimeIfValid(
element as HTMLInputElement & {type: 'time'},
Expand All @@ -61,7 +67,7 @@ export const keypressBehavior: behaviorPlugin[] = [
{
matches: (keyDef, element) =>
keyDef.key?.length === 1 &&
isElementType(element, 'input', {type: 'date'}),
isElementType(element, 'input', {type: 'date', readOnly: false}),
handle: (keyDef, element, options, state) => {
let newEntry = keyDef.key as string

Expand All @@ -78,16 +84,20 @@ export const keypressBehavior: behaviorPlugin[] = [
newEntry,
element as HTMLElement,
)

fireInputEventIfNeeded({
newValue,
newSelectionStart,
eventOverrides: {
data: keyDef.key,
inputType: 'insertText',
},
currentElement: () => element,
})
const prevValue = getValue(element)

// this check was provided by fireInputEventIfNeeded
// TODO: verify if it is even needed by this handler
if (prevValue !== newValue) {
fireInputEvent(element as HTMLInputElement, {
newValue,
newSelectionStart,
eventOverrides: {
data: keyDef.key,
inputType: 'insertText',
},
})
}

if (isValidToBeTyped) {
fireEvent.change(element, {
Expand All @@ -101,7 +111,7 @@ export const keypressBehavior: behaviorPlugin[] = [
{
matches: (keyDef, element) =>
keyDef.key?.length === 1 &&
isElementType(element, 'input', {type: 'number'}),
isElementType(element, 'input', {type: 'number', readOnly: false}),
handle: (keyDef, element, options, state) => {
if (!/[\d.\-e]/.test(keyDef.key as string)) {
return
Expand All @@ -116,14 +126,13 @@ export const keypressBehavior: behaviorPlugin[] = [
oldValue,
)

fireInputEventIfNeeded({
fireInputEvent(element as HTMLInputElement, {
newValue,
newSelectionStart,
eventOverrides: {
data: keyDef.key,
inputType: 'insertText',
},
currentElement: () => element,
})

const appliedValue = getValue(element)
Expand All @@ -137,29 +146,32 @@ export const keypressBehavior: behaviorPlugin[] = [
{
matches: (keyDef, element) =>
keyDef.key?.length === 1 &&
(isElementType(element, ['input', 'textarea']) ||
isContentEditable(element)),
((isElementType(element, ['input', 'textarea'], {readOnly: false}) &&
!isClickableInput(element)) ||
isContentEditable(element)) &&
getSpaceUntilMaxLength(element) !== 0,
handle: (keyDef, element) => {
const {newValue, newSelectionStart} = calculateNewValue(
keyDef.key as string,
element as HTMLElement,
)

fireInputEventIfNeeded({
fireInputEvent(element as HTMLElement, {
newValue,
newSelectionStart,
eventOverrides: {
data: keyDef.key,
inputType: 'insertText',
},
currentElement: () => element,
})
},
},
{
matches: (keyDef, element) =>
keyDef.key === 'Enter' &&
(isElementType(element, 'textarea') || isContentEditable(element)),
(isElementType(element, 'textarea', {readOnly: false}) ||
isContentEditable(element)) &&
getSpaceUntilMaxLength(element) !== 0,
handle: (keyDef, element, options, state) => {
const {newValue, newSelectionStart} = calculateNewValue(
'\n',
Expand All @@ -171,13 +183,12 @@ export const keypressBehavior: behaviorPlugin[] = [
? 'insertParagraph'
: 'insertLineBreak'

fireInputEventIfNeeded({
fireInputEvent(element as HTMLElement, {
newValue,
newSelectionStart,
eventOverrides: {
inputType,
},
currentElement: () => element,
})
},
},
Expand Down
27 changes: 20 additions & 7 deletions src/keyboard/plugins/control.ts
Expand Up @@ -5,13 +5,15 @@

import {behaviorPlugin} from '../types'
import {
calculateNewValue,
getValue,
isContentEditable,
isCursorAtEnd,
isEditable,
isElementType,
setSelectionRange,
} from '../../utils'
import {fireInputEventIfNeeded} from '../shared'
import {calculateNewDeleteValue} from './control/calculateNewDeleteValue'
import {carryValue, fireInputEvent} from '../shared'

export const keydownBehavior: behaviorPlugin[] = [
{
Expand All @@ -30,15 +32,26 @@ export const keydownBehavior: behaviorPlugin[] = [
},
},
{
matches: keyDef => keyDef.key === 'Delete',
handle: (keDef, element) => {
fireInputEventIfNeeded({
...calculateNewDeleteValue(element),
matches: (keyDef, element) =>
keyDef.key === 'Delete' && isEditable(element) && !isCursorAtEnd(element),
handle: (keDef, element, options, state) => {
const {newValue, newSelectionStart} = calculateNewValue(
'',
element as HTMLElement,
state.carryValue,
undefined,
'forward',
)

fireInputEvent(element as HTMLElement, {
newValue,
newSelectionStart,
eventOverrides: {
inputType: 'deleteContentForward',
},
currentElement: () => element,
})

carryValue(element, state, newValue)
},
},
]
33 changes: 0 additions & 33 deletions src/keyboard/plugins/control/calculateNewDeleteValue.ts

This file was deleted.

30 changes: 19 additions & 11 deletions src/keyboard/plugins/functional.ts
Expand Up @@ -4,11 +4,16 @@
*/

import {fireEvent} from '@testing-library/dom'
import {getValue, isClickableInput, isElementType} from '../../utils'
import {
calculateNewValue,
isClickableInput,
isCursorAtStart,
isEditable,
isElementType,
} from '../../utils'
import {getKeyEventProps, getMouseEventProps} from '../getEventProps'
import {fireInputEventIfNeeded} from '../shared'
import {carryValue, fireInputEvent} from '../shared'
import {behaviorPlugin} from '../types'
import {calculateNewBackspaceValue} from './functional/calculateBackspaceValue'

const modifierKeys = {
Alt: 'alt',
Expand Down Expand Up @@ -49,25 +54,28 @@ export const keydownBehavior: behaviorPlugin[] = [
},
},
{
matches: keyDef => keyDef.key === 'Backspace',
matches: (keyDef, element) =>
keyDef.key === 'Backspace' &&
isEditable(element) &&
!isCursorAtStart(element),
handle: (keyDef, element, options, state) => {
const {newValue, newSelectionStart} = calculateNewBackspaceValue(
element,
const {newValue, newSelectionStart} = calculateNewValue(
'',
element as HTMLElement,
state.carryValue,
undefined,
'backward',
)

fireInputEventIfNeeded({
fireInputEvent(element as HTMLElement, {
newValue,
newSelectionStart,
eventOverrides: {
inputType: 'deleteContentBackward',
},
currentElement: () => element,
})

if (state.carryValue) {
state.carryValue = getValue(element) === newValue ? undefined : newValue
}
carryValue(element, state, newValue)
},
},
]
Expand Down
43 changes: 0 additions & 43 deletions src/keyboard/plugins/functional/calculateBackspaceValue.ts

This file was deleted.

0 comments on commit 394d425

Please sign in to comment.