diff --git a/.changeset/clean-seahorses-cheat.md b/.changeset/clean-seahorses-cheat.md
new file mode 100644
index 0000000000..01c5ac2f1a
--- /dev/null
+++ b/.changeset/clean-seahorses-cheat.md
@@ -0,0 +1,5 @@
+---
+"@heroui/autocomplete": patch
+---
+
+ensure focused item matches selected item after filter, selection (#5277)
diff --git a/packages/components/autocomplete/__tests__/autocomplete.test.tsx b/packages/components/autocomplete/__tests__/autocomplete.test.tsx
index 79c09386c0..c8a8f418da 100644
--- a/packages/components/autocomplete/__tests__/autocomplete.test.tsx
+++ b/packages/components/autocomplete/__tests__/autocomplete.test.tsx
@@ -940,3 +940,129 @@ describe("Autocomplete with React Hook Form", () => {
expect(onSubmit).toHaveBeenCalledTimes(1);
});
});
+
+describe("focusedKey management with selected key", () => {
+ let user: UserEvent;
+
+ beforeEach(() => {
+ user = userEvent.setup();
+ });
+
+ it("should set focusedKey to the first non-disabled item when selectedKey is null", async () => {
+ const wrapper = render(
+
+
+ Penguin
+
+ Zebra
+ Shark
+ ,
+ );
+
+ const autocomplete = wrapper.getByTestId("autocomplete");
+
+ // open the select listbox
+ await user.click(autocomplete);
+
+ const options = wrapper.getAllByRole("option");
+
+ // first non-disabled item is zebra
+ const optionItem = options[1];
+
+ expect(optionItem).toHaveAttribute("data-focus", "true");
+ });
+
+ it("should set focusedKey to the item's key when an item is selected", async () => {
+ const wrapper = render(
+
+ Penguin
+ Zebra
+ Shark
+ ,
+ );
+
+ const autocomplete = wrapper.getByTestId("autocomplete");
+
+ // open the select listbox
+ await user.click(autocomplete);
+
+ // select the target item using keyboard
+ await user.keyboard("penguin");
+ await user.keyboard("{Enter}");
+ await user.click(autocomplete);
+
+ const options = wrapper.getAllByRole("option");
+ const optionItem = options[0];
+
+ expect(optionItem).toHaveAttribute("data-focus", "true");
+ });
+
+ it("should set focusedKey to the item's key when selectedKey prop is passed", async () => {
+ const wrapper = render(
+
+ Penguin
+ Zebra
+ Shark
+ ,
+ );
+
+ const autocomplete = wrapper.getByTestId("autocomplete");
+
+ // open the select listbox
+ await user.click(autocomplete);
+
+ const options = wrapper.getAllByRole("option");
+ const optionItem = options[0];
+
+ expect(optionItem).toHaveAttribute("data-focus", "true");
+ });
+
+ it("should set focusedKey to the default item's key when using react-hook-form defaultValues", async () => {
+ const {result} = renderHook(() =>
+ useForm({
+ defaultValues: {
+ withDefaultValue: "zebra",
+ withoutDefaultValue: "",
+ requiredField: "",
+ },
+ }),
+ );
+
+ const {register} = result.current;
+
+ const wrapper = render(
+
,
+ );
+
+ const autocomplete = wrapper.getByTestId("autocomplete");
+
+ // open the select listbox
+ await user.click(autocomplete);
+
+ const options = wrapper.getAllByRole("option");
+ const optionItem = options[1];
+
+ expect(optionItem).toHaveAttribute("data-focus", "true");
+ });
+});
diff --git a/packages/components/autocomplete/src/use-autocomplete.ts b/packages/components/autocomplete/src/use-autocomplete.ts
index 38eef998e1..eecdd519e6 100644
--- a/packages/components/autocomplete/src/use-autocomplete.ts
+++ b/packages/components/autocomplete/src/use-autocomplete.ts
@@ -339,15 +339,27 @@ export function useAutocomplete(originalProps: UseAutocomplete
}
}, [inputRef.current]);
- // focus first non-disabled item
+ // Ensure the focused item in the dropdown correctly reflects the
+ // selected key when the component mounts or relevant state changes.
useEffect(() => {
- let key = state.collection.getFirstKey();
-
- while (key && state.disabledKeys.has(key)) {
- key = state.collection.getKeyAfter(key);
+ let keyToFocus: React.Key | null;
+
+ if (
+ state.selectedKey !== null &&
+ state.collection.getItem(state.selectedKey) &&
+ !state.disabledKeys.has(state.selectedKey)
+ ) {
+ keyToFocus = state.selectedKey;
+ } else {
+ let firstAvailableKey = state.collection.getFirstKey();
+
+ while (firstAvailableKey && state.disabledKeys.has(firstAvailableKey)) {
+ firstAvailableKey = state.collection.getKeyAfter(firstAvailableKey);
+ }
+ keyToFocus = firstAvailableKey;
}
- state.selectionManager.setFocusedKey(key);
- }, [state.collection, state.disabledKeys]);
+ state.selectionManager.setFocusedKey(keyToFocus);
+ }, [state.collection, state.disabledKeys, state.selectedKey]);
useEffect(() => {
if (isOpen) {