Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add immediate prop to <Combobox /> for immediately opening the Combobox when the input receives focus #2686

Merged
merged 10 commits into from
Aug 31, 2023
Merged
4 changes: 4 additions & 0 deletions packages/@headlessui-react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add support for `role="alertdialog"` to `<Dialog>` component ([#2709](https://github.com/tailwindlabs/headlessui/pull/2709))
- Ensure blurring the `Combobox.Input` component closes the `Combobox` ([#2712](https://github.com/tailwindlabs/headlessui/pull/2712))

### Added

- Add `immediate` prop to `<Combobox />` for immediately opening the Combobox when the `input` receives focus ([#2686](https://github.com/tailwindlabs/headlessui/pull/2686))

## [1.7.17] - 2023-08-17

### Fixed
Expand Down
199 changes: 199 additions & 0 deletions packages/@headlessui-react/src/components/combobox/combobox.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4698,6 +4698,176 @@ describe('Mouse interactions', () => {
})
)

it(
'should be possible to open the combobox by focusing the input with immediate mode enabled',
suppressConsoleLogs(async () => {
render(
<Combobox value="test" immediate>
<Combobox.Input onChange={NOOP} />
<Combobox.Button>Trigger</Combobox.Button>
<Combobox.Options>
<Combobox.Option value="a">Option A</Combobox.Option>
<Combobox.Option value="b">Option B</Combobox.Option>
<Combobox.Option value="c">Option C</Combobox.Option>
</Combobox.Options>
</Combobox>
)

assertComboboxButton({
state: ComboboxState.InvisibleUnmounted,
attributes: { id: 'headlessui-combobox-button-2' },
})
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })

// Focus the input
await focus(getComboboxInput())

// Verify it is visible
assertComboboxButton({ state: ComboboxState.Visible })
assertComboboxList({
state: ComboboxState.Visible,
attributes: { id: 'headlessui-combobox-options-3' },
})
assertActiveElement(getComboboxInput())
assertComboboxButtonLinkedWithCombobox()

// Verify we have combobox options
let options = getComboboxOptions()
expect(options).toHaveLength(3)
options.forEach((option) => assertComboboxOption(option))
})
)

it(
'should not be possible to open the combobox by focusing the input with immediate mode disabled',
suppressConsoleLogs(async () => {
render(
<Combobox value="test">
<Combobox.Input onChange={NOOP} />
<Combobox.Button>Trigger</Combobox.Button>
<Combobox.Options>
<Combobox.Option value="a">Option A</Combobox.Option>
<Combobox.Option value="b">Option B</Combobox.Option>
<Combobox.Option value="c">Option C</Combobox.Option>
</Combobox.Options>
</Combobox>
)

assertComboboxButton({
state: ComboboxState.InvisibleUnmounted,
attributes: { id: 'headlessui-combobox-button-2' },
})
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })

// Focus the input
await focus(getComboboxInput())

// Verify it is invisible
assertComboboxButton({
state: ComboboxState.InvisibleUnmounted,
attributes: { id: 'headlessui-combobox-button-2' },
})
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
})
)

it(
'should not be possible to open the combobox by focusing the input with immediate mode enabled when button is disabled',
suppressConsoleLogs(async () => {
render(
<Combobox value="test" disabled immediate>
<Combobox.Input onChange={NOOP} />
<Combobox.Button>Trigger</Combobox.Button>
<Combobox.Options>
<Combobox.Option value="a">Option A</Combobox.Option>
<Combobox.Option value="b">Option B</Combobox.Option>
<Combobox.Option value="c">Option C</Combobox.Option>
</Combobox.Options>
</Combobox>
)

assertComboboxButton({
state: ComboboxState.InvisibleUnmounted,
attributes: { id: 'headlessui-combobox-button-2' },
})
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })

// Focus the input
await focus(getComboboxInput())

// Verify it is invisible
assertComboboxButton({
state: ComboboxState.InvisibleUnmounted,
attributes: { id: 'headlessui-combobox-button-2' },
})
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
})
)

it(
'should be possible to close a combobox on click with immediate mode enabled',
suppressConsoleLogs(async () => {
render(
<Combobox value="test" immediate>
<Combobox.Input onChange={NOOP} />
<Combobox.Button>Trigger</Combobox.Button>
<Combobox.Options>
<Combobox.Option value="a">Option A</Combobox.Option>
<Combobox.Option value="b">Option B</Combobox.Option>
<Combobox.Option value="c">Option C</Combobox.Option>
</Combobox.Options>
</Combobox>
)

// Open combobox
await click(getComboboxButton())

// Verify it is visible
assertComboboxButton({ state: ComboboxState.Visible })

// Click to close
await click(getComboboxButton())

// Verify it is closed
assertComboboxButton({ state: ComboboxState.InvisibleUnmounted })
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
assertActiveElement(getComboboxInput())
})
)

it(
'should be possible to close a focused combobox on click with immediate mode enabled',
suppressConsoleLogs(async () => {
render(
<Combobox value="test" immediate>
<Combobox.Input onChange={NOOP} />
<Combobox.Button>Trigger</Combobox.Button>
<Combobox.Options>
<Combobox.Option value="a">Option A</Combobox.Option>
<Combobox.Option value="b">Option B</Combobox.Option>
<Combobox.Option value="c">Option C</Combobox.Option>
</Combobox.Options>
</Combobox>
)
assertComboboxButton({ state: ComboboxState.InvisibleUnmounted })

// Open combobox by focusing input
await focus(getComboboxInput())
assertActiveElement(getComboboxInput())

// Verify it is visible
assertComboboxButton({ state: ComboboxState.Visible })

// Click to close
await click(getComboboxButton())

// Verify it is closed
assertComboboxButton({ state: ComboboxState.InvisibleUnmounted })
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
assertActiveElement(getComboboxInput())
})
)

it(
'should be possible to open the combobox on click',
suppressConsoleLogs(async () => {
Expand Down Expand Up @@ -5358,6 +5528,35 @@ describe('Mouse interactions', () => {
})
)

it(
'should be possible to click a combobox option, which closes the combobox with immediate mode enabled',
suppressConsoleLogs(async () => {
render(
<Combobox value="test" immediate>
<Combobox.Input onChange={NOOP} />
<Combobox.Button>Trigger</Combobox.Button>
<Combobox.Options>
<Combobox.Option value="a">Option A</Combobox.Option>
<Combobox.Option value="b">Option B</Combobox.Option>
<Combobox.Option value="c">Option C</Combobox.Option>
</Combobox.Options>
</Combobox>
)

// Open combobox by focusing input
await focus(getComboboxInput())
assertActiveElement(getComboboxInput())

assertComboboxList({ state: ComboboxState.Visible })

let options = getComboboxOptions()

// We should be able to click the first option
await click(options[1])
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
})
)

it(
'should be possible to click a disabled combobox option, which is a no-op',
suppressConsoleLogs(async () => {
Expand Down
Loading