mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
fix(input): esc key to clear input value (#4892)
* chore: theme generator credits * fix: blog date and spinner default variant * fix: #4850 Solve Pressing ESC doesn't clear input value * fix: #4850 code review change * fix: undo changes in apps/docs/content/blog/v2.7.0.mdx and add a test case for my changes * fix: run through the test cases successfully * fix: change md content * fix: using isClearable not clear the value * fix: add number-input clearable esc clear * fix: edit review problem * fix: delete unless file * chore(changeset): update changeset * fix: add inputProps.onKeyDown * fix: pressing ESC key in a read-only input not clear --------- Co-authored-by: Junior Garcia <jrgarciadev@gmail.com> Co-authored-by: աӄա <wingkwong.code@gmail.com>
This commit is contained in:
parent
ff8c9b3fec
commit
6453149543
6
.changeset/grumpy-pandas-rescue.md
Normal file
6
.changeset/grumpy-pandas-rescue.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@heroui/input": patch
|
||||||
|
"@heroui/number-input": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
add missing logic to handle esc key to clear input / number-input value (#4850)
|
||||||
@ -300,6 +300,66 @@ describe("Input", () => {
|
|||||||
|
|
||||||
expect(onClear).toHaveBeenCalledTimes(0);
|
expect(onClear).toHaveBeenCalledTimes(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should clear value when isClearable and pressing ESC key", async () => {
|
||||||
|
const onClear = jest.fn();
|
||||||
|
const defaultValue = "test value";
|
||||||
|
|
||||||
|
const {getByRole} = render(<Input isClearable defaultValue={defaultValue} onClear={onClear} />);
|
||||||
|
|
||||||
|
const input = getByRole("textbox") as HTMLInputElement;
|
||||||
|
|
||||||
|
expect(input.value).toBe(defaultValue);
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, {key: "Escape"});
|
||||||
|
|
||||||
|
expect(input.value).toBe("");
|
||||||
|
|
||||||
|
expect(onClear).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not clear value when pressing ESC key if input is empty", () => {
|
||||||
|
const onClear = jest.fn();
|
||||||
|
|
||||||
|
const {getByRole} = render(<Input isClearable defaultValue="" onClear={onClear} />);
|
||||||
|
|
||||||
|
const input = getByRole("textbox");
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, {key: "Escape"});
|
||||||
|
|
||||||
|
expect(onClear).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not clear value when pressing ESC key if input is isClearable", () => {
|
||||||
|
const defaultValue = "test value";
|
||||||
|
|
||||||
|
const {getByRole} = render(<Input defaultValue={defaultValue} />);
|
||||||
|
|
||||||
|
const input = getByRole("textbox") as HTMLInputElement;
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, {key: "Escape"});
|
||||||
|
|
||||||
|
expect(input.value).toBe("test value");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not clear value when pressing ESC key if input is readonly", () => {
|
||||||
|
const onClear = jest.fn();
|
||||||
|
const defaultValue = "test value";
|
||||||
|
|
||||||
|
const {getByRole} = render(
|
||||||
|
<Input isClearable isReadOnly defaultValue={defaultValue} onClear={onClear} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = getByRole("textbox") as HTMLInputElement;
|
||||||
|
|
||||||
|
expect(input.value).toBe(defaultValue);
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, {key: "Escape"});
|
||||||
|
|
||||||
|
expect(input.value).toBe(defaultValue);
|
||||||
|
|
||||||
|
expect(onClear).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Input with React Hook Form", () => {
|
describe("Input with React Hook Form", () => {
|
||||||
|
|||||||
@ -352,6 +352,21 @@ export function useInput<T extends HTMLInputElement | HTMLTextAreaElement = HTML
|
|||||||
[slots, isLabelHovered, labelProps, classNames?.label],
|
[slots, isLabelHovered, labelProps, classNames?.label],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleKeyDown = useCallback(
|
||||||
|
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (
|
||||||
|
e.key === "Escape" &&
|
||||||
|
inputValue &&
|
||||||
|
(isClearable || onClear) &&
|
||||||
|
!originalProps.isReadOnly
|
||||||
|
) {
|
||||||
|
setInputValue("");
|
||||||
|
onClear?.();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[inputValue, setInputValue, onClear, isClearable, originalProps.isReadOnly],
|
||||||
|
);
|
||||||
|
|
||||||
const getInputProps: PropGetter = useCallback(
|
const getInputProps: PropGetter = useCallback(
|
||||||
(props = {}) => {
|
(props = {}) => {
|
||||||
return {
|
return {
|
||||||
@ -375,6 +390,7 @@ export function useInput<T extends HTMLInputElement | HTMLTextAreaElement = HTML
|
|||||||
),
|
),
|
||||||
"aria-readonly": dataAttr(originalProps.isReadOnly),
|
"aria-readonly": dataAttr(originalProps.isReadOnly),
|
||||||
onChange: chain(inputProps.onChange, onChange),
|
onChange: chain(inputProps.onChange, onChange),
|
||||||
|
onKeyDown: chain(inputProps.onKeyDown, props.onKeyDown, handleKeyDown),
|
||||||
ref: domRef,
|
ref: domRef,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -392,6 +408,7 @@ export function useInput<T extends HTMLInputElement | HTMLTextAreaElement = HTML
|
|||||||
originalProps.isReadOnly,
|
originalProps.isReadOnly,
|
||||||
originalProps.isRequired,
|
originalProps.isRequired,
|
||||||
onChange,
|
onChange,
|
||||||
|
handleKeyDown,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -241,6 +241,63 @@ describe("NumberInput", () => {
|
|||||||
expect(stepperButton).toBeNull();
|
expect(stepperButton).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should clear value when isClearable and pressing ESC key", async () => {
|
||||||
|
const onClear = jest.fn();
|
||||||
|
const defaultValue = 12;
|
||||||
|
|
||||||
|
const {container} = render(
|
||||||
|
<NumberInput isClearable defaultValue={defaultValue} onClear={onClear} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = container.querySelector("input") as HTMLInputElement;
|
||||||
|
|
||||||
|
expect(input.value).toBe(defaultValue.toString());
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, {key: "Escape"});
|
||||||
|
expect(input.value).toBe("");
|
||||||
|
expect(onClear).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not clear value when pressing ESC key if input is empty", () => {
|
||||||
|
const onClear = jest.fn();
|
||||||
|
|
||||||
|
const {container} = render(<NumberInput isClearable onClear={onClear} />);
|
||||||
|
|
||||||
|
const input = container.querySelector("input") as HTMLInputElement;
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, {key: "Escape"});
|
||||||
|
expect(onClear).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not clear value when pressing ESC key without isClearable", () => {
|
||||||
|
const defaultValue = 12;
|
||||||
|
|
||||||
|
const {container} = render(<NumberInput defaultValue={defaultValue} />);
|
||||||
|
|
||||||
|
const input = container.querySelector("input") as HTMLInputElement;
|
||||||
|
|
||||||
|
expect(input.value).toBe(defaultValue.toString());
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, {key: "Escape"});
|
||||||
|
expect(input.value).toBe(defaultValue.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not clear value when pressing ESC key if input is readonly", () => {
|
||||||
|
const onClear = jest.fn();
|
||||||
|
const defaultValue = 42;
|
||||||
|
|
||||||
|
const {container} = render(<NumberInput isReadOnly defaultValue={defaultValue} />);
|
||||||
|
|
||||||
|
const input = container.querySelector("input") as HTMLInputElement;
|
||||||
|
|
||||||
|
expect(input.value).toBe(defaultValue.toString());
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, {key: "Escape"});
|
||||||
|
|
||||||
|
expect(input.value).toBe(defaultValue.toString());
|
||||||
|
expect(onClear).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it("should emit onChange", async () => {
|
it("should emit onChange", async () => {
|
||||||
const onChange = jest.fn();
|
const onChange = jest.fn();
|
||||||
|
|
||||||
|
|||||||
@ -239,6 +239,21 @@ export function useNumberInput(originalProps: UseNumberInputProps) {
|
|||||||
[objectToDeps(variantProps), isInvalid, isClearable, hasStartContent, disableAnimation],
|
[objectToDeps(variantProps), isInvalid, isClearable, hasStartContent, disableAnimation],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleKeyDown = useCallback(
|
||||||
|
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (
|
||||||
|
e.key === "Escape" &&
|
||||||
|
inputValue &&
|
||||||
|
(isClearable || onClear) &&
|
||||||
|
!originalProps.isReadOnly
|
||||||
|
) {
|
||||||
|
state.setInputValue("");
|
||||||
|
onClear?.();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[inputValue, state.setInputValue, onClear, isClearable, originalProps.isReadOnly],
|
||||||
|
);
|
||||||
|
|
||||||
const getBaseProps: PropGetter = useCallback(
|
const getBaseProps: PropGetter = useCallback(
|
||||||
(props = {}) => {
|
(props = {}) => {
|
||||||
return {
|
return {
|
||||||
@ -324,6 +339,7 @@ export function useNumberInput(originalProps: UseNumberInputProps) {
|
|||||||
),
|
),
|
||||||
"aria-readonly": dataAttr(originalProps.isReadOnly),
|
"aria-readonly": dataAttr(originalProps.isReadOnly),
|
||||||
onChange: chain(inputProps.onChange, onChange),
|
onChange: chain(inputProps.onChange, onChange),
|
||||||
|
onKeyDown: chain(inputProps.onKeyDown, props.onKeyDown, handleKeyDown),
|
||||||
ref: domRef,
|
ref: domRef,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -339,6 +355,7 @@ export function useNumberInput(originalProps: UseNumberInputProps) {
|
|||||||
originalProps.isReadOnly,
|
originalProps.isReadOnly,
|
||||||
originalProps.isRequired,
|
originalProps.isRequired,
|
||||||
onChange,
|
onChange,
|
||||||
|
handleKeyDown,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user