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

Implement Tabs in editor settings #55360

Merged
merged 23 commits into from
Dec 12, 2023
Merged

Implement Tabs in editor settings #55360

merged 23 commits into from
Dec 12, 2023

Conversation

chad1008
Copy link
Contributor

@chad1008 chad1008 commented Oct 13, 2023

Related: #52997

What?

Implement the new Tabs component in the editor settings sidebar

Why?

Due to its more granular nature, Tabs is better suited to more applications than its predecessor, TabPanel. Implementing it here will ensure it gets all of the accessibility and other benefits, and ongoing support, of the packaged component. This move also cuts down on the amount of custom CSS needed, as we're using a component with its own styles already built in that no longer need to be duplicated.

How?

This PR modifies Tabs slightly by exposing the context that the component uses to pass data to its subcomponents. The editor settings sidebar implements an internal ComplimentaryArea, which in turn has an internal Slot/Fill that we need to pass our context through.

Now that we are exposing Tabs.Context we can use the relevant provider to pass the context wherever it needs to go.

Testing Instructions

  1. Open the post editor
  2. Ensure the editor settings sidebar on the right hand side of the screen opens and closes as expected
  3. Test both tabs (Post and Block). Ensure they display the correct sidebar/controls
  4. In the main editor panel, click between a block and the post title.
  5. Ensure that when you click on a block, the Block sidebar is automatically selected. When you click the post title, the Post sidebar should be displayed.
  6. Open a new page in the editor. Select whatever template you'd like (we're going to edit it in a minute).
  7. Confirm the tabs and editor-based selection you tested on a post also work the same way here.
  8. On the Page sidebar, click on the name of the currently selected template, then click "Edit Template" in the modal that appears.
  9. Confirm that the sidebar correctly enters "Template mode", showing you a Template tab instead of a Page tab.
  10. Confirm once again that clicking on a block activates the Block sidebar, selecting that tab automatically.

Testing Instructions for Keyboard

  1. Open a new post in the post editor.
  2. Enter text into the title field (should be focused by default)
  3. Press Enter/Return to move to the first paragraph block
  4. Confirm that the sidebar updated to the Block tab when the block was focused
  5. Press Up Arrow to return to the Title field
  6. Confirm that the Post tab is automatically selected
  7. Press Tab to focus the sidebar
  8. Use the arrow keys to navigate between buttons and Enter/Return/Space to activate a button and view that tab

Known Issues

  • While writing the above testing steps, I discovered the Tabs implementation is breaking the close button on the sidebar. Activating that button via mouse or keyboard triggers a maximum update depth error from React. That button is provided by the ComplementaryAreaHeader subcomponent. I'm looking into why and how to address that.
  • By default, Tabs should place the focus on the tablist element, and then navigate between individual tabs with arrow keys. Arrowing to a tab should activate it automatically. With this implementation, when tabbing over to the sidebar the focus lands directly on the first tab (which semantically is a button). You then use the arrow keys to move between tabs/buttons, but because the actual button is focused and not the tablist, you need to explicitly activate the button with enter/return/space. I suspect this is being caused Tabs being nested inside a ComplementaryArea but need to look into it further.
Copy link

github-actions bot commented Nov 1, 2023

Size Change: -495 B (0%)

Total Size: 1.71 MB

Filename Size Change
build/edit-post/index.min.js 32.7 kB -96 B (0%)
build/edit-post/style-rtl.css 7.24 kB -201 B (-3%)
build/edit-post/style.css 7.23 kB -198 B (-3%)
ℹ️ 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-editor/index.min.js 247 kB
build/block-editor/style-rtl.css 15.4 kB
build/block-editor/style.css 15.4 kB
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 60 B
build/block-library/blocks/archives/style-rtl.css 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 587 B
build/block-library/blocks/button/editor.css 587 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 432 B
build/block-library/blocks/table/editor.css 432 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.5 kB
build/block-library/editor.css 12.4 kB
build/block-library/elements-rtl.css 54 B
build/block-library/elements.css 54 B
build/block-library/index.min.js 213 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.6 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.87 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-site/index.min.js 194 kB
build/edit-site/style-rtl.css 14.5 kB
build/edit-site/style.css 14.6 kB
build/edit-widgets/index.min.js 17.3 kB
build/edit-widgets/style-rtl.css 4.71 kB
build/edit-widgets/style.css 4.71 kB
build/editor/index.min.js 52.8 kB
build/editor/style-rtl.css 4.32 kB
build/editor/style.css 4.32 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.28 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.5 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 Nov 1, 2023

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

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.

  • While writing the above testing steps, I discovered the Tabs implementation is breaking the close button on the sidebar. Activating that button via mouse or keyboard triggers a maximum update depth error from React. That button is provided by the ComplementaryAreaHeader subcomponent. I'm looking into why and how to address that.

Interesting. Sounds like the click handler there is triggering some sort of infinite loop.

  • By default, Tabs should place the focus on the tablist element, and then navigate between individual tabs with arrow keys. Arrowing to a tab should activate it automatically. With this implementation, when tabbing over to the sidebar the focus lands directly on the first tab (which semantically is a button).

When looking at the Tabs storybook example, I can't see the tablist element receiving focus. Pressing tab seems to move focus directly to the selected tab.

You then use the arrow keys to move between tabs/buttons, but because the actual button is focused and not the tablist, you need to explicitly activate the button with enter/return/space. I suspect this is being caused Tabs being nested inside a ComplementaryArea but need to look into it further.

I'm not sure I understand exactly what is going on. My expectation is that pressing arrow keys when the individual tab buttons are focused should move the tab selection (no need for tablist to be focused).

@chad1008
Copy link
Contributor Author

chad1008 commented Nov 3, 2023

When looking at the Tabs storybook example, I can't see the tablist element receiving focus. Pressing tab seems to move focus directly to the selected tab.

Yeah, you're right, sorry. I was misremembering. Focus does go to the buttons. Here's what the difference in keyboard navigation looks like, after looking more closely:

trunk (legacy behavior):

  • The tab key moves focus to the first tab/button.
  • To move between tabs you use tab/shift+tab.
  • It is possible to focus the currently selected tab by pressing the tab key.
  • Once focused via keyboard action, tabs must be explicitly activated with return/enter/space
  • Focus doesn't wrap around the tablist when tabbing past the first/last tab.

Tabs in Storybook (expected behavior):

  • The tab key moves focus to the first tab/button.
  • To move between tabs you use left arrow/right arrow.
  • It is possible to focus the currently selected tab by pressing the arrow key to get back to it.
  • Once focused via keyboard action, tabs are automatically activated with (unless selectOnMove is set to false)
  • Focus does wrap around the tablist when arrowing past the first/last tab

this PR:

  • ✅ The tab key moves focus to the first tab/button.
  • ✅ To move between tabs you use left arrow/right arrow.
  • ⚠️ It is not possible to focus the currently selected tab by pressing the arrow key to get back to it. The active tab is just skipped over.
  • ⚠️ Once focused via keyboard action, tabs must be explicitly activated with return/enter/space
  • ⚠️ Focus does not wrap around the tablist when arrowing past the first/last tab

So the focus is indeed going to the right place, but navigation and tab selection for keyboard users are a bit off when compared to expected Tabs behavior.

@ciampo
Copy link
Contributor

ciampo commented Nov 7, 2023

I wonder if that's related to Slot/Fill and how events are bubbling. To test my hypothesis, it would be interesting to refactor the code and experiment adding bubblesVirtually to the Slot (not sure if any other changes would be needed off the top of my head).
Another counterproof that this is related to Slot/Fill could be to temporarily remove those components (without caring too much about visual UI changes), and see if the component reverts back to behaving as expected.

@chad1008
Copy link
Contributor Author

chad1008 commented Nov 10, 2023

Another counterproof that this is related to Slot/Fill could be to temporarily remove those components (without caring too much about visual UI changes), and see if the component reverts back to behaving as expected.

I know when you and I looked at this together the other day, bubblesVirtually didn't seem to solve the problem, but I thought the idea of removing the Slot/Fill from the equation was an interesting one. To test it, I removed the ComplementaryAreaFill sub-component entirely, so the Tabs components could just render normally.

My test diff:
diff --git a/packages/interface/src/components/complementary-area/index.js b/packages/interface/src/components/complementary-area/index.js
index 887c447d92..aa82f0e298 100644
--- a/packages/interface/src/components/complementary-area/index.js
+++ b/packages/interface/src/components/complementary-area/index.js
@@ -196,14 +196,7 @@ function ComplementaryArea( {
 				</ComplementaryAreaMoreMenuItem>
 			) }
 			{ isActive && (
-				<ComplementaryAreaFill
-					className={ classnames(
-						'interface-complementary-area',
-						className
-					) }
-					scope={ scope }
-					id={ identifier.replace( '/', ':' ) }
-				>
+				<div className={ 'interface-complementary-area' }>
 					<ComplementaryAreaHeader
 						className={ headerClassName }
 						closeLabel={ closeLabel }
@@ -244,7 +237,7 @@ function ComplementaryArea( {
 						) }
 					</ComplementaryAreaHeader>
 					<Panel className={ panelClassName }>{ children }</Panel>
-				</ComplementaryAreaFill>
+				</div>
 			) }
 		</>
 	);

It messes up the UI placement, and the tab sequence is a bit awkward this way... but once things aren't being routed through a Fill, but here's what I found:

  • Still cannot arrow key to the currently selected tab. It gets skipped. (consistent with this PRs current issues)
  • Tabs are not automatically activated like they should be. (consistent with this PRs current issues)
  • Manually activating a tab automatically moves the focus to the next tab. (new, and not expected behavior)
  • Closing the sidebar is still broken.

So removing the Fill feels like it left keyboard navigation even more broken, which I'll admit surprises me.

I was talking this over with @brookewp yesterday as well, and she made an interesting observation. Keyboard navigation on this PR is behaving a little like the Tabs component has selectOnMove set false, aka "manual activation." If you look at the Manual Activation story in storybook, the data-focus-visible and data-active-item attributes both flow to whatever button is focused as you arrow between tabs. The tab doesn't get selected, but it does register as both focused and active. On this PR, as you arrow between tabs data-focus-visible behaves this way, but data-active-item does not. That attribute doesn't apply until the tab is manually selected, which is another departure from expected Tabs behavior.

@ciampo, you shared the concern with me that it's possible some of Ariakit's internal store/context is what isn't making it through the ComplementaryArea internal Slot/Fill and causing these unexpected behaviors. I'm not sure if the data attribute discovery Brooke made points us more in that direction or not, but it's all very odd at this point.

@jsnajdr, I know you're pretty well-versed in Slot/Fill issues, if you have a chance to look any of this over, does anything jump out at you that I might want to look into? Happy to chat/investigate directly if that would help outline the problem space more clearly.

Edit to add: I did try to recreate the problem in a simplified example as well, albeit unsuccessfully. There's a temporary Slot Fill Problem Example pushed to this PR that tries to replicate the issue, even on that story, complete with slot/fill I wasn't able to recreate the keyboard behavior issues.

@jsnajdr
Copy link
Member

jsnajdr commented Nov 13, 2023

the concern with me that it's possible some of Ariakit's internal store/context is what isn't making it through the ComplementaryArea internal Slot/Fill and causing these unexpected behaviors.

The first thing to check is what type of slot/fill your ComplementaryArea is. Is it the "bubblesVirtually" type, or the "classic" type?

With "bubblesVirtually", a component is rendered in the React context of the Fill, although in DOM it's rendered under the Slot's elements. It doesn't see the Slot's React context. So, if the Slot is rendered as a child of some Ariakit component, the parent's Ariakit context is not visible to it.

This is a common problem, and we've discussed this with @ciampo before, mainly in #51264 which introduced a new forwaredContext API.

@chad1008
Copy link
Contributor Author

The first thing to check is what type of slot/fill your ComplementaryArea is. Is it the "bubblesVirtually" type, or the "classic" type?

Thanks @jsnajdr! This particular slot/fill is not the bubbleVirtually variety, though @ciampo and I did experiment with that while pairing, and it didn't seem to resolve the issue.

Copy link
Contributor

@ciampo ciampo left a comment

Choose a reason for hiding this comment

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

I took some time to look into this properly, and found out what is causing the issues highlighted above (spoiler alert — it's not Slot/Fill!)

Problem 1: keyboard interaction on tabs is broken

When I started looking at the symptoms I was particularly curious about the fact that, while keyboard interactions were not working, clicking individual tabs was working.

I then noticed that the Tab components have dedicated onClick handlers, which made me realize that the Tabs component is not being controlled correctly. The right way to control the component is via the onSelect callback on the Tabs component, while there's no need for those onClick handlers.

I tested those changes on my machine, and all interactions on the tabs started working again (click to expand)
diff --git a/packages/edit-post/src/components/sidebar/settings-header/index.js b/packages/edit-post/src/components/sidebar/settings-header/index.js
index 37045cb697..6c5644acdb 100644
--- a/packages/edit-post/src/components/sidebar/settings-header/index.js
+++ b/packages/edit-post/src/components/sidebar/settings-header/index.js
@@ -15,11 +15,6 @@ import { unlock } from '../../../lock-unlock';
 const { Tabs } = unlock( componentsPrivateApis );
 
 const SettingsHeader = ( { sidebarName } ) => {
-	const { openGeneralSidebar } = useDispatch( editPostStore );
-	const openDocumentSettings = () =>
-		openGeneralSidebar( 'edit-post/document' );
-	const openBlockSettings = () => openGeneralSidebar( 'edit-post/block' );
-
 	const { documentLabel, isTemplateMode } = useSelect( ( select ) => {
 		const postTypeLabel = select( editorStore ).getPostTypeLabel();
 
@@ -53,7 +48,6 @@ const SettingsHeader = ( { sidebarName } ) => {
 			<Tabs.TabList>
 				<Tabs.Tab
 					id={ 'edit-post/document' }
-					onClick={ openDocumentSettings }
 					aria-label={
 						isTemplateMode ? templateAriaLabel : documentAriaLabel
 					}
@@ -65,7 +59,6 @@ const SettingsHeader = ( { sidebarName } ) => {
 				</Tabs.Tab>
 				<Tabs.Tab
 					id={ 'edit-post/block' }
-					onClick={ openBlockSettings }
 					aria-label={ blockAriaLabel }
 					// translators: Data label for the Block Settings Sidebar tab.
 					data-label={ __( 'Block' ) }
diff --git a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js
index e8681205b6..be654060a6 100644
--- a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js
+++ b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js
@@ -5,8 +5,8 @@ import {
 	BlockInspector,
 	store as blockEditorStore,
 } from '@wordpress/block-editor';
-import { useSelect } from '@wordpress/data';
-import { Platform, useContext } from '@wordpress/element';
+import { useSelect, useDispatch } from '@wordpress/data';
+import { Platform, useContext, useCallback } from '@wordpress/element';
 import { isRTL, __ } from '@wordpress/i18n';
 import { drawerLeft, drawerRight } from '@wordpress/icons';
 import { store as interfaceStore } from '@wordpress/interface';
@@ -73,6 +73,8 @@ const SettingsSidebar = () => {
 		[]
 	);
 
+	const { openGeneralSidebar } = useDispatch( editPostStore );
+
 	const Content = () => {
 		// Because `PluginSidebarEditPost` renders a `ComplementaryArea`, we
 		// need to forward the `Tabs` context so it can be passed through the
@@ -120,8 +122,17 @@ const SettingsSidebar = () => {
 		);
 	};
 
+	const onTabsSelect = useCallback(
+		( newSelectedTabId ) => {
+			if ( !! newSelectedTabId ) {
+				openGeneralSidebar( newSelectedTabId );
+			}
+		},
+		[ openGeneralSidebar ]
+	);
+
 	return (
-		<Tabs selectedTabId={ sidebarName }>
+		<Tabs selectedTabId={ sidebarName } onSelect={ onTabsSelect }>
 			<Content />
 		</Tabs>
 	);

Problem 2: closing the sidebar causes the editor to crash

When looking at this issue, I started looking at the console error, which suggested that the component rendering infinitely was the complementary area's Fill, which is where the tabs are rendered.

Looking at the changes that this PR introduces, one of the first suspects for causing those extra re-renders was the part, in the SettingsSidebar component, where we have to manually forward the Tabs.Context to the SettingsHeader component. I tried to comment the context provider out, and closing the sidebar started working again.

Now, that couldn't be the fix, but at least it was a big hint: the issue seems to be related to the Tabs component and its context. I then took a look at the Tabs component, and immediately noticed that the internal context value is not memorized, which means that a new object is going to be created every time the component re-renders. I wrapped the context value in a useMemo hook, and reloaded the editor.

Closing the sidebar still crashed, but the error message was different. Instead of happening in the Fill component, not the infinite rendering loop was happening directly in the Tabs component. I therefore started looking at the code inside the Tabs component, in particular at the three useLayoutEffect hooks. One of them, in particular, runs only when the component is used in controlled mode. I commented it out, and closing the sidebar started to work again!

So, what is happening? Basically, when the sidebar is closed, all the different tabs are removed from the page, which means that this line of code can't find a selected tab (since the array of items is empty). And therefore, this line of code runs, trying to reset the selected tab id to null. Depending on how the controlled Tabs implementation was coded (see how I solved problem 1), this may cause an infinite loop. Therefore, the solution that I found here was to pass selectedTabId={null} to Tabs when the sidebar is closed.

These are changes that I applied on my machine (click to expand)
diff --git a/packages/components/src/tabs/index.tsx b/packages/components/src/tabs/index.tsx
index 826c4e7c9d..5273e4c21f 100644
--- a/packages/components/src/tabs/index.tsx
+++ b/packages/components/src/tabs/index.tsx
@@ -8,7 +8,7 @@ import * as Ariakit from '@ariakit/react';
  * WordPress dependencies
  */
 import { useInstanceId } from '@wordpress/compose';
-import { useLayoutEffect, useRef } from '@wordpress/element';
+import { useLayoutEffect, useMemo, useRef } from '@wordpress/element';
 
 /**
  * Internal dependencies
@@ -154,8 +154,13 @@ function Tabs( {
 		setSelectedId,
 	] );
 
+	const contextValue = useMemo(
+		() => ( { store, instanceId } ),
+		[ store, instanceId ]
+	);
+
 	return (
-		<TabsContext.Provider value={ { store, instanceId } }>
+		<TabsContext.Provider value={ contextValue }>
 			{ children }
 		</TabsContext.Provider>
 	);
diff --git a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js
index be654060a6..89a756d258 100644
--- a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js
+++ b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js
@@ -39,8 +39,8 @@ const SIDEBAR_ACTIVE_BY_DEFAULT = Platform.select( {
 } );
 
 const SettingsSidebar = () => {
-	const { sidebarName, keyboardShortcut, isTemplateMode } = useSelect(
-		( select ) => {
+	const { sidebarName, isSidebarOpen, keyboardShortcut, isTemplateMode } =
+		useSelect( ( select ) => {
 			// The settings sidebar is used by the edit-post/document and edit-post/block sidebars.
 			// sidebarName represents the sidebar that is active or that should be active when the SettingsSidebar toggle button is pressed.
 			// If one of the two sidebars is active the component will contain the content of that sidebar.
@@ -51,6 +51,7 @@ const SettingsSidebar = () => {
 			let sidebar = select( interfaceStore ).getActiveComplementaryArea(
 				editPostStore.name
 			);
+			const isOpen = sidebar !== null;
 			if (
 				! [ 'edit-post/document', 'edit-post/block' ].includes(
 					sidebar
@@ -66,12 +67,11 @@ const SettingsSidebar = () => {
 			).getShortcutRepresentation( 'core/edit-post/toggle-sidebar' );
 			return {
 				sidebarName: sidebar,
+				isSidebarOpen: isOpen,
 				keyboardShortcut: shortcut,
 				isTemplateMode: select( editPostStore ).isEditingTemplate(),
 			};
-		},
-		[]
-	);
+		}, [] );
 
 	const { openGeneralSidebar } = useDispatch( editPostStore );
 
@@ -132,7 +132,10 @@ const SettingsSidebar = () => {
 	);
 
 	return (
-		<Tabs selectedTabId={ sidebarName } onSelect={ onTabsSelect }>
+		<Tabs
+			selectedTabId={ isSidebarOpen ? sidebarName : null }
+			onSelect={ onTabsSelect }
+		>
 			<Content />
 		</Tabs>
 	);
packages/components/src/tabs/index.tsx Outdated Show resolved Hide resolved
packages/interface/src/lock-unlock.js Outdated Show resolved Hide resolved
@chad1008
Copy link
Contributor Author

Thank you for debugging there @ciampo! I've updated the onSelect callback from what you and I looked at when we last spoke to match the more-complete version I see in your first diff.

I was following a similar thread for the closed-sidebar-loop-of-infinite-doom, but you arrived at a speedier and more effective solution than I did, so I've applied those changes as well.

I believe I've applied or replied to all open comments, but please let me know if I've missed anything.

Oh, there are some things that will no longer work with this PR. Specifically, anything that relied on changes to Tabs itself, like the exposure of Tabs.Context or memoizing that context value. Once #56224 merges I can rebase this PR.

@chad1008
Copy link
Contributor Author

rebased ✅

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.

Tabs seem to be working well!

I that there's a double border now showing:

Screenshot 2023-11-17 at 20 01 01

That's because we're now wrapping each tab panel contents into a TabPanel component (where previously we had a react fragment, ie. no dom element). The extra div rendered by the TabPanel between the Panel and the PanelBody causes this rule not to apply anymore, which causes the top border of the first panel body to show.

The simplest solution (without involving style overrides) IMO could be to pass a className to PluginSidebarEditPost, through which we apply a margin-top: -1px style 🤞 We should add a comment to why that is necessary, though.

Apart from that, I think that this PR can be marked as ready for review. It would be also be great if @alexstine could take a look (I know he's been waiting for this part of the editor to have accessible tabs in a while!)

@ciampo ciampo requested review from youknowriad, alexstine and a team November 17, 2023 19:17
@chad1008 chad1008 added [Type] Enhancement A suggestion for improvement. [Package] Components /packages/components [Package] Editor /packages/editor and removed [Package] Components /packages/components labels Nov 17, 2023
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.

Some e2e tests are still failing, could it be because the test selector is looking for the edit-post-sidebar__panel-tab classname, which doesn't seem to be used anymore in this PR?

@chad1008
Copy link
Contributor Author

chad1008 commented Dec 9, 2023

Some e2e tests are still failing, could it be because the test selector is looking for the edit-post-sidebar__panel-tab classname, which doesn't seem to be used anymore in this PR?

Yep! I actually just popped back on after dinner to wrap up (what should be) the last test failure. That now-defunct classname was the culprit in some other spots as well. Checks are running now 🤞

This PR has also been updated and rebased to account for the new controlled mode focus behavior, and the new tabId prop.

@chad1008
Copy link
Contributor Author

chad1008 commented Dec 9, 2023

Green is my new favorite color.

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.

🚀

@chad1008 chad1008 merged commit c90bb03 into trunk Dec 12, 2023
52 checks passed
@chad1008 chad1008 deleted the tabs-editor-settings branch December 12, 2023 19:23
@github-actions github-actions bot added this to the Gutenberg 17.3 milestone Dec 12, 2023
@youknowriad
Copy link
Contributor

I'm almost certain (hard to tell because of the margin error in our performance metrics) that this PR introduced a small performance regression in both "typing" and "block select" metric. Not saying we should revert but it would be good to understand why. https://www.codevitals.run/project/gutenberg

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Package] Editor /packages/editor [Type] Enhancement A suggestion for improvement.
6 participants