@tamagui/web 1.129.12 → 1.129.14

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 (184) hide show
  1. package/dist/cjs/contexts/ComponentContext.cjs +1 -6
  2. package/dist/cjs/contexts/ComponentContext.js +1 -6
  3. package/dist/cjs/contexts/ComponentContext.js.map +1 -1
  4. package/dist/cjs/contexts/ComponentContext.native.js +1 -6
  5. package/dist/cjs/contexts/ComponentContext.native.js.map +2 -2
  6. package/dist/cjs/contexts/GroupContext.cjs +27 -0
  7. package/dist/cjs/contexts/GroupContext.js +22 -0
  8. package/dist/cjs/contexts/GroupContext.js.map +6 -0
  9. package/dist/cjs/contexts/GroupContext.native.js +26 -0
  10. package/dist/cjs/contexts/GroupContext.native.js.map +6 -0
  11. package/dist/cjs/createComponent.cjs +130 -89
  12. package/dist/cjs/createComponent.js +139 -93
  13. package/dist/cjs/createComponent.js.map +2 -2
  14. package/dist/cjs/createComponent.native.js +153 -105
  15. package/dist/cjs/createComponent.native.js.map +2 -2
  16. package/dist/cjs/helpers/createStyledContext.js.map +1 -1
  17. package/dist/cjs/helpers/createStyledContext.native.js.map +1 -1
  18. package/dist/cjs/helpers/getSplitStyles.cjs +68 -49
  19. package/dist/cjs/helpers/getSplitStyles.js +64 -55
  20. package/dist/cjs/helpers/getSplitStyles.js.map +2 -2
  21. package/dist/cjs/helpers/getSplitStyles.native.js +82 -63
  22. package/dist/cjs/helpers/getSplitStyles.native.js.map +2 -2
  23. package/dist/cjs/helpers/pseudoDescriptors.cjs +12 -12
  24. package/dist/cjs/helpers/pseudoDescriptors.js +12 -12
  25. package/dist/cjs/helpers/pseudoDescriptors.js.map +1 -1
  26. package/dist/cjs/helpers/pseudoDescriptors.native.js +12 -12
  27. package/dist/cjs/helpers/pseudoDescriptors.native.js.map +1 -1
  28. package/dist/cjs/helpers/subscribeToContextGroup.cjs +48 -31
  29. package/dist/cjs/helpers/subscribeToContextGroup.js +36 -20
  30. package/dist/cjs/helpers/subscribeToContextGroup.js.map +1 -1
  31. package/dist/cjs/helpers/subscribeToContextGroup.native.js +48 -20
  32. package/dist/cjs/helpers/subscribeToContextGroup.native.js.map +2 -2
  33. package/dist/cjs/hooks/useComponentState.cjs +1 -40
  34. package/dist/cjs/hooks/useComponentState.js +1 -30
  35. package/dist/cjs/hooks/useComponentState.js.map +2 -2
  36. package/dist/cjs/hooks/useComponentState.native.js +3 -45
  37. package/dist/cjs/hooks/useComponentState.native.js.map +2 -2
  38. package/dist/cjs/hooks/useConfiguration.cjs +1 -17
  39. package/dist/cjs/hooks/useConfiguration.js +2 -9
  40. package/dist/cjs/hooks/useConfiguration.js.map +1 -1
  41. package/dist/cjs/hooks/useConfiguration.native.js +2 -7
  42. package/dist/cjs/hooks/useConfiguration.native.js.map +2 -2
  43. package/dist/cjs/hooks/useMedia.cjs +4 -3
  44. package/dist/cjs/hooks/useMedia.js +3 -3
  45. package/dist/cjs/hooks/useMedia.js.map +1 -1
  46. package/dist/cjs/hooks/useMedia.native.js +3 -3
  47. package/dist/cjs/hooks/useMedia.native.js.map +2 -2
  48. package/dist/cjs/hooks/useProps.cjs +7 -6
  49. package/dist/cjs/hooks/useProps.js +13 -11
  50. package/dist/cjs/hooks/useProps.js.map +1 -1
  51. package/dist/cjs/hooks/useProps.native.js +11 -10
  52. package/dist/cjs/hooks/useProps.native.js.map +2 -2
  53. package/dist/cjs/hooks/useThemeState.cjs +2 -2
  54. package/dist/cjs/hooks/useThemeState.js +2 -2
  55. package/dist/cjs/hooks/useThemeState.js.map +1 -1
  56. package/dist/cjs/hooks/useThemeState.native.js +2 -2
  57. package/dist/cjs/hooks/useThemeState.native.js.map +1 -1
  58. package/dist/cjs/index.cjs +1 -0
  59. package/dist/cjs/index.js +1 -0
  60. package/dist/cjs/index.js.map +1 -1
  61. package/dist/cjs/index.native.js +2 -0
  62. package/dist/cjs/index.native.js.map +1 -1
  63. package/dist/cjs/views/TamaguiProvider.js.map +1 -1
  64. package/dist/cjs/views/TamaguiProvider.native.js.map +1 -1
  65. package/dist/esm/contexts/ComponentContext.js +1 -6
  66. package/dist/esm/contexts/ComponentContext.js.map +1 -1
  67. package/dist/esm/contexts/ComponentContext.mjs +1 -6
  68. package/dist/esm/contexts/ComponentContext.mjs.map +1 -1
  69. package/dist/esm/contexts/ComponentContext.native.js +1 -6
  70. package/dist/esm/contexts/ComponentContext.native.js.map +1 -1
  71. package/dist/esm/contexts/GroupContext.js +6 -0
  72. package/dist/esm/contexts/GroupContext.js.map +6 -0
  73. package/dist/esm/contexts/GroupContext.mjs +4 -0
  74. package/dist/esm/contexts/GroupContext.mjs.map +1 -0
  75. package/dist/esm/contexts/GroupContext.native.js +4 -0
  76. package/dist/esm/contexts/GroupContext.native.js.map +1 -0
  77. package/dist/esm/createComponent.js +140 -92
  78. package/dist/esm/createComponent.js.map +2 -2
  79. package/dist/esm/createComponent.mjs +130 -89
  80. package/dist/esm/createComponent.mjs.map +1 -1
  81. package/dist/esm/createComponent.native.js +148 -101
  82. package/dist/esm/createComponent.native.js.map +1 -1
  83. package/dist/esm/helpers/createStyledContext.js.map +1 -1
  84. package/dist/esm/helpers/createStyledContext.mjs.map +1 -1
  85. package/dist/esm/helpers/createStyledContext.native.js.map +1 -1
  86. package/dist/esm/helpers/getSplitStyles.js +68 -56
  87. package/dist/esm/helpers/getSplitStyles.js.map +2 -2
  88. package/dist/esm/helpers/getSplitStyles.mjs +68 -49
  89. package/dist/esm/helpers/getSplitStyles.mjs.map +1 -1
  90. package/dist/esm/helpers/getSplitStyles.native.js +72 -53
  91. package/dist/esm/helpers/getSplitStyles.native.js.map +1 -1
  92. package/dist/esm/helpers/pseudoDescriptors.js +12 -12
  93. package/dist/esm/helpers/pseudoDescriptors.js.map +1 -1
  94. package/dist/esm/helpers/pseudoDescriptors.mjs +12 -12
  95. package/dist/esm/helpers/pseudoDescriptors.mjs.map +1 -1
  96. package/dist/esm/helpers/pseudoDescriptors.native.js +12 -12
  97. package/dist/esm/helpers/pseudoDescriptors.native.js.map +1 -1
  98. package/dist/esm/helpers/subscribeToContextGroup.js +36 -20
  99. package/dist/esm/helpers/subscribeToContextGroup.js.map +1 -1
  100. package/dist/esm/helpers/subscribeToContextGroup.mjs +47 -30
  101. package/dist/esm/helpers/subscribeToContextGroup.mjs.map +1 -1
  102. package/dist/esm/helpers/subscribeToContextGroup.native.js +64 -36
  103. package/dist/esm/helpers/subscribeToContextGroup.native.js.map +1 -1
  104. package/dist/esm/hooks/useComponentState.js +1 -30
  105. package/dist/esm/hooks/useComponentState.js.map +2 -2
  106. package/dist/esm/hooks/useComponentState.mjs +1 -40
  107. package/dist/esm/hooks/useComponentState.mjs.map +1 -1
  108. package/dist/esm/hooks/useComponentState.native.js +4 -48
  109. package/dist/esm/hooks/useComponentState.native.js.map +1 -1
  110. package/dist/esm/hooks/useConfiguration.js +1 -9
  111. package/dist/esm/hooks/useConfiguration.js.map +1 -1
  112. package/dist/esm/hooks/useConfiguration.mjs +1 -17
  113. package/dist/esm/hooks/useConfiguration.mjs.map +1 -1
  114. package/dist/esm/hooks/useConfiguration.native.js +1 -15
  115. package/dist/esm/hooks/useConfiguration.native.js.map +1 -1
  116. package/dist/esm/hooks/useMedia.js +3 -3
  117. package/dist/esm/hooks/useMedia.js.map +1 -1
  118. package/dist/esm/hooks/useMedia.mjs +4 -3
  119. package/dist/esm/hooks/useMedia.mjs.map +1 -1
  120. package/dist/esm/hooks/useMedia.native.js +4 -3
  121. package/dist/esm/hooks/useMedia.native.js.map +1 -1
  122. package/dist/esm/hooks/useProps.js +13 -10
  123. package/dist/esm/hooks/useProps.js.map +1 -1
  124. package/dist/esm/hooks/useProps.mjs +7 -6
  125. package/dist/esm/hooks/useProps.mjs.map +1 -1
  126. package/dist/esm/hooks/useProps.native.js +7 -6
  127. package/dist/esm/hooks/useProps.native.js.map +1 -1
  128. package/dist/esm/hooks/useThemeState.js +2 -2
  129. package/dist/esm/hooks/useThemeState.js.map +1 -1
  130. package/dist/esm/hooks/useThemeState.mjs +2 -2
  131. package/dist/esm/hooks/useThemeState.mjs.map +1 -1
  132. package/dist/esm/hooks/useThemeState.native.js +2 -2
  133. package/dist/esm/hooks/useThemeState.native.js.map +1 -1
  134. package/dist/esm/index.js +1 -0
  135. package/dist/esm/index.js.map +1 -1
  136. package/dist/esm/index.mjs +1 -0
  137. package/dist/esm/index.mjs.map +1 -1
  138. package/dist/esm/index.native.js +1 -0
  139. package/dist/esm/index.native.js.map +1 -1
  140. package/dist/esm/views/TamaguiProvider.js.map +1 -1
  141. package/dist/esm/views/TamaguiProvider.mjs.map +1 -1
  142. package/dist/esm/views/TamaguiProvider.native.js.map +1 -1
  143. package/package.json +12 -12
  144. package/src/contexts/ComponentContext.tsx +0 -5
  145. package/src/contexts/GroupContext.tsx +4 -0
  146. package/src/createComponent.tsx +247 -108
  147. package/src/helpers/createStyledContext.tsx +1 -1
  148. package/src/helpers/getSplitStyles.tsx +77 -56
  149. package/src/helpers/pseudoDescriptors.ts +15 -19
  150. package/src/helpers/subscribeToContextGroup.tsx +70 -34
  151. package/src/hooks/useComponentState.ts +5 -49
  152. package/src/hooks/useConfiguration.tsx +1 -9
  153. package/src/hooks/useMedia.tsx +4 -2
  154. package/src/hooks/useProps.tsx +15 -10
  155. package/src/hooks/useThemeState.ts +2 -2
  156. package/src/index.ts +1 -0
  157. package/src/interfaces/TamaguiComponentState.tsx +4 -3
  158. package/src/types.tsx +32 -36
  159. package/src/views/TamaguiProvider.tsx +1 -0
  160. package/types/contexts/ComponentContext.d.ts.map +1 -1
  161. package/types/contexts/GroupContext.d.ts +3 -0
  162. package/types/contexts/GroupContext.d.ts.map +1 -0
  163. package/types/createComponent.d.ts +1 -1
  164. package/types/createComponent.d.ts.map +1 -1
  165. package/types/helpers/getSplitStyles.d.ts +2 -2
  166. package/types/helpers/getSplitStyles.d.ts.map +1 -1
  167. package/types/helpers/pseudoDescriptors.d.ts +14 -13
  168. package/types/helpers/pseudoDescriptors.d.ts.map +1 -1
  169. package/types/helpers/subscribeToContextGroup.d.ts +7 -6
  170. package/types/helpers/subscribeToContextGroup.d.ts.map +1 -1
  171. package/types/hooks/useComponentState.d.ts +3 -3
  172. package/types/hooks/useComponentState.d.ts.map +1 -1
  173. package/types/hooks/useConfiguration.d.ts +1 -121
  174. package/types/hooks/useConfiguration.d.ts.map +1 -1
  175. package/types/hooks/useMedia.d.ts +2 -2
  176. package/types/hooks/useMedia.d.ts.map +1 -1
  177. package/types/hooks/useProps.d.ts.map +1 -1
  178. package/types/index.d.ts +1 -0
  179. package/types/index.d.ts.map +1 -1
  180. package/types/interfaces/TamaguiComponentState.d.ts +2 -2
  181. package/types/interfaces/TamaguiComponentState.d.ts.map +1 -1
  182. package/types/types.d.ts +21 -33
  183. package/types/types.d.ts.map +1 -1
  184. package/types/views/TamaguiProvider.d.ts.map +1 -1
@@ -8,11 +8,13 @@ import {
8
8
  useIsomorphicLayoutEffect,
9
9
  } from '@tamagui/constants'
10
10
  import { composeEventHandlers, validStyles } from '@tamagui/helpers'
11
- import React, { useMemo } from 'react'
11
+ import { isEqualShallow } from '@tamagui/is-equal-shallow'
12
+ import React, { type RefObject, useEffect, useId, useMemo } from 'react'
12
13
  import { devConfig, onConfiguredOnce } from './config'
13
14
  import { stackDefaultStyles } from './constants/constants'
14
15
  import { isDevTools } from './constants/isDevTools'
15
16
  import { ComponentContext } from './contexts/ComponentContext'
17
+ import { GroupContext } from './contexts/GroupContext'
16
18
  import { didGetVariableValue, setDidGetVariableValue } from './createVariable'
17
19
  import { defaultComponentStateMounted } from './defaultComponentState'
18
20
  import { getShorthandValue } from './helpers/getShorthandValue'
@@ -31,19 +33,24 @@ import type { TamaguiComponentState } from './interfaces/TamaguiComponentState'
31
33
  import type { WebOnlyPressEvents } from './interfaces/WebOnlyPressEvents'
32
34
  import { hooks } from './setupHooks'
33
35
  import type {
34
- ComponentContextI,
36
+ ComponentGroupEmitter,
37
+ ComponentGroupState,
35
38
  DebugProp,
39
+ AllGroupContexts,
40
+ GroupStateListener,
36
41
  LayoutEvent,
42
+ PseudoGroupState,
37
43
  SizeTokens,
38
44
  SpaceDirection,
39
- SpaceValue,
40
45
  SpacerProps,
41
46
  SpacerStyleProps,
47
+ SpaceValue,
42
48
  StackNonStyleProps,
43
49
  StackProps,
44
50
  StaticConfig,
45
51
  StyleableOptions,
46
52
  TamaguiComponent,
53
+ TamaguiComponentStateRef,
47
54
  TamaguiElement,
48
55
  TamaguiInternalConfig,
49
56
  TextProps,
@@ -51,6 +58,7 @@ import type {
51
58
  UseAnimationProps,
52
59
  UseStyleEmitter,
53
60
  UseThemeWithStateProps,
61
+ SingleGroupContext,
54
62
  } from './types'
55
63
  import { Slot } from './views/Slot'
56
64
  import { getThemedChildren } from './views/Theme'
@@ -59,6 +67,7 @@ import { getThemedChildren } from './views/Theme'
59
67
  * All things that need one-time setup after createTamagui is called
60
68
  */
61
69
  let time: any
70
+ const NextState = new WeakMap<any, TamaguiComponentState | undefined>()
62
71
 
63
72
  let debugKeyListeners: Set<Function> | undefined
64
73
  let startVisualizer: Function | undefined
@@ -66,6 +75,16 @@ let startVisualizer: Function | undefined
66
75
  type ComponentSetState = React.Dispatch<React.SetStateAction<TamaguiComponentState>>
67
76
 
68
77
  export const componentSetStates = new Set<ComponentSetState>()
78
+ const avoidReRenderKeys = new Set([
79
+ 'hover',
80
+ 'press',
81
+ 'pressIn',
82
+ 'group',
83
+ 'focus',
84
+ 'focusWithin',
85
+ 'media',
86
+ 'group',
87
+ ])
69
88
 
70
89
  if (process.env.TAMAGUI_TARGET !== 'native' && typeof window !== 'undefined') {
71
90
  const cancelTouches = () => {
@@ -92,7 +111,10 @@ if (process.env.TAMAGUI_TARGET !== 'native' && typeof window !== 'undefined') {
92
111
  if (process.env.NODE_ENV === 'development') {
93
112
  startVisualizer = () => {
94
113
  const devVisualizerConfig = devConfig?.visualizer
95
- if (devVisualizerConfig) {
114
+
115
+ if (devVisualizerConfig && !globalThis.__tamaguiDevVisualizer) {
116
+ globalThis.__tamaguiDevVisualizer = true
117
+
96
118
  debugKeyListeners = new Set()
97
119
  let tm
98
120
  let isShowing = false
@@ -144,15 +166,21 @@ let BaseView: any
144
166
  let hasSetupBaseViews = false
145
167
 
146
168
  const lastInteractionWasKeyboard = { value: false }
147
- if (isWeb && globalThis['document']) {
169
+ if (isWeb && typeof document !== 'undefined') {
148
170
  document.addEventListener('keydown', () => {
149
- lastInteractionWasKeyboard.value = true
171
+ if (!lastInteractionWasKeyboard.value) {
172
+ lastInteractionWasKeyboard.value = true
173
+ }
150
174
  })
151
175
  document.addEventListener('mousedown', () => {
152
- lastInteractionWasKeyboard.value = false
176
+ if (lastInteractionWasKeyboard.value) {
177
+ lastInteractionWasKeyboard.value = false
178
+ }
153
179
  })
154
180
  document.addEventListener('mousemove', () => {
155
- lastInteractionWasKeyboard.value = false
181
+ if (lastInteractionWasKeyboard.value) {
182
+ lastInteractionWasKeyboard.value = false
183
+ }
156
184
  })
157
185
  }
158
186
 
@@ -222,8 +250,6 @@ export function createComponent<
222
250
  }
223
251
  }
224
252
 
225
- const componentContext = React.useContext(ComponentContext)
226
-
227
253
  // set variants through context
228
254
  // order is after default props but before props
229
255
  let styledContextProps: Object | undefined
@@ -294,7 +320,7 @@ export function createComponent<
294
320
  let overlay: HTMLSpanElement | null = null
295
321
 
296
322
  const debugVisualizerHandler = (show = false) => {
297
- const node = curStateRef.host as HTMLElement
323
+ const node = stateRef.current.host as HTMLElement
298
324
  if (!node) return
299
325
 
300
326
  if (show) {
@@ -338,21 +364,19 @@ export function createComponent<
338
364
  }, [componentName])
339
365
  }
340
366
 
341
- /**
342
- * Component state for tracking animations, pseudos
343
- */
367
+ const componentContext = React.useContext(ComponentContext)
368
+ const groupContextParent = React.useContext(GroupContext)
344
369
  const animationDriver = componentContext.animationDriver
345
370
  const useAnimations = animationDriver?.useAnimations as UseAnimationHook | undefined
346
371
 
347
372
  const componentState = useComponentState(
348
373
  props,
349
- componentContext,
374
+ animationDriver,
350
375
  staticConfig,
351
376
  config!
352
377
  )
353
378
 
354
379
  const {
355
- curStateRef,
356
380
  disabled,
357
381
  groupName,
358
382
  hasAnimationProp,
@@ -372,6 +396,60 @@ export function createComponent<
372
396
  startedUnhydrated,
373
397
  } = componentState
374
398
 
399
+ if (hasAnimationProp && animationDriver?.avoidReRenders) {
400
+ useIsomorphicLayoutEffect(() => {
401
+ const pendingState = NextState.get(stateRef)
402
+ if (pendingState) {
403
+ setStateShallow(pendingState)
404
+ NextState.set(stateRef, undefined)
405
+ }
406
+ })
407
+ }
408
+
409
+ // create new context with groups, or else sublings will grab the same one
410
+ const allGroupContexts = useMemo((): AllGroupContexts | null => {
411
+ if (!groupName) {
412
+ return groupContextParent
413
+ }
414
+
415
+ // TODO this shouldn't be in useMemo
416
+ stateRef.current.group?.listeners.clear()
417
+ const listeners = new Set<GroupStateListener>()
418
+ stateRef.current.group = {
419
+ listeners,
420
+ emit(state) {
421
+ listeners.forEach((l) => l(state))
422
+ },
423
+ subscribe(cb) {
424
+ listeners.add(cb)
425
+ if (listeners.size === 1) {
426
+ setStateShallow({ hasDynGroupChildren: true })
427
+ }
428
+ return () => {
429
+ listeners.delete(cb)
430
+ if (listeners.size === 0) {
431
+ setStateShallow({ hasDynGroupChildren: false })
432
+ }
433
+ }
434
+ },
435
+ }
436
+
437
+ return {
438
+ ...groupContextParent,
439
+ [groupName]: {
440
+ state: {
441
+ pseudo: defaultComponentStateMounted,
442
+ },
443
+ subscribe: (listener) => {
444
+ const dispose = stateRef.current.group?.subscribe(listener)
445
+ return () => {
446
+ dispose?.()
447
+ }
448
+ },
449
+ },
450
+ }
451
+ }, [stateRef, groupName, groupContextParent])
452
+
375
453
  // if our animation driver supports noReRender, we'll replace this below with
376
454
  // a version that essentially uses an internall emitter rather than setting state
377
455
  // but still stores the current state and applies if it it needs to during render
@@ -412,13 +490,13 @@ export function createComponent<
412
490
  if (process.env.NODE_ENV === 'development' && time) time`theme-props`
413
491
 
414
492
  if (props.themeShallow) {
415
- curStateRef.themeShallow = true
493
+ stateRef.current.themeShallow = true
416
494
  }
417
495
 
418
496
  const themeStateProps: UseThemeWithStateProps = {
419
497
  componentName,
420
498
  disable: disableTheme,
421
- shallow: curStateRef.themeShallow,
499
+ shallow: stateRef.current.themeShallow,
422
500
  debug: debugProp,
423
501
  }
424
502
 
@@ -430,7 +508,7 @@ export function createComponent<
430
508
  if ('theme' in props) {
431
509
  themeStateProps.name = props.theme
432
510
  }
433
- if (typeof curStateRef.isListeningToTheme === 'boolean') {
511
+ if (typeof stateRef.current.isListeningToTheme === 'boolean') {
434
512
  themeStateProps.needsUpdate = () => !!stateRef.current.isListeningToTheme
435
513
  }
436
514
  // on native we optimize theme changes if fastSchemeChange is enabled, otherwise deopt
@@ -482,7 +560,11 @@ export function createComponent<
482
560
  log('props in:', propsIn)
483
561
  log('final props:', props)
484
562
  log({ state, staticConfig, elementType, themeStateProps })
485
- log({ contextProps: styledContextProps, overriddenContextProps })
563
+ log({
564
+ contextProps: styledContextProps,
565
+ overriddenContextProps,
566
+ componentContext,
567
+ })
486
568
  log({ presence, isAnimated, isHOC, hasAnimationProp, useAnimations })
487
569
  console.groupEnd()
488
570
  }
@@ -535,26 +617,87 @@ export function createComponent<
535
617
  styleProps,
536
618
  null,
537
619
  componentContext,
620
+ allGroupContexts,
538
621
  elementType,
539
622
  startedUnhydrated,
540
623
  debugProp
541
624
  )
542
625
 
626
+ const groupContext = groupName ? allGroupContexts?.[groupName] || null : null
627
+
628
+ // one tiny mutation 🙏 get width/height optimistically from raw values if possible
629
+ // if set hardcoded it avoids extra renders
630
+ if (groupContext) {
631
+ const groupState = groupContext?.state
632
+ if (groupState && groupState.layout === undefined) {
633
+ if (splitStyles.style?.width || splitStyles.style?.height) {
634
+ groupState.layout = {
635
+ width: fromPx(splitStyles.style.width),
636
+ height: fromPx(splitStyles.style.height),
637
+ }
638
+ }
639
+ }
640
+ }
641
+
543
642
  // avoids re-rendering if animation driver supports it
544
643
  // TODO believe we need to set some sort of "pendingState" in case it re-renders
545
- if (hasAnimationProp && animationDriver?.avoidReRenders) {
644
+ if ((hasAnimationProp || groupName) && animationDriver?.avoidReRenders) {
546
645
  const styleListener = stateRef.current.useStyleListener
547
646
  const ogSetStateShallow = setStateShallow
548
- setStateShallow = (next) => {
647
+
648
+ stateRef.current.setStateShallow = (nextOrGetNext) => {
649
+ const prev = NextState.get(stateRef) || state
650
+ const next =
651
+ typeof nextOrGetNext === 'function' ? nextOrGetNext(prev) : nextOrGetNext
652
+
653
+ if (next === prev || isEqualShallow(prev, next)) {
654
+ return
655
+ }
656
+
549
657
  // one thing we have to handle here and where it gets a bit more complex is group styles
550
- // but i think we can just emit to the group too?
551
- const avoidReRenderKeys = new Set(['hover', 'press', 'pressIn'])
552
658
  const canAvoidReRender = Object.keys(next).every((key) =>
553
659
  avoidReRenderKeys.has(key)
554
660
  )
555
- if (canAvoidReRender && styleListener) {
556
- const updatedState = { ...state, ...next }
557
- stateRef.current.nextComponentState = updatedState
661
+
662
+ if (canAvoidReRender) {
663
+ const updatedState = {
664
+ ...prev,
665
+ ...next,
666
+ }
667
+ NextState.set(stateRef, updatedState)
668
+
669
+ if (
670
+ process.env.NODE_ENV === 'development' &&
671
+ debugProp &&
672
+ debugProp !== 'profile'
673
+ ) {
674
+ console.groupCollapsed(`[⚡️] avoid setState`, next, { updatedState, props })
675
+ console.info(stateRef.current.host)
676
+ console.groupEnd()
677
+ }
678
+
679
+ const {
680
+ group,
681
+ hasDynGroupChildren,
682
+ unmounted,
683
+ animation,
684
+ ...childrenGroupState
685
+ } = updatedState
686
+
687
+ // update before getting styles
688
+ if (groupContext) {
689
+ notifyGroupSubscribers(
690
+ groupContext,
691
+ stateRef.current.group || null,
692
+ childrenGroupState
693
+ )
694
+ }
695
+
696
+ // we just emit and return for group without animation ^
697
+ if (!hasAnimationProp || !styleListener) {
698
+ return
699
+ }
700
+
558
701
  const nextStyles = getSplitStyles(
559
702
  props,
560
703
  staticConfig,
@@ -564,26 +707,42 @@ export function createComponent<
564
707
  styleProps,
565
708
  null,
566
709
  componentContext,
710
+ allGroupContexts,
567
711
  elementType,
568
712
  startedUnhydrated,
569
713
  debugProp
570
714
  )
715
+
571
716
  styleListener(nextStyles.style as any)
572
717
  } else {
718
+ if (
719
+ process.env.NODE_ENV === 'development' &&
720
+ debugProp &&
721
+ debugProp !== 'profile'
722
+ ) {
723
+ console.info(`[🐌] re-render`, { canAvoidReRender, next })
724
+ }
573
725
  ogSetStateShallow(next)
574
726
  }
575
727
  }
728
+
729
+ // needs to capture latest props (it's called from memoized `events`)
730
+ setStateShallow = (state) => {
731
+ stateRef.current.setStateShallow?.(state)
732
+ }
576
733
  }
577
734
 
578
735
  if (process.env.NODE_ENV === 'development' && time) time`split-styles`
579
736
 
580
737
  // hide strategy will set this opacity = 0 until measured
581
- if (props.group && props.untilMeasured === 'hide' && !curStateRef.hasMeasured) {
738
+ if (props.group && props.untilMeasured === 'hide' && !stateRef.current.hasMeasured) {
582
739
  splitStyles.style ||= {}
583
740
  splitStyles.style.opacity = 0
584
741
  }
585
742
 
586
- curStateRef.isListeningToTheme = splitStyles.dynamicThemeAccess
743
+ if (splitStyles.dynamicThemeAccess != null) {
744
+ stateRef.current.isListeningToTheme = splitStyles.dynamicThemeAccess
745
+ }
587
746
 
588
747
  // only listen for changes if we are using raw theme values or media space, or dynamic media (native)
589
748
  // array = space media breakpoints
@@ -710,16 +869,16 @@ export function createComponent<
710
869
 
711
870
  if (process.env.NODE_ENV === 'development' && time) time`destructure`
712
871
 
713
- if (groupName) {
872
+ if (groupContext) {
714
873
  nonTamaguiProps.onLayout = composeEventHandlers(
715
874
  nonTamaguiProps.onLayout,
716
875
  (e: LayoutEvent) => {
876
+ // one off update here
717
877
  const layout = e.nativeEvent.layout
718
- stateRef.current.group!.layout = layout
719
- stateRef.current.group!.emit(groupName, {
878
+ groupContext.state.layout = layout
879
+ stateRef.current.group?.emit({
720
880
  layout,
721
881
  })
722
-
723
882
  // force re-render if measure strategy is hide
724
883
  if (!stateRef.current.hasMeasured && props.untilMeasured === 'hide') {
725
884
  setState((prev) => ({ ...prev }))
@@ -735,11 +894,11 @@ export function createComponent<
735
894
  elementType,
736
895
  nonTamaguiProps,
737
896
  stateRef,
738
- curStateRef.willHydrate
897
+ stateRef.current.willHydrate
739
898
  ) || nonTamaguiProps
740
899
 
741
- if (!curStateRef.composedRef) {
742
- curStateRef.composedRef = composeRefs<TamaguiElement>(
900
+ if (!stateRef.current.composedRef) {
901
+ stateRef.current.composedRef = composeRefs<TamaguiElement>(
743
902
  (x) => (stateRef.current.host = x as TamaguiElement),
744
903
  forwardedRef,
745
904
  setElementProps,
@@ -747,7 +906,7 @@ export function createComponent<
747
906
  )
748
907
  }
749
908
 
750
- viewProps.ref = curStateRef.composedRef
909
+ viewProps.ref = stateRef.current.composedRef
751
910
 
752
911
  if (process.env.NODE_ENV === 'development') {
753
912
  if (!isReactNative && !isText && isWeb && !isHOC) {
@@ -822,44 +981,34 @@ export function createComponent<
822
981
  }
823
982
 
824
983
  // Only subscribe to context group if not disabled
825
- const dispose =
826
- !disabled && (pseudoGroups || mediaGroups)
827
- ? subscribeToContextGroup({
828
- componentContext,
829
- setStateShallow,
830
- state,
831
- mediaGroups,
832
- pseudoGroups,
833
- })
834
- : null
835
984
 
836
985
  return () => {
837
- dispose?.()
838
986
  componentSetStates.delete(setState)
839
987
  }
988
+ }, [state.unmounted, disabled])
989
+
990
+ useIsomorphicLayoutEffect(() => {
991
+ if (disabled) return
992
+ if (!pseudoGroups && !mediaGroups) return
993
+ if (!allGroupContexts) return
994
+ return subscribeToContextGroup({
995
+ groupContext: allGroupContexts,
996
+ setStateShallow,
997
+ mediaGroups,
998
+ pseudoGroups,
999
+ })
840
1000
  }, [
841
- state.unmounted,
1001
+ allGroupContexts,
842
1002
  disabled,
843
1003
  pseudoGroups ? Object.keys([...pseudoGroups]).join('') : 0,
844
1004
  mediaGroups ? Object.keys([...mediaGroups]).join('') : 0,
845
1005
  ])
846
1006
 
1007
+ const groupEmitter = stateRef.current.group
847
1008
  useIsomorphicLayoutEffect(() => {
848
- if (!groupName) return
849
- curStateRef.group!.emit(groupName, {
850
- pseudo: state,
851
- layout: curStateRef.group?.layout,
852
- })
853
- const groupContextState = componentContext?.groups
854
- if (groupContextState) {
855
- // and mutate the current since its concurrent safe (children throw it in useState on mount)
856
- const next = {
857
- ...groupContextState[groupName],
858
- ...state,
859
- }
860
- groupContextState[groupName] = next
861
- }
862
- }, [groupName, state])
1009
+ if (!groupContext || !groupEmitter) return
1010
+ notifyGroupSubscribers(groupContext, groupEmitter, state)
1011
+ }, [groupContext, groupEmitter, state])
863
1012
 
864
1013
  // if its a group its gotta listen for pseudos to emit them to children
865
1014
 
@@ -889,6 +1038,7 @@ export function createComponent<
889
1038
  onClick ||
890
1039
  pseudos?.focusVisibleStyle
891
1040
  )
1041
+
892
1042
  const runtimeHoverStyle = !disabled && noClass && pseudos?.hoverStyle
893
1043
  const needsHoverState = Boolean(hasDynamicGroupChildren || runtimeHoverStyle)
894
1044
  const attachHover =
@@ -958,10 +1108,8 @@ export function createComponent<
958
1108
  next.hover = false
959
1109
  }
960
1110
  if (needsPressState) {
961
- if (state.pressIn) {
962
- next.press = false
963
- next.pressIn = false
964
- }
1111
+ next.press = false
1112
+ next.pressIn = false
965
1113
  }
966
1114
  setStateShallow(next)
967
1115
  onHoverOut?.(e)
@@ -1004,22 +1152,20 @@ export function createComponent<
1004
1152
  }),
1005
1153
  ...(attachFocus && {
1006
1154
  onFocus: (e) => {
1155
+ const next: Partial<typeof state> = {}
1007
1156
  if (componentContext.setParentFocusState) {
1008
- componentContext.setParentFocusState({ focusWithin: true })
1157
+ next.focusWithin = true
1009
1158
  }
1010
1159
  if (pseudos?.focusVisibleStyle) {
1011
- setTimeout(() => {
1012
- setStateShallow({
1013
- focus: true,
1014
- focusVisible: !!lastInteractionWasKeyboard.value,
1015
- })
1016
- }, 0)
1160
+ if (lastInteractionWasKeyboard.value) {
1161
+ next.focusVisible = true
1162
+ } else {
1163
+ next.focus = true
1164
+ }
1017
1165
  } else {
1018
- setStateShallow({
1019
- focus: true,
1020
- focusVisible: false,
1021
- })
1166
+ next.focus = true
1022
1167
  }
1168
+ setStateShallow(next)
1023
1169
  onFocus?.(e)
1024
1170
  },
1025
1171
  onBlur: (e) => {
@@ -1029,6 +1175,7 @@ export function createComponent<
1029
1175
  setStateShallow({
1030
1176
  focus: false,
1031
1177
  focusVisible: false,
1178
+ focusWithin: false,
1032
1179
  })
1033
1180
  onBlur?.(e)
1034
1181
  },
@@ -1132,38 +1279,10 @@ export function createComponent<
1132
1279
 
1133
1280
  if (process.env.NODE_ENV === 'development' && time) time`create-element`
1134
1281
 
1135
- // must override context so siblings don't clobber initial state
1136
- const groupState = curStateRef.group
1137
- const subGroupContext = React.useMemo(() => {
1138
- if (!groupState || !groupName) return
1139
- groupState.listeners.clear()
1140
- // change reference so context value updates
1141
-
1142
- return {
1143
- ...componentContext.groups,
1144
- // change reference so as we mutate it doesn't affect siblings etc
1145
- state: {
1146
- ...componentContext.groups.state,
1147
- [groupName]: {
1148
- pseudo: defaultComponentStateMounted,
1149
- // capture just initial width and height if they exist
1150
- // will have top, left, width, height (not x, y)
1151
- layout: {
1152
- width: fromPx(splitStyles.style?.width),
1153
- height: fromPx(splitStyles.style?.height),
1154
- },
1155
- },
1156
- },
1157
- emit: groupState.emit,
1158
- subscribe: groupState.subscribe,
1159
- } satisfies ComponentContextI['groups']
1160
- }, [groupName])
1161
-
1162
- if ('group' in props || propsIn.focusWithinStyle) {
1282
+ if ('focusWithinStyle' in propsIn) {
1163
1283
  content = (
1164
1284
  <ComponentContext.Provider
1165
1285
  {...componentContext}
1166
- groups={subGroupContext}
1167
1286
  setParentFocusState={setStateShallow}
1168
1287
  >
1169
1288
  {content}
@@ -1171,6 +1290,12 @@ export function createComponent<
1171
1290
  )
1172
1291
  }
1173
1292
 
1293
+ if ('group' in props) {
1294
+ content = (
1295
+ <GroupContext.Provider value={allGroupContexts}>{content}</GroupContext.Provider>
1296
+ )
1297
+ }
1298
+
1174
1299
  if (process.env.NODE_ENV === 'development' && time) time`group-context`
1175
1300
 
1176
1301
  content = disableTheme
@@ -1254,6 +1379,7 @@ export function createComponent<
1254
1379
  log({
1255
1380
  propsIn,
1256
1381
  props,
1382
+ attachPress,
1257
1383
  animationStyles,
1258
1384
  classNames,
1259
1385
  content,
@@ -1307,6 +1433,19 @@ export function createComponent<
1307
1433
  return content
1308
1434
  })
1309
1435
 
1436
+ function notifyGroupSubscribers(
1437
+ groupContext: SingleGroupContext | null,
1438
+ groupEmitter: ComponentGroupEmitter | null,
1439
+ pseudo: PseudoGroupState
1440
+ ) {
1441
+ if (!groupContext || !groupEmitter) {
1442
+ return
1443
+ }
1444
+ const nextState = { ...groupContext.state, pseudo }
1445
+ groupEmitter.emit(nextState)
1446
+ groupContext.state = nextState
1447
+ }
1448
+
1310
1449
  // let hasLogged = false
1311
1450
 
1312
1451
  if (staticConfig.componentName) {
@@ -1,5 +1,5 @@
1
1
  import type { Context, ProviderExoticComponent, ReactNode } from 'react'
2
- import React from 'react'
2
+ import React, { useId } from 'react'
3
3
 
4
4
  import { objectIdentityKey } from './objectIdentityKey'
5
5