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 };