diff --git a/packages/block-editor/src/components/block-card/index.js b/packages/block-editor/src/components/block-card/index.js index 4d40fe07d55471..5fd1df62ebbb11 100644 --- a/packages/block-editor/src/components/block-card/index.js +++ b/packages/block-editor/src/components/block-card/index.js @@ -11,6 +11,7 @@ import { Button, __experimentalText as Text, __experimentalVStack as VStack, + __experimentalUseSlotFills as useSlotFills, } from '@wordpress/components'; import { chevronLeft, chevronRight } from '@wordpress/icons'; import { __, isRTL, sprintf } from '@wordpress/i18n'; @@ -21,6 +22,8 @@ import { useSelect, useDispatch } from '@wordpress/data'; */ import BlockIcon from '../block-icon'; import { store as blockEditorStore } from '../../store'; +import useBlockDisplayInformation from '../use-block-display-information'; +import { unlock } from '../../lock-unlock'; function BlockCard( { title, icon, description, blockType, className, name } ) { if ( blockType ) { @@ -31,29 +34,54 @@ function BlockCard( { title, icon, description, blockType, className, name } ) { ( { title, icon, description } = blockType ); } - const { parentNavBlockClientId } = useSelect( ( select ) => { - const { getSelectedBlockClientId, getBlockParentsByBlockName } = - select( blockEditorStore ); + const { isParentNavigationBlock, parentClientId } = useSelect( + ( select ) => { + const { + getSelectedBlockClientId, + getBlockParentsByBlockName, + getContentLockingParent, + } = unlock( select( blockEditorStore ) ); - const _selectedBlockClientId = getSelectedBlockClientId(); + const _selectedBlockClientId = getSelectedBlockClientId(); - return { - parentNavBlockClientId: getBlockParentsByBlockName( + let _parentClientId = getBlockParentsByBlockName( _selectedBlockClientId, 'core/navigation', true - )[ 0 ], - }; - }, [] ); + )[ 0 ]; + const _isParentNavigationBlock = !! _parentClientId; + if ( ! _parentClientId ) { + _parentClientId = getContentLockingParent( + _selectedBlockClientId + ); + } + + return { + isParentNavigationBlock: _isParentNavigationBlock, + parentClientId: _parentClientId, + }; + }, + [] + ); + + const parentDisplayInfo = useBlockDisplayInformation( parentClientId ); + const contentOnlyFills = useSlotFills( 'InspectorControlsContentOnly' ); const { selectBlock } = useDispatch( blockEditorStore ); + const hasParentBackArrow = + isParentNavigationBlock || + ( parentClientId && !! contentOnlyFills?.length ); return (
- { parentNavBlockClientId && ( // This is only used by the Navigation block for now. It's not ideal having Navigation block specific code here. + { hasParentBackArrow && ( // This is only used by the Navigation block for now. It's not ideal having Navigation block specific code here. ); diff --git a/packages/block-editor/src/components/inspector-controls/fill.js b/packages/block-editor/src/components/inspector-controls/fill.js index 456b33af9137fe..ce774b325816e1 100644 --- a/packages/block-editor/src/components/inspector-controls/fill.js +++ b/packages/block-editor/src/components/inspector-controls/fill.js @@ -5,6 +5,7 @@ import { __experimentalStyleProvider as StyleProvider, __experimentalToolsPanelContext as ToolsPanelContext, } from '@wordpress/components'; +import { useDispatch } from '@wordpress/data'; import warning from '@wordpress/warning'; import deprecated from '@wordpress/deprecated'; import { useEffect, useContext } from '@wordpress/element'; @@ -12,6 +13,8 @@ import { useEffect, useContext } from '@wordpress/element'; /** * Internal dependencies */ +import { store as blockEditorStore } from '../../store'; +import { unlock } from '../../lock-unlock'; import { useBlockEditContext, mayDisplayControlsKey, @@ -36,7 +39,29 @@ export default function InspectorControlsFill( { group = __experimentalGroup; } - const context = useBlockEditContext(); + const { clientId, ...context } = useBlockEditContext(); + + // Register any clientIds with `contentOnly` controls before the + // `mayDisplayControls` early return so that parent blocks can know whether + // child blocks have those controls. + const { addContentOnlyControlsBlock, removeContentOnlyControlsBlock } = + unlock( useDispatch( blockEditorStore ) ); + useEffect( () => { + if ( group === 'contentOnly' ) { + addContentOnlyControlsBlock( clientId ); + } + return () => { + if ( group === 'contentOnly' ) { + removeContentOnlyControlsBlock( clientId ); + } + }; + }, [ + clientId, + group, + addContentOnlyControlsBlock, + removeContentOnlyControlsBlock, + ] ); + const Fill = groups[ group ]?.Fill; if ( ! Fill ) { warning( `Unknown InspectorControls group "${ group }" provided.` ); diff --git a/packages/block-editor/src/components/inspector-controls/groups.js b/packages/block-editor/src/components/inspector-controls/groups.js index 34ec49a5e1cb41..fec3631e29bf02 100644 --- a/packages/block-editor/src/components/inspector-controls/groups.js +++ b/packages/block-editor/src/components/inspector-controls/groups.js @@ -22,6 +22,9 @@ const InspectorControlsTypography = createSlotFill( const InspectorControlsListView = createSlotFill( 'InspectorControlsListView' ); const InspectorControlsStyles = createSlotFill( 'InspectorControlsStyles' ); const InspectorControlsEffects = createSlotFill( 'InspectorControlsEffects' ); +const InspectorControlsContentOnly = createSlotFill( + 'InspectorControlsContentOnly' +); const groups = { default: InspectorControlsDefault, @@ -38,6 +41,7 @@ const groups = { settings: InspectorControlsDefault, // Alias for default. styles: InspectorControlsStyles, typography: InspectorControlsTypography, + contentOnly: InspectorControlsContentOnly, }; export default groups; diff --git a/packages/block-editor/src/components/multi-selection-inspector/index.js b/packages/block-editor/src/components/multi-selection-inspector/index.js index f5e7f696347686..4ecacf3e7d4aee 100644 --- a/packages/block-editor/src/components/multi-selection-inspector/index.js +++ b/packages/block-editor/src/components/multi-selection-inspector/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { sprintf, _n } from '@wordpress/i18n'; +import { sprintf, _n, __ } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; import { serialize } from '@wordpress/blocks'; import { count as wordCount } from '@wordpress/wordcount'; @@ -12,6 +12,41 @@ import { copy } from '@wordpress/icons'; */ import BlockIcon from '../block-icon'; import { store as blockEditorStore } from '../../store'; +import { useBorderPanelLabel } from '../../hooks/border'; +import useInspectorControlsTabs from '../inspector-controls-tabs/use-inspector-controls-tabs'; +import { default as InspectorControls } from '../inspector-controls'; +import { default as InspectorControlsTabs } from '../inspector-controls-tabs'; + +export function MultiSelectionControls( { blockType, blockName } ) { + const availableTabs = useInspectorControlsTabs( blockType?.name ); + const showTabs = availableTabs?.length > 1; + const borderPanelLabel = useBorderPanelLabel( { + blockName, + } ); + + return showTabs ? ( + + ) : ( + <> + + + + + + + + ); +} export default function MultiSelectionInspector() { const { blocks } = useSelect( ( select ) => { diff --git a/packages/block-editor/src/store/private-actions.js b/packages/block-editor/src/store/private-actions.js index e79833e0a73da7..06ddd8acd2395f 100644 --- a/packages/block-editor/src/store/private-actions.js +++ b/packages/block-editor/src/store/private-actions.js @@ -380,6 +380,20 @@ export function expandBlock( clientId ) { }; } +export function addContentOnlyControlsBlock( clientId ) { + return { + type: 'ADD_CONTENT_ONLY_CONTROLS_BLOCK', + clientId, + }; +} + +export function removeContentOnlyControlsBlock( clientId ) { + return { + type: 'REMOVE_CONTENT_ONLY_CONTROLS_BLOCK', + clientId, + }; +} + /** * @param {Object} value * @param {string} value.rootClientId The root client ID to insert at. diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index 525aca4c236cbc..79b490649a6aa1 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -490,6 +490,10 @@ export const getContentLockingParent = ( state, clientId ) => { return result; }; +export function getContentOnlyControlsBlocks( state ) { + return state.contentOnlyControlsBlocks; +} + /** * Retrieves the client ID of the parent section block. * diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 2f0fa70d616fd9..f614f78dd190d7 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -2041,6 +2041,26 @@ export function lastFocus( state = false, action ) { return state; } +export function contentOnlyControlsBlocks( state = [], action ) { + switch ( action.type ) { + case 'ADD_CONTENT_ONLY_CONTROLS_BLOCK': { + if ( state.includes( action.clientId ) ) { + return state; + } + return [ ...state, action.clientId ]; + } + case 'DELETE_CONTENT_ONLY_CONTROLS_BLOCK': { + if ( ! state.includes( action.clientId ) ) { + return state; + } + + return state.filter( ( item ) => item !== action.clientId ); + } + } + + return state; +} + /** * Reducer setting currently hovered block. * @@ -2129,6 +2149,7 @@ const combinedReducers = combineReducers( { registeredInserterMediaCategories, hoveredBlockClientId, zoomLevel, + contentOnlyControlsBlocks, } ); function withAutomaticChangeReset( reducer ) { diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index 89bf31f92664b9..28111323485e16 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -10,7 +10,6 @@ import { TextControl, ToolbarButton, ToolbarGroup, - Dropdown, __experimentalToolsPanel as ToolsPanel, __experimentalToolsPanelItem as ToolsPanelItem, __experimentalUseCustomUnits as useCustomUnits, @@ -32,7 +31,6 @@ import { } from '@wordpress/block-editor'; import { useEffect, useMemo, useState, useRef } from '@wordpress/element'; import { __, _x, sprintf, isRTL } from '@wordpress/i18n'; -import { DOWN } from '@wordpress/keycodes'; import { getFilename } from '@wordpress/url'; import { getBlockBindingsSource, switchToBlockType } from '@wordpress/blocks'; import { crop, overlayText, upload } from '@wordpress/icons'; @@ -623,49 +621,31 @@ export default function Image( { ) } { isContentOnlyMode && ( // Add some extra controls for content attributes when content only mode is active. - // With content only mode active, the inspector is hidden, so users need another way - // to edit these attributes. - - ( - { - if ( ! isOpen && event.keyCode === DOWN ) { - event.preventDefault(); - onToggle(); - } - } } - > - { _x( - 'Alternative text', - 'Alternative text for an image. Block toolbar label, a low character count is preferred.' - ) } - - ) } - renderContent={ () => ( + + + !! alt } + onDeselect={ () => + setAttributes( { alt: undefined } ) + } + > { lockAltControlsMessage } ) : ( <> - + { __( 'Describe the purpose of the image.' ) } @@ -679,59 +659,41 @@ export default function Image( { } __nextHasNoMarginBottom /> - ) } - /> - { title && ( - ( - { - if ( - ! isOpen && - event.keyCode === DOWN - ) { - event.preventDefault(); - onToggle(); - } - } } - > - { __( 'Title' ) } - - ) } - renderContent={ () => ( - { lockTitleControlsMessage } - ) : ( - <> + + !! title } + onDeselect={ () => + setAttributes( { title: undefined } ) + } + > + { lockTitleControlsMessage } + ) : ( + <> + { __( + 'Describe the role of this image on the page.' + ) } + { __( - 'Describe the role of this image on the page.' + '(Note: many devices and browsers do not display this text.)' ) } - - { __( - '(Note: many devices and browsers do not display this text.)' - ) } - - - ) - } - /> - ) } - /> - ) } - + + + ) + } + /> + + + ) }