@tamagui/static 3.0.0-beta.807.1 → 3.0.0-beta.881.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 (45) hide show
  1. package/dist/cjs/compiler.cjs +17 -5
  2. package/dist/cjs/compilerHost.cjs +164 -50
  3. package/dist/cjs/componentDiscovery.cjs +90 -0
  4. package/dist/cjs/domStructuralPass.cjs +16 -6
  5. package/dist/cjs/exports.cjs +4 -1
  6. package/dist/cjs/extractor/bundleConfig.cjs +23 -0
  7. package/dist/cjs/staticEvaluationIgnoredModules.cjs +1 -1
  8. package/dist/cjs/zero/theme.cjs +5 -20
  9. package/dist/esm/compiler.mjs +18 -6
  10. package/dist/esm/compiler.mjs.map +1 -1
  11. package/dist/esm/compilerHost.mjs +162 -51
  12. package/dist/esm/compilerHost.mjs.map +1 -1
  13. package/dist/esm/componentDiscovery.mjs +65 -0
  14. package/dist/esm/componentDiscovery.mjs.map +1 -0
  15. package/dist/esm/domStructuralPass.mjs +16 -6
  16. package/dist/esm/domStructuralPass.mjs.map +1 -1
  17. package/dist/esm/exports.mjs +4 -1
  18. package/dist/esm/extractor/bundleConfig.mjs +23 -1
  19. package/dist/esm/extractor/bundleConfig.mjs.map +1 -1
  20. package/dist/esm/staticEvaluationIgnoredModules.mjs +1 -1
  21. package/dist/esm/staticEvaluationIgnoredModules.mjs.map +1 -1
  22. package/dist/esm/zero/theme.mjs +5 -20
  23. package/dist/esm/zero/theme.mjs.map +1 -1
  24. package/package.json +17 -17
  25. package/src/compiler.ts +27 -5
  26. package/src/compilerHost.ts +274 -105
  27. package/src/componentDiscovery.ts +95 -0
  28. package/src/domStructuralPass.ts +26 -6
  29. package/src/exports.ts +2 -0
  30. package/src/extractor/bundleConfig.ts +34 -0
  31. package/src/staticEvaluationIgnoredModules.ts +1 -1
  32. package/src/zero/theme.ts +12 -21
  33. package/types/compiler.d.ts +13 -0
  34. package/types/compiler.d.ts.map +1 -1
  35. package/types/compilerHost.d.ts +13 -0
  36. package/types/compilerHost.d.ts.map +1 -1
  37. package/types/componentDiscovery.d.ts +27 -0
  38. package/types/componentDiscovery.d.ts.map +1 -0
  39. package/types/domStructuralPass.d.ts.map +1 -1
  40. package/types/exports.d.ts +2 -0
  41. package/types/exports.d.ts.map +1 -1
  42. package/types/extractor/bundleConfig.d.ts +7 -0
  43. package/types/extractor/bundleConfig.d.ts.map +1 -1
  44. package/types/zero/theme.d.ts +0 -1
  45. package/types/zero/theme.d.ts.map +1 -1
@@ -21,6 +21,7 @@ import {
21
21
  StyleObjectRules,
22
22
  StyleObjectValue,
23
23
  stylePropsAll,
24
+ stylePropsInput,
24
25
  stylePropsText,
25
26
  validStyles as validStylesView,
26
27
  } from '@tamagui/helpers'
@@ -55,11 +56,38 @@ export interface CompilerComponentModule {
55
56
  resolvedId: string
56
57
  }
57
58
 
59
+ /**
60
+ * The components lowering can resolve, keyed the way element provenance names
61
+ * them. The frontend owns one per compile and grows it as it discovers component
62
+ * modules outside the configured `components` list.
63
+ */
64
+ export interface CompilerComponentRegistry {
65
+ /** host-resolved module id -> the module name its components register under */
66
+ modulesById: Map<string, string>
67
+ componentsByModule: Map<string, LoadedComponents>
68
+ }
69
+
70
+ export function createComponentRegistry(
71
+ components: readonly LoadedComponents[],
72
+ componentModules: readonly CompilerComponentModule[]
73
+ ): CompilerComponentRegistry {
74
+ return {
75
+ modulesById: new Map(
76
+ componentModules.map((module) => [module.resolvedId, module.moduleName])
77
+ ),
78
+ componentsByModule: new Map(
79
+ components.map((component) => [component.moduleName, component])
80
+ ),
81
+ }
82
+ }
83
+
58
84
  export interface TamaguiCompilerHostOptions {
59
85
  target: CompilerTarget
60
86
  tamaguiConfig: TamaguiInternalConfig
61
87
  components: LoadedComponents[]
62
88
  componentModules: CompilerComponentModule[]
89
+ /** shared with the frontend so discovery during `prepare` is visible to this host */
90
+ registry?: CompilerComponentRegistry
63
91
  /** Keep elements with dynamic style props fully on the runtime path. */
64
92
  disablePartialExtraction?: boolean
65
93
  /** emit native theme-token mappings for the native style engine */
@@ -390,6 +418,11 @@ const runtimeAnimationProps = new Set([
390
418
  'animatedBy',
391
419
  ])
392
420
 
421
+ // createComponent reads these and never forwards them to the host element, so a
422
+ // flattened element has to drop them instead of emitting an unknown attribute.
423
+ // only the inert values reach the emit: an active one already bailed.
424
+ const componentOnlyProps = new Set(['asChild', 'disableOptimization', 'themeInverse'])
425
+
393
426
  const cssShorthandConflicts: Record<string, readonly string[]> = {
394
427
  background: [
395
428
  'backgroundAttachment',
@@ -548,6 +581,12 @@ const cssShorthandConflicts: Record<string, readonly string[]> = {
548
581
  }
549
582
 
550
583
  const cssConflictFamilies = [
584
+ new Set(['width', 'inlineSize']),
585
+ new Set(['minWidth', 'minInlineSize']),
586
+ new Set(['maxWidth', 'maxInlineSize']),
587
+ new Set(['height', 'blockSize']),
588
+ new Set(['minHeight', 'minBlockSize']),
589
+ new Set(['maxHeight', 'maxBlockSize']),
551
590
  new Set([
552
591
  'marginInline',
553
592
  'marginInlineEnd',
@@ -759,9 +798,9 @@ function nativeDOMProps(input: LoweringCandidateInput, tag: TagName) {
759
798
  for (const entry of entries) {
760
799
  const value = entrySource(input, entry)
761
800
  const attribute =
762
- (Object.hasOwn(ATTRIBUTES, entry.name) ? ATTRIBUTES[entry.name] : undefined) ??
801
+ (entry.name in ATTRIBUTES ? ATTRIBUTES[entry.name] : undefined) ??
763
802
  (entry.name.startsWith('data-') ? ATTRIBUTES['data-*'] : undefined)
764
- const event = Object.hasOwn(EVENTS, entry.name) ? EVENTS[entry.name] : undefined
803
+ const event = entry.name in EVENTS ? EVENTS[entry.name] : undefined
765
804
  if (event) {
766
805
  if (event.native === 'none') {
767
806
  return {
@@ -943,6 +982,8 @@ export function createTamaguiCompilerHost(
943
982
  ): CompilerLoweringHost {
944
983
  const platform = options.target === 'native' ? 'native' : 'web'
945
984
  const core = requireTamaguiCore(platform) as any
985
+ const compilerVariantStyleResolver = core.styled(core.View, {}).staticConfig
986
+ .variantStyleResolver
946
987
  const firstThemeName = Object.keys(options.tamaguiConfig.themes ?? {})[0] ?? ''
947
988
  const firstTheme = options.tamaguiConfig.themes?.[firstThemeName] ?? {}
948
989
  const theme = firstTheme
@@ -968,6 +1009,39 @@ export function createTamaguiCompilerHost(
968
1009
  }
969
1010
  return false
970
1011
  }
1012
+ // A native bundle knows one thing about its platform: it is not web. So a
1013
+ // `web:` clause can never match there and a bare `native:` clause always
1014
+ // does, which makes a value built only from those two resolvable at compile
1015
+ // time. Every other modifier is live at render: media and container queries
1016
+ // measure, theme and group clauses read context, state and lifecycle clauses
1017
+ // track the component, and the device platforms (ios, android, tv) are not
1018
+ // known until the bundle runs. One of those anywhere in the value keeps the
1019
+ // whole element on the runtime path.
1020
+ const platformModifiersRegistered =
1021
+ modifierRegistry.get('web') === 'platform' &&
1022
+ modifierRegistry.get('native') === 'platform'
1023
+ const nativeClauseValue = (
1024
+ value: unknown
1025
+ ): 'live' | { payload: unknown; matched: boolean } | null => {
1026
+ if (typeof value !== 'string' || !flatClausePattern.test(value)) {
1027
+ return isClauseObjectValue(value) ? 'live' : null
1028
+ }
1029
+ if (!platformModifiersRegistered) return 'live'
1030
+ const parsed = parseValue(value, modifierRegistry)
1031
+ if (!parsed.ok || parsed.value.clauses.length === 0) return 'live'
1032
+ let payload: unknown = parsed.value.base
1033
+ let matched = parsed.value.base !== null
1034
+ for (const clause of parsed.value.clauses) {
1035
+ // a chain containing `web` is dead here whatever else it names
1036
+ if (clause.modifiers.includes('web')) continue
1037
+ if (clause.modifiers.length !== 1 || clause.modifiers[0] !== 'native') return 'live'
1038
+ // platform clauses all rank the same, so a later one wins and any of them
1039
+ // beats the unconditional base
1040
+ payload = clause.payload
1041
+ matched = true
1042
+ }
1043
+ return { payload, matched }
1044
+ }
971
1045
  const configuredAnimationDriver = options.tamaguiConfig.animations as
972
1046
  | AnimationDriver
973
1047
  | undefined
@@ -1031,25 +1105,31 @@ export function createTamaguiCompilerHost(
1031
1105
  }
1032
1106
  return resolved.join(' ')
1033
1107
  }
1034
- const modulesById = new Map(
1035
- options.componentModules.map((module) => [module.resolvedId, module.moduleName])
1036
- )
1037
- const componentsByModule = new Map(
1038
- options.components.map((component) => [component.moduleName, component])
1039
- )
1108
+ const { modulesById, componentsByModule } =
1109
+ options.registry ??
1110
+ createComponentRegistry(options.components, options.componentModules)
1040
1111
 
1041
1112
  // A component's import provenance chose its authoring syntax, and the frontend
1042
1113
  // descriptor frozen onto its static config is the only thing that knows how to
1043
- // resolve that syntax's static input (a class-string base, string variants, string
1044
- // compound variants) into styles. Regular Tamagui components carry no descriptor
1114
+ // resolve that syntax's static input (a class-string base, string variants) into
1115
+ // styles. Regular Tamagui components carry no descriptor
1045
1116
  // because their static config is already the renderer's input shape. The compiler
1046
1117
  // must never import a frontend package: `@tamagui/tailwind/vite` already depends on
1047
1118
  // the static compiler, so reaching the other way would be a cycle.
1048
- const normalizeStaticConfig = (staticConfig: StaticConfig): StaticConfig =>
1049
- (staticConfig as any).styleFrontend?.normalizeStaticConfig?.(
1050
- staticConfig,
1051
- options.tamaguiConfig
1052
- ) ?? staticConfig
1119
+ const normalizedStyleConfigs = new WeakMap<StaticConfig, any>()
1120
+ const normalizeStaticConfig = (staticConfig: StaticConfig): StaticConfig => {
1121
+ if (!staticConfig.styleFrontend) return staticConfig
1122
+ const normalized = core.getStyleStaticConfig(staticConfig, options.tamaguiConfig)
1123
+ const result = {
1124
+ ...staticConfig,
1125
+ baseStyle: normalized.baseStyle,
1126
+ defaultProps: normalized.defaultProps,
1127
+ variants: normalized.variants,
1128
+ passthroughClassName: normalized.passthroughClassName,
1129
+ }
1130
+ normalizedStyleConfigs.set(result, normalized)
1131
+ return result
1132
+ }
1053
1133
 
1054
1134
  const directStaticConfig = (
1055
1135
  element: MaterializedElement
@@ -1081,7 +1161,7 @@ export function createTamaguiCompilerHost(
1081
1161
  if (
1082
1162
  identity?.importedName !== 'html' ||
1083
1163
  !DOM_FRONTENDS.has(identity.specifier) ||
1084
- !Object.hasOwn(TAGS, tag)
1164
+ !(tag in TAGS)
1085
1165
  ) {
1086
1166
  return null
1087
1167
  }
@@ -1113,7 +1193,10 @@ export function createTamaguiCompilerHost(
1113
1193
  isInput: row.backing === 'textinput',
1114
1194
  // CSS text properties may be authored on any element and inherited by
1115
1195
  // descendant text, including from a View-backed tag on native.
1116
- validStyles: { ...validStylesView, ...stylePropsText },
1196
+ validStyles: {
1197
+ ...validStylesView,
1198
+ ...(row.backing === 'textinput' ? stylePropsInput : stylePropsText),
1199
+ },
1117
1200
  defaultProps: {
1118
1201
  ...base.defaultProps,
1119
1202
  ...platformDefaults,
@@ -1141,7 +1224,6 @@ export function createTamaguiCompilerHost(
1141
1224
  const {
1142
1225
  variants,
1143
1226
  defaultVariants,
1144
- compoundVariants,
1145
1227
  displayName,
1146
1228
  context,
1147
1229
  contextProps,
@@ -1152,33 +1234,31 @@ export function createTamaguiCompilerHost(
1152
1234
  typeof definition.baseClassName.value === 'string'
1153
1235
  ? definition.baseClassName.value
1154
1236
  : undefined
1237
+ const localStaticConfig = {
1238
+ ...base.staticConfig,
1239
+ variants: {
1240
+ ...base.staticConfig.variants,
1241
+ ...(variants as object | undefined),
1242
+ },
1243
+ defaultProps: {
1244
+ ...base.staticConfig.defaultProps,
1245
+ ...defaultProps,
1246
+ ...(defaultVariants as object | undefined),
1247
+ },
1248
+ defaultVariants,
1249
+ baseClassName: [base.staticConfig.baseClassName, baseClassName]
1250
+ .filter(Boolean)
1251
+ .join(' '),
1252
+ context: context ?? base.staticConfig.context,
1253
+ contextProps: context
1254
+ ? contextProps
1255
+ : (contextProps ?? base.staticConfig.contextProps),
1256
+ }
1257
+ ;(localStaticConfig as any).variantStyleResolver = compilerVariantStyleResolver
1155
1258
  return {
1156
1259
  key: componentKey(definition.id, definition.name),
1157
1260
  displayName: displayName || base.displayName,
1158
- staticConfig: normalizeStaticConfig({
1159
- ...base.staticConfig,
1160
- variants: {
1161
- ...base.staticConfig.variants,
1162
- ...(variants as object | undefined),
1163
- },
1164
- compoundVariants: [
1165
- ...(base.staticConfig.compoundVariants ?? []),
1166
- ...(compoundVariants ?? []),
1167
- ],
1168
- defaultProps: {
1169
- ...base.staticConfig.defaultProps,
1170
- ...defaultProps,
1171
- ...(defaultVariants as object | undefined),
1172
- },
1173
- defaultVariants,
1174
- baseClassName: [base.staticConfig.baseClassName, baseClassName]
1175
- .filter(Boolean)
1176
- .join(' '),
1177
- context: context ?? base.staticConfig.context,
1178
- contextProps: context
1179
- ? contextProps
1180
- : (contextProps ?? base.staticConfig.contextProps),
1181
- }),
1261
+ staticConfig: normalizeStaticConfig(localStaticConfig),
1182
1262
  }
1183
1263
  }
1184
1264
 
@@ -1190,7 +1270,7 @@ export function createTamaguiCompilerHost(
1190
1270
  const resolved =
1191
1271
  dom ?? styledStaticConfig(styledDefinition) ?? directStaticConfig(element)
1192
1272
  if (!resolved) return null
1193
- const defaultProps = core.getDefaultProps(resolved.staticConfig) ?? {}
1273
+ const defaultProps = resolved.staticConfig.defaultProps ?? {}
1194
1274
  return {
1195
1275
  key: resolved.key,
1196
1276
  canFlatten:
@@ -1209,8 +1289,7 @@ export function createTamaguiCompilerHost(
1209
1289
  Object.keys(defaultProps).length === 0 &&
1210
1290
  !resolved.staticConfig.defaultVariants &&
1211
1291
  !resolved.staticConfig.baseClassName &&
1212
- !resolved.staticConfig.baseStyle &&
1213
- (resolved.staticConfig.compoundVariants?.length ?? 0) === 0,
1292
+ !resolved.staticConfig.baseStyle,
1214
1293
  }
1215
1294
  }
1216
1295
 
@@ -1251,10 +1330,12 @@ export function createTamaguiCompilerHost(
1251
1330
  const staticConfig = component.staticConfig as StaticConfig
1252
1331
  const validStyles =
1253
1332
  staticConfig.validStyles ||
1254
- (staticConfig.isText || staticConfig.isInput ? stylePropsText : validStylesView)
1255
- return (
1256
- name in stylePropsAll && !isValidStyleKey(name, validStyles, staticConfig.accept)
1257
- )
1333
+ (staticConfig.isInput
1334
+ ? stylePropsInput
1335
+ : staticConfig.isText
1336
+ ? stylePropsText
1337
+ : validStylesView)
1338
+ return name in stylePropsAll && !isValidStyleKey(name, validStyles)
1258
1339
  }
1259
1340
 
1260
1341
  const directStyleName = (name: string, component: LoweringComponent): string | null => {
@@ -1282,7 +1363,6 @@ export function createTamaguiCompilerHost(
1282
1363
  }
1283
1364
  process.env.TAMAGUI_TARGET = platform
1284
1365
  try {
1285
- core.prepareStyleStaticConfig(staticConfig)
1286
1366
  return core.getSplitStyles(
1287
1367
  props,
1288
1368
  staticConfig,
@@ -1290,6 +1370,7 @@ export function createTamaguiCompilerHost(
1290
1370
  firstThemeName,
1291
1371
  componentState,
1292
1372
  {
1373
+ isStatic: true,
1293
1374
  resolveValues: platform === 'native' ? 'except-theme' : 'variable',
1294
1375
  noClass: platform === 'native',
1295
1376
  isAnimated: false,
@@ -1301,7 +1382,8 @@ export function createTamaguiCompilerHost(
1301
1382
  undefined,
1302
1383
  undefined,
1303
1384
  undefined,
1304
- animationDriver
1385
+ animationDriver,
1386
+ normalizedStyleConfigs.get(staticConfig)
1305
1387
  )
1306
1388
  } finally {
1307
1389
  if (previousStatic === undefined) delete process.env.IS_STATIC
@@ -1316,7 +1398,6 @@ export function createTamaguiCompilerHost(
1316
1398
  baseStyle: undefined,
1317
1399
  defaultProps: {},
1318
1400
  defaultVariants: undefined,
1319
- compoundVariants: [],
1320
1401
  variants: {},
1321
1402
  })
1322
1403
 
@@ -1499,6 +1580,12 @@ export function createTamaguiCompilerHost(
1499
1580
  ) {
1500
1581
  return true
1501
1582
  }
1583
+ // A component resolver receives the complete props object. Without a
1584
+ // compiler-derived read set, resolving an opaque dynamic callsite would
1585
+ // execute the real resolver with an incomplete props snapshot.
1586
+ if ((component.staticConfig as StaticConfig).resolvers?.length) {
1587
+ return false
1588
+ }
1502
1589
  return (
1503
1590
  !options.disablePartialExtraction &&
1504
1591
  ((platform === 'web' && !!directStyleName(name, component)) ||
@@ -1554,15 +1641,56 @@ export function createTamaguiCompilerHost(
1554
1641
  props[entry.name] = entry.value.value
1555
1642
  }
1556
1643
  }
1557
- const disableOptimizationEntry = input.element.entries.find(
1558
- (entry) => entry.kind === 'prop' && entry.name === 'disableOptimization'
1559
- )
1560
- if (disableOptimizationEntry || Object.hasOwn(props, 'disableOptimization')) {
1644
+ if (component.staticConfig.resolvers?.length) {
1645
+ const unresolvedProps = input.element.entries.filter(
1646
+ (entry) => entry.kind === 'prop' && entry.value.kind !== 'static'
1647
+ )
1648
+ // One conditional with static leaves is still a complete prop set:
1649
+ // lowerCandidate evaluates the full resolver chain once per branch.
1650
+ // Multiple unknown props would require a cross-product/read set, so
1651
+ // they remain on the runtime path.
1652
+ const onlyStaticBranchConditional =
1653
+ unresolvedProps.length === 1 &&
1654
+ unresolvedProps[0]?.kind === 'prop' &&
1655
+ unresolvedProps[0].value.kind === 'conditional' &&
1656
+ canLowerConditionalStyleProp(unresolvedProps[0].name, component)
1657
+ const unresolvedProp = onlyStaticBranchConditional
1658
+ ? undefined
1659
+ : unresolvedProps[0]
1660
+ if (unresolvedProp?.kind === 'prop') {
1661
+ return bailout(
1662
+ input,
1663
+ 'local/dynamic-style-value',
1664
+ `Component resolver prop ${unresolvedProp.name} could not be evaluated`,
1665
+ unresolvedProp.span,
1666
+ undefined,
1667
+ unresolvedProp.name
1668
+ )
1669
+ }
1670
+ }
1671
+ // asChild, disableOptimization and themeInverse each asked only whether the
1672
+ // prop was PRESENT, so a false value retained a component that requested
1673
+ // none of the runtime behavior the branch exists for. read the materialized
1674
+ // value instead, and treat a prop the compiler could not evaluate as active.
1675
+ // a later duplicate wins, so the last entry decides and any unevaluated
1676
+ // entry for that name makes the answer unknown.
1677
+ const componentOnlyProp = (name: string) => {
1678
+ let span: MaterializedElement['entries'][number]['span'] | undefined
1679
+ let unknown = false
1680
+ for (const entry of input.element.entries) {
1681
+ if (entry.kind !== 'prop' || entry.name !== name) continue
1682
+ span = entry.span
1683
+ if (entry.value.kind !== 'static') unknown = true
1684
+ }
1685
+ return { unknown, span }
1686
+ }
1687
+ const disableOptimization = componentOnlyProp('disableOptimization')
1688
+ if (disableOptimization.unknown || props.disableOptimization) {
1561
1689
  return bailout(
1562
1690
  input,
1563
1691
  'local/unsupported-target',
1564
1692
  'disableOptimization keeps the component on the runtime path',
1565
- disableOptimizationEntry?.span
1693
+ disableOptimization.span
1566
1694
  )
1567
1695
  }
1568
1696
  if (component.domTag && props.hidden) props.display = 'none'
@@ -1628,7 +1756,7 @@ export function createTamaguiCompilerHost(
1628
1756
  // had never been written: the element flattened and the prop was dropped
1629
1757
  // with no diagnostic. Same merge function and same precedence as
1630
1758
  // completeProps, so there is one answer to "what is this prop's value".
1631
- const animationDefaultProps = core.getDefaultProps(component.staticConfig) ?? {}
1759
+ const animationDefaultProps = component.staticConfig.defaultProps ?? {}
1632
1760
  const animationProps = core.mergeProps(animationDefaultProps, props)
1633
1761
  const animationNames = new Set([
1634
1762
  ...input.element.entries.flatMap((entry) =>
@@ -1759,11 +1887,7 @@ export function createTamaguiCompilerHost(
1759
1887
  name === 'opacity'
1760
1888
  ? `opacity: (${expression})`
1761
1889
  : `transform: "scale(" + (${expression}) + ")"`
1762
- } else if (
1763
- entry.value.kind === 'bailout' &&
1764
- owners.size === 1 &&
1765
- owners.has(name)
1766
- ) {
1890
+ } else if (entry.value.kind === 'bailout' && owners.has(name)) {
1767
1891
  const dynamic = entry.value.dynamic
1768
1892
  if (dynamic?.type === 'number') {
1769
1893
  property = `${JSON.stringify(name)}: (${expression})`
@@ -1829,8 +1953,13 @@ export function createTamaguiCompilerHost(
1829
1953
  entry.value.kind === 'conditional' &&
1830
1954
  canLowerConditionalStyleProp(entry.name, component)
1831
1955
  )
1832
- if ('theme' in props || 'themeInverse' in props) {
1833
- const themeProp = 'theme' in props ? 'theme' : 'themeInverse'
1956
+ // a theme name of any value selects a theme, while themeInverse only opens a
1957
+ // boundary when it is on
1958
+ const themeEntry = componentOnlyProp('theme')
1959
+ const themeInverseEntry = componentOnlyProp('themeInverse')
1960
+ const themeBoundary = themeEntry.unknown || 'theme' in props
1961
+ if (themeBoundary || themeInverseEntry.unknown || props.themeInverse) {
1962
+ const themeProp = themeBoundary ? 'theme' : 'themeInverse'
1834
1963
  return bailout(
1835
1964
  input,
1836
1965
  'local/unsupported-target',
@@ -1846,15 +1975,13 @@ export function createTamaguiCompilerHost(
1846
1975
  // the single child and emits no element of its own. a flattened host view
1847
1976
  // would add a wrapper the runtime never renders, and the child would
1848
1977
  // never receive the merged props. both platforms.
1849
- const asChildEntry = input.element.entries.find(
1850
- (entry) => entry.kind === 'prop' && entry.name === 'asChild'
1851
- )
1852
- if (asChildEntry || Object.hasOwn(props, 'asChild')) {
1978
+ const asChild = componentOnlyProp('asChild')
1979
+ if (asChild.unknown || props.asChild) {
1853
1980
  return bailout(
1854
1981
  input,
1855
1982
  'local/unsupported-target',
1856
1983
  'asChild renders a Slot, not a host view',
1857
- asChildEntry?.span
1984
+ asChild.span
1858
1985
  )
1859
1986
  }
1860
1987
  // group and container props publish separate keys in the same context:
@@ -2045,7 +2172,11 @@ export function createTamaguiCompilerHost(
2045
2172
  entry.span
2046
2173
  )
2047
2174
  }
2048
- const staticDefaultProps = core.getDefaultProps(component.staticConfig) ?? {}
2175
+ const resolvedStyleStaticConfig = core.getStyleStaticConfig(
2176
+ component.staticConfig,
2177
+ core.getConfig()
2178
+ )
2179
+ const staticDefaultProps = resolvedStyleStaticConfig.defaultProps ?? {}
2049
2180
  const defaultProps =
2050
2181
  platform === 'web' &&
2051
2182
  !component.staticConfig.isText &&
@@ -2060,6 +2191,19 @@ export function createTamaguiCompilerHost(
2060
2191
  props
2061
2192
  )
2062
2193
  }
2194
+ const domStyleProgram = input.element.entries.find(
2195
+ (entry) =>
2196
+ entry.kind === 'prop' &&
2197
+ entry.name === 'style' &&
2198
+ entry.value.kind === 'dom-style'
2199
+ )
2200
+ if (platform === 'native' && !component.domTag && domStyleProgram) {
2201
+ return bailout(
2202
+ input,
2203
+ 'local/unsupported-target',
2204
+ 'Native style() pieces remain on the runtime path'
2205
+ )
2206
+ }
2063
2207
  const propsForConditional = (
2064
2208
  target: Extract<MaterializedElement['entries'][number], { kind: 'prop' }>,
2065
2209
  value: unknown
@@ -2097,22 +2241,35 @@ export function createTamaguiCompilerHost(
2097
2241
  branchProps
2098
2242
  )
2099
2243
  : core.mergeProps(defaultProps, branchProps)
2244
+ // the same platform-clause reduction the unconditional props get: a
2245
+ // branch resolves through the full pipeline, so a clause left standing
2246
+ // here would be decided by the build machine instead of the bundle
2247
+ if (platform === 'native') {
2248
+ for (const [name, value] of Object.entries(branchCompleteProps)) {
2249
+ const clause = isStyleProp(name, component) ? nativeClauseValue(value) : null
2250
+ if (clause === null || clause === 'live') continue
2251
+ if (clause.matched) branchCompleteProps[name] = clause.payload
2252
+ else delete branchCompleteProps[name]
2253
+ }
2254
+ }
2100
2255
  return { branchProps, branchCompleteProps }
2101
2256
  }
2102
- // Against completeProps, not props: clauses also arrive from styled()
2103
- // defaults. Native resolution evaluates them against the build machine's
2104
- // current state, so folding would freeze that state into the bundle.
2257
+ // native resolution evaluates clauses against the build machine's current
2258
+ // state, so folding a call-site, styled base, or variant clause would freeze
2259
+ // that state into the bundle.
2105
2260
  if (platform === 'native') {
2106
2261
  const isClauseValue = (name: string, value: unknown) =>
2107
2262
  isStyleProp(name, component) &&
2108
2263
  ((typeof value === 'string' && flatClausePattern.test(value)) ||
2109
2264
  isClauseObjectValue(value))
2110
2265
  // a clause can also sit inside a variant definition, where it never
2111
- // appears as a prop: variants: { big: { true: { width: 'gt-lg:999px' } } }
2266
+ // appears as a prop: variants: { big: { true: { width: 'gt-lg:999px' } } }.
2267
+ // those live in the styled definition, which this element cannot rewrite,
2268
+ // so a clause there retains whatever kind it is.
2112
2269
  const defaultVariants = component.staticConfig.defaultVariants ?? {}
2113
- const carriesClause =
2114
- Object.entries(completeProps).some(([name, value]) =>
2115
- isClauseValue(name, value)
2270
+ const definitionCarriesClause =
2271
+ Object.entries(resolvedStyleStaticConfig.baseStyle ?? {}).some(
2272
+ ([name, value]) => isClauseValue(name, value)
2116
2273
  ) ||
2117
2274
  Object.entries(component.staticConfig.variants ?? {}).some(
2118
2275
  ([variantName, definitions]) =>
@@ -2126,24 +2283,44 @@ export function createTamaguiCompilerHost(
2126
2283
  isClauseValue(name, value)
2127
2284
  )
2128
2285
  )
2129
- ) ||
2130
- // compound variant styles are contributed by resolveSplitStyles too,
2131
- // so a clause there would also freeze build-machine state if folded
2132
- (component.staticConfig.compoundVariants ?? []).some(
2133
- (compound) =>
2134
- staticObject(compound) &&
2135
- staticObject(compound.style) &&
2136
- Object.entries(compound.style).some(([name, value]) =>
2137
- isClauseValue(name, value)
2138
- )
2139
2286
  )
2140
- if (carriesClause) {
2287
+ if (definitionCarriesClause) {
2141
2288
  return bailout(
2142
2289
  input,
2143
2290
  'local/unsupported-target',
2144
2291
  'Native conditional value programs remain on the runtime path'
2145
2292
  )
2146
2293
  }
2294
+ const reducedProps: Record<string, unknown> = {}
2295
+ for (const [name, value] of Object.entries(completeProps)) {
2296
+ const clause = isStyleProp(name, component) ? nativeClauseValue(value) : null
2297
+ if (clause === 'live') {
2298
+ return bailout(
2299
+ input,
2300
+ 'local/unsupported-target',
2301
+ 'Native conditional value programs remain on the runtime path'
2302
+ )
2303
+ }
2304
+ if (clause === null) {
2305
+ reducedProps[name] = value
2306
+ continue
2307
+ }
2308
+ if (clause.matched) reducedProps[name] = clause.payload
2309
+ }
2310
+ completeProps = reducedProps
2311
+ // a conditional leaf is a clause program the same way a direct prop is,
2312
+ // and propsForConditional reduces the ones that fold
2313
+ for (const entry of dynamicStyleEntries) {
2314
+ if (entry.value.kind !== 'conditional') continue
2315
+ for (const leaf of collectLeaves(entry.value.tree)) {
2316
+ if (nativeClauseValue(leaf.value) !== 'live') continue
2317
+ return bailout(
2318
+ input,
2319
+ 'local/unsupported-target',
2320
+ 'Native conditional value programs remain on the runtime path'
2321
+ )
2322
+ }
2323
+ }
2147
2324
  }
2148
2325
  const split = resolveSplitStyles(
2149
2326
  completeProps,
@@ -2175,13 +2352,6 @@ export function createTamaguiCompilerHost(
2175
2352
  }
2176
2353
  )
2177
2354
  }
2178
- const domStyleProgram = input.element.entries.find(
2179
- (entry) =>
2180
- entry.kind === 'prop' &&
2181
- entry.name === 'style' &&
2182
- entry.value.kind === 'dom-style'
2183
- )
2184
-
2185
2355
  const flatTag =
2186
2356
  component.domTag ??
2187
2357
  (typeof props.render === 'string'
@@ -2201,7 +2371,9 @@ export function createTamaguiCompilerHost(
2201
2371
  }))
2202
2372
 
2203
2373
  const isPropIgnored = (name: string) =>
2204
- isStyleProp(name, component) || isInvalidHostStyleProp(name, component)
2374
+ isStyleProp(name, component) ||
2375
+ isInvalidHostStyleProp(name, component) ||
2376
+ componentOnlyProps.has(name)
2205
2377
  const spreadReplacement = (
2206
2378
  form: MaterializedElement['form'],
2207
2379
  entry: MaterializedElement['entries'][number]
@@ -2218,16 +2390,11 @@ export function createTamaguiCompilerHost(
2218
2390
 
2219
2391
  let styleEntries = input.element.entries.filter(
2220
2392
  (entry) =>
2221
- (entry.kind === 'prop' &&
2222
- (isStyleProp(entry.name, component) ||
2223
- isInvalidHostStyleProp(entry.name, component))) ||
2393
+ (entry.kind === 'prop' && isPropIgnored(entry.name)) ||
2224
2394
  (entry.kind === 'spread' &&
2225
2395
  entry.value.kind === 'static' &&
2226
2396
  staticObject(entry.value.value) &&
2227
- Object.keys(entry.value.value).some(
2228
- (name) =>
2229
- isStyleProp(name, component) || isInvalidHostStyleProp(name, component)
2230
- ))
2397
+ Object.keys(entry.value.value).some(isPropIgnored))
2231
2398
  )
2232
2399
  let invalidHostStyle:
2233
2400
  | { entry: MaterializedElement['entries'][number]; name: string }
@@ -3369,7 +3536,8 @@ function bailout(
3369
3536
  | 'local/unsupported-child',
3370
3537
  message: string,
3371
3538
  span = input.element.span,
3372
- zero?: { rule: ZeroRule; message?: string }
3539
+ zero?: { rule: ZeroRule; message?: string },
3540
+ prop?: string
3373
3541
  ): LoweringCandidateResult {
3374
3542
  return {
3375
3543
  ok: false,
@@ -3379,6 +3547,7 @@ function bailout(
3379
3547
  message,
3380
3548
  span,
3381
3549
  component: input.element.component.name,
3550
+ ...(prop && { prop }),
3382
3551
  ...(zero && { zeroRule: zero.rule, zeroMessage: zero.message }),
3383
3552
  },
3384
3553
  }