diff --git a/packages/@headlessui-react/CHANGELOG.md b/packages/@headlessui-react/CHANGELOG.md index 1008b1923..dfa77c43d 100644 --- a/packages/@headlessui-react/CHANGELOG.md +++ b/packages/@headlessui-react/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ensure `onChange` types are contravariant instead of bivariant ([#3781](https://github.com/tailwindlabs/headlessui/pull/3781)) - Support `` as a focusable element inside `
` ([#3389](https://github.com/tailwindlabs/headlessui/pull/3389)) - Fix `Maximum update depth exceeded` crash when using `transition` prop ([#3782](https://github.com/tailwindlabs/headlessui/pull/3782)) +- Ensure pressing `Tab` in the `ComboboxInput`, correctly syncs the input value ([#3785](https://github.com/tailwindlabs/headlessui/pull/3785)) ## [2.2.7] - 2025-07-30 diff --git a/packages/@headlessui-react/src/components/combobox/combobox.test.tsx b/packages/@headlessui-react/src/components/combobox/combobox.test.tsx index 5390696e3..4d6b38be6 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.test.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.test.tsx @@ -3035,6 +3035,56 @@ describe.each([{ virtual: true }, { virtual: false }])( assertActiveElement(document.querySelector('#before-combobox')) }) ) + + it( + 'pressing Tab should sync the ComboboxInput value again', + suppressConsoleLogs(async () => { + function Example() { + let [value, setValue] = useState(null) + + return ( + <> + value ?? '', + }} + /> + + + ) + } + + render() + + assertComboboxButton({ state: ComboboxState.InvisibleUnmounted }) + assertComboboxList({ state: ComboboxState.InvisibleUnmounted }) + + // Open combobox + await click(getComboboxButton()) + + // Select the 2nd option + await press(Keys.ArrowDown) + + // Tab to the next DOM node + await press(Keys.Tab) + + // Verify it is closed + assertComboboxButton({ state: ComboboxState.InvisibleUnmounted }) + assertComboboxList({ state: ComboboxState.InvisibleUnmounted }) + + // Verify the selected value was the highlighted one + expect(getComboboxInput()?.value).toBe('Option B') + + // Clear the option + await click(document.querySelector('button#clear') as HTMLButtonElement) + + // Verify the input value is cleared + expect(getComboboxInput()?.value).toBe('') + }) + ) }) describe('`Escape` key', () => { diff --git a/packages/@headlessui-react/src/components/combobox/combobox.tsx b/packages/@headlessui-react/src/components/combobox/combobox.tsx index 882d7a3a3..d7c94111d 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx @@ -785,6 +785,7 @@ function InputFn< return machine.actions.closeCombobox() case Keys.Tab: + machine.actions.setIsTyping(false) if (machine.state.comboboxState !== ComboboxState.Open) return if ( data.mode === ValueMode.Single &&