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 and mikachan committed Oct 4, 2023
1 parent 283f4e7 commit fba6504
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();
}
} );
}
}

0 comments on commit fba6504

Please sign in to comment.