rnwind 0.0.2 → 0.0.3

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 (80) hide show
  1. package/lib/cjs/core/parser/color.cjs +53 -24
  2. package/lib/cjs/core/parser/color.cjs.map +1 -1
  3. package/lib/cjs/core/parser/layout-dispatcher.cjs +20 -0
  4. package/lib/cjs/core/parser/layout-dispatcher.cjs.map +1 -1
  5. package/lib/cjs/core/parser/length.cjs +20 -6
  6. package/lib/cjs/core/parser/length.cjs.map +1 -1
  7. package/lib/cjs/core/parser/length.d.ts +6 -3
  8. package/lib/cjs/core/parser/shorthand.cjs +37 -5
  9. package/lib/cjs/core/parser/shorthand.cjs.map +1 -1
  10. package/lib/cjs/core/parser/shorthand.d.ts +11 -5
  11. package/lib/cjs/core/parser/theme-vars.cjs +53 -0
  12. package/lib/cjs/core/parser/theme-vars.cjs.map +1 -1
  13. package/lib/cjs/core/parser/theme-vars.d.ts +21 -0
  14. package/lib/cjs/core/parser/tokens.cjs +183 -1
  15. package/lib/cjs/core/parser/tokens.cjs.map +1 -1
  16. package/lib/cjs/core/parser/tw-parser.cjs +140 -27
  17. package/lib/cjs/core/parser/tw-parser.cjs.map +1 -1
  18. package/lib/cjs/core/parser/tw-parser.d.ts +21 -5
  19. package/lib/cjs/core/parser/typography-dispatcher.cjs +16 -1
  20. package/lib/cjs/core/parser/typography-dispatcher.cjs.map +1 -1
  21. package/lib/cjs/core/style-builder/build-style.cjs +73 -26
  22. package/lib/cjs/core/style-builder/build-style.cjs.map +1 -1
  23. package/lib/cjs/metro/state.cjs +52 -2
  24. package/lib/cjs/metro/state.cjs.map +1 -1
  25. package/lib/cjs/metro/state.d.ts +17 -1
  26. package/lib/cjs/metro/transform-ast.cjs +238 -21
  27. package/lib/cjs/metro/transform-ast.cjs.map +1 -1
  28. package/lib/cjs/metro/transform-ast.d.ts +15 -0
  29. package/lib/cjs/metro/transformer.cjs +29 -2
  30. package/lib/cjs/metro/transformer.cjs.map +1 -1
  31. package/lib/cjs/metro/with-config.cjs +1 -1
  32. package/lib/cjs/metro/with-config.cjs.map +1 -1
  33. package/lib/cjs/metro/with-config.d.ts +22 -0
  34. package/lib/esm/core/parser/color.mjs +53 -24
  35. package/lib/esm/core/parser/color.mjs.map +1 -1
  36. package/lib/esm/core/parser/layout-dispatcher.mjs +20 -0
  37. package/lib/esm/core/parser/layout-dispatcher.mjs.map +1 -1
  38. package/lib/esm/core/parser/length.d.ts +6 -3
  39. package/lib/esm/core/parser/length.mjs +20 -6
  40. package/lib/esm/core/parser/length.mjs.map +1 -1
  41. package/lib/esm/core/parser/shorthand.d.ts +11 -5
  42. package/lib/esm/core/parser/shorthand.mjs +37 -5
  43. package/lib/esm/core/parser/shorthand.mjs.map +1 -1
  44. package/lib/esm/core/parser/theme-vars.d.ts +21 -0
  45. package/lib/esm/core/parser/theme-vars.mjs +53 -1
  46. package/lib/esm/core/parser/theme-vars.mjs.map +1 -1
  47. package/lib/esm/core/parser/tokens.mjs +183 -1
  48. package/lib/esm/core/parser/tokens.mjs.map +1 -1
  49. package/lib/esm/core/parser/tw-parser.d.ts +21 -5
  50. package/lib/esm/core/parser/tw-parser.mjs +141 -28
  51. package/lib/esm/core/parser/tw-parser.mjs.map +1 -1
  52. package/lib/esm/core/parser/typography-dispatcher.mjs +16 -1
  53. package/lib/esm/core/parser/typography-dispatcher.mjs.map +1 -1
  54. package/lib/esm/core/style-builder/build-style.mjs +73 -26
  55. package/lib/esm/core/style-builder/build-style.mjs.map +1 -1
  56. package/lib/esm/metro/state.d.ts +17 -1
  57. package/lib/esm/metro/state.mjs +51 -3
  58. package/lib/esm/metro/state.mjs.map +1 -1
  59. package/lib/esm/metro/transform-ast.d.ts +15 -0
  60. package/lib/esm/metro/transform-ast.mjs +238 -21
  61. package/lib/esm/metro/transform-ast.mjs.map +1 -1
  62. package/lib/esm/metro/transformer.mjs +30 -3
  63. package/lib/esm/metro/transformer.mjs.map +1 -1
  64. package/lib/esm/metro/with-config.d.ts +22 -0
  65. package/lib/esm/metro/with-config.mjs +1 -1
  66. package/lib/esm/metro/with-config.mjs.map +1 -1
  67. package/package.json +2 -1
  68. package/src/core/parser/color.ts +52 -18
  69. package/src/core/parser/layout-dispatcher.ts +19 -0
  70. package/src/core/parser/length.ts +20 -6
  71. package/src/core/parser/shorthand.ts +35 -5
  72. package/src/core/parser/theme-vars.ts +53 -0
  73. package/src/core/parser/tokens.ts +171 -1
  74. package/src/core/parser/tw-parser.ts +147 -28
  75. package/src/core/parser/typography-dispatcher.ts +15 -1
  76. package/src/core/style-builder/build-style.ts +84 -26
  77. package/src/metro/state.ts +49 -1
  78. package/src/metro/transform-ast.ts +249 -18
  79. package/src/metro/transformer.ts +28 -3
  80. package/src/metro/with-config.ts +23 -1
@@ -27,14 +27,18 @@ const DEFAULT_TRANSFORM_OPTIONS = {
27
27
  },
28
28
  include: lightningcss.Features.Nesting | lightningcss.Features.MediaQueries,
29
29
  exclude: lightningcss.Features.LogicalProperties | lightningcss.Features.DirSelector | lightningcss.Features.LightDark,
30
- targets: {
31
- // eslint-disable-next-line sonarjs/no-identical-expressions
32
- safari: (16 << 16) | (4 << 8),
33
- // eslint-disable-next-line camelcase, sonarjs/no-identical-expressions
34
- ios_saf: (16 << 16) | (4 << 8),
35
- firefox: 128 << 16,
36
- chrome: 111 << 16,
37
- },
30
+ // NOTE: deliberately no `targets`. With targets that include
31
+ // color-mix-supporting browsers (Safari 16.4+, Chrome 111+, …),
32
+ // lightningcss EVALUATES `color-mix(in oklab, var(--theme-color)
33
+ // <pct>%, transparent)` at parse time using whichever value of
34
+ // `--theme-color` it sees first in the cascade. Tailwind v4 emits
35
+ // exactly this shape for `<prop>-<themed>/<N>` utilities (e.g.
36
+ // `border-text/20`), so the resulting RGB color is locked to ONE
37
+ // scheme — every variant gets the same value. By dropping targets,
38
+ // lightningcss leaves color-mix as an unparsed function and our
39
+ // per-scheme `unparsedToEntries` substitution path runs instead,
40
+ // producing the right rgba(...) for each scheme. Targets in this
41
+ // pipeline are otherwise unused — we never re-emit CSS from the AST.
38
42
  };
39
43
  /**
40
44
  * Parses one source file's Tailwind usage into RN-ready style objects.
@@ -58,6 +62,13 @@ class TailwindParser {
58
62
  compiler;
59
63
  themeSchemes;
60
64
  schemeAliases;
65
+ /**
66
+ * Scheme names declared via `@custom-variant <name> …;`. A scheme
67
+ * listed here but absent from {@link themeSchemes} (no `@variant`
68
+ * override block) draws its values from the base `@theme` — the
69
+ * standard Tailwind v4 "light defaults + dark override" shape.
70
+ */
71
+ customVariantSchemes;
61
72
  /**
62
73
  * Memoise `resolveCandidates` results by candidate-list fingerprint.
63
74
  * Fast Refresh hits this on every save: oxide's scan is cheap, but
@@ -81,19 +92,42 @@ class TailwindParser {
81
92
  this.config = config;
82
93
  this.themeSchemes = themeVars.extractThemeVars(config.themeCss);
83
94
  this.schemeAliases = themeVars.extractSchemeAliases(config.themeCss);
95
+ this.customVariantSchemes = themeVars.extractCustomVariantSchemes(config.themeCss);
84
96
  this.scanner = new oxide.Scanner({ sources: config.sources ? [...config.sources] : [] });
85
97
  }
86
98
  /**
87
- * Schemes declared by the user — either every `@variant <name>` block
88
- * found (in declaration order) or just `['base']` for themes without
89
- * any variants. Used to decide how many per-scheme buckets the
90
- * per-atom resolver fills. Exposed publicly so Metro integration can
91
- * hand the names to the `.d.ts` generator without a full parse.
99
+ * Schemes declared by the user — the union of every `@custom-variant
100
+ * <name>` declaration and every `@variant <name>` block, or just
101
+ * `['base']` for themes without any. Used to decide how many
102
+ * per-scheme buckets the per-atom resolver fills. Exposed publicly so
103
+ * Metro integration can hand the names to the `.d.ts` generator
104
+ * without a full parse.
105
+ *
106
+ * Both sources matter. `@variant` blocks alone miss the common
107
+ * Tailwind v4 shape where the light palette sits in the base `@theme`
108
+ * and only `@variant dark` overrides it: there `light` exists solely
109
+ * as a `@custom-variant` and would otherwise be dropped, collapsing
110
+ * every themed atom to a single bucket that can't switch.
111
+ * `@custom-variant` order wins (it's where users enumerate their
112
+ * schemes); any `@variant`-only scheme is appended after.
92
113
  * @returns Scheme names.
93
114
  */
94
115
  get declaredSchemes() {
95
- const variants = [...this.themeSchemes.keys()].filter((name) => name !== themeVars.BASE_SCHEME);
96
- return variants.length > 0 ? variants : [themeVars.BASE_SCHEME];
116
+ const ordered = [];
117
+ const seen = new Set();
118
+ for (const name of this.customVariantSchemes) {
119
+ if (seen.has(name))
120
+ continue;
121
+ seen.add(name);
122
+ ordered.push(name);
123
+ }
124
+ for (const name of this.themeSchemes.keys()) {
125
+ if (name === themeVars.BASE_SCHEME || seen.has(name))
126
+ continue;
127
+ seen.add(name);
128
+ ordered.push(name);
129
+ }
130
+ return ordered.length > 0 ? ordered : [themeVars.BASE_SCHEME];
97
131
  }
98
132
  /**
99
133
  * Build an effective var table for one scheme — base vars overridden by
@@ -186,6 +220,19 @@ class TailwindParser {
186
220
  catch (error) {
187
221
  throw wrapThemeError(error);
188
222
  }
223
+ // Tailwind v4 emits opacity-suffixed themed colors as a pre-resolved
224
+ // sRGB fallback PLUS a `@supports`-gated var()-based override:
225
+ // border-color: color-mix(in srgb, #0A0A0A 20%, transparent);
226
+ // @supports (color: color-mix(in lab, red, red)) {
227
+ // border-color: color-mix(in oklab, var(--color-text) 20%, transparent);
228
+ // }
229
+ // Lightningcss takes the OUTER fallback (locked to whichever scheme
230
+ // the compiler resolved first), and our per-scheme substitution
231
+ // never gets a chance. Unwrap the @supports so the var()-based
232
+ // declaration overrides the fallback in the same rule — lightningcss
233
+ // emits the override as `unparsed` and the parser's themeVars-aware
234
+ // path produces correct rgba per scheme.
235
+ css = unwrapColorMixSupports(css);
189
236
  // `compiler.build(candidates)` memoizes across calls — it returns CSS for
190
237
  // every candidate the compiler has EVER seen in this process. To keep
191
238
  // parser output pure per-call we restrict outputs to this call's
@@ -918,20 +965,24 @@ function parseFirstShadow(raw) {
918
965
  * @returns Pixel lengths + the remainder text (color expression).
919
966
  */
920
967
  function extractShadowLengths(head) {
921
- const lengthRegex = /(?<![A-Za-z(])-?\d*\.?\d+(?:px|rem|em|%)?/g;
968
+ // Take ONLY the leading run of length tokens, stopping at the first
969
+ // non-length token (the color). A previous global digit-regex scanned
970
+ // the whole string, so a <4-length shadow like `0 1px 1px rgb(0 0 0 /
971
+ // 0.05)` stole a digit out of the color expression — corrupting the
972
+ // alpha (opacity) or a digit-leading hex. Whitespace-splitting can't
973
+ // reach inside the color because we break as soon as a token isn't a
974
+ // bare/`px`/`rem`/`em`/`%` length.
975
+ // Unambiguous integer-or-decimal (no `\d*\.?\d+` overlap) so there's no
976
+ // super-linear backtracking on long digit runs.
977
+ const isLength = /^-?(?:\d+(?:\.\d+)?|\.\d+)(?:px|rem|em|%)?$/;
978
+ const parts = head.split(/\s+/);
922
979
  const lengths = [];
923
- const matches = [];
924
- let next = lengthRegex.exec(head);
925
- while (next !== null && lengths.length < 4) {
926
- matches.push({ text: next[0], index: next.index });
927
- lengths.push(parseLengthToken(next[0]));
928
- next = lengthRegex.exec(head);
929
- }
930
- let remainder = head;
931
- for (const { text, index } of matches.toReversed()) {
932
- remainder = `${remainder.slice(0, index)}${remainder.slice(index + text.length)}`;
980
+ let index = 0;
981
+ while (index < parts.length && lengths.length < 4 && isLength.test(parts[index])) {
982
+ lengths.push(parseLengthToken(parts[index]));
983
+ index += 1;
933
984
  }
934
- return { lengths, remainder };
985
+ return { lengths, remainder: parts.slice(index).join(' ') };
935
986
  }
936
987
  /**
937
988
  * Coerce one shadow length token into a pixel number. Accepts bare
@@ -1470,6 +1521,68 @@ function resolveAngleExpression(text) {
1470
1521
  * @param css Tailwind's compiled CSS.
1471
1522
  * @returns Map of custom-property name → resolved value.
1472
1523
  */
1524
+ /**
1525
+ * Strip `\@supports (color: color-mix(in lab, red, red)) { … }` wrappers
1526
+ * from Tailwind v4's compiled CSS, hoisting their inner declarations up
1527
+ * to the parent rule.
1528
+ *
1529
+ * Tailwind emits opacity-suffixed themed colors with both a pre-resolved
1530
+ * sRGB fallback AND a var()-based override gated behind the color-mix
1531
+ * `\@supports` clause. The OUTER fallback hard-codes a single scheme's
1532
+ * value of the theme token; the inner override is var()-based and
1533
+ * substitutes correctly per scheme. By unwrapping the gate, the inner
1534
+ * declaration becomes a sibling of the fallback in the same rule body —
1535
+ * lightningcss takes the LATER one (the var()-based unparsed form), and
1536
+ * the parser's themeVars-aware path produces correct rgba per scheme.
1537
+ * Modern RN-targeted browsers all support color-mix anyway, so dropping
1538
+ * the gating is safe.
1539
+ * @param css Tailwind-compiled CSS.
1540
+ * @returns CSS with the color-mix support gates unwrapped.
1541
+ */
1542
+ function unwrapColorMixSupports(css) {
1543
+ const guard = '@supports (color: color-mix(in lab, red, red))';
1544
+ let out = '';
1545
+ let cursor = 0;
1546
+ while (cursor < css.length) {
1547
+ const head = css.indexOf(guard, cursor);
1548
+ if (head === -1) {
1549
+ out += css.slice(cursor);
1550
+ break;
1551
+ }
1552
+ out += css.slice(cursor, head);
1553
+ const brace = css.indexOf('{', head);
1554
+ if (brace === -1) {
1555
+ out += css.slice(head);
1556
+ break;
1557
+ }
1558
+ const blockEnd = findMatchingClose(css, brace + 1);
1559
+ if (blockEnd === -1) {
1560
+ out += css.slice(head);
1561
+ break;
1562
+ }
1563
+ const inner = css.slice(brace + 1, blockEnd);
1564
+ // Only unwrap when the gated declaration substitutes a USER theme
1565
+ // token (`var(--color-…)`). Tailwind also gates `--tw-*` internal
1566
+ // composers (shadow color, ring color, …) on the same supports
1567
+ // clause; their outer fallback is the optimized hex/oklch value
1568
+ // the parser's own composed-prop pass needs (`applyComposedShadow`
1569
+ // reads `--tw-shadow-color` from the rule's local vars). Unwrapping
1570
+ // them would replace the resolvable color with an unresolvable
1571
+ // `color-mix(... var(--tw-shadow-alpha), transparent)` text and
1572
+ // break the composed-shadow path.
1573
+ // Keep the gate intact for non-themed colors — the outer fallback
1574
+ // wins, which is what Tailwind intended.
1575
+ out += inner.includes('var(--color-') ? inner : css.slice(head, blockEnd + 1);
1576
+ cursor = blockEnd + 1;
1577
+ }
1578
+ return out;
1579
+ }
1580
+ /**
1581
+ * Extract every `--name: value` declaration from the `:root` blocks in
1582
+ * Tailwind's compiled CSS into a flat map.
1583
+ * @param css Tailwind-compiled CSS.
1584
+ * @returns Map of custom-property name → resolved value.
1585
+ */
1473
1586
  function extractRootCustomProperties(css) {
1474
1587
  const out = new Map();
1475
1588
  let cursor = 0;