Skip to content

Commit

Permalink
List View: Scroll selected block into view when single block selectio…
Browse files Browse the repository at this point in the history
…n changes (#46895)

* List View: Try scrolling selected blocks into view when single block selection changes

* Use getScrollContainer and calculate real top position of scrollable area instead of using a hard-coded value

* Try rearranging things so that the ref is always attached at the row level

* Move placeholder to its own file

* Tidy up a little

* Tidy comments

* Remove unneeded optional chaining

Co-authored-by: Kai Hao <kevin830726@gmail.com>

* Simplify and improve logic based on feedback

Co-authored-by: Kai Hao <kevin830726@gmail.com>

* Remove unneeded optional chaining

Co-authored-by: Kai Hao <kevin830726@gmail.com>

* Revert placeholder component, update showBlock logic so that selected blocks are rendered as real ListViewBlock components

---------

Co-authored-by: Kai Hao <kevin830726@gmail.com>
  • Loading branch information
andrewserong and kevin940726 authored Feb 15, 2023
1 parent 2cc9840 commit b87e855
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 31 deletions.
12 changes: 12 additions & 0 deletions packages/block-editor/src/components/list-view/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { sprintf, __ } from '@wordpress/i18n';
* Internal dependencies
*/
import ListViewLeaf from './leaf';
import useListViewScrollIntoView from './use-list-view-scroll-into-view';
import {
BlockMoverUpButton,
BlockMoverDownButton,
Expand Down Expand Up @@ -57,6 +58,7 @@ function ListViewBlock( {
isSyncedBranch,
} ) {
const cellRef = useRef( null );
const rowRef = useRef( null );
const [ isHovered, setIsHovered ] = useState( false );
const { clientId } = block;

Expand Down Expand Up @@ -220,6 +222,15 @@ function ListViewBlock( {
? selectedClientIds
: [ clientId ];

// Pass in a ref to the row, so that it can be scrolled
// into view when selected. For long lists, the placeholder for the
// selected block is also observed, within ListViewLeafPlaceholder.
useListViewScrollIntoView( {
isSelected,
rowItemRef: rowRef,
selectedClientIds,
} );

return (
<ListViewLeaf
className={ classes }
Expand All @@ -235,6 +246,7 @@ function ListViewBlock( {
data-block={ clientId }
isExpanded={ canExpand ? isExpanded : undefined }
aria-selected={ !! isSelected || forceSelectionContentLock }
ref={ rowRef }
>
<TreeGridCell
className="block-editor-list-view-block__contents-cell"
Expand Down
3 changes: 1 addition & 2 deletions packages/block-editor/src/components/list-view/branch.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,6 @@ function ListViewBranch( props ) {

const isDragged = !! draggedClientIds?.includes( clientId );

const showBlock = isDragged || blockInView;

// Make updates to the selected or dragged blocks synchronous,
// but asynchronous for any other block.
const isSelected = isClientIdSelected(
Expand All @@ -165,6 +163,7 @@ function ListViewBranch( props ) {
);
const isSelectedBranch =
isBranchSelected || ( isSelected && hasNestedBlocks );
const showBlock = isDragged || blockInView || isSelected;
return (
<AsyncModeProvider key={ clientId } value={ ! isSelected }>
{ showBlock && (
Expand Down
72 changes: 43 additions & 29 deletions packages/block-editor/src/components/list-view/leaf.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import classnames from 'classnames';
* WordPress dependencies
*/
import { __experimentalTreeGridRow as TreeGridRow } from '@wordpress/components';
import { useMergeRefs } from '@wordpress/compose';
import { forwardRef } from '@wordpress/element';

/**
* Internal dependencies
Expand All @@ -16,33 +18,45 @@ import useMovingAnimation from '../use-moving-animation';

const AnimatedTreeGridRow = animated( TreeGridRow );

export default function ListViewLeaf( {
isSelected,
position,
level,
rowCount,
children,
className,
path,
...props
} ) {
const ref = useMovingAnimation( {
isSelected,
adjustScrolling: false,
enableAnimation: true,
triggerAnimationOnChange: path,
} );
const ListViewLeaf = forwardRef(
(
{
isSelected,
position,
level,
rowCount,
children,
className,
path,
...props
},
ref
) => {
const animationRef = useMovingAnimation( {
isSelected,
adjustScrolling: false,
enableAnimation: true,
triggerAnimationOnChange: path,
} );

return (
<AnimatedTreeGridRow
ref={ ref }
className={ classnames( 'block-editor-list-view-leaf', className ) }
level={ level }
positionInSet={ position }
setSize={ rowCount }
{ ...props }
>
{ children }
</AnimatedTreeGridRow>
);
}
const mergedRef = useMergeRefs( [ ref, animationRef ] );

return (
<AnimatedTreeGridRow
ref={ mergedRef }
className={ classnames(
'block-editor-list-view-leaf',
className
) }
level={ level }
positionInSet={ position }
setSize={ rowCount }
{ ...props }
>
{ children }
</AnimatedTreeGridRow>
);
}
);

export default ListViewLeaf;
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* WordPress dependencies
*/
import { getScrollContainer } from '@wordpress/dom';
import { useLayoutEffect } from '@wordpress/element';

export default function useListViewScrollIntoView( {
isSelected,
selectedClientIds,
rowItemRef,
} ) {
const isSingleSelection = selectedClientIds.length === 1;

useLayoutEffect( () => {
// Skip scrolling into view if this particular block isn't selected,
// or if more than one block is selected overall. This is to avoid
// scrolling the view in a multi selection where the user has intentionally
// selected multiple blocks within the list view, but the initially
// selected block may be out of view.
if ( ! isSelected || ! isSingleSelection || ! rowItemRef.current ) {
return;
}

const scrollContainer = getScrollContainer( rowItemRef.current );
const { ownerDocument } = rowItemRef.current;

const windowScroll =
scrollContainer === ownerDocument.body ||
scrollContainer === ownerDocument.documentElement;

// If the there is no scroll container, of if the scroll container is the window,
// do not scroll into view, as the block is already in view.
if ( windowScroll || ! scrollContainer ) {
return;
}

const rowRect = rowItemRef.current.getBoundingClientRect();
const scrollContainerRect = scrollContainer.getBoundingClientRect();

// If the selected block is not currently visible, scroll to it.
if (
rowRect.top < scrollContainerRect.top ||
rowRect.bottom > scrollContainerRect.bottom
) {
rowItemRef.current.scrollIntoView();
}
}, [ isSelected, isSingleSelection, rowItemRef ] );
}

1 comment on commit b87e855

@github-actions
Copy link

@github-actions github-actions bot commented on b87e855 Feb 15, 2023

Choose a reason for hiding this comment

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

Flaky tests detected in b87e855.
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/4188870157
📝 Reported issues:

Please sign in to comment.