Map resolved CSS to canonical block style attributes (#261, #259)#268
Merged
Conversation
Core blocks were emitted with a raw inline `style` STRING attribute
(e.g. wp:paragraph {"style":"color:..."}, wp:group {"style":"padding:..."}).
Core blocks expect a structured `style` OBJECT + `layout`, so the stored HTML
diverged from each block's save() and the editor flagged "unexpected or invalid
content" for nearly every styled element.
Generalize the ButtonStyleResolver approach (#241) to ALL blocks:
- New Style/StyleAttributeMapper translates resolved CSS declarations into the
canonical block style OBJECT (style.typography/color/spacing/border) and
serializes it back to the inline CSS + has-* support classes WordPress emits
in save(), keeping stored markup in sync with the block.
- presentationAttributes() now returns a canonical `style` object (never a
string) plus the `layout` attribute; display:flex|grid folds into `layout`.
- BlockFactory serializes the style object for every block (group, heading,
paragraph, list, quote, columns, icon, ...) via the shared mapper.
- HARD RULE: a raw `style` string is never emitted on a core block. Unmappable
declarations (position, overflow, transform, background images, aspect-ratio,
nav anchor/submenu CSS, ...) are dropped and ride on the preserved className.
- Buttons keep resolving from the raw merged CSS via a threaded resolvedStyle
callback (ButtonStyleResolver unchanged).
- Hidden-state safety (#259): display:none / visibility:hidden / opacity:0 base
states are not frozen onto content-bearing/interactive elements; they are
normalized away and surfaced as a frozen_hidden_state diagnostic finding.
Genuinely decorative / aria-hidden nodes may stay hidden.
Tests:
- New "canonical block style attributes" contract: guard asserting no emitted
core block carries a raw string `style`; positive cases (styled h2/p/div ->
canonical typography/color + layout flex); negative case (unmappable props ->
className, not a raw style string); hidden-nav normalization.
- 15 parity fixtures updated: only the style-attribute SHAPE changed
(raw string -> canonical object / layout / dropped-to-className); content and
structure are unchanged. Full suite green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #261
Closes #259
Problem
Core blocks were emitted with a raw inline
styleSTRING attribute (e.g.wp:paragraph {"style":"color:var(--x)"},wp:heading {"style":"font-size:clamp(...)"},wp:group {"style":"padding:...;background:..."}). Core blocks expect a structuredstyleOBJECT (style.typography/color/spacing/border) + thelayoutattribute, so the stored HTML diverged from each block'ssave()and the editor threw "This block contains unexpected or invalid content" for nearly every styled element. Only buttons were fixed previously (#241).Fix — generalize #241 to ALL blocks
New
Style/StyleAttributeMappertranslates an element's resolved CSS declarations (inline + matched<style>/linked CSS) into canonical block attributes and serializes them back to the inline CSS +has-*support classes WordPress writes insave(), keeping stored markup in sync with the block.Mapping implemented:
style.typography.{fontSize,fontWeight,lineHeight,letterSpacing,textTransform,textDecoration,fontStyle}style.color.{text,background,gradient}(+has-text-color/has-backgroundclasses)style.spacing.{padding,margin}(shorthand expanded to side objects)style.border.{width,style,color,radius}(+has-border-color)display:flex|grid(+ flex/grid/gap props) → the block'slayoutattributeWiring:
presentationAttributes()now returns a canonicalstyleobject (never a string) +layout, used by every block path (group, heading, paragraph, list, quote, columns, icon, …).BlockFactoryserializes the object for all blocks via the shared mapper.resolvedStylecallback (ButtonStyleResolverunchanged).Hard rule: never a raw
stylestring on a core blockDeclarations that don't map to a block support (position, inset, overflow, transform, box-shadow, background images, aspect-ratio, nav anchor/submenu CSS, …) are dropped and ride on the already-preserved
className(+ carried theme/companion CSS) — they are never emitted as astylestring.Hidden-state safety (#259)
display:none/visibility:hidden/opacity:0base states (responsive/JS-revealed) are no longer frozen onto content-bearing/interactive elements (nav, CTA) — they are normalized away and surfaced as afrozen_hidden_statediagnostic finding. Genuinely decorative /aria-hiddennodes may stay hidden.Tests
tests/contract/run.php):styleattr is an object or absent — never a raw string (locks in the fix / prevents regression); also asserts serialized markup carries no rawdisplay:style.<h2>/<p>→ canonicalstyle.typography/style.color; styled<div style="display:flex;...">→layout.type=flex+ canonical color/padding.position/inset/overflow) →className, not a raw style string.<nav style="display:none">is not frozen;frozen_hidden_statefinding surfaced.stylestring → canonical object /layout/ dropped-to-className. No content or structure changes.composer testfully green (contracts, unit, 128 parity fixtures, packaging);php -lclean.Deferred long-tail (documented)
text-align(maps to a per-blockalign/textAlignattribute) andblockGap/gapare dropped for now rather than emitted as raw styles — the no-raw-string guard holds. These can ride in a follow-up.🤖 Generated with Claude Code