rnwind 0.0.7 → 0.0.9

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 (131) 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 +161 -10
  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 +93 -33
  26. package/lib/cjs/core/parser/tw-parser.cjs.map +1 -1
  27. package/lib/cjs/core/parser/typography-dispatcher.cjs +19 -1
  28. package/lib/cjs/core/parser/typography-dispatcher.cjs.map +1 -1
  29. package/lib/cjs/core/parser/typography.cjs +15 -18
  30. package/lib/cjs/core/parser/typography.cjs.map +1 -1
  31. package/lib/cjs/core/parser/typography.d.ts +5 -5
  32. package/lib/cjs/core/style-builder/union-builder.cjs +0 -10
  33. package/lib/cjs/core/style-builder/union-builder.cjs.map +1 -1
  34. package/lib/cjs/core/style-builder/union-builder.d.ts +0 -8
  35. package/lib/cjs/metro/dts.cjs +6 -1
  36. package/lib/cjs/metro/dts.cjs.map +1 -1
  37. package/lib/cjs/metro/transformer.cjs +43 -60
  38. package/lib/cjs/metro/transformer.cjs.map +1 -1
  39. package/lib/cjs/metro/with-config.cjs +9 -29
  40. package/lib/cjs/metro/with-config.cjs.map +1 -1
  41. package/lib/cjs/runtime/hooks/use-scheme.cjs +9 -6
  42. package/lib/cjs/runtime/hooks/use-scheme.cjs.map +1 -1
  43. package/lib/cjs/runtime/hooks/use-scheme.d.ts +7 -4
  44. package/lib/cjs/runtime/index.cjs +1 -1
  45. package/lib/cjs/runtime/index.cjs.map +1 -1
  46. package/lib/cjs/runtime/index.d.ts +1 -1
  47. package/lib/cjs/runtime/lookup-css.cjs +14 -0
  48. package/lib/cjs/runtime/lookup-css.cjs.map +1 -1
  49. package/lib/cjs/runtime/lookup-css.d.ts +11 -0
  50. package/lib/cjs/runtime/resolve.cjs +8 -6
  51. package/lib/cjs/runtime/resolve.cjs.map +1 -1
  52. package/lib/cjs/runtime/wrap.cjs +50 -57
  53. package/lib/cjs/runtime/wrap.cjs.map +1 -1
  54. package/lib/cjs/runtime/wrap.d.ts +10 -4
  55. package/lib/esm/core/parser/color.d.ts +10 -0
  56. package/lib/esm/core/parser/color.mjs +33 -2
  57. package/lib/esm/core/parser/color.mjs.map +1 -1
  58. package/lib/esm/core/parser/declaration.mjs +162 -11
  59. package/lib/esm/core/parser/declaration.mjs.map +1 -1
  60. package/lib/esm/core/parser/gradient.d.ts +2 -1
  61. package/lib/esm/core/parser/gradient.mjs +45 -11
  62. package/lib/esm/core/parser/gradient.mjs.map +1 -1
  63. package/lib/esm/core/parser/keyframes.d.ts +11 -0
  64. package/lib/esm/core/parser/keyframes.mjs +27 -12
  65. package/lib/esm/core/parser/keyframes.mjs.map +1 -1
  66. package/lib/esm/core/parser/layout-dispatcher.mjs +33 -10
  67. package/lib/esm/core/parser/layout-dispatcher.mjs.map +1 -1
  68. package/lib/esm/core/parser/length.mjs +17 -1
  69. package/lib/esm/core/parser/length.mjs.map +1 -1
  70. package/lib/esm/core/parser/safe-area.mjs +24 -3
  71. package/lib/esm/core/parser/safe-area.mjs.map +1 -1
  72. package/lib/esm/core/parser/theme-vars.mjs +58 -8
  73. package/lib/esm/core/parser/theme-vars.mjs.map +1 -1
  74. package/lib/esm/core/parser/tokens.d.ts +9 -0
  75. package/lib/esm/core/parser/tokens.mjs +77 -10
  76. package/lib/esm/core/parser/tokens.mjs.map +1 -1
  77. package/lib/esm/core/parser/transform.mjs +18 -9
  78. package/lib/esm/core/parser/transform.mjs.map +1 -1
  79. package/lib/esm/core/parser/tw-parser.mjs +95 -35
  80. package/lib/esm/core/parser/tw-parser.mjs.map +1 -1
  81. package/lib/esm/core/parser/typography-dispatcher.mjs +19 -1
  82. package/lib/esm/core/parser/typography-dispatcher.mjs.map +1 -1
  83. package/lib/esm/core/parser/typography.d.ts +5 -5
  84. package/lib/esm/core/parser/typography.mjs +15 -18
  85. package/lib/esm/core/parser/typography.mjs.map +1 -1
  86. package/lib/esm/core/style-builder/union-builder.d.ts +0 -8
  87. package/lib/esm/core/style-builder/union-builder.mjs +0 -10
  88. package/lib/esm/core/style-builder/union-builder.mjs.map +1 -1
  89. package/lib/esm/metro/dts.mjs +6 -1
  90. package/lib/esm/metro/dts.mjs.map +1 -1
  91. package/lib/esm/metro/transformer.mjs +43 -60
  92. package/lib/esm/metro/transformer.mjs.map +1 -1
  93. package/lib/esm/metro/with-config.mjs +10 -30
  94. package/lib/esm/metro/with-config.mjs.map +1 -1
  95. package/lib/esm/runtime/hooks/use-scheme.d.ts +7 -4
  96. package/lib/esm/runtime/hooks/use-scheme.mjs +9 -6
  97. package/lib/esm/runtime/hooks/use-scheme.mjs.map +1 -1
  98. package/lib/esm/runtime/index.d.ts +1 -1
  99. package/lib/esm/runtime/index.mjs +1 -1
  100. package/lib/esm/runtime/index.mjs.map +1 -1
  101. package/lib/esm/runtime/lookup-css.d.ts +11 -0
  102. package/lib/esm/runtime/lookup-css.mjs +14 -1
  103. package/lib/esm/runtime/lookup-css.mjs.map +1 -1
  104. package/lib/esm/runtime/resolve.mjs +9 -7
  105. package/lib/esm/runtime/resolve.mjs.map +1 -1
  106. package/lib/esm/runtime/wrap.d.ts +10 -4
  107. package/lib/esm/runtime/wrap.mjs +50 -57
  108. package/lib/esm/runtime/wrap.mjs.map +1 -1
  109. package/package.json +1 -1
  110. package/src/core/parser/color.ts +32 -1
  111. package/src/core/parser/declaration.ts +160 -10
  112. package/src/core/parser/gradient.ts +48 -11
  113. package/src/core/parser/keyframes.ts +31 -3
  114. package/src/core/parser/layout-dispatcher.ts +32 -9
  115. package/src/core/parser/length.ts +18 -1
  116. package/src/core/parser/safe-area.ts +23 -2
  117. package/src/core/parser/theme-vars.ts +75 -8
  118. package/src/core/parser/tokens.ts +76 -9
  119. package/src/core/parser/transform.ts +19 -8
  120. package/src/core/parser/tw-parser.ts +95 -30
  121. package/src/core/parser/typography-dispatcher.ts +20 -1
  122. package/src/core/parser/typography.ts +15 -15
  123. package/src/core/style-builder/union-builder.ts +0 -11
  124. package/src/metro/dts.ts +6 -1
  125. package/src/metro/transformer.ts +45 -61
  126. package/src/metro/with-config.ts +10 -29
  127. package/src/runtime/hooks/use-scheme.ts +9 -6
  128. package/src/runtime/index.ts +1 -1
  129. package/src/runtime/lookup-css.ts +14 -0
  130. package/src/runtime/resolve.ts +9 -7
  131. package/src/runtime/wrap.tsx +57 -61
@@ -1,5 +1,8 @@
1
1
  import { lineHeightToEntries } from './typography.mjs';
2
+ import { firstConcreteFontFamily } from './tokens.mjs';
2
3
 
4
+ /** RN-supported `textDecorationStyle` values (`wavy` has no RN equivalent). */
5
+ const RN_DECORATION_STYLES = new Set(['solid', 'double', 'dotted', 'dashed']);
3
6
  /**
4
7
  * Build the RN `textDecorationLine` entry — string identity for the
5
8
  * single-line cases, joined-string for the array shape.
@@ -51,7 +54,9 @@ function letterSpacingToEntries(value) {
51
54
  const { unit, value: px } = inner.value;
52
55
  if (typeof px !== 'number')
53
56
  return [];
54
- return [['letterSpacing', unit === 'px' ? px : px * 16]];
57
+ const resolved = unit === 'px' ? px : px * 16;
58
+ // Round off lightningcss f32 noise (`0.1em` → `1.600000023841858`).
59
+ return [['letterSpacing', Math.round(resolved * 10_000) / 10_000]];
55
60
  }
56
61
  /**
57
62
  * Lower a CSS `text-align` keyword to one RN's `textAlign` accepts. RN
@@ -87,6 +92,19 @@ function dispatchTypographyDeclaration(decl) {
87
92
  case 'text-decoration-line': {
88
93
  return textDecorationLineToEntries(decl.value);
89
94
  }
95
+ case 'text-decoration-style': {
96
+ // RN <Text> supports textDecorationStyle (solid/double/dotted/dashed).
97
+ const style = String(decl.value);
98
+ return RN_DECORATION_STYLES.has(style) ? [['textDecorationStyle', style]] : [];
99
+ }
100
+ case 'font-family': {
101
+ // Typed `font-family` is a fallback LIST (`font-sans`, `font-mono`,
102
+ // `font-[Inter]`). RN takes one concrete typeface; an all-generic
103
+ // stack (default `font-sans`) emits nothing → system font. The themed
104
+ // `var(--font-*)` path goes through `coerceFontFamily` in declaration.ts.
105
+ const family = firstConcreteFontFamily(decl.value);
106
+ return family === undefined ? [] : [['fontFamily', family]];
107
+ }
90
108
  case 'aspect-ratio': {
91
109
  return aspectRatioToEntries(decl.value);
92
110
  }
@@ -1 +1 @@
1
- {"version":3,"file":"typography-dispatcher.mjs","sources":["../../../../../src/core/parser/typography-dispatcher.ts"],"sourcesContent":["import type { Declaration as LcDeclaration } from 'lightningcss'\nimport { lineHeightToEntries } from './typography'\nimport type { RNEntry } from './types'\n\n/**\n * Build the RN `textDecorationLine` entry — string identity for the\n * single-line cases, joined-string for the array shape.\n * @param value Typed text-decoration-line.\n * @returns Single-entry list with `textDecorationLine`.\n */\nfunction textDecorationLineToEntries(value: LcDeclaration['value']): readonly RNEntry[] {\n if (value === 'none') return [['textDecorationLine', 'none']]\n if (typeof value === 'string') return [['textDecorationLine', value]]\n if (Array.isArray(value)) return [['textDecorationLine', value.join(' ')]]\n return []\n}\n\n/**\n * Build the RN `aspectRatio` entry from lightningcss's typed value.\n * Drops `auto` (no RN equivalent).\n * @param value Typed aspect-ratio value.\n * @param value.auto Whether the value resolved to `auto`.\n * @param value.ratio Numeric `[width, height]` ratio (or null/undefined).\n * @returns Single-entry list or empty.\n */\nfunction aspectRatioToEntries(value: { auto?: boolean; ratio?: readonly [number, number] | null }): readonly RNEntry[] {\n if (value.auto) return []\n if (!value.ratio) return []\n const [w, h] = value.ratio\n if (h === 0) return []\n return [['aspectRatio', w / h]]\n}\n\n/**\n * Build the RN `letterSpacing` entry. RN expects pixel numbers; rem\n * lengths are scaled to px (16-px base).\n * @param value Typed letter-spacing value.\n * @returns Single-entry list or empty.\n */\nfunction letterSpacingToEntries(value: LcDeclaration['value']): readonly RNEntry[] {\n if (typeof value !== 'object') return []\n const tagged = value as { type?: string; value?: { type?: string; value?: { unit?: string; value?: number } } }\n if (tagged.type === 'normal') return [['letterSpacing', 0]]\n const inner = tagged.value\n if (inner?.type !== 'value' || !inner.value) return []\n const { unit, value: px } = inner.value\n if (typeof px !== 'number') return []\n return [['letterSpacing', unit === 'px' ? px : px * 16]]\n}\n\n/**\n * Lower a CSS `text-align` keyword to one RN's `textAlign` accepts. RN\n * has no logical `start`/`end`, so map them to physical sides (LTR\n * default); every other keyword (left/right/center/justify/auto) is\n * already valid and passes through.\n * @param align CSS text-align keyword.\n * @returns RN-valid textAlign keyword.\n */\nfunction physicalTextAlign(align: string): string {\n if (align === 'start') return 'left'\n if (align === 'end') return 'right'\n return align\n}\n\n/**\n * Dispatch typography declarations rnwind cares about (text-align,\n * text-transform, text-decoration-line, line-height, letter-spacing,\n * aspect-ratio). Returns null when the property isn't one of these so\n * the caller can fall through to its main switch.\n * @param decl One lightningcss declaration.\n * @returns RN entries when the property matched, else `null`.\n */\nexport function dispatchTypographyDeclaration(decl: LcDeclaration): readonly RNEntry[] | null {\n switch (decl.property) {\n case 'text-align': {\n return [['textAlign', physicalTextAlign(String(decl.value))]]\n }\n case 'text-transform': {\n return [['textTransform', decl.value.case ?? 'none']]\n }\n case 'text-decoration-line': {\n return textDecorationLineToEntries(decl.value)\n }\n case 'aspect-ratio': {\n return aspectRatioToEntries(decl.value)\n }\n case 'line-height': {\n return lineHeightToEntries(decl.value)\n }\n case 'letter-spacing': {\n return letterSpacingToEntries(decl.value)\n }\n default: {\n return null\n }\n }\n}\n"],"names":[],"mappings":";;AAIA;;;;;AAKG;AACH,SAAS,2BAA2B,CAAC,KAA6B,EAAA;IAChE,IAAI,KAAK,KAAK,MAAM;AAAE,QAAA,OAAO,CAAC,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC;IAC7D,IAAI,OAAO,KAAK,KAAK,QAAQ;AAAE,QAAA,OAAO,CAAC,CAAC,oBAAoB,EAAE,KAAK,CAAC,CAAC;AACrE,IAAA,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;AAAE,QAAA,OAAO,CAAC,CAAC,oBAAoB,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1E,IAAA,OAAO,EAAE;AACX;AAEA;;;;;;;AAOG;AACH,SAAS,oBAAoB,CAAC,KAAmE,EAAA;IAC/F,IAAI,KAAK,CAAC,IAAI;AAAE,QAAA,OAAO,EAAE;IACzB,IAAI,CAAC,KAAK,CAAC,KAAK;AAAE,QAAA,OAAO,EAAE;IAC3B,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK;IAC1B,IAAI,CAAC,KAAK,CAAC;AAAE,QAAA,OAAO,EAAE;IACtB,OAAO,CAAC,CAAC,aAAa,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;AACjC;AAEA;;;;;AAKG;AACH,SAAS,sBAAsB,CAAC,KAA6B,EAAA;IAC3D,IAAI,OAAO,KAAK,KAAK,QAAQ;AAAE,QAAA,OAAO,EAAE;IACxC,MAAM,MAAM,GAAG,KAAgG;AAC/G,IAAA,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ;AAAE,QAAA,OAAO,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;AAC3D,IAAA,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK;IAC1B,IAAI,KAAK,EAAE,IAAI,KAAK,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK;AAAE,QAAA,OAAO,EAAE;IACtD,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,GAAG,KAAK,CAAC,KAAK;IACvC,IAAI,OAAO,EAAE,KAAK,QAAQ;AAAE,QAAA,OAAO,EAAE;AACrC,IAAA,OAAO,CAAC,CAAC,eAAe,EAAE,IAAI,KAAK,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;AAC1D;AAEA;;;;;;;AAOG;AACH,SAAS,iBAAiB,CAAC,KAAa,EAAA;IACtC,IAAI,KAAK,KAAK,OAAO;AAAE,QAAA,OAAO,MAAM;IACpC,IAAI,KAAK,KAAK,KAAK;AAAE,QAAA,OAAO,OAAO;AACnC,IAAA,OAAO,KAAK;AACd;AAEA;;;;;;;AAOG;AACG,SAAU,6BAA6B,CAAC,IAAmB,EAAA;AAC/D,IAAA,QAAQ,IAAI,CAAC,QAAQ;QACnB,KAAK,YAAY,EAAE;AACjB,YAAA,OAAO,CAAC,CAAC,WAAW,EAAE,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC/D;QACA,KAAK,gBAAgB,EAAE;AACrB,YAAA,OAAO,CAAC,CAAC,eAAe,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,MAAM,CAAC,CAAC;QACvD;QACA,KAAK,sBAAsB,EAAE;AAC3B,YAAA,OAAO,2BAA2B,CAAC,IAAI,CAAC,KAAK,CAAC;QAChD;QACA,KAAK,cAAc,EAAE;AACnB,YAAA,OAAO,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC;QACzC;QACA,KAAK,aAAa,EAAE;AAClB,YAAA,OAAO,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC;QACxC;QACA,KAAK,gBAAgB,EAAE;AACrB,YAAA,OAAO,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC;QAC3C;QACA,SAAS;AACP,YAAA,OAAO,IAAI;QACb;;AAEJ;;;;"}
1
+ {"version":3,"file":"typography-dispatcher.mjs","sources":["../../../../../src/core/parser/typography-dispatcher.ts"],"sourcesContent":["import type { Declaration as LcDeclaration } from 'lightningcss'\nimport { lineHeightToEntries } from './typography'\nimport { firstConcreteFontFamily } from './tokens'\nimport type { RNEntry } from './types'\n\n/** RN-supported `textDecorationStyle` values (`wavy` has no RN equivalent). */\nconst RN_DECORATION_STYLES: ReadonlySet<string> = new Set(['solid', 'double', 'dotted', 'dashed'])\n\n/**\n * Build the RN `textDecorationLine` entry — string identity for the\n * single-line cases, joined-string for the array shape.\n * @param value Typed text-decoration-line.\n * @returns Single-entry list with `textDecorationLine`.\n */\nfunction textDecorationLineToEntries(value: LcDeclaration['value']): readonly RNEntry[] {\n if (value === 'none') return [['textDecorationLine', 'none']]\n if (typeof value === 'string') return [['textDecorationLine', value]]\n if (Array.isArray(value)) return [['textDecorationLine', value.join(' ')]]\n return []\n}\n\n/**\n * Build the RN `aspectRatio` entry from lightningcss's typed value.\n * Drops `auto` (no RN equivalent).\n * @param value Typed aspect-ratio value.\n * @param value.auto Whether the value resolved to `auto`.\n * @param value.ratio Numeric `[width, height]` ratio (or null/undefined).\n * @returns Single-entry list or empty.\n */\nfunction aspectRatioToEntries(value: { auto?: boolean; ratio?: readonly [number, number] | null }): readonly RNEntry[] {\n if (value.auto) return []\n if (!value.ratio) return []\n const [w, h] = value.ratio\n if (h === 0) return []\n return [['aspectRatio', w / h]]\n}\n\n/**\n * Build the RN `letterSpacing` entry. RN expects pixel numbers; rem\n * lengths are scaled to px (16-px base).\n * @param value Typed letter-spacing value.\n * @returns Single-entry list or empty.\n */\nfunction letterSpacingToEntries(value: LcDeclaration['value']): readonly RNEntry[] {\n if (typeof value !== 'object') return []\n const tagged = value as { type?: string; value?: { type?: string; value?: { unit?: string; value?: number } } }\n if (tagged.type === 'normal') return [['letterSpacing', 0]]\n const inner = tagged.value\n if (inner?.type !== 'value' || !inner.value) return []\n const { unit, value: px } = inner.value\n if (typeof px !== 'number') return []\n const resolved = unit === 'px' ? px : px * 16\n // Round off lightningcss f32 noise (`0.1em` → `1.600000023841858`).\n return [['letterSpacing', Math.round(resolved * 10_000) / 10_000]]\n}\n\n/**\n * Lower a CSS `text-align` keyword to one RN's `textAlign` accepts. RN\n * has no logical `start`/`end`, so map them to physical sides (LTR\n * default); every other keyword (left/right/center/justify/auto) is\n * already valid and passes through.\n * @param align CSS text-align keyword.\n * @returns RN-valid textAlign keyword.\n */\nfunction physicalTextAlign(align: string): string {\n if (align === 'start') return 'left'\n if (align === 'end') return 'right'\n return align\n}\n\n/**\n * Dispatch typography declarations rnwind cares about (text-align,\n * text-transform, text-decoration-line, line-height, letter-spacing,\n * aspect-ratio). Returns null when the property isn't one of these so\n * the caller can fall through to its main switch.\n * @param decl One lightningcss declaration.\n * @returns RN entries when the property matched, else `null`.\n */\nexport function dispatchTypographyDeclaration(decl: LcDeclaration): readonly RNEntry[] | null {\n switch (decl.property) {\n case 'text-align': {\n return [['textAlign', physicalTextAlign(String(decl.value))]]\n }\n case 'text-transform': {\n return [['textTransform', decl.value.case ?? 'none']]\n }\n case 'text-decoration-line': {\n return textDecorationLineToEntries(decl.value)\n }\n case 'text-decoration-style': {\n // RN <Text> supports textDecorationStyle (solid/double/dotted/dashed).\n const style = String(decl.value)\n return RN_DECORATION_STYLES.has(style) ? [['textDecorationStyle', style]] : []\n }\n case 'font-family': {\n // Typed `font-family` is a fallback LIST (`font-sans`, `font-mono`,\n // `font-[Inter]`). RN takes one concrete typeface; an all-generic\n // stack (default `font-sans`) emits nothing → system font. The themed\n // `var(--font-*)` path goes through `coerceFontFamily` in declaration.ts.\n const family = firstConcreteFontFamily(decl.value as readonly unknown[])\n return family === undefined ? [] : [['fontFamily', family]]\n }\n case 'aspect-ratio': {\n return aspectRatioToEntries(decl.value)\n }\n case 'line-height': {\n return lineHeightToEntries(decl.value)\n }\n case 'letter-spacing': {\n return letterSpacingToEntries(decl.value)\n }\n default: {\n return null\n }\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,11 +1,11 @@
1
1
  import type { Display, FontSize, FontWeight, LineHeight, ZIndex } from 'lightningcss';
2
2
  import type { RNEntry } from './types';
3
3
  /**
4
- * Expand lightningcss's `Display` typed value to an RN `{display}` entry.
5
- * - `keyword` variant (`none`, `flex`, `grid`, `inline`, …) passes through.
6
- * - `pair` variant (the modern CSS model `{inside: {type}, outside,
7
- * isListItem}`) collapses to RN's `'flex'` / `'grid'` when the inside
8
- * type matches, otherwise skips.
4
+ * Expand lightningcss's `Display` typed value to an RN `{display}` entry,
5
+ * keeping only the values RN supports (`none` / `flex` / `contents`).
6
+ * - `keyword` variant emits only when the keyword is RN-valid.
7
+ * - `pair` variant (the modern CSS model) collapses `flex` inside to
8
+ * `'flex'`; `flow` (`block`/`inline`) and `grid` have no RN analog → drop.
9
9
  * @param value Typed display value.
10
10
  * @returns RN entries (zero or one).
11
11
  */
@@ -1,29 +1,26 @@
1
1
  import { dimensionPercentageToNumber } from './length.mjs';
2
2
 
3
3
  /**
4
- * Expand lightningcss's `Display` typed value to an RN `{display}` entry.
5
- * - `keyword` variant (`none`, `flex`, `grid`, `inline`, …) passes through.
6
- * - `pair` variant (the modern CSS model `{inside: {type}, outside,
7
- * isListItem}`) collapses to RN's `'flex'` / `'grid'` when the inside
8
- * type matches, otherwise skips.
4
+ * Display values React Native's `display` style prop actually accepts.
5
+ * Everything else (`block`, `inline`, `inline-block`, `grid`, `table`, …)
6
+ * has no RN analog RN lays out as flex by default, and emitting an invalid
7
+ * value triggers a dev warning + silent drop. So we drop them outright.
8
+ */
9
+ const RN_DISPLAY_VALUES = new Set(['none', 'flex', 'contents']);
10
+ /**
11
+ * Expand lightningcss's `Display` typed value to an RN `{display}` entry,
12
+ * keeping only the values RN supports (`none` / `flex` / `contents`).
13
+ * - `keyword` variant emits only when the keyword is RN-valid.
14
+ * - `pair` variant (the modern CSS model) collapses `flex` inside to
15
+ * `'flex'`; `flow` (`block`/`inline`) and `grid` have no RN analog → drop.
9
16
  * @param value Typed display value.
10
17
  * @returns RN entries (zero or one).
11
18
  */
12
19
  function displayToEntries(value) {
13
20
  if (value.type === 'keyword')
14
- return [['display', value.value]];
15
- if (value.type === 'pair') {
16
- const inside = value.inside.type;
17
- // `flow` is the default inside mode — maps to `block` / `inline` /
18
- // `inline-block` based on the outer; RN only distinguishes `block`-ish
19
- // from `flex`, so collapse the `flow` family to the `outside` keyword.
20
- if (inside === 'flow')
21
- return [['display', value.outside]];
22
- if (inside === 'flex')
23
- return [['display', 'flex']];
24
- if (inside === 'grid')
25
- return [['display', 'grid']];
26
- }
21
+ return RN_DISPLAY_VALUES.has(value.value) ? [['display', value.value]] : [];
22
+ if (value.type === 'pair' && value.inside.type === 'flex')
23
+ return [['display', 'flex']];
27
24
  return [];
28
25
  }
29
26
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"typography.mjs","sources":["../../../../../src/core/parser/typography.ts"],"sourcesContent":["import type { Display, FontSize, FontWeight, LineHeight, ZIndex } from 'lightningcss'\nimport { dimensionPercentageToNumber } from './length'\nimport type { RNEntry } from './types'\n\n/**\n * Expand lightningcss's `Display` typed value to an RN `{display}` entry.\n * - `keyword` variant (`none`, `flex`, `grid`, `inline`, …) passes through.\n * - `pair` variant (the modern CSS model `{inside: {type}, outside,\n * isListItem}`) collapses to RN's `'flex'` / `'grid'` when the inside\n * type matches, otherwise skips.\n * @param value Typed display value.\n * @returns RN entries (zero or one).\n */\nexport function displayToEntries(value: Display): readonly RNEntry[] {\n if (value.type === 'keyword') return [['display', value.value]]\n if (value.type === 'pair') {\n const inside = value.inside.type\n // `flow` is the default inside mode — maps to `block` / `inline` /\n // `inline-block` based on the outer; RN only distinguishes `block`-ish\n // from `flex`, so collapse the `flow` family to the `outside` keyword.\n if (inside === 'flow') return [['display', value.outside]]\n if (inside === 'flex') return [['display', 'flex']]\n if (inside === 'grid') return [['display', 'grid']]\n }\n return []\n}\n\n/**\n * Convert `FontSize` to a pixel number. The `length` variant carries a\n * `DimensionPercentage`; `absolute` / `relative` keyword variants have no\n * RN numeric equivalent and get dropped.\n * @param value Typed font-size value.\n * @returns Pixel size, or `null` when not a pure length.\n */\nexport function fontSizeToPx(value: FontSize): number | null {\n if (value.type !== 'length') return null\n const length = dimensionPercentageToNumber(value.value)\n if (typeof length !== 'number') return null\n return length\n}\n\n/**\n * Convert `FontWeight` to the form RN accepts — numbers for absolute\n * weights, `'bolder'` / `'lighter'` keywords pass through as strings.\n * @param value Typed font-weight value.\n * @returns RN font weight.\n */\nexport function fontWeightToValue(value: FontWeight): number | string {\n if (value.type === 'absolute') {\n if (value.value.type === 'weight') return value.value.value\n return value.value.type\n }\n return value.type\n}\n\n/**\n * Coerce `z-index` to a number. `{type: 'auto'}` has no RN equivalent so\n * it collapses to 0.\n * @param value Typed z-index value.\n * @returns Integer z-index.\n */\nexport function zIndexToNumber(value: ZIndex): number {\n if (value.type === 'auto') return 0\n return value.value\n}\n\n/**\n * Convert lightningcss `LineHeight` into RN's `lineHeight` entry. RN\n * accepts a single number (pixel value). For unitless multipliers we\n * multiply by the default 16-px font size (RN's base); for px values we\n * pass through; percentages are approximated the same way.\n * @param value Typed line-height.\n * @returns RN entry.\n */\nexport function lineHeightToEntries(value: LineHeight): readonly RNEntry[] {\n if (value.type === 'normal') return [['lineHeight', 20]]\n if (value.type === 'number') return [['lineHeight', Math.round(value.value * 16 * 10_000) / 10_000]]\n if (value.type === 'length') {\n const px = dimensionPercentageToNumber(value.value)\n if (typeof px === 'number') return [['lineHeight', Math.round(px * 10_000) / 10_000]]\n }\n return []\n}\n"],"names":[],"mappings":";;AAIA;;;;;;;;AAQG;AACG,SAAU,gBAAgB,CAAC,KAAc,EAAA;AAC7C,IAAA,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,CAAC,CAAC,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;AAC/D,IAAA,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE;AACzB,QAAA,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI;;;;QAIhC,IAAI,MAAM,KAAK,MAAM;YAAE,OAAO,CAAC,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAC1D,IAAI,MAAM,KAAK,MAAM;AAAE,YAAA,OAAO,CAAC,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QACnD,IAAI,MAAM,KAAK,MAAM;AAAE,YAAA,OAAO,CAAC,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACrD;AACA,IAAA,OAAO,EAAE;AACX;AAEA;;;;;;AAMG;AACG,SAAU,YAAY,CAAC,KAAe,EAAA;AAC1C,IAAA,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;AAAE,QAAA,OAAO,IAAI;IACxC,MAAM,MAAM,GAAG,2BAA2B,CAAC,KAAK,CAAC,KAAK,CAAC;IACvD,IAAI,OAAO,MAAM,KAAK,QAAQ;AAAE,QAAA,OAAO,IAAI;AAC3C,IAAA,OAAO,MAAM;AACf;AAEA;;;;;AAKG;AACG,SAAU,iBAAiB,CAAC,KAAiB,EAAA;AACjD,IAAA,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE;AAC7B,QAAA,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ;AAAE,YAAA,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK;AAC3D,QAAA,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI;IACzB;IACA,OAAO,KAAK,CAAC,IAAI;AACnB;AAEA;;;;;AAKG;AACG,SAAU,cAAc,CAAC,KAAa,EAAA;AAC1C,IAAA,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM;AAAE,QAAA,OAAO,CAAC;IACnC,OAAO,KAAK,CAAC,KAAK;AACpB;AAEA;;;;;;;AAOG;AACG,SAAU,mBAAmB,CAAC,KAAiB,EAAA;AACnD,IAAA,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;AAAE,QAAA,OAAO,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;AACxD,IAAA,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,GAAG,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC;AACpG,IAAA,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE;QAC3B,MAAM,EAAE,GAAG,2BAA2B,CAAC,KAAK,CAAC,KAAK,CAAC;QACnD,IAAI,OAAO,EAAE,KAAK,QAAQ;AAAE,YAAA,OAAO,CAAC,CAAC,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC;IACvF;AACA,IAAA,OAAO,EAAE;AACX;;;;"}
1
+ {"version":3,"file":"typography.mjs","sources":["../../../../../src/core/parser/typography.ts"],"sourcesContent":["import type { Display, FontSize, FontWeight, LineHeight, ZIndex } from 'lightningcss'\nimport { dimensionPercentageToNumber } from './length'\nimport type { RNEntry } from './types'\n\n/**\n * Display values React Native's `display` style prop actually accepts.\n * Everything else (`block`, `inline`, `inline-block`, `grid`, `table`, …)\n * has no RN analog — RN lays out as flex by default, and emitting an invalid\n * value triggers a dev warning + silent drop. So we drop them outright.\n */\nconst RN_DISPLAY_VALUES: ReadonlySet<string> = new Set(['none', 'flex', 'contents'])\n\n/**\n * Expand lightningcss's `Display` typed value to an RN `{display}` entry,\n * keeping only the values RN supports (`none` / `flex` / `contents`).\n * - `keyword` variant emits only when the keyword is RN-valid.\n * - `pair` variant (the modern CSS model) collapses `flex` inside to\n * `'flex'`; `flow` (`block`/`inline`) and `grid` have no RN analog drop.\n * @param value Typed display value.\n * @returns RN entries (zero or one).\n */\nexport function displayToEntries(value: Display): readonly RNEntry[] {\n if (value.type === 'keyword') return RN_DISPLAY_VALUES.has(value.value) ? [['display', value.value]] : []\n if (value.type === 'pair' && value.inside.type === 'flex') return [['display', 'flex']]\n return []\n}\n\n/**\n * Convert `FontSize` to a pixel number. The `length` variant carries a\n * `DimensionPercentage`; `absolute` / `relative` keyword variants have no\n * RN numeric equivalent and get dropped.\n * @param value Typed font-size value.\n * @returns Pixel size, or `null` when not a pure length.\n */\nexport function fontSizeToPx(value: FontSize): number | null {\n if (value.type !== 'length') return null\n const length = dimensionPercentageToNumber(value.value)\n if (typeof length !== 'number') return null\n return length\n}\n\n/**\n * Convert `FontWeight` to the form RN accepts — numbers for absolute\n * weights, `'bolder'` / `'lighter'` keywords pass through as strings.\n * @param value Typed font-weight value.\n * @returns RN font weight.\n */\nexport function fontWeightToValue(value: FontWeight): number | string {\n if (value.type === 'absolute') {\n if (value.value.type === 'weight') return value.value.value\n return value.value.type\n }\n return value.type\n}\n\n/**\n * Coerce `z-index` to a number. `{type: 'auto'}` has no RN equivalent so\n * it collapses to 0.\n * @param value Typed z-index value.\n * @returns Integer z-index.\n */\nexport function zIndexToNumber(value: ZIndex): number {\n if (value.type === 'auto') return 0\n return value.value\n}\n\n/**\n * Convert lightningcss `LineHeight` into RN's `lineHeight` entry. RN\n * accepts a single number (pixel value). For unitless multipliers we\n * multiply by the default 16-px font size (RN's base); for px values we\n * pass through; percentages are approximated the same way.\n * @param value Typed line-height.\n * @returns RN entry.\n */\nexport function lineHeightToEntries(value: LineHeight): readonly RNEntry[] {\n if (value.type === 'normal') return [['lineHeight', 20]]\n if (value.type === 'number') return [['lineHeight', Math.round(value.value * 16 * 10_000) / 10_000]]\n if (value.type === 'length') {\n const px = dimensionPercentageToNumber(value.value)\n if (typeof px === 'number') return [['lineHeight', Math.round(px * 10_000) / 10_000]]\n }\n return []\n}\n"],"names":[],"mappings":";;AAIA;;;;;AAKG;AACH,MAAM,iBAAiB,GAAwB,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;AAEpF;;;;;;;;AAQG;AACG,SAAU,gBAAgB,CAAC,KAAc,EAAA;AAC7C,IAAA,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE;AACzG,IAAA,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM;AAAE,QAAA,OAAO,CAAC,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;AACvF,IAAA,OAAO,EAAE;AACX;AAEA;;;;;;AAMG;AACG,SAAU,YAAY,CAAC,KAAe,EAAA;AAC1C,IAAA,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;AAAE,QAAA,OAAO,IAAI;IACxC,MAAM,MAAM,GAAG,2BAA2B,CAAC,KAAK,CAAC,KAAK,CAAC;IACvD,IAAI,OAAO,MAAM,KAAK,QAAQ;AAAE,QAAA,OAAO,IAAI;AAC3C,IAAA,OAAO,MAAM;AACf;AAEA;;;;;AAKG;AACG,SAAU,iBAAiB,CAAC,KAAiB,EAAA;AACjD,IAAA,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE;AAC7B,QAAA,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ;AAAE,YAAA,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK;AAC3D,QAAA,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI;IACzB;IACA,OAAO,KAAK,CAAC,IAAI;AACnB;AAEA;;;;;AAKG;AACG,SAAU,cAAc,CAAC,KAAa,EAAA;AAC1C,IAAA,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM;AAAE,QAAA,OAAO,CAAC;IACnC,OAAO,KAAK,CAAC,KAAK;AACpB;AAEA;;;;;;;AAOG;AACG,SAAU,mBAAmB,CAAC,KAAiB,EAAA;AACnD,IAAA,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;AAAE,QAAA,OAAO,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;AACxD,IAAA,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,GAAG,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC;AACpG,IAAA,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE;QAC3B,MAAM,EAAE,GAAG,2BAA2B,CAAC,KAAK,CAAC,KAAK,CAAC;QACnD,IAAI,OAAO,EAAE,KAAK,QAAQ;AAAE,YAAA,OAAO,CAAC,CAAC,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC;IACvF;AACA,IAAA,OAAO,EAAE;AACX;;;;"}
@@ -65,14 +65,6 @@ declare class UnionBuilder {
65
65
  constructor(cacheDir: string, parser: TailwindParser);
66
66
  /** Absolute path of the manifest module (`rnwind/__generated/schemes`). */
67
67
  get manifestPath(): string;
68
- /**
69
- * Snapshot of every source file the builder has recorded atoms for
70
- * this worker session. Used by `withRnwindConfig`'s CSS watcher to
71
- * touch `mtime` on each and nudge Metro into re-transforming them
72
- * with the new theme values.
73
- * @returns Absolute source paths.
74
- */
75
- recordedFiles(): readonly string[];
76
68
  /** Cumulative cache-miss count — exposed for tests to assert cache behaviour. */
77
69
  get serializedMisses(): number;
78
70
  /**
@@ -140,16 +140,6 @@ class UnionBuilder {
140
140
  get manifestPath() {
141
141
  return path.join(this.cacheDir, MANIFEST_BASENAME);
142
142
  }
143
- /**
144
- * Snapshot of every source file the builder has recorded atoms for
145
- * this worker session. Used by `withRnwindConfig`'s CSS watcher to
146
- * touch `mtime` on each and nudge Metro into re-transforming them
147
- * with the new theme values.
148
- * @returns Absolute source paths.
149
- */
150
- recordedFiles() {
151
- return [...this.fileAtomSets.keys()];
152
- }
153
143
  /** Cumulative cache-miss count — exposed for tests to assert cache behaviour. */
154
144
  get serializedMisses() {
155
145
  return this.serializedMissesCount;
@@ -1 +1 @@
1
- {"version":3,"file":"union-builder.mjs","sources":["../../../../../src/core/style-builder/union-builder.ts"],"sourcesContent":["import { createHash, randomBytes } from 'node:crypto'\nimport { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'\nimport path from 'node:path'\nimport type { GradientAtomInfo, HapticRequest, KeyframeBlock, SchemedStyle, TailwindParser } from '../parser'\nimport { buildSchemeSources, type AtomSerializedCache } from './build-style'\n\n/** Manifest module basename — the file SchemeProvider imports via the resolver. */\nconst MANIFEST_BASENAME = 'schemes.js'\n\n/**\n * Atomic file write — stage to a `.tmp.<pid>.<nonce>` sibling, then\n * `rename()` into place. Skips the write entirely when the existing\n * content matches.\n * @param target Final destination path.\n * @param content Bytes to write.\n * @returns Whether the file was actually rewritten.\n */\nfunction writeIfChanged(target: string, content: string): boolean {\n if (existsSync(target)) {\n try {\n if (readFileSync(target, 'utf8') === content) return false\n } catch {\n // Unreadable — fall through to rewrite.\n }\n }\n mkdirSync(path.dirname(target), { recursive: true })\n const temporary = `${target}.${process.pid}.${randomBytes(4).toString('hex')}.tmp`\n try {\n writeFileSync(temporary, content, 'utf8')\n renameSync(temporary, target)\n return true\n } catch (error) {\n rmSync(temporary, { force: true })\n throw error\n }\n}\n\n/**\n * SHA-256 prefix of a string — cheap signature used to detect whether a\n * per-scheme file's source has changed since the last write.\n * @param text Input text.\n * @returns 16-char hex digest.\n */\nfunction signatureOf(text: string): string {\n return createHash('sha256').update(text).digest('hex').slice(0, 16)\n}\n\n/**\n * Compare two `Set<string>`s for equality — same size + every element\n * of `a` present in `b`.\n * @param a First set.\n * @param b Second set.\n * @returns Whether the two sets contain identical values.\n */\nfunction setsEqual(a: ReadonlySet<string>, b: ReadonlySet<string>): boolean {\n if (a.size !== b.size) return false\n for (const v of a) if (!b.has(v)) return false\n return true\n}\n\n/**\n * Compute the absolute path of a per-scheme style file under the cache dir.\n * @param cacheDir Absolute cache directory.\n * @param scheme Registry key (`'common'` or a variant name).\n * @returns Absolute path, e.g. `<cacheDir>/dark.style.js`.\n */\nfunction schemeFilePath(cacheDir: string, scheme: string): string {\n return path.join(cacheDir, `${scheme}.style.js`)\n}\n\n/**\n * In-memory atom union + per-scheme style-file emitter.\n *\n * Correctness under multi-worker Metro relies on `ensureProjectScanned`:\n * the FIRST `recordFile` / `writeSchemes` call in every worker drives\n * the oxide Scanner across ALL project sources and hydrates the union\n * with the complete set of candidates. Subsequent per-file\n * `recordFile` calls only layer in atoms the scan already knew about,\n * so writes are idempotent — different workers can't clobber each\n * other's scheme files with partial views.\n *\n * Per-file deltas (atom set unchanged → early return) skip\n * serialization entirely. On a theme-CSS change, `getRnwindState`\n * builds a fresh parser + builder; the next call re-runs\n * `ensureProjectScanned` against the new parser, producing scheme\n * files with the new theme values.\n */\nclass UnionBuilder {\n private readonly cacheDir: string\n private readonly parser: TailwindParser\n private readonly unionAtoms = new Map<string, SchemedStyle>()\n private readonly unionKeyframes = new Map<string, KeyframeBlock>()\n /** atom name → gradient role/colour, surfaced into the manifest's `registerGradients`. */\n private readonly unionGradients = new Map<string, GradientAtomInfo>()\n /** atom name → haptic request, surfaced into the manifest's `registerHaptics`. */\n private readonly unionHaptics = new Map<string, HapticRequest>()\n /**\n * Distinct literal className strings seen across all files, pre-merged\n * into per-scheme molecules at write time. Accumulate-only (like\n * `unionAtoms`): orphaned literals just yield unused molecules and get\n * reaped on the next cold start, so no refcount is needed.\n */\n private readonly unionLiterals = new Set<string>()\n /**\n * Responsive breakpoints captured from the parser. Refreshed on every\n * `recordFile` / `ensureProjectScanned` so user-defined\n * `--breakpoint-*` overrides land in the manifest the next time it's\n * written. Identical for every parser call within one parser instance\n * (theme is fixed for the parser's lifetime), so storing the latest\n * snapshot is sufficient.\n */\n private breakpoints: ReadonlyMap<string, number> = new Map()\n /** file → set of atom names this file currently contributes. */\n private readonly fileAtomSets = new Map<string, Set<string>>()\n /** atom name → how many files currently contribute it (refcount). */\n private readonly atomRefCount = new Map<string, number>()\n /** scheme → last-written source SHA. Skips re-writing unchanged schemes. */\n private readonly schemeSignatures = new Map<string, string>()\n /**\n * Per-atom serialized-value cache — identity-keyed on each atom's\n * SchemedStyle reference. Carried across `writeSchemes` calls so the\n * typical \"user added one className\" FR case re-stringifies ONE atom\n * instead of all 175+. Cleared on `ensureProjectScanned` (full\n * rescan replaces every reference) and individually invalidated for\n * any atom `applyDiff` mutates.\n */\n private readonly serializedCache: AtomSerializedCache = new Map()\n /** Running count of stringify passes (cache misses). Test telemetry. */\n private serializedMissesCount = 0\n /** Set after `ensureProjectScanned` completes. */\n private projectScanned = false\n /** Promise guard so concurrent first-calls await ONE scan. */\n private pendingScan: Promise<void> | null = null\n\n constructor(cacheDir: string, parser: TailwindParser) {\n this.cacheDir = cacheDir\n this.parser = parser\n mkdirSync(this.cacheDir, { recursive: true })\n }\n\n /** Absolute path of the manifest module (`rnwind/__generated/schemes`). */\n public get manifestPath(): string {\n return path.join(this.cacheDir, MANIFEST_BASENAME)\n }\n\n /**\n * Snapshot of every source file the builder has recorded atoms for\n * this worker session. Used by `withRnwindConfig`'s CSS watcher to\n * touch `mtime` on each and nudge Metro into re-transforming them\n * with the new theme values.\n * @returns Absolute source paths.\n */\n public recordedFiles(): readonly string[] {\n return [...this.fileAtomSets.keys()]\n }\n\n /** Cumulative cache-miss count — exposed for tests to assert cache behaviour. */\n public get serializedMisses(): number {\n return this.serializedMissesCount\n }\n\n /**\n * Absolute path of one scheme's style file.\n * @param scheme Registry key.\n * @returns Absolute path.\n */\n public schemePath(scheme: string): string {\n return schemeFilePath(this.cacheDir, scheme)\n }\n\n /**\n * One-shot oxide scan + compile across every source the parser was\n * configured with. Idempotent — safe to call from any entry point.\n * Concurrent callers share the same in-flight promise.\n */\n public async ensureProjectScanned(): Promise<void> {\n if (this.projectScanned) return\n if (this.pendingScan) return this.pendingScan\n this.pendingScan = (async () => {\n const parsed = await this.parser.parseProject()\n for (const [name, style] of parsed.atoms) this.unionAtoms.set(name, style)\n for (const [name, kf] of parsed.keyframes) this.unionKeyframes.set(name, kf)\n for (const [name, gradient] of parsed.gradientAtoms) this.unionGradients.set(name, gradient)\n for (const [name, haptic] of parsed.hapticAtoms) this.unionHaptics.set(name, haptic)\n this.breakpoints = parsed.breakpoints\n this.projectScanned = true\n })()\n try {\n await this.pendingScan\n } finally {\n this.pendingScan = null\n }\n }\n\n /**\n * Record one source file's resolved atoms + keyframes. Short-circuits\n * when the file's atom name set hasn't changed — the common case on\n * every Fast Refresh save of a file whose className literals are\n * unchanged.\n * @param file Absolute source file path.\n * @param atoms Per-atom resolved schemed styles from this transform.\n * @param keyframes Keyframe blocks referenced by this file's atoms.\n * @param literals\n * @returns `{ changed: true }` when the union shifted (new atom name,\n * removed atom name, or new keyframe) — the transformer uses this\n * to skip the serializer + `writeSchemes` when nothing changed.\n */\n public async recordFile(\n file: string,\n atoms: ReadonlyMap<string, SchemedStyle>,\n keyframes: ReadonlyMap<string, KeyframeBlock>,\n literals: readonly string[] = [],\n ): Promise<{ changed: boolean }> {\n await this.ensureProjectScanned()\n const literalAdded = this.recordLiterals(literals)\n const newAtomNames = new Set(atoms.keys())\n const previous = this.fileAtomSets.get(file)\n if (previous && setsEqual(previous, newAtomNames)) {\n // Atom set unchanged — skip the unionAtoms update entirely. The\n // project scan already populated them, and re-setting a fresh\n // object ref here would invalidate the per-atom serialization\n // cache on every FR save for no gain (values are identical).\n // Theme edits go through `getRnwindState` → new builder → fresh\n // scan, so stale cache is impossible.\n let keyframeAdded = false\n for (const [name, kf] of keyframes) {\n if (!this.unionKeyframes.has(name)) keyframeAdded = true\n this.unionKeyframes.set(name, kf)\n }\n return { changed: keyframeAdded || literalAdded }\n }\n this.applyDiff(file, newAtomNames, atoms, keyframes)\n return { changed: true }\n }\n\n /**\n * Merge a file's literal classNames into the union. A literal the\n * union hasn't seen flips `changed` so `writeSchemes` re-emits the\n * scheme files with the new molecule.\n * @param literals Distinct literal className strings.\n * @returns Whether any literal was new to the union.\n */\n private recordLiterals(literals: readonly string[]): boolean {\n let added = false\n for (const literal of literals) {\n if (this.unionLiterals.has(literal)) continue\n this.unionLiterals.add(literal)\n added = true\n }\n return added\n }\n\n /**\n * Forget one source file's contribution. Idempotent — repeated calls\n * for a file that's already dropped are no-ops. Does NOT remove the\n * atom from the union when another file (or the project scan) still\n * references it.\n * @param file Absolute source file path.\n */\n public dropFile(file: string): void {\n const previous = this.fileAtomSets.get(file)\n if (!previous) return\n for (const name of previous) {\n const count = (this.atomRefCount.get(name) ?? 0) - 1\n if (count <= 0) this.atomRefCount.delete(name)\n else this.atomRefCount.set(name, count)\n }\n this.fileAtomSets.delete(file)\n }\n\n /**\n * Serialize the union into per-scheme files + manifest, writing only\n * files whose source bytes changed. Called after every `recordFile`\n * from the transformer — and once at Metro startup via\n * `ensureFilesExist` to seed disk from the project scan alone.\n * @returns List of scheme keys whose files were rewritten (empty\n * when the union is byte-identical to the last flush).\n */\n public async writeSchemes(): Promise<{ changedSchemes: readonly string[] }> {\n await this.ensureProjectScanned()\n const sortedAtomNames = [...this.unionAtoms.keys()].toSorted((a, b) => a.localeCompare(b))\n const result = buildSchemeSources(sortedAtomNames, this.unionAtoms, this.unionKeyframes, this.serializedCache, this.breakpoints, this.unionGradients, this.unionHaptics, [...this.unionLiterals])\n this.serializedMissesCount += result.serializedMisses\n const { schemeSources, manifestSource } = result\n\n const changed: string[] = []\n for (const [scheme, source] of Object.entries(schemeSources)) {\n const signature = signatureOf(source)\n const target = schemeFilePath(this.cacheDir, scheme)\n if (this.schemeSignatures.get(scheme) === signature && existsSync(target)) continue\n if (writeIfChanged(target, source)) changed.push(scheme)\n this.schemeSignatures.set(scheme, signature)\n }\n\n const manifestSignature = signatureOf(manifestSource)\n const manifestTarget = path.join(this.cacheDir, MANIFEST_BASENAME)\n if (this.schemeSignatures.get('__manifest') !== manifestSignature || !existsSync(manifestTarget)) {\n if (writeIfChanged(manifestTarget, manifestSource)) changed.push('__manifest')\n this.schemeSignatures.set('__manifest', manifestSignature)\n }\n\n return { changedSchemes: changed }\n }\n\n /**\n * Ensure the manifest + common scheme files exist on disk so Metro's\n * resolver can SHA1 them at boot before the first transform runs.\n */\n public async ensureFilesExist(): Promise<void> {\n if (existsSync(this.manifestPath) && existsSync(schemeFilePath(this.cacheDir, 'common'))) {\n // Still trigger the scan so the in-memory union is complete; file\n // bytes may already be authoritative from a prior Metro run.\n await this.ensureProjectScanned()\n return\n }\n await this.writeSchemes()\n }\n\n /**\n * Apply one file's atom-name diff to the in-memory refcount + union.\n * @param file Source file path.\n * @param newAtoms New atom-name set for the file.\n * @param resolvedAtoms Fresh parser output — carries the resolved\n * styles for every entry in `newAtoms`.\n * @param newKeyframes Keyframes this file's atoms reference.\n */\n private applyDiff(\n file: string,\n newAtoms: ReadonlySet<string>,\n resolvedAtoms: ReadonlyMap<string, SchemedStyle>,\n newKeyframes: ReadonlyMap<string, KeyframeBlock>,\n ): void {\n const previous = this.fileAtomSets.get(file) ?? new Set<string>()\n for (const name of previous) {\n if (newAtoms.has(name)) continue\n const count = (this.atomRefCount.get(name) ?? 0) - 1\n if (count <= 0) this.atomRefCount.delete(name)\n else this.atomRefCount.set(name, count)\n // Do NOT remove `name` from `unionAtoms` — the project scan still\n // references it (orphaned atoms get reaped on the next Metro\n // cold start when the scanner re-walks disk).\n }\n for (const name of newAtoms) {\n if (!previous.has(name)) this.atomRefCount.set(name, (this.atomRefCount.get(name) ?? 0) + 1)\n // Only install the resolved style when the atom is new to the\n // union. Replacing an existing entry with a fresh parser-\n // produced object would swap the identity guard the per-atom\n // serialization cache uses and force a re-stringify for every\n // atom on every FR save. CSS edits rebuild the whole builder\n // (via `getRnwindState`) so stale values aren't possible.\n if (!this.unionAtoms.has(name)) {\n const style = resolvedAtoms.get(name)\n if (style) this.unionAtoms.set(name, style)\n }\n }\n this.fileAtomSets.set(file, new Set(newAtoms))\n for (const [name, kf] of newKeyframes) this.unionKeyframes.set(name, kf)\n }\n}\n\nexport { UnionBuilder }\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"union-builder.mjs","sources":["../../../../../src/core/style-builder/union-builder.ts"],"sourcesContent":["import { createHash, randomBytes } from 'node:crypto'\nimport { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'\nimport path from 'node:path'\nimport type { GradientAtomInfo, HapticRequest, KeyframeBlock, SchemedStyle, TailwindParser } from '../parser'\nimport { buildSchemeSources, type AtomSerializedCache } from './build-style'\n\n/** Manifest module basename — the file SchemeProvider imports via the resolver. */\nconst MANIFEST_BASENAME = 'schemes.js'\n\n/**\n * Atomic file write — stage to a `.tmp.<pid>.<nonce>` sibling, then\n * `rename()` into place. Skips the write entirely when the existing\n * content matches.\n * @param target Final destination path.\n * @param content Bytes to write.\n * @returns Whether the file was actually rewritten.\n */\nfunction writeIfChanged(target: string, content: string): boolean {\n if (existsSync(target)) {\n try {\n if (readFileSync(target, 'utf8') === content) return false\n } catch {\n // Unreadable — fall through to rewrite.\n }\n }\n mkdirSync(path.dirname(target), { recursive: true })\n const temporary = `${target}.${process.pid}.${randomBytes(4).toString('hex')}.tmp`\n try {\n writeFileSync(temporary, content, 'utf8')\n renameSync(temporary, target)\n return true\n } catch (error) {\n rmSync(temporary, { force: true })\n throw error\n }\n}\n\n/**\n * SHA-256 prefix of a string — cheap signature used to detect whether a\n * per-scheme file's source has changed since the last write.\n * @param text Input text.\n * @returns 16-char hex digest.\n */\nfunction signatureOf(text: string): string {\n return createHash('sha256').update(text).digest('hex').slice(0, 16)\n}\n\n/**\n * Compare two `Set<string>`s for equality — same size + every element\n * of `a` present in `b`.\n * @param a First set.\n * @param b Second set.\n * @returns Whether the two sets contain identical values.\n */\nfunction setsEqual(a: ReadonlySet<string>, b: ReadonlySet<string>): boolean {\n if (a.size !== b.size) return false\n for (const v of a) if (!b.has(v)) return false\n return true\n}\n\n/**\n * Compute the absolute path of a per-scheme style file under the cache dir.\n * @param cacheDir Absolute cache directory.\n * @param scheme Registry key (`'common'` or a variant name).\n * @returns Absolute path, e.g. `<cacheDir>/dark.style.js`.\n */\nfunction schemeFilePath(cacheDir: string, scheme: string): string {\n return path.join(cacheDir, `${scheme}.style.js`)\n}\n\n/**\n * In-memory atom union + per-scheme style-file emitter.\n *\n * Correctness under multi-worker Metro relies on `ensureProjectScanned`:\n * the FIRST `recordFile` / `writeSchemes` call in every worker drives\n * the oxide Scanner across ALL project sources and hydrates the union\n * with the complete set of candidates. Subsequent per-file\n * `recordFile` calls only layer in atoms the scan already knew about,\n * so writes are idempotent — different workers can't clobber each\n * other's scheme files with partial views.\n *\n * Per-file deltas (atom set unchanged → early return) skip\n * serialization entirely. On a theme-CSS change, `getRnwindState`\n * builds a fresh parser + builder; the next call re-runs\n * `ensureProjectScanned` against the new parser, producing scheme\n * files with the new theme values.\n */\nclass UnionBuilder {\n private readonly cacheDir: string\n private readonly parser: TailwindParser\n private readonly unionAtoms = new Map<string, SchemedStyle>()\n private readonly unionKeyframes = new Map<string, KeyframeBlock>()\n /** atom name → gradient role/colour, surfaced into the manifest's `registerGradients`. */\n private readonly unionGradients = new Map<string, GradientAtomInfo>()\n /** atom name → haptic request, surfaced into the manifest's `registerHaptics`. */\n private readonly unionHaptics = new Map<string, HapticRequest>()\n /**\n * Distinct literal className strings seen across all files, pre-merged\n * into per-scheme molecules at write time. Accumulate-only (like\n * `unionAtoms`): orphaned literals just yield unused molecules and get\n * reaped on the next cold start, so no refcount is needed.\n */\n private readonly unionLiterals = new Set<string>()\n /**\n * Responsive breakpoints captured from the parser. Refreshed on every\n * `recordFile` / `ensureProjectScanned` so user-defined\n * `--breakpoint-*` overrides land in the manifest the next time it's\n * written. Identical for every parser call within one parser instance\n * (theme is fixed for the parser's lifetime), so storing the latest\n * snapshot is sufficient.\n */\n private breakpoints: ReadonlyMap<string, number> = new Map()\n /** file → set of atom names this file currently contributes. */\n private readonly fileAtomSets = new Map<string, Set<string>>()\n /** atom name → how many files currently contribute it (refcount). */\n private readonly atomRefCount = new Map<string, number>()\n /** scheme → last-written source SHA. Skips re-writing unchanged schemes. */\n private readonly schemeSignatures = new Map<string, string>()\n /**\n * Per-atom serialized-value cache — identity-keyed on each atom's\n * SchemedStyle reference. Carried across `writeSchemes` calls so the\n * typical \"user added one className\" FR case re-stringifies ONE atom\n * instead of all 175+. Cleared on `ensureProjectScanned` (full\n * rescan replaces every reference) and individually invalidated for\n * any atom `applyDiff` mutates.\n */\n private readonly serializedCache: AtomSerializedCache = new Map()\n /** Running count of stringify passes (cache misses). Test telemetry. */\n private serializedMissesCount = 0\n /** Set after `ensureProjectScanned` completes. */\n private projectScanned = false\n /** Promise guard so concurrent first-calls await ONE scan. */\n private pendingScan: Promise<void> | null = null\n\n constructor(cacheDir: string, parser: TailwindParser) {\n this.cacheDir = cacheDir\n this.parser = parser\n mkdirSync(this.cacheDir, { recursive: true })\n }\n\n /** Absolute path of the manifest module (`rnwind/__generated/schemes`). */\n public get manifestPath(): string {\n return path.join(this.cacheDir, MANIFEST_BASENAME)\n }\n\n /** Cumulative cache-miss count — exposed for tests to assert cache behaviour. */\n public get serializedMisses(): number {\n return this.serializedMissesCount\n }\n\n /**\n * Absolute path of one scheme's style file.\n * @param scheme Registry key.\n * @returns Absolute path.\n */\n public schemePath(scheme: string): string {\n return schemeFilePath(this.cacheDir, scheme)\n }\n\n /**\n * One-shot oxide scan + compile across every source the parser was\n * configured with. Idempotent — safe to call from any entry point.\n * Concurrent callers share the same in-flight promise.\n */\n public async ensureProjectScanned(): Promise<void> {\n if (this.projectScanned) return\n if (this.pendingScan) return this.pendingScan\n this.pendingScan = (async () => {\n const parsed = await this.parser.parseProject()\n for (const [name, style] of parsed.atoms) this.unionAtoms.set(name, style)\n for (const [name, kf] of parsed.keyframes) this.unionKeyframes.set(name, kf)\n for (const [name, gradient] of parsed.gradientAtoms) this.unionGradients.set(name, gradient)\n for (const [name, haptic] of parsed.hapticAtoms) this.unionHaptics.set(name, haptic)\n this.breakpoints = parsed.breakpoints\n this.projectScanned = true\n })()\n try {\n await this.pendingScan\n } finally {\n this.pendingScan = null\n }\n }\n\n /**\n * Record one source file's resolved atoms + keyframes. Short-circuits\n * when the file's atom name set hasn't changed — the common case on\n * every Fast Refresh save of a file whose className literals are\n * unchanged.\n * @param file Absolute source file path.\n * @param atoms Per-atom resolved schemed styles from this transform.\n * @param keyframes Keyframe blocks referenced by this file's atoms.\n * @param literals\n * @returns `{ changed: true }` when the union shifted (new atom name,\n * removed atom name, or new keyframe) — the transformer uses this\n * to skip the serializer + `writeSchemes` when nothing changed.\n */\n public async recordFile(\n file: string,\n atoms: ReadonlyMap<string, SchemedStyle>,\n keyframes: ReadonlyMap<string, KeyframeBlock>,\n literals: readonly string[] = [],\n ): Promise<{ changed: boolean }> {\n await this.ensureProjectScanned()\n const literalAdded = this.recordLiterals(literals)\n const newAtomNames = new Set(atoms.keys())\n const previous = this.fileAtomSets.get(file)\n if (previous && setsEqual(previous, newAtomNames)) {\n // Atom set unchanged — skip the unionAtoms update entirely. The\n // project scan already populated them, and re-setting a fresh\n // object ref here would invalidate the per-atom serialization\n // cache on every FR save for no gain (values are identical).\n // Theme edits go through `getRnwindState` → new builder → fresh\n // scan, so stale cache is impossible.\n let keyframeAdded = false\n for (const [name, kf] of keyframes) {\n if (!this.unionKeyframes.has(name)) keyframeAdded = true\n this.unionKeyframes.set(name, kf)\n }\n return { changed: keyframeAdded || literalAdded }\n }\n this.applyDiff(file, newAtomNames, atoms, keyframes)\n return { changed: true }\n }\n\n /**\n * Merge a file's literal classNames into the union. A literal the\n * union hasn't seen flips `changed` so `writeSchemes` re-emits the\n * scheme files with the new molecule.\n * @param literals Distinct literal className strings.\n * @returns Whether any literal was new to the union.\n */\n private recordLiterals(literals: readonly string[]): boolean {\n let added = false\n for (const literal of literals) {\n if (this.unionLiterals.has(literal)) continue\n this.unionLiterals.add(literal)\n added = true\n }\n return added\n }\n\n /**\n * Forget one source file's contribution. Idempotent — repeated calls\n * for a file that's already dropped are no-ops. Does NOT remove the\n * atom from the union when another file (or the project scan) still\n * references it.\n * @param file Absolute source file path.\n */\n public dropFile(file: string): void {\n const previous = this.fileAtomSets.get(file)\n if (!previous) return\n for (const name of previous) {\n const count = (this.atomRefCount.get(name) ?? 0) - 1\n if (count <= 0) this.atomRefCount.delete(name)\n else this.atomRefCount.set(name, count)\n }\n this.fileAtomSets.delete(file)\n }\n\n /**\n * Serialize the union into per-scheme files + manifest, writing only\n * files whose source bytes changed. Called after every `recordFile`\n * from the transformer — and once at Metro startup via\n * `ensureFilesExist` to seed disk from the project scan alone.\n * @returns List of scheme keys whose files were rewritten (empty\n * when the union is byte-identical to the last flush).\n */\n public async writeSchemes(): Promise<{ changedSchemes: readonly string[] }> {\n await this.ensureProjectScanned()\n const sortedAtomNames = [...this.unionAtoms.keys()].toSorted((a, b) => a.localeCompare(b))\n const result = buildSchemeSources(sortedAtomNames, this.unionAtoms, this.unionKeyframes, this.serializedCache, this.breakpoints, this.unionGradients, this.unionHaptics, [...this.unionLiterals])\n this.serializedMissesCount += result.serializedMisses\n const { schemeSources, manifestSource } = result\n\n const changed: string[] = []\n for (const [scheme, source] of Object.entries(schemeSources)) {\n const signature = signatureOf(source)\n const target = schemeFilePath(this.cacheDir, scheme)\n if (this.schemeSignatures.get(scheme) === signature && existsSync(target)) continue\n if (writeIfChanged(target, source)) changed.push(scheme)\n this.schemeSignatures.set(scheme, signature)\n }\n\n const manifestSignature = signatureOf(manifestSource)\n const manifestTarget = path.join(this.cacheDir, MANIFEST_BASENAME)\n if (this.schemeSignatures.get('__manifest') !== manifestSignature || !existsSync(manifestTarget)) {\n if (writeIfChanged(manifestTarget, manifestSource)) changed.push('__manifest')\n this.schemeSignatures.set('__manifest', manifestSignature)\n }\n\n return { changedSchemes: changed }\n }\n\n /**\n * Ensure the manifest + common scheme files exist on disk so Metro's\n * resolver can SHA1 them at boot before the first transform runs.\n */\n public async ensureFilesExist(): Promise<void> {\n if (existsSync(this.manifestPath) && existsSync(schemeFilePath(this.cacheDir, 'common'))) {\n // Still trigger the scan so the in-memory union is complete; file\n // bytes may already be authoritative from a prior Metro run.\n await this.ensureProjectScanned()\n return\n }\n await this.writeSchemes()\n }\n\n /**\n * Apply one file's atom-name diff to the in-memory refcount + union.\n * @param file Source file path.\n * @param newAtoms New atom-name set for the file.\n * @param resolvedAtoms Fresh parser output — carries the resolved\n * styles for every entry in `newAtoms`.\n * @param newKeyframes Keyframes this file's atoms reference.\n */\n private applyDiff(\n file: string,\n newAtoms: ReadonlySet<string>,\n resolvedAtoms: ReadonlyMap<string, SchemedStyle>,\n newKeyframes: ReadonlyMap<string, KeyframeBlock>,\n ): void {\n const previous = this.fileAtomSets.get(file) ?? new Set<string>()\n for (const name of previous) {\n if (newAtoms.has(name)) continue\n const count = (this.atomRefCount.get(name) ?? 0) - 1\n if (count <= 0) this.atomRefCount.delete(name)\n else this.atomRefCount.set(name, count)\n // Do NOT remove `name` from `unionAtoms` — the project scan still\n // references it (orphaned atoms get reaped on the next Metro\n // cold start when the scanner re-walks disk).\n }\n for (const name of newAtoms) {\n if (!previous.has(name)) this.atomRefCount.set(name, (this.atomRefCount.get(name) ?? 0) + 1)\n // Only install the resolved style when the atom is new to the\n // union. Replacing an existing entry with a fresh parser-\n // produced object would swap the identity guard the per-atom\n // serialization cache uses and force a re-stringify for every\n // atom on every FR save. CSS edits rebuild the whole builder\n // (via `getRnwindState`) so stale values aren't possible.\n if (!this.unionAtoms.has(name)) {\n const style = resolvedAtoms.get(name)\n if (style) this.unionAtoms.set(name, style)\n }\n }\n this.fileAtomSets.set(file, new Set(newAtoms))\n for (const [name, kf] of newKeyframes) this.unionKeyframes.set(name, kf)\n }\n}\n\nexport { UnionBuilder }\n"],"names":[],"mappings":";;;;;AAMA;AACA,MAAM,iBAAiB,GAAG,YAAY;AAEtC;;;;;;;AAOG;AACH,SAAS,cAAc,CAAC,MAAc,EAAE,OAAe,EAAA;AACrD,IAAA,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE;AACtB,QAAA,IAAI;AACF,YAAA,IAAI,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,OAAO;AAAE,gBAAA,OAAO,KAAK;QAC5D;AAAE,QAAA,MAAM;;QAER;IACF;AACA,IAAA,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AACpD,IAAA,MAAM,SAAS,GAAG,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,OAAO,CAAC,GAAG,CAAA,CAAA,EAAI,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM;AAClF,IAAA,IAAI;AACF,QAAA,aAAa,CAAC,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC;AACzC,QAAA,UAAU,CAAC,SAAS,EAAE,MAAM,CAAC;AAC7B,QAAA,OAAO,IAAI;IACb;IAAE,OAAO,KAAK,EAAE;QACd,MAAM,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAClC,QAAA,MAAM,KAAK;IACb;AACF;AAEA;;;;;AAKG;AACH,SAAS,WAAW,CAAC,IAAY,EAAA;IAC/B,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;AACrE;AAEA;;;;;;AAMG;AACH,SAAS,SAAS,CAAC,CAAsB,EAAE,CAAsB,EAAA;AAC/D,IAAA,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI;AAAE,QAAA,OAAO,KAAK;IACnC,KAAK,MAAM,CAAC,IAAI,CAAC;AAAE,QAAA,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AAAE,YAAA,OAAO,KAAK;AAC9C,IAAA,OAAO,IAAI;AACb;AAEA;;;;;AAKG;AACH,SAAS,cAAc,CAAC,QAAgB,EAAE,MAAc,EAAA;IACtD,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAA,EAAG,MAAM,CAAA,SAAA,CAAW,CAAC;AAClD;AAEA;;;;;;;;;;;;;;;;AAgBG;AACH,MAAM,YAAY,CAAA;AACC,IAAA,QAAQ;AACR,IAAA,MAAM;AACN,IAAA,UAAU,GAAG,IAAI,GAAG,EAAwB;AAC5C,IAAA,cAAc,GAAG,IAAI,GAAG,EAAyB;;AAEjD,IAAA,cAAc,GAAG,IAAI,GAAG,EAA4B;;AAEpD,IAAA,YAAY,GAAG,IAAI,GAAG,EAAyB;AAChE;;;;;AAKG;AACc,IAAA,aAAa,GAAG,IAAI,GAAG,EAAU;AAClD;;;;;;;AAOG;AACK,IAAA,WAAW,GAAgC,IAAI,GAAG,EAAE;;AAE3C,IAAA,YAAY,GAAG,IAAI,GAAG,EAAuB;;AAE7C,IAAA,YAAY,GAAG,IAAI,GAAG,EAAkB;;AAExC,IAAA,gBAAgB,GAAG,IAAI,GAAG,EAAkB;AAC7D;;;;;;;AAOG;AACc,IAAA,eAAe,GAAwB,IAAI,GAAG,EAAE;;IAEzD,qBAAqB,GAAG,CAAC;;IAEzB,cAAc,GAAG,KAAK;;IAEtB,WAAW,GAAyB,IAAI;IAEhD,WAAA,CAAY,QAAgB,EAAE,MAAsB,EAAA;AAClD,QAAA,IAAI,CAAC,QAAQ,GAAG,QAAQ;AACxB,QAAA,IAAI,CAAC,MAAM,GAAG,MAAM;QACpB,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IAC/C;;AAGA,IAAA,IAAW,YAAY,GAAA;QACrB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACpD;;AAGA,IAAA,IAAW,gBAAgB,GAAA;QACzB,OAAO,IAAI,CAAC,qBAAqB;IACnC;AAEA;;;;AAIG;AACI,IAAA,UAAU,CAAC,MAAc,EAAA;QAC9B,OAAO,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC9C;AAEA;;;;AAIG;AACI,IAAA,MAAM,oBAAoB,GAAA;QAC/B,IAAI,IAAI,CAAC,cAAc;YAAE;QACzB,IAAI,IAAI,CAAC,WAAW;YAAE,OAAO,IAAI,CAAC,WAAW;AAC7C,QAAA,IAAI,CAAC,WAAW,GAAG,CAAC,YAAW;YAC7B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE;YAC/C,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK;gBAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC;YAC1E,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,SAAS;gBAAE,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;YAC5E,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,aAAa;gBAAE,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC;YAC5F,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,WAAW;gBAAE,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC;AACpF,YAAA,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW;AACrC,YAAA,IAAI,CAAC,cAAc,GAAG,IAAI;QAC5B,CAAC,GAAG;AACJ,QAAA,IAAI;YACF,MAAM,IAAI,CAAC,WAAW;QACxB;gBAAU;AACR,YAAA,IAAI,CAAC,WAAW,GAAG,IAAI;QACzB;IACF;AAEA;;;;;;;;;;;;AAYG;IACI,MAAM,UAAU,CACrB,IAAY,EACZ,KAAwC,EACxC,SAA6C,EAC7C,QAAA,GAA8B,EAAE,EAAA;AAEhC,QAAA,MAAM,IAAI,CAAC,oBAAoB,EAAE;QACjC,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC;QAClD,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC;QAC5C,IAAI,QAAQ,IAAI,SAAS,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE;;;;;;;YAOjD,IAAI,aAAa,GAAG,KAAK;YACzB,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,SAAS,EAAE;gBAClC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC;oBAAE,aAAa,GAAG,IAAI;gBACxD,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;YACnC;AACA,YAAA,OAAO,EAAE,OAAO,EAAE,aAAa,IAAI,YAAY,EAAE;QACnD;QACA,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,CAAC;AACpD,QAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE;IAC1B;AAEA;;;;;;AAMG;AACK,IAAA,cAAc,CAAC,QAA2B,EAAA;QAChD,IAAI,KAAK,GAAG,KAAK;AACjB,QAAA,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE;AAC9B,YAAA,IAAI,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC;gBAAE;AACrC,YAAA,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC;YAC/B,KAAK,GAAG,IAAI;QACd;AACA,QAAA,OAAO,KAAK;IACd;AAEA;;;;;;AAMG;AACI,IAAA,QAAQ,CAAC,IAAY,EAAA;QAC1B,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC;AAC5C,QAAA,IAAI,CAAC,QAAQ;YAAE;AACf,QAAA,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE;AAC3B,YAAA,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;YACpD,IAAI,KAAK,IAAI,CAAC;AAAE,gBAAA,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC;;gBACzC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC;QACzC;AACA,QAAA,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC;IAChC;AAEA;;;;;;;AAOG;AACI,IAAA,MAAM,YAAY,GAAA;AACvB,QAAA,MAAM,IAAI,CAAC,oBAAoB,EAAE;AACjC,QAAA,MAAM,eAAe,GAAG,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;AAC1F,QAAA,MAAM,MAAM,GAAG,kBAAkB,CAAC,eAAe,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,eAAe,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC;AACjM,QAAA,IAAI,CAAC,qBAAqB,IAAI,MAAM,CAAC,gBAAgB;AACrD,QAAA,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,GAAG,MAAM;QAEhD,MAAM,OAAO,GAAa,EAAE;AAC5B,QAAA,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE;AAC5D,YAAA,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,CAAC;YACrC,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC;AACpD,YAAA,IAAI,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,SAAS,IAAI,UAAU,CAAC,MAAM,CAAC;gBAAE;AAC3E,YAAA,IAAI,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC;AAAE,gBAAA,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC;YACxD,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC;QAC9C;AAEA,QAAA,MAAM,iBAAiB,GAAG,WAAW,CAAC,cAAc,CAAC;AACrD,QAAA,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,iBAAiB,CAAC;AAClE,QAAA,IAAI,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,iBAAiB,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE;AAChG,YAAA,IAAI,cAAc,CAAC,cAAc,EAAE,cAAc,CAAC;AAAE,gBAAA,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC;YAC9E,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,YAAY,EAAE,iBAAiB,CAAC;QAC5D;AAEA,QAAA,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE;IACpC;AAEA;;;AAGG;AACI,IAAA,MAAM,gBAAgB,GAAA;AAC3B,QAAA,IAAI,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,UAAU,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,EAAE;;;AAGxF,YAAA,MAAM,IAAI,CAAC,oBAAoB,EAAE;YACjC;QACF;AACA,QAAA,MAAM,IAAI,CAAC,YAAY,EAAE;IAC3B;AAEA;;;;;;;AAOG;AACK,IAAA,SAAS,CACf,IAAY,EACZ,QAA6B,EAC7B,aAAgD,EAChD,YAAgD,EAAA;AAEhD,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,EAAU;AACjE,QAAA,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE;AAC3B,YAAA,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE;AACxB,YAAA,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;YACpD,IAAI,KAAK,IAAI,CAAC;AAAE,gBAAA,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC;;gBACzC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC;;;;QAIzC;AACA,QAAA,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE;AAC3B,YAAA,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;;;;;;;YAO5F,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;gBAC9B,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC;AACrC,gBAAA,IAAI,KAAK;oBAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC;YAC7C;QACF;AACA,QAAA,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;AAC9C,QAAA,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,YAAY;YAAE,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;IAC1E;AACD;;;;"}
@@ -97,7 +97,12 @@ function writeDtsFile(targetPath, schemes) {
97
97
  lines.push('}', '');
98
98
  if (schemes.length > 0) {
99
99
  lines.push(`declare module 'rnwind' {`, ` export interface RnwindConfig {`);
100
- const schemeLiterals = schemes.map((s) => `'${s}'`).join(', ');
100
+ // Escape backslash / single-quote so a scheme name with a quote (only
101
+ // reachable via the public `writeDtsFile`, since CSS idents can't contain
102
+ // one) can't emit invalid TS that breaks the whole file and drops the
103
+ // `className` augmentation project-wide. Single-quoted to match the rest
104
+ // of the generated declaration.
105
+ const schemeLiterals = schemes.map((s) => `'${s.replaceAll('\\', '\\\\').replaceAll("'", String.raw `\'`)}'`).join(', ');
101
106
  lines.push(` themes: readonly [${schemeLiterals}]`, ` }`, '}', '');
102
107
  }
103
108
  // The `export {}` is mandatory — without at least one top-level
@@ -1 +1 @@
1
- {"version":3,"file":"dts.mjs","sources":["../../../../src/metro/dts.ts"],"sourcesContent":["import { mkdirSync, writeFileSync } from 'node:fs'\nimport path from 'node:path'\n\n/**\n * React Native component-props interfaces rnwind augments. Adding an\n * optional `className?: string` to each lets TypeScript accept\n * `<View className=\"…\" />` without the user touching types by hand.\n * The generic ones are written with a placeholder so the output type\n * stays parameterised.\n */\nconst INTERFACES: ReadonlyArray<string | { name: string; generic: string }> = [\n 'ViewProps',\n 'TextProps',\n 'ImageProps',\n 'ImageBackgroundProps',\n 'ScrollViewProps',\n 'PressableProps',\n 'ModalProps',\n 'TextInputProps',\n 'TouchableOpacityProps',\n 'TouchableHighlightProps',\n 'TouchableNativeFeedbackProps',\n 'TouchableWithoutFeedbackProps',\n 'SafeAreaViewProps',\n 'KeyboardAvoidingViewProps',\n 'RefreshControlProps',\n 'StatusBarProps',\n 'SwitchProps',\n 'ActivityIndicatorProps',\n 'SectionListProps',\n { name: 'FlatListProps', generic: 'ItemT' },\n { name: 'VirtualizedListProps', generic: 'ItemT' },\n]\n\n/**\n * Built-in prefix wired into every install. Restricted to the interfaces\n * that natively expose `contentContainerStyle` so TypeScript doesn't\n * start accepting `<View contentContainerClassName=\"…\" />` — RN would\n * silently ignore it at runtime and a typed lint is more useful than a\n * permissive one. User prefixes don't come with this restriction; rnwind\n * can't know which component props they target.\n */\nconst BUILTIN_PREFIX = 'contentContainer'\n\n/**\n * Interfaces that expose `contentContainerStyle` in React Native's own\n * types — the set the built-in prefix's `.d.ts` augmentation targets.\n * Everything else stays `className`-only by default.\n */\nconst CONTENT_CONTAINER_INTERFACES: ReadonlySet<string> = new Set([\n 'ScrollViewProps',\n 'FlatListProps',\n 'SectionListProps',\n 'VirtualizedListProps',\n])\n\n/**\n * Build the body of one interface augmentation: `className?: string`\n * plus `contentContainerClassName?: string` for the scroll interfaces\n * that natively expose `contentContainerStyle`. Emits a single-line\n * body so the file stays easy to scan and diff.\n * @param interfaceName Bare interface name (generic parameters stripped).\n * @returns Space-separated property declarations.\n */\nfunction buildInterfaceBody(interfaceName: string): string {\n const props = ['className?: string']\n if (CONTENT_CONTAINER_INTERFACES.has(interfaceName)) props.push(`${BUILTIN_PREFIX}ClassName?: string`)\n return props.join('; ')\n}\n\n/**\n * Write the rnwind TypeScript declaration file. The generated `.d.ts`\n * declares a `rnwind` module augmentation for `react-native`'s\n * component props — every interface gets an optional `className?:\n * string` plus `<prefix>ClassName?: string` for each active prefix.\n * Runtime-rendered schemes (`@variant` blocks) are reflected in the\n * `Scheme` union so `useScheme()` returns the actual names.\n *\n * Called once at Metro-config time — overwrite-on-rewrite so the file\n * stays in sync with the user's current theme CSS.\n * @param targetPath Absolute path to write (typically `rnwind-types.d.ts` at project root).\n * @param schemes Scheme names from the user's `@variant` blocks (empty when none declared).\n */\nexport function writeDtsFile(targetPath: string, schemes: readonly string[]): void {\n const lines: string[] = [\n '// Auto-generated by rnwind — do not edit by hand.',\n '// Overwritten on Metro start / theme CSS change.',\n '',\n `declare module 'react-native' {`,\n ]\n for (const entry of INTERFACES) {\n const name = typeof entry === 'string' ? entry : entry.name\n const body = buildInterfaceBody(name)\n if (typeof entry === 'string') {\n lines.push(` interface ${entry} { ${body} }`)\n continue\n }\n lines.push(` interface ${entry.name}<${entry.generic}> { ${body} }`)\n }\n lines.push('}', '')\n if (schemes.length > 0) {\n lines.push(`declare module 'rnwind' {`, ` export interface RnwindConfig {`)\n const schemeLiterals = schemes.map((s) => `'${s}'`).join(', ')\n lines.push(` themes: readonly [${schemeLiterals}]`, ` }`, '}', '')\n }\n // The `export {}` is mandatory — without at least one top-level\n // import/export, TypeScript treats this file as a SCRIPT and the\n // `declare module 'react-native'` block above becomes a complete\n // *replacement* declaration (hiding the real RN exports like\n // `Pressable`, `useColorScheme`, etc.). With `export {}` the file\n // becomes a module and the `declare module` blocks are interpreted as\n // *augmentations* — which is what we want.\n lines.push('export {}', '')\n mkdirSync(path.dirname(targetPath), { recursive: true })\n writeFileSync(targetPath, lines.join('\\n'), 'utf8')\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dts.mjs","sources":["../../../../src/metro/dts.ts"],"sourcesContent":["import { mkdirSync, writeFileSync } from 'node:fs'\nimport path from 'node:path'\n\n/**\n * React Native component-props interfaces rnwind augments. Adding an\n * optional `className?: string` to each lets TypeScript accept\n * `<View className=\"…\" />` without the user touching types by hand.\n * The generic ones are written with a placeholder so the output type\n * stays parameterised.\n */\nconst INTERFACES: ReadonlyArray<string | { name: string; generic: string }> = [\n 'ViewProps',\n 'TextProps',\n 'ImageProps',\n 'ImageBackgroundProps',\n 'ScrollViewProps',\n 'PressableProps',\n 'ModalProps',\n 'TextInputProps',\n 'TouchableOpacityProps',\n 'TouchableHighlightProps',\n 'TouchableNativeFeedbackProps',\n 'TouchableWithoutFeedbackProps',\n 'SafeAreaViewProps',\n 'KeyboardAvoidingViewProps',\n 'RefreshControlProps',\n 'StatusBarProps',\n 'SwitchProps',\n 'ActivityIndicatorProps',\n 'SectionListProps',\n { name: 'FlatListProps', generic: 'ItemT' },\n { name: 'VirtualizedListProps', generic: 'ItemT' },\n]\n\n/**\n * Built-in prefix wired into every install. Restricted to the interfaces\n * that natively expose `contentContainerStyle` so TypeScript doesn't\n * start accepting `<View contentContainerClassName=\"…\" />` — RN would\n * silently ignore it at runtime and a typed lint is more useful than a\n * permissive one. User prefixes don't come with this restriction; rnwind\n * can't know which component props they target.\n */\nconst BUILTIN_PREFIX = 'contentContainer'\n\n/**\n * Interfaces that expose `contentContainerStyle` in React Native's own\n * types — the set the built-in prefix's `.d.ts` augmentation targets.\n * Everything else stays `className`-only by default.\n */\nconst CONTENT_CONTAINER_INTERFACES: ReadonlySet<string> = new Set([\n 'ScrollViewProps',\n 'FlatListProps',\n 'SectionListProps',\n 'VirtualizedListProps',\n])\n\n/**\n * Build the body of one interface augmentation: `className?: string`\n * plus `contentContainerClassName?: string` for the scroll interfaces\n * that natively expose `contentContainerStyle`. Emits a single-line\n * body so the file stays easy to scan and diff.\n * @param interfaceName Bare interface name (generic parameters stripped).\n * @returns Space-separated property declarations.\n */\nfunction buildInterfaceBody(interfaceName: string): string {\n const props = ['className?: string']\n if (CONTENT_CONTAINER_INTERFACES.has(interfaceName)) props.push(`${BUILTIN_PREFIX}ClassName?: string`)\n return props.join('; ')\n}\n\n/**\n * Write the rnwind TypeScript declaration file. The generated `.d.ts`\n * declares a `rnwind` module augmentation for `react-native`'s\n * component props — every interface gets an optional `className?:\n * string` plus `<prefix>ClassName?: string` for each active prefix.\n * Runtime-rendered schemes (`@variant` blocks) are reflected in the\n * `Scheme` union so `useScheme()` returns the actual names.\n *\n * Called once at Metro-config time — overwrite-on-rewrite so the file\n * stays in sync with the user's current theme CSS.\n * @param targetPath Absolute path to write (typically `rnwind-types.d.ts` at project root).\n * @param schemes Scheme names from the user's `@variant` blocks (empty when none declared).\n */\nexport function writeDtsFile(targetPath: string, schemes: readonly string[]): void {\n const lines: string[] = [\n '// Auto-generated by rnwind — do not edit by hand.',\n '// Overwritten on Metro start / theme CSS change.',\n '',\n `declare module 'react-native' {`,\n ]\n for (const entry of INTERFACES) {\n const name = typeof entry === 'string' ? entry : entry.name\n const body = buildInterfaceBody(name)\n if (typeof entry === 'string') {\n lines.push(` interface ${entry} { ${body} }`)\n continue\n }\n lines.push(` interface ${entry.name}<${entry.generic}> { ${body} }`)\n }\n lines.push('}', '')\n if (schemes.length > 0) {\n lines.push(`declare module 'rnwind' {`, ` export interface RnwindConfig {`)\n // Escape backslash / single-quote so a scheme name with a quote (only\n // reachable via the public `writeDtsFile`, since CSS idents can't contain\n // one) can't emit invalid TS that breaks the whole file and drops the\n // `className` augmentation project-wide. Single-quoted to match the rest\n // of the generated declaration.\n const schemeLiterals = schemes.map((s) => `'${s.replaceAll('\\\\', '\\\\\\\\').replaceAll(\"'\", String.raw`\\'`)}'`).join(', ')\n lines.push(` themes: readonly [${schemeLiterals}]`, ` }`, '}', '')\n }\n // The `export {}` is mandatory — without at least one top-level\n // import/export, TypeScript treats this file as a SCRIPT and the\n // `declare module 'react-native'` block above becomes a complete\n // *replacement* declaration (hiding the real RN exports like\n // `Pressable`, `useColorScheme`, etc.). With `export {}` the file\n // becomes a module and the `declare module` blocks are interpreted as\n // *augmentations* — which is what we want.\n lines.push('export {}', '')\n mkdirSync(path.dirname(targetPath), { recursive: true })\n writeFileSync(targetPath, lines.join('\\n'), 'utf8')\n}\n"],"names":[],"mappings":"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"}
@@ -124,30 +124,48 @@ function isThemeCssEntry(filename) {
124
124
  * @returns Rewritten source text.
125
125
  */
126
126
  async function rewriteSource(args) {
127
- const ast = parseUserSource(args.src);
128
- if (!ast)
129
- return args.src;
130
- // Wrap host component imports so `<View className=…>` resolves at render
131
- // through the runtime `wrap` (works for literal, spread, and forwarded
132
- // classNames alike). No JSX is rewritten here.
133
- const wrapped = rewriteWrapImports(ast, getWrapModules());
134
- if (!/classname=/i.test(args.src)) {
135
- // Import-only file: nothing to compile. Drop any stale atom
136
- // contribution (className may have just been removed) and emit the
137
- // wrapped imports — or the untouched source when nothing wrapped.
138
- dropFileSafely(args.filename, projectRootOf(args));
139
- return wrapped ? generateModule(ast).code : args.src;
140
- }
127
+ // SCAN FIRST — exactly like Tailwind: oxide extracts class candidates from
128
+ // the WHOLE file content (className, cva/clsx, plain strings, anywhere),
129
+ // and the compiler is the only filter. No babel parse needed to scan, so
130
+ // the common "file with no classes" case stays cheap and the source is
131
+ // returned untouched. This is what makes adding a class ANYWHERE register
132
+ // on hot-reload, not just inside a `className=` or a known helper call.
141
133
  const state = getRnwindState(projectRootOf(args));
142
134
  const extension = extensionOf(args.filename);
143
135
  const parsed = await state.parser.parseAtoms({ content: args.src, extension });
144
- warnUnknownClasses(args.src, parsed.candidates, parsed.atoms, args.filename, [parsed.gradientAtoms, parsed.hapticAtoms]);
145
- if (parsed.atoms.size === 0) {
136
+ const hasAtoms = parsed.atoms.size > 0;
137
+ const hasClassName = /classname=/i.test(args.src);
138
+ const hasSpread = /\{\s*\.\.\./.test(args.src);
139
+ // Nothing for rnwind to do: no Tailwind class compiled AND no host
140
+ // wrapping needed. Drop any stale contribution and emit the file as-is.
141
+ if (!hasAtoms && !hasClassName && !hasSpread) {
146
142
  state.builder.dropFile(args.filename);
147
- await state.builder.writeSchemes();
148
- injectThemeSignatureImport(ast);
149
- return generateModule(ast).code;
143
+ return args.src;
144
+ }
145
+ const ast = parseUserSource(args.src);
146
+ if (!ast) {
147
+ // Can't parse to wrap/inject, but we DID scan — still record the atoms so
148
+ // they register (a malformed-but-recoverable file shouldn't lose styles).
149
+ if (hasAtoms) {
150
+ await state.builder.recordFile(args.filename, parsed.atoms, parsed.keyframes, []);
151
+ await state.builder.writeSchemes();
152
+ }
153
+ else {
154
+ state.builder.dropFile(args.filename);
155
+ }
156
+ return args.src;
157
+ }
158
+ // Wrap host imports ONLY when className flows through a component — written
159
+ // (`hasClassName`) or forwarded (`{...rest}`). A `useCss`/`cva`-only file
160
+ // resolves manually, so its imports (e.g. skia drawing primitives) are
161
+ // left untouched.
162
+ const wrapped = hasClassName || hasSpread ? rewriteWrapImports(ast, getWrapModules()) : false;
163
+ if (!hasAtoms) {
164
+ // Wrap-only forwarder — no classes to record/register.
165
+ state.builder.dropFile(args.filename);
166
+ return wrapped ? generateModule(ast).code : args.src;
150
167
  }
168
+ warnUnknownClasses(args.src, parsed.candidates, parsed.atoms, args.filename, [parsed.gradientAtoms, parsed.hapticAtoms]);
151
169
  const literals = collectClassNameLiterals(ast);
152
170
  const { changed } = await state.builder.recordFile(args.filename, parsed.atoms, parsed.keyframes, literals);
153
171
  if (changed)
@@ -156,21 +174,6 @@ async function rewriteSource(args) {
156
174
  injectThemeSignatureImport(ast);
157
175
  return generateModule(ast).code;
158
176
  }
159
- /**
160
- * Drop a file's union contribution, swallowing the "state not configured"
161
- * error unit tests hit when they call the transformer without
162
- * `configureRnwindState`.
163
- * @param filename Absolute source path.
164
- * @param projectRoot Project root for state lookup.
165
- */
166
- function dropFileSafely(filename, projectRoot) {
167
- try {
168
- getRnwindState(projectRoot).builder.dropFile(filename);
169
- }
170
- catch {
171
- // State not configured (standalone/unit test). Nothing to drop.
172
- }
173
- }
174
177
  /**
175
178
  * Whether a JSX attribute names a className-style prop (`className` or
176
179
  * any `<prefix>ClassName`).
@@ -379,17 +382,12 @@ function loadUpstream() {
379
382
  function isRewriteCandidate(args) {
380
383
  if (!/\.(?:tsx|ts|jsx|js)$/i.test(args.filename))
381
384
  return false;
382
- // Process the file when it either:
383
- // - carries a `className=` / `<prefix>ClassName=` literal (case-
384
- // insensitive `contentContainerClassName=` has a capital C), or
385
- // - spreads props (`{...rest}`) onto a host from a wrap-module, where a
386
- // forwarded className must still get its import wrapped (no literal
387
- // appears in this file). A style-less `<View/>` with neither is left
388
- // alone so it never pays for an unused wrapper.
389
- const hasClassName = /classname=/i.test(args.src);
390
- const isForwarder = /\{\s*\.\.\./.test(args.src) && mentionsWrapModule(args.src);
391
- if (!hasClassName && !isForwarder)
392
- return false;
385
+ // EVERY user source file is scanned — exactly like Tailwind walks its
386
+ // whole content set. The compiler is the only filter (see rewriteSource);
387
+ // no className/helper pre-filter, because that "filter magic" missed
388
+ // classes living in cva/clsx/plain-string files and broke their hot-reload.
389
+ // (Cheap when the file has no classes: oxide finds nothing, no babel parse,
390
+ // source returned untouched.)
393
391
  if (!args.filename.includes('/node_modules/'))
394
392
  return true;
395
393
  // node_modules in path → could be a workspace symlink; resolve it.
@@ -401,21 +399,6 @@ function isRewriteCandidate(args) {
401
399
  return false;
402
400
  }
403
401
  }
404
- /**
405
- * Cheap pre-parse check: does the source import from any configured
406
- * wrap-module? A quoted specifier match is enough — `rewriteWrapImports`
407
- * re-verifies precisely on the AST, so a false positive only costs a
408
- * no-op parse.
409
- * @param source Source text.
410
- * @returns True when a wrap-module specifier appears in the source.
411
- */
412
- function mentionsWrapModule(source) {
413
- for (const moduleName of getWrapModules().keys()) {
414
- if (source.includes(`'${moduleName}'`) || source.includes(`"${moduleName}"`))
415
- return true;
416
- }
417
- return false;
418
- }
419
402
  /**
420
403
  * Fallback parse when no upstream is configured AND Metro didn't hand
421
404
  * us an AST. Used by unit tests and standalone setups.