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

Block Editor Settings: use v2 DropdownMenu #51400

Closed
wants to merge 35 commits into from

Conversation

ciampo
Copy link
Contributor

@ciampo ciampo commented Jun 11, 2023

⚠️ ⚠️ ⚠️ WIP ⚠️ ⚠️ ⚠️

Part of #50459

What?

This PR refactors the block toolbar's "settings" dropdown menu — the menu that is toggled by pressing the last button (labelled "Options" and associated to a vertical "..." icon.

The refactor involved migrating from the legacy DropdownMenu component to the new version (currently still experimental and only available as a locked private API of the @wordpress/components package).

Why?

The main advantage of migrating to the new version of the component is the support for nested menus.

This PR also represents the first "real world" usage of the experimental component (ie. not isolated in Storybook), which will expose a number of issues related to integrating the component with the existing set of components and constraints of the block editor.

Is this PR including breaking changes?

No, this PR does not include breaking changes. The dropdown menu and all its items have been refactored to use the new experimental version of the DropdownMenu component, without otherwise changing any public-facing APIs (including the fillProps passed to the different slotfills).

Even if a third-party developer was to add a legacy MenuItem to the block settings dropdown via the BlockSettingsMenuControls slotfill, the MenuItem would continue to render without any errors, and clicking on the item would still cause the expected results. The only difference may be visual, as the new dropdownmenu components have slightly different styles and spacing.

How?

These are the changes being proposed in this PR. As the PR grows in scope, we may decide to split some of the proposed changes to separate, smaller PRs.

📝 Changes

Expand to show the detailed list of changes

General changes:

  • Swapped legacy components (namely DropdownMenu, MenuGroup, MenuItem) with the new equivalent experimental components. Main differences:
    • Instead of toggleProps prop, the component requires a trigger to be explicitly passed via the trigger prop. I've therefore passed a Button component as the trigger and forwarded the trigger props to it
    • Instead of the menuProps prop, the component can accept certain props directly (like onKeyDown)
    • The component doesn't accept the disableOpenOnArrowDown prop. I've implemented the same functionality by controlling the DropdownMenu component with internal state and added a keyboard event listener on the trigger to preventDefault when the arrow down key is pressed.
    • The new DropdownMenuItem component doesn't accept a shortcut prop. Instead, I've created a custom Shortcut component and passed it via the suffix prop
    • On a similar note, the new DropdownMenuItem component doesn't accept an icon prop. Instead, I've used the prefix prop and passed the icon via the Icon component
    • Renamed onClick props to onSelect for the new DropdownMenuItem.
    • The old DropdownMenu component accepted, as children, a render function. This function accepted an object containing the onClose callback, which could be used to programmatically close the dropdown menu. The new DropdownMenu component doesn't expose this functionality, but an equivalent callback has been created and passed to the children components for backward-compat reasons.
    • The new DropdownMenuItem component automatically closes the dropdown menu when clicked. This meant that a lot of the calls to the onClose callback have been removed, and that explicit event.preventDefault() calls have been added where the menu needs to stay open after clicking the menu item.
    • The new DropdownMenuGroup component doesn't render a line to visually demarcate the group. Instead, I used the new DropdownMenuSeparator component.
  • Refactored the "copy" functionality away from the useCopyToClipboard hook, and instead implemented a custom solution using native web APIs instead (more details on this below)
  • Overhauled some classnames and styles associated to the dropdown. In particular, styles the packages/block-editor/src/components/block-settings-menu/style.scss were deleted, since they were targeting the legacy dropdown. As a consequence, I also removed all occurrences of the block-editor-block-settings-menu and block-editor-block-settings-menu__popover classes from the repository.
    • Instead, I've added new classnames for the button trigger, which seem to be needed in order to hide the dropdown in react-native environments. While doing so, I've created three different classnames, instead of reusing the same classname across packages.
    • Another consequence of this change is that one of the puppeteer e2e test required refactoring.
  • Added styles for the dropdown menu's content to allow for the menu to resize and show a scrollbar when there isn't enough space for it to display fully
  • The useCopyToClipboard hook has been updated to support an container argument, useful to circumvent the focus trap added by the DropdownMenu component

Changes applied to the experimental DropdownMenu component:

  • Added support for the onKeyDown prop on the root DropdownMenu component. This is to support the use-case of the block toolbar settings menu, which uses the prop to support keyboard shortcuts (source)
  • Changed cursor styles to show a pointer over the menu items (apart from disabled items)
  • Added the same z-index for content wrapper elements as the legacy Popover menu, to avoid stacking order bugs when other modals are opened.
  • Added support for a few pointer-related callback props on DropdownMenuItem. This is to support the use-case of the "select parent block" menu item, which is supposed to highlight the parent block when hovering the menu item
  • Added support for the href, rel, and target props on DropdownMenuItem. This is to support the use-case of the "Manage reusable blocks" menu item, which is supposed to link away from the editor. When the href prop is not empty, the DropdownMenuItem automatically renders as an <a /> tag instead of a <div />. Some extra styles were also necessary to override the default Gutenberg styles associated with anchor tags.
  • Allow passing a few more HTML attributes to DropdownMenu, like id, aria-labelledby, className. These attributes are often used by consumers of the component, and were necessary in ensuring that the trigger's id was passed correctly to the menu's context in this specific PR instance
  • Added some additional styles to the content wrappers to make legacy MenuItem components look better

Changes applied to the Modal component:

  • Added pointer-events: all to the modal wrapper. This is to override styles added by the Radix DropdownMenu, which sets pointer-events: none when opened in a modal way. Without the style override, the modal would not receive pointer events.
    • Note that this doesn't seem to be enough in order to make modals opened on top of the DropdownMenu behave as expected.

🛑 Pending issues

The new DropdownMenu component interferes with Modal components opened on top of the dropdown

Due to how the new DropdownMenu component is implemented, Modals rendered on top of open dropdown menus don't react to pointer events and don't get keyboard focus.

I was able to (potentially) solve the pointer events problem (by forcing pointer-events: all on the modal), although that solution would require more testing to make sure it's robust (for example, what happens if a modal DropdownMenu is opened inside a modal?).

What I haven't been able to solve so far regards keyboard focus. The new DropdownMenu component, in fact, seems to trap keyboard focus on the dropdown. And therefore, when opening a modal on top of an opened dropdown menu, the keyboard focus stays on the dropdown menu (this can be verified because, when typing letters, the dropdown menu still highlights the matching menu item.

It looks like we'd need to use the Dialog component from Radix to have modals work as expected: https://codesandbox.io/s/dropdownmenu-dialog-items-forked-9sy6j8?file=/src/App.js

💬 Pending discussions

Where and how to implement the resize behavior when meeting the edge of the viewport

Radix's DropdownMenu offers this behavior by exposing the --radix-dropdown-menu-content-available-height CSS variable, and letting the consumer of the component actually implement the resizing behaviour. This is what I've done so far in this PR, but we should ask ourselves a few questions:

  • Should we offer this same functionality directly in the DropdownMenu component? If so, we should think carefully about how to expose it and future-proof it against potential radix changes
  • In case we keep it out of DropdownMenu, would it be ok that consumers of the component use a CSS variable that clearly states '"radix" ? It would be good to keep radix as an implementation detail.
Decide the best way to allow `DropdownMenuItem` to render as an anchor tag

Currently, the DropdownMenuItem exposes the href prop and internally decided to render an <a /> or a <div />. This approach is simple and mimics the APIs of the legacy MenuItem and the Button component, but it lacks in scalability (what if we needed to render something else as a menu item?)

Alternative approaches:

  • expose the asChild prop, allowing consumers of the component to pass whatever component they want
  • always leave asChild set to true in the internal implementation, requiring consumers of the component to always specify a "menu item wrapper" element
Reflect on which props we should expose on `DropdownMenu` and `DropdownMenuItem`

Using the components in a real-world scenario highlighted the need for consumers of the components to pass certain HTML attributes, like className, onKeyDown, and aria-labelledby.

So far, our strategy on these components has been to reduce the amount of props exposed as much as possible, but I wonder if we should reconsider that and allow a wider range of props (ie. standard HTML attributes?)

Testing Instructions

Make sure that:

  • the block toolbar settings dropdown menu opens/closes correctly
  • the block toolbar settings dropdown menu positions itself correctly when meeting the edge of the viewport (including nested menus?)
  • modal behavior: when the new dropdown is visible, the rest of the page is not interactive (ie. does not scroll / react to pointer events / content is not accessible)
  • all menu items are shown/hidden following the correct logic
  • all menu items behave as expected when clicked (both the functionality that they execute when clicked, and the fact that the dropdown menu is closed or stays open)
  • make sure that all other v1 dropdown menus are still working as expected, especially the ones that share a lot of the same functionality (like the one in the list view)
  • keyboard shortcuts work as expected (including onKeyDown ev listener)
  • All the menu items refactored to the new dropdownmenu component are not rendered in other legacy menus

Testing Instructions for Keyboard

Screenshots or screencast

trunk this PR (wip)
Screenshot 2023-06-12 at 12 02 40 Screenshot 2023-06-12 at 13 35 45

@github-actions
Copy link

github-actions bot commented Jun 11, 2023

Size Change: +1.02 kB (0%)

Total Size: 1.4 MB

Filename Size Change
build/block-editor/index.min.js 196 kB +416 B (0%)
build/block-editor/style-rtl.css 14.9 kB +27 B (0%)
build/block-editor/style.css 14.9 kB +28 B (0%)
build/block-library/index.min.js 201 kB +46 B (0%)
build/components/index.min.js 231 kB +164 B (0%)
build/components/style-rtl.css 11.8 kB +4 B (0%)
build/components/style.css 11.8 kB +2 B (0%)
build/compose/index.min.js 12.1 kB +32 B (0%)
build/customize-widgets/index.min.js 12 kB -3 B (0%)
build/edit-post/index.min.js 34.1 kB +37 B (0%)
build/edit-site/index.min.js 70.8 kB +61 B (0%)
build/private-apis/index.min.js 947 B +8 B (+1%)
build/reusable-blocks/index.min.js 2.41 kB +201 B (+9%) 🔍
ℹ️ 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.29 kB
build/autop/index.min.js 2.1 kB
build/blob/index.min.js 451 B
build/block-directory/index.min.js 7.05 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.22 kB
build/block-editor/content.css 4.22 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 624 B
build/block-library/blocks/button/style.css 623 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 409 B
build/block-library/blocks/columns/style.css 409 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.61 kB
build/block-library/blocks/cover/style.css 1.6 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 159 B
build/block-library/blocks/details/style.css 159 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/interactivity.min.js 395 B
build/block-library/blocks/file/style-rtl.css 269 B
build/block-library/blocks/file/style.css 270 B
build/block-library/blocks/freeform/editor-rtl.css 2.58 kB
build/block-library/blocks/freeform/editor.css 2.58 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/interactivity.min.js 1.34 kB
build/block-library/blocks/image/style-rtl.css 1.34 kB
build/block-library/blocks/image/style.css 1.34 kB
build/block-library/blocks/image/theme-rtl.css 126 B
build/block-library/blocks/image/theme.css 126 B
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 507 B
build/block-library/blocks/media-text/style.css 505 B
build/block-library/blocks/more/editor-rtl.css 431 B
build/block-library/blocks/more/editor.css 431 B
build/block-library/blocks/navigation-link/editor-rtl.css 712 B
build/block-library/blocks/navigation-link/editor.css 711 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.35 kB
build/block-library/blocks/navigation/editor.css 2.36 kB
build/block-library/blocks/navigation/interactivity.min.js 978 B
build/block-library/blocks/navigation/style-rtl.css 2.21 kB
build/block-library/blocks/navigation/style.css 2.2 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 174 B
build/block-library/blocks/paragraph/editor.css 174 B
build/block-library/blocks/paragraph/style-rtl.css 279 B
build/block-library/blocks/paragraph/style.css 281 B
build/block-library/blocks/post-author/style-rtl.css 175 B
build/block-library/blocks/post-author/style.css 176 B
build/block-library/blocks/post-comments-form/editor-rtl.css 96 B
build/block-library/blocks/post-comments-form/editor.css 96 B
build/block-library/blocks/post-comments-form/style-rtl.css 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 153 B
build/block-library/blocks/post-navigation-link/style.css 153 B
build/block-library/blocks/post-template/editor-rtl.css 99 B
build/block-library/blocks/post-template/editor.css 98 B
build/block-library/blocks/post-template/style-rtl.css 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 103 B
build/block-library/blocks/preformatted/style.css 103 B
build/block-library/blocks/pullquote/editor-rtl.css 135 B
build/block-library/blocks/pullquote/editor.css 135 B
build/block-library/blocks/pullquote/style-rtl.css 335 B
build/block-library/blocks/pullquote/style.css 335 B
build/block-library/blocks/pullquote/theme-rtl.css 167 B
build/block-library/blocks/pullquote/theme.css 167 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/query-pagination-numbers/editor.css 121 B
build/block-library/blocks/query-pagination/editor-rtl.css 221 B
build/block-library/blocks/query-pagination/editor.css 211 B
build/block-library/blocks/query-pagination/style-rtl.css 288 B
build/block-library/blocks/query-pagination/style.css 284 B
build/block-library/blocks/query-title/style-rtl.css 63 B
build/block-library/blocks/query-title/style.css 63 B
build/block-library/blocks/query/editor-rtl.css 450 B
build/block-library/blocks/query/editor.css 449 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 587 B
build/block-library/blocks/search/style.css 584 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 531 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 203 B
build/block-library/blocks/site-logo/style.css 203 B
build/block-library/blocks/site-tagline/editor-rtl.css 86 B
build/block-library/blocks/site-tagline/editor.css 86 B
build/block-library/blocks/site-title/editor-rtl.css 116 B
build/block-library/blocks/site-title/editor.css 116 B
build/block-library/blocks/site-title/style-rtl.css 57 B
build/block-library/blocks/site-title/style.css 57 B
build/block-library/blocks/social-link/editor-rtl.css 184 B
build/block-library/blocks/social-link/editor.css 184 B
build/block-library/blocks/social-links/editor-rtl.css 674 B
build/block-library/blocks/social-links/editor.css 673 B
build/block-library/blocks/social-links/style-rtl.css 1.43 kB
build/block-library/blocks/social-links/style.css 1.42 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 433 B
build/block-library/blocks/table/editor.css 433 B
build/block-library/blocks/table/style-rtl.css 645 B
build/block-library/blocks/table/style.css 644 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 174 B
build/block-library/blocks/video/style.css 174 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/editor-rtl.css 12.2 kB
build/block-library/editor.css 12.1 kB
build/block-library/elements-rtl.css 54 B
build/block-library/elements.css 54 B
build/block-library/interactivity/runtime.min.js 2.69 kB
build/block-library/interactivity/vendors.min.js 8.2 kB
build/block-library/reset-rtl.css 478 B
build/block-library/reset.css 478 B
build/block-library/style-rtl.css 13.5 kB
build/block-library/style.css 13.5 kB
build/block-library/theme-rtl.css 686 B
build/block-library/theme.css 691 B
build/block-serialization-default-parser/index.min.js 1.12 kB
build/block-serialization-spec-parser/index.min.js 2.87 kB
build/blocks/index.min.js 50.8 kB
build/commands/index.min.js 14.9 kB
build/commands/style-rtl.css 827 B
build/commands/style.css 827 B
build/core-commands/index.min.js 2.12 kB
build/core-data/index.min.js 15.7 kB
build/customize-widgets/style-rtl.css 1.38 kB
build/customize-widgets/style.css 1.38 kB
build/data-controls/index.min.js 640 B
build/data/index.min.js 8.3 kB
build/date/index.min.js 40.4 kB
build/deprecated/index.min.js 451 B
build/dom-ready/index.min.js 324 B
build/dom/index.min.js 4.63 kB
build/edit-post/classic-rtl.css 544 B
build/edit-post/classic.css 545 B
build/edit-post/style-rtl.css 7.58 kB
build/edit-post/style.css 7.57 kB
build/edit-site/style-rtl.css 11.6 kB
build/edit-site/style.css 11.6 kB
build/edit-widgets/index.min.js 16.8 kB
build/edit-widgets/style-rtl.css 4.53 kB
build/edit-widgets/style.css 4.53 kB
build/editor/index.min.js 44.7 kB
build/editor/style-rtl.css 3.54 kB
build/editor/style.css 3.53 kB
build/element/index.min.js 4.8 kB
build/escape-html/index.min.js 537 B
build/format-library/index.min.js 7.57 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/is-shallow-equal/index.min.js 527 B
build/keyboard-shortcuts/index.min.js 1.71 kB
build/keycodes/index.min.js 1.84 kB
build/list-reusable-blocks/index.min.js 2.13 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/plugins/index.min.js 1.84 kB
build/preferences-persistence/index.min.js 1.84 kB
build/preferences/index.min.js 1.24 kB
build/primitives/index.min.js 941 B
build/priority-queue/index.min.js 1.52 kB
build/react-i18n/index.min.js 696 B
build/react-refresh-entry/index.min.js 8.44 kB
build/react-refresh-runtime/index.min.js 7.31 kB
build/redux-routine/index.min.js 2.7 kB
build/reusable-blocks/style-rtl.css 243 B
build/reusable-blocks/style.css 243 B
build/rich-text/index.min.js 10.7 kB
build/router/index.min.js 1.77 kB
build/server-side-render/index.min.js 2.02 kB
build/shortcode/index.min.js 1.39 kB
build/style-engine/index.min.js 1.42 kB
build/token-list/index.min.js 582 B
build/url/index.min.js 3.57 kB
build/vendors/inert-polyfill.min.js 2.48 kB
build/vendors/react-dom.min.js 41.8 kB
build/vendors/react.min.js 4.02 kB
build/viewport/index.min.js 1.04 kB
build/warning/index.min.js 268 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

@github-actions
Copy link

github-actions bot commented Jun 11, 2023

Flaky tests detected in 601a07e.
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/5289786548
📝 Reported issues:

@ciampo ciampo force-pushed the try/block-editor-block-settings-dropdownmenu-v2 branch 10 times, most recently from 5a5b8dc to 3701a7b Compare June 13, 2023 10:17
@andrewserong
Copy link
Contributor

I know this is still a WIP, but just thought I'd take it for a very quick spin. It looks like this is also going to resolve #50988 from early testing (CMD+click with another browser window no longer results in multiple block settings dropdown menus displaying), so I'm quite excited for this one! 😄

2023-06-15 14 31 01

@ciampo ciampo force-pushed the try/block-editor-block-settings-dropdownmenu-v2 branch from 1bcc5a2 to 8811ea2 Compare June 15, 2023 19:23
@ciampo ciampo force-pushed the try/block-editor-block-settings-dropdownmenu-v2 branch from 8811ea2 to d7dcf02 Compare June 15, 2023 20:37
@ciampo
Copy link
Contributor Author

ciampo commented Jun 16, 2023

Update:

After working on this PR pretty much for the whole week, I've managed to iron out most wrinkles, but I've hit a bit of a wall: modals opened on top of the dropdown (for example, the one for creating reusable blocks, or for locking/unlocking a block) don't work properly. (as also highlighted by e2e test failures).

This is a consequence of the modal nature of the new experimental DropdownMenu, which causes the component to trap focus and disable pointer events when opened.
Modal components displayed on top of DropdownMenu don't receive keyboard focus, making them not interactive. I'm currently in the process of looking for solutions to this problem, but so far the only potential solution seems to be to refactor our Modal component to use Radix's Dialog, which is able to communicate with Radix's DropdownMenu and handle focus correctly.

A possibility that I briefly explored is to close the DropdownMenu component when opening a Modal, but that approach also has its flaws:

  • we would still have focus management issues, since the DropdownMenu component would normally restore focus on its trigger when closed, thus stealing it from the Modal
  • In the react tree, Modals are usually rendered as children of DropdownMenu; therefore, closing the dropdown menu also instantly hides the modals. Avoiding this situation would require a large re-architecture and would likely cause breaking changes for the third-party developers.

I've also explored setting the DropdownMenu as non-modal:

  • it would only partially solve the compatibility issues with our Modal component
  • it would cause a series of other issues, like:
    • the one mentioned by Andrew in his comment above
    • the dropdown menu would not sync its position correctly with the block toolbar as the page scrolls (this can happen when the dropdown menu is not modal)
    • probably more incompatibility issues with our Popover-based components, which we haven't discovered yet

We always knew that there was a chance for this kind of issue when introducing a new component that doesn't rely on our internal Popover, and this PR is definitely informing us on how to best tackle them.

@ciampo ciampo force-pushed the try/block-editor-block-settings-dropdownmenu-v2 branch from 4dfe304 to 601a07e Compare June 16, 2023 12:11
@diegohaz
Copy link
Member

This is a consequence of the modal nature of the new experimental DropdownMenu, which causes the component to trap focus and disable pointer events when opened.
Modal components displayed on top of DropdownMenu don't receive keyboard focus, making them not interactive. I'm currently in the process of looking for solutions to this problem, but so far the only potential solution seems to be to refactor our Modal component to use Radix's Dialog, which is able to communicate with Radix's DropdownMenu and handle focus correctly.

I'd suggest disabling the default modal behavior and implementing a custom modal logic that considers nested/third-party dialogs. Even though you can change all dialogs in the app to use Radix's Dialog, you can't control how third-party dialogs will render. This includes browser extensions users may have installed, like Google Translate, Grammarly, 1Password, etc., but also other persistent widgets that should be accessible in parallel (like floating chat widgets).

Here's how we fixed this on Ariakit with a brief explanation: ariakit/ariakit#2339

@diegohaz
Copy link
Member

diegohaz commented Jun 17, 2023

Even if a third-party developer was to add a legacy MenuItem to the block settings dropdown via the BlockSettingsMenuControls slotfill, the MenuItem would continue to render without any errors, and clicking on the item would still cause the expected results. The only difference may be visual, as the new dropdownmenu components have slightly different styles and spacing.

I'm also curious to learn how this works. As far as I know, SlotFill won't automatically provide context to elements rendered outside the original tree. If Radix uses React Context to control its menu items, the context should be manually re-created in the fill tree, otherwise things like roving tabindex may not work as expected. That's what the Block Toolbar component does.


Oh, I think I got it. The dropdown slots aren't using the bubblesVirtually prop. The block toolbar slot does. Is migrating all slots to bubblesVirtually still the plan? If so, things might break for third-party components that depend on an internal React context like Radix. I wrote about it a few years ago.

I created this example using Ariakit Menu and the Modal component from @wordpress/components that works with SlotFill independently of the bubblesVirtually prop: https://stackblitz.com/edit/ariakit-nested-menu-wordpress?file=menu-nested%2Fmenu.tsx,menu-nested%2Findex.tsx

Some things to note:

  • It works with the WordPress modal component and most third-party dialogs.

  • The modal behavior on Ariakit is similar to the native dialog element. Keyboard users can access the browser chrome the same way as mouse users do.

  • Screen reader users on touch devices can't press Esc to close the menu. If you don't explicitly render a close menu button within the modal menu, those users will be trapped there. Their only options will be either reloading the page or clicking on a menu item (that does another thing in addition to closing the menu). That's why Ariakit will automatically render a visually hidden dismiss button inside the modal menu.

  • Currently, Ariakit uses the store to control most of the menu behaviors, but nested menus are controlled using React Context, just like Radix. Nested menus inside SlotFill with bubblesVirtually may not work as expected (they will just be a dropdown menu inside another dropdown menu). But supporting nested menus that aren't nested in the React tree is in the plans. This should be a relatively easy implementation, and the API would be as simple as this:

    const parent = useMenuStore()
    const nested = useMenuStore({ parent })

@ciampo
Copy link
Contributor Author

ciampo commented Jun 19, 2023

Hey @diegohaz , thank you for chiming in!

I'd suggest disabling the default modal behavior and implementing a custom modal logic that considers nested/third-party dialogs. . Even though you can change all dialogs in the app to use Radix's Dialog, you can't control how third-party dialogs will render. [...] Here's how we fixed this on Ariakit with a brief explanation: ariakit/ariakit#2339

I'll look into this and report back. As I stated above, removing modality will cause another issue: radix's (or other third-party libs) popover-based components won't work well with anchors in the iframed editor. In our first-party Popover component we fixed that by applying some custom middleware to floating-ui, and to apply that same "fix" for Radix (or any third-party library) we'd need to:

  • either add custom npm patch
  • manage to get the fix in Radix (or third party library), wait for the release
  • manage to get the fix in floating-ui, wait for a release, then hope that Radix (or third party library) updates their dependencies, and then we'll get it

I'm also curious to learn how this works [...] otherwise things like roving tabindex may not work as expected

My point regarding the MenuItem component was more about reducing breaking changes to a minimum when the legacy MenuItem component is rendered inside the new v2 DropdownMenu component via slotfill.

The point that you make about internal context not being passed down correctly makes sense though, and would also apply to using the new menu items from the new DropdownMenu component.

Is migrating all slots to bubblesVirtually still the plan? If so, things might break for third-party components that depend on an internal React context like Radix.

I'm not 100% sure if it is, but I assume that if we got Radix (or third-party libraries) to export their context, we could simply forward them via fillProps like we already do with other contexts?

I created this example using Ariakit Menu and the Modal component from @wordpress/components that works with SlotFill independently of the bubblesVirtually prop

Is the reason why it works because data is shared via ariakit stores instead of React Context?

@diegohaz
Copy link
Member

diegohaz commented Jun 19, 2023

I'll look into this and report back. As I stated above, removing modality will cause another issue: radix's (or other third-party libs) popover-based components won't work well with anchors in the iframed editor. In our first-party Popover component we fixed that by applying some custom middleware to floating-ui

Where can I learn more about that? Is there a minimum reproduction of that behavior somewhere so I can play with?

I'm not 100% sure if it is, but I assume that if we got Radix (or third-party libraries) to export their context, we could simply forward them via fillProps like we already do with other contexts?

Yes. But that means they will either have to expose their internals or design a public API around that. The first option will put a burden on consumers that will rely on a very unstable code. The latter puts a burden on the library maintainers as designing that kind of API may take a lot of time (it took me years).

Is the reason why it works because data is shared via ariakit stores instead of React Context?

For the SlotFill thing, yes.

@ciampo
Copy link
Contributor Author

ciampo commented Jun 19, 2023

Where can I learn more about that? Is there a minimum reproduction of that behavior somewhere so I can play with?

Last year the Popover component was refactored to using floating-ui, causing a very high number of regressions that took months to fix (see this tracking issue for a few examples).

I had also opened a few issues on floating-ui's repository, the main one being this issue about cross-document support.

On our first party Popover, we do a few things to improve the situation when the anchor if in the iframe and the floating is in the iframe's parent document:

Yes. But that means they will either have to expose their internals or design a public API around that. The first option will put a burden on consumers that will rely on a very unstable code. The latter puts a burden on the library maintainers as designing that kind of API may take a lot of time (it took me years).

Couldn't the Context be exposed, while leaving the contents of the context as a private implementation detail? Consumers would just need to know that if they need to forward the context, they can do so without worrying about what data is included.

@richtabor
Copy link
Member

Just following up on this; was there any movement since June?

@ciampo
Copy link
Contributor Author

ciampo commented Sep 1, 2023

Just following up on this; was there any movement since June?

Hey @richtabor , I have been AFK for the past 2 months and therefore wasn't able to continue work on this.

At the moment, it looks like the experimental Radix UI-based DropdownMenu is not meeting our requirements (see #51400 (comment) for more details). I even opened an issue on the Radix UI repo, but 3 months have passed and no one from the project's maintainers has even replied.

In the upcoming weeks, I will try to test Ariakit's DropdownMenu component for this very same test case (the block editor settings dropdown), to see if hopefully it will meet our requirements.

@tyxla
Copy link
Member

tyxla commented Dec 21, 2023

@ciampo should we drop this now that we're not planning to continue with Radix as an alternative for the DropdownMenu implementation?

@ciampo
Copy link
Contributor Author

ciampo commented Dec 22, 2023

Yes. I will still refer to this PR when applying the same changes with the ariakit-based dropdown menu, but in the meantime we can close this PR.

@ciampo ciampo closed this Dec 22, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants