diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 7b987a3608e03..17eb272371f67 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -24,7 +24,6 @@ import { isReusableBlock, getBlockDefaultClassName, hasBlockSupport, - __experimentalGetBlockAttributesNamesByRole, store as blocksStore, privateApis as blocksPrivateApis, } from '@wordpress/blocks'; @@ -48,7 +47,7 @@ import { PrivateBlockContext } from './private-block-context'; import { unlock } from '../../lock-unlock'; -const { isAttributeUnmodified } = unlock( blocksPrivateApis ); +const { isBlockContentUnmodified } = unlock( blocksPrivateApis ); /** * Merges wrapper props with special handling for classNames and styles. @@ -311,26 +310,6 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, registry ) => { canInsertBlockType, } = registry.select( blockEditorStore ); - /** - * A block is "empty" if all of its content attributes are unmodified. - * - * @param {WPBlock} block The block to check. - * @return {boolean} Whether the block is empty. - */ - function isBlockEmpty( block ) { - const blockType = getBlockType( block.name ); - const contentAttributes = - __experimentalGetBlockAttributesNamesByRole( - block.name, - 'content' - ); - return contentAttributes.every( ( attribute ) => { - const definition = blockType.attributes[ attribute ]; - const value = block.attributes[ attribute ]; - return isAttributeUnmodified( definition, value ); - } ); - } - function switchToDefaultOrRemove() { const block = getBlock( clientId ); const defaultBlockName = getDefaultBlockName(); @@ -375,7 +354,8 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, registry ) => { } else { registry.batch( () => { const firstBlock = getBlock( firstClientId ); - const isFirstBlockEmpty = isBlockEmpty( firstBlock ); + const isFirstBlockContentUnmodified = + isBlockContentUnmodified( firstBlock ); const defaultBlockName = getDefaultBlockName(); const replacement = switchToBlockType( firstBlock, @@ -387,7 +367,10 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, registry ) => { canInsertBlockType( block.name, _clientId ) ); - if ( isFirstBlockEmpty && canTransformToDefaultBlock ) { + if ( + isFirstBlockContentUnmodified && + canTransformToDefaultBlock + ) { // Step 1: If the block is empty and can be transformed to the default block type. replaceBlocks( firstClientId, @@ -395,7 +378,7 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, registry ) => { changeSelection ); } else if ( - isFirstBlockEmpty && + isFirstBlockContentUnmodified && firstBlock.name === defaultBlockName ) { // Step 2: If the block is empty and is already the default block type. diff --git a/packages/blocks/src/api/index.js b/packages/blocks/src/api/index.js index 6e9e2d7becc75..72f7ca18704d8 100644 --- a/packages/blocks/src/api/index.js +++ b/packages/blocks/src/api/index.js @@ -8,7 +8,7 @@ import { getBlockBindingsSource, getBlockBindingsSources, } from './registration'; -import { isAttributeUnmodified } from './utils'; +import { isBlockContentUnmodified } from './utils'; // The blocktype is the most important concept within the block API. It defines // all aspects of the block configuration and its interfaces, including `edit` @@ -184,5 +184,5 @@ lock( privateApis, { unregisterBlockBindingsSource, getBlockBindingsSource, getBlockBindingsSources, - isAttributeUnmodified, + isBlockContentUnmodified, } ); diff --git a/packages/blocks/src/api/utils.js b/packages/blocks/src/api/utils.js index 8f7092ece2f03..25458fde05225 100644 --- a/packages/blocks/src/api/utils.js +++ b/packages/blocks/src/api/utils.js @@ -37,7 +37,7 @@ const ICON_COLORS = [ '#191e23', '#f8f9f9' ]; * @param {*} value The attribute's value. * @return {boolean} Whether the attribute is unmodified. */ -export function isAttributeUnmodified( attributeDefinition, value ) { +function isAttributeUnmodified( attributeDefinition, value ) { // Every attribute that has a default must match the default. if ( attributeDefinition.hasOwnProperty( 'default' ) ) { return value === attributeDefinition.default; @@ -84,6 +84,35 @@ export function isUnmodifiedDefaultBlock( block ) { return block.name === getDefaultBlockName() && isUnmodifiedBlock( block ); } +/** + * Determines whether the block content is unmodified. A block content is + * considered unmodified if all the attributes that have a role of 'content' + * are equal to the default attributes (or undefined). + * If the block does not have any attributes with a role of 'content', it + * will be considered unmodified if all the attributes are equal to the default + * attributes (or undefined). + * + * @param {WPBlock} block Block Object + * @return {boolean} Whether the block content is unmodified. + */ +export function isBlockContentUnmodified( block ) { + const contentAttributes = getBlockAttributesNamesByRole( + block.name, + 'content' + ); + + if ( contentAttributes.length === 0 ) { + return isUnmodifiedBlock( block ); + } + + return contentAttributes.every( ( key ) => { + const definition = getBlockType( block.name )?.attributes[ key ]; + const value = block.attributes[ key ]; + + return isAttributeUnmodified( definition, value ); + } ); +} + /** * Function that checks if the parameter is a valid icon. * diff --git a/test/e2e/specs/editor/various/splitting-merging.spec.js b/test/e2e/specs/editor/various/splitting-merging.spec.js index 81b83288fbb3b..eba9f1d3163fd 100644 --- a/test/e2e/specs/editor/various/splitting-merging.spec.js +++ b/test/e2e/specs/editor/various/splitting-merging.spec.js @@ -374,7 +374,7 @@ test.describe( 'splitting and merging blocks (@firefox, @webkit)', () => { } ); // Fix for https://github.com/WordPress/gutenberg/issues/65174. - test( 'should handle unwrapping and merging blocks', async ( { + test( 'should handle unwrapping and merging blocks with empty contents', async ( { editor, page, } ) => {