@tamagui/core 1.0.1-beta.193 → 1.0.1-beta.195

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 (220) hide show
  1. package/dist/cjs/Tamagui.js +4 -2
  2. package/dist/cjs/Tamagui.js.map +2 -2
  3. package/dist/cjs/config.js +1 -2
  4. package/dist/cjs/config.js.map +2 -2
  5. package/dist/cjs/constants/constants.js +13 -8
  6. package/dist/cjs/constants/constants.js.map +2 -2
  7. package/dist/cjs/contexts/TextAncestorContext.js +3 -16
  8. package/dist/cjs/contexts/TextAncestorContext.js.map +3 -3
  9. package/dist/cjs/createComponent.js +204 -294
  10. package/dist/cjs/createComponent.js.map +2 -2
  11. package/dist/cjs/createTamagui.js +15 -16
  12. package/dist/cjs/createTamagui.js.map +3 -3
  13. package/dist/cjs/createVariable.js +1 -2
  14. package/dist/cjs/createVariable.js.map +2 -2
  15. package/dist/cjs/helpers/ThemeManager.js +7 -14
  16. package/dist/cjs/helpers/ThemeManager.js.map +2 -2
  17. package/dist/cjs/helpers/{createShallowUpdate.js → cache.js} +20 -21
  18. package/dist/cjs/helpers/cache.js.map +7 -0
  19. package/dist/cjs/helpers/createMediaStyle.js +14 -8
  20. package/dist/cjs/helpers/createMediaStyle.js.map +2 -2
  21. package/dist/cjs/helpers/extendStaticConfig.js +0 -1
  22. package/dist/cjs/helpers/extendStaticConfig.js.map +2 -2
  23. package/dist/cjs/helpers/getSplitStyles.js +212 -93
  24. package/dist/cjs/helpers/getSplitStyles.js.map +3 -3
  25. package/dist/cjs/helpers/getStylesAtomic.js +16 -36
  26. package/dist/cjs/helpers/getStylesAtomic.js.map +3 -3
  27. package/dist/cjs/helpers/insertStyleRule.js +14 -6
  28. package/dist/cjs/helpers/insertStyleRule.js.map +2 -2
  29. package/dist/cjs/helpers/matchMedia.js.map +2 -2
  30. package/dist/cjs/helpers/mergeTransform.js.map +1 -1
  31. package/dist/cjs/helpers/normalizeValueWithProperty.js +32 -2
  32. package/dist/cjs/helpers/normalizeValueWithProperty.js.map +2 -2
  33. package/dist/cjs/helpers/useShallowSetState.js.map +2 -2
  34. package/dist/cjs/hooks/getThemeUnwrapped.js +1 -3
  35. package/dist/cjs/hooks/getThemeUnwrapped.js.map +2 -2
  36. package/dist/cjs/hooks/useElementLayout.js +92 -0
  37. package/dist/cjs/hooks/useElementLayout.js.map +7 -0
  38. package/dist/cjs/hooks/useIsTouchDevice.js +2 -10
  39. package/dist/cjs/hooks/useIsTouchDevice.js.map +2 -2
  40. package/dist/cjs/hooks/useMedia.js +77 -84
  41. package/dist/cjs/hooks/useMedia.js.map +2 -2
  42. package/dist/cjs/hooks/useMedia.test.js +2 -0
  43. package/dist/cjs/{helpers/validate.js.map → hooks/useMedia.test.js.map} +0 -0
  44. package/dist/cjs/hooks/usePressablePressResponder.js +370 -0
  45. package/dist/cjs/hooks/usePressablePressResponder.js.map +7 -0
  46. package/dist/cjs/hooks/useSafeRef.js +13 -15
  47. package/dist/cjs/hooks/useSafeRef.js.map +2 -2
  48. package/dist/cjs/hooks/useTheme.js +3 -4
  49. package/dist/cjs/hooks/useTheme.js.map +2 -2
  50. package/dist/cjs/index.js +1 -17
  51. package/dist/cjs/index.js.map +2 -2
  52. package/dist/cjs/static.js +0 -20
  53. package/dist/cjs/static.js.map +2 -2
  54. package/dist/cjs/styled.js +4 -22
  55. package/dist/cjs/styled.js.map +2 -2
  56. package/dist/cjs/types.js.map +1 -1
  57. package/dist/cjs/{hooks/useIsSSR.js → views/AbsoluteFill.js} +18 -23
  58. package/dist/cjs/views/AbsoluteFill.js.map +7 -0
  59. package/dist/cjs/views/AbsoluteFill.native.js +45 -0
  60. package/dist/cjs/views/AbsoluteFill.native.js.map +7 -0
  61. package/dist/cjs/views/FontLanguage.native.js.map +2 -2
  62. package/dist/cjs/views/Stack.js.map +1 -1
  63. package/dist/cjs/views/TamaguiProvider.js +3 -2
  64. package/dist/cjs/views/TamaguiProvider.js.map +2 -2
  65. package/dist/cjs/views/Text.js.map +1 -1
  66. package/dist/cjs/views/ThemeReset.js +10 -10
  67. package/dist/cjs/views/ThemeReset.js.map +2 -2
  68. package/dist/esm/Tamagui.js +4 -2
  69. package/dist/esm/Tamagui.js.map +2 -2
  70. package/dist/esm/config.js +1 -2
  71. package/dist/esm/config.js.map +2 -2
  72. package/dist/esm/constants/constants.js +11 -7
  73. package/dist/esm/constants/constants.js.map +2 -2
  74. package/dist/esm/contexts/TextAncestorContext.js +2 -13
  75. package/dist/esm/contexts/TextAncestorContext.js.map +2 -2
  76. package/dist/esm/createComponent.js +203 -296
  77. package/dist/esm/createComponent.js.map +2 -2
  78. package/dist/esm/createTamagui.js +15 -16
  79. package/dist/esm/createTamagui.js.map +3 -3
  80. package/dist/esm/createVariable.js +1 -2
  81. package/dist/esm/createVariable.js.map +2 -2
  82. package/dist/esm/helpers/ThemeManager.js +7 -14
  83. package/dist/esm/helpers/ThemeManager.js.map +2 -2
  84. package/dist/esm/helpers/cache.js +18 -0
  85. package/dist/esm/helpers/cache.js.map +7 -0
  86. package/dist/esm/helpers/createMediaStyle.js +14 -8
  87. package/dist/esm/helpers/createMediaStyle.js.map +2 -2
  88. package/dist/esm/helpers/extendStaticConfig.js +0 -1
  89. package/dist/esm/helpers/extendStaticConfig.js.map +2 -2
  90. package/dist/esm/helpers/getSplitStyles.js +221 -94
  91. package/dist/esm/helpers/getSplitStyles.js.map +3 -3
  92. package/dist/esm/helpers/getStylesAtomic.js +13 -26
  93. package/dist/esm/helpers/getStylesAtomic.js.map +2 -2
  94. package/dist/esm/helpers/insertStyleRule.js +14 -6
  95. package/dist/esm/helpers/insertStyleRule.js.map +2 -2
  96. package/dist/esm/helpers/matchMedia.js.map +2 -2
  97. package/dist/esm/helpers/mergeTransform.js.map +1 -1
  98. package/dist/esm/helpers/normalizeValueWithProperty.js +30 -1
  99. package/dist/esm/helpers/normalizeValueWithProperty.js.map +2 -2
  100. package/dist/esm/helpers/useShallowSetState.js.map +2 -2
  101. package/dist/esm/hooks/getThemeUnwrapped.js +1 -3
  102. package/dist/esm/hooks/getThemeUnwrapped.js.map +2 -2
  103. package/dist/esm/hooks/useElementLayout.js +66 -0
  104. package/dist/esm/hooks/useElementLayout.js.map +7 -0
  105. package/dist/esm/hooks/useIsTouchDevice.js +3 -11
  106. package/dist/esm/hooks/useIsTouchDevice.js.map +2 -2
  107. package/dist/esm/hooks/useMedia.js +74 -83
  108. package/dist/esm/hooks/useMedia.js.map +2 -2
  109. package/dist/esm/hooks/useMedia.test.js +1 -0
  110. package/dist/esm/{helpers/validate.js.map → hooks/useMedia.test.js.map} +0 -0
  111. package/dist/esm/hooks/usePressablePressResponder.js +346 -0
  112. package/dist/esm/hooks/usePressablePressResponder.js.map +7 -0
  113. package/dist/esm/hooks/useSafeRef.js +13 -15
  114. package/dist/esm/hooks/useSafeRef.js.map +2 -2
  115. package/dist/esm/hooks/useTheme.js +3 -4
  116. package/dist/esm/hooks/useTheme.js.map +2 -2
  117. package/dist/esm/index.js +1 -14
  118. package/dist/esm/index.js.map +2 -2
  119. package/dist/esm/static.js +0 -14
  120. package/dist/esm/static.js.map +2 -2
  121. package/dist/esm/styled.js +4 -22
  122. package/dist/esm/styled.js.map +2 -2
  123. package/dist/esm/views/AbsoluteFill.js +17 -0
  124. package/dist/esm/views/AbsoluteFill.js.map +7 -0
  125. package/dist/esm/views/AbsoluteFill.native.js +21 -0
  126. package/dist/esm/views/AbsoluteFill.native.js.map +7 -0
  127. package/dist/esm/views/FontLanguage.native.js.map +2 -2
  128. package/dist/esm/views/Stack.js.map +1 -1
  129. package/dist/esm/views/TamaguiProvider.js +5 -4
  130. package/dist/esm/views/TamaguiProvider.js.map +2 -2
  131. package/dist/esm/views/Text.js.map +1 -1
  132. package/dist/esm/views/ThemeReset.js +10 -10
  133. package/dist/esm/views/ThemeReset.js.map +2 -2
  134. package/package.json +14 -14
  135. package/src/Tamagui.ts +7 -3
  136. package/src/config.ts +2 -3
  137. package/src/constants/constants.ts +10 -6
  138. package/src/contexts/TextAncestorContext.tsx +1 -8
  139. package/src/createComponent.tsx +256 -421
  140. package/src/createTamagui.ts +16 -19
  141. package/src/createVariable.ts +1 -3
  142. package/src/helpers/ThemeManager.tsx +7 -15
  143. package/src/helpers/cache.ts +18 -0
  144. package/src/helpers/createMediaStyle.ts +16 -9
  145. package/src/helpers/extendStaticConfig.ts +0 -1
  146. package/src/helpers/getSplitStyles.tsx +257 -151
  147. package/src/helpers/getStylesAtomic.ts +14 -34
  148. package/src/helpers/insertStyleRule.tsx +16 -8
  149. package/src/helpers/matchMedia.ts +1 -0
  150. package/src/helpers/mergeTransform.ts +1 -1
  151. package/src/helpers/normalizeValueWithProperty.ts +30 -0
  152. package/src/helpers/useShallowSetState.tsx +0 -1
  153. package/src/hooks/getThemeUnwrapped.tsx +1 -2
  154. package/src/hooks/useElementLayout.tsx +90 -0
  155. package/src/hooks/useIsTouchDevice.tsx +3 -17
  156. package/src/hooks/useMedia.tsx +80 -87
  157. package/src/hooks/useSafeRef.ts +17 -18
  158. package/src/hooks/useTheme.tsx +4 -4
  159. package/src/index.ts +1 -11
  160. package/src/static.ts +0 -10
  161. package/src/styled.tsx +15 -33
  162. package/src/types.tsx +58 -55
  163. package/src/views/FontLanguage.native.tsx +1 -0
  164. package/src/views/Stack.tsx +1 -1
  165. package/src/views/TamaguiProvider.tsx +5 -5
  166. package/src/views/Text.tsx +1 -1
  167. package/src/views/ThemeReset.tsx +11 -11
  168. package/types/Tamagui.d.ts +6 -4
  169. package/types/constants/constants.d.ts +14 -3
  170. package/types/contexts/TextAncestorContext.d.ts +1 -3
  171. package/types/createComponent.d.ts +0 -15
  172. package/types/helpers/ThemeManager.d.ts +2 -3
  173. package/types/helpers/cache.d.ts +12 -0
  174. package/types/helpers/calc.d.ts +0 -16
  175. package/types/helpers/createMediaStyle.d.ts +1 -1
  176. package/types/helpers/getSplitStyles.d.ts +6 -10
  177. package/types/helpers/getStylesAtomic.d.ts +1 -5
  178. package/types/helpers/insertStyleRule.d.ts +2 -2
  179. package/types/helpers/mergeTransform.d.ts +1 -1
  180. package/types/helpers/normalizeValueWithProperty.d.ts +1 -0
  181. package/types/hooks/useElementLayout.d.ts +27 -0
  182. package/types/hooks/useMedia.d.ts +3 -1
  183. package/types/hooks/useMedia.test.d.ts +1 -0
  184. package/types/hooks/usePressablePressResponder.d.ts +84 -0
  185. package/types/index.d.ts +1 -11
  186. package/types/static.d.ts +0 -1
  187. package/types/types.d.ts +42 -44
  188. package/types/views/AbsoluteFill.d.ts +5 -0
  189. package/types/views/AbsoluteFill.native.d.ts +3 -0
  190. package/types/views/Stack.d.ts +1 -1
  191. package/types/views/Text.d.ts +1 -1
  192. package/dist/cjs/constants/platform.js +0 -74
  193. package/dist/cjs/constants/platform.js.map +0 -7
  194. package/dist/cjs/constants/platform.native.js +0 -62
  195. package/dist/cjs/constants/platform.native.js.map +0 -7
  196. package/dist/cjs/helpers/createShallowUpdate.js.map +0 -7
  197. package/dist/cjs/helpers/validate.js +0 -2
  198. package/dist/cjs/hooks/useDidFinishSSR.js +0 -60
  199. package/dist/cjs/hooks/useDidFinishSSR.js.map +0 -7
  200. package/dist/cjs/hooks/useIsSSR.js.map +0 -7
  201. package/dist/esm/constants/platform.js +0 -39
  202. package/dist/esm/constants/platform.js.map +0 -7
  203. package/dist/esm/constants/platform.native.js +0 -28
  204. package/dist/esm/constants/platform.native.js.map +0 -7
  205. package/dist/esm/helpers/createShallowUpdate.js +0 -20
  206. package/dist/esm/helpers/createShallowUpdate.js.map +0 -7
  207. package/dist/esm/helpers/validate.js +0 -1
  208. package/dist/esm/hooks/useDidFinishSSR.js +0 -35
  209. package/dist/esm/hooks/useDidFinishSSR.js.map +0 -7
  210. package/dist/esm/hooks/useIsSSR.js +0 -22
  211. package/dist/esm/hooks/useIsSSR.js.map +0 -7
  212. package/src/helpers/assignNativePropsToWeb.tsx +0 -178
  213. package/src/helpers/calc.ts +0 -68
  214. package/src/helpers/getFont.tsx +0 -43
  215. package/src/helpers/getStackSize.ts +0 -22
  216. package/src/helpers/startTransition.ts +0 -4
  217. package/src/hooks/useConstant.tsx +0 -19
  218. package/src/hooks/useIsMounted.ts +0 -16
  219. package/src/hooks/usePressable.tsx +0 -9
  220. package/src/hooks/useUnmountEffect.ts +0 -5
@@ -4,11 +4,11 @@
4
4
  */
5
5
 
6
6
  import { StyleObject, simpleHash } from '@tamagui/helpers'
7
- import hyphenateStyleName from 'hyphenate-style-name'
8
- import type { TextStyle, ViewStyle } from 'react-native'
7
+ import type { TextStyle, ViewStyle } from '@tamagui/types-react-native'
9
8
 
10
9
  import { getConfig } from '../config'
11
10
  import { TamaguiInternalConfig } from '../types'
11
+ import { Style, cache } from './cache'
12
12
  import { defaultOffset } from './expandStyles'
13
13
  import { normalizeValueWithProperty } from './normalizeValueWithProperty.js'
14
14
  import { PseudoDescriptor, pseudoDescriptors } from './pseudoDescriptors'
@@ -166,31 +166,23 @@ const mapTransform = (transform) => {
166
166
  }
167
167
  }
168
168
 
169
- // cache
170
- type Value = Object | Array<any> | string | number
171
- export type Style = { [key: string]: Value }
172
- class Cache {
173
- values: Record<string, any> = {}
174
-
175
- get(key: string, valStr: string) {
176
- return this.values[key]?.[valStr]
177
- }
178
-
179
- set(key: string, valStr: string, object: any) {
180
- this.values[key] ??= {}
181
- this.values[key][valStr] = object
182
- }
183
- }
184
- const cache = new Cache()
185
-
186
169
  function createDeclarationBlock(style: Style, important = false) {
187
- let declarations = ''
170
+ let next = ''
188
171
  for (const key in style) {
189
172
  const prop = hyphenateStyleName(key)
190
173
  const value = style[key]
191
- declarations += `${prop}:${value}${important ? ` !important` : ''};`
174
+ next += `${prop}:${value}${important ? ` !important` : ''};`
192
175
  }
193
- return `{${declarations}}`
176
+ return `{${next}}`
177
+ }
178
+
179
+ const hcache = {}
180
+ const toHyphenLower = (match: string) => `-${match.toLowerCase()}`
181
+ const hyphenateStyleName = (key: string) => {
182
+ let val = hcache[key]
183
+ if (val) return val
184
+ hcache[key] = val = key.replace(/[A-Z]/g, toHyphenLower)
185
+ return val
194
186
  }
195
187
 
196
188
  const pseudoSelectorPrefixes = (() => {
@@ -239,7 +231,6 @@ function createAtomicRules(
239
231
  }
240
232
 
241
233
  // Polyfill for additional 'pointer-events' values
242
- // See d13f78622b233a0afc0c7a200c0a0792c8ca9e58
243
234
  case 'pointerEvents': {
244
235
  let finalValue = value
245
236
  if (value === 'auto' || value === 'box-only') {
@@ -258,17 +249,6 @@ function createAtomicRules(
258
249
  break
259
250
  }
260
251
 
261
- // Polyfill for draft spec
262
- // https://drafts.csswg.org/css-scrollbars-1/
263
- case 'scrollbarWidth': {
264
- if (value === 'none') {
265
- rules.push(`${selector}::-webkit-scrollbar{display:none}`)
266
- }
267
- const block = createDeclarationBlock({ scrollbarWidth: value }, important)
268
- rules.push(`${selector}${block}`)
269
- break
270
- }
271
-
272
252
  default: {
273
253
  const block = createDeclarationBlock({ [property]: value }, important)
274
254
  rules.push(`${selector}${block}`)
@@ -4,8 +4,8 @@
4
4
 
5
5
  import type { StyleObject } from '@tamagui/helpers'
6
6
 
7
- const allSelectors = {}
8
- const allRules = {}
7
+ const allSelectors: Record<string, string> = {}
8
+ const allRules: Record<string, boolean | string[]> = {}
9
9
  export const insertedTransforms = {}
10
10
 
11
11
  export const getAllSelectors = () => allSelectors
@@ -84,7 +84,7 @@ export function updateInserted() {
84
84
  const isTransform = identifier.startsWith('_transform')
85
85
  const shouldInsert = isTransform ? addTransform(identifier, cssRule.cssText, cssRule) : true
86
86
  if (shouldInsert) {
87
- allSelectors[identifier] = process.env.NODE_ENV === 'development' ? cssRule.cssText : true
87
+ allSelectors[identifier] = cssRule.cssText
88
88
  }
89
89
  }
90
90
  }
@@ -134,13 +134,21 @@ export type PartialStyleObject = Pick<StyleObject, 'identifier' | 'property' | '
134
134
  export type RulesToInsert = PartialStyleObject[]
135
135
 
136
136
  export function insertStyleRules(rulesToInsert: RulesToInsert) {
137
- // // pointerevents can have more than one, others could to needs some refactor
137
+ if (isClient && !sheet) {
138
+ if (process.env.NODE_ENV === 'development') {
139
+ // eslint-disable-next-line no-console
140
+ console.warn(`Missing sheet`)
141
+ }
142
+ return
143
+ }
138
144
  for (const { identifier, rules } of rulesToInsert) {
139
- if (allSelectors[identifier] || !sheet) return
140
- allSelectors[identifier] = process.env.NODE_ENV === 'development' ? rules : true
145
+ if (allSelectors[identifier]) continue
146
+ allSelectors[identifier] = process.env.NODE_ENV === 'development' ? rules.join('\n') : ''
141
147
  updateRules(identifier, rules)
142
- for (const rule of rules) {
143
- sheet.insertRule(rule, sheet.cssRules.length)
148
+ if (sheet) {
149
+ for (const rule of rules) {
150
+ sheet.insertRule(rule, sheet.cssRules.length)
151
+ }
144
152
  }
145
153
  }
146
154
  }
@@ -6,6 +6,7 @@ function matchMediaFallback(query: string): MediaQueryList {
6
6
  !process.env.IS_STATIC &&
7
7
  process.env.TAMAGUI_TARGET !== 'web'
8
8
  ) {
9
+ // eslint-disable-next-line no-console
9
10
  console.log('warning: matchMedia not loading! Native not picking up .native.js files?')
10
11
  }
11
12
  return {
@@ -1,4 +1,4 @@
1
- import type { ViewStyle } from 'react-native'
1
+ import type { ViewStyle } from '@tamagui/types-react-native'
2
2
 
3
3
  export const mergeTransform = (obj: ViewStyle, key: string, val: any, backwards = false) => {
4
4
  obj.transform ||= []
@@ -3,11 +3,15 @@
3
3
  * Copyright (c) Nicolas Gallagher licensed under the MIT license.
4
4
  */
5
5
 
6
+ import { getAllSelectors } from './insertStyleRule'
6
7
  import { normalizeColor } from './normalizeColor'
7
8
 
8
9
  const cache = {}
9
10
 
10
11
  export function normalizeValueWithProperty(value: any, property?: string): any {
12
+ if (property && unitlessNumbers[property]) {
13
+ return value
14
+ }
11
15
  const cached = cache[value]
12
16
  if (cached) {
13
17
  return cached
@@ -78,3 +82,29 @@ const unitlessNumbers = {
78
82
  scaleZ: true,
79
83
  shadowOpacity: true,
80
84
  }
85
+
86
+ // getting real values for colors for animations (reverse mapped from CSS)
87
+ // this isn't beautiful, but will do relatively fine performance for now
88
+ const rcache = {}
89
+ export function reverseMapClassNameToValue(key: string, className: string) {
90
+ const selectors = getAllSelectors()
91
+ const cssRule = selectors[className]
92
+ if (rcache[cssRule]) return rcache[cssRule]
93
+ if (!cssRule) {
94
+ return
95
+ }
96
+ const cssVal = cssRule.replace(/.*:/, '').replace(/;.*/, '')
97
+ let res: any
98
+ if (unitlessNumbers[key]) {
99
+ res = +cssVal
100
+ } else if (cssVal.endsWith('px')) {
101
+ res = +cssVal.replace('px', '')
102
+ } else {
103
+ res = cssVal
104
+ }
105
+ if (typeof res === 'string') {
106
+ res = res.trim()
107
+ }
108
+ rcache[cssRule] = res
109
+ return res
110
+ }
@@ -3,7 +3,6 @@ import React, { useCallback } from 'react'
3
3
  export function useShallowSetState<State extends Object>(
4
4
  setter: React.Dispatch<React.SetStateAction<State>>
5
5
  ) {
6
- // eslint-disable-next-line react-hooks/rules-of-hooks
7
6
  return useCallback(
8
7
  (next: Partial<State>) => {
9
8
  setter((prev) => {
@@ -1,8 +1,7 @@
1
1
  export const getThemeUnwrapped = (theme: any) => {
2
2
  if (!theme) return null
3
3
  const first = theme[GetThemeUnwrapped]
4
- if (first) return first[GetThemeUnwrapped] || first
5
- return theme
4
+ return first?.[GetThemeUnwrapped] || first || theme
6
5
  }
7
6
 
8
7
  export const GetThemeUnwrapped = Symbol()
@@ -0,0 +1,90 @@
1
+ import { useIsomorphicLayoutEffect } from '@tamagui/constants'
2
+ import { getBoundingClientRect } from '@tamagui/react-native-use-responder-events'
3
+ import { useEvent } from '@tamagui/use-event'
4
+ import { RefObject } from 'react'
5
+
6
+ const LayoutHandlers = new WeakMap<Element, Function>()
7
+
8
+ export type LayoutValue = {
9
+ x: number
10
+ y: number
11
+ width: number
12
+ height: number
13
+ left: number
14
+ top: number
15
+ }
16
+
17
+ export type LayoutEvent = {
18
+ nativeEvent: {
19
+ layout: LayoutValue
20
+ target: any
21
+ }
22
+ timeStamp: number
23
+ }
24
+
25
+ let resizeObserver: ResizeObserver | null = null
26
+
27
+ if (typeof window !== 'undefined' && 'ResizeObserver' in window) {
28
+ resizeObserver = new ResizeObserver((entries) => {
29
+ for (const { target } of entries) {
30
+ const onLayout = LayoutHandlers.get(target)
31
+ if (typeof onLayout !== 'function') return
32
+ measureLayout(target as HTMLElement, null, (x, y, width, height, left, top) => {
33
+ onLayout({
34
+ nativeEvent: {
35
+ layout: { x, y, width, height, left, top },
36
+ target,
37
+ },
38
+ timeStamp: Date.now(),
39
+ })
40
+ })
41
+ }
42
+ })
43
+ }
44
+
45
+ export const getRect = (node: HTMLElement) => {
46
+ const rect = getBoundingClientRect(node)
47
+ if (!rect) return
48
+ const { x, y, top, left } = rect
49
+ return { x, y, width: node.offsetWidth, height: node.offsetHeight, top, left }
50
+ }
51
+
52
+ export const measureLayout = (
53
+ node: HTMLElement,
54
+ relativeTo: HTMLElement | null,
55
+ callback: Function
56
+ ) => {
57
+ const relativeNode = relativeTo || node?.parentNode
58
+ if (relativeNode instanceof HTMLElement) {
59
+ setTimeout(() => {
60
+ const relativeRect = getBoundingClientRect(relativeNode)!
61
+ const { height, left, top, width } = getRect(node)!
62
+ const x = left - relativeRect.left
63
+ const y = top - relativeRect.top
64
+ callback(x, y, width, height, left, top)
65
+ }, 0)
66
+ }
67
+ }
68
+
69
+ const idFn = () => {}
70
+
71
+ export function useElementLayout(
72
+ ref: RefObject<Element>,
73
+ onLayout?: ((e: LayoutEvent) => void) | null
74
+ ) {
75
+ const hasLayoutEvent = !!onLayout
76
+ const onLayoutEvent = useEvent(onLayout || idFn)
77
+
78
+ // Observing is done in a separate effect to avoid this effect running
79
+ // when 'onLayout' changes.
80
+ useIsomorphicLayoutEffect(() => {
81
+ if (!resizeObserver || !hasLayoutEvent) return
82
+ const node = ref.current
83
+ if (!node) return
84
+ LayoutHandlers.set(node, onLayoutEvent)
85
+ resizeObserver.observe(node)
86
+ return () => {
87
+ resizeObserver?.unobserve(node)
88
+ }
89
+ }, [ref, hasLayoutEvent])
90
+ }
@@ -1,20 +1,6 @@
1
- import { isServer, isTouchable, isWeb, useIsomorphicLayoutEffect } from '@tamagui/constants'
2
- import { useState } from 'react'
3
-
4
- // ssr friendly
1
+ import { isTouchable, isWeb } from '@tamagui/constants'
2
+ import { useDidFinishSSR } from '@tamagui/use-did-finish-ssr'
5
3
 
6
4
  export const useIsTouchDevice = () => {
7
- const [touchOnly, setTouchOnly] = useState(false)
8
-
9
- if (isWeb && !isServer) {
10
- // only ever false on web SSR (env), so run effect conditionally
11
- // eslint-disable-next-line react-hooks/rules-of-hooks
12
- useIsomorphicLayoutEffect(() => {
13
- if (isTouchable) {
14
- setTouchOnly(true)
15
- }
16
- }, [])
17
- }
18
-
19
- return touchOnly
5
+ return !isWeb ? true : useDidFinishSSR() ? isTouchable : false
20
6
  }
@@ -1,5 +1,5 @@
1
1
  import { useIsomorphicLayoutEffect } from '@tamagui/constants'
2
- import { startTransition, useEffect, useMemo, useState } from 'react'
2
+ import { MutableRefObject, useEffect, useMemo, useState } from 'react'
3
3
 
4
4
  import { getConfig } from '../config'
5
5
  import { createProxy } from '../helpers/createProxy'
@@ -31,20 +31,20 @@ export const mediaState: MediaQueryState =
31
31
 
32
32
  type MediaListener = (next: boolean) => void
33
33
 
34
- const mediaQueryListeners: { [key: string]: Set<MediaListener> } = {}
34
+ const listeners: { [key: string]: Set<MediaListener> } = {}
35
35
 
36
36
  export function addMediaQueryListener(key: MediaQueryKey, cb: MediaListener) {
37
37
  if (process.env.NODE_ENV === 'development' && key[0] === '$') {
38
38
  // eslint-disable-next-line no-console
39
39
  console.warn(`Warning, listening to media queries shouldn't use the "$" prefix`)
40
40
  }
41
- mediaQueryListeners[key] = mediaQueryListeners[key] || new Set()
42
- mediaQueryListeners[key].add(cb)
41
+ listeners[key] = listeners[key] || new Set()
42
+ listeners[key].add(cb)
43
43
  return () => removeMediaQueryListener(key, cb)
44
44
  }
45
45
 
46
46
  export function removeMediaQueryListener(key: MediaQueryKey, cb: MediaListener) {
47
- mediaQueryListeners[key]?.delete(cb)
47
+ listeners[key]?.delete(cb)
48
48
  }
49
49
 
50
50
  export const mediaQueryConfig: MediaQueries = {}
@@ -54,16 +54,17 @@ export const getMedia = () => {
54
54
  }
55
55
 
56
56
  // for SSR capture it at time of startup
57
- let currentMediaState: MediaQueryState | null
58
- let initialMediaState: MediaQueryState | null
59
-
57
+ let curState: MediaQueryState
58
+ let initState: MediaQueryState
60
59
  export const getInitialMediaState = () => {
61
- if (getConfig().disableSSR) {
62
- return currentMediaState || {}
63
- }
64
- return initialMediaState || {}
60
+ return (getConfig().disableSSR ? curState : initState) || {}
65
61
  }
66
62
 
63
+ let mediaKeysOrdered: string[]
64
+ export const getMediaKeyImportance = (key: string) =>
65
+ // + 2 because we set base usedKeys=1 in getSplitStyles and all media go above 1
66
+ mediaKeysOrdered.indexOf(key[0] === '$' ? key.slice(1) : key) + 2
67
+
67
68
  const dispose = new Set<Function>()
68
69
 
69
70
  export const configureMedia = (config: TamaguiInternalConfig) => {
@@ -73,8 +74,9 @@ export const configureMedia = (config: TamaguiInternalConfig) => {
73
74
  mediaState[key] = mediaQueryDefaultActive?.[key] || false
74
75
  }
75
76
  Object.assign(mediaQueryConfig, media)
76
- currentMediaState = { ...mediaState }
77
- initialMediaState = currentMediaState
77
+ curState = { ...mediaState }
78
+ initState = curState
79
+ mediaKeysOrdered = Object.keys(media)
78
80
  if (config.disableSSR) {
79
81
  setupMediaListeners()
80
82
  }
@@ -102,40 +104,34 @@ function setupMediaListeners() {
102
104
  // hmr, undo existing before re-binding
103
105
  unlisten()
104
106
 
105
- // theoretically this should make media reactions smoother but potentially delayed?
106
- // but i believe consistent, so that's a nicer default (we'll see)
107
- startTransition(() => {
108
- for (const key in mediaQueryConfig) {
109
- const str = mediaObjectToString(mediaQueryConfig[key])
110
- const getMatch = () => matchMedia(str)
111
- const match = getMatch()
112
- if (!match) {
113
- throw new Error('⚠️ No match')
114
- }
115
- // react native needs these deprecated apis for now
116
- match.addListener(update)
117
- dispose.add(() => match.removeListener(update))
107
+ for (const key in mediaQueryConfig) {
108
+ const str = mediaObjectToString(mediaQueryConfig[key])
109
+ const getMatch = () => matchMedia(str)
110
+ const match = getMatch()
111
+ if (!match) {
112
+ throw new Error('⚠️ No match')
113
+ }
114
+ // react native needs these deprecated apis for now
115
+ match.addListener(update)
116
+ dispose.add(() => match.removeListener(update))
118
117
 
119
- update()
118
+ update()
120
119
 
121
- function update() {
122
- const next = !!getMatch().matches
123
- if (next === mediaState[key]) return
124
- mediaState[key] = next
125
- currentMediaState = { ...mediaState }
126
- const listeners = mediaQueryListeners[key]
127
- if (listeners?.size) {
128
- listeners.forEach((cb) => cb(next))
129
- }
120
+ function update() {
121
+ const next = !!getMatch().matches
122
+ if (next === mediaState[key]) return
123
+ mediaState[key] = next
124
+ curState = { ...mediaState }
125
+ const cur = listeners[key]
126
+ if (cur?.size) {
127
+ cur.forEach((cb) => cb(next))
130
128
  }
131
129
  }
132
- })
130
+ }
133
131
  }
134
132
 
135
- export function useMediaQueryListeners(config: TamaguiInternalConfig) {
136
- if (config.disableSSR) {
137
- return
138
- }
133
+ export function useMediaListeners(config: TamaguiInternalConfig) {
134
+ if (config.disableSSR) return
139
135
  useEffect(() => {
140
136
  setupMediaListeners()
141
137
  return unlisten
@@ -145,14 +141,14 @@ export function useMediaQueryListeners(config: TamaguiInternalConfig) {
145
141
  export function useMedia(): {
146
142
  [key in MediaQueryKey]: boolean
147
143
  } {
148
- const [state, setState] = useState<MediaQueryState>(initialMediaState || {})
149
144
  const keys = useSafeRef({} as Record<string, boolean>)
145
+ const [state, setState] = useState<MediaQueryState>(() => getMediaState(initState || {}, keys))
150
146
 
151
147
  function updateState() {
152
148
  setState((prev) => {
153
149
  for (const key in keys.current) {
154
150
  if (prev[key] !== mediaState[key]) {
155
- return { ...mediaState }
151
+ return getMediaState({ ...mediaState }, keys)
156
152
  }
157
153
  }
158
154
  return prev
@@ -160,34 +156,27 @@ export function useMedia(): {
160
156
  }
161
157
 
162
158
  useIsomorphicLayoutEffect(() => {
163
- const disposes: Function[] = Object.keys(keys.current).map((key) =>
159
+ updateState()
160
+ const disposers = Object.keys(keys.current).map((key) =>
164
161
  addMediaQueryListener(key, updateState)
165
162
  )
166
- updateState()
167
163
  return () => {
168
- disposes.forEach((cb) => cb())
164
+ disposers.forEach((cb) => cb())
169
165
  }
170
- })
166
+ }, [keys.current])
171
167
 
172
- return useMemo(
173
- () =>
174
- new Proxy(state, {
175
- get(_, key: string) {
176
- if (process.env.NODE_ENV === 'development') {
177
- if (!currentMediaState) {
178
- throw new Error(
179
- `To use useMedia() you must pass in media configuration to createTamagui, none was found.`
180
- )
181
- }
182
- }
183
- if (!keys.current[key]) {
184
- keys.current[key] = true
185
- }
186
- return Reflect.get(state, key)
187
- },
188
- }),
189
- [state, keys]
190
- )
168
+ return state
169
+ }
170
+
171
+ function getMediaState(state: MediaQueryState, keys: MutableRefObject<Record<string, boolean>>) {
172
+ return new Proxy(state, {
173
+ get(_, key: string) {
174
+ if (!keys.current[key]) {
175
+ keys.current[key] = true
176
+ }
177
+ return Reflect.get(state, key)
178
+ },
179
+ })
191
180
  }
192
181
 
193
182
  /**
@@ -206,44 +195,48 @@ export function useMediaPropsActive<A extends Object>(
206
195
  const media = useMedia()
207
196
 
208
197
  return useMemo(() => {
209
- const evaluated = {} as A
210
- const lastSetImportanceForKey = {}
198
+ const next = {} as A
199
+ const importancesUsed = {}
211
200
  const propNames = Object.keys(props)
212
- const mediaKeysOrdered = Object.keys(getMedia())
213
-
214
- function update(k: string, v: any, importance = 0) {
215
- if (lastSetImportanceForKey[k] > importance) {
216
- return
217
- }
218
- lastSetImportanceForKey[k] = importance
219
- evaluated[k] = v
220
- }
221
201
 
222
202
  for (let i = propNames.length - 1; i >= 0; i--) {
223
203
  const key = propNames[i]
224
204
  const val = props[key]
225
205
  if (key[0] === '$') {
226
- const shortKey = key.slice(1)
227
- if (!media[shortKey]) {
206
+ if (!media[key.slice(1)]) {
228
207
  continue
229
208
  }
230
209
  if (val && typeof val === 'object') {
231
- const subPropNames = Object.keys(val)
232
- const mediaImportance = mediaKeysOrdered.indexOf(shortKey)
233
- for (let j = subPropNames.length; j--; j >= 0) {
234
- const skey = subPropNames[j]
235
- update(skey, val[skey], mediaImportance)
210
+ const subKeys = Object.keys(val)
211
+ for (let j = subKeys.length; j--; j >= 0) {
212
+ const subKey = subKeys[j]
213
+ mergeMediaByImportance(next, subKey, val[subKey], importancesUsed)
236
214
  }
237
215
  }
238
216
  } else {
239
- update(key, val)
217
+ mergeMediaByImportance(next, key, val, importancesUsed)
240
218
  }
241
219
  }
242
220
 
243
- return evaluated
221
+ return next
244
222
  }, [media, props])
245
223
  }
246
224
 
225
+ export function mergeMediaByImportance(
226
+ onto: Record<string, any>,
227
+ key: string,
228
+ value: any,
229
+ importancesUsed: Record<string, number>
230
+ ) {
231
+ const importance = getMediaKeyImportance(key)
232
+ if (importancesUsed[key] > importance) {
233
+ return false
234
+ }
235
+ importancesUsed[key] = importance
236
+ onto[key] = value
237
+ return true
238
+ }
239
+
247
240
  function camelToHyphen(str: string) {
248
241
  return str.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`).toLowerCase()
249
242
  }
@@ -3,11 +3,12 @@ import { useRef } from 'react'
3
3
 
4
4
  // dai-shi 🙏
5
5
  // https://dev.to/uhyo_/a-concurrent-mode-safe-version-of-useref-1325
6
+ // probably can live without it though to save a bit of b
6
7
 
7
8
  type Raw<T> = {
8
- isRendering: boolean
9
- comittedValue: T
10
- currentValue: T
9
+ hold: boolean
10
+ next: T
11
+ cur: T
11
12
  ref: { current: T }
12
13
  }
13
14
 
@@ -17,32 +18,30 @@ export const useSafeRef = <T>(initialValue: T) => {
17
18
  const raw: Raw<T> =
18
19
  rawRef.current ??
19
20
  (rawRef.current = {
20
- isRendering: true,
21
- comittedValue: initialValue,
22
- currentValue: initialValue,
21
+ hold: true,
22
+ next: initialValue,
23
+ cur: initialValue,
23
24
  ref: {
24
25
  get current() {
25
- if (raw.isRendering) {
26
- return raw.currentValue
27
- } else {
28
- return raw.comittedValue
29
- }
26
+ return raw.hold ? raw.cur : raw.next
30
27
  },
31
28
  set current(v) {
32
- if (!raw.isRendering) {
33
- raw.comittedValue = v
29
+ if (!raw.hold) {
30
+ raw.next = v
34
31
  }
35
- raw.currentValue = v
32
+ raw.cur = v
36
33
  },
37
34
  },
38
35
  })
39
36
 
40
- raw.isRendering = true
41
- Promise.resolve().then(() => (raw.isRendering = false))
42
- raw.currentValue = raw.comittedValue
37
+ raw.hold = true
38
+ Promise.resolve().then(() => {
39
+ raw.hold = false
40
+ })
41
+ raw.cur = raw.next
43
42
 
44
43
  useIsomorphicLayoutEffect(() => {
45
- raw.comittedValue = raw.currentValue
44
+ raw.next = raw.cur
46
45
  })
47
46
 
48
47
  return raw.ref