@tamagui/core 1.0.2 → 1.0.4

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 (117) hide show
  1. package/dist/cjs/config.js +1 -1
  2. package/dist/cjs/config.js.map +2 -2
  3. package/dist/cjs/createComponent.js +51 -95
  4. package/dist/cjs/createComponent.js.map +2 -2
  5. package/dist/cjs/createTamagui.js +7 -4
  6. package/dist/cjs/createTamagui.js.map +2 -2
  7. package/dist/cjs/createVariable.js +1 -1
  8. package/dist/cjs/createVariable.js.map +2 -2
  9. package/dist/cjs/createVariables.js +12 -3
  10. package/dist/cjs/createVariables.js.map +2 -2
  11. package/dist/cjs/helpers/ThemeManager.js +12 -2
  12. package/dist/cjs/helpers/ThemeManager.js.map +2 -2
  13. package/dist/cjs/helpers/getSize.js +9 -5
  14. package/dist/cjs/helpers/getSize.js.map +2 -2
  15. package/dist/cjs/helpers/getSplitStyles.js +39 -45
  16. package/dist/cjs/helpers/getSplitStyles.js.map +2 -2
  17. package/dist/cjs/helpers/getStylesAtomic.js +32 -26
  18. package/dist/cjs/helpers/getStylesAtomic.js.map +2 -2
  19. package/dist/cjs/helpers/getStylesAtomic.native.js +6 -2
  20. package/dist/cjs/helpers/getStylesAtomic.native.js.map +2 -2
  21. package/dist/cjs/helpers/insertStyleRule.js +12 -1
  22. package/dist/cjs/helpers/insertStyleRule.js.map +2 -2
  23. package/dist/cjs/helpers/normalizeColor.js +8 -7
  24. package/dist/cjs/helpers/normalizeColor.js.map +2 -2
  25. package/dist/cjs/hooks/useMedia.js +37 -36
  26. package/dist/cjs/hooks/useMedia.js.map +2 -2
  27. package/dist/cjs/hooks/useTheme.js +28 -42
  28. package/dist/cjs/hooks/useTheme.js.map +3 -3
  29. package/dist/cjs/index.js +0 -1
  30. package/dist/cjs/index.js.map +2 -2
  31. package/dist/cjs/styled.js +4 -1
  32. package/dist/cjs/styled.js.map +2 -2
  33. package/dist/cjs/types.js.map +1 -1
  34. package/dist/cjs/views/Slot.js +1 -33
  35. package/dist/cjs/views/Slot.js.map +3 -3
  36. package/dist/cjs/views/Stack.test.js +4 -6
  37. package/dist/cjs/views/Stack.test.js.map +2 -2
  38. package/dist/cjs/views/Theme.js +45 -41
  39. package/dist/cjs/views/Theme.js.map +2 -2
  40. package/dist/cjs/views/ThemeProvider.js +1 -1
  41. package/dist/cjs/views/ThemeProvider.js.map +2 -2
  42. package/dist/esm/config.js +1 -1
  43. package/dist/esm/config.js.map +2 -2
  44. package/dist/esm/createComponent.js +53 -97
  45. package/dist/esm/createComponent.js.map +2 -2
  46. package/dist/esm/createTamagui.js +8 -5
  47. package/dist/esm/createTamagui.js.map +2 -2
  48. package/dist/esm/createVariable.js +1 -1
  49. package/dist/esm/createVariable.js.map +2 -2
  50. package/dist/esm/createVariables.js +10 -2
  51. package/dist/esm/createVariables.js.map +2 -2
  52. package/dist/esm/helpers/ThemeManager.js +13 -3
  53. package/dist/esm/helpers/ThemeManager.js.map +2 -2
  54. package/dist/esm/helpers/getSize.js +9 -5
  55. package/dist/esm/helpers/getSize.js.map +2 -2
  56. package/dist/esm/helpers/getSplitStyles.js +39 -45
  57. package/dist/esm/helpers/getSplitStyles.js.map +2 -2
  58. package/dist/esm/helpers/getStylesAtomic.js +31 -26
  59. package/dist/esm/helpers/getStylesAtomic.js.map +2 -2
  60. package/dist/esm/helpers/getStylesAtomic.native.js +4 -1
  61. package/dist/esm/helpers/getStylesAtomic.native.js.map +2 -2
  62. package/dist/esm/helpers/insertStyleRule.js +12 -1
  63. package/dist/esm/helpers/insertStyleRule.js.map +2 -2
  64. package/dist/esm/helpers/normalizeColor.js +8 -7
  65. package/dist/esm/helpers/normalizeColor.js.map +2 -2
  66. package/dist/esm/hooks/useMedia.js +37 -37
  67. package/dist/esm/hooks/useMedia.js.map +2 -2
  68. package/dist/esm/hooks/useTheme.js +25 -39
  69. package/dist/esm/hooks/useTheme.js.map +3 -3
  70. package/dist/esm/index.js +0 -1
  71. package/dist/esm/index.js.map +2 -2
  72. package/dist/esm/styled.js +4 -1
  73. package/dist/esm/styled.js.map +2 -2
  74. package/dist/esm/views/Slot.js +1 -33
  75. package/dist/esm/views/Slot.js.map +3 -3
  76. package/dist/esm/views/Stack.test.js +1 -3
  77. package/dist/esm/views/Stack.test.js.map +2 -2
  78. package/dist/esm/views/Theme.js +44 -40
  79. package/dist/esm/views/Theme.js.map +2 -2
  80. package/dist/esm/views/ThemeProvider.js +1 -1
  81. package/dist/esm/views/ThemeProvider.js.map +2 -2
  82. package/package.json +11 -11
  83. package/reset.css +4 -0
  84. package/src/__snapshots__/createTokens.test.ts.snap +90 -195
  85. package/src/config.ts +1 -1
  86. package/src/createComponent.tsx +72 -152
  87. package/src/createTamagui.ts +8 -5
  88. package/src/createVariable.ts +2 -1
  89. package/src/createVariables.ts +14 -1
  90. package/src/helpers/ThemeManager.tsx +13 -3
  91. package/src/helpers/getSplitStyles.tsx +43 -53
  92. package/src/helpers/getStylesAtomic.native.ts +2 -0
  93. package/src/helpers/getStylesAtomic.ts +36 -31
  94. package/src/helpers/insertStyleRule.tsx +12 -2
  95. package/src/helpers/normalizeColor.ts +8 -7
  96. package/src/hooks/useMedia.tsx +53 -48
  97. package/src/hooks/useTheme.tsx +37 -56
  98. package/src/index.ts +0 -1
  99. package/src/styled.tsx +4 -1
  100. package/src/types.tsx +7 -3
  101. package/src/views/Slot.tsx +3 -59
  102. package/src/views/Stack.test.tsx +1 -4
  103. package/src/views/Theme.tsx +60 -54
  104. package/src/views/ThemeProvider.tsx +1 -1
  105. package/types/createVariables.d.ts +1 -0
  106. package/types/helpers/ThemeManager.d.ts +1 -1
  107. package/types/helpers/getSplitStyles.d.ts +2 -3
  108. package/types/helpers/getStylesAtomic.d.ts +1 -0
  109. package/types/helpers/getStylesAtomic.native.d.ts +1 -0
  110. package/types/hooks/useMedia.d.ts +8 -4
  111. package/types/hooks/useTheme.d.ts +16 -2
  112. package/types/index.d.ts +0 -1
  113. package/types/types.d.ts +3 -2
  114. package/types/views/Slot.d.ts +5 -4
  115. package/types/views/Theme.d.ts +5 -2
  116. package/src/helpers/cache.ts +0 -13
  117. package/src/helpers/getSize.ts +0 -23
@@ -58,8 +58,7 @@ import {
58
58
  import { pseudoDescriptors } from './pseudoDescriptors'
59
59
 
60
60
  type GetStyleResult = {
61
- pseudos: PseudoStyles
62
- medias: Record<MediaQueryKey, ViewStyle>
61
+ pseudos?: PseudoStyles
63
62
  style: ViewStyle
64
63
  classNames: ClassNamesObject
65
64
  rulesToInsert: RulesToInsert
@@ -222,6 +221,10 @@ export const getSplitStyles: StyleSplitter = (
222
221
  return cache.get(props)
223
222
  }
224
223
 
224
+ if (props.debug) {
225
+ console.log('props', props)
226
+ }
227
+
225
228
  conf = conf || getConfig()
226
229
  const { shorthands } = conf
227
230
  const {
@@ -235,14 +238,13 @@ export const getSplitStyles: StyleSplitter = (
235
238
  } = staticConfig
236
239
  const validStyleProps = staticConfig.isText ? stylePropsText : validStyles
237
240
  const viewProps: GetStyleResult['viewProps'] = {}
238
- const pseudos: PseudoStyles = {}
241
+ let pseudos: PseudoStyles | null = null
239
242
  let psuedosUsed: Record<string, number> | null = null
240
- const medias: Record<MediaQueryKey, ViewStyle> = {}
241
243
  const mediaState = state.mediaState || globalMediaState
242
244
  const usedKeys: Record<string, number> = {}
243
245
  const propKeys = Object.keys(props)
244
246
  let space: SpaceTokens | null = props.space
245
- let hasMedia: boolean | 'space' = space ? 'space' : false
247
+ let hasMedia: boolean | string[] = false
246
248
 
247
249
  const shouldDoClasses =
248
250
  staticConfig.acceptsClassName && (isWeb || IS_STATIC) && !state.noClassNames
@@ -617,6 +619,7 @@ export const getSplitStyles: StyleSplitter = (
617
619
  }
618
620
 
619
621
  if (!shouldDoClasses || IS_STATIC) {
622
+ pseudos ||= {}
620
623
  pseudos[key] ||= {}
621
624
  Object.assign(pseudos[key], pseudoStyleObject)
622
625
  }
@@ -675,6 +678,7 @@ export const getSplitStyles: StyleSplitter = (
675
678
  const curImportance = psuedosUsed[importance] || 0
676
679
  if (importance >= curImportance) {
677
680
  psuedosUsed[pkey] = importance
681
+ pseudos ||= {}
678
682
  pseudos[key] ||= {}
679
683
  pseudos[key][pkey] = val
680
684
  // eslint-disable-next-line no-console
@@ -707,29 +711,37 @@ export const getSplitStyles: StyleSplitter = (
707
711
  const mediaKeyShort = key.slice(1)
708
712
 
709
713
  if (process.env.NODE_ENV === 'development' && debug === 'verbose') {
714
+ // prettier-ignore
710
715
  // eslint-disable-next-line no-console
711
- console.log(` 📺 ${key}`, mediaState[mediaKeyShort], {
712
- key,
713
- mediaStyle,
714
- props,
715
- shouldDoClasses,
716
- mediaState: { ...mediaState },
717
- })
716
+ console.log(` 📺 ${key}`, { key, mediaStyle, props, shouldDoClasses })
718
717
  }
719
718
 
720
719
  if ('space' in mediaStyle) {
721
- hasMedia = 'space'
720
+ if (!Array.isArray(hasMedia)) {
721
+ hasMedia = []
722
+ }
723
+ hasMedia.push(mediaKeyShort)
722
724
  }
723
725
 
724
726
  if (shouldDoClasses) {
725
727
  if ('space' in mediaStyle) {
726
- const val = valInit.space
727
728
  delete mediaStyle['space']
728
- if (
729
- mediaState[mediaKeyShort] &&
730
- getMediaImportanceIfMoreImportant(mediaKeyShort, 'space', usedKeys)
731
- ) {
732
- space = val
729
+ if (mediaState[mediaKeyShort]) {
730
+ const val = valInit.space
731
+ const importance = getMediaImportanceIfMoreImportant(
732
+ mediaKeyShort,
733
+ 'space',
734
+ usedKeys
735
+ )
736
+ if (importance) {
737
+ space = val
738
+ usedKeys['space'] = importance
739
+ if (process.env.NODE_ENV === 'development' && debug === 'verbose') {
740
+ console.log(
741
+ `Found more important space for current media ${mediaKeyShort}: ${val} (importance: ${importance})`
742
+ )
743
+ }
744
+ }
733
745
  }
734
746
  }
735
747
  const mediaStyles = getStylesAtomic(mediaStyle)
@@ -879,7 +891,6 @@ export const getSplitStyles: StyleSplitter = (
879
891
  fontFamily,
880
892
  viewProps,
881
893
  style,
882
- medias,
883
894
  pseudos,
884
895
  classNames,
885
896
  rulesToInsert,
@@ -893,15 +904,8 @@ export const getSplitStyles: StyleSplitter = (
893
904
  if (isDevTools) {
894
905
  // eslint-disable-next-line no-console
895
906
  console.groupCollapsed(' 🔹 styles =>')
896
- const logs = {
897
- transforms,
898
- viewProps,
899
- state,
900
- rulesToInsert,
901
- parentSplitStyles,
902
- flatTransforms,
903
- result,
904
- }
907
+ // prettier-ignore
908
+ const logs = { transforms, viewProps, state, rulesToInsert, parentSplitStyles, flatTransforms, result }
905
909
  for (const key in { ...result, ...logs }) {
906
910
  // eslint-disable-next-line no-console
907
911
  console.log(key, logs[key])
@@ -926,35 +930,21 @@ function mergeClassName(
926
930
  if (process.env.TAMAGUI_TARGET === 'web') {
927
931
  // empty classnames passed by compiler sometimes
928
932
  if (!val) return
929
- if (val.startsWith('_transform-')) {
930
- const namespace: TransformNamespaceKey = isMediaOrPseudo ? key : 'transform'
933
+ if (val[0] === '_' && val.startsWith('_transform-')) {
934
+ const ns: TransformNamespaceKey = isMediaOrPseudo ? key : 'transform'
931
935
  let transform = insertedTransforms[val]
932
- if (isClient) {
933
- if (!transform) {
934
- // HMR or loaded a new chunk
935
- console.warn(`got a new ting`)
936
- scanAllSheets()
937
- transform = insertedTransforms[val]
938
- if (process.env.NODE_ENV === 'development') {
939
- if (!transform) {
940
- // eslint-disable-next-line no-console
941
- console.warn('no transform found', { insertedTransforms, val })
942
- }
943
- }
944
- }
945
- if (!transform) {
946
- if (isWeb && val[0] !== '_') {
947
- // runtime insert
948
- transform = val
949
- }
936
+ if (isClient && !transform) {
937
+ scanAllSheets() // HMR or loaded a new chunk
938
+ transform = insertedTransforms[val]
939
+ if (!transform && isWeb && val[0] !== '_') {
940
+ transform = val // runtime insert
950
941
  }
951
942
  }
952
- transforms[namespace] = transforms[namespace] || ['', '']
953
- const identifier = val.replace('_transform', '')
954
- transforms[namespace][0] += identifier
943
+ transforms[ns] ||= ['', '']
944
+ transforms[ns][0] += val.replace('_transform', '')
955
945
  // ssr doesn't need to do anything just make the right classname
956
946
  if (transform) {
957
- transforms[namespace][1] += transform
947
+ transforms[ns][1] += transform
958
948
  }
959
949
  } else {
960
950
  classNames[key] = val
@@ -2,3 +2,5 @@ export function getStylesAtomic() {
2
2
  // eslint-disable-next-line no-console
3
3
  console.warn('no-op native')
4
4
  }
5
+
6
+ export const startClearStyleCacheInterval = () => {}
@@ -8,7 +8,6 @@ import type { TextStyle, ViewStyle } from 'react-native'
8
8
 
9
9
  import { getConfig } from '../config'
10
10
  import { TamaguiInternalConfig } from '../types'
11
- import { Style, cache } from './cache'
12
11
  import { defaultOffset } from './expandStyles'
13
12
  import { normalizeValueWithProperty } from './normalizeValueWithProperty.js'
14
13
  import { PseudoDescriptor, pseudoDescriptors } from './pseudoDescriptors'
@@ -48,17 +47,31 @@ export function getStylesAtomic(stylesIn: ViewStyleWithPseudos) {
48
47
  return res
49
48
  }
50
49
 
50
+ const cache = new Map()
51
+
52
+ export const startClearStyleCacheInterval = () => {
53
+ setInterval(() => {
54
+ cache.clear()
55
+ }, 10_000)
56
+ }
57
+
51
58
  export function getAtomicStyle(
52
59
  style: ViewOrTextStyle,
53
60
  pseudo?: PseudoDescriptor
54
61
  ): StyleObject[] {
62
+ if (!style) return []
63
+ const key = JSON.stringify(style) + (pseudo ? JSON.stringify(pseudo) : '')
64
+ if (cache.has(key)) {
65
+ return cache.get(key)
66
+ }
55
67
  if (process.env.NODE_ENV === 'development') {
56
68
  if (!style || typeof style !== 'object') {
57
69
  throw new Error(`Wrong style type: "${typeof style}": ${style}`)
58
70
  }
59
71
  }
60
- if (!style) return []
61
- return generateAtomicStyles(style, pseudo)
72
+ const out = generateAtomicStyles(style, pseudo)
73
+ cache.set(key, out)
74
+ return out
62
75
  }
63
76
 
64
77
  let conf: TamaguiInternalConfig
@@ -96,35 +109,27 @@ const generateAtomicStyles = (
96
109
  const out: StyleObject[] = []
97
110
  for (const key in style) {
98
111
  const value = style[key]
99
- if (value != null && value !== undefined) {
100
- const uid = key + (pseudo?.name || '')
101
- const cachedResult = cache.get(uid, value)
102
- if (cachedResult !== undefined) {
103
- out.push(cachedResult)
104
- } else {
105
- const hash = presetHashes[value]
106
- ? value
107
- : typeof value === 'string'
108
- ? simpleHash(value)
109
- : `${value}`.replace('.', 'dot')
110
-
111
- const pseudoPrefix = pseudo ? `0${pseudo.name}-` : ''
112
- const shortProp = conf.inverseShorthands[key] || key
113
- const identifier = `_${shortProp}-${pseudoPrefix}${hash}`
114
- const rules = createAtomicRules(identifier, key, value, pseudo)
115
- const styleObject: StyleObject = {
116
- property: key,
117
- pseudo: pseudo?.name as any,
118
- value,
119
- identifier,
120
- rules,
121
- }
122
-
123
- cache.set(uid, value, styleObject)
124
- out.push(styleObject)
125
- }
112
+ if (value == null || value == undefined) continue
113
+ const hash = presetHashes[value]
114
+ ? value
115
+ : typeof value === 'string'
116
+ ? simpleHash(value)
117
+ : `${value}`.replace('.', 'dot')
118
+
119
+ const pseudoPrefix = pseudo ? `0${pseudo.name}-` : ''
120
+ const shortProp = conf.inverseShorthands[key] || key
121
+ const identifier = `_${shortProp}-${pseudoPrefix}${hash}`
122
+ const rules = createAtomicRules(identifier, key, value, pseudo)
123
+ const styleObject: StyleObject = {
124
+ property: key,
125
+ pseudo: pseudo?.name as any,
126
+ value,
127
+ identifier,
128
+ rules,
126
129
  }
130
+ out.push(styleObject)
127
131
  }
132
+
128
133
  return out
129
134
  }
130
135
 
@@ -166,7 +171,7 @@ export function styleToCSS(style: Record<string, any>) {
166
171
  }
167
172
  }
168
173
 
169
- function createDeclarationBlock(style: Style, important = false) {
174
+ function createDeclarationBlock(style: Record<string, any>, important = false) {
170
175
  let next = ''
171
176
  for (const key in style) {
172
177
  const prop = hyphenateStyleName(key)
@@ -134,7 +134,6 @@ function getTamaguiSelector(
134
134
  ): readonly [string, CSSStyleRule] | null {
135
135
  if (rule instanceof CSSStyleRule) {
136
136
  const text = rule.selectorText
137
- // .startsWith('._')
138
137
  if (text[0] === '.' && text[1] === '_') {
139
138
  return [text.slice(1), rule]
140
139
  }
@@ -185,7 +184,18 @@ export function insertStyleRules(rulesToInsert: RulesToInsert) {
185
184
  updateRules(identifier, rules)
186
185
  if (sheet) {
187
186
  for (const rule of rules) {
188
- sheet.insertRule(rule, sheet.cssRules.length)
187
+ try {
188
+ sheet.insertRule(rule, sheet.cssRules.length)
189
+ } catch (err) {
190
+ if (process.env.NODE_ENV !== 'production') {
191
+ console.groupCollapsed(
192
+ `Error inserting rule into CSSStyleSheet: ${String(err)}`
193
+ )
194
+ console.log({ rule, rulesToInsert })
195
+ console.trace()
196
+ console.groupEnd()
197
+ }
198
+ }
189
199
  }
190
200
  }
191
201
  }
@@ -1,3 +1,4 @@
1
+ import { isWeb } from '@tamagui/constants'
1
2
  import normalizeCSSColor from 'normalize-css-color'
2
3
 
3
4
  export const normalizeColor = (color?: number | string | null, opacity?: number) => {
@@ -6,7 +7,7 @@ export const normalizeColor = (color?: number | string | null, opacity?: number)
6
7
  }
7
8
  if (
8
9
  color[0] === '$' ||
9
- (process.env.TAMAGUI_TARGET === 'web' &&
10
+ (isWeb &&
10
11
  typeof color === 'string' &&
11
12
  (webColors[color] || color[0] === '_' || color.startsWith('var(')))
12
13
  ) {
@@ -30,12 +31,12 @@ const webColors = {
30
31
  }
31
32
 
32
33
  export function colorToRGBA(color: string | number) {
33
- const colorInt = processColor(color)
34
- if (colorInt != null) {
35
- const r = (colorInt >> 16) & 255
36
- const g = (colorInt >> 8) & 255
37
- const b = colorInt & 255
38
- const a = ((colorInt >> 24) & 255) / 255
34
+ const ci = processColor(color)
35
+ if (ci != null) {
36
+ const r = (ci >> 16) & 255
37
+ const g = (ci >> 8) & 255
38
+ const b = ci & 255
39
+ const a = ((ci >> 24) & 255) / 255
39
40
  return [r, g, b, a] as const
40
41
  }
41
42
  return null
@@ -1,4 +1,4 @@
1
- import { useEffect, useMemo, useSyncExternalStore } from 'react'
1
+ import { startTransition, useEffect, useMemo, useSyncExternalStore } from 'react'
2
2
 
3
3
  import { getConfig } from '../config'
4
4
  import { createProxy } from '../helpers/createProxy'
@@ -12,7 +12,7 @@ import {
12
12
  } from '../types'
13
13
  import { useSafeRef } from './useSafeRef'
14
14
 
15
- export const mediaState: MediaQueryState =
15
+ export let mediaState: MediaQueryState =
16
16
  // development only safeguard
17
17
  process.env.NODE_ENV === 'development'
18
18
  ? createProxy(
@@ -109,7 +109,7 @@ function setupMediaListeners() {
109
109
  function update() {
110
110
  const next = !!getMatch().matches
111
111
  if (next === mediaState[key]) return
112
- mediaState[key] = next
112
+ mediaState = { ...mediaState, [key]: next }
113
113
  updateCurrentState()
114
114
  }
115
115
  }
@@ -123,12 +123,17 @@ export function useMediaListeners(config: TamaguiInternalConfig) {
123
123
  }
124
124
 
125
125
  const currentStateListeners = new Set<any>()
126
+ let isFlushing = false
127
+
126
128
  function updateCurrentState() {
127
- currentStateListeners.forEach((cb) => cb(mediaState))
128
- }
129
- function subscribe(subscriber: any) {
130
- currentStateListeners.add(subscriber)
131
- return () => currentStateListeners.delete(subscriber)
129
+ if (isFlushing) return
130
+ isFlushing = true
131
+ setTimeout(() => {
132
+ startTransition(() => {
133
+ currentStateListeners.forEach((cb) => cb(mediaState))
134
+ })
135
+ isFlushing = false
136
+ }, 0)
132
137
  }
133
138
 
134
139
  type MediaKeysState = {
@@ -139,61 +144,61 @@ type UseMediaState = {
139
144
  [key in MediaQueryKey]: boolean
140
145
  }
141
146
 
142
- const shouldUpdate = new WeakMap<any, boolean>()
143
- export function setMediaShouldUpdate(ref: any, val: boolean) {
144
- return shouldUpdate.set(ref, val)
147
+ type UpdateState = {
148
+ enabled: boolean
149
+ keys: MediaQueryKey[]
145
150
  }
146
151
 
147
- type UseMediaInternalState = { prev: MediaKeysState; next: MediaKeysState }
148
- const empty = {}
149
- let initialUseState: UseMediaInternalState
152
+ const shouldUpdate = new WeakMap<any, UpdateState>()
150
153
 
151
- export function useMedia(): UseMediaState {
152
- // setup once
153
- initialUseState ||= {
154
- prev: initState,
155
- next: empty,
156
- }
154
+ export function setMediaShouldUpdate(ref: any, props: UpdateState) {
155
+ return shouldUpdate.set(ref, props)
156
+ }
157
+
158
+ type UseMediaInternalState = {
159
+ prev: MediaKeysState
160
+ touched?: Set<string>
161
+ }
162
+
163
+ function subscribe(subscriber: any) {
164
+ currentStateListeners.add(subscriber)
165
+ return () => currentStateListeners.delete(subscriber)
166
+ }
157
167
 
158
- const keys = useSafeRef<UseMediaInternalState>(initialUseState)
168
+ export function useMedia(uid?: any, debug?: any): UseMediaState {
169
+ const internal = useSafeRef<UseMediaInternalState>(undefined as any)
170
+ if (!internal.current) {
171
+ internal.current = {
172
+ prev: initState,
173
+ }
174
+ }
159
175
  const state = useSyncExternalStore<MediaQueryState>(
160
176
  subscribe,
161
177
  () => {
162
- const curState = keys.current
163
- const preventUpdate = arguments[0] && shouldUpdate.get(arguments[0]) !== true
164
- if (preventUpdate) {
165
- return curState.prev
166
- }
167
-
168
- let didUpdate = false
169
-
170
- // ensure we keep the minimal object of only used keys
171
- const next = {}
172
-
173
- for (const key in curState.next) {
174
- const val = mediaState[key]
175
- if (curState.prev[key] !== val) {
176
- didUpdate = true
177
- }
178
- next[key] = val
178
+ const { touched, prev } = internal.current
179
+ const componentState = uid ? shouldUpdate.get(uid) : undefined
180
+ if (componentState?.enabled === false) {
181
+ return prev
179
182
  }
183
+ const testKeys =
184
+ componentState?.keys ?? (componentState?.enabled && touched ? [...touched] : null)
180
185
 
181
- if (!didUpdate) {
182
- return curState.prev
186
+ if (testKeys?.every((key) => mediaState[key] === prev[key])) {
187
+ return prev
183
188
  }
184
189
 
185
- keys.current = { next, prev: next }
186
- return next
190
+ internal.current.prev = mediaState
191
+ return mediaState
187
192
  },
188
193
  () => initState
189
194
  )
190
195
 
191
196
  return useMemo(() => {
192
- return new Proxy(state === empty ? initState : state, {
193
- get(_, key: string) {
194
- if (!(key in keys.current.next)) {
195
- keys.current.next[key] = true
196
- keys.current = { ...keys.current }
197
+ return new Proxy(state, {
198
+ get(_, key) {
199
+ if (typeof key === 'string') {
200
+ internal.current.touched ||= new Set()
201
+ internal.current.touched.add(key)
197
202
  }
198
203
  return Reflect.get(state, key)
199
204
  },
@@ -205,7 +210,7 @@ export function useMedia(): UseMediaState {
205
210
  * Useful for more complex components that need access to the currently active props,
206
211
  * accounting for the currently active media queries.
207
212
  *
208
- * Use sparingly, is will loop props and trigger re-render on all media queries.
213
+ * Use sparingly, it will loop props and trigger re-render on all media queries.
209
214
  *
210
215
  * */
211
216
  export function useMediaPropsActive<A extends Object>(
@@ -39,31 +39,36 @@ interface UseThemeState {
39
39
  }
40
40
 
41
41
  export const useTheme = (props: ThemeProps = emptyProps): ThemeParsed => {
42
- if (isRSC) {
43
- return getDefaultThemeProxied()
44
- }
42
+ return (isRSC ? null : useThemeWithState(props)?.theme) || getDefaultThemeProxied()
43
+ }
45
44
 
45
+ export const useThemeWithState = (props: ThemeProps) => {
46
46
  const state = useServerRef() as React.MutableRefObject<UseThemeState>
47
47
  if (isClient && !state.current) {
48
48
  state.current = {
49
49
  keys: new Set(),
50
50
  }
51
51
  }
52
+
53
+ // clear at start of every render to track usages
52
54
  state.current?.keys.clear()
53
55
 
54
- const { name, theme, themeManager, className, isNewTheme } = useChangeThemeEffect(
56
+ const changedTheme = useChangeThemeEffect(
55
57
  props,
56
58
  false,
57
- isClient ? () => state.current.keys.size === 0 : undefined
59
+ isClient
60
+ ? () => {
61
+ return props.shouldUpdate?.() ?? state.current.keys.size === 0
62
+ }
63
+ : undefined
58
64
  )
59
65
 
66
+ const { themeManager, isNewTheme, theme, name, className } = changedTheme
67
+
60
68
  if (process.env.NODE_ENV === 'development') {
61
69
  // ensure we aren't creating too many ThemeManagers
62
- if (
63
- isWeb &&
64
- isNewTheme &&
65
- className === themeManager?.parentManager?.state.className
66
- ) {
70
+ // prettier-ignore
71
+ if (isWeb && isNewTheme && className === themeManager?.parentManager?.state.className) {
67
72
  console.error('Should always change, duplicating ThemeMananger bug', themeManager)
68
73
  }
69
74
  if (props.debug === 'verbose') {
@@ -76,38 +81,29 @@ export const useTheme = (props: ThemeProps = emptyProps): ThemeParsed => {
76
81
  }
77
82
  }
78
83
 
79
- if (!theme) {
84
+ if (!changedTheme.theme) {
80
85
  if (process.env.NODE_ENV === 'development') {
81
86
  console.warn('No theme found', name, props, themeManager)
82
87
  }
83
- return getDefaultThemeProxied()
88
+ return null
84
89
  }
85
90
 
86
- return useMemo(() => {
87
- return getThemeProxied({
88
- isNewTheme,
89
- theme,
90
- name,
91
- className,
92
- themeManager,
93
- state,
94
- })
91
+ const proxiedTheme = useMemo(() => {
92
+ return getThemeProxied(changedTheme as any)
95
93
  }, [theme, isNewTheme, name, className, themeManager])
94
+
95
+ return {
96
+ ...changedTheme,
97
+ theme: proxiedTheme,
98
+ }
96
99
  }
97
100
 
98
- function getThemeProxied({
101
+ export function getThemeProxied({
99
102
  theme,
100
- name,
101
- className,
102
103
  themeManager,
103
- isNewTheme,
104
104
  state,
105
- }: {
106
- theme: any
107
- name: string
108
- className?: string
109
- themeManager?: ThemeManager | null
110
- isNewTheme?: boolean
105
+ }: Partial<ChangedThemeResponse> & {
106
+ theme: ThemeParsed
111
107
  state?: React.RefObject<UseThemeState>
112
108
  }) {
113
109
  return createProxy(theme, {
@@ -121,26 +117,19 @@ function getThemeProxied({
121
117
  get(_, key) {
122
118
  if (key === GetThemeUnwrapped) {
123
119
  return theme
124
- } else if (key === GetThemeManager) {
125
- return themeManager
126
- } else if (key === GetIsNewTheme) {
127
- return isNewTheme
128
- } else if (key === 'name') {
129
- return name
130
- } else if (key === 'className') {
131
- return className
132
- } else if (
133
- !name ||
120
+ }
121
+ if (
122
+ key === 'undefined' ||
134
123
  key === '__proto__' ||
135
- typeof key === 'symbol' ||
136
- key === '$typeof'
124
+ key === '$typeof' ||
125
+ typeof key !== 'string' ||
126
+ !themeManager
137
127
  ) {
138
128
  return Reflect.get(_, key)
139
129
  }
140
130
  // auto convert variables to plain
141
- if (key[0] === '$') key = key.slice(1)
142
- if (!themeManager) {
143
- return theme[key]
131
+ if (key[0] === '$') {
132
+ key = key.slice(1)
144
133
  }
145
134
  const val = themeManager.getValue(key)
146
135
  if (val && state) {
@@ -150,7 +139,7 @@ function getThemeProxied({
150
139
  if (subkey === 'val') {
151
140
  state.current!.keys.add(key as any)
152
141
  }
153
- return Reflect.get(val, subkey)
142
+ return Reflect.get(val as any, subkey)
154
143
  },
155
144
  })
156
145
  }
@@ -160,14 +149,6 @@ function getThemeProxied({
160
149
  })
161
150
  }
162
151
 
163
- const GetThemeManager = Symbol()
164
- const GetIsNewTheme = Symbol()
165
-
166
- export const getThemeManager = (theme: any): ThemeManager | undefined =>
167
- theme?.[GetThemeManager]
168
- export const getThemeIsNewTheme = (theme: any): ThemeManager | undefined =>
169
- theme?.[GetIsNewTheme]
170
-
171
152
  export const activeThemeManagers = new Set<ThemeManager>()
172
153
 
173
154
  export const useChangeThemeEffect = (
@@ -191,7 +172,7 @@ export const useChangeThemeEffect = (
191
172
  } = props
192
173
 
193
174
  if (disable) {
194
- if (!parentManager) throw new Error('Disable topmost')
175
+ if (!parentManager) throw `❌`
195
176
  return {
196
177
  ...parentManager.state,
197
178
  isNewTheme: false,
package/src/index.ts CHANGED
@@ -24,7 +24,6 @@ export * from './helpers/useShallowSetState'
24
24
  export * from './helpers/getVariantExtras'
25
25
  export * from './helpers/getAnimationDriver'
26
26
  export * from './helpers/getExpandedShorthands'
27
- export * from './helpers/getSize'
28
27
  export * from './helpers/getSplitStyles'
29
28
  export * from './helpers/getStylesAtomic'
30
29
  export * from './helpers/pseudoDescriptors'