diff --git a/docs/reference-guides/data/data-core-block-editor.md b/docs/reference-guides/data/data-core-block-editor.md index cfb95389ba039..7a2051aa91aee 100644 --- a/docs/reference-guides/data/data-core-block-editor.md +++ b/docs/reference-guides/data/data-core-block-editor.md @@ -1087,6 +1087,10 @@ _Returns_ - `boolean`: Whether block is first in multi-selection. +### isIframeIncompatible + +Undocumented declaration. + ### isLastBlockChangePersistent Returns true if the most recent block change is be considered persistent, or diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index 129a4f6f178bb..64f224095c0da 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -56,6 +56,7 @@ "@wordpress/preferences": "file:../preferences", "@wordpress/private-apis": "file:../private-apis", "@wordpress/rich-text": "file:../rich-text", + "@wordpress/server-side-render": "file:../server-side-render", "@wordpress/shortcode": "file:../shortcode", "@wordpress/style-engine": "file:../style-engine", "@wordpress/token-list": "file:../token-list", diff --git a/packages/block-editor/src/components/block-edit/edit.js b/packages/block-editor/src/components/block-edit/edit.js index 1154b99efebab..f07f50dab8412 100644 --- a/packages/block-editor/src/components/block-edit/edit.js +++ b/packages/block-editor/src/components/block-edit/edit.js @@ -12,12 +12,23 @@ import { hasBlockSupport, getBlockType, } from '@wordpress/blocks'; -import { useContext, useMemo } from '@wordpress/element'; +import { useContext, useMemo, useRef } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import ServerSideRender from '@wordpress/server-side-render'; +import { useResizeObserver } from '@wordpress/compose'; /** * Internal dependencies */ import BlockContext from '../block-context'; +import { useSelect } from '@wordpress/data'; +import { store } from '../../store'; +import { + useBlockProps, + DisableBlockProps, +} from '../block-list/use-block-props'; +import Warning from '../warning'; +import BlockPopover from '../block-popover'; /** * Default value used for blocks which do not define their own context needs, @@ -29,8 +40,65 @@ import BlockContext from '../block-context'; */ const DEFAULT_BLOCK_CONTEXT = {}; +function LoadingResponsePlaceholder() { + return { __( 'Loading preview…' ) }; +} + +function IframeCompat( { + clientId, + blockType, + attributes, + isSelected, + children, +} ) { + const isIframeIncompatible = useSelect( + ( select ) => select( store ).isIframeIncompatible( clientId ), + [ clientId ] + ); + const [ resizeListener, sizes ] = useResizeObserver(); + const ref = useRef(); + const blockProps = useBlockProps( { + ref, + style: { + height: isSelected ? sizes?.height : undefined, + }, + } ); + + if ( ! isIframeIncompatible ) { + return children; + } + + return ( +
+ { isSelected && ( + + +
+ { resizeListener } + { children } +
+
+
+ ) } + { ! isSelected && ( + + ) } +
+ ); +} + export const Edit = ( props ) => { - const { attributes = {}, name } = props; + const { attributes = {}, name, clientId, isSelected } = props; const blockType = getBlockType( name ); const blockContext = useContext( BlockContext ); @@ -55,7 +123,16 @@ export const Edit = ( props ) => { const Component = blockType.edit || blockType.save; if ( blockType.apiVersion > 1 ) { - return ; + return ( + + + + ); } // Generate a class name for the block's editable form. @@ -69,7 +146,18 @@ export const Edit = ( props ) => { ); return ( - + + + ); }; diff --git a/packages/block-editor/src/components/block-list/use-block-props/index.js b/packages/block-editor/src/components/block-list/use-block-props/index.js index ca6bb4355f52d..b044a82e3493f 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/index.js +++ b/packages/block-editor/src/components/block-list/use-block-props/index.js @@ -6,7 +6,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { useContext } from '@wordpress/element'; +import { useContext, createContext } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { __unstableGetBlockProps as getBlockProps, @@ -34,6 +34,7 @@ import { useEventHandlers } from './use-selected-block-event-handlers'; import { useNavModeExit } from './use-nav-mode-exit'; import { useBlockRefProvider } from './use-block-refs'; import { useIntersectionObserver } from './use-intersection-observer'; +import { useNonReactObserver } from './use-non-react-observer'; import { store as blockEditorStore } from '../../../store'; import useBlockOverlayActive from '../../block-content-overlay'; @@ -43,6 +44,8 @@ import useBlockOverlayActive from '../../block-content-overlay'; */ const BLOCK_ANIMATION_THRESHOLD = 200; +export const DisableBlockProps = createContext(); + /** * This hook is used to lightly mark an element as a block element. The element * should be the outermost element of a block. Call this hook and pass the @@ -66,6 +69,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { wrapperProps = {}, isAligned, } = useContext( BlockListBlockContext ); + const disableBlockProps = useContext( DisableBlockProps ); const { index, mode, @@ -117,9 +121,6 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { ); const hasOverlay = useBlockOverlayActive( clientId ); - - // translators: %s: Type of block (i.e. Text, Image etc) - const blockLabel = sprintf( __( 'Block: %s' ), blockTitle ); const htmlSuffix = mode === 'html' && ! __unstableIsHtml ? '-visual' : ''; const mergedRefs = useMergeRefs( [ props.ref, @@ -137,6 +138,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { triggerAnimationOnChange: index, } ), useDisabled( { isDisabled: ! hasOverlay } ), + useNonReactObserver( clientId ), ] ); const blockEditContext = useBlockEditContext(); @@ -147,6 +149,20 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { ); } + const additionalClassNames = [ + useBlockClassNames( clientId ), + useBlockDefaultClassName( clientId ), + useBlockCustomClassName( clientId ), + useBlockMovingModeClassNames( clientId ), + ]; + + if ( disableBlockProps ) { + return {}; + } + + // translators: %s: Type of block (i.e. Text, Image etc) + const blockLabel = sprintf( __( 'Block: %s' ), blockTitle ); + return { tabIndex: 0, ...wrapperProps, @@ -167,10 +183,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { className, props.className, wrapperProps.className, - useBlockClassNames( clientId ), - useBlockDefaultClassName( clientId ), - useBlockCustomClassName( clientId ), - useBlockMovingModeClassNames( clientId ) + ...additionalClassNames ), style: { ...wrapperProps.style, ...props.style }, }; diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-non-react-observer.js b/packages/block-editor/src/components/block-list/use-block-props/use-non-react-observer.js new file mode 100644 index 0000000000000..09142059752d0 --- /dev/null +++ b/packages/block-editor/src/components/block-list/use-block-props/use-non-react-observer.js @@ -0,0 +1,46 @@ +/** + * WordPress dependencies + */ +import { useRefEffect } from '@wordpress/compose'; +import { useDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { store as blockEditorStore } from '../../../store'; + +export function useNonReactObserver( clientId ) { + const { __unstableIframeIncompatible } = useDispatch( blockEditorStore ); + return useRefEffect( ( node ) => { + const observer = new node.ownerDocument.defaultView.MutationObserver( + ( mutationList ) => { + for ( const mutation of mutationList ) { + for ( const addedNode of mutation.addedNodes ) { + if ( ! addedNode.isConnected ) continue; + + if ( + addedNode.isContentEditable || + addedNode.parentElement.isContentEditable + ) + continue; + + if ( + Object.keys( addedNode ).some( ( i ) => { + // Yes, React could change this at any point, + // but we'll know when we update the version. + return i.startsWith( '__react' ); + } ) + ) + continue; + + __unstableIframeIncompatible( clientId ); + } + } + } + ); + observer.observe( node, { childList: true, subtree: true } ); + return () => { + observer.disconnect( node ); + }; + }, [] ); +} diff --git a/packages/block-editor/src/components/block-popover/use-popover-scroll.js b/packages/block-editor/src/components/block-popover/use-popover-scroll.js index 8aeb768e302f6..e7c002514f0ab 100644 --- a/packages/block-editor/src/components/block-popover/use-popover-scroll.js +++ b/packages/block-editor/src/components/block-popover/use-popover-scroll.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { useRefEffect } from '@wordpress/compose'; +import { getScrollContainer } from '@wordpress/dom'; /** * Allow scrolling "through" popovers over the canvas. This is only called for @@ -19,14 +20,28 @@ function usePopoverScroll( scrollableRef ) { function onWheel( event ) { const { deltaX, deltaY } = event; + const scrollContainer = getScrollContainer( + scrollableRef.current + ); + const { ownerDocument } = scrollContainer; + const { defaultView } = ownerDocument; + const windowScroll = + scrollContainer === ownerDocument.body || + scrollContainer === ownerDocument.documentElement; scrollableRef.current.scrollBy( deltaX, deltaY ); + + if ( windowScroll ) { + defaultView.scrollBy( 0, deltaY ); + } else { + scrollContainer.scrollLeft += deltaX; + scrollContainer.scrollTop += deltaY; + } + + event.preventDefault(); } - // Tell the browser that we do not call event.preventDefault - // See https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#improving_scrolling_performance_with_passive_listeners - const options = { passive: true }; - node.addEventListener( 'wheel', onWheel, options ); + node.addEventListener( 'wheel', onWheel ); return () => { - node.removeEventListener( 'wheel', onWheel, options ); + node.removeEventListener( 'wheel', onWheel ); }; }, [ scrollableRef ] diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 98bdf1bffc78c..9a749a6da0a47 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -1264,6 +1264,13 @@ export function toggleBlockMode( clientId ) { }; } +export function __unstableIframeIncompatible( clientId ) { + return { + type: 'IFRAME_INCOMPATIBLE', + clientId, + }; +} + /** * Returns an action object used in signalling that the user has begun to type. * diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index c207df38692b2..6b04c76e19f9a 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -1507,6 +1507,18 @@ export function blocksMode( state = {}, action ) { return state; } +export function iframeIncompatible( state = {}, action ) { + if ( action.type === 'IFRAME_INCOMPATIBLE' ) { + const { clientId } = action; + return { + ...state, + [ clientId ]: true, + }; + } + + return state; +} + /** * Reducer returning the block insertion point visibility, either null if there * is not an explicit insertion point assigned, or an object of its `index` and @@ -1873,6 +1885,7 @@ export default combineReducers( { isSelectionEnabled, initialPosition, blocksMode, + iframeIncompatible, blockListSettings, insertionPoint, template, diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 543da7dd1debb..2452a744ec555 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -1279,6 +1279,10 @@ export function getBlockMode( state, clientId ) { return state.blocksMode[ clientId ] || 'visual'; } +export function isIframeIncompatible( state, clientId ) { + return state.iframeIncompatible[ clientId ] || false; +} + /** * Returns true if the user is typing, or false otherwise. *