Add optional onClose callback to Combobox component (#3122)
* add optional `onClose` callback to `Combobox` component * update changelog * add tests to ensure `onClose` is called when `Combobox` closes
This commit is contained in:
@@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Ensure anchored components are properly stacked on top of `Dialog` components ([#3111](https://github.com/tailwindlabs/headlessui/pull/3111))
|
||||
- Move focus to `ListboxOptions` and `MenuItems` when they are rendered later ([#3112](https://github.com/tailwindlabs/headlessui/pull/3112))
|
||||
- Ensure anchored components are always rendered in a stacking context ([#3115](https://github.com/tailwindlabs/headlessui/pull/3115))
|
||||
- Add optional `onClose` callback to `Combobox` component ([#3122](https://github.com/tailwindlabs/headlessui/pull/3122))
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
@@ -542,6 +542,7 @@ describe('Rendering', () => {
|
||||
it(
|
||||
'should close the Combobox when the input is blurred',
|
||||
suppressConsoleLogs(async () => {
|
||||
let closeHandler = jest.fn()
|
||||
let data = [
|
||||
{ id: 1, name: 'alice', label: 'Alice' },
|
||||
{ id: 2, name: 'bob', label: 'Bob' },
|
||||
@@ -549,7 +550,7 @@ describe('Rendering', () => {
|
||||
]
|
||||
|
||||
render(
|
||||
<Combobox<(typeof data)[number]> name="assignee" by="id">
|
||||
<Combobox<(typeof data)[number]> name="assignee" by="id" onClose={closeHandler}>
|
||||
<Combobox.Input onChange={NOOP} />
|
||||
<Combobox.Button />
|
||||
<Combobox.Options>
|
||||
@@ -569,7 +570,9 @@ describe('Rendering', () => {
|
||||
assertComboboxList({ state: ComboboxState.Visible })
|
||||
|
||||
// Close the combobox
|
||||
expect(closeHandler).toHaveBeenCalledTimes(0)
|
||||
await blur(getComboboxInput())
|
||||
expect(closeHandler).toHaveBeenCalledTimes(1)
|
||||
|
||||
// Verify it is closed
|
||||
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
|
||||
@@ -2825,6 +2828,7 @@ describe.each([{ virtual: true }, { virtual: false }])(
|
||||
'should be possible to close the combobox with Enter and choose the active combobox option',
|
||||
suppressConsoleLogs(async () => {
|
||||
let handleChange = jest.fn()
|
||||
let closeHandler = jest.fn()
|
||||
|
||||
function Example() {
|
||||
let [value, setValue] = useState<string | undefined>(undefined)
|
||||
@@ -2833,6 +2837,7 @@ describe.each([{ virtual: true }, { virtual: false }])(
|
||||
<MyCombobox
|
||||
comboboxProps={{
|
||||
value,
|
||||
onClose: closeHandler,
|
||||
onChange(value: string | undefined) {
|
||||
setValue(value)
|
||||
handleChange(value)
|
||||
@@ -2861,7 +2866,9 @@ describe.each([{ virtual: true }, { virtual: false }])(
|
||||
await mouseMove(options[0])
|
||||
|
||||
// Choose option, and close combobox
|
||||
expect(closeHandler).toHaveBeenCalledTimes(0)
|
||||
await press(Keys.Enter)
|
||||
expect(closeHandler).toHaveBeenCalledTimes(1)
|
||||
|
||||
// Verify it is closed
|
||||
assertComboboxButton({ state: ComboboxState.InvisibleUnmounted })
|
||||
@@ -4883,7 +4890,8 @@ describe.each([{ virtual: true }, { virtual: false }])('Mouse interactions %s',
|
||||
it(
|
||||
'should be possible to click outside of the combobox which should close the combobox (even if we press the combobox button)',
|
||||
suppressConsoleLogs(async () => {
|
||||
render(<MyCombobox />)
|
||||
let closeHandler = jest.fn()
|
||||
render(<MyCombobox comboboxProps={{ onClose: closeHandler }} />)
|
||||
|
||||
// Open combobox
|
||||
await click(getComboboxButton())
|
||||
@@ -4891,7 +4899,9 @@ describe.each([{ virtual: true }, { virtual: false }])('Mouse interactions %s',
|
||||
assertActiveElement(getComboboxInput())
|
||||
|
||||
// Click the combobox button again
|
||||
expect(closeHandler).toHaveBeenCalledTimes(0)
|
||||
await click(getComboboxButton())
|
||||
expect(closeHandler).toHaveBeenCalledTimes(1)
|
||||
|
||||
// Should be closed now
|
||||
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
|
||||
|
||||
@@ -588,6 +588,8 @@ export type ComboboxProps<
|
||||
disabled?: (value: NoInfer<TValue>) => boolean
|
||||
} | null
|
||||
|
||||
onClose?(): void
|
||||
|
||||
__demoMode?: boolean
|
||||
}
|
||||
>
|
||||
@@ -605,6 +607,7 @@ function ComboboxFn<TValue, TTag extends ElementType = typeof DEFAULT_COMBOBOX_T
|
||||
name,
|
||||
by,
|
||||
disabled = providedDisabled || false,
|
||||
onClose,
|
||||
__demoMode = false,
|
||||
multiple = false,
|
||||
immediate = false,
|
||||
@@ -771,6 +774,7 @@ function ComboboxFn<TValue, TTag extends ElementType = typeof DEFAULT_COMBOBOX_T
|
||||
let closeCombobox = useEvent(() => {
|
||||
dispatch({ type: ActionTypes.CloseCombobox })
|
||||
defaultToFirstOption.current = false
|
||||
onClose?.()
|
||||
})
|
||||
|
||||
let goToOption = useEvent((focus, idx, trigger) => {
|
||||
|
||||
Reference in New Issue
Block a user