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: Add multi-select behaviour for blocks when shift key is selected #38314

Merged
merged 24 commits into from
Feb 18, 2022

Conversation

andrewserong
Copy link
Contributor

@andrewserong andrewserong commented Jan 28, 2022

Description

Fixes: #37514

This PR borrows logic from the useMultiSelection hook to implement multiple block selection from the list view within the post, site, and widget editors, and the list view in the Navigation block. The goal is that wherever the ListView component is used, it comes with multi-block selection via the SHIFT key by default.

With the SHIFT key held down, you should be able to select multiple blocks from the same nesting level.

Testing Instructions

Test shift+click behaviour

  1. In the post editor, try selecting multiple blocks from the list view and re-arrange the blocks by dragging.
  2. Running a blocks-based theme, In the site editor, try selecting multiple blocks from the list view and re-arrange the blocks by dragging. Also, try selecting a few blocks together and click on the contextual menu in the editor canvas and group the blocks.
  3. Running a classic theme, open up the widgets editor, and try selecting multiple blocks from the list view and re-arrange the blocks by dragging.
  4. In a post, page, or site editor view, add a Navigation block with several child blocks. With the parent Navigation block selected, select the block toolbar option to view the list view (see screengrab below) and shift-click to select multiple blocks and then drag to rearrange.

Test shift+up/down arrow behaviour

  1. Try tabbing to the list view and shift+press up and down arrows to select multiple blocks. Note that shift+selecting from within a selection should extend the selection from where the selection began. If you shift+select from a block that is not adjacent to the current selection, it should start a new selection.

There are an additional two e2e tests added in this PR to cover mouse and keyboard behaviour. You can run them locally via:

npm run test-e2e -- --testPathPattern packages/e2e-tests/specs/editor/various/multi-block-selection.test.js

While running locally, I noticed that some of the other tests failed intermittently. If you wish to run this locally, too, to make it easier for local testing, you can update the it( lines in the multi-block-selection.test.js file to it.only( to only test the added tests.

There is also an added unit test which can be run via:

npm run test-unit -- --testPathPattern packages/block-editor/src/components/list-view

Screenshots

Post editor Site editor
Kapture 2022-02-01 at 16 16 13 Kapture 2022-02-01 at 16 19 42
Widget editor Navigation block list view
Kapture 2022-02-01 at 16 08 33 Kapture 2022-02-01 at 15 54 46

Types of changes

New feature (non-breaking change which adds functionality)

To-do

  • If no blocks are selected and you go to shift-select the first block, it should select it
  • Multi-select behaviour + drag and drop to rearrange blocks doesn't appear to be working properly (it only moves the first item instead of all selected items, and the selection after rearranging the blocks doesn't appear to be quite right)
  • There's duplication between post the edit-post and edit-site behaviours — can we move the select logic to a hook to remove the duplication? Update: yes! I've moved the logic to the component since we'd like this logic in place in each of the use cases for the list view.
  • Add tests

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • I've tested my changes with keyboard and screen readers.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.
  • I've updated all React Native files affected by any refactorings/renamings in this PR (please manually search all *.native.js files for terms that need renaming or removal).
  • I've updated related schemas if appropriate.

@andrewserong andrewserong added [Type] Enhancement A suggestion for improvement. [Feature] Block Multi Selection The ability to select and manipulate multiple blocks [Feature] List View Menu item in the top toolbar to select blocks from a list of links. labels Jan 28, 2022
@andrewserong andrewserong self-assigned this Jan 28, 2022
@github-actions
Copy link

github-actions bot commented Jan 28, 2022

Size Change: +253 B (0%)

Total Size: 1.15 MB

Filename Size Change
build/block-directory/index.min.js 6.49 kB -18 B (0%)
build/block-editor/index.min.js 143 kB +530 B (0%)
build/block-library/index.min.js 168 kB +339 B (0%)
build/blocks/index.min.js 46.4 kB -12 B (0%)
build/components/index.min.js 215 kB -249 B (0%)
build/core-data/index.min.js 14 kB -13 B (0%)
build/customize-widgets/index.min.js 11.4 kB -18 B (0%)
build/customize-widgets/style-rtl.css 1.49 kB -3 B (0%)
build/customize-widgets/style.css 1.49 kB -3 B (0%)
build/data/index.min.js 7.45 kB -26 B (0%)
build/edit-navigation/index.min.js 16.2 kB -12 B (0%)
build/edit-post/index.min.js 29.9 kB -151 B (-1%)
build/edit-site/index.min.js 42.1 kB -115 B (0%)
build/edit-widgets/index.min.js 16.7 kB -46 B (0%)
build/edit-widgets/style-rtl.css 4.17 kB +4 B (0%)
build/edit-widgets/style.css 4.17 kB +3 B (0%)
build/editor/index.min.js 38.5 kB +54 B (0%)
build/reusable-blocks/index.min.js 2.24 kB -11 B (0%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 993 B
build/admin-manifest/index.min.js 1.24 kB
build/annotations/index.min.js 2.77 kB
build/api-fetch/index.min.js 2.25 kB
build/autop/index.min.js 2.15 kB
build/blob/index.min.js 487 B
build/block-directory/style-rtl.css 1.01 kB
build/block-directory/style.css 1.01 kB
build/block-editor/default-editor-styles-rtl.css 378 B
build/block-editor/default-editor-styles.css 378 B
build/block-editor/style-rtl.css 14.8 kB
build/block-editor/style.css 14.8 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 65 B
build/block-library/blocks/archives/style.css 65 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 111 B
build/block-library/blocks/audio/style.css 111 B
build/block-library/blocks/audio/theme-rtl.css 125 B
build/block-library/blocks/audio/theme.css 125 B
build/block-library/blocks/block/editor-rtl.css 161 B
build/block-library/blocks/block/editor.css 161 B
build/block-library/blocks/button/editor-rtl.css 470 B
build/block-library/blocks/button/editor.css 470 B
build/block-library/blocks/button/style-rtl.css 560 B
build/block-library/blocks/button/style.css 560 B
build/block-library/blocks/buttons/editor-rtl.css 292 B
build/block-library/blocks/buttons/editor.css 292 B
build/block-library/blocks/buttons/style-rtl.css 275 B
build/block-library/blocks/buttons/style.css 275 B
build/block-library/blocks/calendar/style-rtl.css 207 B
build/block-library/blocks/calendar/style.css 207 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 79 B
build/block-library/blocks/categories/style.css 79 B
build/block-library/blocks/code/style-rtl.css 90 B
build/block-library/blocks/code/style.css 90 B
build/block-library/blocks/code/theme-rtl.css 131 B
build/block-library/blocks/code/theme.css 131 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-template/style-rtl.css 127 B
build/block-library/blocks/comment-template/style.css 127 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-query-loop/editor-rtl.css 95 B
build/block-library/blocks/comments-query-loop/editor.css 95 B
build/block-library/blocks/cover/editor-rtl.css 546 B
build/block-library/blocks/cover/editor.css 547 B
build/block-library/blocks/cover/style-rtl.css 1.55 kB
build/block-library/blocks/cover/style.css 1.55 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 417 B
build/block-library/blocks/embed/style.css 417 B
build/block-library/blocks/embed/theme-rtl.css 124 B
build/block-library/blocks/embed/theme.css 124 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 255 B
build/block-library/blocks/file/style.css 255 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 965 B
build/block-library/blocks/gallery/editor.css 967 B
build/block-library/blocks/gallery/style-rtl.css 1.61 kB
build/block-library/blocks/gallery/style.css 1.61 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 159 B
build/block-library/blocks/group/editor.css 159 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 114 B
build/block-library/blocks/heading/style.css 114 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 731 B
build/block-library/blocks/image/editor.css 730 B
build/block-library/blocks/image/style-rtl.css 518 B
build/block-library/blocks/image/style.css 523 B
build/block-library/blocks/image/theme-rtl.css 124 B
build/block-library/blocks/image/theme.css 124 B
build/block-library/blocks/latest-comments/style-rtl.css 284 B
build/block-library/blocks/latest-comments/style.css 284 B
build/block-library/blocks/latest-posts/editor-rtl.css 199 B
build/block-library/blocks/latest-posts/editor.css 198 B
build/block-library/blocks/latest-posts/style-rtl.css 447 B
build/block-library/blocks/latest-posts/style.css 446 B
build/block-library/blocks/list/style-rtl.css 94 B
build/block-library/blocks/list/style.css 94 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 493 B
build/block-library/blocks/media-text/style.css 490 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 649 B
build/block-library/blocks/navigation-link/editor.css 650 B
build/block-library/blocks/navigation-link/style-rtl.css 94 B
build/block-library/blocks/navigation-link/style.css 94 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-submenu/view.min.js 375 B
build/block-library/blocks/navigation/editor-rtl.css 1.98 kB
build/block-library/blocks/navigation/editor.css 1.99 kB
build/block-library/blocks/navigation/style-rtl.css 1.89 kB
build/block-library/blocks/navigation/style.css 1.88 kB
build/block-library/blocks/navigation/view.min.js 2.85 kB
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 401 B
build/block-library/blocks/page-list/editor.css 402 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 157 B
build/block-library/blocks/paragraph/editor.css 157 B
build/block-library/blocks/paragraph/style-rtl.css 273 B
build/block-library/blocks/paragraph/style.css 273 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/style-rtl.css 446 B
build/block-library/blocks/post-comments-form/style.css 446 B
build/block-library/blocks/post-comments/style-rtl.css 521 B
build/block-library/blocks/post-comments/style.css 521 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 69 B
build/block-library/blocks/post-excerpt/style.css 69 B
build/block-library/blocks/post-featured-image/editor-rtl.css 721 B
build/block-library/blocks/post-featured-image/editor.css 721 B
build/block-library/blocks/post-featured-image/style-rtl.css 153 B
build/block-library/blocks/post-featured-image/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 323 B
build/block-library/blocks/post-template/style.css 323 B
build/block-library/blocks/post-terms/style-rtl.css 73 B
build/block-library/blocks/post-terms/style.css 73 B
build/block-library/blocks/post-title/style-rtl.css 80 B
build/block-library/blocks/post-title/style.css 80 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 198 B
build/block-library/blocks/pullquote/editor.css 198 B
build/block-library/blocks/pullquote/style-rtl.css 389 B
build/block-library/blocks/pullquote/style.css 388 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 234 B
build/block-library/blocks/query-pagination/style.css 231 B
build/block-library/blocks/query/editor-rtl.css 131 B
build/block-library/blocks/query/editor.css 132 B
build/block-library/blocks/quote/style-rtl.css 201 B
build/block-library/blocks/quote/style.css 201 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 202 B
build/block-library/blocks/rss/editor.css 204 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 397 B
build/block-library/blocks/search/style.css 398 B
build/block-library/blocks/search/theme-rtl.css 64 B
build/block-library/blocks/search/theme.css 64 B
build/block-library/blocks/separator/editor-rtl.css 99 B
build/block-library/blocks/separator/editor.css 99 B
build/block-library/blocks/separator/style-rtl.css 233 B
build/block-library/blocks/separator/style.css 233 B
build/block-library/blocks/separator/theme-rtl.css 172 B
build/block-library/blocks/separator/theme.css 172 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 744 B
build/block-library/blocks/site-logo/editor.css 744 B
build/block-library/blocks/site-logo/style-rtl.css 181 B
build/block-library/blocks/site-logo/style.css 181 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 84 B
build/block-library/blocks/site-title/editor.css 84 B
build/block-library/blocks/social-link/editor-rtl.css 177 B
build/block-library/blocks/social-link/editor.css 177 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.37 kB
build/block-library/blocks/social-links/style.css 1.36 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 471 B
build/block-library/blocks/table/editor.css 472 B
build/block-library/blocks/table/style-rtl.css 481 B
build/block-library/blocks/table/style.css 481 B
build/block-library/blocks/table/theme-rtl.css 188 B
build/block-library/blocks/table/theme.css 188 B
build/block-library/blocks/tag-cloud/style-rtl.css 214 B
build/block-library/blocks/tag-cloud/style.css 215 B
build/block-library/blocks/template-part/editor-rtl.css 560 B
build/block-library/blocks/template-part/editor.css 559 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 87 B
build/block-library/blocks/verse/style.css 87 B
build/block-library/blocks/video/editor-rtl.css 571 B
build/block-library/blocks/video/editor.css 572 B
build/block-library/blocks/video/style-rtl.css 173 B
build/block-library/blocks/video/style.css 173 B
build/block-library/blocks/video/theme-rtl.css 124 B
build/block-library/blocks/video/theme.css 124 B
build/block-library/common-rtl.css 921 B
build/block-library/common.css 919 B
build/block-library/editor-rtl.css 10.1 kB
build/block-library/editor.css 10.1 kB
build/block-library/reset-rtl.css 474 B
build/block-library/reset.css 474 B
build/block-library/style-rtl.css 11.3 kB
build/block-library/style.css 11.3 kB
build/block-library/theme-rtl.css 672 B
build/block-library/theme.css 676 B
build/block-serialization-default-parser/index.min.js 1.12 kB
build/block-serialization-spec-parser/index.min.js 2.83 kB
build/components/style-rtl.css 15.5 kB
build/components/style.css 15.5 kB
build/compose/index.min.js 11.2 kB
build/data-controls/index.min.js 663 B
build/date/index.min.js 31.9 kB
build/deprecated/index.min.js 518 B
build/dom-ready/index.min.js 336 B
build/dom/index.min.js 4.53 kB
build/edit-navigation/style-rtl.css 3.76 kB
build/edit-navigation/style.css 3.76 kB
build/edit-post/classic-rtl.css 546 B
build/edit-post/classic.css 547 B
build/edit-post/style-rtl.css 7.19 kB
build/edit-post/style.css 7.18 kB
build/edit-site/style-rtl.css 7.23 kB
build/edit-site/style.css 7.22 kB
build/editor/style-rtl.css 3.71 kB
build/editor/style.css 3.71 kB
build/element/index.min.js 3.32 kB
build/escape-html/index.min.js 548 B
build/format-library/index.min.js 6.62 kB
build/format-library/style-rtl.css 571 B
build/format-library/style.css 571 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.83 kB
build/keycodes/index.min.js 1.41 kB
build/list-reusable-blocks/index.min.js 1.75 kB
build/list-reusable-blocks/style-rtl.css 838 B
build/list-reusable-blocks/style.css 838 B
build/media-utils/index.min.js 2.94 kB
build/notices/index.min.js 957 B
build/nux/index.min.js 2.12 kB
build/nux/style-rtl.css 747 B
build/nux/style.css 743 B
build/plugins/index.min.js 1.98 kB
build/primitives/index.min.js 949 B
build/priority-queue/index.min.js 611 B
build/react-i18n/index.min.js 704 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.69 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 11.1 kB
build/server-side-render/index.min.js 1.61 kB
build/shortcode/index.min.js 1.52 kB
build/token-list/index.min.js 668 B
build/url/index.min.js 1.92 kB
build/viewport/index.min.js 1.08 kB
build/warning/index.min.js 280 B
build/widgets/index.min.js 7.18 kB
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.07 kB

compressed-size-action

} = select( blockEditorStore );
return {
rootClientId: getBlockRootClientId( clientId ) || '',
Copy link
Contributor Author

Choose a reason for hiding this comment

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

As of #33320 the rootClientId is no longer used in this function, so I think it can safely be removed here.

@andrewserong
Copy link
Contributor Author

Hi folks! (Looks like this PR touches files that ping quite a number of folks!) 👋

I don't have a lot of experience making changes to the ListView component, so I'm keen for feedback on whether or not it looks okay moving the multi select logic to the component. It seems to work pretty well for me in testing (across the post, site, and widget editors, and the navigation block's list view), but let me know if you run into any issues!

@noisysocks
Copy link
Member

Wow this works insanely well! Nice job.

I tested: selecting multiple blocks and using keyboard shortcuts (e.g. Del, Cmd+c), selecting multiple blocks and dragging them within the same parent, selecting multiple blocks and dragging them into a different parent, selecting multiple blocks that have children, and doing this across each of the four editors. (Thank you for remembering that we have multiple editors!)

I set Firefox to emulate an iPad in landscape mode and noticed that I could not select multiple blocks. (iPads support bluetooth keyboards.) But you can't do this using the block editor either, so definitely not a blocker for this PR.

My only thought is that maybe we should hide the ⋮ buttons when multiple blocks are selected? They're a bit confusing in this context. It's not clear if clicking on one and selecting an action will perform that action on one or multiple blocks.

Screen Shot 2022-02-02 at 16 52 33

packages/block-editor/CHANGELOG.md Outdated Show resolved Hide resolved
@@ -89,11 +98,64 @@ function ListView(
[ draggedClientIds ]
);
const selectEditorBlock = useCallback(
Copy link
Member

Choose a reason for hiding this comment

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

This callback does a lot more than selecting an editor block now so maybe its name should change to updateBlockSelection or something like that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, nice idea. Happy to update it 👍

multiSelect( start, end );
}
}
}
onSelect( clientId );
Copy link
Member

Choose a reason for hiding this comment

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

Should we pass multiple IDs to this prop?

Or, since it looks like we're no longer using it, remove the prop altogether? Maybe third parties would like to have it though.

ListView is currently experimental so we can safely make changes to its API.

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 actually really like the idea of removing it altogether if we can. Happy for any feedback folks might have!

@noisysocks
Copy link
Member

I'm keen for feedback on whether or not it looks okay moving the multi select logic to the component.

@gwwar and @talldan would know better but I think it seems pretty reasonable. It's interesting that ListView was already (unnecessarily?) calling selectBlock.

@andrewserong
Copy link
Contributor Author

Thanks for reviewing and taking this for a spin @noisysocks! 🙇

My only thought is that maybe we should hide the ⋮ buttons when multiple blocks are selected? They're a bit confusing in this context. It's not clear if clicking on one and selecting an action will perform that action on one or multiple blocks.

That's a good question — currently the ⋮ button adjusts settings for the individual block. Personally, I'd lean toward making changes to the UI in subsequent PRs if we can. Another nice thing to look into would be how we visualise dragging multiple blocks so that it's clear it isn't the one block that's being dragged. But my thinking was, if we can land the multi-select first, then it'll hopefully be easier to look at some of these more granular UI things in isolation.

Happy to do it here, though, if folks think it's a blocker!

@talldan
Copy link
Contributor

talldan commented Feb 2, 2022

Wow, this is a pretty cool feature! Appreciate all the hard work.

@gwwar and @talldan would know better but I think it seems pretty reasonable. It's interesting that ListView was already (unnecessarily?) calling selectBlock.

I don't have any other suggestions, so this seems good. Might be nice to wrap it up into a hook or something.

It'd be good to make sure this gets some accessibility testing. @alexstine has been working on List View a lot recently so can possibly assist.

One thing that jumps out is that some spoken messages might be required to announce the selection range. Though potentially that could already be implemented in the multiselect store actions. Worth double-checking that and whether any existing messages are still up to the task.

(edit: I notice now this is only usable via shift+click, it should probably work via arrow keys too. It could be done in stages via a separate PR, but without that it's not really an accessible feature)

@ramonjd
Copy link
Member

ramonjd commented Feb 2, 2022

Here's what I'm seeing

Feb-02-2022 20-42-49

I tested with

👍 SHIFT clicking and dragging/dropping collapsed and non-collapsed blocks.

👍 SHIFT clicking blocks in the editor: the corresponding blocks are highlighted in the list

👍 SHIFT clicking expanded and top level list items: the nested blocks aren't displayed as selected, which is what I'd expect

Screen Shot 2022-02-02 at 8 39 42 pm

👍 SHIFT clicking and selected 30+ blocks at a time. Perceived performance was the same as dragging one.

This is a wonderful UX improvement, thank you! And for the e2e tests.

Might be nice to wrap it up into a hook or something

+1

@gwwar
Copy link
Contributor

gwwar commented Feb 2, 2022

It's interesting that ListView was already (unnecessarily?) calling selectBlock.

🤔 I don't remember offhand, but I'm still catching up on things after my 3 month sabbatical too. 😆 I'll try and 🔍 this one today.

This is a bit of a tangent, but one other thought that comes to mind is that I'd really like to optionally run performance tests with list view open when we make changes in this component. I'll maybe smoke test in a large post for now, and think through some options in another PR.

@gwwar
Copy link
Contributor

gwwar commented Feb 2, 2022

Reserving this comment for manual testing:

TIL that shift + mousewheel = scroll horizontally

✅ In a large post (where we end up using windowing), the selection behaves as expected

example.of.selection.in.large.post.mp4

❌ This might be arguable/better left for a follow up PR. A selection starting from nested child blocks, ends up selecting the parent. If you compare behavior in a normal filesystem, this isn't the case.

unexpected.selection.mp4

🤔 Looking at some older videos, we didn't use to select blocks in the modal view (behavior is now in trunk too). Was this an intentional change?

❌ Open List View, also open the navigation modal. Nav menu is removed and inserted to bottom of post:

list.view.open.drag.items.in.menu.bug.mp4

@alexstine
Copy link
Contributor

@andrewserong Accessibility with the keyboard is getting much better, great work. 👍 My only comment is about the unselect behavior. If I select a Paragraph Block and a Table Block, then unselect the Table Block, it announces "1 Block selected, Paragraph Block selected". Is there anyway to change this so it says which item was "unselected"? E.g. "1 Blocks selected, Table Block unselected". It may even be a good idea to change it around so name of Block comes first followed by how count.

Thoughts?

Thanks.

@andrewserong andrewserong force-pushed the add/multi-selection-to-list-view branch from 4c7e257 to db31ffd Compare February 16, 2022 04:06
@andrewserong
Copy link
Contributor Author

@talldan I've updated this PR to pass in the selectedClientIds to the dropdown menu so that when there's a multiple selection, the dropdown menu applies to the whole selection. If a user clicks on one of the blocks that isn't part of the selection, then much like the grad behaviour, it only applies to the clicked on block. I think this should now fix most of the issues you raised.

@javierarce great catch about the expand / collapse button. I've added in an event.preventDefault call and that appears to be fixed now (toggling the visibility has no effect on the selection).

@andrewserong
Copy link
Contributor Author

@alexstine, thanks for giving this another test! 🙇 That's a great idea about announcing the blocks that are deselected. I've made an attempt at adding in an announcement, but I'm not sure if it's quite right. It now announces the name of the block that has been deselected if there is only one. If there is greater than one deselected block, then it announces the number of blocks deselected.

Because the code that announces the selection of blocks is spread across different areas, I'm not sure how feasible it is in this PR to move the logic all to the one place, which means that we might be limited in terms of the order in which things are announced, unless there's another way to manipulate it. It now reads out both the block that is selected and the one that is deselected, so I could imagine that being confusing. I think the block that is selected is announced via the component we are currently focused on, rather than a call to speak, so it might need some more tweaking to get it right.

Please let me know if you have further feedback on how it should work, and I'm happy to keep iterating!

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 working really well. Thanks for all the hard work on this PR!

Comment on lines 115 to 137
###### onChangeRow( event: Event, startRow: HTMLElement, destinationRow: HTMLElement )

Callback that fires when focus is shifted from one row to another via the UP and DOWN keys.
The callback is passed the event, the start row element that the focus was on originally, and
the destination row element after the focus has moved.

- Type: `Function`
- Required: No

###### onCollapseRow( row: HTMLElement )

A callback that passes in the row element to be collapsed.

- Type: `Function`
- Required: No

###### onExpandRow( row: HTMLElement )

A callback that passes in the row element to be expanded.

- Type: `Function`
- Required: No

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for documenting the expand/collapse props too!

}

if ( label ) {
speak( label );
Copy link
Contributor

@talldan talldan Feb 17, 2022

Choose a reason for hiding this comment

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

This message works pretty well in testing.

In terms of code, it could be an option to try moving this announcement to the multiSelect action which already has a call to speak.

It should hopefully be possible to combine it with the existing message. Something like '2 blocks selected (Paragraph deselected)'. I'll defer to Alex's call on what the best text would be.

I think it'd be ok to try this out in a separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for confirming, Dan! I agree, this PR has already gotten fairly big, so it'd probably be a good thing to explore separately as a follow-up.

@andrewserong
Copy link
Contributor Author

Thanks for reviewing again @talldan! I'll leave this PR overnight just in case anyone else has feedback they'd like to see incorporated before merging. If there aren't any objections, I'll then merge it in tomorrow. Thanks again for all the reviews, everyone 🙇

Copy link
Contributor

@alexstine alexstine left a comment

Choose a reason for hiding this comment

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

This looks good for accessibility. Can improve in separate PR where diffs will be smaller and easier to test.

Thanks for your hard work on this @andrewserong . Certainly looks like a ton more code involved for the keyboard part of this.

Copy link
Contributor

@ciampo ciampo left a comment

Choose a reason for hiding this comment

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

I'll leave this PR overnight just in case anyone else has feedback they'd like to see incorporated before merging. If there aren't any objections, I'll then merge it in tomorrow. Thanks again for all the reviews, everyone 🙇

Sorry for my late review, @andrewserong ! Unfortunately I'm lagging a bit behind with PR reviews these days 😅 I only left a couple of tiny inline comments, nothing that should really delay this PR further.

Thank you!

packages/components/src/tree-grid/README.md Outdated Show resolved Hide resolved
packages/components/CHANGELOG.md Outdated Show resolved Hide resolved
@andrewserong
Copy link
Contributor Author

Alrighty, tests are passing, so I'll merge this in now. Thanks so much again @noisysocks, @gwwar, @talldan, @alexstine, @ciampo, and @javierarce for all the reviews, testing, and feedback — it's been immeasurably helpful in developing this feature (and I've learnt a lot in the process, too). Kudos! 🙇

@andrewserong andrewserong merged commit 798484c into trunk Feb 18, 2022
@andrewserong andrewserong deleted the add/multi-selection-to-list-view branch February 18, 2022 01:35
@github-actions github-actions bot added this to the Gutenberg 12.7 milestone Feb 18, 2022
@jasmussen
Copy link
Contributor

🔥

@andrewserong
Copy link
Contributor Author

I've just opened a PR in #38942 as a follow-up to add in some unit tests for the TreeGrid component's callback functions — this is mostly to ensure that any future changes to that component don't affect the expected behaviour in the list view.

export const multiSelect = (
start,
end,
__experimentalInitialPosition = 0
Copy link
Member

Choose a reason for hiding this comment

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

@andrewserong Why did we add this parameter? How does initialPosition make any sense for a multi block selection? initialPosition implies collapsed selection, while multi selection implies an uncollapsed selection.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ellatrix the existing initialPosition state is used in the writing flow to control whether or not to shift focus to the editor (for single block selection), so I thought it might make sense to re-use that same logic for multiple block selection so that the logic is as consistent as we can make it. In this case, we're using it effectively like a boolean, so setting to null says "there is no initial position" (and therefore don't focus in the editor canvas), and with the default 0 value, the position is "within the editor canvas".

For accessibility it was important that we don't shift focus to the editor canvas when multi-selecting from the list view. If there's a more semantic way to do this than using initialPosition we can definitely refactor it (one of the reasons I think @talldan suggested we use the __experimental prefix for the moment).

Do you have any guidance on how best to preserve the behaviour of skipping focusing on the editor canvas, if we were to refactor this?

Copy link
Member

@ellatrix ellatrix Feb 23, 2022

Choose a reason for hiding this comment

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

Yes, I understood the intent after digging a bit further. :) The naming is a bit confusing and I'm not sure if reusing the 0 value makes sense to mean selecting all content. Another thing to keep in mind is that focus and selection are two separate things. Even if focus has to stay in the List View, we still need to select the block contents. Anyway, it's not important right now :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, great. Thanks for confirming @ellatrix, please do ping me if it does become important / needs updating, though, and I can follow up!

@andrewserong
Copy link
Contributor Author

I've opened up another small follow-up PR in #39272 to look at adding support for shift-selecting via the Home and End keys.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Block Multi Selection The ability to select and manipulate multiple blocks [Feature] List View Menu item in the top toolbar to select blocks from a list of links. [Package] Block editor /packages/block-editor [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

List View: Enable multiple block selection using CMD + Click