Skip to content

Commit f874afd

Browse files
committed
fix: allow controlled InputSearch to be cleared by passing empty string
1 parent ee0b2e9 commit f874afd

File tree

2 files changed

+59
-27
lines changed

2 files changed

+59
-27
lines changed

packages/kit/src/input-search/InputSearchInput.tsx

+14-1
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,26 @@ export const InputSearchInput = React.forwardRef<
6262
}
6363
}, [state.selectionManager.focusedKey, setTempText]);
6464

65-
if (value) {
65+
if (value !== undefined && value !== null) {
6666
inputProps.value = value;
6767
}
68+
6869
if (autocomplete && withKeyboard.current) {
6970
inputProps.value = tempText;
7071
}
7172

73+
const [, setRerender] = React.useState(false);
74+
if (!inputProps.value && inputRef.current) {
75+
const el = inputRef.current as HTMLInputElement;
76+
if (el.value) {
77+
// if a controlled input is passed an empty value,
78+
// an extra render is needed to reset the input's internal state
79+
requestAnimationFrame(() => {
80+
setRerender((prev) => !prev);
81+
});
82+
}
83+
}
84+
7285
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
7386
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
7487
withKeyboard.current = true;

packages/kit/src/input-search/play.stories.tsx

+45-26
Original file line numberDiff line numberDiff line change
@@ -223,34 +223,44 @@ export const Scroll = {
223223
const ControlledTemplate: StoryFn<typeof InputSearch.Root> = (args) => {
224224
const [term, setTerm] = useState("");
225225
return (
226-
<Box css={{ width: "275px" }}>
227-
<InputSearch.Root
228-
{...args}
229-
aria-label="Example-Search"
230-
openOnFocus
231-
onSelect={(value) => {
232-
setTerm(value);
226+
<>
227+
<button
228+
onClick={() => {
229+
setTerm("");
233230
}}
234231
>
235-
<InputSearch.Input
236-
name="fruit"
237-
id="fruit"
238-
value={term}
239-
onChange={(event) => {
240-
setTerm(event.target.value);
232+
External Clear
233+
</button>
234+
<br />
235+
<Box css={{ width: "275px" }}>
236+
<InputSearch.Root
237+
{...args}
238+
aria-label="Example-Search"
239+
openOnFocus
240+
onSelect={(value) => {
241+
setTerm(value);
241242
}}
242-
/>
243-
<InputSearch.Popover>
244-
<InputSearch.List>
245-
<InputSearch.ListItem value="Apple" />
246-
<InputSearch.ListItem value="Banana" />
247-
<InputSearch.ListItem value="Orange" />
248-
<InputSearch.ListItem value="Kiwi" />
249-
<InputSearch.ListItem value="Pineapple" />
250-
</InputSearch.List>
251-
</InputSearch.Popover>
252-
</InputSearch.Root>
253-
</Box>
243+
>
244+
<InputSearch.Input
245+
name="fruit"
246+
id="fruit"
247+
value={term}
248+
onChange={(event) => {
249+
setTerm(event.target.value);
250+
}}
251+
/>
252+
<InputSearch.Popover>
253+
<InputSearch.List>
254+
<InputSearch.ListItem value="Apple" />
255+
<InputSearch.ListItem value="Banana" />
256+
<InputSearch.ListItem value="Orange" />
257+
<InputSearch.ListItem value="Kiwi" />
258+
<InputSearch.ListItem value="Pineapple" />
259+
</InputSearch.List>
260+
</InputSearch.Popover>
261+
</InputSearch.Root>
262+
</Box>
263+
</>
254264
);
255265
};
256266

@@ -298,7 +308,7 @@ export const ControlledKeyboardInteractions = {
298308

299309
play: async () => {
300310
const input = await screen.findByLabelText("Search");
301-
await userEvent.type(input, "app", {
311+
await userEvent.type(input, "test", {
302312
delay: 100,
303313
});
304314
await userEvent.keyboard("[ArrowDown]");
@@ -310,5 +320,14 @@ export const ControlledKeyboardInteractions = {
310320
const clearButton = await screen.findByText("Clear");
311321
await userEvent.click(clearButton);
312322
await expect(input).toHaveDisplayValue("");
323+
await userEvent.type(input, "test", {
324+
delay: 100,
325+
});
326+
await userEvent.keyboard("[ArrowDown]");
327+
await expect(input).toHaveDisplayValue("Apple");
328+
const externalClearButton = await screen.findByText("External Clear");
329+
await userEvent.click(externalClearButton);
330+
await expect(input).toHaveDisplayValue("");
331+
//
313332
},
314333
};

0 commit comments

Comments
 (0)