Skip to content

Commit

Permalink
increment or decrement value of number input when pressing arrow keys
Browse files Browse the repository at this point in the history
  • Loading branch information
Haberkamp committed Feb 8, 2025
1 parent 63ac399 commit c39d029
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 0 deletions.
8 changes: 8 additions & 0 deletions src/event/behavior/keydown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -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)) {
Expand Down
31 changes: 31 additions & 0 deletions src/event/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
EditableInputOrTextarea,
getMaxLength,
getNextCursorPosition,
isDisabled,
isEditable,
isElementType,
isValidDateOrTimeValue,
supportsMaxLength,
Expand Down Expand Up @@ -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,
Expand Down
150 changes: 150 additions & 0 deletions tests/event/behavior/keydown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,3 +387,153 @@ cases(
},
},
)

test("increment number input's value when pressing the arrow up key", () => {
const {element} = render<HTMLInputElement>(`<input value="1" type="number"/>`)

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<HTMLInputElement>(
`<input value="1" type="number" max="1"/>`,
)

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<HTMLInputElement>(`<input value="1" type="number"/>`)

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<HTMLInputElement>(
`<input value="1" type="number" min="1"/>`,
)

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<HTMLInputElement>(
`<input value="10" type="number" step="10"/>`,
)

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<HTMLInputElement>(
`<input value="10" type="number" step="10"/>`,
)

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<HTMLInputElement>(
`<input value="5" type="number" min="0" step="10"/>`,
)

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<HTMLInputElement>(
`<input value="5" type="number" max="10" step="10"/>`,
)

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<HTMLInputElement>(
`<input value="1" type="number" disabled/>`,
)

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<HTMLInputElement>(
`<input value="1" type="number" disabled/>`,
)

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<HTMLInputElement>(
`<input value="1" type="number" readonly/>`,
)

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<HTMLInputElement>(
`<input value="1" type="number" readonly/>`,
)

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<HTMLInputElement>(`<input value="0" type="number"/>`)

const instance = setupInstance()

instance.dispatchUIEvent(element, 'keydown', {key: 'ArrowDown'})

expect(element).toHaveValue(-1)
})

0 comments on commit c39d029

Please sign in to comment.