Skip to content

Commit

Permalink
List View: avoid re-rendering all items on block focus (#35706)
Browse files Browse the repository at this point in the history
* List View: add e2e utils for list view open and close
* List View: move selection querying to List View Block to avoid re-rendering all items on block focus
* List View: simplify useListViewClientIds and remove showOnlyCurrentHierarchy prop
* List View: move nested AsyncModeProvider call to ListViewBlock
* List View: make available flags present in JSDoc
* List View: remove unneeded is-last-of-selected-branch logic
* Edit Widgets: use list view sidebar instead of dropdown
* Edit Widgets: do not show actions in list view for widget areas
  • Loading branch information
gwwar authored Oct 22, 2021
1 parent 070deca commit 30689b8
Show file tree
Hide file tree
Showing 24 changed files with 600 additions and 367 deletions.
8 changes: 8 additions & 0 deletions packages/block-editor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## Unreleased

### Performance

- Avoid re-rendering all List View items on block focus [#35706](https://github.com/WordPress/gutenberg/pull/35706). These changes speed up block focus time in large posts by 80% when List View is open.

### Breaking change

- List View no longer supports the `showOnlyCurrentHierarchy` flag [#35706](https://github.com/WordPress/gutenberg/pull/35706). To display a subset of blocks, use the `blocks` parameter instead.

## 7.0.0 (2021-07-29)

### Breaking Change
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ function BlockNavigationDropdown(

<ListView
showNestedBlocks
showOnlyCurrentHierarchy
__experimentalFeatures={ __experimentalFeatures }
/>
</div>
Expand Down
270 changes: 168 additions & 102 deletions packages/block-editor/src/components/list-view/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
__experimentalTreeGridItem as TreeGridItem,
} from '@wordpress/components';
import { moreVertical } from '@wordpress/icons';
import { useState, useRef, useEffect } from '@wordpress/element';
import { useDispatch } from '@wordpress/data';
import { useState, useRef, useEffect, useCallback } from '@wordpress/element';
import { useDispatch, useSelect, AsyncModeProvider } from '@wordpress/data';

/**
* Internal dependencies
Expand All @@ -26,15 +26,12 @@ import ListViewBlockContents from './block-contents';
import BlockSettingsDropdown from '../block-settings-menu/block-settings-dropdown';
import { useListViewContext } from './context';
import { store as blockEditorStore } from '../../store';
import { isClientIdSelected } from './utils';

export default function ListViewBlock( {
block,
isSelected,
isDragged,
isBranchSelected,
isLastOfSelectedBranch,
onClick,
onToggleExpanded,
selectBlock,
position,
level,
rowCount,
Expand All @@ -49,17 +46,50 @@ export default function ListViewBlock( {

const { toggleBlockHighlight } = useDispatch( blockEditorStore );

const {
__experimentalFeatures: withExperimentalFeatures,
__experimentalPersistentListViewFeatures: withExperimentalPersistentListViewFeatures,
__experimentalHideContainerBlockActions: hideContainerBlockActions,
isTreeGridMounted,
expand,
collapse,
} = useListViewContext();

const { isBranchSelected, isSelected } = useSelect(
( select ) => {
const {
getSelectedBlockClientId,
getSelectedBlockClientIds,
getBlockParents,
} = select( blockEditorStore );

const selectedClientIds = withExperimentalPersistentListViewFeatures
? getSelectedBlockClientIds()
: [ getSelectedBlockClientId() ];
const blockParents = getBlockParents( clientId );
const _isSelected = isClientIdSelected(
clientId,
selectedClientIds
);
return {
isSelected: _isSelected,
isBranchSelected:
_isSelected ||
blockParents.some( ( id ) => {
return isClientIdSelected( id, selectedClientIds );
} ),
};
},
[ withExperimentalPersistentListViewFeatures, clientId ]
);

const hasSiblings = siblingBlockCount > 0;
const hasRenderedMovers = showBlockMovers && hasSiblings;
const moverCellClassName = classnames(
'block-editor-list-view-block__mover-cell',
{ 'is-visible': isHovered || isSelected }
);
const {
__experimentalFeatures: withExperimentalFeatures,
__experimentalPersistentListViewFeatures: withExperimentalPersistentListViewFeatures,
isTreeGridMounted,
} = useListViewContext();

const listViewBlockSettingsClassName = classnames(
'block-editor-list-view-block__menu-cell',
{ 'is-visible': isHovered || isSelected }
Expand All @@ -82,112 +112,148 @@ export default function ListViewBlock( {
? toggleBlockHighlight
: () => {};

const onMouseEnter = () => {
const onMouseEnter = useCallback( () => {
setIsHovered( true );
highlightBlock( clientId, true );
};
const onMouseLeave = () => {
}, [ clientId, setIsHovered, highlightBlock ] );
const onMouseLeave = useCallback( () => {
setIsHovered( false );
highlightBlock( clientId, false );
};
}, [ clientId, setIsHovered, highlightBlock ] );

const selectEditorBlock = useCallback(
( event ) => {
event.stopPropagation();
selectBlock( clientId );
},
[ clientId, selectBlock ]
);

const toggleExpanded = useCallback(
( event ) => {
event.stopPropagation();
if ( isExpanded === true ) {
collapse( clientId );
} else if ( isExpanded === false ) {
expand( clientId );
}
},
[ clientId, expand, collapse, isExpanded ]
);

const showBlockActions =
withExperimentalFeatures &&
//hide actions for blocks like core/widget-areas
( ! hideContainerBlockActions ||
( hideContainerBlockActions && level > 1 ) );

const hideBlockActions = withExperimentalFeatures && ! showBlockActions;

let colSpan;
if ( hasRenderedMovers ) {
colSpan = 2;
} else if ( hideBlockActions ) {
colSpan = 3;
}

const classes = classnames( {
'is-selected': isSelected,
'is-branch-selected':
withExperimentalPersistentListViewFeatures && isBranchSelected,
'is-last-of-selected-branch':
withExperimentalPersistentListViewFeatures &&
isLastOfSelectedBranch,
'is-dragging': isDragged,
'has-single-cell': hideBlockActions,
} );

return (
<ListViewLeaf
className={ classes }
onMouseEnter={ onMouseEnter }
onMouseLeave={ onMouseLeave }
onFocus={ onMouseEnter }
onBlur={ onMouseLeave }
level={ level }
position={ position }
rowCount={ rowCount }
path={ path }
id={ `list-view-block-${ clientId }` }
data-block={ clientId }
isExpanded={ isExpanded }
>
<TreeGridCell
className="block-editor-list-view-block__contents-cell"
colSpan={ hasRenderedMovers ? undefined : 2 }
ref={ cellRef }
<AsyncModeProvider value={ ! isSelected }>
<ListViewLeaf
className={ classes }
onMouseEnter={ onMouseEnter }
onMouseLeave={ onMouseLeave }
onFocus={ onMouseEnter }
onBlur={ onMouseLeave }
level={ level }
position={ position }
rowCount={ rowCount }
path={ path }
id={ `list-view-block-${ clientId }` }
data-block={ clientId }
isExpanded={ isExpanded }
>
{ ( { ref, tabIndex, onFocus } ) => (
<div className="block-editor-list-view-block__contents-container">
<ListViewBlockContents
block={ block }
onClick={ onClick }
onToggleExpanded={ onToggleExpanded }
isSelected={ isSelected }
position={ position }
siblingBlockCount={ siblingBlockCount }
level={ level }
ref={ ref }
tabIndex={ tabIndex }
onFocus={ onFocus }
/>
</div>
) }
</TreeGridCell>
{ hasRenderedMovers && (
<>
<TreeGridCell
className={ moverCellClassName }
withoutGridItem
>
<TreeGridItem>
{ ( { ref, tabIndex, onFocus } ) => (
<BlockMoverUpButton
orientation="vertical"
clientIds={ [ clientId ] }
ref={ ref }
tabIndex={ tabIndex }
onFocus={ onFocus }
/>
) }
</TreeGridItem>
<TreeGridItem>
{ ( { ref, tabIndex, onFocus } ) => (
<BlockMoverDownButton
orientation="vertical"
clientIds={ [ clientId ] }
ref={ ref }
tabIndex={ tabIndex }
onFocus={ onFocus }
/>
) }
</TreeGridItem>
</TreeGridCell>
</>
) }

{ withExperimentalFeatures && (
<TreeGridCell className={ listViewBlockSettingsClassName }>
<TreeGridCell
className="block-editor-list-view-block__contents-cell"
colSpan={ colSpan }
ref={ cellRef }
>
{ ( { ref, tabIndex, onFocus } ) => (
<BlockSettingsDropdown
clientIds={ [ clientId ] }
icon={ moreVertical }
toggleProps={ {
ref,
className: 'block-editor-list-view-block__menu',
tabIndex,
onFocus,
} }
disableOpenOnArrowDown
__experimentalSelectBlock={ onClick }
/>
<div className="block-editor-list-view-block__contents-container">
<ListViewBlockContents
block={ block }
onClick={ selectEditorBlock }
onToggleExpanded={ toggleExpanded }
isSelected={ isSelected }
position={ position }
siblingBlockCount={ siblingBlockCount }
level={ level }
ref={ ref }
tabIndex={ tabIndex }
onFocus={ onFocus }
/>
</div>
) }
</TreeGridCell>
) }
</ListViewLeaf>
{ hasRenderedMovers && (
<>
<TreeGridCell
className={ moverCellClassName }
withoutGridItem
>
<TreeGridItem>
{ ( { ref, tabIndex, onFocus } ) => (
<BlockMoverUpButton
orientation="vertical"
clientIds={ [ clientId ] }
ref={ ref }
tabIndex={ tabIndex }
onFocus={ onFocus }
/>
) }
</TreeGridItem>
<TreeGridItem>
{ ( { ref, tabIndex, onFocus } ) => (
<BlockMoverDownButton
orientation="vertical"
clientIds={ [ clientId ] }
ref={ ref }
tabIndex={ tabIndex }
onFocus={ onFocus }
/>
) }
</TreeGridItem>
</TreeGridCell>
</>
) }

{ showBlockActions && (
<TreeGridCell className={ listViewBlockSettingsClassName }>
{ ( { ref, tabIndex, onFocus } ) => (
<BlockSettingsDropdown
clientIds={ [ clientId ] }
icon={ moreVertical }
toggleProps={ {
ref,
className:
'block-editor-list-view-block__menu',
tabIndex,
onFocus,
} }
disableOpenOnArrowDown
__experimentalSelectBlock={ selectEditorBlock }
/>
) }
</TreeGridCell>
) }
</ListViewLeaf>
</AsyncModeProvider>
);
}
Loading

0 comments on commit 30689b8

Please sign in to comment.