@tamagui/static 3.0.0-beta.831.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 (41) hide show
  1. package/dist/cjs/compiler.cjs +17 -5
  2. package/dist/cjs/compilerHost.cjs +142 -38
  3. package/dist/cjs/componentDiscovery.cjs +90 -0
  4. package/dist/cjs/domStructuralPass.cjs +12 -2
  5. package/dist/cjs/exports.cjs +4 -1
  6. package/dist/cjs/extractor/bundleConfig.cjs +23 -0
  7. package/dist/cjs/zero/theme.cjs +5 -20
  8. package/dist/esm/compiler.mjs +18 -6
  9. package/dist/esm/compiler.mjs.map +1 -1
  10. package/dist/esm/compilerHost.mjs +140 -39
  11. package/dist/esm/compilerHost.mjs.map +1 -1
  12. package/dist/esm/componentDiscovery.mjs +65 -0
  13. package/dist/esm/componentDiscovery.mjs.map +1 -0
  14. package/dist/esm/domStructuralPass.mjs +12 -2
  15. package/dist/esm/domStructuralPass.mjs.map +1 -1
  16. package/dist/esm/exports.mjs +4 -1
  17. package/dist/esm/extractor/bundleConfig.mjs +23 -1
  18. package/dist/esm/extractor/bundleConfig.mjs.map +1 -1
  19. package/dist/esm/zero/theme.mjs +5 -20
  20. package/dist/esm/zero/theme.mjs.map +1 -1
  21. package/package.json +17 -17
  22. package/src/compiler.ts +27 -5
  23. package/src/compilerHost.ts +250 -73
  24. package/src/componentDiscovery.ts +95 -0
  25. package/src/domStructuralPass.ts +22 -2
  26. package/src/exports.ts +2 -0
  27. package/src/extractor/bundleConfig.ts +34 -0
  28. package/src/zero/theme.ts +12 -21
  29. package/types/compiler.d.ts +13 -0
  30. package/types/compiler.d.ts.map +1 -1
  31. package/types/compilerHost.d.ts +13 -0
  32. package/types/compilerHost.d.ts.map +1 -1
  33. package/types/componentDiscovery.d.ts +27 -0
  34. package/types/componentDiscovery.d.ts.map +1 -0
  35. package/types/domStructuralPass.d.ts.map +1 -1
  36. package/types/exports.d.ts +2 -0
  37. package/types/exports.d.ts.map +1 -1
  38. package/types/extractor/bundleConfig.d.ts +7 -0
  39. package/types/extractor/bundleConfig.d.ts.map +1 -1
  40. package/types/zero/theme.d.ts +0 -1
  41. package/types/zero/theme.d.ts.map +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tamagui/static",
3
- "version": "3.0.0-beta.831.1",
3
+ "version": "3.0.0-beta.881.1",
4
4
  "gitHead": "a49cc7ea6b93ba384e77a4880ae48ac4a5635c14",
5
5
  "license": "MIT",
6
6
  "source": "src/index.ts",
@@ -45,21 +45,21 @@
45
45
  "clean:build": "tamagui-build clean:build"
46
46
  },
47
47
  "dependencies": {
48
- "@tamagui/cli-color": "3.0.0-beta.831.1",
49
- "@tamagui/compiler-core": "3.0.0-beta.831.1",
50
- "@tamagui/config-default": "3.0.0-beta.831.1",
51
- "@tamagui/core": "3.0.0-beta.831.1",
52
- "@tamagui/dom": "3.0.0-beta.831.1",
53
- "@tamagui/fake-react-native": "3.0.0-beta.831.1",
54
- "@tamagui/generate-themes": "3.0.0-beta.831.1",
55
- "@tamagui/helpers": "3.0.0-beta.831.1",
56
- "@tamagui/helpers-node": "3.0.0-beta.831.1",
57
- "@tamagui/react-native-web-internals": "3.0.0-beta.831.1",
58
- "@tamagui/react-native-web-lite": "3.0.0-beta.831.1",
59
- "@tamagui/shorthands": "3.0.0-beta.831.1",
60
- "@tamagui/style-grammar": "3.0.0-beta.831.1",
61
- "@tamagui/types": "3.0.0-beta.831.1",
62
- "@tamagui/web": "3.0.0-beta.831.1",
48
+ "@tamagui/cli-color": "3.0.0-beta.881.1",
49
+ "@tamagui/compiler-core": "3.0.0-beta.881.1",
50
+ "@tamagui/config-default": "3.0.0-beta.881.1",
51
+ "@tamagui/core": "3.0.0-beta.881.1",
52
+ "@tamagui/dom": "3.0.0-beta.881.1",
53
+ "@tamagui/fake-react-native": "3.0.0-beta.881.1",
54
+ "@tamagui/generate-themes": "3.0.0-beta.881.1",
55
+ "@tamagui/helpers": "3.0.0-beta.881.1",
56
+ "@tamagui/helpers-node": "3.0.0-beta.881.1",
57
+ "@tamagui/react-native-web-internals": "3.0.0-beta.881.1",
58
+ "@tamagui/react-native-web-lite": "3.0.0-beta.881.1",
59
+ "@tamagui/shorthands": "3.0.0-beta.881.1",
60
+ "@tamagui/style-grammar": "3.0.0-beta.881.1",
61
+ "@tamagui/types": "3.0.0-beta.881.1",
62
+ "@tamagui/web": "3.0.0-beta.881.1",
63
63
  "browserslist": "^4.28.1",
64
64
  "check-dependency-version-consistency": "^4.1.0",
65
65
  "esbuild": "0.28.1",
@@ -71,7 +71,7 @@
71
71
  "react-native-web": "~0.21.0"
72
72
  },
73
73
  "devDependencies": {
74
- "@tamagui/build": "3.0.0-beta.831.1",
74
+ "@tamagui/build": "3.0.0-beta.881.1",
75
75
  "@types/find-root": "^1.1.2",
76
76
  "@types/node": "^22.1.0",
77
77
  "@types/webpack": "^4.41.26",
package/src/compiler.ts CHANGED
@@ -19,7 +19,8 @@ import { readFileSync } from 'node:fs'
19
19
  import { createRequire } from 'node:module'
20
20
  import path from 'node:path'
21
21
 
22
- import { createTamaguiCompilerHost } from './compilerHost'
22
+ import { ComponentDiscovery, type ComponentModuleEvaluator } from './componentDiscovery'
23
+ import { createComponentRegistry, createTamaguiCompilerHost } from './compilerHost'
23
24
  import { domStructuralPass } from './domStructuralPass'
24
25
  import type { TamaguiProjectInfo } from './extractor/bundleConfig'
25
26
  import { loadTamagui } from './extractor/loadTamagui'
@@ -217,6 +218,15 @@ export interface CompilerInput {
217
218
  project: CompilerProject
218
219
  resolve(specifier: string, importer: string): Promise<CompilerResolution | null>
219
220
  load(id: string): Promise<string | null>
221
+ /**
222
+ * Evaluate a host-resolved module and return its exports. The frontend asks
223
+ * for every package a JSX element or styled() base imports from that is not
224
+ * in the configured `components` list, once per module per project, and
225
+ * lowers against the static configs it finds. Return null (or throw) when
226
+ * the host cannot evaluate the module; those elements stay on the runtime
227
+ * path exactly as before.
228
+ */
229
+ evaluate?: ComponentModuleEvaluator
220
230
  }
221
231
 
222
232
  export type CompilerUpdateInput = CompilerInput
@@ -257,6 +267,7 @@ export class CompilerFrontend {
257
267
  private readonly planCaches = new Map<string, ModulePlanCache>()
258
268
  private readonly moduleRecords = new Map<ResolvedModuleId, HostModuleInput>()
259
269
  private moduleContext: string | null = null
270
+ private readonly discovery = new ComponentDiscovery()
260
271
 
261
272
  /**
262
273
  * One cache per project root and platform. Absent when the project produced
@@ -358,14 +369,18 @@ export class CompilerFrontend {
358
369
  if (!projectInfo.tamaguiConfig || !projectInfo.components) {
359
370
  throw new Error('The compiler requires evaluated Tamagui config and components')
360
371
  }
372
+ const componentModules = input.project.componentModules.map((component) => ({
373
+ moduleName: component.moduleName,
374
+ resolvedId: cleanId(component.id),
375
+ }))
376
+ const registry = createComponentRegistry(projectInfo.components, componentModules)
377
+ this.discovery.seed(registry)
361
378
  const host = createTamaguiCompilerHost({
362
379
  target: input.target,
363
380
  tamaguiConfig: projectInfo.tamaguiConfig,
364
381
  components: projectInfo.components,
365
- componentModules: input.project.componentModules.map((component) => ({
366
- moduleName: component.moduleName,
367
- resolvedId: cleanId(component.id),
368
- })),
382
+ componentModules,
383
+ registry,
369
384
  disablePartialExtraction: input.project.disablePartialExtraction,
370
385
  experimentalNativeFastPath: input.project.experimentalNativeFastPath,
371
386
  zeroRuntime: input.project.zeroRuntime,
@@ -380,6 +395,7 @@ export class CompilerFrontend {
380
395
  async load(id) {
381
396
  return modules.get(id) ?? null
382
397
  },
398
+ prepare: (module) => this.discovery.prepare(module, registry, input.evaluate),
383
399
  },
384
400
  structuralPass: domStructuralPass,
385
401
  })
@@ -391,6 +407,11 @@ export class CompilerFrontend {
391
407
  }
392
408
  }
393
409
 
410
+ /** host-resolved ids of every module discovery found components in */
411
+ discoveredModuleIds(): string[] {
412
+ return this.discovery.ids()
413
+ }
414
+
394
415
  private async buildTree(input: CompilerUpdateInput): Promise<{
395
416
  rootModule: HostModuleInput
396
417
  modules: Map<ResolvedModuleId, HostModuleInput>
@@ -398,6 +419,7 @@ export class CompilerFrontend {
398
419
  const moduleContext = compilerContext(input)
399
420
  if (this.moduleContext !== moduleContext) {
400
421
  this.moduleRecords.clear()
422
+ this.discovery.clear()
401
423
  this.moduleContext = moduleContext
402
424
  }
403
425
 
@@ -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',
@@ -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,12 +1105,9 @@ 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
@@ -1122,7 +1193,10 @@ export function createTamaguiCompilerHost(
1122
1193
  isInput: row.backing === 'textinput',
1123
1194
  // CSS text properties may be authored on any element and inherited by
1124
1195
  // descendant text, including from a View-backed tag on native.
1125
- validStyles: { ...validStylesView, ...stylePropsText },
1196
+ validStyles: {
1197
+ ...validStylesView,
1198
+ ...(row.backing === 'textinput' ? stylePropsInput : stylePropsText),
1199
+ },
1126
1200
  defaultProps: {
1127
1201
  ...base.defaultProps,
1128
1202
  ...platformDefaults,
@@ -1160,29 +1234,31 @@ export function createTamaguiCompilerHost(
1160
1234
  typeof definition.baseClassName.value === 'string'
1161
1235
  ? definition.baseClassName.value
1162
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
1163
1258
  return {
1164
1259
  key: componentKey(definition.id, definition.name),
1165
1260
  displayName: displayName || base.displayName,
1166
- staticConfig: normalizeStaticConfig({
1167
- ...base.staticConfig,
1168
- variants: {
1169
- ...base.staticConfig.variants,
1170
- ...(variants as object | undefined),
1171
- },
1172
- defaultProps: {
1173
- ...base.staticConfig.defaultProps,
1174
- ...defaultProps,
1175
- ...(defaultVariants as object | undefined),
1176
- },
1177
- defaultVariants,
1178
- baseClassName: [base.staticConfig.baseClassName, baseClassName]
1179
- .filter(Boolean)
1180
- .join(' '),
1181
- context: context ?? base.staticConfig.context,
1182
- contextProps: context
1183
- ? contextProps
1184
- : (contextProps ?? base.staticConfig.contextProps),
1185
- }),
1261
+ staticConfig: normalizeStaticConfig(localStaticConfig),
1186
1262
  }
1187
1263
  }
1188
1264
 
@@ -1254,10 +1330,12 @@ export function createTamaguiCompilerHost(
1254
1330
  const staticConfig = component.staticConfig as StaticConfig
1255
1331
  const validStyles =
1256
1332
  staticConfig.validStyles ||
1257
- (staticConfig.isText || staticConfig.isInput ? stylePropsText : validStylesView)
1258
- return (
1259
- name in stylePropsAll && !isValidStyleKey(name, validStyles, staticConfig.accept)
1260
- )
1333
+ (staticConfig.isInput
1334
+ ? stylePropsInput
1335
+ : staticConfig.isText
1336
+ ? stylePropsText
1337
+ : validStylesView)
1338
+ return name in stylePropsAll && !isValidStyleKey(name, validStyles)
1261
1339
  }
1262
1340
 
1263
1341
  const directStyleName = (name: string, component: LoweringComponent): string | null => {
@@ -1292,6 +1370,7 @@ export function createTamaguiCompilerHost(
1292
1370
  firstThemeName,
1293
1371
  componentState,
1294
1372
  {
1373
+ isStatic: true,
1295
1374
  resolveValues: platform === 'native' ? 'except-theme' : 'variable',
1296
1375
  noClass: platform === 'native',
1297
1376
  isAnimated: false,
@@ -1501,6 +1580,12 @@ export function createTamaguiCompilerHost(
1501
1580
  ) {
1502
1581
  return true
1503
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
+ }
1504
1589
  return (
1505
1590
  !options.disablePartialExtraction &&
1506
1591
  ((platform === 'web' && !!directStyleName(name, component)) ||
@@ -1556,15 +1641,56 @@ export function createTamaguiCompilerHost(
1556
1641
  props[entry.name] = entry.value.value
1557
1642
  }
1558
1643
  }
1559
- const disableOptimizationEntry = input.element.entries.find(
1560
- (entry) => entry.kind === 'prop' && entry.name === 'disableOptimization'
1561
- )
1562
- if (disableOptimizationEntry || 'disableOptimization' in props) {
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) {
1563
1689
  return bailout(
1564
1690
  input,
1565
1691
  'local/unsupported-target',
1566
1692
  'disableOptimization keeps the component on the runtime path',
1567
- disableOptimizationEntry?.span
1693
+ disableOptimization.span
1568
1694
  )
1569
1695
  }
1570
1696
  if (component.domTag && props.hidden) props.display = 'none'
@@ -1761,11 +1887,7 @@ export function createTamaguiCompilerHost(
1761
1887
  name === 'opacity'
1762
1888
  ? `opacity: (${expression})`
1763
1889
  : `transform: "scale(" + (${expression}) + ")"`
1764
- } else if (
1765
- entry.value.kind === 'bailout' &&
1766
- owners.size === 1 &&
1767
- owners.has(name)
1768
- ) {
1890
+ } else if (entry.value.kind === 'bailout' && owners.has(name)) {
1769
1891
  const dynamic = entry.value.dynamic
1770
1892
  if (dynamic?.type === 'number') {
1771
1893
  property = `${JSON.stringify(name)}: (${expression})`
@@ -1831,8 +1953,13 @@ export function createTamaguiCompilerHost(
1831
1953
  entry.value.kind === 'conditional' &&
1832
1954
  canLowerConditionalStyleProp(entry.name, component)
1833
1955
  )
1834
- if ('theme' in props || 'themeInverse' in props) {
1835
- 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'
1836
1963
  return bailout(
1837
1964
  input,
1838
1965
  'local/unsupported-target',
@@ -1848,15 +1975,13 @@ export function createTamaguiCompilerHost(
1848
1975
  // the single child and emits no element of its own. a flattened host view
1849
1976
  // would add a wrapper the runtime never renders, and the child would
1850
1977
  // never receive the merged props. both platforms.
1851
- const asChildEntry = input.element.entries.find(
1852
- (entry) => entry.kind === 'prop' && entry.name === 'asChild'
1853
- )
1854
- if (asChildEntry || 'asChild' in props) {
1978
+ const asChild = componentOnlyProp('asChild')
1979
+ if (asChild.unknown || props.asChild) {
1855
1980
  return bailout(
1856
1981
  input,
1857
1982
  'local/unsupported-target',
1858
1983
  'asChild renders a Slot, not a host view',
1859
- asChildEntry?.span
1984
+ asChild.span
1860
1985
  )
1861
1986
  }
1862
1987
  // group and container props publish separate keys in the same context:
@@ -2047,7 +2172,11 @@ export function createTamaguiCompilerHost(
2047
2172
  entry.span
2048
2173
  )
2049
2174
  }
2050
- const staticDefaultProps = component.staticConfig.defaultProps ?? {}
2175
+ const resolvedStyleStaticConfig = core.getStyleStaticConfig(
2176
+ component.staticConfig,
2177
+ core.getConfig()
2178
+ )
2179
+ const staticDefaultProps = resolvedStyleStaticConfig.defaultProps ?? {}
2051
2180
  const defaultProps =
2052
2181
  platform === 'web' &&
2053
2182
  !component.staticConfig.isText &&
@@ -2062,6 +2191,19 @@ export function createTamaguiCompilerHost(
2062
2191
  props
2063
2192
  )
2064
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
+ }
2065
2207
  const propsForConditional = (
2066
2208
  target: Extract<MaterializedElement['entries'][number], { kind: 'prop' }>,
2067
2209
  value: unknown
@@ -2099,22 +2241,35 @@ export function createTamaguiCompilerHost(
2099
2241
  branchProps
2100
2242
  )
2101
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
+ }
2102
2255
  return { branchProps, branchCompleteProps }
2103
2256
  }
2104
- // Against completeProps, not props: clauses also arrive from styled()
2105
- // defaults. Native resolution evaluates them against the build machine's
2106
- // 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.
2107
2260
  if (platform === 'native') {
2108
2261
  const isClauseValue = (name: string, value: unknown) =>
2109
2262
  isStyleProp(name, component) &&
2110
2263
  ((typeof value === 'string' && flatClausePattern.test(value)) ||
2111
2264
  isClauseObjectValue(value))
2112
2265
  // a clause can also sit inside a variant definition, where it never
2113
- // 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.
2114
2269
  const defaultVariants = component.staticConfig.defaultVariants ?? {}
2115
- const carriesClause =
2116
- Object.entries(completeProps).some(([name, value]) =>
2117
- isClauseValue(name, value)
2270
+ const definitionCarriesClause =
2271
+ Object.entries(resolvedStyleStaticConfig.baseStyle ?? {}).some(
2272
+ ([name, value]) => isClauseValue(name, value)
2118
2273
  ) ||
2119
2274
  Object.entries(component.staticConfig.variants ?? {}).some(
2120
2275
  ([variantName, definitions]) =>
@@ -2129,13 +2284,43 @@ export function createTamaguiCompilerHost(
2129
2284
  )
2130
2285
  )
2131
2286
  )
2132
- if (carriesClause) {
2287
+ if (definitionCarriesClause) {
2133
2288
  return bailout(
2134
2289
  input,
2135
2290
  'local/unsupported-target',
2136
2291
  'Native conditional value programs remain on the runtime path'
2137
2292
  )
2138
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
+ }
2139
2324
  }
2140
2325
  const split = resolveSplitStyles(
2141
2326
  completeProps,
@@ -2167,13 +2352,6 @@ export function createTamaguiCompilerHost(
2167
2352
  }
2168
2353
  )
2169
2354
  }
2170
- const domStyleProgram = input.element.entries.find(
2171
- (entry) =>
2172
- entry.kind === 'prop' &&
2173
- entry.name === 'style' &&
2174
- entry.value.kind === 'dom-style'
2175
- )
2176
-
2177
2355
  const flatTag =
2178
2356
  component.domTag ??
2179
2357
  (typeof props.render === 'string'
@@ -2193,7 +2371,9 @@ export function createTamaguiCompilerHost(
2193
2371
  }))
2194
2372
 
2195
2373
  const isPropIgnored = (name: string) =>
2196
- isStyleProp(name, component) || isInvalidHostStyleProp(name, component)
2374
+ isStyleProp(name, component) ||
2375
+ isInvalidHostStyleProp(name, component) ||
2376
+ componentOnlyProps.has(name)
2197
2377
  const spreadReplacement = (
2198
2378
  form: MaterializedElement['form'],
2199
2379
  entry: MaterializedElement['entries'][number]
@@ -2210,16 +2390,11 @@ export function createTamaguiCompilerHost(
2210
2390
 
2211
2391
  let styleEntries = input.element.entries.filter(
2212
2392
  (entry) =>
2213
- (entry.kind === 'prop' &&
2214
- (isStyleProp(entry.name, component) ||
2215
- isInvalidHostStyleProp(entry.name, component))) ||
2393
+ (entry.kind === 'prop' && isPropIgnored(entry.name)) ||
2216
2394
  (entry.kind === 'spread' &&
2217
2395
  entry.value.kind === 'static' &&
2218
2396
  staticObject(entry.value.value) &&
2219
- Object.keys(entry.value.value).some(
2220
- (name) =>
2221
- isStyleProp(name, component) || isInvalidHostStyleProp(name, component)
2222
- ))
2397
+ Object.keys(entry.value.value).some(isPropIgnored))
2223
2398
  )
2224
2399
  let invalidHostStyle:
2225
2400
  | { entry: MaterializedElement['entries'][number]; name: string }
@@ -3361,7 +3536,8 @@ function bailout(
3361
3536
  | 'local/unsupported-child',
3362
3537
  message: string,
3363
3538
  span = input.element.span,
3364
- zero?: { rule: ZeroRule; message?: string }
3539
+ zero?: { rule: ZeroRule; message?: string },
3540
+ prop?: string
3365
3541
  ): LoweringCandidateResult {
3366
3542
  return {
3367
3543
  ok: false,
@@ -3371,6 +3547,7 @@ function bailout(
3371
3547
  message,
3372
3548
  span,
3373
3549
  component: input.element.component.name,
3550
+ ...(prop && { prop }),
3374
3551
  ...(zero && { zeroRule: zero.rule, zeroMessage: zero.message }),
3375
3552
  },
3376
3553
  }