@tamagui/core 1.0.1-beta.147 → 1.0.1-beta.150

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.
@@ -20,6 +20,7 @@ import React, {
20
20
  memo,
21
21
  useCallback,
22
22
  useContext,
23
+ useEffect,
23
24
  } from 'react'
24
25
  import { StyleSheet, Text, View, ViewStyle } from 'react-native'
25
26
 
@@ -32,12 +33,16 @@ import { assignNativePropsToWeb } from './helpers/assignNativePropsToWeb'
32
33
  import { getReturnVariablesAs } from './helpers/createPropMapper'
33
34
  import { createShallowUpdate } from './helpers/createShallowUpdate'
34
35
  import { extendStaticConfig, parseStaticConfig } from './helpers/extendStaticConfig'
35
- import { SplitStyleResult, insertSplitStyles, useSplitStyles } from './helpers/getSplitStyles'
36
+ import {
37
+ SplitStyleResult,
38
+ getSubStyle,
39
+ insertSplitStyles,
40
+ useSplitStyles,
41
+ } from './helpers/getSplitStyles'
36
42
  import { getAllSelectors } from './helpers/insertStyleRule'
37
43
  import { mergeProps } from './helpers/mergeProps'
38
44
  import { proxyThemeVariables } from './helpers/proxyThemeVariables'
39
- import { useFeatures } from './hooks/useFeatures'
40
- import { mediaState } from './hooks/useMedia'
45
+ import { addMediaQueryListener, mediaState, removeMediaQueryListener } from './hooks/useMedia'
41
46
  import { usePressable } from './hooks/usePressable'
42
47
  import { useServerRef, useServerState } from './hooks/useServerHooks'
43
48
  import { getThemeManager, useTheme } from './hooks/useTheme'
@@ -54,6 +59,7 @@ import {
54
59
  TamaguiElement,
55
60
  TamaguiInternalConfig,
56
61
  UseAnimationHook,
62
+ UseAnimationProps,
57
63
  } from './types'
58
64
  import { usePressability } from './vendor/Pressability'
59
65
  import { Slot, mergeEvent } from './views/Slot'
@@ -92,6 +98,7 @@ function mergeShorthands({ defaultProps }: StaticConfigParsed, { shorthands }: T
92
98
 
93
99
  let initialTheme: any
94
100
  let config: TamaguiInternalConfig
101
+ let mediaPropNames: string[] = []
95
102
 
96
103
  export function createComponent<
97
104
  ComponentPropTypes extends Object = {},
@@ -145,7 +152,6 @@ export function createComponent<
145
152
 
146
153
  const shouldAvoidClasses = !!(props.animation && avoidClasses)
147
154
  const shouldForcePseudo = !!propsIn.forceStyle
148
- // eslint-disable-next-line react-hooks/rules-of-hooks
149
155
  const hasTextAncestor = !!(isWeb && isText ? useContext(TextAncestorContext) : false)
150
156
  const splitStyleState =
151
157
  !shouldAvoidClasses && !shouldForcePseudo
@@ -200,25 +206,114 @@ export function createComponent<
200
206
  }
201
207
  }
202
208
 
203
- const { viewProps: viewPropsIn, pseudos, medias, style, classNames } = splitStyles
209
+ const { viewProps: viewPropsIn, pseudos, medias, style, classNames, mediaKeys } = splitStyles
210
+
211
+ // media queries
212
+ useIsomorphicLayoutEffect(() => {
213
+ for (const key of mediaKeys) {
214
+ addMediaQueryListener(key, forceUpdate)
215
+ }
216
+ return () => {
217
+ for (const key of mediaKeys) {
218
+ removeMediaQueryListener(key, forceUpdate)
219
+ }
220
+ }
221
+ }, [mediaKeys.join(',')])
222
+
223
+ // animations
204
224
  const useAnimations = tamaguiConfig?.animations?.useAnimations as UseAnimationHook | undefined
205
225
  const isAnimated = !!(useAnimations && props.animation)
206
226
  const hasEnterStyle = !!props.enterStyle
207
-
208
227
  const animationFeatureStylesIn = props.animation ? { ...defaultNativeStyle, ...style } : null
228
+ const propsWithAnimation = props as UseAnimationProps
229
+
230
+ // lets make changing the key mandatory for adding/removing animations
231
+ // reason is animations are heavy no way around it, and must be run inline here (🙅 loading as a sub-component)
232
+ // because they need to sync update on first render. it's pretty rare too going to/from no animation => animation
233
+ // so requiring key change on animation change is least-bad option.
234
+ // plus, React use() may let us get away with conditional hooks soon :)
235
+ if (!isRSC && isAnimated) {
236
+ const animationState = useAnimations(propsWithAnimation, {
237
+ state,
238
+ pseudos,
239
+ onDidAnimate: props.onDidAnimate,
240
+ hostRef,
241
+ staticConfig,
242
+ getStyle({ isExiting, isEntering, exitVariant, enterVariant } = {}) {
243
+ // we have to merge such that transforms keys all exist
244
+ const style = animationFeatureStylesIn
245
+
246
+ const enterStyle = isEntering
247
+ ? enterVariant && staticConfig.variants?.[enterVariant]['true']
248
+ ? getSubStyle(
249
+ '',
250
+ staticConfig.variants?.[enterVariant]['true'],
251
+ staticConfig,
252
+ theme,
253
+ props,
254
+ state,
255
+ tamaguiConfig
256
+ ) || pseudos.enterStyle
257
+ : null || pseudos.enterStyle
258
+ : null
259
+
260
+ const exitStyle = isExiting
261
+ ? exitVariant && staticConfig.variants?.[exitVariant]['true']
262
+ ? getSubStyle(
263
+ '',
264
+ staticConfig.variants?.[exitVariant]['true'],
265
+ staticConfig,
266
+ theme,
267
+ props,
268
+ state,
269
+ tamaguiConfig
270
+ ) || pseudos.exitStyle
271
+ : null || pseudos.exitStyle
272
+ : null
273
+
274
+ // if you have hoverStyle={{ scale: 1.1 }} and don't have scale set on the base style
275
+ // no animation will run! this is really confusing. this will look at all variants,
276
+ // and fill in base styles if they don't exist. eg:
277
+ // input:
278
+ // base: {}
279
+ // hoverStyle: { scale: 2 }
280
+ // enterStyle: { x: 100 }
281
+ // output:
282
+ // base: { x: 0, scale: 1 }
283
+ ensureBaseHasDefaults(
284
+ style,
285
+ // enterStyle,
286
+ pseudos.hoverStyle,
287
+ pseudos.focusStyle,
288
+ pseudos.pressStyle,
289
+ pseudos.pressStyle
290
+ )
209
291
 
210
- const features = useFeatures(props, {
211
- forceUpdate,
212
- setStateShallow,
213
- useAnimations,
214
- state,
215
- style: animationFeatureStylesIn,
216
- pseudos,
217
- staticConfig,
218
- theme,
219
- hostRef,
220
- onDidAnimate: props.onDidAnimate,
221
- })
292
+ enterStyle && isEntering && merge(style, enterStyle)
293
+ state.hover && pseudos.hoverStyle && merge(style, pseudos.hoverStyle)
294
+ state.focus && pseudos.focusStyle && merge(style, pseudos.focusStyle)
295
+ state.press && pseudos.pressStyle && merge(style, pseudos.pressStyle)
296
+ exitStyle && isExiting && merge(style, exitStyle)
297
+
298
+ if (process.env.NODE_ENV === 'development') {
299
+ if (props['debug']) {
300
+ // eslint-disable-next-line no-console
301
+ console.log('animation style', style)
302
+ }
303
+ }
304
+
305
+ return style
306
+ },
307
+ //, delay
308
+ })
309
+
310
+ // must be properly memoized
311
+ if (animationState !== state.animation) {
312
+ setStateShallow({
313
+ animation: animationState,
314
+ })
315
+ }
316
+ }
222
317
 
223
318
  const {
224
319
  tag,
@@ -435,7 +530,27 @@ export function createComponent<
435
530
 
436
531
  // from react-native-web
437
532
  const platformMethodsRef = usePlatformMethods(viewProps)
438
- const setRef = useMergeRefs(hostRef, platformMethodsRef, forwardedRef as any)
533
+
534
+ const shouldSetMounted = Boolean(
535
+ isClient && (hasEnterStyle || props.animation) && !state.mounted
536
+ )
537
+
538
+ const setRef = useMergeRefs(
539
+ hostRef,
540
+ platformMethodsRef,
541
+ forwardedRef as any,
542
+ shouldSetMounted
543
+ ? () => {
544
+ // for some reason without some small delay it doesn't animate css
545
+ setTimeout(() => {
546
+ setStateShallow({
547
+ mounted: true,
548
+ })
549
+ }, 10)
550
+ }
551
+ : undefined
552
+ )
553
+
439
554
  viewProps.ref = setRef
440
555
  }
441
556
 
@@ -504,31 +619,14 @@ export function createComponent<
504
619
  })
505
620
  }
506
621
 
507
- // isMounted
508
- const internal = useServerRef<{ isMounted: boolean; unmountEffects?: Function[] }>()
509
- if (!internal.current) {
510
- internal.current = {
511
- isMounted: true,
512
- }
622
+ if (!isRSC) {
623
+ useEffect(() => {
624
+ return () => {
625
+ mouseUps.delete(unPress)
626
+ }
627
+ }, [])
513
628
  }
514
629
 
515
- useIsomorphicLayoutEffect(() => {
516
- // we need to use state to properly have mounted go from false => true
517
- if (isClient && (hasEnterStyle || props.animation) && !state.mounted) {
518
- // for SSR we never set mounted, ensuring enterStyle={{}} is set by default
519
- setStateShallow({
520
- mounted: true,
521
- })
522
- }
523
-
524
- internal.current!.isMounted = true
525
- return () => {
526
- mouseUps.delete(unPress)
527
- internal.current!.isMounted = false
528
- internal.current!.unmountEffects?.forEach((cb) => cb())
529
- }
530
- }, [hasEnterStyle, props.animation])
531
-
532
630
  let styles: any[]
533
631
 
534
632
  const animationStyles = state.animation ? state.animation.style : null
@@ -547,11 +645,13 @@ export function createComponent<
547
645
  ]
548
646
  if (!animationStyles && initialSplitStyles) {
549
647
  const initPseudos = initialSplitStyles.pseudos
550
- const force = shouldForcePseudo
551
648
  !state.mounted && addPseudoToStyles(styles, initPseudos, pseudos, 'enterStyle')
552
- state.hover && addPseudoToStyles(styles, initPseudos, pseudos, 'hoverStyle', force)
553
- state.focus && addPseudoToStyles(styles, initPseudos, pseudos, 'focusStyle', force)
554
- state.press && addPseudoToStyles(styles, initPseudos, pseudos, 'pressStyle', force)
649
+ state.hover &&
650
+ addPseudoToStyles(styles, initPseudos, pseudos, 'hoverStyle', shouldForcePseudo)
651
+ state.focus &&
652
+ addPseudoToStyles(styles, initPseudos, pseudos, 'focusStyle', shouldForcePseudo)
653
+ state.press &&
654
+ addPseudoToStyles(styles, initPseudos, pseudos, 'pressStyle', shouldForcePseudo)
555
655
  }
556
656
  // ugly but for now...
557
657
  if (shouldForcePseudo) {
@@ -649,7 +749,6 @@ export function createComponent<
649
749
  'onMouseLeave' in props)))
650
750
 
651
751
  const unPress = useCallback(() => {
652
- if (!internal.current!.isMounted) return
653
752
  setStateShallow({
654
753
  press: false,
655
754
  pressIn: false,
@@ -740,8 +839,9 @@ export function createComponent<
740
839
  : null
741
840
 
742
841
  let space = spaceProp
842
+
743
843
  // find space by media query
744
- if (features.enabled.mediaQuery) {
844
+ if (mediaKeys.length) {
745
845
  for (const key in mediaState) {
746
846
  if (!mediaState[key]) continue
747
847
  if (props[key] && props[key].space !== undefined) {
@@ -855,7 +955,9 @@ export function createComponent<
855
955
  // only necessary when animating because some AnimatedView which wraps RNW View doesn't forward dataSet className
856
956
  const isAnimatedRNWView = isAnimated && typeof elementType !== 'string' // assuming for now as reanimated is only driver
857
957
  const shouldWrapWithComponentTheme =
858
- isAnimatedRNWView && getReturnVariablesAs(props, splitStyleState) === 'non-color-value'
958
+ !!theme.className &&
959
+ isAnimatedRNWView &&
960
+ getReturnVariablesAs(props, splitStyleState) === 'non-color-value'
859
961
  const shouldWrapWithHover = Boolean(events || isRSC) && attachHover
860
962
 
861
963
  if (shouldWrapWithHover || shouldWrapWithComponentTheme) {
@@ -902,14 +1004,7 @@ export function createComponent<
902
1004
  }
903
1005
  }
904
1006
 
905
- return !isRSC && features.elements.length ? (
906
- <>
907
- {features.elements}
908
- {content}
909
- </>
910
- ) : (
911
- content
912
- )
1007
+ return content
913
1008
  })
914
1009
 
915
1010
  if (staticConfig.componentName) {
@@ -1336,3 +1431,69 @@ function mergeConfigDefaultProps(
1336
1431
  }
1337
1432
  return props
1338
1433
  }
1434
+
1435
+ const defaults = {
1436
+ left: 0,
1437
+ right: 0,
1438
+ top: 0,
1439
+ bottom: 0,
1440
+ opacity: 1,
1441
+ translateX: 0,
1442
+ translateY: 0,
1443
+ skew: 0,
1444
+ skewX: 0,
1445
+ skewY: 0,
1446
+ scale: 1,
1447
+ rotate: '0deg',
1448
+ rotateY: '0deg',
1449
+ rotateX: '0deg',
1450
+ }
1451
+
1452
+ function ensureBaseHasDefaults(base: ViewStyle, ...pseudos: (ViewStyle | null | undefined)[]) {
1453
+ for (const pseudo of pseudos) {
1454
+ if (!pseudo) continue
1455
+ for (const key in pseudo) {
1456
+ const val = pseudo[key]
1457
+ // can we just use merge() here?
1458
+ if (key === 'transform') {
1459
+ if (typeof val === 'string') {
1460
+ continue
1461
+ }
1462
+ for (const t of val) {
1463
+ const tkey = Object.keys(t)[0]
1464
+ const defaultVal = defaults[tkey]
1465
+ const tDefaultVal = { [tkey]: defaultVal } as any
1466
+ if (!base.transform) {
1467
+ base.transform = [tDefaultVal]
1468
+ } else {
1469
+ if (!base.transform.find((x) => x[tkey])) {
1470
+ base.transform.push(tDefaultVal)
1471
+ }
1472
+ }
1473
+ }
1474
+ } else {
1475
+ if (!(key in base)) {
1476
+ base[key] = defaults[key]
1477
+ }
1478
+ }
1479
+ }
1480
+ }
1481
+ }
1482
+
1483
+ function merge(base: ViewStyle, next: ViewStyle) {
1484
+ if (!next.transform || !base.transform) {
1485
+ Object.assign(base, next)
1486
+ return
1487
+ }
1488
+ const { transform, ...rest } = next
1489
+ Object.assign(base, rest)
1490
+ for (const t of transform) {
1491
+ const key = Object.keys(t)[0]
1492
+ const existing = base.transform.find((x) => key in x)
1493
+ if (existing) {
1494
+ existing[key] = t[key]
1495
+ } else {
1496
+ base.transform.push(t)
1497
+ }
1498
+ }
1499
+ }
@@ -78,6 +78,7 @@ type StyleSplitter = (
78
78
  rulesToInsert: RulesToInsert
79
79
  viewProps: StackProps
80
80
  fontFamily: string | undefined
81
+ mediaKeys: string[]
81
82
  }
82
83
 
83
84
  export const PROP_SPLIT = '-'
@@ -108,12 +109,14 @@ export const getSplitStyles: StyleSplitter = (
108
109
  ) => {
109
110
  conf = conf || getConfig()
110
111
  const validStyleProps = staticConfig.isText ? stylePropsText : validStyles
112
+ const mediaKeys: string[] = []
111
113
  const viewProps: StackProps = {}
112
114
  const pseudos: PseudoStyles = {}
113
115
  const medias: Record<MediaQueryKey, ViewStyle> = {}
114
116
  const usedKeys = new Set<string>()
115
117
  const propKeys = Object.keys(props)
116
- const shouldDoClasses = (isWeb || process.env.IS_STATIC === 'is_static') && !state.noClassNames
118
+ const shouldDoClasses =
119
+ (isWeb || process.env.IS_STATIC === 'is_static') && !state.noClassNames && !props.animation
117
120
  const len = propKeys.length
118
121
  const rulesToInsert: RulesToInsert = []
119
122
  const style: ViewStyle = {}
@@ -125,54 +128,7 @@ export const getSplitStyles: StyleSplitter = (
125
128
  let classNames: ClassNamesObject = {}
126
129
  // we need to gather these specific to each media query / pseudo
127
130
  // value is [hash, val], so ["-jnjad-asdnjk", "scaleX(1) rotate(10deg)"]
128
- let transforms = null as Record<TransformNamespaceKey, [string, string]> | null
129
-
130
- function mergeClassName(key: string, val: string, isMediaOrPseudo = false) {
131
- if (process.env.TAMAGUI_TARGET === 'web') {
132
- // empty classnames passed by compiler sometimes
133
- if (!val) return
134
- if (val.startsWith('_transform-')) {
135
- const namespace: TransformNamespaceKey = isMediaOrPseudo ? key : 'transform'
136
-
137
- let transform = insertedTransforms[val]
138
- if (isClient) {
139
- if (!transform) {
140
- // HMR or loaded a new chunk
141
- updateInserted()
142
- transform = insertedTransforms[val]
143
- if (process.env.NODE_ENV === 'development') {
144
- if (!transform) {
145
- // eslint-disable-next-line no-console
146
- console.warn('no transform found', { insertedTransforms, val })
147
- }
148
- }
149
- }
150
- if (!transform) {
151
- if (isWeb && val[0] !== '_') {
152
- // runtime insert
153
- transform = val
154
- }
155
- }
156
- if (process.env.NODE_ENV === 'development' && debug === 'verbose') {
157
- // prettier-ignore
158
- // eslint-disable-next-line no-console
159
- console.log(' 🔹 getSplitStyles mergeClassName transform', { key, val, namespace, transform, insertedTransforms })
160
- }
161
- }
162
- transforms = transforms || {}
163
- transforms[namespace] = transforms[namespace] || ['', '']
164
- const identifier = val.replace('_transform', '')
165
- transforms[namespace][0] += identifier
166
- // ssr doesn't need to do anything just make the right classname
167
- if (transform) {
168
- transforms[namespace][1] += transform
169
- }
170
- } else {
171
- classNames[key] = val
172
- }
173
- }
174
- }
175
-
131
+ const transforms: Record<TransformNamespaceKey, [string, string]> = {}
176
132
  // fontFamily is our special baby, ensure we grab the latest set one always
177
133
  let fontFamily: string | undefined
178
134
 
@@ -237,14 +193,14 @@ export const getSplitStyles: StyleSplitter = (
237
193
  fullKey += `${PROP_SPLIT}${pseudoShortKey}`
238
194
  }
239
195
  usedKeys.add(fullKey)
240
- mergeClassName(fullKey, cn, isMediaOrPseudo)
196
+ mergeClassName(transforms, classNames, fullKey, cn, isMediaOrPseudo)
241
197
  } else {
242
198
  nonTamaguis += ' ' + cn
243
199
  }
244
200
  }
245
201
  if (nonTamaguis) {
246
202
  usedKeys.add(keyInit)
247
- mergeClassName(keyInit, nonTamaguis)
203
+ mergeClassName(transforms, classNames, keyInit, nonTamaguis)
248
204
  }
249
205
  continue
250
206
  }
@@ -259,7 +215,7 @@ export const getSplitStyles: StyleSplitter = (
259
215
  validStyles[keyInit.split(PROP_SPLIT)[0]]
260
216
  if (isValidClassName || isMediaOrPseudo) {
261
217
  usedKeys.add(keyInit)
262
- mergeClassName(keyInit, valInit, isMediaOrPseudo)
218
+ mergeClassName(transforms, classNames, keyInit, valInit, isMediaOrPseudo)
263
219
  continue
264
220
  }
265
221
  }
@@ -350,7 +306,7 @@ export const getSplitStyles: StyleSplitter = (
350
306
  continue
351
307
  }
352
308
  pseudos[key] = pseudos[key] || {}
353
- pseudos[key] = getSubStyle(
309
+ const pseudoStyleObject = getSubStyle(
354
310
  key,
355
311
  val,
356
312
  staticConfig,
@@ -361,15 +317,16 @@ export const getSplitStyles: StyleSplitter = (
361
317
  languageContext,
362
318
  true
363
319
  )
364
- if (shouldDoClasses) {
365
- const pseudoStyles = getAtomicStyle(pseudos[key], pseudoDescriptors[key])
320
+ pseudos[key] = pseudoStyleObject
321
+ if (shouldDoClasses && key !== 'enterStyle' && key !== 'exitStyle') {
322
+ const pseudoStyles = getAtomicStyle(pseudoStyleObject, pseudoDescriptors[key])
366
323
  for (const style of pseudoStyles) {
367
324
  const postfix = pseudoDescriptors[key]?.name || key // exitStyle/enterStyle dont have pseudoDescriptors
368
325
  const fullKey = `${style.property}${PROP_SPLIT}${postfix}`
369
326
  if (!usedKeys.has(fullKey)) {
370
327
  usedKeys.add(fullKey)
371
328
  addStyleToInsertRules(rulesToInsert, style)
372
- mergeClassName(fullKey, style.identifier, isMediaOrPseudo)
329
+ mergeClassName(transforms, classNames, fullKey, style.identifier, isMediaOrPseudo)
373
330
  }
374
331
  }
375
332
  }
@@ -379,6 +336,7 @@ export const getSplitStyles: StyleSplitter = (
379
336
  // media
380
337
  if (isMedia) {
381
338
  const mediaKey = key
339
+ mediaKeys.push(key)
382
340
  const mediaKeyShort = mediaKey.slice(1)
383
341
 
384
342
  if (!mediaQueryConfig[mediaKeyShort]) {
@@ -419,7 +377,7 @@ export const getSplitStyles: StyleSplitter = (
419
377
  if (!usedKeys.has(fullKey)) {
420
378
  usedKeys.add(fullKey)
421
379
  addStyleToInsertRules(rulesToInsert, out)
422
- mergeClassName(fullKey, out.identifier, true)
380
+ mergeClassName(transforms, classNames, fullKey, out.identifier, true)
423
381
  }
424
382
  }
425
383
  } else {
@@ -428,7 +386,14 @@ export const getSplitStyles: StyleSplitter = (
428
386
  // eslint-disable-next-line no-console
429
387
  console.log('apply media style', mediaKey, mediaState)
430
388
  }
431
- Object.assign(shouldDoClasses ? medias : style, mediaStyle)
389
+ if (shouldDoClasses) {
390
+ Object.assign(medias, mediaStyle)
391
+ } else {
392
+ for (const key in mediaStyle) {
393
+ usedKeys.add(key)
394
+ style[key] = mediaStyle[key]
395
+ }
396
+ }
432
397
  }
433
398
  }
434
399
  continue
@@ -447,13 +412,7 @@ export const getSplitStyles: StyleSplitter = (
447
412
  } else if (key in stylePropsTransform) {
448
413
  mergeTransform(style, key, val, true)
449
414
  } else {
450
- // if (key === 'pointerEvents') {
451
- // // pointer events can be box-none which requires css
452
- // addStyleToInsertRules(out.identifier, out.styleRule)
453
- // mergeClassName(fullKey, out.identifier, true)
454
- // } else {
455
415
  style[key] = normalizeValueWithProperty(val, key)
456
- // }
457
416
  }
458
417
  continue
459
418
  }
@@ -489,7 +448,7 @@ export const getSplitStyles: StyleSplitter = (
489
448
  // should probably have a few more exceptions here!
490
449
  if (key === 'pointerEvents' || !state.dynamicStylesInline) {
491
450
  addStyleToInsertRules(rulesToInsert, atomicStyle)
492
- mergeClassName(key, atomicStyle.identifier)
451
+ mergeClassName(transforms, classNames, key, atomicStyle.identifier)
493
452
  } else {
494
453
  style[key] = atomicStyle.value
495
454
  }
@@ -568,12 +527,58 @@ export const getSplitStyles: StyleSplitter = (
568
527
  viewProps,
569
528
  style,
570
529
  medias,
530
+ mediaKeys,
571
531
  pseudos,
572
532
  classNames,
573
533
  rulesToInsert,
574
534
  }
575
535
  }
576
536
 
537
+ function mergeClassName(
538
+ transforms: Record<string, any[]>,
539
+ classNames: Record<string, string>,
540
+ key: string,
541
+ val: string,
542
+ isMediaOrPseudo = false
543
+ ) {
544
+ if (process.env.TAMAGUI_TARGET === 'web') {
545
+ // empty classnames passed by compiler sometimes
546
+ if (!val) return
547
+ if (val.startsWith('_transform-')) {
548
+ const namespace: TransformNamespaceKey = isMediaOrPseudo ? key : 'transform'
549
+ let transform = insertedTransforms[val]
550
+ if (isClient) {
551
+ if (!transform) {
552
+ // HMR or loaded a new chunk
553
+ updateInserted()
554
+ transform = insertedTransforms[val]
555
+ if (process.env.NODE_ENV === 'development') {
556
+ if (!transform) {
557
+ // eslint-disable-next-line no-console
558
+ console.warn('no transform found', { insertedTransforms, val })
559
+ }
560
+ }
561
+ }
562
+ if (!transform) {
563
+ if (isWeb && val[0] !== '_') {
564
+ // runtime insert
565
+ transform = val
566
+ }
567
+ }
568
+ }
569
+ transforms[namespace] = transforms[namespace] || ['', '']
570
+ const identifier = val.replace('_transform', '')
571
+ transforms[namespace][0] += identifier
572
+ // ssr doesn't need to do anything just make the right classname
573
+ if (transform) {
574
+ transforms[namespace][1] += transform
575
+ }
576
+ } else {
577
+ classNames[key] = val
578
+ }
579
+ }
580
+ }
581
+
577
582
  /**
578
583
  * getSubStyle calls propMapper with props
579
584
  * those should be specific to the substyle, but fallback to the base props
@@ -4,10 +4,12 @@ export const mergeTransform = (obj: ViewStyle, key: string, val: any, backwards
4
4
  obj.transform ||= []
5
5
  obj.transform[backwards ? 'unshift' : 'push']({ [mapTransformKeys[key] || key]: val } as any)
6
6
  }
7
+
7
8
  const mapTransformKeys = {
8
9
  x: 'translateX',
9
10
  y: 'translateY',
10
11
  }
12
+
11
13
  export const invertMapTransformKeys = {
12
14
  translateX: 'x',
13
15
  translateY: 'y',
@@ -220,16 +220,13 @@ export const useChangeThemeEffect = (
220
220
  // eslint-disable-next-line react-hooks/rules-of-hooks
221
221
  const forceUpdate = forceUpdateProp || useForceUpdate()
222
222
 
223
- const themeManager = useConstant<ThemeManager | null>(() => {
223
+ const themeManager = useConstant(() => {
224
224
  return new ThemeManager(next.name, next.theme, parentManager, reset)
225
225
  })
226
226
 
227
227
  // if not SSR
228
228
  if (!isSSR) {
229
- // eslint-disable-next-line react-hooks/rules-of-hooks
230
229
  useLayoutEffect(() => {
231
- if (!themeManager) return
232
-
233
230
  activeThemeManagers.add(themeManager)
234
231
 
235
232
  if (next?.name) {
@@ -240,6 +237,9 @@ export const useChangeThemeEffect = (
240
237
  const next = parentManager.getNextTheme(getThemeProps, debug)
241
238
  if (!next) return
242
239
  if (themeManager.update(next)) {
240
+ if (process.env.NODE_ENV === 'development' && debug) {
241
+ console.log('Changed theme', componentName, next, { props })
242
+ }
243
243
  forceUpdate()
244
244
  }
245
245
  })
package/src/types.tsx CHANGED
@@ -1,7 +1,7 @@
1
1
  // TODO split this into own package @tamagui/types to share with animations packages
2
2
 
3
3
  import { Properties } from 'csstype'
4
- import React from 'react'
4
+ import React, { RefObject } from 'react'
5
5
  import {
6
6
  GestureResponderEvent,
7
7
  Image,
@@ -1242,6 +1242,7 @@ export type AnimationDriver<A extends AnimationConfig = AnimationConfig> = {
1242
1242
  export type UseAnimationProps = TamaguiComponentPropsBase & Record<string, any>
1243
1243
 
1244
1244
  export type UseAnimationHelpers = {
1245
+ hostRef: RefObject<HTMLElement | View>
1245
1246
  staticConfig: StaticConfigParsed
1246
1247
  getStyle: (props?: {
1247
1248
  isEntering?: boolean
@@ -1262,6 +1263,6 @@ export type UseAnimationHelpers = {
1262
1263
  export type UseAnimationHook = (
1263
1264
  props: UseAnimationProps,
1264
1265
  helpers: UseAnimationHelpers
1265
- ) => {
1266
+ ) => null | {
1266
1267
  style?: StackStylePropsBase | StackStylePropsBase[]
1267
1268
  }