Files
uni-stack__uniwind/.husky
Dima Lebedynskyi b8f5d8c66c feat: scoped variables (#611)
* feat: add ScopedVariables component for per-subtree CSS variable overrides

* feat: add opt-in cacheKey to ScopedVariables for native style caching

* docs(expo-example): add ScopedVariables demo

Demonstrate per-subtree CSS variable overrides in the expo example app:
theme default, scoped override, nested inheritance (nearest wins), and the
opt-in cacheKey. Adds --color-primary/--color-surface/--gap theme defaults so
the unscoped baseline renders intentionally.

* fix(ScopedVariables): apply scoped variables to the web DOM cascade

On web, Uniwind passes classes through RNW unchanged, so real elements
resolve `var(--name)` from the live CSS cascade. The wrapper only applied
its variables to the hidden dummyParent used for JS reads, so scoped
overrides never reached descendants — styling stayed at the theme default
while useCSSVariable readouts (which use the dummyParent path) looked
correct. Set the variables as inline custom properties on the
display:contents wrapper so they cascade to children (numbers -> px).

Add web regression tests asserting the wrapper carries the overrides
inline, nested wrappers only declare their own overrides, and invalid
keys are dropped. Rework the expo-example demo with origin pills
(default/set here/inherited), swatches, and a gap strip so the
override/inherit story is legible.

* fix(ScopedVariables): address review feedback on PR #611

- useCSSVariable: recompute on context change, not only on global
  Theme/Variables events — an updated <ScopedVariables> variables prop
  (or a nearer provider) now surfaces the new value. Adds web + native
  regression tests for a prop update.
- utils: always drop non-`--` keys (not just in dev), gating only the
  Logger.error behind __DEV__, so invalid keys can't reach the web read
  helper and corrupt a resolved inheritable property in production.
- getWebStyles/getWebVariable: wrap the scoped-variable read in
  try/finally so the temporary custom properties are always cleared,
  even if a DOM read throws.
- Document the variables prop stability contract (define outside render
  or useMemo) in JSDoc.
- Remove two unused @ts-expect-error directives in tests.
- Reword the demo's cacheKey section so it no longer claims parity with
  section 2.

* refactor(ScopedVariables): address maintainer review on PR #611

- Derive the native style cache key from the merged variables map instead
  of a user-supplied cacheKey prop; removes the cache-bypass path and the
  stale-key footgun
- Drop the display: contents View wrapper on native, render the bare
  provider like ScopedTheme.native
- Remove the expo-example demo (playground only, covered by tests)
- useCSSVariable: skip the mount-time recompute, useState already
  resolved the initial value
- Extract toWebValue (number -> px) web util, reuse in config,
  getWebStyles and the web wrapper
- Remove the ScopedVariables prop type test
- Trim long comments to match repo style

* fix(ScopedVariables): use JSON.stringify for the derived cache key

The key:value; concatenation had no escaping, so values containing
separators could collide ({'--a': '1;--b:2'} vs {'--a': '1', '--b': '2'})
and serve wrong cached styles.

* chore: general corrections

* chore: stream ui precommit

* chore: more improvements

* feat: add ScopedVariables component for per-subtree CSS variable overrides

* feat: add opt-in cacheKey to ScopedVariables for native style caching

* docs(expo-example): add ScopedVariables demo

Demonstrate per-subtree CSS variable overrides in the expo example app:
theme default, scoped override, nested inheritance (nearest wins), and the
opt-in cacheKey. Adds --color-primary/--color-surface/--gap theme defaults so
the unscoped baseline renders intentionally.

* fix(ScopedVariables): apply scoped variables to the web DOM cascade

On web, Uniwind passes classes through RNW unchanged, so real elements
resolve `var(--name)` from the live CSS cascade. The wrapper only applied
its variables to the hidden dummyParent used for JS reads, so scoped
overrides never reached descendants — styling stayed at the theme default
while useCSSVariable readouts (which use the dummyParent path) looked
correct. Set the variables as inline custom properties on the
display:contents wrapper so they cascade to children (numbers -> px).

Add web regression tests asserting the wrapper carries the overrides
inline, nested wrappers only declare their own overrides, and invalid
keys are dropped. Rework the expo-example demo with origin pills
(default/set here/inherited), swatches, and a gap strip so the
override/inherit story is legible.

* fix(ScopedVariables): address review feedback on PR #611

- useCSSVariable: recompute on context change, not only on global
  Theme/Variables events — an updated <ScopedVariables> variables prop
  (or a nearer provider) now surfaces the new value. Adds web + native
  regression tests for a prop update.
- utils: always drop non-`--` keys (not just in dev), gating only the
  Logger.error behind __DEV__, so invalid keys can't reach the web read
  helper and corrupt a resolved inheritable property in production.
- getWebStyles/getWebVariable: wrap the scoped-variable read in
  try/finally so the temporary custom properties are always cleared,
  even if a DOM read throws.
- Document the variables prop stability contract (define outside render
  or useMemo) in JSDoc.
- Remove two unused @ts-expect-error directives in tests.
- Reword the demo's cacheKey section so it no longer claims parity with
  section 2.

* refactor(ScopedVariables): address maintainer review on PR #611

- Derive the native style cache key from the merged variables map instead
  of a user-supplied cacheKey prop; removes the cache-bypass path and the
  stale-key footgun
- Drop the display: contents View wrapper on native, render the bare
  provider like ScopedTheme.native
- Remove the expo-example demo (playground only, covered by tests)
- useCSSVariable: skip the mount-time recompute, useState already
  resolved the initial value
- Extract toWebValue (number -> px) web util, reuse in config,
  getWebStyles and the web wrapper
- Remove the ScopedVariables prop type test
- Trim long comments to match repo style

* fix(ScopedVariables): use JSON.stringify for the derived cache key

The key:value; concatenation had no escaping, so values containing
separators could collide ({'--a': '1;--b:2'} vs {'--a': '1', '--b': '2'})
and serve wrong cached styles.

* chore: general corrections

* chore: stream ui precommit

* chore: more improvements

* chore: remove unused from test

---------

Co-authored-by: Dima Lebedynskyi <dimalebe@amazon.com>
Co-authored-by: Hubert Bieszczad <brentlokk@gmail.com>
2026-08-11 13:17:20 +02:00
..
2026-08-11 13:17:20 +02:00