Skip to content

Commit

Permalink
Query Loop: Add useBlockPreview, fix Query Loop wide alignment in the…
Browse files Browse the repository at this point in the history
… editor (#36431)

* Add useBlockPreview, fix Query Loop wide alignment in the editor

* Add test for useDisabled

* Add minimal test for useBlockPreview hook

* Add useDisabled test to cover updates to a component

* Update useBlockPreview test to ensure elements within the block content are disabled

* Switch queryBy to getBy to ensure check for block is stricter

* Remove comment

* Export useBlockPreview as __experimentalUseBlockPreview

* Optimise block preview via memoizing the component, and always rendering a preview for each block context

* Tidy up settings useMemo

Co-authored-by: Ramon <[email protected]>

* Add documentation for useDisabled hook

* Move documentation to the hook

Co-authored-by: Ramon <[email protected]>
  • Loading branch information
andrewserong and ramonjd authored Dec 15, 2021
1 parent 80ce42f commit 7a050db
Show file tree
Hide file tree
Showing 8 changed files with 459 additions and 18 deletions.
60 changes: 60 additions & 0 deletions packages/block-editor/src/components/block-preview/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@
* External dependencies
*/
import { castArray } from 'lodash';
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import {
__experimentalUseDisabled as useDisabled,
useMergeRefs,
} from '@wordpress/compose';
import { useSelect } from '@wordpress/data';
import { memo, useMemo } from '@wordpress/element';

Expand All @@ -16,6 +21,7 @@ import BlockEditorProvider from '../provider';
import LiveBlockPreview from './live';
import AutoHeightBlockPreview from './auto';
import { store as blockEditorStore } from '../../store';
import { BlockListItems } from '../block-list';

export function BlockPreview( {
blocks,
Expand Down Expand Up @@ -63,3 +69,57 @@ export function BlockPreview( {
* @return {WPComponent} The component to be rendered.
*/
export default memo( BlockPreview );

/**
* This hook is used to lightly mark an element as a block preview wrapper
* element. Call this hook and pass the returned props to the element to mark as
* a block preview wrapper, automatically rendering inner blocks as children. If
* you define a ref for the element, it is important to pass the ref to this
* hook, which the hook in turn will pass to the component through the props it
* returns. Optionally, you can also pass any other props through this hook, and
* they will be merged and returned.
*
* @param {Object} options Preview options.
* @param {WPBlock[]} options.blocks Block objects.
* @param {Object} options.props Optional. Props to pass to the element. Must contain
* the ref if one is defined.
* @param {Object} options.__experimentalLayout Layout settings to be used in the preview.
*
*/
export function useBlockPreview( {
blocks,
props = {},
__experimentalLayout,
} ) {
const originalSettings = useSelect(
( select ) => select( blockEditorStore ).getSettings(),
[]
);
const disabledRef = useDisabled();
const ref = useMergeRefs( [ props.ref, disabledRef ] );
const settings = useMemo(
() => ( { ...originalSettings, __experimentalBlockPatterns: [] } ),
[ originalSettings ]
);
const renderedBlocks = useMemo( () => castArray( blocks ), [ blocks ] );

const children = (
<BlockEditorProvider value={ renderedBlocks } settings={ settings }>
<BlockListItems
renderAppender={ false }
__experimentalLayout={ __experimentalLayout }
/>
</BlockEditorProvider>
);

return {
...props,
ref,
className: classnames(
props.className,
'block-editor-block-preview__live-content',
'components-disabled'
),
children: blocks?.length ? children : null,
};
}
23 changes: 23 additions & 0 deletions packages/block-editor/src/components/block-preview/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,26 @@
.block-editor-block-preview__content-iframe .block-list-appender {
display: none;
}

.block-editor-block-preview__live-content {
* {
pointer-events: none;
}

// Hide the block appender, as the block is not editable in this context.
.block-list-appender {
display: none;
}

// Revert button disable styles to ensure that button styles render as they will on the
// front end of the site. For example, this ensures that Social Links icons display correctly.
.components-button:disabled {
opacity: initial;
}

// Hide placeholders.
.components-placeholder,
.block-editor-block-list__block[data-empty="true"] {
display: none;
}
}
114 changes: 114 additions & 0 deletions packages/block-editor/src/components/block-preview/test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* External dependencies
*/
import { render, screen, waitFor } from '@testing-library/react';

/**
* WordPress dependencies
*/
import {
registerBlockType,
unregisterBlockType,
createBlock,
} from '@wordpress/blocks';

/**
* Internal dependencies
*/
import { useBlockPreview } from '../';

jest.mock( '@wordpress/dom', () => {
const focus = jest.requireActual( '../../../../../dom/src' ).focus;

return {
focus: {
...focus,
focusable: {
...focus.focusable,
find( context ) {
// In JSDOM, all elements have zero'd widths and height.
// This is a metric for focusable's `isVisible`, so find
// and apply an arbitrary non-zero width.
Array.from( context.querySelectorAll( '*' ) ).forEach(
( element ) => {
Object.defineProperties( element, {
offsetWidth: {
get: () => 1,
configurable: true,
},
} );
}
);

return focus.focusable.find( ...arguments );
},
},
},
};
} );

describe( 'useBlockPreview', () => {
beforeAll( () => {
registerBlockType( 'core/test-block', {
save: () => (
<div>
Test block save view
<button>Button</button>
</div>
),
edit: () => (
<div>
Test block edit view
<button>Button</button>
</div>
),
category: 'text',
title: 'test block',
} );
} );

afterAll( () => {
unregisterBlockType( 'core/test-block' );
} );

function BlockPreviewComponent( { blocks, className } ) {
const blockPreviewProps = useBlockPreview( {
blocks,
props: { className },
} );
return <div { ...blockPreviewProps } />;
}

it( 'will render a block preview with minimal nesting', async () => {
const blocks = [];
blocks.push( createBlock( 'core/test-block' ) );

const { container } = render(
<BlockPreviewComponent
className="test-container-classname"
blocks={ blocks }
/>
);

// Test block and block contents are rendered.
const previewedBlock = screen.getByLabelText( 'Block: test block' );
const previewedBlockContents = screen.getByText(
'Test block edit view'
);
expect( previewedBlockContents ).toBeInTheDocument();

// Test elements within block contents are disabled.
await waitFor( () => {
const button = screen.getByText( 'Button' );
expect( button.hasAttribute( 'disabled' ) ).toBe( true );
} );

// Ensure the block preview class names are merged with the component's class name.
expect( container.firstChild.className ).toBe(
'test-container-classname block-editor-block-preview__live-content components-disabled'
);

// Ensure there is no nesting between the parent component and rendered blocks.
expect( container.firstChild.firstChild ).toBe( previewedBlock );
} );
} );
5 changes: 4 additions & 1 deletion packages/block-editor/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,10 @@ export { default as BlockList } from './block-list';
export { useBlockProps } from './block-list/use-block-props';
export { LayoutStyle as __experimentalLayoutStyle } from './block-list/layout';
export { default as BlockMover } from './block-mover';
export { default as BlockPreview } from './block-preview';
export {
default as BlockPreview,
useBlockPreview as __experimentalUseBlockPreview,
} from './block-preview';
export {
default as BlockSelectionClearer,
useBlockSelectionClearer as __unstableUseBlockSelectionClearer,
Expand Down
71 changes: 54 additions & 17 deletions packages/block-library/src/post-template/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { useState, useMemo } from '@wordpress/element';
import { memo, useMemo, useState } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import {
BlockContextProvider,
BlockPreview,
__experimentalUseBlockPreview as useBlockPreview,
useBlockProps,
useInnerBlocksProps,
store as blockEditorStore,
Expand All @@ -30,6 +30,39 @@ function PostTemplateInnerBlocks() {
return <li { ...innerBlocksProps } />;
}

function PostTemplateBlockPreview( {
blocks,
blockContextId,
isHidden,
setActiveBlockContextId,
} ) {
const blockPreviewProps = useBlockPreview( {
blocks,
} );

const handleOnClick = () => {
setActiveBlockContextId( blockContextId );
};

const style = {
display: isHidden ? 'none' : undefined,
};

return (
<li
{ ...blockPreviewProps }
tabIndex={ 0 }
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-to-interactive-role
role="button"
onClick={ handleOnClick }
onKeyPress={ handleOnClick }
style={ style }
/>
);
}

const MemoizedPostTemplateBlockPreview = memo( PostTemplateBlockPreview );

export default function PostTemplateEdit( {
clientId,
context: {
Expand All @@ -53,7 +86,7 @@ export default function PostTemplateEdit( {
},
} ) {
const [ { page } ] = queryContext;
const [ activeBlockContext, setActiveBlockContext ] = useState();
const [ activeBlockContextId, setActiveBlockContextId ] = useState();

const { posts, blocks } = useSelect(
( select ) => {
Expand Down Expand Up @@ -115,7 +148,6 @@ export default function PostTemplateEdit( {
templateSlug,
]
);

const blockContexts = useMemo(
() =>
posts?.map( ( post ) => ( {
Expand Down Expand Up @@ -144,6 +176,10 @@ export default function PostTemplateEdit( {
return <p { ...blockProps }> { __( 'No results found.' ) }</p>;
}

// To avoid flicker when switching active block contexts, a preview is rendered
// for each block context, but the preview for the active block context is hidden.
// This ensures that when it is displayed again, the cached rendering of the
// block preview is used, instead of having to re-render the preview from scratch.
return (
<ul { ...blockProps }>
{ blockContexts &&
Expand All @@ -152,20 +188,21 @@ export default function PostTemplateEdit( {
key={ blockContext.postId }
value={ blockContext }
>
{ blockContext ===
( activeBlockContext || blockContexts[ 0 ] ) ? (
{ blockContext.postId ===
( activeBlockContextId ||
blockContexts[ 0 ]?.postId ) ? (
<PostTemplateInnerBlocks />
) : (
<li>
<BlockPreview
blocks={ blocks }
__experimentalLive
__experimentalOnClick={ () =>
setActiveBlockContext( blockContext )
}
/>
</li>
) }
) : null }
<MemoizedPostTemplateBlockPreview
blocks={ blocks }
blockContextId={ blockContext.postId }
setActiveBlockContextId={ setActiveBlockContextId }
isHidden={
blockContext.postId ===
( activeBlockContextId ||
blockContexts[ 0 ]?.postId )
}
/>
</BlockContextProvider>
) ) }
</ul>
Expand Down
Loading

0 comments on commit 7a050db

Please sign in to comment.