Skip to content

fix(native): let an element's own custom property outrank an inherited one - #443

Open
YevheniiKotyrlo wants to merge 1 commit into
nativewind:mainfrom
YevheniiKotyrlo:fix/vars-deprecation-scope
Open

fix(native): let an element's own custom property outrank an inherited one#443
YevheniiKotyrlo wants to merge 1 commit into
nativewind:mainfrom
YevheniiKotyrlo:fix/vars-deprecation-scope

Conversation

@YevheniiKotyrlo

Copy link
Copy Markdown
Contributor

Problem

varResolver reads the inherited variable context before the element's own record and returns from it, so a vars() declaration on the element is used only when no ancestor declared the same name. Put a <VariableContextProvider> anywhere above the element and its own declaration is discarded.

css-cascade-4 §7.2 makes inheritance a defaulting step: an element inherits a property only when the cascade yields no declared value for it. A custom property is an ordinary property (css-variables-1 §2), so an element that declares --x uses its own value, never its ancestor's.

Reproduction

--my-var is declared twice so inline-variables.ts cannot fold it into the consuming declaration — a single definition performs no runtime var() read and would pass without exercising precedence at all.

registerCSS(`
  .decoy { --my-var: seed; }
  .other-decoy { --my-var: seed2; }
  .my-class { color: var(--my-var); }
`);

render(
  <VariableContextProvider value={{ "--my-var": "red" }}>
    <View testID={testID} className="my-class" style={vars({ "--my-var": "blue" })} />
  </VariableContextProvider>,
);
Expected { color: "blue" }
Actual on main { color: "red" }

Only this case diverges. I probed seven trees — vars() alone, provider alone, provider above a parent with a reading child, vars() on a parent with a reading child, two siblings under one provider, two siblings with their own vars() — and every other one already matches the expected value.

Which planes

Web is unaffected and already correct. It implements no precedence of its own: vars() returns a plain {"--x": v} style object and VariableContextProvider renders a display: contents div carrying the property, so the browser's cascade decides. I measured the same tree in chromium — the element's own declaration wins.

The compiler is unaffected; it emits v / vr tuples and never orders the two channels.

Fix

The inherited read moves below the element's own record. It keeps pushing the raw descriptor rather than the resolved value, because testGuards compares the guard against the next render's context and a resolved value would never match.

The second read of variables[name] that followed the inline arm is deleted. It was unreachable-effective: it ran only when name was absent from variables, so resolve(variables[name]) was resolve(undefined) and its value !== undefined guard never fired.

Net: 6 insertions, 12 deletions in one function.

Tests

Two added to src/__tests__/native/vars.test.tsx:

  • an element's own vars() outranks an inherited value — fails on main with - "color": "blue" / + "color": "red", passes with the fix;
  • an inherited value still applies when the element declares nothing — passes both with and without the fix, so the first one cannot be satisfied by simply ignoring the context.

Validation

yarn test — 1050 passed. yarn lint and yarn typecheck clean.

src/__tests__/babel/react-native.test.ts and react-native-web.test.ts report 3 failures on module-specifier resolution. Those are pre-existing on main: I measured the baseline by swapping only variables.ts back to main's copy and got the same 3. They look like a Windows path-separator issue and are unrelated to this change.

…d one

`varResolver` read the inherited variable context before the element's own
record and returned from it, so a `vars()` declaration on the element was used
only when no ancestor declared the same name.

css-cascade-4 §7.2 makes inheritance a defaulting step: an element inherits a
property only when the cascade yields no declared value for it. A custom
property is an ordinary property (css-variables-1 §2), so an element declaring
`--x` uses its own value rather than its ancestor's.

Measured against a provider declaring `--my-var: red` around an element whose
`vars()` declares `--my-var: blue`, with the class reading `color: var(--my-var)`:

  expected  { color: "blue" }
  actual    { color: "red" }

Web is unaffected and already correct — it implements no precedence of its own.
`vars()` returns a plain `{"--x": v}` style object and `VariableContextProvider`
renders a `display: contents` div carrying the property, so the browser's cascade
decides.

The inherited read moves below the element's own record and keeps pushing the
RAW descriptor, which is what `testGuards` compares against the next render's
context. The second read of `variables[name]` that followed it is deleted: it
ran only when `name` was absent from `variables`, so it resolved `undefined` and
its guard never fired.
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