Skip to content

fix: inline styles were not being shifted to custom properties - #448

Merged
jgerigmeyer merged 11 commits into
oddbird:mainfrom
jpzwarte:fix/435-shift-inline-styles
Aug 14, 2026
Merged

fix: inline styles were not being shifted to custom properties#448
jgerigmeyer merged 11 commits into
oddbird:mainfrom
jpzwarte:fix/435-shift-inline-styles

Conversation

@jpzwarte

@jpzwarte jpzwarte commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Fixes #435

fetchInlineStyles() only collected elements whose style attribute contains anchor or position-area, so an inline margin-left: 40px never went through cascadeCSS() and never got shifted into --margin-left-<uuid>.

The query is now built from SHIFTED_PROPERTIES, so inline insets, margins, sizing, padding and self-alignment are collected as well. Shifting appends the custom property rather than replacing the declaration, so the extra elements just get --margin-left-<uuid>: 40px alongside what they already had.

Without the fix:
CleanShot 2026-07-27 at 12 15 30@2x

With the fix:
CleanShot 2026-07-27 at 12 16 27@2x

Native:
CleanShot 2026-07-27 at 12 17 09@2x

@netlify

netlify Bot commented Jul 25, 2026

Copy link
Copy Markdown

Deploy Preview for anchor-position-wpt canceled.

Name Link
🔨 Latest commit e922b94
🔍 Latest deploy log https://app.netlify.com/projects/anchor-position-wpt/deploys/6a7f29134e41f90008ba1fea

@netlify

netlify Bot commented Jul 25, 2026

Copy link
Copy Markdown

Deploy Preview for anchor-polyfill ready!

Name Link
🔨 Latest commit e922b94
🔍 Latest deploy log https://app.netlify.com/projects/anchor-polyfill/deploys/6a7f2912d8e91c0008526ed9
😎 Deploy Preview https://deploy-preview-448--anchor-polyfill.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@jpzwarte
jpzwarte marked this pull request as ready for review July 25, 2026 12:02

@jamesnw jamesnw left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good, just a couple questions on this. Thanks!

Comment thread src/fetch.ts Outdated
Comment thread src/fetch.ts
// `position-area` — and not just the anchor-specific ones: a target can take
// its `position-area` from a stylesheet while setting its margin inline.
// `anchor` is matched on its own as well, for `anchor()`/`anchor-size()` values.
// Built on first use rather than at module evaluation: `cascade.js` and this

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This isn't the first time we've run into this cycle- is there a different file org that would avoid that?

@jpzwarte jpzwarte Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Checking the real runtime graph, there are exactly two cycles:

1. cascade → utils → dom → cascade (with utils ↔ dom nested inside it)

This is the one that actually bites. Probing module-body evaluation shows SHIFTED_PROPERTIES is not yet initialized when dom.ts's body runs — it only gets away with it because the read happens inside getCSSPropertyValue rather than at module scope. All three edges are single-use:

  • dom → cascade exists solely for SHIFTED_PROPERTIES (dom.ts:51)
  • utils → dom exists solely for strategyForElement (utils.ts:292getCSSPropertyValue)
  • dom → utils exists solely for getRootStyleContainer (dom.ts:105)

2. parse ↔ fallback

Only isIdentifier is a real value edge — AnchorPosition, AnchorPositions and TryBlock are types and already erase. parse needs parsePositionFallbacks; fallback needs those four.

Perhaps look at improving this in a new PR?

@jgerigmeyer jgerigmeyer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good! I left one comment for a potential efficiency gain, and I think there's another related improvement we could make in this PR...

Repeat polyfill() runs grow the inline style attributes, because we keep adding them. That isn't caused by this PR, but we've now amplified it since we're matching against more declarations.

I think we could fix this by removing stale attributes that we've added on a previous run before we re-shift on a subsequent run. Something like this in cascade.ts (I haven't tested this):

diff --git a/src/cascade.ts b/src/cascade.ts
--- a/src/cascade.ts
+++ b/src/cascade.ts
@@ -132,12 +132,31 @@ export function registerShiftedProperties(
   }
 }
 
+/**
+ * Remove every declaration of `property` from `block`.
+ *
+ * Used before appending a generated declaration, so that re-processing a block
+ * we already wrote doesn't accumulate a duplicate each time. Inline styles are
+ * re-parsed from the `style` attribute a previous run wrote back, so this is
+ * the difference between an idempotent run and one that grows the attribute
+ * without bound. Removing rather than skipping keeps last-wins order when a
+ * block declares the same property twice.
+ */
+function dropExistingDeclarations(block: Block, property: string) {
+  block.children.forEach((child, item) => {
+    if (isDeclaration(child) && child.property === property) {
+      block.children.remove(item);
+    }
+  });
+}
+
 /**
  * Shift property declarations for properties that are not yet natively
  * supported into custom properties.
  */
 function shiftUnsupportedProperties(node: CssNode, block?: Block) {
   if (isDeclaration(node) && SHIFTED_PROPERTIES[node.property] && block) {
+    dropExistingDeclarations(block, SHIFTED_PROPERTIES[node.property]);
     block.children.appendData({
       ...node,
       property: SHIFTED_PROPERTIES[node.property],
@@ -162,6 +181,7 @@ function expandInsetShorthands(node: CssNode, block?: Block) {
 
   const appendProperty = (property: string, value?: CssNode) => {
     if (!value) return;
+    dropExistingDeclarations(block, property);
     block.children.appendData({
       ...node,
       property,

Or @jpzwarte and @jamesnw if you think this deserves its own issue and PR, that's fine too!

Comment thread src/fetch.ts
Comment thread src/fetch.ts Outdated
@jamesnw

jamesnw commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Repeat polyfill() runs grow the inline style attributes, because we keep adding them. That isn't caused by this PR, but we've now amplified it since we're matching against more declarations.

Or @jpzwarte and @jamesnw if you think this deserves its own issue and PR, that's fine too!

I have some questions (do we drop all, or just overwritten, for example) so I opened #461

Comment thread src/fetch.ts Outdated
Comment thread tests/unit/fetch.test.ts Outdated
@jgerigmeyer
jgerigmeyer merged commit 354cc5c into oddbird:main Aug 14, 2026
10 checks passed
@jpzwarte
jpzwarte deleted the fix/435-shift-inline-styles branch August 15, 2026 08:09
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.

[BUG] Inline styles are not shifted

3 participants