Skip to content

fix(native): resolve a deferred declaration on its own target, in cascade order - #474

Draft
YevheniiKotyrlo wants to merge 2 commits into
nativewind:mainfrom
YevheniiKotyrlo:fix/deferred-declarations
Draft

YevheniiKotyrlo wants to merge 2 commits into
nativewind:mainfrom
YevheniiKotyrlo:fix/deferred-declarations

Conversation

@YevheniiKotyrlo

Copy link
Copy Markdown
Contributor

Problem

A declaration whose value depends on a var() resolves after every other declaration of its element. Two defects on main follow from that:

  1. It resolves against the wrong target. Its closure captured applyDeclarations's shared target binding, so it checked its placeholder against whatever nested target the last declaration ended on, missed it, and left the internal placeholder in the style. color: var(--c); box-shadow: 1px 1px blue renders color: { color: true }, text-shadow does the same, and -webkit-line-clamp: var(--n) beside a deferred colour renders numberOfLines: { numberOfLines: true }.
  2. A deferred shorthand overwrites a later declaration. border: var(--b); border-color: blue paints the variable's colour, within a rule and across rules, and so does border-inline-width: var(--w) over a later border-inline-start-width.

Solution

  • Each declaration walks into a binding of its own.
  • Each element records, per target and key, the cascade position of the declaration that last set it. A deferred shorthand learns its keys only once it resolves, so it claims them one by one and keeps the ones a later declaration holds (css-cascade-5 §6.4).

Tests

  • src/__tests__/native/deferred-declarations.test.tsx: each placeholder shape, and each shorthand before and after its longhand, in one rule and across rules.
  • On main, 7 of its 10 cases fail; the 3 that pass there are the orders main already gets right, kept so they stay right.
  • Each of 6 mutations fails its own cases.
  • Every expectation is what Chromium 153, Firefox 155 and WebKit 26.6 compute for the same CSS.

Verification

On Windows with Node 26:

  • yarn lint clean
  • yarn typecheck clean
  • yarn test --coverage: 1341 passed, 4 failed. The 4 are the babel cases that also fail on main on this machine.
  • yarn build clean
  • yarn example expo export --platform web exported
  • nothing unstaged after either build

Merge order

#393 builds on this, so this lands first.

It also shares lines with #389, #391 and #411 (native/styles/calculate-props.ts); whichever lands second rebases.

Base

Branched off main (a5002c5).

applyDeclarations reassigned its target parameter per declaration, and a
deferred declaration's closure captured that shared binding. The closure
runs after every declaration in the rule has been walked, so it read
whatever nested target the last declaration ended on: a deferred color
beside a box-shadow checked its placeholder against the shadow entry,
missed it, and left the internal { color: true } in the style.
-webkit-line-clamp: var(--n) left { numberOfLines: true } on the prop the
same way.

Each declaration now walks into a binding of its own.
A deferred declaration resolves after every other declaration of the
element, so a shorthand that resolved at runtime spread its keys over
whatever a later declaration had set: border: var(--b) followed by
border-color: blue painted the variable's colour, within a rule and across
rules, and so did border-inline-width: var(--w) over a later
border-inline-start-width. Each key belongs to the declaration that set it
last (css-cascade-5 §6.4).

Each element now records, per target and key, the cascade position of its
last writer. A deferred shorthand learns its keys only when it resolves, so
it claims them one by one and keeps the ones a later declaration holds.

This branch has not been deployed

No deployments
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.

1 participant