Ensure the exposed activeIndex is up to date for the Combobox component (#2463)

* ensure the exposed `activeIndex` is up to date

* update changelog
This commit is contained in:
Robin Malfait
2023-04-28 15:11:18 +02:00
committed by GitHub
parent 4c102946fe
commit 8e558a7087
3 changed files with 80 additions and 1 deletions
+1
View File
@@ -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
@@ -4670,6 +4670,75 @@ describe('Keyboard interactions', () => {
)
})
})
it(
'should sync the active index properly',
suppressConsoleLogs(async () => {
renderTemplate({
template: html`
<Combobox v-model="value" v-slot="{ activeIndex }">
<ComboboxInput @input="filter" />
<ComboboxButton>Trigger</ComboboxButton>
<span data-test="idx">{{ activeIndex }}</span>
<ComboboxOptions>
<ComboboxOption v-for="option in options" :value="option" :key="option"
>{{ option }}</ComboboxOption
>
</ComboboxOptions>
</Combobox>
`,
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<typeof getComboboxOptions>
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', () => {
@@ -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