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

BlockSettingsMenu: Ensure only one block settings menu is open at a time #54083

Merged

Conversation

andrewserong
Copy link
Contributor

@andrewserong andrewserong commented Aug 31, 2023

What?

Fixes #50988 and unblocks #50273.

Ensure only one BlockSettingsMenu is open at a time by adding state with the clientId of the block settings menu most recently opened. All other open block settings menus will close if they have a clientId and it does not match the id of the menu currently open.

Why?

This resolves an issue where multiple block settings menus can be open at one time if the current window does not have focus. While this sounds like an edge case, it's fairly common on Macs when users right click into a window that is not currently active, which is an issue that's revealed when we go to attempt to add in right-click support as in #50273. However, it's also an (albeit very subtle) issue for cases where a user CMD+clicks from an active window into an inactive one. This will fire a click into a window without ever shifting focus there, so the current onFocusOutside logic isn't quite sufficient for the block settings menu.

How?

If we introduce some state in the block editor store to keep track of the latest clientId of the most recently opened block settings menu, then we can auto-close any block settings menus that are already opened.

  • Add state to track the id of the most recently opened block settings menu
  • Allow onToggle and open to be passed down so that we can:
    • Add a callback so that we can perform an action when the dropdown menu is toggled open (so we can then update state).
    • Pass down an explicit open state so that we can treat the dropdowns as controlled components.

This PR updates the block editor package, the necessary updates to the Dropdown and DropdownMenu components were made in #54257.

Testing Instructions

  1. On a Mac, with focus in a window that is not your test site, hover the mouse cursor over an inactive window that's open to the post or site editors and that also has the list view open.
  2. CMD+Click on the area of list view items where the ellipsis block settings menu button should be.
  3. Move the mouse cursor up to another list view item and CMD+Click on where the ellipsis menu button should be.
  4. Prior to this PR, multiple instances of the dropdown menu will be open.
  5. With this PR applied, any already open menus should automatically close.

Note: while the issue in testing here is quite subtle, it's a good idea for us to try to fix it prior to introducing right click in #50273 as it's fairly common for folks to click between two browser windows — and (on a Mac at least) right click does not change focus between windows.

Testing Instructions for Keyboard

Screenshots or screencast

This is how it can look if a user has multiple block settings menus open (prior to this PR):

image

Before After
2023-08-31 16 27 43 2023-08-31 16 25 34

@andrewserong andrewserong added [Type] Enhancement A suggestion for improvement. [Feature] UI Components Impacts or related to the UI component system [Package] Components /packages/components labels Aug 31, 2023
@andrewserong andrewserong self-assigned this Aug 31, 2023
@github-actions
Copy link

github-actions bot commented Aug 31, 2023

Size Change: +2.11 kB (0%)

Total Size: 1.52 MB

Filename Size Change
build/block-editor/index.min.js 216 kB +280 B (0%)
build/block-editor/style-rtl.css 15.1 kB -227 B (-1%)
build/block-editor/style.css 15.1 kB -225 B (-1%)
build/block-library/blocks/navigation-link/editor-rtl.css 668 B -44 B (-6%)
build/block-library/blocks/navigation-link/editor.css 669 B -42 B (-6%)
build/block-library/blocks/query/editor-rtl.css 478 B +28 B (+6%) 🔍
build/block-library/blocks/query/editor.css 477 B +28 B (+6%) 🔍
build/block-library/blocks/query/view.min.js 555 B -4 B (-1%)
build/block-library/editor-rtl.css 12.1 kB -15 B (0%)
build/block-library/editor.css 12.1 kB -15 B (0%)
build/block-library/index.min.js 204 kB +414 B (0%)
build/blocks/index.min.js 51.4 kB -4 B (0%)
build/components/index.min.js 255 kB -229 B (0%)
build/components/style-rtl.css 11.7 kB -8 B (0%)
build/components/style.css 11.7 kB -7 B (0%)
build/core-data/index.min.js 16.8 kB +35 B (0%)
build/edit-post/index.min.js 35.5 kB +66 B (0%)
build/edit-post/style-rtl.css 7.84 kB +195 B (+3%)
build/edit-post/style.css 7.83 kB +192 B (+3%)
build/edit-site/index.min.js 91.8 kB +653 B (+1%)
build/edit-site/style-rtl.css 13.5 kB +247 B (+2%)
build/edit-site/style.css 13.5 kB +255 B (+2%)
build/edit-widgets/index.min.js 16.9 kB +25 B (0%)
build/edit-widgets/style-rtl.css 4.8 kB +223 B (+5%) 🔍
build/edit-widgets/style.css 4.79 kB +217 B (+5%) 🔍
build/editor/index.min.js 45.5 kB +71 B (0%)
build/editor/style-rtl.css 3.53 kB +1 B (0%)
build/editor/style.css 3.52 kB +1 B (0%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 955 B
build/annotations/index.min.js 2.69 kB
build/api-fetch/index.min.js 2.28 kB
build/autop/index.min.js 2.1 kB
build/blob/index.min.js 451 B
build/block-directory/index.min.js 7.01 kB
build/block-directory/style-rtl.css 1.02 kB
build/block-directory/style.css 1.02 kB
build/block-editor/content-rtl.css 4.25 kB
build/block-editor/content.css 4.24 kB
build/block-editor/default-editor-styles-rtl.css 381 B
build/block-editor/default-editor-styles.css 381 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 126 B
build/block-library/blocks/audio/theme.css 126 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 584 B
build/block-library/blocks/button/editor.css 582 B
build/block-library/blocks/button/style-rtl.css 629 B
build/block-library/blocks/button/style.css 628 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.69 kB
build/block-library/blocks/cover/style.css 1.68 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 126 B
build/block-library/blocks/embed/theme.css 126 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 311 B
build/block-library/blocks/file/style.css 312 B
build/block-library/blocks/file/view.min.js 318 B
build/block-library/blocks/footnotes/style-rtl.css 201 B
build/block-library/blocks/footnotes/style.css 199 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 947 B
build/block-library/blocks/gallery/editor.css 952 B
build/block-library/blocks/gallery/style-rtl.css 1.53 kB
build/block-library/blocks/gallery/style.css 1.53 kB
build/block-library/blocks/gallery/theme-rtl.css 108 B
build/block-library/blocks/gallery/theme.css 108 B
build/block-library/blocks/group/editor-rtl.css 654 B
build/block-library/blocks/group/editor.css 654 B
build/block-library/blocks/group/style-rtl.css 57 B
build/block-library/blocks/group/style.css 57 B
build/block-library/blocks/group/theme-rtl.css 78 B
build/block-library/blocks/group/theme.css 78 B
build/block-library/blocks/heading/style-rtl.css 76 B
build/block-library/blocks/heading/style.css 76 B
build/block-library/blocks/html/editor-rtl.css 336 B
build/block-library/blocks/html/editor.css 337 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.42 kB
build/block-library/blocks/image/style.css 1.41 kB
build/block-library/blocks/image/theme-rtl.css 126 B
build/block-library/blocks/image/theme.css 126 B
build/block-library/blocks/image/view-interactivity.min.js 1.83 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/style-rtl.css 115 B
build/block-library/blocks/navigation-link/style.css 115 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 296 B
build/block-library/blocks/navigation-submenu/editor.css 295 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.23 kB
build/block-library/blocks/navigation/style.css 2.22 kB
build/block-library/blocks/navigation/view.min.js 984 B
build/block-library/blocks/nextpage/editor-rtl.css 395 B
build/block-library/blocks/nextpage/editor.css 395 B
build/block-library/blocks/page-list/editor-rtl.css 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 588 B
build/block-library/blocks/post-featured-image/editor.css 586 B
build/block-library/blocks/post-featured-image/style-rtl.css 319 B
build/block-library/blocks/post-featured-image/style.css 319 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 314 B
build/block-library/blocks/post-template/style.css 314 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 302 B
build/block-library/blocks/query-pagination/style.css 299 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/style-rtl.css 370 B
build/block-library/blocks/query/style.css 368 B
build/block-library/blocks/quote/style-rtl.css 222 B
build/block-library/blocks/quote/style.css 222 B
build/block-library/blocks/quote/theme-rtl.css 223 B
build/block-library/blocks/quote/theme.css 226 B
build/block-library/blocks/read-more/style-rtl.css 132 B
build/block-library/blocks/read-more/style.css 132 B
build/block-library/blocks/rss/editor-rtl.css 149 B
build/block-library/blocks/rss/editor.css 149 B
build/block-library/blocks/rss/style-rtl.css 289 B
build/block-library/blocks/rss/style.css 288 B
build/block-library/blocks/search/editor-rtl.css 178 B
build/block-library/blocks/search/editor.css 178 B
build/block-library/blocks/search/style-rtl.css 607 B
build/block-library/blocks/search/style.css 607 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 468 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 323 B
build/block-library/blocks/shortcode/editor.css 323 B
build/block-library/blocks/site-logo/editor-rtl.css 754 B
build/block-library/blocks/site-logo/editor.css 754 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.44 kB
build/block-library/blocks/social-links/style.css 1.43 kB
build/block-library/blocks/spacer/editor-rtl.css 348 B
build/block-library/blocks/spacer/editor.css 348 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 432 B
build/block-library/blocks/table/editor.css 432 B
build/block-library/blocks/table/style-rtl.css 639 B
build/block-library/blocks/table/style.css 639 B
build/block-library/blocks/table/theme-rtl.css 146 B
build/block-library/blocks/table/theme.css 146 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 185 B
build/block-library/blocks/video/style.css 185 B
build/block-library/blocks/video/theme-rtl.css 126 B
build/block-library/blocks/video/theme.css 126 B
build/block-library/classic-rtl.css 179 B
build/block-library/classic.css 179 B
build/block-library/common-rtl.css 1.1 kB
build/block-library/common.css 1.1 kB
build/block-library/editor-elements-rtl.css 75 B
build/block-library/editor-elements.css 75 B
build/block-library/elements-rtl.css 54 B
build/block-library/elements.css 54 B
build/block-library/reset-rtl.css 472 B
build/block-library/reset.css 472 B
build/block-library/style-rtl.css 13.9 kB
build/block-library/style.css 13.9 kB
build/block-library/theme-rtl.css 688 B
build/block-library/theme.css 693 B
build/block-serialization-default-parser/index.min.js 1.12 kB
build/block-serialization-spec-parser/index.min.js 2.87 kB
build/commands/index.min.js 15.5 kB
build/commands/style-rtl.css 921 B
build/commands/style.css 918 B
build/compose/index.min.js 12.1 kB
build/core-commands/index.min.js 2.6 kB
build/customize-widgets/index.min.js 12 kB
build/customize-widgets/style-rtl.css 1.48 kB
build/customize-widgets/style.css 1.48 kB
build/data-controls/index.min.js 640 B
build/data/index.min.js 8.84 kB
build/date/index.min.js 17.8 kB
build/deprecated/index.min.js 451 B
build/dom-ready/index.min.js 324 B
build/dom/index.min.js 4.64 kB
build/edit-post/classic-rtl.css 544 B
build/edit-post/classic.css 545 B
build/element/index.min.js 4.82 kB
build/escape-html/index.min.js 537 B
build/format-library/index.min.js 7.71 kB
build/format-library/style-rtl.css 554 B
build/format-library/style.css 553 B
build/hooks/index.min.js 1.55 kB
build/html-entities/index.min.js 448 B
build/i18n/index.min.js 3.58 kB
build/interactivity/index.min.js 11.3 kB
build/is-shallow-equal/index.min.js 527 B
build/keyboard-shortcuts/index.min.js 1.72 kB
build/keycodes/index.min.js 1.87 kB
build/list-reusable-blocks/index.min.js 2.2 kB
build/list-reusable-blocks/style-rtl.css 836 B
build/list-reusable-blocks/style.css 836 B
build/media-utils/index.min.js 2.9 kB
build/notices/index.min.js 948 B
build/nux/index.min.js 1.99 kB
build/nux/style-rtl.css 735 B
build/nux/style.css 732 B
build/patterns/index.min.js 2.7 kB
build/patterns/style-rtl.css 240 B
build/patterns/style.css 240 B
build/plugins/index.min.js 1.79 kB
build/preferences-persistence/index.min.js 1.84 kB
build/preferences/index.min.js 1.24 kB
build/primitives/index.min.js 943 B
build/priority-queue/index.min.js 1.52 kB
build/private-apis/index.min.js 958 B
build/react-i18n/index.min.js 615 B
build/react-refresh-entry/index.min.js 9.47 kB
build/react-refresh-runtime/index.min.js 7.31 kB
build/redux-routine/index.min.js 2.7 kB
build/reusable-blocks/index.min.js 2.7 kB
build/reusable-blocks/style-rtl.css 243 B
build/reusable-blocks/style.css 243 B
build/rich-text/index.min.js 10.2 kB
build/router/index.min.js 1.78 kB
build/server-side-render/index.min.js 1.94 kB
build/shortcode/index.min.js 1.39 kB
build/style-engine/index.min.js 1.97 kB
build/sync/index.min.js 53.8 kB
build/token-list/index.min.js 582 B
build/url/index.min.js 3.73 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 958 B
build/warning/index.min.js 249 B
build/widgets/index.min.js 7.16 kB
build/widgets/style-rtl.css 1.15 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.02 kB

compressed-size-action

@andrewserong andrewserong marked this pull request as ready for review August 31, 2023 06:42
@andrewserong andrewserong requested review from mirka and ciampo August 31, 2023 06:42
@andrewserong
Copy link
Contributor Author

Just added a couple of folks as reviewers — for context with this PR, my idea is to come up with a fix for the block settings menu so that we can auto-close menus that should not be open (for situations not covered by onFocusOutside). The primary use case is for introducing right-click behaviour in the list view (being worked on over in #50273). The goal behind this PR is to see if it'd be viable for us to add a little state to the block editor to track which menu is open, and then use an isOpen prop (along with the onToggle callback) to force close any block settings menus that should not be open.

While it looked like the v2 of the DropDown menu was quite promising to resolve this problem (#51400), since there have been some other blockers with that work, I wondered if something like this PR could work in the shorter-term?

@andrewserong andrewserong force-pushed the update/block-settings-menu-to-ensure-only-one-menu-is-open branch from 511dacb to 94b4025 Compare September 1, 2023 04:23
@github-actions
Copy link

github-actions bot commented Sep 1, 2023

Flaky tests detected in 5c51724.
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/6153433212
📝 Reported issues:

Copy link
Member

@ramonjd ramonjd left a comment

Choose a reason for hiding this comment

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

This is working well for me.

Before

Screenshot 2023-09-05 at 1 59 13 pm

After

2023-09-05.14.01.17.mp4

Code looks good too, but I'd want to get the seal of approval from the components folks as well.

packages/block-editor/src/store/reducer.js Outdated Show resolved Hide resolved
@andrewserong
Copy link
Contributor Author

Thanks for reviewing, Ramon!

Code looks good too, but I'd want to get the seal of approval from the components folks as well.

Yes, I'd love to get some feedback from components folks to see if this is a good direction to resolve this issue in the shorter-term. I think it's probably a fairly pragmatic way for us to go, so that we can unblock the work on right-click in the list view while explorations into refactored dropdown menus continue.

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.

Also working correctly for me as per Ramon's video above. 👍

@ciampo
Copy link
Contributor

ciampo commented Sep 5, 2023

Hey folks, sorry for the delay in answering here — I've been catching up with the notification queue after some extended AFK

I'll try have a look at this PR tomorrow and share my thoughts

@andrewserong
Copy link
Contributor Author

sorry for the delay in answering here — I've been catching up with the notification queue after some extended AFK

Thanks Marco, and not a worry at all. I assumed you must have a pretty big backlog of notifications!

@andrewserong andrewserong force-pushed the update/block-settings-menu-to-ensure-only-one-menu-is-open branch from 01d31d2 to 19d73b8 Compare September 6, 2023 00:50
Copy link
Contributor

@ciampo ciampo left a comment

Choose a reason for hiding this comment

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

So! From what I gather, this PR is proposing to add support to DropdownMenu and Dropdown so that they be used in controlled mode (while so far they could only be used in uncontrolled mode).

That sounds reasonable! But I'd propose a couple of tweaks to the implementation:

  • to match general prop naming convention a bit better, let's rename the isOpen prop to open
  • let's add a third prop to both Dropdown and DropdownMenu called defaultOpen — it will be used to set the dropdown's initial "open" state when the components are used in uncontrolled mode
  • internally in Dropdown we can use the useControlledValue hook which should take care automatically of the controlled/uncontrolled logic (open maps to value, onToggle maps to onChange, and defaultOpen maps to defaultValue)

Let me know if that makes sense and/or if you struggle to make this work!

@andrewserong
Copy link
Contributor Author

Thanks for the feedback @ciampo! I've pushed the rename from isOpen to open in 19f88d3.

The other feedback sounded really good to me, but unfortunately I did wind up struggling to make it work 😅. Please excuse the wall of text that follows, but hopefully it captures both my thinking and where I ran into some stumbling blocks 🙂

From what I gather, this PR is proposing to add support to DropdownMenu and Dropdown so that they be used in controlled mode (while so far they could only be used in uncontrolled mode).

Not quite. This PR proposes that we can close the dropdown menu from the outside, but not fully control it. My objective wasn't really to use the menu in a fully controlled mode, but instead to ensure only one dropdown is open at a time, to work around the bug with DropdownMenu where onFocusOutside never fires if focus was never in the window to begin with. Ideally, DropdownMenu could be aware enough to only have a single menu open at a time. My hope is that we can find a fairly minor code change that gets us that behaviour for the block settings menu, without unintentionally affecting any other components.

Still, the controlled component idea is a compelling one! I've had a hack around, but unfortunately I haven't managed to get it working properly. I ran into a few issues:

  • Switching to a controlled mode means we then need to handle the open state, which makes the block editor more responsible for the state of the BlockSettingsMenu than might be desired. It seemed to mostly work, but I didn't feel 100% confident with it:
  • There's no option to only override to force close a dropdown menu, but otherwise allow internal state to open a dropdown, as useControlledValue skips updating internal state when a value is provided here.
  • The typing of useControlledValue doesn't appear to be compatible with props expected by renderContent and renderToggle. For some reason, even if we use useControlledValue< boolean >, TS thinks the value can be undefined, so we wind up needing to provide a fallback false value, anyway.
  • If we switch to a controlled mode, then the unmounting logic that calls onToggle( false ) here winds up unintentionally closing the dropdown when we CMD+click into an inactive window and first click on a dropdown menu. From a user perspective, we see the dropdown flicker open and closed very quickly — I think the problem here is that we only want the local cleanup to happen, and not to fire onToggle and then clear out the block editor's state for which block menu is open? But that's quite specific to the desired goal in this PR 🤔. In any case, here's how it's looking:
2023-09-07.13.49.10.mp4

Unfortunately, I've become stuck on that last issue, so I'm not feeling very confident about the fully controlled component approach, but I very well could be missing something obvious! To keep this PR in its current working form, I've pushed my commit trying out a controlled component to a separate throwaway PR over in #54236. The main commit I tried is in 67801dd.

What do you think might be a good next step? Should we aim for a partially controlled component state as in this PR, or see if there's more we can do to debug the flickery issues in #54236? Alternately, happy to explore any other ideas!

@ciampo
Copy link
Contributor

ciampo commented Sep 7, 2023

To demonstrate what I had in mind, I created this draft PR with a POC:

  • adds controlled mode (for the open state) to both Dropdown and DropdownMenu
  • tweaks storybook examples to make sure controlled version works as expected
  • adds one more storybook example to show how we could use (in principle) controlled mode to make sure that only one dropdownmenu among a certain group is open at the time

Hopefully this can help you to get on the right direction, but happy to discuss further in case it doesn't

@andrewserong andrewserong force-pushed the update/block-settings-menu-to-ensure-only-one-menu-is-open branch from 19f88d3 to 23d973f Compare September 8, 2023 00:57
@andrewserong
Copy link
Contributor Author

To demonstrate what I had in mind, I created #54257 with a POC:

Ah, perfect, that unlocks it, thanks so much @ciampo! The bit that I'd missed in my exploration was the removal of the useEffect cleanup function — that was the bit that tripped up the required behaviour in this PR. I've pushed a commit to this PR that uses your approach so that it's wired up to the block settings menu so that we can test it out. It seems to be testing quite nicely for me now with the fully controlled component.

Since this PR updates both the components package as well as the block-editor package, I'd be happy for us to use #54257 for the components updates if that helps keep things neater?

@andrewserong andrewserong force-pushed the update/block-settings-menu-to-ensure-only-one-menu-is-open branch from 615d9f9 to 5c51724 Compare September 12, 2023 00:46
@andrewserong andrewserong changed the title BlockSettingsMenu and DropdownMenu: Ensure only one block settings menu is open at a time BlockSettingsMenu: Ensure only one block settings menu is open at a time Sep 12, 2023
@andrewserong
Copy link
Contributor Author

Alrighty, I've rebased this now that #54257 is merged, and it still seems to be testing well for me, so this should be ready for review now 🙂

Since this PR does add additional state in the block-editor store, I'll just do a wider ping to @WordPress/gutenberg-core for visibility, in case anyone has any objections to using state in this way.

@andrewserong andrewserong added [Package] Block editor /packages/block-editor and removed [Package] Components /packages/components labels Sep 12, 2023
Copy link
Member

@ramonjd ramonjd left a comment

Choose a reason for hiding this comment

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

Working great for me.

Before

2023-09-12.13.23.35.mp4

After

2023-09-12.13.25.47.mp4

Co-authored by: Ramon <[email protected]>
@andrewserong
Copy link
Contributor Author

Thanks for the approving review @ramonjd! 🙇

I'll leave this PR open overnight to see if we get any other feedback from folks, and if not, will merge this in tomorrow.

@youknowriad
Copy link
Contributor

I'm wondering, this seems like an issue that impacts all dropdowns (basically all of our usage of "focus outside to close"), so is there a generic solution to this? like for all dropdowns?

@youknowriad
Copy link
Contributor

Like: could a fix here be: when you open any dropdown, close all others?

@ciampo
Copy link
Contributor

ciampo commented Sep 12, 2023

I'm wondering, this seems like an issue that impacts all dropdowns (basically all of our usage of "focus outside to close"), so is there a generic solution to this? like for all dropdowns?
Like: could a fix here be: when you open any dropdown, close all others?

The solution proposed in this PR is more of a temporary solution — ideally we'd be able to achieve this in the next version of DropdownMenu — I had already tried an approach, but then I came across some blockers. I plan on trying an alternative approach soon (with ariakit instead of radix).

We will also want to make sure we fully understand the desired behaviour — from what I understand, it looks like we'd want all DropdownMenus to be modal, ie. there can only be one menu open at a time, and interactions outside of the menu are disabled.

In case, for any reasons, we couldn't manage to successfully wrap up the new version of DropdownMenu, then I'm sure that we could look at implementing the "only one menu at a time" functionality directly in the existing component.

@youknowriad
Copy link
Contributor

Thanks for the context @ciampo that plan works for me.

There might be a case where we want to retain multiple dropdowns (but to be confirmed): "nested dropdowns"

Comment on lines +191 to +192
// When a currentClientId is in use, treat the menu as a controlled component.
// This ensures that only one block settings menu is open at a time.
Copy link
Contributor

Choose a reason for hiding this comment

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

My only comment at this point is: wouldn't be simpler to always use DropdownMenu in a controlled way? Something like

const open = currentClientId !== undefined && openedBlockSettingsMenu === currentClientId;

I believe that the logic would just be simpler, and the behaviour of the DropdownMenu component more predictable (furthermore, React usually warns when this pattern is applied to input elements)

Copy link
Contributor Author

@andrewserong andrewserong Sep 13, 2023

Choose a reason for hiding this comment

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

It'd be simpler in this component, but due to current usage of the BlockSettingsDropdown, a currentClientId isn't always available as a block object isn't always provided. So we currently need to support both controlled and uncontrolled in this component.

In practice, at the moment, the List View provides a block object, however the block settings menu in the toolbar doesn't (it provides clientIds for the selected blocks instead, which isn't quite the same as the concept of the block when used in the list view), so if we switch to always controlling the component, we wind up breaking the dropdown in the editor canvas:

2023-09-13.10.34.46.mp4

Separately to this PR we could potentially look at refactoring other usage of the block settings dropdown to ensure a block object with clientId is always provided, and then we could have the component always operate in controlled mode.

For now, I'd prefer to try to contain the scope of this PR to the BlockSettingsMenu itself, so I think retaining this slightly complex ternary is probably a good step to go with for now, as it focuses on the main use case to solve which is with the behaviour in the list view.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is the block of code that I'm suggesting to add some more comments about it being a temp solution..

@andrewserong
Copy link
Contributor Author

Thanks for the continued feedback @ciampo and @youknowriad (and for reviewing @aristath)!

The solution proposed in this PR is more of a temporary solution

Yes, since other efforts wound up encountering blockers, the goal here is to find a good enough temporary solution to unblock work on right click within the list view. My hope is that the change here isn't too intrusive, and with keeping the action and selector private, we can always remove this behaviour in a follow-up once there's a longer term solution in place for dropdown menus in general.

For now, are there any objections to us merging this in as-is? If not, I'll look at landing this PR tomorrow.

@ntsekouras
Copy link
Contributor

Thanks for the PR @andrewserong! I don't want to block the work here for the temp solution, but the first thing that come in mind, is what Riad also said about handling it in useFocusOutside probably. Could you please add some comments about this in the new block of code, that the plan is to be handled differently? There is added complexity here and it can be difficult to remember when time passes. Thanks!

@andrewserong
Copy link
Contributor Author

Thanks for taking a look @ntsekouras! Great idea about adding an extra comment. I've added some further explanation in ba4579f, along with a link to this PR. Happy to tweak the wording of course!

Copy link
Contributor

@ntsekouras ntsekouras left a comment

Choose a reason for hiding this comment

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

Thank you @andrewserong and for adding the extra comment!

Copy link
Member

@tyxla tyxla left a comment

Choose a reason for hiding this comment

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

This looks and works great overall 👍

I've just left a few suggestions to improve the consistency of the store functionality.

@@ -1913,6 +1913,13 @@ export function blockEditingModes( state = new Map(), action ) {
return state;
}

export function openedBlockSettingsMenu( state = null, action ) {
if ( 'SET_OPENED_BLOCK_SETTINGS_MENU' === action.type ) {
return action?.clientId;
Copy link
Member

Choose a reason for hiding this comment

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

For better precision, did we mean to return the default state of null instead of undefined in that scenario?

Suggested change
return action?.clientId;
return action?.clientId ?? null;

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 call, that helps give this reducer consistency with other reducers that track a client id or null 👍

@@ -1913,6 +1913,13 @@ export function blockEditingModes( state = new Map(), action ) {
return state;
}

export function openedBlockSettingsMenu( state = null, action ) {
Copy link
Member

Choose a reason for hiding this comment

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

The block editor reducer is quite thoroughly covered by unit tests. A couple of simple tests could be useful to cover this new reducer subtree.

* Returns the client ID of the block settings menu that is currently open.
*
* @param {Object} state Global application state.
* @return {string|undefined} The client ID of the block menu that is currently open.
Copy link
Member

Choose a reason for hiding this comment

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

Did you mean null or undefined? The default value is null and currently we'll return undefined if a clientId is missing for some reason when dispatching the setOpenedBlockSettingsMenu() action (this feels like an inconsistent behavior on its own, so I'm commenting with it separately).

* @param {string} clientId The block client ID.
* @return {Object} Action object.
*/
export function setOpenedBlockSettingsMenu( clientId = '' ) {
Copy link
Member

Choose a reason for hiding this comment

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

Do we really mean to have the default clientId as an empty string? I can see the action is set up to handle undefined clientId and the reducer will also return a null, and those are all three different values representing "empty", which seems like an inconsistent behavior. Should we make it consistent and ultimately only go with either of them?

@andrewserong
Copy link
Contributor Author

Thanks for the review @tyxla! I've updated the action, reducer, and selector for consistency so that if the passed in clientId is undefined then the state is always null for this, which I believe is consistent with some of the other reducers. I've added in a few tests for the reducer and a couple for the action, too.

@ramonjd
Copy link
Member

ramonjd commented Sep 14, 2023

Latest changes LGTM.

The tests cover the arg type changes 👍🏻

Also ran the branch manually - things work as per the last test.

@andrewserong
Copy link
Contributor Author

Excellent, thanks for confirming! I'll merge this in now, but happy to work on any follow-ups if there's feedback after the fact.

Thanks again everyone! 🙇

@andrewserong andrewserong merged commit 21b6a37 into trunk Sep 14, 2023
50 checks passed
@andrewserong andrewserong deleted the update/block-settings-menu-to-ensure-only-one-menu-is-open branch September 14, 2023 01:18
@github-actions github-actions bot added this to the Gutenberg 16.7 milestone Sep 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] UI Components Impacts or related to the UI component system [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.

Dropdown / Block Settings Menu: Ensure only one block settings menu is open at a time
8 participants