rnwind 0.0.8 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/lib/cjs/core/parser/color.cjs +33 -1
  2. package/lib/cjs/core/parser/color.cjs.map +1 -1
  3. package/lib/cjs/core/parser/color.d.ts +10 -0
  4. package/lib/cjs/core/parser/declaration.cjs +121 -9
  5. package/lib/cjs/core/parser/declaration.cjs.map +1 -1
  6. package/lib/cjs/core/parser/gradient.cjs +46 -12
  7. package/lib/cjs/core/parser/gradient.cjs.map +1 -1
  8. package/lib/cjs/core/parser/gradient.d.ts +2 -1
  9. package/lib/cjs/core/parser/keyframes.cjs +27 -12
  10. package/lib/cjs/core/parser/keyframes.cjs.map +1 -1
  11. package/lib/cjs/core/parser/keyframes.d.ts +11 -0
  12. package/lib/cjs/core/parser/layout-dispatcher.cjs +33 -10
  13. package/lib/cjs/core/parser/layout-dispatcher.cjs.map +1 -1
  14. package/lib/cjs/core/parser/length.cjs +17 -1
  15. package/lib/cjs/core/parser/length.cjs.map +1 -1
  16. package/lib/cjs/core/parser/safe-area.cjs +24 -3
  17. package/lib/cjs/core/parser/safe-area.cjs.map +1 -1
  18. package/lib/cjs/core/parser/theme-vars.cjs +58 -8
  19. package/lib/cjs/core/parser/theme-vars.cjs.map +1 -1
  20. package/lib/cjs/core/parser/tokens.cjs +77 -9
  21. package/lib/cjs/core/parser/tokens.cjs.map +1 -1
  22. package/lib/cjs/core/parser/tokens.d.ts +9 -0
  23. package/lib/cjs/core/parser/transform.cjs +18 -9
  24. package/lib/cjs/core/parser/transform.cjs.map +1 -1
  25. package/lib/cjs/core/parser/tw-parser.cjs +136 -34
  26. package/lib/cjs/core/parser/tw-parser.cjs.map +1 -1
  27. package/lib/cjs/core/parser/tw-parser.d.ts +20 -0
  28. package/lib/cjs/core/parser/typography-dispatcher.cjs +19 -1
  29. package/lib/cjs/core/parser/typography-dispatcher.cjs.map +1 -1
  30. package/lib/cjs/core/parser/typography.cjs +15 -18
  31. package/lib/cjs/core/parser/typography.cjs.map +1 -1
  32. package/lib/cjs/core/parser/typography.d.ts +5 -5
  33. package/lib/cjs/core/style-builder/build-style.cjs +12 -3
  34. package/lib/cjs/core/style-builder/build-style.cjs.map +1 -1
  35. package/lib/cjs/core/style-builder/build-style.d.ts +3 -1
  36. package/lib/cjs/core/style-builder/union-builder.cjs +9 -11
  37. package/lib/cjs/core/style-builder/union-builder.cjs.map +1 -1
  38. package/lib/cjs/core/style-builder/union-builder.d.ts +7 -8
  39. package/lib/cjs/metro/dts.cjs +6 -1
  40. package/lib/cjs/metro/dts.cjs.map +1 -1
  41. package/lib/cjs/metro/transformer.cjs +42 -77
  42. package/lib/cjs/metro/transformer.cjs.map +1 -1
  43. package/lib/cjs/metro/with-config.cjs +9 -29
  44. package/lib/cjs/metro/with-config.cjs.map +1 -1
  45. package/lib/cjs/runtime/hooks/use-scheme.cjs +17 -11
  46. package/lib/cjs/runtime/hooks/use-scheme.cjs.map +1 -1
  47. package/lib/cjs/runtime/hooks/use-scheme.d.ts +7 -4
  48. package/lib/cjs/runtime/index.cjs +2 -1
  49. package/lib/cjs/runtime/index.cjs.map +1 -1
  50. package/lib/cjs/runtime/index.d.ts +2 -2
  51. package/lib/cjs/runtime/lookup-css.cjs +41 -0
  52. package/lib/cjs/runtime/lookup-css.cjs.map +1 -1
  53. package/lib/cjs/runtime/lookup-css.d.ts +29 -0
  54. package/lib/cjs/runtime/resolve.cjs +8 -6
  55. package/lib/cjs/runtime/resolve.cjs.map +1 -1
  56. package/lib/cjs/runtime/wrap.cjs +50 -57
  57. package/lib/cjs/runtime/wrap.cjs.map +1 -1
  58. package/lib/cjs/runtime/wrap.d.ts +10 -4
  59. package/lib/cjs/testing/index.cjs +1 -1
  60. package/lib/cjs/testing/index.cjs.map +1 -1
  61. package/lib/esm/core/parser/color.d.ts +10 -0
  62. package/lib/esm/core/parser/color.mjs +34 -3
  63. package/lib/esm/core/parser/color.mjs.map +1 -1
  64. package/lib/esm/core/parser/declaration.mjs +122 -10
  65. package/lib/esm/core/parser/declaration.mjs.map +1 -1
  66. package/lib/esm/core/parser/gradient.d.ts +2 -1
  67. package/lib/esm/core/parser/gradient.mjs +45 -11
  68. package/lib/esm/core/parser/gradient.mjs.map +1 -1
  69. package/lib/esm/core/parser/keyframes.d.ts +11 -0
  70. package/lib/esm/core/parser/keyframes.mjs +27 -12
  71. package/lib/esm/core/parser/keyframes.mjs.map +1 -1
  72. package/lib/esm/core/parser/layout-dispatcher.mjs +33 -10
  73. package/lib/esm/core/parser/layout-dispatcher.mjs.map +1 -1
  74. package/lib/esm/core/parser/length.mjs +17 -1
  75. package/lib/esm/core/parser/length.mjs.map +1 -1
  76. package/lib/esm/core/parser/safe-area.mjs +24 -3
  77. package/lib/esm/core/parser/safe-area.mjs.map +1 -1
  78. package/lib/esm/core/parser/theme-vars.mjs +58 -8
  79. package/lib/esm/core/parser/theme-vars.mjs.map +1 -1
  80. package/lib/esm/core/parser/tokens.d.ts +9 -0
  81. package/lib/esm/core/parser/tokens.mjs +77 -10
  82. package/lib/esm/core/parser/tokens.mjs.map +1 -1
  83. package/lib/esm/core/parser/transform.mjs +18 -9
  84. package/lib/esm/core/parser/transform.mjs.map +1 -1
  85. package/lib/esm/core/parser/tw-parser.d.ts +20 -0
  86. package/lib/esm/core/parser/tw-parser.mjs +138 -36
  87. package/lib/esm/core/parser/tw-parser.mjs.map +1 -1
  88. package/lib/esm/core/parser/typography-dispatcher.mjs +19 -1
  89. package/lib/esm/core/parser/typography-dispatcher.mjs.map +1 -1
  90. package/lib/esm/core/parser/typography.d.ts +5 -5
  91. package/lib/esm/core/parser/typography.mjs +15 -18
  92. package/lib/esm/core/parser/typography.mjs.map +1 -1
  93. package/lib/esm/core/style-builder/build-style.d.ts +3 -1
  94. package/lib/esm/core/style-builder/build-style.mjs +12 -3
  95. package/lib/esm/core/style-builder/build-style.mjs.map +1 -1
  96. package/lib/esm/core/style-builder/union-builder.d.ts +7 -8
  97. package/lib/esm/core/style-builder/union-builder.mjs +9 -11
  98. package/lib/esm/core/style-builder/union-builder.mjs.map +1 -1
  99. package/lib/esm/metro/dts.mjs +6 -1
  100. package/lib/esm/metro/dts.mjs.map +1 -1
  101. package/lib/esm/metro/transformer.mjs +42 -77
  102. package/lib/esm/metro/transformer.mjs.map +1 -1
  103. package/lib/esm/metro/with-config.mjs +10 -30
  104. package/lib/esm/metro/with-config.mjs.map +1 -1
  105. package/lib/esm/runtime/hooks/use-scheme.d.ts +7 -4
  106. package/lib/esm/runtime/hooks/use-scheme.mjs +17 -11
  107. package/lib/esm/runtime/hooks/use-scheme.mjs.map +1 -1
  108. package/lib/esm/runtime/index.d.ts +2 -2
  109. package/lib/esm/runtime/index.mjs +2 -2
  110. package/lib/esm/runtime/index.mjs.map +1 -1
  111. package/lib/esm/runtime/lookup-css.d.ts +29 -0
  112. package/lib/esm/runtime/lookup-css.mjs +39 -1
  113. package/lib/esm/runtime/lookup-css.mjs.map +1 -1
  114. package/lib/esm/runtime/resolve.mjs +9 -7
  115. package/lib/esm/runtime/resolve.mjs.map +1 -1
  116. package/lib/esm/runtime/wrap.d.ts +10 -4
  117. package/lib/esm/runtime/wrap.mjs +50 -57
  118. package/lib/esm/runtime/wrap.mjs.map +1 -1
  119. package/lib/esm/testing/index.mjs +2 -2
  120. package/lib/esm/testing/index.mjs.map +1 -1
  121. package/package.json +1 -1
  122. package/src/core/parser/color.ts +32 -1
  123. package/src/core/parser/declaration.ts +119 -10
  124. package/src/core/parser/gradient.ts +48 -11
  125. package/src/core/parser/keyframes.ts +31 -3
  126. package/src/core/parser/layout-dispatcher.ts +32 -9
  127. package/src/core/parser/length.ts +18 -1
  128. package/src/core/parser/safe-area.ts +23 -2
  129. package/src/core/parser/theme-vars.ts +75 -8
  130. package/src/core/parser/tokens.ts +76 -9
  131. package/src/core/parser/transform.ts +19 -8
  132. package/src/core/parser/tw-parser.ts +148 -31
  133. package/src/core/parser/typography-dispatcher.ts +20 -1
  134. package/src/core/parser/typography.ts +15 -15
  135. package/src/core/style-builder/build-style.ts +12 -1
  136. package/src/core/style-builder/union-builder.ts +10 -12
  137. package/src/metro/dts.ts +6 -1
  138. package/src/metro/transformer.ts +42 -78
  139. package/src/metro/with-config.ts +10 -29
  140. package/src/runtime/hooks/use-scheme.ts +17 -10
  141. package/src/runtime/index.ts +2 -1
  142. package/src/runtime/lookup-css.ts +42 -0
  143. package/src/runtime/resolve.ts +9 -7
  144. package/src/runtime/wrap.tsx +57 -61
  145. package/src/testing/index.ts +3 -0
@@ -2,6 +2,7 @@ import { type SourceEntry } from '@tailwindcss/oxide';
2
2
  import { type GradientAtomInfo } from './gradient';
3
3
  import { type HapticRequest } from './haptics';
4
4
  import type { RNStyle } from './types';
5
+ import type { ThemeTables } from '../types';
5
6
  /** Parser configuration — one instance per Metro session, theme CSS fixed. */
6
7
  export interface TailwindParserConfig {
7
8
  /**
@@ -94,6 +95,14 @@ export interface ParsedOutput {
94
95
  * provider's `windowWidth`.
95
96
  */
96
97
  breakpoints: ReadonlyMap<string, number>;
98
+ /**
99
+ * Per-scheme user theme token tables (`--color-*`, `--spacing-*`, …) with
100
+ * `--color-*` values lowered to sRGB. The style-builder emits these as
101
+ * `registerThemeTokens({...})` in the manifest so `useColor` / `useToken` /
102
+ * `useSize` resolve out of the box, without the user threading a `tables`
103
+ * prop on the provider. Keyed by scheme (`base` + each declared variant).
104
+ */
105
+ themeTokens: ThemeTables;
97
106
  }
98
107
  /**
99
108
  * Parses one source file's Tailwind usage into RN-ready style objects.
@@ -170,6 +179,17 @@ export declare class TailwindParser {
170
179
  * @returns Effective var name → value lookup for the scheme.
171
180
  */
172
181
  private effectiveVars;
182
+ /**
183
+ * Build the per-scheme theme token tables for `registerThemeTokens` —
184
+ * the data source for `useColor` / `useToken` / `useSize`. Emits one table
185
+ * per scheme the user wrote tokens under (`base` + each variant block /
186
+ * `.dark` override). `--color-*` values are lowered to sRGB (matching the
187
+ * className path) using `resolver` so a wide-gamut or `var()`-referencing
188
+ * token resolves to an RN-renderable string; other tokens pass through.
189
+ * @param resolver Full compiled `:root` table, for resolving `var()` refs.
190
+ * @returns Scheme → (token name → value) map.
191
+ */
192
+ private buildThemeTokens;
173
193
  /**
174
194
  * Build the Tailwind compiler on first use and cache it. The theme CSS
175
195
  * gets a `theme(inline)` modifier on its `@import 'tailwindcss'` so
@@ -5,11 +5,12 @@ import { Features, transform } from 'lightningcss';
5
5
  import { declarationToRnEntries } from './declaration.mjs';
6
6
  import { detectGradientAtom } from './gradient.mjs';
7
7
  import { detectHapticAtom } from './haptics.mjs';
8
- import { keyframesName, keyframeSelectorOffset, pickAnimationName } from './keyframes.mjs';
8
+ import { keyframesName, keyframeSelectorOffsets, pickAnimationName } from './keyframes.mjs';
9
9
  import { serializeInitialValue } from './property.mjs';
10
10
  import { classNameFromSelector } from './selector.mjs';
11
11
  import { extractThemeVars, extractSchemeAliases, extractCustomVariantSchemes, BASE_SCHEME, compileReadyTheme } from './theme-vars.mjs';
12
- import { serializeTokens } from './tokens.mjs';
12
+ import { substituteThemeVars, serializeTokens, coerceUnparsedValue } from './tokens.mjs';
13
+ import { normalizeColorString } from './color.mjs';
13
14
 
14
15
  /**
15
16
  * Default LightningCSS transform options for TailwindParser's visitor.
@@ -145,6 +146,33 @@ class TailwindParser {
145
146
  merged.set(k, v);
146
147
  return merged;
147
148
  }
149
+ /**
150
+ * Build the per-scheme theme token tables for `registerThemeTokens` —
151
+ * the data source for `useColor` / `useToken` / `useSize`. Emits one table
152
+ * per scheme the user wrote tokens under (`base` + each variant block /
153
+ * `.dark` override). `--color-*` values are lowered to sRGB (matching the
154
+ * className path) using `resolver` so a wide-gamut or `var()`-referencing
155
+ * token resolves to an RN-renderable string; other tokens pass through.
156
+ * @param resolver Full compiled `:root` table, for resolving `var()` refs.
157
+ * @returns Scheme → (token name → value) map.
158
+ */
159
+ buildThemeTokens(resolver) {
160
+ const out = {};
161
+ for (const scheme of this.themeSchemes.keys()) {
162
+ const userTable = this.themeSchemes.get(scheme);
163
+ if (!userTable || userTable.size === 0)
164
+ continue;
165
+ const schemeResolver = new Map(resolver);
166
+ for (const [k, v] of this.effectiveVars(scheme))
167
+ schemeResolver.set(k, v);
168
+ const table = {};
169
+ for (const [name, raw] of userTable) {
170
+ table[name] = name.startsWith('--color-') ? lowerColorToken(raw, schemeResolver) : raw;
171
+ }
172
+ out[scheme] = table;
173
+ }
174
+ return out;
175
+ }
148
176
  /**
149
177
  * Build the Tailwind compiler on first use and cache it. The theme CSS
150
178
  * gets a `theme(inline)` modifier on its `@import 'tailwindcss'` so
@@ -281,7 +309,8 @@ class TailwindParser {
281
309
  // surface their role + resolved colour so the transformer
282
310
  // can rewrite `<LinearGradient className="...">` into
283
311
  // `colors={...}` / `start={...}` / `end={...}` props.
284
- const gradient = detectGradientAtom(rule.value.declarations.declarations);
312
+ const gradientTable = schemeTables.get(BASE_SCHEME) ?? schemeTables.get(schemes[0] ?? BASE_SCHEME);
313
+ const gradient = detectGradientAtom(rule.value.declarations.declarations, gradientTable);
285
314
  if (gradient)
286
315
  gradientAtoms.set(className, gradient);
287
316
  // Haptics may live on the rule directly OR inside a
@@ -302,8 +331,8 @@ class TailwindParser {
302
331
  const steps = [];
303
332
  const baseTable = schemeTables.get(BASE_SCHEME) ?? schemeTables.get(schemes[0] ?? BASE_SCHEME);
304
333
  for (const frame of rule.value.keyframes) {
305
- const offset = keyframeSelectorOffset(frame.selectors);
306
- if (!offset)
334
+ const offsets = keyframeSelectorOffsets(frame.selectors);
335
+ if (offsets.length === 0)
307
336
  continue;
308
337
  const style = {};
309
338
  const frameDecls = frame.declarations.declarations ?? [];
@@ -311,7 +340,10 @@ class TailwindParser {
311
340
  for (const [key, value] of declarationToRnEntries(decl, baseTable))
312
341
  style[key] = value;
313
342
  }
314
- steps.push({ offset, style });
343
+ // One frame can carry several offsets (`0%, 100% { }`); emit a
344
+ // step for each so the terminal frame isn't lost.
345
+ for (const offset of offsets)
346
+ steps.push({ offset, style });
315
347
  }
316
348
  keyframes.set(name, { name, steps });
317
349
  },
@@ -332,9 +364,23 @@ class TailwindParser {
332
364
  if (!referencedKeyframes.has(name))
333
365
  keyframes.delete(name);
334
366
  }
335
- return { atoms, keyframes, propertyDefaults, gradientAtoms, hapticAtoms, candidates: [...candidates], schemes, breakpoints };
367
+ const themeTokens = this.buildThemeTokens(compiledTheme);
368
+ return { atoms, keyframes, propertyDefaults, gradientAtoms, hapticAtoms, candidates: [...candidates], schemes, breakpoints, themeTokens };
336
369
  }
337
370
  }
371
+ /**
372
+ * Lower a `--color-*` token value to an RN-renderable sRGB string, matching
373
+ * the className path: resolve any `var()` ref via `resolver`, then lower a
374
+ * wide-gamut form (`oklch(…)`, `lab(…)`, `color(p3 …)`) to sRGB. Hex / rgb /
375
+ * named colors pass through unchanged.
376
+ * @param raw Raw token value.
377
+ * @param resolver Var name → value table for resolving `var()` references.
378
+ * @returns RN-safe color string.
379
+ */
380
+ function lowerColorToken(raw, resolver) {
381
+ const substituted = substituteThemeVars(raw, resolver);
382
+ return normalizeColorString(substituted) ?? substituted;
383
+ }
338
384
  /**
339
385
  * Wrap an error from `@tailwindcss/node`'s compiler or `lightningcss`'s
340
386
  * transform with a `rnwind:` prefix so the user sees a clear "this came
@@ -381,6 +427,7 @@ function emptyOutput() {
381
427
  candidates: [],
382
428
  schemes: [BASE_SCHEME],
383
429
  breakpoints: new Map(),
430
+ themeTokens: {},
384
431
  };
385
432
  }
386
433
  /**
@@ -452,8 +499,8 @@ function processStyleRule(declarations, className, ctx, nestedRules = []) {
452
499
  ctx.referencedKeyframes.add(animationRef);
453
500
  }
454
501
  applyComposedTransform(bucket, ctx.schemes, ruleLocalVars);
455
- applyComposedShadow(bucket, ctx.schemes, ruleLocalVars);
456
- applyComposedRing(bucket, ctx.schemes, ruleLocalVars);
502
+ applyComposedShadow(bucket, ctx.schemes, ruleLocalVars, ruleSchemeTables);
503
+ applyComposedRing(bucket, ctx.schemes, ruleLocalVars, ruleSchemeTables);
457
504
  // Phase 2: nested rules — three orthogonal flavours, dispatched on
458
505
  // the lightningcss node `type`:
459
506
  // - `media`: Tailwind v4 responsive variants (`sm:`, `md:`, …) wrap
@@ -598,7 +645,7 @@ function applyMediaRule(nested, className, bucket, ctx, ruleSchemeTables, ruleLo
598
645
  for (const [k, v] of collectRuleLocalVars(decls))
599
646
  nestedLocalVars.set(k, v);
600
647
  applyComposedTransformToScheme(schemeBucket, nestedLocalVars);
601
- applyComposedShadowToScheme(schemeBucket, nestedLocalVars);
648
+ applyComposedShadowToScheme(schemeBucket, nestedLocalVars, table);
602
649
  bucket[scheme] = schemeBucket;
603
650
  }
604
651
  }
@@ -635,7 +682,7 @@ function applyInteractiveNestedRule(nested, bucket, ctx, ruleSchemeTables, ruleL
635
682
  for (const [k, v] of collectRuleLocalVars(decls))
636
683
  nestedLocalVars.set(k, v);
637
684
  applyComposedTransformToScheme(schemeBucket, nestedLocalVars);
638
- applyComposedShadowToScheme(schemeBucket, nestedLocalVars);
685
+ applyComposedShadowToScheme(schemeBucket, nestedLocalVars, table);
639
686
  bucket[scheme] = schemeBucket;
640
687
  }
641
688
  }
@@ -701,7 +748,7 @@ function applyNestedSchemeRule(nested, bucket, ctx, ruleSchemeTables, ruleLocalV
701
748
  for (const [k, v] of collectRuleLocalVars(innerDecls))
702
749
  nestedLocalVars.set(k, v);
703
750
  applyComposedTransformToScheme(schemeBucket, nestedLocalVars);
704
- applyComposedShadowToScheme(schemeBucket, nestedLocalVars);
751
+ applyComposedShadowToScheme(schemeBucket, nestedLocalVars, table);
705
752
  bucket[targetScheme] = schemeBucket;
706
753
  }
707
754
  /**
@@ -802,12 +849,13 @@ function applyComposedTransformToScheme(style, ruleLocalVars) {
802
849
  * prop.
803
850
  * @param style Scheme-specific style map.
804
851
  * @param ruleLocalVars Combined outer+nested `--tw-*` vars.
852
+ * @param table Per-scheme var table for resolving `var(--color-x)` in colors.
805
853
  */
806
- function applyComposedShadowToScheme(style, ruleLocalVars) {
854
+ function applyComposedShadowToScheme(style, ruleLocalVars, table) {
807
855
  const rawShadow = ruleLocalVars.get('--tw-shadow');
808
856
  const rawShadowColor = ruleLocalVars.get('--tw-shadow-color');
809
857
  if (!rawShadow && rawShadowColor) {
810
- const color = resolveCustomColorString(rawShadowColor);
858
+ const color = resolveCustomColorString(rawShadowColor, table);
811
859
  if (!color)
812
860
  return;
813
861
  delete style.boxShadow;
@@ -836,8 +884,9 @@ function applyComposedShadowToScheme(style, ruleLocalVars) {
836
884
  * @param bucket Per-scheme style map for the atom.
837
885
  * @param schemes Scheme names active for this parse.
838
886
  * @param ruleLocalVars Rule-local `--tw-*` vars.
887
+ * @param schemeTables Per-scheme var tables for resolving `var(--color-x)`.
839
888
  */
840
- function applyComposedShadow(bucket, schemes, ruleLocalVars) {
889
+ function applyComposedShadow(bucket, schemes, ruleLocalVars, schemeTables) {
841
890
  const rawShadow = ruleLocalVars.get('--tw-shadow');
842
891
  const rawShadowColor = ruleLocalVars.get('--tw-shadow-color');
843
892
  // Color-only utility (`shadow-red-50`, `shadow-gray-200`, …): emit
@@ -846,10 +895,11 @@ function applyComposedShadow(bucket, schemes, ruleLocalVars) {
846
895
  // where setting `--tw-shadow-color` swaps in a solid color). Offset /
847
896
  // blur / elevation come from the partner size utility's atom.
848
897
  if (!rawShadow && rawShadowColor) {
849
- const color = resolveCustomColorString(rawShadowColor);
850
- if (!color)
851
- return;
852
898
  for (const scheme of schemes) {
899
+ // Resolve per scheme — a custom token may differ between light/dark.
900
+ const color = resolveCustomColorString(rawShadowColor, schemeTables.get(scheme));
901
+ if (!color)
902
+ continue;
853
903
  const style = bucket[scheme] ?? {};
854
904
  delete style.boxShadow;
855
905
  style.shadowColor = color;
@@ -882,35 +932,62 @@ function applyComposedShadow(bucket, schemes, ruleLocalVars) {
882
932
  * @param bucket Per-scheme style map for the atom.
883
933
  * @param schemes Scheme names active for this parse.
884
934
  * @param ruleLocalVars Rule-local `--tw-*` vars.
935
+ * @param schemeTables Per-scheme var tables for resolving `var(--color-x)`.
885
936
  */
886
- function applyComposedRing(bucket, schemes, ruleLocalVars) {
937
+ function applyComposedRing(bucket, schemes, ruleLocalVars, schemeTables) {
887
938
  const ringColor = ruleLocalVars.get('--tw-ring-color');
888
939
  if (!ringColor)
889
940
  return;
890
- const color = resolveCustomColorString(ringColor);
891
- if (!color)
892
- return;
893
941
  for (const scheme of schemes) {
942
+ // Resolve per scheme — a custom token may differ between light/dark.
943
+ const color = resolveCustomColorString(ringColor, schemeTables.get(scheme));
944
+ if (!color)
945
+ continue;
894
946
  const style = bucket[scheme] ?? {};
895
947
  if (!('borderColor' in style))
896
948
  style.borderColor = color;
897
949
  bucket[scheme] = style;
898
950
  }
899
951
  }
952
+ /**
953
+ * Tailwind composable shadow/inset-shadow alpha defaults. Their `100%` lives
954
+ * in an `@property` initial-value (not the rule's local vars), so after the
955
+ * `@supports` color-mix is unwrapped, `var(--tw-shadow-alpha)` is left dangling
956
+ * and the shadow color fails to resolve. Seed the default; a `/<opacity>`
957
+ * modifier still wins because the in-rule table value overrides it.
958
+ */
959
+ const COMPOSABLE_ALPHA_DEFAULTS = new Map([
960
+ ['--tw-shadow-alpha', '100%'],
961
+ ['--tw-inset-shadow-alpha', '100%'],
962
+ ]);
900
963
  /**
901
964
  * Resolve a CSS color string (`oklch(0.971 0.013 17.38)`, `#ff0000`,
902
965
  * `rgb(0 0 0 / 0.1)`) to the hex string RN's `shadowColor` accepts.
903
966
  * Wraps culori's parser via {@link parseCssColorToHex}.
904
- * @param raw Raw color text from a `--tw-shadow-color` custom prop.
967
+ *
968
+ * Custom `@theme` color tokens arrive as `var(--color-x)` (only the default
969
+ * palette is `theme(inline)`-d), so `table` is substituted FIRST — without it
970
+ * `shadow-<token>` / `ring-<token>` silently drop the color (culori can't
971
+ * parse a bare `var()`). The table is per-scheme so a token that differs
972
+ * between light/dark resolves to the right value for each.
973
+ * @param raw Raw color text from a `--tw-shadow-color` / `--tw-ring-color` prop.
974
+ * @param table Per-scheme var table for resolving `var(--color-x)` references.
905
975
  * @returns `#rrggbb` string, or null when culori can't parse it.
906
976
  */
907
- function resolveCustomColorString(raw) {
908
- const text = unwrapVariableFallback(raw).trim();
909
- if (text.length === 0)
977
+ function resolveCustomColorString(raw, table) {
978
+ const seeded = new Map([...COMPOSABLE_ALPHA_DEFAULTS, ...(table ?? [])]);
979
+ const substituted = substituteThemeVars(raw, seeded);
980
+ // `coerceUnparsedValue` collapses Tailwind's opacity shape
981
+ // `color-mix(in oklab, <color> <pct>%, transparent)` (emitted by
982
+ // `shadow-<token>` / `ring-<token>`) to a flat rgba/hex and unwraps
983
+ // `var(…, fallback)`. Modern spaces (`oklch(…)`) then lower via
984
+ // `normalizeColorString`; anything still un-RN-safe falls to culori.
985
+ const coerced = coerceUnparsedValue(unwrapVariableFallback(substituted).trim());
986
+ if (typeof coerced !== 'string' || coerced.length === 0 || coerced.startsWith('var('))
910
987
  return null;
911
- if (text.startsWith('#'))
912
- return text;
913
- return parseCssColorToHex(text);
988
+ if (coerced.startsWith('#') || coerced.startsWith('rgb') || coerced.startsWith('hsl'))
989
+ return coerced;
990
+ return normalizeColorString(coerced) ?? parseCssColorToHex(coerced);
914
991
  }
915
992
  /**
916
993
  * Parse any CSS color expression into an `#rrggbb` string via culori.
@@ -1016,6 +1093,12 @@ function parseShadowColor(expr) {
1016
1093
  return rgba;
1017
1094
  if (working.startsWith('#'))
1018
1095
  return { color: working, opacity: 1 };
1096
+ // Named (`red`) / modern (`hsl(…)`, `oklch(…)`) colors — culori → sRGB hex.
1097
+ // Without this they fell to the default black at 0.1 alpha, silently losing
1098
+ // the user's `shadow-[0_2px_4px_red]` color.
1099
+ const hex = formatHexSafe(working);
1100
+ if (hex)
1101
+ return { color: hex, opacity: 1 };
1019
1102
  return { color: '#000', opacity: 0.1 };
1020
1103
  }
1021
1104
  /**
@@ -1220,8 +1303,8 @@ function resolveLengthExpression(text) {
1220
1303
  if (evaluated.unit === '%')
1221
1304
  return `${stripTrailingZeros(evaluated.value)}%`;
1222
1305
  if (evaluated.unit === 'rem')
1223
- return evaluated.value * 16;
1224
- return evaluated.value;
1306
+ return roundTransformValue(evaluated.value * 16);
1307
+ return roundTransformValue(evaluated.value);
1225
1308
  }
1226
1309
  /**
1227
1310
  * Evaluate a CSS length expression to a `{value, unit}` pair.
@@ -1448,19 +1531,38 @@ function parseArithmeticFactor(tokens, cursor) {
1448
1531
  return number_;
1449
1532
  }
1450
1533
  /**
1451
- * Resolve a scale factor expressed as a percentage (`150%`) or number (`1.5`).
1534
+ * Resolve a scale factor expressed as a percentage (`150%`), number (`1.5`),
1535
+ * or a `calc()` expression. Tailwind emits NEGATIVE scale utilities as a calc
1536
+ * (`-scale-x-100` → `calc(100% * -1)`), so a plain percent/number regex
1537
+ * silently dropped them — `-scale-*` (the horizontal-flip idiom) rendered
1538
+ * nothing. Fall back to the shared arithmetic evaluator, reading `%` as a
1539
+ * fraction (`100%` → 1) and rounding off f32 noise.
1452
1540
  * @param text Raw value.
1453
- * @returns Scale number (e.g. 1.5 for 150%), or null.
1541
+ * @returns Scale number (e.g. 1.5 for 150%, -1 for `calc(100% * -1)`), or null.
1454
1542
  */
1455
1543
  function resolveNumberOrPercent(text) {
1456
1544
  const trimmed = text.trim();
1457
1545
  const percent = /^(-?\d+(?:\.\d+)?)%$/.exec(trimmed);
1458
1546
  if (percent)
1459
- return Number(percent[1]) / 100;
1547
+ return roundTransformValue(Number(percent[1]) / 100);
1460
1548
  const bare = /^-?\d+(?:\.\d+)?$/.exec(trimmed);
1461
1549
  if (bare)
1462
- return Number(trimmed);
1463
- return null;
1550
+ return roundTransformValue(Number(trimmed));
1551
+ const evaluated = evaluateLengthExpr(trimmed);
1552
+ if (!evaluated || evaluated.unit === 'rem')
1553
+ return null;
1554
+ return roundTransformValue(evaluated.unit === '%' ? evaluated.value / 100 : evaluated.value);
1555
+ }
1556
+ /**
1557
+ * Round a composed-transform numeric value to 4 decimals. lightningcss
1558
+ * serializes arbitrary literals (`scale-x-[0.333]`) back as noisy f32 text
1559
+ * (`0.3330000042915344`), and the resolvers `Number()` that verbatim — round
1560
+ * so the RN `transform` array stays clean.
1561
+ * @param value Raw number.
1562
+ * @returns Rounded number.
1563
+ */
1564
+ function roundTransformValue(value) {
1565
+ return Math.round(value * 10_000) / 10_000;
1464
1566
  }
1465
1567
  /**
1466
1568
  * Extract the angle from Tailwind's `skewX(12deg)` / `skewY(-5deg)` /