diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 689194bf6b1bdf..f908905db6d0e0 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -93,6 +93,7 @@ function BlockListBlock( { toggleSelection, index, enableAnimation, + __experimentalRenderCallback: renderCallback, } ) { // In addition to withSelect, we should favor using useSelect in this // component going forward to avoid leaking new props to the public API @@ -241,6 +242,10 @@ function BlockListBlock( { block = { blockEdit }; } + if ( renderCallback ) { + block = renderCallback( block ); + } + return ( diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index 48acece1a9fa66..1b077577e19b68 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -28,6 +28,7 @@ function BlockList( className, rootClientId, renderAppender, + __experimentalItemCallback, __experimentalTagName = 'div', __experimentalAppenderTagName, __experimentalPassedProps = {}, @@ -110,6 +111,9 @@ function BlockList( isDropTarget && orientation === 'horizontal', } ) } + __experimentalRenderCallback={ + __experimentalItemCallback + } /> ); diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index d882c0ad671ab8..1ea9a0ed371c51 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -260,6 +260,7 @@ export class BlockList extends Component { parentWidth, marginVertical = styles.defaultBlock.marginTop, marginHorizontal = styles.defaultBlock.marginLeft, + __experimentalItemCallback, } = this.props; return ( ); } diff --git a/packages/block-editor/src/components/inner-blocks/index.js b/packages/block-editor/src/components/inner-blocks/index.js index 668c6c5e89a07e..027a3a354a2c10 100644 --- a/packages/block-editor/src/components/inner-blocks/index.js +++ b/packages/block-editor/src/components/inner-blocks/index.js @@ -40,6 +40,7 @@ function UncontrolledInnerBlocks( props ) { const { clientId, allowedBlocks, + __experimentalItemCallback: itemCallback, template, templateLock, forwardedRef, @@ -93,6 +94,7 @@ function UncontrolledInnerBlocks( props ) { { ...props } ref={ forwardedRef } rootClientId={ clientId } + __experimentalItemCallback={ itemCallback } className={ classes } /> ); @@ -157,7 +159,11 @@ ForwardedInnerBlocks.DefaultBlockAppender = DefaultBlockAppender; ForwardedInnerBlocks.ButtonBlockAppender = ButtonBlockAppender; ForwardedInnerBlocks.Content = withBlockContentContext( - ( { BlockContent } ) => + ( { BlockContent, __experimentalItemCallback } ) => ( + + ) ); /** diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js index 433ad8c2b4ee77..b3d7c82f642580 100644 --- a/packages/block-editor/src/components/inner-blocks/index.native.js +++ b/packages/block-editor/src/components/inner-blocks/index.native.js @@ -49,6 +49,7 @@ function UncontrolledInnerBlocks( props ) { marginHorizontal, horizontalAlignment, filterInnerBlocks, + __experimentalItemCallback, } = props; const block = useSelect( @@ -82,6 +83,7 @@ function UncontrolledInnerBlocks( props ) { onAddBlock={ onAddBlock } onDeleteBlock={ onDeleteBlock } filterInnerBlocks={ filterInnerBlocks } + __experimentalItemCallback={ __experimentalItemCallback } /> ); 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 93a1688139e43a..6e9d05d7f0d72d 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 @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { difference } from 'lodash'; + /** * WordPress dependencies */ @@ -50,11 +55,6 @@ export function getNearestBlockIndex( elements, position, orientation ) { let candidateDistance; elements.forEach( ( element, index ) => { - // Ensure the element is a block. It should have the `wp-block` class. - if ( ! element.classList.contains( 'wp-block' ) ) { - return; - } - const rect = element.getBoundingClientRect(); const cursorLateralPosition = isHorizontal ? y : x; const cursorForwardPosition = isHorizontal ? x : y; @@ -326,7 +326,16 @@ export default function useBlockDropZone( { useEffect( () => { if ( position ) { - const blockElements = Array.from( element.current.children ); + // Get the root elements of blocks inside the element, ignoring + // InnerBlocks item wrappers and the children of the blocks. + const blockElements = difference( + Array.from( element.current.querySelectorAll( '.wp-block' ) ), + Array.from( + element.current.querySelectorAll( + ':scope .wp-block .wp-block' + ) + ) + ); const targetIndex = getNearestBlockIndex( blockElements, diff --git a/packages/block-editor/src/components/use-block-drop-zone/test/index.js b/packages/block-editor/src/components/use-block-drop-zone/test/index.js index c0aac7b5827d92..7cc220e5ce9c38 100644 --- a/packages/block-editor/src/components/use-block-drop-zone/test/index.js +++ b/packages/block-editor/src/components/use-block-drop-zone/test/index.js @@ -83,22 +83,6 @@ describe( 'getNearestBlockIndex', () => { expect( result ).toBeUndefined(); } ); - it( 'returns `undefined` if the elements do not have the `wp-block` class', () => { - const nonBlockElements = [ - { classList: createMockClassList( 'some-other-class' ) }, - ]; - const position = { x: 0, y: 0 }; - const orientation = 'horizontal'; - - const result = getNearestBlockIndex( - nonBlockElements, - position, - orientation - ); - - expect( result ).toBeUndefined(); - } ); - describe( 'Vertical block lists', () => { const orientation = 'vertical'; diff --git a/packages/blocks/src/api/serializer.js b/packages/blocks/src/api/serializer.js index 886a1ad69b6138..edcfc6008a0c1c 100644 --- a/packages/blocks/src/api/serializer.js +++ b/packages/blocks/src/api/serializer.js @@ -6,7 +6,12 @@ import { isEmpty, reduce, isObject, castArray, startsWith } from 'lodash'; /** * WordPress dependencies */ -import { Component, cloneElement, renderToString } from '@wordpress/element'; +import { + Component, + RawHTML, + cloneElement, + renderToString, +} from '@wordpress/element'; import { hasFilter, applyFilters } from '@wordpress/hooks'; import isShallowEqual from '@wordpress/is-shallow-equal'; @@ -21,10 +26,16 @@ import { import { normalizeBlockType } from './utils'; import BlockContentProvider from '../block-content-provider'; +/** @typedef {import('@wordpress/element').WPElement} WPElement */ + /** * @typedef {Object} WPBlockSerializationOptions Serialization Options. * - * @property {boolean} isInnerBlocks Whether we are serializing inner blocks. + * @property {boolean} isInnerBlocks + * Whether we are serializing inner blocks. + * @property {WPElement} [__experimentalRenderCallback] + * Callback to define HTML surrounding block, outside of the comment + * delimiters. Used by InnerBlocks API. */ /** @@ -307,20 +318,41 @@ export function getCommentDelimitedContent( * * @return {string} Serialized block. */ -export function serializeBlock( block, { isInnerBlocks = false } = {} ) { +export function serializeBlock( + block, + { isInnerBlocks = false, __experimentalRenderCallback: renderCallback } = {} +) { const blockName = block.name; const saveContent = getBlockContent( block ); + // Serialized block content before wrapping it with an InnerBlocks item + // wrapper. + let unwrappedContent; + if ( blockName === getUnregisteredTypeHandlerName() || ( ! isInnerBlocks && blockName === getFreeformContentHandlerName() ) ) { - return saveContent; + unwrappedContent = saveContent; + } else { + const blockType = getBlockType( blockName ); + const saveAttributes = getCommentAttributes( + blockType, + block.attributes + ); + unwrappedContent = getCommentDelimitedContent( + blockName, + saveAttributes, + saveContent + ); } - const blockType = getBlockType( blockName ); - const saveAttributes = getCommentAttributes( blockType, block.attributes ); - return getCommentDelimitedContent( blockName, saveAttributes, saveContent ); + if ( renderCallback ) { + return renderToString( + renderCallback( { unwrappedContent } ) + ); + } + return unwrappedContent; } /** diff --git a/packages/blocks/src/block-content-provider/index.js b/packages/blocks/src/block-content-provider/index.js index 60b6dd947f4d8f..e1171ca345b526 100644 --- a/packages/blocks/src/block-content-provider/index.js +++ b/packages/blocks/src/block-content-provider/index.js @@ -33,9 +33,12 @@ const { Consumer, Provider } = createContext( () => {} ); * @return {WPComponent} Element with BlockContent injected via context. */ const BlockContentProvider = ( { children, innerBlocks } ) => { - const BlockContent = () => { + const BlockContent = ( { __experimentalItemCallback } ) => { // Value is an array of blocks, so defer to block serializer - const html = serialize( innerBlocks, { isInnerBlocks: true } ); + const html = serialize( innerBlocks, { + isInnerBlocks: true, + __experimentalRenderCallback: __experimentalItemCallback, + } ); // Use special-cased raw HTML tag to avoid default escaping return { html };