Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

List View: Scroll selected block into view when single block selection changes #46895

Merged

Conversation

andrewserong
Copy link
Contributor

@andrewserong andrewserong commented Jan 5, 2023

What?

Fixes #46828, fixes #30432

While the list view is open, scroll the selected item into view if it is not already in view.

A caveat on the approach in this PR: I'm not sure how performant it is, so very happy for any feedback on this one!

Why?

As described in #46828, with the list view open and a long post or page, it can be confusing if you change the selection within the editor canvas, but the list view does not scroll that block into view. It can be quite easy to lose your place on longer posts.

How?

  • Add a hook to the list view that scrolls the selected block's element into view if it isn't already visible.
  • Use this at the ListViewBlock level, and also at the ListViewBranch level to factor in very long posts/pages where the placeholder for the selected block falls outside of the current window where "real" list view blocks are rendered. This way, if a user selects a block that is outside of the currently windowed area, we can still scroll it into view.

Testing Instructions

In a really long post or page, with the list view opened, scroll the editor canvas to far down the page or post and select a block outside of the currently visible area within the list view. The list view should scroll the selected block into view.

To test the windowed area versus the non-windowed area, add a very large number of blocks (e.g. 60+) and try selecting from the very top block to the very bottom block in the list, via the editor canvas.

Testing Instructions for Keyboard

It should be possible to test this via keyboard – with the list view opened and focus on the editor canvas, press the down arrow to navigate down blocks, and as you scroll past the bottom of the screen, or past the top of the screen, the list view should always display the selected block.

Screenshots or screencast

In the below screengrab, with the list view open, we go from the first block being selected, to scrolling the editor canvas and selecting a block that is outside of the visible area of the list view. When the block is selected, the list view is scrolled so that the selected block is at the top of the visible area of the list view.

list-view-scroll-into-view.mp4

@andrewserong andrewserong added [Type] Enhancement A suggestion for improvement. [Feature] List View Menu item in the top toolbar to select blocks from a list of links. labels Jan 5, 2023
@andrewserong andrewserong self-assigned this Jan 5, 2023
@andrewserong andrewserong requested a review from ellatrix as a code owner January 5, 2023 06:44
@andrewserong andrewserong requested a review from talldan January 5, 2023 06:46
@github-actions
Copy link

github-actions bot commented Jan 5, 2023

Size Change: +172 B (0%)

Total Size: 1.33 MB

Filename Size Change
build/block-editor/index.min.js 194 kB +172 B (0%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 993 B
build/annotations/index.min.js 2.78 kB
build/api-fetch/index.min.js 2.27 kB
build/autop/index.min.js 2.15 kB
build/blob/index.min.js 483 B
build/block-directory/index.min.js 7.2 kB
build/block-directory/style-rtl.css 1.04 kB
build/block-directory/style.css 1.04 kB
build/block-editor/content-rtl.css 4.11 kB
build/block-editor/content.css 4.1 kB
build/block-editor/default-editor-styles-rtl.css 403 B
build/block-editor/default-editor-styles.css 403 B
build/block-editor/style-rtl.css 14.4 kB
build/block-editor/style.css 14.4 kB
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 60 B
build/block-library/blocks/archives/style-rtl.css 90 B
build/block-library/blocks/archives/style.css 90 B
build/block-library/blocks/audio/editor-rtl.css 150 B
build/block-library/blocks/audio/editor.css 150 B
build/block-library/blocks/audio/style-rtl.css 122 B
build/block-library/blocks/audio/style.css 122 B
build/block-library/blocks/audio/theme-rtl.css 138 B
build/block-library/blocks/audio/theme.css 138 B
build/block-library/blocks/avatar/editor-rtl.css 116 B
build/block-library/blocks/avatar/editor.css 116 B
build/block-library/blocks/avatar/style-rtl.css 91 B
build/block-library/blocks/avatar/style.css 91 B
build/block-library/blocks/block/editor-rtl.css 305 B
build/block-library/blocks/block/editor.css 305 B
build/block-library/blocks/button/editor-rtl.css 587 B
build/block-library/blocks/button/editor.css 587 B
build/block-library/blocks/button/style-rtl.css 628 B
build/block-library/blocks/button/style.css 627 B
build/block-library/blocks/buttons/editor-rtl.css 337 B
build/block-library/blocks/buttons/editor.css 337 B
build/block-library/blocks/buttons/style-rtl.css 332 B
build/block-library/blocks/buttons/style.css 332 B
build/block-library/blocks/calendar/style-rtl.css 239 B
build/block-library/blocks/calendar/style.css 239 B
build/block-library/blocks/categories/editor-rtl.css 84 B
build/block-library/blocks/categories/editor.css 83 B
build/block-library/blocks/categories/style-rtl.css 100 B
build/block-library/blocks/categories/style.css 100 B
build/block-library/blocks/code/editor-rtl.css 53 B
build/block-library/blocks/code/editor.css 53 B
build/block-library/blocks/code/style-rtl.css 121 B
build/block-library/blocks/code/style.css 121 B
build/block-library/blocks/code/theme-rtl.css 124 B
build/block-library/blocks/code/theme.css 124 B
build/block-library/blocks/columns/editor-rtl.css 108 B
build/block-library/blocks/columns/editor.css 108 B
build/block-library/blocks/columns/style-rtl.css 406 B
build/block-library/blocks/columns/style.css 406 B
build/block-library/blocks/comment-author-avatar/editor-rtl.css 125 B
build/block-library/blocks/comment-author-avatar/editor.css 125 B
build/block-library/blocks/comment-content/style-rtl.css 92 B
build/block-library/blocks/comment-content/style.css 92 B
build/block-library/blocks/comment-template/style-rtl.css 199 B
build/block-library/blocks/comment-template/style.css 198 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 123 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 222 B
build/block-library/blocks/comments-pagination/editor.css 209 B
build/block-library/blocks/comments-pagination/style-rtl.css 235 B
build/block-library/blocks/comments-pagination/style.css 231 B
build/block-library/blocks/comments-title/editor-rtl.css 75 B
build/block-library/blocks/comments-title/editor.css 75 B
build/block-library/blocks/comments/editor-rtl.css 840 B
build/block-library/blocks/comments/editor.css 839 B
build/block-library/blocks/comments/style-rtl.css 637 B
build/block-library/blocks/comments/style.css 636 B
build/block-library/blocks/cover/editor-rtl.css 612 B
build/block-library/blocks/cover/editor.css 613 B
build/block-library/blocks/cover/style-rtl.css 1.57 kB
build/block-library/blocks/cover/style.css 1.56 kB
build/block-library/blocks/embed/editor-rtl.css 293 B
build/block-library/blocks/embed/editor.css 293 B
build/block-library/blocks/embed/style-rtl.css 410 B
build/block-library/blocks/embed/style.css 410 B
build/block-library/blocks/embed/theme-rtl.css 138 B
build/block-library/blocks/embed/theme.css 138 B
build/block-library/blocks/file/editor-rtl.css 300 B
build/block-library/blocks/file/editor.css 300 B
build/block-library/blocks/file/style-rtl.css 265 B
build/block-library/blocks/file/style.css 265 B
build/block-library/blocks/file/view.min.js 353 B
build/block-library/blocks/freeform/editor-rtl.css 2.44 kB
build/block-library/blocks/freeform/editor.css 2.44 kB
build/block-library/blocks/gallery/editor-rtl.css 984 B
build/block-library/blocks/gallery/editor.css 988 B
build/block-library/blocks/gallery/style-rtl.css 1.55 kB
build/block-library/blocks/gallery/style.css 1.55 kB
build/block-library/blocks/gallery/theme-rtl.css 122 B
build/block-library/blocks/gallery/theme.css 122 B
build/block-library/blocks/group/editor-rtl.css 654 B
build/block-library/blocks/group/editor.css 654 B
build/block-library/blocks/group/style-rtl.css 57 B
build/block-library/blocks/group/style.css 57 B
build/block-library/blocks/group/theme-rtl.css 78 B
build/block-library/blocks/group/theme.css 78 B
build/block-library/blocks/heading/style-rtl.css 76 B
build/block-library/blocks/heading/style.css 76 B
build/block-library/blocks/html/editor-rtl.css 332 B
build/block-library/blocks/html/editor.css 333 B
build/block-library/blocks/image/editor-rtl.css 830 B
build/block-library/blocks/image/editor.css 829 B
build/block-library/blocks/image/style-rtl.css 652 B
build/block-library/blocks/image/style.css 652 B
build/block-library/blocks/image/theme-rtl.css 137 B
build/block-library/blocks/image/theme.css 137 B
build/block-library/blocks/latest-comments/style-rtl.css 298 B
build/block-library/blocks/latest-comments/style.css 298 B
build/block-library/blocks/latest-posts/editor-rtl.css 213 B
build/block-library/blocks/latest-posts/editor.css 212 B
build/block-library/blocks/latest-posts/style-rtl.css 478 B
build/block-library/blocks/latest-posts/style.css 478 B
build/block-library/blocks/list/style-rtl.css 88 B
build/block-library/blocks/list/style.css 88 B
build/block-library/blocks/media-text/editor-rtl.css 266 B
build/block-library/blocks/media-text/editor.css 263 B
build/block-library/blocks/media-text/style-rtl.css 507 B
build/block-library/blocks/media-text/style.css 505 B
build/block-library/blocks/more/editor-rtl.css 431 B
build/block-library/blocks/more/editor.css 431 B
build/block-library/blocks/navigation-link/editor-rtl.css 716 B
build/block-library/blocks/navigation-link/editor.css 715 B
build/block-library/blocks/navigation-link/style-rtl.css 115 B
build/block-library/blocks/navigation-link/style.css 115 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 299 B
build/block-library/blocks/navigation-submenu/editor.css 299 B
build/block-library/blocks/navigation/editor-rtl.css 2.13 kB
build/block-library/blocks/navigation/editor.css 2.14 kB
build/block-library/blocks/navigation/style-rtl.css 2.22 kB
build/block-library/blocks/navigation/style.css 2.2 kB
build/block-library/blocks/navigation/view-modal.min.js 2.81 kB
build/block-library/blocks/navigation/view.min.js 447 B
build/block-library/blocks/nextpage/editor-rtl.css 395 B
build/block-library/blocks/nextpage/editor.css 395 B
build/block-library/blocks/page-list/editor-rtl.css 376 B
build/block-library/blocks/page-list/editor.css 376 B
build/block-library/blocks/page-list/style-rtl.css 175 B
build/block-library/blocks/page-list/style.css 175 B
build/block-library/blocks/paragraph/editor-rtl.css 174 B
build/block-library/blocks/paragraph/editor.css 174 B
build/block-library/blocks/paragraph/style-rtl.css 279 B
build/block-library/blocks/paragraph/style.css 281 B
build/block-library/blocks/post-author/style-rtl.css 175 B
build/block-library/blocks/post-author/style.css 176 B
build/block-library/blocks/post-comments-form/editor-rtl.css 96 B
build/block-library/blocks/post-comments-form/editor.css 96 B
build/block-library/blocks/post-comments-form/style-rtl.css 501 B
build/block-library/blocks/post-comments-form/style.css 501 B
build/block-library/blocks/post-date/style-rtl.css 61 B
build/block-library/blocks/post-date/style.css 61 B
build/block-library/blocks/post-excerpt/editor-rtl.css 73 B
build/block-library/blocks/post-excerpt/editor.css 73 B
build/block-library/blocks/post-excerpt/style-rtl.css 134 B
build/block-library/blocks/post-excerpt/style.css 134 B
build/block-library/blocks/post-featured-image/editor-rtl.css 586 B
build/block-library/blocks/post-featured-image/editor.css 584 B
build/block-library/blocks/post-featured-image/style-rtl.css 318 B
build/block-library/blocks/post-featured-image/style.css 318 B
build/block-library/blocks/post-navigation-link/style-rtl.css 153 B
build/block-library/blocks/post-navigation-link/style.css 153 B
build/block-library/blocks/post-template/editor-rtl.css 99 B
build/block-library/blocks/post-template/editor.css 98 B
build/block-library/blocks/post-template/style-rtl.css 282 B
build/block-library/blocks/post-template/style.css 282 B
build/block-library/blocks/post-terms/style-rtl.css 96 B
build/block-library/blocks/post-terms/style.css 96 B
build/block-library/blocks/post-title/style-rtl.css 100 B
build/block-library/blocks/post-title/style.css 100 B
build/block-library/blocks/preformatted/style-rtl.css 103 B
build/block-library/blocks/preformatted/style.css 103 B
build/block-library/blocks/pullquote/editor-rtl.css 135 B
build/block-library/blocks/pullquote/editor.css 135 B
build/block-library/blocks/pullquote/style-rtl.css 326 B
build/block-library/blocks/pullquote/style.css 325 B
build/block-library/blocks/pullquote/theme-rtl.css 167 B
build/block-library/blocks/pullquote/theme.css 167 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/query-pagination-numbers/editor.css 121 B
build/block-library/blocks/query-pagination/editor-rtl.css 221 B
build/block-library/blocks/query-pagination/editor.css 211 B
build/block-library/blocks/query-pagination/style-rtl.css 288 B
build/block-library/blocks/query-pagination/style.css 284 B
build/block-library/blocks/query-title/style-rtl.css 63 B
build/block-library/blocks/query-title/style.css 63 B
build/block-library/blocks/query/editor-rtl.css 458 B
build/block-library/blocks/query/editor.css 457 B
build/block-library/blocks/quote/style-rtl.css 213 B
build/block-library/blocks/quote/style.css 213 B
build/block-library/blocks/quote/theme-rtl.css 223 B
build/block-library/blocks/quote/theme.css 226 B
build/block-library/blocks/read-more/style-rtl.css 132 B
build/block-library/blocks/read-more/style.css 132 B
build/block-library/blocks/rss/editor-rtl.css 149 B
build/block-library/blocks/rss/editor.css 149 B
build/block-library/blocks/rss/style-rtl.css 289 B
build/block-library/blocks/rss/style.css 288 B
build/block-library/blocks/search/editor-rtl.css 165 B
build/block-library/blocks/search/editor.css 165 B
build/block-library/blocks/search/style-rtl.css 409 B
build/block-library/blocks/search/style.css 406 B
build/block-library/blocks/search/theme-rtl.css 114 B
build/block-library/blocks/search/theme.css 114 B
build/block-library/blocks/separator/editor-rtl.css 146 B
build/block-library/blocks/separator/editor.css 146 B
build/block-library/blocks/separator/style-rtl.css 234 B
build/block-library/blocks/separator/style.css 234 B
build/block-library/blocks/separator/theme-rtl.css 194 B
build/block-library/blocks/separator/theme.css 194 B
build/block-library/blocks/shortcode/editor-rtl.css 474 B
build/block-library/blocks/shortcode/editor.css 474 B
build/block-library/blocks/site-logo/editor-rtl.css 490 B
build/block-library/blocks/site-logo/editor.css 490 B
build/block-library/blocks/site-logo/style-rtl.css 203 B
build/block-library/blocks/site-logo/style.css 203 B
build/block-library/blocks/site-tagline/editor-rtl.css 86 B
build/block-library/blocks/site-tagline/editor.css 86 B
build/block-library/blocks/site-title/editor-rtl.css 116 B
build/block-library/blocks/site-title/editor.css 116 B
build/block-library/blocks/site-title/style-rtl.css 57 B
build/block-library/blocks/site-title/style.css 57 B
build/block-library/blocks/social-link/editor-rtl.css 184 B
build/block-library/blocks/social-link/editor.css 184 B
build/block-library/blocks/social-links/editor-rtl.css 674 B
build/block-library/blocks/social-links/editor.css 673 B
build/block-library/blocks/social-links/style-rtl.css 1.4 kB
build/block-library/blocks/social-links/style.css 1.39 kB
build/block-library/blocks/spacer/editor-rtl.css 332 B
build/block-library/blocks/spacer/editor.css 332 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table/editor-rtl.css 433 B
build/block-library/blocks/table/editor.css 433 B
build/block-library/blocks/table/style-rtl.css 651 B
build/block-library/blocks/table/style.css 650 B
build/block-library/blocks/table/theme-rtl.css 157 B
build/block-library/blocks/table/theme.css 157 B
build/block-library/blocks/tag-cloud/style-rtl.css 251 B
build/block-library/blocks/tag-cloud/style.css 253 B
build/block-library/blocks/template-part/editor-rtl.css 404 B
build/block-library/blocks/template-part/editor.css 404 B
build/block-library/blocks/template-part/theme-rtl.css 101 B
build/block-library/blocks/template-part/theme.css 101 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 166 B
build/block-library/blocks/text-columns/style.css 166 B
build/block-library/blocks/verse/style-rtl.css 99 B
build/block-library/blocks/verse/style.css 99 B
build/block-library/blocks/video/editor-rtl.css 552 B
build/block-library/blocks/video/editor.css 555 B
build/block-library/blocks/video/style-rtl.css 179 B
build/block-library/blocks/video/style.css 179 B
build/block-library/blocks/video/theme-rtl.css 139 B
build/block-library/blocks/video/theme.css 139 B
build/block-library/classic-rtl.css 179 B
build/block-library/classic.css 179 B
build/block-library/common-rtl.css 1.11 kB
build/block-library/common.css 1.11 kB
build/block-library/editor-elements-rtl.css 75 B
build/block-library/editor-elements.css 75 B
build/block-library/editor-rtl.css 11.6 kB
build/block-library/editor.css 11.6 kB
build/block-library/elements-rtl.css 54 B
build/block-library/elements.css 54 B
build/block-library/index.min.js 200 kB
build/block-library/reset-rtl.css 478 B
build/block-library/reset.css 478 B
build/block-library/style-rtl.css 12.6 kB
build/block-library/style.css 12.6 kB
build/block-library/theme-rtl.css 698 B
build/block-library/theme.css 703 B
build/block-serialization-default-parser/index.min.js 1.13 kB
build/block-serialization-spec-parser/index.min.js 2.83 kB
build/blocks/index.min.js 51 kB
build/components/index.min.js 206 kB
build/components/style-rtl.css 11.6 kB
build/components/style.css 11.6 kB
build/compose/index.min.js 12.3 kB
build/core-data/index.min.js 15.9 kB
build/customize-widgets/index.min.js 11.9 kB
build/customize-widgets/style-rtl.css 1.41 kB
build/customize-widgets/style.css 1.41 kB
build/data-controls/index.min.js 663 B
build/data/index.min.js 8.57 kB
build/date/index.min.js 40.4 kB
build/deprecated/index.min.js 518 B
build/dom-ready/index.min.js 336 B
build/dom/index.min.js 4.71 kB
build/edit-post/classic-rtl.css 571 B
build/edit-post/classic.css 571 B
build/edit-post/index.min.js 34.5 kB
build/edit-post/style-rtl.css 7.5 kB
build/edit-post/style.css 7.5 kB
build/edit-site/index.min.js 64.7 kB
build/edit-site/style-rtl.css 9.91 kB
build/edit-site/style.css 9.9 kB
build/edit-widgets/index.min.js 17 kB
build/edit-widgets/style-rtl.css 4.52 kB
build/edit-widgets/style.css 4.52 kB
build/editor/index.min.js 45.5 kB
build/editor/style-rtl.css 3.54 kB
build/editor/style.css 3.53 kB
build/element/index.min.js 4.93 kB
build/escape-html/index.min.js 548 B
build/format-library/index.min.js 7.27 kB
build/format-library/style-rtl.css 557 B
build/format-library/style.css 556 B
build/hooks/index.min.js 1.66 kB
build/html-entities/index.min.js 454 B
build/i18n/index.min.js 3.79 kB
build/is-shallow-equal/index.min.js 535 B
build/keyboard-shortcuts/index.min.js 1.79 kB
build/keycodes/index.min.js 1.94 kB
build/list-reusable-blocks/index.min.js 2.14 kB
build/list-reusable-blocks/style-rtl.css 865 B
build/list-reusable-blocks/style.css 865 B
build/media-utils/index.min.js 2.99 kB
build/notices/index.min.js 977 B
build/plugins/index.min.js 1.95 kB
build/preferences-persistence/index.min.js 2.23 kB
build/preferences/index.min.js 1.35 kB
build/primitives/index.min.js 960 B
build/priority-queue/index.min.js 1.52 kB
build/private-apis/index.min.js 940 B
build/react-i18n/index.min.js 702 B
build/react-refresh-entry/index.min.js 8.44 kB
build/react-refresh-runtime/index.min.js 7.31 kB
build/redux-routine/index.min.js 2.75 kB
build/reusable-blocks/index.min.js 2.26 kB
build/reusable-blocks/style-rtl.css 265 B
build/reusable-blocks/style.css 265 B
build/rich-text/index.min.js 10.8 kB
build/server-side-render/index.min.js 2.09 kB
build/shortcode/index.min.js 1.52 kB
build/style-engine/index.min.js 1.53 kB
build/token-list/index.min.js 650 B
build/url/index.min.js 3.69 kB
build/vendors/inert-polyfill.min.js 2.48 kB
build/vendors/react-dom.min.js 41.8 kB
build/vendors/react.min.js 4.02 kB
build/viewport/index.min.js 1.09 kB
build/warning/index.min.js 280 B
build/widgets/index.min.js 7.31 kB
build/widgets/style-rtl.css 1.18 kB
build/widgets/style.css 1.18 kB
build/wordcount/index.min.js 1.06 kB

compressed-size-action

const { innerHeight } = window;

// If the selected block is not visible, scroll to it.
// The hard-coded value of 110 corresponds to the position at the top of the scrollable area.
Copy link
Contributor

Choose a reason for hiding this comment

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

What does the "position at the top of the scrollable area" mean here? Is that the height of the UI at the top?

Copy link
Contributor Author

@andrewserong andrewserong Jan 6, 2023

Choose a reason for hiding this comment

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

Is that the height of the UI at the top?

Yep! Thanks for the question here — I couldn't quite work out how to describe it. The top value is from the top of the screen, but we want to calculate the value in relation to the visible area which is up to the bottom of this tabbed area:

image

We can get that value by doing a look up using getScrollContainer in the @wordpress/dom package, but I included a hard-coded value for now, since I think that might be a bit more performant than having to do a look up. Happy to swap it out for the "real" calculation if you think that'd be a better approach (or to try to update the comment to be clearer!)

Copy link
Contributor

Choose a reason for hiding this comment

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

I definitely think it'd be better to use getScrollContainer, especially as this UI has undergone recent change, and might end up being changed further.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks, I thought there might be a solution that wouldn't cause problems if the UI changed. 👍

Copy link
Contributor

@talldan talldan left a comment

Choose a reason for hiding this comment

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

This is very cool, thanks for working on it!

Comment on lines 125 to 126
firstSelectedBlockClientId: selectedClientIds?.[ 0 ],
numBlocksSelected: selectedClientIds?.length,
Copy link
Contributor

Choose a reason for hiding this comment

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

The interface could potentially be simplified to just useListViewScrollIntoView( { selectedClientIds, selectedItemRef } ), moving the logic that grabs the first clientId and the length into the hook.

Comment on lines 123 to 124
const invisibleSelectedItemRef = useRef();
useListViewScrollIntoView( {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to use the hook only here and remove the usage in ListViewBlock?

I find the naming invisibleSelectedItemRef a bit confusing, and it might be cleaner if there were only one selectedBlockRef that were passed to either the placeholder row or ListViewBlock as a prop.

const { innerHeight } = window;

// If the selected block is not visible, scroll to it.
// The hard-coded value of 110 corresponds to the position at the top of the scrollable area.
Copy link
Contributor

Choose a reason for hiding this comment

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

I definitely think it'd be better to use getScrollContainer, especially as this UI has undergone recent change, and might end up being changed further.

} ) {
useEffect( () => {
if (
numBlocksSelected !== 1 ||
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the reason for the early return when there's a multiselection?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was mostly to try to simplify the approach — when a multiselection is applied, we can't know (in this implementation) whether or not it was done from the list view, in which case we wouldn't want the list view to be scrolled. For example, if a user selects a block, scrolls down the list view and then shift-selects a block further down, we don't want the list view to suddenly scroll back up to the initially selected block.

I imagined the main use case for scrolling the list view is changing single block selection, but happy to dig in further if you think we should handle multiselection from the outset.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, cool, that does sound like a good reason to start with single block selection. It'd be good to add a comment to that effect.

}, [
firstSelectedBlockClientId,
numBlocksSelected,
selectedItemRef?.current,
Copy link
Contributor

Choose a reason for hiding this comment

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

A ref changing won't trigger a re-render, so I don't think this needs to be specified.

useListViewScrollIntoView( {
firstSelectedBlockClientId: selectedClientIds?.[ 0 ],
numBlocksSelected: selectedClientIds?.length,
selectedItemRef: isSelected ? cellRef : undefined,
Copy link
Contributor

Choose a reason for hiding this comment

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

There's a small inconsistency in that in one place the ref is bound to a row, and in the other a cell. I think it'd be tidied up by only calling useListViewScrollIntoView in ListViewBranch (removing it here) and having ListViewBlock accept a ref to its row.

@andrewserong
Copy link
Contributor Author

Great feedback, thanks for the detailed review, Dan! I ran out of time today, but I'll dig through the rest of the feedback and update this PR hopefully later on this week 🙂

@andrewserong
Copy link
Contributor Author

andrewserong commented Jan 13, 2023

I've removed the hard-coded top position of the scroll container and replaced with getScrollContainer to use an accurate measurement. This PR isn't quite ready for a re-review (I still need to tidy up the refs, etc), so I'll finish off this pass of changes next week.

@github-actions
Copy link

github-actions bot commented Jan 13, 2023

Flaky tests detected in 1e6580a.
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/4169445716
📝 Reported issues:

@andrewserong andrewserong force-pushed the try/list-view-scroll-off-screen-selected-blocks-into-view branch from 308cc7d to be7edb2 Compare January 17, 2023 05:13
@andrewserong
Copy link
Contributor Author

Update: in be7edb2 I've moved things around a bit. Some of the issues I ran into while messing around with the refs:

Using a ref at the branch level seemed to cause reliability issues, as conditionally setting the ref between the real selected item or the placeholder row caused an issue once we removed it from the block level and only attached it at the branch level. I think this might have been either due to the async rendering via the async provider (I'm not too sure on the details there), or because changing the ref value doesn't cause a re-render. The result was that sometimes if you clicked from far down in the document back to the top of the document, the useListViewScrollIntoView would receive a null reference for the ref rather than the "real" node, even though the ref had updated in the parent component.

I think a more explicit way to do it might be to ensure that the hook is always attached, but setting it at the row level. So far I've done this by doing the following:

  • Allow passing a ref to ListViewLeaf
  • In ListViewBlock pass a ref to the ListViewLeaf and retain usage of useListViewScrollIntoView
  • In ListViewBranch remove the hook from the parent level, and conditionally render a PlaceholderRow for the selected block if it's to be rendered as a placeholder. Within this component, use useListViewScrollIntoView.

With the above changes, we're no longer trying to wrestle with the ref, and it also avoids having to store the reference to the DOM node in state. On the other hand, it still depends on multiple calls to useListViewScrollIntoView.

I'm not sure I'm entirely sold on the approach I've gone with here, but it's the one that feels the most stable in usage to me, so far. Very happy for feedback on this if and when folks have time — I'm quite happy to go in another direction with this, as the current one doesn't feel quite neat enough for my own liking, but I wasn't too sure how else to achieve it while avoiding the issue with conditionally attaching ref at the branch level 🤔

Copy link
Contributor

@talldan talldan left a comment

Choose a reason for hiding this comment

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

I think this might have been either due to the async rendering via the async provider

Yep, that's probably it. The problem is likely that if you call the hook in ListViewBranch, the hook will be outside the <AsyncModeProvider value={ false}>, so any data updates will be async instead of sync, and may not trigger a timely re-render.

useListViewScrollIntoView needs to be defined in one of the children of the async mode provider to solve that, which seems to match what the latest commits do.

The idea of making the Placeholder its own component seems a good one and makes things more consistent, but I think it could be in a separate file with some docs to explain its purpose. Or the logic is moved into ListViewBlock or ListViewLeaf.

Comment on lines 209 to 219
( isSelected ? (
<PlaceholderRow
clientId={ clientId }
isSelected={ isSelected }
selectedClientIds={ selectedClientIds }
/>
) : (
<tr>
<td className="block-editor-list-view-placeholder" />
</tr>
) ) }
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't quite follow the reasoning for the ternary here, as the else state seems to render the same thing as PlaceholderRow.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wasn't sure if there was a performance reason for using <tr> directly over a separate React component for the placeholder rows, so this ternary preserved the <tr> for the majority of the placeholders. It'd be neater to remove it and just use PlaceholderRow, though, so I'll have a play with consolidating and see how that goes.

@andrewserong
Copy link
Contributor Author

The idea of making the Placeholder its own component seems a good one and makes things more consistent, but I think it could be in a separate file with some docs to explain its purpose. Or the logic is moved into ListViewBlock or ListViewLeaf.

Sounds good! Thanks for the feedback, Dan — I was a little on the fence about the approach, but I think this will help tidy it up 👍

@andrewserong
Copy link
Contributor Author

andrewserong commented Jan 18, 2023

I've moved the placeholder into a separate file and renamed it ListViewLeafPlaceholder for consistency with the other component naming. I think this PR's feeling a lot neater than it was earlier, so I'm feeling pretty optimistic about the direction now, thanks again for all the feedback/pointers.

Let me know if anyone would like any other changes made, and I can keep tinkering! It'd also be good to double-check the performance stats once the performance job finishes, to make sure this doesn't introduce any regressions there.

@apeatling apeatling self-requested a review January 25, 2023 04:19
Copy link
Contributor

@apeatling apeatling left a comment

Choose a reason for hiding this comment

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

Gave this another run through and it's looking and working well. I checked the performance tests job and noticed some large changes on the site editor stats. Looks unrelated, but worth running again?

@andrewserong
Copy link
Contributor Author

Thanks for taking this for another spin @apeatling! Yeah, in theory I wouldn't think this would affect performance like that, so worth running it again. I'll give this PR a rebase, then we can see what the results are looking like. For posterity, the current performance results as of the last commit appear to be:

┌──────────────────────┬──────────────────────────────────────────┬─────────────┐
│       (index)        │ 5d2c8647bf92d4b7b4c52e699b9d9528bc057043 │    trunk    │
├──────────────────────┼──────────────────────────────────────────┼─────────────┤
│    serverResponse    │                '88.63 ms'                │ '126.43 ms' │
│      firstPaint      │               '465.97 ms'                │ '347.3 ms'  │
│   domContentLoaded   │               '939.93 ms'                │ '495.3 ms'  │
│        loaded        │                '943.2 ms'                │ '495.93 ms' │
│ firstContentfulPaint │               '1296.43 ms'               │ '595.5 ms'  │
│      firstBlock      │               '9749.5 ms'                │ '1632.4 ms' │
│         type         │                '48.95 ms'                │ '43.32 ms'  │
│       minType        │                '42.3 ms'                 │ '40.81 ms'  │
│       maxType        │               '288.06 ms'                │ '107.88 ms' │
└──────────────────────┴──────────────────────────────────────────┴─────────────┘

@andrewserong andrewserong force-pushed the try/list-view-scroll-off-screen-selected-blocks-into-view branch from 4e47f7d to c51ad2e Compare January 25, 2023 04:53
@andrewserong
Copy link
Contributor Author

Update: after a rebase, I'm still seeing firstBlock time in the site editor as being much slower than on trunk in the performance tests results. After double checking another couple of open PRs (#47389, #47384) it seems this is pretty consistent with that Github Action running on open PRs, so I suspect it isn't caused by this PR. It's a bit unfortunate to see, though!

@andrewserong
Copy link
Contributor Author

Thanks for the detailed review @kevin940726, much appreciated! 🙇
I'm a little stretched for time today, so if I don't get to implementing your suggestions today (they all look like good notes 👍), I'll follow up on Monday 🙂

Copy link
Member

@kevin940726 kevin940726 left a comment

Choose a reason for hiding this comment

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

LGTM 👍 , great work!

@andrewserong
Copy link
Contributor Author

After giving this a re-test, I think this PR currently introduces a regression when scrolling a very long list quickly — the list snaps back up to the top unexpectedly. It's possibly due to the switching between list view items being rendered as a placeholder versus a "real" row item — my hunch is that when the component is swapped, the hook gets fired thinking that the block has been freshly selected, or something along those lines... here's a screengrab:

2023-02-13 17 26 09

I might need to give the hook logic a little more thought — we only want it to fire when a selection change has been made 🤔

@andrewserong andrewserong force-pushed the try/list-view-scroll-off-screen-selected-blocks-into-view branch from f9b42c6 to 1e6580a Compare February 14, 2023 01:16
@andrewserong
Copy link
Contributor Author

Update: I believe I've fixed the regression I encountered yesterday (#46895 (comment)) by doing the following:

  • Update the showBlock logic so that if a block is selected, we render a real ListViewBlock component instead of the placeholder.
  • Revert the ListViewLeafPlaceholder component, and restore using a simple <tr><td> element, thereby removing the duplicate logic calling the useListViewScrollIntoView hook. While I liked the placeholder component, it should be simpler using DOM elements here rather than introducing more React components 🤞

The result is that now there's only one "version" of a selected block in the list view, so the hook's scrollIntoView() call now appears to fire correctly when scrolling a very long list view really fast — the swapping between a placeholder and a real component no longer occurs for selected blocks.

I think this is working pretty well now, but could probably use another review / re-test to confirm that I haven't missed anything if anyone gets a chance 🙂

Thanks everyone for all the reviews and help so far! 🙇

Copy link
Member

@kevin940726 kevin940726 left a comment

Choose a reason for hiding this comment

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

Works great in my testing. Thanks!

Copy link
Contributor

@apeatling apeatling left a comment

Choose a reason for hiding this comment

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

Given this one another good run through and everything is working as expected. I could not reproduce the list jumping issue you found, so that one looks good to me. 👍

@andrewserong
Copy link
Contributor Author

Thanks for the re-reviews! Much appreciated 🙇

@andrewserong andrewserong changed the title List View: Try scrolling selected blocks into view when single block selection changes List View: Scroll selected block into view when single block selection changes Feb 15, 2023
@andrewserong andrewserong merged commit b87e855 into trunk Feb 15, 2023
@andrewserong andrewserong deleted the try/list-view-scroll-off-screen-selected-blocks-into-view branch February 15, 2023 22:53
@github-actions github-actions bot added this to the Gutenberg 15.3 milestone Feb 15, 2023
@ntsekouras ntsekouras added the Backport to WP 6.7 Beta/RC Pull request that needs to be backported to the WordPress major release that's currently in beta label Feb 16, 2023
ntsekouras pushed a commit that referenced this pull request Feb 20, 2023
…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 <[email protected]>

* Simplify and improve logic based on feedback

Co-authored-by: Kai Hao <[email protected]>

* Remove unneeded optional chaining

Co-authored-by: Kai Hao <[email protected]>

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

---------

Co-authored-by: Kai Hao <[email protected]>
@ntsekouras
Copy link
Contributor

I just cherry-picked this PR to the wp/6.2 branch to get it included in the next release: 7951add

@ntsekouras ntsekouras removed the Backport to WP 6.7 Beta/RC Pull request that needs to be backported to the WordPress major release that's currently in beta label Feb 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] List View Menu item in the top toolbar to select blocks from a list of links. [Type] Enhancement A suggestion for improvement.
Projects
No open projects
5 participants