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: Experiment with drag in place #34022

Closed
wants to merge 8 commits into from
Closed

Conversation

gwwar
Copy link
Contributor

@gwwar gwwar commented Aug 11, 2021

This PR explores alternative drag interactions using motion framer. Instead of using a drag chip, we instead allow items to drag in place. Follow up to: #33765

This PR is pretty large in size since I wasn't able to find any smaller chunks to safely land. If folks spot pieces we can break off, I'd be happy to break this into additional PRs.

Dragging.mp4

Testing Instructions

Verify in each environment that:

  • Expand/Collapse list is now animated
  • Moving blocks is animated
  • When reduce motion is requested, animations are disabled, but drag is still available.
Site Editor
Screen Shot 2021-08-26 at 2 24 00 PM
Post Editor
Screen Shot 2021-08-26 at 2 26 25 PM
Block Navigation List Modal
Screen Shot 2021-08-26 at 2 21 49 PM Screen Shot 2021-08-26 at 2 21 56 PM

Performance

Using motion components comes with a performance cost. I did my best here to memo key items while dragging and avoid unneeded renders. When adding new blocks or removing blocks, we may see additional lag for updating items in list view. Note that some odd laggy behavior is also present in trunk, but render costs may be more pronounced with this change. One thing to consider would be list virtualization, though I do understand that there are a11y concerns to work with. I think it'd be worth investigating in a follow up.

UX interactions

For this first pass, folks have noted that we would like to see:

  • How little styling we need
  • Other enhancements like a hover drag handle to follow up PRs
  • Holding drag near top/bottom to scroll will be handled in a follow up

Do let us know if any interactions don't feel quite right. Dragging so far works roughly like this:

  • Straight Up/Down movement - Swap Siblings
  • Left + movement - Move item after/before target item (usually under a new parent)
  • Right + movement - Move item as child of target parent

Drag And Drop E2E

Puppeteer recently added some drag and drop E2E support, but I was unable to get this working with this drag implementation. (It currently selects text across elements instead of initiating a drag) @talldan let me know if you have any other tips. If we're not able to reasonably fix this, I'd recommend dropping the test.

@gwwar gwwar added [Type] Enhancement A suggestion for improvement. [Feature] List View Menu item in the top toolbar to select blocks from a list of links. [Package] Block editor /packages/block-editor labels Aug 11, 2021
@gwwar gwwar self-assigned this Aug 11, 2021
@github-actions
Copy link

github-actions bot commented Aug 11, 2021

Size Change: +2.98 kB (0%)

Total Size: 1.07 MB

Filename Size Change
build/block-editor/index.min.js 135 kB +836 B (+1%)
build/block-editor/style-rtl.css 13.9 kB -58 B (0%)
build/block-editor/style.css 13.9 kB -56 B (0%)
build/block-library/index.min.js 146 kB +6 B (0%)
build/components/index.min.js 216 kB +2.23 kB (+1%)
build/edit-post/index.min.js 29.2 kB -2 B (0%)
build/edit-post/style-rtl.css 7.2 kB +7 B (0%)
build/edit-post/style.css 7.19 kB +8 B (0%)
build/edit-site/index.min.js 29 kB +1 B (0%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 931 B
build/admin-manifest/index.min.js 1.09 kB
build/annotations/index.min.js 2.7 kB
build/api-fetch/index.min.js 2.21 kB
build/autop/index.min.js 2.08 kB
build/blob/index.min.js 459 B
build/block-directory/index.min.js 6.19 kB
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-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 58 B
build/block-library/blocks/audio/editor.css 58 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 474 B
build/block-library/blocks/button/editor.css 474 B
build/block-library/blocks/button/style-rtl.css 600 B
build/block-library/blocks/button/style.css 600 B
build/block-library/blocks/buttons/editor-rtl.css 315 B
build/block-library/blocks/buttons/editor.css 315 B
build/block-library/blocks/buttons/style-rtl.css 370 B
build/block-library/blocks/buttons/style.css 370 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 206 B
build/block-library/blocks/columns/editor.css 205 B
build/block-library/blocks/columns/style-rtl.css 497 B
build/block-library/blocks/columns/style.css 496 B
build/block-library/blocks/cover/editor-rtl.css 666 B
build/block-library/blocks/cover/editor.css 670 B
build/block-library/blocks/cover/style-rtl.css 1.23 kB
build/block-library/blocks/cover/style.css 1.23 kB
build/block-library/blocks/embed/editor-rtl.css 488 B
build/block-library/blocks/embed/editor.css 488 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 322 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 977 B
build/block-library/blocks/gallery/editor.css 982 B
build/block-library/blocks/gallery/style-rtl.css 1.6 kB
build/block-library/blocks/gallery/style.css 1.59 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/home-link/style-rtl.css 247 B
build/block-library/blocks/home-link/style.css 247 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 728 B
build/block-library/blocks/image/editor.css 728 B
build/block-library/blocks/image/style-rtl.css 482 B
build/block-library/blocks/image/style.css 487 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 137 B
build/block-library/blocks/latest-posts/editor.css 137 B
build/block-library/blocks/latest-posts/style-rtl.css 528 B
build/block-library/blocks/latest-posts/style.css 527 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 488 B
build/block-library/blocks/media-text/style.css 485 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 568 B
build/block-library/blocks/navigation-link/editor.css 570 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 300 B
build/block-library/blocks/navigation-submenu/editor.css 299 B
build/block-library/blocks/navigation-submenu/style-rtl.css 195 B
build/block-library/blocks/navigation-submenu/style.css 195 B
build/block-library/blocks/navigation-submenu/view.min.js 343 B
build/block-library/blocks/navigation/editor-rtl.css 1.72 kB
build/block-library/blocks/navigation/editor.css 1.72 kB
build/block-library/blocks/navigation/style-rtl.css 1.62 kB
build/block-library/blocks/navigation/style.css 1.61 kB
build/block-library/blocks/navigation/view.min.js 2.74 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 377 B
build/block-library/blocks/page-list/editor.css 377 B
build/block-library/blocks/page-list/style-rtl.css 198 B
build/block-library/blocks/page-list/style.css 198 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/editor-rtl.css 210 B
build/block-library/blocks/post-author/editor.css 210 B
build/block-library/blocks/post-author/style-rtl.css 182 B
build/block-library/blocks/post-author/style.css 181 B
build/block-library/blocks/post-comments-form/style-rtl.css 140 B
build/block-library/blocks/post-comments-form/style.css 140 B
build/block-library/blocks/post-comments/style-rtl.css 360 B
build/block-library/blocks/post-comments/style.css 359 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 398 B
build/block-library/blocks/post-featured-image/editor.css 398 B
build/block-library/blocks/post-featured-image/style-rtl.css 146 B
build/block-library/blocks/post-featured-image/style.css 146 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 391 B
build/block-library/blocks/post-template/style.css 392 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 60 B
build/block-library/blocks/post-title/style.css 60 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 378 B
build/block-library/blocks/pullquote/style.css 378 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 262 B
build/block-library/blocks/query-pagination/editor.css 255 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-title/editor-rtl.css 85 B
build/block-library/blocks/query-title/editor.css 85 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 187 B
build/block-library/blocks/quote/style.css 187 B
build/block-library/blocks/quote/theme-rtl.css 220 B
build/block-library/blocks/quote/theme.css 222 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 374 B
build/block-library/blocks/search/style.css 375 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 250 B
build/block-library/blocks/separator/style.css 250 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 462 B
build/block-library/blocks/site-logo/editor.css 464 B
build/block-library/blocks/site-logo/style-rtl.css 153 B
build/block-library/blocks/site-logo/style.css 153 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 165 B
build/block-library/blocks/social-link/editor.css 165 B
build/block-library/blocks/social-links/editor-rtl.css 812 B
build/block-library/blocks/social-links/editor.css 811 B
build/block-library/blocks/social-links/style-rtl.css 1.3 kB
build/block-library/blocks/social-links/style.css 1.3 kB
build/block-library/blocks/spacer/editor-rtl.css 307 B
build/block-library/blocks/spacer/editor.css 307 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 146 B
build/block-library/blocks/tag-cloud/style.css 146 B
build/block-library/blocks/template-part/editor-rtl.css 636 B
build/block-library/blocks/template-part/editor.css 635 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/term-description/editor-rtl.css 90 B
build/block-library/blocks/term-description/editor.css 90 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 853 B
build/block-library/common.css 849 B
build/block-library/editor-rtl.css 9.72 kB
build/block-library/editor.css 9.71 kB
build/block-library/reset-rtl.css 536 B
build/block-library/reset.css 536 B
build/block-library/style-rtl.css 10.4 kB
build/block-library/style.css 10.4 kB
build/block-library/theme-rtl.css 665 B
build/block-library/theme.css 669 B
build/block-serialization-default-parser/index.min.js 1.09 kB
build/block-serialization-spec-parser/index.min.js 2.79 kB
build/blocks/index.min.js 45.7 kB
build/components/style-rtl.css 15.9 kB
build/components/style.css 15.9 kB
build/compose/index.min.js 10.3 kB
build/core-data/index.min.js 12.4 kB
build/customize-widgets/index.min.js 11.1 kB
build/customize-widgets/style-rtl.css 1.5 kB
build/customize-widgets/style.css 1.49 kB
build/data-controls/index.min.js 614 B
build/data/index.min.js 7.1 kB
build/date/index.min.js 31.5 kB
build/deprecated/index.min.js 428 B
build/dom-ready/index.min.js 304 B
build/dom/index.min.js 4.45 kB
build/edit-navigation/index.min.js 15.4 kB
build/edit-navigation/style-rtl.css 3.74 kB
build/edit-navigation/style.css 3.74 kB
build/edit-post/classic-rtl.css 492 B
build/edit-post/classic.css 494 B
build/edit-site/style-rtl.css 5.43 kB
build/edit-site/style.css 5.43 kB
build/edit-widgets/index.min.js 15.7 kB
build/edit-widgets/style-rtl.css 4.1 kB
build/edit-widgets/style.css 4.1 kB
build/editor/index.min.js 37.5 kB
build/editor/style-rtl.css 3.76 kB
build/editor/style.css 3.75 kB
build/element/index.min.js 3.17 kB
build/escape-html/index.min.js 517 B
build/format-library/index.min.js 5.93 kB
build/format-library/style-rtl.css 571 B
build/format-library/style.css 571 B
build/hooks/index.min.js 1.55 kB
build/html-entities/index.min.js 424 B
build/i18n/index.min.js 3.6 kB
build/is-shallow-equal/index.min.js 501 B
build/keyboard-shortcuts/index.min.js 1.72 kB
build/keycodes/index.min.js 1.3 kB
build/list-reusable-blocks/index.min.js 1.85 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.92 kB
build/notices/index.min.js 845 B
build/nux/index.min.js 2.03 kB
build/nux/style-rtl.css 747 B
build/nux/style.css 743 B
build/plugins/index.min.js 1.83 kB
build/primitives/index.min.js 921 B
build/priority-queue/index.min.js 582 B
build/react-i18n/index.min.js 671 B
build/redux-routine/index.min.js 2.63 kB
build/reusable-blocks/index.min.js 2.28 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 10.6 kB
build/server-side-render/index.min.js 1.5 kB
build/shortcode/index.min.js 1.48 kB
build/token-list/index.min.js 562 B
build/url/index.min.js 1.74 kB
build/viewport/index.min.js 1.02 kB
build/warning/index.min.js 248 B
build/widgets/index.min.js 7.11 kB
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.04 kB

compressed-size-action

@gwwar
Copy link
Contributor Author

gwwar commented Aug 11, 2021

Leaving a note to myself that the current drag implementation doesn't correctly commit a valid drag operation sometimes either. Likely need to debug what's going on in the moveBlocksToPosition action

Edit: found the issue in this branch, was calculating the wrong index when the item to be moved was still in the tree.

current.drag.error.mp4

@talldan
Copy link
Contributor

talldan commented Aug 12, 2021

That first video looks so good!

@gwwar
Copy link
Contributor Author

gwwar commented Aug 12, 2021

Leaving another note to myself that if we're skipping over items like in the following video, the translate value from onViewportBoxUpdate can no longer be used to properly indicate drag direction. 🤔

Some other things to try:

  • I'll maybe look into allowing the dragged item to pass through invalid pieces one at a time, but disallow trying to commit the block change.
  • Try collapsing invalid containers before allowing items to be dragged. (This might be tricky to pause any drag movement, while the list finishes updating.)
ignore.mp4

@gwwar
Copy link
Contributor Author

gwwar commented Aug 12, 2021

Edit: okay I think I can work around this by keeping track of velocity as a motion value in onDrag, and also using a shared layoutId so drag will continue.

When breaking out of a group, the dragging interaction will stop forcefully. This is likely due to the fact that instead of a single list, we render multiple lists using branch.js . If we like the inline swapping, I'll likely want to refactor this to remove branch.js, and instead favor looping over ListViewBlock using a selector that gives us a flat representation:

5268ff8#diff-aa92c48095c35f17feb834dd919baf52274a73c7b1180ff5acce7b6087b126cfR2236-R2263

dragging.forced.to.stop.mp4

We may also need to add a UI piece at the bottom of block containers to more easily allow drag interactions where we swap drag parents.

@mtias
Copy link
Member

mtias commented Aug 13, 2021

This is excellent!

@gwwar
Copy link
Contributor Author

gwwar commented Aug 13, 2021

Some quick videos of moving items out of a container group:

Moving up out of a group feels somewhat natural. The group item, acts as the swappable:

move.out.of.group.mp4

Moving down out of a group needs more thought. When we break out of a container, it should actually remain at the same item position (just with a different parent). Maybe worth inserting an "End group" item on drag so we can swap easily in and out? Or maybe more padding to containers?

Kapture.2021-08-13.at.13.55.56.mp4

Implementation Notes: Due to how List View is implemented, when moving items in or out of groups, we need to use a Shared drag session since it's moving to a different branch parent (and thus uses a different component). This is somewhat experimental. So far there's a bug around whileDrag styling not setting itself on init so we can see the scale jump from our drag scale (1.1) to (1.0) to (1.1) again when we move the mouse, so there's a bit of jitter. If this is easily reproducible I'll file an issue upstream.

@talldan
Copy link
Contributor

talldan commented Aug 17, 2021

Moving down out of a group needs more thought. When we break out of a container, it should actually remain at the same item position (just with a different parent). Maybe worth inserting an "End group" item on drag so we can swap easily in and out? Or maybe more padding to containers?

I think it seems good at the moment. From what I can tell, it's not possible right now to do the opposite and drag an item to inner blocks. Is that something I'm not doing right or a work in progress? It could be worth addressing that first because it's a closely related interaction that requires clearly showing the nesting level.

@gwwar
Copy link
Contributor Author

gwwar commented Aug 17, 2021

Is that something I'm not doing right or a work in progress?

More of a work in progress, I can tidy that. In terms of interactions, @talldan it sounds like you don't mind keeping a simple up/down drag interaction in containers (skipping over invalid items), vs another gesture (say left or right) to move up or down a level?

@talldan
Copy link
Contributor

talldan commented Aug 18, 2021

More of a work in progress, I can tidy that. In terms of interactions, @talldan it sounds like you don't mind keeping a simple up/down drag interaction in containers (skipping over invalid items), vs another gesture (say left or right) to move up or down a level?

I think that mostly is the best solution, but there are cases where some other gesture will probably be required:

Empty parent blocks

There needs to be a way to differentiate dropping a block after the parent vs dropping a block as an inner block of the parent.

There are some options, dragging to the right when hovering underneath an empty group (how it works now), or dropping on top of the parent block.

Nested structures where there's no sibling at the same level (#33678)

This is hard to describe but it's something unsolved in the implementation in trunk.

With a nested structure like this:
Screenshot 2021-08-18 at 9 44 03 am

If I want to drag and drop the image block into one of the first, second or third level groups, but after the paragraph, that requires some way for the user to indicate the depth.

The same situation happens when a group block with inner blocks is at the end of List View and a user might want to drop a block after the group.

@gwwar
Copy link
Contributor Author

gwwar commented Aug 18, 2021

@talldan So right now I'm testing drag right + up/down for picking a new parent, and left + up/down to break out of a parent. A straight up/down drag will swap with the next sibling if possible. It feels pretty natural so far, but I'll see if I hit any other corner cases.

left.right.mp4

The Navigation Block makes the need for alternate gestures pretty clear since each navigation link is nestable. This means that a simple up/down only interaction is pretty confusing since we can't guess the user's intent on if they mean to swap a sibling or nest it.

@gwwar
Copy link
Contributor Author

gwwar commented Aug 18, 2021

Empty parent blocks

✅ Here's what it looks like to drag left/right.

empty.group.mp4

Nested structures where there's no sibling at the same level

If I want to drag and drop the image block into one of the first, second or third level groups, but after the paragraph, that requires some way for the user to indicate the depth.

✅ This one is possible using two drags. @talldan were you thinking of this being possible with just one? Drag down + right to nest, then up/down on sibling to reorder.

The same situation happens when a group block with inner blocks is at the end of List View and a user might want to drop a block after the group.

✅ This one is covered with a simple up/down drag.

ordering.mp4

Navigation Example:

Navigation is interesting since each link is nestable:

nav.example.mp4

@gwwar
Copy link
Contributor Author

gwwar commented Aug 18, 2021

I think folks like the interaction scheme, some other things to work on next:

  • Untangle previous drag styling
  • Turn off dragging in other instances besides the persistent list view
  • Respect reduce motion
  • Simplify state management
  • Profile / make this more performant when rendering a larger post. (I'm probably doing some silly stuff at the moment). (Blocker).
  • Implement drag-hold scrolling near top/bottom edges. (Though we can maybe follow up with this).

@gwwar
Copy link
Contributor Author

gwwar commented Aug 18, 2021

Re: performance, one idea I might try is instead of modifying a cloned tree as we drag, we can see what happens if we update state to let child items add 36px padding or not to simulate where we intend to drop an item

@jasmussen
Copy link
Contributor

Took this for a spin. This is really very nice:

drag interactions

There are some small things that I imagine you're aware of since the PR is in draft, for example when first clicking and holding to move, it didn't take. It'd be nice to also use the grabby hand cursor as drag indicator (made a codepen here). Also need to tweak the interaction to avoid the horizontal scrollbar.

The moving left/right to drop in a nested context is generally feeling valid here. But it feels slightly less obvious since any item is able to move freely in all directions, matching that of the cursor. The alternative would be to restrict vertical movement to the current nesting level, kind of like how Google Keep does with lists:

list movement

I know we're a bit limited in emulating the full behavior on display here, I don't even think we want it with splitting and such. But just the restricting of the vertical axis might make the indent/outdent behavior more obvious. What do you think?

@gwwar
Copy link
Contributor Author

gwwar commented Aug 19, 2021

For folks 👀. As a heads up performance is a blocker for the current prototype. I'm working on seeing if I can retain this interaction behavior, but avoid needing to swap items in place as we drag. The other swap-in-place drag demos are toy examples with like 5 items, so this isn't unexpected that I need to throw out parts of this branch in favor of another implementation approach. I'm reserving some of the finer grain details for styling/polish for after that's resolved.

It'd be nice to also use the grabby hand cursor

@jasmussen 💯 that makes sense! Did you link to the right codepen? I'm seeing lorem ipsum text without any cursor styling. Also, do we have any thoughts as to whether to give this a more consistent UI drag hint, like the 6 dots?

Screen Shot 2021-08-19 at 9 06 07 AM

The alternative would be to restrict vertical movement to the current nesting level

So, constraining y-movement definitely makes sense in certain contexts (for example block children that aren't insertable in the root document like Button), but the wrinkle here is that our block relationships are more complex. I assume in Google keep all items are nestable under each other?

Columns is a good one example. Here, a Columns block may only insert a Column block. A Column block may insert a lot of content blocks. Usually a user might want to move content like a paragraph from one column to another, or move it out of the columns into the root document.

In this case constraining to the container is probably too restrictive. Likewise, I ruled out a pure left/right movement, since it leads to unpredictable behavior. For example if we pull left for this paragraph, did we mean to insert it above the Columns block, below it? Or something else altogether?

Screen Shot 2021-08-19 at 8 52 26 AM

@jasmussen
Copy link
Contributor

Did you link to the right codepen? I'm seeing lorem ipsum text without any cursor styling.

Ack, I had made a typo in the comment, so it broke. Fixed it, try again.

Also, do we have any thoughts as to whether to give this a more consistent UI drag hint, like the 6 dots?

Yes, I think we should start without any such interface as the drag and drop is purely additive. And if the grabby hand isn't sufficient (which I suspect it will be), one idea is to replace the block icon with the 6 dot grab handle on hover.

So, constraining y-movement definitely makes sense in certain contexts (for example block children that aren't insertable in the root document like Button), but the wrinkle here is that our block relationships are more complex. I assume in Google keep all items are nestable under each other?

Yep, it's definitely simpler in Keep where you only have a single nesting level and nesting is more of a visual "indentation" thing than actual nesting.

For example if we pull left for this paragraph, did we mean to insert it above the Columns block, below it? Or something else altogether?

That is indeed the wrinkle. I picture in your example, pulling left wouldn't do anything at all — pulling left would only work if it was the last block in the column block, and then it would land below the columns block.

And to be clear, I'm not entirely sure it would be a good experience — so if it turns out to be onerous to explore, we'll think of something — it's mostly an instinct that could live or die by prototype.

@gwwar
Copy link
Contributor Author

gwwar commented Aug 19, 2021

if it was the last block in the column block, and then it would land below the columns block.

👍 I think that aligns well with the idea for this interaction style. Mainly that someone will naturally try to move an item to align the icon under the parent they want. So this might be more around relaxing the requirements for making sure that some amount of y-movement has happened, since it's valid for certain cases.

I'm curious if we do need to think of providing some hint of feedback when we detect a gesture (say let's move to another parent, but let folks know it's an invalid move).

@gwwar
Copy link
Contributor Author

gwwar commented Aug 19, 2021

For folks 👀. As a heads up performance is a blocker for the current prototype. I'm working on seeing if I can retain this interaction behavior, but avoid needing to swap items in place as we drag.

The padding approach wasn't as promising as I would like. Going to fire up the profiler, and 🔪 going through some obvious things. Already spotted clientIdTrees here returning a new reference unnecessarily.

@gwwar
Copy link
Contributor Author

gwwar commented Aug 19, 2021

I think I've gotten down when to switch back to global state correctly. (We should no longer see items flying back on drop/to the new item). Typing should also reflect block changes in the sidebar.

fix.state.swapping.mp4

@jasmussen
Copy link
Contributor

I'm curious if we do need to think of providing some hint of feedback when we detect a gesture (say let's move to another parent, but let folks know it's an invalid move).

I'm always a fan of starting with the least we can. Not just to keep PRs as small as they can be, or even just to ship these sweet improvements sooner, but also in case we find some of the proposed changes unnecessary in practical use.

However if we do find the need to give feedback to "droppable areas", I do think we could use the blue line to indicate spaces where the item will end up, just like we do in the canvas. By virtue of that blue line not showing up as a direct descendant of the Columns (plural) block — unless of course you're dragging a Column block (singular), can hopefully help imply what's up.

@gwwar
Copy link
Contributor Author

gwwar commented Sep 23, 2021

I'm missing the technical term for this, but could we chunk up to only ever animate 32 blocks at a time, and so if you drag far enough, from a chunk of 32 blocks into an adjacent chunk of 32 blocks

A pretty natural approach for this component would be windowing (eg only render the items we see) but we haven't used it yet since there are some accessibility concerns with that approach. (Can't search all items / not a good way of knowing when more results are available with a screen reader).

I did try making a smarter item block update when dragging in 7efb06f. Try git checkout 7efb06fa74e6cdee91760a27bfd2e00776edf0c2; npm run dev; If you'd like to see if that helps with your perf test case.

@gwwar
Copy link
Contributor Author

gwwar commented Sep 23, 2021

Given how much improved the experience is as a whole, I think we should be willing to accept a fair bit here, given it's likely going to be a great experience in the majority of posts? Could we throttle some of the animations with more than n blocks?

If folks think it's good enough for most posts we can maybe leave some of the performance tuning for follow ups as well. Any thoughts here @draganescu @talldan @mtias ?

@jasmussen
Copy link
Contributor

Thanks, I'm taking it through the wringer with this one:
perf

I can't decide if that experimental commit is better or worse than the one I tested yesterday. It feels like starting and ending drag operations is perhaps slightly more stuttery, but the actual drag operation itself is a bit faster.

In both cases I personally think it's worth it.

(Can't search all items / not a good way of knowing when more results are available with a screen reader).

These are issues when the list is chunked up in its resting state, correct? Could the chunking happen only when you're actually dragging? This is definitely beyond my skill to fully comprehend so forgive me if this is off base.

Copy link
Contributor

@getdave getdave left a comment

Choose a reason for hiding this comment

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

First of all, super kudos to you for taking this on. It's very complex and it's going to be a amazing improving once it's done.

I gave this a spin on my older Mac (2.9 GHz 6-Core Intel Core i9 from 2018).

Unfortunately the experience was less smooth than I'd hope even without CPU throttling. As you should be able to see from the video below I noticed:

  • a "jankyness" when moving the dragged item over another item boundary.
  • a noticeable delay in "committing" the drag change on mouseup.
Screen.Capture.on.2021-09-24.at.09-51-29.mov

This is on a Macbook. Many people will have far less powerful machines even today.

When I tried with CPU throttling it became unusable for me and crashed my browser.

My gut is that we need more perf tuning here.

I did a quick profile and to me it looks like we're having the entire tree of [sidebar] "block" nodes re-render even though only a single block is being dragged. Am I right?

Screenshot 2021-09-24 at 09 57 45

I notice that each <ListViewBlock /> re-renders due to props changes:

  • draggingId changes on each render. Why is that? Is it not the ID of the block being dragged. Therefore it seems to be ripe for memoization of ListViewBlock via React.memo(). We can check (using the comparator 2nd argument) whether that ID really changes and if it doesn't we can skip the render for that "block".
  • Some of the changing props are new function references (eg: onClick, dragStart...etc) on each render. Could these be memoized via useCallback to ensure unchanging function refs? Currently I think that would be hard because it's within a map but with some restructuring perhaps we could get there?

You've probably considered all this but I thought I'd mention it.

@gwwar
Copy link
Contributor Author

gwwar commented Sep 24, 2021

@getdave did you try it with 7efb06f ? I removed the memo usages as the last commit, since I know we're pretty sensitive to adding those.

To make it easier, I'll just drop the last commit ;)

@gwwar
Copy link
Contributor Author

gwwar commented Sep 24, 2021

draggingId changes on each render.

Do you have a scenario where you see this? It should only happen on the initial drag and drop. (it changes from null to the clientId, and back to null on drop).

CleanShot.2021-09-24.at.09.53.55.mp4

@gwwar
Copy link
Contributor Author

gwwar commented Sep 24, 2021

With how much overhead each Framer Item has we may need to look into windowing next, where we limit the number of items we render (typically a little more than what's visible). For every person there's some point where rendering >X list items will cause performance issues.

Items in list view are fixed height which lends itself well to the problem. The downsides to this is additional complexity and unsolved a11y challenges. (How do screenreaders know that there are items next)?

@gwwar
Copy link
Contributor Author

gwwar commented Sep 24, 2021

Could the chunking happen only when you're actually dragging?

@jasmussen That's an interesting thought to virtualize only on drag. I'll keep that in mind.

I'm missing the technical term for this, but could we chunk up to only ever animate 32 blocks at a time

I also did a quick experiment with only setting the framer props on items ~20 away from the dragged item. It does help a bit during drag, but it can be unstable in some cases, so we may want to avoid this.

CleanShot 2021-09-24 at 12 24 07@2x

@gwwar
Copy link
Contributor Author

gwwar commented Oct 1, 2021

I have some early work for using windowing in #35230. I applied it to this branch in d5ace41

So as a proof of concept this helps quite a bit in terms of performance. The following video is of the performance test post that has more than 900 blocks.

However the technique here also makes some of framer's constraint system work against us and I'm seeing some elements fly in as we scroll. This suggests that I might need to turn off framer props while scrolling. Not a blocker, but definitely more to work through if we decide to use the windowing technique.

CleanShot.2021-10-04.at.09.12.57.mp4

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. [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.

7 participants