diff --git a/src/event/behavior/keydown.ts b/src/event/behavior/keydown.ts index e0d5b9be..140a222f 100644 --- a/src/event/behavior/keydown.ts +++ b/src/event/behavior/keydown.ts @@ -28,6 +28,10 @@ const keydownBehavior: { if (isElementType(target, 'input', {type: 'radio'} as const)) { return () => walkRadio(instance, target, 1) } + + if (isElementType(target, 'input', {type: 'number'} as const)) { + return () => input(instance, target, 'ArrowDown', 'changeNumberInput') + } }, ArrowLeft: (event, target, instance) => { if (isElementType(target, 'input', {type: 'radio'} as const)) { @@ -46,6 +50,10 @@ const keydownBehavior: { if (isElementType(target, 'input', {type: 'radio'} as const)) { return () => walkRadio(instance, target, -1) } + + if (isElementType(target, 'input', {type: 'number'} as const)) { + return () => input(instance, target, 'ArrowUp', 'changeNumberInput') + } }, Backspace: (event, target, instance) => { if (isEditable(target)) { diff --git a/src/event/input.ts b/src/event/input.ts index 23fbe264..aa946e80 100644 --- a/src/event/input.ts +++ b/src/event/input.ts @@ -11,6 +11,8 @@ import { EditableInputOrTextarea, getMaxLength, getNextCursorPosition, + isDisabled, + isEditable, isElementType, isValidDateOrTimeValue, supportsMaxLength, @@ -220,6 +222,35 @@ function calculateNewValue( } } + if ( + isElementType(node, 'input', {type: 'number'} as const) && + inputType === 'changeNumberInput' && + !isDisabled(node) && + !node.readOnly + ) { + const step = node.step ? Number(node.step) : 1 + + const reachedMax = value === node.max + if (inputData === 'ArrowUp' && !reachedMax) { + const exceedsMax = Number(value) + step > Number(node.max) + if (exceedsMax && !!node.max) { + newValue = node.max + } else { + newValue = (Number(value) + step).toString() + } + } + + const reachedMin = value === node.min + if (inputData === 'ArrowDown' && !reachedMin) { + const exceedsMin = Number(value) - step < Number(node.min) + if (exceedsMin && !!node.min) { + newValue = node.min + } else { + newValue = (Number(value) - step).toString() + } + } + } + return { oldValue: value, newValue, diff --git a/tests/event/behavior/keydown.ts b/tests/event/behavior/keydown.ts index da6f10dc..fb512de7 100644 --- a/tests/event/behavior/keydown.ts +++ b/tests/event/behavior/keydown.ts @@ -387,3 +387,153 @@ cases( }, }, ) + +test("increment number input's value when pressing the arrow up key", () => { + const {element} = render(``) + + const instance = setupInstance() + + instance.dispatchUIEvent(element, 'keydown', {key: 'ArrowUp'}) + + expect(element).toHaveValue(2) +}) + +test("do not increment number input's value when pressing the arrow up key and it would go above the max value", () => { + const {element} = render( + ``, + ) + + const instance = setupInstance() + + instance.dispatchUIEvent(element, 'keydown', {key: 'ArrowUp'}) + + expect(element).toHaveValue(1) +}) + +test("decrement number input's value when pressing the arrow down key", () => { + const {element} = render(``) + + const instance = setupInstance() + + instance.dispatchUIEvent(element, 'keydown', {key: 'ArrowDown'}) + + expect(element).toHaveValue(0) +}) + +test("do not decrement number input's value when pressing the arrow down key and it would go below the min value", () => { + const {element} = render( + ``, + ) + + const instance = setupInstance() + + instance.dispatchUIEvent(element, 'keydown', {key: 'ArrowDown'}) + + expect(element).toHaveValue(1) +}) + +test("increments number input's value by the defined steps when pressing the arrow up key", () => { + const {element} = render( + ``, + ) + + const instance = setupInstance() + + instance.dispatchUIEvent(element, 'keydown', {key: 'ArrowUp'}) + + expect(element).toHaveValue(20) +}) + +test("decrements number input's value by the defined steps when pressing the arrow down key", () => { + const {element} = render( + ``, + ) + + const instance = setupInstance() + + instance.dispatchUIEvent(element, 'keydown', {key: 'ArrowDown'}) + + expect(element).toHaveValue(0) +}) + +test('decrements only to the min value when pressing the arrow down key and steps are too large', async () => { + const {element} = render( + ``, + ) + + const instance = setupInstance() + + instance.dispatchUIEvent(element, 'keydown', {key: 'ArrowDown'}) + + expect(element).toHaveValue(0) +}) + +test('increments only to the max value when pressing the arrow up key and steps are too large', async () => { + const {element} = render( + ``, + ) + + const instance = setupInstance() + + instance.dispatchUIEvent(element, 'keydown', {key: 'ArrowUp'}) + + expect(element).toHaveValue(10) +}) + +test("does not increment number input's value when pressing the arrow up key and the input is disabled", () => { + const {element} = render( + ``, + ) + + const instance = setupInstance() + + instance.dispatchUIEvent(element, 'keydown', {key: 'ArrowUp'}) + + expect(element).toHaveValue(1) +}) + +test("does not decrement number input's value when pressing the arrow down key and the input is disabled", () => { + const {element} = render( + ``, + ) + + const instance = setupInstance() + + instance.dispatchUIEvent(element, 'keydown', {key: 'ArrowDown'}) + + expect(element).toHaveValue(1) +}) + +test("does not increment number input's value when pressing the arrow up key and the input is readonly", () => { + const {element} = render( + ``, + ) + + const instance = setupInstance() + + instance.dispatchUIEvent(element, 'keydown', {key: 'ArrowUp'}) + + expect(element).toHaveValue(1) +}) + +test("does not decrement number input's value when pressing the arrow down key and the input is readonly", () => { + const {element} = render( + ``, + ) + + const instance = setupInstance() + + instance.dispatchUIEvent(element, 'keydown', {key: 'ArrowDown'}) + + expect(element).toHaveValue(1) +}) + +test('decrements to a negative value when pressing the arrow down key', () => { + const {element} = render(``) + + const instance = setupInstance() + + instance.dispatchUIEvent(element, 'keydown', {key: 'ArrowDown'}) + + expect(element).toHaveValue(-1) +})