diff --git a/packages/block-editor/src/components/block-toolbar/style.scss b/packages/block-editor/src/components/block-toolbar/style.scss
index eef1678e2dfab3..3f8a7057aef84f 100644
--- a/packages/block-editor/src/components/block-toolbar/style.scss
+++ b/packages/block-editor/src/components/block-toolbar/style.scss
@@ -56,6 +56,14 @@
}
}
+.block-editor-block-contextual-toolbar.is-fixed {
+ position: sticky;
+ top: 0;
+ z-index: z-index(".block-editor-block-popover");
+ display: block;
+ width: 100%;
+}
+
// on desktop browsers the fixed toolbar has tweaked borders
@include break-medium() {
.block-editor-block-contextual-toolbar.is-fixed {
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 deac140f412ca5..83e241010330f7 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,22 +7,8 @@ import classnames from 'classnames';
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
-import {
- forwardRef,
- useLayoutEffect,
- useEffect,
- useRef,
- useState,
-} from '@wordpress/element';
import { hasBlockSupport, store as blocksStore } from '@wordpress/blocks';
import { useSelect } from '@wordpress/data';
-import {
- ToolbarItem,
- ToolbarButton,
- ToolbarGroup,
-} from '@wordpress/components';
-import { next, previous } from '@wordpress/icons';
-import { useViewportMatch } from '@wordpress/compose';
/**
* Internal dependencies
@@ -32,141 +18,46 @@ import BlockToolbar from '../block-toolbar';
import { store as blockEditorStore } from '../../store';
import { useHasAnyBlockControls } from '../block-controls/use-has-block-controls';
-function UnforwardedBlockContextualToolbar(
- { focusOnMount, isFixed, ...props },
- ref
-) {
- // When the toolbar is fixed it can be collapsed
- const [ isCollapsed, setIsCollapsed ] = useState( false );
- const toolbarButtonRef = useRef();
-
- const isLargeViewport = useViewportMatch( 'medium' );
- const {
- blockType,
- blockEditingMode,
- hasParents,
- showParentSelector,
- selectedBlockClientId,
- } = useSelect( ( select ) => {
- const {
- getBlockName,
- getBlockParents,
- getSelectedBlockClientIds,
- getBlockEditingMode,
- } = select( blockEditorStore );
- const { getBlockType } = select( blocksStore );
- const selectedBlockClientIds = getSelectedBlockClientIds();
- const _selectedBlockClientId = selectedBlockClientIds[ 0 ];
- const parents = getBlockParents( _selectedBlockClientId );
- const firstParentClientId = parents[ parents.length - 1 ];
- const parentBlockName = getBlockName( firstParentClientId );
- const parentBlockType = getBlockType( parentBlockName );
-
- return {
- selectedBlockClientId: _selectedBlockClientId,
- blockType:
- _selectedBlockClientId &&
- getBlockType( getBlockName( _selectedBlockClientId ) ),
- blockEditingMode: getBlockEditingMode( _selectedBlockClientId ),
- hasParents: parents.length,
- showParentSelector:
- parentBlockType &&
- getBlockEditingMode( firstParentClientId ) === 'default' &&
- hasBlockSupport(
- parentBlockType,
- '__experimentalParentSelector',
- true
- ) &&
- selectedBlockClientIds.length <= 1 &&
- getBlockEditingMode( _selectedBlockClientId ) === 'default',
- };
- }, [] );
-
- useEffect( () => {
- setIsCollapsed( false );
- }, [ selectedBlockClientId ] );
-
- const isLargerThanTabletViewport = useViewportMatch( 'large', '>=' );
- const isFullscreen =
- document.body.classList.contains( 'is-fullscreen-mode' );
-
- /**
- * The following code is a workaround to fix the width of the toolbar
- * it should be removed when the toolbar will be rendered inline
- * FIXME: remove this layout effect when the toolbar is no longer
- * absolutely positioned
- */
- useLayoutEffect( () => {
- // don't do anything if not fixed toolbar
- if ( ! isFixed ) {
- return;
- }
-
- const blockToolbar = document.querySelector(
- '.block-editor-block-contextual-toolbar'
- );
-
- if ( ! blockToolbar ) {
- return;
- }
-
- if ( ! blockType ) {
- blockToolbar.style.width = 'initial';
- 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 or widget editor
- const pinnedItems = document.querySelector(
- '.edit-post-header__settings, .edit-widgets-header__actions'
- );
- // 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,
- ] );
+export default function BlockContextualToolbar( {
+ focusOnMount,
+ isFixed,
+ ...props
+} ) {
+ const { blockType, blockEditingMode, hasParents, showParentSelector } =
+ useSelect( ( select ) => {
+ const {
+ getBlockName,
+ getBlockParents,
+ getSelectedBlockClientIds,
+ getBlockEditingMode,
+ } = select( blockEditorStore );
+ const { getBlockType } = select( blocksStore );
+ const selectedBlockClientIds = getSelectedBlockClientIds();
+ const _selectedBlockClientId = selectedBlockClientIds[ 0 ];
+ const parents = getBlockParents( _selectedBlockClientId );
+ const firstParentClientId = parents[ parents.length - 1 ];
+ const parentBlockName = getBlockName( firstParentClientId );
+ const parentBlockType = getBlockType( parentBlockName );
+
+ return {
+ selectedBlockClientId: _selectedBlockClientId,
+ blockType:
+ _selectedBlockClientId &&
+ getBlockType( getBlockName( _selectedBlockClientId ) ),
+ blockEditingMode: getBlockEditingMode( _selectedBlockClientId ),
+ hasParents: parents.length,
+ showParentSelector:
+ parentBlockType &&
+ getBlockEditingMode( firstParentClientId ) === 'default' &&
+ hasBlockSupport(
+ parentBlockType,
+ '__experimentalParentSelector',
+ true
+ ) &&
+ selectedBlockClientIds.length <= 1 &&
+ getBlockEditingMode( _selectedBlockClientId ) === 'default',
+ };
+ }, [] );
const isToolbarEnabled =
blockType &&
@@ -183,12 +74,10 @@ function UnforwardedBlockContextualToolbar(
const classes = classnames( 'block-editor-block-contextual-toolbar', {
'has-parent': hasParents && showParentSelector,
'is-fixed': isFixed,
- 'is-collapsed': isCollapsed,
} );
return (
- { ! isCollapsed && }
- { isFixed && isLargeViewport && blockType && (
-
- {
- setIsCollapsed( ( collapsed ) => ! collapsed );
- toolbarButtonRef.current.focus();
- } }
- label={
- isCollapsed
- ? __( 'Show block tools' )
- : __( 'Hide block tools' )
- }
- />
-
- ) }
+
);
}
-
-export const BlockContextualToolbar = forwardRef(
- UnforwardedBlockContextualToolbar
-);
-
-export default BlockContextualToolbar;
diff --git a/packages/block-editor/src/components/block-tools/index.js b/packages/block-editor/src/components/block-tools/index.js
index 696299689d1850..bc2729fbb15990 100644
--- a/packages/block-editor/src/components/block-tools/index.js
+++ b/packages/block-editor/src/components/block-tools/index.js
@@ -88,8 +88,6 @@ export default function BlockTools( {
moveBlocksDown,
} = useDispatch( blockEditorStore );
- const selectedBlockToolsRef = useRef( null );
-
function onKeyDown( event ) {
if ( event.defaultPrevented ) return;
@@ -132,7 +130,7 @@ export default function BlockTools( {
insertBeforeBlock( clientIds[ 0 ] );
}
} else if ( isMatch( 'core/block-editor/unselect', event ) ) {
- if ( selectedBlockToolsRef?.current?.contains( event.target ) ) {
+ if ( event.target.closest( '[role=toolbar]' ) ) {
// 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.
@@ -164,6 +162,12 @@ export default function BlockTools( {
const blockToolbarRef = usePopoverScroll( __unstableContentRef );
const blockToolbarAfterRef = usePopoverScroll( __unstableContentRef );
+ // Conditions for fixed toolbar
+ // 1. Not zoom out mode
+ // 2. It's a large viewport. If it's a smaller viewport, let the floating toolbar handle it as it already has styles attached to make it render that way.
+ // 3. Fixed toolbar is enabled
+ const isTopToolbar = ! isZoomOutMode && hasFixedToolbar && isLargeViewport;
+
return (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
@@ -173,13 +177,11 @@ export default function BlockTools( {
__unstableContentRef={ __unstableContentRef }
/>
) }
- { ! isZoomOutMode &&
- ( hasFixedToolbar || ! isLargeViewport ) && (
-
- ) }
+ { /* If there is no slot available, such as in the standalone block editor, render within the editor */ }
+
+ { ! isLargeViewport && ( // Small viewports always get a fixed toolbar
+
+ ) }
{ showEmptyBlockSideInserter && (
) }
{ /* Used for the inline rich text toolbar. */ }
-
+ { ! isTopToolbar && (
+
+ ) }
{ children }
{ /* Used for inline rich text popovers. */ }
{ shouldShowContextualToolbar && (
.block-editor-block-toolbar {
diff --git a/packages/block-editor/src/components/navigable-toolbar/index.js b/packages/block-editor/src/components/navigable-toolbar/index.js
index e1204b90d0c5d6..fe216e1058f6f0 100644
--- a/packages/block-editor/src/components/navigable-toolbar/index.js
+++ b/packages/block-editor/src/components/navigable-toolbar/index.js
@@ -3,7 +3,6 @@
*/
import { NavigableMenu, Toolbar } from '@wordpress/components';
import {
- forwardRef,
useState,
useRef,
useLayoutEffect,
@@ -196,21 +195,16 @@ function useToolbarFocus( {
}, [ focusEditorOnEscape, lastFocus, toolbarRef ] );
}
-function UnforwardedNavigableToolbar(
- {
- children,
- focusOnMount,
- focusEditorOnEscape = false,
- shouldUseKeyboardFocusShortcut = true,
- __experimentalInitialIndex: initialIndex,
- __experimentalOnIndexChange: onIndexChange,
- ...props
- },
- ref
-) {
- const maybeRef = useRef();
- // If a ref was not forwarded, we create one.
- const toolbarRef = ref || maybeRef;
+export default function NavigableToolbar( {
+ children,
+ focusOnMount,
+ focusEditorOnEscape = false,
+ shouldUseKeyboardFocusShortcut = true,
+ __experimentalInitialIndex: initialIndex,
+ __experimentalOnIndexChange: onIndexChange,
+ ...props
+} ) {
+ const toolbarRef = useRef();
const isAccessibleToolbar = useIsAccessibleToolbar( toolbarRef );
useToolbarFocus( {
@@ -246,7 +240,3 @@ function UnforwardedNavigableToolbar(
);
}
-
-export const NavigableToolbar = forwardRef( UnforwardedNavigableToolbar );
-
-export default NavigableToolbar;
diff --git a/packages/block-editor/src/private-apis.js b/packages/block-editor/src/private-apis.js
index fe16d791dc7c5a..cf4e2518742825 100644
--- a/packages/block-editor/src/private-apis.js
+++ b/packages/block-editor/src/private-apis.js
@@ -10,6 +10,7 @@ import ResizableBoxPopover from './components/resizable-box-popover';
import { ComposedPrivateInserter as PrivateInserter } from './components/inserter';
import { PrivateListView } from './components/list-view';
import BlockInfo from './components/block-info-slot-fill';
+import BlockContextualToolbar from './components/block-tools/block-contextual-toolbar';
import { useShouldContextualToolbarShow } from './utils/use-should-contextual-toolbar-show';
import { cleanEmptyObject } from './hooks/utils';
import BlockQuickNavigation from './components/block-quick-navigation';
@@ -41,6 +42,7 @@ lock( privateApis, {
PrivateListView,
ResizableBoxPopover,
BlockInfo,
+ BlockContextualToolbar,
useShouldContextualToolbarShow,
cleanEmptyObject,
BlockQuickNavigation,
diff --git a/packages/customize-widgets/src/components/header/index.js b/packages/customize-widgets/src/components/header/index.js
index 201831b90cd78a..34e4573c719dd5 100644
--- a/packages/customize-widgets/src/components/header/index.js
+++ b/packages/customize-widgets/src/components/header/index.js
@@ -6,11 +6,15 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
-import { createPortal, useState, useEffect } from '@wordpress/element';
-import { __, _x, isRTL } from '@wordpress/i18n';
-import { ToolbarButton } from '@wordpress/components';
-import { NavigableToolbar } from '@wordpress/block-editor';
+import { Popover, ToolbarButton } from '@wordpress/components';
+import { useViewportMatch } from '@wordpress/compose';
+import {
+ NavigableToolbar,
+ privateApis as blockEditorPrivateApis,
+} from '@wordpress/block-editor';
+import { createPortal, useEffect, useRef, useState } from '@wordpress/element';
import { displayShortcut, isAppleOS } from '@wordpress/keycodes';
+import { __, _x, isRTL } from '@wordpress/i18n';
import { plus, undo as undoIcon, redo as redoIcon } from '@wordpress/icons';
/**
@@ -18,6 +22,9 @@ import { plus, undo as undoIcon, redo as redoIcon } from '@wordpress/icons';
*/
import Inserter from '../inserter';
import MoreMenu from '../more-menu';
+import { unlock } from '../../lock-unlock';
+
+const { BlockContextualToolbar } = unlock( blockEditorPrivateApis );
function Header( {
sidebar,
@@ -26,6 +33,8 @@ function Header( {
setIsInserterOpened,
isFixedToolbarActive,
} ) {
+ const isLargeViewport = useViewportMatch( 'medium' );
+ const blockToolbarRef = useRef();
const [ [ hasUndo, hasRedo ], setUndoRedo ] = useState( [
sidebar.hasUndo(),
sidebar.hasRedo(),
@@ -98,6 +107,18 @@ function Header( {
,
inserter.contentContainer[ 0 ]
) }
+
+ { isFixedToolbarActive && isLargeViewport && (
+ <>
+
+
+
+
+ >
+ ) }
>
);
}
diff --git a/packages/customize-widgets/src/style.scss b/packages/customize-widgets/src/style.scss
index bd6d16b89c7fa7..3bf341c34c0eb1 100644
--- a/packages/customize-widgets/src/style.scss
+++ b/packages/customize-widgets/src/style.scss
@@ -17,34 +17,3 @@
.customize-widgets-popover {
@include reset;
}
-
-/**
- Fixed bloock toolbar overrides. We can't detect each editor instance
- in the styles of the block editor component so we need to override
- the fixed styles here because the breakpoint css does not fire in the
- customizer's left panel.
-*/
-.block-editor-block-contextual-toolbar {
- &.is-fixed {
- position: sticky;
- top: 0;
- left: 0;
- z-index: z-index(".block-editor-block-list__insertion-point");
- width: calc(100% + 2 * 12px); //12px is the padding of customizer sidebar content
-
- overflow-y: hidden;
-
- border: none;
- border-bottom: $border-width solid $gray-200;
- border-radius: 0;
-
- .block-editor-block-toolbar .components-toolbar-group,
- .block-editor-block-toolbar .components-toolbar {
- border-right-color: $gray-200;
- }
-
- &.is-collapsed {
- margin-left: -12px; //12px is the padding of customizer sidebar content
- }
- }
-}
diff --git a/packages/edit-post/src/components/header/document-actions/index.js b/packages/edit-post/src/components/header/document-actions/index.js
index 52df978e2cd5b3..105b31e3122ac9 100644
--- a/packages/edit-post/src/components/header/document-actions/index.js
+++ b/packages/edit-post/src/components/header/document-actions/index.js
@@ -20,21 +20,18 @@ import { displayShortcut } from '@wordpress/keycodes';
import { store as editPostStore } from '../../../store';
function DocumentActions() {
- const { template, isEditing } = useSelect( ( select ) => {
- const { isEditingTemplate, getEditedPostTemplate } =
- select( editPostStore );
- const _isEditing = isEditingTemplate();
+ const { template } = useSelect( ( select ) => {
+ const { getEditedPostTemplate } = select( editPostStore );
return {
- template: _isEditing ? getEditedPostTemplate() : null,
- isEditing: _isEditing,
+ template: getEditedPostTemplate(),
};
}, [] );
const { clearSelectedBlock } = useDispatch( blockEditorStore );
const { setIsEditingTemplate } = useDispatch( editPostStore );
const { open: openCommandCenter } = useDispatch( commandsStore );
- if ( ! isEditing || ! template ) {
+ if ( ! template ) {
return null;
}
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 b86e66af7a849e..9c650f8660da18 100644
--- a/packages/edit-post/src/components/header/header-toolbar/index.js
+++ b/packages/edit-post/src/components/header/header-toolbar/index.js
@@ -19,7 +19,6 @@ import { Button, ToolbarItem } from '@wordpress/components';
import { listView, plus } from '@wordpress/icons';
import { useRef, useCallback } from '@wordpress/element';
import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';
-import { store as preferencesStore } from '@wordpress/preferences';
/**
* Internal dependencies
@@ -33,7 +32,7 @@ const preventDefault = ( event ) => {
event.preventDefault();
};
-function HeaderToolbar( { setListViewToggleElement } ) {
+function HeaderToolbar( { hasFixedToolbar, setListViewToggleElement } ) {
const inserterButton = useRef();
const { setIsInserterOpened, setIsListViewOpened } =
useDispatch( editPostStore );
@@ -44,7 +43,6 @@ function HeaderToolbar( { setListViewToggleElement } ) {
showIconLabels,
isListViewOpen,
listViewShortcut,
- hasFixedToolbar,
} = useSelect( ( select ) => {
const { hasInserterItems, getBlockRootClientId, getBlockSelectionEnd } =
select( blockEditorStore );
@@ -52,7 +50,6 @@ function HeaderToolbar( { setListViewToggleElement } ) {
const { getEditorMode, isFeatureActive, isListViewOpened } =
select( editPostStore );
const { getShortcutRepresentation } = select( keyboardShortcutsStore );
- const { get: getPreference } = select( preferencesStore );
return {
// This setting (richEditingEnabled) should not live in the block editor's setting.
@@ -69,7 +66,6 @@ function HeaderToolbar( { setListViewToggleElement } ) {
listViewShortcut: getShortcutRepresentation(
'core/edit-post/toggle-list-view'
),
- hasFixedToolbar: getPreference( 'core/edit-post', 'fixedToolbar' ),
};
}, [] );
diff --git a/packages/edit-post/src/components/header/index.js b/packages/edit-post/src/components/header/index.js
index 2e0d470818fecd..c1c8222394979d 100644
--- a/packages/edit-post/src/components/header/index.js
+++ b/packages/edit-post/src/components/header/index.js
@@ -1,11 +1,28 @@
+/**
+ * External dependencies
+ */
+import classnames from 'classnames';
+
/**
* WordPress dependencies
*/
+import {
+ privateApis as blockEditorPrivateApis,
+ store as blockEditorStore,
+} from '@wordpress/block-editor';
import { PostSavedState, PostPreviewButton } from '@wordpress/editor';
+import { useEffect, useRef, useState } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
+import { __ } from '@wordpress/i18n';
+import { next, previous } from '@wordpress/icons';
import { PinnedItems } from '@wordpress/interface';
import { useViewportMatch } from '@wordpress/compose';
-import { __unstableMotion as motion } from '@wordpress/components';
+import {
+ Button,
+ __unstableMotion as motion,
+ Popover,
+} from '@wordpress/components';
+import { store as preferencesStore } from '@wordpress/preferences';
/**
* Internal dependencies
@@ -19,6 +36,9 @@ import ViewLink from '../view-link';
import MainDashboardButton from './main-dashboard-button';
import { store as editPostStore } from '../../store';
import DocumentActions from './document-actions';
+import { unlock } from '../../lock-unlock';
+
+const { BlockContextualToolbar } = unlock( blockEditorPrivateApis );
const slideY = {
hidden: { y: '-50px' },
@@ -36,18 +56,43 @@ function Header( {
setEntitiesSavedStatesCallback,
setListViewToggleElement,
} ) {
- const isLargeViewport = useViewportMatch( 'large' );
- const { hasActiveMetaboxes, isPublishSidebarOpened, showIconLabels } =
- useSelect(
- ( select ) => ( {
- hasActiveMetaboxes: select( editPostStore ).hasMetaBoxes(),
- isPublishSidebarOpened:
- select( editPostStore ).isPublishSidebarOpened(),
- showIconLabels:
- select( editPostStore ).isFeatureActive( 'showIconLabels' ),
- } ),
- []
- );
+ const isWideViewport = useViewportMatch( 'large' );
+ const isLargeViewport = useViewportMatch( 'medium' );
+ const blockToolbarRef = useRef();
+ const {
+ blockSelectionStart,
+ hasActiveMetaboxes,
+ hasFixedToolbar,
+ isEditingTemplate,
+ isPublishSidebarOpened,
+ showIconLabels,
+ } = useSelect( ( select ) => {
+ const { get: getPreference } = select( preferencesStore );
+
+ return {
+ blockSelectionStart:
+ select( blockEditorStore ).getBlockSelectionStart(),
+ hasActiveMetaboxes: select( editPostStore ).hasMetaBoxes(),
+ isEditingTemplate: select( editPostStore ).isEditingTemplate(),
+ isPublishSidebarOpened:
+ select( editPostStore ).isPublishSidebarOpened(),
+ hasFixedToolbar: getPreference( 'core/edit-post', 'fixedToolbar' ),
+ showIconLabels:
+ select( editPostStore ).isFeatureActive( 'showIconLabels' ),
+ };
+ }, [] );
+
+ const [ isBlockToolsCollapsed, setIsBlockToolsCollapsed ] =
+ useState( true );
+
+ const hasBlockSelected = !! blockSelectionStart;
+
+ useEffect( () => {
+ // If we have a new block selection, show the block tools
+ if ( blockSelectionStart ) {
+ setIsBlockToolsCollapsed( false );
+ }
+ }, [ blockSelectionStart ] );
return (
@@ -65,10 +110,52 @@ function Header( {
className="edit-post-header__toolbar"
>
-
-
+ { hasFixedToolbar && isLargeViewport && (
+ <>
+
+
+
+
+ { isEditingTemplate && hasBlockSelected && (
+