From 5d7bda72ab6afe24e88018136e0aa856e74ce1bf Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Wed, 9 Aug 2023 14:04:48 -0500 Subject: [PATCH 01/25] Move Selected Block Tools into the header toolbar in the DOM but preserve all visual interactions This is a big commit with a large potential for bugs. Think of this as a spike or POC for now. There'll be a lot to address. Some general TODOs: - [ ] Refactor shared code between empty-block-inserter and selected-block-tools - [ ] More explicitly pass the popover slot and content ref into the SelectedBlockTools - [ ] Shortcut/keystrokes for returning from the toolbar to where the cursor was before moving into the toolbar - [ ] Inline Tools either move to the top toolbar or get inserted into the main block toolbar (think image caption formatting tools) - [ ] Visual styles for the top toolbar --- .../block-tools/empty-block-inserter.js | 182 ++++++++++++++++++ .../src/components/block-tools/index.js | 18 +- ...ock-popover.js => selected-block-tools.js} | 63 +++--- packages/block-editor/src/components/index.js | 1 + .../components/header/header-toolbar/index.js | 90 +++++---- 5 files changed, 258 insertions(+), 96 deletions(-) create mode 100644 packages/block-editor/src/components/block-tools/empty-block-inserter.js rename packages/block-editor/src/components/block-tools/{selected-block-popover.js => selected-block-tools.js} (82%) diff --git a/packages/block-editor/src/components/block-tools/empty-block-inserter.js b/packages/block-editor/src/components/block-tools/empty-block-inserter.js new file mode 100644 index 00000000000000..30e5b43fb499fd --- /dev/null +++ b/packages/block-editor/src/components/block-tools/empty-block-inserter.js @@ -0,0 +1,182 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { isUnmodifiedDefaultBlock } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { store as blockEditorStore } from '../../store'; +import BlockPopover from '../block-popover'; +import useBlockToolbarPopoverProps from './use-block-toolbar-popover-props'; +import Inserter from '../inserter'; + +function selector( select ) { + const { + __unstableGetEditorMode, + hasMultiSelection, + isTyping, + getLastMultiSelectedBlockClientId, + } = select( blockEditorStore ); + + return { + editorMode: __unstableGetEditorMode(), + hasMultiSelection: hasMultiSelection(), + isTyping: isTyping(), + lastClientId: hasMultiSelection() + ? getLastMultiSelectedBlockClientId() + : null, + }; +} + +function EmptyBlockInserter( { + clientId, + rootClientId, + isEmptyDefaultBlock, + capturingClientId, + __unstablePopoverSlot, + __unstableContentRef, +} ) { + const { editorMode, isTyping, lastClientId } = useSelect( selector, [] ); + + const isInsertionPointVisible = useSelect( + ( select ) => { + const { + isBlockInsertionPointVisible, + getBlockInsertionPoint, + getBlockOrder, + } = select( blockEditorStore ); + + if ( ! isBlockInsertionPointVisible() ) { + return false; + } + + const insertionPoint = getBlockInsertionPoint(); + const order = getBlockOrder( insertionPoint.rootClientId ); + return order[ insertionPoint.index ] === clientId; + }, + [ clientId ] + ); + + const showEmptyBlockSideInserter = + ! isTyping && editorMode === 'edit' && isEmptyDefaultBlock; + + const popoverProps = useBlockToolbarPopoverProps( { + contentElement: __unstableContentRef?.current, + clientId, + } ); + + if ( showEmptyBlockSideInserter ) { + return ( + +
+ +
+
+ ); + } + + return null; +} + +function wrapperSelector( select ) { + const { + getSelectedBlockClientId, + getFirstMultiSelectedBlockClientId, + getBlockRootClientId, + getBlock, + getBlockParents, + __experimentalGetBlockListSettingsForBlocks, + } = select( blockEditorStore ); + + const clientId = + getSelectedBlockClientId() || getFirstMultiSelectedBlockClientId(); + + if ( ! clientId ) { + return; + } + + const { name, attributes = {} } = getBlock( clientId ) || {}; + const blockParentsClientIds = getBlockParents( clientId ); + + // Get Block List Settings for all ancestors of the current Block clientId. + const parentBlockListSettings = __experimentalGetBlockListSettingsForBlocks( + blockParentsClientIds + ); + + // Get the clientId of the topmost parent with the capture toolbars setting. + const capturingClientId = blockParentsClientIds.find( + ( parentClientId ) => + parentBlockListSettings[ parentClientId ] + ?.__experimentalCaptureToolbars + ); + + return { + clientId, + rootClientId: getBlockRootClientId( clientId ), + name, + isEmptyDefaultBlock: + name && isUnmodifiedDefaultBlock( { name, attributes } ), + capturingClientId, + }; +} + +export default function WrappedEmptyBlockInserter( { + __unstablePopoverSlot, + __unstableContentRef, +} ) { + const selected = useSelect( wrapperSelector, [] ); + + if ( ! selected ) { + return null; + } + + const { + clientId, + rootClientId, + name, + isEmptyDefaultBlock, + capturingClientId, + } = selected; + + if ( ! name ) { + return null; + } + + return ( + + ); +} diff --git a/packages/block-editor/src/components/block-tools/index.js b/packages/block-editor/src/components/block-tools/index.js index 8e3b240838fd04..c92a357dd41646 100644 --- a/packages/block-editor/src/components/block-tools/index.js +++ b/packages/block-editor/src/components/block-tools/index.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { useSelect, useDispatch } from '@wordpress/data'; -import { useViewportMatch } from '@wordpress/compose'; import { Popover } from '@wordpress/components'; import { __unstableUseShortcutEventMatch as useShortcutEventMatch } from '@wordpress/keyboard-shortcuts'; import { useRef } from '@wordpress/element'; @@ -14,9 +13,8 @@ import { InsertionPointOpenRef, default as InsertionPoint, } from './insertion-point'; -import SelectedBlockPopover from './selected-block-popover'; +import EmptyBlockInserter from './empty-block-inserter'; import { store as blockEditorStore } from '../../store'; -import BlockContextualToolbar from './block-contextual-toolbar'; import usePopoverScroll from '../block-popover/use-popover-scroll'; import ZoomOutModeInserters from './zoom-out-mode-inserters'; @@ -45,11 +43,7 @@ export default function BlockTools( { __unstableContentRef, ...props } ) { - const isLargeViewport = useViewportMatch( 'medium' ); - const { hasFixedToolbar, isZoomOutMode, isTyping } = useSelect( - selector, - [] - ); + const { isZoomOutMode, isTyping } = useSelect( selector, [] ); const isMatch = useShortcutEventMatch(); const { getSelectedBlockClientIds, getBlockRootClientId } = useSelect( blockEditorStore ); @@ -138,13 +132,7 @@ export default function BlockTools( { __unstableContentRef={ __unstableContentRef } /> ) } - { ! isZoomOutMode && - ( hasFixedToolbar || ! isLargeViewport ) && ( - - ) } - { /* Even if the toolbar is fixed, the block popover is still - needed for navigation and zoom-out mode. */ } - { /* Used for the inline rich text toolbar. */ } diff --git a/packages/block-editor/src/components/block-tools/selected-block-popover.js b/packages/block-editor/src/components/block-tools/selected-block-tools.js similarity index 82% rename from packages/block-editor/src/components/block-tools/selected-block-popover.js rename to packages/block-editor/src/components/block-tools/selected-block-tools.js index 8c87d8ea3a4739..8703857e3fa84a 100644 --- a/packages/block-editor/src/components/block-tools/selected-block-popover.js +++ b/packages/block-editor/src/components/block-tools/selected-block-tools.js @@ -10,6 +10,7 @@ import { useRef, useEffect } from '@wordpress/element'; import { isUnmodifiedDefaultBlock } from '@wordpress/blocks'; import { useDispatch, useSelect } from '@wordpress/data'; import { useShortcut } from '@wordpress/keyboard-shortcuts'; +import { getScrollContainer } from '@wordpress/dom'; /** * Internal dependencies @@ -19,7 +20,6 @@ import BlockContextualToolbar from './block-contextual-toolbar'; import { store as blockEditorStore } from '../../store'; import BlockPopover from '../block-popover'; import useBlockToolbarPopoverProps from './use-block-toolbar-popover-props'; -import Inserter from '../inserter'; import { useShouldContextualToolbarShow } from '../../utils/use-should-contextual-toolbar-show'; function selector( select ) { @@ -40,13 +40,12 @@ function selector( select ) { }; } -function SelectedBlockPopover( { +function SelectedBlockTools( { clientId, rootClientId, isEmptyDefaultBlock, + isFixed, capturingClientId, - __unstablePopoverSlot, - __unstableContentRef, } ) { const { editorMode, hasMultiSelection, isTyping, lastClientId } = useSelect( selector, @@ -109,40 +108,30 @@ function SelectedBlockPopover( { }, [ clientId ] ); const popoverProps = useBlockToolbarPopoverProps( { - contentElement: __unstableContentRef?.current, + contentElement: getScrollContainer(), // This is what useBlockToolbarPopoverProps does when the contentRef is undefined. This likely works by accident. It was being passed in via the BlockTools clientId, } ); - if ( showEmptyBlockSideInserter ) { + if ( isFixed ) { return ( - -
- -
-
+ { + initialToolbarItemIndexRef.current = index; + } } + // Resets the index whenever the active block changes so + // this is not persisted. See https://github.com/WordPress/gutenberg/pull/25760#issuecomment-717906169 + key={ clientId } + /> ); } + if ( showEmptyBlockSideInserter ) { + return null; + } + if ( shouldShowBreadcrumb || shouldShowContextualToolbar ) { return ( @@ -230,10 +217,7 @@ function wrapperSelector( select ) { }; } -export default function WrappedBlockPopover( { - __unstablePopoverSlot, - __unstableContentRef, -} ) { +export default function WrappedSelectedBlockTools( { isFixed } ) { const selected = useSelect( wrapperSelector, [] ); if ( ! selected ) { @@ -253,13 +237,12 @@ export default function WrappedBlockPopover( { } return ( - ); } diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index 7be30aa5a858ba..472a7498f52192 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -127,6 +127,7 @@ export { default as BlockSettingsMenuControls } from './block-settings-menu-cont export { default as BlockTitle } from './block-title'; export { default as BlockToolbar } from './block-toolbar'; export { default as BlockTools } from './block-tools'; +export { default as _experimentalSelectedBlockTools } from './block-tools/selected-block-tools'; export { default as CopyHandler, useClipboardHandler as __unstableUseClipboardHandler, diff --git a/packages/edit-post/src/components/header/header-toolbar/index.js b/packages/edit-post/src/components/header/header-toolbar/index.js index 840067e9fb9b3d..d06545b93f1ca1 100644 --- a/packages/edit-post/src/components/header/header-toolbar/index.js +++ b/packages/edit-post/src/components/header/header-toolbar/index.js @@ -7,6 +7,7 @@ import { __, _x } from '@wordpress/i18n'; import { NavigableToolbar, ToolSelector, + _experimentalSelectedBlockTools, store as blockEditorStore, privateApis as blockEditorPrivateApis, } from '@wordpress/block-editor'; @@ -131,53 +132,60 @@ function HeaderToolbar() { const shortLabel = ! isInserterOpened ? __( 'Add' ) : __( 'Close' ); return ( - -
- - { ( isWideViewport || ! showIconLabels ) && ( - <> - { isLargeViewport && ! hasFixedToolbar && ( + <> + +
+ + { ( isWideViewport || ! showIconLabels ) && ( + <> + { isLargeViewport && ! hasFixedToolbar && ( + + ) } + - ) } - - - { overflowItems } - - ) } -
-
+ { overflowItems } + + ) } +
+
+ <_experimentalSelectedBlockTools isFixed={ hasFixedToolbar } /> + ); } From c593e05be6c875d61afc40e442090a7291f6b399 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Thu, 17 Aug 2023 12:08:24 -0500 Subject: [PATCH 02/25] Render selected block toolbar as a fill within a slot in the header toolbar --- .../block-editor/src/components/block-tools/index.js | 11 +++++++++-- packages/block-editor/src/components/index.js | 1 - .../src/components/header/header-toolbar/index.js | 5 ++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/components/block-tools/index.js b/packages/block-editor/src/components/block-tools/index.js index c92a357dd41646..0eaee2164a0f48 100644 --- a/packages/block-editor/src/components/block-tools/index.js +++ b/packages/block-editor/src/components/block-tools/index.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { useSelect, useDispatch } from '@wordpress/data'; -import { Popover } from '@wordpress/components'; +import { Fill, Popover } from '@wordpress/components'; import { __unstableUseShortcutEventMatch as useShortcutEventMatch } from '@wordpress/keyboard-shortcuts'; import { useRef } from '@wordpress/element'; @@ -14,6 +14,7 @@ import { default as InsertionPoint, } from './insertion-point'; import EmptyBlockInserter from './empty-block-inserter'; +import SelectedBlockTools from './selected-block-tools'; import { store as blockEditorStore } from '../../store'; import usePopoverScroll from '../block-popover/use-popover-scroll'; import ZoomOutModeInserters from './zoom-out-mode-inserters'; @@ -43,7 +44,10 @@ export default function BlockTools( { __unstableContentRef, ...props } ) { - const { isZoomOutMode, isTyping } = useSelect( selector, [] ); + const { hasFixedToolbar, isZoomOutMode, isTyping } = useSelect( + selector, + [] + ); const isMatch = useShortcutEventMatch(); const { getSelectedBlockClientIds, getBlockRootClientId } = useSelect( blockEditorStore ); @@ -135,6 +139,9 @@ export default function BlockTools( { + + + { /* Used for the inline rich text toolbar. */ } { children } diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index 472a7498f52192..7be30aa5a858ba 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -127,7 +127,6 @@ export { default as BlockSettingsMenuControls } from './block-settings-menu-cont export { default as BlockTitle } from './block-title'; export { default as BlockToolbar } from './block-toolbar'; export { default as BlockTools } from './block-tools'; -export { default as _experimentalSelectedBlockTools } from './block-tools/selected-block-tools'; export { default as CopyHandler, useClipboardHandler as __unstableUseClipboardHandler, diff --git a/packages/edit-post/src/components/header/header-toolbar/index.js b/packages/edit-post/src/components/header/header-toolbar/index.js index d06545b93f1ca1..dcce0124c668e4 100644 --- a/packages/edit-post/src/components/header/header-toolbar/index.js +++ b/packages/edit-post/src/components/header/header-toolbar/index.js @@ -7,7 +7,6 @@ import { __, _x } from '@wordpress/i18n'; import { NavigableToolbar, ToolSelector, - _experimentalSelectedBlockTools, store as blockEditorStore, privateApis as blockEditorPrivateApis, } from '@wordpress/block-editor'; @@ -16,7 +15,7 @@ import { EditorHistoryUndo, store as editorStore, } from '@wordpress/editor'; -import { Button, ToolbarItem } from '@wordpress/components'; +import { Button, Slot, ToolbarItem } from '@wordpress/components'; import { listView, plus } from '@wordpress/icons'; import { useRef, useCallback } from '@wordpress/element'; import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; @@ -184,7 +183,7 @@ function HeaderToolbar() { ) } - <_experimentalSelectedBlockTools isFixed={ hasFixedToolbar } /> + ); } From 4e0c9091a653200890885b8225136941df4b7ed8 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Wed, 9 Aug 2023 15:35:27 -0500 Subject: [PATCH 03/25] Pass isFixed to ContextualToolbar for styling --- .../src/components/block-tools/selected-block-tools.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-editor/src/components/block-tools/selected-block-tools.js b/packages/block-editor/src/components/block-tools/selected-block-tools.js index 8703857e3fa84a..c9c7d6d3a8626e 100644 --- a/packages/block-editor/src/components/block-tools/selected-block-tools.js +++ b/packages/block-editor/src/components/block-tools/selected-block-tools.js @@ -115,6 +115,7 @@ function SelectedBlockTools( { if ( isFixed ) { return ( Date: Thu, 17 Aug 2023 13:38:54 -0500 Subject: [PATCH 04/25] Add block popover to edit site header slot --- .../src/components/header-edit-mode/index.js | 118 +++++++++++++----- 1 file changed, 90 insertions(+), 28 deletions(-) diff --git a/packages/edit-site/src/components/header-edit-mode/index.js b/packages/edit-site/src/components/header-edit-mode/index.js index da682e995defd3..71d24ab2844d33 100644 --- a/packages/edit-site/src/components/header-edit-mode/index.js +++ b/packages/edit-site/src/components/header-edit-mode/index.js @@ -26,6 +26,7 @@ import { ToolbarItem, MenuGroup, MenuItem, + Slot, VisuallyHidden, } from '@wordpress/components'; import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; @@ -190,6 +191,7 @@ export default function HeaderEditMode() { } ) } > { hasDefaultEditorCanvasView && ( + <> ) } - { ! isDistractionFree && ( + ) } + + + { ! isDistractionFree && ( { - setPreviewDeviceType( - 'Desktop' - ); - __unstableSetEditorMode( - isZoomedOutView - ? 'edit' - : 'zoom-out' - ); - } } + label={ __( 'List View' ) } + onClick={ toggleListView } + shortcut={ listViewShortcut } + showTooltip={ ! showIconLabels } + variant={ + showIconLabels + ? 'tertiary' + : undefined + } /> ) } - - ) } - - + { isZoomedOutViewExperimentEnabled && + ! isDistractionFree && + ! hasFixedToolbar && ( + { + setPreviewDeviceType( + 'Desktop' + ); + __unstableSetEditorMode( + isZoomedOutView + ? 'edit' + : 'zoom-out' + ); + } } + /> + ) } + + ) } + + + + ) } { ! isDistractionFree && ( From f0da0e817ce909090adf053426be78d3910ff25a Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Wed, 9 Aug 2023 16:06:14 -0500 Subject: [PATCH 05/25] poc: adjust styles for top toolbar in happy path on desktop and tablet --- .../src/components/block-tools/style.scss | 78 ++----------------- 1 file changed, 8 insertions(+), 70 deletions(-) diff --git a/packages/block-editor/src/components/block-tools/style.scss b/packages/block-editor/src/components/block-tools/style.scss index a6b7d636f491ba..cf4d4acdde2266 100644 --- a/packages/block-editor/src/components/block-tools/style.scss +++ b/packages/block-editor/src/components/block-tools/style.scss @@ -90,7 +90,7 @@ */ // Base left position for the toolbar when fixed. -@include editor-left(".block-editor-block-contextual-toolbar.is-fixed"); +// @include editor-left(".block-editor-block-contextual-toolbar.is-fixed"); .block-editor-block-contextual-toolbar { // Block UI appearance. @@ -105,10 +105,9 @@ } &.is-fixed { - position: sticky; - top: 0; + position: fixed; + top: 107px; // Magic number. Needs updated. Should be whatever the toolbar + admin toolbar is? z-index: z-index(".block-editor-block-popover"); - display: block; width: 100%; overflow: hidden; @@ -142,48 +141,19 @@ $toolbar-margin: $grid-unit-80 * 3 - 2 * $grid-unit + $grid-unit-05; @include break-medium() { &.is-fixed { - // leave room for block inserter, undo and redo, list view - margin-left: $toolbar-margin; - // position on top of interface header - position: fixed; - top: $admin-bar-height; - // Don't fill up when empty - min-height: initial; + position: relative; + top: 0; + display: inline-flex; + width: auto; + // remove the border border-bottom: none; - // has to be flex for collapse button to fit - display: flex; - - // Mimic the height of the parent, vertically align center, and provide a max-height. - height: $header-height; - align-items: center; &.is-collapsed { width: initial; } - &:empty { - width: initial; - } - - .is-fullscreen-mode & { - // leave room for block inserter, undo and redo, list view - // and some margin left - margin-left: $grid-unit-80 * 4 - 2 * $grid-unit; - - top: 0; - - &.is-collapsed { - width: initial; - } - - &:empty { - width: initial; - } - } - & > .block-editor-block-toolbar { - flex-grow: initial; width: initial; // Add a border as separator in the block toolbar. @@ -332,38 +302,6 @@ } } } - - // on tablet viewports the toolbar is fixed - // on top of interface header and covers the whole header - // except for the inserter on the left - @include break-medium() { - &.is-fixed { - width: calc(100% - #{$toolbar-margin}); - - .show-icon-labels & { - width: calc(100% + 40px - #{$toolbar-margin}); //there are no undo, redo and list view buttons - } - - } - } - - // on desktop viewports the toolbar is fixed - // on top of interface header and leaves room - // for the block inserter the publish button - @include break-large() { - &.is-fixed { - width: auto; - .show-icon-labels & { - width: auto; //there are no undo, redo and list view buttons - } - } - .is-fullscreen-mode &.is-fixed { - // in full screen mode we need to account for - // the combined with of the tools at the right of the header and the margin left - // of the toolbar which includes four buttons - width: calc(100% - 280px - #{4 * $grid-unit-80}); - } - } } /** From 9fc803c20f06309285f6b08a9c2bc26e0a7d5860 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Thu, 10 Aug 2023 09:46:10 -0500 Subject: [PATCH 06/25] Use SCSS variables for calculating top toolbar height --- packages/block-editor/src/components/block-tools/style.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/block-tools/style.scss b/packages/block-editor/src/components/block-tools/style.scss index cf4d4acdde2266..36f194858f6de9 100644 --- a/packages/block-editor/src/components/block-tools/style.scss +++ b/packages/block-editor/src/components/block-tools/style.scss @@ -106,7 +106,7 @@ &.is-fixed { position: fixed; - top: 107px; // Magic number. Needs updated. Should be whatever the toolbar + admin toolbar is? + top: $admin-bar-height-big + $header-height; z-index: z-index(".block-editor-block-popover"); width: 100%; overflow: hidden; @@ -118,6 +118,7 @@ border: none; border-bottom: $border-width solid $gray-200; + border-top: $border-width solid $gray-200; border-radius: 0; .block-editor-block-toolbar .components-toolbar-group, @@ -147,7 +148,7 @@ width: auto; // remove the border - border-bottom: none; + border: none; &.is-collapsed { width: initial; From 5257349d835f7225612a4c9edecdbfe193c86e8c Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Thu, 10 Aug 2023 10:02:11 -0500 Subject: [PATCH 07/25] Remove unnecessary z-index --- packages/block-editor/src/components/block-tools/style.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/block-editor/src/components/block-tools/style.scss b/packages/block-editor/src/components/block-tools/style.scss index 36f194858f6de9..94abe73406d148 100644 --- a/packages/block-editor/src/components/block-tools/style.scss +++ b/packages/block-editor/src/components/block-tools/style.scss @@ -107,7 +107,6 @@ &.is-fixed { position: fixed; top: $admin-bar-height-big + $header-height; - z-index: z-index(".block-editor-block-popover"); width: 100%; overflow: hidden; From 9bd48f92863771429546be218bee4e7837f4bfa7 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Thu, 10 Aug 2023 10:56:24 -0500 Subject: [PATCH 08/25] Show the toolbar as fixed when on tablet breakpoint even if using popover setting --- .../src/components/block-tools/selected-block-tools.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/block-tools/selected-block-tools.js b/packages/block-editor/src/components/block-tools/selected-block-tools.js index c9c7d6d3a8626e..0f4c0b60f23816 100644 --- a/packages/block-editor/src/components/block-tools/selected-block-tools.js +++ b/packages/block-editor/src/components/block-tools/selected-block-tools.js @@ -11,6 +11,7 @@ import { isUnmodifiedDefaultBlock } from '@wordpress/blocks'; import { useDispatch, useSelect } from '@wordpress/data'; import { useShortcut } from '@wordpress/keyboard-shortcuts'; import { getScrollContainer } from '@wordpress/dom'; +import { useViewportMatch } from '@wordpress/compose'; /** * Internal dependencies @@ -70,6 +71,7 @@ function SelectedBlockTools( { }, [ clientId ] ); + const isLargeViewport = useViewportMatch( 'medium' ); const isToolbarForced = useRef( false ); const { shouldShowContextualToolbar, canFocusHiddenToolbar } = useShouldContextualToolbarShow(); @@ -112,10 +114,11 @@ function SelectedBlockTools( { clientId, } ); - if ( isFixed ) { + // We need to show the toolbar as fixed when we're on the tablet breakpoint. + if ( isFixed || ! isLargeViewport ) { return ( Date: Thu, 17 Aug 2023 13:54:06 -0500 Subject: [PATCH 09/25] Add classname to selected block tools slot and use bubblesVirtually --- .../edit-post/src/components/header/header-toolbar/index.js | 6 +++++- packages/edit-site/src/components/header-edit-mode/index.js | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/edit-post/src/components/header/header-toolbar/index.js b/packages/edit-post/src/components/header/header-toolbar/index.js index dcce0124c668e4..a8a5c562f96b3c 100644 --- a/packages/edit-post/src/components/header/header-toolbar/index.js +++ b/packages/edit-post/src/components/header/header-toolbar/index.js @@ -183,7 +183,11 @@ function HeaderToolbar() { ) } - + ); } diff --git a/packages/edit-site/src/components/header-edit-mode/index.js b/packages/edit-site/src/components/header-edit-mode/index.js index 71d24ab2844d33..b318d74c9df412 100644 --- a/packages/edit-site/src/components/header-edit-mode/index.js +++ b/packages/edit-site/src/components/header-edit-mode/index.js @@ -353,6 +353,7 @@ export default function HeaderEditMode() { From 96b184bd3da21cec196e09f3375bc46fad60a2b5 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Thu, 17 Aug 2023 14:41:46 -0500 Subject: [PATCH 10/25] POC: Remove calculated width from toolbar --- .../block-tools/block-contextual-toolbar.js | 151 +++++++++--------- 1 file changed, 74 insertions(+), 77 deletions(-) diff --git a/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js b/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js index b2087ac2ff5f6d..fca1998698e1fb 100644 --- a/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js +++ b/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js @@ -7,12 +7,7 @@ import classnames from 'classnames'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { - useLayoutEffect, - useEffect, - useRef, - useState, -} from '@wordpress/element'; +import { useEffect, useRef, useState } from '@wordpress/element'; import { hasBlockSupport, store as blocksStore } from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; import { @@ -82,77 +77,79 @@ function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) { setIsCollapsed( false ); }, [ selectedBlockClientId ] ); - const isLargerThanTabletViewport = useViewportMatch( 'large', '>=' ); - const isFullscreen = - document.body.classList.contains( 'is-fullscreen-mode' ); - - useLayoutEffect( () => { - // don't do anything if not fixed toolbar - if ( ! isFixed || ! blockType ) { - return; - } - - const blockToolbar = document.querySelector( - '.block-editor-block-contextual-toolbar' - ); - - if ( ! blockToolbar ) { - return; - } - - if ( ! isLargerThanTabletViewport ) { - // set the width of the toolbar to auto - blockToolbar.style = {}; - return; - } - - if ( isCollapsed ) { - // set the width of the toolbar to auto - blockToolbar.style.width = 'auto'; - return; - } - - // get the width of the pinned items in the post editor - const pinnedItems = document.querySelector( - '.edit-post-header__settings' - ); - - // get the width of the left header in the site editor - const leftHeader = document.querySelector( - '.edit-site-header-edit-mode__end' - ); - - const computedToolbarStyle = window.getComputedStyle( blockToolbar ); - const computedPinnedItemsStyle = pinnedItems - ? window.getComputedStyle( pinnedItems ) - : false; - const computedLeftHeaderStyle = leftHeader - ? window.getComputedStyle( leftHeader ) - : false; - - const marginLeft = parseFloat( computedToolbarStyle.marginLeft ); - const pinnedItemsWidth = computedPinnedItemsStyle - ? parseFloat( computedPinnedItemsStyle.width ) - : 0; - const leftHeaderWidth = computedLeftHeaderStyle - ? parseFloat( computedLeftHeaderStyle.width ) - : 0; - - // set the new witdth of the toolbar - blockToolbar.style.width = `calc(100% - ${ - leftHeaderWidth + - pinnedItemsWidth + - marginLeft + - ( pinnedItems || leftHeader ? 2 : 0 ) + // Prevents button focus border from being cut off - ( isFullscreen ? 0 : 160 ) // the width of the admin sidebar expanded - }px)`; - }, [ - isFixed, - isLargerThanTabletViewport, - isCollapsed, - isFullscreen, - blockType, - ] ); + // TODO: Do we need all of this width calculation?? + + // const isLargerThanTabletViewport = useViewportMatch( 'large', '>=' ); + // const isFullscreen = + // document.body.classList.contains( 'is-fullscreen-mode' ); + + // useLayoutEffect( () => { + // // don't do anything if not fixed toolbar + // if ( ! isFixed || ! blockType ) { + // return; + // } + + // const blockToolbar = document.querySelector( + // '.block-editor-block-contextual-toolbar' + // ); + + // if ( ! blockToolbar ) { + // return; + // } + + // if ( ! isLargerThanTabletViewport ) { + // // set the width of the toolbar to auto + // blockToolbar.style = {}; + // return; + // } + + // if ( isCollapsed ) { + // // set the width of the toolbar to auto + // blockToolbar.style.width = 'auto'; + // return; + // } + + // // get the width of the pinned items in the post editor + // const pinnedItems = document.querySelector( + // '.edit-post-header__settings' + // ); + + // // get the width of the left header in the site editor + // const leftHeader = document.querySelector( + // '.edit-site-header-edit-mode__end' + // ); + + // const computedToolbarStyle = window.getComputedStyle( blockToolbar ); + // const computedPinnedItemsStyle = pinnedItems + // ? window.getComputedStyle( pinnedItems ) + // : false; + // const computedLeftHeaderStyle = leftHeader + // ? window.getComputedStyle( leftHeader ) + // : false; + + // const marginLeft = parseFloat( computedToolbarStyle.marginLeft ); + // const pinnedItemsWidth = computedPinnedItemsStyle + // ? parseFloat( computedPinnedItemsStyle.width ) + 10 // 10 is the pinned items padding + // : 0; + // const leftHeaderWidth = computedLeftHeaderStyle + // ? parseFloat( computedLeftHeaderStyle.width ) + // : 0; + + // // set the new witdth of the toolbar + // blockToolbar.style.width = `calc(100% - ${ + // leftHeaderWidth + + // pinnedItemsWidth + + // marginLeft + + // ( pinnedItems || leftHeader ? 2 : 0 ) + // Prevents button focus border from being cut off + // ( isFullscreen ? 0 : 160 ) // the width of the admin sidebar expanded + // }px)`; + // }, [ + // isFixed, + // isLargerThanTabletViewport, + // isCollapsed, + // isFullscreen, + // blockType, + // ] ); const isToolbarEnabled = ! blockType || From fd31f65fa75aaa1237af897f6236d31c6f83ef4f Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Fri, 25 Aug 2023 08:54:12 -0500 Subject: [PATCH 11/25] Move lastFocus into redux store --- .../data/data-core-block-editor.md | 24 +++++++++++++++++++ .../components/writing-flow/use-tab-nav.js | 11 ++++++--- packages/block-editor/src/store/actions.js | 15 ++++++++++++ packages/block-editor/src/store/reducer.js | 18 ++++++++++++++ packages/block-editor/src/store/selectors.js | 11 +++++++++ 5 files changed, 76 insertions(+), 3 deletions(-) diff --git a/docs/reference-guides/data/data-core-block-editor.md b/docs/reference-guides/data/data-core-block-editor.md index 025b4eaf15ab8f..2ec59c486be1a7 100644 --- a/docs/reference-guides/data/data-core-block-editor.md +++ b/docs/reference-guides/data/data-core-block-editor.md @@ -588,6 +588,18 @@ _Properties_ - _isDisabled_ `boolean`: Whether or not the user should be prevented from inserting this item. - _frecency_ `number`: Heuristic that combines frequency and recency. +### getLastFocus + +Returns the element of the last element that had focus when focus left the editor canvas. + +_Parameters_ + +- _state_ `Object`: Block editor state. + +_Returns_ + +- `Object`: Element. + ### getLastMultiSelectedBlockClientId Returns the client ID of the last block in the multi-selection set, or null if there is no multi-selection. @@ -1651,6 +1663,18 @@ _Parameters_ - _clientId_ `string`: The block's clientId. - _hasControlledInnerBlocks_ `boolean`: True if the block's inner blocks are controlled. +### setLastFocus + +Action that sets the element that had focus when focus leaves the editor canvas. + +_Parameters_ + +- _lastFocus_ `Object`: The last focused element. + +_Returns_ + +- `Object`: Action object. + ### setNavigationMode Action that enables or disables the navigation mode. 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 616da1bc758136..b1fb1800a53ea2 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 @@ -17,15 +17,20 @@ export default function useTabNav() { const container = useRef(); const focusCaptureBeforeRef = useRef(); const focusCaptureAfterRef = useRef(); - const lastFocus = useRef(); + const { hasMultiSelection, getSelectedBlockClientId, getBlockCount } = useSelect( blockEditorStore ); - const { setNavigationMode } = useDispatch( blockEditorStore ); + const { setNavigationMode, setLastFocus } = useDispatch( blockEditorStore ); const isNavigationMode = useSelect( ( select ) => select( blockEditorStore ).isNavigationMode(), [] ); + const lastFocus = useSelect( + ( select ) => select( blockEditorStore ).getLastFocus(), + [] + ); + // Don't allow tabbing to this element in Navigation mode. const focusCaptureTabIndex = ! isNavigationMode ? '0' : undefined; @@ -158,7 +163,7 @@ export default function useTabNav() { } function onFocusOut( event ) { - lastFocus.current = event.target; + setLastFocus( { ...lastFocus, current: event.target } ); const { ownerDocument } = node; diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 32108de713f754..5fea452a9666d6 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -1925,3 +1925,18 @@ export function unsetBlockEditingMode( clientId = '' ) { clientId, }; } + +/** + * Action that sets the element that had focus when focus leaves the editor canvas. + * + * @param {Object} lastFocus The last focused element. + * + * + * @return {Object} Action object. + */ +export function setLastFocus( lastFocus = null ) { + return { + type: 'LAST_FOCUS', + lastFocus, + }; +} diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 245aaf7adb0fdf..b1d9e77d118bd5 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -1913,6 +1913,23 @@ export function blockEditingModes( state = new Map(), action ) { return state; } +/** + * Reducer setting last focused element + * + * @param {boolean} state Current state. + * @param {Object} action Dispatched action. + * + * @return {boolean} Updated state. + */ +export function lastFocus( state = false, action ) { + switch ( action.type ) { + case 'LAST_FOCUS': + return action.lastFocus; + } + + return state; +} + const combinedReducers = combineReducers( { blocks, isTyping, @@ -1929,6 +1946,7 @@ const combinedReducers = combineReducers( { settings, preferences, lastBlockAttributesChange, + lastFocus, editorMode, hasBlockMovingClientId, highlightedBlock, diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 2de9e3f00be75f..408705e2f1919e 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -3006,3 +3006,14 @@ export const isGroupable = createRegistrySelector( ); } ); + +/* + * Returns the element of the last element that had focus when focus left the editor canvas. + * + * @param {Object} state Block editor state. + * + * @return {Object} Element. + */ +export function getLastFocus( state ) { + return state.lastFocus; +} From c3c76b968646d8e2b06f78365342e1a44340a74a Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Fri, 25 Aug 2023 16:03:56 -0500 Subject: [PATCH 12/25] Register global shortcut for moving focus back to the last focused block. Still more todo. This shouldn't go to the last focused block but the last _selection_ which will be a bit more work. Probably best to refactor how it works within use-tab-nav. Maybe turn it into a hook or something that can have centralized logic. --- .../global-keyboard-shortcuts/index.js | 16 ++++++++++++++++ .../register-shortcuts.js | 12 ++++++++++++ 2 files changed, 28 insertions(+) diff --git a/packages/editor/src/components/global-keyboard-shortcuts/index.js b/packages/editor/src/components/global-keyboard-shortcuts/index.js index 4b45fe449123f4..7db1f55bad24ae 100644 --- a/packages/editor/src/components/global-keyboard-shortcuts/index.js +++ b/packages/editor/src/components/global-keyboard-shortcuts/index.js @@ -3,6 +3,7 @@ */ import { useShortcut } from '@wordpress/keyboard-shortcuts'; import { useDispatch, useSelect } from '@wordpress/data'; +import { store as blockEditorStore } from '@wordpress/block-editor'; /** * Internal dependencies @@ -12,6 +13,8 @@ import { store as editorStore } from '../../store'; export default function EditorKeyboardShortcuts() { const { redo, undo, savePost } = useDispatch( editorStore ); const { isEditedPostDirty, isPostSavingLocked } = useSelect( editorStore ); + const { getLastFocus, getSelectedBlockClientId } = + useSelect( blockEditorStore ); useShortcut( 'core/editor/undo', ( event ) => { undo(); @@ -45,5 +48,18 @@ export default function EditorKeyboardShortcuts() { savePost(); } ); + useShortcut( 'core/block-editor/focus-editor', ( event ) => { + event.preventDefault(); + const lastFocus = getLastFocus(); + // Only move focus if the selected block is a match with the last focused block + if ( + getSelectedBlockClientId() && + lastFocus?.current && + lastFocus?.current.id.includes( getSelectedBlockClientId() ) + ) { + lastFocus.current.focus(); + } + } ); + return null; } diff --git a/packages/editor/src/components/global-keyboard-shortcuts/register-shortcuts.js b/packages/editor/src/components/global-keyboard-shortcuts/register-shortcuts.js index 8e8f4c42ca6dd6..611179e0021880 100644 --- a/packages/editor/src/components/global-keyboard-shortcuts/register-shortcuts.js +++ b/packages/editor/src/components/global-keyboard-shortcuts/register-shortcuts.js @@ -53,6 +53,18 @@ function EditorKeyboardShortcutsRegister() { }, ], } ); + + registerShortcut( { + name: 'core/block-editor/focus-editor', + category: 'global', + description: __( + 'Navigate to the last focused element in the editor.' + ), + keyCombination: { + modifier: 'alt', + character: 'F9', + }, + } ); }, [ registerShortcut ] ); return ; From e747b8824ea7efea082f3b0e4c3f4807f52e7a03 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Fri, 25 Aug 2023 16:08:38 -0500 Subject: [PATCH 13/25] Start on escape to return to block selection It's not working for buttons that contain dropdowns, such as the align or options buttons. The dropdown toggle is intercepting the escape keypress and not letting it bubble. --- .../components/block-tools/block-contextual-toolbar.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js b/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js index fca1998698e1fb..e7ce372a094da0 100644 --- a/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js +++ b/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js @@ -17,6 +17,7 @@ import { } from '@wordpress/components'; import { next, previous } from '@wordpress/icons'; import { useViewportMatch } from '@wordpress/compose'; +import { ESCAPE } from '@wordpress/keycodes'; /** * Internal dependencies @@ -35,6 +36,7 @@ function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) { const { blockType, blockEditingMode, + lastFocus, hasParents, showParentSelector, selectedBlockClientId, @@ -42,6 +44,7 @@ function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) { const { getBlockName, getBlockParents, + getLastFocus, getSelectedBlockClientIds, getBlockEditingMode, } = select( blockEditorStore ); @@ -59,6 +62,7 @@ function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) { _selectedBlockClientId && getBlockType( getBlockName( _selectedBlockClientId ) ), blockEditingMode: getBlockEditingMode( _selectedBlockClientId ), + lastFocus: getLastFocus(), hasParents: parents.length, showParentSelector: parentBlockType && @@ -176,6 +180,12 @@ function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) { /* translators: accessibility text for the block toolbar */ aria-label={ __( 'Block tools' ) } { ...props } + onKeyDown={ ( event ) => { + if ( event.keyCode === ESCAPE && lastFocus?.current ) { + event.preventDefault(); + lastFocus.current.focus(); + } + } } > { ! isCollapsed && } { isFixed && isLargeViewport && blockType && ( From 3b696bc869c313d055f0a58ffbcd309121b08bb8 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Fri, 25 Aug 2023 16:18:13 -0500 Subject: [PATCH 14/25] Add move focus to selected block global shortcut to site editor --- .../keyboard-shortcuts/edit-mode.js | 21 +++++++++++++++++-- .../components/keyboard-shortcuts/register.js | 12 +++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/edit-site/src/components/keyboard-shortcuts/edit-mode.js b/packages/edit-site/src/components/keyboard-shortcuts/edit-mode.js index 1346041b6a94c1..b8abbedda20f33 100644 --- a/packages/edit-site/src/components/keyboard-shortcuts/edit-mode.js +++ b/packages/edit-site/src/components/keyboard-shortcuts/edit-mode.js @@ -35,8 +35,12 @@ function KeyboardShortcutsEditMode() { useDispatch( interfaceStore ); const { replaceBlocks } = useDispatch( blockEditorStore ); - const { getBlockName, getSelectedBlockClientId, getBlockAttributes } = - useSelect( blockEditorStore ); + const { + getBlockName, + getLastFocus, + getSelectedBlockClientId, + getBlockAttributes, + } = useSelect( blockEditorStore ); const handleTextLevelShortcut = ( event, level ) => { event.preventDefault(); @@ -118,6 +122,19 @@ function KeyboardShortcutsEditMode() { toggleDistractionFree(); } ); + useShortcut( 'core/edit-site/focus-editor', ( event ) => { + event.preventDefault(); + const lastFocus = getLastFocus(); + // Only move focus if the selected block is a match with the last focused block + if ( + getSelectedBlockClientId() && + lastFocus?.current && + lastFocus?.current.id.includes( getSelectedBlockClientId() ) + ) { + lastFocus.current.focus(); + } + } ); + return null; } diff --git a/packages/edit-site/src/components/keyboard-shortcuts/register.js b/packages/edit-site/src/components/keyboard-shortcuts/register.js index 8dfd1e3e2a45bf..7923af6bcf09bd 100644 --- a/packages/edit-site/src/components/keyboard-shortcuts/register.js +++ b/packages/edit-site/src/components/keyboard-shortcuts/register.js @@ -159,6 +159,18 @@ function KeyboardShortcutsRegister() { character: '\\', }, } ); + + registerShortcut( { + name: 'core/edit-site/focus-editor', + category: 'global', + description: __( + 'Navigate to the last focused element in the editor.' + ), + keyCombination: { + modifier: 'alt', + character: 'F9', + }, + } ); }, [ registerShortcut ] ); return null; From 732fabbf3f89c200d88403861e40a0a7b2dc14b1 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Fri, 25 Aug 2023 16:18:49 -0500 Subject: [PATCH 15/25] Rename editor focus-editor shortcut --- .../editor/src/components/global-keyboard-shortcuts/index.js | 2 +- .../components/global-keyboard-shortcuts/register-shortcuts.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/components/global-keyboard-shortcuts/index.js b/packages/editor/src/components/global-keyboard-shortcuts/index.js index 7db1f55bad24ae..f2b48c285905ae 100644 --- a/packages/editor/src/components/global-keyboard-shortcuts/index.js +++ b/packages/editor/src/components/global-keyboard-shortcuts/index.js @@ -48,7 +48,7 @@ export default function EditorKeyboardShortcuts() { savePost(); } ); - useShortcut( 'core/block-editor/focus-editor', ( event ) => { + useShortcut( 'core/editor/focus-editor', ( event ) => { event.preventDefault(); const lastFocus = getLastFocus(); // Only move focus if the selected block is a match with the last focused block diff --git a/packages/editor/src/components/global-keyboard-shortcuts/register-shortcuts.js b/packages/editor/src/components/global-keyboard-shortcuts/register-shortcuts.js index 611179e0021880..456d6b90458cb5 100644 --- a/packages/editor/src/components/global-keyboard-shortcuts/register-shortcuts.js +++ b/packages/editor/src/components/global-keyboard-shortcuts/register-shortcuts.js @@ -55,7 +55,7 @@ function EditorKeyboardShortcutsRegister() { } ); registerShortcut( { - name: 'core/block-editor/focus-editor', + name: 'core/editor/focus-editor', category: 'global', description: __( 'Navigate to the last focused element in the editor.' From 9acb93d5c10a2e6a4e0667ae01646b17880b78bd Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Fri, 1 Sep 2023 11:58:33 -0500 Subject: [PATCH 16/25] Handle Escape on Toolbar Option 1: Add event listeners to all children via onChildrenKeyDown passed to NavigableToolbar Note: Due to the use of portals in the toolbar slots, consistently handling event listeners cleanly is difficult. This method adds event listeners to all children of a toolbar if a `onChildrenKeyDown` prop is passed to the via a querySelectorAll( '[data-toolbar-item="true"]' ) on the NavigableToolbar ref, then applying an addEventListener to all the returned buttons. Pros: Event listener is applied and handled in one location. Cons: Feels yucky. Feels like crossing our fingers and hoping it works consistently, which it might. --- .../block-tools/block-contextual-toolbar.js | 4 +-- .../src/components/navigable-toolbar/index.js | 31 ++++++++++++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js b/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js index e7ce372a094da0..12258b0f56f8e5 100644 --- a/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js +++ b/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js @@ -179,13 +179,13 @@ function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) { className={ classes } /* translators: accessibility text for the block toolbar */ aria-label={ __( 'Block tools' ) } - { ...props } - onKeyDown={ ( event ) => { + onChildrenKeyDown={ ( event ) => { if ( event.keyCode === ESCAPE && lastFocus?.current ) { event.preventDefault(); lastFocus.current.focus(); } } } + { ...props } > { ! isCollapsed && } { isFixed && isLargeViewport && blockType && ( diff --git a/packages/block-editor/src/components/navigable-toolbar/index.js b/packages/block-editor/src/components/navigable-toolbar/index.js index 3e531c93c11989..0b36ae87d5505b 100644 --- a/packages/block-editor/src/components/navigable-toolbar/index.js +++ b/packages/block-editor/src/components/navigable-toolbar/index.js @@ -156,6 +156,7 @@ function useToolbarFocus( function NavigableToolbar( { children, focusOnMount, + onChildrenKeyDown = () => {}, shouldUseKeyboardFocusShortcut = true, __experimentalInitialIndex: initialIndex, __experimentalOnIndexChange: onIndexChange, @@ -163,7 +164,6 @@ function NavigableToolbar( { } ) { const ref = useRef(); const isAccessibleToolbar = useIsAccessibleToolbar( ref ); - useToolbarFocus( ref, focusOnMount, @@ -173,6 +173,35 @@ function NavigableToolbar( { shouldUseKeyboardFocusShortcut ); + useEffect( () => { + const navigableToolbarRef = ref.current; + const toolbarButtons = navigableToolbarRef.querySelectorAll( + '[data-toolbar-item="true"]' + ); + + if ( onChildrenKeyDown ) { + const handleChildrenKeyDown = ( event ) => { + onChildrenKeyDown( event ); + }; + + toolbarButtons.forEach( ( toolbarButton ) => { + toolbarButton.addEventListener( + 'keydown', + handleChildrenKeyDown + ); + } ); + + return () => { + toolbarButtons.forEach( ( toolbarButton ) => { + toolbarButton.removeEventListener( + 'keydown', + handleChildrenKeyDown + ); + } ); + }; + } + }, [ onChildrenKeyDown, children ] ); + if ( isAccessibleToolbar ) { return ( From b5f52484225719c919225259c0d48afa6ad89bd6 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Fri, 1 Sep 2023 15:47:42 -0500 Subject: [PATCH 17/25] Handle Escape on Toolbar Option 2: Use addEventListener on the selectedblock toolbar, attached to the NavigableToolbar This method requires forwarding a ref from the editor level down to the navigable toolbar so that the escape unselect shortcut can be blocked and the navigable toolbar event listener will still fire. Blocking the global escape event shouldn't be necessary, but we have a combination of a few things all combining to create a situation where: - Because the block toolbar uses createPortal to populate the block toolbar fills, we can't rely on the React event bubbling to hit the onKeyDown listener for the block toolbar - Since we can't use the React tree, we use the DOM tree which _should_ handle the event bubbling correctly from a `createPortal` element. - This bubbles via the React tree, which hits this `unselect` escape keypress before the block toolbar DOM event listener has access to it. --- .../block-tools/block-contextual-toolbar.js | 12 ++- .../src/components/block-tools/index.js | 16 +++- .../block-tools/selected-block-tools.js | 25 +++-- .../src/components/navigable-toolbar/index.js | 93 ++++++++++--------- 4 files changed, 86 insertions(+), 60 deletions(-) diff --git a/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js b/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js index 12258b0f56f8e5..ba69826d8533a5 100644 --- a/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js +++ b/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js @@ -7,7 +7,7 @@ import classnames from 'classnames'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { useEffect, useRef, useState } from '@wordpress/element'; +import { forwardRef, useEffect, useRef, useState } from '@wordpress/element'; import { hasBlockSupport, store as blocksStore } from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; import { @@ -27,7 +27,10 @@ import BlockToolbar from '../block-toolbar'; import { store as blockEditorStore } from '../../store'; import { useHasAnyBlockControls } from '../block-controls/use-has-block-controls'; -function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) { +function UnforwardBlockContextualToolbar( + { focusOnMount, isFixed, ...props }, + ref +) { // When the toolbar is fixed it can be collapsed const [ isCollapsed, setIsCollapsed ] = useState( false ); const toolbarButtonRef = useRef(); @@ -175,11 +178,12 @@ function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) { return ( { + handleOnKeyDown={ ( event ) => { if ( event.keyCode === ESCAPE && lastFocus?.current ) { event.preventDefault(); lastFocus.current.focus(); @@ -216,4 +220,4 @@ function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) { ); } -export default BlockContextualToolbar; +export default forwardRef( UnforwardBlockContextualToolbar ); diff --git a/packages/block-editor/src/components/block-tools/index.js b/packages/block-editor/src/components/block-tools/index.js index 0eaee2164a0f48..7831eab0fa5bcf 100644 --- a/packages/block-editor/src/components/block-tools/index.js +++ b/packages/block-editor/src/components/block-tools/index.js @@ -62,6 +62,8 @@ export default function BlockTools( { moveBlocksDown, } = useDispatch( blockEditorStore ); + const selectedBlockToolsRef = useRef( null ); + function onKeyDown( event ) { if ( event.defaultPrevented ) return; @@ -104,6 +106,15 @@ export default function BlockTools( { insertBeforeBlock( clientIds[ 0 ] ); } } else if ( isMatch( 'core/block-editor/unselect', event ) ) { + if ( selectedBlockToolsRef.current.contains( event.target ) ) { + // This shouldn't be necessary, but we have a combination of a few things all combining to create a situation where: + // - Because the block toolbar uses createPortal to populate the block toolbar fills, we can't rely on the React event bubbling to hit the onKeyDown listener for the block toolbar + // - Since we can't use the React tree, we use the DOM tree which _should_ handle the event bubbling correctly from a `createPortal` element. + // - This bubbles via the React tree, which hits this `unselect` escape keypress before the block toolbar DOM event listener has access to it. + // An alternative would be to remove the addEventListener on the navigableToolbar and use this event to handle it directly right here. That feels hacky too though. + return; + } + const clientIds = getSelectedBlockClientIds(); if ( clientIds.length ) { event.preventDefault(); @@ -140,7 +151,10 @@ export default function BlockTools( { __unstableContentRef={ __unstableContentRef } /> - + { /* Used for the inline rich text toolbar. */ } diff --git a/packages/block-editor/src/components/block-tools/selected-block-tools.js b/packages/block-editor/src/components/block-tools/selected-block-tools.js index 0f4c0b60f23816..d3c0b37e607f27 100644 --- a/packages/block-editor/src/components/block-tools/selected-block-tools.js +++ b/packages/block-editor/src/components/block-tools/selected-block-tools.js @@ -6,7 +6,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { useRef, useEffect } from '@wordpress/element'; +import { forwardRef, useRef, useEffect } from '@wordpress/element'; import { isUnmodifiedDefaultBlock } from '@wordpress/blocks'; import { useDispatch, useSelect } from '@wordpress/data'; import { useShortcut } from '@wordpress/keyboard-shortcuts'; @@ -41,13 +41,10 @@ function selector( select ) { }; } -function SelectedBlockTools( { - clientId, - rootClientId, - isEmptyDefaultBlock, - isFixed, - capturingClientId, -} ) { +function WrappedSelectedBlockTools( + { clientId, rootClientId, isEmptyDefaultBlock, isFixed, capturingClientId }, + ref +) { const { editorMode, hasMultiSelection, isTyping, lastClientId } = useSelect( selector, [] @@ -118,6 +115,7 @@ function SelectedBlockTools( { if ( isFixed || ! isLargeViewport ) { return ( { shouldShowContextualToolbar && ( { + const { isFixed } = props; const selected = useSelect( wrapperSelector, [] ); if ( ! selected ) { @@ -242,6 +244,7 @@ export default function WrappedSelectedBlockTools( { isFixed } ) { return ( ); -} +}; + +export default forwardRef( UnforwardWrappedSelectedBlockTools ); diff --git a/packages/block-editor/src/components/navigable-toolbar/index.js b/packages/block-editor/src/components/navigable-toolbar/index.js index 0b36ae87d5505b..c9c2b6530c2b1e 100644 --- a/packages/block-editor/src/components/navigable-toolbar/index.js +++ b/packages/block-editor/src/components/navigable-toolbar/index.js @@ -3,6 +3,7 @@ */ import { NavigableMenu, Toolbar } from '@wordpress/components'; import { + forwardRef, useState, useRef, useLayoutEffect, @@ -38,7 +39,7 @@ function focusFirstTabbableIn( container ) { } } -function useIsAccessibleToolbar( ref ) { +function useIsAccessibleToolbar( toolbarRef ) { /* * By default, we'll assume the starting accessible state of the Toolbar * is true, as it seems to be the most common case. @@ -62,7 +63,7 @@ function useIsAccessibleToolbar( ref ) { ); const determineIsAccessibleToolbar = useCallback( () => { - const tabbables = focus.tabbable.find( ref.current ); + const tabbables = focus.tabbable.find( toolbarRef.current ); const onlyToolbarItem = hasOnlyToolbarItem( tabbables ); if ( ! onlyToolbarItem ) { deprecated( 'Using custom components as toolbar controls', { @@ -73,7 +74,7 @@ function useIsAccessibleToolbar( ref ) { } ); } setIsAccessibleToolbar( onlyToolbarItem ); - }, [] ); + }, [ toolbarRef ] ); useLayoutEffect( () => { // Toolbar buttons may be rendered asynchronously, so we use @@ -81,15 +82,18 @@ function useIsAccessibleToolbar( ref ) { const observer = new window.MutationObserver( determineIsAccessibleToolbar ); - observer.observe( ref.current, { childList: true, subtree: true } ); + observer.observe( toolbarRef.current, { + childList: true, + subtree: true, + } ); return () => observer.disconnect(); - }, [ isAccessibleToolbar ] ); + }, [ isAccessibleToolbar, determineIsAccessibleToolbar, toolbarRef ] ); return isAccessibleToolbar; } function useToolbarFocus( - ref, + toolbarRef, focusOnMount, isAccessibleToolbar, defaultIndex, @@ -101,8 +105,8 @@ function useToolbarFocus( const [ initialIndex ] = useState( defaultIndex ); const focusToolbar = useCallback( () => { - focusFirstTabbableIn( ref.current ); - }, [] ); + focusFirstTabbableIn( toolbarRef.current ); + }, [ toolbarRef ] ); const focusToolbarViaShortcut = () => { if ( shouldUseKeyboardFocusShortcut ) { @@ -121,7 +125,7 @@ function useToolbarFocus( useEffect( () => { // Store ref so we have access on useEffect cleanup: https://legacy.reactjs.org/blog/2020/08/10/react-v17-rc.html#effect-cleanup-timing - const navigableToolbarRef = ref.current; + const navigableToolbarRef = toolbarRef.current; // If initialIndex is passed, we focus on that toolbar item when the // toolbar gets mounted and initial focus is not forced. // We have to wait for the next browser paint because block controls aren't @@ -150,22 +154,27 @@ function useToolbarFocus( const index = items.findIndex( ( item ) => item.tabIndex === 0 ); onIndexChange( index ); }; - }, [ initialIndex, initialFocusOnMount ] ); + }, [ initialIndex, initialFocusOnMount, toolbarRef, onIndexChange ] ); } -function NavigableToolbar( { - children, - focusOnMount, - onChildrenKeyDown = () => {}, - shouldUseKeyboardFocusShortcut = true, - __experimentalInitialIndex: initialIndex, - __experimentalOnIndexChange: onIndexChange, - ...props -} ) { - const ref = useRef(); - const isAccessibleToolbar = useIsAccessibleToolbar( ref ); +function UnforwardNavigableToolbar( + { + children, + focusOnMount, + handleOnKeyDown, + shouldUseKeyboardFocusShortcut = true, + __experimentalInitialIndex: initialIndex, + __experimentalOnIndexChange: onIndexChange, + ...props + }, + ref +) { + const maybeRef = useRef(); + // If a ref was not forwarded, we create one. + const toolbarRef = ref || maybeRef; + const isAccessibleToolbar = useIsAccessibleToolbar( toolbarRef ); useToolbarFocus( - ref, + toolbarRef, focusOnMount, isAccessibleToolbar, initialIndex, @@ -174,37 +183,31 @@ function NavigableToolbar( { ); useEffect( () => { - const navigableToolbarRef = ref.current; - const toolbarButtons = navigableToolbarRef.querySelectorAll( - '[data-toolbar-item="true"]' - ); + const navigableToolbarRef = toolbarRef.current; - if ( onChildrenKeyDown ) { - const handleChildrenKeyDown = ( event ) => { - onChildrenKeyDown( event ); + if ( handleOnKeyDown ) { + const handleKeyDown = ( event ) => { + handleOnKeyDown( event ); }; - toolbarButtons.forEach( ( toolbarButton ) => { - toolbarButton.addEventListener( - 'keydown', - handleChildrenKeyDown - ); - } ); + navigableToolbarRef.addEventListener( 'keydown', handleKeyDown ); return () => { - toolbarButtons.forEach( ( toolbarButton ) => { - toolbarButton.removeEventListener( - 'keydown', - handleChildrenKeyDown - ); - } ); + navigableToolbarRef.removeEventListener( + 'keydown', + handleKeyDown + ); }; } - }, [ onChildrenKeyDown, children ] ); + }, [ handleOnKeyDown, toolbarRef ] ); if ( isAccessibleToolbar ) { return ( - + { children } ); @@ -214,7 +217,7 @@ function NavigableToolbar( { { children } @@ -222,4 +225,4 @@ function NavigableToolbar( { ); } -export default NavigableToolbar; +export default forwardRef( UnforwardNavigableToolbar ); From 401cf4c9b3557278d97b017754b7b2497622902e Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Fri, 1 Sep 2023 16:01:32 -0500 Subject: [PATCH 18/25] Handle Escape on Toolbar Option 3: Remove bubblesVirtually from block-controls slot If remove the bubblesVirtually from the block-controls slot, then the event will bubble in the DOM as normal (instead of the React Tree only since bubblesVirtually uses createPortal for the fills). This means we could handle the event without any forwardRefs as we don't need to block the escape shortcut keypress event from the React Tree. However, we assume bubblesVirtually was made for a reason. So, we're unsure if something would break. --- .../src/components/block-controls/slot.js | 2 +- .../block-editor/src/components/block-tools/index.js | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/block-controls/slot.js b/packages/block-editor/src/components/block-controls/slot.js index ad800b49ab40db..fb2ace0ba17a10 100644 --- a/packages/block-editor/src/components/block-controls/slot.js +++ b/packages/block-editor/src/components/block-controls/slot.js @@ -42,7 +42,7 @@ export default function BlockControlsSlot( { group = 'default', ...props } ) { return null; } - const slot = ; + const slot = ; if ( group === 'default' ) { return slot; diff --git a/packages/block-editor/src/components/block-tools/index.js b/packages/block-editor/src/components/block-tools/index.js index 7831eab0fa5bcf..e0cc0e8477d8da 100644 --- a/packages/block-editor/src/components/block-tools/index.js +++ b/packages/block-editor/src/components/block-tools/index.js @@ -62,6 +62,9 @@ export default function BlockTools( { moveBlocksDown, } = useDispatch( blockEditorStore ); + // If we go with removing bubblesVirtually from the block controls slot, + // we can also remove all of this and all the navigable toolbar forwardRef stuff, + // as we don't need to stop the escape unselect shortcut from hitting first. const selectedBlockToolsRef = useRef( null ); function onKeyDown( event ) { @@ -106,15 +109,6 @@ export default function BlockTools( { insertBeforeBlock( clientIds[ 0 ] ); } } else if ( isMatch( 'core/block-editor/unselect', event ) ) { - if ( selectedBlockToolsRef.current.contains( event.target ) ) { - // This shouldn't be necessary, but we have a combination of a few things all combining to create a situation where: - // - Because the block toolbar uses createPortal to populate the block toolbar fills, we can't rely on the React event bubbling to hit the onKeyDown listener for the block toolbar - // - Since we can't use the React tree, we use the DOM tree which _should_ handle the event bubbling correctly from a `createPortal` element. - // - This bubbles via the React tree, which hits this `unselect` escape keypress before the block toolbar DOM event listener has access to it. - // An alternative would be to remove the addEventListener on the navigableToolbar and use this event to handle it directly right here. That feels hacky too though. - return; - } - const clientIds = getSelectedBlockClientIds(); if ( clientIds.length ) { event.preventDefault(); From 25a79f29ebdc3e972bcd8d4e4ca6f9c467ab29e5 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Wed, 6 Sep 2023 15:36:17 -0500 Subject: [PATCH 19/25] POC: DON'T MERGE THIS. Allow tab keypress formatting in rich text fields in editor. --- .../src/components/rich-text/index.js | 5 +++ .../src/components/rich-text/use-tab.js | 38 +++++++++++++++++++ packages/rich-text/src/create.js | 2 +- 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 packages/block-editor/src/components/rich-text/use-tab.js diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index a22b251dd607c2..ebd32a0cb5940b 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -37,6 +37,7 @@ import { useBeforeInputRules } from './use-before-input-rules'; import { useInputRules } from './use-input-rules'; import { useDelete } from './use-delete'; import { useEnter } from './use-enter'; +import { useTab } from './use-tab'; import { useFormatTypes } from './use-format-types'; import { useRemoveBrowserShortcuts } from './use-remove-browser-shortcuts'; import { useShortcuts } from './use-shortcuts'; @@ -399,6 +400,10 @@ function RichTextWrapper( disableLineBreaks, onSplitAtEnd, } ), + useTab( { + value, + onChange, + } ), useFirefoxCompat(), anchorRef, ] ) } diff --git a/packages/block-editor/src/components/rich-text/use-tab.js b/packages/block-editor/src/components/rich-text/use-tab.js new file mode 100644 index 00000000000000..e5ca01f594cdb8 --- /dev/null +++ b/packages/block-editor/src/components/rich-text/use-tab.js @@ -0,0 +1,38 @@ +/** + * WordPress dependencies + */ +import { useRef } from '@wordpress/element'; +import { insert } from '@wordpress/rich-text'; +import { useRefEffect } from '@wordpress/compose'; +import { TAB } from '@wordpress/keycodes'; + +export function useTab( props ) { + const propsRef = useRef( props ); + propsRef.current = props; + return useRefEffect( ( element ) => { + function onKeyDown( event ) { + const { keyCode } = event; + + if ( event.defaultPrevented ) { + return; + } + + const { value, onChange } = propsRef.current; + const _value = { ...value }; + + if ( keyCode === TAB ) { + event.preventDefault(); + + const { start, end } = value; + + onChange( insert( _value, '\t', start, end ) ); + } + } + + element.addEventListener( 'keydown', onKeyDown ); + return () => { + element.removeEventListener( 'keydown', onKeyDown ); + }; + }, [] ); + +} \ No newline at end of file diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index fa2befc603b7e4..d3b66090e18d2b 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -298,7 +298,7 @@ function filterRange( node, range, filter ) { * @param {string} string */ function collapseWhiteSpace( string ) { - return string.replace( /[\n\r\t]+/g, ' ' ); + return string.replace( /[\n\r]+/g, ' ' ); } /** From 384e7171cb37fd941092cc9370d5553e985e52d2 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Mon, 11 Sep 2023 16:12:11 -0500 Subject: [PATCH 20/25] Add inline rich tools to header popover --- packages/block-editor/src/components/block-tools/index.js | 6 ++++-- .../edit-post/src/components/header/header-toolbar/index.js | 5 +++++ packages/edit-site/src/components/header-edit-mode/index.js | 5 +++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/block-tools/index.js b/packages/block-editor/src/components/block-tools/index.js index e0cc0e8477d8da..b34b591c111c1f 100644 --- a/packages/block-editor/src/components/block-tools/index.js +++ b/packages/block-editor/src/components/block-tools/index.js @@ -150,8 +150,10 @@ export default function BlockTools( { isFixed={ hasFixedToolbar } /> - { /* Used for the inline rich text toolbar. */ } - + + { /* Used for the inline rich text toolbar. */ } + + { children } { /* Used for inline rich text popovers. */ } + ); } diff --git a/packages/edit-site/src/components/header-edit-mode/index.js b/packages/edit-site/src/components/header-edit-mode/index.js index b318d74c9df412..17655a80968ed7 100644 --- a/packages/edit-site/src/components/header-edit-mode/index.js +++ b/packages/edit-site/src/components/header-edit-mode/index.js @@ -357,6 +357,11 @@ export default function HeaderEditMode() { name="__experimentalSelectedBlockTools" bubblesVirtually /> + ) } From 399afc6e7793f5d1f4629fa4210e9c2d01e83db2 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Mon, 11 Sep 2023 16:13:33 -0500 Subject: [PATCH 21/25] Remove z-index hack for using editor block tools in header --- .../edit-site/src/components/layout/style.scss | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/packages/edit-site/src/components/layout/style.scss b/packages/edit-site/src/components/layout/style.scss index 11c7bdeeaf2a19..5557266aead4b4 100644 --- a/packages/edit-site/src/components/layout/style.scss +++ b/packages/edit-site/src/components/layout/style.scss @@ -249,23 +249,6 @@ } } -.edit-site-layout.has-fixed-toolbar { - // making the header be lower than the content - // so the fixed toolbar can be positioned on top of it - // but only on desktop - @include break-medium() { - .edit-site-layout__canvas-container { - z-index: 5; - } - .edit-site-site-hub { - z-index: 4; - } - .edit-site-layout__header:focus-within { - z-index: 3; - } - } -} - .is-edit-mode.is-distraction-free { .edit-site-layout__header-container { From 051fd41e581fd3ed64f8007f564845120f68c68c Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Mon, 11 Sep 2023 16:40:18 -0500 Subject: [PATCH 22/25] Add menubar role to toolbar group --- .../components/header/header-toolbar/index.js | 4 +- .../src/components/header-edit-mode/index.js | 137 +++++++++--------- 2 files changed, 72 insertions(+), 69 deletions(-) diff --git a/packages/edit-post/src/components/header/header-toolbar/index.js b/packages/edit-post/src/components/header/header-toolbar/index.js index 314354c57568a2..e08d20d187c463 100644 --- a/packages/edit-post/src/components/header/header-toolbar/index.js +++ b/packages/edit-post/src/components/header/header-toolbar/index.js @@ -131,7 +131,7 @@ function HeaderToolbar() { const shortLabel = ! isInserterOpened ? __( 'Add' ) : __( 'Close' ); return ( - <> + - + ); } diff --git a/packages/edit-site/src/components/header-edit-mode/index.js b/packages/edit-site/src/components/header-edit-mode/index.js index 17655a80968ed7..c86ff0e1f9b7d8 100644 --- a/packages/edit-site/src/components/header-edit-mode/index.js +++ b/packages/edit-site/src/components/header-edit-mode/index.js @@ -191,50 +191,19 @@ export default function HeaderEditMode() { } ) } > { hasDefaultEditorCanvasView && ( - <> - -
- { ! isDistractionFree && ( - - ) } - { isLargeViewport && ( - <> - { ! hasFixedToolbar && ( - - ) } + + +
+ { ! isDistractionFree && ( - { ! isDistractionFree && ( + ) } + { isLargeViewport && ( + <> + { ! hasFixedToolbar && ( + + ) } - ) } - { isZoomedOutViewExperimentEnabled && - ! isDistractionFree && - ! hasFixedToolbar && ( + { ! isDistractionFree && ( ) } + { isZoomedOutViewExperimentEnabled && + ! isDistractionFree && + ! hasFixedToolbar && ( + + ) } - + ) } { ! isDistractionFree && ( From 44851ddc75f4ac4ec32e24cf56e777e2861d2bc6 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Mon, 11 Sep 2023 16:49:03 -0500 Subject: [PATCH 23/25] Fix accidental duplicate block iserter in site editor document toolbar --- .../src/components/header-edit-mode/index.js | 55 +------------------ 1 file changed, 1 insertion(+), 54 deletions(-) diff --git a/packages/edit-site/src/components/header-edit-mode/index.js b/packages/edit-site/src/components/header-edit-mode/index.js index c86ff0e1f9b7d8..cdbc6f0bd91448 100644 --- a/packages/edit-site/src/components/header-edit-mode/index.js +++ b/packages/edit-site/src/components/header-edit-mode/index.js @@ -235,60 +235,6 @@ export default function HeaderEditMode() { disabled={ ! isVisualMode } /> ) } - - { ! isDistractionFree && ( - - ) } - { isZoomedOutViewExperimentEnabled && - ! isDistractionFree && - ! hasFixedToolbar && ( - - ) } ) } { isZoomedOutViewExperimentEnabled && From 5fc387cf40d987597ef1f70ce6bba33763304d47 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Tue, 12 Sep 2023 09:37:34 -0500 Subject: [PATCH 24/25] Make entire header bar menubar role --- .../edit-post/src/components/header/header-toolbar/index.js | 4 ++-- packages/edit-post/src/components/header/index.js | 2 +- packages/edit-site/src/components/header-edit-mode/index.js | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/edit-post/src/components/header/header-toolbar/index.js b/packages/edit-post/src/components/header/header-toolbar/index.js index e08d20d187c463..314354c57568a2 100644 --- a/packages/edit-post/src/components/header/header-toolbar/index.js +++ b/packages/edit-post/src/components/header/header-toolbar/index.js @@ -131,7 +131,7 @@ function HeaderToolbar() { const shortLabel = ! isInserterOpened ? __( 'Add' ) : __( 'Close' ); return ( - + <> - + ); } diff --git a/packages/edit-post/src/components/header/index.js b/packages/edit-post/src/components/header/index.js index ab4bbd4bbc5d15..c1accada0cb3af 100644 --- a/packages/edit-post/src/components/header/index.js +++ b/packages/edit-post/src/components/header/index.js @@ -45,7 +45,7 @@ function Header( { setEntitiesSavedStatesCallback } ) { ); return ( -
+
{ hasDefaultEditorCanvasView && ( - + <> - + ) } { ! isDistractionFree && ( From a9b07109a01d4c3a2696d776f853ba8630aca5b2 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Tue, 12 Sep 2023 17:34:56 -0500 Subject: [PATCH 25/25] Add focusEditorOnEscape to NavigableToolbar to return focus to editor by default on escape keypress Not committed to this, but it does work since we've removed bubblesVirtually from the toolbar slots and the inline rich editor doesn't use slots for its tools. I like that this brings this behavior by default to the toolbars, but unsure about if it's a good idea overall. --- .../block-tools/block-contextual-toolbar.js | 10 ----- .../src/components/navigable-toolbar/index.js | 40 +++++++++++++++++++ 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js b/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js index ba69826d8533a5..ac27341df1a973 100644 --- a/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js +++ b/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js @@ -17,7 +17,6 @@ import { } from '@wordpress/components'; import { next, previous } from '@wordpress/icons'; import { useViewportMatch } from '@wordpress/compose'; -import { ESCAPE } from '@wordpress/keycodes'; /** * Internal dependencies @@ -39,7 +38,6 @@ function UnforwardBlockContextualToolbar( const { blockType, blockEditingMode, - lastFocus, hasParents, showParentSelector, selectedBlockClientId, @@ -47,7 +45,6 @@ function UnforwardBlockContextualToolbar( const { getBlockName, getBlockParents, - getLastFocus, getSelectedBlockClientIds, getBlockEditingMode, } = select( blockEditorStore ); @@ -65,7 +62,6 @@ function UnforwardBlockContextualToolbar( _selectedBlockClientId && getBlockType( getBlockName( _selectedBlockClientId ) ), blockEditingMode: getBlockEditingMode( _selectedBlockClientId ), - lastFocus: getLastFocus(), hasParents: parents.length, showParentSelector: parentBlockType && @@ -183,12 +179,6 @@ function UnforwardBlockContextualToolbar( className={ classes } /* translators: accessibility text for the block toolbar */ aria-label={ __( 'Block tools' ) } - handleOnKeyDown={ ( event ) => { - if ( event.keyCode === ESCAPE && lastFocus?.current ) { - event.preventDefault(); - lastFocus.current.focus(); - } - } } { ...props } > { ! isCollapsed && } diff --git a/packages/block-editor/src/components/navigable-toolbar/index.js b/packages/block-editor/src/components/navigable-toolbar/index.js index c9c2b6530c2b1e..0314aec454fb96 100644 --- a/packages/block-editor/src/components/navigable-toolbar/index.js +++ b/packages/block-editor/src/components/navigable-toolbar/index.js @@ -10,10 +10,17 @@ import { useEffect, useCallback, } from '@wordpress/element'; +import { useSelect } from '@wordpress/data'; import deprecated from '@wordpress/deprecated'; import { focus } from '@wordpress/dom'; +import { ESCAPE } from '@wordpress/keycodes'; import { useShortcut } from '@wordpress/keyboard-shortcuts'; +/** + * Internal dependencies + */ +import { store as blockEditorStore } from '../../store'; + function hasOnlyToolbarItem( elements ) { const dataProp = 'toolbarItem'; return ! elements.some( ( element ) => ! ( dataProp in element.dataset ) ); @@ -165,6 +172,7 @@ function UnforwardNavigableToolbar( shouldUseKeyboardFocusShortcut = true, __experimentalInitialIndex: initialIndex, __experimentalOnIndexChange: onIndexChange, + focusEditorOnEscape = true, ...props }, ref @@ -182,6 +190,13 @@ function UnforwardNavigableToolbar( shouldUseKeyboardFocusShortcut ); + const { lastFocus } = useSelect( ( select ) => { + const { getLastFocus } = select( blockEditorStore ); + return { + lastFocus: getLastFocus(), + }; + }, [] ); + useEffect( () => { const navigableToolbarRef = toolbarRef.current; @@ -201,6 +216,31 @@ function UnforwardNavigableToolbar( } }, [ handleOnKeyDown, toolbarRef ] ); + + // TODO: Not sure if this is a good idea... but it's working for now and gets the behavior we're after with the fewest lines of code, but also is limiting. + useEffect( () => { + const navigableToolbarRef = toolbarRef.current; + + if ( focusEditorOnEscape ) { + const handleKeyDown = ( event ) => { + if ( event.keyCode === ESCAPE && lastFocus?.current ) { + // Focus the last focused element when pressing escape. + event.preventDefault(); + lastFocus.current.focus(); + } + }; + + navigableToolbarRef.addEventListener( 'keydown', handleKeyDown ); + + return () => { + navigableToolbarRef.removeEventListener( + 'keydown', + handleKeyDown + ); + }; + } + }, [ focusEditorOnEscape, toolbarRef, lastFocus ] ); + if ( isAccessibleToolbar ) { return (