diff --git a/packages/block-editor/src/components/block-toolbar/style.scss b/packages/block-editor/src/components/block-toolbar/style.scss index eef1678e2dfab..3f8a7057aef84 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 5ba71bee88987..9d9f3edc5442f 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 696299689d185..bc2729fbb1599 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 e1204b90d0c5d..fe216e1058f6f 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 fe16d791dc7c5..cf4e251874282 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 201831b90cd78..34e4573c719dd 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 bd6d16b89c7fa..3bf341c34c0eb 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 52df978e2cd5b..105b31e3122ac 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 b86e66af7a849..9c650f8660da1 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 2e0d470818fec..c1c8222394979 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 && ( +