mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
fix(number-input): backspace behavior with formatted numbers (#5719)
* fix(number-input): backspace behavior with formatted numbers * refactor(number-input): handle locale separator * fix(number-input): set number input value logic
This commit is contained in:
parent
f13c6875db
commit
736293b8a6
5
.changeset/gentle-owls-arrive.md
Normal file
5
.changeset/gentle-owls-arrive.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"@heroui/number-input": patch
|
||||
---
|
||||
|
||||
fix backspace behavior with formatted numbers (#5712)
|
||||
@ -621,5 +621,63 @@ describe("NumberInput with React Hook Form", () => {
|
||||
await user.keyboard("1234");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Backspace behavior with formatted numbers", () => {
|
||||
it("should handle backspace when cursor is between first digit and comma", async () => {
|
||||
const {container} = render(
|
||||
<NumberInput
|
||||
defaultValue={1234}
|
||||
formatOptions={{
|
||||
style: "decimal",
|
||||
useGrouping: true,
|
||||
}}
|
||||
label="test number input"
|
||||
/>,
|
||||
);
|
||||
|
||||
const input = container.querySelector("input[type='text']") as HTMLInputElement;
|
||||
|
||||
expect(input.value).toBe("1,234");
|
||||
|
||||
act(() => {
|
||||
input.focus();
|
||||
input.setSelectionRange(1, 1);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
fireEvent.keyDown(input, {key: "Backspace", code: "Backspace"});
|
||||
});
|
||||
|
||||
expect(input.value).toBe("234");
|
||||
});
|
||||
|
||||
it("should handle backspace for other formatted number scenarios", async () => {
|
||||
const {container} = render(
|
||||
<NumberInput
|
||||
defaultValue={1234567}
|
||||
formatOptions={{
|
||||
style: "decimal",
|
||||
useGrouping: true,
|
||||
}}
|
||||
label="test number input"
|
||||
/>,
|
||||
);
|
||||
|
||||
const input = container.querySelector("input[type='text']") as HTMLInputElement;
|
||||
|
||||
expect(input.value).toBe("1,234,567");
|
||||
|
||||
act(() => {
|
||||
input.focus();
|
||||
input.setSelectionRange(5, 5);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
fireEvent.keyDown(input, {key: "Backspace", code: "Backspace"});
|
||||
});
|
||||
|
||||
expect(input.value).toBe("123,567");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -242,7 +242,48 @@ export function useNumberInput(originalProps: UseNumberInputProps) {
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
const inputElement = e.currentTarget;
|
||||
const {selectionStart, selectionEnd, value} = inputElement;
|
||||
// locale-aware grouping separator
|
||||
const nf = new Intl.NumberFormat(locale, {useGrouping: true});
|
||||
const groupChar = nf.formatToParts(1000).find((p) => p.type === "group")?.value ?? ",";
|
||||
|
||||
// handle backspace when cursor is between a digit and the first group separator
|
||||
// e.g. 1|,234 (en-US) or 1|.234 (de-DE) -> backspace removes the preceding digit if (
|
||||
if (
|
||||
e.key === "Backspace" &&
|
||||
!originalProps.isReadOnly &&
|
||||
!originalProps.isDisabled &&
|
||||
selectionStart !== null &&
|
||||
selectionEnd !== null &&
|
||||
selectionStart === selectionEnd &&
|
||||
selectionStart > 0 &&
|
||||
value[selectionStart] === groupChar &&
|
||||
value[selectionStart - 1] !== groupChar
|
||||
) {
|
||||
e.preventDefault();
|
||||
// e.g. 1,234 -> ,234
|
||||
const newValue = value.slice(0, selectionStart - 1) + value.slice(selectionStart);
|
||||
// e.g. ,234 -> 234
|
||||
const cleanValue = newValue.replace(/[^\d.-]/g, "");
|
||||
|
||||
if (cleanValue === "" || cleanValue === "-") {
|
||||
state.setInputValue("");
|
||||
} else {
|
||||
const numberValue = parseFloat(cleanValue);
|
||||
|
||||
if (!isNaN(numberValue)) {
|
||||
state.setNumberValue(numberValue);
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
// set the new cursor position
|
||||
const pos = Math.max(0, selectionStart - 1);
|
||||
|
||||
inputElement.setSelectionRange(pos, pos);
|
||||
}, 0);
|
||||
} else if (
|
||||
e.key === "Escape" &&
|
||||
inputValue &&
|
||||
(isClearable || onClear) &&
|
||||
@ -252,7 +293,7 @@ export function useNumberInput(originalProps: UseNumberInputProps) {
|
||||
onClear?.();
|
||||
}
|
||||
},
|
||||
[inputValue, state.setInputValue, onClear, isClearable, originalProps.isReadOnly],
|
||||
[inputValue, state, onClear, isClearable, originalProps.isReadOnly],
|
||||
);
|
||||
|
||||
const getBaseProps: PropGetter = useCallback(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user