Skip to content

Commit

Permalink
List View: Fix performance issue when selecting all blocks (#54900)
Browse files Browse the repository at this point in the history
* List View: Fix performance issue when selecting all blocks within the editor canvas in long posts

* Add a comment, rename const

* Move block focus to be performed only once at the root of the list view, instead of within each block
  • Loading branch information
andrewserong authored Oct 4, 2023
1 parent 39b57b0 commit 059928a
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 48 deletions.
46 changes: 3 additions & 43 deletions packages/block-editor/src/components/list-view/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,9 @@ import {
} from '@wordpress/components';
import { useInstanceId } from '@wordpress/compose';
import { moreVertical } from '@wordpress/icons';
import {
useState,
useRef,
useEffect,
useCallback,
memo,
} from '@wordpress/element';
import { useState, useRef, useCallback, memo } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
import { sprintf, __ } from '@wordpress/i18n';
import { focus } from '@wordpress/dom';
import { ESCAPE } from '@wordpress/keycodes';

/**
Expand All @@ -36,7 +29,7 @@ import {
} from '../block-mover/button';
import ListViewBlockContents from './block-contents';
import { useListViewContext } from './context';
import { getBlockPositionDescription } from './utils';
import { getBlockPositionDescription, focusListItem } from './utils';
import { store as blockEditorStore } from '../../store';
import useBlockDisplayInformation from '../use-block-display-information';
import { useBlockLock } from '../block-lock';
Expand Down Expand Up @@ -120,7 +113,6 @@ function ListViewBlock( {
);

const {
isTreeGridMounted,
expand,
collapse,
BlockSettingsMenu,
Expand All @@ -142,15 +134,6 @@ function ListViewBlock( {
{ 'is-visible': isHovered || isFirstSelectedBlock }
);

// If ListView has experimental features related to the Persistent List View,
// only focus the selected list item on mount; otherwise the list would always
// try to steal the focus from the editor canvas.
useEffect( () => {
if ( ! isTreeGridMounted && isSelected ) {
cellRef.current.focus();
}
}, [] );

// If multiple blocks are selected, deselect all blocks when the user
// presses the escape key.
const onKeyDown = ( event ) => {
Expand Down Expand Up @@ -188,30 +171,7 @@ function ListViewBlock( {
selectBlock( undefined, focusClientId, null, null );
}

const getFocusElement = () => {
const row = treeGridElementRef.current?.querySelector(
`[role=row][data-block="${ focusClientId }"]`
);
if ( ! row ) return null;
// Focus the first focusable in the row, which is the ListViewBlockSelectButton.
return focus.focusable.find( row )[ 0 ];
};

let focusElement = getFocusElement();
if ( focusElement ) {
focusElement.focus();
} else {
// The element hasn't been painted yet. Defer focusing on the next frame.
// This could happen when all blocks have been deleted and the default block
// hasn't been added to the editor yet.
window.requestAnimationFrame( () => {
focusElement = getFocusElement();
// Ignore if the element still doesn't exist.
if ( focusElement ) {
focusElement.focus();
}
} );
}
focusListItem( focusClientId, treeGridElementRef );
},
[ selectBlock, treeGridElementRef ]
);
Expand Down
12 changes: 11 additions & 1 deletion packages/block-editor/src/components/list-view/branch.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,18 @@ function ListViewBranch( props ) {
);
const isSelectedBranch =
isBranchSelected || ( isSelected && hasNestedBlocks );

// To avoid performance issues, we only render blocks that are in view,
// or blocks that are selected or dragged. If a block is selected,
// it is only counted if it is the first of the block selection.
// This prevents the entire tree from being rendered when a branch is
// selected, or a user selects all blocks, while still enabling scroll
// into view behavior when selecting a block or opening the list view.
const showBlock =
isDragged || blockInView || isSelected || isBranchDragged;
isDragged ||
blockInView ||
isBranchDragged ||
( isSelected && clientId === selectedClientIds[ 0 ] );
return (
<AsyncModeProvider key={ clientId } value={ ! isSelected }>
{ showBlock && (
Expand Down
12 changes: 8 additions & 4 deletions packages/block-editor/src/components/list-view/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import useListViewDropZone from './use-list-view-drop-zone';
import useListViewExpandSelectedItem from './use-list-view-expand-selected-item';
import { store as blockEditorStore } from '../../store';
import { BlockSettingsDropdown } from '../block-settings-menu/block-settings-dropdown';
import { focusListItem } from './utils';

const expanded = ( state, action ) => {
if ( Array.isArray( action.clientIds ) ) {
Expand Down Expand Up @@ -132,8 +133,6 @@ function ListViewComponent(
const elementRef = useRef();
const treeGridRef = useMergeRefs( [ elementRef, dropZoneRef, ref ] );

const isMounted = useRef( false );

const [ insertedBlock, setInsertedBlock ] = useState( null );

const { setSelectedTreeId } = useListViewExpandSelectedItem( {
Expand All @@ -156,7 +155,13 @@ function ListViewComponent(
[ setSelectedTreeId, updateBlockSelection, onSelect, getBlock ]
);
useEffect( () => {
isMounted.current = true;
// If a blocks are already selected when the list view is initially
// mounted, shift focus to the first selected block.
if ( selectedClientIds?.length ) {
focusListItem( selectedClientIds[ 0 ], elementRef );
}
// Disable reason: Only focus on the selected item when the list view is mounted.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [] );

const expand = useCallback(
Expand Down Expand Up @@ -204,7 +209,6 @@ function ListViewComponent(

const contextValue = useMemo(
() => ( {
isTreeGridMounted: isMounted.current,
draggedClientIds,
expandedState,
expand,
Expand Down
37 changes: 37 additions & 0 deletions packages/block-editor/src/components/list-view/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { focus } from '@wordpress/dom';

export const getBlockPositionDescription = ( position, siblingCount, level ) =>
sprintf(
Expand Down Expand Up @@ -56,3 +57,39 @@ export function getCommonDepthClientIds(
end,
};
}

/**
* Shift focus to the list view item associated with a particular clientId.
*
* @typedef {import('@wordpress/element').RefObject} RefObject
*
* @param {string} focusClientId The client ID of the block to focus.
* @param {RefObject<HTMLElement>} treeGridElementRef The container element to search within.
*/
export function focusListItem( focusClientId, treeGridElementRef ) {
const getFocusElement = () => {
const row = treeGridElementRef.current?.querySelector(
`[role=row][data-block="${ focusClientId }"]`
);
if ( ! row ) return null;
// Focus the first focusable in the row, which is the ListViewBlockSelectButton.
return focus.focusable.find( row )[ 0 ];
};

let focusElement = getFocusElement();
if ( focusElement ) {
focusElement.focus();
} else {
// The element hasn't been painted yet. Defer focusing on the next frame.
// This could happen when all blocks have been deleted and the default block
// hasn't been added to the editor yet.
window.requestAnimationFrame( () => {
focusElement = getFocusElement();

// Ignore if the element still doesn't exist.
if ( focusElement ) {
focusElement.focus();
}
} );
}
}

1 comment on commit 059928a

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky tests detected in 059928a.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/6400795176
📝 Reported issues:

Please sign in to comment.