rnwind 0.0.1 → 0.0.2

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 +164 -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 +100 -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 +96 -0
  36. package/lib/cjs/core/parser/length.cjs.map +1 -0
  37. package/lib/cjs/core/parser/length.d.ts +48 -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 +156 -0
  51. package/lib/cjs/core/parser/shorthand.cjs.map +1 -0
  52. package/lib/cjs/core/parser/shorthand.d.ts +61 -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 +414 -0
  57. package/lib/cjs/core/parser/theme-vars.cjs.map +1 -0
  58. package/lib/cjs/core/parser/theme-vars.d.ts +61 -0
  59. package/lib/cjs/core/parser/tokens.cjs +304 -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 +1567 -0
  66. package/lib/cjs/core/parser/tw-parser.cjs.map +1 -0
  67. package/lib/cjs/core/parser/tw-parser.d.ts +194 -0
  68. package/lib/cjs/core/parser/types.d.ts +37 -0
  69. package/lib/cjs/core/parser/typography-dispatcher.cjs +93 -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 +397 -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 +251 -0
  93. package/lib/cjs/metro/state.cjs.map +1 -0
  94. package/lib/cjs/metro/state.d.ts +72 -0
  95. package/lib/cjs/metro/transform-ast.cjs +1255 -0
  96. package/lib/cjs/metro/transform-ast.cjs.map +1 -0
  97. package/lib/cjs/metro/transform-ast.d.ts +73 -0
  98. package/lib/cjs/metro/transformer.cjs +345 -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 +57 -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 +162 -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 +98 -0
  172. package/lib/esm/core/parser/layout-dispatcher.mjs.map +1 -0
  173. package/lib/esm/core/parser/length.d.ts +48 -0
  174. package/lib/esm/core/parser/length.mjs +90 -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 +61 -0
  189. package/lib/esm/core/parser/shorthand.mjs +148 -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 +61 -0
  195. package/lib/esm/core/parser/theme-vars.mjs +409 -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 +298 -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 +194 -0
  204. package/lib/esm/core/parser/tw-parser.mjs +1565 -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 +91 -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 +395 -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 +72 -0
  231. package/lib/esm/metro/state.mjs +243 -0
  232. package/lib/esm/metro/state.mjs.map +1 -0
  233. package/lib/esm/metro/transform-ast.d.ts +73 -0
  234. package/lib/esm/metro/transform-ast.mjs +1234 -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 +322 -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 +57 -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 +79 -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 +157 -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 +92 -0
  291. package/src/core/parser/length.ts +100 -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 +152 -0
  297. package/src/core/parser/text-truncate.ts +79 -0
  298. package/src/core/parser/theme-vars.ts +412 -0
  299. package/src/core/parser/tokens.ts +286 -0
  300. package/src/core/parser/transform.ts +195 -0
  301. package/src/core/parser/tw-parser.ts +1709 -0
  302. package/src/core/parser/types.ts +45 -0
  303. package/src/core/parser/typography-dispatcher.ts +83 -0
  304. package/src/core/parser/typography.ts +83 -0
  305. package/src/core/style-builder/build-style.ts +442 -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 +257 -0
  313. package/src/metro/transform-ast.ts +1498 -0
  314. package/src/metro/transformer.ts +347 -0
  315. package/src/metro/warn-unknown-classes.ts +79 -0
  316. package/src/metro/with-config.ts +229 -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,412 @@
1
+ /**
2
+ * Lightweight extractor for user-declared `@theme` and `@variant` blocks
3
+ * inside `:root` / `@layer theme`. Used by the parser to resolve
4
+ * `var(--x)` references that Tailwind's `theme(inline)` leaves
5
+ * unresolved (it only inlines its own known tokens, not user-added ones).
6
+ *
7
+ * Kept regex-free — a linear paren-balanced scan handles nesting without
8
+ * the backtracking risks of `{[^}]*}` patterns. CSS comment blocks are
9
+ * stripped before scanning so slash-star-sequences inside declaration
10
+ * values don't confuse the parser.
11
+ */
12
+
13
+ /**
14
+ * `@custom-variant <name> ...content-up-to-semicolon...;`
15
+ * The `[^;]+` class is constant-width relative to its input (one char
16
+ * per step, no alternation backtracking), so sonarjs/slow-regex doesn't
17
+ * apply — the flagged pattern is a false positive.
18
+ */
19
+ // eslint-disable-next-line sonarjs/slow-regex
20
+ const CUSTOM_VARIANT_WITH_SELECTOR = /@custom-variant\s+([A-Za-z][A-Za-z0-9_-]*)\s+([^;]+);/g
21
+ /** Bare `.classname` occurrences (one capture group per class). */
22
+ const CLASS_IN_SELECTOR = /\.([A-Za-z_][\w-]*)/g
23
+
24
+ /**
25
+ * One iteration step of the block walker — decides whether the next
26
+ * interesting token is a declaration, a nested block, or the end of
27
+ * scope. Kept small to keep {@link walkBlocks} below the cognitive
28
+ * complexity threshold.
29
+ */
30
+ interface WalkStep {
31
+ /** Next scan index, or `-1` to terminate the outer loop. */
32
+ next: number
33
+ }
34
+
35
+ /**
36
+ * Recursively descend into `{ ... }` blocks, collecting `--x: y;`
37
+ * declarations under the active scheme. When we hit `@variant <name> {`,
38
+ * the child scope's scheme becomes `<name>` until that block closes.
39
+ * @param source Pre-stripped CSS source.
40
+ * @param start Start index of the block body (0 for top-level).
41
+ * @param scheme Active scheme name for declarations inside this scope.
42
+ * @param table Destination table, mutated in place.
43
+ */
44
+ function walkBlocks(source: string, start: number, scheme: string, table: ThemeSchemeTable): void {
45
+ let index = start
46
+ while (index < source.length) {
47
+ const step = nextWalkStep(source, index, scheme, table)
48
+ if (step.next === -1) return
49
+ index = step.next
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Decide the next action at `index`: declaration, nested block, or EOF.
55
+ * @param source Pre-stripped CSS.
56
+ * @param index Current scan index.
57
+ * @param scheme Active scheme name.
58
+ * @param table Destination table.
59
+ * @returns Step descriptor with the next scan index (or -1 for EOF).
60
+ */
61
+ function nextWalkStep(source: string, index: number, scheme: string, table: ThemeSchemeTable): WalkStep {
62
+ // Find the next TOP-LEVEL `--` (outside any `( ... )`). That's the only
63
+ // place a custom-property declaration can start. `--foo` that appears
64
+ // inside `var(--foo)` or `calc(... --value(integer) ...)` is part of a
65
+ // value, not a new declaration — accidentally treating it as one would
66
+ // splice into the `@theme` block's declaration list and produce broken
67
+ // CSS that Tailwind rejects downstream.
68
+ const atIndex = findTopLevelDoubleDash(source, index)
69
+ const openIndex = source.indexOf('{', index)
70
+ const closeIndex = source.indexOf('}', index)
71
+ const blockClose = closeIndex === -1 ? Number.POSITIVE_INFINITY : closeIndex
72
+ if (isDeclarationNext(atIndex, openIndex, blockClose)) {
73
+ return { next: consumeDeclaration(source, atIndex, scheme, table) }
74
+ }
75
+ if (openIndex !== -1 && openIndex < blockClose) {
76
+ return { next: enterBlock(source, index, openIndex, scheme, table) }
77
+ }
78
+ return { next: -1 }
79
+ }
80
+
81
+ /**
82
+ * Find the next `--` in `source` that's not inside a `( ... )` group.
83
+ * Stops early if we hit an unmatched `}` (end of the containing block)
84
+ * — a `--` inside a nested block isn't our concern either.
85
+ * @param source Pre-stripped CSS source.
86
+ * @param from Start index to scan from.
87
+ * @returns Index of the next top-level `--`, or `-1` when none.
88
+ */
89
+ function findTopLevelDoubleDash(source: string, from: number): number {
90
+ let parenDepth = 0
91
+ for (let index = from; index < source.length - 1; index += 1) {
92
+ const ch = source.codePointAt(index)
93
+ if (ch === 40 /* ( */) {
94
+ parenDepth += 1
95
+ continue
96
+ }
97
+ if (ch === 41 /* ) */) {
98
+ if (parenDepth > 0) parenDepth -= 1
99
+ continue
100
+ }
101
+ if (ch === 125 /* } */ && parenDepth === 0) return -1
102
+ if (ch === 45 /* - */ && parenDepth === 0 && source.codePointAt(index + 1) === 45) {
103
+ return index
104
+ }
105
+ }
106
+ return -1
107
+ }
108
+
109
+ /**
110
+ * Whether the next action at the current scan position is a declaration.
111
+ * @param atIndex Index of the next `--`.
112
+ * @param openIndex Index of the next `{`.
113
+ * @param blockClose Index of the next `}` (or `+Infinity` when absent).
114
+ * @returns True when a declaration should be consumed next.
115
+ */
116
+ function isDeclarationNext(atIndex: number, openIndex: number, blockClose: number): boolean {
117
+ if (atIndex === -1 || atIndex >= blockClose) return false
118
+ return openIndex === -1 || atIndex < openIndex
119
+ }
120
+
121
+ /**
122
+ * Handle a nested `{ ... }` block: recurse with the right scheme, then
123
+ * skip past the matching closing brace.
124
+ * @param source Pre-stripped CSS.
125
+ * @param index Scan position at the start of the header.
126
+ * @param openIndex Index of the opening brace.
127
+ * @param scheme Scheme active in the parent scope.
128
+ * @param table Destination table.
129
+ * @returns Index past the matching closing brace.
130
+ */
131
+ function enterBlock(source: string, index: number, openIndex: number, scheme: string, table: ThemeSchemeTable): number {
132
+ const header = source.slice(index, openIndex).trim()
133
+ // Skip blocks that define utilities / at-rules that carry declarations
134
+ // meant for a downstream compiler, not custom-property values for the
135
+ // active theme scheme. `@utility name-*` bodies contain Tailwind v4's
136
+ // `--value(...)` meta-syntax which would otherwise confuse the
137
+ // top-level declaration walker and spill into the extracted theme.
138
+ if (isNonThemeAtRule(header)) return skipMatchingBrace(source, openIndex + 1)
139
+ const childScheme = variantNameOf(header) ?? scheme
140
+ walkBlocks(source, openIndex + 1, childScheme, table)
141
+ return skipMatchingBrace(source, openIndex + 1)
142
+ }
143
+
144
+ /**
145
+ * Whether a block header belongs to an at-rule whose body should be
146
+ * ignored by the theme-var extractor. `@utility` / `@media` / `@keyframes`
147
+ * / `@supports` / `@container` bodies contain property declarations that
148
+ * are NOT custom-property definitions for the active theme.
149
+ * @param header Text between the outer statement and the opening brace.
150
+ * @returns `true` when the block body should be skipped.
151
+ */
152
+ function isNonThemeAtRule(header: string): boolean {
153
+ const trimmed = header.trimStart()
154
+ if (!trimmed.startsWith('@')) return false
155
+ const match = /^@([A-Za-z-]+)/.exec(trimmed)
156
+ if (!match) return false
157
+ const name = match[1]!
158
+ return name === 'utility' || name === 'keyframes' || name === 'media' || name === 'supports' || name === 'container'
159
+ }
160
+
161
+ /**
162
+ * Consume one `--name: value;` declaration starting at `start`. Commits
163
+ * the name/value pair into the active scheme's map and returns the index
164
+ * just past the terminating `;`.
165
+ * @param source CSS source.
166
+ * @param start Index of the `--`.
167
+ * @param scheme Active scheme.
168
+ * @param table Destination table.
169
+ * @returns Next scan index.
170
+ */
171
+ function consumeDeclaration(source: string, start: number, scheme: string, table: ThemeSchemeTable): number {
172
+ const colon = source.indexOf(':', start)
173
+ if (colon === -1) return source.length
174
+ const name = source.slice(start, colon).trim()
175
+ const end = source.indexOf(';', colon)
176
+ const stop = end === -1 ? source.indexOf('}', colon) : end
177
+ if (stop === -1) return source.length
178
+ const value = source.slice(colon + 1, stop).trim()
179
+ if (name.startsWith('--')) {
180
+ const bucket = table.get(scheme) ?? new Map()
181
+ bucket.set(name, value)
182
+ table.set(scheme, bucket)
183
+ }
184
+ return stop + 1
185
+ }
186
+
187
+ /**
188
+ * Walk forward through the source tracking brace depth; return the index
189
+ * just past the matching `}` for the opener at `start - 1`.
190
+ * @param source CSS source.
191
+ * @param start Index just past the opening brace.
192
+ * @returns Index past the matching closer (or source.length on imbalance).
193
+ */
194
+ function skipMatchingBrace(source: string, start: number): number {
195
+ let depth = 1
196
+ for (let index = start; index < source.length; index += 1) {
197
+ const ch = source[index]
198
+ if (ch === '{') depth += 1
199
+ else if (ch === '}') {
200
+ depth -= 1
201
+ if (depth === 0) return index + 1
202
+ }
203
+ }
204
+ return source.length
205
+ }
206
+
207
+ /**
208
+ * If `header` text (right before an opening brace) names a variant
209
+ * (`@variant <name>`), return that name. Otherwise null.
210
+ * @param header Text between the previous statement and the next `{`.
211
+ * @returns Variant name, or null when this block isn't a `@variant`.
212
+ */
213
+ function variantNameOf(header: string): string | null {
214
+ const match = /^@variant\s+([A-Za-z][A-Za-z0-9_-]*)/.exec(header)
215
+ if (!match) return null
216
+ return match[1] ?? null
217
+ }
218
+
219
+ /**
220
+ * Emit `@custom-variant` declarations for every scheme the user declared
221
+ * but hasn't manually registered. Makes Tailwind accept `light:*` /
222
+ * `dark:*` / etc. as valid class-name prefixes.
223
+ * @param css Stripped CSS source used to detect already-declared variants.
224
+ * @param schemes Non-base scheme names from the extracted table.
225
+ * @returns Zero or more `@custom-variant ... ;` lines.
226
+ */
227
+ function buildCustomVariantDeclarations(css: string, schemes: readonly string[]): string[] {
228
+ if (schemes.length === 0) return []
229
+ const existing = new Set<string>()
230
+ for (const match of css.matchAll(CUSTOM_VARIANT_REGEX)) existing.add(match[1]!)
231
+ const out: string[] = []
232
+ for (const name of schemes) {
233
+ if (!existing.has(name)) out.push(`@custom-variant ${name} (&:where(.${name}, .${name} *));`)
234
+ }
235
+ return out
236
+ }
237
+
238
+ /**
239
+ * Re-publish variant-scoped tokens into a flat theme block so Tailwind
240
+ * sees every custom-property name it needs. Values come from the base
241
+ * scheme first, with the FIRST declared non-base scheme layering on top
242
+ * — Tailwind only cares about token shape (color vs length), not the
243
+ * exact value, since rnwind does per-scheme substitution at walk time.
244
+ * @param table Extracted scheme → var-name → value table.
245
+ * @param schemes Non-base scheme names (sorted by declaration order).
246
+ * @returns A single `@theme { ... }` block or `null` when there are no tokens.
247
+ */
248
+ function buildUnifiedThemeBlock(table: ThemeSchemeTable, schemes: readonly string[]): string | null {
249
+ const unifiedVars = new Map<string, string>()
250
+ const baseTokens = table.get(BASE_SCHEME)
251
+ if (baseTokens) for (const [k, v] of baseTokens) unifiedVars.set(k, v)
252
+ if (schemes.length > 0) {
253
+ const firstTokens = table.get(schemes[0]!)
254
+ if (firstTokens) for (const [k, v] of firstTokens) unifiedVars.set(k, v)
255
+ }
256
+ if (unifiedVars.size === 0) return null
257
+ const decls = [...unifiedVars].map(([name, value]) => ` ${name}: ${value};`).join('\n')
258
+ return `@theme {\n${decls}\n}`
259
+ }
260
+
261
+ const CUSTOM_VARIANT_REGEX = /@custom-variant\s+([A-Za-z][A-Za-z0-9_-]*)\b/g
262
+ const VARIANT_OPEN_REGEX = /@variant\s+[A-Za-z0-9_-]+\s*\{/g
263
+
264
+ /**
265
+ * Strip every `@variant <name> { ... }` block (including nested braces)
266
+ * from a CSS source.
267
+ * @param css Raw CSS.
268
+ * @returns CSS without variant blocks.
269
+ */
270
+ function stripVariantBlocks(css: string): string {
271
+ let out = ''
272
+ let cursor = 0
273
+ VARIANT_OPEN_REGEX.lastIndex = 0
274
+ let match: RegExpExecArray | null
275
+ while ((match = VARIANT_OPEN_REGEX.exec(css)) !== null) {
276
+ const openEnd = match.index + match[0].length
277
+ const closeIndex = findMatchingBrace(css, openEnd)
278
+ if (closeIndex === -1) break
279
+ out += css.slice(cursor, match.index)
280
+ cursor = closeIndex + 1
281
+ VARIANT_OPEN_REGEX.lastIndex = cursor
282
+ }
283
+ out += css.slice(cursor)
284
+ return out
285
+ }
286
+
287
+ /**
288
+ * Walk forward from `start` tracking brace depth; return the index of the
289
+ * matching `}` for the opener just before `start`.
290
+ * @param source Source string.
291
+ * @param start Index just past the opening `{`.
292
+ * @returns Index of matching `}`, or `-1` on imbalance.
293
+ */
294
+ function findMatchingBrace(source: string, start: number): number {
295
+ let depth = 1
296
+ for (let index = start; index < source.length; index += 1) {
297
+ const ch = source[index]
298
+ if (ch === '{') depth += 1
299
+ else if (ch === '}') {
300
+ depth -= 1
301
+ if (depth === 0) return index
302
+ }
303
+ }
304
+ return -1
305
+ }
306
+
307
+ /**
308
+ * Drop CSS comment blocks from a source. Keeps the token-declaration
309
+ * scanner simple — comments can contain braces / `--` sequences that
310
+ * would otherwise fool it.
311
+ * @param css Raw CSS source.
312
+ * @returns Source with every comment block removed.
313
+ */
314
+ function stripComments(css: string): string {
315
+ let out = ''
316
+ let index = 0
317
+ while (index < css.length) {
318
+ const open = css.indexOf('/*', index)
319
+ if (open === -1) {
320
+ out += css.slice(index)
321
+ break
322
+ }
323
+ out += css.slice(index, open)
324
+ const close = css.indexOf('*/', open + 2)
325
+ if (close === -1) break
326
+ index = close + 2
327
+ }
328
+ return out
329
+ }
330
+
331
+ /** Scheme name → (var name → value). The base scheme key is `'base'`. */
332
+ export type ThemeSchemeTable = Map<string, Map<string, string>>
333
+
334
+ /** Synthetic scheme name used for tokens declared outside any `@variant`. */
335
+ export const BASE_SCHEME = 'base'
336
+
337
+ /**
338
+ * Extract user-declared theme tokens from a CSS source. Scans for:
339
+ * - classic v4 `@theme` blocks (`@theme { --x: y; ... }`)
340
+ * - `@variant <name> { --x: y; ... }` inside any surrounding block
341
+ *
342
+ * Both flavors contribute to the returned table: vars outside any
343
+ * `@variant` go under `'base'`; vars inside `@variant <name>` go under
344
+ * `<name>`.
345
+ *
346
+ * Not a full CSS parser — just enough to recognise the token-declaration
347
+ * shape Tailwind v4 users actually write.
348
+ * @param css Theme CSS source.
349
+ * @returns Scheme → var-name → value map.
350
+ */
351
+ export function extractThemeVars(css: string): ThemeSchemeTable {
352
+ const table: ThemeSchemeTable = new Map()
353
+ const stripped = stripComments(css)
354
+ walkBlocks(stripped, 0, BASE_SCHEME, table)
355
+ return table
356
+ }
357
+
358
+ /**
359
+ * Build a `<class-in-selector> → <scheme-name>` alias map from any
360
+ * `@custom-variant <name> (<selector>);` declarations in the theme CSS.
361
+ *
362
+ * Tailwind compiles `dark:opacity-100` to a nested rule whose selector is
363
+ * the user's declared variant selector. When the user writes
364
+ * `@custom-variant dark (&:where(.scheme-dark, .scheme-dark *));`, the
365
+ * generated rule references `.scheme-dark` instead of the literal `.dark`
366
+ * class rnwind's selector matcher defaults to. This alias map lets the
367
+ * parser map those custom selector classes back to their scheme name.
368
+ * @param css Theme CSS source.
369
+ * @returns Class-name → scheme-name map. Empty when no `@custom-variant`
370
+ * declarations are present (rnwind's auto-generated defaults already
371
+ * use the literal scheme name and don't need an alias).
372
+ */
373
+ export function extractSchemeAliases(css: string): Map<string, string> {
374
+ const aliases = new Map<string, string>()
375
+ const stripped = stripComments(css)
376
+ for (const match of stripped.matchAll(CUSTOM_VARIANT_WITH_SELECTOR)) {
377
+ const schemeName = match[1]!
378
+ const selectorBody = match[2]!
379
+ for (const cls of selectorBody.matchAll(CLASS_IN_SELECTOR)) {
380
+ const className = cls[1]!
381
+ if (className === schemeName) continue
382
+ aliases.set(className, schemeName)
383
+ }
384
+ }
385
+ return aliases
386
+ }
387
+
388
+ /**
389
+ * Rewrite the theme CSS so Tailwind's compiler accepts it:
390
+ * 1. Strip every `@variant <name> { ... }` block — Tailwind rejects
391
+ * them inline because `@variant` is a rnwind concept, not a Tailwind
392
+ * at-rule. The scheme-scoped vars are preserved in the extracted
393
+ * {@link ThemeSchemeTable} and substituted at walk time.
394
+ * 2. Prepend `@custom-variant <name> (&:where(.<name>, .<name> *));`
395
+ * for every scheme name we saw, so Tailwind accepts `light:bg-*`,
396
+ * `dark:bg-*`, etc. as valid class-name prefixes without the user
397
+ * having to duplicate the variant declaration.
398
+ * @param css Theme CSS source.
399
+ * @param table Scheme → var-name → value table from {@link extractThemeVars}.
400
+ * @returns CSS ready to feed `@tailwindcss/node`'s compiler.
401
+ */
402
+ export function compileReadyTheme(css: string, table: ThemeSchemeTable): string {
403
+ const schemes = [...table.keys()].filter((name) => name !== BASE_SCHEME)
404
+ const stripped = stripVariantBlocks(css)
405
+ const prelude: string[] = []
406
+ const customVariants = buildCustomVariantDeclarations(stripped, schemes)
407
+ if (customVariants.length > 0) prelude.push(...customVariants)
408
+ const themeBlock = buildUnifiedThemeBlock(table, schemes)
409
+ if (themeBlock) prelude.push(themeBlock)
410
+ if (prelude.length === 0) return stripped
411
+ return `${prelude.join('\n')}\n${stripped}`
412
+ }
@@ -0,0 +1,286 @@
1
+ import type { Token, TokenOrValue } from 'lightningcss'
2
+ import { BARE_NUMBER_REGEX, CALC_MUL_REGEX, CALC_RATIO_REGEX, LENGTH_PX_REGEX, LENGTH_REM_REGEX, REM_TO_PX } from './constants'
3
+ import { cssColorToString } from './color'
4
+ import type { RNStyleValue } from './types'
5
+
6
+ /**
7
+ * Extract the fallback clause of a `var(--name, fallback)` by walking the
8
+ * string with paren-depth tracking. Linear-time; safe on nested
9
+ * `var(--a, var(--b, var(--c, 1rem)))` without regex backtracking.
10
+ * @param text CSS value already trimmed.
11
+ * @returns Fallback text, or `null` when `text` is not a `var(..., ...)`
12
+ * with a fallback.
13
+ */
14
+ function extractVariableFallback(text: string): string | null {
15
+ if (!text.startsWith('var(') || !text.endsWith(')')) return null
16
+ const inner = text.slice(4, -1)
17
+ let depth = 0
18
+ for (let index = 0; index < inner.length; index += 1) {
19
+ const ch = inner[index]
20
+ if (ch === '(') depth += 1
21
+ else if (ch === ')') depth -= 1
22
+ else if (ch === ',' && depth === 0) return inner.slice(index + 1).trim()
23
+ }
24
+ return null
25
+ }
26
+
27
+ /**
28
+ * Find the matching `)` for the opening `var(` whose body starts at
29
+ * `start`. Returns `-1` when the parens are unbalanced.
30
+ * @param text Source text.
31
+ * @param start Index just past the opening `var(`.
32
+ * @returns Index of the matching `)`, or `-1`.
33
+ */
34
+ function findBalancedParenEnd(text: string, start: number): number {
35
+ let depth = 1
36
+ for (let index = start; index < text.length; index += 1) {
37
+ const ch = text[index]
38
+ if (ch === '(') depth += 1
39
+ else if (ch === ')') {
40
+ depth -= 1
41
+ if (depth === 0) return index
42
+ }
43
+ }
44
+ return -1
45
+ }
46
+
47
+ /**
48
+ * Resolve a `var(…)` body (the bit between parentheses). Reads the
49
+ * variable name, then either returns the table lookup or recurses into
50
+ * the fallback clause. When neither resolves, re-wraps as `var(…)` so
51
+ * downstream coercion still sees a well-formed reference.
52
+ * @param body Text between the outer parentheses of a `var()` call.
53
+ * @param table var → value map.
54
+ * @returns Substituted text fragment.
55
+ */
56
+ function resolveVariableBody(body: string, table: ReadonlyMap<string, string>): string {
57
+ let depth = 0
58
+ let commaIndex = -1
59
+ for (const [index, ch] of [...body].entries()) {
60
+ if (ch === '(') depth += 1
61
+ else if (ch === ')') depth -= 1
62
+ else if (ch === ',' && depth === 0) {
63
+ commaIndex = index
64
+ break
65
+ }
66
+ }
67
+ const rawName = (commaIndex === -1 ? body : body.slice(0, commaIndex)).trim()
68
+ const fallback = commaIndex === -1 ? null : body.slice(commaIndex + 1).trim()
69
+ const resolved = table.get(rawName)
70
+ if (resolved !== undefined) return resolved
71
+ if (fallback !== null) return substitutePass(fallback, table)
72
+ return `var(${body})`
73
+ }
74
+
75
+ /**
76
+ * One pass of left-to-right `var(...)` substitution. Separate from the
77
+ * fixed-point driver so the recursion depth stays bounded.
78
+ * @param text Value text to scan.
79
+ * @param table var → value map.
80
+ * @returns Value text after one pass.
81
+ */
82
+ function substitutePass(text: string, table: ReadonlyMap<string, string>): string {
83
+ let out = ''
84
+ let index = 0
85
+ while (index < text.length) {
86
+ const head = text.indexOf('var(', index)
87
+ if (head === -1) {
88
+ out += text.slice(index)
89
+ break
90
+ }
91
+ out += text.slice(index, head)
92
+ const end = findBalancedParenEnd(text, head + 4)
93
+ if (end === -1) {
94
+ out += text.slice(head)
95
+ break
96
+ }
97
+ const body = text.slice(head + 4, end)
98
+ out += resolveVariableBody(body, table)
99
+ index = end + 1
100
+ }
101
+ return out
102
+ }
103
+
104
+ /**
105
+ * Serialize a list of `TokenOrValue` nodes into a CSS-ish value string.
106
+ * Preserves the CSS form closely enough for downstream numeric coercion.
107
+ * @param tokens Token list from an unparsed declaration or custom-property body.
108
+ * @returns Concatenated CSS-value fragment.
109
+ */
110
+ export function serializeTokens(tokens: readonly TokenOrValue[]): string {
111
+ let out = ''
112
+ for (const token of tokens) out += serializeToken(token)
113
+ return out.trim()
114
+ }
115
+
116
+ /**
117
+ * Serialize one `TokenOrValue` node back to CSS text. Handles the shapes
118
+ * Tailwind v4 actually emits in utility-class bodies: raw tokens, `var()`
119
+ * references, and numeric functions (`calc()`).
120
+ * @param token One token node.
121
+ * @returns CSS-value fragment.
122
+ */
123
+ export function serializeToken(token: TokenOrValue): string {
124
+ switch (token.type) {
125
+ case 'token': {
126
+ return serializeRawToken(token.value)
127
+ }
128
+ case 'var': {
129
+ const head = token.value.name.ident
130
+ const { fallback } = token.value
131
+ if (!fallback || fallback.length === 0) return `var(${head})`
132
+ return `var(${head}, ${serializeTokens(fallback)})`
133
+ }
134
+ case 'function': {
135
+ return `${token.value.name}(${serializeTokens(token.value.arguments)})`
136
+ }
137
+ case 'length': {
138
+ return `${token.value.value}${token.value.unit}`
139
+ }
140
+ case 'dashed-ident': {
141
+ return token.value
142
+ }
143
+ case 'angle': {
144
+ return `${token.value.value}${token.value.type}`
145
+ }
146
+ case 'time': {
147
+ const unit = token.value.type === 'milliseconds' ? 'ms' : 's'
148
+ return `${token.value.value}${unit}`
149
+ }
150
+ case 'resolution': {
151
+ return `${token.value.value}${token.value.type}`
152
+ }
153
+ case 'color': {
154
+ // Pre-resolved CSS color (`oklch(...)`, `rgb(...)`, etc.) — render
155
+ // it back to a hex/rgba string RN can read.
156
+ return cssColorToString(token.value)
157
+ }
158
+ case 'env':
159
+ case 'unresolved-color':
160
+ case 'url':
161
+ case 'animation-name': {
162
+ return ''
163
+ }
164
+ default: {
165
+ return ''
166
+ }
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Serialize a raw `Token` back to CSS text. `TokenOrValue` with
172
+ * `type === 'token'` wraps one of these; the discriminated union lets
173
+ * TypeScript narrow per branch without casts.
174
+ * @param token Raw token.
175
+ * @returns CSS text fragment.
176
+ */
177
+ export function serializeRawToken(token: Token): string {
178
+ switch (token.type) {
179
+ case 'ident':
180
+ case 'at-keyword':
181
+ case 'string':
182
+ case 'unquoted-url':
183
+ case 'function': {
184
+ return token.value
185
+ }
186
+ case 'hash':
187
+ case 'id-hash': {
188
+ return `#${token.value}`
189
+ }
190
+ case 'number': {
191
+ return String(token.value)
192
+ }
193
+ case 'percentage': {
194
+ return `${token.value * 100}%`
195
+ }
196
+ case 'dimension': {
197
+ return `${token.value}${token.unit}`
198
+ }
199
+ case 'white-space': {
200
+ return ' '
201
+ }
202
+ case 'delim': {
203
+ return token.value
204
+ }
205
+ case 'comma': {
206
+ return ','
207
+ }
208
+ case 'colon':
209
+ case 'semicolon':
210
+ case 'parenthesis-block':
211
+ case 'square-bracket-block':
212
+ case 'curly-bracket-block':
213
+ case 'cdo':
214
+ case 'cdc':
215
+ case 'include-match':
216
+ case 'dash-match':
217
+ case 'prefix-match':
218
+ case 'suffix-match':
219
+ case 'substring-match':
220
+ case 'comment':
221
+ case 'bad-url':
222
+ case 'bad-string': {
223
+ return ''
224
+ }
225
+ default: {
226
+ return ''
227
+ }
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Coerce the flat serialization of an unparsed value into an RN scalar.
233
+ * Handles the shapes Tailwind v4 actually emits: bare numbers, pixel /
234
+ * rem lengths, calc ratios, and `var(--x, fallback)` where we recurse into
235
+ * the fallback. Anything else passes through as a string.
236
+ * @param text Serialized CSS value.
237
+ * @returns Coerced primitive, or `null` when unrepresentable.
238
+ */
239
+ export function coerceUnparsedValue(text: string): RNStyleValue | null {
240
+ const trimmed = text.trim()
241
+ if (trimmed.length === 0) return null
242
+ if (BARE_NUMBER_REGEX.test(trimmed)) return Number(trimmed)
243
+ const px = LENGTH_PX_REGEX.exec(trimmed)
244
+ if (px) return Number(px[1])
245
+ const rem = LENGTH_REM_REGEX.exec(trimmed)
246
+ if (rem) return Number(rem[1]) * REM_TO_PX
247
+ const fallback = extractVariableFallback(trimmed)
248
+ if (fallback !== null) return coerceUnparsedValue(fallback)
249
+ const calcRatio = CALC_RATIO_REGEX.exec(trimmed)
250
+ if (calcRatio) {
251
+ const right = Number(calcRatio[2])
252
+ if (right === 0) return null
253
+ return Number(calcRatio[1]) / right
254
+ }
255
+ const calcMul = CALC_MUL_REGEX.exec(trimmed)
256
+ if (calcMul) {
257
+ // Unit-aware multiply: `calc(0.5rem * 2)` → 16 (rem scaled to px).
258
+ // Regex captures `(number)(unit?) * (number)` — the unit is implicit
259
+ // in the match position; rebuild via the full match text.
260
+ const unitMatch = /^calc\(\s*-?\d+(?:\.\d+)?(rem|px)?/.exec(trimmed)
261
+ const unit = unitMatch?.[1]
262
+ const base = Number(calcMul[1]) * Number(calcMul[2])
263
+ return unit === 'rem' ? base * REM_TO_PX : base
264
+ }
265
+ return trimmed
266
+ }
267
+
268
+ /**
269
+ * Substitute every `var(--name [, fallback])` reference in `text` with
270
+ * the value from `table` (or the fallback clause when the name misses).
271
+ * Paren-balanced so nested `var(…)` refs don't confuse the scanner.
272
+ * Iterates to a fixed point so multi-hop substitutions land in one call
273
+ * (with a safety cap so a malformed self-referential token can't loop).
274
+ * @param text Raw CSS value.
275
+ * @param table var name → resolved value map.
276
+ * @returns Substituted text.
277
+ */
278
+ export function substituteThemeVars(text: string, table: ReadonlyMap<string, string>): string {
279
+ let current = text
280
+ for (let pass = 0; pass < 8; pass += 1) {
281
+ const next = substitutePass(current, table)
282
+ if (next === current) return next
283
+ current = next
284
+ }
285
+ return current
286
+ }