@tamagui/static 2.7.7 → 3.0.0-beta.643.1

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 (329) hide show
  1. package/dist/cjs/check-dep-versions.cjs +355 -0
  2. package/dist/cjs/checkDeps.cjs +302 -0
  3. package/dist/cjs/compiler.cjs +286 -0
  4. package/dist/cjs/compilerHost.cjs +1695 -0
  5. package/dist/cjs/constants.cjs +33 -0
  6. package/dist/cjs/domStructuralPass.cjs +112 -0
  7. package/dist/cjs/exports.cjs +42 -0
  8. package/dist/cjs/extractor/addLocalExports.cjs +64 -0
  9. package/dist/cjs/extractor/bundle.cjs +203 -0
  10. package/dist/cjs/extractor/bundleConfig.cjs +747 -0
  11. package/dist/cjs/extractor/concatClassName.cjs +117 -0
  12. package/dist/cjs/extractor/detectModuleFormat.cjs +54 -0
  13. package/dist/cjs/extractor/esbuildAliasPlugin.cjs +47 -0
  14. package/dist/cjs/extractor/esbuildTsconfigPaths.cjs +92 -0
  15. package/dist/cjs/extractor/getTamaguiConfigPathFromOptionsConfig.cjs +38 -0
  16. package/dist/cjs/extractor/loadTamagui.cjs +344 -0
  17. package/dist/cjs/extractor/regenerateConfig.cjs +161 -0
  18. package/dist/cjs/extractor/watchTamaguiConfig.cjs +64 -0
  19. package/dist/cjs/getPragmaOptions.cjs +67 -0
  20. package/dist/cjs/helpers/memoize.cjs +42 -0
  21. package/dist/cjs/helpers/requireTamaguiCore.cjs +40 -0
  22. package/dist/cjs/index.cjs +34 -0
  23. package/dist/cjs/registerRequire.cjs +220 -0
  24. package/dist/cjs/server.cjs +66 -0
  25. package/dist/cjs/staticEvaluationIgnoredModules.cjs +86 -0
  26. package/dist/cjs/types.cjs +17 -0
  27. package/dist/cjs/zero/artifact.cjs +145 -0
  28. package/dist/cjs/zero/graph.cjs +209 -0
  29. package/dist/cjs/zero/identity.cjs +61 -0
  30. package/dist/cjs/zero/index.cjs +26 -0
  31. package/dist/cjs/zero/islands.cjs +232 -0
  32. package/dist/cjs/zero/options.cjs +163 -0
  33. package/dist/cjs/zero/ownership.cjs +79 -0
  34. package/dist/cjs/zero/theme.cjs +328 -0
  35. package/dist/cjs/zero/transform.cjs +203 -0
  36. package/dist/esm/check-dep-versions.mjs +329 -0
  37. package/dist/esm/check-dep-versions.mjs.map +1 -0
  38. package/dist/esm/checkDeps.mjs +279 -0
  39. package/dist/esm/checkDeps.mjs.map +1 -0
  40. package/dist/esm/compiler.mjs +255 -0
  41. package/dist/esm/compiler.mjs.map +1 -0
  42. package/dist/esm/compilerHost.mjs +1675 -0
  43. package/dist/esm/compilerHost.mjs.map +1 -0
  44. package/dist/esm/constants.mjs +7 -0
  45. package/dist/esm/constants.mjs.map +1 -0
  46. package/dist/esm/domStructuralPass.mjs +92 -0
  47. package/dist/esm/domStructuralPass.mjs.map +1 -0
  48. package/dist/esm/exports.mjs +26 -0
  49. package/dist/esm/extractor/addLocalExports.mjs +44 -0
  50. package/dist/esm/extractor/addLocalExports.mjs.map +1 -0
  51. package/dist/esm/extractor/bundle.mjs +173 -0
  52. package/dist/esm/extractor/bundle.mjs.map +1 -0
  53. package/dist/esm/extractor/bundleConfig.mjs +706 -0
  54. package/dist/esm/extractor/bundleConfig.mjs.map +1 -0
  55. package/dist/esm/extractor/concatClassName.mjs +96 -0
  56. package/dist/esm/extractor/concatClassName.mjs.map +1 -0
  57. package/dist/esm/extractor/detectModuleFormat.mjs +31 -0
  58. package/dist/esm/extractor/detectModuleFormat.mjs.map +1 -0
  59. package/dist/esm/extractor/esbuildAliasPlugin.mjs +26 -0
  60. package/dist/esm/extractor/esbuildAliasPlugin.mjs.map +1 -0
  61. package/dist/esm/extractor/esbuildTsconfigPaths.mjs +63 -0
  62. package/dist/esm/extractor/esbuildTsconfigPaths.mjs.map +1 -0
  63. package/dist/esm/extractor/getTamaguiConfigPathFromOptionsConfig.mjs +18 -0
  64. package/dist/esm/extractor/getTamaguiConfigPathFromOptionsConfig.mjs.map +1 -0
  65. package/dist/esm/extractor/loadTamagui.mjs +306 -0
  66. package/dist/esm/extractor/loadTamagui.mjs.map +1 -0
  67. package/dist/esm/extractor/regenerateConfig.mjs +131 -0
  68. package/dist/esm/extractor/regenerateConfig.mjs.map +1 -0
  69. package/dist/esm/extractor/watchTamaguiConfig.mjs +43 -0
  70. package/dist/esm/extractor/watchTamaguiConfig.mjs.map +1 -0
  71. package/dist/esm/getPragmaOptions.mjs +46 -0
  72. package/dist/esm/getPragmaOptions.mjs.map +1 -0
  73. package/dist/esm/helpers/memoize.mjs +21 -0
  74. package/dist/esm/helpers/memoize.mjs.map +1 -0
  75. package/dist/esm/helpers/requireTamaguiCore.mjs +19 -0
  76. package/dist/esm/helpers/requireTamaguiCore.mjs.map +1 -0
  77. package/dist/esm/index.mjs +8 -0
  78. package/dist/esm/index.mjs.map +1 -0
  79. package/dist/esm/registerRequire.mjs +195 -0
  80. package/dist/esm/registerRequire.mjs.map +1 -0
  81. package/dist/esm/server.mjs +40 -0
  82. package/dist/esm/server.mjs.map +1 -0
  83. package/dist/esm/setup.mjs +0 -0
  84. package/dist/esm/staticEvaluationIgnoredModules.mjs +61 -0
  85. package/dist/esm/staticEvaluationIgnoredModules.mjs.map +1 -0
  86. package/dist/esm/types.mjs +0 -0
  87. package/dist/esm/zero/artifact.mjs +119 -0
  88. package/dist/esm/zero/artifact.mjs.map +1 -0
  89. package/dist/esm/zero/graph.mjs +170 -0
  90. package/dist/esm/zero/graph.mjs.map +1 -0
  91. package/dist/esm/zero/identity.mjs +36 -0
  92. package/dist/esm/zero/identity.mjs.map +1 -0
  93. package/dist/esm/zero/index.mjs +15 -0
  94. package/dist/esm/zero/islands.mjs +197 -0
  95. package/dist/esm/zero/islands.mjs.map +1 -0
  96. package/dist/esm/zero/options.mjs +129 -0
  97. package/dist/esm/zero/options.mjs.map +1 -0
  98. package/dist/esm/zero/ownership.mjs +48 -0
  99. package/dist/esm/zero/ownership.mjs.map +1 -0
  100. package/dist/esm/zero/theme.mjs +303 -0
  101. package/dist/esm/zero/theme.mjs.map +1 -0
  102. package/dist/esm/zero/transform.mjs +177 -0
  103. package/dist/esm/zero/transform.mjs.map +1 -0
  104. package/package.json +33 -43
  105. package/src/check-dep-versions.ts +2 -2
  106. package/src/checkDeps.ts +3 -2
  107. package/src/compiler.ts +425 -0
  108. package/src/compilerHost.ts +3125 -0
  109. package/src/constants.ts +0 -5
  110. package/src/domStructuralPass.ts +170 -0
  111. package/src/exports.ts +3 -6
  112. package/src/extractor/addLocalExports.ts +49 -0
  113. package/src/extractor/bundle.ts +20 -35
  114. package/src/extractor/bundleConfig.ts +334 -187
  115. package/src/extractor/concatClassName.ts +13 -20
  116. package/src/extractor/getTamaguiConfigPathFromOptionsConfig.ts +5 -4
  117. package/src/extractor/loadTamagui.ts +196 -143
  118. package/src/extractor/regenerateConfig.ts +65 -13
  119. package/src/extractor/watchTamaguiConfig.ts +6 -1
  120. package/src/helpers/requireTamaguiCore.ts +6 -1
  121. package/src/registerRequire.ts +94 -132
  122. package/src/staticEvaluationIgnoredModules.ts +74 -0
  123. package/src/types.ts +2 -107
  124. package/src/zero/artifact.ts +142 -0
  125. package/src/zero/graph.ts +359 -0
  126. package/src/zero/identity.ts +73 -0
  127. package/src/zero/index.ts +8 -0
  128. package/src/zero/islands.ts +262 -0
  129. package/src/zero/options.ts +239 -0
  130. package/src/zero/ownership.ts +113 -0
  131. package/src/zero/theme.ts +478 -0
  132. package/src/zero/transform.ts +320 -0
  133. package/types/check-dep-versions.d.ts.map +1 -1
  134. package/types/checkDeps.d.ts.map +1 -1
  135. package/types/compiler.d.ts +117 -0
  136. package/types/compiler.d.ts.map +1 -0
  137. package/types/compilerHost.d.ts +25 -0
  138. package/types/compilerHost.d.ts.map +1 -0
  139. package/types/constants.d.ts +0 -1
  140. package/types/constants.d.ts.map +1 -1
  141. package/types/domStructuralPass.d.ts +3 -0
  142. package/types/domStructuralPass.d.ts.map +1 -0
  143. package/types/exports.d.ts +3 -6
  144. package/types/exports.d.ts.map +1 -1
  145. package/types/extractor/addLocalExports.d.ts +3 -0
  146. package/types/extractor/addLocalExports.d.ts.map +1 -0
  147. package/types/extractor/bundle.d.ts +66 -65
  148. package/types/extractor/bundle.d.ts.map +1 -1
  149. package/types/extractor/bundleConfig.d.ts +19 -4
  150. package/types/extractor/bundleConfig.d.ts.map +1 -1
  151. package/types/extractor/esbuildAliasPlugin.d.ts.map +1 -1
  152. package/types/extractor/getTamaguiConfigPathFromOptionsConfig.d.ts +1 -1
  153. package/types/extractor/getTamaguiConfigPathFromOptionsConfig.d.ts.map +1 -1
  154. package/types/extractor/loadTamagui.d.ts +26 -3
  155. package/types/extractor/loadTamagui.d.ts.map +1 -1
  156. package/types/extractor/regenerateConfig.d.ts +1 -1
  157. package/types/extractor/regenerateConfig.d.ts.map +1 -1
  158. package/types/extractor/watchTamaguiConfig.d.ts.map +1 -1
  159. package/types/getPragmaOptions.d.ts +1 -1
  160. package/types/helpers/memoize.d.ts +1 -1
  161. package/types/helpers/memoize.d.ts.map +1 -1
  162. package/types/helpers/requireTamaguiCore.d.ts.map +1 -1
  163. package/types/index.d.ts.map +1 -1
  164. package/types/registerRequire.d.ts +2 -2
  165. package/types/registerRequire.d.ts.map +1 -1
  166. package/types/staticEvaluationIgnoredModules.d.ts +9 -0
  167. package/types/staticEvaluationIgnoredModules.d.ts.map +1 -0
  168. package/types/types.d.ts +2 -90
  169. package/types/types.d.ts.map +1 -1
  170. package/types/zero/artifact.d.ts +44 -0
  171. package/types/zero/artifact.d.ts.map +1 -0
  172. package/types/zero/graph.d.ts +139 -0
  173. package/types/zero/graph.d.ts.map +1 -0
  174. package/types/zero/identity.d.ts +33 -0
  175. package/types/zero/identity.d.ts.map +1 -0
  176. package/types/zero/index.d.ts +9 -0
  177. package/types/zero/index.d.ts.map +1 -0
  178. package/types/zero/islands.d.ts +56 -0
  179. package/types/zero/islands.d.ts.map +1 -0
  180. package/types/zero/options.d.ts +58 -0
  181. package/types/zero/options.d.ts.map +1 -0
  182. package/types/zero/ownership.d.ts +51 -0
  183. package/types/zero/ownership.d.ts.map +1 -0
  184. package/types/zero/theme.d.ts +83 -0
  185. package/types/zero/theme.d.ts.map +1 -0
  186. package/types/zero/transform.d.ts +53 -0
  187. package/types/zero/transform.d.ts.map +1 -0
  188. package/dist/check-dep-versions.cjs +0 -372
  189. package/dist/checkDeps.cjs +0 -271
  190. package/dist/constants.cjs +0 -51
  191. package/dist/exports.cjs +0 -48
  192. package/dist/extractor/accessSafe.cjs +0 -48
  193. package/dist/extractor/babelParse.cjs +0 -56
  194. package/dist/extractor/buildClassName.cjs +0 -86
  195. package/dist/extractor/bundle.cjs +0 -234
  196. package/dist/extractor/bundleConfig.cjs +0 -729
  197. package/dist/extractor/concatClassName.cjs +0 -129
  198. package/dist/extractor/createEvaluator.cjs +0 -88
  199. package/dist/extractor/createExtractor.cjs +0 -1968
  200. package/dist/extractor/createLogger.cjs +0 -50
  201. package/dist/extractor/detectModuleFormat.cjs +0 -55
  202. package/dist/extractor/ensureImportingConcat.cjs +0 -54
  203. package/dist/extractor/errors.cjs +0 -28
  204. package/dist/extractor/esbuildAliasPlugin.cjs +0 -56
  205. package/dist/extractor/esbuildTsconfigPaths.cjs +0 -108
  206. package/dist/extractor/evaluateAstNode.cjs +0 -128
  207. package/dist/extractor/extractHelpers.cjs +0 -182
  208. package/dist/extractor/extractMediaStyle.cjs +0 -169
  209. package/dist/extractor/extractToClassNames.cjs +0 -475
  210. package/dist/extractor/extractToNative.cjs +0 -369
  211. package/dist/extractor/findTopmostFunction.cjs +0 -43
  212. package/dist/extractor/generatedUid.cjs +0 -58
  213. package/dist/extractor/getPrefixLogs.cjs +0 -31
  214. package/dist/extractor/getPropValueFromAttributes.cjs +0 -85
  215. package/dist/extractor/getSourceModule.cjs +0 -100
  216. package/dist/extractor/getStaticBindingsForScope.cjs +0 -213
  217. package/dist/extractor/getTamaguiConfigPathFromOptionsConfig.cjs +0 -41
  218. package/dist/extractor/hasTopLevelAwait.cjs +0 -62
  219. package/dist/extractor/hoistClassNames.cjs +0 -76
  220. package/dist/extractor/literalToAst.cjs +0 -109
  221. package/dist/extractor/loadFile.cjs +0 -17
  222. package/dist/extractor/loadTamagui.cjs +0 -373
  223. package/dist/extractor/logLines.cjs +0 -43
  224. package/dist/extractor/normalizeTernaries.cjs +0 -100
  225. package/dist/extractor/propsToFontFamilyCache.cjs +0 -42
  226. package/dist/extractor/regenerateConfig.cjs +0 -178
  227. package/dist/extractor/removeUnusedHooks.cjs +0 -108
  228. package/dist/extractor/timer.cjs +0 -52
  229. package/dist/extractor/validHTMLAttributes.cjs +0 -79
  230. package/dist/extractor/watchTamaguiConfig.cjs +0 -70
  231. package/dist/getPragmaOptions.cjs +0 -73
  232. package/dist/helpers/memoize.cjs +0 -45
  233. package/dist/helpers/requireTamaguiCore.cjs +0 -40
  234. package/dist/index.cjs +0 -42
  235. package/dist/registerRequire.cjs +0 -209
  236. package/dist/server.cjs +0 -82
  237. package/dist/types.cjs +0 -18
  238. package/dist/worker.cjs +0 -111
  239. package/src/extractor/accessSafe.ts +0 -22
  240. package/src/extractor/babelParse.ts +0 -38
  241. package/src/extractor/buildClassName.ts +0 -76
  242. package/src/extractor/createEvaluator.ts +0 -82
  243. package/src/extractor/createExtractor.ts +0 -2970
  244. package/src/extractor/createLogger.ts +0 -41
  245. package/src/extractor/ensureImportingConcat.ts +0 -36
  246. package/src/extractor/errors.ts +0 -1
  247. package/src/extractor/evaluateAstNode.ts +0 -136
  248. package/src/extractor/extractHelpers.ts +0 -211
  249. package/src/extractor/extractMediaStyle.ts +0 -194
  250. package/src/extractor/extractToClassNames.ts +0 -733
  251. package/src/extractor/extractToNative.ts +0 -506
  252. package/src/extractor/findTopmostFunction.ts +0 -24
  253. package/src/extractor/generatedUid.ts +0 -41
  254. package/src/extractor/getPrefixLogs.ts +0 -9
  255. package/src/extractor/getPropValueFromAttributes.ts +0 -95
  256. package/src/extractor/getSourceModule.ts +0 -96
  257. package/src/extractor/getStaticBindingsForScope.ts +0 -237
  258. package/src/extractor/hasTopLevelAwait.ts +0 -28
  259. package/src/extractor/hoistClassNames.ts +0 -52
  260. package/src/extractor/literalToAst.ts +0 -85
  261. package/src/extractor/loadFile.ts +0 -17
  262. package/src/extractor/logLines.ts +0 -16
  263. package/src/extractor/normalizeTernaries.ts +0 -89
  264. package/src/extractor/propsToFontFamilyCache.ts +0 -18
  265. package/src/extractor/removeUnusedHooks.ts +0 -83
  266. package/src/extractor/timer.ts +0 -25
  267. package/src/extractor/validHTMLAttributes.ts +0 -52
  268. package/src/worker.ts +0 -142
  269. package/types/extractor/accessSafe.d.ts +0 -3
  270. package/types/extractor/accessSafe.d.ts.map +0 -1
  271. package/types/extractor/babelParse.d.ts +0 -5
  272. package/types/extractor/babelParse.d.ts.map +0 -1
  273. package/types/extractor/buildClassName.d.ts +0 -7
  274. package/types/extractor/buildClassName.d.ts.map +0 -1
  275. package/types/extractor/createEvaluator.d.ts +0 -12
  276. package/types/extractor/createEvaluator.d.ts.map +0 -1
  277. package/types/extractor/createExtractor.d.ts +0 -32
  278. package/types/extractor/createExtractor.d.ts.map +0 -1
  279. package/types/extractor/createLogger.d.ts +0 -3
  280. package/types/extractor/createLogger.d.ts.map +0 -1
  281. package/types/extractor/ensureImportingConcat.d.ts +0 -4
  282. package/types/extractor/ensureImportingConcat.d.ts.map +0 -1
  283. package/types/extractor/errors.d.ts +0 -3
  284. package/types/extractor/errors.d.ts.map +0 -1
  285. package/types/extractor/evaluateAstNode.d.ts +0 -3
  286. package/types/extractor/evaluateAstNode.d.ts.map +0 -1
  287. package/types/extractor/extractHelpers.d.ts +0 -28
  288. package/types/extractor/extractHelpers.d.ts.map +0 -1
  289. package/types/extractor/extractMediaStyle.d.ts +0 -11
  290. package/types/extractor/extractMediaStyle.d.ts.map +0 -1
  291. package/types/extractor/extractToClassNames.d.ts +0 -25
  292. package/types/extractor/extractToClassNames.d.ts.map +0 -1
  293. package/types/extractor/extractToNative.d.ts +0 -13
  294. package/types/extractor/extractToNative.d.ts.map +0 -1
  295. package/types/extractor/findTopmostFunction.d.ts +0 -4
  296. package/types/extractor/findTopmostFunction.d.ts.map +0 -1
  297. package/types/extractor/generatedUid.d.ts +0 -5
  298. package/types/extractor/generatedUid.d.ts.map +0 -1
  299. package/types/extractor/getPrefixLogs.d.ts +0 -3
  300. package/types/extractor/getPrefixLogs.d.ts.map +0 -1
  301. package/types/extractor/getPropValueFromAttributes.d.ts +0 -19
  302. package/types/extractor/getPropValueFromAttributes.d.ts.map +0 -1
  303. package/types/extractor/getSourceModule.d.ts +0 -16
  304. package/types/extractor/getSourceModule.d.ts.map +0 -1
  305. package/types/extractor/getStaticBindingsForScope.d.ts +0 -5
  306. package/types/extractor/getStaticBindingsForScope.d.ts.map +0 -1
  307. package/types/extractor/hasTopLevelAwait.d.ts +0 -2
  308. package/types/extractor/hasTopLevelAwait.d.ts.map +0 -1
  309. package/types/extractor/hoistClassNames.d.ts +0 -6
  310. package/types/extractor/hoistClassNames.d.ts.map +0 -1
  311. package/types/extractor/literalToAst.d.ts +0 -4
  312. package/types/extractor/literalToAst.d.ts.map +0 -1
  313. package/types/extractor/loadFile.d.ts +0 -1
  314. package/types/extractor/loadFile.d.ts.map +0 -1
  315. package/types/extractor/logLines.d.ts +0 -2
  316. package/types/extractor/logLines.d.ts.map +0 -1
  317. package/types/extractor/normalizeTernaries.d.ts +0 -5
  318. package/types/extractor/normalizeTernaries.d.ts.map +0 -1
  319. package/types/extractor/propsToFontFamilyCache.d.ts +0 -4
  320. package/types/extractor/propsToFontFamilyCache.d.ts.map +0 -1
  321. package/types/extractor/removeUnusedHooks.d.ts +0 -3
  322. package/types/extractor/removeUnusedHooks.d.ts.map +0 -1
  323. package/types/extractor/timer.d.ts +0 -5
  324. package/types/extractor/timer.d.ts.map +0 -1
  325. package/types/extractor/validHTMLAttributes.d.ts +0 -52
  326. package/types/extractor/validHTMLAttributes.d.ts.map +0 -1
  327. package/types/worker.d.ts +0 -46
  328. package/types/worker.d.ts.map +0 -1
  329. /package/dist/{setup.cjs → cjs/setup.cjs} +0 -0
@@ -0,0 +1,3125 @@
1
+ import type {
2
+ CompilerLoweringHost,
3
+ CompilerTarget,
4
+ LoweringCandidateInput,
5
+ LoweringCandidateResult,
6
+ LoweringComponent,
7
+ MaterializedElement,
8
+ MaterializedStyledDefinition,
9
+ SourceEdit,
10
+ ZeroRule,
11
+ } from '@tamagui/compiler-core'
12
+ import { zeroRuleMessage, zeroThemeBoundaryMessage } from '@tamagui/compiler-core'
13
+ import {
14
+ StyleObjectIdentifier,
15
+ StyleObjectProperty,
16
+ StyleObjectRules,
17
+ stylePropsAll,
18
+ stylePropsText,
19
+ validStyles as validStylesView,
20
+ } from '@tamagui/helpers'
21
+ import {
22
+ ATTRIBUTES,
23
+ DISPLAY_WEB_RESET,
24
+ EVENTS,
25
+ NATIVE_BACKING,
26
+ NATIVE_BLOCK_DEFAULTS,
27
+ NATIVE_ELEMENT_DEFAULTS,
28
+ NATIVE_FLEX_DEFAULTS,
29
+ NATIVE_INPUT_TYPES,
30
+ NATIVE_PRIMITIVE_MODULE,
31
+ TAGS,
32
+ TAG_WEB_DEFAULTS,
33
+ type TagName,
34
+ } from '@tamagui/dom'
35
+ import {
36
+ createModifierRegistry,
37
+ parseTransition,
38
+ parseValue,
39
+ } from '@tamagui/style-grammar'
40
+ import { isValidStyleKey } from '@tamagui/web'
41
+ import type { AnimationDriver, StaticConfig, TamaguiInternalConfig } from '@tamagui/web'
42
+
43
+ import type { LoadedComponents } from './extractor/bundleConfig'
44
+ import { concatClassName } from './extractor/concatClassName'
45
+ import { requireTamaguiCore } from './helpers/requireTamaguiCore'
46
+
47
+ export interface CompilerComponentModule {
48
+ moduleName: string
49
+ resolvedId: string
50
+ }
51
+
52
+ export interface TamaguiCompilerHostOptions {
53
+ target: CompilerTarget
54
+ tamaguiConfig: TamaguiInternalConfig
55
+ components: LoadedComponents[]
56
+ componentModules: CompilerComponentModule[]
57
+ /** Keep elements with dynamic style props fully on the runtime path. */
58
+ disablePartialExtraction?: boolean
59
+ /** emit native theme-token mappings for the native style engine */
60
+ experimentalNativeFastPath?: boolean
61
+ /**
62
+ * Zero-runtime mode. The diagnostics stay the same shape; what changes is that
63
+ * a spread the compiler cannot prove style-free is rejected instead of merged,
64
+ * and the sites whose rule differs from their code's default say so.
65
+ */
66
+ zeroRuntime?: boolean
67
+ }
68
+
69
+ interface TamaguiLoweringComponent extends LoweringComponent {
70
+ staticConfig: StaticConfig
71
+ displayName?: string
72
+ partialRuntimeSafe: boolean
73
+ domTag?: TagName
74
+ }
75
+
76
+ const DOM_FRONTENDS = new Set([
77
+ 'tamagui',
78
+ 'tamagui/dom',
79
+ '@tamagui/core',
80
+ '@tamagui/core/dom',
81
+ '@tamagui/tailwind',
82
+ ])
83
+
84
+ const componentState = {
85
+ focus: false,
86
+ focusVisible: false,
87
+ focusWithin: false,
88
+ hover: false,
89
+ unmounted: true,
90
+ press: false,
91
+ pressIn: false,
92
+ disabled: false,
93
+ } as const
94
+
95
+ function staticObject(value: unknown): value is Record<string, unknown> {
96
+ return !!value && typeof value === 'object' && !Array.isArray(value)
97
+ }
98
+
99
+ const flatClausePattern = /(?:^|\s)@?[A-Za-z][A-Za-z0-9-]*(?:\/[A-Za-z0-9_-]+)?:/
100
+ const nativeInheritedKeywordPattern = /(?:^|:)(?:inherit|unset)(?=\s|$)/
101
+ const nativeInitialKeywordPattern = /(?:^|:)initial(?=\s|$)/
102
+ const nativeRuntimeOnlyStyleProperties = new Set(['textIndent'])
103
+
104
+ function componentKey(resolvedId: string, exportName: string): string {
105
+ return `${resolvedId}#${exportName}`
106
+ }
107
+
108
+ function cssFromRules(rules: Record<string, any>): string[] {
109
+ return Object.values(rules).flatMap((styleObject: any) => {
110
+ const identifier = styleObject?.[StyleObjectIdentifier]
111
+ const styleRules = styleObject?.[StyleObjectRules]
112
+ return identifier && Array.isArray(styleRules) ? styleRules : []
113
+ })
114
+ }
115
+
116
+ function jsxClassName(value: string): string {
117
+ return `className=${JSON.stringify(value)}`
118
+ }
119
+
120
+ function objectClassName(value: string): string {
121
+ return `className: ${JSON.stringify(value)}`
122
+ }
123
+
124
+ function serializedStyle(
125
+ style: Record<string, unknown> | null,
126
+ dynamicProperties: readonly string[]
127
+ ): string {
128
+ if (dynamicProperties.length === 0) return style ? JSON.stringify(style) : ''
129
+ return `{ ${[
130
+ ...(style
131
+ ? Object.entries(style).map(
132
+ ([name, value]) => `${JSON.stringify(name)}: ${JSON.stringify(value)}`
133
+ )
134
+ : []),
135
+ ...dynamicProperties,
136
+ ].join(', ')} }`
137
+ }
138
+
139
+ function jsxStyleAttributes(
140
+ className: string,
141
+ style: Record<string, unknown> | null,
142
+ dynamicProperties: readonly string[] = []
143
+ ) {
144
+ const serialized = serializedStyle(style, dynamicProperties)
145
+ return [
146
+ className ? jsxClassName(className) : '',
147
+ serialized ? `style={${serialized}}` : '',
148
+ ]
149
+ .filter(Boolean)
150
+ .join(' ')
151
+ }
152
+
153
+ function objectStyleProperties(
154
+ className: string,
155
+ style: Record<string, unknown> | null,
156
+ dynamicProperties: readonly string[] = []
157
+ ) {
158
+ const serialized = serializedStyle(style, dynamicProperties)
159
+ return [
160
+ className ? objectClassName(className) : '',
161
+ serialized ? `style: ${serialized}` : '',
162
+ ]
163
+ .filter(Boolean)
164
+ .join(', ')
165
+ }
166
+
167
+ function extractedStyleArtifacts(
168
+ split: any,
169
+ props: Record<string, unknown>,
170
+ config: TamaguiInternalConfig,
171
+ includeRuntimeBase = true,
172
+ hasStyleFrontend = false
173
+ ): { className: string; css: string[] } {
174
+ const callerClassName =
175
+ !hasStyleFrontend && typeof props.className === 'string' ? props.className : ''
176
+ const viewClassName =
177
+ typeof split.viewProps?.className === 'string' ? split.viewProps.className : ''
178
+ const classNameWithoutCaller =
179
+ callerClassName && viewClassName.endsWith(callerClassName)
180
+ ? viewClassName.slice(0, -callerClassName.length).trimEnd()
181
+ : viewClassName
182
+ const rules = split.rulesToInsert ?? {}
183
+ const mediaNames = new Set(Object.keys(config.media ?? {}))
184
+ const pseudoNames = [
185
+ 'hover',
186
+ 'press',
187
+ 'focus',
188
+ 'focusVisible',
189
+ 'focusWithin',
190
+ 'disabled',
191
+ ]
192
+ const buckets = {
193
+ group: [] as string[],
194
+ normal: [] as string[],
195
+ pseudo: [] as string[],
196
+ theme: [] as string[],
197
+ media: [] as string[],
198
+ }
199
+ const classKeys = new Map<string, string>()
200
+ for (const [key, identifier] of Object.entries(split.classNames ?? {})) {
201
+ if (typeof identifier === 'string') classKeys.set(identifier, key)
202
+ }
203
+ const identifiers = new Set<string>([
204
+ ...classKeys.keys(),
205
+ ...Object.values(rules).flatMap((styleObject: any) => {
206
+ const identifier = styleObject?.[StyleObjectIdentifier]
207
+ return typeof identifier === 'string' ? [identifier] : []
208
+ }),
209
+ ])
210
+ for (const identifier of identifiers) {
211
+ const key = classKeys.get(identifier) ?? ''
212
+ const css = cssFromRules(
213
+ Object.fromEntries(
214
+ Object.entries(rules).filter(
215
+ ([, styleObject]: [string, any]) =>
216
+ styleObject?.[StyleObjectIdentifier] === identifier
217
+ )
218
+ )
219
+ ).join('')
220
+ if (identifier.startsWith('t_group_')) {
221
+ buckets.group.push(identifier)
222
+ } else if (pseudoNames.some((name) => key.endsWith(`-${name}`))) {
223
+ buckets.pseudo.push(identifier)
224
+ } else if (css.includes('.t_')) {
225
+ buckets.theme.push(identifier)
226
+ } else if ([...mediaNames].some((name) => key.endsWith(`-${name}`))) {
227
+ buckets.media.push(identifier)
228
+ } else {
229
+ buckets.normal.push(identifier)
230
+ }
231
+ }
232
+ const orderedIdentifiers = [
233
+ ...buckets.group,
234
+ ...buckets.normal,
235
+ ...buckets.pseudo,
236
+ ...buckets.theme,
237
+ ...buckets.media,
238
+ ]
239
+ const orderedSet = new Set(orderedIdentifiers)
240
+ const baseViewClassName = includeRuntimeBase
241
+ ? classNameWithoutCaller
242
+ .split(/\s+/)
243
+ .filter((token) => token && !orderedSet.has(token))
244
+ .join(' ')
245
+ : ''
246
+ const css = orderedIdentifiers.flatMap((identifier) => {
247
+ const identifierRules = cssFromRules(
248
+ Object.fromEntries(
249
+ Object.entries(rules).filter(
250
+ ([, styleObject]: [string, any]) =>
251
+ styleObject?.[StyleObjectIdentifier] === identifier
252
+ )
253
+ )
254
+ )
255
+ // The legacy static theme-block path adds one root specificity level after
256
+ // resolving tokens. Keep that output contract until the legacy oracle is removed.
257
+ return buckets.theme.includes(identifier)
258
+ ? identifierRules.map((rule) =>
259
+ rule.replace(':root:root:root.t_', ':root:root:root:root.t_')
260
+ )
261
+ : identifierRules
262
+ })
263
+ return {
264
+ className: concatClassName(baseViewClassName, orderedIdentifiers, callerClassName),
265
+ css,
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Granular edits inside a compiled-call props object. Whole-span replacement would
271
+ * also cover the children property, so nested candidates could never both commit.
272
+ */
273
+ function compiledPropsEdits(
274
+ input: LoweringCandidateInput,
275
+ styleEntries: MaterializedElement['entries'],
276
+ replacement: string
277
+ ): SourceEdit[] | null {
278
+ const propsSpan = input.element.propsSpan
279
+ if (!propsSpan) return null
280
+ const original = input.source.slice(propsSpan.start, propsSpan.end)
281
+ const trimmed = original.trim()
282
+ if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
283
+ return [
284
+ {
285
+ start: propsSpan.start,
286
+ end: propsSpan.end,
287
+ content: `{ ${replacement} }`,
288
+ origin: propsSpan,
289
+ },
290
+ ]
291
+ }
292
+
293
+ if (styleEntries.length === 0) {
294
+ const close = original.lastIndexOf('}')
295
+ const separator = original.slice(1, close).trim() ? ', ' : ' '
296
+ const position = propsSpan.start + close
297
+ return [
298
+ {
299
+ start: position,
300
+ end: position,
301
+ content: `${separator}${replacement} `,
302
+ origin: propsSpan,
303
+ },
304
+ ]
305
+ }
306
+
307
+ const edits: SourceEdit[] = []
308
+ for (const [index, entry] of styleEntries.entries()) {
309
+ let start = entry.span.start
310
+ let end = entry.span.end
311
+ if (index === 0) {
312
+ edits.push({ start, end, content: replacement, origin: entry.span })
313
+ continue
314
+ }
315
+
316
+ let cursor = end - propsSpan.start
317
+ while (cursor < original.length - 1 && /\s/.test(original[cursor]!)) cursor++
318
+ if (original[cursor] === ',') {
319
+ end = propsSpan.start + cursor + 1
320
+ } else {
321
+ cursor = start - propsSpan.start - 1
322
+ while (cursor > 0 && /\s/.test(original[cursor]!)) cursor--
323
+ if (original[cursor] === ',') start = propsSpan.start + cursor
324
+ }
325
+ edits.push({ start, end, content: '', origin: entry.span })
326
+ }
327
+
328
+ const merged: SourceEdit[] = []
329
+ for (const edit of edits.sort((left, right) => left.start - right.start)) {
330
+ const previous = merged.at(-1)
331
+ if (previous && edit.start <= previous.end && !previous.content && !edit.content) {
332
+ previous.end = Math.max(previous.end, edit.end)
333
+ } else {
334
+ merged.push(edit)
335
+ }
336
+ }
337
+ return merged
338
+ }
339
+
340
+ const compilerStyleProps = new Set([
341
+ 'className',
342
+ 'style',
343
+ 'group',
344
+ 'transition',
345
+ 'animation',
346
+ 'animateOnly',
347
+ 'animatePresence',
348
+ 'animatedBy',
349
+ 'fontFamily',
350
+ 'render',
351
+ ])
352
+
353
+ const runtimeAnimationProps = new Set([
354
+ 'transition',
355
+ 'animation',
356
+ 'animateOnly',
357
+ 'animatePresence',
358
+ 'animatedBy',
359
+ ])
360
+
361
+ const cssShorthandConflicts: Record<string, readonly string[]> = {
362
+ background: [
363
+ 'backgroundAttachment',
364
+ 'backgroundBlendMode',
365
+ 'backgroundClip',
366
+ 'backgroundColor',
367
+ 'backgroundImage',
368
+ 'backgroundOrigin',
369
+ 'backgroundPosition',
370
+ 'backgroundRepeat',
371
+ 'backgroundSize',
372
+ ],
373
+ border: [
374
+ 'borderTop',
375
+ 'borderTopColor',
376
+ 'borderTopStyle',
377
+ 'borderTopWidth',
378
+ 'borderRight',
379
+ 'borderRightColor',
380
+ 'borderRightStyle',
381
+ 'borderRightWidth',
382
+ 'borderBottom',
383
+ 'borderBottomColor',
384
+ 'borderBottomStyle',
385
+ 'borderBottomWidth',
386
+ 'borderLeft',
387
+ 'borderLeftColor',
388
+ 'borderLeftStyle',
389
+ 'borderLeftWidth',
390
+ 'borderColor',
391
+ 'borderStyle',
392
+ 'borderWidth',
393
+ 'borderImage',
394
+ 'borderImageOutset',
395
+ 'borderImageRepeat',
396
+ 'borderImageSlice',
397
+ 'borderImageSource',
398
+ 'borderImageWidth',
399
+ 'borderInlineColor',
400
+ 'borderInlineEndColor',
401
+ 'borderInlineEndStyle',
402
+ 'borderInlineEndWidth',
403
+ 'borderInlineStartColor',
404
+ 'borderInlineStartStyle',
405
+ 'borderInlineStartWidth',
406
+ 'borderInlineStyle',
407
+ 'borderInlineWidth',
408
+ ],
409
+ borderTop: ['borderTopColor', 'borderTopStyle', 'borderTopWidth'],
410
+ borderRight: [
411
+ 'borderRightColor',
412
+ 'borderRightStyle',
413
+ 'borderRightWidth',
414
+ 'borderInlineColor',
415
+ 'borderInlineEndColor',
416
+ 'borderInlineEndStyle',
417
+ 'borderInlineEndWidth',
418
+ 'borderInlineStartColor',
419
+ 'borderInlineStartStyle',
420
+ 'borderInlineStartWidth',
421
+ 'borderInlineStyle',
422
+ 'borderInlineWidth',
423
+ ],
424
+ borderBottom: ['borderBottomColor', 'borderBottomStyle', 'borderBottomWidth'],
425
+ borderLeft: [
426
+ 'borderLeftColor',
427
+ 'borderLeftStyle',
428
+ 'borderLeftWidth',
429
+ 'borderInlineColor',
430
+ 'borderInlineEndColor',
431
+ 'borderInlineEndStyle',
432
+ 'borderInlineEndWidth',
433
+ 'borderInlineStartColor',
434
+ 'borderInlineStartStyle',
435
+ 'borderInlineStartWidth',
436
+ 'borderInlineStyle',
437
+ 'borderInlineWidth',
438
+ ],
439
+ borderImage: [
440
+ 'borderImageOutset',
441
+ 'borderImageRepeat',
442
+ 'borderImageSlice',
443
+ 'borderImageSource',
444
+ 'borderImageWidth',
445
+ ],
446
+ outline: ['outlineColor', 'outlineStyle', 'outlineWidth'],
447
+ gap: ['columnGap', 'rowGap'],
448
+ gridColumn: ['gridColumnEnd', 'gridColumnStart'],
449
+ gridRow: ['gridRowEnd', 'gridRowStart'],
450
+ marginInline: [
451
+ 'marginInlineEnd',
452
+ 'marginInlineStart',
453
+ 'marginEnd',
454
+ 'marginStart',
455
+ 'marginLeft',
456
+ 'marginRight',
457
+ ],
458
+ paddingInline: [
459
+ 'paddingInlineEnd',
460
+ 'paddingInlineStart',
461
+ 'paddingEnd',
462
+ 'paddingStart',
463
+ 'paddingLeft',
464
+ 'paddingRight',
465
+ ],
466
+ insetInline: ['insetInlineEnd', 'insetInlineStart', 'end', 'start', 'left', 'right'],
467
+ borderInlineColor: [
468
+ 'borderInlineEndColor',
469
+ 'borderInlineStartColor',
470
+ 'borderEndColor',
471
+ 'borderStartColor',
472
+ 'borderLeftColor',
473
+ 'borderRightColor',
474
+ ],
475
+ borderInlineStyle: [
476
+ 'borderInlineEndStyle',
477
+ 'borderInlineStartStyle',
478
+ 'borderEndStyle',
479
+ 'borderStartStyle',
480
+ 'borderLeftStyle',
481
+ 'borderRightStyle',
482
+ ],
483
+ borderInlineWidth: [
484
+ 'borderInlineEndWidth',
485
+ 'borderInlineStartWidth',
486
+ 'borderEndWidth',
487
+ 'borderStartWidth',
488
+ 'borderLeftWidth',
489
+ 'borderRightWidth',
490
+ ],
491
+ mask: [
492
+ 'maskBorder',
493
+ 'maskBorderMode',
494
+ 'maskBorderOutset',
495
+ 'maskBorderRepeat',
496
+ 'maskBorderSlice',
497
+ 'maskBorderSource',
498
+ 'maskBorderWidth',
499
+ 'maskClip',
500
+ 'maskComposite',
501
+ 'maskImage',
502
+ 'maskMode',
503
+ 'maskOrigin',
504
+ 'maskPosition',
505
+ 'maskRepeat',
506
+ 'maskSize',
507
+ ],
508
+ maskBorder: [
509
+ 'maskBorderMode',
510
+ 'maskBorderOutset',
511
+ 'maskBorderRepeat',
512
+ 'maskBorderSlice',
513
+ 'maskBorderSource',
514
+ 'maskBorderWidth',
515
+ ],
516
+ }
517
+
518
+ const cssConflictFamilies = [
519
+ new Set([
520
+ 'marginInline',
521
+ 'marginInlineEnd',
522
+ 'marginInlineStart',
523
+ 'marginEnd',
524
+ 'marginStart',
525
+ 'marginLeft',
526
+ 'marginRight',
527
+ ]),
528
+ new Set([
529
+ 'paddingInline',
530
+ 'paddingInlineEnd',
531
+ 'paddingInlineStart',
532
+ 'paddingEnd',
533
+ 'paddingStart',
534
+ 'paddingLeft',
535
+ 'paddingRight',
536
+ ]),
537
+ new Set([
538
+ 'insetInline',
539
+ 'insetInlineEnd',
540
+ 'insetInlineStart',
541
+ 'end',
542
+ 'start',
543
+ 'left',
544
+ 'right',
545
+ ]),
546
+ new Set([
547
+ 'borderInlineColor',
548
+ 'borderInlineEndColor',
549
+ 'borderInlineStartColor',
550
+ 'borderEndColor',
551
+ 'borderStartColor',
552
+ 'borderLeftColor',
553
+ 'borderRightColor',
554
+ ]),
555
+ new Set([
556
+ 'borderInlineStyle',
557
+ 'borderInlineEndStyle',
558
+ 'borderInlineStartStyle',
559
+ 'borderEndStyle',
560
+ 'borderStartStyle',
561
+ 'borderLeftStyle',
562
+ 'borderRightStyle',
563
+ ]),
564
+ new Set([
565
+ 'borderInlineWidth',
566
+ 'borderInlineEndWidth',
567
+ 'borderInlineStartWidth',
568
+ 'borderEndWidth',
569
+ 'borderStartWidth',
570
+ 'borderLeftWidth',
571
+ 'borderRightWidth',
572
+ ]),
573
+ ]
574
+
575
+ function cssOwnersConflict(left: Set<string>, right: Set<string>): boolean {
576
+ for (const leftOwner of left) {
577
+ for (const rightOwner of right) {
578
+ if (
579
+ leftOwner === rightOwner ||
580
+ cssShorthandConflicts[leftOwner]?.includes(rightOwner) ||
581
+ cssShorthandConflicts[rightOwner]?.includes(leftOwner) ||
582
+ cssConflictFamilies.some(
583
+ (family) => family.has(leftOwner) && family.has(rightOwner)
584
+ )
585
+ ) {
586
+ return true
587
+ }
588
+ }
589
+ }
590
+ return false
591
+ }
592
+
593
+ const runtimeEventProps = new Set([
594
+ 'onHoverIn',
595
+ 'onHoverOut',
596
+ 'onLongPress',
597
+ 'onPress',
598
+ 'onPressIn',
599
+ 'onPressOut',
600
+ ])
601
+
602
+ /**
603
+ * DOM attributes whose react native spelling is a style key, so they lower into
604
+ * the style rather than onto the host: `dir` is the text style
605
+ * `writingDirection`. An attribute already named like its style key needs no
606
+ * entry, because the split recognizes it by name. The runtime reaches the same
607
+ * place for free: its frame classifies the renamed prop against validStyles.
608
+ */
609
+ const DOM_STYLE_ATTRIBUTES = new Map(
610
+ Object.entries(ATTRIBUTES)
611
+ .filter(
612
+ ([name, row]) =>
613
+ row.native !== 'none' &&
614
+ row.nativeProp &&
615
+ row.nativeProp !== name &&
616
+ row.nativeProp in stylePropsText
617
+ )
618
+ .map(([name, row]) => [name, row.nativeProp as string])
619
+ )
620
+
621
+ // native-only: usePointerEvents maps these to touch events at runtime; a
622
+ // flattened bare RN View ignores them (RN's W3C pointer events are flag-gated
623
+ // off). on web they are real DOM events and pass through, so no bail there.
624
+ const nativePointerEventProps = new Set([
625
+ 'onPointerCancel',
626
+ 'onPointerDown',
627
+ 'onPointerEnter',
628
+ 'onPointerLeave',
629
+ 'onPointerMove',
630
+ 'onPointerUp',
631
+ ])
632
+
633
+ function isSerializableNativeStyle(value: unknown): boolean {
634
+ if (value == null || typeof value === 'number' || typeof value === 'boolean') {
635
+ return true
636
+ }
637
+ if (typeof value === 'string') return true
638
+ if (Array.isArray(value)) return value.every(isSerializableNativeStyle)
639
+ if (!staticObject(value)) return false
640
+ const prototype = Object.getPrototypeOf(value)
641
+ if (prototype !== Object.prototype && prototype !== null) return false
642
+ return Object.values(value).every(isSerializableNativeStyle)
643
+ }
644
+
645
+ function unusedIdentifier(source: string, base: string): string {
646
+ let candidate = base
647
+ let suffix = 0
648
+ while (new RegExp(`\\b${candidate}\\b`).test(source)) {
649
+ candidate = `${base}${++suffix}`
650
+ }
651
+ return candidate
652
+ }
653
+
654
+ type DOMPropEntry = Extract<MaterializedElement['entries'][number], { kind: 'prop' }>
655
+
656
+ /** props RN's <View> remaps or composes before handing them to the host component */
657
+ const VIEW_WRAPPER_PROPS =
658
+ /^(aria-|accessibilityState$|accessibilityValue$|id$|tabIndex$|nativeID$)/
659
+
660
+ /**
661
+ * Whether an element can render RN's host component directly instead of
662
+ * <View>. A child is disqualifying because <View> resets TextAncestorContext
663
+ * for its descendants when it sits inside a <Text>, and no per-element
664
+ * analysis can rule that out; with no children nothing can observe it. A
665
+ * spread is disqualifying because it can carry the aria props this checks for.
666
+ */
667
+ function isBareHostView(element: MaterializedElement): boolean {
668
+ return !element.entries.some(
669
+ (entry) =>
670
+ entry.kind === 'child' ||
671
+ entry.kind === 'spread' ||
672
+ (entry.kind === 'prop' && VIEW_WRAPPER_PROPS.test(entry.name))
673
+ )
674
+ }
675
+
676
+ function entrySource(input: LoweringCandidateInput, entry: DOMPropEntry): string {
677
+ return entry.value.kind === 'static'
678
+ ? JSON.stringify(entry.value.value)
679
+ : input.source.slice(entry.value.span.start, entry.value.span.end)
680
+ }
681
+
682
+ function renamedPropEdit(
683
+ input: LoweringCandidateInput,
684
+ entry: DOMPropEntry,
685
+ name: string
686
+ ): SourceEdit | null {
687
+ if (entry.name === name) return null
688
+ const content = input.source.slice(entry.span.start, entry.span.end)
689
+ const offset = content.indexOf(entry.name)
690
+ if (offset === -1) return null
691
+ const quoted = content[offset - 1] === '"' || content[offset - 1] === "'"
692
+ return {
693
+ start: entry.span.start + offset,
694
+ end: entry.span.start + offset + entry.name.length,
695
+ content: input.element.form === 'jsx' || quoted ? name : JSON.stringify(name),
696
+ origin: entry.span,
697
+ }
698
+ }
699
+
700
+ function serializedProps(
701
+ form: MaterializedElement['form'],
702
+ props: readonly [name: string, value: string][]
703
+ ): string {
704
+ return props
705
+ .map(([name, value]) =>
706
+ form === 'jsx' ? `${name}={${value}}` : `${JSON.stringify(name)}: ${value}`
707
+ )
708
+ .join(form === 'jsx' ? ' ' : ', ')
709
+ }
710
+
711
+ function nativeDOMProps(input: LoweringCandidateInput, tag: TagName) {
712
+ const consumed: DOMPropEntry[] = []
713
+ const edits: SourceEdit[] = []
714
+ const additions: [string, string][] = []
715
+ const nested = new Map<string, [string, string][]>()
716
+ const entries = input.element.entries.filter(
717
+ (entry): entry is DOMPropEntry => entry.kind === 'prop'
718
+ )
719
+ const names = new Set(entries.map((entry) => entry.name))
720
+ if (names.has('ref') || tag === 'br') additions.push(['__tag', JSON.stringify(tag)])
721
+ if (TAGS[tag].backing === 'text' || TAGS[tag].backing === 'textinput') {
722
+ additions.push(['__inherit', 'true'])
723
+ }
724
+ const add = (name: string, value: string) => additions.push([name, value])
725
+ const consume = (entry: DOMPropEntry) => consumed.push(entry)
726
+
727
+ for (const entry of entries) {
728
+ const value = entrySource(input, entry)
729
+ const attribute =
730
+ (Object.hasOwn(ATTRIBUTES, entry.name) ? ATTRIBUTES[entry.name] : undefined) ??
731
+ (entry.name.startsWith('data-') ? ATTRIBUTES['data-*'] : undefined)
732
+ const event = Object.hasOwn(EVENTS, entry.name) ? EVENTS[entry.name] : undefined
733
+ if (event) {
734
+ if (event.native === 'none') {
735
+ return {
736
+ consumed,
737
+ edits,
738
+ additions,
739
+ diagnostic: `${entry.name} has no native DOM event equivalent`,
740
+ diagnosticSpan: entry.span,
741
+ }
742
+ }
743
+ if (entry.name === 'onKeyDown' && tag !== 'input' && tag !== 'textarea') {
744
+ return {
745
+ consumed,
746
+ edits,
747
+ additions,
748
+ diagnostic: `onKeyDown requires a native text-entry control`,
749
+ diagnosticSpan: entry.span,
750
+ }
751
+ }
752
+ // These payloads are adapted by the hookless DOM primitives.
753
+ if (
754
+ ['onClick', 'onLoad', 'onError', 'onChange', 'onInput', 'onKeyDown'].includes(
755
+ entry.name
756
+ )
757
+ ) {
758
+ continue
759
+ }
760
+ if (event.nativeProp) {
761
+ const edit = renamedPropEdit(input, entry, event.nativeProp)
762
+ if (edit) edits.push(edit)
763
+ }
764
+ continue
765
+ }
766
+ if (!attribute || attribute.native === 'none' || entry.name === 'style') continue
767
+
768
+ if (DOM_STYLE_ATTRIBUTES.has(entry.name)) {
769
+ // the style resolution above already read it under its style key
770
+ if (entry.value.kind !== 'static') {
771
+ return {
772
+ consumed,
773
+ edits,
774
+ additions,
775
+ diagnostic: `html.${tag} ${entry.name} must be statically known for native lowering`,
776
+ diagnosticSpan: entry.span,
777
+ }
778
+ }
779
+ consume(entry)
780
+ continue
781
+ }
782
+ if (entry.name === 'hidden') {
783
+ if (entry.value.kind !== 'static') {
784
+ return {
785
+ consumed,
786
+ edits,
787
+ additions,
788
+ diagnostic: `html.${tag} hidden must be statically known for native lowering`,
789
+ diagnosticSpan: entry.span,
790
+ }
791
+ }
792
+ consume(entry)
793
+ continue
794
+ }
795
+ if (entry.name === 'tabIndex') {
796
+ consume(entry)
797
+ add('focusable', `(${value}) === 0`)
798
+ continue
799
+ }
800
+ if (entry.name === 'readOnly') {
801
+ consume(entry)
802
+ add('editable', `!(${value})`)
803
+ continue
804
+ }
805
+ if (entry.name === 'disabled') {
806
+ consume(entry)
807
+ add('disabled', value)
808
+ add('focusable', `!(${value})`)
809
+ if (tag === 'input' || tag === 'textarea') add('editable', `!(${value})`)
810
+ const state = nested.get('accessibilityState') ?? []
811
+ state.push(['disabled', value])
812
+ nested.set('accessibilityState', state)
813
+ continue
814
+ }
815
+ if (entry.name === 'type') {
816
+ consume(entry)
817
+ if (tag === 'input' && entry.value.kind !== 'static') {
818
+ return {
819
+ consumed,
820
+ edits,
821
+ additions,
822
+ diagnostic: `html.input type must be statically known for native lowering`,
823
+ diagnosticSpan: entry.span,
824
+ }
825
+ }
826
+ if (tag === 'input' && entry.value.kind === 'static') {
827
+ const type = entry.value.value
828
+ if (type === 'password') add('secureTextEntry', 'true')
829
+ else if (
830
+ typeof type === 'string' &&
831
+ type !== 'text' &&
832
+ NATIVE_INPUT_TYPES.includes(type)
833
+ ) {
834
+ if (!names.has('inputMode')) {
835
+ add('inputMode', JSON.stringify(type === 'number' ? 'numeric' : type))
836
+ }
837
+ }
838
+ }
839
+ continue
840
+ }
841
+ if (entry.name === 'aria-hidden') {
842
+ consume(entry)
843
+ add('accessibilityElementsHidden', value)
844
+ add('importantForAccessibility', `(${value}) ? "no-hide-descendants" : "auto"`)
845
+ continue
846
+ }
847
+ if (entry.name === 'aria-live') {
848
+ consume(entry)
849
+ add(
850
+ 'accessibilityLiveRegion',
851
+ entry.value.kind === 'static' && entry.value.value === 'off' ? '"none"' : value
852
+ )
853
+ continue
854
+ }
855
+ const nativeProp = attribute.nativeProp
856
+ if (!nativeProp) continue
857
+ if (nativeProp.includes('.')) {
858
+ consume(entry)
859
+ const [parent, child] = nativeProp.split('.') as [string, string]
860
+ const values = nested.get(parent) ?? []
861
+ values.push([child, value])
862
+ nested.set(parent, values)
863
+ continue
864
+ }
865
+ const edit = renamedPropEdit(input, entry, nativeProp)
866
+ if (edit) edits.push(edit)
867
+ }
868
+
869
+ for (const [name, values] of nested) {
870
+ add(name, `{ ${values.map(([key, value]) => `${key}: ${value}`).join(', ')} }`)
871
+ }
872
+ if (!names.has('role') && TAGS[tag].role) add('role', JSON.stringify(TAGS[tag].role))
873
+ if (tag === 'textarea') add('multiline', 'true')
874
+ return { consumed, edits, additions }
875
+ }
876
+
877
+ function webDOMProps(input: LoweringCandidateInput, tag: TagName) {
878
+ const edits: SourceEdit[] = []
879
+ const additions: [string, string][] = []
880
+ const entries = input.element.entries.filter(
881
+ (entry): entry is DOMPropEntry => entry.kind === 'prop'
882
+ )
883
+ const names = new Set(entries.map((entry) => entry.name))
884
+ for (const entry of entries) {
885
+ if (entry.name === 'for') {
886
+ const edit = renamedPropEdit(input, entry, 'htmlFor')
887
+ if (edit) edits.push(edit)
888
+ }
889
+ if (
890
+ entry.name === 'role' &&
891
+ entry.value.kind === 'static' &&
892
+ entry.value.value === 'none'
893
+ ) {
894
+ edits.push({
895
+ start: entry.value.span.start,
896
+ end: entry.value.span.end,
897
+ content: JSON.stringify('presentation'),
898
+ origin: entry.value.span,
899
+ })
900
+ }
901
+ }
902
+ if (tag === 'button' && !names.has('type')) additions.push(['type', '"button"'])
903
+ if ((tag === 'input' || tag === 'textarea') && !names.has('dir')) {
904
+ additions.push(['dir', '"auto"'])
905
+ }
906
+ return { edits, additions }
907
+ }
908
+
909
+ export function createTamaguiCompilerHost(
910
+ options: TamaguiCompilerHostOptions
911
+ ): CompilerLoweringHost {
912
+ const platform = options.target === 'native' ? 'native' : 'web'
913
+ const core = requireTamaguiCore(platform) as any
914
+ const firstThemeName = Object.keys(options.tamaguiConfig.themes ?? {})[0] ?? ''
915
+ const firstTheme = options.tamaguiConfig.themes?.[firstThemeName] ?? {}
916
+ const theme = firstTheme
917
+ const modifierRegistry = createModifierRegistry({
918
+ mediaNames: options.tamaguiConfig.media ?? {},
919
+ themeNames: options.tamaguiConfig.themes ?? {},
920
+ }).registry
921
+ const configuredAnimationDriver = options.tamaguiConfig.animations as
922
+ | AnimationDriver
923
+ | undefined
924
+ const configuredCssAnimationDriver =
925
+ platform === 'web' &&
926
+ configuredAnimationDriver?.outputStyle === 'css' &&
927
+ !options.tamaguiConfig.animationDrivers
928
+ ? configuredAnimationDriver
929
+ : null
930
+ const resolveStaticCssTransition = (
931
+ value: unknown,
932
+ transitionPresets: Record<string, unknown>
933
+ ): string | null => {
934
+ if (platform !== 'web' || typeof value !== 'string') return null
935
+ const transitionPresetNames = new Set(Object.keys(transitionPresets))
936
+ const program = parseValue(value, modifierRegistry)
937
+ if (!program.ok) return null
938
+ const resolvedPayloads: string[] = []
939
+ const payloads = [
940
+ program.value.base,
941
+ ...program.value.clauses.map((clause) => clause.payload),
942
+ ].filter((payload): payload is string => payload !== null)
943
+ if (payloads.length === 0) return null
944
+ for (const payload of payloads) {
945
+ const transition = parseTransition(payload, transitionPresetNames)
946
+ if (!transition.ok) return null
947
+ if (
948
+ transition.value.kind === 'global' ||
949
+ transition.value.entries.every((entry) => entry.timing.type === 'css')
950
+ ) {
951
+ resolvedPayloads.push(payload)
952
+ continue
953
+ }
954
+ if (
955
+ transition.value.entries.length !== 1 ||
956
+ transition.value.entries[0]!.timing.type !== 'preset'
957
+ ) {
958
+ return null
959
+ }
960
+ const preset = transitionPresets[transition.value.entries[0]!.timing.name]
961
+ if (typeof preset !== 'string') return null
962
+ const parsedPreset = parseTransition(preset)
963
+ if (
964
+ !parsedPreset.ok ||
965
+ parsedPreset.value.kind !== 'transition' ||
966
+ parsedPreset.value.entries.length !== 1 ||
967
+ parsedPreset.value.entries[0]!.property !== 'all' ||
968
+ parsedPreset.value.entries[0]!.timing.type !== 'css'
969
+ ) {
970
+ return null
971
+ }
972
+ resolvedPayloads.push(`all ${preset}`)
973
+ }
974
+ let payloadIndex = 0
975
+ const resolved: string[] = []
976
+ if (program.value.base !== null) {
977
+ resolved.push(resolvedPayloads[payloadIndex++]!)
978
+ }
979
+ for (const clause of program.value.clauses) {
980
+ resolved.push(`${clause.modifiers.join(':')}:${resolvedPayloads[payloadIndex++]!}`)
981
+ }
982
+ return resolved.join(' ')
983
+ }
984
+ const modulesById = new Map(
985
+ options.componentModules.map((module) => [module.resolvedId, module.moduleName])
986
+ )
987
+ const componentsByModule = new Map(
988
+ options.components.map((component) => [component.moduleName, component])
989
+ )
990
+
991
+ // A component's import provenance chose its authoring syntax, and the frontend
992
+ // descriptor frozen onto its static config is the only thing that knows how to
993
+ // resolve that syntax's static input (a class-string base, string variants, string
994
+ // compound variants) into styles. Regular Tamagui components carry no descriptor
995
+ // because their static config is already the renderer's input shape. The compiler
996
+ // must never import a frontend package: `@tamagui/tailwind/vite` already depends on
997
+ // the static compiler, so reaching the other way would be a cycle.
998
+ const normalizeStaticConfig = (staticConfig: StaticConfig): StaticConfig =>
999
+ (staticConfig as any).styleFrontend?.normalizeStaticConfig?.(
1000
+ staticConfig,
1001
+ options.tamaguiConfig
1002
+ ) ?? staticConfig
1003
+
1004
+ const directStaticConfig = (
1005
+ element: MaterializedElement
1006
+ ): { key: string; staticConfig: StaticConfig; displayName?: string } | null => {
1007
+ const identity = element.component.provenance
1008
+ if (!identity) return null
1009
+ const moduleName = modulesById.get(identity.resolvedId)
1010
+ const component = moduleName ? componentsByModule.get(moduleName) : undefined
1011
+ const info = component?.nameToInfo[identity.importedName]
1012
+ return info
1013
+ ? {
1014
+ key: componentKey(identity.resolvedId, identity.importedName),
1015
+ staticConfig: normalizeStaticConfig(info.staticConfig),
1016
+ displayName: info.displayName,
1017
+ }
1018
+ : null
1019
+ }
1020
+
1021
+ const domStaticConfig = (
1022
+ element: MaterializedElement
1023
+ ): {
1024
+ key: string
1025
+ staticConfig: StaticConfig
1026
+ displayName: string
1027
+ tag: TagName
1028
+ } | null => {
1029
+ const identity = element.component.provenance
1030
+ const tag = element.component.name as TagName
1031
+ if (
1032
+ identity?.importedName !== 'html' ||
1033
+ !DOM_FRONTENDS.has(identity.specifier) ||
1034
+ !Object.hasOwn(TAGS, tag)
1035
+ ) {
1036
+ return null
1037
+ }
1038
+ const row = TAGS[tag]
1039
+ const textLike = row.backing === 'text' || row.backing === 'textinput'
1040
+ const base = (textLike ? core.Text : core.View)?.staticConfig as
1041
+ | StaticConfig
1042
+ | undefined
1043
+ if (!base) return null
1044
+ const platformDefaults =
1045
+ platform === 'web'
1046
+ ? {
1047
+ ...DISPLAY_WEB_RESET[row.display],
1048
+ ...row.defaults,
1049
+ ...TAG_WEB_DEFAULTS[tag],
1050
+ }
1051
+ : {
1052
+ ...NATIVE_ELEMENT_DEFAULTS,
1053
+ ...(row.display === 'block' ? NATIVE_BLOCK_DEFAULTS : null),
1054
+ ...row.defaults,
1055
+ ...row.nativeDefaults,
1056
+ }
1057
+ return {
1058
+ key: componentKey(identity.resolvedId, `html.${tag}`),
1059
+ tag,
1060
+ displayName: tag,
1061
+ staticConfig: normalizeStaticConfig({
1062
+ ...base,
1063
+ isInput: row.backing === 'textinput',
1064
+ // CSS text properties may be authored on any element and inherited by
1065
+ // descendant text, including from a View-backed tag on native.
1066
+ validStyles: { ...validStylesView, ...stylePropsText },
1067
+ defaultProps: {
1068
+ ...base.defaultProps,
1069
+ ...platformDefaults,
1070
+ },
1071
+ }),
1072
+ }
1073
+ }
1074
+
1075
+ const styledStaticConfig = (
1076
+ definition: MaterializedStyledDefinition | null
1077
+ ): { key: string; staticConfig: StaticConfig; displayName?: string } | null => {
1078
+ if (!definition || definition.options.kind !== 'static') return null
1079
+ const base = directStaticConfig({
1080
+ kind: 'element',
1081
+ form: 'jsx',
1082
+ id: definition.id,
1083
+ span: definition.span,
1084
+ propsSpan: null,
1085
+ component: definition.base,
1086
+ complete: true,
1087
+ entries: [],
1088
+ bailouts: [],
1089
+ })
1090
+ if (!base || !staticObject(definition.options.value)) return null
1091
+ const {
1092
+ variants,
1093
+ defaultVariants,
1094
+ compoundVariants,
1095
+ displayName,
1096
+ context,
1097
+ contextProps,
1098
+ ...defaultProps
1099
+ } = definition.options.value as Record<string, any>
1100
+ const baseClassName =
1101
+ definition.baseClassName?.kind === 'static' &&
1102
+ typeof definition.baseClassName.value === 'string'
1103
+ ? definition.baseClassName.value
1104
+ : undefined
1105
+ return {
1106
+ key: componentKey(definition.id, definition.name),
1107
+ displayName: displayName || base.displayName,
1108
+ staticConfig: normalizeStaticConfig({
1109
+ ...base.staticConfig,
1110
+ variants: {
1111
+ ...base.staticConfig.variants,
1112
+ ...(variants as object | undefined),
1113
+ },
1114
+ compoundVariants: [
1115
+ ...(base.staticConfig.compoundVariants ?? []),
1116
+ ...(compoundVariants ?? []),
1117
+ ],
1118
+ defaultProps: {
1119
+ ...base.staticConfig.defaultProps,
1120
+ ...defaultProps,
1121
+ ...(defaultVariants as object | undefined),
1122
+ },
1123
+ defaultVariants,
1124
+ baseClassName: [base.staticConfig.baseClassName, baseClassName]
1125
+ .filter(Boolean)
1126
+ .join(' '),
1127
+ context: context ?? base.staticConfig.context,
1128
+ contextProps: context
1129
+ ? contextProps
1130
+ : (contextProps ?? base.staticConfig.contextProps),
1131
+ }),
1132
+ }
1133
+ }
1134
+
1135
+ const resolve = (
1136
+ element: MaterializedElement,
1137
+ styledDefinition: MaterializedStyledDefinition | null
1138
+ ): TamaguiLoweringComponent | null => {
1139
+ const dom = domStaticConfig(element)
1140
+ const resolved =
1141
+ dom ?? styledStaticConfig(styledDefinition) ?? directStaticConfig(element)
1142
+ if (!resolved) return null
1143
+ const defaultProps = core.getDefaultProps(resolved.staticConfig) ?? {}
1144
+ return {
1145
+ key: resolved.key,
1146
+ canFlatten:
1147
+ resolved.staticConfig.acceptsClassName !== false &&
1148
+ !resolved.staticConfig.neverFlatten &&
1149
+ !resolved.staticConfig.context,
1150
+ staticConfig: resolved.staticConfig,
1151
+ displayName: resolved.displayName,
1152
+ ...(dom && { domTag: dom.tag }),
1153
+ // retaining the component also retains these runtime style sources.
1154
+ // splitting their output into equal-specificity atomic classes would
1155
+ // make stylesheet insertion order decide the winner.
1156
+ partialRuntimeSafe:
1157
+ !dom &&
1158
+ !styledDefinition &&
1159
+ Object.keys(defaultProps).length === 0 &&
1160
+ !resolved.staticConfig.defaultVariants &&
1161
+ !resolved.staticConfig.baseClassName &&
1162
+ !resolved.staticConfig.baseStyle &&
1163
+ (resolved.staticConfig.compoundVariants?.length ?? 0) === 0,
1164
+ }
1165
+ }
1166
+
1167
+ const isStyleProp = (name: string, component: LoweringComponent): boolean => {
1168
+ const staticConfig = component.staticConfig as StaticConfig
1169
+ return (
1170
+ compilerStyleProps.has(name) ||
1171
+ name in (options.tamaguiConfig.shorthands ?? {}) ||
1172
+ !!staticConfig.validStyles?.[name] ||
1173
+ !!staticConfig.variants?.[name]
1174
+ )
1175
+ }
1176
+
1177
+ // props the compiler can never pre-resolve per-branch: they carry runtime
1178
+ // semantics beyond style resolution. Everything else that isStyleProp admits
1179
+ // — including fontFamily and variants — is safe for conditional lowering,
1180
+ // because each branch runs the full resolveSplitStyles pipeline
1181
+ const runtimeOnlyStyleProps = new Set([
1182
+ 'className',
1183
+ 'style',
1184
+ 'group',
1185
+ 'transition',
1186
+ 'animation',
1187
+ 'animateOnly',
1188
+ 'animatePresence',
1189
+ 'animatedBy',
1190
+ 'render',
1191
+ ])
1192
+ const canLowerConditionalStyleProp = (
1193
+ name: string,
1194
+ component: LoweringComponent
1195
+ ): boolean => isStyleProp(name, component) && !runtimeOnlyStyleProps.has(name)
1196
+
1197
+ const isInvalidHostStyleProp = (
1198
+ name: string,
1199
+ component: LoweringComponent
1200
+ ): boolean => {
1201
+ const staticConfig = component.staticConfig as StaticConfig
1202
+ const validStyles =
1203
+ staticConfig.validStyles ||
1204
+ (staticConfig.isText || staticConfig.isInput ? stylePropsText : validStylesView)
1205
+ return (
1206
+ name in stylePropsAll && !isValidStyleKey(name, validStyles, staticConfig.accept)
1207
+ )
1208
+ }
1209
+
1210
+ const directStyleName = (name: string, component: LoweringComponent): string | null => {
1211
+ if (compilerStyleProps.has(name) || name === 'style') {
1212
+ return null
1213
+ }
1214
+ const staticConfig = component.staticConfig as StaticConfig
1215
+ if (staticConfig.variants?.[name]) return null
1216
+ const expanded = options.tamaguiConfig.shorthands?.[name] ?? name
1217
+ return staticConfig.validStyles?.[expanded] ? expanded : null
1218
+ }
1219
+
1220
+ const resolveSplitStyles = (
1221
+ props: Record<string, unknown>,
1222
+ staticConfig: StaticConfig,
1223
+ animationDriver?: AnimationDriver | null,
1224
+ displayName?: string
1225
+ ) => {
1226
+ const previousStatic = process.env.IS_STATIC
1227
+ const previousTarget = process.env.TAMAGUI_TARGET
1228
+ if (platform === 'native') {
1229
+ process.env.IS_STATIC = 'is_static'
1230
+ } else {
1231
+ delete process.env.IS_STATIC
1232
+ }
1233
+ process.env.TAMAGUI_TARGET = platform
1234
+ try {
1235
+ return core.getSplitStyles(
1236
+ props,
1237
+ staticConfig,
1238
+ theme,
1239
+ firstThemeName,
1240
+ componentState,
1241
+ {
1242
+ resolveValues: platform === 'native' ? 'except-theme' : 'variable',
1243
+ noClass: platform === 'native',
1244
+ isAnimated: false,
1245
+ displayName,
1246
+ },
1247
+ undefined,
1248
+ undefined,
1249
+ undefined,
1250
+ undefined,
1251
+ undefined,
1252
+ undefined,
1253
+ animationDriver
1254
+ )
1255
+ } finally {
1256
+ if (previousStatic === undefined) delete process.env.IS_STATIC
1257
+ else process.env.IS_STATIC = previousStatic
1258
+ if (previousTarget === undefined) delete process.env.TAMAGUI_TARGET
1259
+ else process.env.TAMAGUI_TARGET = previousTarget
1260
+ }
1261
+ }
1262
+
1263
+ const partialStaticConfig = (staticConfig: StaticConfig): StaticConfig => ({
1264
+ ...staticConfig,
1265
+ baseStyle: undefined,
1266
+ defaultProps: {},
1267
+ defaultVariants: undefined,
1268
+ compoundVariants: [],
1269
+ variants: {},
1270
+ })
1271
+
1272
+ const styleOwners = (
1273
+ name: string,
1274
+ value: unknown,
1275
+ staticConfig: StaticConfig
1276
+ ): Set<string> | null => {
1277
+ const split = resolveSplitStyles({ [name]: value }, partialStaticConfig(staticConfig))
1278
+ if (!split) return null
1279
+
1280
+ const inlineStyle = split.viewProps?.style
1281
+ if (
1282
+ staticObject(inlineStyle) &&
1283
+ !inlineStyle['$$css'] &&
1284
+ Object.keys(inlineStyle).length > 0
1285
+ ) {
1286
+ return null
1287
+ }
1288
+
1289
+ const owners = new Set<string>(Object.keys(split.classNames ?? {}))
1290
+ for (const styleObject of Object.values(split.rulesToInsert ?? {}) as any[]) {
1291
+ const property = styleObject?.[StyleObjectProperty]
1292
+ if (typeof property === 'string') owners.add(property)
1293
+ }
1294
+ return owners.size > 0 ? owners : null
1295
+ }
1296
+
1297
+ const dynamicStyleOwners = (
1298
+ name: string,
1299
+ staticConfig: StaticConfig
1300
+ ): Set<string> | null => {
1301
+ // most web style normalization depends only on the prop name. these values
1302
+ // exercise the few structured inputs so getSplitStyles can report the
1303
+ // normalized CSS property it would own. flex is value-dependent, so include
1304
+ // every property produced by both its numeric and CSS-shorthand forms.
1305
+ const probeValue =
1306
+ name === 'transform'
1307
+ ? [{ scale: 1 }]
1308
+ : name === 'transformMatrix'
1309
+ ? [1, 0, 0, 1, 0, 0]
1310
+ : name === 'shadowOffset'
1311
+ ? { width: 0, height: 0 }
1312
+ : name === 'shadowColor'
1313
+ ? 'black'
1314
+ : name === 'border' || name === 'outline'
1315
+ ? '0 solid transparent'
1316
+ : name === 'position'
1317
+ ? 'relative'
1318
+ : name === 'objectFit'
1319
+ ? 'contain'
1320
+ : 0
1321
+ const owners = styleOwners(name, probeValue, staticConfig)
1322
+ if (!owners || name !== 'flex') return owners
1323
+ const shorthandOwners = styleOwners(name, '0 1 auto', staticConfig)
1324
+ if (!shorthandOwners) return null
1325
+ for (const owner of shorthandOwners) owners.add(owner)
1326
+ return owners
1327
+ }
1328
+
1329
+ const developmentDebugInstrumentation = (
1330
+ input: LoweringCandidateInput,
1331
+ result: LoweringCandidateResult
1332
+ ) => {
1333
+ if (process.env.NODE_ENV !== 'development') return
1334
+ const debugEntry = input.element.entries.find(
1335
+ (entry) =>
1336
+ entry.kind === 'prop' &&
1337
+ entry.name === 'debug' &&
1338
+ entry.value.kind === 'static' &&
1339
+ entry.value.value === 'verbose'
1340
+ )
1341
+ if (!debugEntry) return
1342
+
1343
+ const component = input.component as TamaguiLoweringComponent
1344
+ const styleEntries = input.element.entries.filter(
1345
+ (entry): entry is DOMPropEntry =>
1346
+ entry.kind === 'prop' &&
1347
+ entry.name !== 'debug' &&
1348
+ (entry.name === 'style' || isStyleProp(entry.name, component))
1349
+ )
1350
+ const flattened = result.ok && !!result.flattened
1351
+ const tiers = result.ok
1352
+ ? [
1353
+ 'lowered' as const,
1354
+ ...(flattened ? (['flattened'] as const) : []),
1355
+ ...(input.styledDefinition ? (['styled'] as const) : []),
1356
+ ]
1357
+ : (['bailed'] as const)
1358
+ const why = result.ok
1359
+ ? flattened
1360
+ ? 'Static styles were lowered and the component was flattened to a host element.'
1361
+ : 'Static styles were lowered while the Tamagui component remained at runtime.'
1362
+ : result.bailout.message
1363
+
1364
+ const styles = styleEntries.map((entry) => {
1365
+ if (!result.ok) {
1366
+ return {
1367
+ prop: entry.name,
1368
+ tier: 'bailed' as const,
1369
+ runtime: true as const,
1370
+ why: `${result.bailout.message}. The Tamagui runtime resolved this prop.`,
1371
+ }
1372
+ }
1373
+
1374
+ const edited = result.edits.some(
1375
+ (edit) => edit.start < entry.span.end && edit.end > entry.span.start
1376
+ )
1377
+ if (!flattened && !edited) {
1378
+ return {
1379
+ prop: entry.name,
1380
+ tier: 'bailed' as const,
1381
+ runtime: true as const,
1382
+ why: 'This prop remained for Tamagui runtime resolution.',
1383
+ }
1384
+ }
1385
+
1386
+ if (entry.value.kind === 'static') {
1387
+ const split = resolveSplitStyles(
1388
+ { [entry.name]: entry.value.value },
1389
+ partialStaticConfig(component.staticConfig)
1390
+ )
1391
+ const hasOutput = Boolean(
1392
+ split &&
1393
+ (Object.keys(split.classNames ?? {}).length > 0 ||
1394
+ Object.keys(split.rulesToInsert ?? {}).length > 0 ||
1395
+ (staticObject(split.style) && Object.keys(split.style).length > 0) ||
1396
+ (staticObject(split.viewProps?.style) &&
1397
+ Object.keys(split.viewProps.style).length > 0))
1398
+ )
1399
+ if (!hasOutput) {
1400
+ return {
1401
+ prop: entry.name,
1402
+ tier: flattened ? ('flattened' as const) : ('lowered' as const),
1403
+ dropped: true as const,
1404
+ why: `No ${platform} style output was produced for this prop.`,
1405
+ }
1406
+ }
1407
+ }
1408
+
1409
+ return {
1410
+ prop: entry.name,
1411
+ tier: flattened ? ('flattened' as const) : ('lowered' as const),
1412
+ why: flattened
1413
+ ? 'The compiler emitted this prop on the flattened host element.'
1414
+ : 'The compiler lowered this prop while retaining the Tamagui component.',
1415
+ }
1416
+ })
1417
+
1418
+ const receipt = JSON.stringify({
1419
+ component: input.element.component.name,
1420
+ tiers,
1421
+ why,
1422
+ styles,
1423
+ })
1424
+ const content = flattened
1425
+ ? input.element.form === 'jsx'
1426
+ ? `{...(console.info('🔹 Tamagui style receipt', ${receipt}), {})}`
1427
+ : `...(console.info('🔹 Tamagui style receipt', ${receipt}), {})`
1428
+ : input.element.form === 'jsx'
1429
+ ? `debug="verbose" __tamaguiStyleDebugReceipt={${receipt}}`
1430
+ : `debug: "verbose", __tamaguiStyleDebugReceipt: ${receipt}`
1431
+
1432
+ return [
1433
+ {
1434
+ start: debugEntry.span.start,
1435
+ end: debugEntry.span.end,
1436
+ content,
1437
+ origin: debugEntry.span,
1438
+ },
1439
+ ]
1440
+ }
1441
+
1442
+ return {
1443
+ resolveComponent: resolve,
1444
+ isStyleProp,
1445
+ canLowerDynamicStyleProp(name, component, valueKind) {
1446
+ // a conditional with static branches lowers per-branch on both
1447
+ // platforms: each side resolves at compile time, only the test survives
1448
+ if (
1449
+ !options.disablePartialExtraction &&
1450
+ valueKind === 'conditional' &&
1451
+ canLowerConditionalStyleProp(name, component)
1452
+ ) {
1453
+ return true
1454
+ }
1455
+ return (
1456
+ !options.disablePartialExtraction &&
1457
+ ((platform === 'web' && !!directStyleName(name, component)) ||
1458
+ (platform === 'native' && directStyleName(name, component) === 'opacity'))
1459
+ )
1460
+ },
1461
+ developmentDebugInstrumentation,
1462
+ lowerCandidate(input): LoweringCandidateResult {
1463
+ const component = input.component as TamaguiLoweringComponent
1464
+ if (!component.canFlatten) {
1465
+ const reason =
1466
+ component.staticConfig.acceptsClassName === false
1467
+ ? `${component.key} does not accept className`
1468
+ : component.staticConfig.neverFlatten
1469
+ ? `${component.key} is never flattened (behavior HOC)`
1470
+ : `${component.key} provides a styled context`
1471
+ return bailout(input, 'local/unsupported-target', reason, input.element.span, {
1472
+ rule: 6,
1473
+ })
1474
+ }
1475
+ // A zero graph has no runtime left to merge a spread into, and a spread the
1476
+ // compiler evaluated is still a prop set it cannot attribute to an author's
1477
+ // style intent. Both shapes are rejected, so the message is one message.
1478
+ if (options.zeroRuntime) {
1479
+ const spread = input.element.entries.find((entry) => entry.kind === 'spread')
1480
+ if (spread) {
1481
+ return bailout(
1482
+ input,
1483
+ 'local/unsafe-style-spread',
1484
+ 'Zero-runtime rejects prop spreads',
1485
+ spread.span,
1486
+ {
1487
+ rule: 1,
1488
+ message: zeroRuleMessage(1, { component: input.element.component.name }),
1489
+ }
1490
+ )
1491
+ }
1492
+ }
1493
+ const props: Record<string, unknown> = {}
1494
+ for (const entry of input.element.entries) {
1495
+ if (entry.kind === 'child' || entry.value.kind !== 'static') continue
1496
+ if (entry.kind === 'spread') {
1497
+ if (!staticObject(entry.value.value)) {
1498
+ return bailout(
1499
+ input,
1500
+ 'local/unsafe-style-spread',
1501
+ 'Static spread did not materialize to an object',
1502
+ entry.span
1503
+ )
1504
+ }
1505
+ Object.assign(props, entry.value.value)
1506
+ } else {
1507
+ props[entry.name] = entry.value.value
1508
+ }
1509
+ }
1510
+ const disableOptimizationEntry = input.element.entries.find(
1511
+ (entry) => entry.kind === 'prop' && entry.name === 'disableOptimization'
1512
+ )
1513
+ if (disableOptimizationEntry || Object.hasOwn(props, 'disableOptimization')) {
1514
+ return bailout(
1515
+ input,
1516
+ 'local/unsupported-target',
1517
+ 'disableOptimization keeps the component on the runtime path',
1518
+ disableOptimizationEntry?.span
1519
+ )
1520
+ }
1521
+ if (component.domTag && props.hidden) props.display = 'none'
1522
+ if (component.domTag && platform === 'native') {
1523
+ for (const [name, styleKey] of DOM_STYLE_ATTRIBUTES) {
1524
+ if (name in props) {
1525
+ props[styleKey] = props[name]
1526
+ delete props[name]
1527
+ }
1528
+ }
1529
+ }
1530
+ if (
1531
+ platform === 'native' &&
1532
+ component.domTag === 'input' &&
1533
+ typeof props.type === 'string' &&
1534
+ !NATIVE_INPUT_TYPES.includes(props.type)
1535
+ ) {
1536
+ return bailout(
1537
+ input,
1538
+ 'local/unsupported-target',
1539
+ `input type ${props.type} has no native text-entry control`
1540
+ )
1541
+ }
1542
+ const dynamicStyleEntries = input.element.entries.filter(
1543
+ (
1544
+ entry
1545
+ ): entry is Extract<MaterializedElement['entries'][number], { kind: 'prop' }> =>
1546
+ entry.kind === 'prop' &&
1547
+ (entry.value.kind === 'bailout' || entry.value.kind === 'conditional') &&
1548
+ isStyleProp(entry.name, component)
1549
+ )
1550
+ // both platforms: web needs runtime event mapping, and a flattened bare
1551
+ // RN View silently ignores onPress/onLongPress (Tamagui wires press via
1552
+ // its responder system at runtime)
1553
+ {
1554
+ // DOM primitives (html.*) keep their own event contract (see
1555
+ // @tamagui/dom events table) — only bail for tamagui components,
1556
+ // whose pointer handlers exist solely via usePointerEvents.
1557
+ const needsRuntimeMapping = (name: string) =>
1558
+ runtimeEventProps.has(name) ||
1559
+ (platform === 'native' &&
1560
+ !component.domTag &&
1561
+ nativePointerEventProps.has(name))
1562
+ const directRuntimeEvent = input.element.entries.find(
1563
+ (entry) => entry.kind === 'prop' && needsRuntimeMapping(entry.name)
1564
+ )
1565
+ const runtimeEvent =
1566
+ directRuntimeEvent?.kind === 'prop'
1567
+ ? directRuntimeEvent.name
1568
+ : Object.keys(props).find(needsRuntimeMapping)
1569
+ if (runtimeEvent) {
1570
+ return bailout(
1571
+ input,
1572
+ 'local/unsupported-target',
1573
+ `${runtimeEvent} requires Tamagui runtime event mapping`
1574
+ )
1575
+ }
1576
+ }
1577
+ // A styled() definition's animation props decide the same things the call
1578
+ // site's do, so the lowering decision reads both. completeProps merges
1579
+ // them far below, long after this point, and reading only the call site
1580
+ // here decided a definition's transition/animation/animateOnly as if it
1581
+ // had never been written: the element flattened and the prop was dropped
1582
+ // with no diagnostic. Same merge function and same precedence as
1583
+ // completeProps, so there is one answer to "what is this prop's value".
1584
+ const animationDefaultProps = core.getDefaultProps(component.staticConfig) ?? {}
1585
+ const animationProps = core.mergeProps(animationDefaultProps, props)
1586
+ const animationNames = new Set([
1587
+ ...input.element.entries.flatMap((entry) =>
1588
+ entry.kind === 'prop' && runtimeAnimationProps.has(entry.name)
1589
+ ? [entry.name]
1590
+ : []
1591
+ ),
1592
+ ...Object.keys(animationProps).filter(
1593
+ (name) => runtimeAnimationProps.has(name) && animationProps[name] !== undefined
1594
+ ),
1595
+ ])
1596
+ const animateOnlyEntry = input.element.entries.find(
1597
+ (entry) => entry.kind === 'prop' && entry.name === 'animateOnly'
1598
+ )
1599
+ if (animationNames.has('animateOnly')) {
1600
+ return bailout(
1601
+ input,
1602
+ 'local/unsupported-target',
1603
+ 'Animated candidates remain on the runtime path',
1604
+ animateOnlyEntry?.span,
1605
+ {
1606
+ rule: 5,
1607
+ message: zeroRuleMessage(5, {
1608
+ // without the origin an author reads this against JSX that does
1609
+ // not carry the prop, because the styled() definition does
1610
+ detail: animateOnlyEntry
1611
+ ? `animateOnly on ${input.element.component.name}`
1612
+ : `animateOnly in the styled() definition of ${input.element.component.name}`,
1613
+ }),
1614
+ }
1615
+ )
1616
+ }
1617
+ const transitionEntry = input.element.entries.find(
1618
+ (entry) => entry.kind === 'prop' && entry.name === 'transition'
1619
+ )
1620
+ const animatedBy =
1621
+ typeof animationProps.animatedBy === 'string'
1622
+ ? animationProps.animatedBy.trim()
1623
+ : null
1624
+ const namedAnimationDriver =
1625
+ animatedBy === null ? null : options.tamaguiConfig.animationDrivers?.[animatedBy]
1626
+ const namedCssAnimationDriver =
1627
+ platform === 'web' && namedAnimationDriver?.outputStyle === 'css'
1628
+ ? namedAnimationDriver
1629
+ : null
1630
+ const cssAnimationDriver =
1631
+ namedCssAnimationDriver ??
1632
+ (animatedBy === null || animatedBy === 'default'
1633
+ ? configuredCssAnimationDriver
1634
+ : null)
1635
+ const resolvedCssTransition =
1636
+ animationNames.has('transition') && cssAnimationDriver
1637
+ ? resolveStaticCssTransition(
1638
+ animationProps.transition,
1639
+ cssAnimationDriver.animations ?? {}
1640
+ )
1641
+ : null
1642
+ // onto props, not animationProps: props is what completeProps merges over
1643
+ // the defaults, so this is also how a resolved definition transition wins
1644
+ // over the preset name the definition wrote
1645
+ if (resolvedCssTransition !== null) {
1646
+ props.transition = resolvedCssTransition
1647
+ }
1648
+ const animatedByNeedsRuntime =
1649
+ animationNames.has('animatedBy') &&
1650
+ !animationNames.has('transition') &&
1651
+ (dynamicStyleEntries.length > 0 ||
1652
+ Object.keys(animationProps).some(
1653
+ (name) =>
1654
+ name !== 'animatedBy' &&
1655
+ ((isStyleProp(name, component) &&
1656
+ typeof animationProps[name] === 'string' &&
1657
+ animationProps[name].includes(':')) ||
1658
+ name === 'animationConfig' ||
1659
+ name === 'forceStyle' ||
1660
+ name === 'onTransition')
1661
+ ))
1662
+ const runtimeAnimationRequired =
1663
+ (animationNames.has('transition') && resolvedCssTransition === null) ||
1664
+ [...animationNames].some(
1665
+ (name) =>
1666
+ name !== 'transition' && name !== 'animatedBy' && name !== 'animateOnly'
1667
+ ) ||
1668
+ animatedByNeedsRuntime
1669
+ let dynamicHostStyleProperties: string[] | null = null
1670
+ if (
1671
+ resolvedCssTransition !== null &&
1672
+ dynamicStyleEntries.length > 0 &&
1673
+ !input.element.entries.some((entry) => entry.kind === 'spread')
1674
+ ) {
1675
+ const seen = new Set<string>()
1676
+ const properties: string[] = []
1677
+ const dynamicOwners = new Set<string>()
1678
+ for (const entry of dynamicStyleEntries) {
1679
+ if (
1680
+ entry.kind !== 'prop' ||
1681
+ (entry.value.kind !== 'bailout' && entry.value.kind !== 'conditional')
1682
+ ) {
1683
+ continue
1684
+ }
1685
+ const name = directStyleName(entry.name, component)
1686
+ if ((name !== 'opacity' && name !== 'scale') || seen.has(name)) {
1687
+ properties.length = 0
1688
+ break
1689
+ }
1690
+ const owners = dynamicStyleOwners(name, component.staticConfig as StaticConfig)
1691
+ if (!owners) {
1692
+ properties.length = 0
1693
+ break
1694
+ }
1695
+ seen.add(name)
1696
+ for (const owner of owners) dynamicOwners.add(owner)
1697
+ const expression = input.source.slice(
1698
+ entry.value.span.start,
1699
+ entry.value.span.end
1700
+ )
1701
+ properties.push(
1702
+ name === 'opacity'
1703
+ ? `opacity: (${expression})`
1704
+ : `transform: "scale(" + (${expression}) + ")"`
1705
+ )
1706
+ }
1707
+ if (
1708
+ properties.length === dynamicStyleEntries.length &&
1709
+ !input.element.entries.some((entry) => {
1710
+ if (entry.kind !== 'prop' || entry.value.kind !== 'static') return false
1711
+ const name = directStyleName(entry.name, component)
1712
+ if (!name) return false
1713
+ const owners = styleOwners(
1714
+ name,
1715
+ entry.value.value,
1716
+ component.staticConfig as StaticConfig
1717
+ )
1718
+ return !!owners && cssOwnersConflict(owners, dynamicOwners)
1719
+ })
1720
+ ) {
1721
+ dynamicHostStyleProperties = properties
1722
+ }
1723
+ }
1724
+ // web per-branch conditional classes need the same preconditions the
1725
+ // native per-branch path has, and take precedence over web partial
1726
+ // extraction because a full flatten beats a runtime component
1727
+ // exactly one conditional: several would need a shared class
1728
+ // intersection across every branch combination, so elements with more
1729
+ // keep the web partial-extraction path they had before
1730
+ const supportsWebConditionalClasses =
1731
+ platform === 'web' &&
1732
+ !options.disablePartialExtraction &&
1733
+ (input.element.form === 'jsx' || input.element.propsSpan !== null) &&
1734
+ dynamicHostStyleProperties === null &&
1735
+ dynamicStyleEntries.length === 1 &&
1736
+ dynamicStyleEntries.every(
1737
+ (entry) =>
1738
+ entry.kind === 'prop' &&
1739
+ entry.value.kind === 'conditional' &&
1740
+ canLowerConditionalStyleProp(entry.name, component)
1741
+ )
1742
+ if ('theme' in props || 'themeInverse' in props) {
1743
+ const themeProp = 'theme' in props ? 'theme' : 'themeInverse'
1744
+ return bailout(
1745
+ input,
1746
+ 'local/unsupported-target',
1747
+ 'Theme boundary candidates remain on the runtime path',
1748
+ input.element.span,
1749
+ {
1750
+ rule: 4,
1751
+ message: zeroThemeBoundaryMessage(input.element.component.name, themeProp),
1752
+ }
1753
+ )
1754
+ }
1755
+ // asChild makes createComponent render a Slot: it merges its props into
1756
+ // the single child and emits no element of its own. a flattened host view
1757
+ // would add a wrapper the runtime never renders, and the child would
1758
+ // never receive the merged props. both platforms.
1759
+ const asChildEntry = input.element.entries.find(
1760
+ (entry) => entry.kind === 'prop' && entry.name === 'asChild'
1761
+ )
1762
+ if (asChildEntry || Object.hasOwn(props, 'asChild')) {
1763
+ return bailout(
1764
+ input,
1765
+ 'local/unsupported-target',
1766
+ 'asChild renders a Slot, not a host view',
1767
+ asChildEntry?.span
1768
+ )
1769
+ }
1770
+ // group and the container props publish the same context: group writes
1771
+ // the group name, container writes '@' and '@name'. descendants read it
1772
+ // for `group-name-*` and `@name-*` clauses, so a flattened provider
1773
+ // silently breaks every consumer under it. web compiles these to CSS
1774
+ // container rules instead, so only native needs the bail.
1775
+ if (
1776
+ platform === 'native' &&
1777
+ ('group' in props ||
1778
+ 'container' in props ||
1779
+ 'containerName' in props ||
1780
+ 'containerType' in props)
1781
+ ) {
1782
+ return bailout(
1783
+ input,
1784
+ 'local/unsupported-target',
1785
+ 'Native group containers remain on the runtime path'
1786
+ )
1787
+ }
1788
+ if (runtimeAnimationRequired && !cssAnimationDriver) {
1789
+ return bailout(
1790
+ input,
1791
+ 'local/unsupported-target',
1792
+ 'Animated candidates remain on the runtime path',
1793
+ transitionEntry?.span,
1794
+ {
1795
+ rule: 5,
1796
+ message: zeroRuleMessage(5, {
1797
+ detail: `the animation configured on ${input.element.component.name}`,
1798
+ }),
1799
+ }
1800
+ )
1801
+ }
1802
+ if (
1803
+ platform === 'web' &&
1804
+ (dynamicStyleEntries.length > 0 || runtimeAnimationRequired) &&
1805
+ dynamicHostStyleProperties === null &&
1806
+ !supportsWebConditionalClasses &&
1807
+ component.partialRuntimeSafe
1808
+ ) {
1809
+ const hasSpread = input.element.entries.some((entry) => entry.kind === 'spread')
1810
+ const unsupportedRuntimeStyle = input.element.entries.find(
1811
+ (entry) =>
1812
+ entry.kind === 'prop' &&
1813
+ isStyleProp(entry.name, component) &&
1814
+ !runtimeAnimationProps.has(entry.name) &&
1815
+ !directStyleName(entry.name, component)
1816
+ )
1817
+ if (!hasSpread && !unsupportedRuntimeStyle) {
1818
+ const dynamicOwners = new Set<string>()
1819
+ let canProveDynamicOwnership = true
1820
+ for (const entry of dynamicStyleEntries) {
1821
+ if (entry.kind !== 'prop') continue
1822
+ const name = directStyleName(entry.name, component)
1823
+ if (!name) {
1824
+ canProveDynamicOwnership = false
1825
+ break
1826
+ }
1827
+ const owners = dynamicStyleOwners(
1828
+ name,
1829
+ component.staticConfig as StaticConfig
1830
+ )
1831
+ if (!owners) {
1832
+ canProveDynamicOwnership = false
1833
+ break
1834
+ }
1835
+ for (const owner of owners) dynamicOwners.add(owner)
1836
+ }
1837
+ const staticStyleEntries = canProveDynamicOwnership
1838
+ ? input.element.entries.filter((entry) => {
1839
+ if (entry.kind !== 'prop' || entry.value.kind !== 'static') return false
1840
+ if (runtimeAnimationProps.has(entry.name)) return false
1841
+ const name = directStyleName(entry.name, component)
1842
+ if (!name) return false
1843
+ const owners = styleOwners(
1844
+ name,
1845
+ entry.value.value,
1846
+ component.staticConfig as StaticConfig
1847
+ )
1848
+ return !!owners && !cssOwnersConflict(owners, dynamicOwners)
1849
+ })
1850
+ : []
1851
+ if (staticStyleEntries.length > 0) {
1852
+ const partialProps: Record<string, unknown> = {}
1853
+ for (const entry of staticStyleEntries) {
1854
+ if (entry.kind === 'prop' && entry.value.kind === 'static') {
1855
+ partialProps[entry.name] = entry.value.value
1856
+ }
1857
+ }
1858
+ const partialSplit = resolveSplitStyles(
1859
+ partialProps,
1860
+ partialStaticConfig(component.staticConfig as StaticConfig)
1861
+ )
1862
+ const partialInlineStyle = partialSplit?.viewProps?.style
1863
+ const hasPartialInlineStyle =
1864
+ staticObject(partialInlineStyle) &&
1865
+ !partialInlineStyle['$$css'] &&
1866
+ Object.keys(partialInlineStyle).length > 0
1867
+ if (partialSplit && !hasPartialInlineStyle) {
1868
+ const artifacts = extractedStyleArtifacts(
1869
+ partialSplit,
1870
+ partialProps,
1871
+ options.tamaguiConfig,
1872
+ false
1873
+ )
1874
+ if (artifacts.className) {
1875
+ if (input.element.form !== 'jsx') {
1876
+ const propsEdits = compiledPropsEdits(
1877
+ input,
1878
+ staticStyleEntries,
1879
+ objectClassName(artifacts.className)
1880
+ )
1881
+ if (propsEdits) {
1882
+ return {
1883
+ ok: true,
1884
+ edits: propsEdits,
1885
+ css: artifacts.css,
1886
+ imports: [],
1887
+ flattened: false,
1888
+ }
1889
+ }
1890
+ } else {
1891
+ const [first, ...rest] = staticStyleEntries
1892
+ return {
1893
+ ok: true,
1894
+ edits: [
1895
+ {
1896
+ start: first!.span.start,
1897
+ end: first!.span.end,
1898
+ content: jsxClassName(artifacts.className),
1899
+ origin: first!.span,
1900
+ },
1901
+ ...rest.map((entry) => ({
1902
+ start: entry.span.start,
1903
+ end: entry.span.end,
1904
+ content: '',
1905
+ origin: entry.span,
1906
+ })),
1907
+ ],
1908
+ css: artifacts.css,
1909
+ imports: [],
1910
+ flattened: false,
1911
+ }
1912
+ }
1913
+ }
1914
+ }
1915
+ }
1916
+ }
1917
+ }
1918
+ if (runtimeAnimationRequired) {
1919
+ return bailout(
1920
+ input,
1921
+ 'local/unsupported-target',
1922
+ 'Animated candidates remain on the runtime path',
1923
+ transitionEntry?.span,
1924
+ {
1925
+ rule: 5,
1926
+ message: zeroRuleMessage(5, {
1927
+ detail: `the animation configured on ${input.element.component.name}`,
1928
+ }),
1929
+ }
1930
+ )
1931
+ }
1932
+ const supportsNativeDynamicStyles =
1933
+ platform === 'native' &&
1934
+ !options.disablePartialExtraction &&
1935
+ (input.element.form === 'jsx' || input.element.propsSpan !== null) &&
1936
+ dynamicStyleEntries.every(
1937
+ (entry) =>
1938
+ entry.kind === 'prop' &&
1939
+ (directStyleName(entry.name, component) === 'opacity' ||
1940
+ (entry.value.kind === 'conditional' &&
1941
+ canLowerConditionalStyleProp(entry.name, component)))
1942
+ )
1943
+ if (
1944
+ dynamicStyleEntries.length > 0 &&
1945
+ dynamicHostStyleProperties === null &&
1946
+ !supportsNativeDynamicStyles &&
1947
+ !supportsWebConditionalClasses
1948
+ ) {
1949
+ const entry = dynamicStyleEntries[0]!
1950
+ return bailout(
1951
+ input,
1952
+ 'local/dynamic-style-value',
1953
+ `Style prop ${entry.kind === 'prop' ? entry.name : 'unknown'} could not be safely extracted`,
1954
+ entry.span
1955
+ )
1956
+ }
1957
+ const staticDefaultProps = core.getDefaultProps(component.staticConfig) ?? {}
1958
+ const defaultProps =
1959
+ platform === 'web' &&
1960
+ !component.staticConfig.isText &&
1961
+ options.tamaguiConfig.settings.defaultPosition === 'relative' &&
1962
+ staticDefaultProps.position === undefined
1963
+ ? core.mergeProps({ position: 'relative' }, staticDefaultProps)
1964
+ : staticDefaultProps
1965
+ let completeProps = core.mergeProps(defaultProps, props)
1966
+ if (platform === 'native' && component.domTag && props.display === 'flex') {
1967
+ completeProps = core.mergeProps(
1968
+ core.mergeProps(defaultProps, NATIVE_FLEX_DEFAULTS),
1969
+ props
1970
+ )
1971
+ }
1972
+ // Against completeProps, not props: clauses also arrive from styled()
1973
+ // defaults. Native resolution evaluates them against the build machine's
1974
+ // current state, so folding would freeze that state into the bundle.
1975
+ if (platform === 'native') {
1976
+ const isClauseValue = (name: string, value: unknown) =>
1977
+ isStyleProp(name, component) &&
1978
+ typeof value === 'string' &&
1979
+ flatClausePattern.test(value)
1980
+ // a clause can also sit inside a variant definition, where it never
1981
+ // appears as a prop: variants: { big: { true: { width: 'gt-lg:999px' } } }
1982
+ const defaultVariants = component.staticConfig.defaultVariants ?? {}
1983
+ const carriesClause =
1984
+ Object.entries(completeProps).some(([name, value]) =>
1985
+ isClauseValue(name, value)
1986
+ ) ||
1987
+ Object.entries(component.staticConfig.variants ?? {}).some(
1988
+ ([variantName, definitions]) =>
1989
+ (completeProps[variantName] !== undefined ||
1990
+ defaultVariants[variantName] !== undefined) &&
1991
+ staticObject(definitions) &&
1992
+ Object.values(definitions).some(
1993
+ (definition) =>
1994
+ staticObject(definition) &&
1995
+ Object.entries(definition).some(([name, value]) =>
1996
+ isClauseValue(name, value)
1997
+ )
1998
+ )
1999
+ )
2000
+ if (carriesClause) {
2001
+ return bailout(
2002
+ input,
2003
+ 'local/unsupported-target',
2004
+ 'Native conditional value programs remain on the runtime path'
2005
+ )
2006
+ }
2007
+ }
2008
+ const split = resolveSplitStyles(
2009
+ completeProps,
2010
+ component.staticConfig,
2011
+ cssAnimationDriver,
2012
+ component.displayName
2013
+ )
2014
+ if (!split) {
2015
+ return bailout(
2016
+ input,
2017
+ 'local/style-resolution-failed',
2018
+ 'getSplitStyles returned no static result'
2019
+ )
2020
+ }
2021
+ if (
2022
+ split.programLifecycleStyleKeys?.enter?.size ||
2023
+ split.programLifecycleStyleKeys?.exit?.size
2024
+ ) {
2025
+ return bailout(
2026
+ input,
2027
+ 'local/unsupported-target',
2028
+ 'Lifecycle value programs remain on the runtime path',
2029
+ input.element.span,
2030
+ {
2031
+ rule: 5,
2032
+ message: zeroRuleMessage(5, {
2033
+ detail: `an enter or exit style program on ${input.element.component.name}`,
2034
+ }),
2035
+ }
2036
+ )
2037
+ }
2038
+ const domStyleProgram = input.element.entries.find(
2039
+ (entry) =>
2040
+ entry.kind === 'prop' &&
2041
+ entry.name === 'style' &&
2042
+ entry.value.kind === 'dom-style'
2043
+ )
2044
+
2045
+ const flatTag =
2046
+ component.domTag ??
2047
+ (typeof props.render === 'string'
2048
+ ? props.render
2049
+ : typeof defaultProps.render === 'string'
2050
+ ? defaultProps.render
2051
+ : component.staticConfig.isText
2052
+ ? 'span'
2053
+ : 'div')
2054
+ const tagEdits = [input.element.component.span, input.element.component.closingSpan]
2055
+ .filter((span): span is NonNullable<typeof span> => !!span)
2056
+ .map((span) => ({
2057
+ start: span.start,
2058
+ end: span.end,
2059
+ content: input.element.form === 'jsx' ? flatTag : JSON.stringify(flatTag),
2060
+ origin: span,
2061
+ }))
2062
+
2063
+ let styleEntries = input.element.entries.filter(
2064
+ (entry) =>
2065
+ (entry.kind === 'prop' &&
2066
+ (isStyleProp(entry.name, component) ||
2067
+ isInvalidHostStyleProp(entry.name, component))) ||
2068
+ (entry.kind === 'spread' &&
2069
+ entry.value.kind === 'static' &&
2070
+ staticObject(entry.value.value) &&
2071
+ Object.keys(entry.value.value).every(
2072
+ (name) =>
2073
+ isStyleProp(name, component) || isInvalidHostStyleProp(name, component)
2074
+ ))
2075
+ )
2076
+ let invalidHostStyle:
2077
+ | { entry: MaterializedElement['entries'][number]; name: string }
2078
+ | undefined
2079
+ for (const entry of input.element.entries) {
2080
+ if (entry.kind === 'prop') {
2081
+ if (isInvalidHostStyleProp(entry.name, component)) {
2082
+ invalidHostStyle = { entry, name: entry.name }
2083
+ break
2084
+ }
2085
+ continue
2086
+ }
2087
+ if (
2088
+ entry.kind === 'spread' &&
2089
+ entry.value.kind === 'static' &&
2090
+ staticObject(entry.value.value)
2091
+ ) {
2092
+ const name = Object.keys(entry.value.value).find((name) =>
2093
+ isInvalidHostStyleProp(name, component)
2094
+ )
2095
+ if (name) {
2096
+ invalidHostStyle = { entry, name }
2097
+ break
2098
+ }
2099
+ }
2100
+ }
2101
+ if (invalidHostStyle) {
2102
+ return bailout(
2103
+ input,
2104
+ 'local/unsupported-target',
2105
+ `"${invalidHostStyle.name}" is a text style prop and this component is not text. Use a Text-based component, or html.* for raw web elements.`,
2106
+ invalidHostStyle.entry.span
2107
+ )
2108
+ }
2109
+ const webPropEdits: SourceEdit[] =
2110
+ platform === 'web'
2111
+ ? input.element.entries.flatMap((entry) => {
2112
+ if (entry.kind !== 'prop' || entry.name !== 'testID') return []
2113
+ const content = input.source.slice(entry.span.start, entry.span.end)
2114
+ const nameOffset = content.indexOf('testID')
2115
+ if (nameOffset === -1) return []
2116
+ const alreadyQuoted =
2117
+ content[nameOffset - 1] === '"' || content[nameOffset - 1] === "'"
2118
+ return [
2119
+ {
2120
+ start: entry.span.start + nameOffset,
2121
+ end: entry.span.start + nameOffset + 'testID'.length,
2122
+ content:
2123
+ input.element.form === 'jsx' || alreadyQuoted
2124
+ ? 'data-testid'
2125
+ : `'data-testid'`,
2126
+ origin: entry.span,
2127
+ },
2128
+ ]
2129
+ })
2130
+ : []
2131
+ const webDOMResult =
2132
+ platform === 'web' && component.domTag
2133
+ ? webDOMProps(input, component.domTag)
2134
+ : { edits: [] as SourceEdit[], additions: [] as [string, string][] }
2135
+ webPropEdits.push(...webDOMResult.edits)
2136
+ const unsafeSpread = input.element.entries.find(
2137
+ (entry) =>
2138
+ entry.kind === 'spread' &&
2139
+ !styleEntries.includes(entry) &&
2140
+ entry.value.kind === 'static' &&
2141
+ staticObject(entry.value.value) &&
2142
+ Object.keys(entry.value.value).some((name) => isStyleProp(name, component))
2143
+ )
2144
+ if (unsafeSpread) {
2145
+ return bailout(
2146
+ input,
2147
+ 'local/unsafe-style-spread',
2148
+ 'A mixed style/non-style spread cannot be removed transactionally',
2149
+ unsafeSpread.span
2150
+ )
2151
+ }
2152
+
2153
+ if (platform === 'native') {
2154
+ const nativeStyleResolved = split.viewProps?.style
2155
+ // Theme-backed values arrive as sentinel strings under
2156
+ // resolveValues: 'except-theme'. Split them out the way V2's
2157
+ // extractToNative did: plain styles stay hoisted and static, themed
2158
+ // keys read the live theme through _withStableStyle so a flattened
2159
+ // component still updates when the theme changes.
2160
+ let themedStyleKeys: Record<string, string> | null = null
2161
+ let nativeStyle = nativeStyleResolved
2162
+ if (staticObject(nativeStyleResolved)) {
2163
+ for (const [styleKey, styleValue] of Object.entries(nativeStyleResolved)) {
2164
+ if (!core.containsThemeRef(styleValue)) continue
2165
+ const themeKey = core.themeRefKey(styleValue)
2166
+ if (!themeKey) {
2167
+ return bailout(
2168
+ input,
2169
+ 'local/dynamic-style-value',
2170
+ `Style ${styleKey} uses a theme value in a compound or modified position that compiled output cannot represent`
2171
+ )
2172
+ }
2173
+ ;(themedStyleKeys ||= {})[styleKey] = themeKey
2174
+ }
2175
+ if (themedStyleKeys) {
2176
+ const plain: Record<string, unknown> = {}
2177
+ for (const [styleKey, styleValue] of Object.entries(nativeStyleResolved)) {
2178
+ if (!(styleKey in themedStyleKeys)) plain[styleKey] = styleValue
2179
+ }
2180
+ nativeStyle = plain
2181
+ }
2182
+ } else if (core.containsThemeRef(nativeStyleResolved)) {
2183
+ return bailout(
2184
+ input,
2185
+ 'local/dynamic-style-value',
2186
+ 'Theme values inside non-object native style output stay on the runtime path'
2187
+ )
2188
+ }
2189
+ if (!isSerializableNativeStyle(nativeStyle)) {
2190
+ return bailout(
2191
+ input,
2192
+ 'local/unsupported-target',
2193
+ 'Native style output is not a static serializable value'
2194
+ )
2195
+ }
2196
+ const nativeStyleLocal = unusedIdentifier(
2197
+ input.source,
2198
+ `__TamaguiNativeStyle${input.element.span.start}`
2199
+ )
2200
+ const nativeStyleImports = [
2201
+ {
2202
+ content: `\nfunction ${nativeStyleLocal}() { return ${nativeStyleLocal}._ ?? (${nativeStyleLocal}._ = ${JSON.stringify(nativeStyle ?? {})}); } ${nativeStyleLocal}();`,
2203
+ origin: input.element.component.span,
2204
+ },
2205
+ ]
2206
+ const nativeStyleSource = `${nativeStyleLocal}._ ?? ${nativeStyleLocal}()`
2207
+ let nativeFastPath:
2208
+ | {
2209
+ imports: { content: string; origin: MaterializedElement['span'] }[]
2210
+ mappingLocal: string
2211
+ }
2212
+ | undefined
2213
+ if (
2214
+ themedStyleKeys &&
2215
+ options.experimentalNativeFastPath &&
2216
+ dynamicStyleEntries.length === 0 &&
2217
+ !input.element.entries.some((entry) => entry.kind === 'spread')
2218
+ ) {
2219
+ const mappingLocal = unusedIdentifier(
2220
+ input.source,
2221
+ `__TamaguiNativeMapping${input.element.span.start}`
2222
+ )
2223
+ nativeFastPath = {
2224
+ mappingLocal,
2225
+ imports: [
2226
+ {
2227
+ content: `\nconst ${mappingLocal} = ${JSON.stringify(themedStyleKeys)};`,
2228
+ origin: input.element.component.span,
2229
+ },
2230
+ ],
2231
+ }
2232
+ }
2233
+ if (component.domTag) {
2234
+ if (themedStyleKeys) {
2235
+ return bailout(
2236
+ input,
2237
+ 'local/unsupported-target',
2238
+ 'Theme values on DOM-tag native output stay on the runtime path'
2239
+ )
2240
+ }
2241
+ const row = TAGS[component.domTag]
2242
+ const basePrimitive = NATIVE_BACKING[row.backing].primitive
2243
+ let primitive = basePrimitive
2244
+ const propsResult = nativeDOMProps(input, component.domTag)
2245
+ if (propsResult.diagnostic) {
2246
+ return bailout(
2247
+ input,
2248
+ 'local/unsupported-target',
2249
+ propsResult.diagnostic,
2250
+ propsResult.diagnosticSpan
2251
+ )
2252
+ }
2253
+ styleEntries = [...new Set([...styleEntries, ...propsResult.consumed])]
2254
+ let nativeDOMStyleSource = nativeStyleSource
2255
+ let runtimeStyleSource: string | null = null
2256
+ if (
2257
+ domStyleProgram?.kind === 'prop' &&
2258
+ domStyleProgram.value.kind === 'dom-style'
2259
+ ) {
2260
+ const needsRuntime = domStyleProgram.value.items.some(
2261
+ (item) =>
2262
+ item.value.kind === 'static' &&
2263
+ staticObject(item.value.value) &&
2264
+ (Object.keys(item.value.value).some((property) =>
2265
+ nativeRuntimeOnlyStyleProperties.has(property)
2266
+ ) ||
2267
+ Object.values(item.value.value).some(
2268
+ (value) =>
2269
+ typeof value === 'string' &&
2270
+ (flatClausePattern.test(value) ||
2271
+ nativeInheritedKeywordPattern.test(value))
2272
+ ))
2273
+ )
2274
+ const itemSources: string[] = []
2275
+ for (const item of domStyleProgram.value.items) {
2276
+ if (item.value.kind !== 'static' || !staticObject(item.value.value)) {
2277
+ return bailout(
2278
+ input,
2279
+ 'local/dynamic-style-value',
2280
+ 'Every style() handle in a style array must be statically evaluable',
2281
+ item.value.span
2282
+ )
2283
+ }
2284
+ if (
2285
+ Object.values(item.value.value).some(
2286
+ (value) =>
2287
+ typeof value === 'string' && nativeInitialKeywordPattern.test(value)
2288
+ )
2289
+ ) {
2290
+ return bailout(
2291
+ input,
2292
+ 'local/unsupported-target',
2293
+ 'Native DOM style() does not support the CSS initial keyword, matching the pinned upstream limitation',
2294
+ item.value.span
2295
+ )
2296
+ }
2297
+ let value: string
2298
+ if (needsRuntime) {
2299
+ value = JSON.stringify(item.value.value)
2300
+ } else {
2301
+ const itemSplit = resolveSplitStyles(
2302
+ item.value.value,
2303
+ partialStaticConfig(component.staticConfig)
2304
+ )
2305
+ const itemStyle = itemSplit?.viewProps?.style
2306
+ if (
2307
+ !isSerializableNativeStyle(itemStyle) ||
2308
+ core.containsThemeRef(itemStyle)
2309
+ ) {
2310
+ return bailout(
2311
+ input,
2312
+ 'local/unsupported-target',
2313
+ 'Native style() output is not serializable',
2314
+ item.value.span
2315
+ )
2316
+ }
2317
+ value = JSON.stringify(itemStyle ?? {})
2318
+ }
2319
+ const condition = item.condition
2320
+ ? input.source.slice(item.condition.start, item.condition.end)
2321
+ : null
2322
+ itemSources.push(condition ? `(${condition}) && ${value}` : value)
2323
+ }
2324
+ if (needsRuntime) {
2325
+ primitive = `DOMRuntime${basePrimitive.slice('DOM'.length)}`
2326
+ runtimeStyleSource = `[${itemSources.join(', ')}]`
2327
+ } else {
2328
+ nativeDOMStyleSource = `[${[nativeDOMStyleSource, ...itemSources].join(', ')}]`
2329
+ }
2330
+ }
2331
+ const nativeLocal = unusedIdentifier(input.source, `__Tamagui${primitive}`)
2332
+ const propertyContent = [
2333
+ input.element.form === 'jsx'
2334
+ ? `style={${nativeDOMStyleSource}}`
2335
+ : `style: ${nativeDOMStyleSource}`,
2336
+ runtimeStyleSource
2337
+ ? input.element.form === 'jsx'
2338
+ ? `__styles={${runtimeStyleSource}}`
2339
+ : `__styles: ${runtimeStyleSource}`
2340
+ : '',
2341
+ serializedProps(input.element.form, propsResult.additions),
2342
+ ]
2343
+ .filter(Boolean)
2344
+ .join(input.element.form === 'jsx' ? ' ' : ', ')
2345
+ const primitiveTagEdits = [
2346
+ input.element.component.span,
2347
+ input.element.component.closingSpan,
2348
+ ]
2349
+ .filter((span): span is NonNullable<typeof span> => !!span)
2350
+ .map((span) => ({
2351
+ start: span.start,
2352
+ end: span.end,
2353
+ content:
2354
+ input.element.form === 'jsx'
2355
+ ? nativeLocal
2356
+ : JSON.stringify(nativeLocal).slice(1, -1),
2357
+ origin: span,
2358
+ }))
2359
+ const literalEdits: SourceEdit[] = []
2360
+ const imports = [
2361
+ ...nativeStyleImports,
2362
+ {
2363
+ content: `\nimport { ${primitive} as ${nativeLocal} } from ${JSON.stringify(NATIVE_PRIMITIVE_MODULE)}\n`,
2364
+ origin: input.element.component.span,
2365
+ },
2366
+ ]
2367
+ if (NATIVE_BACKING[row.backing].wrapsLiteralText) {
2368
+ const textLocal = unusedIdentifier(input.source, '__TamaguiDOMText')
2369
+ const createElementLocal = unusedIdentifier(
2370
+ input.source,
2371
+ '__TamaguiCreateElement'
2372
+ )
2373
+ let needsText = false
2374
+ let needsCreateElement = false
2375
+ for (const entry of input.element.entries) {
2376
+ if (entry.kind !== 'child') continue
2377
+ if (
2378
+ entry.value.kind === 'empty' ||
2379
+ entry.value.kind === 'element' ||
2380
+ (entry.value.kind === 'static' &&
2381
+ (entry.value.value === null || typeof entry.value.value === 'boolean'))
2382
+ ) {
2383
+ continue
2384
+ }
2385
+ if (
2386
+ entry.value.kind !== 'static' ||
2387
+ entry.value.literalOrigin !== true ||
2388
+ (typeof entry.value.value !== 'string' &&
2389
+ typeof entry.value.value !== 'number')
2390
+ ) {
2391
+ return bailout(
2392
+ input,
2393
+ 'local/unsupported-child',
2394
+ `html.${component.domTag} has a direct child that may render unwrapped native text; write a literal as JSX text or wrap the child in html.span`,
2395
+ entry.span
2396
+ )
2397
+ }
2398
+ needsText = true
2399
+ const child = input.source.slice(entry.span.start, entry.span.end)
2400
+ if (input.element.form === 'jsx') {
2401
+ literalEdits.push({
2402
+ start: entry.span.start,
2403
+ end: entry.span.end,
2404
+ content: `<${textLocal} __inherit>${child}</${textLocal}>`,
2405
+ origin: entry.span,
2406
+ })
2407
+ } else {
2408
+ needsCreateElement = true
2409
+ literalEdits.push({
2410
+ start: entry.span.start,
2411
+ end: entry.span.end,
2412
+ content: `${createElementLocal}(${textLocal}, { __inherit: true }, ${child})`,
2413
+ origin: entry.span,
2414
+ })
2415
+ }
2416
+ }
2417
+ if (needsText) {
2418
+ imports.push({
2419
+ content: `\nimport { DOMText as ${textLocal} } from ${JSON.stringify(NATIVE_PRIMITIVE_MODULE)}\n`,
2420
+ origin: input.element.component.span,
2421
+ })
2422
+ }
2423
+ if (needsCreateElement) {
2424
+ imports.push({
2425
+ content: `\nimport { createElement as ${createElementLocal} } from "react"\n`,
2426
+ origin: input.element.component.span,
2427
+ })
2428
+ }
2429
+ }
2430
+ const propsEdits =
2431
+ input.element.form === 'jsx'
2432
+ ? styleEntries.length === 0
2433
+ ? [
2434
+ {
2435
+ start: input.element.component.span.end,
2436
+ end: input.element.component.span.end,
2437
+ content: ` ${propertyContent}`,
2438
+ origin: input.element.component.span,
2439
+ },
2440
+ ]
2441
+ : [
2442
+ {
2443
+ start: styleEntries[0]!.span.start,
2444
+ end: styleEntries[0]!.span.end,
2445
+ content: propertyContent,
2446
+ origin: styleEntries[0]!.span,
2447
+ },
2448
+ ...styleEntries.slice(1).map((entry) => ({
2449
+ start: entry.span.start,
2450
+ end: entry.span.end,
2451
+ content: '',
2452
+ origin: entry.span,
2453
+ })),
2454
+ ]
2455
+ : compiledPropsEdits(input, styleEntries, propertyContent)
2456
+ if (!propsEdits) {
2457
+ return bailout(
2458
+ input,
2459
+ 'local/unsupported-target',
2460
+ `Compiled ${input.element.form} call has no editable props argument`
2461
+ )
2462
+ }
2463
+ return {
2464
+ ok: true,
2465
+ edits: [
2466
+ ...primitiveTagEdits,
2467
+ ...propsResult.edits,
2468
+ ...propsEdits,
2469
+ ...literalEdits,
2470
+ ],
2471
+ css: [],
2472
+ imports,
2473
+ flattened: true,
2474
+ }
2475
+ }
2476
+ const nativeName = component.staticConfig.isText ? 'Text' : 'View'
2477
+ // RN's <View> is a JS component wrapping the real host component: it
2478
+ // reads TextAncestorContext, remaps ~25 aria/id/tabIndex props, then
2479
+ // renders <ViewNativeComponent>. RN exports that inner component as
2480
+ // `unstable_NativeView`, and its registry returns the component NAME,
2481
+ // so emitting it is literally `createElement('RCTView', props)` and
2482
+ // saves a context read plus a React element per view per render.
2483
+ // Only when the call site needs nothing the wrapper adds: the aria
2484
+ // remapping (visible in the static props) and the TextAncestorContext
2485
+ // reset, which only a child can observe. <Text>'s wrapper does far
2486
+ // more than <View>'s, so text keeps the wrapper.
2487
+ const useHostView =
2488
+ options.experimentalNativeFastPath &&
2489
+ nativeName === 'View' &&
2490
+ isBareHostView(input.element)
2491
+ const nativeExport = useHostView ? 'unstable_NativeView' : nativeName
2492
+ // the local is named after the BINDING, not the component: a file can
2493
+ // hold both kinds at once, and a shared name would emit two different
2494
+ // declarations of one identifier
2495
+ const nativeLocal = unusedIdentifier(
2496
+ input.source,
2497
+ `__TamaguiNative${useHostView ? 'HostView' : nativeName}`
2498
+ )
2499
+ if (themedStyleKeys && options.disablePartialExtraction) {
2500
+ return bailout(
2501
+ input,
2502
+ 'local/dynamic-style-value',
2503
+ 'Theme values require partial extraction, which is disabled'
2504
+ )
2505
+ }
2506
+ if (dynamicStyleEntries.length > 0 || themedStyleKeys) {
2507
+ if (nativeFastPath) {
2508
+ const fastLocal = `__TamaguiNativeFast${nativeName}${input.element.span.start}`
2509
+ const tagEdits = [
2510
+ input.element.component.span,
2511
+ input.element.component.closingSpan,
2512
+ ]
2513
+ .filter((span): span is NonNullable<typeof span> => !!span)
2514
+ .map((span) => ({
2515
+ start: span.start,
2516
+ end: span.end,
2517
+ content: fastLocal,
2518
+ origin: span,
2519
+ }))
2520
+ const [first, ...rest] = styleEntries
2521
+ const styleEdits =
2522
+ styleEntries.length === 0
2523
+ ? []
2524
+ : input.element.form === 'jsx'
2525
+ ? [
2526
+ {
2527
+ start: first!.span.start,
2528
+ end: first!.span.end,
2529
+ content: '',
2530
+ origin: first!.span,
2531
+ },
2532
+ ...rest.map((entry) => ({
2533
+ start: entry.span.start,
2534
+ end: entry.span.end,
2535
+ content: '',
2536
+ origin: entry.span,
2537
+ })),
2538
+ ]
2539
+ : compiledPropsEdits(input, styleEntries, `_expressions: []`)
2540
+ if (!styleEdits) {
2541
+ return bailout(
2542
+ input,
2543
+ 'local/unsupported-target',
2544
+ `Compiled ${input.element.form} call has no editable props argument`
2545
+ )
2546
+ }
2547
+ return {
2548
+ ok: true,
2549
+ edits: [...tagEdits, ...styleEdits],
2550
+ css: [],
2551
+ imports: [
2552
+ ...nativeStyleImports,
2553
+ ...nativeFastPath.imports,
2554
+ {
2555
+ content: `\nconst ${nativeLocal} = require('react-native').${nativeExport};`,
2556
+ origin: input.element.component.span,
2557
+ },
2558
+ {
2559
+ content: `\nconst ${fastLocal} = require('@tamagui/core')._withNativeStyle(${nativeLocal}, ${nativeStyleSource}, ${nativeFastPath.mappingLocal});`,
2560
+ origin: input.element.component.span,
2561
+ },
2562
+ ],
2563
+ flattened: true,
2564
+ }
2565
+ }
2566
+ const stableLocal = `__TamaguiStable${nativeName}${input.element.span.start}`
2567
+ const expressions: string[] = []
2568
+ const plainDynamicParts: string[] = []
2569
+ // V2-parity per-branch lowering: a conditional whose branches
2570
+ // evaluated statically resolves EACH branch through the full style
2571
+ // pipeline at compile time, so cross-key effects (a fontFamily
2572
+ // switch changing size/lineHeight resolution) are captured; only the
2573
+ // test expression survives into the output
2574
+ const conditionalParts: string[] = []
2575
+ const conditionalKeys = new Set<string>()
2576
+ const baseStyleForDiff = staticObject(nativeStyleResolved)
2577
+ ? nativeStyleResolved
2578
+ : {}
2579
+ for (const entry of dynamicStyleEntries) {
2580
+ // opacity keeps the leaner inline-expression form even for
2581
+ // ternaries; per-branch lowering is for props that form cannot carry
2582
+ if (
2583
+ entry.value.kind === 'conditional' &&
2584
+ directStyleName(entry.name, component) !== 'opacity'
2585
+ ) {
2586
+ const branchDiffs: Record<string, unknown>[] = []
2587
+ for (const branchValue of [entry.value.whenTrue, entry.value.whenFalse]) {
2588
+ const branchSplit = resolveSplitStyles(
2589
+ { ...completeProps, [entry.name]: branchValue },
2590
+ component.staticConfig,
2591
+ cssAnimationDriver,
2592
+ component.displayName
2593
+ )
2594
+ const branchStyle = branchSplit?.viewProps?.style
2595
+ if (!staticObject(branchStyle)) {
2596
+ return bailout(
2597
+ input,
2598
+ 'local/dynamic-style-value',
2599
+ `Conditional ${entry.name} branch did not resolve to a static native style`,
2600
+ entry.value.span
2601
+ )
2602
+ }
2603
+ // a branch (e.g. through a variant) may change viewProps
2604
+ // beyond style; the style array cannot express those, so any
2605
+ // non-style difference sends the element to the runtime path
2606
+ for (const viewPropsKey of new Set([
2607
+ ...Object.keys(branchSplit?.viewProps ?? {}),
2608
+ ...Object.keys(split.viewProps ?? {}),
2609
+ ])) {
2610
+ if (viewPropsKey === 'style') continue
2611
+ if (
2612
+ JSON.stringify(branchSplit?.viewProps?.[viewPropsKey]) !==
2613
+ JSON.stringify((split.viewProps as any)?.[viewPropsKey])
2614
+ ) {
2615
+ return bailout(
2616
+ input,
2617
+ 'local/dynamic-style-value',
2618
+ `Conditional ${entry.name} branch changes ${viewPropsKey}, which the compiled style array cannot express`,
2619
+ entry.value.span
2620
+ )
2621
+ }
2622
+ }
2623
+ for (const key of Object.keys(baseStyleForDiff)) {
2624
+ if (!(key in branchStyle)) {
2625
+ return bailout(
2626
+ input,
2627
+ 'local/dynamic-style-value',
2628
+ `Conditional ${entry.name} branch removes ${key}, which an additive style array cannot express`,
2629
+ entry.value.span
2630
+ )
2631
+ }
2632
+ }
2633
+ const diff: Record<string, unknown> = {}
2634
+ for (const [key, value] of Object.entries(branchStyle)) {
2635
+ if (
2636
+ JSON.stringify((baseStyleForDiff as any)[key]) !==
2637
+ JSON.stringify(value)
2638
+ ) {
2639
+ diff[key] = value
2640
+ }
2641
+ }
2642
+ if (!isSerializableNativeStyle(diff) || core.containsThemeRef(diff)) {
2643
+ return bailout(
2644
+ input,
2645
+ 'local/dynamic-style-value',
2646
+ `Conditional ${entry.name} branch style is not statically representable`,
2647
+ entry.value.span
2648
+ )
2649
+ }
2650
+ for (const key of Object.keys(diff)) {
2651
+ if (conditionalKeys.has(key)) {
2652
+ return bailout(
2653
+ input,
2654
+ 'local/dynamic-style-value',
2655
+ `Multiple conditionals contribute ${key}; their interaction cannot be resolved per-branch`,
2656
+ entry.value.span
2657
+ )
2658
+ }
2659
+ }
2660
+ branchDiffs.push(diff)
2661
+ }
2662
+ for (const diff of branchDiffs) {
2663
+ for (const key of Object.keys(diff)) conditionalKeys.add(key)
2664
+ }
2665
+ const index = expressions.length
2666
+ expressions.push(
2667
+ input.source.slice(entry.value.test.start, entry.value.test.end)
2668
+ )
2669
+ conditionalParts.push(
2670
+ `expressions[${index}] ? ${JSON.stringify(branchDiffs[0])} : ${JSON.stringify(branchDiffs[1])}`
2671
+ )
2672
+ } else {
2673
+ const index = expressions.length
2674
+ expressions.push(
2675
+ input.source.slice(entry.value.span.start, entry.value.span.end)
2676
+ )
2677
+ plainDynamicParts.push(
2678
+ `${JSON.stringify(directStyleName(entry.name, component))}: expressions[${index}]`
2679
+ )
2680
+ }
2681
+ }
2682
+ const dynamicStyle = plainDynamicParts.join(', ')
2683
+ // themed keys were resolved with the exact theme key known ahead of
2684
+ // time; the wrapper subscribes via useTheme() only when hasThemeKeys
2685
+ // is set, so purely-dynamic elements pay no theme cost
2686
+ const themedStyle = themedStyleKeys
2687
+ ? Object.entries(themedStyleKeys)
2688
+ .map(
2689
+ ([styleKey, themeKey]) =>
2690
+ `${JSON.stringify(styleKey)}: _theme[${JSON.stringify(themeKey)}]?.get()`
2691
+ )
2692
+ .join(', ')
2693
+ : null
2694
+ const styleParts = [
2695
+ nativeStyleSource,
2696
+ ...(themedStyle ? [`{ ${themedStyle} }`] : []),
2697
+ ...conditionalParts,
2698
+ ...(dynamicStyle ? [`{ ${dynamicStyle} }`] : []),
2699
+ ]
2700
+ const tagEdits = [
2701
+ input.element.component.span,
2702
+ input.element.component.closingSpan,
2703
+ ]
2704
+ .filter((span): span is NonNullable<typeof span> => !!span)
2705
+ .map((span) => ({
2706
+ start: span.start,
2707
+ end: span.end,
2708
+ content: stableLocal,
2709
+ origin: span,
2710
+ }))
2711
+ const [first, ...rest] = styleEntries
2712
+ const expressionEdits =
2713
+ styleEntries.length === 0
2714
+ ? []
2715
+ : input.element.form === 'jsx'
2716
+ ? [
2717
+ {
2718
+ start: first!.span.start,
2719
+ end: first!.span.end,
2720
+ content:
2721
+ expressions.length > 0
2722
+ ? `_expressions={[${expressions.join(', ')}]}`
2723
+ : '',
2724
+ origin: first!.span,
2725
+ },
2726
+ ...rest.map((entry) => ({
2727
+ start: entry.span.start,
2728
+ end: entry.span.end,
2729
+ content: '',
2730
+ origin: entry.span,
2731
+ })),
2732
+ ]
2733
+ : compiledPropsEdits(
2734
+ input,
2735
+ styleEntries,
2736
+ `_expressions: [${expressions.join(', ')}]`
2737
+ )
2738
+ if (!expressionEdits) {
2739
+ return bailout(
2740
+ input,
2741
+ 'local/unsupported-target',
2742
+ `Compiled ${input.element.form} call has no editable props argument`
2743
+ )
2744
+ }
2745
+ return {
2746
+ ok: true,
2747
+ edits: [...tagEdits, ...expressionEdits],
2748
+ css: [],
2749
+ imports: [
2750
+ ...nativeStyleImports,
2751
+ {
2752
+ content: `\nconst ${nativeLocal} = require('react-native').${nativeExport};`,
2753
+ origin: input.element.component.span,
2754
+ },
2755
+ {
2756
+ content: `\nconst ${stableLocal} = require('@tamagui/core')._withStableStyle(${nativeLocal}, (_theme, expressions) => [${styleParts.join(', ')}], ${themedStyleKeys ? 'true' : 'false'}, false);`,
2757
+ origin: input.element.component.span,
2758
+ },
2759
+ ],
2760
+ flattened: true,
2761
+ }
2762
+ }
2763
+ const styleContent = `style: ${nativeStyleSource}`
2764
+ const tagEdits = [
2765
+ input.element.component.span,
2766
+ input.element.component.closingSpan,
2767
+ ]
2768
+ .filter((span): span is NonNullable<typeof span> => !!span)
2769
+ .map((span) => ({
2770
+ start: span.start,
2771
+ end: span.end,
2772
+ content: nativeLocal,
2773
+ origin: span,
2774
+ }))
2775
+ if (input.element.form !== 'jsx') {
2776
+ const propsEdits = compiledPropsEdits(input, styleEntries, styleContent)
2777
+ if (!propsEdits) {
2778
+ return bailout(
2779
+ input,
2780
+ 'local/unsupported-target',
2781
+ `Compiled ${input.element.form} call has no editable props argument`
2782
+ )
2783
+ }
2784
+ return {
2785
+ ok: true,
2786
+ edits: [...tagEdits, ...propsEdits],
2787
+ css: [],
2788
+ imports: [
2789
+ ...nativeStyleImports,
2790
+ {
2791
+ content: `\nconst ${nativeLocal} = require('react-native').${nativeExport};`,
2792
+ origin: input.element.component.span,
2793
+ },
2794
+ ],
2795
+ flattened: true,
2796
+ }
2797
+ }
2798
+ if (styleEntries.length === 0) {
2799
+ return {
2800
+ ok: true,
2801
+ edits: [
2802
+ ...tagEdits,
2803
+ {
2804
+ start: input.element.component.span.end,
2805
+ end: input.element.component.span.end,
2806
+ content: ` style={${nativeStyleSource}}`,
2807
+ origin: input.element.component.span,
2808
+ },
2809
+ ],
2810
+ css: [],
2811
+ imports: [
2812
+ ...nativeStyleImports,
2813
+ {
2814
+ content: `\nconst ${nativeLocal} = require('react-native').${nativeExport};`,
2815
+ origin: input.element.component.span,
2816
+ },
2817
+ ],
2818
+ flattened: true,
2819
+ }
2820
+ }
2821
+ const [first, ...rest] = styleEntries
2822
+ return {
2823
+ ok: true,
2824
+ edits: [
2825
+ ...tagEdits,
2826
+ ...webPropEdits,
2827
+ {
2828
+ start: first!.span.start,
2829
+ end: first!.span.end,
2830
+ content: `style={${nativeStyleSource}}`,
2831
+ origin: first!.span,
2832
+ },
2833
+ ...rest.map((entry) => ({
2834
+ start: entry.span.start,
2835
+ end: entry.span.end,
2836
+ content: '',
2837
+ origin: entry.span,
2838
+ })),
2839
+ ],
2840
+ css: [],
2841
+ imports: [
2842
+ ...nativeStyleImports,
2843
+ {
2844
+ content: `\nconst ${nativeLocal} = require('react-native').${nativeExport};`,
2845
+ origin: input.element.component.span,
2846
+ },
2847
+ ],
2848
+ flattened: true,
2849
+ }
2850
+ }
2851
+ const artifacts = extractedStyleArtifacts(
2852
+ split,
2853
+ props,
2854
+ options.tamaguiConfig,
2855
+ !component.domTag,
2856
+ Boolean(component.staticConfig.styleFrontend)
2857
+ )
2858
+ const className = artifacts.className
2859
+ const rawInlineStyle = split.viewProps?.style
2860
+ const inlineStyle =
2861
+ staticObject(rawInlineStyle) &&
2862
+ !rawInlineStyle['$$css'] &&
2863
+ Object.keys(rawInlineStyle).length > 0
2864
+ ? (rawInlineStyle as Record<string, unknown>)
2865
+ : null
2866
+ if (rawInlineStyle && !inlineStyle && !isSerializableNativeStyle(rawInlineStyle)) {
2867
+ return bailout(
2868
+ input,
2869
+ 'local/unsupported-target',
2870
+ 'Web inline style output is not a static serializable value'
2871
+ )
2872
+ }
2873
+ const programCSS: string[] = []
2874
+ const programClassSources: string[] = []
2875
+ if (
2876
+ domStyleProgram?.kind === 'prop' &&
2877
+ domStyleProgram.value.kind === 'dom-style'
2878
+ ) {
2879
+ for (const item of domStyleProgram.value.items) {
2880
+ if (item.value.kind !== 'static' || !staticObject(item.value.value)) {
2881
+ return bailout(
2882
+ input,
2883
+ 'local/dynamic-style-value',
2884
+ 'Every style() handle in a style array must be statically evaluable',
2885
+ item.value.span
2886
+ )
2887
+ }
2888
+ const itemSplit = resolveSplitStyles(
2889
+ item.value.value,
2890
+ partialStaticConfig(component.staticConfig)
2891
+ )
2892
+ if (!itemSplit) {
2893
+ return bailout(
2894
+ input,
2895
+ 'local/style-resolution-failed',
2896
+ 'A style() handle could not be resolved',
2897
+ item.value.span
2898
+ )
2899
+ }
2900
+ const itemInline = itemSplit.viewProps?.style
2901
+ if (
2902
+ staticObject(itemInline) &&
2903
+ !itemInline['$$css'] &&
2904
+ Object.keys(itemInline).length > 0
2905
+ ) {
2906
+ return bailout(
2907
+ input,
2908
+ 'local/unsupported-target',
2909
+ 'Conditional web style() handles must lower to CSS classes',
2910
+ item.value.span
2911
+ )
2912
+ }
2913
+ const itemArtifacts = extractedStyleArtifacts(
2914
+ itemSplit,
2915
+ item.value.value,
2916
+ options.tamaguiConfig,
2917
+ false
2918
+ )
2919
+ programCSS.push(...itemArtifacts.css)
2920
+ if (itemArtifacts.className) {
2921
+ const condition = item.condition
2922
+ ? input.source.slice(item.condition.start, item.condition.end)
2923
+ : null
2924
+ programClassSources.push(
2925
+ condition
2926
+ ? `(${condition}) && ${JSON.stringify(itemArtifacts.className)}`
2927
+ : JSON.stringify(itemArtifacts.className)
2928
+ )
2929
+ }
2930
+ }
2931
+ }
2932
+ // web per-branch conditional lowering: both branches resolve through the
2933
+ // full pipeline; classes shared by both branches stay static and each
2934
+ // branch's remainder becomes one ternary className segment. v3's web
2935
+ // font architecture makes this cheap: sizes are family-independent
2936
+ // variables, so a conditional fontFamily flips only its font_* class.
2937
+ let webClassName = className
2938
+ const webConditionalCSS: string[] = []
2939
+ const webConditionalEntries = supportsWebConditionalClasses
2940
+ ? dynamicStyleEntries.filter((entry) => entry.value.kind === 'conditional')
2941
+ : []
2942
+ for (const entry of webConditionalEntries) {
2943
+ if (entry.value.kind !== 'conditional') continue
2944
+ const branches: { classes: string[]; css: string[] }[] = []
2945
+ for (const branchValue of [entry.value.whenTrue, entry.value.whenFalse]) {
2946
+ const branchSplit = resolveSplitStyles(
2947
+ { ...completeProps, [entry.name]: branchValue },
2948
+ component.staticConfig,
2949
+ cssAnimationDriver,
2950
+ component.displayName
2951
+ )
2952
+ if (!branchSplit) {
2953
+ return bailout(
2954
+ input,
2955
+ 'local/dynamic-style-value',
2956
+ `Conditional ${entry.name} branch could not be resolved`,
2957
+ entry.value.span
2958
+ )
2959
+ }
2960
+ for (const viewPropsKey of new Set([
2961
+ ...Object.keys(branchSplit.viewProps ?? {}),
2962
+ ...Object.keys(split.viewProps ?? {}),
2963
+ ])) {
2964
+ if (viewPropsKey === 'className') continue
2965
+ if (
2966
+ JSON.stringify(branchSplit.viewProps?.[viewPropsKey]) !==
2967
+ JSON.stringify((split.viewProps as any)?.[viewPropsKey])
2968
+ ) {
2969
+ return bailout(
2970
+ input,
2971
+ 'local/dynamic-style-value',
2972
+ `Conditional ${entry.name} branch changes ${viewPropsKey}, which conditional classes cannot express`,
2973
+ entry.value.span
2974
+ )
2975
+ }
2976
+ }
2977
+ const branchArtifacts = extractedStyleArtifacts(
2978
+ branchSplit,
2979
+ { ...props, [entry.name]: branchValue },
2980
+ options.tamaguiConfig,
2981
+ !component.domTag,
2982
+ Boolean(component.staticConfig.styleFrontend)
2983
+ )
2984
+ branches.push({
2985
+ classes: branchArtifacts.className.split(' ').filter(Boolean),
2986
+ css: branchArtifacts.css,
2987
+ })
2988
+ }
2989
+ const [whenTrue, whenFalse] = branches
2990
+ const shared = new Set(
2991
+ whenTrue!.classes.filter((item) => whenFalse!.classes.includes(item))
2992
+ )
2993
+ const trueOnly = whenTrue!.classes.filter((item) => !shared.has(item))
2994
+ const falseOnly = whenFalse!.classes.filter((item) => !shared.has(item))
2995
+ webClassName = [...shared].join(' ')
2996
+ webConditionalCSS.push(...whenTrue!.css, ...whenFalse!.css)
2997
+ if (trueOnly.length > 0 || falseOnly.length > 0) {
2998
+ const test = input.source.slice(entry.value.test.start, entry.value.test.end)
2999
+ programClassSources.push(
3000
+ `(${test}) ? ${JSON.stringify(trueOnly.join(' '))} : ${JSON.stringify(falseOnly.join(' '))}`
3001
+ )
3002
+ }
3003
+ }
3004
+ const hasStyleProgram = programClassSources.length > 0
3005
+ const classNameExpression = hasStyleProgram
3006
+ ? `[${[JSON.stringify(webClassName), ...programClassSources].join(', ')}].filter(Boolean).join(" ")`
3007
+ : null
3008
+ const serializedInlineStyle = serializedStyle(
3009
+ inlineStyle,
3010
+ dynamicHostStyleProperties ?? []
3011
+ )
3012
+ const jsxWebStyle = classNameExpression
3013
+ ? [
3014
+ `className={${classNameExpression}}`,
3015
+ serializedInlineStyle ? `style={${serializedInlineStyle}}` : '',
3016
+ ]
3017
+ .filter(Boolean)
3018
+ .join(' ')
3019
+ : jsxStyleAttributes(webClassName, inlineStyle, dynamicHostStyleProperties ?? [])
3020
+ const objectWebStyle = classNameExpression
3021
+ ? [
3022
+ `className: ${classNameExpression}`,
3023
+ serializedInlineStyle ? `style: ${serializedInlineStyle}` : '',
3024
+ ]
3025
+ .filter(Boolean)
3026
+ .join(', ')
3027
+ : objectStyleProperties(
3028
+ webClassName,
3029
+ inlineStyle,
3030
+ dynamicHostStyleProperties ?? []
3031
+ )
3032
+ const webCSS = [...new Set([...artifacts.css, ...programCSS, ...webConditionalCSS])]
3033
+ const webExtraProps = serializedProps(input.element.form, webDOMResult.additions)
3034
+ if (input.element.form !== 'jsx') {
3035
+ const replacement = [objectWebStyle, webExtraProps].filter(Boolean).join(', ')
3036
+ const propsEdits = compiledPropsEdits(input, styleEntries, replacement)
3037
+ if (!propsEdits) {
3038
+ return bailout(
3039
+ input,
3040
+ 'local/unsupported-target',
3041
+ `Compiled ${input.element.form} call has no editable props argument`
3042
+ )
3043
+ }
3044
+ return {
3045
+ ok: true,
3046
+ edits: [...tagEdits, ...webPropEdits, ...propsEdits],
3047
+ css: webCSS,
3048
+ imports: [],
3049
+ flattened: true,
3050
+ }
3051
+ }
3052
+ if (styleEntries.length === 0) {
3053
+ const attributes = [jsxWebStyle, webExtraProps].filter(Boolean).join(' ')
3054
+ return {
3055
+ ok: true,
3056
+ edits: attributes
3057
+ ? [
3058
+ ...tagEdits,
3059
+ ...webPropEdits,
3060
+ {
3061
+ start: input.element.component.span.end,
3062
+ end: input.element.component.span.end,
3063
+ content: ` ${attributes}`,
3064
+ origin: input.element.component.span,
3065
+ },
3066
+ ]
3067
+ : [...tagEdits, ...webPropEdits],
3068
+ css: webCSS,
3069
+ imports: [],
3070
+ flattened: true,
3071
+ }
3072
+ }
3073
+
3074
+ const [first, ...rest] = styleEntries
3075
+ const attributes = [jsxWebStyle, webExtraProps].filter(Boolean).join(' ')
3076
+ return {
3077
+ ok: true,
3078
+ edits: [
3079
+ ...tagEdits,
3080
+ ...webPropEdits,
3081
+ {
3082
+ start: first!.span.start,
3083
+ end: first!.span.end,
3084
+ content: attributes,
3085
+ origin: first!.span,
3086
+ },
3087
+ ...rest.map((entry) => ({
3088
+ start: entry.span.start,
3089
+ end: entry.span.end,
3090
+ content: '',
3091
+ origin: entry.span,
3092
+ })),
3093
+ ],
3094
+ css: webCSS,
3095
+ imports: [],
3096
+ flattened: true,
3097
+ }
3098
+ },
3099
+ }
3100
+ }
3101
+
3102
+ function bailout(
3103
+ input: LoweringCandidateInput,
3104
+ code:
3105
+ | 'local/unsupported-target'
3106
+ | 'local/dynamic-style-value'
3107
+ | 'local/unsafe-style-spread'
3108
+ | 'local/style-resolution-failed'
3109
+ | 'local/unsupported-child',
3110
+ message: string,
3111
+ span = input.element.span,
3112
+ zero?: { rule: ZeroRule; message?: string }
3113
+ ): LoweringCandidateResult {
3114
+ return {
3115
+ ok: false,
3116
+ bailout: {
3117
+ code,
3118
+ kind: 'local',
3119
+ message,
3120
+ span,
3121
+ component: input.element.component.name,
3122
+ ...(zero && { zeroRule: zero.rule, zeroMessage: zero.message }),
3123
+ },
3124
+ }
3125
+ }