From b06b3741bf2036bf87fe1903984074a6628a71e4 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 3 Nov 2021 17:26:44 +1100 Subject: [PATCH] Refactor block preview behaviour to a useBlockPreview hook for the post content block --- packages/block-editor/README.md | 17 ++++ .../src/components/block-preview/index.js | 67 ++++++++++++-- .../src/components/block-preview/live.js | 40 ++------ packages/block-editor/src/components/index.js | 2 +- .../block-library/src/post-content/edit.js | 42 ++++----- .../compose/src/hooks/use-disabled/index.js | 92 +++++++++++++++++++ packages/compose/src/index.js | 1 + 7 files changed, 195 insertions(+), 66 deletions(-) create mode 100644 packages/compose/src/hooks/use-disabled/index.js diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 5c8f30a5802ce..63315ada1be69 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -660,6 +660,23 @@ _Returns_ Undocumented declaration. +### useBlockPreview + +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. + +_Parameters_ + +- _props_ `Object`: Optional. Props to pass to the element. Must contain the ref if one is defined. +- _options_ `Object`: Preview options. +- _options.blocks_ `WPBlock[]`: Block objects. +- _options.\_\_experimentalLayout_ `Object`: Layout settings to be used in the preview. + ### useBlockProps This hook is used to lightly mark an element as a block element. The element diff --git a/packages/block-editor/src/components/block-preview/index.js b/packages/block-editor/src/components/block-preview/index.js index 8fff2adfa1a83..c3002e9045be0 100644 --- a/packages/block-editor/src/components/block-preview/index.js +++ b/packages/block-editor/src/components/block-preview/index.js @@ -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'; @@ -16,12 +21,12 @@ 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, __experimentalPadding = 0, viewportWidth = 1200, - __experimentalAsButton = true, __experimentalLive = false, __experimentalOnClick, } ) { @@ -41,11 +46,7 @@ export function BlockPreview( { return ( { __experimentalLive ? ( - + ) : ( select( blockEditorStore ).getSettings(), + [] + ); + const disabledRef = useDisabled(); + const ref = useMergeRefs( [ props.ref, disabledRef ] ); + const settings = useMemo( () => { + const _settings = { ...originalSettings }; + _settings.__experimentalBlockPatterns = []; + return _settings; + }, [ originalSettings ] ); + const renderedBlocks = useMemo( () => castArray( blocks ), [ blocks ] ); + + const children = ( + + + + ); + + return { + ...props, + ref, + className: classnames( + props.className, + 'block-editor-block-preview__live-content', + 'components-disabled' + ), + children: blocks?.length ? children : null, + }; +} diff --git a/packages/block-editor/src/components/block-preview/live.js b/packages/block-editor/src/components/block-preview/live.js index 8b946c00b5629..792740bb2c02b 100644 --- a/packages/block-editor/src/components/block-preview/live.js +++ b/packages/block-editor/src/components/block-preview/live.js @@ -6,47 +6,19 @@ import { Disabled } from '@wordpress/components'; /** * Internal dependencies */ -import { BlockListItems } from '../block-list'; +import BlockList from '../block-list'; -const DEFAULT_CONTROLS = [ 'none', 'left', 'center', 'right', 'wide', 'full' ]; -const WIDE_CONTROLS = [ 'wide', 'full' ]; - -const layout = { - type: 'default', - alignments: [ ...DEFAULT_CONTROLS, ...WIDE_CONTROLS ], -}; - -export default function LiveBlockPreview( { - onClick, - __experimentalAsButton = true, - themeSupportsLayout = false, -} ) { - let blockList; - - if ( __experimentalAsButton ) { - blockList = ( - - - - ); - } else { - const props = {}; - if ( themeSupportsLayout ) { - props.__experimentalLayout = layout; - } - blockList = ; - } - - return __experimentalAsButton ? ( +export default function LiveBlockPreview( { onClick } ) { + return (
- { blockList } + + +
- ) : ( - <>{ blockList } ); } diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index dcccb216217e3..a2e4b5c3a02db 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -103,7 +103,7 @@ 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 } from './block-preview'; export { default as BlockSelectionClearer, useBlockSelectionClearer as __unstableUseBlockSelectionClearer, diff --git a/packages/block-library/src/post-content/edit.js b/packages/block-library/src/post-content/edit.js index 625d704462bf3..da5ce6f4175d5 100644 --- a/packages/block-library/src/post-content/edit.js +++ b/packages/block-library/src/post-content/edit.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; - /** * WordPress dependencies */ @@ -10,7 +5,7 @@ import { __ } from '@wordpress/i18n'; import { parse } from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; import { - BlockPreview, + useBlockPreview, useBlockProps, useInnerBlocksProps, useSetting, @@ -19,49 +14,45 @@ import { Warning, } from '@wordpress/block-editor'; import { useEntityProp, useEntityBlockEditor } from '@wordpress/core-data'; -import { useMergeRefs } from '@wordpress/compose'; import { useMemo } from '@wordpress/element'; /** * Internal dependencies */ import { useCanEditEntity } from '../utils/hooks'; -import { useDisabled } from './use-disabled'; -function ReadOnlyContent( { userCanEdit, postType, postId } ) { +function ReadOnlyContent( { layout, userCanEdit, postType, postId } ) { const [ , , content ] = useEntityProp( 'postType', postType, 'content', postId ); - const { ref, ...blockProps } = useBlockProps( { - className: classnames( - 'components-disabled', - 'block-editor-block-preview__live-content' - ), - } ); - const node = useDisabled(); + const blockProps = useBlockProps(); - const mergedRefs = useMergeRefs( [ ref, node ] ); + const themeSupportsLayout = useSelect( ( select ) => { + const { getSettings } = select( blockEditorStore ); + return getSettings()?.supportsLayout; + }, [] ); + const defaultLayout = useSetting( 'layout' ) || {}; + const usedLayout = !! layout && layout.inherit ? defaultLayout : layout; const rawContent = content?.raw; const blocks = useMemo( () => { return rawContent ? parse( rawContent ) : []; }, [ rawContent ] ); + const blockPreviewProps = useBlockPreview( blockProps, { + blocks, + __experimentalLayout: themeSupportsLayout ? usedLayout : undefined, + } ); + return content?.protected && ! userCanEdit ? (
{ __( 'This content is password protected.' ) }
) : ( -
- -
+
); } @@ -92,7 +83,7 @@ function EditableContent( { layout, context = {} } ) { } function Content( props ) { - const { context: { queryId, postType, postId } = {} } = props; + const { context: { queryId, postType, postId } = {}, layout } = props; const isDescendentOfQueryLoop = !! queryId; const userCanEdit = useCanEditEntity( 'postType', postType, postId ); const isEditable = userCanEdit && ! isDescendentOfQueryLoop; @@ -101,6 +92,7 @@ function Content( props ) { ) : ( } */ + const node = useRef( null ); + + const disable = () => { + if ( ! node.current ) { + return; + } + + focus.focusable.find( node.current ).forEach( ( focusable ) => { + if ( + includes( DISABLED_ELIGIBLE_NODE_NAMES, focusable.nodeName ) + ) { + focusable.setAttribute( 'disabled', '' ); + } + + if ( focusable.nodeName === 'A' ) { + focusable.setAttribute( 'tabindex', '-1' ); + } + + const tabIndex = focusable.getAttribute( 'tabindex' ); + if ( tabIndex !== null && tabIndex !== '-1' ) { + focusable.removeAttribute( 'tabindex' ); + } + + if ( focusable.hasAttribute( 'contenteditable' ) ) { + focusable.setAttribute( 'contenteditable', 'false' ); + } + } ); + }; + + // Debounce re-disable since disabling process itself will incur + // additional mutations which should be ignored. + const debouncedDisable = useCallback( + debounce( disable, undefined, { leading: true } ), + [] + ); + + useLayoutEffect( () => { + disable(); + + /** @type {MutationObserver | undefined} */ + let observer; + if ( node.current ) { + observer = new window.MutationObserver( debouncedDisable ); + observer.observe( node.current, { + childList: true, + attributes: true, + subtree: true, + } ); + } + + return () => { + if ( observer ) { + observer.disconnect(); + } + debouncedDisable.cancel(); + }; + }, [] ); + + return node; +} diff --git a/packages/compose/src/index.js b/packages/compose/src/index.js index 2ce3a2ab33f6f..2bfc7c7f5f848 100644 --- a/packages/compose/src/index.js +++ b/packages/compose/src/index.js @@ -17,6 +17,7 @@ export { default as useConstrainedTabbing } from './hooks/use-constrained-tabbin export { default as useCopyOnClick } from './hooks/use-copy-on-click'; export { default as useCopyToClipboard } from './hooks/use-copy-to-clipboard'; export { default as __experimentalUseDialog } from './hooks/use-dialog'; +export { default as __experimentalUseDisabled } from './hooks/use-disabled'; export { default as __experimentalUseDragging } from './hooks/use-dragging'; export { default as useFocusOnMount } from './hooks/use-focus-on-mount'; export { default as __experimentalUseFocusOutside } from './hooks/use-focus-outside';