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 acc2bd7f510ed..ab65e120024d6 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 @@ -36,6 +36,7 @@ import { useBlockRefProvider } from './use-block-refs'; import { useIntersectionObserver } from './use-intersection-observer'; import { store as blockEditorStore } from '../../../store'; import useBlockOverlayActive from '../../block-content-overlay'; +import { unlock } from '../../../lock-unlock'; /** * If the block count exceeds the threshold, we disable the reordering animation @@ -75,6 +76,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { isPartOfSelection, adjustScrolling, enableAnimation, + isSubtreeDisabled, } = useSelect( ( select ) => { const { @@ -88,7 +90,8 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { isBlockMultiSelected, isAncestorMultiSelected, isFirstMultiSelectedBlock, - } = select( blockEditorStore ); + isBlockSubtreeDisabled, + } = unlock( select( blockEditorStore ) ); const { getActiveBlockVariation } = select( blocksStore ); const isSelected = isBlockSelected( clientId ); const isPartOfMultiSelection = @@ -111,6 +114,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { enableAnimation: ! isTyping() && getGlobalBlockCount() <= BLOCK_ANIMATION_THRESHOLD, + isSubtreeDisabled: isBlockSubtreeDisabled( clientId ), }; }, [ clientId ] @@ -158,6 +162,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { 'data-block': clientId, 'data-type': name, 'data-title': blockTitle, + inert: isSubtreeDisabled ? 'true' : undefined, className: classnames( // The wp-block className is important for editor styles. classnames( 'block-editor-block-list__block', { diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index ce7802036184e..0f549fc5ad055 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -16,6 +16,7 @@ import { getBlockRootClientId, getTemplateLock, getBlockName, + getBlockOrder, } from './selectors'; /** @@ -74,7 +75,7 @@ export function getLastInsertedBlocksClientIds( state ) { export const getBlockEditingMode = createRegistrySelector( ( select ) => ( state, clientId = '' ) => { - const explicitEditingMode = getExplcitBlockEditingMode( + const explicitEditingMode = getExplicitBlockEditingMode( state, clientId ); @@ -101,7 +102,7 @@ export const getBlockEditingMode = createRegistrySelector( } ); -const getExplcitBlockEditingMode = createSelector( +const getExplicitBlockEditingMode = createSelector( ( state, clientId = '' ) => { while ( ! state.blockEditingModes.has( clientId ) && @@ -113,3 +114,31 @@ const getExplcitBlockEditingMode = createSelector( }, ( state ) => [ state.blockEditingModes, state.blocks.parents ] ); + +/** + * Returns true if the block with the given client ID and all of its descendants + * have an editing mode of 'disabled', or false otherwise. + * + * @param {Object} state Global application state. + * @param {string} clientId The block client ID. + * + * @return {boolean} Whether the block and its descendants are disabled. + */ +export const isBlockSubtreeDisabled = createSelector( + ( state, clientId ) => { + const isChildSubtreeDisabled = ( childClientId ) => { + const mode = state.blockEditingModes.get( childClientId ); + return ( + ( mode === undefined || mode === 'disabled' ) && + getBlockOrder( state, childClientId ).every( + isChildSubtreeDisabled + ) + ); + }; + return ( + getExplicitBlockEditingMode( state, clientId ) === 'disabled' && + getBlockOrder( state, clientId ).every( isChildSubtreeDisabled ) + ); + }, + ( state ) => [ state.blockEditingModes, state.blocks.parents ] +); diff --git a/packages/block-editor/src/store/test/private-selectors.js b/packages/block-editor/src/store/test/private-selectors.js index 954c8c94c1379..1c87d0fc18e19 100644 --- a/packages/block-editor/src/store/test/private-selectors.js +++ b/packages/block-editor/src/store/test/private-selectors.js @@ -5,6 +5,7 @@ import { isBlockInterfaceHidden, getLastInsertedBlocksClientIds, getBlockEditingMode, + isBlockSubtreeDisabled, } from '../private-selectors'; describe( 'private selectors', () => { @@ -51,7 +52,7 @@ describe( 'private selectors', () => { } ); } ); - describe( 'getBlockEditingMode', () => { + describe( 'block editing mode selectors', () => { const baseState = { settings: {}, blocks: { @@ -63,6 +64,27 @@ describe( 'private selectors', () => { [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', {} ], // | | Paragraph [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', {} ], // | | Paragraph ] ), + order: new Map( [ + [ '', [ '6cf70164-9097-4460-bcbf-200560546988' ] ], + [ '6cf70164-9097-4460-bcbf-200560546988', [] ], + [ + 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', + [ + 'b26fc763-417d-4f01-b81c-2ec61e14a972', + '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', + ], + ], + [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', [] ], + [ + '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', + [ + 'b3247f75-fd94-4fef-97f9-5bfd162cc416', + 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', + ], + ], + [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', [] ], + [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', [] ], + ] ), parents: new Map( [ [ '6cf70164-9097-4460-bcbf-200560546988', '' ], [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', '' ], @@ -91,120 +113,222 @@ describe( 'private selectors', () => { blockEditingModes: new Map( [] ), }; - const __experimentalHasContentRoleAttribute = jest.fn( () => false ); - getBlockEditingMode.registry = { - select: jest.fn( () => ( { - __experimentalHasContentRoleAttribute, - } ) ), - }; + describe( 'getBlockEditingMode', () => { + const __experimentalHasContentRoleAttribute = jest.fn( + () => false + ); + getBlockEditingMode.registry = { + select: jest.fn( () => ( { + __experimentalHasContentRoleAttribute, + } ) ), + }; - it( 'should return default by default', () => { - expect( - getBlockEditingMode( - baseState, - 'b3247f75-fd94-4fef-97f9-5bfd162cc416' - ) - ).toBe( 'default' ); - } ); + it( 'should return default by default', () => { + expect( + getBlockEditingMode( + baseState, + 'b3247f75-fd94-4fef-97f9-5bfd162cc416' + ) + ).toBe( 'default' ); + } ); + + [ 'disabled', 'contentOnly' ].forEach( ( mode ) => { + it( `should return ${ mode } if explicitly set`, () => { + const state = { + ...baseState, + blockEditingModes: new Map( [ + [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', mode ], + ] ), + }; + expect( + getBlockEditingMode( + state, + 'b3247f75-fd94-4fef-97f9-5bfd162cc416' + ) + ).toBe( mode ); + } ); + + it( `should return ${ mode } if explicitly set on a parent`, () => { + const state = { + ...baseState, + blockEditingModes: new Map( [ + [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', mode ], + ] ), + }; + expect( + getBlockEditingMode( + state, + 'b3247f75-fd94-4fef-97f9-5bfd162cc416' + ) + ).toBe( mode ); + } ); - [ 'disabled', 'contentOnly' ].forEach( ( mode ) => { - it( `should return ${ mode } if explicitly set`, () => { + it( `should return ${ mode } if overridden by a parent`, () => { + const state = { + ...baseState, + blockEditingModes: new Map( [ + [ '', mode ], + [ + 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', + 'default', + ], + [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', mode ], + ] ), + }; + expect( + getBlockEditingMode( + state, + 'b3247f75-fd94-4fef-97f9-5bfd162cc416' + ) + ).toBe( mode ); + } ); + + it( `should return ${ mode } if explicitly set on root`, () => { + const state = { + ...baseState, + blockEditingModes: new Map( [ [ '', mode ] ] ), + }; + expect( + getBlockEditingMode( + state, + 'b3247f75-fd94-4fef-97f9-5bfd162cc416' + ) + ).toBe( mode ); + } ); + } ); + + it( 'should return disabled if parent is locked and the block has no content role', () => { const state = { ...baseState, - blockEditingModes: new Map( [ - [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', mode ], - ] ), + blockListSettings: { + ...baseState.blockListSettings, + '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f': { + templateLock: 'contentOnly', + }, + }, }; + __experimentalHasContentRoleAttribute.mockReturnValueOnce( + false + ); expect( getBlockEditingMode( state, 'b3247f75-fd94-4fef-97f9-5bfd162cc416' ) - ).toBe( mode ); + ).toBe( 'disabled' ); } ); - it( `should return ${ mode } if explicitly set on a parent`, () => { + it( 'should return contentOnly if parent is locked and the block has a content role', () => { const state = { ...baseState, - blockEditingModes: new Map( [ - [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', mode ], - ] ), + blockListSettings: { + ...baseState.blockListSettings, + '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f': { + templateLock: 'contentOnly', + }, + }, }; + __experimentalHasContentRoleAttribute.mockReturnValueOnce( + true + ); expect( getBlockEditingMode( state, 'b3247f75-fd94-4fef-97f9-5bfd162cc416' ) - ).toBe( mode ); + ).toBe( 'contentOnly' ); } ); + } ); - it( `should return ${ mode } if overridden by a parent`, () => { + describe( 'isBlockSubtreeDisabled', () => { + it( 'should return false when top level block is not disabled', () => { + const state = { + ...baseState, + blockEditingModes: new Map( [] ), + }; + expect( + isBlockSubtreeDisabled( + state, + 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' + ) + ).toBe( false ); + } ); + + it( 'should return true when top level block is disabled and there are no editing modes within it', () => { const state = { ...baseState, blockEditingModes: new Map( [ - [ '', mode ], - [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'default' ], - [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', mode ], + [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], ] ), }; expect( - getBlockEditingMode( + isBlockSubtreeDisabled( state, - 'b3247f75-fd94-4fef-97f9-5bfd162cc416' + 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ) - ).toBe( mode ); + ).toBe( true ); } ); - it( `should return ${ mode } if explicitly set on root`, () => { + it( 'should return true when top level block is disabled via inheritence and there are no editing modes within it', () => { const state = { ...baseState, - blockEditingModes: new Map( [ [ '', mode ] ] ), + blockEditingModes: new Map( [ [ '', 'disabled' ] ] ), }; expect( - getBlockEditingMode( + isBlockSubtreeDisabled( state, - 'b3247f75-fd94-4fef-97f9-5bfd162cc416' + 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ) - ).toBe( mode ); + ).toBe( true ); } ); - } ); - it( 'should return disabled if parent is locked and the block has no content role', () => { - const state = { - ...baseState, - blockListSettings: { - ...baseState.blockListSettings, - '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f': { - templateLock: 'contentOnly', - }, - }, - }; - __experimentalHasContentRoleAttribute.mockReturnValueOnce( false ); - expect( - getBlockEditingMode( - state, - 'b3247f75-fd94-4fef-97f9-5bfd162cc416' - ) - ).toBe( 'disabled' ); - } ); + it( 'should return true when top level block is disabled and there are disabled editing modes within it', () => { + const state = { + ...baseState, + blockEditingModes: new Map( [ + [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], + [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'disabled' ], + ] ), + }; + expect( + isBlockSubtreeDisabled( + state, + 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' + ) + ).toBe( true ); + } ); - it( 'should return contentOnly if parent is locked and the block has a content role', () => { - const state = { - ...baseState, - blockListSettings: { - ...baseState.blockListSettings, - '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f': { - templateLock: 'contentOnly', - }, - }, - }; - __experimentalHasContentRoleAttribute.mockReturnValueOnce( true ); - expect( - getBlockEditingMode( - state, - 'b3247f75-fd94-4fef-97f9-5bfd162cc416' - ) - ).toBe( 'contentOnly' ); + it( 'should return false when top level block is disabled and there are non-disabled editing modes within it', () => { + const state = { + ...baseState, + blockEditingModes: new Map( [ + [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], + [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'default' ], + ] ), + }; + expect( + isBlockSubtreeDisabled( + state, + 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' + ) + ).toBe( false ); + } ); + + it( 'should return false when top level block is disabled via inheritence and there are non-disabled editing modes within it', () => { + const state = { + ...baseState, + blockEditingModes: new Map( [ + [ '', 'disabled' ], + [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'default' ], + ] ), + }; + expect( + isBlockSubtreeDisabled( + state, + 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' + ) + ).toBe( false ); + } ); } ); } ); } );