diff --git a/packages/@headlessui-vue/CHANGELOG.md b/packages/@headlessui-vue/CHANGELOG.md
index c5d04a6..acd57d3 100644
--- a/packages/@headlessui-vue/CHANGELOG.md
+++ b/packages/@headlessui-vue/CHANGELOG.md
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix memory leak in `Popover` component ([#2430](https://github.com/tailwindlabs/headlessui/pull/2430))
- Ensure `FocusTrap` is only active when the given `enabled` value is `true` ([#2456](https://github.com/tailwindlabs/headlessui/pull/2456))
+- Ensure the exposed `activeIndex` is up to date for the `Combobox` component ([#2463](https://github.com/tailwindlabs/headlessui/pull/2463))
## [1.7.13] - 2023-04-12
diff --git a/packages/@headlessui-vue/src/components/combobox/combobox.test.ts b/packages/@headlessui-vue/src/components/combobox/combobox.test.ts
index 3c066fb..1306252 100644
--- a/packages/@headlessui-vue/src/components/combobox/combobox.test.ts
+++ b/packages/@headlessui-vue/src/components/combobox/combobox.test.ts
@@ -4670,6 +4670,75 @@ describe('Keyboard interactions', () => {
)
})
})
+
+ it(
+ 'should sync the active index properly',
+ suppressConsoleLogs(async () => {
+ renderTemplate({
+ template: html`
+
+
+ Trigger
+ {{ activeIndex }}
+
+ {{ option }}
+
+
+ `,
+ setup: () => {
+ let value = ref(null)
+ let options = ref(['Option A', 'Option B', 'Option C', 'Option D'])
+
+ let query = ref('')
+ let filteredOptions = computed(() => {
+ return query.value === ''
+ ? options.value
+ : options.value.filter((option) => option.includes(query.value))
+ })
+
+ function filter(event: Event & { target: HTMLInputElement }) {
+ query.value = event.target.value
+ }
+
+ return { value, options: filteredOptions, filter }
+ },
+ })
+
+ // Open combobox
+ await click(getComboboxButton())
+
+ let activeIndexEl = document.querySelector('[data-test="idx"]')
+ function activeIndex() {
+ return Number(activeIndexEl?.innerHTML)
+ }
+
+ expect(activeIndex()).toEqual(0)
+
+ let options: ReturnType
+
+ await focus(getComboboxInput())
+ await type(word('Option B'))
+
+ // Option B should be active
+ options = getComboboxOptions()
+ expect(options[0]).toHaveTextContent('Option B')
+ assertActiveComboboxOption(options[0])
+
+ expect(activeIndex()).toEqual(0)
+
+ // Reveal all options again
+ await type(word('Option'))
+
+ // Option B should still be active
+ options = getComboboxOptions()
+ expect(options[1]).toHaveTextContent('Option B')
+ assertActiveComboboxOption(options[1])
+
+ expect(activeIndex()).toEqual(1)
+ })
+ )
})
describe('Mouse interactions', () => {
diff --git a/packages/@headlessui-vue/src/components/combobox/combobox.ts b/packages/@headlessui-vue/src/components/combobox/combobox.ts
index c0f26aa..7cf476e 100644
--- a/packages/@headlessui-vue/src/components/combobox/combobox.ts
+++ b/packages/@headlessui-vue/src/components/combobox/combobox.ts
@@ -232,7 +232,7 @@ export let Combobox = defineComponent({
) {
let localActiveOptionIndex = options.value.findIndex((option) => !option.dataRef.disabled)
if (localActiveOptionIndex !== -1) {
- return localActiveOptionIndex
+ activeOptionIndex.value = localActiveOptionIndex
}
}
@@ -391,6 +391,15 @@ export let Combobox = defineComponent({
options.value = adjustedState.options
activeOptionIndex.value = adjustedState.activeOptionIndex
activationTrigger.value = ActivationTrigger.Other
+
+ // If some of the DOM elements aren't ready yet, then we can retry in the next tick.
+ if (adjustedState.options.some((option) => !dom(option.dataRef.domRef))) {
+ requestAnimationFrame(() => {
+ let adjustedState = adjustOrderedState()
+ options.value = adjustedState.options
+ activeOptionIndex.value = adjustedState.activeOptionIndex
+ })
+ }
},
unregisterOption(id: string) {
// When we are unregistering the currently active option, then we also have to make sure to