Tabs: Pre-stabilization API cleanup and refactoring#79337
Conversation
The focusRef was never assigned a requestAnimationFrame id, so the unmount cleanup was always a no-op. Remove the unused ref, the window.cancelAnimationFrame destructuring, and the empty effect. Co-Authored-By: Claude <[email protected]>
The .is-active::before active indicator in editor.scss duplicated the rule already defined in style.scss, which loads in the editor too. Drop editor.scss and its editorStyle reference in block.json. Co-Authored-By: Claude <[email protected]>
The editor buttons used an is-active class while the front end used aria-selected, requiring two selectors for the same indicator. Give the editor buttons role="tab" and aria-selected, and drop the is-active class so style.scss matches a single [aria-selected="true"] selector. Co-Authored-By: Claude <[email protected]>
The tabs and tab-panel blocks ship an init.js individual-block entry, but tab-list and tab-panels were missing one, leaving the four-block set inconsistent. Add matching init.js so each block has a standalone build entry ahead of stabilization. Co-Authored-By: Claude <[email protected]>
The block defines no attributes, so the empty attributes object was redundant. Remove it to match blocks that simply omit the key. Co-Authored-By: Claude <[email protected]>
Tabs was the only block-library block exposing an unlocked public Interactivity store (core/tabs) alongside a locked core/tabs/private one, requiring a createReadOnlyProxy wrapper to guard internal state. No other block offers such a third-party extension point, and keeping it would commit us to a public API contract at stabilization. Collapse the two stores into a single locked core/tabs store, matching the pattern used by accordion, navigation, image, etc. Remove the now unused createReadOnlyProxy and rename privateState/privateActions to state/actions. Update the data-wp-interactive and wp_interactivity_state namespaces accordingly. Co-Authored-By: Claude <[email protected]>
The server-side tabs list carried `label` and `index` on every entry, but the front end only ever reads each tab's `id` (in view.js lookups and in the tab-list render callback). Those extra fields only inflated the serialized `wp_interactivity_state` payload and the `core/tabs-list` context. Emit the list as a plain array of ID strings and update both consumers accordingly, shrinking the SSR state and simplifying the lookups. Co-Authored-By: Claude <[email protected]>
`effectiveActiveIndex` was wrapped in useMemo despite being a plain `??` fallback between two primitives. Memoizing it costs more (dependency comparison plus hook overhead) than recomputing the value, so inline it and drop the now-unused import. Co-Authored-By: Claude <[email protected]>
The tabs block family has not shipped yet, so its server-side functions are introduced in 7.1.0 rather than 7.0.0. Correct the @SInCE tags to match the actual release. Co-Authored-By: Claude <[email protected]>
The render callback re-set `role="tabpanel"` (already produced by save.js) and a constant `tabindex="0"`, injecting static markup at render time for no reason. Move `tabindex` into the saved output and drop both static set_attribute() calls, leaving the callback to handle only the attributes that depend on the server-generated tab ID or interactivity state. Co-Authored-By: Claude <[email protected]>
The render callback set `data-wp-interactive` and a `data-wp-context` carrying `tab.id` on every panel. The namespace and the active-tab context are already inherited from the ancestor `wp-block-tabs` element, and nothing ever read `tab.id` (panel visibility resolves via the panel's own `id` attribute and the inherited `activeTabIndex`). Remove both so the callback only sets the panel-specific `aria-labelledby` and hidden binding. Co-Authored-By: Claude <[email protected]>
| // Public store for third-party extensibility. | ||
| store( 'core/tabs', { |
There was a problem hiding this comment.
Currently, there are no blocks that expose the interactivity API store. Whether or not to expose it should be carefully considered in the future based on user feedback.
Removing tab-list/editor.scss left a `@use` import in the block-library editor.scss aggregate, breaking the build. Remove the stale import. Co-Authored-By: Claude <[email protected]>
|
Size Change: -1.28 kB (-0.02%) Total Size: 7.5 MB 📦 View Changed
|
|
Flaky tests detected in d8173a8. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/28079631833
|
The Tab List block renders its buttons from the `tabs` attribute instead of inner blocks, so the layout block support never applied its container classes (`is-layout-flex`, `wp-container-*`) to the wrapper in the editor — those are only added via `useInnerBlocksProps`. As a result flex layout and blockGap had no effect in the editor, which previously required hardcoded styles. Apply `__unstableLayoutClassNames` to the wrapper manually so the layout support's flex and blockGap styles take effect in the editor, matching the front end. Set the default layout `flexWrap` to `nowrap` so the no-wrap behavior is driven by the layout support in both contexts. Co-Authored-By: Claude <[email protected]>
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
|
Let's also audit effects that update attributes and see if those values can be derived. While this has always been a bad pattern, it has become even flakier with RTC. See #78989. |
That's right. It's possible that useTabListItemsSync might be causing some issues in RTC. Since refactoring it would likely involve a significant amount of work, we are considering addressing it in a follow-up. P.S. If there are any other blockers that should be addressed to stabilize the Tabs block, please comment on #73230 🙇♂️ |
Mamaduka
left a comment
There was a problem hiding this comment.
Looks good to me ✅
Noticed a couple of issues, but those aren't related to this PR.
|
Thank you, @Mamaduka! |
Related to #73230
What?
Pre-stabilization cleanup for the Tabs block family.
Why?
This block is currently experimental, but once it is stabilized, we must maintain backward compatibility. At this stage, we need to remove redundant APIs and dead code to make it robust.
How?
The following is a list of the changes included in this PR.
useBlockProps. To fix this, I used__unstableLayoutClassNames. (ref)core/tabsstore and collapse everything into a single lockedcore/tabsstore (closes the third-party extension point) (ref)focusRef/cancelAnimationFramecleanup that never actually ran (ref)useMemoand an emptyattributesdefinition (ref1, ref2)role/tabindexfrom render-time output into the saved markup (ref1, ref2)editor.scssand unify the active state onaria-selected(ref1, ref2)init.jsto tab-list / tab-panels so all four blocks are structured the same way (ref1, ref2)@sincetags to 7.1.0Testing Instructions
Insert a Tab block and perform a smoke test. It should work correctly as before.
Use of AI Tools
This PR was authored with the assistance of Claude Code (Anthropic). All changes have been reviewed by the author.