From c3947bd0ce45006056e4a42a6b6ca2381451f9be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Fri, 14 May 2021 19:24:26 +0300 Subject: [PATCH] Block editor: async mode: enable only for blocks out of view --- .../src/components/block-list/index.js | 75 +++++++++++-------- .../block-list/use-block-props/index.js | 2 + .../use-intersection-observer.js | 25 +++++++ packages/block-editor/src/utils/dom.js | 13 ++-- 4 files changed, 79 insertions(+), 36 deletions(-) create mode 100644 packages/block-editor/src/components/block-list/use-block-props/use-intersection-observer.js diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index 9ba63035bfa8e7..0fe8347f812e68 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -8,6 +8,7 @@ import classnames from 'classnames'; */ import { AsyncModeProvider, useSelect } from '@wordpress/data'; import { useViewportMatch, useMergeRefs } from '@wordpress/compose'; +import { createContext, useState, useMemo } from '@wordpress/element'; /** * Internal dependencies @@ -21,6 +22,8 @@ import { usePreParsePatterns } from '../../utils/pre-parse-patterns'; import { LayoutProvider, defaultLayout } from './layout'; import BlockToolsBackCompat from '../block-tools/back-compat'; +export const IntersectionObserver = createContext(); + function Root( { className, children } ) { const isLargeViewport = useViewportMatch( 'medium' ); const { @@ -82,39 +85,51 @@ function Items( { __experimentalAppenderTagName, __experimentalLayout: layout = defaultLayout, } ) { - function selector( select ) { - const { - getBlockOrder, - getSelectedBlockClientId, - getMultiSelectedBlockClientIds, - hasMultiSelection, - } = select( blockEditorStore ); - return { - blockClientIds: getBlockOrder( rootClientId ), - selectedBlockClientId: getSelectedBlockClientId(), - multiSelectedBlockClientIds: getMultiSelectedBlockClientIds(), - hasMultiSelection: hasMultiSelection(), - }; - } + const [ intersectingBlocks, setIntersectingBlocks ] = useState( new Set() ); + const intersectionObserver = useMemo( () => { + const { IntersectionObserver: Observer } = window; - const { - blockClientIds, - selectedBlockClientId, - multiSelectedBlockClientIds, - hasMultiSelection, - } = useSelect( selector, [ rootClientId ] ); + if ( ! Observer ) { + return; + } + + return new Observer( ( entries ) => { + setIntersectingBlocks( ( oldIntersectingBlocks ) => { + const newIntersectingBlocks = new Set( oldIntersectingBlocks ); + for ( const entry of entries ) { + const clientId = entry.target.getAttribute( 'data-block' ); + const action = entry.isIntersecting ? 'add' : 'delete'; + newIntersectingBlocks[ action ]( clientId ); + } + return newIntersectingBlocks; + } ); + } ); + }, [ setIntersectingBlocks ] ); + const { order, selectedBlocks } = useSelect( + ( select ) => { + const { getBlockOrder, getSelectedBlockClientIds } = select( + blockEditorStore + ); + return { + order: getBlockOrder( rootClientId ), + selectedBlocks: getSelectedBlockClientIds(), + }; + }, + [ rootClientId ] + ); return ( - { blockClientIds.map( ( clientId, index ) => { - const isBlockInSelection = hasMultiSelection - ? multiSelectedBlockClientIds.includes( clientId ) - : selectedBlockClientId === clientId; - - return ( + + { order.map( ( clientId, index ) => ( - ); - } ) } - { blockClientIds.length < 1 && placeholder } + ) ) } + + { order.length < 1 && placeholder } { + if ( observer ) { + observer.observe( node ); + return () => { + observer.unobserve( node ); + }; + } + }, + [ observer ] + ); +} diff --git a/packages/block-editor/src/utils/dom.js b/packages/block-editor/src/utils/dom.js index f99468a7e751c7..3a6db1685aebb1 100644 --- a/packages/block-editor/src/utils/dom.js +++ b/packages/block-editor/src/utils/dom.js @@ -1,3 +1,7 @@ +// Consider the block appender to be a child block of its own, which also has +// this class. +const BLOCK_SELECTOR = '.wp-block'; + /** * Returns true if two elements are contained within the same block. * @@ -7,10 +11,7 @@ * @return {boolean} Whether elements are in the same block. */ export function isInSameBlock( a, b ) { - return ( - a.closest( '.block-editor-block-list__block' ) === - b.closest( '.block-editor-block-list__block' ) - ); + return a.closest( BLOCK_SELECTOR ) === b.closest( BLOCK_SELECTOR ); } /** @@ -24,7 +25,7 @@ export function isInSameBlock( a, b ) { * children. */ export function isInsideRootBlock( blockElement, element ) { - const parentBlock = element.closest( '.block-editor-block-list__block' ); + const parentBlock = element.closest( BLOCK_SELECTOR ); return parentBlock === blockElement; } @@ -46,7 +47,7 @@ export function getBlockClientId( node ) { } const elementNode = /** @type {Element} */ ( node ); - const blockNode = elementNode.closest( '.block-editor-block-list__block' ); + const blockNode = elementNode.closest( BLOCK_SELECTOR ); if ( ! blockNode ) { return;