compose/useDialog: add stopPropagation() to Escape handler#76861
compose/useDialog: add stopPropagation() to Escape handler#76861
stopPropagation() to Escape handler#76861Conversation
When a legacy Popover (using useDialog) is open inside a Base UI Dialog, pressing Escape should only close the Popover, not bubble up and also dismiss the parent Dialog. Adding event.stopPropagation() to the keydown handler prevents this double-close behavior. Made-with: Cursor
|
Size Change: +5 B (0%) Total Size: 7.73 MB
ℹ️ View Unchanged
|
stopPropagation() to Escape handler
|
Flaky tests detected in 07aa69c. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/23645649384
|
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
* Compose: Add stopPropagation to useDialog Escape handler When a legacy Popover (using useDialog) is open inside a Base UI Dialog, pressing Escape should only close the Popover, not bubble up and also dismiss the parent Dialog. Adding event.stopPropagation() to the keydown handler prevents this double-close behavior. Made-with: Cursor * CHANGELOG * Add tests * Add comment explaining current limitations with testing keyboard events * format
* Compose: Add stopPropagation to useDialog Escape handler When a legacy Popover (using useDialog) is open inside a Base UI Dialog, pressing Escape should only close the Popover, not bubble up and also dismiss the parent Dialog. Adding event.stopPropagation() to the keydown handler prevents this double-close behavior. Made-with: Cursor * CHANGELOG * Add tests * Add comment explaining current limitations with testing keyboard events * format
* Compose: Add stopPropagation to useDialog Escape handler When a legacy Popover (using useDialog) is open inside a Base UI Dialog, pressing Escape should only close the Popover, not bubble up and also dismiss the parent Dialog. Adding event.stopPropagation() to the keydown handler prevents this double-close behavior. Made-with: Cursor * CHANGELOG * Add tests * Add comment explaining current limitations with testing keyboard events * format
What?
Part of #76487
Extracted from #76837
Add
event.stopPropagation()to the Escape key handler in theuseDialoghook from@wordpress/compose.Why?
When a legacy Popover (which uses
useDialoginternally) is rendered inside a Base UIDialog, pressing Escape currently bubbles up and closes both the Popover and the parent Dialog in a single keystroke.This happens because the Popover's keydown handler calls
event.preventDefault()but notevent.stopPropagation(), so the Escape event continues to propagate up the DOM tree where Base UI's Dialog picks it up and also dismisses itself.The expected behavior is that Escape closes only the innermost overlay — pressing Escape once should close the Popover, and pressing it a second time should close the Dialog.
How?
Added
event.stopPropagation()immediately afterevent.preventDefault()in thecloseOnEscapeRefcallback withinuseDialog. This prevents the Escape keydown event from bubbling past the Popover to parent overlay listeners.Regression risk assessment
The
stopPropagation()only fires whenonCloseis providedThe guard clause (
currentOptions.current?.onClose) ensures that if noonClosehandler is set (e.g., the DataViews dropdown), the Escape event still propagates normally — no behavior change.Legacy Modal already checks
defaultPreventedThe
@wordpress/componentsModal'shandleEscapeKeyDownalready checks!event.defaultPrevented, so the existingpreventDefault()call already prevents it from double-closing. However,stopPropagation()adds a stronger guarantee — the event never even reaches the Modal's handler. All 27 Modal unit tests still pass, including the nested modal Escape stacking test.All 91 Popover tests still pass
Confirming no regression for the primary consumer of
useDialog.Base UI Dialog doesn't check
defaultPreventedIt listens for Escape independently. This is precisely the scenario the fix addresses: without
stopPropagation(), a Popover inside a Base UI Dialog would see both close on a single Escape. With it, only the innermost overlay closes.Nested
useDialoginstances work correctlyIf the inner dialog has
onClose, it handles Escape and stops propagation so the outer dialog doesn't fire. If the inner dialog has noonClose, the event propagates naturally to the outer one.Testing Instructions
Testing Instructions for Keyboard
Same as above — the entire test is keyboard-driven.
Use of AI Tools
Cursor + Claude Opus 4.6