From d56a77bf524832325fa2c269f8a79badd067b38e Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 24 Apr 2024 19:17:55 +0200 Subject: [PATCH] Add frozen value to `ComboboxOptions` component (#3126) * add frozen state to `Combobox` component Once you choose an option, the `selected` state remains on the "old" value until the combobox is fully closed. This way the potential visual indicators such as a check mark doesn't move around while the Combobox is closing (when using transitions) Same as the `Listbox`, this is purely about visual state and exposed data from the `ComboboxOptions` component and down that tree. The top-level `Combobox` and `ComboboxInput` components still know the correct (new) value and will update the `aria-activedescendant` correctly. This is achieved by storing the old data (only in single value mode), and overriding the `isSelected` check function via context provided by the `ComboboxOptions` component. * remove check that verified that no `aria-selected` was present But now with this change, it will be present. * update changelog * Update CHANGELOG.md --- packages/@headlessui-react/CHANGELOG.md | 1 + .../src/components/combobox/combobox.test.tsx | 1 - .../src/components/combobox/combobox.tsx | 38 ++++++++++++++----- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/packages/@headlessui-react/CHANGELOG.md b/packages/@headlessui-react/CHANGELOG.md index 758a8dc7a8..10eeeadd67 100644 --- a/packages/@headlessui-react/CHANGELOG.md +++ b/packages/@headlessui-react/CHANGELOG.md @@ -45,6 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add new `CloseButton` component and `useClose` hook ([#3096](https://github.com/tailwindlabs/headlessui/pull/3096)) - Allow passing a boolean to the `anchor` prop ([#3121](https://github.com/tailwindlabs/headlessui/pull/3121)) - Add `portal` prop to `Combobox`, `Listbox`, `Menu` and `Popover` components ([#3124](https://github.com/tailwindlabs/headlessui/pull/3124)) +- Add frozen value to `ComboboxOptions` component ([#3126](https://github.com/tailwindlabs/headlessui/pull/3126)) ## [1.7.19] - 2024-04-15 diff --git a/packages/@headlessui-react/src/components/combobox/combobox.test.tsx b/packages/@headlessui-react/src/components/combobox/combobox.test.tsx index 912964d43a..4012bd634c 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.test.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.test.tsx @@ -4289,7 +4289,6 @@ describe.each([{ virtual: true }, { virtual: false }])( // Verify that we don't have an selected option anymore assertNotActiveComboboxOption(options[1]) - assertNoSelectedComboboxOption() // Verify that we saw the `null` change coming in expect(handleChange).toHaveBeenCalledTimes(1) diff --git a/packages/@headlessui-react/src/components/combobox/combobox.tsx b/packages/@headlessui-react/src/components/combobox/combobox.tsx index 204a2bc899..118d8dfaff 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx @@ -1646,17 +1646,35 @@ function OptionsFn( }) } + // Frozen state, the selected value will only update visually when the user re-opens the + let [frozenValue, setFrozenValue] = useState(data.value) + if ( + data.value !== frozenValue && + data.comboboxState === ComboboxState.Open && + data.mode !== ValueMode.Multi + ) { + setFrozenValue(data.value) + } + + let isSelected = useEvent((compareValue: unknown) => { + return data.compare(frozenValue, compareValue) + }) + return ( - {render({ - ourProps, - theirProps, - slot, - defaultTag: DEFAULT_OPTIONS_TAG, - features: OptionsRenderFeatures, - visible, - name: 'Combobox.Options', - })} + + {render({ + ourProps, + theirProps, + slot, + defaultTag: DEFAULT_OPTIONS_TAG, + features: OptionsRenderFeatures, + visible, + name: 'Combobox.Options', + })} + ) } @@ -1796,7 +1814,7 @@ function OptionFn< } if (data.mode === ValueMode.Single) { - requestAnimationFrame(() => actions.closeCombobox()) + actions.closeCombobox() } })