@tamagui/web 1.129.12 → 1.129.13

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 +131 -90
  12. package/dist/cjs/createComponent.js +139 -94
  13. package/dist/cjs/createComponent.js.map +2 -2
  14. package/dist/cjs/createComponent.native.js +156 -106
  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 +2 -40
  34. package/dist/cjs/hooks/useComponentState.js +2 -30
  35. package/dist/cjs/hooks/useComponentState.js.map +2 -2
  36. package/dist/cjs/hooks/useComponentState.native.js +3 -43
  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 +141 -94
  78. package/dist/esm/createComponent.js.map +2 -2
  79. package/dist/esm/createComponent.mjs +132 -91
  80. package/dist/esm/createComponent.mjs.map +1 -1
  81. package/dist/esm/createComponent.native.js +152 -102
  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 +2 -30
  105. package/dist/esm/hooks/useComponentState.js.map +2 -2
  106. package/dist/esm/hooks/useComponentState.mjs +2 -40
  107. package/dist/esm/hooks/useComponentState.mjs.map +1 -1
  108. package/dist/esm/hooks/useComponentState.native.js +3 -46
  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 +251 -107
  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 +2 -46
  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 +33 -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 +22 -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'
@@ -66,6 +74,16 @@ let startVisualizer: Function | undefined
66
74
  type ComponentSetState = React.Dispatch<React.SetStateAction<TamaguiComponentState>>
67
75
 
68
76
  export const componentSetStates = new Set<ComponentSetState>()
77
+ const avoidReRenderKeys = new Set([
78
+ 'hover',
79
+ 'press',
80
+ 'pressIn',
81
+ 'group',
82
+ 'focus',
83
+ 'focusWithin',
84
+ 'media',
85
+ 'group',
86
+ ])
69
87
 
70
88
  if (process.env.TAMAGUI_TARGET !== 'native' && typeof window !== 'undefined') {
71
89
  const cancelTouches = () => {
@@ -92,7 +110,10 @@ if (process.env.TAMAGUI_TARGET !== 'native' && typeof window !== 'undefined') {
92
110
  if (process.env.NODE_ENV === 'development') {
93
111
  startVisualizer = () => {
94
112
  const devVisualizerConfig = devConfig?.visualizer
95
- if (devVisualizerConfig) {
113
+
114
+ if (devVisualizerConfig && !globalThis.__tamaguiDevVisualizer) {
115
+ globalThis.__tamaguiDevVisualizer = true
116
+
96
117
  debugKeyListeners = new Set()
97
118
  let tm
98
119
  let isShowing = false
@@ -144,15 +165,21 @@ let BaseView: any
144
165
  let hasSetupBaseViews = false
145
166
 
146
167
  const lastInteractionWasKeyboard = { value: false }
147
- if (isWeb && globalThis['document']) {
168
+ if (isWeb && typeof document !== 'undefined') {
148
169
  document.addEventListener('keydown', () => {
149
- lastInteractionWasKeyboard.value = true
170
+ if (!lastInteractionWasKeyboard.value) {
171
+ lastInteractionWasKeyboard.value = true
172
+ }
150
173
  })
151
174
  document.addEventListener('mousedown', () => {
152
- lastInteractionWasKeyboard.value = false
175
+ if (lastInteractionWasKeyboard.value) {
176
+ lastInteractionWasKeyboard.value = false
177
+ }
153
178
  })
154
179
  document.addEventListener('mousemove', () => {
155
- lastInteractionWasKeyboard.value = false
180
+ if (lastInteractionWasKeyboard.value) {
181
+ lastInteractionWasKeyboard.value = false
182
+ }
156
183
  })
157
184
  }
158
185
 
@@ -222,8 +249,6 @@ export function createComponent<
222
249
  }
223
250
  }
224
251
 
225
- const componentContext = React.useContext(ComponentContext)
226
-
227
252
  // set variants through context
228
253
  // order is after default props but before props
229
254
  let styledContextProps: Object | undefined
@@ -294,7 +319,7 @@ export function createComponent<
294
319
  let overlay: HTMLSpanElement | null = null
295
320
 
296
321
  const debugVisualizerHandler = (show = false) => {
297
- const node = curStateRef.host as HTMLElement
322
+ const node = stateRef.current.host as HTMLElement
298
323
  if (!node) return
299
324
 
300
325
  if (show) {
@@ -338,21 +363,19 @@ export function createComponent<
338
363
  }, [componentName])
339
364
  }
340
365
 
341
- /**
342
- * Component state for tracking animations, pseudos
343
- */
366
+ const componentContext = React.useContext(ComponentContext)
367
+ const groupContextParent = React.useContext(GroupContext)
344
368
  const animationDriver = componentContext.animationDriver
345
369
  const useAnimations = animationDriver?.useAnimations as UseAnimationHook | undefined
346
370
 
347
371
  const componentState = useComponentState(
348
372
  props,
349
- componentContext,
373
+ animationDriver,
350
374
  staticConfig,
351
375
  config!
352
376
  )
353
377
 
354
378
  const {
355
- curStateRef,
356
379
  disabled,
357
380
  groupName,
358
381
  hasAnimationProp,
@@ -372,11 +395,64 @@ export function createComponent<
372
395
  startedUnhydrated,
373
396
  } = componentState
374
397
 
398
+ // create new context with groups, or else sublings will grab the same one
399
+ const allGroupContexts = useMemo((): AllGroupContexts | null => {
400
+ if (!groupName) {
401
+ return groupContextParent
402
+ }
403
+
404
+ // TODO this shouldn't be in useMemo
405
+ stateRef.current.group?.listeners.clear()
406
+ const listeners = new Set<GroupStateListener>()
407
+ stateRef.current.group = {
408
+ listeners,
409
+ emit(state) {
410
+ listeners.forEach((l) => l(state))
411
+ },
412
+ subscribe(cb) {
413
+ listeners.add(cb)
414
+ if (listeners.size === 1) {
415
+ setStateShallow({ hasDynGroupChildren: true })
416
+ }
417
+ return () => {
418
+ listeners.delete(cb)
419
+ if (listeners.size === 0) {
420
+ setStateShallow({ hasDynGroupChildren: false })
421
+ }
422
+ }
423
+ },
424
+ }
425
+
426
+ return {
427
+ ...groupContextParent,
428
+ [groupName]: {
429
+ state: {
430
+ pseudo: defaultComponentStateMounted,
431
+ },
432
+ subscribe: (listener) => {
433
+ const dispose = stateRef.current.group?.subscribe(listener)
434
+ return () => {
435
+ dispose?.()
436
+ }
437
+ },
438
+ },
439
+ }
440
+ }, [stateRef, groupName, groupContextParent])
441
+
375
442
  // if our animation driver supports noReRender, we'll replace this below with
376
443
  // a version that essentially uses an internall emitter rather than setting state
377
444
  // but still stores the current state and applies if it it needs to during render
378
445
  let setStateShallow = componentState.setStateShallow
379
446
 
447
+ const pendingState = stateRef.current.nextComponentState
448
+ if (pendingState) {
449
+ stateRef.current.nextComponentState = undefined
450
+ componentState.setState((prev) => ({
451
+ ...prev,
452
+ ...pendingState,
453
+ }))
454
+ }
455
+
380
456
  if (process.env.NODE_ENV === 'development' && time) time`use-state`
381
457
 
382
458
  const hasTextAncestor = !!(isWeb && isText ? componentContext.inText : false)
@@ -412,13 +488,13 @@ export function createComponent<
412
488
  if (process.env.NODE_ENV === 'development' && time) time`theme-props`
413
489
 
414
490
  if (props.themeShallow) {
415
- curStateRef.themeShallow = true
491
+ stateRef.current.themeShallow = true
416
492
  }
417
493
 
418
494
  const themeStateProps: UseThemeWithStateProps = {
419
495
  componentName,
420
496
  disable: disableTheme,
421
- shallow: curStateRef.themeShallow,
497
+ shallow: stateRef.current.themeShallow,
422
498
  debug: debugProp,
423
499
  }
424
500
 
@@ -430,7 +506,7 @@ export function createComponent<
430
506
  if ('theme' in props) {
431
507
  themeStateProps.name = props.theme
432
508
  }
433
- if (typeof curStateRef.isListeningToTheme === 'boolean') {
509
+ if (typeof stateRef.current.isListeningToTheme === 'boolean') {
434
510
  themeStateProps.needsUpdate = () => !!stateRef.current.isListeningToTheme
435
511
  }
436
512
  // on native we optimize theme changes if fastSchemeChange is enabled, otherwise deopt
@@ -482,7 +558,11 @@ export function createComponent<
482
558
  log('props in:', propsIn)
483
559
  log('final props:', props)
484
560
  log({ state, staticConfig, elementType, themeStateProps })
485
- log({ contextProps: styledContextProps, overriddenContextProps })
561
+ log({
562
+ contextProps: styledContextProps,
563
+ overriddenContextProps,
564
+ componentContext,
565
+ })
486
566
  log({ presence, isAnimated, isHOC, hasAnimationProp, useAnimations })
487
567
  console.groupEnd()
488
568
  }
@@ -535,26 +615,87 @@ export function createComponent<
535
615
  styleProps,
536
616
  null,
537
617
  componentContext,
618
+ allGroupContexts,
538
619
  elementType,
539
620
  startedUnhydrated,
540
621
  debugProp
541
622
  )
542
623
 
624
+ const groupContext = groupName ? allGroupContexts?.[groupName] || null : null
625
+
626
+ // one tiny mutation 🙏 get width/height optimistically from raw values if possible
627
+ // if set hardcoded it avoids extra renders
628
+ if (groupContext) {
629
+ const groupState = groupContext?.state
630
+ if (groupState && groupState.layout === undefined) {
631
+ if (splitStyles.style?.width || splitStyles.style?.height) {
632
+ groupState.layout = {
633
+ width: fromPx(splitStyles.style.width),
634
+ height: fromPx(splitStyles.style.height),
635
+ }
636
+ }
637
+ }
638
+ }
639
+
543
640
  // avoids re-rendering if animation driver supports it
544
641
  // TODO believe we need to set some sort of "pendingState" in case it re-renders
545
- if (hasAnimationProp && animationDriver?.avoidReRenders) {
642
+ if ((hasAnimationProp || groupName) && animationDriver?.avoidReRenders) {
546
643
  const styleListener = stateRef.current.useStyleListener
547
644
  const ogSetStateShallow = setStateShallow
548
- setStateShallow = (next) => {
645
+
646
+ stateRef.current.setStateShallow = (nextOrGetNext) => {
647
+ const prev = stateRef.current.nextComponentState || state
648
+ const next =
649
+ typeof nextOrGetNext === 'function' ? nextOrGetNext(prev) : nextOrGetNext
650
+
651
+ if (next === prev || isEqualShallow(prev, next)) {
652
+ return
653
+ }
654
+
549
655
  // 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
656
  const canAvoidReRender = Object.keys(next).every((key) =>
553
657
  avoidReRenderKeys.has(key)
554
658
  )
555
- if (canAvoidReRender && styleListener) {
556
- const updatedState = { ...state, ...next }
659
+
660
+ if (canAvoidReRender) {
661
+ const updatedState = {
662
+ ...prev,
663
+ ...next,
664
+ }
557
665
  stateRef.current.nextComponentState = updatedState
666
+
667
+ if (
668
+ process.env.NODE_ENV === 'development' &&
669
+ debugProp &&
670
+ debugProp !== 'profile'
671
+ ) {
672
+ console.groupCollapsed(`[⚡️] avoid setState`, next, { updatedState, props })
673
+ console.info(stateRef.current.host)
674
+ console.groupEnd()
675
+ }
676
+
677
+ const {
678
+ group,
679
+ hasDynGroupChildren,
680
+ unmounted,
681
+ animation,
682
+ ...childrenGroupState
683
+ } = updatedState
684
+
685
+ // update before getting styles
686
+ if (groupContext) {
687
+ notifyGroupSubscribers(
688
+ groupContext,
689
+ stateRef.current.group || null,
690
+ childrenGroupState
691
+ )
692
+ }
693
+
694
+ // we just emit and return for group without animation ^
695
+ if (!hasAnimationProp || !styleListener) {
696
+ return
697
+ }
698
+
558
699
  const nextStyles = getSplitStyles(
559
700
  props,
560
701
  staticConfig,
@@ -564,26 +705,42 @@ export function createComponent<
564
705
  styleProps,
565
706
  null,
566
707
  componentContext,
708
+ allGroupContexts,
567
709
  elementType,
568
710
  startedUnhydrated,
569
711
  debugProp
570
712
  )
713
+
571
714
  styleListener(nextStyles.style as any)
572
715
  } else {
716
+ if (
717
+ process.env.NODE_ENV === 'development' &&
718
+ debugProp &&
719
+ debugProp !== 'profile'
720
+ ) {
721
+ console.info(`[🐌] re-render`, { canAvoidReRender, next })
722
+ }
573
723
  ogSetStateShallow(next)
574
724
  }
575
725
  }
726
+
727
+ // needs to capture latest props (it's called from memoized `events`)
728
+ setStateShallow = (state) => {
729
+ stateRef.current.setStateShallow?.(state)
730
+ }
576
731
  }
577
732
 
578
733
  if (process.env.NODE_ENV === 'development' && time) time`split-styles`
579
734
 
580
735
  // hide strategy will set this opacity = 0 until measured
581
- if (props.group && props.untilMeasured === 'hide' && !curStateRef.hasMeasured) {
736
+ if (props.group && props.untilMeasured === 'hide' && !stateRef.current.hasMeasured) {
582
737
  splitStyles.style ||= {}
583
738
  splitStyles.style.opacity = 0
584
739
  }
585
740
 
586
- curStateRef.isListeningToTheme = splitStyles.dynamicThemeAccess
741
+ if (splitStyles.dynamicThemeAccess != null) {
742
+ stateRef.current.isListeningToTheme = splitStyles.dynamicThemeAccess
743
+ }
587
744
 
588
745
  // only listen for changes if we are using raw theme values or media space, or dynamic media (native)
589
746
  // array = space media breakpoints
@@ -710,16 +867,16 @@ export function createComponent<
710
867
 
711
868
  if (process.env.NODE_ENV === 'development' && time) time`destructure`
712
869
 
713
- if (groupName) {
870
+ if (groupContext) {
714
871
  nonTamaguiProps.onLayout = composeEventHandlers(
715
872
  nonTamaguiProps.onLayout,
716
873
  (e: LayoutEvent) => {
874
+ // one off update here
717
875
  const layout = e.nativeEvent.layout
718
- stateRef.current.group!.layout = layout
719
- stateRef.current.group!.emit(groupName, {
876
+ groupContext.state.layout = layout
877
+ stateRef.current.group?.emit({
720
878
  layout,
721
879
  })
722
-
723
880
  // force re-render if measure strategy is hide
724
881
  if (!stateRef.current.hasMeasured && props.untilMeasured === 'hide') {
725
882
  setState((prev) => ({ ...prev }))
@@ -735,11 +892,11 @@ export function createComponent<
735
892
  elementType,
736
893
  nonTamaguiProps,
737
894
  stateRef,
738
- curStateRef.willHydrate
895
+ stateRef.current.willHydrate
739
896
  ) || nonTamaguiProps
740
897
 
741
- if (!curStateRef.composedRef) {
742
- curStateRef.composedRef = composeRefs<TamaguiElement>(
898
+ if (!stateRef.current.composedRef) {
899
+ stateRef.current.composedRef = composeRefs<TamaguiElement>(
743
900
  (x) => (stateRef.current.host = x as TamaguiElement),
744
901
  forwardedRef,
745
902
  setElementProps,
@@ -747,7 +904,7 @@ export function createComponent<
747
904
  )
748
905
  }
749
906
 
750
- viewProps.ref = curStateRef.composedRef
907
+ viewProps.ref = stateRef.current.composedRef
751
908
 
752
909
  if (process.env.NODE_ENV === 'development') {
753
910
  if (!isReactNative && !isText && isWeb && !isHOC) {
@@ -822,44 +979,41 @@ export function createComponent<
822
979
  }
823
980
 
824
981
  // 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
982
 
836
983
  return () => {
837
- dispose?.()
838
984
  componentSetStates.delete(setState)
839
985
  }
986
+ }, [state.unmounted, disabled])
987
+
988
+ useIsomorphicLayoutEffect(() => {
989
+ if (disabled) return
990
+ if (!pseudoGroups && !mediaGroups) return
991
+ if (!allGroupContexts) return
992
+ return subscribeToContextGroup({
993
+ groupContext: allGroupContexts,
994
+ setStateShallow,
995
+ mediaGroups,
996
+ pseudoGroups,
997
+ })
840
998
  }, [
841
- state.unmounted,
999
+ allGroupContexts,
842
1000
  disabled,
843
1001
  pseudoGroups ? Object.keys([...pseudoGroups]).join('') : 0,
844
1002
  mediaGroups ? Object.keys([...mediaGroups]).join('') : 0,
845
1003
  ])
846
1004
 
847
- useIsomorphicLayoutEffect(() => {
848
- if (!groupName) return
849
- curStateRef.group!.emit(groupName, {
850
- pseudo: state,
851
- layout: curStateRef.group?.layout,
1005
+ if (hasAnimationProp && animationDriver?.avoidReRenders) {
1006
+ useEffect(() => {
1007
+ // always clear after render
1008
+ stateRef.current.nextComponentState = undefined
852
1009
  })
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])
1010
+ }
1011
+
1012
+ const groupEmitter = stateRef.current.group
1013
+ useIsomorphicLayoutEffect(() => {
1014
+ if (!groupContext || !groupEmitter) return
1015
+ notifyGroupSubscribers(groupContext, groupEmitter, state)
1016
+ }, [groupContext, groupEmitter, state])
863
1017
 
864
1018
  // if its a group its gotta listen for pseudos to emit them to children
865
1019
 
@@ -889,6 +1043,7 @@ export function createComponent<
889
1043
  onClick ||
890
1044
  pseudos?.focusVisibleStyle
891
1045
  )
1046
+
892
1047
  const runtimeHoverStyle = !disabled && noClass && pseudos?.hoverStyle
893
1048
  const needsHoverState = Boolean(hasDynamicGroupChildren || runtimeHoverStyle)
894
1049
  const attachHover =
@@ -958,10 +1113,8 @@ export function createComponent<
958
1113
  next.hover = false
959
1114
  }
960
1115
  if (needsPressState) {
961
- if (state.pressIn) {
962
- next.press = false
963
- next.pressIn = false
964
- }
1116
+ next.press = false
1117
+ next.pressIn = false
965
1118
  }
966
1119
  setStateShallow(next)
967
1120
  onHoverOut?.(e)
@@ -1004,22 +1157,20 @@ export function createComponent<
1004
1157
  }),
1005
1158
  ...(attachFocus && {
1006
1159
  onFocus: (e) => {
1160
+ const next: Partial<typeof state> = {}
1007
1161
  if (componentContext.setParentFocusState) {
1008
- componentContext.setParentFocusState({ focusWithin: true })
1162
+ next.focusWithin = true
1009
1163
  }
1010
1164
  if (pseudos?.focusVisibleStyle) {
1011
- setTimeout(() => {
1012
- setStateShallow({
1013
- focus: true,
1014
- focusVisible: !!lastInteractionWasKeyboard.value,
1015
- })
1016
- }, 0)
1165
+ if (lastInteractionWasKeyboard.value) {
1166
+ next.focusVisible = true
1167
+ } else {
1168
+ next.focus = true
1169
+ }
1017
1170
  } else {
1018
- setStateShallow({
1019
- focus: true,
1020
- focusVisible: false,
1021
- })
1171
+ next.focus = true
1022
1172
  }
1173
+ setStateShallow(next)
1023
1174
  onFocus?.(e)
1024
1175
  },
1025
1176
  onBlur: (e) => {
@@ -1029,6 +1180,7 @@ export function createComponent<
1029
1180
  setStateShallow({
1030
1181
  focus: false,
1031
1182
  focusVisible: false,
1183
+ focusWithin: false,
1032
1184
  })
1033
1185
  onBlur?.(e)
1034
1186
  },
@@ -1132,38 +1284,10 @@ export function createComponent<
1132
1284
 
1133
1285
  if (process.env.NODE_ENV === 'development' && time) time`create-element`
1134
1286
 
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) {
1287
+ if ('focusWithinStyle' in propsIn) {
1163
1288
  content = (
1164
1289
  <ComponentContext.Provider
1165
1290
  {...componentContext}
1166
- groups={subGroupContext}
1167
1291
  setParentFocusState={setStateShallow}
1168
1292
  >
1169
1293
  {content}
@@ -1171,6 +1295,12 @@ export function createComponent<
1171
1295
  )
1172
1296
  }
1173
1297
 
1298
+ if ('group' in props) {
1299
+ content = (
1300
+ <GroupContext.Provider value={allGroupContexts}>{content}</GroupContext.Provider>
1301
+ )
1302
+ }
1303
+
1174
1304
  if (process.env.NODE_ENV === 'development' && time) time`group-context`
1175
1305
 
1176
1306
  content = disableTheme
@@ -1254,6 +1384,7 @@ export function createComponent<
1254
1384
  log({
1255
1385
  propsIn,
1256
1386
  props,
1387
+ attachPress,
1257
1388
  animationStyles,
1258
1389
  classNames,
1259
1390
  content,
@@ -1307,6 +1438,19 @@ export function createComponent<
1307
1438
  return content
1308
1439
  })
1309
1440
 
1441
+ function notifyGroupSubscribers(
1442
+ groupContext: SingleGroupContext | null,
1443
+ groupEmitter: ComponentGroupEmitter | null,
1444
+ pseudo: PseudoGroupState
1445
+ ) {
1446
+ if (!groupContext || !groupEmitter) {
1447
+ return
1448
+ }
1449
+ const nextState = { ...groupContext.state, pseudo }
1450
+ groupEmitter.emit(nextState)
1451
+ groupContext.state = nextState
1452
+ }
1453
+
1310
1454
  // let hasLogged = false
1311
1455
 
1312
1456
  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