diff --git a/packages/block-editor/src/components/block-tools/block-selection-button.js b/packages/block-editor/src/components/block-tools/block-selection-button.js
index d4ec0f8cf79fb6..805e41c580f950 100644
--- a/packages/block-editor/src/components/block-tools/block-selection-button.js
+++ b/packages/block-editor/src/components/block-tools/block-selection-button.js
@@ -9,7 +9,7 @@ import clsx from 'clsx';
import { dragHandle, trash } from '@wordpress/icons';
import { Button, Flex, FlexItem, ToolbarButton } from '@wordpress/components';
import { useSelect, useDispatch } from '@wordpress/data';
-import { useEffect, useRef } from '@wordpress/element';
+import { forwardRef, useEffect } from '@wordpress/element';
import {
BACKSPACE,
DELETE,
@@ -48,10 +48,11 @@ import Shuffle from '../block-toolbar/shuffle';
*
* @param {string} props Component props.
* @param {string} props.clientId Client ID of block.
+ * @param {Object} ref Reference to the component.
*
* @return {Component} The component to be rendered.
*/
-function BlockSelectionButton( { clientId, rootClientId } ) {
+function BlockSelectionButton( { clientId, rootClientId }, ref ) {
const selected = useSelect(
( select ) => {
const {
@@ -125,7 +126,6 @@ function BlockSelectionButton( { clientId, rootClientId } ) {
canMove,
} = selected;
const { setNavigationMode, removeBlock } = useDispatch( blockEditorStore );
- const ref = useRef();
// Focus the breadcrumb in navigation mode.
useEffect( () => {
@@ -164,11 +164,6 @@ function BlockSelectionButton( { clientId, rootClientId } ) {
const isEnter = keyCode === ENTER;
const isSpace = keyCode === SPACE;
const isShift = event.shiftKey;
- if ( isEscape && editorMode === 'navigation' ) {
- setNavigationMode( false );
- event.preventDefault();
- return;
- }
if ( keyCode === BACKSPACE || keyCode === DELETE ) {
removeBlock( clientId );
@@ -368,4 +363,4 @@ function BlockSelectionButton( { clientId, rootClientId } ) {
);
}
-export default BlockSelectionButton;
+export default forwardRef( BlockSelectionButton );
diff --git a/packages/block-editor/src/components/block-tools/block-toolbar-breadcrumb.js b/packages/block-editor/src/components/block-tools/block-toolbar-breadcrumb.js
index 0ae67e1be0001e..ae03bdb4f51647 100644
--- a/packages/block-editor/src/components/block-tools/block-toolbar-breadcrumb.js
+++ b/packages/block-editor/src/components/block-tools/block-toolbar-breadcrumb.js
@@ -3,6 +3,11 @@
*/
import clsx from 'clsx';
+/**
+ * WordPress dependencies
+ */
+import { forwardRef } from '@wordpress/element';
+
/**
* Internal dependencies
*/
@@ -11,10 +16,7 @@ import { PrivateBlockPopover } from '../block-popover';
import useBlockToolbarPopoverProps from './use-block-toolbar-popover-props';
import useSelectedBlockToolProps from './use-selected-block-tool-props';
-export default function BlockToolbarBreadcrumb( {
- clientId,
- __unstableContentRef,
-} ) {
+function BlockToolbarBreadcrumb( { clientId, __unstableContentRef }, ref ) {
const {
capturingClientId,
isInsertionPointVisible,
@@ -38,9 +40,12 @@ export default function BlockToolbarBreadcrumb( {
{ ...popoverProps }
>
);
}
+
+export default forwardRef( BlockToolbarBreadcrumb );
diff --git a/packages/block-editor/src/components/block-tools/index.js b/packages/block-editor/src/components/block-tools/index.js
index ad744a81cca623..0e89e7991320e0 100644
--- a/packages/block-editor/src/components/block-tools/index.js
+++ b/packages/block-editor/src/components/block-tools/index.js
@@ -81,6 +81,7 @@ export default function BlockTools( {
} = useShowBlockTools();
const {
+ clearSelectedBlock,
duplicateBlocks,
removeBlocks,
replaceBlocks,
@@ -92,6 +93,8 @@ export default function BlockTools( {
expandBlock,
} = unlock( useDispatch( blockEditorStore ) );
+ const blockSelectionButtonRef = useRef();
+
function onKeyDown( event ) {
if ( event.defaultPrevented ) {
return;
@@ -152,6 +155,39 @@ export default function BlockTools( {
// block so that focus is directed back to the beginning of the selection.
// In effect, to the user this feels like deselecting the multi-selection.
selectBlock( clientIds[ 0 ] );
+ } else if (
+ clientIds.length === 1 &&
+ event.target === blockSelectionButtonRef?.current
+ ) {
+ event.preventDefault();
+ clearSelectedBlock();
+ // If there are multiple editors, we need to find the iframe that contains our contentRef to make sure
+ // we're focusing the region that contains this editor.
+ const editorCanvas =
+ Array.from(
+ document
+ .querySelectorAll( 'iframe[name="editor-canvas"]' )
+ .values()
+ ).find( ( iframe ) => {
+ // Find the iframe that contains our contentRef
+ const iframeDocument =
+ iframe.contentDocument ||
+ iframe.contentWindow.document;
+
+ return (
+ iframeDocument ===
+ __unstableContentRef.current.ownerDocument
+ );
+ } ) ?? __unstableContentRef.current;
+
+ // The region is provivided by the editor, not the block-editor.
+ // We should send focus to the region if one is available to reuse the
+ // same interface for navigating landmarks. If no region is available,
+ // use the canvas instead.
+ const focusableWrapper =
+ editorCanvas?.closest( '[role="region"]' ) ?? editorCanvas;
+
+ focusableWrapper.focus();
}
} else if ( isMatch( 'core/block-editor/collapse-list-view', event ) ) {
// If focus is currently within a text field, such as a rich text block or other editable field,
@@ -182,7 +218,6 @@ export default function BlockTools( {
}
}
}
-
const blockToolbarRef = usePopoverScroll( __unstableContentRef );
const blockToolbarAfterRef = usePopoverScroll( __unstableContentRef );
@@ -213,6 +248,7 @@ export default function BlockTools( {
{ showBreadcrumb && (
diff --git a/packages/components/src/higher-order/navigate-regions/style.scss b/packages/components/src/higher-order/navigate-regions/style.scss
index b3a4a0c1a9d1b5..5c3767e310b8f4 100644
--- a/packages/components/src/higher-order/navigate-regions/style.scss
+++ b/packages/components/src/higher-order/navigate-regions/style.scss
@@ -1,22 +1,35 @@
// Allow the position to be easily overridden to e.g. fixed.
+
+@mixin region-selection-outline {
+ outline: 4px solid $components-color-accent;
+ outline-offset: -4px;
+}
+
+@mixin region-selection-focus {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ content: "";
+ pointer-events: none;
+ @include region-selection-outline;
+ z-index: z-index(".is-focusing-regions {region} :focus::after");
+}
+
[role="region"] {
position: relative;
+
+ // Handles the focus when we programatically send focus to this region
+ &.interface-interface-skeleton__content:focus-visible::after {
+ @include region-selection-focus;
+ }
}
.is-focusing-regions {
[role="region"]:focus::after {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- content: "";
- pointer-events: none;
- outline: 4px solid $components-color-accent;
- outline-offset: -4px;
- z-index: z-index(".is-focusing-regions {region} :focus::after");
+ @include region-selection-focus;
}
-
// Fixes for edge cases.
// Some of the regions are currently used for layout purposes as 'interface skeleton'
// items. When they're absolutely positioned or when they contain absolutely
@@ -33,7 +46,6 @@
.interface-interface-skeleton__actions .editor-layout__toggle-publish-panel,
.interface-interface-skeleton__actions .editor-layout__toggle-entities-saved-states-panel,
.editor-post-publish-panel {
- outline: 4px solid $components-color-accent;
- outline-offset: -4px;
+ @include region-selection-outline;
}
}
diff --git a/test/e2e/specs/editor/various/writing-flow.spec.js b/test/e2e/specs/editor/various/writing-flow.spec.js
index 1af46a80896f07..bd1552ad4cb66a 100644
--- a/test/e2e/specs/editor/various/writing-flow.spec.js
+++ b/test/e2e/specs/editor/various/writing-flow.spec.js
@@ -958,7 +958,7 @@ test.describe( 'Writing Flow (@firefox, @webkit)', () => {
` );
} );
- test( 'escape should toggle between edit and navigation modes', async ( {
+ test( 'escape should set select mode and then focus the canvas', async ( {
page,
writingFlowUtils,
} ) => {
@@ -975,15 +975,13 @@ test.describe( 'Writing Flow (@firefox, @webkit)', () => {
.poll( writingFlowUtils.getActiveBlockName )
.toBe( 'core/paragraph' );
- // Second escape Toggles back to Edit Mode
+ // Second escape should send focus to the canvas
await page.keyboard.press( 'Escape' );
+ // The navigation button should be hidden.
await expect( navigationButton ).toBeHidden();
- const blockToolbar = page.getByLabel( 'Block tools' );
-
- await expect( blockToolbar ).toBeVisible();
- await expect
- .poll( writingFlowUtils.getActiveBlockName )
- .toBe( 'core/paragraph' );
+ await expect(
+ page.getByRole( 'region', { name: 'Editor content' } )
+ ).toBeFocused();
} );
// Checks for regressions of https://github.com/WordPress/gutenberg/issues/40091.