diff --git a/packages/block-editor/src/components/block-controls/use-has-block-controls.js b/packages/block-editor/src/components/block-controls/use-has-block-controls.js deleted file mode 100644 index f7884cc1882ed5..00000000000000 --- a/packages/block-editor/src/components/block-controls/use-has-block-controls.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * WordPress dependencies - */ -import { __experimentalUseSlotFills as useSlotFills } from '@wordpress/components'; -import warning from '@wordpress/warning'; - -/** - * Internal dependencies - */ -import groups from './groups'; - -export function useHasAnyBlockControls() { - let hasAnyBlockControls = false; - for ( const group in groups ) { - // It is safe to violate the rules of hooks here as the `groups` object - // is static and will not change length between renders. Do not return - // early as that will cause the hook to be called a different number of - // times between renders. - // eslint-disable-next-line react-hooks/rules-of-hooks - if ( useHasBlockControls( group ) ) { - hasAnyBlockControls = true; - } - } - return hasAnyBlockControls; -} - -export function useHasBlockControls( group = 'default' ) { - const Slot = groups[ group ]?.Slot; - const fills = useSlotFills( Slot?.__unstableName ); - if ( ! Slot ) { - warning( `Unknown BlockControls group "${ group }" provided.` ); - return null; - } - return !! fills?.length; -} diff --git a/packages/block-editor/src/components/block-parent-selector/index.js b/packages/block-editor/src/components/block-parent-selector/index.js index 80b314eeb42e5c..9090de42f8b7d7 100644 --- a/packages/block-editor/src/components/block-parent-selector/index.js +++ b/packages/block-editor/src/components/block-parent-selector/index.js @@ -14,6 +14,7 @@ import useBlockDisplayInformation from '../use-block-display-information'; import BlockIcon from '../block-icon'; import { useShowHoveredOrFocusedGestures } from '../block-toolbar/utils'; import { store as blockEditorStore } from '../../store'; +import { unlock } from '../../lock-unlock'; /** * Block parent selector component, displaying the hierarchy of the @@ -23,24 +24,26 @@ import { store as blockEditorStore } from '../../store'; */ export default function BlockParentSelector() { const { selectBlock } = useDispatch( blockEditorStore ); - const { firstParentClientId, isVisible } = useSelect( ( select ) => { + const { parentClientId, isVisible } = useSelect( ( select ) => { const { getBlockName, getBlockParents, getSelectedBlockClientId, getBlockEditingMode, - } = select( blockEditorStore ); + getParentSectionBlock, + } = unlock( select( blockEditorStore ) ); const { hasBlockSupport } = select( blocksStore ); const selectedBlockClientId = getSelectedBlockClientId(); + const parentSection = getParentSectionBlock( selectedBlockClientId ); const parents = getBlockParents( selectedBlockClientId ); - const _firstParentClientId = parents[ parents.length - 1 ]; - const parentBlockName = getBlockName( _firstParentClientId ); + const _parentClientId = parentSection ?? parents[ parents.length - 1 ]; + const parentBlockName = getBlockName( _parentClientId ); const _parentBlockType = getBlockType( parentBlockName ); return { - firstParentClientId: _firstParentClientId, + parentClientId: _parentClientId, isVisible: - _firstParentClientId && - getBlockEditingMode( _firstParentClientId ) === 'default' && + _parentClientId && + getBlockEditingMode( _parentClientId ) !== 'disabled' && hasBlockSupport( _parentBlockType, '__experimentalParentSelector', @@ -48,7 +51,7 @@ export default function BlockParentSelector() { ), }; }, [] ); - const blockInformation = useBlockDisplayInformation( firstParentClientId ); + const blockInformation = useBlockDisplayInformation( parentClientId ); // Allows highlighting the parent block outline when focusing or hovering // the parent block selector within the child. @@ -65,13 +68,13 @@ export default function BlockParentSelector() { return (
selectBlock( firstParentClientId ) } + onClick={ () => selectBlock( parentClientId ) } label={ sprintf( /* translators: %s: Name of the block's parent. */ __( 'Select parent block: %s' ), diff --git a/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js b/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js index fff5acc7b79c46..ac2b99ac2bb620 100644 --- a/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js +++ b/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js @@ -183,6 +183,9 @@ export function BlockSettingsDropdown( { } } + const shouldShowBlockParentMenuItem = + ! parentBlockIsSelected && !! firstParentClientId; + return ( ( - - { ( { onClose } ) => ( - <> - - <__unstableBlockSettingsMenuFirstItem.Slot - fillProps={ { onClose } } - /> - { ! parentBlockIsSelected && - !! firstParentClientId && ( + } ) => { + // It is possible that some plugins register fills for this menu + // even if Core doesn't render anything in the block settings menu. + // in which case, we may want to render the menu anyway. + // That said for now, we can start more conservative. + const isEmpty = + ! canRemove && + ! canDuplicate && + ! canInsertBlock && + isContentOnly; + + if ( isEmpty ) { + return null; + } + + return ( + + { ( { onClose } ) => ( + <> + + <__unstableBlockSettingsMenuFirstItem.Slot + fillProps={ { onClose } } + /> + { shouldShowBlockParentMenuItem && ( ) } - { count === 1 && ( - - ) } - { ! isContentOnly && ( - - ) } - { canDuplicate && ( - - { __( 'Duplicate' ) } - - ) } - { canInsertBlock && ! isContentOnly && ( - <> + { count === 1 && ( + + ) } + { ! isContentOnly && ( + + ) } + { canDuplicate && ( - { __( 'Add before' ) } + { __( 'Duplicate' ) } + + ) } + { canInsertBlock && ! isContentOnly && ( + <> + + { __( 'Add before' ) } + + + { __( 'Add after' ) } + + + ) } + + { canCopyStyles && ! isContentOnly && ( + + + + { __( 'Paste styles' ) } + + ) } + + { typeof children === 'function' + ? children( { onClose } ) + : Children.map( ( child ) => + cloneElement( child, { onClose } ) + ) } + { canRemove && ( + - { __( 'Add after' ) } + { __( 'Delete' ) } - + ) } - - { canCopyStyles && ! isContentOnly && ( - - - - { __( 'Paste styles' ) } - - - ) } - - { typeof children === 'function' - ? children( { onClose } ) - : Children.map( ( child ) => - cloneElement( child, { onClose } ) - ) } - { canRemove && ( - - - { __( 'Delete' ) } - - - ) } - - ) } - - ) } + + ) } + + ); + } } ); } diff --git a/packages/block-editor/src/components/block-switcher/index.js b/packages/block-editor/src/components/block-switcher/index.js index 98e7f7b2d21420..79f33bd30d7537 100644 --- a/packages/block-editor/src/components/block-switcher/index.js +++ b/packages/block-editor/src/components/block-switcher/index.js @@ -35,36 +35,40 @@ function BlockSwitcherDropdownMenuContents( { clientIds, hasBlockStyles, canRemove, - isUsingBindings, } ) { const { replaceBlocks, multiSelect, updateBlockAttributes } = useDispatch( blockEditorStore ); - const { possibleBlockTransformations, patterns, blocks } = useSelect( - ( select ) => { - const { - getBlocksByClientId, - getBlockRootClientId, - getBlockTransformItems, - __experimentalGetPatternTransformItems, - } = select( blockEditorStore ); - const rootClientId = getBlockRootClientId( - Array.isArray( clientIds ) ? clientIds[ 0 ] : clientIds - ); - const _blocks = getBlocksByClientId( clientIds ); - return { - blocks: _blocks, - possibleBlockTransformations: getBlockTransformItems( - _blocks, - rootClientId - ), - patterns: __experimentalGetPatternTransformItems( - _blocks, - rootClientId - ), - }; - }, - [ clientIds ] - ); + const { possibleBlockTransformations, patterns, blocks, isUsingBindings } = + useSelect( + ( select ) => { + const { + getBlockAttributes, + getBlocksByClientId, + getBlockRootClientId, + getBlockTransformItems, + __experimentalGetPatternTransformItems, + } = select( blockEditorStore ); + const rootClientId = getBlockRootClientId( clientIds[ 0 ] ); + const _blocks = getBlocksByClientId( clientIds ); + return { + blocks: _blocks, + possibleBlockTransformations: getBlockTransformItems( + _blocks, + rootClientId + ), + patterns: __experimentalGetPatternTransformItems( + _blocks, + rootClientId + ), + isUsingBindings: clientIds.every( + ( clientId ) => + !! getBlockAttributes( clientId )?.metadata + ?.bindings + ), + }; + }, + [ clientIds ] + ); const blockVariationTransformations = useBlockVariationTransforms( { clientIds, blocks, @@ -196,7 +200,7 @@ const BlockIndicator = ( { icon, showTitle, blockTitle } ) => ( ); -export const BlockSwitcher = ( { clientIds, disabled, isUsingBindings } ) => { +export const BlockSwitcher = ( { clientIds } ) => { const { hasContentOnlyLocking, canRemove, @@ -205,6 +209,7 @@ export const BlockSwitcher = ( { clientIds, disabled, isUsingBindings } ) => { invalidBlocks, isReusable, isTemplate, + isDisabled, } = useSelect( ( select ) => { const { @@ -212,6 +217,7 @@ export const BlockSwitcher = ( { clientIds, disabled, isUsingBindings } ) => { getBlocksByClientId, getBlockAttributes, canRemoveBlocks, + getBlockEditingMode, } = select( blockEditorStore ); const { getBlockStyles, getBlockType, getActiveBlockVariation } = select( blocksStore ); @@ -222,6 +228,7 @@ export const BlockSwitcher = ( { clientIds, disabled, isUsingBindings } ) => { const [ { name: firstBlockName } ] = _blocks; const _isSingleBlockSelected = _blocks.length === 1; const blockType = getBlockType( firstBlockName ); + const editingMode = getBlockEditingMode( clientIds[ 0 ] ); let _icon; let _hasTemplateLock; @@ -256,6 +263,7 @@ export const BlockSwitcher = ( { clientIds, disabled, isUsingBindings } ) => { isTemplate: _isSingleBlockSelected && isTemplatePart( _blocks[ 0 ] ), hasContentOnlyLocking: _hasTemplateLock, + isDisabled: editingMode !== 'default', }; }, [ clientIds ] @@ -275,7 +283,7 @@ export const BlockSwitcher = ( { clientIds, disabled, isUsingBindings } ) => { : __( 'Multiple blocks selected' ); const hideDropdown = - disabled || + isDisabled || ( ! hasBlockStyles && ! canRemove ) || hasContentOnlyLocking; @@ -339,7 +347,6 @@ export const BlockSwitcher = ( { clientIds, disabled, isUsingBindings } ) => { clientIds={ clientIds } hasBlockStyles={ hasBlockStyles } canRemove={ canRemove } - isUsingBindings={ isUsingBindings } /> ) } diff --git a/packages/block-editor/src/components/block-toolbar/index.js b/packages/block-editor/src/components/block-toolbar/index.js index 6c4789cb2924f2..2ac2cbb12ff352 100644 --- a/packages/block-editor/src/components/block-toolbar/index.js +++ b/packages/block-editor/src/components/block-toolbar/index.js @@ -35,6 +35,7 @@ import { store as blockEditorStore } from '../../store'; import __unstableBlockNameContext from './block-name-context'; import NavigableToolbar from '../navigable-toolbar'; import { useHasBlockToolbar } from './use-has-block-toolbar'; +import { unlock } from '../../lock-unlock'; /** * Renders the block toolbar. @@ -58,7 +59,6 @@ export function PrivateBlockToolbar( { const { blockClientId, blockClientIds, - isContentOnlyEditingMode, isDefaultEditingMode, blockType, toolbarKey, @@ -78,12 +78,14 @@ export function PrivateBlockToolbar( { getBlockAttributes, getBlockParentsByBlockName, getTemplateLock, - } = select( blockEditorStore ); + getParentSectionBlock, + } = unlock( select( blockEditorStore ) ); const selectedBlockClientIds = getSelectedBlockClientIds(); const selectedBlockClientId = selectedBlockClientIds[ 0 ]; const parents = getBlockParents( selectedBlockClientId ); - const firstParentClientId = parents[ parents.length - 1 ]; - const parentBlockName = getBlockName( firstParentClientId ); + const parentSection = getParentSectionBlock( selectedBlockClientId ); + const parentClientId = parentSection ?? parents[ parents.length - 1 ]; + const parentBlockName = getBlockName( parentClientId ); const parentBlockType = getBlockType( parentBlockName ); const editingMode = getBlockEditingMode( selectedBlockClientId ); const _isDefaultEditingMode = editingMode === 'default'; @@ -112,21 +114,19 @@ export function PrivateBlockToolbar( { return { blockClientId: selectedBlockClientId, blockClientIds: selectedBlockClientIds, - isContentOnlyEditingMode: editingMode === 'contentOnly', isDefaultEditingMode: _isDefaultEditingMode, blockType: selectedBlockClientId && getBlockType( _blockName ), shouldShowVisualToolbar: isValid && isVisual, - toolbarKey: `${ selectedBlockClientId }${ firstParentClientId }`, + toolbarKey: `${ selectedBlockClientId }${ parentClientId }`, showParentSelector: parentBlockType && - getBlockEditingMode( firstParentClientId ) === 'default' && + getBlockEditingMode( parentClientId ) !== 'disabled' && hasBlockSupport( parentBlockType, '__experimentalParentSelector', true ) && - selectedBlockClientIds.length === 1 && - _isDefaultEditingMode, + selectedBlockClientIds.length === 1, isUsingBindings: _isUsingBindings, hasParentPattern: _hasParentPattern, hasContentOnlyLocking: _hasTemplateLock, @@ -179,36 +179,26 @@ export function PrivateBlockToolbar( { key={ toolbarKey } >
- { ! isMultiToolbar && - isLargeViewport && - isDefaultEditingMode && } + { ! isMultiToolbar && isLargeViewport && ( + + ) } { ( shouldShowVisualToolbar || isMultiToolbar ) && - ( isDefaultEditingMode || - ( isContentOnlyEditingMode && ! hasParentPattern ) || - isSynced ) && ( + ! hasParentPattern && (
- + { ! isMultiToolbar && isDefaultEditingMode && ( + + ) } + - { isDefaultEditingMode && ( - <> - { ! isMultiToolbar && ( - - ) } - - - ) }
) } @@ -242,9 +232,7 @@ export function PrivateBlockToolbar( { ) } - { isDefaultEditingMode && ( - - ) } +
); diff --git a/packages/block-editor/src/components/block-toolbar/style.scss b/packages/block-editor/src/components/block-toolbar/style.scss index 40d748dd0a1568..2a0f68a6976686 100644 --- a/packages/block-editor/src/components/block-toolbar/style.scss +++ b/packages/block-editor/src/components/block-toolbar/style.scss @@ -52,9 +52,18 @@ > :last-child, > :last-child .components-toolbar-group, - > :last-child .components-toolbar { + > :last-child .components-toolbar, + // If the last toolbar group is empty, + // we need to remove the double border from the penultimate one. + &:has(> :last-child:empty) > :nth-last-child(2), + &:has(> :last-child:empty) > :nth-last-child(2) .components-toolbar-group, + &:has(> :last-child:empty) > :nth-last-child(2) .components-toolbar { border-right: none; } + + .components-toolbar-group:empty { + display: none; + } } .block-editor-block-contextual-toolbar { diff --git a/packages/block-editor/src/components/block-toolbar/use-has-block-toolbar.js b/packages/block-editor/src/components/block-toolbar/use-has-block-toolbar.js index c4e228f8a3c07b..80ce3691147834 100644 --- a/packages/block-editor/src/components/block-toolbar/use-has-block-toolbar.js +++ b/packages/block-editor/src/components/block-toolbar/use-has-block-toolbar.js @@ -7,7 +7,6 @@ import { getBlockType, hasBlockSupport } from '@wordpress/blocks'; * Internal dependencies */ import { store as blockEditorStore } from '../../store'; -import { useHasAnyBlockControls } from '../block-controls/use-has-block-controls'; /** * Returns true if the block toolbar should be shown. @@ -15,40 +14,29 @@ import { useHasAnyBlockControls } from '../block-controls/use-has-block-controls * @return {boolean} Whether the block toolbar component will be rendered. */ export function useHasBlockToolbar() { - const { isToolbarEnabled, isDefaultEditingMode } = useSelect( - ( select ) => { - const { - getBlockEditingMode, - getBlockName, - getBlockSelectionStart, - } = select( blockEditorStore ); + const { isToolbarEnabled, isBlockDisabled } = useSelect( ( select ) => { + const { getBlockEditingMode, getBlockName, getBlockSelectionStart } = + select( blockEditorStore ); - // we only care about the 1st selected block - // for the toolbar, so we use getBlockSelectionStart - // instead of getSelectedBlockClientIds - const selectedBlockClientId = getBlockSelectionStart(); + // we only care about the 1st selected block + // for the toolbar, so we use getBlockSelectionStart + // instead of getSelectedBlockClientIds + const selectedBlockClientId = getBlockSelectionStart(); - const blockType = - selectedBlockClientId && - getBlockType( getBlockName( selectedBlockClientId ) ); + const blockType = + selectedBlockClientId && + getBlockType( getBlockName( selectedBlockClientId ) ); - return { - isToolbarEnabled: - blockType && - hasBlockSupport( blockType, '__experimentalToolbar', true ), - isDefaultEditingMode: - getBlockEditingMode( selectedBlockClientId ) === 'default', - }; - }, - [] - ); + return { + isToolbarEnabled: + blockType && + hasBlockSupport( blockType, '__experimentalToolbar', true ), + isBlockDisabled: + getBlockEditingMode( selectedBlockClientId ) === 'disabled', + }; + }, [] ); - const hasAnyBlockControls = useHasAnyBlockControls(); - - if ( - ! isToolbarEnabled || - ( ! isDefaultEditingMode && ! hasAnyBlockControls ) - ) { + if ( ! isToolbarEnabled || isBlockDisabled ) { return false; } diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index d8955bd6342c4c..9e99176819ae89 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -116,6 +116,7 @@ export const getEnabledClientIdsTree = createSelector( state.settings.templateLock, state.blockListSettings, state.editorMode, + getSectionRootClientId( state ), ] ); diff --git a/packages/block-editor/src/store/utils.js b/packages/block-editor/src/store/utils.js index af991608238e2e..79e15255e6cc15 100644 --- a/packages/block-editor/src/store/utils.js +++ b/packages/block-editor/src/store/utils.js @@ -10,6 +10,7 @@ import { parse as grammarParse } from '@wordpress/block-serialization-default-pa import { selectBlockPatternsKey } from './private-keys'; import { unlock } from '../lock-unlock'; import { STORE_NAME } from './constants'; +import { getSectionRootClientId } from './private-selectors'; export const withRootClientIdOptionKey = Symbol( 'withRootClientId' ); @@ -118,5 +119,6 @@ export function getInsertBlockTypeDependants( state, rootClientId ) { state.settings.templateLock, state.blockEditingModes, state.editorMode, + getSectionRootClientId( state ), ]; }