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

Hide drop indicator where block isn't allowed to drop. #56843

Merged
merged 16 commits into from Dec 21, 2023

Conversation

tellthemachines
Copy link
Contributor

@tellthemachines tellthemachines commented Dec 7, 2023

What?

Fixes #24174.

If a block isn't allowed to drop in a certain area, the drop indicator shouldn't show.

In addition, the draggable chip changes color and icon when hovering over an area where there are no valid drop targets.

Testing Instructions

  1. In the post or template editor, add a few block containers of different types (Group, Gallery, Navigation, List for example) and also add a few loose blocks of different types such as Paragraph and Image.
  2. Try dragging single blocks and also multiple selections of different blocks into the different containers.
  3. Check that drop indicator only shows if all selected blocks being dragged are allowed in the container.
  4. Try dragging single and multiple blocks around at root level and check this always works well.
  5. Try dragging blocks that should only exist inside certain containers (List Item, Navigation Link for example) outside of their containers - no drop indicators should show.

Note the transparency of the draggable chip isn't yet quite in sync with the display of valid drop targets. This can be fine-tuned but it would be great to get some initial feedback on the general approach first!
Update: changes to draggable chip should now be in sync with showing the drop indicator.

Screenshots or screencast

droppingdisabled.mov
dropping-buttons.mov

Copy link

github-actions bot commented Dec 7, 2023

Size Change: +570 B (0%)

Total Size: 1.71 MB

Filename Size Change
build/block-editor/index.min.js 246 kB +463 B (0%)
build/block-editor/style-rtl.css 15.3 kB +129 B (+1%)
build/block-editor/style.css 15.3 kB +129 B (+1%)
build/edit-site/index.min.js 194 kB -93 B (0%)
build/edit-site/style-rtl.css 14.9 kB -31 B (0%)
build/edit-site/style.css 14.9 kB -27 B (0%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 964 B
build/annotations/index.min.js 2.71 kB
build/api-fetch/index.min.js 2.29 kB
build/autop/index.min.js 2.11 kB
build/blob/index.min.js 590 B
build/block-directory/index.min.js 7.25 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.29 kB
build/block-editor/content.css 4.28 kB
build/block-editor/default-editor-styles-rtl.css 403 B
build/block-editor/default-editor-styles.css 403 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 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 104 B
build/block-library/blocks/avatar/style.css 104 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 419 B
build/block-library/blocks/button/editor.css 417 B
build/block-library/blocks/button/style-rtl.css 633 B
build/block-library/blocks/button/style.css 632 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 113 B
build/block-library/blocks/categories/editor.css 112 B
build/block-library/blocks/categories/style-rtl.css 124 B
build/block-library/blocks/categories/style.css 124 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 421 B
build/block-library/blocks/columns/style.css 421 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 647 B
build/block-library/blocks/cover/editor.css 650 B
build/block-library/blocks/cover/style-rtl.css 1.7 kB
build/block-library/blocks/cover/style.css 1.69 kB
build/block-library/blocks/details/editor-rtl.css 65 B
build/block-library/blocks/details/editor.css 65 B
build/block-library/blocks/details/style-rtl.css 98 B
build/block-library/blocks/details/style.css 98 B
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 316 B
build/block-library/blocks/file/editor.css 316 B
build/block-library/blocks/file/style-rtl.css 280 B
build/block-library/blocks/file/style.css 281 B
build/block-library/blocks/file/view.min.js 322 B
build/block-library/blocks/footnotes/style-rtl.css 201 B
build/block-library/blocks/footnotes/style.css 199 B
build/block-library/blocks/form-input/editor-rtl.css 229 B
build/block-library/blocks/form-input/editor.css 228 B
build/block-library/blocks/form-input/style-rtl.css 343 B
build/block-library/blocks/form-input/style.css 343 B
build/block-library/blocks/form-submission-notification/editor-rtl.css 343 B
build/block-library/blocks/form-submission-notification/editor.css 342 B
build/block-library/blocks/form-submit-button/style-rtl.css 69 B
build/block-library/blocks/form-submit-button/style.css 69 B
build/block-library/blocks/form/view.min.js 452 B
build/block-library/blocks/freeform/editor-rtl.css 2.61 kB
build/block-library/blocks/freeform/editor.css 2.61 kB
build/block-library/blocks/gallery/editor-rtl.css 957 B
build/block-library/blocks/gallery/editor.css 962 B
build/block-library/blocks/gallery/style-rtl.css 1.75 kB
build/block-library/blocks/gallery/style.css 1.75 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 189 B
build/block-library/blocks/heading/style.css 189 B
build/block-library/blocks/html/editor-rtl.css 340 B
build/block-library/blocks/html/editor.css 341 B
build/block-library/blocks/image/editor-rtl.css 834 B
build/block-library/blocks/image/editor.css 833 B
build/block-library/blocks/image/style-rtl.css 1.61 kB
build/block-library/blocks/image/style.css 1.6 kB
build/block-library/blocks/image/theme-rtl.css 137 B
build/block-library/blocks/image/theme.css 137 B
build/block-library/blocks/image/view.min.js 2.02 kB
build/block-library/blocks/latest-comments/style-rtl.css 357 B
build/block-library/blocks/latest-comments/style.css 357 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 505 B
build/block-library/blocks/media-text/style.css 503 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 671 B
build/block-library/blocks/navigation-link/editor.css 672 B
build/block-library/blocks/navigation-link/style-rtl.css 103 B
build/block-library/blocks/navigation-link/style.css 103 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.26 kB
build/block-library/blocks/navigation/editor.css 2.26 kB
build/block-library/blocks/navigation/style-rtl.css 2.27 kB
build/block-library/blocks/navigation/style.css 2.26 kB
build/block-library/blocks/navigation/view.min.js 1.04 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 401 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 235 B
build/block-library/blocks/paragraph/editor.css 235 B
build/block-library/blocks/paragraph/style-rtl.css 335 B
build/block-library/blocks/paragraph/style.css 335 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 508 B
build/block-library/blocks/post-comments-form/style.css 508 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 71 B
build/block-library/blocks/post-excerpt/editor.css 71 B
build/block-library/blocks/post-excerpt/style-rtl.css 141 B
build/block-library/blocks/post-excerpt/style.css 141 B
build/block-library/blocks/post-featured-image/editor-rtl.css 666 B
build/block-library/blocks/post-featured-image/editor.css 662 B
build/block-library/blocks/post-featured-image/style-rtl.css 345 B
build/block-library/blocks/post-featured-image/style.css 345 B
build/block-library/blocks/post-navigation-link/style-rtl.css 215 B
build/block-library/blocks/post-navigation-link/style.css 214 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 409 B
build/block-library/blocks/post-template/style.css 408 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-time-to-read/style-rtl.css 69 B
build/block-library/blocks/post-time-to-read/style.css 69 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 125 B
build/block-library/blocks/preformatted/style.css 125 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 335 B
build/block-library/blocks/pullquote/style.css 335 B
build/block-library/blocks/pullquote/theme-rtl.css 168 B
build/block-library/blocks/pullquote/theme.css 168 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 486 B
build/block-library/blocks/query/editor.css 486 B
build/block-library/blocks/query/style-rtl.css 312 B
build/block-library/blocks/query/style.css 308 B
build/block-library/blocks/query/view.min.js 647 B
build/block-library/blocks/quote/style-rtl.css 237 B
build/block-library/blocks/quote/style.css 237 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 140 B
build/block-library/blocks/read-more/style.css 140 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 184 B
build/block-library/blocks/search/editor.css 184 B
build/block-library/blocks/search/style-rtl.css 613 B
build/block-library/blocks/search/style.css 613 B
build/block-library/blocks/search/theme-rtl.css 114 B
build/block-library/blocks/search/theme.css 114 B
build/block-library/blocks/search/view.min.js 475 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 329 B
build/block-library/blocks/shortcode/editor.css 329 B
build/block-library/blocks/site-logo/editor-rtl.css 760 B
build/block-library/blocks/site-logo/editor.css 760 B
build/block-library/blocks/site-logo/style-rtl.css 204 B
build/block-library/blocks/site-logo/style.css 204 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 682 B
build/block-library/blocks/social-links/editor.css 681 B
build/block-library/blocks/social-links/style-rtl.css 1.49 kB
build/block-library/blocks/social-links/style.css 1.49 kB
build/block-library/blocks/spacer/editor-rtl.css 359 B
build/block-library/blocks/spacer/editor.css 359 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 399 B
build/block-library/blocks/table/editor.css 399 B
build/block-library/blocks/table/style-rtl.css 646 B
build/block-library/blocks/table/style.css 645 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 403 B
build/block-library/blocks/template-part/editor.css 403 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/style-rtl.css 111 B
build/block-library/blocks/term-description/style.css 111 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 191 B
build/block-library/blocks/video/style.css 191 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 12.3 kB
build/block-library/editor.css 12.3 kB
build/block-library/elements-rtl.css 54 B
build/block-library/elements.css 54 B
build/block-library/index.min.js 214 kB
build/block-library/reset-rtl.css 472 B
build/block-library/reset.css 472 B
build/block-library/style-rtl.css 14.7 kB
build/block-library/style.css 14.7 kB
build/block-library/theme-rtl.css 700 B
build/block-library/theme.css 705 B
build/block-serialization-default-parser/index.min.js 1.13 kB
build/block-serialization-spec-parser/index.min.js 2.87 kB
build/blocks/index.min.js 51.3 kB
build/commands/index.min.js 15.5 kB
build/commands/style-rtl.css 947 B
build/commands/style.css 942 B
build/components/index.min.js 257 kB
build/components/style-rtl.css 12.1 kB
build/components/style.css 12.1 kB
build/compose/index.min.js 12.8 kB
build/core-commands/index.min.js 2.73 kB
build/core-data/index.min.js 72.7 kB
build/customize-widgets/index.min.js 12.1 kB
build/customize-widgets/style-rtl.css 1.36 kB
build/customize-widgets/style.css 1.36 kB
build/data-controls/index.min.js 651 B
build/data/index.min.js 8.94 kB
build/date/index.min.js 17.9 kB
build/deprecated/index.min.js 462 B
build/dom-ready/index.min.js 336 B
build/dom/index.min.js 4.68 kB
build/edit-post/classic-rtl.css 571 B
build/edit-post/classic.css 571 B
build/edit-post/index.min.js 31.3 kB
build/edit-post/style-rtl.css 7.16 kB
build/edit-post/style.css 7.15 kB
build/edit-widgets/index.min.js 17.7 kB
build/edit-widgets/style-rtl.css 4.71 kB
build/edit-widgets/style.css 4.71 kB
build/editor/index.min.js 55.2 kB
build/editor/style-rtl.css 4.38 kB
build/editor/style.css 4.38 kB
build/element/index.min.js 4.87 kB
build/escape-html/index.min.js 548 B
build/format-library/index.min.js 7.76 kB
build/format-library/style-rtl.css 577 B
build/format-library/style.css 577 B
build/hooks/index.min.js 1.57 kB
build/html-entities/index.min.js 454 B
build/i18n/index.min.js 3.61 kB
build/interactivity/file.min.js 442 B
build/interactivity/image.min.js 2.15 kB
build/interactivity/index.min.js 12.5 kB
build/interactivity/navigation.min.js 1.16 kB
build/interactivity/query.min.js 791 B
build/interactivity/search.min.js 610 B
build/is-shallow-equal/index.min.js 535 B
build/keyboard-shortcuts/index.min.js 1.76 kB
build/keycodes/index.min.js 1.49 kB
build/list-reusable-blocks/index.min.js 2.11 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.92 kB
build/modules/importmap-polyfill.min.js 12.2 kB
build/notices/index.min.js 964 B
build/nux/index.min.js 2.01 kB
build/nux/style-rtl.css 775 B
build/nux/style.css 771 B
build/patterns/index.min.js 5.27 kB
build/patterns/style-rtl.css 564 B
build/patterns/style.css 564 B
build/plugins/index.min.js 1.81 kB
build/preferences-persistence/index.min.js 1.85 kB
build/preferences/index.min.js 1.26 kB
build/primitives/index.min.js 994 B
build/priority-queue/index.min.js 1.52 kB
build/private-apis/index.min.js 994 B
build/react-i18n/index.min.js 631 B
build/react-refresh-entry/index.min.js 9.46 kB
build/react-refresh-runtime/index.min.js 6.78 kB
build/redux-routine/index.min.js 2.71 kB
build/reusable-blocks/index.min.js 2.74 kB
build/reusable-blocks/style-rtl.css 265 B
build/reusable-blocks/style.css 265 B
build/rich-text/index.min.js 10.4 kB
build/router/index.min.js 1.79 kB
build/server-side-render/index.min.js 1.96 kB
build/shortcode/index.min.js 1.4 kB
build/style-engine/index.min.js 1.98 kB
build/token-list/index.min.js 587 B
build/url/index.min.js 3.83 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 967 B
build/warning/index.min.js 259 B
build/widgets/index.min.js 7.18 kB
build/widgets/style-rtl.css 1.18 kB
build/widgets/style.css 1.18 kB
build/wordcount/index.min.js 1.03 kB

compressed-size-action

Copy link

github-actions bot commented Dec 7, 2023

Flaky tests detected in 240cd39.
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/7284552776
📝 Reported issues:

@tellthemachines tellthemachines self-assigned this Dec 7, 2023
@tellthemachines tellthemachines added [Type] Bug An existing feature does not function as intended [Feature] Drag and Drop Drag and drop functionality when working with blocks labels Dec 7, 2023
@tellthemachines tellthemachines marked this pull request as ready for review December 12, 2023 05:43
Copy link
Contributor

@andrewserong andrewserong left a comment

Choose a reason for hiding this comment

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

Nice work, it feels much better not showing a drop indicator when you can't drop somewhere to me, and I also don't mind the opacity to flag that a drop isn't available. It's really nice seeing some visual feedback between a valid area and a not valid area 👍

Some of my review comments might be a bit out of order, I'm sorry, so I'll try to line up some of my thinking as I was looking through the code change:

Instead of toggling disabling the drop zone altogether, would it be possible to call hideInsertionPoint instead, from within the throttled useCallback in useBlockDropZone (or update the showInsertionPoint call to pass in null)? I haven't used that before, but from looking at the BlockDropZonePopover component, it looks like it checks that state here, so I was wondering if that might be a simpler way to hide the drop indicator? (I'm assuming that when it's hidden in state, that results in the drop indicator disappearing altogether, but I haven't tested it)

Then, potentially BlockDraggable could also use getBlockInsertionPoint to see if it's nullish? That might also help with syncing between the two bits of code, as the opacity change would only happen when the useCallback fires the changes in useBlockDropZone 🤔

I'm not sure if all that would work, there's quite likely a bunch of complexity there that I'm not aware of, so apologies if that's not a helpful idea, or if it's something you already tried while exploring this!

* @param {string} targetClientId The client id of the target.
* @return {boolean} Whether the dragged blocks can be dropped on the target.
*/
export function useIsDropTargetValid( draggedBlockClientIds, targetClientId ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This hook appears to get called even when a drag event isn't happening. Is it possible to move some of the logic to the throttled useCallback so that we only figure out the allowed blocks in the throttled function that gets called while a user is dragging? Ideally it'd be good to limit what gets called before the drag actions occur, but it might not be possible to move everything over 🤔

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 could be refactored to a simple function taking getBlockType, allowedBlocks, draggedBlockNames, targetBlockName as parameters and then called inside the throttle. I'm unsure if determining isBlockDroppingAllowed inside the throttle will cause any lag but let's give it a try 😄

count={ clientIds.length }
icon={ icon }
clientIds={ clientIds }
targetClientId={ targetClientId }
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this addition currently doing anything? It looks like BlockDraggableChip isn't currently using it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah I must have left that in while experimenting 😄


const [ targetClientId, setTargetClientId ] = useState( null );

const isDropTargetValid = useIsDropTargetValid( clientIds, targetClientId );
Copy link
Contributor

Choose a reason for hiding this comment

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

Does the BlockDraggable component need to know whether the drop target is valid? Could we use the getBlockInsertionPoint state to figure out if an insertion point is currently set instead?

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 don't think so; given that getBlockInsertionPoint is always meant to return an index, it would be hard to work out which targets are valid.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, gotcha! Looks like there's another selector isBlockInsertionPointVisible which handles the null case. Could that work if we'd called hideInsertionPoint in the main throttled useCallback in useBlockDropZone?

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 find! Lemme try that out

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok checking if the insertion point is visible does fix the discrepancy between transparent drag chip and inserter, but I found it necessary to keep the check for valid insertion point in place, because otherwise the chip keeps flickering every time the insertion point switches places. Still it's working much better now so thanks for the tip!

// and if it's a container block
if (
targetClientId !== newTargetClientId &&
getBlockListSettings( newTargetClientId )
Copy link
Member

Choose a reason for hiding this comment

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

Is getBlockListSettings( newTargetClientId ) checking for nested blocks to see if it's a container?

I think calling a select outside useSelect is an anti-pattern as it risks not returning fresh data when the store is updated.

I tested by updating the list settings for a block via wp.data.dispatch('core/block-editor').updateBlockListSettings() and it looks like the return state is updated, I guess because a rerender is triggered by another prop. So it's working in that case as far as I can tell.

cc @jsnajdr in case I missed something.

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 don't think it's an anti-pattern, we use select functions outside of useSelect all over the place 😅

getBlockListSettings was the most reliable way I found of checking whether a block is a container, given that only containers are block lists (and they might not have any inner blocks if they're empty). I'm definitely open to ideas on this one though 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually scratch that, I don't think we need to check for the container anymore 😂 now we're using the inserter visibility as per @andrewserong's suggestion.

Still keen to hear about preferred code patterns though 👀

Copy link
Member

Choose a reason for hiding this comment

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

Here getBlockListSettings is called in an event handler (onDragOver) and that's fine. We want to read the fresh value when the event is being dispatched, and that's what happens here. No rerenders need to be triggered when the value changes.

The only thing I would change here is to write the code, for clarity, as two separate useSelect calls:

const { getBlockListSettings } = useSelect( blockEditorStore );
// or alternatively
const { getBlockListSettings } = useRegistry().select( blockEditorStore );

const { ...rest } = useSelect( ( select ) => ... );

One of them just gets a reference to the selector, without any store subscriptions or reactivity, and the second reads data in the usual reactive way.

Copy link
Member

@ramonjd ramonjd Dec 13, 2023

Choose a reason for hiding this comment

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

Thanks for confirming and setting me straight on that one, @jsnajdr!

And sorry about the misdirection @tellthemachines

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok thanks for the clarification!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The only thing I would change here is to write the code, for clarity, as two separate useSelect calls

There are other selectors we're grabbing directly and using inside the effect such as getBlockType and getAllowedBlocks. I guess following that logic they should all be in a separate useSelect call?

@ramonjd
Copy link
Member

ramonjd commented Dec 13, 2023

This a sweet improvement. 🎉

I can drop things anywhere in trunk!

2023-12-13.15.25.28.mp4

This PR has more sensible feedback:

2023-12-13.15.08.43.mp4

I just pinged about a possible risk of calling the getBlockListSettings selector outside of useSelect - might be nothing.

@tellthemachines
Copy link
Contributor Author

Thanks for the feedback and testing folks! I think all the code suggestions have been addressed so far. The experience is much improved but I'm now seeing a slight flakiness when moving over large non-container blocks such as Image. I think that might be due to the removal of the check for container status with getBlockListSettings. I removed it because it was outputting a false negative for the root container, but might have to think of another way of not flagging discrete blocks as "undroppable" 🤔

Copy link
Contributor

@andrewserong andrewserong 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 testing pretty well for me! I did notice a bit of inconsistency with when the opacity styles are applied when dragging from the list view. Sometimes the drag chip was greyed out, and sometimes it wasn't. A subtle bit was when dragging between Image blocks in a Gallery block where it flickers a little:

2023-12-14.10.25.02.mp4

The main issue I ran into is a performance issue while dragging over blocks — I think it's the onDragOver event in BlockDraggable, which will likely need to be throttled? (Or alternately removed, and consolidate the show/hide in useBlockDropZone's callback)

2023-12-14.10.31.45.mp4

Another potential option, if it proves to be tricky to resolve the flickering with the opacity issue, could be to split this PR into two: one PR to implement the hiding the drop indicator line, and then a separate follow-up PR to look at the opacity change for the drag chip?

<div className="block-editor-block-draggable-chip-wrapper">
<div
className="block-editor-block-draggable-chip-wrapper"
style={ { opacity: 'var(--wp--block-draggable-valid-opacity)' } }
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this applied via an inline style and CSS variable instead of by classname and direct value in style.scss? Or to put it slightly differently, does the JS version of the component need to have an opinion about how it's styled?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question! I don't think it has to be applied as an inline style; I initially added it that way for the sake of development speed. It does have to be a CSS custom property, because its value needs to change when the body class gets added, and given that the chip we see on the page is a stored copy of itself changing classes on the chip itself won't work. Essentially the custom property is acting like a ref to the element, so we can manipulate its value from outside.

Copy link
Contributor

Choose a reason for hiding this comment

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

It does have to be a CSS custom property, because its value needs to change when the body class gets added

Oh, I was imagining something like the following:

.block-editor-block-draggable-chip {
	opacity: 1;
}

.block-draggable-invalid-drag-token {
	.block-editor-block-draggable-chip {
		opacity: 0.6;
	}
}

Or does that not work? (Totally fine to go with a CSS variable by the way, just trying to understand what the limitations are 🙂)

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 right, yeah that should work too! Would that be preferable to a custom property? I don't think specificity matters either way.

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 feel strongly either way, but I'd lean slightly more toward the nested selectors so that it's easy to swap out for (or combine) other styles like background color, border, or other subtle styling changes that folks might want to explore in the future.

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 good point, yeah that would make it easier to change styles altogether if we wanted to.

return;
}

const onDragOver = ( event ) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this callback might need to be throttled similar to the code in useBlockDropZone as this currently fires very frequently:

2023-12-14.10.37.55.mp4

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hrm yeah I'm wondering how best to do that given the inability to use hooks inside the effect callback 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

I think I ran into this issue when testing, it seemed to crash in Safari and I couldn't move the chip any more.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oo wait I see there's a throttle util in compose that we may be able to leverage, will try 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.

Ok the throttle seems to make it better! I set it to the same as draggable.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's a little better, but it's still lagging a bit for me with the throttle at 16 (it's more visible when using CPU throttling in dev tools). I think a couple of things might be at play:

  • Since the useEffect doesn't use a dependency array, it seems that it's being called quite frequently, and potentially adding (and removing) event listeners too frequently? For example, if I increase the throttle time to 2000, it still appears to be firing rapidly
  • In draggable, 16ms is needed because it's updating the position of the drag chip. Here, I'm wondering if we can use a bigger delay / throttle, if that could smooth over the flicker when dragging between image blocks in a gallery block? I.e. if we had it at 200ms to match useBlockDropZone then if folks are dragging quickly between blocks, hopefully it shouldn't flicker as much?

Would it work to maybe move defining the onDragOver callback to be outside of the useEffect (possibly in a useCallback) so that the useEffect could have a dependency array and only attach the event listener once? Apologies if you've already tried 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.

So I had a think about this 😄 and realised that we actually don't need to use local state for targetClientId, and we can use a dependency array for the effect, which improves things a little, but it still has to re-render whenever either draggedBlockNames or visibleInserter change. Moving the callback out of the effect makes no difference, because it calls whichever version of the callback was attached to the event listener when the effect ran, so if we want the values inside the callback to change the effect has to re-run.

I also tried increasing the throttle time and it doesn't seem to make things any worse, so might leave it at 200 or so. I just pushed an update, let me know if it improves things at all!

Copy link
Contributor

Choose a reason for hiding this comment

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

Nice work! That appears to have fixed up the performance issues for me. Subjectively, without CPU throttling, it's feeling smooth now, and with CPU throttling, it seems much the same as trunk to me now 🎉

// Add a dragover event listener to the editor root to track the blocks being dragged over.
// The listener has to be inside the editor iframe otherwise the target isn't accessible.
// Check if the dragged blocks are allowed inside the target. If not, grey out the draggable.
useEffect( () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this useEffect and its onDragOver still needed? Since the useBlockDropZone is hiding the insertion point, could we replace the whole useEffect with something like:

	useEffect( () => {
		if ( ! visibleInserter ) {
			window?.document?.body?.classList?.add(
				'block-draggable-invalid-drag-token'
			);
		} else {
			window?.document?.body?.classList?.remove(
				'block-draggable-invalid-drag-token'
			);
		}
	}, [ visibleInserter ] );

Then the logic of when to show and hide the insertion point could live in one place, so that the BlockDraggable doesn't have to also perform checks with its own drag event handler.

Or is there another reason why this component needs to perform its own separate checks?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, so the crucial bit here is finding the targetClientId, which tells us the container that the draggable is currently hovering over. To find that we need a dragover event listener attached to the inside of the editor iframe (if it's on the outside, it won't be able to access the elements inside the iframe). We need the targetClientId to check if the drop target is valid, which is necessary because if the chip is hovering over a valid container but there's no drop indicator currently showing (if we're moving between drop areas for instance), we don't want it to grey out.

Unfortunately there's not much that can be done to restrict how many times this runs without slowing down the responsiveness of the drag chip to what it's hovering over. The problem here is that, unlike with the drop indicators that exist statically between all blocks so we know where they're placed from the start, we need to find out what the draggable is being dragged over, and afaik the only way to do that is through the event target of a dragover listener.

Copy link
Contributor

Choose a reason for hiding this comment

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

We need the targetClientId to check if the drop target is valid, which is necessary because if the chip is hovering over a valid container but there's no drop indicator currently showing (if we're moving between drop areas for instance), we don't want it to grey out.

Ah, gotcha! Thanks for the explanation, that makes sense now 👍

Unfortunately there's not much that can be done to restrict how many times this runs without slowing down the responsiveness of the drag chip to what it's hovering over.

Yeah, it can be really tricky to get the right balance when slowing things down so that it's slow enough for performance reasons, but fast enough to feel responsive to a user! 😅

we need to find out what the draggable is being dragged over, and afaik the only way to do that is through the event target of a dragover listener.

That sounds about right to me, too. If it winds up being a bit of a rabbithole trying to get it working smoothly, would it be worth splitting out the useBlockDropZone change and landing that first? Totally cool if you want to keep it all together while figuring everything out of course!

@tellthemachines
Copy link
Contributor Author

Thanks for the further testing and feedback!

A subtle bit was when dragging between Image blocks in a Gallery block where it flickers a little

Yeah I think this is the same issue I spotted yesterday evening and commented on here.

@richtabor
Copy link
Member

The fade out works well, but what about also using an icon that is universally considered to mean "no" or "not possible":

I like this idea too. I'd say combined with the likes of #56843 (comment) would communicate strongly.

@tellthemachines
Copy link
Contributor Author

Thanks for the suggestions folks! There doesn't seem to be a forbidden icon exactly like the one @apeatling suggests in our icon library so I've used our cancel one for now. I'm not sure it works as well but we can use it to test out the idea. How would we go about adding the forbidden one? I'm guessing a new one would have to be designed to fit in with our icon aesthetic?

@apeatling
Copy link
Contributor

apeatling commented Dec 18, 2023

Oh interesting. I found it in the WordPress design library Figma. It's called "no-swatch". I did increase the stroke width slightly.

Screenshot 2023-12-18 at 9 09 51 AM

@tellthemachines
Copy link
Contributor Author

It's called "no-swatch". I did increase the stroke width slightly.

Oooh I see, it's not an actual icon but a styled span:
Screenshot 2023-12-19 at 10 34 52 am
Guess we can duplicate those styles for now.

@tellthemachines
Copy link
Contributor Author

Ok updated to use a span styled like a white, slightly thicker version of the no-swatch design!

@SaxonF
Copy link
Contributor

SaxonF commented Dec 19, 2023

Testing really well for me. Massive improvement over what's in trunk. From a design perspective I think we can :shipit:

button-drag.mp4

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.

Looks great, much better and clearer now! I do wish the performance of the crossfade was better, it feels sluggish to me. I think this is shippable, but it would be great to follow up and either get that really smooth, or remove the transition.

Copy link
Contributor

@andrewserong andrewserong left a comment

Choose a reason for hiding this comment

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

Looks great, much better and clearer now!

Yes, visually this is looking terrific! No issues or conflicts with the list view anymore, either 👍

I'm a bit hesitant about the performance side of things as the crossfade issue sounds a bit like there could be more event firing happening than is intended. Given that there's been quite a few performance improvements in the editor landing of late, it might be worth double-checking with @ellatrix and @youknowriad to see if the dragover event listener sounds okay to go with for now. Since there can be multiple BlockDraggable components rendered on the page at a time, I'm wondering if it's possible that we're seeing competing callbacks firing, even though each individual callback is throttled?

Other than that performance issue, this is looking great to me! 🎉

@tellthemachines
Copy link
Contributor Author

I do wish the performance of the crossfade was better, it feels sluggish to me

Could you share a visual of this? I'm finding it really hard to reproduce even with CPU throttling. Is it just about the CSS transition or the general movement of the chip?

Copy link
Contributor

@andrewserong andrewserong left a comment

Choose a reason for hiding this comment

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

Not sure if this is the same as Andy was seeing, but it is a little stuttery for me. A bit hard to show in a screengrab, but here's one I captured with CPU throttling (site editor with the list view opened):

2023-12-19.16.26.30.mp4

When hovering over the paragraph block, the crossed out icon appears to flicker on for a moment before going away.

In terms of the movement of the chip, anecdotally, after re-testing trunk it seems that the drag chip performance when dragging over the editor canvas feels much the same to me between this PR and trunk with CPU throttling enabled.

@tellthemachines
Copy link
Contributor Author

Ooooh ok, that stuttering isn't a performance issue, it's because any block that's not a container currently counts as an area that's not droppable in. I previously tried making sure the droppable areas were containers as a workaround for this, but that didn't work for root level areas, because drop indicators at root level seem to be attached to their sibling block client ids, as opposed to their containers.

I couldn't find a reliable workaround for this. Open to ideas!

@jasmussen
Copy link
Contributor

Nice improvements!

@apeatling
Copy link
Contributor

Could you share a visual of this? I'm finding it really hard to reproduce even with CPU throttling. Is it just about the CSS transition or the general movement of the chip?

Not the movement of the chip, it's the crossfade between the two states within the chip:

stutter.mov

@andrewserong
Copy link
Contributor

I couldn't find a reliable workaround for this. Open to ideas!

Not the movement of the chip, it's the crossfade between the two states within the chip:

What a tricky bit of detail! I haven't had much luck digging further into either of these so far, but something I observed with the stuttering is that it looks like the classnames flicker on and off a couple of times when hovering over a block. So that might be one part of the stuttering?

2023-12-20.15.55.11.mp4

@andrewserong
Copy link
Contributor

Update: I have a hunch that I think might fix the stuttering, but in the process further exposes the issue of the disabled status being flagged when dragging over individual blocks. Here's what I could get running locally:

2023-12-20.16.29.45.mp4

As far as I can tell, it seems that the use of the useEffect is resulting in onDragOver being fired too frequently, which I think is the cause of the stuttering / the classnames flickering on and off. One way to work around this could be to remove the useEffect and instead add a couple of small functions for attaching and removing the event listeners:

	const startDragOverBehavior = () => {
		editorRoot?.addEventListener( 'dragover', throttledOnDragOver );
	};

	const endDropOverBehavior = () => {
		editorRoot?.removeEventListener( 'dragover', throttledOnDragOver );
	};

Then, further down in the component we'd call these in the Draggable component's onDragStart and onDragEnd callbacks respectively. That way the event listener is only registered once, and only for the block being dragged.

In order to get onDragOver working without the useEffect, I then needed to construct draggedBlockNames within the onDragOver callback instead of outside it, so that we're not dependent on values that change, and can have a stable function for the throttle behaviour.

Here's a diff of where I got up to:

Diff of hacking around removing the `useEffect`
diff --git a/packages/block-editor/src/components/block-draggable/index.js b/packages/block-editor/src/components/block-draggable/index.js
index e5945841d1e..d509337d439 100644
--- a/packages/block-editor/src/components/block-draggable/index.js
+++ b/packages/block-editor/src/components/block-draggable/index.js
@@ -31,8 +31,9 @@ const BlockDraggable = ( {
 		visibleInserter,
 		getBlockType,
 		getAllowedBlocks,
-		draggedBlockNames,
+		// draggedBlockNames,
 		getBlockNamesByClientId,
+		getDraggedBlockClientIds,
 	} = useSelect(
 		( select ) => {
 			const {
@@ -41,7 +42,7 @@ const BlockDraggable = ( {
 				getBlockName,
 				getBlockAttributes,
 				isBlockInsertionPointVisible,
-				getDraggedBlockClientIds,
+				getDraggedBlockClientIds: _getDraggedBlockClientIds,
 				getBlockNamesByClientId: _getBlockNamesByClientId,
 				getAllowedBlocks: _getAllowedBlocks,
 			} = select( blockEditorStore );
@@ -62,9 +63,10 @@ const BlockDraggable = ( {
 				getBlockType: _getBlockType,
 				getAllowedBlocks: _getAllowedBlocks,
 				draggedBlockNames: _getBlockNamesByClientId(
-					getDraggedBlockClientIds()
+					_getDraggedBlockClientIds()
 				),
 				getBlockNamesByClientId: _getBlockNamesByClientId,
+				getDraggedBlockClientIds: _getDraggedBlockClientIds,
 			};
 		},
 		[ clientIds ]
@@ -90,63 +92,53 @@ const BlockDraggable = ( {
 	const blockRef = useBlockRef( clientIds[ 0 ] );
 	const editorRoot = blockRef.current?.closest( 'body' );

-	// Add a dragover event listener to the editor root to track the blocks being dragged over.
-	// The listener has to be inside the editor iframe otherwise the target isn't accessible.
-	// Check if the dragged blocks are allowed inside the target. If not, grey out the draggable.
-	useEffect( () => {
-		if ( ! editorRoot || ! fadeWhenDisabled ) {
+	const onDragOver = ( event ) => {
+		if ( ! event.target.closest( '[data-block]' ) ) {
 			return;
 		}

-		const onDragOver = ( event ) => {
-			if ( ! event.target.closest( '[data-block]' ) ) {
-				return;
-			}
-
-			const targetClientId = event.target
-				.closest( '[data-block]' )
-				.getAttribute( 'data-block' );
-
-			const allowedBlocks = getAllowedBlocks( targetClientId );
-			const targetBlockName = getBlockNamesByClientId( [
-				targetClientId,
-			] )[ 0 ];
-			const dropTargetValid = isDropTargetValid(
-				getBlockType,
-				allowedBlocks,
-				draggedBlockNames,
-				targetBlockName
+		const targetClientId = event.target
+			.closest( '[data-block]' )
+			.getAttribute( 'data-block' );
+
+		const draggedBlockNames = getBlockNamesByClientId(
+			getDraggedBlockClientIds()
+		);
+
+		const allowedBlocks = getAllowedBlocks( targetClientId );
+		const targetBlockName = getBlockNamesByClientId( [
+			targetClientId,
+		] )[ 0 ];
+		const dropTargetValid = isDropTargetValid(
+			getBlockType,
+			allowedBlocks,
+			draggedBlockNames,
+			targetBlockName
+		);
+
+		// Update the body class to reflect if drop target is valid.
+		// This has to be done on the document body because the draggable
+		// chip is rendered outside of the editor iframe.
+		if ( dropTargetValid && ! visibleInserter ) {
+			window?.document?.body?.classList?.add(
+				'block-draggable-invalid-drag-token'
 			);
+		} else {
+			window?.document?.body?.classList?.remove(
+				'block-draggable-invalid-drag-token'
+			);
+		}
+	};

-			// Update the body class to reflect if drop target is valid.
-			// This has to be done on the document body because the draggable
-			// chip is rendered outside of the editor iframe.
-			if ( ! dropTargetValid && ! visibleInserter ) {
-				window?.document?.body?.classList?.add(
-					'block-draggable-invalid-drag-token'
-				);
-			} else {
-				window?.document?.body?.classList?.remove(
-					'block-draggable-invalid-drag-token'
-				);
-			}
-		};
-
-		const throttledOnDragOver = throttle( onDragOver, 200 );
+	const throttledOnDragOver = throttle( onDragOver, 200 );

-		editorRoot.addEventListener( 'dragover', throttledOnDragOver );
+	const startDragOverBehavior = () => {
+		editorRoot?.addEventListener( 'dragover', throttledOnDragOver );
+	};

-		return () => {
-			editorRoot.removeEventListener( 'dragover', throttledOnDragOver );
-		};
-	}, [
-		draggedBlockNames,
-		editorRoot,
-		getAllowedBlocks,
-		getBlockNamesByClientId,
-		getBlockType,
-		visibleInserter,
-	] );
+	const endDropOverBehavior = () => {
+		editorRoot?.removeEventListener( 'dragover', throttledOnDragOver );
+	};

 	if ( ! isDraggable ) {
 		return children( { draggable: false } );
@@ -176,6 +168,7 @@ const BlockDraggable = ( {
 						onDragStart();
 					}
 				} );
+				startDragOverBehavior();
 			} }
 			onDragOver={ scrollOnDragOver }
 			onDragEnd={ () => {
@@ -187,6 +180,7 @@ const BlockDraggable = ( {
 				if ( onDragEnd ) {
 					onDragEnd();
 				}
+				endDropOverBehavior();
 			} }
 			__experimentalDragComponent={
 				<BlockDraggableChip

It's pretty messy and makes things a little more broken in the process, but just thought I'd share it in case it helps the investigating! Happy to keep looking tomorrow, too.

@tellthemachines
Copy link
Contributor Author

Thanks for all the testing and screengrabs folks! And thanks @andrewserong for diving in and looking at options ❤️

I think I've fixed it now. The problem wasn't as much in the effect itself - because it can run repeatedly without causing the body classname to get added or removed - but in the conditions we were checking. Because non-container blocks were flagged as non-droppable, dragging over them (particularly the Paragraphs for some reason) was causing the classname to get added and removed more than it should. I changed the check to look at the parent block when the current block isn't a container, and that seems to have done the trick!

I can't reproduce the crossfade issue @apeatling noticed anymore, so I think it might have been related? But let me know if you still see it!

Copy link
Contributor

@andrewserong andrewserong left a comment

Choose a reason for hiding this comment

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

Nicely done, that's resolved the issue for me!

The problem wasn't as much in the effect itself - because it can run repeatedly without causing the body classname to get added or removed - but in the conditions we were checking

That seems consistent with what I'm seeing in testing now. The effect is still running more frequently than it needs to (i.e. because of the useEffect resulting in multiple handlers being called, it runs at greater than once every 200ms), however every time it runs, the output is consistent, which means there's no longer a flicker of the classname being added and removed 👍

To check how frequently it's being called, I added a let numTimesCalled = 0; definition at the root of the file and called console.log( ++numTimesCalled ); from within onDragOver. It's more obvious while dragging, but it gets called a fair bit:

2023-12-21.16.30.27.mp4

That doesn't seem like a blocker to me, as it seems to already be being called less frequently than once per frame, and removing the useEffect could be a good candidate for a code quality follow-up, rather than something that needs to block the feature getting in.

LGTM, great work with all the follow-ups here and figuring out all this nuanced logic! ✨

@tellthemachines
Copy link
Contributor Author

The only way I can think of to remove the effect is if we can decouple the callback from visibleInserter. I guess one thing we could look at is, instead of checking if the inserter is visible before adding the body class, adding another body class when the inserter is visible (that could be added elsewhere in the actual inserter logic). The downside of that is we'd be adding that body class when it's not needed as it would be much more frequent than the invalid drop one. Not sure if that would be much of an optimisation 😅

@andrewserong
Copy link
Contributor

Yes, that does sound like a bit of a rabbithole (and a good argument for leaving it for now!). I'm happy to have a hack around at it in the new year if you'd like, but no need to delay this PR any longer for now IMO 🙂

@tellthemachines
Copy link
Contributor Author

I just merged trunk into this branch as I think some checks were failing because of the node update.

@tellthemachines tellthemachines enabled auto-merge (squash) December 21, 2023 07:01
@tellthemachines tellthemachines merged commit bf87c21 into trunk Dec 21, 2023
56 checks passed
@tellthemachines tellthemachines deleted the fix/drop-indicator-not-allowed branch December 21, 2023 07:20
@github-actions github-actions bot added this to the Gutenberg 17.4 milestone Dec 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Drag and Drop Drag and drop functionality when working with blocks [Type] Bug An existing feature does not function as intended
Projects
None yet
8 participants