Skip to content

Commit

Permalink
InnerBlocks: introduce prop to specify render callback for each block. (
Browse files Browse the repository at this point in the history
#24232)

* InnerBlocks: introduce prop to specify wrapper element for children.

Initial web edit implementation. (No save or native implementations yet.)

* Initial save implementation.

* For testing purposes: wrap each child of Group in section tag.

* Fix save implementation over-escaping.

* Move wrapper logic from BlockList to BlockListBlock.

Fixes block moving animations for inner blocks.

* Fix drag-and-drop for inner blocks.

* Initial native implementation.

* Remove need for 'block-editor-block-list-block__wrapper'.

This makes it possible to switch to a callback-based API, e.g. "itemWrapper={ ( item ) => <div>{ item }</div> }".

* Switch to callback-based API.

Edit implementation:

<InnerBlocks
    __experimentalItemCallback={
        ( item ) => <div>{ item }</div>
    }
/>

Save implementation:

<InnerBlocks.Content
    __experimentalItemCallback={
        ( item ) => <div>{ item }</div>
    }
/>

* Rename __experimentalCallback to __experimentalRenderCallback.

* Apply render callback at a slightly higher level.

Fixes non-light blocks having the render callback applied inside the Block component rather than outside.

Also moves the "Edit as HTML" form of blocks inside the render callback.

* Remove obsolete unit test.

getNearestBlockIndex now assumes all elements passed to it are the root elements of blocks; we filter out the other elements before passing the array to the function.

* Revert temporary Group block changes.
  • Loading branch information
ZebulanStanphill authored Aug 15, 2020
1 parent 759a98a commit 48c4fb9
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 32 deletions.
5 changes: 5 additions & 0 deletions packages/block-editor/src/components/block-list/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -241,6 +242,10 @@ function BlockListBlock( {
block = <Block.div { ...wrapperProps }>{ blockEdit }</Block.div>;
}

if ( renderCallback ) {
block = renderCallback( block );
}

return (
<BlockListBlockContext.Provider value={ memoizedValue }>
<BlockCrashBoundary onError={ onBlockError }>
Expand Down
4 changes: 4 additions & 0 deletions packages/block-editor/src/components/block-list/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ function BlockList(
className,
rootClientId,
renderAppender,
__experimentalItemCallback,
__experimentalTagName = 'div',
__experimentalAppenderTagName,
__experimentalPassedProps = {},
Expand Down Expand Up @@ -110,6 +111,9 @@ function BlockList(
isDropTarget &&
orientation === 'horizontal',
} ) }
__experimentalRenderCallback={
__experimentalItemCallback
}
/>
</AsyncModeProvider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ export class BlockList extends Component {
parentWidth,
marginVertical = styles.defaultBlock.marginTop,
marginHorizontal = styles.defaultBlock.marginLeft,
__experimentalItemCallback,
} = this.props;
return (
<BlockListItem
Expand All @@ -279,6 +280,7 @@ export class BlockList extends Component {
onCaretVerticalPositionChange={
this.onCaretVerticalPositionChange
}
__experimentalRenderCallback={ __experimentalItemCallback }
/>
);
}
Expand Down
8 changes: 7 additions & 1 deletion packages/block-editor/src/components/inner-blocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ function UncontrolledInnerBlocks( props ) {
const {
clientId,
allowedBlocks,
__experimentalItemCallback: itemCallback,
template,
templateLock,
forwardedRef,
Expand Down Expand Up @@ -93,6 +94,7 @@ function UncontrolledInnerBlocks( props ) {
{ ...props }
ref={ forwardedRef }
rootClientId={ clientId }
__experimentalItemCallback={ itemCallback }
className={ classes }
/>
);
Expand Down Expand Up @@ -157,7 +159,11 @@ ForwardedInnerBlocks.DefaultBlockAppender = DefaultBlockAppender;
ForwardedInnerBlocks.ButtonBlockAppender = ButtonBlockAppender;

ForwardedInnerBlocks.Content = withBlockContentContext(
( { BlockContent } ) => <BlockContent />
( { BlockContent, __experimentalItemCallback } ) => (
<BlockContent
__experimentalItemCallback={ __experimentalItemCallback }
/>
)
);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ function UncontrolledInnerBlocks( props ) {
marginHorizontal,
horizontalAlignment,
filterInnerBlocks,
__experimentalItemCallback,
} = props;

const block = useSelect(
Expand Down Expand Up @@ -82,6 +83,7 @@ function UncontrolledInnerBlocks( props ) {
onAddBlock={ onAddBlock }
onDeleteBlock={ onDeleteBlock }
filterInnerBlocks={ filterInnerBlocks }
__experimentalItemCallback={ __experimentalItemCallback }
/>
);

Expand Down
21 changes: 15 additions & 6 deletions packages/block-editor/src/components/use-block-drop-zone/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* External dependencies
*/
import { difference } from 'lodash';

/**
* WordPress dependencies
*/
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
46 changes: 39 additions & 7 deletions packages/blocks/src/api/serializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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.
*/

/**
Expand Down Expand Up @@ -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( <RawHTML>{ unwrappedContent }</RawHTML> )
);
}
return unwrappedContent;
}

/**
Expand Down
7 changes: 5 additions & 2 deletions packages/blocks/src/block-content-provider/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <RawHTML>{ html }</RawHTML>;
Expand Down

0 comments on commit 48c4fb9

Please sign in to comment.