diff --git a/packages/components/autocomplete/__tests__/autocomplete.test.tsx b/packages/components/autocomplete/__tests__/autocomplete.test.tsx index b61c57ed8b..73ffcb3b5b 100644 --- a/packages/components/autocomplete/__tests__/autocomplete.test.tsx +++ b/packages/components/autocomplete/__tests__/autocomplete.test.tsx @@ -235,6 +235,50 @@ describe("Autocomplete", () => { expect(autocomplete).toHaveFocus(); }); + it("should clear value after clicking clear button (controlled)", async () => { + const wrapper = render( + + {(item) => {item.value}} + , + ); + + const autocomplete = wrapper.getByTestId("autocomplete"); + + // open the select listbox + await act(async () => { + await userEvent.click(autocomplete); + }); + + // assert that the autocomplete listbox is open + expect(autocomplete).toHaveAttribute("aria-expanded", "true"); + + let options = wrapper.getAllByRole("option"); + + // select the target item + await act(async () => { + await userEvent.click(options[0]); + }); + + const {container} = wrapper; + + const clearButton = container.querySelector( + "[data-slot='inner-wrapper'] button:nth-of-type(1)", + )!; + + expect(clearButton).not.toBeNull(); + + // select the target item + await act(async () => { + await userEvent.click(clearButton); + }); + + // assert that the input has empty value + expect(autocomplete).toHaveValue(""); + + // assert that input is focused + expect(autocomplete).toHaveFocus(); + }); + it("should open and close listbox by clicking selector button", async () => { const wrapper = render( diff --git a/packages/components/autocomplete/src/use-autocomplete.ts b/packages/components/autocomplete/src/use-autocomplete.ts index b3c9890764..f70f631881 100644 --- a/packages/components/autocomplete/src/use-autocomplete.ts +++ b/packages/components/autocomplete/src/use-autocomplete.ts @@ -364,7 +364,6 @@ export function useAutocomplete(originalProps: UseAutocomplete const onClear = useCallback(() => { state.setInputValue(""); state.setSelectedKey(null); - state.close(); }, [state]); const onFocus = useCallback( diff --git a/packages/components/autocomplete/stories/autocomplete.stories.tsx b/packages/components/autocomplete/stories/autocomplete.stories.tsx index e5758cf07e..ca4db45df2 100644 --- a/packages/components/autocomplete/stories/autocomplete.stories.tsx +++ b/packages/components/autocomplete/stories/autocomplete.stories.tsx @@ -3,6 +3,7 @@ import type {ValidationResult} from "@react-types/shared"; import React, {Key} from "react"; import {Meta} from "@storybook/react"; import {useForm} from "react-hook-form"; +import {useFilter} from "@react-aria/i18n"; import {autocomplete, input, button} from "@nextui-org/theme"; import { Pokemon, @@ -161,6 +162,76 @@ const FormTemplate = ({color, variant, ...args}: AutocompleteProps) => { ); }; +const FullyControlledTemplate = () => { + // Store Autocomplete input value, selected option, open state, and items + // in a state tracker + const [fieldState, setFieldState] = React.useState({ + selectedKey: "", + inputValue: "", + items: animalsData, + }); + + // Implement custom filtering logic and control what items are + // available to the Autocomplete. + const {startsWith} = useFilter({sensitivity: "base"}); + + // Specify how each of the Autocomplete values should change when an + // option is selected from the list box + const onSelectionChange = (key) => { + // eslint-disable-next-line no-console + console.log(`onSelectionChange ${key}`); + setFieldState((prevState) => { + let selectedItem = prevState.items.find((option) => option.value === key); + + return { + inputValue: selectedItem?.label || "", + selectedKey: key, + items: animalsData.filter((item) => startsWith(item.label, selectedItem?.label || "")), + }; + }); + }; + + // Specify how each of the Autocomplete values should change when the input + // field is altered by the user + const onInputChange = (value) => { + // eslint-disable-next-line no-console + console.log(`onInputChange ${value}`); + setFieldState((prevState: any) => ({ + inputValue: value, + selectedKey: value === "" ? null : prevState.selectedKey, + items: animalsData.filter((item) => startsWith(item.label, value)), + })); + }; + + // Show entire list if user opens the menu manually + const onOpenChange = (isOpen, menuTrigger) => { + if (menuTrigger === "manual" && isOpen) { + setFieldState((prevState) => ({ + inputValue: prevState.inputValue, + selectedKey: prevState.selectedKey, + items: animalsData, + })); + } + }; + + return ( + + {(item) => {item.label}} + + ); +}; + const MirrorTemplate = ({color, variant, ...args}: AutocompleteProps) => (