Skip to content

Commit 4ed344a

Browse files
Combobox improvements (#1101)
* ensure combobox option gets activated on hover (while static) * rename combobox test file * remove leftover `horizontal` prop * remove unnecessary handleLeave calls These are implemented on the `Combobox.Option` instead of the `Combobox.Options`. This allows you to have additional visual padding between `Combobox.Options` and `Combobox.Option` and if you hover over that area then the option becomes inactive. If we implement it on the `Combobox.Options` instead then this isn't _that_ easy to do. We can do it by checking the target and whether or not it is inside a headlessui-combobox-option. This would only have a single listener instead of `N` listeners though. Potential improvements! * implement `hold` in favor of `latestActiveOption` * update changelog * Allow Escape to bubble when options is static You’ve taken control of the open/close state yourself in which case this should be allowed to be handled by other event handlers Co-authored-by: Jordan Pittman <[email protected]>
1 parent dcf2f75 commit 4ed344a

File tree

5 files changed

+298
-132
lines changed

5 files changed

+298
-132
lines changed

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818

1919
### Added
2020

21-
- Add `Combobox` component ([#1047](https://github.com/tailwindlabs/headlessui/pull/1047), [#1099](https://github.com/tailwindlabs/headlessui/pull/1099))
21+
- Add `Combobox` component ([#1047](https://github.com/tailwindlabs/headlessui/pull/1047), [#1099](https://github.com/tailwindlabs/headlessui/pull/1099), [#1101](https://github.com/tailwindlabs/headlessui/pull/1101))
2222

2323
## [Unreleased - @headlessui/vue]
2424

@@ -32,7 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3232

3333
### Added
3434

35-
- Add `Combobox` component ([#1047](https://github.com/tailwindlabs/headlessui/pull/1047), [#1099](https://github.com/tailwindlabs/headlessui/pull/1099))
35+
- Add `Combobox` component ([#1047](https://github.com/tailwindlabs/headlessui/pull/1047), [#1099](https://github.com/tailwindlabs/headlessui/pull/1099), [#1101](https://github.com/tailwindlabs/headlessui/pull/1101))
3636

3737
## [@headlessui/react@v1.4.3] - 2022-01-14
3838

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

Lines changed: 106 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1387,6 +1387,71 @@ describe('Keyboard interactions', () => {
13871387
assertActiveElement(getComboboxInput())
13881388
})
13891389
)
1390+
1391+
it(
1392+
'Static options should allow escape to bubble',
1393+
suppressConsoleLogs(async () => {
1394+
render(
1395+
<Combobox value="test" onChange={console.log}>
1396+
<Combobox.Input onChange={NOOP} />
1397+
<Combobox.Button>Trigger</Combobox.Button>
1398+
<Combobox.Options static>
1399+
<Combobox.Option value="a">Option A</Combobox.Option>
1400+
<Combobox.Option value="b">Option B</Combobox.Option>
1401+
<Combobox.Option value="c">Option C</Combobox.Option>
1402+
</Combobox.Options>
1403+
</Combobox>
1404+
)
1405+
1406+
let spy = jest.fn()
1407+
1408+
window.addEventListener(
1409+
'keydown',
1410+
(evt) => {
1411+
if (evt.key === 'Escape') {
1412+
spy()
1413+
}
1414+
},
1415+
{ capture: true }
1416+
)
1417+
1418+
window.addEventListener('keydown', (evt) => {
1419+
if (evt.key === 'Escape') {
1420+
spy()
1421+
}
1422+
})
1423+
1424+
// Open combobox
1425+
await click(getComboboxButton())
1426+
1427+
// Verify it is visible
1428+
assertComboboxButton({ state: ComboboxState.Visible })
1429+
assertComboboxList({
1430+
state: ComboboxState.Visible,
1431+
attributes: { id: 'headlessui-combobox-options-3' },
1432+
})
1433+
assertActiveElement(getComboboxInput())
1434+
assertComboboxButtonLinkedWithCombobox()
1435+
1436+
// Re-focus the button
1437+
getComboboxButton()?.focus()
1438+
assertActiveElement(getComboboxButton())
1439+
1440+
// Close combobox
1441+
await press(Keys.Escape)
1442+
1443+
// TODO: Verify it is rendered — with static it's not visible or invisible from an assert perspective
1444+
// assertComboboxButton({ state: ComboboxState.InvisibleUnmounted })
1445+
// assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
1446+
1447+
// Verify the input is focused again
1448+
assertActiveElement(getComboboxInput())
1449+
1450+
// The external event handler should've been called twice
1451+
// Once in the capture phase and once in the bubble phase
1452+
expect(spy).toHaveBeenCalledTimes(2)
1453+
})
1454+
)
13901455
})
13911456

13921457
describe('`ArrowDown` key', () => {
@@ -3778,6 +3843,36 @@ describe('Mouse interactions', () => {
37783843
})
37793844
)
37803845

3846+
it(
3847+
'should be possible to hover an option and make it active when using `static`',
3848+
suppressConsoleLogs(async () => {
3849+
render(
3850+
<Combobox value="test" onChange={console.log}>
3851+
<Combobox.Input onChange={NOOP} />
3852+
<Combobox.Button>Trigger</Combobox.Button>
3853+
<Combobox.Options static>
3854+
<Combobox.Option value="alice">alice</Combobox.Option>
3855+
<Combobox.Option value="bob">bob</Combobox.Option>
3856+
<Combobox.Option value="charlie">charlie</Combobox.Option>
3857+
</Combobox.Options>
3858+
</Combobox>
3859+
)
3860+
3861+
let options = getComboboxOptions()
3862+
// We should be able to go to the second option
3863+
await mouseMove(options[1])
3864+
assertActiveComboboxOption(options[1])
3865+
3866+
// We should be able to go to the first option
3867+
await mouseMove(options[0])
3868+
assertActiveComboboxOption(options[0])
3869+
3870+
// We should be able to go to the last option
3871+
await mouseMove(options[2])
3872+
assertActiveComboboxOption(options[2])
3873+
})
3874+
)
3875+
37813876
it(
37823877
'should make a combobox option active when you move the mouse over it',
37833878
suppressConsoleLogs(async () => {
@@ -4139,24 +4234,17 @@ describe('Mouse interactions', () => {
41394234
)
41404235

41414236
it(
4142-
'Combobox preserves the latest known active option after an option becomes inactive',
4237+
'should be possible to hold the last active option',
41434238
suppressConsoleLogs(async () => {
41444239
render(
4145-
<Combobox value="test" onChange={console.log}>
4146-
{({ open, latestActiveOption }) => (
4147-
<>
4148-
<Combobox.Input onChange={NOOP} />
4149-
<Combobox.Button>Trigger</Combobox.Button>
4150-
<div id="latestActiveOption">{latestActiveOption}</div>
4151-
{open && (
4152-
<Combobox.Options>
4153-
<Combobox.Option value="a">Option A</Combobox.Option>
4154-
<Combobox.Option value="b">Option B</Combobox.Option>
4155-
<Combobox.Option value="c">Option C</Combobox.Option>
4156-
</Combobox.Options>
4157-
)}
4158-
</>
4159-
)}
4240+
<Combobox value="test" onChange={console.log} hold>
4241+
<Combobox.Input onChange={NOOP} />
4242+
<Combobox.Button>Trigger</Combobox.Button>
4243+
<Combobox.Options>
4244+
<Combobox.Option value="a">Option A</Combobox.Option>
4245+
<Combobox.Option value="b">Option B</Combobox.Option>
4246+
<Combobox.Option value="c">Option C</Combobox.Option>
4247+
</Combobox.Options>
41604248
</Combobox>
41614249
)
41624250

@@ -4181,24 +4269,19 @@ describe('Mouse interactions', () => {
41814269

41824270
// Verify that the first combobox option is active
41834271
assertActiveComboboxOption(options[0])
4184-
expect(document.getElementById('latestActiveOption')!.textContent).toBe('a')
41854272

41864273
// Focus the second item
41874274
await mouseMove(options[1])
41884275

41894276
// Verify that the second combobox option is active
41904277
assertActiveComboboxOption(options[1])
4191-
expect(document.getElementById('latestActiveOption')!.textContent).toBe('b')
41924278

41934279
// Move the mouse off of the second combobox option
41944280
await mouseLeave(options[1])
41954281
await mouseMove(document.body)
41964282

4197-
// Verify that the second combobox option is NOT active
4198-
assertNoActiveComboboxOption()
4199-
4200-
// But the last known active option is still recorded
4201-
expect(document.getElementById('latestActiveOption')!.textContent).toBe('b')
4283+
// Verify that the second combobox option is still active
4284+
assertActiveComboboxOption(options[1])
42024285
})
42034286
)
42044287
})

0 commit comments

Comments
 (0)