diff --git a/packages/block-editor/src/components/list-view/block.js b/packages/block-editor/src/components/list-view/block.js index 43fe4df4cb75aa..375f39a7cc3c81 100644 --- a/packages/block-editor/src/components/list-view/block.js +++ b/packages/block-editor/src/components/list-view/block.js @@ -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'; /** @@ -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'; @@ -120,7 +113,6 @@ function ListViewBlock( { ); const { - isTreeGridMounted, expand, collapse, BlockSettingsMenu, @@ -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 ) => { @@ -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 ] ); diff --git a/packages/block-editor/src/components/list-view/branch.js b/packages/block-editor/src/components/list-view/branch.js index d3b555c055afd1..e2e27f5f2cb5af 100644 --- a/packages/block-editor/src/components/list-view/branch.js +++ b/packages/block-editor/src/components/list-view/branch.js @@ -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 ( { showBlock && ( diff --git a/packages/block-editor/src/components/list-view/index.js b/packages/block-editor/src/components/list-view/index.js index 917ebd883aa8d1..085864c4c88f45 100644 --- a/packages/block-editor/src/components/list-view/index.js +++ b/packages/block-editor/src/components/list-view/index.js @@ -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 ) ) { @@ -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( { @@ -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( @@ -204,7 +209,6 @@ function ListViewComponent( const contextValue = useMemo( () => ( { - isTreeGridMounted: isMounted.current, draggedClientIds, expandedState, expand, diff --git a/packages/block-editor/src/components/list-view/utils.js b/packages/block-editor/src/components/list-view/utils.js index f53f5a4cd4884a..632173e120691f 100644 --- a/packages/block-editor/src/components/list-view/utils.js +++ b/packages/block-editor/src/components/list-view/utils.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; +import { focus } from '@wordpress/dom'; export const getBlockPositionDescription = ( position, siblingCount, level ) => sprintf( @@ -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} 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(); + } + } ); + } +}