diff --git a/packages/block-editor/src/components/block-inspector/style.scss b/packages/block-editor/src/components/block-inspector/style.scss
index 393121ce4d8bd4..cb864b73ebef3d 100644
--- a/packages/block-editor/src/components/block-inspector/style.scss
+++ b/packages/block-editor/src/components/block-inspector/style.scss
@@ -34,3 +34,17 @@
padding: ($grid-unit-20 * 2) $grid-unit-20;
text-align: center;
}
+
+
+.block-editor-block-inspector__block-buttons-container {
+ border-top: $border-width solid $gray-200;
+ padding: $grid-unit-20;
+}
+
+.block-editor-block-inspector__block-type-type {
+ font-weight: 500;
+ &.block-editor-block-inspector__block-type-type {
+ line-height: $button-size-small;
+ margin: 0 0 $grid-unit-05;
+ }
+}
diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js
index fc668c174d2272..a554069d97bb65 100644
--- a/packages/block-editor/src/components/block-list/block.js
+++ b/packages/block-editor/src/components/block-list/block.js
@@ -18,6 +18,7 @@ import {
isUnmodifiedDefaultBlock,
serializeRawBlock,
switchToBlockType,
+ store as blocksStore,
} from '@wordpress/blocks';
import { withFilters } from '@wordpress/components';
import {
@@ -93,10 +94,38 @@ function BlockListBlock( {
onMerge,
toggleSelection,
} ) {
- const themeSupportsLayout = useSelect( ( select ) => {
- const { getSettings } = select( blockEditorStore );
- return getSettings().supportsLayout;
- }, [] );
+ const {
+ themeSupportsLayout,
+ hasContentLockedParent,
+ isContentBlock,
+ isContentLocking,
+ isTemporarilyEditingAsBlocks,
+ } = useSelect(
+ ( select ) => {
+ const {
+ getSettings,
+ __unstableGetContentLockingParent,
+ getTemplateLock,
+ __unstableGetTemporarilyEditingAsBlocks,
+ } = select( blockEditorStore );
+ const _hasContentLockedParent =
+ !! __unstableGetContentLockingParent( clientId );
+ return {
+ themeSupportsLayout: getSettings().supportsLayout,
+ isContentBlock:
+ select( blocksStore ).__experimentalHasContentRoleAttribute(
+ name
+ ),
+ hasContentLockedParent: _hasContentLockedParent,
+ isContentLocking:
+ getTemplateLock( clientId ) === 'noContent' &&
+ ! _hasContentLockedParent,
+ isTemporarilyEditingAsBlocks:
+ __unstableGetTemporarilyEditingAsBlocks() === clientId,
+ };
+ },
+ [ name, clientId ]
+ );
const { removeBlock } = useDispatch( blockEditorStore );
const onRemove = useCallback( () => removeBlock( clientId ), [ clientId ] );
@@ -122,6 +151,12 @@ function BlockListBlock( {
const blockType = getBlockType( name );
+ if ( hasContentLockedParent && ! isContentBlock ) {
+ wrapperProps = {
+ ...wrapperProps,
+ tabIndex: -1,
+ };
+ }
// Determine whether the block has props to apply to the wrapper.
if ( blockType?.getEditWrapperProps ) {
wrapperProps = mergeWrapperProps(
@@ -188,13 +223,20 @@ function BlockListBlock( {
const value = {
clientId,
- className:
- dataAlign && themeSupportsLayout
- ? classnames( className, `align${ dataAlign }` )
- : className,
+ className: classnames(
+ {
+ 'is-content-locked': isContentLocking,
+ 'is-content-locked-temporarily-editing-as-blocks':
+ isTemporarilyEditingAsBlocks,
+ 'is-content-block': hasContentLockedParent && isContentBlock,
+ },
+ dataAlign && themeSupportsLayout && `align${ dataAlign }`,
+ className
+ ),
wrapperProps: restWrapperProps,
isAligned,
};
+
const memoizedValue = useMemo( () => value, Object.values( value ) );
return (
diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss
index 860b468e708421..cf6f2324a58ae4 100644
--- a/packages/block-editor/src/components/block-list/style.scss
+++ b/packages/block-editor/src/components/block-list/style.scss
@@ -123,6 +123,15 @@
padding: 0;
}
+.is-content-locked {
+ .block-editor-block-list__block {
+ pointer-events: none;
+ }
+ .is-content-block {
+ pointer-events: initial;
+ }
+}
+
.block-editor-block-list__layout .block-editor-block-list__block {
position: relative;
@@ -331,6 +340,14 @@
}
}
+.is-focus-mode .block-editor-block-list__block.is-content-locked.has-child-selected,
+.is-focus-mode .block-editor-block-list__block.is-content-locked-temporarily-editing-as-blocks.has-child-selected {
+ &,
+ & .block-editor-block-list__block {
+ opacity: 1;
+ }
+}
+
.wp-block[data-align="left"] > *,
.wp-block[data-align="right"] > *,
.wp-block.alignleft,
diff --git a/packages/block-editor/src/components/block-list/use-block-props/index.js b/packages/block-editor/src/components/block-list/use-block-props/index.js
index 24a25d41d8b36b..7733c197286dbb 100644
--- a/packages/block-editor/src/components/block-list/use-block-props/index.js
+++ b/packages/block-editor/src/components/block-list/use-block-props/index.js
@@ -144,11 +144,11 @@ export function useBlockProps(
}
return {
+ tabIndex: 0,
...wrapperProps,
...props,
ref: mergedRefs,
id: `block-${ clientId }${ htmlSuffix }`,
- tabIndex: 0,
role: 'document',
'aria-label': blockLabel,
'data-block': clientId,
diff --git a/packages/block-editor/src/components/block-toolbar/index.js b/packages/block-editor/src/components/block-toolbar/index.js
index 93a2265f61fb47..07d4e861f60947 100644
--- a/packages/block-editor/src/components/block-toolbar/index.js
+++ b/packages/block-editor/src/components/block-toolbar/index.js
@@ -37,6 +37,7 @@ const BlockToolbar = ( { hideDragHandle } ) => {
hasReducedUI,
isValid,
isVisual,
+ isContentLocked,
} = useSelect( ( select ) => {
const {
getBlockName,
@@ -45,6 +46,7 @@ const BlockToolbar = ( { hideDragHandle } ) => {
isBlockValid,
getBlockRootClientId,
getSettings,
+ __unstableGetContentLockingParent,
} = select( blockEditorStore );
const selectedBlockClientIds = getSelectedBlockClientIds();
const selectedBlockClientId = selectedBlockClientIds[ 0 ];
@@ -66,6 +68,9 @@ const BlockToolbar = ( { hideDragHandle } ) => {
isVisual: selectedBlockClientIds.every(
( id ) => getBlockMode( id ) === 'visual'
),
+ isContentLocked: !! __unstableGetContentLockingParent(
+ selectedBlockClientId
+ ),
};
}, [] );
@@ -112,24 +117,27 @@ const BlockToolbar = ( { hideDragHandle } ) => {
return (
- { ! isMultiToolbar && ! displayHeaderToolbar && (
-
- ) }
+ { ! isMultiToolbar &&
+ ! displayHeaderToolbar &&
+ ! isContentLocked &&
}
- { ( shouldShowVisualToolbar || isMultiToolbar ) && (
-
-
- { ! isMultiToolbar && (
-
+
+ { ! isMultiToolbar && (
+
+ ) }
+
- ) }
-
-
- ) }
+
+ ) }
{ shouldShowVisualToolbar && isMultiToolbar && (
@@ -161,7 +169,9 @@ const BlockToolbar = ( { hideDragHandle } ) => {
>
) }
-
+ { ! isContentLocked && (
+
+ ) }
);
};
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 bd7a3547e13748..ba31043ada2dd8 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
@@ -20,8 +20,12 @@ import { store as blockEditorStore } from '../../store';
function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) {
const { blockType, hasParents, showParentSelector } = useSelect(
( select ) => {
- const { getBlockName, getBlockParents, getSelectedBlockClientIds } =
- select( blockEditorStore );
+ const {
+ getBlockName,
+ getBlockParents,
+ getSelectedBlockClientIds,
+ __unstableGetContentLockingParent,
+ } = select( blockEditorStore );
const { getBlockType } = select( blocksStore );
const selectedBlockClientIds = getSelectedBlockClientIds();
const selectedBlockClientId = selectedBlockClientIds[ 0 ];
@@ -42,7 +46,10 @@ function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) {
'__experimentalParentSelector',
true
) &&
- selectedBlockClientIds.length <= 1,
+ selectedBlockClientIds.length <= 1 &&
+ ! __unstableGetContentLockingParent(
+ selectedBlockClientId
+ ),
};
},
[]
diff --git a/packages/block-editor/src/components/inner-blocks/README.md b/packages/block-editor/src/components/inner-blocks/README.md
index 029d6cf1dad949..d564819acf0ed2 100644
--- a/packages/block-editor/src/components/inner-blocks/README.md
+++ b/packages/block-editor/src/components/inner-blocks/README.md
@@ -125,6 +125,7 @@ Template locking of `InnerBlocks` is similar to [Custom Post Type templates lock
Template locking allows locking the `InnerBlocks` area for the current template.
_Options:_
+- `noContent` — prevents all operations. Additionally, the block types that don't have content are hidden from the list view and can't gain focus within the block list.
- `'all'` — prevents all operations. It is not possible to insert new blocks. Move existing blocks or delete them.
- `'insert'` — prevents inserting or removing blocks, but allows moving existing ones.
- `false` — prevents locking from being applied to an `InnerBlocks` area even if a parent block contains locking. ( Boolean )
diff --git a/packages/block-editor/src/components/inner-blocks/use-inner-block-template-sync.js b/packages/block-editor/src/components/inner-blocks/use-inner-block-template-sync.js
index 47efbc41b4a61c..8323f8f7ce4ffc 100644
--- a/packages/block-editor/src/components/inner-blocks/use-inner-block-template-sync.js
+++ b/packages/block-editor/src/components/inner-blocks/use-inner-block-template-sync.js
@@ -19,8 +19,8 @@ import { store as blockEditorStore } from '../../store';
* This hook makes sure that a block's inner blocks stay in sync with the given
* block "template". The template is a block hierarchy to which inner blocks must
* conform. If the blocks get "out of sync" with the template and the template
- * is meant to be locked (e.g. templateLock = "all"), then we replace the inner
- * blocks with the correct value after synchronizing it with the template.
+ * is meant to be locked (e.g. templateLock = "all" or templateLock = "noContent"),
+ * then we replace the inner blocks with the correct value after synchronizing it with the template.
*
* @param {string} clientId The block client ID.
* @param {Object} template The template to match.
@@ -51,9 +51,13 @@ export default function useInnerBlockTemplateSync(
// Maintain a reference to the previous value so we can do a deep equality check.
const existingTemplate = useRef( null );
useLayoutEffect( () => {
- // Only synchronize innerBlocks with template if innerBlocks are empty or
- // a locking all exists directly on the block.
- if ( innerBlocks.length === 0 || templateLock === 'all' ) {
+ // Only synchronize innerBlocks with template if innerBlocks are empty
+ // or a locking "all" or "noContent" exists directly on the block.
+ if (
+ innerBlocks.length === 0 ||
+ templateLock === 'all' ||
+ templateLock === 'noContent'
+ ) {
const hasTemplateChanged = ! isEqual(
template,
existingTemplate.current
diff --git a/packages/block-editor/src/components/inner-blocks/use-nested-settings-update.js b/packages/block-editor/src/components/inner-blocks/use-nested-settings-update.js
index d0bf71d1e72d22..c8e2e3443ae15d 100644
--- a/packages/block-editor/src/components/inner-blocks/use-nested-settings-update.js
+++ b/packages/block-editor/src/components/inner-blocks/use-nested-settings-update.js
@@ -69,7 +69,9 @@ export default function useNestedSettingsUpdate(
const newSettings = {
allowedBlocks: _allowedBlocks,
templateLock:
- templateLock === undefined ? parentLock : templateLock,
+ templateLock === undefined || parentLock === 'noContent'
+ ? parentLock
+ : templateLock,
};
// These values are not defined for RN, so only include them if they
diff --git a/packages/block-editor/src/components/list-view/block.js b/packages/block-editor/src/components/list-view/block.js
index f8645a1071ff10..ef066bbc61aacd 100644
--- a/packages/block-editor/src/components/list-view/block.js
+++ b/packages/block-editor/src/components/list-view/block.js
@@ -67,8 +67,15 @@ function ListViewBlock( {
const { toggleBlockHighlight } = useDispatch( blockEditorStore );
const blockInformation = useBlockDisplayInformation( clientId );
- const blockName = useSelect(
- ( select ) => select( blockEditorStore ).getBlockName( clientId ),
+ const { isContentLocked, blockName } = useSelect(
+ ( select ) => {
+ const { getBlockName, getTemplateLock } =
+ select( blockEditorStore );
+ return {
+ blockName: getBlockName( clientId ),
+ isContentLocked: getTemplateLock( clientId ) === 'noContent',
+ };
+ },
[ clientId ]
);
@@ -210,7 +217,7 @@ function ListViewBlock( {
path={ path }
id={ `list-view-block-${ clientId }` }
data-block={ clientId }
- isExpanded={ isExpanded }
+ isExpanded={ isContentLocked ? undefined : isExpanded }
aria-selected={ !! isSelected }
>
{ ( { ref, tabIndex, onFocus } ) => (
diff --git a/packages/block-editor/src/components/list-view/branch.js b/packages/block-editor/src/components/list-view/branch.js
index 16f0e6488431c2..f486d3631834ba 100644
--- a/packages/block-editor/src/components/list-view/branch.js
+++ b/packages/block-editor/src/components/list-view/branch.js
@@ -2,14 +2,18 @@
* WordPress dependencies
*/
import { memo } from '@wordpress/element';
-import { AsyncModeProvider } from '@wordpress/data';
+import { AsyncModeProvider, useSelect } from '@wordpress/data';
+/**
+ * Internal dependencies
+ */
/**
* Internal dependencies
*/
import ListViewBlock from './block';
import { useListViewContext } from './context';
import { isClientIdSelected } from './utils';
+import { store as blockEditorStore } from '../../store';
/**
* Given a block, returns the total number of blocks in that subtree. This is used to help determine
@@ -86,10 +90,26 @@ function ListViewBranch( props ) {
listPosition = 0,
fixedListWindow,
isExpanded,
+ parentId,
} = props;
+ const isContentLocked = useSelect(
+ ( select ) => {
+ return !! (
+ parentId &&
+ select( blockEditorStore ).getTemplateLock( parentId ) ===
+ 'noContent'
+ );
+ },
+ [ parentId ]
+ );
+
const { expandedState, draggedClientIds } = useListViewContext();
+ if ( isContentLocked ) {
+ return null;
+ }
+
const filteredBlocks = blocks.filter( Boolean );
const blockCount = filteredBlocks.length;
let nextPosition = listPosition;
@@ -161,6 +181,7 @@ function ListViewBranch( props ) {
) }
{ hasNestedBlocks && shouldExpand && ! isDragged && (
{
const { getGlobalBlockCount, getClientIdsOfDescendants } =
diff --git a/packages/block-editor/src/components/list-view/use-block-selection.js b/packages/block-editor/src/components/list-view/use-block-selection.js
index 417fb436212319..59aaaeacb01d40 100644
--- a/packages/block-editor/src/components/list-view/use-block-selection.js
+++ b/packages/block-editor/src/components/list-view/use-block-selection.js
@@ -32,7 +32,6 @@ export default function useBlockSelection() {
const updateBlockSelection = useCallback(
async ( event, clientId, destinationClientId ) => {
if ( ! event?.shiftKey ) {
- await clearSelectedBlock();
selectBlock( clientId );
return;
}
diff --git a/packages/block-editor/src/components/use-block-drop-zone/index.js b/packages/block-editor/src/components/use-block-drop-zone/index.js
index 427dfcbb56ad51..ab88e1596b508f 100644
--- a/packages/block-editor/src/components/use-block-drop-zone/index.js
+++ b/packages/block-editor/src/components/use-block-drop-zone/index.js
@@ -92,10 +92,13 @@ export default function useBlockDropZone( {
} = {} ) {
const [ targetBlockIndex, setTargetBlockIndex ] = useState( null );
- const isLockedAll = useSelect(
+ const isLocked = useSelect(
( select ) => {
const { getTemplateLock } = select( blockEditorStore );
- return getTemplateLock( targetRootClientId ) === 'all';
+ const templateLock = getTemplateLock( targetRootClientId );
+ return [ 'all', 'noContent' ].some(
+ ( lock ) => lock === templateLock
+ );
},
[ targetRootClientId ]
);
@@ -127,7 +130,7 @@ export default function useBlockDropZone( {
);
return useDropZone( {
- isDisabled: isLockedAll,
+ isDisabled: isLocked,
onDrop: onBlockDrop,
onDragOver( event ) {
// `currentTarget` is only available while the event is being
diff --git a/packages/block-editor/src/hooks/content-lock-ui.js b/packages/block-editor/src/hooks/content-lock-ui.js
new file mode 100644
index 00000000000000..98098cf583361c
--- /dev/null
+++ b/packages/block-editor/src/hooks/content-lock-ui.js
@@ -0,0 +1,161 @@
+/**
+ * WordPress dependencies
+ */
+import { ToolbarButton } from '@wordpress/components';
+import { createHigherOrderComponent } from '@wordpress/compose';
+import { useDispatch, useSelect } from '@wordpress/data';
+import { addFilter } from '@wordpress/hooks';
+import { __ } from '@wordpress/i18n';
+import { useEffect, useRef, useCallback } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import { store as blockEditorStore } from '../store';
+import { BlockControls } from '../components';
+/**
+ * External dependencies
+ */
+import classnames from 'classnames';
+
+function StopEditingAsBlocksOnOutsideSelect( {
+ clientId,
+ stopEditingAsBlock,
+} ) {
+ const isBlockOrDescendantSelected = useSelect(
+ ( select ) => {
+ const { isBlockSelected, hasSelectedInnerBlock } =
+ select( blockEditorStore );
+ return (
+ isBlockSelected( clientId ) ||
+ hasSelectedInnerBlock( clientId, true )
+ );
+ },
+ [ clientId ]
+ );
+ useEffect( () => {
+ if ( ! isBlockOrDescendantSelected ) {
+ stopEditingAsBlock();
+ }
+ }, [ isBlockOrDescendantSelected ] );
+ return null;
+}
+
+export const withBlockControls = createHigherOrderComponent(
+ ( BlockEdit ) => ( props ) => {
+ const { getBlockListSettings, getSettings } =
+ useSelect( blockEditorStore );
+ const focusModeToRevert = useRef();
+ const { templateLock, isLockedByParent, isEditingAsBlocks } = useSelect(
+ ( select ) => {
+ const {
+ __unstableGetContentLockingParent,
+ getTemplateLock,
+ __unstableGetTemporarilyEditingAsBlocks,
+ } = select( blockEditorStore );
+ return {
+ templateLock: getTemplateLock( props.clientId ),
+ isLockedByParent: !! __unstableGetContentLockingParent(
+ props.clientId
+ ),
+ isEditingAsBlocks:
+ __unstableGetTemporarilyEditingAsBlocks() ===
+ props.clientId,
+ };
+ },
+ [ props.clientId ]
+ );
+
+ const {
+ updateSettings,
+ updateBlockListSettings,
+ __unstableSetTemporarilyEditingAsBlocks,
+ } = useDispatch( blockEditorStore );
+ const isContentLocked =
+ ! isLockedByParent && templateLock === 'noContent';
+ const {
+ __unstableMarkNextChangeAsNotPersistent,
+ updateBlockAttributes,
+ } = useDispatch( blockEditorStore );
+
+ const stopEditingAsBlock = useCallback( () => {
+ __unstableMarkNextChangeAsNotPersistent();
+ updateBlockAttributes( props.clientId, {
+ templateLock: 'noContent',
+ } );
+ updateBlockListSettings( props.clientId, {
+ ...getBlockListSettings( props.clientId ),
+ templateLock: 'noContent',
+ } );
+ updateSettings( { focusMode: focusModeToRevert.current } );
+ __unstableSetTemporarilyEditingAsBlocks();
+ }, [
+ props.clientId,
+ focusModeToRevert,
+ updateSettings,
+ updateBlockListSettings,
+ getBlockListSettings,
+ __unstableMarkNextChangeAsNotPersistent,
+ updateBlockAttributes,
+ __unstableSetTemporarilyEditingAsBlocks,
+ ] );
+
+ if ( ! isContentLocked && ! isEditingAsBlocks ) {
+ return ;
+ }
+
+ return (
+ <>
+ { isEditingAsBlocks && ! isContentLocked && (
+
+ ) }
+
+ {
+ if ( isEditingAsBlocks && ! isContentLocked ) {
+ stopEditingAsBlock();
+ } else {
+ __unstableMarkNextChangeAsNotPersistent();
+ updateBlockAttributes( props.clientId, {
+ templateLock: undefined,
+ } );
+ updateBlockListSettings( props.clientId, {
+ ...getBlockListSettings( props.clientId ),
+ templateLock: false,
+ } );
+ focusModeToRevert.current =
+ getSettings().focusMode;
+ updateSettings( { focusMode: true } );
+ __unstableSetTemporarilyEditingAsBlocks(
+ props.clientId
+ );
+ }
+ } }
+ >
+ { isEditingAsBlocks && ! isContentLocked
+ ? __( 'Done' )
+ : __( 'Modify' ) }
+
+
+
+ >
+ );
+ },
+ 'withToolbarControls'
+);
+
+addFilter(
+ 'editor.BlockEdit',
+ 'core/style/with-block-controls',
+ withBlockControls
+);
diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js
index 272e79e78dbdd3..5a07d864beb620 100644
--- a/packages/block-editor/src/hooks/index.js
+++ b/packages/block-editor/src/hooks/index.js
@@ -15,6 +15,7 @@ import './duotone';
import './font-size';
import './border';
import './layout';
+import './content-lock-ui';
export { useCustomSides } from './dimensions';
export { getBorderClassesAndStyles, useBorderProps } from './use-border-props';
diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js
index b292bf6437c388..a1c5bea96cdae8 100644
--- a/packages/block-editor/src/store/actions.js
+++ b/packages/block-editor/src/store/actions.js
@@ -1673,3 +1673,17 @@ export function setBlockVisibility( updates ) {
updates,
};
}
+
+/**
+ * Action that sets whether a block is being temporaritly edited as blocks.
+ *
+ * @param {?string} temporarilyEditingAsBlocks The block's clientId being temporaritly edited as blocks.
+ */
+export function __unstableSetTemporarilyEditingAsBlocks(
+ temporarilyEditingAsBlocks
+) {
+ return {
+ type: 'SET_TEMPORARILY_EDITING_AS_BLOCKS',
+ temporarilyEditingAsBlocks,
+ };
+}
diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js
index 534a69c67f1382..73055bdef71996 100644
--- a/packages/block-editor/src/store/reducer.js
+++ b/packages/block-editor/src/store/reducer.js
@@ -1778,6 +1778,21 @@ export function lastBlockInserted( state = {}, action ) {
return state;
}
+/**
+ * Reducer returning the block that is eding temporarily edited as blocks.
+ *
+ * @param {Object} state Current state.
+ * @param {Object} action Dispatched action.
+ *
+ * @return {Object} Updated state.
+ */
+export function temporarilyEditingAsBlocks( state = '', action ) {
+ if ( action.type === 'SET_TEMPORARILY_EDITING_AS_BLOCKS' ) {
+ return action.temporarilyEditingAsBlocks;
+ }
+ return state;
+}
+
export default combineReducers( {
blocks,
isTyping,
@@ -1798,4 +1813,5 @@ export default combineReducers( {
automaticChangeStatus,
highlightedBlock,
lastBlockInserted,
+ temporarilyEditingAsBlocks,
} );
diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js
index 030977812dcd4d..9f986bcf228ced 100644
--- a/packages/block-editor/src/store/selectors.js
+++ b/packages/block-editor/src/store/selectors.js
@@ -2679,3 +2679,22 @@ export const __unstableGetVisibleBlocks = createSelector(
},
( state ) => [ state.blocks.visibility ]
);
+
+export const __unstableGetContentLockingParent = createSelector(
+ ( state, clientId ) => {
+ let current = clientId;
+ let result;
+ while ( !! state.blocks.parents[ current ] ) {
+ current = state.blocks.parents[ current ];
+ if ( getTemplateLock( state, current ) === 'noContent' ) {
+ result = current;
+ }
+ }
+ return result;
+ },
+ ( state ) => [ state.blocks.parents, state.blockListSettings ]
+);
+
+export function __unstableGetTemporarilyEditingAsBlocks( state ) {
+ return state.temporarilyEditingAsBlocks;
+}
diff --git a/packages/block-library/src/column/block.json b/packages/block-library/src/column/block.json
index 0601abddd79be7..59472b4c124046 100644
--- a/packages/block-library/src/column/block.json
+++ b/packages/block-library/src/column/block.json
@@ -19,7 +19,7 @@
},
"templateLock": {
"type": [ "string", "boolean" ],
- "enum": [ "all", "insert", false ]
+ "enum": [ "all", "insert", "noContent", false ]
}
},
"supports": {
diff --git a/packages/block-library/src/cover/block.json b/packages/block-library/src/cover/block.json
index 12c717370c426d..a7b495cfc3f8f6 100644
--- a/packages/block-library/src/cover/block.json
+++ b/packages/block-library/src/cover/block.json
@@ -73,7 +73,7 @@
},
"templateLock": {
"type": [ "string", "boolean" ],
- "enum": [ "all", "insert", false ]
+ "enum": [ "all", "insert", "noContent", false ]
}
},
"usesContext": [ "postId", "postType" ],
diff --git a/packages/block-library/src/group/block.json b/packages/block-library/src/group/block.json
index cfc07d59d15b4a..4e0f15c90d6e96 100644
--- a/packages/block-library/src/group/block.json
+++ b/packages/block-library/src/group/block.json
@@ -14,7 +14,7 @@
},
"templateLock": {
"type": [ "string", "boolean" ],
- "enum": [ "all", "insert", false ]
+ "enum": [ "all", "insert", "noContent", false ]
},
"layout": {
"type": "object",
diff --git a/packages/blocks/src/store/selectors.js b/packages/blocks/src/store/selectors.js
index 04c87a439fd593..74237b5d5f210c 100644
--- a/packages/blocks/src/store/selectors.js
+++ b/packages/blocks/src/store/selectors.js
@@ -790,3 +790,13 @@ export const hasChildBlocksWithInserterSupport = ( state, blockName ) => {
return hasBlockSupport( state, childBlockName, 'inserter', true );
} );
};
+
+export const __experimentalHasContentRoleAttribute = createSelector(
+ ( state, blockTypeName ) => {
+ const blockType = getBlockType( state, blockTypeName );
+ return Object.entries( blockType.attributes ).some(
+ ( [ , { __experimentalRole } ] ) => __experimentalRole === 'content'
+ );
+ },
+ ( state, blockTypeName ) => [ state.blockTypes[ blockTypeName ].attributes ]
+);