@tamagui/web 1.112.1 → 1.112.2

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 (114) hide show
  1. package/dist/cjs/createComponent.js +6 -141
  2. package/dist/cjs/createComponent.js.map +2 -2
  3. package/dist/cjs/createComponent.native.js +8 -143
  4. package/dist/cjs/createComponent.native.js.map +2 -2
  5. package/dist/cjs/createTamagui.js +2 -3
  6. package/dist/cjs/createTamagui.js.map +1 -1
  7. package/dist/cjs/createTamagui.native.js +2 -5
  8. package/dist/cjs/createTamagui.native.js.map +2 -2
  9. package/dist/cjs/helpers/getSplitStyles.js +2 -2
  10. package/dist/cjs/helpers/getSplitStyles.js.map +1 -1
  11. package/dist/cjs/helpers/getSplitStyles.native.js +2 -2
  12. package/dist/cjs/helpers/getSplitStyles.native.js.map +2 -2
  13. package/dist/cjs/helpers/getThemeCSSRules.js +2 -2
  14. package/dist/cjs/helpers/getThemeCSSRules.js.map +1 -1
  15. package/dist/cjs/helpers/insertStyleRule.js.map +1 -1
  16. package/dist/cjs/helpers/insertStyleRule.native.js.map +1 -1
  17. package/dist/cjs/helpers/sortString.js +21 -0
  18. package/dist/cjs/helpers/sortString.js.map +6 -0
  19. package/dist/cjs/helpers/sortString.native.js +28 -0
  20. package/dist/cjs/helpers/sortString.native.js.map +6 -0
  21. package/dist/cjs/helpers/subscribeToContextGroup.js +53 -0
  22. package/dist/cjs/helpers/subscribeToContextGroup.js.map +6 -0
  23. package/dist/cjs/helpers/subscribeToContextGroup.native.js +52 -0
  24. package/dist/cjs/helpers/subscribeToContextGroup.native.js.map +6 -0
  25. package/dist/cjs/hooks/useComponentState.js +113 -0
  26. package/dist/cjs/hooks/useComponentState.js.map +6 -0
  27. package/dist/cjs/hooks/useComponentState.native.js +127 -0
  28. package/dist/cjs/hooks/useComponentState.native.js.map +6 -0
  29. package/dist/cjs/hooks/useProps.js +3 -3
  30. package/dist/cjs/hooks/useProps.js.map +1 -1
  31. package/dist/cjs/hooks/useProps.native.js +3 -3
  32. package/dist/cjs/hooks/useProps.native.js.map +1 -1
  33. package/dist/cjs/subscribeToContextGroup.js +53 -0
  34. package/dist/cjs/subscribeToContextGroup.js.map +6 -0
  35. package/dist/cjs/subscribeToContextGroup.native.js +52 -0
  36. package/dist/cjs/subscribeToContextGroup.native.js.map +6 -0
  37. package/dist/esm/createComponent.js +8 -151
  38. package/dist/esm/createComponent.js.map +2 -2
  39. package/dist/esm/createComponent.mjs +18 -182
  40. package/dist/esm/createComponent.mjs.map +1 -1
  41. package/dist/esm/createComponent.native.js +20 -200
  42. package/dist/esm/createComponent.native.js.map +1 -1
  43. package/dist/esm/createTamagui.js +2 -3
  44. package/dist/esm/createTamagui.js.map +1 -1
  45. package/dist/esm/createTamagui.mjs +2 -5
  46. package/dist/esm/createTamagui.mjs.map +1 -1
  47. package/dist/esm/createTamagui.native.js +2 -5
  48. package/dist/esm/createTamagui.native.js.map +1 -1
  49. package/dist/esm/helpers/getSplitStyles.js +2 -1
  50. package/dist/esm/helpers/getSplitStyles.js.map +1 -1
  51. package/dist/esm/helpers/getSplitStyles.mjs +2 -1
  52. package/dist/esm/helpers/getSplitStyles.mjs.map +1 -1
  53. package/dist/esm/helpers/getSplitStyles.native.js +2 -1
  54. package/dist/esm/helpers/getSplitStyles.native.js.map +1 -1
  55. package/dist/esm/helpers/getThemeCSSRules.js +2 -1
  56. package/dist/esm/helpers/getThemeCSSRules.js.map +1 -1
  57. package/dist/esm/helpers/getThemeCSSRules.mjs +2 -1
  58. package/dist/esm/helpers/getThemeCSSRules.mjs.map +1 -1
  59. package/dist/esm/helpers/insertStyleRule.js.map +1 -1
  60. package/dist/esm/helpers/insertStyleRule.mjs.map +1 -1
  61. package/dist/esm/helpers/insertStyleRule.native.js.map +1 -1
  62. package/dist/esm/helpers/sortString.js +5 -0
  63. package/dist/esm/helpers/sortString.js.map +6 -0
  64. package/dist/esm/helpers/sortString.mjs +3 -0
  65. package/dist/esm/helpers/sortString.mjs.map +1 -0
  66. package/dist/esm/helpers/sortString.native.js +5 -0
  67. package/dist/esm/helpers/sortString.native.js.map +1 -0
  68. package/dist/esm/helpers/subscribeToContextGroup.js +38 -0
  69. package/dist/esm/helpers/subscribeToContextGroup.js.map +6 -0
  70. package/dist/esm/helpers/subscribeToContextGroup.mjs +38 -0
  71. package/dist/esm/helpers/subscribeToContextGroup.mjs.map +1 -0
  72. package/dist/esm/helpers/subscribeToContextGroup.native.js +42 -0
  73. package/dist/esm/helpers/subscribeToContextGroup.native.js.map +1 -0
  74. package/dist/esm/hooks/useComponentState.js +107 -0
  75. package/dist/esm/hooks/useComponentState.js.map +6 -0
  76. package/dist/esm/hooks/useComponentState.mjs +135 -0
  77. package/dist/esm/hooks/useComponentState.mjs.map +1 -0
  78. package/dist/esm/hooks/useComponentState.native.js +147 -0
  79. package/dist/esm/hooks/useComponentState.native.js.map +1 -0
  80. package/dist/esm/hooks/useProps.js +2 -1
  81. package/dist/esm/hooks/useProps.js.map +1 -1
  82. package/dist/esm/hooks/useProps.mjs +2 -1
  83. package/dist/esm/hooks/useProps.mjs.map +1 -1
  84. package/dist/esm/hooks/useProps.native.js +2 -1
  85. package/dist/esm/hooks/useProps.native.js.map +1 -1
  86. package/dist/esm/subscribeToContextGroup.js +38 -0
  87. package/dist/esm/subscribeToContextGroup.js.map +6 -0
  88. package/dist/esm/subscribeToContextGroup.mjs +38 -0
  89. package/dist/esm/subscribeToContextGroup.mjs.map +1 -0
  90. package/dist/esm/subscribeToContextGroup.native.js +42 -0
  91. package/dist/esm/subscribeToContextGroup.native.js.map +1 -0
  92. package/package.json +12 -12
  93. package/src/createComponent.tsx +6 -316
  94. package/src/createTamagui.ts +11 -4
  95. package/src/helpers/getSplitStyles.tsx +2 -1
  96. package/src/helpers/getThemeCSSRules.ts +2 -1
  97. package/src/helpers/insertStyleRule.tsx +1 -0
  98. package/src/helpers/sortString.ts +1 -0
  99. package/src/helpers/subscribeToContextGroup.tsx +56 -0
  100. package/src/hooks/useComponentState.ts +258 -0
  101. package/src/hooks/useProps.tsx +2 -2
  102. package/types/createComponent.d.ts +1 -30
  103. package/types/createComponent.d.ts.map +1 -1
  104. package/types/createTamagui.d.ts.map +1 -1
  105. package/types/helpers/getSplitStyles.d.ts.map +1 -1
  106. package/types/helpers/getThemeCSSRules.d.ts.map +1 -1
  107. package/types/helpers/insertStyleRule.d.ts.map +1 -1
  108. package/types/helpers/sortString.d.ts +2 -0
  109. package/types/helpers/sortString.d.ts.map +1 -0
  110. package/types/helpers/subscribeToContextGroup.d.ts +10 -0
  111. package/types/helpers/subscribeToContextGroup.d.ts.map +1 -0
  112. package/types/hooks/useComponentState.d.ts +22 -0
  113. package/types/hooks/useComponentState.d.ts.map +1 -0
  114. package/types/hooks/useProps.d.ts.map +1 -1
@@ -1,4 +1,3 @@
1
- import React from 'react'
2
1
  import { composeRefs } from '@tamagui/compose-refs'
3
2
  import { isClient, isServer, isWeb, useIsomorphicLayoutEffect } from '@tamagui/constants'
4
3
  import {
@@ -7,29 +6,21 @@ import {
7
6
  composeEventHandlers,
8
7
  validStyles,
9
8
  } from '@tamagui/helpers'
10
-
9
+ import React from 'react'
11
10
  import { devConfig, getConfig, onConfiguredOnce } from './config'
12
11
  import { stackDefaultStyles } from './constants/constants'
13
12
  import { isDevTools } from './constants/isDevTools'
14
13
  import { ComponentContext } from './contexts/ComponentContext'
15
14
  import { didGetVariableValue, setDidGetVariableValue } from './createVariable'
16
- import {
17
- defaultComponentState,
18
- defaultComponentStateMounted,
19
- defaultComponentStateShouldEnter,
20
- } from './defaultComponentState'
21
- import {
22
- createShallowSetState,
23
- mergeIfNotShallowEqual,
24
- } from './helpers/createShallowSetState'
15
+ import { defaultComponentStateMounted } from './defaultComponentState'
25
16
  import { useSplitStyles } from './helpers/getSplitStyles'
26
- import { isObj } from './helpers/isObj'
27
17
  import { log } from './helpers/log'
28
18
  import { mergeProps } from './helpers/mergeProps'
29
19
  import { setElementProps } from './helpers/setElementProps'
20
+ import { subscribeToContextGroup } from './helpers/subscribeToContextGroup'
30
21
  import { themeable } from './helpers/themeable'
31
- import { useDidHydrateOnce } from './hooks/useDidHydrateOnce'
32
- import { getMediaState, setMediaShouldUpdate, useMedia } from './hooks/useMedia'
22
+ import { useComponentState } from './hooks/useComponentState'
23
+ import { setMediaShouldUpdate, useMedia } from './hooks/useMedia'
33
24
  import { useThemeWithState } from './hooks/useTheme'
34
25
  import type { TamaguiComponentEvents } from './interfaces/TamaguiComponentEvents'
35
26
  import type { TamaguiComponentState } from './interfaces/TamaguiComponentState'
@@ -38,8 +29,6 @@ import { hooks } from './setupHooks'
38
29
  import type {
39
30
  ComponentContextI,
40
31
  DebugProp,
41
- GroupState,
42
- GroupStateListener,
43
32
  LayoutEvent,
44
33
  SizeTokens,
45
34
  SpaceDirection,
@@ -51,7 +40,6 @@ import type {
51
40
  StaticConfig,
52
41
  StyleableOptions,
53
42
  TamaguiComponent,
54
- TamaguiComponentStateRef,
55
43
  TamaguiElement,
56
44
  TamaguiInternalConfig,
57
45
  TextProps,
@@ -140,227 +128,6 @@ if (typeof document !== 'undefined') {
140
128
  }
141
129
  }
142
130
 
143
- export const useComponentState = (
144
- props: StackProps | TextProps | Record<string, any>,
145
- { animationDriver, groups }: ComponentContextI,
146
- staticConfig: StaticConfig,
147
- config: TamaguiInternalConfig
148
- ) => {
149
- const useAnimations = animationDriver?.useAnimations as UseAnimationHook | undefined
150
-
151
- const stateRef = React.useRef<TamaguiComponentStateRef>(
152
- undefined as any as TamaguiComponentStateRef
153
- )
154
- if (!stateRef.current) {
155
- stateRef.current = {}
156
- }
157
-
158
- // after we get states mount we need to turn off isAnimated for server side
159
- const hasAnimationProp = Boolean(
160
- 'animation' in props || (props.style && hasAnimatedStyleValue(props.style))
161
- )
162
-
163
- // disable for now still ssr issues
164
- const supportsCSSVars = animationDriver?.supportsCSSVars
165
- const curStateRef = stateRef.current
166
-
167
- const willBeAnimatedClient = (() => {
168
- const next = !!(hasAnimationProp && !staticConfig.isHOC && useAnimations)
169
- return Boolean(next || curStateRef.hasAnimated)
170
- })()
171
-
172
- const willBeAnimated = !isServer && willBeAnimatedClient
173
-
174
- // once animated, always animated to preserve hooks / vdom structure
175
- if (willBeAnimated && !curStateRef.hasAnimated) {
176
- curStateRef.hasAnimated = true
177
- }
178
-
179
- // HOOK
180
- const presence =
181
- (willBeAnimated &&
182
- props['animatePresence'] !== false &&
183
- animationDriver?.usePresence?.()) ||
184
- null
185
- const presenceState = presence?.[2]
186
- const isExiting = presenceState?.isPresent === false
187
- const isEntering = presenceState?.isPresent === true && presenceState.initial !== false
188
-
189
- const hasEnterStyle = !!props.enterStyle
190
- // finish animated logic, avoid isAnimated when unmounted
191
- const hasRNAnimation = hasAnimationProp && animationDriver?.isReactNative
192
-
193
- if (process.env.NODE_ENV === 'development' && time) time`pre-use-state`
194
-
195
- const hasEnterState = hasEnterStyle || isEntering
196
-
197
- // this can be conditional because its only ever needed with animations
198
- const didHydrateOnce = willBeAnimated ? useDidHydrateOnce() : true
199
- const shouldEnter = hasEnterState || (!didHydrateOnce && hasRNAnimation)
200
- const shouldEnterFromUnhydrated = isWeb && !didHydrateOnce
201
-
202
- const initialState = shouldEnter
203
- ? // on the very first render we switch all spring animation drivers to css rendering
204
- // this is because we need to use css variables, which they don't support to do proper SSR
205
- // without flickers of the wrong colors.
206
- // but once we do that initial hydration and we are in client side rendering mode,
207
- // we can avoid the extra re-render on mount
208
- shouldEnterFromUnhydrated
209
- ? defaultComponentState
210
- : defaultComponentStateShouldEnter
211
- : defaultComponentStateMounted
212
-
213
- // will be nice to deprecate half of these:
214
- const disabled = isDisabled(props)
215
-
216
- if (disabled != null) {
217
- initialState.disabled = disabled
218
- }
219
-
220
- // HOOK
221
- const states = React.useState<TamaguiComponentState>(initialState)
222
-
223
- const state = props.forceStyle ? { ...states[0], [props.forceStyle]: true } : states[0]
224
- const setState = states[1]
225
-
226
- const isHydrated = state.unmounted === false || state.unmounted === 'should-enter'
227
-
228
- // only web server + initial client render run this when not hydrated:
229
- let isAnimated = willBeAnimated
230
- if (isWeb && hasRNAnimation && !staticConfig.isHOC && state.unmounted === true) {
231
- isAnimated = false
232
- curStateRef.willHydrate = true
233
- }
234
-
235
- // immediately update disabled state and reset component state
236
- if (disabled !== state.disabled) {
237
- state.disabled = disabled
238
- // if disabled remove all press/focus/hover states
239
- if (disabled) {
240
- Object.assign(state, defaultComponentStateMounted)
241
- }
242
- setState({ ...state })
243
- }
244
-
245
- let setStateShallow = createShallowSetState(setState, disabled, false, props.debug)
246
-
247
- // if (isHydrated && state.unmounted === 'should-enter') {
248
- // state.unmounted = true
249
- // }
250
-
251
- // set enter/exit variants onto our new props object
252
- if (presenceState && isAnimated && isHydrated && staticConfig.variants) {
253
- if (process.env.NODE_ENV === 'development' && props.debug === 'verbose') {
254
- console.warn(`has presenceState ${JSON.stringify(presenceState)}`)
255
- }
256
- const { enterVariant, exitVariant, enterExitVariant, custom } = presenceState
257
- if (isObj(custom)) {
258
- Object.assign(props, custom)
259
- }
260
- const exv = exitVariant ?? enterExitVariant
261
- const env = enterVariant ?? enterExitVariant
262
- if (state.unmounted && env && staticConfig.variants[env]) {
263
- if (process.env.NODE_ENV === 'development' && props.debug === 'verbose') {
264
- console.warn(`Animating presence ENTER "${env}"`)
265
- }
266
- props[env] = true
267
- } else if (isExiting && exv) {
268
- if (process.env.NODE_ENV === 'development' && props.debug === 'verbose') {
269
- console.warn(`Animating presence EXIT "${exv}"`)
270
- }
271
- props[exv] = exitVariant !== enterExitVariant
272
- }
273
- }
274
-
275
- let shouldAvoidClasses = !isWeb
276
-
277
- // on server for SSR and animation compat added the && isHydrated but perhaps we want
278
- // disableClassName="until-hydrated" to be more straightforward
279
- // see issue if not, Button sets disableClassName to true <Button animation="" /> with
280
- // the react-native driver errors because it tries to animate var(--color) to rbga(..)
281
- if (isWeb) {
282
- const { disableClassName } = props
283
-
284
- const isAnimatedAndHydrated =
285
- isAnimated && !supportsCSSVars && didHydrateOnce && !isServer
286
-
287
- const isClassNameDisabled =
288
- !staticConfig.acceptsClassName && (config.disableSSR || didHydrateOnce)
289
-
290
- const isDisabledManually =
291
- disableClassName && !isServer && didHydrateOnce && state.unmounted === true
292
-
293
- if (isAnimatedAndHydrated || isDisabledManually || isClassNameDisabled) {
294
- shouldAvoidClasses = true
295
-
296
- if (process.env.NODE_ENV === 'development' && props.debug) {
297
- log(`avoiding className`, {
298
- isAnimatedAndHydrated,
299
- isDisabledManually,
300
- isClassNameDisabled,
301
- })
302
- }
303
- }
304
- }
305
-
306
- const groupName = props.group as any as string
307
-
308
- if (groupName && !curStateRef.group) {
309
- const listeners = new Set<GroupStateListener>()
310
- curStateRef.group = {
311
- listeners,
312
- emit(name, state) {
313
- listeners.forEach((l) => l(name, state))
314
- },
315
- subscribe(cb) {
316
- listeners.add(cb)
317
- return () => {
318
- listeners.delete(cb)
319
- }
320
- },
321
- }
322
- }
323
-
324
- if (groupName) {
325
- // when we set state we also set our group state and emit an event for children listening:
326
- const groupContextState = groups.state
327
- const og = setStateShallow
328
- setStateShallow = (state) => {
329
- og(state)
330
- curStateRef.group!.emit(groupName, {
331
- pseudo: state,
332
- })
333
- // and mutate the current since its concurrent safe (children throw it in useState on mount)
334
- const next = {
335
- ...groupContextState[groupName],
336
- ...state,
337
- }
338
- groupContextState[groupName] = next
339
- }
340
- }
341
-
342
- return {
343
- curStateRef,
344
- disabled,
345
- groupName,
346
- hasAnimationProp,
347
- hasEnterStyle,
348
- isAnimated,
349
- isExiting,
350
- isHydrated,
351
- presence,
352
- presenceState,
353
- setState,
354
- setStateShallow,
355
- shouldAvoidClasses,
356
- state,
357
- stateRef,
358
- supportsCSSVars,
359
- willBeAnimated,
360
- willBeAnimatedClient,
361
- }
362
- }
363
-
364
131
  /**
365
132
  * Only on native do we need the actual underlying View/Text
366
133
  * On the web we avoid react-native dep altogether.
@@ -406,14 +173,7 @@ export function createComponent<
406
173
  }
407
174
  })
408
175
 
409
- const {
410
- Component,
411
- isText,
412
- isZStack,
413
- isHOC,
414
- validStyles = {},
415
- variants = {},
416
- } = staticConfig
176
+ const { Component, isText, isZStack, isHOC } = staticConfig
417
177
 
418
178
  if (process.env.NODE_ENV === 'development' && staticConfig.defaultProps?.['debug']) {
419
179
  if (process.env.IS_STATIC !== 'is_static') {
@@ -1757,75 +1517,5 @@ const AbsoluteFill: any = createComponent({
1757
1517
  },
1758
1518
  })
1759
1519
 
1760
- function hasAnimatedStyleValue(style: Object) {
1761
- return Object.keys(style).some((k) => {
1762
- const val = style[k]
1763
- return val && typeof val === 'object' && '_animation' in val
1764
- })
1765
- }
1766
-
1767
1520
  const fromPx = (val?: number | string) =>
1768
1521
  typeof val !== 'string' ? val : +val.replace('px', '')
1769
-
1770
- export const isDisabled = (props: any) => {
1771
- return (
1772
- props.disabled ||
1773
- props.accessibilityState?.disabled ||
1774
- props['aria-disabled'] ||
1775
- props.accessibilityDisabled ||
1776
- false
1777
- )
1778
- }
1779
-
1780
- export const subscribeToContextGroup = ({
1781
- disabled = false,
1782
- setStateShallow,
1783
- pseudoGroups,
1784
- mediaGroups,
1785
- componentContext,
1786
- state,
1787
- }: {
1788
- disabled?: boolean
1789
- setStateShallow: (next?: Partial<TamaguiComponentState> | undefined) => void
1790
- pseudoGroups?: Set<string>
1791
- mediaGroups?: Set<string>
1792
- componentContext: ComponentContextI
1793
- state: TamaguiComponentState
1794
- }) => {
1795
- // parent group pseudo listening
1796
- if (pseudoGroups || mediaGroups) {
1797
- const current = {
1798
- pseudo: {},
1799
- media: {},
1800
- } satisfies GroupState
1801
-
1802
- if (process.env.NODE_ENV === 'development' && !componentContext.groups) {
1803
- console.debug(`No context group found`)
1804
- }
1805
-
1806
- return componentContext.groups?.subscribe?.((name, { layout, pseudo }) => {
1807
- if (pseudo && pseudoGroups?.has(String(name))) {
1808
- // we emit a partial so merge it + change reference so mergeIfNotShallowEqual runs
1809
- Object.assign(current.pseudo, pseudo)
1810
- persist()
1811
- } else if (layout && mediaGroups) {
1812
- const mediaState = getMediaState(mediaGroups, layout)
1813
- const next = mergeIfNotShallowEqual(current.media, mediaState)
1814
- if (next !== current.media) {
1815
- Object.assign(current.media, next)
1816
- persist()
1817
- }
1818
- }
1819
- function persist() {
1820
- // force it to be referentially different so it always updates
1821
- const group = {
1822
- ...state.group,
1823
- [name]: current,
1824
- }
1825
- setStateShallow({
1826
- group,
1827
- })
1828
- }
1829
- })
1830
- }
1831
- }
@@ -64,11 +64,18 @@ export function createTamagui<Conf extends CreateTamaguiProps>(
64
64
  let foundThemes: DedupedThemes | undefined
65
65
  if (configIn.themes) {
66
66
  const noThemes = Object.keys(configIn.themes).length === 0
67
- foundThemes = scanAllSheets(noThemes, tokensParsed)
67
+ if (noThemes) {
68
+ foundThemes = scanAllSheets(noThemes, tokensParsed)
69
+ }
70
+ if (process.env.TAMAGUI_REACT_19 && process.env.TAMAGUI_SKIP_THEME_OPTIMIZATION) {
71
+ // save some bundle
72
+ } else {
73
+ if (noThemes) {
74
+ listenForSheetChanges()
75
+ }
76
+ }
68
77
  }
69
78
 
70
- listenForSheetChanges()
71
-
72
79
  let fontSizeTokens: Set<string> | null = null
73
80
  let fontsParsed:
74
81
  | {
@@ -164,7 +171,7 @@ export function createTamagui<Conf extends CreateTamaguiProps>(
164
171
  }
165
172
  }
166
173
 
167
- const themesIn = { ...configIn.themes } as ThemesLikeObject
174
+ const themesIn = configIn.themes as ThemesLikeObject
168
175
  const dedupedThemes = foundThemes ?? getThemesDeduped(themesIn)
169
176
  const themes = proxyThemesToParents(dedupedThemes)
170
177
 
@@ -71,6 +71,7 @@ import { pseudoDescriptors, pseudoPriorities } from './pseudoDescriptors'
71
71
  import { skipProps } from './skipProps'
72
72
  import { transformsToString } from './transformsToString'
73
73
  import { isActivePlatform } from './isActivePlatform'
74
+ import { sortString } from './sortString'
74
75
 
75
76
  const consoleGroupCollapsed = isWeb ? console.groupCollapsed : console.info
76
77
 
@@ -1108,7 +1109,7 @@ export const getSplitStyles: StyleSplitter = (
1108
1109
  // to the "flat" transform props
1109
1110
  styleState.style ||= {}
1110
1111
  Object.entries(styleState.flatTransforms)
1111
- .sort(([a], [b]) => a.localeCompare(b))
1112
+ .sort(([a], [b]) => sortString(a, b))
1112
1113
  .forEach(([key, val]) => {
1113
1114
  mergeTransform(styleState.style!, key, val, true)
1114
1115
  })
@@ -6,6 +6,7 @@ import { variableToString } from '../createVariable'
6
6
  import type { CreateTamaguiProps, ThemeParsed } from '../types'
7
7
  import { tokensValueToVariable } from './registerCSSVariable'
8
8
  import { getSetting } from '../config'
9
+ import { sortString } from './sortString'
9
10
 
10
11
  const darkLight = ['dark', 'light']
11
12
  const lightDark = ['light', 'dark']
@@ -114,7 +115,7 @@ export function getThemeCSSRules(props: {
114
115
  }
115
116
  }
116
117
 
117
- const selectors = [...selectorsSet].sort((a, b) => a.localeCompare(b))
118
+ const selectors = [...selectorsSet].sort(sortString)
118
119
 
119
120
  // only do our :root attach if it's not light/dark - not support sub themes on root saves a lot of effort/size
120
121
  // this isBaseTheme logic could probably be done more efficiently above
@@ -39,6 +39,7 @@ function addTransform(identifier: string, css: string, rule?: CSSRule) {
39
39
  }
40
40
  }
41
41
 
42
+ // once react 19 onyl supported we can remove most of this
42
43
  // gets existing ones (client side)
43
44
  // takes ~0.1ms for a fairly large page
44
45
  // used now for three things:
@@ -0,0 +1 @@
1
+ export const sortString = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0)
@@ -0,0 +1,56 @@
1
+ import { mergeIfNotShallowEqual } from './createShallowSetState'
2
+ import { getMediaState } from '../hooks/useMedia'
3
+ import type { TamaguiComponentState, ComponentContextI, GroupState } from '../types'
4
+
5
+ export const subscribeToContextGroup = ({
6
+ disabled = false,
7
+ setStateShallow,
8
+ pseudoGroups,
9
+ mediaGroups,
10
+ componentContext,
11
+ state,
12
+ }: {
13
+ disabled?: boolean
14
+ setStateShallow: (next?: Partial<TamaguiComponentState> | undefined) => void
15
+ pseudoGroups?: Set<string>
16
+ mediaGroups?: Set<string>
17
+ componentContext: ComponentContextI
18
+ state: TamaguiComponentState
19
+ }) => {
20
+ // parent group pseudo listening
21
+ if (pseudoGroups || mediaGroups) {
22
+ const current = {
23
+ pseudo: {},
24
+ media: {},
25
+ } satisfies GroupState
26
+
27
+ if (process.env.NODE_ENV === 'development' && !componentContext.groups) {
28
+ console.debug(`No context group found`)
29
+ }
30
+
31
+ return componentContext.groups?.subscribe?.((name, { layout, pseudo }) => {
32
+ if (pseudo && pseudoGroups?.has(String(name))) {
33
+ // we emit a partial so merge it + change reference so mergeIfNotShallowEqual runs
34
+ Object.assign(current.pseudo, pseudo)
35
+ persist()
36
+ } else if (layout && mediaGroups) {
37
+ const mediaState = getMediaState(mediaGroups, layout)
38
+ const next = mergeIfNotShallowEqual(current.media, mediaState)
39
+ if (next !== current.media) {
40
+ Object.assign(current.media, next)
41
+ persist()
42
+ }
43
+ }
44
+ function persist() {
45
+ // force it to be referentially different so it always updates
46
+ const group = {
47
+ ...state.group,
48
+ [name]: current,
49
+ }
50
+ setStateShallow({
51
+ group,
52
+ })
53
+ }
54
+ })
55
+ }
56
+ }