@tamagui/web 1.85.3 → 1.85.4

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 (85) hide show
  1. package/dist/cjs/createComponent.js +86 -111
  2. package/dist/cjs/createComponent.js.map +2 -2
  3. package/dist/cjs/createComponent.native.js +78 -106
  4. package/dist/cjs/createComponent.native.js.map +2 -2
  5. package/dist/cjs/helpers/ThemeManager.js +2 -3
  6. package/dist/cjs/helpers/ThemeManager.js.map +1 -1
  7. package/dist/cjs/helpers/ThemeManager.native.js +2 -3
  8. package/dist/cjs/helpers/ThemeManager.native.js.map +1 -1
  9. package/dist/cjs/helpers/createShallowSetState.js +4 -4
  10. package/dist/cjs/helpers/createShallowSetState.js.map +1 -1
  11. package/dist/cjs/helpers/createShallowSetState.native.js +4 -4
  12. package/dist/cjs/helpers/createShallowSetState.native.js.map +1 -1
  13. package/dist/cjs/helpers/getSplitStyles.js +12 -31
  14. package/dist/cjs/helpers/getSplitStyles.js.map +1 -1
  15. package/dist/cjs/helpers/getSplitStyles.native.js +7 -12
  16. package/dist/cjs/helpers/getSplitStyles.native.js.map +1 -1
  17. package/dist/cjs/helpers/getStylesAtomic.js +5 -11
  18. package/dist/cjs/helpers/getStylesAtomic.js.map +1 -1
  19. package/dist/cjs/helpers/pseudoDescriptors.js +1 -1
  20. package/dist/cjs/helpers/pseudoDescriptors.native.js +1 -1
  21. package/dist/cjs/hooks/useTheme.js +1 -1
  22. package/dist/cjs/hooks/useTheme.js.map +1 -1
  23. package/dist/cjs/hooks/useTheme.native.js +1 -1
  24. package/dist/cjs/hooks/useTheme.native.js.map +1 -1
  25. package/dist/cjs/views/TamaguiProvider.js +6 -6
  26. package/dist/cjs/views/TamaguiProvider.js.map +1 -1
  27. package/dist/cjs/views/TamaguiProvider.native.js +6 -6
  28. package/dist/cjs/views/TamaguiProvider.native.js.map +1 -1
  29. package/dist/cjs/views/Theme.js +1 -1
  30. package/dist/cjs/views/Theme.native.js +1 -1
  31. package/dist/esm/createComponent.js +75 -103
  32. package/dist/esm/createComponent.js.map +2 -2
  33. package/dist/esm/createComponent.native.js +67 -97
  34. package/dist/esm/createComponent.native.js.map +2 -2
  35. package/dist/esm/helpers/ThemeManager.js +2 -3
  36. package/dist/esm/helpers/ThemeManager.js.map +1 -1
  37. package/dist/esm/helpers/ThemeManager.native.js +2 -3
  38. package/dist/esm/helpers/ThemeManager.native.js.map +1 -1
  39. package/dist/esm/helpers/createShallowSetState.js +4 -4
  40. package/dist/esm/helpers/createShallowSetState.js.map +1 -1
  41. package/dist/esm/helpers/createShallowSetState.native.js +4 -4
  42. package/dist/esm/helpers/createShallowSetState.native.js.map +1 -1
  43. package/dist/esm/helpers/getSplitStyles.js +12 -31
  44. package/dist/esm/helpers/getSplitStyles.js.map +1 -1
  45. package/dist/esm/helpers/getSplitStyles.native.js +7 -12
  46. package/dist/esm/helpers/getSplitStyles.native.js.map +1 -1
  47. package/dist/esm/helpers/getStylesAtomic.js +5 -11
  48. package/dist/esm/helpers/getStylesAtomic.js.map +1 -1
  49. package/dist/esm/helpers/pseudoDescriptors.js +1 -1
  50. package/dist/esm/helpers/pseudoDescriptors.native.js +1 -1
  51. package/dist/esm/hooks/useTheme.js +1 -1
  52. package/dist/esm/hooks/useTheme.js.map +1 -1
  53. package/dist/esm/hooks/useTheme.native.js +1 -1
  54. package/dist/esm/hooks/useTheme.native.js.map +1 -1
  55. package/dist/esm/views/TamaguiProvider.js +6 -6
  56. package/dist/esm/views/TamaguiProvider.js.map +1 -1
  57. package/dist/esm/views/TamaguiProvider.native.js +6 -6
  58. package/dist/esm/views/TamaguiProvider.native.js.map +1 -1
  59. package/dist/esm/views/Theme.js +1 -1
  60. package/dist/esm/views/Theme.native.js +1 -1
  61. package/package.json +10 -10
  62. package/src/createComponent.tsx +169 -207
  63. package/src/helpers/ThemeManager.tsx +5 -12
  64. package/src/helpers/createShallowSetState.tsx +19 -4
  65. package/src/helpers/getSplitStyles.tsx +21 -57
  66. package/src/helpers/getStylesAtomic.ts +8 -9
  67. package/src/helpers/pseudoDescriptors.ts +2 -1
  68. package/src/hooks/useTheme.tsx +1 -1
  69. package/src/setupHooks.ts +2 -1
  70. package/src/types.tsx +0 -2
  71. package/src/views/TamaguiProvider.tsx +13 -10
  72. package/src/views/Theme.tsx +1 -1
  73. package/types/createComponent.d.ts.map +1 -1
  74. package/types/helpers/ThemeManager.d.ts.map +1 -1
  75. package/types/helpers/createShallowSetState.d.ts +3 -2
  76. package/types/helpers/createShallowSetState.d.ts.map +1 -1
  77. package/types/helpers/getSplitStyles.d.ts.map +1 -1
  78. package/types/helpers/getStylesAtomic.d.ts.map +1 -1
  79. package/types/helpers/pseudoDescriptors.d.ts +1 -0
  80. package/types/helpers/pseudoDescriptors.d.ts.map +1 -1
  81. package/types/setupHooks.d.ts +1 -1
  82. package/types/setupHooks.d.ts.map +1 -1
  83. package/types/types.d.ts +0 -2
  84. package/types/types.d.ts.map +1 -1
  85. package/types/views/TamaguiProvider.d.ts.map +1 -1
@@ -8,7 +8,6 @@ import React, {
8
8
  createElement,
9
9
  forwardRef,
10
10
  memo,
11
- useCallback,
12
11
  useContext,
13
12
  useEffect,
14
13
  useId,
@@ -33,7 +32,6 @@ import {
33
32
  import { useSplitStyles } from './helpers/getSplitStyles'
34
33
  import { log } from './helpers/log'
35
34
  import { mergeProps } from './helpers/mergeProps'
36
- import { proxyThemeVariables } from './helpers/proxyThemeVariables'
37
35
  import { themeable } from './helpers/themeable'
38
36
  import { mediaKeyMatch, setMediaShouldUpdate, useMedia } from './hooks/useMedia'
39
37
  import { useThemeWithState } from './hooks/useTheme'
@@ -75,7 +73,6 @@ process.env.TAMAGUI_TARGET
75
73
  * All things that need one-time setup after createTamagui is called
76
74
  */
77
75
  let tamaguiConfig: TamaguiInternalConfig
78
- let initialTheme: any
79
76
  let time: any
80
77
 
81
78
  let debugKeyListeners: Set<Function> | undefined
@@ -147,33 +144,20 @@ export function createComponent<
147
144
  Ref = TamaguiElement,
148
145
  BaseProps = never,
149
146
  >(staticConfig: StaticConfig) {
147
+ const { componentName } = staticConfig
148
+
150
149
  let config: TamaguiInternalConfig | null = null
151
150
  let defaultProps = staticConfig.defaultProps
152
151
 
153
152
  onConfiguredOnce((conf) => {
154
153
  config = conf
155
154
 
156
- if (staticConfig.componentName) {
157
- const defaultForComponent = conf.defaultProps?.[staticConfig.componentName]
155
+ if (componentName) {
156
+ const defaultForComponent = conf.defaultProps?.[componentName]
158
157
  if (defaultForComponent) {
159
158
  defaultProps = { ...defaultForComponent, ...defaultProps }
160
159
  }
161
160
  }
162
-
163
- // one time only setup
164
- if (!tamaguiConfig) {
165
- tamaguiConfig = conf
166
-
167
- if (!initialTheme) {
168
- const next = conf.themes[Object.keys(conf.themes)[0]]
169
- initialTheme = proxyThemeVariables(next)
170
- if (process.env.NODE_ENV === 'development') {
171
- if (!initialTheme) {
172
- log('Warning: Missing theme')
173
- }
174
- }
175
- }
176
- }
177
161
  })
178
162
 
179
163
  const {
@@ -185,11 +169,9 @@ export function createComponent<
185
169
  variants = {},
186
170
  } = staticConfig
187
171
 
188
- const defaultComponentClassName = `is_${staticConfig.componentName}`
189
-
190
172
  if (process.env.NODE_ENV === 'development' && staticConfig.defaultProps?.['debug']) {
191
173
  if (process.env.IS_STATIC !== 'is_static') {
192
- log(`🐛 [${staticConfig.componentName || 'Component'}]`, {
174
+ log(`🐛 [${componentName || 'Component'}]`, {
193
175
  staticConfig,
194
176
  defaultProps,
195
177
  defaultPropsKeyOrder: defaultProps ? Object.keys(defaultProps) : [],
@@ -198,6 +180,7 @@ export function createComponent<
198
180
  }
199
181
 
200
182
  const component = forwardRef<Ref, ComponentPropTypes>((propsIn, forwardedRef) => {
183
+ // HOOK
201
184
  const internalID = process.env.NODE_ENV === 'development' ? useId() : ''
202
185
 
203
186
  if (process.env.NODE_ENV === 'development') {
@@ -227,6 +210,7 @@ export function createComponent<
227
210
  }
228
211
  }
229
212
 
213
+ // HOOK
230
214
  const componentContext = useContext(ComponentContext)
231
215
 
232
216
  // set variants through context
@@ -237,6 +221,7 @@ export function createComponent<
237
221
  const { context } = staticConfig
238
222
 
239
223
  if (context) {
224
+ // HOOK 3 (-1 if production)
240
225
  contextValue = useContext(context)
241
226
  const { inverseShorthands } = getConfig()
242
227
  for (const key in context.props) {
@@ -280,6 +265,7 @@ export function createComponent<
280
265
  const componentName = props.componentName || staticConfig.componentName
281
266
 
282
267
  if (process.env.NODE_ENV === 'development' && isClient) {
268
+ // HOOK
283
269
  useEffect(() => {
284
270
  let overlay: HTMLSpanElement | null = null
285
271
 
@@ -339,18 +325,19 @@ export function createComponent<
339
325
  }
340
326
  if (process.env.NODE_ENV === 'development' && time) time`start (ignore)`
341
327
 
342
- const isHydrated = config?.disableSSR ? true : useDidFinishSSR()
343
-
344
328
  if (process.env.NODE_ENV === 'development' && time) time`did-finish-ssr`
345
329
 
346
330
  // conditional but if ever true stays true
347
331
  // [animated, inversed]
332
+ // HOOK
348
333
  const stateRef = useRef(
349
334
  {} as any as {
335
+ willHydrate?: boolean
350
336
  hasMeasured?: boolean
351
337
  hasAnimated?: boolean
352
338
  themeShallow?: boolean
353
339
  isListeningToTheme?: boolean
340
+ unPress?: Function
354
341
  group?: {
355
342
  listeners: Set<GroupStateListener>
356
343
  emit: GroupStateListener
@@ -362,6 +349,7 @@ export function createComponent<
362
349
  if (process.env.NODE_ENV === 'development' && time) time`stateref`
363
350
 
364
351
  // TODO can remove and fold into stateRef
352
+ // HOOK
365
353
  const hostRef = useRef<TamaguiElement>(null)
366
354
 
367
355
  /**
@@ -377,43 +365,99 @@ export function createComponent<
377
365
 
378
366
  // disable for now still ssr issues
379
367
  const supportsCSSVars = animationsConfig?.supportsCSSVars
368
+ const curState = stateRef.current
380
369
 
381
- const willBeAnimated = (() => {
382
- if (isServer && !supportsCSSVars) return false
383
- const curState = stateRef.current
370
+ const willBeAnimatedClient = (() => {
384
371
  const next = !!(hasAnimationProp && !isHOC && useAnimations)
385
372
  return Boolean(next || curState.hasAnimated)
386
373
  })()
387
374
 
388
- const usePresence = animationsConfig?.usePresence
389
- const presence = (willBeAnimated && usePresence?.()) || null
375
+ const willBeAnimated = !isServer && willBeAnimatedClient
376
+
377
+ // once animated, always animated to preserve hooks / vdom structure
378
+ if (willBeAnimated && !curState.hasAnimated) {
379
+ curState.hasAnimated = true
380
+ }
381
+
382
+ // HOOK
383
+ const isHydrated = config?.disableSSR ? true : useDidFinishSSR()
384
+
385
+ // HOOK
386
+ const presence = (willBeAnimated && animationsConfig?.usePresence?.()) || null
387
+ const presenceState = presence?.[2]
388
+ const enterExitVariant = presenceState?.enterExitVariant
389
+ const enterVariant = enterExitVariant ?? presenceState?.enterVariant
390
390
 
391
391
  const hasEnterStyle = !!props.enterStyle
392
- const needsMount = Boolean((isWeb ? isClient : true) && willBeAnimated)
392
+ // finish animated logic, avoid isAnimated when unmounted
393
+ const hasRNAnimation = hasAnimationProp && animationsConfig?.isReactNative
394
+ const isReactNative = staticConfig.isReactNative
395
+
396
+ // only web server + initial client render run this when not hydrated:
397
+ let isAnimated = willBeAnimated
398
+ if (!isReactNative && hasRNAnimation && !isHOC && !isHydrated) {
399
+ isAnimated = false
400
+ curState.willHydrate = true
401
+ }
393
402
 
394
403
  if (process.env.NODE_ENV === 'development' && time) time`pre-use-state`
395
404
 
396
- const initialState = willBeAnimated
397
- ? supportsCSSVars
398
- ? defaultComponentStateShouldEnter!
399
- : defaultComponentState!
400
- : defaultComponentStateMounted!
405
+ const initialState =
406
+ hasEnterStyle || enterVariant
407
+ ? !isHydrated
408
+ ? defaultComponentStateShouldEnter
409
+ : defaultComponentState
410
+ : defaultComponentStateMounted
401
411
 
412
+ // HOOK
402
413
  const states = useState<TamaguiComponentState>(initialState)
403
414
 
404
- const state = propsIn.forceStyle
405
- ? { ...states[0], [propsIn.forceStyle]: true }
415
+ const state = props.forceStyle
416
+ ? { ...states[0], [props.forceStyle]: true }
406
417
  : states[0]
418
+
407
419
  const setState = states[1]
420
+ let setStateShallow = createShallowSetState(setState, debugProp)
421
+
422
+ if (isHydrated && state.unmounted === 'should-enter') {
423
+ state.unmounted = true
424
+ }
425
+
426
+ // set enter/exit variants onto our new props object
427
+ if (presenceState && isAnimated && isHydrated) {
428
+ const isExiting = !presenceState.isPresent
429
+ const exitVariant = enterExitVariant ?? presenceState.exitVariant
430
+
431
+ if (state.unmounted && enterVariant) {
432
+ if (process.env.NODE_ENV === 'development' && debugProp === 'verbose') {
433
+ console.warn(`Animating presence ENTER "${enterVariant}"`)
434
+ }
435
+
436
+ props[enterVariant] = true
437
+ } else if (isExiting && exitVariant) {
438
+ if (process.env.NODE_ENV === 'development' && debugProp === 'verbose') {
439
+ console.warn(`Animating presence EXIT "${enterVariant}"`)
440
+ }
408
441
 
409
- let setStateShallow = createShallowSetState(setState)
442
+ props[exitVariant] = enterExitVariant ? false : true
443
+ }
444
+ }
445
+
446
+ const shouldAvoidClasses = Boolean(
447
+ !isWeb ||
448
+ (isAnimated && !supportsCSSVars) ||
449
+ !staticConfig.acceptsClassName ||
450
+ propsIn.disableClassName
451
+ )
452
+ const shouldForcePseudo = !!propsIn.forceStyle
453
+ const noClassNames = shouldAvoidClasses || shouldForcePseudo
410
454
 
411
455
  const groupName = props.group as any as string
412
456
  const groupClassName = groupName ? `t_group_${props.group}` : ''
413
457
 
414
- if (groupName && !stateRef.current.group) {
458
+ if (groupName && !curState.group) {
415
459
  const listeners = new Set<GroupStateListener>()
416
- stateRef.current.group = {
460
+ curState.group = {
417
461
  listeners,
418
462
  emit(name, state) {
419
463
  listeners.forEach((l) => l(name, state))
@@ -433,7 +477,7 @@ export function createComponent<
433
477
  const og = setStateShallow
434
478
  setStateShallow = (state) => {
435
479
  og(state)
436
- stateRef.current.group!.emit(groupName, {
480
+ curState.group!.emit(groupName, {
437
481
  pseudo: state,
438
482
  })
439
483
  // and mutate the current since its concurrent safe (children throw it in useState on mount)
@@ -447,26 +491,9 @@ export function createComponent<
447
491
 
448
492
  if (process.env.NODE_ENV === 'development' && time) time`use-state`
449
493
 
450
- let isAnimated = willBeAnimated
451
-
452
- if (willBeAnimated && !supportsCSSVars) {
453
- if (!presence && isHydrated) {
454
- if (isServer || state.unmounted === true) {
455
- isAnimated = false
456
- }
457
- }
458
- }
459
-
460
- // once animated, always animated to preserve hooks
461
- if (willBeAnimated && !stateRef.current.hasAnimated) {
462
- stateRef.current.hasAnimated = true
463
- }
464
-
465
- const componentClassName = props.asChild
466
- ? ''
467
- : props.componentName
468
- ? `is_${props.componentName}`
469
- : defaultComponentClassName
494
+ const componentNameFinal = props.componentName || componentName
495
+ const componentClassName =
496
+ props.asChild || !componentNameFinal ? '' : `is_${componentNameFinal}`
470
497
 
471
498
  const hasTextAncestor = !!(isWeb && isText ? componentContext.inText : false)
472
499
  const isDisabled = props.disabled ?? props.accessibilityState?.disabled
@@ -481,45 +508,11 @@ export function createComponent<
481
508
  const BaseViewComponent = BaseView || element || (hasTextAncestor ? 'span' : 'div')
482
509
 
483
510
  let elementType = isText ? BaseTextComponent : BaseViewComponent
484
- if (animationsConfig && willBeAnimated) {
485
- elementType = animationsConfig[isText ? 'Text' : 'View'] || elementType
486
- }
487
511
 
488
- // set enter/exit variants onto our new props object
489
- if (isAnimated && presence) {
490
- const presenceState = presence[2]
491
- if (presenceState) {
492
- const isEntering = state.unmounted
493
- const isExiting = !presenceState.isPresent
494
- const enterExitVariant = presenceState.enterExitVariant
495
- const enterVariant = enterExitVariant ?? presenceState.enterVariant
496
- const exitVariant = enterExitVariant ?? presenceState.exitVariant
497
-
498
- if (isEntering && enterVariant) {
499
- if (process.env.NODE_ENV === 'development' && debugProp === 'verbose') {
500
- console.warn(`Animating presence ENTER "${enterVariant}"`)
501
- }
502
-
503
- props[enterVariant] = true
504
- } else if (isExiting && exitVariant) {
505
- if (process.env.NODE_ENV === 'development' && debugProp === 'verbose') {
506
- console.warn(`Animating presence EXIT "${enterVariant}"`)
507
- }
508
-
509
- props[exitVariant] = enterExitVariant ? false : true
510
- }
511
- }
512
+ if (animationsConfig && isAnimated) {
513
+ elementType = animationsConfig[isText ? 'Text' : 'View'] || elementType
512
514
  }
513
515
 
514
- const isAnimatedReactNative = hasAnimationProp && animationsConfig?.isReactNative
515
- const isReactNative = Boolean(staticConfig.isReactNative || isAnimatedReactNative)
516
- const shouldAvoidClasses = Boolean(
517
- !isWeb || isAnimated || !staticConfig.acceptsClassName || propsIn.disableClassName
518
- )
519
-
520
- const shouldForcePseudo = !!propsIn.forceStyle
521
- const noClassNames = shouldAvoidClasses || shouldForcePseudo
522
-
523
516
  // internal use only
524
517
  const disableThemeProp =
525
518
  process.env.TAMAGUI_TARGET === 'native' ? false : props['data-disable-theme']
@@ -528,19 +521,19 @@ export function createComponent<
528
521
  if (process.env.NODE_ENV === 'development' && time) time`theme-props`
529
522
 
530
523
  if (props.themeShallow) {
531
- stateRef.current.themeShallow = true
524
+ curState.themeShallow = true
532
525
  }
533
526
 
534
527
  const themeStateProps: UseThemeWithStateProps = {
535
528
  name: props.theme,
536
529
  componentName,
537
530
  disable: disableTheme,
538
- shallow: stateRef.current.themeShallow,
531
+ shallow: curState.themeShallow,
539
532
  inverse: props.themeInverse,
540
533
  debug: debugProp,
541
534
  }
542
535
 
543
- if (typeof stateRef.current.isListeningToTheme === 'boolean') {
536
+ if (typeof curState.isListeningToTheme === 'boolean') {
544
537
  themeStateProps.shouldUpdate = () => stateRef.current.isListeningToTheme
545
538
  }
546
539
 
@@ -553,45 +546,35 @@ export function createComponent<
553
546
 
554
547
  if (process.env.NODE_ENV === 'development') {
555
548
  if (debugProp && debugProp !== 'profile') {
556
- // prettier-ignore
557
549
  const name = `${
558
550
  componentName ||
559
551
  Component?.displayName ||
560
552
  Component?.name ||
561
553
  '[Unnamed Component]'
562
554
  }`
563
- const type = isAnimatedReactNative ? '(animated)' : isReactNative ? '(rnw)' : ''
555
+ const type =
556
+ (hasEnterStyle ? '(hasEnter)' : '') +
557
+ (isAnimated ? '(animated)' : '') +
558
+ (isReactNative ? '(rnw)' : '')
564
559
  const dataIs = propsIn['data-is'] || ''
565
- const banner = `${name}${dataIs ? ` ${dataIs}` : ''} ${type} id ${internalID}`
560
+ const banner = `${internalID} ${name}${dataIs ? ` ${dataIs}` : ''} ${type}`
566
561
  console.group(
567
- `%c ${banner} (unmounted: ${state.unmounted})${
568
- presence ? ` (presence: ${presence[0]})` : ''
569
- } ${isHydrated ? '💦' : '🏜️'}`,
562
+ `%c ${banner} (hydrated: ${isHydrated}) (unmounted: ${state.unmounted})`,
570
563
  'background: green; color: white;'
571
564
  )
572
565
  if (!isServer) {
566
+ // if strict mode or something messes with our nesting this fixes:
567
+ console.groupEnd()
568
+ console.groupEnd()
569
+
573
570
  console.groupCollapsed(
574
571
  `Info (collapsed): ${state.press || state.pressIn ? 'PRESSED ' : ''}${
575
572
  state.hover ? 'HOVERED ' : ''
576
573
  }${state.focus ? 'FOCUSED' : ' '}`
577
574
  )
578
-
579
- log({
580
- propsIn,
581
- props,
582
- state,
583
- staticConfig,
584
- elementType,
585
- themeStateProps,
586
- styledContext: { contextProps: styledContextProps, overriddenContextProps },
587
- presence,
588
- isAnimated,
589
- isHOC,
590
- hasAnimationProp,
591
- useAnimations,
592
- propsInOrder: Object.keys(propsIn),
593
- propsOrder: Object.keys(props),
594
- })
575
+ log({ propsIn, props, state, staticConfig, elementType, themeStateProps })
576
+ log({ contextProps: styledContextProps, overriddenContextProps })
577
+ log({ presence, isAnimated, isHOC, hasAnimationProp, useAnimations })
595
578
  console.groupEnd()
596
579
  }
597
580
  }
@@ -599,6 +582,7 @@ export function createComponent<
599
582
 
600
583
  if (process.env.NODE_ENV === 'development' && time) time`pre-theme-media`
601
584
 
585
+ // HOOK 10-13 (-1 if no animation, -1 if disableSSR, -1 if no context, -1 if production)
602
586
  const [themeState, theme] = useThemeWithState(themeStateProps)
603
587
 
604
588
  elementType = Component || elementType
@@ -606,6 +590,7 @@ export function createComponent<
606
590
 
607
591
  if (process.env.NODE_ENV === 'development' && time) time`theme`
608
592
 
593
+ // HOOK 14 (-1 if no animation, -1 if disableSSR, -1 if no context, -1 if production)
609
594
  const mediaState = useMedia(stateRef, componentContext)
610
595
 
611
596
  if (process.env.NODE_ENV === 'development' && time) time`media`
@@ -619,18 +604,15 @@ export function createComponent<
619
604
  ? 'value'
620
605
  : 'auto'
621
606
 
622
- // temp: once we fix above we can disable this
623
- const keepStyleSSR = willBeAnimated && animationsConfig?.keepStyleSSR
624
-
625
607
  const styleProps = {
626
608
  mediaState,
627
609
  noClassNames,
628
610
  resolveValues,
629
611
  isExiting,
630
612
  isAnimated,
631
- keepStyleSSR,
632
613
  } as const
633
614
 
615
+ // HOOK 15 (-1 if no animation, -1 if disableSSR, -1 if no context, -1 if production)
634
616
  const splitStyles = useSplitStyles(
635
617
  props,
636
618
  staticConfig,
@@ -645,13 +627,13 @@ export function createComponent<
645
627
  )
646
628
 
647
629
  // hide strategy will set this opacity = 0 until measured
648
- if (props.group && props.untilMeasured === 'hide' && !stateRef.current.hasMeasured) {
630
+ if (props.group && props.untilMeasured === 'hide' && !curState.hasMeasured) {
649
631
  splitStyles.style.opacity = 0
650
632
  }
651
633
 
652
634
  if (process.env.NODE_ENV === 'development' && time) time`split-styles`
653
635
 
654
- stateRef.current.isListeningToTheme = splitStyles.dynamicThemeAccess
636
+ curState.isListeningToTheme = splitStyles.dynamicThemeAccess
655
637
 
656
638
  // only listen for changes if we are using raw theme values or media space, or dynamic media (native)
657
639
  // array = space media breakpoints
@@ -667,34 +649,6 @@ export function createComponent<
667
649
  keys: mediaListeningKeys,
668
650
  })
669
651
 
670
- // animation setup
671
- const isAnimatedReactNativeWeb = hasAnimationProp && isReactNative
672
-
673
- if (process.env.NODE_ENV === 'development') {
674
- if (!process.env.TAMAGUI_TARGET) {
675
- console.error(
676
- `No process.env.TAMAGUI_TARGET set, please set it to "native" or "web".`
677
- )
678
- }
679
-
680
- if (debugProp && debugProp !== 'profile') {
681
- console.groupCollapsed('>>>')
682
-
683
- log('props in', propsIn, 'mapped to', props, 'in order', Object.keys(props))
684
- log('splitStyles', splitStyles)
685
- log('media', { shouldListenForMedia, isMediaArray, mediaListeningKeys })
686
- log('className', Object.values(splitStyles.classNames))
687
- if (isClient) {
688
- log('ref', hostRef, '(click to view)')
689
- }
690
- console.groupEnd()
691
- if (debugProp === 'break') {
692
- // biome-ignore lint/suspicious/noDebugger: ok
693
- debugger
694
- }
695
- }
696
- }
697
-
698
652
  const {
699
653
  viewProps: viewPropsIn,
700
654
  pseudos,
@@ -708,12 +662,17 @@ export function createComponent<
708
662
  // once you set animation prop don't remove it, you can set to undefined/false
709
663
  // reason is animations are heavy - no way around it, and must be run inline here (🙅 loading as a sub-component)
710
664
  let animationStyles: any
711
- if (willBeAnimated && useAnimations && !isHOC) {
665
+ if (
666
+ // if it supports css vars we run it on server too to get matching initial style
667
+ (supportsCSSVars ? willBeAnimatedClient : willBeAnimated) &&
668
+ useAnimations &&
669
+ !isHOC
670
+ ) {
671
+ // HOOK 16... (depends on driver) (-1 if no animation, -1 if disableSSR, -1 if no context, -1 if production)
712
672
  const animations = useAnimations({
713
673
  props: propsWithAnimation,
714
674
  // if hydrating, send empty style
715
675
  style: splitStylesStyle,
716
- // style: splitStylesStyle,
717
676
  presence,
718
677
  componentState: state,
719
678
  styleProps,
@@ -723,7 +682,7 @@ export function createComponent<
723
682
  staticConfig,
724
683
  })
725
684
 
726
- if (isAnimated && animations) {
685
+ if ((isAnimated || supportsCSSVars) && animations) {
727
686
  animationStyles = animations.style
728
687
  }
729
688
 
@@ -803,13 +762,16 @@ export function createComponent<
803
762
  )
804
763
  }
805
764
 
806
- // if react-native-web view just pass all props down
807
- if (process.env.TAMAGUI_TARGET === 'web' && !isReactNative && !asChild) {
808
- viewProps = hooks.usePropsTransform?.(elementType, nonTamaguiProps, hostRef)
809
- } else {
810
- viewProps = nonTamaguiProps
811
- }
765
+ // HOOKS (0-4 more):
766
+ viewProps =
767
+ hooks.usePropsTransform?.(
768
+ elementType,
769
+ nonTamaguiProps,
770
+ hostRef,
771
+ curState.willHydrate
772
+ ) ?? nonTamaguiProps
812
773
 
774
+ // HOOK (1 more):
813
775
  const composedRef = useComposedRefs(hostRef as any, forwardedRef)
814
776
  viewProps.ref = composedRef
815
777
 
@@ -828,33 +790,24 @@ export function createComponent<
828
790
 
829
791
  if (process.env.NODE_ENV === 'development' && time) time`events-hooks`
830
792
 
831
- let unPress = () =>
832
- setStateShallow({
833
- press: false,
834
- pressIn: false,
835
- })
836
-
837
- if (process.env.TAMAGUI_TARGET === 'web') {
838
- // needs to be referentially stable for web as we add to mouseUps
839
- unPress = useCallback(unPress, [])
840
- }
841
-
842
793
  // combined multiple effects into one for performance so be careful with logic
843
794
  // should not be a layout effect because otherwise it wont render the initial state
844
795
  // for example css driver needs to render once with the first styles, then again with the next
845
796
  // if its a layout effect it will just skip that first <render >output
846
- const shouldSetMounted = needsMount && state.unmounted
847
797
  const { pseudoGroups, mediaGroups } = splitStyles
848
798
 
799
+ // TODO if you add a group prop setStateShallow changes identity...
800
+ if (!curState.unPress) {
801
+ curState.unPress = () => setStateShallow({ press: false, pressIn: false })
802
+ }
803
+
804
+ const unPress = curState.unPress!
805
+ const shouldEnter = state.unmounted
806
+
849
807
  useEffect(() => {
850
- if (shouldSetMounted) {
851
- const unmounted =
852
- state.unmounted === true && hasEnterStyle ? 'should-enter' : false
853
- setStateShallow({
854
- unmounted,
855
- })
808
+ if (shouldEnter) {
809
+ setStateShallow({ unmounted: false })
856
810
  return
857
- // no need for mouseUp removal effect if its not even mounted yet
858
811
  }
859
812
 
860
813
  // parent group pseudo listening
@@ -896,14 +849,11 @@ export function createComponent<
896
849
  mouseUps.delete(unPress)
897
850
  }
898
851
  }, [
899
- shouldSetMounted,
900
- state.unmounted,
852
+ shouldEnter,
901
853
  pseudoGroups ? Object.keys([...pseudoGroups]).join('') : 0,
902
854
  mediaGroups ? Object.keys([...mediaGroups]).join('') : 0,
903
855
  ])
904
856
 
905
- const avoidAnimationStyle = keepStyleSSR && state.unmounted === true
906
-
907
857
  let fontFamily = isText
908
858
  ? splitStyles.fontFamily || staticConfig.defaultProps?.fontFamily
909
859
  : null
@@ -912,9 +862,7 @@ export function createComponent<
912
862
  }
913
863
  const fontFamilyClassName = fontFamily ? `font_${fontFamily}` : ''
914
864
 
915
- const style = avoidAnimationStyle
916
- ? splitStyles.style
917
- : animationStyles || splitStyles.style
865
+ const style = animationStyles || splitStyles.style
918
866
 
919
867
  let className: string | undefined
920
868
 
@@ -931,10 +879,9 @@ export function createComponent<
931
879
 
932
880
  className = classList.join(' ')
933
881
 
934
- if (isAnimatedReactNativeWeb && !avoidAnimationStyle) {
882
+ if (isAnimated && !supportsCSSVars && isReactNative) {
935
883
  viewProps.style = style
936
884
  } else if (isReactNative) {
937
- // TODO these shouldn't really return from getSplitStyles when in Native mode
938
885
  const cnStyles = { $$css: true }
939
886
  for (const name of className.split(' ')) {
940
887
  cnStyles[name] = name
@@ -1168,14 +1115,20 @@ export function createComponent<
1168
1115
 
1169
1116
  // needs to reset the presence state for nested children
1170
1117
  const ResetPresence = config?.animations?.ResetPresence
1171
- if (willBeAnimated && presence && ResetPresence && typeof content !== 'string') {
1118
+ if (
1119
+ willBeAnimated &&
1120
+ hasEnterStyle &&
1121
+ ResetPresence &&
1122
+ content &&
1123
+ typeof content !== 'string'
1124
+ ) {
1172
1125
  content = <ResetPresence>{content}</ResetPresence>
1173
1126
  }
1174
1127
 
1175
1128
  if (process.env.NODE_ENV === 'development' && time) time`create-element`
1176
1129
 
1177
1130
  // must override context so siblings don't clobber initial state
1178
- const groupState = stateRef.current.group
1131
+ const groupState = curState.group
1179
1132
  const subGroupContext = useMemo(() => {
1180
1133
  if (!groupState || !groupName) return
1181
1134
  groupState.listeners.clear()
@@ -1186,7 +1139,7 @@ export function createComponent<
1186
1139
  state: {
1187
1140
  ...componentContext.groups.state,
1188
1141
  [groupName]: {
1189
- pseudo: initialState,
1142
+ pseudo: defaultComponentStateMounted,
1190
1143
  // capture just initial width and height if they exist
1191
1144
  // will have top, left, width, height (not x, y)
1192
1145
  layout: {
@@ -1226,11 +1179,17 @@ export function createComponent<
1226
1179
  }
1227
1180
 
1228
1181
  if (process.env.TAMAGUI_TARGET === 'web') {
1229
- if (isReactNative) {
1182
+ if (isReactNative && !asChild && !isHOC) {
1230
1183
  content = (
1231
1184
  <span
1232
- className={`${isAnimatedReactNativeWeb ? className : ''} _dsp_contents`}
1233
- {...(events && getWebEvents(events))}
1185
+ {...(!isHydrated
1186
+ ? {
1187
+ className: `_dsp_contents`,
1188
+ }
1189
+ : {
1190
+ className: `_dsp_contents`,
1191
+ ...(events && getWebEvents(events)),
1192
+ })}
1234
1193
  >
1235
1194
  {content}
1236
1195
  </span>
@@ -1264,19 +1223,19 @@ export function createComponent<
1264
1223
  console.groupCollapsed(`render <${element} /> (${internalID}) with props`)
1265
1224
  try {
1266
1225
  log('viewProps', viewProps)
1267
- log('viewPropsOrder', Object.keys(viewProps))
1268
- for (const key in viewProps) {
1269
- log(' - ', key, viewProps[key])
1270
- }
1271
1226
  log('children', content)
1272
1227
  if (typeof window !== 'undefined') {
1228
+ log('props in', propsIn, 'mapped to', props, 'in order', Object.keys(props))
1273
1229
  log({
1230
+ shouldListenForMedia,
1231
+ mediaListeningKeys,
1232
+ isMediaArray,
1274
1233
  viewProps,
1234
+ hostRef,
1275
1235
  state,
1276
1236
  styleProps,
1277
1237
  themeState,
1278
1238
  isAnimated,
1279
- isAnimatedReactNativeWeb,
1280
1239
  defaultProps,
1281
1240
  splitStyles,
1282
1241
  animationStyles,
@@ -1303,7 +1262,10 @@ export function createComponent<
1303
1262
  // RN can run into PayloadTooLargeError: request entity too large
1304
1263
  }
1305
1264
  console.groupEnd()
1306
- console.groupEnd()
1265
+ if (debugProp === 'break') {
1266
+ // biome-ignore lint/suspicious/noDebugger: ok
1267
+ debugger
1268
+ }
1307
1269
  }
1308
1270
  }
1309
1271