Skip to content

Commit ae793dd

Browse files
authored
fix: allow for reselecting the last selection of InputSearch if cleared. Trigger InputSearch onSelect callback when selection is cleared. (#682)
1 parent 53a18d0 commit ae793dd

File tree

4 files changed

+54
-36
lines changed

4 files changed

+54
-36
lines changed

build.washingtonpost.com/components/Markdown/Examples/Breakpoints.jsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const Ruler = styled("div", {
2121
const Body = styled("div", {
2222
paddingBlock: "1px",
2323
fontFamily: theme.fonts.meta,
24-
placeItems: "left"
24+
placeItems: "left",
2525
});
2626

2727
const PointsList = styled("ul", {
@@ -177,4 +177,4 @@ const Breakpoints = () => {
177177
);
178178
};
179179

180-
export default Breakpoints;
180+
export default Breakpoints;

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

+9-16
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const InputSearchInput = React.forwardRef<
2424
{
2525
label = "Search",
2626
autocomplete = true,
27+
autoComplete = "off",
2728
id,
2829
value,
2930
...rest
@@ -46,6 +47,13 @@ export const InputSearchInput = React.forwardRef<
4647
if (inputProps.onChange) inputProps.onChange(event);
4748
};
4849

50+
React.useEffect(() => {
51+
// allow for external changes for controlled inputs
52+
if (value !== undefined && value !== null && value !== inputProps.value) {
53+
state.setInputValue(value);
54+
}
55+
}, [value, inputProps.value, state]);
56+
4957
const [tempText, setTempText] = React.useState<string>();
5058
const withKeyboard = React.useRef(false);
5159
React.useEffect(() => {
@@ -62,26 +70,10 @@ export const InputSearchInput = React.forwardRef<
6270
}
6371
}, [state.selectionManager.focusedKey, setTempText]);
6472

65-
if (value !== undefined && value !== null) {
66-
inputProps.value = value;
67-
}
68-
6973
if (autocomplete && withKeyboard.current) {
7074
inputProps.value = tempText;
7175
}
7276

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-
8577
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
8678
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
8779
withKeyboard.current = true;
@@ -111,6 +103,7 @@ export const InputSearchInput = React.forwardRef<
111103
onChange={handleChange}
112104
onKeyDown={handleKeyDown}
113105
onKeyUp={handleKeyUp}
106+
autoComplete={autoComplete}
114107
/>
115108
);
116109
}

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

+10-8
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,16 @@ export const InputSearchRoot = ({
107107

108108
const prevSelectedKey = React.useRef(state.selectedKey);
109109
React.useEffect(() => {
110-
if (
111-
state.selectedItem &&
112-
onSelect &&
113-
prevSelectedKey.current !== state.selectedKey
114-
) {
115-
onSelect(
116-
state.selectedItem.textValue || (state.selectedItem.rendered as string)
117-
);
110+
if (!onSelect) return;
111+
if (prevSelectedKey.current !== state.selectedKey) {
112+
if (state.selectedItem) {
113+
onSelect(
114+
state.selectedItem.textValue ||
115+
(state.selectedItem.rendered as string)
116+
);
117+
} else if (state.selectedItem === null) {
118+
onSelect("");
119+
}
118120
prevSelectedKey.current = state.selectedKey;
119121
}
120122
}, [state.selectedItem, onSelect]);

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

+33-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import React, { useMemo, useState, useEffect } from "react";
2-
import { screen, userEvent } from "@storybook/testing-library";
3-
import { expect } from "@storybook/jest";
1+
import React, { useMemo, useState, useEffect, use } from "react";
2+
import { screen, userEvent, waitFor } from "@storybook/testing-library";
3+
import { expect, jest } from "@storybook/jest";
44
import { Box } from "../box";
55
import { matchSorter } from "match-sorter";
66
import { InputSearch } from "./";
@@ -267,6 +267,8 @@ const ControlledTemplate: StoryFn<typeof InputSearch.Root> = (args) => {
267267
openOnFocus
268268
onSelect={(value) => {
269269
setTerm(value);
270+
console.log("onSelect", value);
271+
args.onSelect && args.onSelect(value);
270272
}}
271273
>
272274
<InputSearch.Input
@@ -294,16 +296,21 @@ const ControlledTemplate: StoryFn<typeof InputSearch.Root> = (args) => {
294296

295297
export const Controlled = {
296298
render: ControlledTemplate,
297-
args: {},
298-
299+
args: {
300+
onSelect: jest.fn(),
301+
},
299302
parameters: {
300303
chromatic: { disableSnapshot: true },
301304
},
302305
};
303306

304-
const InteractionsTemplate: StoryFn<typeof InputSearch.Root> = () => (
307+
const InteractionsTemplate: StoryFn<typeof InputSearch.Root> = (args) => (
305308
<Box css={{ width: "275px", height: "340px" }}>
306-
<InputSearch.Root aria-label="Example-Search" openOnFocus>
309+
<InputSearch.Root
310+
aria-label="Example-Search"
311+
openOnFocus
312+
onSelect={args.onSelect}
313+
>
307314
<InputSearch.Input name="city" id="city" />
308315
<InputSearch.Popover>
309316
<InputSearch.List>
@@ -320,21 +327,28 @@ const InteractionsTemplate: StoryFn<typeof InputSearch.Root> = () => (
320327

321328
export const Interactions = {
322329
render: InteractionsTemplate,
323-
324-
play: async () => {
330+
args: {
331+
onSelect: jest.fn(),
332+
},
333+
play: async ({ args }) => {
325334
const input = await screen.findByLabelText("Search");
326335
await userEvent.type(input, "app", {
327336
delay: 100,
328337
});
329338
await userEvent.keyboard("[ArrowDown]");
330339
await expect(input).toHaveDisplayValue("Apple");
340+
await userEvent.keyboard("[Enter]");
341+
await expect(args.onSelect).toHaveBeenCalledWith("Apple");
342+
const clearButton = await screen.findByRole("button", { name: "Clear" });
343+
await userEvent.click(clearButton);
344+
await expect(args.onSelect).toHaveBeenCalledWith("");
331345
},
332346
};
333347

334348
export const ControlledKeyboardInteractions = {
335349
render: ControlledTemplate,
336350

337-
play: async () => {
351+
play: async ({ args }) => {
338352
const input = await screen.findByLabelText("Search");
339353
await userEvent.type(input, "test", {
340354
delay: 100,
@@ -345,6 +359,9 @@ export const ControlledKeyboardInteractions = {
345359
await expect(input).toHaveDisplayValue("Orange");
346360
await userEvent.keyboard("[Backspace]");
347361
await expect(input).toHaveDisplayValue("Orang");
362+
await userEvent.keyboard("[ArrowUp]");
363+
await userEvent.keyboard("[Enter]");
364+
await expect(args.onSelect).toHaveBeenCalledWith("Pineapple");
348365
const clearButton = await screen.findByText("Clear");
349366
await userEvent.click(clearButton);
350367
await expect(input).toHaveDisplayValue("");
@@ -356,6 +373,12 @@ export const ControlledKeyboardInteractions = {
356373
const externalClearButton = await screen.findByText("External Clear");
357374
await userEvent.click(externalClearButton);
358375
await expect(input).toHaveDisplayValue("");
376+
await userEvent.click(input);
377+
await expect(input).toHaveFocus();
378+
const appleOption = await screen.findByRole("option", { name: "Apple" });
379+
await userEvent.click(appleOption);
380+
await expect(input).toHaveDisplayValue("Apple");
381+
await expect(args.onSelect).toHaveBeenCalledWith("Apple");
359382
//
360383
},
361384
};

0 commit comments

Comments
 (0)