Skip to content

Commit 060f37b

Browse files
Fix use of undefined and displayValue in Combobox (#1865)
* Work around Vue multi-source + undefined bug * Update changelog
1 parent b267ce3 commit 060f37b

File tree

4 files changed

+93
-5
lines changed

4 files changed

+93
-5
lines changed

packages/@headlessui-react/src/components/combobox/combobox.test.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,46 @@ describe('Rendering', () => {
460460
})
461461
)
462462

463+
it(
464+
'selecting an option puts the display value into Combobox.Input when displayValue is provided (when value is undefined)',
465+
suppressConsoleLogs(async () => {
466+
function Example() {
467+
let [value, setValue] = useState(undefined)
468+
469+
return (
470+
<Combobox value={value} onChange={setValue}>
471+
<Combobox.Input
472+
onChange={NOOP}
473+
displayValue={(str?: string) => str?.toUpperCase() ?? ''}
474+
/>
475+
<Combobox.Button>Trigger</Combobox.Button>
476+
<Combobox.Options>
477+
<Combobox.Option value="a">Option A</Combobox.Option>
478+
<Combobox.Option value="b">Option B</Combobox.Option>
479+
<Combobox.Option value="c">Option C</Combobox.Option>
480+
</Combobox.Options>
481+
</Combobox>
482+
)
483+
}
484+
485+
render(<Example />)
486+
487+
// Focus the input
488+
await focus(getComboboxInput())
489+
490+
// Type in it
491+
await type(word('A'), getComboboxInput())
492+
493+
// Stop typing (and clear the input)
494+
await press(Keys.Escape, getComboboxInput())
495+
496+
// Focus the body (so the input loses focus)
497+
await focus(document.body)
498+
499+
expect(getComboboxInput()).toHaveValue('')
500+
})
501+
)
502+
463503
it(
464504
'conditionally rendering the input should allow changing the display value',
465505
suppressConsoleLogs(async () => {

packages/@headlessui-vue/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10-
- Nothing yet!
10+
### Fixed
11+
12+
- Call `displayValue` with a v-model of `ref(undefined)` on `ComboboxInput` ([#1865](https://github.com/tailwindlabs/headlessui/pull/1865))
1113

1214
## [1.7.2] - 2022-09-15
1315

packages/@headlessui-vue/src/components/combobox/combobox.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,43 @@ describe('Rendering', () => {
493493
})
494494
)
495495

496+
// This really is a bug in Vue but we have a workaround for it
497+
it(
498+
'selecting an option puts the display value into Combobox.Input when displayValue is provided (when v-model is undefined)',
499+
suppressConsoleLogs(async () => {
500+
let Example = defineComponent({
501+
template: html`
502+
<Combobox v-model="value">
503+
<ComboboxInput :displayValue="(str) => str?.toUpperCase() ?? ''" />
504+
<ComboboxButton>Trigger</ComboboxButton>
505+
<ComboboxOptions>
506+
<ComboboxOption value="a">Option A</ComboboxOption>
507+
<ComboboxOption value="b">Option B</ComboboxOption>
508+
<ComboboxOption value="c">Option C</ComboboxOption>
509+
</ComboboxOptions>
510+
</Combobox>
511+
`,
512+
setup: () => ({ value: ref(undefined) }),
513+
})
514+
515+
renderTemplate(Example)
516+
517+
// Focus the input
518+
await focus(getComboboxInput())
519+
520+
// Type in it
521+
await type(word('A'), getComboboxInput())
522+
523+
// Stop typing (and clear the input)
524+
await press(Keys.Escape, getComboboxInput())
525+
526+
// Focus the body (so the input loses focus)
527+
await focus(document.body)
528+
529+
expect(getComboboxInput()).toHaveValue('')
530+
})
531+
)
532+
496533
it('conditionally rendering the input should allow changing the display value', async () => {
497534
let Example = defineComponent({
498535
template: html`

packages/@headlessui-vue/src/components/combobox/combobox.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -629,11 +629,20 @@ export let ComboboxInput = defineComponent({
629629
}
630630
}
631631

632+
// Workaround Vue bug where watching [ref(undefined)] is not fired immediately even when value is true
633+
const __fixVueImmediateWatchBug__ = ref('')
634+
632635
onMounted(() => {
633-
watch([api.value], () => (currentValue.value = getCurrentValue()), {
634-
flush: 'sync',
635-
immediate: true,
636-
})
636+
watch(
637+
[api.value, __fixVueImmediateWatchBug__],
638+
() => {
639+
currentValue.value = getCurrentValue()
640+
},
641+
{
642+
flush: 'sync',
643+
immediate: true,
644+
}
645+
)
637646

638647
watch(
639648
[currentValue, api.comboboxState],

0 commit comments

Comments
 (0)