diff --git a/packages/block-editor/src/components/writing-flow/use-tab-nav.js b/packages/block-editor/src/components/writing-flow/use-tab-nav.js index 00528be96aa79f..b94a6cfb86fb9d 100644 --- a/packages/block-editor/src/components/writing-flow/use-tab-nav.js +++ b/packages/block-editor/src/components/writing-flow/use-tab-nav.js @@ -91,6 +91,7 @@ export default function useTabNav() { const ref = useRefEffect( ( node ) => { function onKeyDown( event ) { if ( event.keyCode === ESCAPE && ! hasMultiSelection() ) { + event.stopPropagation(); setNavigationMode( true ); return; } diff --git a/packages/components/src/popover/index.js b/packages/components/src/popover/index.js index b0e8d4bccb7a37..befbb5b9139f78 100644 --- a/packages/components/src/popover/index.js +++ b/packages/components/src/popover/index.js @@ -23,6 +23,7 @@ import { useConstrainedTabbing, useFocusReturn, useMergeRefs, + useRefEffect, } from '@wordpress/compose'; import { close } from '@wordpress/icons'; @@ -232,7 +233,6 @@ const Popover = ( { headerTitle, onClose, - onKeyDown, children, className, noArrow = true, @@ -477,6 +477,26 @@ const Popover = ( __unstableBoundaryParent, ] ); + // Event handlers for closing the popover. + const closeEventRef = useRefEffect( + ( node ) => { + function maybeClose( event ) { + // Close on escape. + if ( event.keyCode === ESCAPE && onClose ) { + event.stopPropagation(); + onClose(); + } + } + + node.addEventListener( 'keydown', maybeClose ); + + return () => { + node.removeEventListener( 'keydown', maybeClose ); + }; + }, + [ onClose ] + ); + const constrainedTabbingRef = useConstrainedTabbing(); const focusReturnRef = useFocusReturn(); const focusOnMountRef = useFocusOnMount( focusOnMount ); @@ -484,25 +504,13 @@ const Popover = ( const mergedRefs = useMergeRefs( [ ref, containerRef, + // Don't register the event at all if there's no onClose callback. + onClose ? closeEventRef : null, focusOnMount ? constrainedTabbingRef : null, focusOnMount ? focusReturnRef : null, focusOnMount ? focusOnMountRef : null, ] ); - // Event handlers - const maybeClose = ( event ) => { - // Close on escape - if ( event.keyCode === ESCAPE && onClose ) { - event.stopPropagation(); - onClose(); - } - - // Preserve original content prop behavior - if ( onKeyDown ) { - onKeyDown( event ); - } - }; - /** * Shims an onFocusOutside callback to be compatible with a deprecated * onClickOutside prop function, if provided. @@ -587,7 +595,6 @@ const Popover = ( } ) } { ...contentProps } - onKeyDown={ maybeClose } { ...focusOutsideProps } ref={ mergedRefs } tabIndex="-1" diff --git a/packages/e2e-tests/specs/widgets/customizing-widgets.test.js b/packages/e2e-tests/specs/widgets/customizing-widgets.test.js index cf3a7b99e1dcd5..a2d38addfd7091 100644 --- a/packages/e2e-tests/specs/widgets/customizing-widgets.test.js +++ b/packages/e2e-tests/specs/widgets/customizing-widgets.test.js @@ -511,6 +511,57 @@ describe( 'Widgets Customizer', () => { "The page delivered both an 'X-Frame-Options' header and a 'Content-Security-Policy' header with a 'frame-ancestors' directive. Although the 'X-Frame-Options' header alone would have blocked embedding, it has been ignored." ); } ); + + it( 'should handle esc key events', async () => { + const widgetsPanel = await find( { + role: 'heading', + name: /Widgets/, + level: 3, + } ); + await widgetsPanel.click(); + + const footer1Section = await find( { + role: 'heading', + name: /^Footer #1/, + level: 3, + } ); + await footer1Section.click(); + + const paragraphBlock = await addBlock( 'Paragraph' ); + await page.keyboard.type( 'First Paragraph' ); + await showBlockToolbar(); + + // Open the more menu dropdown in block toolbar. + await clickBlockToolbarButton( 'Options' ); + await expect( { + role: 'menu', + name: 'Options', + } ).toBeFound(); + + // Expect pressing the Escape key to close the dropdown, + // but not close the editor. + await page.keyboard.press( 'Escape' ); + await expect( { + role: 'menu', + name: 'Options', + } ).not.toBeFound(); + await expect( paragraphBlock ).toBeVisible(); + + await paragraphBlock.focus(); + + // Expect pressing the Escape key to enter navigation mode, + // but not close the editor. + await page.keyboard.press( 'Escape' ); + await expect( { + text: /^You are currently in navigation mode\./, + selector: '*[aria-live="polite"][aria-relevant="additions text"]', + } ).toBeFound(); + await expect( paragraphBlock ).toBeVisible(); + + expect( console ).toHaveWarned( + "The page delivered both an 'X-Frame-Options' header and a 'Content-Security-Policy' header with a 'frame-ancestors' directive. Although the 'X-Frame-Options' header alone would have blocked embedding, it has been ignored." + ); + } ); } ); /**