diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md
index 5c8f30a5802ce..1736d857763df 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_
+
+- _options_ `Object`: Preview options.
+- _options.blocks_ `WPBlock[]`: Block objects.
+- _options.props_ `Object`: Optional. Props to pass to the element. Must contain the ref if one is defined.
+- _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 cea2e8ee9b62c..e8ea15edf9271 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,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,
@@ -63,3 +69,58 @@ 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( () => {
+ 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/style.scss b/packages/block-editor/src/components/block-preview/style.scss
index 71ca31e9bc221..23661a1232f67 100644
--- a/packages/block-editor/src/components/block-preview/style.scss
+++ b/packages/block-editor/src/components/block-preview/style.scss
@@ -36,3 +36,26 @@
}
}
}
+
+.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;
+ }
+}
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 4a0c277a1f5cf..fa4eb89973979 100644
--- a/packages/block-library/src/post-content/edit.js
+++ b/packages/block-library/src/post-content/edit.js
@@ -2,9 +2,10 @@
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
+import { parse } from '@wordpress/blocks';
import { useSelect } from '@wordpress/data';
-import { RawHTML } from '@wordpress/element';
import {
+ useBlockPreview,
useBlockProps,
useInnerBlocksProps,
useSetting,
@@ -13,13 +14,19 @@ import {
Warning,
} from '@wordpress/block-editor';
import { useEntityProp, useEntityBlockEditor } from '@wordpress/core-data';
+import { useMemo } from '@wordpress/element';
/**
* Internal dependencies
*/
import { useCanEditEntity } from '../utils/hooks';
-function ReadOnlyContent( { userCanEdit, postType, postId } ) {
+function ReadOnlyContent( {
+ __experimentalLayout,
+ postId,
+ postType,
+ userCanEdit,
+} ) {
const [ , , content ] = useEntityProp(
'postType',
postType,
@@ -27,25 +34,29 @@ function ReadOnlyContent( { userCanEdit, postType, postId } ) {
postId
);
const blockProps = useBlockProps();
+
+ const rawContent = content?.raw;
+ const blocks = useMemo( () => {
+ return rawContent ? parse( rawContent ) : [];
+ }, [ rawContent ] );
+
+ const blockPreviewProps = useBlockPreview( {
+ blocks,
+ props: blockProps,
+ __experimentalLayout,
+ } );
+
return content?.protected && ! userCanEdit ? (
{ __( 'This content is password protected.' ) }
) : (
-
- { content?.rendered }
-
+
);
}
-function EditableContent( { layout, context = {} } ) {
+function EditableContent( { __experimentalLayout, context = {} } ) {
const { postType, postId } = context;
- const themeSupportsLayout = useSelect( ( select ) => {
- const { getSettings } = select( blockEditorStore );
- return getSettings()?.supportsLayout;
- }, [] );
- const defaultLayout = useSetting( 'layout' ) || {};
- const usedLayout = !! layout && layout.inherit ? defaultLayout : layout;
const [ blocks, onInput, onChange ] = useEntityBlockEditor(
'postType',
postType,
@@ -58,22 +69,34 @@ function EditableContent( { layout, context = {} } ) {
value: blocks,
onInput,
onChange,
- __experimentalLayout: themeSupportsLayout ? usedLayout : undefined,
+ __experimentalLayout,
}
);
return ;
}
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;
+ const themeSupportsLayout = useSelect( ( select ) => {
+ const { getSettings } = select( blockEditorStore );
+ return getSettings()?.supportsLayout;
+ }, [] );
+ const defaultLayout = useSetting( 'layout' ) || {};
+ const usedLayout = !! layout && layout.inherit ? defaultLayout : layout;
+ const __experimentalLayout = themeSupportsLayout ? usedLayout : undefined;
+
return isEditable ? (
-
+
) : (
} */
+ 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';