rnwind 0.0.1 → 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 (330) hide show
  1. package/lib/cjs/core/parser/animation.cjs +427 -0
  2. package/lib/cjs/core/parser/animation.cjs.map +1 -0
  3. package/lib/cjs/core/parser/animation.d.ts +126 -0
  4. package/lib/cjs/core/parser/border-dispatcher.cjs +180 -0
  5. package/lib/cjs/core/parser/border-dispatcher.cjs.map +1 -0
  6. package/lib/cjs/core/parser/border-dispatcher.d.ts +15 -0
  7. package/lib/cjs/core/parser/case-convert.cjs +15 -0
  8. package/lib/cjs/core/parser/case-convert.cjs.map +1 -0
  9. package/lib/cjs/core/parser/case-convert.d.ts +6 -0
  10. package/lib/cjs/core/parser/color-properties-dispatcher.cjs +84 -0
  11. package/lib/cjs/core/parser/color-properties-dispatcher.cjs.map +1 -0
  12. package/lib/cjs/core/parser/color-properties-dispatcher.d.ts +19 -0
  13. package/lib/cjs/core/parser/color.cjs +193 -0
  14. package/lib/cjs/core/parser/color.cjs.map +1 -0
  15. package/lib/cjs/core/parser/color.d.ts +12 -0
  16. package/lib/cjs/core/parser/constants.cjs +21 -0
  17. package/lib/cjs/core/parser/constants.cjs.map +1 -0
  18. package/lib/cjs/core/parser/constants.d.ts +8 -0
  19. package/lib/cjs/core/parser/declaration.cjs +347 -0
  20. package/lib/cjs/core/parser/declaration.cjs.map +1 -0
  21. package/lib/cjs/core/parser/declaration.d.ts +15 -0
  22. package/lib/cjs/core/parser/gradient.cjs +132 -0
  23. package/lib/cjs/core/parser/gradient.cjs.map +1 -0
  24. package/lib/cjs/core/parser/gradient.d.ts +59 -0
  25. package/lib/cjs/core/parser/haptics.cjs +73 -0
  26. package/lib/cjs/core/parser/haptics.cjs.map +1 -0
  27. package/lib/cjs/core/parser/haptics.d.ts +47 -0
  28. package/lib/cjs/core/parser/index.d.ts +8 -0
  29. package/lib/cjs/core/parser/keyframes.cjs +95 -0
  30. package/lib/cjs/core/parser/keyframes.cjs.map +1 -0
  31. package/lib/cjs/core/parser/keyframes.d.ts +26 -0
  32. package/lib/cjs/core/parser/layout-dispatcher.cjs +120 -0
  33. package/lib/cjs/core/parser/layout-dispatcher.cjs.map +1 -0
  34. package/lib/cjs/core/parser/layout-dispatcher.d.ts +14 -0
  35. package/lib/cjs/core/parser/length.cjs +110 -0
  36. package/lib/cjs/core/parser/length.cjs.map +1 -0
  37. package/lib/cjs/core/parser/length.d.ts +51 -0
  38. package/lib/cjs/core/parser/motion-dispatcher.cjs +77 -0
  39. package/lib/cjs/core/parser/motion-dispatcher.cjs.map +1 -0
  40. package/lib/cjs/core/parser/motion-dispatcher.d.ts +11 -0
  41. package/lib/cjs/core/parser/property.cjs +22 -0
  42. package/lib/cjs/core/parser/property.cjs.map +1 -0
  43. package/lib/cjs/core/parser/property.d.ts +8 -0
  44. package/lib/cjs/core/parser/safe-area.cjs +404 -0
  45. package/lib/cjs/core/parser/safe-area.cjs.map +1 -0
  46. package/lib/cjs/core/parser/safe-area.d.ts +39 -0
  47. package/lib/cjs/core/parser/selector.cjs +22 -0
  48. package/lib/cjs/core/parser/selector.cjs.map +1 -0
  49. package/lib/cjs/core/parser/selector.d.ts +11 -0
  50. package/lib/cjs/core/parser/shorthand.cjs +188 -0
  51. package/lib/cjs/core/parser/shorthand.cjs.map +1 -0
  52. package/lib/cjs/core/parser/shorthand.d.ts +67 -0
  53. package/lib/cjs/core/parser/text-truncate.cjs +78 -0
  54. package/lib/cjs/core/parser/text-truncate.cjs.map +1 -0
  55. package/lib/cjs/core/parser/text-truncate.d.ts +44 -0
  56. package/lib/cjs/core/parser/theme-vars.cjs +467 -0
  57. package/lib/cjs/core/parser/theme-vars.cjs.map +1 -0
  58. package/lib/cjs/core/parser/theme-vars.d.ts +82 -0
  59. package/lib/cjs/core/parser/tokens.cjs +486 -0
  60. package/lib/cjs/core/parser/tokens.cjs.map +1 -0
  61. package/lib/cjs/core/parser/tokens.d.ts +45 -0
  62. package/lib/cjs/core/parser/transform.cjs +198 -0
  63. package/lib/cjs/core/parser/transform.cjs.map +1 -0
  64. package/lib/cjs/core/parser/transform.d.ts +36 -0
  65. package/lib/cjs/core/parser/tw-parser.cjs +1680 -0
  66. package/lib/cjs/core/parser/tw-parser.cjs.map +1 -0
  67. package/lib/cjs/core/parser/tw-parser.d.ts +210 -0
  68. package/lib/cjs/core/parser/types.d.ts +37 -0
  69. package/lib/cjs/core/parser/typography-dispatcher.cjs +108 -0
  70. package/lib/cjs/core/parser/typography-dispatcher.cjs.map +1 -0
  71. package/lib/cjs/core/parser/typography-dispatcher.d.ts +11 -0
  72. package/lib/cjs/core/parser/typography.cjs +97 -0
  73. package/lib/cjs/core/parser/typography.cjs.map +1 -0
  74. package/lib/cjs/core/parser/typography.d.ts +43 -0
  75. package/lib/cjs/core/style-builder/build-style.cjs +444 -0
  76. package/lib/cjs/core/style-builder/build-style.cjs.map +1 -0
  77. package/lib/cjs/core/style-builder/build-style.d.ts +54 -0
  78. package/lib/cjs/core/style-builder/index.d.ts +3 -0
  79. package/lib/cjs/core/style-builder/union-builder.cjs +326 -0
  80. package/lib/cjs/core/style-builder/union-builder.cjs.map +1 -0
  81. package/lib/cjs/core/style-builder/union-builder.d.ts +128 -0
  82. package/lib/cjs/core/types.d.ts +14 -0
  83. package/lib/cjs/metro/dts.cjs +127 -0
  84. package/lib/cjs/metro/dts.cjs.map +1 -0
  85. package/lib/cjs/metro/dts.d.ts +16 -0
  86. package/lib/cjs/metro/index.cjs +19 -0
  87. package/lib/cjs/metro/index.cjs.map +1 -0
  88. package/lib/cjs/metro/index.d.ts +9 -0
  89. package/lib/cjs/metro/resolver.cjs +47 -0
  90. package/lib/cjs/metro/resolver.cjs.map +1 -0
  91. package/lib/cjs/metro/resolver.d.ts +22 -0
  92. package/lib/cjs/metro/state.cjs +301 -0
  93. package/lib/cjs/metro/state.cjs.map +1 -0
  94. package/lib/cjs/metro/state.d.ts +88 -0
  95. package/lib/cjs/metro/transform-ast.cjs +1472 -0
  96. package/lib/cjs/metro/transform-ast.cjs.map +1 -0
  97. package/lib/cjs/metro/transform-ast.d.ts +88 -0
  98. package/lib/cjs/metro/transformer.cjs +372 -0
  99. package/lib/cjs/metro/transformer.cjs.map +1 -0
  100. package/lib/cjs/metro/transformer.d.ts +47 -0
  101. package/lib/cjs/metro/warn-unknown-classes.cjs +86 -0
  102. package/lib/cjs/metro/warn-unknown-classes.cjs.map +1 -0
  103. package/lib/cjs/metro/warn-unknown-classes.d.ts +21 -0
  104. package/lib/cjs/metro/with-config.cjs +196 -0
  105. package/lib/cjs/metro/with-config.cjs.map +1 -0
  106. package/lib/cjs/metro/with-config.d.ts +79 -0
  107. package/lib/cjs/runtime/chain-handlers.cjs +37 -0
  108. package/lib/cjs/runtime/chain-handlers.cjs.map +1 -0
  109. package/lib/cjs/runtime/chain-handlers.d.ts +33 -0
  110. package/lib/cjs/runtime/components/rnwind-provider.cjs +98 -0
  111. package/lib/cjs/runtime/components/rnwind-provider.cjs.map +1 -0
  112. package/lib/cjs/runtime/components/rnwind-provider.d.ts +84 -0
  113. package/lib/cjs/runtime/gradient-types.d.ts +58 -0
  114. package/lib/cjs/runtime/haptics.cjs +113 -0
  115. package/lib/cjs/runtime/haptics.cjs.map +1 -0
  116. package/lib/cjs/runtime/haptics.d.ts +48 -0
  117. package/lib/cjs/runtime/hooks/use-css.cjs +21 -0
  118. package/lib/cjs/runtime/hooks/use-css.cjs.map +1 -0
  119. package/lib/cjs/runtime/hooks/use-css.d.ts +11 -0
  120. package/lib/cjs/runtime/hooks/use-interact.cjs +46 -0
  121. package/lib/cjs/runtime/hooks/use-interact.cjs.map +1 -0
  122. package/lib/cjs/runtime/hooks/use-interact.d.ts +42 -0
  123. package/lib/cjs/runtime/hooks/use-scheme.cjs +68 -0
  124. package/lib/cjs/runtime/hooks/use-scheme.cjs.map +1 -0
  125. package/lib/cjs/runtime/hooks/use-scheme.d.ts +34 -0
  126. package/lib/cjs/runtime/index.cjs +45 -0
  127. package/lib/cjs/runtime/index.cjs.map +1 -0
  128. package/lib/cjs/runtime/index.d.ts +27 -0
  129. package/lib/cjs/runtime/interactive-box.cjs +35 -0
  130. package/lib/cjs/runtime/interactive-box.cjs.map +1 -0
  131. package/lib/cjs/runtime/interactive-box.d.ts +40 -0
  132. package/lib/cjs/runtime/lookup-css.cjs +542 -0
  133. package/lib/cjs/runtime/lookup-css.cjs.map +1 -0
  134. package/lib/cjs/runtime/lookup-css.d.ts +164 -0
  135. package/lib/cjs/runtime/types.d.ts +29 -0
  136. package/lib/cjs/testing/index.cjs +367 -0
  137. package/lib/cjs/testing/index.cjs.map +1 -0
  138. package/lib/cjs/testing/index.d.ts +145 -0
  139. package/lib/esm/core/parser/animation.d.ts +126 -0
  140. package/lib/esm/core/parser/animation.mjs +408 -0
  141. package/lib/esm/core/parser/animation.mjs.map +1 -0
  142. package/lib/esm/core/parser/border-dispatcher.d.ts +15 -0
  143. package/lib/esm/core/parser/border-dispatcher.mjs +178 -0
  144. package/lib/esm/core/parser/border-dispatcher.mjs.map +1 -0
  145. package/lib/esm/core/parser/case-convert.d.ts +6 -0
  146. package/lib/esm/core/parser/case-convert.mjs +13 -0
  147. package/lib/esm/core/parser/case-convert.mjs.map +1 -0
  148. package/lib/esm/core/parser/color-properties-dispatcher.d.ts +19 -0
  149. package/lib/esm/core/parser/color-properties-dispatcher.mjs +82 -0
  150. package/lib/esm/core/parser/color-properties-dispatcher.mjs.map +1 -0
  151. package/lib/esm/core/parser/color.d.ts +12 -0
  152. package/lib/esm/core/parser/color.mjs +191 -0
  153. package/lib/esm/core/parser/color.mjs.map +1 -0
  154. package/lib/esm/core/parser/constants.d.ts +8 -0
  155. package/lib/esm/core/parser/constants.mjs +13 -0
  156. package/lib/esm/core/parser/constants.mjs.map +1 -0
  157. package/lib/esm/core/parser/declaration.d.ts +15 -0
  158. package/lib/esm/core/parser/declaration.mjs +345 -0
  159. package/lib/esm/core/parser/declaration.mjs.map +1 -0
  160. package/lib/esm/core/parser/gradient.d.ts +59 -0
  161. package/lib/esm/core/parser/gradient.mjs +130 -0
  162. package/lib/esm/core/parser/gradient.mjs.map +1 -0
  163. package/lib/esm/core/parser/haptics.d.ts +47 -0
  164. package/lib/esm/core/parser/haptics.mjs +71 -0
  165. package/lib/esm/core/parser/haptics.mjs.map +1 -0
  166. package/lib/esm/core/parser/index.d.ts +8 -0
  167. package/lib/esm/core/parser/keyframes.d.ts +26 -0
  168. package/lib/esm/core/parser/keyframes.mjs +91 -0
  169. package/lib/esm/core/parser/keyframes.mjs.map +1 -0
  170. package/lib/esm/core/parser/layout-dispatcher.d.ts +14 -0
  171. package/lib/esm/core/parser/layout-dispatcher.mjs +118 -0
  172. package/lib/esm/core/parser/layout-dispatcher.mjs.map +1 -0
  173. package/lib/esm/core/parser/length.d.ts +51 -0
  174. package/lib/esm/core/parser/length.mjs +104 -0
  175. package/lib/esm/core/parser/length.mjs.map +1 -0
  176. package/lib/esm/core/parser/motion-dispatcher.d.ts +11 -0
  177. package/lib/esm/core/parser/motion-dispatcher.mjs +75 -0
  178. package/lib/esm/core/parser/motion-dispatcher.mjs.map +1 -0
  179. package/lib/esm/core/parser/property.d.ts +8 -0
  180. package/lib/esm/core/parser/property.mjs +20 -0
  181. package/lib/esm/core/parser/property.mjs.map +1 -0
  182. package/lib/esm/core/parser/safe-area.d.ts +39 -0
  183. package/lib/esm/core/parser/safe-area.mjs +402 -0
  184. package/lib/esm/core/parser/safe-area.mjs.map +1 -0
  185. package/lib/esm/core/parser/selector.d.ts +11 -0
  186. package/lib/esm/core/parser/selector.mjs +20 -0
  187. package/lib/esm/core/parser/selector.mjs.map +1 -0
  188. package/lib/esm/core/parser/shorthand.d.ts +67 -0
  189. package/lib/esm/core/parser/shorthand.mjs +180 -0
  190. package/lib/esm/core/parser/shorthand.mjs.map +1 -0
  191. package/lib/esm/core/parser/text-truncate.d.ts +44 -0
  192. package/lib/esm/core/parser/text-truncate.mjs +75 -0
  193. package/lib/esm/core/parser/text-truncate.mjs.map +1 -0
  194. package/lib/esm/core/parser/theme-vars.d.ts +82 -0
  195. package/lib/esm/core/parser/theme-vars.mjs +461 -0
  196. package/lib/esm/core/parser/theme-vars.mjs.map +1 -0
  197. package/lib/esm/core/parser/tokens.d.ts +45 -0
  198. package/lib/esm/core/parser/tokens.mjs +480 -0
  199. package/lib/esm/core/parser/tokens.mjs.map +1 -0
  200. package/lib/esm/core/parser/transform.d.ts +36 -0
  201. package/lib/esm/core/parser/transform.mjs +193 -0
  202. package/lib/esm/core/parser/transform.mjs.map +1 -0
  203. package/lib/esm/core/parser/tw-parser.d.ts +210 -0
  204. package/lib/esm/core/parser/tw-parser.mjs +1678 -0
  205. package/lib/esm/core/parser/tw-parser.mjs.map +1 -0
  206. package/lib/esm/core/parser/types.d.ts +37 -0
  207. package/lib/esm/core/parser/typography-dispatcher.d.ts +11 -0
  208. package/lib/esm/core/parser/typography-dispatcher.mjs +106 -0
  209. package/lib/esm/core/parser/typography-dispatcher.mjs.map +1 -0
  210. package/lib/esm/core/parser/typography.d.ts +43 -0
  211. package/lib/esm/core/parser/typography.mjs +91 -0
  212. package/lib/esm/core/parser/typography.mjs.map +1 -0
  213. package/lib/esm/core/style-builder/build-style.d.ts +54 -0
  214. package/lib/esm/core/style-builder/build-style.mjs +442 -0
  215. package/lib/esm/core/style-builder/build-style.mjs.map +1 -0
  216. package/lib/esm/core/style-builder/index.d.ts +3 -0
  217. package/lib/esm/core/style-builder/union-builder.d.ts +128 -0
  218. package/lib/esm/core/style-builder/union-builder.mjs +324 -0
  219. package/lib/esm/core/style-builder/union-builder.mjs.map +1 -0
  220. package/lib/esm/core/types.d.ts +14 -0
  221. package/lib/esm/metro/dts.d.ts +16 -0
  222. package/lib/esm/metro/dts.mjs +125 -0
  223. package/lib/esm/metro/dts.mjs.map +1 -0
  224. package/lib/esm/metro/index.d.ts +9 -0
  225. package/lib/esm/metro/index.mjs +6 -0
  226. package/lib/esm/metro/index.mjs.map +1 -0
  227. package/lib/esm/metro/resolver.d.ts +22 -0
  228. package/lib/esm/metro/resolver.mjs +43 -0
  229. package/lib/esm/metro/resolver.mjs.map +1 -0
  230. package/lib/esm/metro/state.d.ts +88 -0
  231. package/lib/esm/metro/state.mjs +291 -0
  232. package/lib/esm/metro/state.mjs.map +1 -0
  233. package/lib/esm/metro/transform-ast.d.ts +88 -0
  234. package/lib/esm/metro/transform-ast.mjs +1451 -0
  235. package/lib/esm/metro/transform-ast.mjs.map +1 -0
  236. package/lib/esm/metro/transformer.d.ts +47 -0
  237. package/lib/esm/metro/transformer.mjs +349 -0
  238. package/lib/esm/metro/transformer.mjs.map +1 -0
  239. package/lib/esm/metro/warn-unknown-classes.d.ts +21 -0
  240. package/lib/esm/metro/warn-unknown-classes.mjs +84 -0
  241. package/lib/esm/metro/warn-unknown-classes.mjs.map +1 -0
  242. package/lib/esm/metro/with-config.d.ts +79 -0
  243. package/lib/esm/metro/with-config.mjs +194 -0
  244. package/lib/esm/metro/with-config.mjs.map +1 -0
  245. package/lib/esm/runtime/chain-handlers.d.ts +33 -0
  246. package/lib/esm/runtime/chain-handlers.mjs +34 -0
  247. package/lib/esm/runtime/chain-handlers.mjs.map +1 -0
  248. package/lib/esm/runtime/components/rnwind-provider.d.ts +84 -0
  249. package/lib/esm/runtime/components/rnwind-provider.mjs +94 -0
  250. package/lib/esm/runtime/components/rnwind-provider.mjs.map +1 -0
  251. package/lib/esm/runtime/gradient-types.d.ts +58 -0
  252. package/lib/esm/runtime/haptics.d.ts +48 -0
  253. package/lib/esm/runtime/haptics.mjs +110 -0
  254. package/lib/esm/runtime/haptics.mjs.map +1 -0
  255. package/lib/esm/runtime/hooks/use-css.d.ts +11 -0
  256. package/lib/esm/runtime/hooks/use-css.mjs +19 -0
  257. package/lib/esm/runtime/hooks/use-css.mjs.map +1 -0
  258. package/lib/esm/runtime/hooks/use-interact.d.ts +42 -0
  259. package/lib/esm/runtime/hooks/use-interact.mjs +44 -0
  260. package/lib/esm/runtime/hooks/use-interact.mjs.map +1 -0
  261. package/lib/esm/runtime/hooks/use-scheme.d.ts +34 -0
  262. package/lib/esm/runtime/hooks/use-scheme.mjs +63 -0
  263. package/lib/esm/runtime/hooks/use-scheme.mjs.map +1 -0
  264. package/lib/esm/runtime/index.d.ts +27 -0
  265. package/lib/esm/runtime/index.mjs +18 -0
  266. package/lib/esm/runtime/index.mjs.map +1 -0
  267. package/lib/esm/runtime/interactive-box.d.ts +40 -0
  268. package/lib/esm/runtime/interactive-box.mjs +33 -0
  269. package/lib/esm/runtime/interactive-box.mjs.map +1 -0
  270. package/lib/esm/runtime/lookup-css.d.ts +164 -0
  271. package/lib/esm/runtime/lookup-css.mjs +531 -0
  272. package/lib/esm/runtime/lookup-css.mjs.map +1 -0
  273. package/lib/esm/runtime/types.d.ts +29 -0
  274. package/lib/esm/testing/index.d.ts +145 -0
  275. package/lib/esm/testing/index.mjs +344 -0
  276. package/lib/esm/testing/index.mjs.map +1 -0
  277. package/package.json +80 -13
  278. package/preset.css +1171 -0
  279. package/src/core/parser/animation.ts +404 -0
  280. package/src/core/parser/border-dispatcher.ts +176 -0
  281. package/src/core/parser/case-convert.ts +10 -0
  282. package/src/core/parser/color-properties-dispatcher.ts +78 -0
  283. package/src/core/parser/color.ts +191 -0
  284. package/src/core/parser/constants.ts +11 -0
  285. package/src/core/parser/declaration.ts +340 -0
  286. package/src/core/parser/gradient.ts +148 -0
  287. package/src/core/parser/haptics.ts +88 -0
  288. package/src/core/parser/index.ts +8 -0
  289. package/src/core/parser/keyframes.ts +84 -0
  290. package/src/core/parser/layout-dispatcher.ts +111 -0
  291. package/src/core/parser/length.ts +114 -0
  292. package/src/core/parser/motion-dispatcher.ts +89 -0
  293. package/src/core/parser/property.ts +15 -0
  294. package/src/core/parser/safe-area.ts +404 -0
  295. package/src/core/parser/selector.ts +17 -0
  296. package/src/core/parser/shorthand.ts +182 -0
  297. package/src/core/parser/text-truncate.ts +79 -0
  298. package/src/core/parser/theme-vars.ts +465 -0
  299. package/src/core/parser/tokens.ts +456 -0
  300. package/src/core/parser/transform.ts +195 -0
  301. package/src/core/parser/tw-parser.ts +1828 -0
  302. package/src/core/parser/types.ts +45 -0
  303. package/src/core/parser/typography-dispatcher.ts +97 -0
  304. package/src/core/parser/typography.ts +83 -0
  305. package/src/core/style-builder/build-style.ts +500 -0
  306. package/src/core/style-builder/index.ts +3 -0
  307. package/src/core/style-builder/union-builder.ts +328 -0
  308. package/src/core/types.ts +15 -0
  309. package/src/metro/dts.ts +128 -0
  310. package/src/metro/index.ts +9 -0
  311. package/src/metro/resolver.ts +42 -0
  312. package/src/metro/state.ts +305 -0
  313. package/src/metro/transform-ast.ts +1729 -0
  314. package/src/metro/transformer.ts +372 -0
  315. package/src/metro/warn-unknown-classes.ts +79 -0
  316. package/src/metro/with-config.ts +251 -0
  317. package/src/runtime/chain-handlers.ts +47 -0
  318. package/src/runtime/components/rnwind-provider.tsx +144 -0
  319. package/src/runtime/gradient-types.ts +60 -0
  320. package/src/runtime/haptics.ts +120 -0
  321. package/src/runtime/hooks/use-css.ts +16 -0
  322. package/src/runtime/hooks/use-interact.ts +65 -0
  323. package/src/runtime/hooks/use-scheme.ts +63 -0
  324. package/src/runtime/index.ts +54 -0
  325. package/src/runtime/interactive-box.tsx +57 -0
  326. package/src/runtime/lookup-css.ts +628 -0
  327. package/src/runtime/types.ts +32 -0
  328. package/src/testing/index.ts +507 -0
  329. package/src/types/tailwindcss-node.d.ts +33 -0
  330. package/src/index.ts +0 -1
@@ -0,0 +1,1678 @@
1
+ import { compile } from '@tailwindcss/node';
2
+ import { Scanner } from '@tailwindcss/oxide';
3
+ import { formatHex } from 'culori';
4
+ import { Features, transform } from 'lightningcss';
5
+ import { declarationToRnEntries } from './declaration.mjs';
6
+ import { detectGradientAtom } from './gradient.mjs';
7
+ import { detectHapticAtom } from './haptics.mjs';
8
+ import { keyframesName, keyframeSelectorOffset, pickAnimationName } from './keyframes.mjs';
9
+ import { serializeInitialValue } from './property.mjs';
10
+ import { classNameFromSelector } from './selector.mjs';
11
+ import { extractThemeVars, extractSchemeAliases, extractCustomVariantSchemes, BASE_SCHEME, compileReadyTheme } from './theme-vars.mjs';
12
+ import { serializeTokens } from './tokens.mjs';
13
+
14
+ /**
15
+ * Default LightningCSS transform options for TailwindParser's visitor.
16
+ * Its taken from official Tailwind source:
17
+ * https://github.com/tailwindlabs/tailwindcss/blob/main/packages/%40tailwindcss-node/src/optimize.ts
18
+ */
19
+ const DEFAULT_TRANSFORM_OPTIONS = {
20
+ drafts: {
21
+ customMedia: true,
22
+ },
23
+ nonStandard: {
24
+ deepSelectorCombinator: true,
25
+ },
26
+ include: Features.Nesting | Features.MediaQueries,
27
+ exclude: Features.LogicalProperties | Features.DirSelector | Features.LightDark,
28
+ // NOTE: deliberately no `targets`. With targets that include
29
+ // color-mix-supporting browsers (Safari 16.4+, Chrome 111+, …),
30
+ // lightningcss EVALUATES `color-mix(in oklab, var(--theme-color)
31
+ // <pct>%, transparent)` at parse time using whichever value of
32
+ // `--theme-color` it sees first in the cascade. Tailwind v4 emits
33
+ // exactly this shape for `<prop>-<themed>/<N>` utilities (e.g.
34
+ // `border-text/20`), so the resulting RGB color is locked to ONE
35
+ // scheme — every variant gets the same value. By dropping targets,
36
+ // lightningcss leaves color-mix as an unparsed function and our
37
+ // per-scheme `unparsedToEntries` substitution path runs instead,
38
+ // producing the right rgba(...) for each scheme. Targets in this
39
+ // pipeline are otherwise unused — we never re-emit CSS from the AST.
40
+ };
41
+ /**
42
+ * Parses one source file's Tailwind usage into RN-ready style objects.
43
+ *
44
+ * Pipeline:
45
+ * 1. `@tailwindcss/oxide` Scanner finds every Tailwind candidate.
46
+ * 2. `@tailwindcss/node` compiler emits CSS for those candidates with
47
+ * theme tokens inlined.
48
+ * 3. lightningcss `transform` + typed visitor walks the emitted CSS:
49
+ * - `style` rules → per-class RN-style object.
50
+ * - `@keyframes` rules → per-name step map (Reanimated-ready).
51
+ * - `@property` rules → custom-property initial values.
52
+ *
53
+ * One instance holds one Scanner + one lazily-built compiler so repeated
54
+ * calls share upstream state. Theme CSS is fixed at construction — theme
55
+ * edits require a new parser.
56
+ */
57
+ class TailwindParser {
58
+ config;
59
+ scanner;
60
+ compiler;
61
+ themeSchemes;
62
+ schemeAliases;
63
+ /**
64
+ * Scheme names declared via `@custom-variant <name> …;`. A scheme
65
+ * listed here but absent from {@link themeSchemes} (no `@variant`
66
+ * override block) draws its values from the base `@theme` — the
67
+ * standard Tailwind v4 "light defaults + dark override" shape.
68
+ */
69
+ customVariantSchemes;
70
+ /**
71
+ * Memoise `resolveCandidates` results by candidate-list fingerprint.
72
+ * Fast Refresh hits this on every save: oxide's scan is cheap, but
73
+ * the LightningCSS visitor walk over the compiled CSS is ~2ms per
74
+ * file. A file whose `className` literals didn't change returns the
75
+ * SAME candidate set, so the second `parseAtoms` call returns the
76
+ * cached `ParsedOutput` — zero compile, zero visitor walk.
77
+ *
78
+ * Theme CSS changes build a new `TailwindParser` (from
79
+ * `getRnwindState` detecting the hash shift), so this cache is
80
+ * naturally invalidated — no stale-theme values leak through.
81
+ */
82
+ parseCache = new Map();
83
+ /**
84
+ * Build a parser bound to a theme CSS source. `@theme` and
85
+ * `@variant` blocks are extracted eagerly into a scheme table the
86
+ * visitor consults when resolving `var(--x)` references.
87
+ * @param config Parser configuration.
88
+ */
89
+ constructor(config) {
90
+ this.config = config;
91
+ this.themeSchemes = extractThemeVars(config.themeCss);
92
+ this.schemeAliases = extractSchemeAliases(config.themeCss);
93
+ this.customVariantSchemes = extractCustomVariantSchemes(config.themeCss);
94
+ this.scanner = new Scanner({ sources: config.sources ? [...config.sources] : [] });
95
+ }
96
+ /**
97
+ * Schemes declared by the user — the union of every `@custom-variant
98
+ * <name>` declaration and every `@variant <name>` block, or just
99
+ * `['base']` for themes without any. Used to decide how many
100
+ * per-scheme buckets the per-atom resolver fills. Exposed publicly so
101
+ * Metro integration can hand the names to the `.d.ts` generator
102
+ * without a full parse.
103
+ *
104
+ * Both sources matter. `@variant` blocks alone miss the common
105
+ * Tailwind v4 shape where the light palette sits in the base `@theme`
106
+ * and only `@variant dark` overrides it: there `light` exists solely
107
+ * as a `@custom-variant` and would otherwise be dropped, collapsing
108
+ * every themed atom to a single bucket that can't switch.
109
+ * `@custom-variant` order wins (it's where users enumerate their
110
+ * schemes); any `@variant`-only scheme is appended after.
111
+ * @returns Scheme names.
112
+ */
113
+ get declaredSchemes() {
114
+ const ordered = [];
115
+ const seen = new Set();
116
+ for (const name of this.customVariantSchemes) {
117
+ if (seen.has(name))
118
+ continue;
119
+ seen.add(name);
120
+ ordered.push(name);
121
+ }
122
+ for (const name of this.themeSchemes.keys()) {
123
+ if (name === BASE_SCHEME || seen.has(name))
124
+ continue;
125
+ seen.add(name);
126
+ ordered.push(name);
127
+ }
128
+ return ordered.length > 0 ? ordered : [BASE_SCHEME];
129
+ }
130
+ /**
131
+ * Build an effective var table for one scheme — base vars overridden by
132
+ * variant vars. When the scheme IS `'base'` (no variants declared), the
133
+ * base table is returned unchanged.
134
+ * @param scheme Scheme name.
135
+ * @returns Effective var name → value lookup for the scheme.
136
+ */
137
+ effectiveVars(scheme) {
138
+ const base = this.themeSchemes.get(BASE_SCHEME);
139
+ const variant = scheme === BASE_SCHEME ? undefined : this.themeSchemes.get(scheme);
140
+ if (!variant)
141
+ return base ?? new Map();
142
+ // eslint-disable-next-line unicorn/no-useless-collection-argument
143
+ const merged = new Map(base ?? []);
144
+ for (const [k, v] of variant)
145
+ merged.set(k, v);
146
+ return merged;
147
+ }
148
+ /**
149
+ * Build the Tailwind compiler on first use and cache it. The theme CSS
150
+ * gets a `theme(inline)` modifier on its `@import 'tailwindcss'` so
151
+ * lightningcss sees resolved colors/lengths instead of `var()` refs.
152
+ * @returns Cached compiler instance.
153
+ */
154
+ async ensureCompiler() {
155
+ if (this.compiler)
156
+ return this.compiler;
157
+ const ready = compileReadyTheme(this.config.themeCss, this.themeSchemes);
158
+ try {
159
+ this.compiler = await compile(withInlineTheme(ready), {
160
+ base: process.cwd(),
161
+ onDependency: () => { },
162
+ });
163
+ }
164
+ catch (error) {
165
+ throw wrapThemeError(error);
166
+ }
167
+ return this.compiler;
168
+ }
169
+ /**
170
+ * Parse one file's Tailwind usage into the full typed result.
171
+ * @param options Source content + extension.
172
+ * @param options.content Raw source text to scan for Tailwind candidates.
173
+ * @param options.extension File extension (`tsx`, `ts`, `jsx`, `js`) — feeds oxide's tokenizer.
174
+ * @returns RN atoms, keyframes, property defaults, candidates list.
175
+ */
176
+ async parseAtoms({ content, extension }) {
177
+ const candidates = this.scanner.getCandidatesWithPositions({ content, extension }).map((c) => c.candidate);
178
+ const fingerprint = fingerprintCandidates(candidates);
179
+ const cached = this.parseCache.get(fingerprint);
180
+ if (cached)
181
+ return cached;
182
+ const result = await this.resolveCandidates(candidates);
183
+ this.parseCache.set(fingerprint, result);
184
+ return result;
185
+ }
186
+ /**
187
+ * Scan every source file the Scanner was configured to watch via
188
+ * `sources` and resolve the union of candidates in one pass. Used by
189
+ * `UnionBuilder` at Metro startup (and on first worker access) to
190
+ * populate the complete atom registry before ANY per-file transform
191
+ * has run — so scheme files never ship a partial view of the theme.
192
+ *
193
+ * Hot-reload path uses `parseAtoms` for the per-file delta; this one
194
+ * only runs once per parser instance (and whenever the parser is
195
+ * rebuilt due to a theme CSS change).
196
+ * @returns Full RN atoms, keyframes, property defaults for every
197
+ * candidate discovered across the configured sources.
198
+ */
199
+ async parseProject() {
200
+ const candidates = this.scanner.scan();
201
+ return this.resolveCandidates(candidates);
202
+ }
203
+ /**
204
+ * Compile + typed-visit the given candidate class names. Shared
205
+ * implementation for both `parseAtoms` (single file) and
206
+ * `parseProject` (whole project).
207
+ * @param candidates Class-name candidates the oxide Scanner produced.
208
+ * @returns Fully-typed parser result.
209
+ */
210
+ async resolveCandidates(candidates) {
211
+ if (candidates.length === 0)
212
+ return emptyOutput();
213
+ const compiler = await this.ensureCompiler();
214
+ let css;
215
+ try {
216
+ css = compiler.build([...candidates]);
217
+ }
218
+ catch (error) {
219
+ throw wrapThemeError(error);
220
+ }
221
+ // Tailwind v4 emits opacity-suffixed themed colors as a pre-resolved
222
+ // sRGB fallback PLUS a `@supports`-gated var()-based override:
223
+ // border-color: color-mix(in srgb, #0A0A0A 20%, transparent);
224
+ // @supports (color: color-mix(in lab, red, red)) {
225
+ // border-color: color-mix(in oklab, var(--color-text) 20%, transparent);
226
+ // }
227
+ // Lightningcss takes the OUTER fallback (locked to whichever scheme
228
+ // the compiler resolved first), and our per-scheme substitution
229
+ // never gets a chance. Unwrap the @supports so the var()-based
230
+ // declaration overrides the fallback in the same rule — lightningcss
231
+ // emits the override as `unparsed` and the parser's themeVars-aware
232
+ // path produces correct rgba per scheme.
233
+ css = unwrapColorMixSupports(css);
234
+ // `compiler.build(candidates)` memoizes across calls — it returns CSS for
235
+ // every candidate the compiler has EVER seen in this process. To keep
236
+ // parser output pure per-call we restrict outputs to this call's
237
+ // candidates:
238
+ // - atoms: match class selectors against `wanted`.
239
+ // - keyframes: collect `animation-name` references during the style
240
+ // walk, then filter the visited keyframes to referenced names.
241
+ const wanted = new Set(candidates);
242
+ const schemes = this.declaredSchemes;
243
+ // Tailwind's compiled CSS contains every theme token — including
244
+ // ones imported from secondary CSS files (e.g. `@import
245
+ // 'rnwind/css'`). Pull them out of the `:root` block so
246
+ // `var(--duration-normal)` style references in unparsed declarations
247
+ // resolve to literal values (`220ms`) instead of being passed through
248
+ // to RN, which can't read CSS custom properties.
249
+ const compiledTheme = extractRootCustomProperties(css);
250
+ const schemeTables = new Map();
251
+ for (const scheme of schemes) {
252
+ const merged = new Map(compiledTheme);
253
+ for (const [k, v] of this.effectiveVars(scheme))
254
+ merged.set(k, v);
255
+ schemeTables.set(scheme, merged);
256
+ }
257
+ const atoms = new Map();
258
+ const keyframes = new Map();
259
+ const referencedKeyframes = new Set();
260
+ const propertyDefaults = new Map();
261
+ const gradientAtoms = new Map();
262
+ const hapticAtoms = new Map();
263
+ const breakpoints = new Map();
264
+ const { schemeAliases } = this;
265
+ try {
266
+ transform({
267
+ ...DEFAULT_TRANSFORM_OPTIONS,
268
+ filename: 'rnwind-virtual.css',
269
+ code: Buffer.from(css),
270
+ visitor: {
271
+ Rule: {
272
+ style(rule) {
273
+ for (const selector of rule.value.selectors) {
274
+ const className = classNameFromSelector(selector);
275
+ if (!className || !wanted.has(className))
276
+ continue;
277
+ processStyleRule(rule.value.declarations.declarations, className, { schemes, schemeTables, atoms, referencedKeyframes, schemeAliases, breakpoints }, rule.value.rules ?? []);
278
+ // Gradient atoms are detected per rule: the parser's main
279
+ // RN-style path drops the `--tw-gradient-*` customs as
280
+ // unsupported, but for gradient utilities we want to
281
+ // surface their role + resolved colour so the transformer
282
+ // can rewrite `<LinearGradient className="...">` into
283
+ // `colors={...}` / `start={...}` / `end={...}` props.
284
+ const gradient = detectGradientAtom(rule.value.declarations.declarations);
285
+ if (gradient)
286
+ gradientAtoms.set(className, gradient);
287
+ // Haptics may live on the rule directly OR inside a
288
+ // nested pseudo (e.g. `&:active` for `active:haptic-*`).
289
+ // Inspect both so `active:haptic-medium` registers.
290
+ const hapticDecls = [...rule.value.declarations.declarations];
291
+ for (const nested of rule.value.rules ?? [])
292
+ hapticDecls.push(...collectNestedDecls(nested));
293
+ const haptic = detectHapticAtom(hapticDecls);
294
+ if (haptic)
295
+ hapticAtoms.set(className, haptic);
296
+ }
297
+ },
298
+ keyframes(rule) {
299
+ const name = keyframesName(rule.value.name);
300
+ if (!name)
301
+ return;
302
+ const steps = [];
303
+ const baseTable = schemeTables.get(BASE_SCHEME) ?? schemeTables.get(schemes[0] ?? BASE_SCHEME);
304
+ for (const frame of rule.value.keyframes) {
305
+ const offset = keyframeSelectorOffset(frame.selectors);
306
+ if (!offset)
307
+ continue;
308
+ const style = {};
309
+ const frameDecls = frame.declarations.declarations ?? [];
310
+ for (const decl of frameDecls) {
311
+ for (const [key, value] of declarationToRnEntries(decl, baseTable))
312
+ style[key] = value;
313
+ }
314
+ steps.push({ offset, style });
315
+ }
316
+ keyframes.set(name, { name, steps });
317
+ },
318
+ property(rule) {
319
+ const initial = serializeInitialValue(rule.value.initialValue);
320
+ if (initial !== null)
321
+ propertyDefaults.set(rule.value.name, initial);
322
+ },
323
+ },
324
+ },
325
+ });
326
+ }
327
+ catch (error) {
328
+ throw wrapThemeError(error);
329
+ }
330
+ // Prune keyframes to those actually referenced by this call's atoms.
331
+ for (const name of keyframes.keys()) {
332
+ if (!referencedKeyframes.has(name))
333
+ keyframes.delete(name);
334
+ }
335
+ return { atoms, keyframes, propertyDefaults, gradientAtoms, hapticAtoms, candidates: [...candidates], schemes, breakpoints };
336
+ }
337
+ }
338
+ /**
339
+ * Wrap an error from `@tailwindcss/node`'s compiler or `lightningcss`'s
340
+ * transform with a `rnwind:` prefix so the user sees a clear "this came
341
+ * from your theme CSS" signal in Metro's stack trace. Preserves the
342
+ * original error as `cause` so downstream tooling (Sentry, Metro
343
+ * symbolication) can still inspect it.
344
+ * @param error Underlying error from the compiler or transform.
345
+ * @returns Prefixed Error with the original attached as `cause`.
346
+ */
347
+ function wrapThemeError(error) {
348
+ const message = error instanceof Error ? error.message : String(error);
349
+ const wrapped = new Error(`rnwind: failed to compile theme CSS — ${message}\n` +
350
+ `Check your global.css for unbalanced braces, unknown @utility / @variant declarations, ` +
351
+ `or unsupported color functions. Run \`bun run --cwd packages/rnwind code-check\` if this is the rnwind repo itself.`);
352
+ if (error instanceof Error)
353
+ wrapped.cause = error;
354
+ return wrapped;
355
+ }
356
+ /**
357
+ * Fingerprint a candidate list for `parseCache` lookup. Sorting gives
358
+ * the same key regardless of source order (the set of candidates is
359
+ * what drives `resolveCandidates`'s output, not their order).
360
+ * @param candidates Raw oxide-scanner output for one file.
361
+ * @returns Canonical string key.
362
+ */
363
+ function fingerprintCandidates(candidates) {
364
+ if (candidates.length === 0)
365
+ return '';
366
+ if (candidates.length === 1)
367
+ return candidates[0];
368
+ return [...candidates].toSorted((a, b) => a.localeCompare(b)).join('\0');
369
+ }
370
+ /**
371
+ * Empty sentinel returned when oxide finds no candidates in the file.
372
+ * @returns Zero-atom result with only the `base` scheme declared.
373
+ */
374
+ function emptyOutput() {
375
+ return {
376
+ atoms: new Map(),
377
+ keyframes: new Map(),
378
+ propertyDefaults: new Map(),
379
+ gradientAtoms: new Map(),
380
+ hapticAtoms: new Map(),
381
+ candidates: [],
382
+ schemes: [BASE_SCHEME],
383
+ breakpoints: new Map(),
384
+ };
385
+ }
386
+ /**
387
+ * Upgrade `@import 'tailwindcss'` (single- or double-quoted, with or
388
+ * without an existing `theme(...)` clause) to `@import 'tailwindcss'
389
+ * theme(inline)`. User-authored `theme(...)` clauses are preserved so
390
+ * overrides win.
391
+ * @param css Theme CSS source.
392
+ * @returns CSS with the Tailwind import upgraded.
393
+ */
394
+ function withInlineTheme(css) {
395
+ return css.replaceAll(/(@import\s+['"]tailwindcss['"])(?!\s*theme\()/g, '$1 theme(inline)');
396
+ }
397
+ /**
398
+ * Collect rule-local custom-property writes (`--tw-translate-x`,
399
+ * `--tw-scale-x`, `--tw-skew-y`, …). Tailwind v4 uses these as
400
+ * composable transform tokens that `translate: var(--tw-translate-x)
401
+ * var(--tw-translate-y)` then references. Surfacing them as theme vars
402
+ * lets the declaration converter resolve the references as if they
403
+ * were declared in `@theme`.
404
+ * @param decls All declarations from one lightningcss style rule.
405
+ * @returns Map from custom-property name (with leading `--`) to its raw value.
406
+ */
407
+ function collectRuleLocalVars(decls) {
408
+ const out = new Map();
409
+ for (const decl of decls) {
410
+ if (decl.property !== 'custom')
411
+ continue;
412
+ const custom = decl.value;
413
+ const rawName = typeof custom.name === 'string' ? custom.name : custom.name.name;
414
+ if (!rawName.startsWith('--tw-'))
415
+ continue;
416
+ if (!custom.value)
417
+ continue;
418
+ const text = serializeTokens(custom.value).trim();
419
+ if (text.length > 0)
420
+ out.set(rawName, text);
421
+ }
422
+ return out;
423
+ }
424
+ /**
425
+ * Process one matched style rule for a given class name: fold its
426
+ * declarations into the per-scheme buckets, detect referenced keyframes,
427
+ * then apply Tailwind's composable transform post-pass.
428
+ * @param declarations Declarations from one lightningcss style rule.
429
+ * @param className Class name this rule's selectors matched.
430
+ * @param ctx Parser-call-wide context (schemes, tables, output maps).
431
+ * @param nestedRules Nested style rules (Tailwind variant prefixes like `dark:`
432
+ * wrap their decls in `&:where(.<scheme>, .<scheme> *)` nested rules).
433
+ */
434
+ function processStyleRule(declarations, className, ctx, nestedRules = []) {
435
+ const bucket = ctx.atoms.get(className) ?? {};
436
+ const ruleLocalVars = collectRuleLocalVars(declarations);
437
+ const ruleSchemeTables = mergeRuleVars(ctx.schemeTables, ruleLocalVars);
438
+ // Detect active:/focus: prefix on the class name. Tailwind emits the
439
+ // actual decls inside `&:active { … }` / `&:focus { … }`, and the
440
+ // OUTER rule has zero direct decls — so the existing per-scheme phase
441
+ // below produces an empty bucket. We collect the inner decls into the
442
+ // base scheme and tag the bucket with `__state` so the style-builder
443
+ // can route the atom into `precomputeHoist`'s state buckets when the
444
+ // interactive state.
445
+ const interactiveState = classNameStateOf(className);
446
+ // Phase 1: outer declarations apply to EVERY scheme (unconditional
447
+ // utilities like `opacity-50`).
448
+ for (const decl of declarations) {
449
+ applyDeclarationToBucket(decl, bucket, ctx.schemes, ruleSchemeTables);
450
+ const animationRef = pickAnimationName(decl);
451
+ if (animationRef)
452
+ ctx.referencedKeyframes.add(animationRef);
453
+ }
454
+ applyComposedTransform(bucket, ctx.schemes, ruleLocalVars);
455
+ applyComposedShadow(bucket, ctx.schemes, ruleLocalVars);
456
+ applyComposedRing(bucket, ctx.schemes, ruleLocalVars);
457
+ // Phase 2: nested rules — three orthogonal flavours, dispatched on
458
+ // the lightningcss node `type`:
459
+ // - `media`: Tailwind v4 responsive variants (`sm:`, `md:`, …) wrap
460
+ // declarations in `@media (width >= Xrem)`. Decls fold into every
461
+ // scheme; the atom's name carries the breakpoint association so
462
+ // the runtime can gate it on `windowWidth`.
463
+ // - `style` + `interactiveState`: `&:active` / `&:focus` pseudo
464
+ // decls (every scheme — interaction is theme-orthogonal).
465
+ // - `style` (default): Tailwind v4 scheme variants
466
+ // (`&:where(.<scheme>, .<scheme> *)`).
467
+ for (const scheme of ctx.schemes)
468
+ bucket[scheme] = bucket[scheme] ?? {};
469
+ for (const nested of nestedRules) {
470
+ const node = nested;
471
+ if (node?.type === 'media') {
472
+ applyMediaRule(nested, className, bucket, ctx, ruleSchemeTables, ruleLocalVars);
473
+ continue;
474
+ }
475
+ if (interactiveState) {
476
+ applyInteractiveNestedRule(nested, bucket, ctx, ruleSchemeTables, ruleLocalVars);
477
+ }
478
+ else {
479
+ applyNestedSchemeRule(nested, bucket, ctx, ruleSchemeTables, ruleLocalVars);
480
+ }
481
+ }
482
+ normalizeLineHeightToPx(bucket, ctx.schemes);
483
+ if (interactiveState)
484
+ bucket.__state = interactiveState;
485
+ ctx.atoms.set(className, bucket);
486
+ }
487
+ /** Recognised interactive variant prefixes. RN can drive both at runtime. */
488
+ const INTERACTIVE_PREFIXES = new Set(['active', 'focus']);
489
+ /**
490
+ * Inspect a class name for a leading interactive variant prefix. The
491
+ * Tailwind compiler emits `active:bg-sky-700` literally (backslash-
492
+ * escaped for CSS), and that name is the same string the parser sees.
493
+ * So a cheap `split(':')` on the head is enough — no need to parse the
494
+ * pseudo-class out of the selector itself.
495
+ * @param className Tailwind utility class (e.g. `active:bg-sky-700`).
496
+ * @returns The interactive state prefix (`'active'` / `'focus'`), or null when none.
497
+ */
498
+ function classNameStateOf(className) {
499
+ const colon = className.indexOf(':');
500
+ if (colon === -1)
501
+ return null;
502
+ const prefix = className.slice(0, colon);
503
+ if (!INTERACTIVE_PREFIXES.has(prefix))
504
+ return null;
505
+ return prefix;
506
+ }
507
+ /**
508
+ * Pull the `min-width` threshold (px) out of a `@media (width >= Xrem)`
509
+ * lightningcss node. Tailwind v4 emits exactly this shape for every
510
+ * responsive variant — `>= 40rem` for `sm`, `>= 48rem` for `md`, etc.
511
+ * Returns null for any other media condition (`hover`, `prefers-*`,
512
+ * range bounds we don't model) so the caller can skip non-responsive
513
+ * media wrappers without breaking.
514
+ * @param nested A lightningcss `media` rule node.
515
+ * @returns Threshold in px, or null when the condition isn't a simple
516
+ * width-min check.
517
+ */
518
+ function readMediaMinWidthPx(nested) {
519
+ if (typeof nested !== 'object' || nested === null)
520
+ return null;
521
+ const node = nested;
522
+ if (node.type !== 'media')
523
+ return null;
524
+ const queries = node.value?.query?.mediaQueries;
525
+ if (queries?.length !== 1)
526
+ return null;
527
+ const query = queries[0];
528
+ const { condition } = query;
529
+ if (condition?.type !== 'feature')
530
+ return null;
531
+ const feature = condition.value;
532
+ if (feature?.type !== 'range' || feature.name !== 'width')
533
+ return null;
534
+ if (feature.operator !== 'greater-than-equal')
535
+ return null;
536
+ const length = feature.value;
537
+ if (length?.type !== 'length')
538
+ return null;
539
+ const inner = length.value?.value;
540
+ if (!inner || typeof inner.value !== 'number')
541
+ return null;
542
+ if (inner.unit === 'rem')
543
+ return inner.value * 16;
544
+ if (inner.unit === 'px')
545
+ return inner.value;
546
+ return null;
547
+ }
548
+ /**
549
+ * Pull the leading `prefix:` segment off a className. `md:bg-red-500`
550
+ * → `'md'`. Returns null for atoms without a colon (the common case)
551
+ * or for empty prefixes.
552
+ * @param className Atom name.
553
+ * @returns Prefix or null.
554
+ */
555
+ function leadingPrefix(className) {
556
+ const colon = className.indexOf(':');
557
+ if (colon <= 0)
558
+ return null;
559
+ return className.slice(0, colon);
560
+ }
561
+ /**
562
+ * Fold a Tailwind v4 responsive `@media (width >= Xrem)` nested rule
563
+ * into every scheme's bucket and record the breakpoint threshold
564
+ * (`md` → 768) on the parser-call context. The runtime later gates the
565
+ * atom on `windowWidth` against this threshold via the prefix on the
566
+ * atom's class name.
567
+ *
568
+ * Read directly from the media condition rather than from compiled
569
+ * `--breakpoint-*` `:root` tokens because Tailwind's `theme(inline)`
570
+ * mode strips those — the only authoritative source for the actual
571
+ * thresholds Tailwind generated is the `@media` query itself.
572
+ * @param nested One nested `media` node from `rule.value.rules`.
573
+ * @param className Outer rule's class name (carries the breakpoint prefix).
574
+ * @param bucket Per-scheme style map for the atom.
575
+ * @param ctx Parser-call-wide context.
576
+ * @param ruleSchemeTables Per-scheme var tables (outer rule's merged table).
577
+ * @param ruleLocalVars Outer rule's `--tw-*` vars (inherited for inner decls).
578
+ */
579
+ function applyMediaRule(nested, className, bucket, ctx, ruleSchemeTables, ruleLocalVars) {
580
+ const decls = collectNestedDecls(nested);
581
+ if (decls.length === 0)
582
+ return;
583
+ const minWidth = readMediaMinWidthPx(nested);
584
+ const prefix = leadingPrefix(className);
585
+ if (minWidth !== null && prefix !== null)
586
+ ctx.breakpoints.set(prefix, minWidth);
587
+ for (const scheme of ctx.schemes) {
588
+ const table = ruleSchemeTables.get(scheme);
589
+ const schemeBucket = bucket[scheme] ?? {};
590
+ for (const decl of decls) {
591
+ for (const [key, value] of declarationToRnEntries(decl, table))
592
+ schemeBucket[key] = value;
593
+ const animationRef = pickAnimationName(decl);
594
+ if (animationRef)
595
+ ctx.referencedKeyframes.add(animationRef);
596
+ }
597
+ const nestedLocalVars = new Map(ruleLocalVars);
598
+ for (const [k, v] of collectRuleLocalVars(decls))
599
+ nestedLocalVars.set(k, v);
600
+ applyComposedTransformToScheme(schemeBucket, nestedLocalVars);
601
+ applyComposedShadowToScheme(schemeBucket, nestedLocalVars);
602
+ bucket[scheme] = schemeBucket;
603
+ }
604
+ }
605
+ /**
606
+ * Fold one nested rule from an interactive (`active:` / `focus:`) atom
607
+ * into every scheme's bucket. The Tailwind output for `active:bg-sky-700`
608
+ * is `.active\:bg-sky-700 { &:active { background-color: oklch(...) } }`
609
+ * — the outer rule has zero
610
+ * decls; the leaf lives three levels deep through pseudo + media. We
611
+ * unwrap the pseudo and the media shell, take the inner decls, and
612
+ * apply them across every scheme — interactivity is orthogonal to
613
+ * theme. The bucket gets `__state: 'active' | 'focus'` set elsewhere.
614
+ * @param nested One nested-rule node from `rule.value.rules`.
615
+ * @param bucket Per-scheme style map for the atom.
616
+ * @param ctx Parser-call-wide context.
617
+ * @param ruleSchemeTables Per-scheme var tables (outer rule's merged table).
618
+ * @param ruleLocalVars Outer rule's `--tw-*` vars (inherited for inner decls).
619
+ */
620
+ function applyInteractiveNestedRule(nested, bucket, ctx, ruleSchemeTables, ruleLocalVars) {
621
+ const decls = collectNestedDecls(nested);
622
+ if (decls.length === 0)
623
+ return;
624
+ for (const scheme of ctx.schemes) {
625
+ const table = ruleSchemeTables.get(scheme);
626
+ const schemeBucket = bucket[scheme] ?? {};
627
+ for (const decl of decls) {
628
+ for (const [key, value] of declarationToRnEntries(decl, table))
629
+ schemeBucket[key] = value;
630
+ const animationRef = pickAnimationName(decl);
631
+ if (animationRef)
632
+ ctx.referencedKeyframes.add(animationRef);
633
+ }
634
+ const nestedLocalVars = new Map(ruleLocalVars);
635
+ for (const [k, v] of collectRuleLocalVars(decls))
636
+ nestedLocalVars.set(k, v);
637
+ applyComposedTransformToScheme(schemeBucket, nestedLocalVars);
638
+ applyComposedShadowToScheme(schemeBucket, nestedLocalVars);
639
+ bucket[scheme] = schemeBucket;
640
+ }
641
+ }
642
+ /**
643
+ * Recursively flatten a nested style/media rule into its leaf
644
+ * declarations. Tailwind wraps interactive pseudo decls in `style`
645
+ * nodes (`&:active`, `&:focus`); the walk unwraps them and any
646
+ * `@media` shell around them.
647
+ * @param nested A nested rule node.
648
+ * @returns Every declaration found in the nested subtree.
649
+ */
650
+ function collectNestedDecls(nested) {
651
+ if (typeof nested !== 'object' || nested === null)
652
+ return [];
653
+ const node = nested;
654
+ if (!node.value)
655
+ return [];
656
+ // `style` (with `&:active`) and `media` wrappers both surface the
657
+ // actual decls. lightningcss surfaces inner-only declarations as a
658
+ // `nested-declarations` node — flatten that too.
659
+ if (node.type === 'nested-declarations')
660
+ return [...(node.value.declarations?.declarations ?? [])];
661
+ if (node.type !== 'style' && node.type !== 'media')
662
+ return [];
663
+ const out = [...(node.value.declarations?.declarations ?? [])];
664
+ for (const child of node.value.rules ?? [])
665
+ out.push(...collectNestedDecls(child));
666
+ return out;
667
+ }
668
+ /**
669
+ * Fold one nested style rule (Tailwind's `&:where(.<scheme>, .<scheme> *)`
670
+ * pattern) into the scheme bucket its selector targets. Rules we can't
671
+ * attribute to a single scheme are skipped — they'd only ever reach the
672
+ * bucket via CSS cascading in a browser, which doesn't translate to RN.
673
+ * @param nested One nested-rule node from `rule.value.rules`.
674
+ * @param bucket Per-scheme style map for the atom.
675
+ * @param ctx Parser-call-wide context.
676
+ * @param ruleSchemeTables Per-scheme var tables (outer rule's merged table).
677
+ * @param ruleLocalVars Outer rule's `--tw-*` vars (inherited for inner decls).
678
+ */
679
+ function applyNestedSchemeRule(nested, bucket, ctx, ruleSchemeTables, ruleLocalVars) {
680
+ if (typeof nested !== 'object' || nested === null)
681
+ return;
682
+ const node = nested;
683
+ if (node.type !== 'style' || !node.value)
684
+ return;
685
+ const targetScheme = detectNestedScheme(node.value.selectors, ctx.schemes, ctx.schemeAliases);
686
+ if (!targetScheme)
687
+ return;
688
+ const innerDecls = node.value.declarations?.declarations ?? [];
689
+ const table = ruleSchemeTables.get(targetScheme);
690
+ const schemeBucket = bucket[targetScheme] ?? {};
691
+ for (const decl of innerDecls) {
692
+ for (const [key, value] of declarationToRnEntries(decl, table))
693
+ schemeBucket[key] = value;
694
+ const animationRef = pickAnimationName(decl);
695
+ if (animationRef)
696
+ ctx.referencedKeyframes.add(animationRef);
697
+ }
698
+ // Apply the composed-transform + shadow post-passes to just this one
699
+ // scheme, so nested `--tw-*` custom prop writes compose correctly.
700
+ const nestedLocalVars = new Map(ruleLocalVars);
701
+ for (const [k, v] of collectRuleLocalVars(innerDecls))
702
+ nestedLocalVars.set(k, v);
703
+ applyComposedTransformToScheme(schemeBucket, nestedLocalVars);
704
+ applyComposedShadowToScheme(schemeBucket, nestedLocalVars);
705
+ bucket[targetScheme] = schemeBucket;
706
+ }
707
+ /**
708
+ * Detect which scheme a nested `&:where(.<scheme>, .<scheme> *)`
709
+ * selector targets. Recognises both the rnwind-default literal class
710
+ * (`.dark`) and any user-declared `@custom-variant` selector class
711
+ * (`.scheme-dark`), via the `aliases` map built from the theme CSS.
712
+ * @param selectors Nested rule's selector lists.
713
+ * @param schemes Declared scheme names.
714
+ * @param aliases Class-name → scheme-name map from `@custom-variant` decls.
715
+ * @returns Matching scheme name, or null when the selector isn't scheme-scoped.
716
+ */
717
+ function detectNestedScheme(selectors, schemes, aliases) {
718
+ const known = new Set(schemes);
719
+ for (const selector of selectors) {
720
+ const found = findSchemeInSelector(selector, schemes, known, aliases);
721
+ if (found)
722
+ return found;
723
+ }
724
+ return null;
725
+ }
726
+ /**
727
+ * Inner half of {@link detectNestedScheme} — extracted so each function
728
+ * stays under the cognitive-complexity cap.
729
+ * @param selector One compound selector (sequence of simple parts).
730
+ * @param schemes Declared scheme names (for recursion).
731
+ * @param known Set form of `schemes` for O(1) lookups.
732
+ * @param aliases Class-name → scheme-name map from `@custom-variant` decls.
733
+ * @returns Matching scheme name, or null.
734
+ */
735
+ function findSchemeInSelector(selector, schemes, known, aliases) {
736
+ for (const part of selector) {
737
+ const direct = matchSchemeClass(part, known, aliases);
738
+ if (direct)
739
+ return direct;
740
+ const nested = matchSchemeInWhere(part, schemes, aliases);
741
+ if (nested)
742
+ return nested;
743
+ }
744
+ return null;
745
+ }
746
+ /**
747
+ * Match a `.scheme` class part against the declared schemes (literal
748
+ * match) or against the `@custom-variant` alias map (e.g. `.scheme-dark`
749
+ * → `dark`).
750
+ * @param part One simple selector part.
751
+ * @param known Declared scheme names.
752
+ * @param aliases Class-name → scheme-name map from `@custom-variant` decls.
753
+ * @returns Matching scheme name, or null.
754
+ */
755
+ function matchSchemeClass(part, known, aliases) {
756
+ if (typeof part !== 'object' || part === null)
757
+ return null;
758
+ const node = part;
759
+ if (node.type !== 'class')
760
+ return null;
761
+ if (typeof node.name !== 'string')
762
+ return null;
763
+ if (known.has(node.name))
764
+ return node.name;
765
+ return aliases.get(node.name) ?? null;
766
+ }
767
+ /**
768
+ * Match a `:where(.scheme, …)` pseudo-class wrapper and recurse into
769
+ * its inner selectors.
770
+ * @param part One simple selector part.
771
+ * @param schemes Declared scheme names.
772
+ * @param aliases Class-name → scheme-name map from `@custom-variant` decls.
773
+ * @returns Matching scheme name from inside the `where`, or null.
774
+ */
775
+ function matchSchemeInWhere(part, schemes, aliases) {
776
+ if (typeof part !== 'object' || part === null)
777
+ return null;
778
+ const node = part;
779
+ if (node.type !== 'pseudo-class' || node.kind !== 'where' || !node.selectors)
780
+ return null;
781
+ return detectNestedScheme(node.selectors, schemes, aliases);
782
+ }
783
+ /**
784
+ * Per-scheme version of `applyComposedTransform` — synthesize a
785
+ * `transform` array into a SINGLE scheme's style from its rule-local
786
+ * `--tw-*` vars.
787
+ * @param style Scheme-specific style map.
788
+ * @param ruleLocalVars Combined outer+nested `--tw-*` vars.
789
+ */
790
+ function applyComposedTransformToScheme(style, ruleLocalVars) {
791
+ const composed = composeTransformFromVars(ruleLocalVars);
792
+ if (composed.length === 0)
793
+ return;
794
+ delete style.translate;
795
+ delete style.scale;
796
+ delete style.rotate;
797
+ style.transform = composed;
798
+ }
799
+ /**
800
+ * Per-scheme version of `applyComposedShadow` — synthesize RN shadow
801
+ * longhands into a SINGLE scheme's style from its `--tw-shadow` custom
802
+ * prop.
803
+ * @param style Scheme-specific style map.
804
+ * @param ruleLocalVars Combined outer+nested `--tw-*` vars.
805
+ */
806
+ function applyComposedShadowToScheme(style, ruleLocalVars) {
807
+ const rawShadow = ruleLocalVars.get('--tw-shadow');
808
+ const rawShadowColor = ruleLocalVars.get('--tw-shadow-color');
809
+ if (!rawShadow && rawShadowColor) {
810
+ const color = resolveCustomColorString(rawShadowColor);
811
+ if (!color)
812
+ return;
813
+ delete style.boxShadow;
814
+ style.shadowColor = color;
815
+ return;
816
+ }
817
+ if (!rawShadow)
818
+ return;
819
+ const shadow = parseFirstShadow(rawShadow);
820
+ if (!shadow)
821
+ return;
822
+ delete style.boxShadow;
823
+ style.shadowColor = shadow.color;
824
+ style.shadowOffset = { width: shadow.x, height: shadow.y };
825
+ style.shadowOpacity = shadow.opacity;
826
+ style.shadowRadius = shadow.blur;
827
+ style.elevation = Math.max(1, Math.min(24, Math.round(Math.max(shadow.y, shadow.blur / 2))));
828
+ }
829
+ /**
830
+ * Tailwind v4's `shadow-*` utilities write a `--tw-shadow` custom prop
831
+ * holding the actual `<x> <y> <blur> <spread> <color>` shadow values,
832
+ * then a `box-shadow: var(--tw-shadow)` declaration RN can't use. This
833
+ * post-pass parses `--tw-shadow` and emits the RN shadow longhands —
834
+ * `shadowColor` / `shadowOffset` / `shadowOpacity` / `shadowRadius` /
835
+ * `elevation` — so iOS and Android both render the shadow.
836
+ * @param bucket Per-scheme style map for the atom.
837
+ * @param schemes Scheme names active for this parse.
838
+ * @param ruleLocalVars Rule-local `--tw-*` vars.
839
+ */
840
+ function applyComposedShadow(bucket, schemes, ruleLocalVars) {
841
+ const rawShadow = ruleLocalVars.get('--tw-shadow');
842
+ const rawShadowColor = ruleLocalVars.get('--tw-shadow-color');
843
+ // Color-only utility (`shadow-red-50`, `shadow-gray-200`, …): emit
844
+ // `shadowColor` + `shadowOpacity: 1` so the explicit color overrides
845
+ // the size utility's 0.1 alpha fallback (matches Tailwind v4 web,
846
+ // where setting `--tw-shadow-color` swaps in a solid color). Offset /
847
+ // blur / elevation come from the partner size utility's atom.
848
+ if (!rawShadow && rawShadowColor) {
849
+ const color = resolveCustomColorString(rawShadowColor);
850
+ if (!color)
851
+ return;
852
+ for (const scheme of schemes) {
853
+ const style = bucket[scheme] ?? {};
854
+ delete style.boxShadow;
855
+ style.shadowColor = color;
856
+ bucket[scheme] = style;
857
+ }
858
+ return;
859
+ }
860
+ if (!rawShadow)
861
+ return;
862
+ const shadow = parseFirstShadow(rawShadow);
863
+ if (!shadow)
864
+ return;
865
+ for (const scheme of schemes) {
866
+ const style = bucket[scheme] ?? {};
867
+ delete style.boxShadow;
868
+ style.shadowColor = shadow.color;
869
+ style.shadowOffset = { width: shadow.x, height: shadow.y };
870
+ style.shadowOpacity = shadow.opacity;
871
+ style.shadowRadius = shadow.blur;
872
+ style.elevation = Math.max(1, Math.min(24, Math.round(Math.max(shadow.y, shadow.blur / 2))));
873
+ bucket[scheme] = style;
874
+ }
875
+ }
876
+ /**
877
+ * Synthesize RN ring styles from Tailwind's `--tw-ring-color` /
878
+ * `--tw-ring-shadow` composable custom props. RN has no native ring;
879
+ * we approximate by writing `borderColor` + `borderWidth` so the
880
+ * outline is visible. Atoms that ALREADY set borderColor (e.g. paired
881
+ * with `border-2`) keep their value — the ring just won't override.
882
+ * @param bucket Per-scheme style map for the atom.
883
+ * @param schemes Scheme names active for this parse.
884
+ * @param ruleLocalVars Rule-local `--tw-*` vars.
885
+ */
886
+ function applyComposedRing(bucket, schemes, ruleLocalVars) {
887
+ const ringColor = ruleLocalVars.get('--tw-ring-color');
888
+ if (!ringColor)
889
+ return;
890
+ const color = resolveCustomColorString(ringColor);
891
+ if (!color)
892
+ return;
893
+ for (const scheme of schemes) {
894
+ const style = bucket[scheme] ?? {};
895
+ if (!('borderColor' in style))
896
+ style.borderColor = color;
897
+ bucket[scheme] = style;
898
+ }
899
+ }
900
+ /**
901
+ * Resolve a CSS color string (`oklch(0.971 0.013 17.38)`, `#ff0000`,
902
+ * `rgb(0 0 0 / 0.1)`) to the hex string RN's `shadowColor` accepts.
903
+ * Wraps culori's parser via {@link parseCssColorToHex}.
904
+ * @param raw Raw color text from a `--tw-shadow-color` custom prop.
905
+ * @returns `#rrggbb` string, or null when culori can't parse it.
906
+ */
907
+ function resolveCustomColorString(raw) {
908
+ const text = unwrapVariableFallback(raw).trim();
909
+ if (text.length === 0)
910
+ return null;
911
+ if (text.startsWith('#'))
912
+ return text;
913
+ return parseCssColorToHex(text);
914
+ }
915
+ /**
916
+ * Parse any CSS color expression into an `#rrggbb` string via culori.
917
+ * Falls back to null when culori doesn't recognize the format.
918
+ * @param text CSS color value.
919
+ * @returns Hex string, or null.
920
+ */
921
+ function parseCssColorToHex(text) {
922
+ return formatHexSafe(text);
923
+ }
924
+ /**
925
+ * Format a CSS color via culori.
926
+ * @param text CSS color value.
927
+ * @returns `#rrggbb` string when culori succeeds, else null.
928
+ */
929
+ function formatHexSafe(text) {
930
+ try {
931
+ const hex = formatHex(text);
932
+ return typeof hex === 'string' ? hex : null;
933
+ }
934
+ catch {
935
+ return null;
936
+ }
937
+ }
938
+ /**
939
+ * Parse the first shadow from a `--tw-shadow` custom-property value.
940
+ * The value is a comma-separated list of shadows; each shadow is
941
+ * `<x> <y> <blur> <spread> <color>`. RN renders only one shadow per
942
+ * view, so we keep the first.
943
+ * @param raw Raw `--tw-shadow` text (post-substitution).
944
+ * @returns Parsed shadow, or null when the shape is unrecognized.
945
+ */
946
+ function parseFirstShadow(raw) {
947
+ // Split on top-level commas (parens-aware) so colors like `rgba(0,0,0,0.5)`
948
+ // don't fragment the list.
949
+ const head = topLevelSplit(raw, ',')[0]?.trim();
950
+ if (!head)
951
+ return null;
952
+ const { lengths, remainder } = extractShadowLengths(head);
953
+ const [x = 0, y = 0, blur = 0, spread = 0] = lengths;
954
+ const { color, opacity } = parseShadowColor(remainder.trim());
955
+ return { x, y, blur, spread, color, opacity };
956
+ }
957
+ /**
958
+ * Pull the first 4 numeric tokens out of a shadow expression and return
959
+ * them alongside the remaining text (which is the color expression).
960
+ * Shadow shape: `<x> <y> <blur> <spread> <color>` — tokens may be bare
961
+ * (`0`), px-dimensioned (`1px`), or rem/em/%.
962
+ * @param head Single shadow expression (one comma-separated entry).
963
+ * @returns Pixel lengths + the remainder text (color expression).
964
+ */
965
+ function extractShadowLengths(head) {
966
+ // Take ONLY the leading run of length tokens, stopping at the first
967
+ // non-length token (the color). A previous global digit-regex scanned
968
+ // the whole string, so a <4-length shadow like `0 1px 1px rgb(0 0 0 /
969
+ // 0.05)` stole a digit out of the color expression — corrupting the
970
+ // alpha (opacity) or a digit-leading hex. Whitespace-splitting can't
971
+ // reach inside the color because we break as soon as a token isn't a
972
+ // bare/`px`/`rem`/`em`/`%` length.
973
+ // Unambiguous integer-or-decimal (no `\d*\.?\d+` overlap) so there's no
974
+ // super-linear backtracking on long digit runs.
975
+ const isLength = /^-?(?:\d+(?:\.\d+)?|\.\d+)(?:px|rem|em|%)?$/;
976
+ const parts = head.split(/\s+/);
977
+ const lengths = [];
978
+ let index = 0;
979
+ while (index < parts.length && lengths.length < 4 && isLength.test(parts[index])) {
980
+ lengths.push(parseLengthToken(parts[index]));
981
+ index += 1;
982
+ }
983
+ return { lengths, remainder: parts.slice(index).join(' ') };
984
+ }
985
+ /**
986
+ * Coerce one shadow length token into a pixel number. Accepts bare
987
+ * integers (`0`), `Npx`, and `Nrem` — every shape Tailwind's
988
+ * `--tw-shadow` value uses.
989
+ * @param token Token text.
990
+ * @returns Pixel number.
991
+ */
992
+ function parseLengthToken(token) {
993
+ if (token.endsWith('rem'))
994
+ return Number(token.slice(0, -3)) * 16;
995
+ if (token.endsWith('em'))
996
+ return Number(token.slice(0, -2)) * 16;
997
+ if (token.endsWith('px'))
998
+ return Number(token.slice(0, -2));
999
+ if (token.endsWith('%'))
1000
+ return Number(token.slice(0, -1));
1001
+ return Number(token);
1002
+ }
1003
+ /**
1004
+ * Extract a color string + extracted alpha from a shadow's color
1005
+ * expression. Supports `rgb(...)` / `rgba(...)` / `#rrggbb` / hex with
1006
+ * alpha / theme-resolved CSS color strings.
1007
+ * @param expr Color expression text.
1008
+ * @returns Color string for `shadowColor` + alpha for `shadowOpacity`.
1009
+ */
1010
+ function parseShadowColor(expr) {
1011
+ const working = unwrapVariableFallback(expr).trim();
1012
+ if (working.length === 0)
1013
+ return { color: '#000', opacity: 0.1 };
1014
+ const rgba = parseRgbaExpression(working);
1015
+ if (rgba)
1016
+ return rgba;
1017
+ if (working.startsWith('#'))
1018
+ return { color: working, opacity: 1 };
1019
+ return { color: '#000', opacity: 0.1 };
1020
+ }
1021
+ /**
1022
+ * Strip the `var(--name, fallback)` wrapper from a CSS value. Tailwind
1023
+ * wraps shadow colors as `var(--tw-shadow-color, rgb(0 0 0 / 0.1))`,
1024
+ * and when the var is unresolved we want the fallback.
1025
+ * @param expr Raw CSS value.
1026
+ * @returns Inner fallback when wrapped, otherwise the input unchanged.
1027
+ */
1028
+ function unwrapVariableFallback(expr) {
1029
+ const trimmed = expr.trim();
1030
+ if (!trimmed.startsWith('var(') || !trimmed.endsWith(')'))
1031
+ return trimmed;
1032
+ const inner = trimmed.slice(4, -1);
1033
+ let depth = 0;
1034
+ for (let index = 0; index < inner.length; index += 1) {
1035
+ const ch = inner[index];
1036
+ if (ch === '(')
1037
+ depth += 1;
1038
+ else if (ch === ')')
1039
+ depth -= 1;
1040
+ else if (ch === ',' && depth === 0)
1041
+ return inner.slice(index + 1);
1042
+ }
1043
+ return trimmed;
1044
+ }
1045
+ /**
1046
+ * Parse an `rgb(r g b)` / `rgba(r,g,b,a)` / `rgb(r g b / a)` color
1047
+ * expression into a hex + alpha pair. Returns `null` when the shape
1048
+ * doesn't match.
1049
+ * @param text Expression text (already trimmed and unwrapped).
1050
+ * @returns Hex color + alpha, or null.
1051
+ */
1052
+ function parseRgbaExpression(text) {
1053
+ const head = /^rgba?\(([^)]+)\)$/i.exec(text);
1054
+ if (!head)
1055
+ return null;
1056
+ const inner = head[1].replaceAll(',', ' ').replaceAll('/', ' ');
1057
+ const tokens = inner.split(/\s+/).filter((part) => part.length > 0);
1058
+ if (tokens.length < 3)
1059
+ return null;
1060
+ const [r, g, b, alphaText] = tokens;
1061
+ let opacity = 1;
1062
+ if (typeof alphaText === 'string') {
1063
+ opacity = alphaText.endsWith('%') ? Number(alphaText.slice(0, -1)) / 100 : Number(alphaText);
1064
+ }
1065
+ const hex = `#${[r, g, b]
1066
+ .map((n) => Math.max(0, Math.min(255, Math.round(Number(n))))
1067
+ .toString(16)
1068
+ .padStart(2, '0'))
1069
+ .join('')}`;
1070
+ return { color: hex, opacity };
1071
+ }
1072
+ /**
1073
+ * Split `text` at top-level occurrences of `delimiter`, treating
1074
+ * parentheses as nesting. Used to safely split shadow lists without
1075
+ * fragmenting `rgb(0, 0, 0, 0.5)` on its commas.
1076
+ * @param text Source text.
1077
+ * @param delimiter Single-character delimiter to split on.
1078
+ * @returns Parts of the text between top-level delimiters.
1079
+ */
1080
+ function topLevelSplit(text, delimiter) {
1081
+ const parts = [];
1082
+ let depth = 0;
1083
+ let start = 0;
1084
+ for (let index = 0; index < text.length; index += 1) {
1085
+ const ch = text[index];
1086
+ if (ch === '(')
1087
+ depth += 1;
1088
+ else if (ch === ')')
1089
+ depth -= 1;
1090
+ else if (ch === delimiter && depth === 0) {
1091
+ parts.push(text.slice(start, index));
1092
+ start = index + 1;
1093
+ }
1094
+ }
1095
+ parts.push(text.slice(start));
1096
+ return parts;
1097
+ }
1098
+ /**
1099
+ * Tailwind v4's `text-*` utilities emit `line-height` as a unitless
1100
+ * multiplier (`calc(2.5 / 2.25)` for `text-4xl`) that the browser
1101
+ * resolves against the element's `font-size`. RN's `lineHeight` is
1102
+ * always pixels — so when both `fontSize` and a multiplier-shaped
1103
+ * `lineHeight` (less than 10) land on the same atom, multiply through
1104
+ * to a pixel value. Atoms with only one of the two are left alone.
1105
+ * @param bucket Per-scheme style map for the atom.
1106
+ * @param schemes Scheme names active for this parse.
1107
+ */
1108
+ function normalizeLineHeightToPx(bucket, schemes) {
1109
+ for (const scheme of schemes) {
1110
+ const style = bucket[scheme];
1111
+ if (!style)
1112
+ continue;
1113
+ const { fontSize } = style;
1114
+ const { lineHeight } = style;
1115
+ if (typeof fontSize !== 'number' || typeof lineHeight !== 'number')
1116
+ continue;
1117
+ if (lineHeight >= 10)
1118
+ continue;
1119
+ style.lineHeight = Math.round(fontSize * lineHeight * 10_000) / 10_000;
1120
+ }
1121
+ }
1122
+ /**
1123
+ * Fold one declaration's resolved entries into every scheme's bucket on
1124
+ * the target atom.
1125
+ * @param decl Lightningcss declaration to convert.
1126
+ * @param bucket Per-scheme style map for the atom.
1127
+ * @param schemes Scheme names active for this parse.
1128
+ * @param ruleSchemeTables Per-scheme var tables (with rule-local overrides folded in).
1129
+ */
1130
+ function applyDeclarationToBucket(decl, bucket, schemes, ruleSchemeTables) {
1131
+ for (const scheme of schemes) {
1132
+ const schemeBucket = bucket[scheme] ?? {};
1133
+ for (const [key, value] of declarationToRnEntries(decl, ruleSchemeTables.get(scheme))) {
1134
+ schemeBucket[key] = value;
1135
+ }
1136
+ bucket[scheme] = schemeBucket;
1137
+ }
1138
+ }
1139
+ /**
1140
+ * Apply the composed-transform post-pass for a single atom: if any of
1141
+ * Tailwind's `--tw-translate-*` / `--tw-scale-*` / `--tw-skew-*` vars
1142
+ * were written, synthesize a single `transform` array and drop the
1143
+ * intermediate `translate`/`scale`/`rotate` shorthand entries.
1144
+ * @param bucket Per-scheme style map for the atom.
1145
+ * @param schemes Scheme names active for this parse.
1146
+ * @param ruleLocalVars Rule-local `--tw-*` vars.
1147
+ */
1148
+ function applyComposedTransform(bucket, schemes, ruleLocalVars) {
1149
+ const composed = composeTransformFromVars(ruleLocalVars);
1150
+ if (composed.length === 0)
1151
+ return;
1152
+ for (const scheme of schemes) {
1153
+ const schemeBucket = bucket[scheme] ?? {};
1154
+ delete schemeBucket.translate;
1155
+ delete schemeBucket.scale;
1156
+ delete schemeBucket.rotate;
1157
+ schemeBucket.transform = composed;
1158
+ bucket[scheme] = schemeBucket;
1159
+ }
1160
+ }
1161
+ /**
1162
+ * Synthesize an RN `transform` array from Tailwind v4's composable
1163
+ * `--tw-translate-x/y`, `--tw-scale-x/y`, `--tw-skew-x/y`, and
1164
+ * `--tw-rotate-x/y/z` custom properties. Returns an empty array when
1165
+ * none of those props were written, letting the caller skip the
1166
+ * post-pass.
1167
+ * @param ruleVars Rule-local `--tw-*` vars collected from the style rule.
1168
+ * @returns RN transform operations (possibly empty).
1169
+ */
1170
+ function composeTransformFromVars(ruleVars) {
1171
+ const ops = [];
1172
+ addAxisOp(ops, 'translateX', ruleVars.get('--tw-translate-x'), resolveLengthExpression);
1173
+ addAxisOp(ops, 'translateY', ruleVars.get('--tw-translate-y'), resolveLengthExpression);
1174
+ addAxisOp(ops, 'scaleX', ruleVars.get('--tw-scale-x'), resolveNumberOrPercent);
1175
+ addAxisOp(ops, 'scaleY', ruleVars.get('--tw-scale-y'), resolveNumberOrPercent);
1176
+ addAxisOp(ops, 'skewX', ruleVars.get('--tw-skew-x'), extractAngleFromSkewFunction);
1177
+ addAxisOp(ops, 'skewY', ruleVars.get('--tw-skew-y'), extractAngleFromSkewFunction);
1178
+ return ops;
1179
+ }
1180
+ /**
1181
+ * Push `{<key>: resolved(raw)}` into `ops` when `raw` is present and the
1182
+ * resolver returns non-null. Keeps {@link composeTransformFromVars}
1183
+ * below the cognitive complexity threshold.
1184
+ * @param ops Target array to mutate.
1185
+ * @param key RN transform op key (e.g. `'translateX'`).
1186
+ * @param raw Rule-local var value (possibly undefined).
1187
+ * @param resolve Value-resolver for this axis type.
1188
+ */
1189
+ function addAxisOp(ops, key, raw, resolve) {
1190
+ if (!raw)
1191
+ return;
1192
+ const value = resolve(raw);
1193
+ if (value !== null)
1194
+ ops.push({ [key]: value });
1195
+ }
1196
+ /**
1197
+ * Resolve a CSS length expression into the value a RN transform op
1198
+ * accepts — pixels as a number, or a percentage string preserved
1199
+ * verbatim. Supports the shapes Tailwind v4 emits into `--tw-translate-*`:
1200
+ *
1201
+ * - Direct lengths: `16px`, `1rem`, bare `42`.
1202
+ * - Percentages: `100%`, `-100%`.
1203
+ * - Flat calc: `calc(0.25rem * 52)`.
1204
+ * - Fractional calc: `calc(1 / 2 * 100%)` (→ `translate-x-1/2`).
1205
+ * - Nested calc with sign flip: `calc(calc(1 / 3 * 100%) * -1)` (→ `-translate-x-1/3`).
1206
+ *
1207
+ * Returns null when the expression mixes units (`calc(100% - 10px)` —
1208
+ * RN can't express those) or contains a token the evaluator can't
1209
+ * interpret; the transform op is simply skipped in that case.
1210
+ * @param text Length expression text.
1211
+ * @returns Pixel number, percentage string, or null when unrepresentable.
1212
+ */
1213
+ function resolveLengthExpression(text) {
1214
+ const trimmed = text.trim();
1215
+ if (trimmed.length === 0)
1216
+ return null;
1217
+ const evaluated = evaluateLengthExpr(trimmed);
1218
+ if (!evaluated)
1219
+ return null;
1220
+ if (evaluated.unit === '%')
1221
+ return `${stripTrailingZeros(evaluated.value)}%`;
1222
+ if (evaluated.unit === 'rem')
1223
+ return evaluated.value * 16;
1224
+ return evaluated.value;
1225
+ }
1226
+ /**
1227
+ * Evaluate a CSS length expression to a `{value, unit}` pair.
1228
+ *
1229
+ * Strategy: detect the (at most one) unit suffix present in the text,
1230
+ * strip every `calc(` to `(`, strip the unit suffix from numeric tokens,
1231
+ * and run a small arithmetic evaluator. Mixed-unit expressions are
1232
+ * rejected because RN has no way to express `calc(100% - 10px)` in a
1233
+ * flat transform op.
1234
+ * @param text Raw CSS length expression (already trimmed).
1235
+ * @returns Evaluated length with its unit, or `null` when invalid.
1236
+ */
1237
+ function evaluateLengthExpr(text) {
1238
+ const units = detectUnits(text);
1239
+ if (units.length > 1)
1240
+ return null;
1241
+ const unit = (units[0] ?? '');
1242
+ const arithmetic = stripCalcAndUnits(text);
1243
+ const value = evaluateArithmetic(arithmetic);
1244
+ if (value === null || !Number.isFinite(value))
1245
+ return null;
1246
+ return { value, unit };
1247
+ }
1248
+ /**
1249
+ * Detect which length units appear in the expression. Multi-unit
1250
+ * expressions (e.g. `calc(100% - 1rem)`) aren't representable in one RN
1251
+ * transform op, so we reject them.
1252
+ * @param text Length expression.
1253
+ * @returns Sorted, deduped unit list found in the text.
1254
+ */
1255
+ function detectUnits(text) {
1256
+ const found = new Set();
1257
+ if (/\d%/.test(text))
1258
+ found.add('%');
1259
+ if (/[\d.]rem\b/.test(text))
1260
+ found.add('rem');
1261
+ if (/[\d.]px\b/.test(text))
1262
+ found.add('px');
1263
+ return [...found];
1264
+ }
1265
+ /**
1266
+ * Strip every `calc(` wrapper to a plain `(`, and strip `%` / `rem` /
1267
+ * `px` unit suffixes from numeric tokens. Result is a plain arithmetic
1268
+ * expression the evaluator can consume.
1269
+ * @param text Length expression.
1270
+ * @returns Arithmetic text suitable for {@link evaluateArithmetic}.
1271
+ */
1272
+ function stripCalcAndUnits(text) {
1273
+ // Input is Tailwind's compiled CSS, not user-controlled — no ReDoS risk.
1274
+ // eslint-disable-next-line sonarjs/slow-regex
1275
+ return text.replaceAll(/\bcalc\s*\(/g, '(').replaceAll(/([\d.]+)(?:rem|px|%)/g, '$1');
1276
+ }
1277
+ /**
1278
+ * Format a percentage number so `50` stays `"50%"` (not `"50.00000001%"`)
1279
+ * when float drift is in the low bits. Strips trailing-zero decimals.
1280
+ * @param value Percentage magnitude.
1281
+ * @returns Integer-ish string.
1282
+ */
1283
+ function stripTrailingZeros(value) {
1284
+ const rounded = Math.round(value * 1_000_000) / 1_000_000;
1285
+ return String(rounded);
1286
+ }
1287
+ /**
1288
+ * Tiny recursive-descent evaluator for CSS arithmetic. Accepts `+`, `-`,
1289
+ * `*`, `/`, parens, and decimal numbers. Returns `null` on malformed
1290
+ * input — rejects anything the tokenizer can't classify.
1291
+ * @param text Arithmetic text (post {@link stripCalcAndUnits}).
1292
+ * @returns Evaluated number, or `null`.
1293
+ */
1294
+ function evaluateArithmetic(text) {
1295
+ const tokens = tokenizeArithmetic(text);
1296
+ if (!tokens)
1297
+ return null;
1298
+ const cursor = { index: 0 };
1299
+ const result = parseArithmeticExpr(tokens, cursor);
1300
+ if (cursor.index !== tokens.length)
1301
+ return null;
1302
+ return result;
1303
+ }
1304
+ /**
1305
+ * Split arithmetic text into numeric and operator tokens. Returns null
1306
+ * when the text contains any character outside the allowed set.
1307
+ * @param text Arithmetic text.
1308
+ * @returns Token list, or null on unexpected character.
1309
+ */
1310
+ function tokenizeArithmetic(text) {
1311
+ const tokens = [];
1312
+ let index = 0;
1313
+ while (index < text.length) {
1314
+ const ch = text[index];
1315
+ if (isArithmeticWhitespace(ch)) {
1316
+ index += 1;
1317
+ }
1318
+ else if (isArithmeticOperator(ch)) {
1319
+ tokens.push(ch);
1320
+ index += 1;
1321
+ }
1322
+ else if (isDigitOrDot(ch)) {
1323
+ const next = consumeNumber(text, index);
1324
+ tokens.push(text.slice(index, next));
1325
+ index = next;
1326
+ }
1327
+ else {
1328
+ return null;
1329
+ }
1330
+ }
1331
+ return tokens;
1332
+ }
1333
+ /**
1334
+ * Check whether `ch` is a whitespace character the arithmetic tokenizer
1335
+ * may skip.
1336
+ * @param ch Single-character string.
1337
+ * @returns True for space / tab / newline.
1338
+ */
1339
+ function isArithmeticWhitespace(ch) {
1340
+ return ch === ' ' || ch === '\t' || ch === '\n';
1341
+ }
1342
+ /**
1343
+ * Check whether `ch` is one of the arithmetic operator tokens.
1344
+ * @param ch Single-character string.
1345
+ * @returns True for `(`, `)`, `+`, `-`, `*`, `/`.
1346
+ */
1347
+ function isArithmeticOperator(ch) {
1348
+ return ch === '(' || ch === ')' || ch === '+' || ch === '-' || ch === '*' || ch === '/';
1349
+ }
1350
+ /**
1351
+ * Check whether `ch` belongs to a numeric token.
1352
+ * @param ch Single-character string.
1353
+ * @returns True for a digit `0`–`9` or `.`.
1354
+ */
1355
+ function isDigitOrDot(ch) {
1356
+ return (ch >= '0' && ch <= '9') || ch === '.';
1357
+ }
1358
+ /**
1359
+ * Advance past a numeric token starting at `start`.
1360
+ * @param text Source text.
1361
+ * @param start Index of the first digit or dot.
1362
+ * @returns Index just past the last digit-or-dot.
1363
+ */
1364
+ function consumeNumber(text, start) {
1365
+ let index = start;
1366
+ while (index < text.length && isDigitOrDot(text[index]))
1367
+ index += 1;
1368
+ return index;
1369
+ }
1370
+ /**
1371
+ * Parse an additive expression: `term (('+'|'-') term)*`.
1372
+ * @param tokens Token list.
1373
+ * @param cursor Mutable cursor.
1374
+ * @param cursor.index Current token index; advanced past consumed tokens.
1375
+ * @returns Evaluated number, or `null` on parse failure.
1376
+ */
1377
+ function parseArithmeticExpr(tokens, cursor) {
1378
+ let left = parseArithmeticTerm(tokens, cursor);
1379
+ if (left === null)
1380
+ return null;
1381
+ while (cursor.index < tokens.length) {
1382
+ const op = tokens[cursor.index];
1383
+ if (op !== '+' && op !== '-')
1384
+ break;
1385
+ cursor.index += 1;
1386
+ const right = parseArithmeticTerm(tokens, cursor);
1387
+ if (right === null)
1388
+ return null;
1389
+ left = op === '+' ? left + right : left - right;
1390
+ }
1391
+ return left;
1392
+ }
1393
+ /**
1394
+ * Parse a multiplicative expression: `factor (('*'|'/') factor)*`.
1395
+ * @param tokens Token list.
1396
+ * @param cursor Mutable cursor.
1397
+ * @param cursor.index Current token index; advanced past consumed tokens.
1398
+ * @returns Evaluated number, or `null`.
1399
+ */
1400
+ function parseArithmeticTerm(tokens, cursor) {
1401
+ let left = parseArithmeticFactor(tokens, cursor);
1402
+ if (left === null)
1403
+ return null;
1404
+ while (cursor.index < tokens.length) {
1405
+ const op = tokens[cursor.index];
1406
+ if (op !== '*' && op !== '/')
1407
+ break;
1408
+ cursor.index += 1;
1409
+ const right = parseArithmeticFactor(tokens, cursor);
1410
+ if (right === null)
1411
+ return null;
1412
+ left = op === '*' ? left * right : left / right;
1413
+ }
1414
+ return left;
1415
+ }
1416
+ /**
1417
+ * Parse a factor: unary minus, parenthesised expression, or number.
1418
+ * @param tokens Token list.
1419
+ * @param cursor Mutable cursor.
1420
+ * @param cursor.index Current token index; advanced past consumed tokens.
1421
+ * @returns Evaluated number, or `null`.
1422
+ */
1423
+ function parseArithmeticFactor(tokens, cursor) {
1424
+ if (cursor.index >= tokens.length)
1425
+ return null;
1426
+ const tok = tokens[cursor.index];
1427
+ if (tok === '-') {
1428
+ cursor.index += 1;
1429
+ const right = parseArithmeticFactor(tokens, cursor);
1430
+ return right === null ? null : -right;
1431
+ }
1432
+ if (tok === '+') {
1433
+ cursor.index += 1;
1434
+ return parseArithmeticFactor(tokens, cursor);
1435
+ }
1436
+ if (tok === '(') {
1437
+ cursor.index += 1;
1438
+ const inner = parseArithmeticExpr(tokens, cursor);
1439
+ if (cursor.index >= tokens.length || tokens[cursor.index] !== ')')
1440
+ return null;
1441
+ cursor.index += 1;
1442
+ return inner;
1443
+ }
1444
+ const number_ = Number(tok);
1445
+ if (!Number.isFinite(number_))
1446
+ return null;
1447
+ cursor.index += 1;
1448
+ return number_;
1449
+ }
1450
+ /**
1451
+ * Resolve a scale factor expressed as a percentage (`150%`) or number (`1.5`).
1452
+ * @param text Raw value.
1453
+ * @returns Scale number (e.g. 1.5 for 150%), or null.
1454
+ */
1455
+ function resolveNumberOrPercent(text) {
1456
+ const trimmed = text.trim();
1457
+ const percent = /^(-?\d+(?:\.\d+)?)%$/.exec(trimmed);
1458
+ if (percent)
1459
+ return Number(percent[1]) / 100;
1460
+ const bare = /^-?\d+(?:\.\d+)?$/.exec(trimmed);
1461
+ if (bare)
1462
+ return Number(trimmed);
1463
+ return null;
1464
+ }
1465
+ /**
1466
+ * Extract the angle from Tailwind's `skewX(12deg)` / `skewY(-5deg)` /
1467
+ * `skewX(calc(6deg * -1))` custom-property value shape. Returns null
1468
+ * when the inner expression doesn't reduce to a degree value.
1469
+ *
1470
+ * Tailwind v4 emits negative skew utilities as a nested `calc()`
1471
+ * (`-skew-x-6` → `skewX(calc(6deg * -1))`), so the inner body has to be
1472
+ * evaluated as arithmetic — a bare-angle regex silently drops those.
1473
+ * @param text Raw value.
1474
+ * @returns `<N>deg` string, or null.
1475
+ */
1476
+ function extractAngleFromSkewFunction(text) {
1477
+ const trimmed = text.trim();
1478
+ if (!trimmed.endsWith(')'))
1479
+ return null;
1480
+ const openIdx = trimmed.indexOf('(');
1481
+ if (openIdx < 5)
1482
+ return null;
1483
+ const head = trimmed.slice(0, openIdx);
1484
+ if (head !== 'skewX' && head !== 'skewY')
1485
+ return null;
1486
+ const inner = trimmed.slice(openIdx + 1, -1).trim();
1487
+ return resolveAngleExpression(inner);
1488
+ }
1489
+ /**
1490
+ * Evaluate an expression whose single unit is `deg`. Strips `calc(`
1491
+ * wrappers and `deg` suffixes, runs the arithmetic evaluator, reapplies
1492
+ * `deg`. Returns null for unit mismatches or unparseable text.
1493
+ * @param text Angle expression (e.g. `6deg`, `calc(6deg * -1)`).
1494
+ * @returns `<N>deg`, or null when not representable.
1495
+ */
1496
+ function resolveAngleExpression(text) {
1497
+ if (!/[\d.]deg\b/.test(text))
1498
+ return null;
1499
+ // Input is Tailwind's compiled CSS, not user-controlled — no ReDoS risk.
1500
+ // eslint-disable-next-line sonarjs/slow-regex
1501
+ const arithmetic = text.replaceAll(/\bcalc\s*\(/g, '(').replaceAll(/([\d.]+)deg/g, '$1');
1502
+ const value = evaluateArithmetic(arithmetic);
1503
+ if (value === null || !Number.isFinite(value))
1504
+ return null;
1505
+ return `${stripTrailingZeros(value)}deg`;
1506
+ }
1507
+ /**
1508
+ * Scan Tailwind's compiled CSS for `:root, :host { --x: y; … }` blocks
1509
+ * and pull the custom-property declarations out. This captures every
1510
+ * theme token Tailwind resolved — including tokens imported from the
1511
+ * user's secondary `@import` files (e.g. `rnwind/css`'s
1512
+ * `--duration-normal: 220ms`) — without rnwind having to re-implement
1513
+ * `@import` resolution.
1514
+ *
1515
+ * Regex-free scanner: finds `:root` prefixes, walks forward with
1516
+ * brace-depth tracking to find the matching block close, then extracts
1517
+ * every `--name: value;` pair with a paren-balanced walker so commas
1518
+ * inside `rgb(0, 0, 0)` don't confuse the split.
1519
+ * @param css Tailwind's compiled CSS.
1520
+ * @returns Map of custom-property name → resolved value.
1521
+ */
1522
+ /**
1523
+ * Strip `\@supports (color: color-mix(in lab, red, red)) { … }` wrappers
1524
+ * from Tailwind v4's compiled CSS, hoisting their inner declarations up
1525
+ * to the parent rule.
1526
+ *
1527
+ * Tailwind emits opacity-suffixed themed colors with both a pre-resolved
1528
+ * sRGB fallback AND a var()-based override gated behind the color-mix
1529
+ * `\@supports` clause. The OUTER fallback hard-codes a single scheme's
1530
+ * value of the theme token; the inner override is var()-based and
1531
+ * substitutes correctly per scheme. By unwrapping the gate, the inner
1532
+ * declaration becomes a sibling of the fallback in the same rule body —
1533
+ * lightningcss takes the LATER one (the var()-based unparsed form), and
1534
+ * the parser's themeVars-aware path produces correct rgba per scheme.
1535
+ * Modern RN-targeted browsers all support color-mix anyway, so dropping
1536
+ * the gating is safe.
1537
+ * @param css Tailwind-compiled CSS.
1538
+ * @returns CSS with the color-mix support gates unwrapped.
1539
+ */
1540
+ function unwrapColorMixSupports(css) {
1541
+ const guard = '@supports (color: color-mix(in lab, red, red))';
1542
+ let out = '';
1543
+ let cursor = 0;
1544
+ while (cursor < css.length) {
1545
+ const head = css.indexOf(guard, cursor);
1546
+ if (head === -1) {
1547
+ out += css.slice(cursor);
1548
+ break;
1549
+ }
1550
+ out += css.slice(cursor, head);
1551
+ const brace = css.indexOf('{', head);
1552
+ if (brace === -1) {
1553
+ out += css.slice(head);
1554
+ break;
1555
+ }
1556
+ const blockEnd = findMatchingClose(css, brace + 1);
1557
+ if (blockEnd === -1) {
1558
+ out += css.slice(head);
1559
+ break;
1560
+ }
1561
+ const inner = css.slice(brace + 1, blockEnd);
1562
+ // Only unwrap when the gated declaration substitutes a USER theme
1563
+ // token (`var(--color-…)`). Tailwind also gates `--tw-*` internal
1564
+ // composers (shadow color, ring color, …) on the same supports
1565
+ // clause; their outer fallback is the optimized hex/oklch value
1566
+ // the parser's own composed-prop pass needs (`applyComposedShadow`
1567
+ // reads `--tw-shadow-color` from the rule's local vars). Unwrapping
1568
+ // them would replace the resolvable color with an unresolvable
1569
+ // `color-mix(... var(--tw-shadow-alpha), transparent)` text and
1570
+ // break the composed-shadow path.
1571
+ // Keep the gate intact for non-themed colors — the outer fallback
1572
+ // wins, which is what Tailwind intended.
1573
+ out += inner.includes('var(--color-') ? inner : css.slice(head, blockEnd + 1);
1574
+ cursor = blockEnd + 1;
1575
+ }
1576
+ return out;
1577
+ }
1578
+ /**
1579
+ * Extract every `--name: value` declaration from the `:root` blocks in
1580
+ * Tailwind's compiled CSS into a flat map.
1581
+ * @param css Tailwind-compiled CSS.
1582
+ * @returns Map of custom-property name → resolved value.
1583
+ */
1584
+ function extractRootCustomProperties(css) {
1585
+ const out = new Map();
1586
+ let cursor = 0;
1587
+ while (cursor < css.length) {
1588
+ const blockEnd = consumeNextRootBlock(css, cursor, out);
1589
+ if (blockEnd === -1)
1590
+ break;
1591
+ cursor = blockEnd + 1;
1592
+ }
1593
+ return out;
1594
+ }
1595
+ /**
1596
+ * Locate the next `:root` block from `cursor`, extract its custom
1597
+ * properties into `out`, and return the index of its closing brace.
1598
+ * Split out from {@link extractRootCustomProperties} to keep complexity
1599
+ * below the cap.
1600
+ * @param css Source CSS.
1601
+ * @param cursor Start index for the search.
1602
+ * @param out Destination map, mutated.
1603
+ * @returns Index of the closing brace, or -1 when no block remains.
1604
+ */
1605
+ function consumeNextRootBlock(css, cursor, out) {
1606
+ const head = css.indexOf(':root', cursor);
1607
+ if (head === -1)
1608
+ return -1;
1609
+ const brace = css.indexOf('{', head);
1610
+ if (brace === -1)
1611
+ return -1;
1612
+ const blockEnd = findMatchingClose(css, brace + 1);
1613
+ if (blockEnd === -1)
1614
+ return -1;
1615
+ collectCustomDeclarations(css.slice(brace + 1, blockEnd), out);
1616
+ return blockEnd;
1617
+ }
1618
+ /**
1619
+ * Parse the body of a `:root` block — a `;`-separated list of `--name:
1620
+ * value` declarations — into the output map. Top-level `;` split is
1621
+ * paren-aware so `rgb(0, 0, 0)` doesn't fragment the list.
1622
+ * @param body Block body text (between braces).
1623
+ * @param out Destination map, mutated.
1624
+ */
1625
+ function collectCustomDeclarations(body, out) {
1626
+ for (const declaration of topLevelSplit(body, ';')) {
1627
+ const colon = declaration.indexOf(':');
1628
+ if (colon === -1)
1629
+ continue;
1630
+ const name = declaration.slice(0, colon).trim();
1631
+ const value = declaration.slice(colon + 1).trim();
1632
+ if (name.startsWith('--') && value.length > 0)
1633
+ out.set(name, value);
1634
+ }
1635
+ }
1636
+ /**
1637
+ * Walk forward from `start` tracking brace depth; return the index of
1638
+ * the matching `}` for the opener just before `start`.
1639
+ * @param source Source text.
1640
+ * @param start Index just past the opening `{`.
1641
+ * @returns Index of matching `}`, or `-1` on imbalance.
1642
+ */
1643
+ function findMatchingClose(source, start) {
1644
+ let depth = 1;
1645
+ for (let index = start; index < source.length; index += 1) {
1646
+ const ch = source[index];
1647
+ if (ch === '{')
1648
+ depth += 1;
1649
+ else if (ch === '}') {
1650
+ depth -= 1;
1651
+ if (depth === 0)
1652
+ return index;
1653
+ }
1654
+ }
1655
+ return -1;
1656
+ }
1657
+ /**
1658
+ * Merge rule-local custom vars into every scheme's var table. Creates
1659
+ * fresh maps so the rule pass doesn't mutate the shared parser state.
1660
+ * @param schemeTables Base per-scheme var tables.
1661
+ * @param ruleVars Rule-local `--tw-*` overrides.
1662
+ * @returns Merged per-scheme tables.
1663
+ */
1664
+ function mergeRuleVars(schemeTables, ruleVars) {
1665
+ if (ruleVars.size === 0)
1666
+ return new Map(schemeTables);
1667
+ const out = new Map();
1668
+ for (const [scheme, table] of schemeTables) {
1669
+ const merged = new Map(table);
1670
+ for (const [k, v] of ruleVars)
1671
+ merged.set(k, v);
1672
+ out.set(scheme, merged);
1673
+ }
1674
+ return out;
1675
+ }
1676
+
1677
+ export { TailwindParser };
1678
+ //# sourceMappingURL=tw-parser.mjs.map