From 469d65d3787864351db976d0c33389ae62aa4ac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Thu, 9 Feb 2023 22:27:12 +0200 Subject: [PATCH 1/5] wip --- .../block-list/use-block-props/index.js | 2 + .../use-block-props/use-non-react-observer.js | 45 +++++++++++++++++++ packages/block-editor/src/store/actions.js | 7 +++ packages/block-editor/src/store/reducer.js | 13 ++++++ packages/block-editor/src/store/selectors.js | 4 ++ 5 files changed, 71 insertions(+) create mode 100644 packages/block-editor/src/components/block-list/use-block-props/use-non-react-observer.js 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..1d9d8e2173507 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 @@ -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'; @@ -137,6 +138,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { triggerAnimationOnChange: index, } ), useDisabled( { isDisabled: ! hasOverlay } ), + useNonReactObserver( clientId ), ] ); const blockEditContext = useBlockEditContext(); 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..b11d83220980a --- /dev/null +++ b/packages/block-editor/src/components/block-list/use-block-props/use-non-react-observer.js @@ -0,0 +1,45 @@ +/** + * 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.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; + + console.log( addedNode ); + __unstableIframeIncompatible( clientId ); + } + } + } + ); + observer.observe( node, { childList: true, subtree: true } ); + return () => { + observer.disconnect( node ); + }; + }, [] ); +} 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..6a3ab3c31f3d2 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 = true, action ) { + if ( action.type === 'IFRAME_INCOMPATIBLE' ) { + const { clientId } = action; + return { + ...state, + [ clientId ]: false, + }; + } + + 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. * From c4fdc0544bb9fc63270a3bfa97b3e367018b3dd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Fri, 10 Feb 2023 20:02:18 +0200 Subject: [PATCH 2/5] wip --- packages/block-editor/package.json | 1 + .../src/components/block-edit/edit.js | 83 +++++++++++++++++-- .../use-block-props/use-non-react-observer.js | 3 +- packages/block-editor/src/store/reducer.js | 4 +- 4 files changed, 83 insertions(+), 8 deletions(-) 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..934161631b72a 100644 --- a/packages/block-editor/src/components/block-edit/edit.js +++ b/packages/block-editor/src/components/block-edit/edit.js @@ -6,18 +6,32 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { withFilters } from '@wordpress/components'; +import { + withFilters, + Modal, + ToolbarGroup, + ToolbarButton, + Disabled, +} from '@wordpress/components'; import { getBlockDefaultClassName, hasBlockSupport, getBlockType, } from '@wordpress/blocks'; -import { useContext, useMemo } from '@wordpress/element'; +import { useContext, useMemo, useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import ServerSideRender from '@wordpress/server-side-render'; /** * Internal dependencies */ import BlockContext from '../block-context'; +import { useSelect } from '@wordpress/data'; +import { store } from '../../store'; +import BlockControls from '../block-controls'; +import { useBlockProps } from '../block-list/use-block-props'; +import BlockToolbar from '../block-toolbar'; +import Warning from '../warning'; /** * Default value used for blocks which do not define their own context needs, @@ -29,8 +43,49 @@ import BlockContext from '../block-context'; */ const DEFAULT_BLOCK_CONTEXT = {}; +function IframeCompat( { clientId, blockType, attributes, children } ) { + const isIframeIncompatible = useSelect( + ( select ) => select( store ).isIframeIncompatible( clientId ), + [ clientId ] + ); + const [ open, setOpen ] = useState( false ); + const blockProps = useBlockProps(); + + if ( ! isIframeIncompatible ) { + return children; + } + + return ( +
+ { ! open && ( + + + setOpen( true ) }> + { __( 'Edit' ) } + + + + ) } + { open && ( + setOpen( false ) } + // Third party popovers may trigger a click outside. + shouldCloseOnClickOutside={ false } + > + { children } + + ) } + +
+ ); +} + export const Edit = ( props ) => { - const { attributes = {}, name } = props; + const { attributes = {}, name, clientId } = props; const blockType = getBlockType( name ); const blockContext = useContext( BlockContext ); @@ -55,7 +110,15 @@ 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 +132,17 @@ export const Edit = ( props ) => { ); return ( - + + + ); }; 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 index b11d83220980a..09142059752d0 100644 --- 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 @@ -16,6 +16,8 @@ export function useNonReactObserver( clientId ) { ( mutationList ) => { for ( const mutation of mutationList ) { for ( const addedNode of mutation.addedNodes ) { + if ( ! addedNode.isConnected ) continue; + if ( addedNode.isContentEditable || addedNode.parentElement.isContentEditable @@ -31,7 +33,6 @@ export function useNonReactObserver( clientId ) { ) continue; - console.log( addedNode ); __unstableIframeIncompatible( clientId ); } } diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 6a3ab3c31f3d2..6b04c76e19f9a 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -1507,12 +1507,12 @@ export function blocksMode( state = {}, action ) { return state; } -export function iframeIncompatible( state = true, action ) { +export function iframeIncompatible( state = {}, action ) { if ( action.type === 'IFRAME_INCOMPATIBLE' ) { const { clientId } = action; return { ...state, - [ clientId ]: false, + [ clientId ]: true, }; } From 4cf8d76601d2fbd8a5c717169233a967424ec755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Fri, 10 Feb 2023 21:15:28 +0200 Subject: [PATCH 3/5] Popover approach --- .../data/data-core-block-editor.md | 4 + .../src/components/block-edit/edit.js | 83 +++++++++++-------- .../block-list/use-block-props/index.js | 27 ++++-- 3 files changed, 70 insertions(+), 44 deletions(-) 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/src/components/block-edit/edit.js b/packages/block-editor/src/components/block-edit/edit.js index 934161631b72a..5f0cfb2c7e898 100644 --- a/packages/block-editor/src/components/block-edit/edit.js +++ b/packages/block-editor/src/components/block-edit/edit.js @@ -6,21 +6,16 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { - withFilters, - Modal, - ToolbarGroup, - ToolbarButton, - Disabled, -} from '@wordpress/components'; +import { withFilters } from '@wordpress/components'; import { getBlockDefaultClassName, hasBlockSupport, getBlockType, } from '@wordpress/blocks'; -import { useContext, useMemo, useState } from '@wordpress/element'; +import { useContext, useMemo } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import ServerSideRender from '@wordpress/server-side-render'; +import { useResizeObserver } from '@wordpress/compose'; /** * Internal dependencies @@ -28,10 +23,12 @@ import ServerSideRender from '@wordpress/server-side-render'; import BlockContext from '../block-context'; import { useSelect } from '@wordpress/data'; import { store } from '../../store'; -import BlockControls from '../block-controls'; -import { useBlockProps } from '../block-list/use-block-props'; -import BlockToolbar from '../block-toolbar'; +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, @@ -43,13 +40,27 @@ import Warning from '../warning'; */ const DEFAULT_BLOCK_CONTEXT = {}; -function IframeCompat( { clientId, blockType, attributes, children } ) { +function LoadingResponsePlaceholder() { + return { __( 'Loading preview…' ) }; +} + +function IframeCompat( { + clientId, + blockType, + attributes, + isSelected, + children, +} ) { const isIframeIncompatible = useSelect( ( select ) => select( store ).isIframeIncompatible( clientId ), [ clientId ] ); - const [ open, setOpen ] = useState( false ); - const blockProps = useBlockProps(); + const [ resizeListener, sizes ] = useResizeObserver(); + const blockProps = useBlockProps( { + style: { + height: isSelected ? sizes?.height : undefined, + }, + } ); if ( ! isIframeIncompatible ) { return children; @@ -57,35 +68,33 @@ function IframeCompat( { clientId, blockType, attributes, children } ) { return (
- { ! open && ( - - - setOpen( true ) }> - { __( 'Edit' ) } - - - - ) } - { open && ( - setOpen( false ) } - // Third party popovers may trigger a click outside. - shouldCloseOnClickOutside={ false } + { isSelected && ( + - { children } - + +
+ { resizeListener } + { children } +
+
+ + ) } + { ! isSelected && ( + ) } -
); } export const Edit = ( props ) => { - const { attributes = {}, name, clientId } = props; + const { attributes = {}, name, clientId, isSelected } = props; const blockType = getBlockType( name ); const blockContext = useContext( BlockContext ); @@ -115,6 +124,7 @@ export const Edit = ( props ) => { clientId={ clientId } blockType={ blockType } attributes={ attributes } + isSelected={ isSelected } > @@ -136,6 +146,7 @@ export const Edit = ( props ) => { clientId={ clientId } blockType={ blockType } attributes={ attributes } + isSelected={ isSelected } > Date: Fri, 10 Feb 2023 21:32:20 +0200 Subject: [PATCH 4/5] don't shift popover --- packages/block-editor/src/components/block-edit/edit.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-editor/src/components/block-edit/edit.js b/packages/block-editor/src/components/block-edit/edit.js index 5f0cfb2c7e898..d0f048e294ede 100644 --- a/packages/block-editor/src/components/block-edit/edit.js +++ b/packages/block-editor/src/components/block-edit/edit.js @@ -73,6 +73,7 @@ function IframeCompat( { clientId={ clientId } __unstablePopoverSlot="Popover" placement="overlay" + shift={ false } >
From 8c73e1f9674d0e4e3d2da57d0bd80c8488fe632f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Mon, 13 Feb 2023 17:33:48 +0200 Subject: [PATCH 5/5] Fix scroll issues --- .../src/components/block-edit/edit.js | 5 +++- .../block-popover/use-popover-scroll.js | 25 +++++++++++++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/components/block-edit/edit.js b/packages/block-editor/src/components/block-edit/edit.js index d0f048e294ede..f07f50dab8412 100644 --- a/packages/block-editor/src/components/block-edit/edit.js +++ b/packages/block-editor/src/components/block-edit/edit.js @@ -12,7 +12,7 @@ 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'; @@ -56,7 +56,9 @@ function IframeCompat( { [ clientId ] ); const [ resizeListener, sizes ] = useResizeObserver(); + const ref = useRef(); const blockProps = useBlockProps( { + ref, style: { height: isSelected ? sizes?.height : undefined, }, @@ -72,6 +74,7 @@ function IframeCompat( { 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 ]