diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index eeb987fc00f61..c322898031065 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -684,21 +684,3 @@ export function getClosestAllowedInsertionPointForPattern( export function getInsertionPoint( state ) { return state.insertionPoint; } - -/** - * Retrieves the number of parent pattern blocks. - * - * @param {Object} state Global application state. - * @param {string} clientId The block client ID. - * - * @return {number} The number of parent pattern blocks. - */ -export function getParentPatternCount( state, clientId ) { - const parents = getBlockParents( state, clientId ); - return parents.reduce( ( count, parent ) => { - if ( getBlockName( state, parent ) === 'core/block' ) { - return count + 1; - } - return count; - }, 0 ); -} diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 812c48c87e7dc..6cf6aae296141 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -40,7 +40,6 @@ import { getSectionRootClientId, isSectionBlock, getParentSectionBlock, - getParentPatternCount, } from './private-selectors'; /** @@ -2973,43 +2972,6 @@ export const getBlockEditingMode = createRegistrySelector( clientId = ''; } - // Handle pattern blocks (core/block) and the content of those blocks. - const parentPatternCount = getParentPatternCount( state, clientId ); - - // Make the outer pattern block content only mode. - if ( - getBlockName( state, clientId ) === 'core/block' && - parentPatternCount === 0 - ) { - return 'contentOnly'; - } - - if ( parentPatternCount > 0 ) { - // Disable nested patterns. - if ( parentPatternCount > 1 ) { - return 'disabled'; - } - - // Disable pattern content editing in zoom-out mode. - const _isZoomOut = - __unstableGetEditorMode( state ) === 'zoom-out'; - if ( _isZoomOut ) { - return 'disabled'; - } - - // If the block has a binding of any kind, allow content only editing. - const attributes = getBlockAttributes( state, clientId ); - if ( - Object.keys( attributes?.metadata?.bindings ?? {} ) - ?.length > 0 - ) { - return 'contentOnly'; - } - - // Otherwise, the block is part of the pattern source and should not be editable. - return 'disabled'; - } - // In zoom-out mode, override the behavior set by // __unstableSetBlockEditingMode to only allow editing the top-level // sections. diff --git a/packages/block-editor/src/store/test/private-selectors.js b/packages/block-editor/src/store/test/private-selectors.js index cca0714f3856d..cbb75daa4baaa 100644 --- a/packages/block-editor/src/store/test/private-selectors.js +++ b/packages/block-editor/src/store/test/private-selectors.js @@ -428,6 +428,7 @@ describe( 'private selectors', () => { 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', ], ] ), + order: new Map( [ [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', @@ -442,7 +443,6 @@ describe( 'private selectors', () => { ], [ '', [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ] ], ] ), - byClientId: new Map( [] ), }, blockEditingModes: new Map( [ [ '', 'disabled' ], @@ -495,7 +495,6 @@ describe( 'private selectors', () => { ], [ '', [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ] ], ] ), - byClientId: new Map( [] ), }, blockEditingModes: new Map( [ [ '', 'disabled' ], diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index 67af4d1b63116..a08c2e0dde150 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -3079,7 +3079,7 @@ describe( 'selectors', () => { byClientId: new Map( Object.entries( { block1: { name: 'core/test-block-ancestor' }, - block2: { name: 'core/group' }, + block2: { name: 'core/block' }, block3: { name: 'core/test-block-parent' }, } ) ), @@ -4668,114 +4668,4 @@ describe( 'getBlockEditingMode', () => { ) ).toBe( 'disabled' ); } ); - - describe( 'pattern blocks', () => { - const patternBlockState = { - settings: {}, - blocks: { - byClientId: new Map( - Object.entries( { - 'pattern-a': { name: 'core/block' }, - 'pattern-b': { name: 'core/block' }, - 'heading-a': { name: 'core/heading' }, - 'paragraph-a': { name: 'core/paragraph' }, - 'paragraph-b': { name: 'core/paragraph' }, - } ) - ), - order: new Map( - Object.entries( { - '': [ 'pattern-a' ], - 'pattern-a': [ - 'heading-a', - 'paragraph-a', - 'pattern-b', - ], - 'pattern-b': [ 'paragraph-b' ], - } ) - ), - parents: new Map( - Object.entries( { - 'paragraph-b': 'pattern-b', - 'pattern-b': 'pattern-a', - 'paragraph-a': 'pattern-a', - 'heading-a': 'pattern-a', - 'pattern-a': '', - } ) - ), - blockListSettings: { - 'pattern-a': {}, - 'pattern-b': {}, - }, - attributes: new Map( - Object.entries( { - 'paragraph-a': { - metadata: { - bindings: { - __default: { - source: 'core/pattern-overrides', - }, - }, - }, - }, - 'paragraph-b': { - metadata: { - bindings: { - __default: { - source: 'core/pattern-overrides', - }, - }, - }, - }, - } ) - ), - }, - }; - - it( 'should return contentOnly for the outer pattern block', () => { - expect( - getBlockEditingMode( patternBlockState, 'pattern-a' ) - ).toBe( 'contentOnly' ); - } ); - - it( 'should return contentOnly for blocks with bindings in the outer pattern', () => { - expect( - getBlockEditingMode( patternBlockState, 'paragraph-a' ) - ).toBe( 'contentOnly' ); - } ); - - it( 'should return disabled for unbound blocks', () => { - expect( - getBlockEditingMode( patternBlockState, 'heading-a' ) - ).toBe( 'disabled' ); - } ); - - it( 'should return disabled for the nested pattern', () => { - expect( - getBlockEditingMode( patternBlockState, 'pattern-a' ) - ).toBe( 'contentOnly' ); - } ); - - it( 'should return disabled for blocks with bindings in the nested pattern', () => { - expect( - getBlockEditingMode( patternBlockState, 'paragraph-b' ) - ).toBe( 'disabled' ); - } ); - - it( 'should disable all inner blocks of the outer pattern in zoom out mode with the outer pattern in content only mode', () => { - const state = { - ...patternBlockState, - editorMode: 'zoom-out', - }; - expect( getBlockEditingMode( state, 'pattern-a' ) ).toBe( - 'contentOnly' - ); - [ 'paragraph-a', 'paragraph-b', 'heading-a', 'pattern-b' ].forEach( - ( block ) => { - expect( getBlockEditingMode( state, block ) ).toBe( - 'disabled' - ); - } - ); - } ); - } ); } ); diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index 3d4d07e52b386..104b07157cba7 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -7,7 +7,7 @@ import clsx from 'clsx'; * WordPress dependencies */ import { useSelect, useDispatch } from '@wordpress/data'; -import { useRef, useMemo } from '@wordpress/element'; +import { useRef, useMemo, useEffect } from '@wordpress/element'; import { useEntityRecord, store as coreStore, @@ -37,10 +37,12 @@ import { getBlockBindingsSource } from '@wordpress/blocks'; /** * Internal dependencies */ +import { name as patternBlockName } from './index'; import { unlock } from '../lock-unlock'; const { useLayoutClasses } = unlock( blockEditorPrivateApis ); -const { hasOverridableBlocks } = unlock( patternsPrivateApis ); +const { isOverridableBlock, hasOverridableBlocks } = + unlock( patternsPrivateApis ); const fullAlignments = [ 'full', 'wide', 'left', 'right' ]; @@ -73,6 +75,22 @@ const useInferredLayout = ( blocks, parentLayout ) => { }, [ blocks, parentLayout ] ); }; +function setBlockEditMode( setEditMode, blocks, mode ) { + blocks.forEach( ( block ) => { + const editMode = + mode || + ( isOverridableBlock( block ) ? 'contentOnly' : 'disabled' ); + setEditMode( block.clientId, editMode ); + + setBlockEditMode( + setEditMode, + block.innerBlocks, + // Disable editing for nested patterns. + block.name === patternBlockName ? 'disabled' : mode + ); + } ); +} + function RecursionWarning() { const blockProps = useBlockProps(); return ( @@ -153,6 +171,7 @@ function ReusableBlockEdit( { name, attributes: { ref, content }, __unstableParentLayout: parentLayout, + clientId: patternClientId, setAttributes, } ) { const { record, hasResolved } = useEntityRecord( @@ -165,24 +184,49 @@ function ReusableBlockEdit( { } ); const isMissing = hasResolved && ! record; - const { __unstableMarkLastChangeAsPersistent } = + const { setBlockEditingMode, __unstableMarkLastChangeAsPersistent } = useDispatch( blockEditorStore ); - const { onNavigateToEntityRecord, hasPatternOverridesSource } = useSelect( + const { + innerBlocks, + onNavigateToEntityRecord, + editingMode, + hasPatternOverridesSource, + } = useSelect( ( select ) => { - const { getSettings } = select( blockEditorStore ); + const { getBlocks, getSettings, getBlockEditingMode } = + select( blockEditorStore ); // For editing link to the site editor if the theme and user permissions support it. return { + innerBlocks: getBlocks( patternClientId ), onNavigateToEntityRecord: getSettings().onNavigateToEntityRecord, + editingMode: getBlockEditingMode( patternClientId ), hasPatternOverridesSource: !! getBlockBindingsSource( 'core/pattern-overrides' ), }; }, - [] + [ patternClientId ] ); + // Sync the editing mode of the pattern block with the inner blocks. + useEffect( () => { + setBlockEditMode( + setBlockEditingMode, + innerBlocks, + // Disable editing if the pattern itself is disabled. + editingMode === 'disabled' || ! hasPatternOverridesSource + ? 'disabled' + : undefined + ); + }, [ + editingMode, + innerBlocks, + setBlockEditingMode, + hasPatternOverridesSource, + ] ); + const canOverrideBlocks = useMemo( () => hasPatternOverridesSource && hasOverridableBlocks( blocks ), [ hasPatternOverridesSource, blocks ] @@ -200,6 +244,7 @@ function ReusableBlockEdit( { } ); const innerBlocksProps = useInnerBlocksProps( blockProps, { + templateLock: 'all', layout, value: blocks, onInput: NOOP,