Skip to content

Commit c3947bd

Browse files
committed
Block editor: async mode: enable only for blocks out of view
1 parent f21f09d commit c3947bd

File tree

4 files changed

+79
-36
lines changed

4 files changed

+79
-36
lines changed

packages/block-editor/src/components/block-list/index.js

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import classnames from 'classnames';
88
*/
99
import { AsyncModeProvider, useSelect } from '@wordpress/data';
1010
import { useViewportMatch, useMergeRefs } from '@wordpress/compose';
11+
import { createContext, useState, useMemo } from '@wordpress/element';
1112

1213
/**
1314
* Internal dependencies
@@ -21,6 +22,8 @@ import { usePreParsePatterns } from '../../utils/pre-parse-patterns';
2122
import { LayoutProvider, defaultLayout } from './layout';
2223
import BlockToolsBackCompat from '../block-tools/back-compat';
2324

25+
export const IntersectionObserver = createContext();
26+
2427
function Root( { className, children } ) {
2528
const isLargeViewport = useViewportMatch( 'medium' );
2629
const {
@@ -82,39 +85,51 @@ function Items( {
8285
__experimentalAppenderTagName,
8386
__experimentalLayout: layout = defaultLayout,
8487
} ) {
85-
function selector( select ) {
86-
const {
87-
getBlockOrder,
88-
getSelectedBlockClientId,
89-
getMultiSelectedBlockClientIds,
90-
hasMultiSelection,
91-
} = select( blockEditorStore );
92-
return {
93-
blockClientIds: getBlockOrder( rootClientId ),
94-
selectedBlockClientId: getSelectedBlockClientId(),
95-
multiSelectedBlockClientIds: getMultiSelectedBlockClientIds(),
96-
hasMultiSelection: hasMultiSelection(),
97-
};
98-
}
88+
const [ intersectingBlocks, setIntersectingBlocks ] = useState( new Set() );
89+
const intersectionObserver = useMemo( () => {
90+
const { IntersectionObserver: Observer } = window;
9991

100-
const {
101-
blockClientIds,
102-
selectedBlockClientId,
103-
multiSelectedBlockClientIds,
104-
hasMultiSelection,
105-
} = useSelect( selector, [ rootClientId ] );
92+
if ( ! Observer ) {
93+
return;
94+
}
95+
96+
return new Observer( ( entries ) => {
97+
setIntersectingBlocks( ( oldIntersectingBlocks ) => {
98+
const newIntersectingBlocks = new Set( oldIntersectingBlocks );
99+
for ( const entry of entries ) {
100+
const clientId = entry.target.getAttribute( 'data-block' );
101+
const action = entry.isIntersecting ? 'add' : 'delete';
102+
newIntersectingBlocks[ action ]( clientId );
103+
}
104+
return newIntersectingBlocks;
105+
} );
106+
} );
107+
}, [ setIntersectingBlocks ] );
108+
const { order, selectedBlocks } = useSelect(
109+
( select ) => {
110+
const { getBlockOrder, getSelectedBlockClientIds } = select(
111+
blockEditorStore
112+
);
113+
return {
114+
order: getBlockOrder( rootClientId ),
115+
selectedBlocks: getSelectedBlockClientIds(),
116+
};
117+
},
118+
[ rootClientId ]
119+
);
106120

107121
return (
108122
<LayoutProvider value={ layout }>
109-
{ blockClientIds.map( ( clientId, index ) => {
110-
const isBlockInSelection = hasMultiSelection
111-
? multiSelectedBlockClientIds.includes( clientId )
112-
: selectedBlockClientId === clientId;
113-
114-
return (
123+
<IntersectionObserver.Provider value={ intersectionObserver }>
124+
{ order.map( ( clientId, index ) => (
115125
<AsyncModeProvider
116126
key={ clientId }
117-
value={ ! isBlockInSelection }
127+
value={
128+
// Only provide data asynchronously if the block is
129+
// not visible and not selected.
130+
! intersectingBlocks.has( clientId ) &&
131+
! selectedBlocks.includes( clientId )
132+
}
118133
>
119134
<BlockListBlock
120135
rootClientId={ rootClientId }
@@ -125,9 +140,9 @@ function Items( {
125140
index={ index }
126141
/>
127142
</AsyncModeProvider>
128-
);
129-
} ) }
130-
{ blockClientIds.length < 1 && placeholder }
143+
) ) }
144+
</IntersectionObserver.Provider>
145+
{ order.length < 1 && placeholder }
131146
<BlockListAppender
132147
tagName={ __experimentalAppenderTagName }
133148
rootClientId={ rootClientId }

packages/block-editor/src/components/block-list/use-block-props/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { useNavModeExit } from './use-nav-mode-exit';
3232
import { useScrollIntoView } from './use-scroll-into-view';
3333
import { useBlockRefProvider } from './use-block-refs';
3434
import { useMultiSelection } from './use-multi-selection';
35+
import { useIntersectionObserver } from './use-intersection-observer';
3536
import { store as blockEditorStore } from '../../../store';
3637

3738
/**
@@ -113,6 +114,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) {
113114
useEventHandlers( clientId ),
114115
useNavModeExit( clientId ),
115116
useIsHovered(),
117+
useIntersectionObserver(),
116118
useMovingAnimation( {
117119
isSelected: isPartOfSelection,
118120
adjustScrolling,
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* WordPress dependencies
3+
*/
4+
import { useRefEffect } from '@wordpress/compose';
5+
import { useContext } from '@wordpress/element';
6+
7+
/**
8+
* Internal dependencies
9+
*/
10+
import { IntersectionObserver } from '../';
11+
12+
export function useIntersectionObserver() {
13+
const observer = useContext( IntersectionObserver );
14+
return useRefEffect(
15+
( node ) => {
16+
if ( observer ) {
17+
observer.observe( node );
18+
return () => {
19+
observer.unobserve( node );
20+
};
21+
}
22+
},
23+
[ observer ]
24+
);
25+
}

packages/block-editor/src/utils/dom.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
// Consider the block appender to be a child block of its own, which also has
2+
// this class.
3+
const BLOCK_SELECTOR = '.wp-block';
4+
15
/**
26
* Returns true if two elements are contained within the same block.
37
*
@@ -7,10 +11,7 @@
711
* @return {boolean} Whether elements are in the same block.
812
*/
913
export function isInSameBlock( a, b ) {
10-
return (
11-
a.closest( '.block-editor-block-list__block' ) ===
12-
b.closest( '.block-editor-block-list__block' )
13-
);
14+
return a.closest( BLOCK_SELECTOR ) === b.closest( BLOCK_SELECTOR );
1415
}
1516

1617
/**
@@ -24,7 +25,7 @@ export function isInSameBlock( a, b ) {
2425
* children.
2526
*/
2627
export function isInsideRootBlock( blockElement, element ) {
27-
const parentBlock = element.closest( '.block-editor-block-list__block' );
28+
const parentBlock = element.closest( BLOCK_SELECTOR );
2829
return parentBlock === blockElement;
2930
}
3031

@@ -46,7 +47,7 @@ export function getBlockClientId( node ) {
4647
}
4748

4849
const elementNode = /** @type {Element} */ ( node );
49-
const blockNode = elementNode.closest( '.block-editor-block-list__block' );
50+
const blockNode = elementNode.closest( BLOCK_SELECTOR );
5051

5152
if ( ! blockNode ) {
5253
return;

0 commit comments

Comments
 (0)