Repository navigation
States: Scope per-instance rules to the block's root element - #83986
Conversation
🤖 PR meta 🤖🎉 PropsIf 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. Updated as activity occurs, without notifying anyone named here. Add the 📦 Bundle sizeSize Change: +225 B (0%) Total Size: 8.29 MB 📦 View Changed
⚡ PerformanceShow the resultsClient side metrics exclude the server response time. front-end-block-theme
front-end-classic-theme
media-processing
media-upload
post-editor
site-editor
|
talldan
left a comment
There was a problem hiding this comment.
I guess it fixes it, and at least it's somewhat understandable why the list item selector is defined like it is given the block on the frontend has no classname of its own.
The selectors API is a bit worrying. It's simple on the surface which makes it seem like any selector whatsoever can be defined, but underneath there's increasing complexity (#78276, #78326 also) to support that simplicity.
Tough one to improve though, I don't really have any ideas. 🤷
58f3784 to
dbb4114
Compare
dbb4114 to
12b3f72
Compare
The unit tests pass selectors as literals, so a change to a block's `block.json` would not reach them. Two tests now exercise what the block actually registers. The PHP one reads `core/list-item`'s registered `root` and `border` selectors and asserts the scoped result lands on the block's own element. The end-to-end one gives a List Item a mobile background, narrows the viewport and reads the computed colour off the `li`, with a paragraph alongside it as a control. Both fail on trunk and pass here.
459c870 to
44dde8e
Compare
Yeah, this is an interesting case. As further features like block style variations, states, and responsive styles got added, what is being asked of that API is much more complex or demanding now. I think a part of the problem is weird legacy choices for some blocks. Like styles landing on inner elements by default as if they were root selectors e.g Button block. Or, Paragraph and List Item blocks being elements and so on. Things would be much simpler if every block had block class on its wrapper and a default or root selector that targets that. Then anything belonging on inner elements defined via the feature selectors in the Selectors API. Like you said, I'm not seeing a huge number of alternatives or clean paths there. For now, with the additional tests including an e2e added, I'll get this merged so the List Item block supports relying on it can work as intended. Happy to address anything further in a follow-up. Thanks for the review and insight 👍 |
Fixes responsive and pseudo-state block values doing nothing on List Item, in the editor and on the front end.
What?
Scopes per-instance state rules to the element the instance class is on, when a block's selector starts with a different block's class.
Why?
List Item declares
"root": ".wp-block-list > li". The instance class goes on theli, but the rule swapped the leading.wp-block-listfor it. That gives.wp-states-xxxx > li, which asks for anliinside the list item and matches nothing.So a List Item mobile value for colour, background, typography, spacing or border is inert today. Global Styles and the desktop block value already work. Found while adding background support to List Item.
How?
A leading compound is treated as an ancestor only when:
wp-block-*class that is not this block's own, derived from the block name, andwp-block-*class of its own.Then the ancestor and its combinator are kept, and the next compound is swapped for the instance class. Everything else keeps today's leading swap.
.wp-block-list > li(List Item).wp-block-list > .wp-states-xxxx.wp-block-list:not(.wp-block-list .wp-block-list) > li(List Item border).wp-block-list:not(.wp-block-list .wp-block-list) > .wp-states-xxxx.wp-block-search .wp-block-search__input.wp-states-xxxx .wp-block-search__input, as today.wp-block-button .wp-block-button__link.wp-states-xxxx .wp-block-button__link, as todayThe ancestor is kept rather than dropped, so the state rule matches the same element as the non-state rule and keeps the same specificity.
The same logic lives on both sides:
gutenberg_build_state_selector()inlib/block-supports/states.php, andbuildScopedBlockSelector()inpackages/block-editor/src/hooks/state-utils.js. Both now get the block name. No new API orblock.jsonfield.Blocks covered: List Item, for its root selector, its border selector and responsive element styles. Button, Icon, Table, Paragraph, Search and every other library selector are unchanged, since their leading compound is the block's own class or not a
wp-block-*class at all.One edge: a third-party block that borrows another block's class as its root, with a bare element after it, would now be read as an ancestor.
Testing Instructions
Setup: a List with three items. On the middle one set a background colour, then the same with Mobile selected (View > Responsive styles > Mobile) in a different colour.
Unit tests:
phpunit/block-supports/states-test.phpandpackages/block-editor/src/hooks/test/state-utils.js.Screenshots or screencast
Use of AI Tools
Implemented, built and screenshotted by a Claude Code agent from a predefined task.