+ unlock( select( blockEditorStore ) ).getContentOnlyControlsBlocks(),
+ []
+ );
+
return (
) }
diff --git a/packages/block-editor/src/components/block-quick-navigation/index.js b/packages/block-editor/src/components/block-quick-navigation/index.js
index fdb3475b3e180f..43d099f82295ae 100644
--- a/packages/block-editor/src/components/block-quick-navigation/index.js
+++ b/packages/block-editor/src/components/block-quick-navigation/index.js
@@ -9,7 +9,9 @@ import {
Flex,
FlexBlock,
FlexItem,
+ Icon,
} from '@wordpress/components';
+import { chevronRight } from '@wordpress/icons';
/**
* Internal dependencies
@@ -19,7 +21,11 @@ import BlockIcon from '../block-icon';
import useBlockDisplayInformation from '../use-block-display-information';
import useBlockDisplayTitle from '../block-title/use-block-display-title';
-export default function BlockQuickNavigation( { clientIds, onSelect } ) {
+export default function BlockQuickNavigation( {
+ clientIds,
+ clientIdsWithControls,
+ onSelect,
+} ) {
if ( ! clientIds.length ) {
return null;
}
@@ -30,13 +36,14 @@ export default function BlockQuickNavigation( { clientIds, onSelect } ) {
onSelect={ onSelect }
key={ clientId }
clientId={ clientId }
+ hasControls={ clientIdsWithControls?.includes( clientId ) }
/>
) ) }
);
}
-function BlockQuickNavigationItem( { clientId, onSelect } ) {
+function BlockQuickNavigationItem( { clientId, hasControls, onSelect } ) {
const blockInformation = useBlockDisplayInformation( clientId );
const blockTitle = useBlockDisplayTitle( {
clientId,
@@ -75,6 +82,11 @@ function BlockQuickNavigationItem( { clientId, onSelect } ) {
{ blockTitle }
+ { hasControls && (
+
+
+
+ ) }
);
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.)'
- ) }
-
- >
- )
- }
- />
- ) }
- />
- ) }
-
+
+ >
+ )
+ }
+ />
+
+
+
) }