@tamagui/web 1.92.1 → 1.93.0

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 (86) hide show
  1. package/dist/cjs/createComponent.js +156 -104
  2. package/dist/cjs/createComponent.js.map +2 -2
  3. package/dist/cjs/createComponent.native.js +139 -104
  4. package/dist/cjs/createComponent.native.js.map +2 -2
  5. package/dist/cjs/createVariable.js +7 -3
  6. package/dist/cjs/createVariable.js.map +1 -1
  7. package/dist/cjs/createVariable.native.js +7 -3
  8. package/dist/cjs/createVariable.native.js.map +2 -2
  9. package/dist/cjs/helpers/expandStyle.native.js +0 -5
  10. package/dist/cjs/helpers/expandStyle.native.js.map +2 -2
  11. package/dist/cjs/helpers/getSplitStyles.native.js +1 -15
  12. package/dist/cjs/helpers/getSplitStyles.native.js.map +1 -1
  13. package/dist/cjs/helpers/getThemeCSSRules.js +4 -1
  14. package/dist/cjs/helpers/getThemeCSSRules.js.map +1 -1
  15. package/dist/cjs/helpers/insertStyleRule.js +4 -1
  16. package/dist/cjs/helpers/insertStyleRule.js.map +1 -1
  17. package/dist/cjs/helpers/insertStyleRule.native.js +3 -1
  18. package/dist/cjs/helpers/insertStyleRule.native.js.map +2 -2
  19. package/dist/cjs/helpers/registerCSSVariable.js +4 -1
  20. package/dist/cjs/helpers/registerCSSVariable.js.map +1 -1
  21. package/dist/cjs/helpers/registerCSSVariable.native.js +1 -1
  22. package/dist/cjs/helpers/registerCSSVariable.native.js.map +2 -2
  23. package/dist/cjs/hooks/useProps.js +29 -5
  24. package/dist/cjs/hooks/useProps.js.map +1 -1
  25. package/dist/cjs/hooks/useProps.native.js +39 -4
  26. package/dist/cjs/hooks/useProps.native.js.map +2 -2
  27. package/dist/cjs/index.js +1 -0
  28. package/dist/cjs/index.js.map +1 -1
  29. package/dist/cjs/index.native.js +2 -0
  30. package/dist/cjs/index.native.js.map +1 -1
  31. package/dist/esm/createComponent.js +156 -104
  32. package/dist/esm/createComponent.js.map +2 -2
  33. package/dist/esm/createComponent.mjs +193 -135
  34. package/dist/esm/createComponent.native.js +135 -103
  35. package/dist/esm/createComponent.native.js.map +2 -2
  36. package/dist/esm/createVariable.js +7 -3
  37. package/dist/esm/createVariable.js.map +1 -1
  38. package/dist/esm/createVariable.mjs +19 -16
  39. package/dist/esm/createVariable.native.js +7 -3
  40. package/dist/esm/createVariable.native.js.map +2 -2
  41. package/dist/esm/helpers/expandStyle.native.js +0 -5
  42. package/dist/esm/helpers/expandStyle.native.js.map +2 -2
  43. package/dist/esm/helpers/getSplitStyles.native.js +1 -15
  44. package/dist/esm/helpers/getSplitStyles.native.js.map +1 -1
  45. package/dist/esm/helpers/getThemeCSSRules.js +4 -1
  46. package/dist/esm/helpers/getThemeCSSRules.js.map +1 -1
  47. package/dist/esm/helpers/getThemeCSSRules.mjs +1 -1
  48. package/dist/esm/helpers/insertStyleRule.js +4 -1
  49. package/dist/esm/helpers/insertStyleRule.js.map +1 -1
  50. package/dist/esm/helpers/insertStyleRule.mjs +4 -3
  51. package/dist/esm/helpers/insertStyleRule.native.js +3 -1
  52. package/dist/esm/helpers/insertStyleRule.native.js.map +2 -2
  53. package/dist/esm/helpers/registerCSSVariable.js +4 -1
  54. package/dist/esm/helpers/registerCSSVariable.js.map +1 -1
  55. package/dist/esm/helpers/registerCSSVariable.mjs +1 -1
  56. package/dist/esm/helpers/registerCSSVariable.native.js +1 -1
  57. package/dist/esm/helpers/registerCSSVariable.native.js.map +2 -2
  58. package/dist/esm/hooks/useProps.js +31 -6
  59. package/dist/esm/hooks/useProps.js.map +1 -1
  60. package/dist/esm/hooks/useProps.mjs +32 -5
  61. package/dist/esm/hooks/useProps.native.js +41 -5
  62. package/dist/esm/hooks/useProps.native.js.map +2 -2
  63. package/dist/esm/index.js +1 -0
  64. package/dist/esm/index.js.map +1 -1
  65. package/dist/esm/index.mjs +1 -0
  66. package/dist/esm/index.native.js +1 -0
  67. package/dist/esm/index.native.js.map +1 -1
  68. package/package.json +12 -12
  69. package/reset.css +1 -0
  70. package/src/createComponent.tsx +301 -229
  71. package/src/createVariable.ts +11 -2
  72. package/src/helpers/getThemeCSSRules.ts +4 -1
  73. package/src/helpers/insertStyleRule.tsx +4 -1
  74. package/src/helpers/registerCSSVariable.ts +4 -3
  75. package/src/hooks/useProps.tsx +46 -14
  76. package/src/index.ts +1 -0
  77. package/types/createComponent.d.ts +31 -1
  78. package/types/createComponent.d.ts.map +1 -1
  79. package/types/createVariable.d.ts.map +1 -1
  80. package/types/helpers/getThemeCSSRules.d.ts.map +1 -1
  81. package/types/helpers/insertStyleRule.d.ts.map +1 -1
  82. package/types/helpers/registerCSSVariable.d.ts.map +1 -1
  83. package/types/hooks/useProps.d.ts +7 -4
  84. package/types/hooks/useProps.d.ts.map +1 -1
  85. package/types/index.d.ts +1 -0
  86. package/types/index.d.ts.map +1 -1
@@ -132,6 +132,194 @@ if (typeof document !== 'undefined') {
132
132
  }
133
133
  }
134
134
 
135
+ export const useComponentState = (
136
+ props: StackProps | TextProps | Record<string, any>,
137
+ { animationDriver, groups }: ComponentContextI,
138
+ staticConfig: StaticConfig,
139
+ config: TamaguiInternalConfig
140
+ ) => {
141
+ const useAnimations = animationDriver?.useAnimations as UseAnimationHook | undefined
142
+
143
+ const stateRef = useRef<TamaguiComponentStateRef>({})
144
+
145
+ // after we get states mount we need to turn off isAnimated for server side
146
+ const hasAnimationProp = Boolean(
147
+ 'animation' in props || (props.style && hasAnimatedStyleValue(props.style))
148
+ )
149
+
150
+ // disable for now still ssr issues
151
+ const supportsCSSVars = animationDriver?.supportsCSSVars
152
+ const curStateRef = stateRef.current
153
+
154
+ const willBeAnimatedClient = (() => {
155
+ const next = !!(hasAnimationProp && !staticConfig.isHOC && useAnimations)
156
+ return Boolean(next || curStateRef.hasAnimated)
157
+ })()
158
+
159
+ const willBeAnimated = !isServer && willBeAnimatedClient
160
+
161
+ // once animated, always animated to preserve hooks / vdom structure
162
+ if (willBeAnimated && !curStateRef.hasAnimated) {
163
+ curStateRef.hasAnimated = true
164
+ }
165
+
166
+ // HOOK
167
+ const isHydrated = config?.disableSSR ? true : useDidFinishSSR()
168
+
169
+ // HOOK
170
+ const presence =
171
+ (willBeAnimated &&
172
+ props['animatePresence'] !== false &&
173
+ animationDriver?.usePresence?.()) ||
174
+ null
175
+ const presenceState = presence?.[2]
176
+ const isExiting = presenceState?.isPresent === false
177
+ const isEntering = presenceState?.isPresent === true && presenceState.initial !== false
178
+
179
+ const hasEnterStyle = !!props.enterStyle
180
+ // finish animated logic, avoid isAnimated when unmounted
181
+ const hasRNAnimation = hasAnimationProp && animationDriver?.isReactNative
182
+ const isReactNative = staticConfig.isReactNative
183
+
184
+ // only web server + initial client render run this when not hydrated:
185
+ let isAnimated = willBeAnimated
186
+ if (!isReactNative && hasRNAnimation && !staticConfig.isHOC && !isHydrated) {
187
+ isAnimated = false
188
+ curStateRef.willHydrate = true
189
+ }
190
+
191
+ if (process.env.NODE_ENV === 'development' && time) time`pre-use-state`
192
+
193
+ const hasEnterState = hasEnterStyle || isEntering
194
+ const needsToMount = !isHydrated || !curStateRef.host
195
+
196
+ const initialState = hasEnterState
197
+ ? needsToMount
198
+ ? defaultComponentStateShouldEnter
199
+ : defaultComponentState
200
+ : defaultComponentStateMounted
201
+
202
+ // will be nice to deprecate half of these:
203
+ const disabled = isDisabled(props)
204
+
205
+ if (disabled != null) {
206
+ initialState.disabled = disabled
207
+ }
208
+
209
+ // HOOK
210
+ const states = useState<TamaguiComponentState>(initialState)
211
+
212
+ const state = props.forceStyle ? { ...states[0], [props.forceStyle]: true } : states[0]
213
+ const setState = states[1]
214
+
215
+ // immediately update disabled state and reset component state
216
+ if (disabled !== state.disabled) {
217
+ setState({
218
+ ...state,
219
+ ...defaultComponentState, // removes any stale press state etc
220
+ disabled,
221
+ })
222
+ }
223
+
224
+ let setStateShallow = createShallowSetState(setState, disabled, props.debug)
225
+
226
+ if (isHydrated && state.unmounted === 'should-enter') {
227
+ state.unmounted = true
228
+ }
229
+
230
+ // set enter/exit variants onto our new props object
231
+ if (presenceState && isAnimated && isHydrated && staticConfig.variants) {
232
+ if (process.env.NODE_ENV === 'development' && props.debug === 'verbose') {
233
+ console.warn(`has presenceState ${JSON.stringify(presenceState)}`)
234
+ }
235
+ const { enterVariant, exitVariant, enterExitVariant, custom } = presenceState
236
+ if (isObj(custom)) {
237
+ Object.assign(props, custom)
238
+ }
239
+ const exv = exitVariant ?? enterExitVariant
240
+ const env = enterVariant ?? enterExitVariant
241
+ if (state.unmounted && env && staticConfig.variants[env]) {
242
+ if (process.env.NODE_ENV === 'development' && props.debug === 'verbose') {
243
+ console.warn(`Animating presence ENTER "${env}"`)
244
+ }
245
+ props[env] = true
246
+ } else if (isExiting && exv) {
247
+ if (process.env.NODE_ENV === 'development' && props.debug === 'verbose') {
248
+ console.warn(`Animating presence EXIT "${exv}"`)
249
+ }
250
+ props[exv] = exitVariant === enterExitVariant ? false : true
251
+ }
252
+ }
253
+
254
+ const shouldAvoidClasses = Boolean(
255
+ !isWeb ||
256
+ (isAnimated && !supportsCSSVars) ||
257
+ !staticConfig.acceptsClassName ||
258
+ // on server for SSR and animation compat added the && isHydrated but perhaps we want
259
+ // disableClassName="until-hydrated" to be more straightforward
260
+ // see issue if not, Button sets disableClassName to true <Button animation="" /> with
261
+ // the react-native driver errors because it tries to animate var(--color) to rbga(..)
262
+ (props.disableClassName && isHydrated)
263
+ )
264
+
265
+ const groupName = props.group as any as string
266
+
267
+ if (groupName && !curStateRef.group) {
268
+ const listeners = new Set<GroupStateListener>()
269
+ curStateRef.group = {
270
+ listeners,
271
+ emit(name, state) {
272
+ listeners.forEach((l) => l(name, state))
273
+ },
274
+ subscribe(cb) {
275
+ listeners.add(cb)
276
+ return () => {
277
+ listeners.delete(cb)
278
+ }
279
+ },
280
+ }
281
+ }
282
+
283
+ if (groupName) {
284
+ // when we set state we also set our group state and emit an event for children listening:
285
+ const groupContextState = groups.state
286
+ const og = setStateShallow
287
+ setStateShallow = (state) => {
288
+ og(state)
289
+ curStateRef.group!.emit(groupName, {
290
+ pseudo: state,
291
+ })
292
+ // and mutate the current since its concurrent safe (children throw it in useState on mount)
293
+ const next = {
294
+ ...groupContextState[groupName],
295
+ ...state,
296
+ }
297
+ groupContextState[groupName] = next
298
+ }
299
+ }
300
+
301
+ return {
302
+ curStateRef,
303
+ disabled,
304
+ groupName,
305
+ hasAnimationProp,
306
+ hasEnterStyle,
307
+ isAnimated,
308
+ isExiting,
309
+ isHydrated,
310
+ presence,
311
+ presenceState,
312
+ setState,
313
+ setStateShallow,
314
+ shouldAvoidClasses,
315
+ state,
316
+ stateRef,
317
+ supportsCSSVars,
318
+ willBeAnimated,
319
+ willBeAnimatedClient,
320
+ }
321
+ }
322
+
135
323
  /**
136
324
  * Only on native do we need the actual underlying View/Text
137
325
  * On the web we avoid react-native dep altogether.
@@ -178,7 +366,6 @@ export function createComponent<
178
366
  const {
179
367
  Component,
180
368
  isText,
181
- isInput,
182
369
  isZStack,
183
370
  isHOC,
184
371
  validStyles = {},
@@ -234,7 +421,7 @@ export function createComponent<
234
421
  let styledContextProps: Object | undefined
235
422
  let overriddenContextProps: Object | undefined
236
423
  let contextValue: Object | null | undefined
237
- const { context } = staticConfig
424
+ const { context, isReactNative } = staticConfig
238
425
 
239
426
  if (context) {
240
427
  // HOOK 3 (-1 if production)
@@ -286,7 +473,7 @@ export function createComponent<
286
473
  let overlay: HTMLSpanElement | null = null
287
474
 
288
475
  const debugVisualizerHandler = (show = false) => {
289
- const node = curState.host as HTMLElement
476
+ const node = curStateRef.host as HTMLElement
290
477
  if (!node) return
291
478
 
292
479
  if (show) {
@@ -346,183 +533,39 @@ export function createComponent<
346
533
  // conditional but if ever true stays true
347
534
  // [animated, inversed]
348
535
  // HOOK
349
- const stateRef = useRef<TamaguiComponentStateRef>({})
536
+
350
537
  if (process.env.NODE_ENV === 'development' && time) time`stateref`
351
538
 
352
539
  /**
353
540
  * Component state for tracking animations, pseudos
354
541
  */
355
- const animationsConfig = componentContext.animationDriver
356
- const useAnimations = animationsConfig?.useAnimations as UseAnimationHook | undefined
357
-
358
- // after we get states mount we need to turn off isAnimated for server side
359
- const hasAnimationProp = Boolean(
360
- 'animation' in props || (props.style && hasAnimatedStyleValue(props.style))
361
- )
362
-
363
- // disable for now still ssr issues
364
- const supportsCSSVars = animationsConfig?.supportsCSSVars
365
- const curState = stateRef.current
366
-
367
- const willBeAnimatedClient = (() => {
368
- const next = !!(hasAnimationProp && !isHOC && useAnimations)
369
- return Boolean(next || curState.hasAnimated)
370
- })()
371
-
372
- const willBeAnimated = !isServer && willBeAnimatedClient
373
-
374
- // once animated, always animated to preserve hooks / vdom structure
375
- if (willBeAnimated && !curState.hasAnimated) {
376
- curState.hasAnimated = true
377
- }
378
-
379
- // HOOK
380
- const isHydrated = config?.disableSSR ? true : useDidFinishSSR()
381
-
382
- // HOOK
383
- const presence =
384
- (willBeAnimated &&
385
- props['animatePresence'] !== false &&
386
- animationsConfig?.usePresence?.()) ||
387
- null
388
- const presenceState = presence?.[2]
389
- const isExiting = presenceState?.isPresent === false
390
- const isEntering =
391
- presenceState?.isPresent === true && presenceState.initial !== false
392
-
393
- const hasEnterStyle = !!props.enterStyle
394
- // finish animated logic, avoid isAnimated when unmounted
395
- const hasRNAnimation = hasAnimationProp && animationsConfig?.isReactNative
396
- const isReactNative = staticConfig.isReactNative
397
-
398
- // only web server + initial client render run this when not hydrated:
399
- let isAnimated = willBeAnimated
400
- if (!isReactNative && hasRNAnimation && !isHOC && !isHydrated) {
401
- isAnimated = false
402
- curState.willHydrate = true
403
- }
404
-
405
- if (process.env.NODE_ENV === 'development' && time) time`pre-use-state`
406
-
407
- const hasEnterState = hasEnterStyle || isEntering
408
- const needsToMount = !isHydrated || !curState.host
409
-
410
- const initialState = hasEnterState
411
- ? needsToMount
412
- ? defaultComponentStateShouldEnter
413
- : defaultComponentState
414
- : defaultComponentStateMounted
415
-
416
- // will be nice to deprecate half of these:
417
- const disabled =
418
- props.disabled ||
419
- props.accessibilityState?.disabled ||
420
- props['aria-disabled'] ||
421
- // @ts-expect-error (comes from core)
422
- props.accessibilityDisabled ||
423
- false
542
+ const animationDriver = componentContext.animationDriver
543
+ const useAnimations = animationDriver?.useAnimations as UseAnimationHook | undefined
424
544
 
425
- if (disabled != null) {
426
- initialState.disabled = disabled
427
- }
428
-
429
- // HOOK
430
- const states = useState<TamaguiComponentState>(initialState)
431
-
432
- const state = props.forceStyle
433
- ? { ...states[0], [props.forceStyle]: true }
434
- : states[0]
435
- const setState = states[1]
436
-
437
- // immediately update disabled state and reset component state
438
- if (disabled !== state.disabled) {
439
- setState({
440
- ...state,
441
- ...defaultComponentState, // removes any stale press state etc
442
- disabled,
443
- })
444
- }
445
-
446
- let setStateShallow = createShallowSetState(setState, disabled, debugProp)
447
-
448
- if (isHydrated && state.unmounted === 'should-enter') {
449
- state.unmounted = true
450
- }
451
-
452
- // set enter/exit variants onto our new props object
453
- if (presenceState && isAnimated && isHydrated && staticConfig.variants) {
454
- if (process.env.NODE_ENV === 'development' && debugProp === 'verbose') {
455
- console.warn(`has presenceState ${JSON.stringify(presenceState)}`)
456
- }
457
- const { enterVariant, exitVariant, enterExitVariant, custom } = presenceState
458
- if (isObj(custom)) {
459
- Object.assign(props, custom)
460
- }
461
- const exv = exitVariant ?? enterExitVariant
462
- const env = enterVariant ?? enterExitVariant
463
- if (state.unmounted && env && staticConfig.variants[env]) {
464
- if (process.env.NODE_ENV === 'development' && debugProp === 'verbose') {
465
- console.warn(`Animating presence ENTER "${env}"`)
466
- }
467
- props[env] = true
468
- } else if (isExiting && exv) {
469
- if (process.env.NODE_ENV === 'development' && debugProp === 'verbose') {
470
- console.warn(`Animating presence EXIT "${exv}"`)
471
- }
472
- props[exv] = exitVariant === enterExitVariant ? false : true
473
- }
474
- }
475
-
476
- const shouldAvoidClasses = Boolean(
477
- !isWeb ||
478
- (isAnimated && !supportsCSSVars) ||
479
- !staticConfig.acceptsClassName ||
480
- // on server for SSR and animation compat added the && isHydrated but perhaps we want
481
- // disableClassName="until-hydrated" to be more straightforward
482
- // see issue if not, Button sets disableClassName to true <Button animation="" /> with
483
- // the react-native driver errors because it tries to animate var(--color) to rbga(..)
484
- (propsIn.disableClassName && isHydrated)
485
- )
545
+ const {
546
+ curStateRef,
547
+ disabled,
548
+ groupName,
549
+ hasAnimationProp,
550
+ hasEnterStyle,
551
+ isAnimated,
552
+ isExiting,
553
+ isHydrated,
554
+ presence,
555
+ presenceState,
556
+ setState,
557
+ setStateShallow,
558
+ shouldAvoidClasses,
559
+ state,
560
+ stateRef,
561
+ supportsCSSVars,
562
+ willBeAnimated,
563
+ willBeAnimatedClient,
564
+ } = useComponentState(props, componentContext, staticConfig, config!)
486
565
 
487
566
  const shouldForcePseudo = !!propsIn.forceStyle
488
567
  const noClassNames = shouldAvoidClasses || shouldForcePseudo
489
568
 
490
- const groupName = props.group as any as string
491
-
492
- if (groupName && !curState.group) {
493
- const listeners = new Set<GroupStateListener>()
494
- curState.group = {
495
- listeners,
496
- emit(name, state) {
497
- listeners.forEach((l) => l(name, state))
498
- },
499
- subscribe(cb) {
500
- listeners.add(cb)
501
- return () => {
502
- listeners.delete(cb)
503
- }
504
- },
505
- }
506
- }
507
-
508
- if (groupName) {
509
- // when we set state we also set our group state and emit an event for children listening:
510
- const groupContextState = componentContext.groups.state
511
- const og = setStateShallow
512
- setStateShallow = (state) => {
513
- og(state)
514
- curState.group!.emit(groupName, {
515
- pseudo: state,
516
- })
517
- // and mutate the current since its concurrent safe (children throw it in useState on mount)
518
- const next = {
519
- ...groupContextState[groupName],
520
- ...state,
521
- }
522
- groupContextState[groupName] = next
523
- }
524
- }
525
-
526
569
  if (process.env.NODE_ENV === 'development' && time) time`use-state`
527
570
 
528
571
  const hasTextAncestor = !!(isWeb && isText ? componentContext.inText : false)
@@ -539,8 +582,8 @@ export function createComponent<
539
582
 
540
583
  let elementType = isText ? BaseTextComponent : BaseViewComponent
541
584
 
542
- if (animationsConfig && isAnimated) {
543
- elementType = animationsConfig[isText ? 'Text' : 'View'] || elementType
585
+ if (animationDriver && isAnimated) {
586
+ elementType = animationDriver[isText ? 'Text' : 'View'] || elementType
544
587
  }
545
588
 
546
589
  // internal use only
@@ -552,19 +595,19 @@ export function createComponent<
552
595
  if (process.env.NODE_ENV === 'development' && time) time`theme-props`
553
596
 
554
597
  if (props.themeShallow) {
555
- curState.themeShallow = true
598
+ curStateRef.themeShallow = true
556
599
  }
557
600
 
558
601
  const themeStateProps: UseThemeWithStateProps = {
559
602
  name: props.theme,
560
603
  componentName,
561
604
  disable: disableTheme,
562
- shallow: curState.themeShallow,
605
+ shallow: curStateRef.themeShallow,
563
606
  inverse: props.themeInverse,
564
607
  debug: debugProp,
565
608
  }
566
609
 
567
- if (typeof curState.isListeningToTheme === 'boolean') {
610
+ if (typeof curStateRef.isListeningToTheme === 'boolean') {
568
611
  themeStateProps.shouldUpdate = () => stateRef.current.isListeningToTheme
569
612
  }
570
613
 
@@ -668,14 +711,14 @@ export function createComponent<
668
711
  )
669
712
 
670
713
  // hide strategy will set this opacity = 0 until measured
671
- if (props.group && props.untilMeasured === 'hide' && !curState.hasMeasured) {
714
+ if (props.group && props.untilMeasured === 'hide' && !curStateRef.hasMeasured) {
672
715
  splitStyles.style ||= {}
673
716
  splitStyles.style.opacity = 0
674
717
  }
675
718
 
676
719
  if (process.env.NODE_ENV === 'development' && time) time`split-styles`
677
720
 
678
- curState.isListeningToTheme = splitStyles.dynamicThemeAccess
721
+ curStateRef.isListeningToTheme = splitStyles.dynamicThemeAccess
679
722
 
680
723
  // only listen for changes if we are using raw theme values or media space, or dynamic media (native)
681
724
  // array = space media breakpoints
@@ -805,18 +848,18 @@ export function createComponent<
805
848
  elementType,
806
849
  nonTamaguiProps,
807
850
  stateRef,
808
- curState.willHydrate
851
+ curStateRef.willHydrate
809
852
  ) || nonTamaguiProps
810
853
 
811
854
  // HOOK (1 more):
812
- if (!curState.composedRef) {
813
- curState.composedRef = composeRefs<TamaguiElement>(
855
+ if (!curStateRef.composedRef) {
856
+ curStateRef.composedRef = composeRefs<TamaguiElement>(
814
857
  (x) => (stateRef.current.host = x as TamaguiElement),
815
858
  forwardedRef
816
859
  )
817
860
  }
818
861
 
819
- viewProps.ref = curState.composedRef
862
+ viewProps.ref = curStateRef.composedRef
820
863
 
821
864
  if (process.env.NODE_ENV === 'development') {
822
865
  if (!isReactNative && !isText && isWeb && !isHOC) {
@@ -840,70 +883,37 @@ export function createComponent<
840
883
  const { pseudoGroups, mediaGroups } = splitStyles
841
884
 
842
885
  // TODO if you add a group prop setStateShallow changes identity...
843
- if (!curState.unPress) {
844
- curState.unPress = () => setStateShallow({ press: false, pressIn: false })
886
+ if (!curStateRef.unPress) {
887
+ curStateRef.unPress = () => setStateShallow({ press: false, pressIn: false })
845
888
  }
846
889
 
847
- const unPress = curState.unPress!
848
- const shouldEnter = state.unmounted
890
+ const unPress = curStateRef.unPress!
849
891
 
850
892
  useEffect(() => {
851
893
  if (disabled) {
852
894
  return
853
895
  }
854
896
 
855
- if (shouldEnter) {
897
+ if (state.unmounted) {
856
898
  setStateShallow({ unmounted: false })
857
899
  return
858
900
  }
859
901
 
860
- // parent group pseudo listening
861
- let disposeGroupsListener: DisposeFn | undefined
862
- if (pseudoGroups || mediaGroups) {
863
- const current = {
864
- pseudo: {},
865
- media: {},
866
- } satisfies GroupState
867
-
868
- if (process.env.NODE_ENV === 'development' && !componentContext.groups) {
869
- console.debug(`No context group found`)
870
- }
871
-
872
- disposeGroupsListener = componentContext.groups?.subscribe(
873
- (name, { layout, pseudo }) => {
874
- if (pseudo && pseudoGroups?.has(name)) {
875
- // we emit a partial so merge it + change reference so mergeIfNotShallowEqual runs
876
- Object.assign(current.pseudo, pseudo)
877
- persist()
878
- } else if (layout && mediaGroups) {
879
- const mediaState = getMediaState(mediaGroups, layout)
880
- const next = mergeIfNotShallowEqual(current.media, mediaState)
881
- if (next !== current.media) {
882
- Object.assign(current.media, next)
883
- persist()
884
- }
885
- }
886
- function persist() {
887
- // force it to be referentially different so it always updates
888
- const group = {
889
- ...state.group,
890
- [name]: current,
891
- }
892
- setStateShallow({
893
- group,
894
- })
895
- }
896
- }
897
- )
898
- }
902
+ const dispose = subscribeToContextGroup({
903
+ disabled,
904
+ componentContext,
905
+ setStateShallow,
906
+ state,
907
+ mediaGroups,
908
+ pseudoGroups,
909
+ })
899
910
 
900
911
  return () => {
901
- disposeGroupsListener?.()
912
+ dispose?.()
902
913
  mouseUps.delete(unPress)
903
914
  }
904
915
  }, [
905
916
  disabled,
906
- shouldEnter,
907
917
  pseudoGroups ? Object.keys([...pseudoGroups]).join('') : 0,
908
918
  mediaGroups ? Object.keys([...mediaGroups]).join('') : 0,
909
919
  ])
@@ -1161,7 +1171,7 @@ export function createComponent<
1161
1171
  if (process.env.NODE_ENV === 'development' && time) time`create-element`
1162
1172
 
1163
1173
  // must override context so siblings don't clobber initial state
1164
- const groupState = curState.group
1174
+ const groupState = curStateRef.group
1165
1175
  const subGroupContext = useMemo(() => {
1166
1176
  if (!groupState || !groupName) return
1167
1177
  groupState.listeners.clear()
@@ -1272,7 +1282,6 @@ export function createComponent<
1272
1282
  defaultProps,
1273
1283
  elementType,
1274
1284
  events,
1275
- initialState,
1276
1285
  isAnimated,
1277
1286
  isMediaArray,
1278
1287
  isStringElement,
@@ -1628,3 +1637,66 @@ function getMediaState(
1628
1637
 
1629
1638
  const fromPx = (val?: number | string) =>
1630
1639
  typeof val !== 'string' ? val : +val.replace('px', '')
1640
+
1641
+ export const isDisabled = (props: any) => {
1642
+ return (
1643
+ props.disabled ||
1644
+ props.accessibilityState?.disabled ||
1645
+ props['aria-disabled'] ||
1646
+ props.accessibilityDisabled ||
1647
+ false
1648
+ )
1649
+ }
1650
+
1651
+ export const subscribeToContextGroup = ({
1652
+ disabled = false,
1653
+ setStateShallow,
1654
+ pseudoGroups,
1655
+ mediaGroups,
1656
+ componentContext,
1657
+ state,
1658
+ }: {
1659
+ disabled?: boolean
1660
+ setStateShallow: (next?: Partial<TamaguiComponentState> | undefined) => void
1661
+ pseudoGroups?: Set<string>
1662
+ mediaGroups?: Set<string>
1663
+ componentContext: ComponentContextI
1664
+ state: TamaguiComponentState
1665
+ }) => {
1666
+ // parent group pseudo listening
1667
+ if (pseudoGroups || mediaGroups) {
1668
+ const current = {
1669
+ pseudo: {},
1670
+ media: {},
1671
+ } satisfies GroupState
1672
+
1673
+ if (process.env.NODE_ENV === 'development' && !componentContext.groups) {
1674
+ console.debug(`No context group found`)
1675
+ }
1676
+
1677
+ return componentContext.groups?.subscribe((name, { layout, pseudo }) => {
1678
+ if (pseudo && pseudoGroups?.has(name)) {
1679
+ // we emit a partial so merge it + change reference so mergeIfNotShallowEqual runs
1680
+ Object.assign(current.pseudo, pseudo)
1681
+ persist()
1682
+ } else if (layout && mediaGroups) {
1683
+ const mediaState = getMediaState(mediaGroups, layout)
1684
+ const next = mergeIfNotShallowEqual(current.media, mediaState)
1685
+ if (next !== current.media) {
1686
+ Object.assign(current.media, next)
1687
+ persist()
1688
+ }
1689
+ }
1690
+ function persist() {
1691
+ // force it to be referentially different so it always updates
1692
+ const group = {
1693
+ ...state.group,
1694
+ [name]: current,
1695
+ }
1696
+ setStateShallow({
1697
+ group,
1698
+ })
1699
+ }
1700
+ })
1701
+ }
1702
+ }
@@ -20,19 +20,28 @@ export interface Variable<A = any> {
20
20
 
21
21
  export type MakeVariable<A = any> = A extends string | number ? Variable<A> : A
22
22
 
23
+ function constructCSSVariableName(name: string) {
24
+ return `var(--${process.env.TAMAGUI_CSS_VARIABLE_PREFIX || ''}${name})`
25
+ }
26
+
23
27
  type VariableIn<A = any> = Pick<Variable<A>, 'key' | 'name' | 'val'>
24
28
  export const createVariable = <A extends string | number | Variable = any>(
25
29
  props: VariableIn<A>,
26
30
  skipHash = false
27
31
  ): Variable<A> => {
28
32
  if (!skipHash && isVariable(props)) return props
33
+
29
34
  const { key, name, val } = props
30
35
  return {
31
36
  [IS_VAR]: true,
32
37
  key: key!,
33
38
  name: skipHash ? '' : simpleHash(name, 40),
34
39
  val: val as any,
35
- variable: isWeb ? (skipHash ? `var(--${name})` : createCSSVariable(name)) : '',
40
+ variable: isWeb
41
+ ? skipHash
42
+ ? constructCSSVariableName(name)
43
+ : createCSSVariable(name)
44
+ : '',
36
45
  }
37
46
  }
38
47
 
@@ -90,5 +99,5 @@ export const createCSSVariable = (nameProp: string, includeVar = true) => {
90
99
  }
91
100
  }
92
101
  const name = simpleHash(nameProp, 60)
93
- return includeVar ? `var(--${name})` : name
102
+ return includeVar ? constructCSSVariableName(name) : name
94
103
  }