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,456 @@
1
+ import type { Token, TokenOrValue } from 'lightningcss'
2
+ import { rgb as culoriRgb } from 'culori'
3
+ import { BARE_NUMBER_REGEX, CALC_MUL_REGEX, CALC_RATIO_REGEX, LENGTH_PX_REGEX, LENGTH_REM_REGEX, REM_TO_PX } from './constants'
4
+ import { cssColorToString } from './color'
5
+ import type { RNStyleValue } from './types'
6
+
7
+ /**
8
+ * Extract the fallback clause of a `var(--name, fallback)` by walking the
9
+ * string with paren-depth tracking. Linear-time; safe on nested
10
+ * `var(--a, var(--b, var(--c, 1rem)))` without regex backtracking.
11
+ * @param text CSS value already trimmed.
12
+ * @returns Fallback text, or `null` when `text` is not a `var(..., ...)`
13
+ * with a fallback.
14
+ */
15
+ function extractVariableFallback(text: string): string | null {
16
+ if (!text.startsWith('var(') || !text.endsWith(')')) return null
17
+ const inner = text.slice(4, -1)
18
+ let depth = 0
19
+ for (let index = 0; index < inner.length; index += 1) {
20
+ const ch = inner[index]
21
+ if (ch === '(') depth += 1
22
+ else if (ch === ')') depth -= 1
23
+ else if (ch === ',' && depth === 0) return inner.slice(index + 1).trim()
24
+ }
25
+ return null
26
+ }
27
+
28
+ /**
29
+ * Find the matching `)` for the opening `var(` whose body starts at
30
+ * `start`. Returns `-1` when the parens are unbalanced.
31
+ * @param text Source text.
32
+ * @param start Index just past the opening `var(`.
33
+ * @returns Index of the matching `)`, or `-1`.
34
+ */
35
+ function findBalancedParenEnd(text: string, start: number): number {
36
+ let depth = 1
37
+ for (let index = start; index < text.length; index += 1) {
38
+ const ch = text[index]
39
+ if (ch === '(') depth += 1
40
+ else if (ch === ')') {
41
+ depth -= 1
42
+ if (depth === 0) return index
43
+ }
44
+ }
45
+ return -1
46
+ }
47
+
48
+ /**
49
+ * Resolve a `var(…)` body (the bit between parentheses). Reads the
50
+ * variable name, then either returns the table lookup or recurses into
51
+ * the fallback clause. When neither resolves, re-wraps as `var(…)` so
52
+ * downstream coercion still sees a well-formed reference.
53
+ * @param body Text between the outer parentheses of a `var()` call.
54
+ * @param table var → value map.
55
+ * @returns Substituted text fragment.
56
+ */
57
+ function resolveVariableBody(body: string, table: ReadonlyMap<string, string>): string {
58
+ let depth = 0
59
+ let commaIndex = -1
60
+ for (const [index, ch] of [...body].entries()) {
61
+ if (ch === '(') depth += 1
62
+ else if (ch === ')') depth -= 1
63
+ else if (ch === ',' && depth === 0) {
64
+ commaIndex = index
65
+ break
66
+ }
67
+ }
68
+ const rawName = (commaIndex === -1 ? body : body.slice(0, commaIndex)).trim()
69
+ const fallback = commaIndex === -1 ? null : body.slice(commaIndex + 1).trim()
70
+ const resolved = table.get(rawName)
71
+ if (resolved !== undefined) return resolved
72
+ if (fallback !== null) return substitutePass(fallback, table)
73
+ return `var(${body})`
74
+ }
75
+
76
+ /**
77
+ * One pass of left-to-right `var(...)` substitution. Separate from the
78
+ * fixed-point driver so the recursion depth stays bounded.
79
+ * @param text Value text to scan.
80
+ * @param table var → value map.
81
+ * @returns Value text after one pass.
82
+ */
83
+ function substitutePass(text: string, table: ReadonlyMap<string, string>): string {
84
+ let out = ''
85
+ let index = 0
86
+ while (index < text.length) {
87
+ const head = text.indexOf('var(', index)
88
+ if (head === -1) {
89
+ out += text.slice(index)
90
+ break
91
+ }
92
+ out += text.slice(index, head)
93
+ const end = findBalancedParenEnd(text, head + 4)
94
+ if (end === -1) {
95
+ out += text.slice(head)
96
+ break
97
+ }
98
+ const body = text.slice(head + 4, end)
99
+ out += resolveVariableBody(body, table)
100
+ index = end + 1
101
+ }
102
+ return out
103
+ }
104
+
105
+ /**
106
+ * Serialize a list of `TokenOrValue` nodes into a CSS-ish value string.
107
+ * Preserves the CSS form closely enough for downstream numeric coercion.
108
+ * @param tokens Token list from an unparsed declaration or custom-property body.
109
+ * @returns Concatenated CSS-value fragment.
110
+ */
111
+ export function serializeTokens(tokens: readonly TokenOrValue[]): string {
112
+ let out = ''
113
+ for (const token of tokens) out += serializeToken(token)
114
+ return out.trim()
115
+ }
116
+
117
+ /**
118
+ * Serialize one `TokenOrValue` node back to CSS text. Handles the shapes
119
+ * Tailwind v4 actually emits in utility-class bodies: raw tokens, `var()`
120
+ * references, and numeric functions (`calc()`).
121
+ * @param token One token node.
122
+ * @returns CSS-value fragment.
123
+ */
124
+ export function serializeToken(token: TokenOrValue): string {
125
+ switch (token.type) {
126
+ case 'token': {
127
+ return serializeRawToken(token.value)
128
+ }
129
+ case 'var': {
130
+ const head = token.value.name.ident
131
+ const { fallback } = token.value
132
+ if (!fallback || fallback.length === 0) return `var(${head})`
133
+ return `var(${head}, ${serializeTokens(fallback)})`
134
+ }
135
+ case 'function': {
136
+ return `${token.value.name}(${serializeTokens(token.value.arguments)})`
137
+ }
138
+ case 'length': {
139
+ return `${token.value.value}${token.value.unit}`
140
+ }
141
+ case 'dashed-ident': {
142
+ return token.value
143
+ }
144
+ case 'angle': {
145
+ return `${token.value.value}${token.value.type}`
146
+ }
147
+ case 'time': {
148
+ const unit = token.value.type === 'milliseconds' ? 'ms' : 's'
149
+ return `${token.value.value}${unit}`
150
+ }
151
+ case 'resolution': {
152
+ return `${token.value.value}${token.value.type}`
153
+ }
154
+ case 'color': {
155
+ // Pre-resolved CSS color (`oklch(...)`, `rgb(...)`, etc.) — render
156
+ // it back to a hex/rgba string RN can read.
157
+ return cssColorToString(token.value)
158
+ }
159
+ case 'env':
160
+ case 'unresolved-color':
161
+ case 'url':
162
+ case 'animation-name': {
163
+ return ''
164
+ }
165
+ default: {
166
+ return ''
167
+ }
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Serialize a raw `Token` back to CSS text. `TokenOrValue` with
173
+ * `type === 'token'` wraps one of these; the discriminated union lets
174
+ * TypeScript narrow per branch without casts.
175
+ * @param token Raw token.
176
+ * @returns CSS text fragment.
177
+ */
178
+ export function serializeRawToken(token: Token): string {
179
+ switch (token.type) {
180
+ case 'ident':
181
+ case 'at-keyword':
182
+ case 'string':
183
+ case 'unquoted-url':
184
+ case 'function': {
185
+ return token.value
186
+ }
187
+ case 'hash':
188
+ case 'id-hash': {
189
+ return `#${token.value}`
190
+ }
191
+ case 'number': {
192
+ return String(token.value)
193
+ }
194
+ case 'percentage': {
195
+ return `${token.value * 100}%`
196
+ }
197
+ case 'dimension': {
198
+ return `${token.value}${token.unit}`
199
+ }
200
+ case 'white-space': {
201
+ return ' '
202
+ }
203
+ case 'delim': {
204
+ return token.value
205
+ }
206
+ case 'comma': {
207
+ return ','
208
+ }
209
+ case 'colon':
210
+ case 'semicolon':
211
+ case 'parenthesis-block':
212
+ case 'square-bracket-block':
213
+ case 'curly-bracket-block':
214
+ case 'cdo':
215
+ case 'cdc':
216
+ case 'include-match':
217
+ case 'dash-match':
218
+ case 'prefix-match':
219
+ case 'suffix-match':
220
+ case 'substring-match':
221
+ case 'comment':
222
+ case 'bad-url':
223
+ case 'bad-string': {
224
+ return ''
225
+ }
226
+ default: {
227
+ return ''
228
+ }
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Coerce the flat serialization of an unparsed value into an RN scalar.
234
+ * Handles the shapes Tailwind v4 actually emits: bare numbers, pixel /
235
+ * rem lengths, calc ratios, and `var(--x, fallback)` where we recurse into
236
+ * the fallback. Anything else passes through as a string.
237
+ * @param text Serialized CSS value.
238
+ * @returns Coerced primitive, or `null` when unrepresentable.
239
+ */
240
+ export function coerceUnparsedValue(text: string): RNStyleValue | null {
241
+ const trimmed = text.trim()
242
+ if (trimmed.length === 0) return null
243
+ if (BARE_NUMBER_REGEX.test(trimmed)) return Number(trimmed)
244
+ const px = LENGTH_PX_REGEX.exec(trimmed)
245
+ if (px) return Number(px[1])
246
+ const rem = LENGTH_REM_REGEX.exec(trimmed)
247
+ if (rem) return Number(rem[1]) * REM_TO_PX
248
+ const colorMix = evaluateColorMixWithTransparent(trimmed)
249
+ if (colorMix !== null) return colorMix
250
+ const fallback = extractVariableFallback(trimmed)
251
+ if (fallback !== null) return coerceUnparsedValue(fallback)
252
+ const calcRatio = CALC_RATIO_REGEX.exec(trimmed)
253
+ if (calcRatio) {
254
+ const right = Number(calcRatio[2])
255
+ if (right === 0) return null
256
+ return Number(calcRatio[1]) / right
257
+ }
258
+ const calcMul = CALC_MUL_REGEX.exec(trimmed)
259
+ if (calcMul) {
260
+ // Unit-aware multiply: `calc(0.5rem * 2)` → 16 (rem scaled to px).
261
+ // Regex captures `(number)(unit?) * (number)` — the unit is implicit
262
+ // in the match position; rebuild via the full match text.
263
+ const unitMatch = /^calc\(\s*-?\d+(?:\.\d+)?(rem|px)?/.exec(trimmed)
264
+ const unit = unitMatch?.[1]
265
+ const base = Number(calcMul[1]) * Number(calcMul[2])
266
+ return unit === 'rem' ? base * REM_TO_PX : base
267
+ }
268
+ return unquoteCssString(trimmed)
269
+ }
270
+
271
+ /**
272
+ * Evaluate the specific `color-mix(in <space>, <color> <pct>%, transparent)`
273
+ * shape Tailwind v4 emits for opacity-suffixed themed colors (e.g.
274
+ * `border-text/20`, `bg-on-background/30`). The result is the original
275
+ * color with `alpha = originalAlpha * pct/100` — no actual color-space
276
+ * conversion is needed because mixing a color with `transparent` only
277
+ * changes its alpha, regardless of the named space (oklab / srgb /
278
+ * lab / …) — the chrominance is preserved.
279
+ *
280
+ * Returns null when the expression isn't this shape (handed back to
281
+ * the caller for the next coercion strategy).
282
+ * @param text Trimmed CSS value.
283
+ * @returns RN-compatible `rgba(...)` string, or null when unmatched.
284
+ */
285
+ function evaluateColorMixWithTransparent(text: string): string | null {
286
+ const lower = text.toLowerCase()
287
+ if (!lower.startsWith('color-mix(')) return null
288
+ // Match the trailing `, transparent)` (allowing optional whitespace).
289
+ const tail = /,\s*transparent\s*\)\s*$/.exec(text)
290
+ if (!tail) return null
291
+ // Skip the `in <space>` clause (everything up to the FIRST comma after
292
+ // the opening paren). Walking by hand instead of regex because the
293
+ // color slot may itself contain `(...)` (e.g. `rgb(...)`).
294
+ const inComma = text.indexOf(',', 'color-mix('.length)
295
+ if (inComma === -1 || inComma > tail.index) return null
296
+ const middle = text.slice(inComma + 1, tail.index).trim()
297
+ // `<color> <pct>%` — the `<num>%` token is at the END of `middle`.
298
+ // Anchored, no backtracking — explicit `% ` then end.
299
+ const pctMatch = COLOR_MIX_PCT_TAIL.exec(middle)
300
+ if (!pctMatch) return null
301
+ const pct = Number(pctMatch[1]) / 100
302
+ if (!Number.isFinite(pct)) return null
303
+ const colorText = middle.slice(0, pctMatch.index).trim()
304
+ if (colorText.length === 0) return null
305
+ return applyAlphaToCssColor(colorText, pct)
306
+ }
307
+
308
+ /** End-anchored `<num>%` matcher used to slice a color-mix percentage off the right of an expression. */
309
+ // eslint-disable-next-line sonarjs/slow-regex -- end-anchored, atomic-style group; bounded backtracking is safe.
310
+ const COLOR_MIX_PCT_TAIL = /(-?\d+(?:\.\d+)?)%$/
311
+
312
+ /**
313
+ * Multiply the alpha channel of a serialized CSS color by `multiplier`
314
+ * (0…1). Recognises `#rgb` / `#rrggbb` / `#rrggbbaa` hex, named colors
315
+ * (only `transparent` matters here), and `rgb(…)` / `rgba(…)` forms —
316
+ * which is what theme tokens resolve to after substitution.
317
+ * @param color CSS color text.
318
+ * @param multiplier Alpha multiplier (0…1).
319
+ * @returns `rgba(r, g, b, a)` string with the adjusted alpha.
320
+ */
321
+ function applyAlphaToCssColor(color: string, multiplier: number): string | null {
322
+ const trimmed = color.trim()
323
+ if (trimmed === 'transparent') return 'rgba(0, 0, 0, 0)'
324
+ return alphaFromHex(trimmed, multiplier) ?? alphaFromRgbFunction(trimmed, multiplier) ?? alphaFromCulori(trimmed, multiplier)
325
+ }
326
+
327
+ /**
328
+ * Apply the alpha multiplier to a hex literal, expanding 3/4/6/8-digit forms.
329
+ * @param text
330
+ * @param multiplier
331
+ */
332
+ function alphaFromHex(text: string, multiplier: number): string | null {
333
+ const hexMatch = /^#([0-9a-fA-F]{3,8})$/.exec(text)
334
+ if (!hexMatch) return null
335
+ const expanded = expandHex(hexMatch[1]!)
336
+ if (!expanded) return null
337
+ return `rgba(${expanded.r}, ${expanded.g}, ${expanded.b}, ${expanded.alpha * multiplier})`
338
+ }
339
+
340
+ /**
341
+ * Apply alpha to an `rgb(…)` / `rgba(…)` literal. Walks the channels by
342
+ * hand instead of a multi-capture regex (the linter flags the regex
343
+ * form as backtracking-prone).
344
+ * @param text
345
+ * @param multiplier
346
+ */
347
+ function alphaFromRgbFunction(text: string, multiplier: number): string | null {
348
+ if (!text.startsWith('rgb(') && !text.startsWith('rgba(')) return null
349
+ const inner = text.slice(text.indexOf('(') + 1, -1)
350
+ const channels = inner.split(/\s*[\s,]\s*/).filter((part) => part.length > 0)
351
+ if (channels.length !== 3 && channels.length !== 4) return null
352
+ const r = Math.round(Number(channels[0]))
353
+ const g = Math.round(Number(channels[1]))
354
+ const b = Math.round(Number(channels[2]))
355
+ const baseAlpha = channels.length === 4 ? Number(channels[3]) : 1
356
+ if (![r, g, b, baseAlpha].every((value) => Number.isFinite(value))) return null
357
+ return `rgba(${r}, ${g}, ${b}, ${baseAlpha * multiplier})`
358
+ }
359
+
360
+ /**
361
+ * Fallback for wide-gamut color forms (`oklch`, `oklab`, `lab`, `lch`,
362
+ * `hsl`, …) — culori parses every CSS color shape and yields RGB. Lets
363
+ * `color-mix(in oklab, oklch(...) 50%, transparent)` resolve when
364
+ * Tailwind emits the source color in a wide-gamut space (every
365
+ * built-in `bg-red-500` / `shadow-red-500` does, in v4).
366
+ * @param text
367
+ * @param multiplier
368
+ */
369
+ function alphaFromCulori(text: string, multiplier: number): string | null {
370
+ try {
371
+ const parsed = culoriRgb(text) as { r?: number; g?: number; b?: number; alpha?: number } | undefined
372
+ if (!parsed) return null
373
+ if (![parsed.r, parsed.g, parsed.b].every((v) => typeof v === 'number' && Number.isFinite(v))) return null
374
+ const r = Math.round(Math.max(0, Math.min(1, parsed.r!)) * 255)
375
+ const g = Math.round(Math.max(0, Math.min(1, parsed.g!)) * 255)
376
+ const b = Math.round(Math.max(0, Math.min(1, parsed.b!)) * 255)
377
+ const baseAlpha = typeof parsed.alpha === 'number' ? parsed.alpha : 1
378
+ return `rgba(${r}, ${g}, ${b}, ${baseAlpha * multiplier})`
379
+ } catch {
380
+ // culori threw on an unrecognised CSS form — fall through.
381
+ return null
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Expand `#rgb` / `#rrggbb` / `#rrggbbaa` hex to its `{r, g, b, alpha}`
387
+ * components. Returns null when the digit count doesn't match a CSS hex
388
+ * shape.
389
+ * @param digits Hex digits without the leading `#`.
390
+ * @returns Decoded color or null.
391
+ */
392
+ function expandHex(digits: string): { r: number; g: number; b: number; alpha: number } | null {
393
+ const {length} = digits
394
+ if (length === 3 || length === 4) {
395
+ const r = Number.parseInt(digits[0]! + digits[0]!, 16)
396
+ const g = Number.parseInt(digits[1]! + digits[1]!, 16)
397
+ const b = Number.parseInt(digits[2]! + digits[2]!, 16)
398
+ const alpha = length === 4 ? Number.parseInt(digits[3]! + digits[3]!, 16) / 255 : 1
399
+ return Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b) ? null : { r, g, b, alpha }
400
+ }
401
+ if (length === 6 || length === 8) {
402
+ const r = Number.parseInt(digits.slice(0, 2), 16)
403
+ const g = Number.parseInt(digits.slice(2, 4), 16)
404
+ const b = Number.parseInt(digits.slice(4, 6), 16)
405
+ const alpha = length === 8 ? Number.parseInt(digits.slice(6, 8), 16) / 255 : 1
406
+ return Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b) ? null : { r, g, b, alpha }
407
+ }
408
+ return null
409
+ }
410
+
411
+ /**
412
+ * Strip the matched outer quote characters from a CSS string literal.
413
+ * `--font-sans: 'Inter-Medium'` flows through `var(--font-sans)`
414
+ * substitution as the raw value text — quotes included. Without this
415
+ * step `fontFamily` lands on the RN style as `"'Inter-Medium'"` (with
416
+ * literal quote characters), which RN can't match against the registered
417
+ * native font and silently falls back to the system face.
418
+ *
419
+ * Only strips when both ends agree (`'…'` or `"…"`) and there are no
420
+ * other top-level quote chars — keeps multi-segment fallbacks like
421
+ * `'Inter', sans-serif` untouched (those get split downstream).
422
+ * @param text Trimmed CSS value.
423
+ * @returns Same text with outer matching quotes removed, or unchanged.
424
+ */
425
+ function unquoteCssString(text: string): string {
426
+ if (text.length < 2) return text
427
+ const first = text.codePointAt(0)
428
+ const last = text.codePointAt(text.length - 1)
429
+ if (first === undefined || first !== last) return text
430
+ if (first !== 34 && first !== 39) return text // " or '
431
+ const inner = text.slice(1, -1)
432
+ // Don't unquote when the inner string itself contains an unescaped
433
+ // matching quote — that means we'd be merging two adjacent literals.
434
+ if (inner.includes(text[0]!)) return text
435
+ return inner
436
+ }
437
+
438
+ /**
439
+ * Substitute every `var(--name [, fallback])` reference in `text` with
440
+ * the value from `table` (or the fallback clause when the name misses).
441
+ * Paren-balanced so nested `var(…)` refs don't confuse the scanner.
442
+ * Iterates to a fixed point so multi-hop substitutions land in one call
443
+ * (with a safety cap so a malformed self-referential token can't loop).
444
+ * @param text Raw CSS value.
445
+ * @param table var name → resolved value map.
446
+ * @returns Substituted text.
447
+ */
448
+ export function substituteThemeVars(text: string, table: ReadonlyMap<string, string>): string {
449
+ let current = text
450
+ for (let pass = 0; pass < 8; pass += 1) {
451
+ const next = substitutePass(current, table)
452
+ if (next === current) return next
453
+ current = next
454
+ }
455
+ return current
456
+ }
@@ -0,0 +1,195 @@
1
+ import type { Angle, LengthValue, NumberOrPercentage, Rotate, Scale, Transform, Translate } from 'lightningcss'
2
+ import type { RNEntry } from './types'
3
+ import { lengthToPx } from './length'
4
+
5
+ type RnTransformRecord = Record<string, string | number>
6
+
7
+ type DimensionPercent =
8
+ | { type: 'dimension'; value: LengthValue }
9
+ | { type: 'percentage'; value: number }
10
+ | { type: 'calc'; value: unknown }
11
+
12
+ /**
13
+ * Map a single typed transform function into zero-or-more RN transform
14
+ * operations. Compound ops (like `translate`, `scale`, `skew`) expand
15
+ * into per-axis entries the way RN expects them.
16
+ * @param fn Typed transform function.
17
+ * @returns RN transform operations.
18
+ */
19
+ function mapTransformFunction(fn: Transform): RnTransformRecord[] | null {
20
+ switch (fn.type) {
21
+ case 'rotate':
22
+ case 'rotateZ': {
23
+ return [{ rotate: angleToString(fn.value) }]
24
+ }
25
+ case 'rotateX': {
26
+ return [{ rotateX: angleToString(fn.value) }]
27
+ }
28
+ case 'rotateY': {
29
+ return [{ rotateY: angleToString(fn.value) }]
30
+ }
31
+ case 'scale': {
32
+ const [x, y] = fn.value
33
+ return [{ scaleX: numberOrPercentageToNumber(x) }, { scaleY: numberOrPercentageToNumber(y) }]
34
+ }
35
+ case 'scaleX': {
36
+ return [{ scaleX: numberOrPercentageToNumber(fn.value) }]
37
+ }
38
+ case 'scaleY': {
39
+ return [{ scaleY: numberOrPercentageToNumber(fn.value) }]
40
+ }
41
+ case 'translateX': {
42
+ return [{ translateX: lengthOrPercentToNumber(fn.value) }]
43
+ }
44
+ case 'translateY': {
45
+ return [{ translateY: lengthOrPercentToNumber(fn.value) }]
46
+ }
47
+ case 'translate': {
48
+ const [x, y] = fn.value
49
+ const out: RnTransformRecord[] = [{ translateX: lengthOrPercentToNumber(x) }]
50
+ if (y) out.push({ translateY: lengthOrPercentToNumber(y) })
51
+ return out
52
+ }
53
+ case 'translate3d': {
54
+ const [x, y] = fn.value
55
+ return [{ translateX: lengthOrPercentToNumber(x) }, { translateY: lengthOrPercentToNumber(y) }]
56
+ }
57
+ case 'skew': {
58
+ const [x, y] = fn.value
59
+ const out: RnTransformRecord[] = [{ skewX: angleToString(x) }]
60
+ if (y) out.push({ skewY: angleToString(y) })
61
+ return out
62
+ }
63
+ case 'skewX': {
64
+ return [{ skewX: angleToString(fn.value) }]
65
+ }
66
+ case 'skewY': {
67
+ return [{ skewY: angleToString(fn.value) }]
68
+ }
69
+ default: {
70
+ // RN doesn't have a direct equivalent for `matrix()` / `matrix3d()` /
71
+ // `perspective()` at the transform-op level — skip silently. Tailwind's
72
+ // generated transforms stay within rotate/translate/scale/skew.
73
+ return null
74
+ }
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Serialize a typed angle into the CSS degree string RN accepts
80
+ * (`'45deg'`, `'0.5turn'` → `'180deg'`).
81
+ * @param angle Typed angle.
82
+ * @returns Degree string.
83
+ */
84
+ function angleToString(angle: Angle): string {
85
+ switch (angle.type) {
86
+ case 'deg': {
87
+ return `${formatNumber(angle.value)}deg`
88
+ }
89
+ case 'rad': {
90
+ return `${formatNumber((angle.value * 180) / Math.PI)}deg`
91
+ }
92
+ case 'grad': {
93
+ return `${formatNumber((angle.value * 360) / 400)}deg`
94
+ }
95
+ case 'turn': {
96
+ return `${formatNumber(angle.value * 360)}deg`
97
+ }
98
+ default: {
99
+ return '0deg'
100
+ }
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Convert a `NumberOrPercentage` to a plain number. Percentages become
106
+ * their fractional equivalent (e.g. `50%` → `0.5`).
107
+ * @param value Typed value.
108
+ * @returns Plain number.
109
+ */
110
+ function numberOrPercentageToNumber(value: NumberOrPercentage): number {
111
+ if (value.type === 'percentage') return value.value
112
+ return value.value
113
+ }
114
+
115
+ /**
116
+ * Convert a length-or-percentage used by translate into the shape RN
117
+ * accepts (`number` for px, `string` for `%`). Percentages stay as
118
+ * strings so RN layout can resolve them against the element size.
119
+ * @param value Typed length or percentage.
120
+ * @returns RN-style translate value.
121
+ */
122
+ function lengthOrPercentToNumber(value: DimensionPercent | { type: 'value'; value: LengthValue }): number | string {
123
+ if (value.type === 'dimension') return lengthToPx(value.value)
124
+ if (value.type === 'value') return lengthToPx(value.value)
125
+ if (value.type === 'percentage') return `${formatNumber(value.value * 100)}%`
126
+ return 0
127
+ }
128
+
129
+ /**
130
+ * Render a number without trailing IEEE noise.
131
+ * @param value Number to format.
132
+ * @returns Compact string form.
133
+ */
134
+ function formatNumber(value: number): string {
135
+ const rounded = Math.round(value * 10_000) / 10_000
136
+ return String(rounded)
137
+ }
138
+
139
+ /**
140
+ * Convert lightningcss's typed `transform: ...` value into RN's
141
+ * `transform: [{ op: value }, ...]` array. RN supports a restricted subset
142
+ * of CSS transforms — this function picks out the ones it actually
143
+ * handles and drops the rest.
144
+ *
145
+ * Reanimated v4's CSS engine reads this same array shape, so the output
146
+ * is drop-in for both static RN `style` props and `Animated.View` styles.
147
+ * @param fns Typed transform function list.
148
+ * @returns Zero-or-one RN entry with the `transform` array.
149
+ */
150
+ export function transformFunctionsToEntries(fns: readonly Transform[]): readonly RNEntry[] {
151
+ const ops: RnTransformRecord[] = []
152
+ for (const fn of fns) {
153
+ const mapped = mapTransformFunction(fn)
154
+ if (mapped) ops.push(...mapped)
155
+ }
156
+ if (ops.length === 0) return []
157
+ return [['transform', ops]]
158
+ }
159
+
160
+ /**
161
+ * Convert Tailwind v4's typed `rotate: ...` (individual property) into
162
+ * the RN transform array. Tailwind's `rotate-*` utilities emit this
163
+ * property rather than the classic `transform: rotate(...)` shorthand.
164
+ * @param value Typed rotate value.
165
+ * @returns Zero-or-one RN entry.
166
+ */
167
+ export function rotateToEntries(value: Rotate | 'none'): readonly RNEntry[] {
168
+ if (value === 'none') return []
169
+ return [['transform', [{ rotate: angleToString(value.angle) }]]]
170
+ }
171
+
172
+ /**
173
+ * Convert Tailwind v4's typed `translate: ...` into the RN transform
174
+ * array. Both axes are emitted as separate ops so each is independently
175
+ * animatable by Reanimated.
176
+ * @param value Typed translate value.
177
+ * @returns Zero-or-one RN entry.
178
+ */
179
+ export function translateToEntries(value: Translate | 'none'): readonly RNEntry[] {
180
+ if (value === 'none') return []
181
+ const ops: RnTransformRecord[] = [{ translateX: lengthOrPercentToNumber(value.x) }]
182
+ const yNumber = lengthOrPercentToNumber(value.y)
183
+ if (yNumber !== 0) ops.push({ translateY: yNumber })
184
+ return [['transform', ops]]
185
+ }
186
+
187
+ /**
188
+ * Convert Tailwind v4's typed `scale: ...` into the RN transform array.
189
+ * @param value Typed scale value.
190
+ * @returns Zero-or-one RN entry.
191
+ */
192
+ export function scaleToEntries(value: Scale | 'none'): readonly RNEntry[] {
193
+ if (value === 'none') return []
194
+ return [['transform', [{ scaleX: numberOrPercentageToNumber(value.x) }, { scaleY: numberOrPercentageToNumber(value.y) }]]]
195
+ }