diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9794709..abe3b4a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `by` prop for `Listbox`, `Combobox` and `RadioGroup` ([#1482](https://github.com/tailwindlabs/headlessui/pull/1482))
- Add `@headlessui/tailwindcss` plugin ([#1487](https://github.com/tailwindlabs/headlessui/pull/1487))
+### Fixed
+
+- Ensure `Escape` propagates correctly in `Combobox` component ([#1511](https://github.com/tailwindlabs/headlessui/pull/1511))
+
## [Unreleased - @headlessui/vue]
### Added
@@ -19,6 +23,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `by` prop for `Listbox`, `Combobox` and `RadioGroup` ([#1482](https://github.com/tailwindlabs/headlessui/pull/1482))
- Add `@headlessui/tailwindcss` plugin ([#1487](https://github.com/tailwindlabs/headlessui/pull/1487))
+### Fixed
+
+- Ensure `Escape` propagates correctly in `Combobox` component ([#1511](https://github.com/tailwindlabs/headlessui/pull/1511))
+
## [Unreleased - @headlessui/tailwindcss]
- Nothing yet!
diff --git a/packages/@headlessui-react/src/components/combobox/combobox.test.tsx b/packages/@headlessui-react/src/components/combobox/combobox.test.tsx
index 8284245..fd33475 100644
--- a/packages/@headlessui-react/src/components/combobox/combobox.test.tsx
+++ b/packages/@headlessui-react/src/components/combobox/combobox.test.tsx
@@ -1499,6 +1499,64 @@ describe('Keyboard interactions', () => {
assertActiveElement(getComboboxInput())
})
)
+
+ it(
+ 'should not propagate the Escape event when the combobox is open',
+ suppressConsoleLogs(async () => {
+ let handleKeyDown = jest.fn()
+ render(
+
+
+
+ Trigger
+
+ Option A
+ Option B
+ Option C
+
+
+
+ )
+
+ // Open combobox
+ await click(getComboboxButton())
+
+ // Close combobox
+ await press(Keys.Escape)
+
+ // We should never see the Escape event
+ expect(handleKeyDown).toHaveBeenCalledTimes(0)
+ })
+ )
+
+ it(
+ 'should propagate the Escape event when the combobox is closed',
+ suppressConsoleLogs(async () => {
+ let handleKeyDown = jest.fn()
+ render(
+
+
+
+ Trigger
+
+ Option A
+ Option B
+ Option C
+
+
+
+ )
+
+ // Focus the input field
+ await focus(getComboboxInput())
+
+ // Close combobox
+ await press(Keys.Escape)
+
+ // We should never see the Escape event
+ expect(handleKeyDown).toHaveBeenCalledTimes(1)
+ })
+ )
})
describe('`ArrowDown` key', () => {
diff --git a/packages/@headlessui-react/src/components/combobox/combobox.tsx b/packages/@headlessui-react/src/components/combobox/combobox.tsx
index df98d32..efad64f 100644
--- a/packages/@headlessui-react/src/components/combobox/combobox.tsx
+++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx
@@ -621,6 +621,7 @@ let Input = forwardRefWithAs(function Input<
case Keys.Backspace:
case Keys.Delete:
+ if (data.comboboxState !== ComboboxState.Open) return
if (data.mode !== ValueMode.Single) return
if (!data.nullable) return
@@ -707,6 +708,7 @@ let Input = forwardRefWithAs(function Input<
return actions.goToOption(Focus.Last)
case Keys.Escape:
+ if (data.comboboxState !== ComboboxState.Open) return
event.preventDefault()
if (data.optionsRef.current && !data.optionsPropsRef.current.static) {
event.stopPropagation()
@@ -714,6 +716,7 @@ let Input = forwardRefWithAs(function Input<
return actions.closeCombobox()
case Keys.Tab:
+ if (data.comboboxState !== ComboboxState.Open) return
actions.selectActiveOption()
actions.closeCombobox()
break
@@ -830,6 +833,7 @@ let Button = forwardRefWithAs(function Button data.inputRef.current?.focus({ preventScroll: true }))
case Keys.Escape:
+ if (data.comboboxState !== ComboboxState.Open) return
event.preventDefault()
if (data.optionsRef.current && !data.optionsPropsRef.current.static) {
event.stopPropagation()
diff --git a/packages/@headlessui-vue/src/components/combobox/combobox.test.ts b/packages/@headlessui-vue/src/components/combobox/combobox.test.ts
index c579fb0..ca10160 100644
--- a/packages/@headlessui-vue/src/components/combobox/combobox.test.ts
+++ b/packages/@headlessui-vue/src/components/combobox/combobox.test.ts
@@ -1644,6 +1644,74 @@ describe('Keyboard interactions', () => {
assertActiveElement(getComboboxInput())
})
)
+
+ it(
+ 'should not propagate the Escape event when the combobox is open',
+ suppressConsoleLogs(async () => {
+ let handleKeyDown = jest.fn()
+ renderTemplate({
+ template: html`
+
+
+ Trigger
+
+ Option A
+ Option B
+ Option C
+
+
+ `,
+ setup: () => ({ value: ref(null) }),
+ })
+
+ window.addEventListener('keydown', handleKeyDown)
+
+ // Open combobox
+ await click(getComboboxButton())
+
+ // Close combobox
+ await press(Keys.Escape)
+
+ // We should never see the Escape event
+ expect(handleKeyDown).toHaveBeenCalledTimes(0)
+
+ window.removeEventListener('keydown', handleKeyDown)
+ })
+ )
+
+ it(
+ 'should propagate the Escape event when the combobox is closed',
+ suppressConsoleLogs(async () => {
+ let handleKeyDown = jest.fn()
+ renderTemplate({
+ template: html`
+
+
+ Trigger
+
+ Option A
+ Option B
+ Option C
+
+
+ `,
+ setup: () => ({ value: ref(null) }),
+ })
+
+ window.addEventListener('keydown', handleKeyDown)
+
+ // Focus the input field
+ await focus(getComboboxInput())
+
+ // Close combobox
+ await press(Keys.Escape)
+
+ // We should never see the Escape event
+ expect(handleKeyDown).toHaveBeenCalledTimes(1)
+
+ window.removeEventListener('keydown', handleKeyDown)
+ })
+ )
})
describe('`ArrowDown` key', () => {
diff --git a/packages/@headlessui-vue/src/components/combobox/combobox.ts b/packages/@headlessui-vue/src/components/combobox/combobox.ts
index 5e70ac2..29658ab 100644
--- a/packages/@headlessui-vue/src/components/combobox/combobox.ts
+++ b/packages/@headlessui-vue/src/components/combobox/combobox.ts
@@ -576,6 +576,7 @@ export let ComboboxButton = defineComponent({
return
case Keys.Escape:
+ if (api.comboboxState.value !== ComboboxStates.Open) return
event.preventDefault()
if (api.optionsRef.value && !api.optionsPropsRef.value.static) {
event.stopPropagation()
@@ -649,6 +650,7 @@ export let ComboboxInput = defineComponent({
case Keys.Backspace:
case Keys.Delete:
+ if (api.comboboxState.value !== ComboboxStates.Open) return
if (api.mode.value !== ValueMode.Single) return
if (!api.nullable.value) return
@@ -725,6 +727,7 @@ export let ComboboxInput = defineComponent({
return api.goToOption(Focus.Last)
case Keys.Escape:
+ if (api.comboboxState.value !== ComboboxStates.Open) return
event.preventDefault()
if (api.optionsRef.value && !api.optionsPropsRef.value.static) {
event.stopPropagation()
@@ -733,6 +736,7 @@ export let ComboboxInput = defineComponent({
break
case Keys.Tab:
+ if (api.comboboxState.value !== ComboboxStates.Open) return
api.selectActiveOption()
api.closeCombobox()
break