@tamagui/core 1.0.1-beta.167 → 1.0.1-beta.169

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 (94) hide show
  1. package/dist/cjs/addTheme.js.map +1 -1
  2. package/dist/cjs/config.js.map +1 -1
  3. package/dist/cjs/constants/platform.js +4 -4
  4. package/dist/cjs/constants/platform.js.map +2 -2
  5. package/dist/cjs/constants/platform.native.js +3 -3
  6. package/dist/cjs/constants/platform.native.js.map +2 -2
  7. package/dist/cjs/createComponent.js +39 -17
  8. package/dist/cjs/createComponent.js.map +2 -2
  9. package/dist/cjs/createTamagui.js +1 -4
  10. package/dist/cjs/createTamagui.js.map +2 -2
  11. package/dist/cjs/helpers/ThemeManager.js.map +1 -1
  12. package/dist/cjs/helpers/getSplitStyles.js +18 -10
  13. package/dist/cjs/helpers/getSplitStyles.js.map +3 -3
  14. package/dist/cjs/helpers/getThemeCSSRules.js.map +1 -1
  15. package/dist/cjs/hooks/useIsSSR.js +11 -7
  16. package/dist/cjs/hooks/useIsSSR.js.map +2 -2
  17. package/dist/cjs/hooks/useIsTouchDevice.js +1 -1
  18. package/dist/cjs/hooks/useIsTouchDevice.js.map +2 -2
  19. package/dist/cjs/hooks/useMedia.js +51 -29
  20. package/dist/cjs/hooks/useMedia.js.map +2 -2
  21. package/dist/cjs/hooks/useTheme.js +1 -1
  22. package/dist/cjs/hooks/useTheme.js.map +2 -2
  23. package/dist/cjs/styled.js +11 -8
  24. package/dist/cjs/styled.js.map +2 -2
  25. package/dist/cjs/types.js.map +1 -1
  26. package/dist/cjs/updateTheme.js.map +2 -2
  27. package/dist/cjs/views/TamaguiProvider.js +2 -2
  28. package/dist/cjs/views/TamaguiProvider.js.map +2 -2
  29. package/dist/esm/addTheme.js.map +1 -1
  30. package/dist/esm/config.js.map +1 -1
  31. package/dist/esm/constants/platform.js +3 -3
  32. package/dist/esm/constants/platform.js.map +2 -2
  33. package/dist/esm/constants/platform.native.js +2 -2
  34. package/dist/esm/constants/platform.native.js.map +2 -2
  35. package/dist/esm/createComponent.js +43 -18
  36. package/dist/esm/createComponent.js.map +2 -2
  37. package/dist/esm/createTamagui.js +1 -4
  38. package/dist/esm/createTamagui.js.map +2 -2
  39. package/dist/esm/helpers/ThemeManager.js.map +1 -1
  40. package/dist/esm/helpers/getSplitStyles.js +18 -10
  41. package/dist/esm/helpers/getSplitStyles.js.map +2 -2
  42. package/dist/esm/helpers/getThemeCSSRules.js.map +1 -1
  43. package/dist/esm/hooks/useIsSSR.js +12 -8
  44. package/dist/esm/hooks/useIsSSR.js.map +2 -2
  45. package/dist/esm/hooks/useIsTouchDevice.js +2 -2
  46. package/dist/esm/hooks/useIsTouchDevice.js.map +2 -2
  47. package/dist/esm/hooks/useMedia.js +51 -30
  48. package/dist/esm/hooks/useMedia.js.map +2 -2
  49. package/dist/esm/hooks/useTheme.js +2 -2
  50. package/dist/esm/hooks/useTheme.js.map +2 -2
  51. package/dist/esm/styled.js +11 -8
  52. package/dist/esm/styled.js.map +2 -2
  53. package/dist/esm/updateTheme.js.map +2 -2
  54. package/dist/esm/views/TamaguiProvider.js +3 -3
  55. package/dist/esm/views/TamaguiProvider.js.map +2 -2
  56. package/package.json +10 -10
  57. package/src/addTheme.ts +3 -3
  58. package/src/config.ts +1 -1
  59. package/src/constants/platform.native.ts +1 -1
  60. package/src/constants/platform.ts +2 -2
  61. package/src/createComponent.tsx +51 -23
  62. package/src/createTamagui.ts +7 -10
  63. package/src/helpers/ThemeManager.tsx +4 -4
  64. package/src/helpers/getSplitStyles.tsx +27 -13
  65. package/src/helpers/getThemeCSSRules.ts +2 -2
  66. package/src/hooks/useIsSSR.tsx +13 -8
  67. package/src/hooks/useIsTouchDevice.tsx +2 -2
  68. package/src/hooks/useMedia.tsx +72 -39
  69. package/src/hooks/useTheme.tsx +6 -7
  70. package/src/styled.tsx +14 -8
  71. package/src/types.tsx +41 -12
  72. package/src/updateTheme.ts +2 -2
  73. package/src/views/TamaguiProvider.tsx +3 -4
  74. package/types/addTheme.d.ts +2 -2
  75. package/types/config.d.ts +18 -2
  76. package/types/constants/platform.d.ts +1 -1
  77. package/types/constants/platform.native.d.ts +1 -1
  78. package/types/createVariable.d.ts +4 -0
  79. package/types/helpers/ThemeManager.d.ts +5 -5
  80. package/types/helpers/calc.d.ts +5 -0
  81. package/types/helpers/expandStyle.d.ts +4 -0
  82. package/types/helpers/expandStyles.d.ts +7 -0
  83. package/types/helpers/getSplitStyles.d.ts +3 -3
  84. package/types/helpers/getStylesAtomic.d.ts +4 -0
  85. package/types/helpers/getThemeCSSRules.d.ts +2 -2
  86. package/types/helpers/mergeProps.d.ts +13 -0
  87. package/types/helpers/normalizeValueWithProperty.d.ts +4 -0
  88. package/types/hooks/useIsSSR.d.ts +3 -1
  89. package/types/hooks/useMedia.d.ts +8 -5
  90. package/types/hooks/useTheme.d.ts +4 -4
  91. package/types/insertFont.d.ts +3 -0
  92. package/types/styled.d.ts +1 -0
  93. package/types/types.d.ts +111 -11
  94. package/types/updateTheme.d.ts +2 -2
@@ -1,11 +1,11 @@
1
1
  import { stylePropsText, stylePropsTransform, validPseudoKeys, validStyles } from '@tamagui/helpers'
2
- import { useInsertionEffect, useMemo } from 'react'
2
+ import { useInsertionEffect } from 'react'
3
3
  import type { ViewStyle } from 'react-native'
4
4
 
5
5
  import { getConfig } from '../config'
6
6
  import { isDevTools } from '../constants/isDevTools'
7
7
  import { isClient, isRSC, isWeb, useIsomorphicLayoutEffect } from '../constants/platform'
8
- import { mediaQueryConfig, mediaState } from '../hooks/useMedia'
8
+ import { mediaState as globalMediaState, mediaQueryConfig } from '../hooks/useMedia'
9
9
  import type {
10
10
  DebugProp,
11
11
  MediaQueryKey,
@@ -15,7 +15,7 @@ import type {
15
15
  StackProps,
16
16
  StaticConfigParsed,
17
17
  TamaguiInternalConfig,
18
- ThemeObject,
18
+ ThemeParsed,
19
19
  } from '../types'
20
20
  import { FontLanguageProps, LanguageContextType } from '../views/FontLanguage.types'
21
21
  import { createMediaStyle } from './createMediaStyle'
@@ -30,6 +30,7 @@ import {
30
30
  updateInserted,
31
31
  updateRules,
32
32
  } from './insertStyleRule'
33
+ import { mergeProps } from './mergeProps'
33
34
  import { mergeTransform } from './mergeTransform'
34
35
  import { normalizeValueWithProperty } from './normalizeValueWithProperty.js'
35
36
  import { pseudoDescriptors } from './pseudoDescriptors'
@@ -66,7 +67,7 @@ let conf: TamaguiInternalConfig
66
67
  type StyleSplitter = (
67
68
  props: { [key: string]: any },
68
69
  staticConfig: StaticConfigParsed,
69
- theme: ThemeObject,
70
+ theme: ThemeParsed,
70
71
  state: SplitStyleState,
71
72
  defaultClassNames?: any,
72
73
  languageContext?: LanguageContextType,
@@ -114,10 +115,14 @@ export const getSplitStyles: StyleSplitter = (
114
115
  const viewProps: StackProps = {}
115
116
  const pseudos: PseudoStyles = {}
116
117
  const medias: Record<MediaQueryKey, ViewStyle> = {}
118
+ const mediaState = state.mediaState || globalMediaState
117
119
  const usedKeys = new Set<string>()
118
120
  const propKeys = Object.keys(props)
119
121
  const shouldDoClasses =
120
- (isWeb || process.env.IS_STATIC === 'is_static') && !state.noClassNames && !props.animation
122
+ staticConfig.acceptsClassName &&
123
+ (isWeb || process.env.IS_STATIC === 'is_static') &&
124
+ !state.noClassNames &&
125
+ !props.animation
121
126
  const len = propKeys.length
122
127
  const rulesToInsert: RulesToInsert = []
123
128
  const style: ViewStyle = {}
@@ -126,7 +131,7 @@ export const getSplitStyles: StyleSplitter = (
126
131
  // parity with react-native-web
127
132
  style.display = 'inline-flex' as any
128
133
  }
129
- let classNames: ClassNamesObject = {}
134
+ const classNames: ClassNamesObject = {}
130
135
  // we need to gather these specific to each media query / pseudo
131
136
  // value is [hash, val], so ["-jnjad-asdnjk", "scaleX(1) rotate(10deg)"]
132
137
  const transforms: Record<TransformNamespaceKey, [string, string]> = {}
@@ -346,10 +351,18 @@ export const getSplitStyles: StyleSplitter = (
346
351
  // media
347
352
  if (isMedia) {
348
353
  const mediaKey = key
349
- mediaKeys.push(key)
350
354
  const mediaKeyShort = mediaKey.slice(1)
351
355
 
356
+ mediaKeys.push(mediaKeyShort)
357
+
352
358
  if (!mediaQueryConfig[mediaKeyShort]) {
359
+ if (process.env.NODE_ENV === 'development' && debug) {
360
+ // eslint-disable-next-line no-console
361
+ console.warn(
362
+ `Warning: using a $ prefixed prop that doesn't match to a media query, may be an error`,
363
+ key
364
+ )
365
+ }
353
366
  if (!usedKeys.has(key)) {
354
367
  // this isn't a media key, pass through
355
368
  viewProps[key] = val
@@ -376,7 +389,7 @@ export const getSplitStyles: StyleSplitter = (
376
389
  if (process.env.NODE_ENV === 'development' && debug === 'verbose') {
377
390
  // prettier-ignore
378
391
  // eslint-disable-next-line no-console
379
- console.log(' 🔹 mediaStyle', { mediaKey, mediaStyle, props, shouldDoClasses })
392
+ console.log(` 📺 ${mediaKey}`, mediaState[mediaKeyShort], { mediaKey, mediaStyle, props, shouldDoClasses, mediaState: { ...mediaState } })
380
393
  }
381
394
 
382
395
  if (shouldDoClasses) {
@@ -391,7 +404,7 @@ export const getSplitStyles: StyleSplitter = (
391
404
  }
392
405
  }
393
406
  } else {
394
- if (mediaState[mediaKey]) {
407
+ if (mediaState[mediaKeyShort]) {
395
408
  if (process.env.NODE_ENV === 'development' && debug) {
396
409
  // eslint-disable-next-line no-console
397
410
  console.log('apply media style', mediaKey, mediaState)
@@ -447,9 +460,10 @@ export const getSplitStyles: StyleSplitter = (
447
460
 
448
461
  // add in defaults if not set:
449
462
  if (defaultClassNames) {
450
- classNames = {
451
- ...defaultClassNames,
452
- ...classNames,
463
+ for (const key in defaultClassNames) {
464
+ if (!(key in classNames)) {
465
+ classNames[key] = defaultClassNames[key]
466
+ }
453
467
  }
454
468
  }
455
469
 
@@ -646,7 +660,7 @@ export const getSubStyle = (
646
660
  subKey: string,
647
661
  styleIn: Object,
648
662
  staticConfig: StaticConfigParsed,
649
- theme: ThemeObject,
663
+ theme: ThemeParsed,
650
664
  props: any,
651
665
  state: SplitStyleState,
652
666
  conf: TamaguiInternalConfig,
@@ -1,6 +1,6 @@
1
1
  import { THEME_CLASSNAME_PREFIX } from '../constants/constants'
2
2
  import { Variable, variableToString } from '../createVariable'
3
- import { CreateTamaguiProps, ThemeObject } from '../types'
3
+ import { CreateTamaguiProps, ThemeParsed } from '../types'
4
4
  import { tokensValueToVariable } from './registerCSSVariable'
5
5
 
6
6
  export function getThemeCSSRules({
@@ -11,7 +11,7 @@ export function getThemeCSSRules({
11
11
  }: {
12
12
  config: CreateTamaguiProps
13
13
  themeName: string
14
- theme: ThemeObject
14
+ theme: ThemeParsed
15
15
  names: string[]
16
16
  }) {
17
17
  const cssRuleSets: string[] = []
@@ -1,20 +1,25 @@
1
1
  import { useState } from 'react'
2
2
 
3
- import { isSSR, isWeb, useIsomorphicLayoutEffect } from '../constants/platform'
3
+ import { isServer, isWeb, useIsomorphicLayoutEffect } from '../constants/platform'
4
4
  import { startTransition } from '../helpers/startTransition'
5
5
 
6
6
  // because any change before first useEffect causes hydration / mismatch issues
7
7
 
8
- export const useIsSSR = () => {
8
+ export const useIsSSR = (props?: { immediate?: boolean }) => {
9
9
  const [val, setVal] = useState(true)
10
10
 
11
- useIsomorphicLayoutEffect(() => {
12
- if (isWeb && !isSSR) {
13
- startTransition(() => {
11
+ if (isWeb && !isServer) {
12
+ useIsomorphicLayoutEffect(() => {
13
+ // could also have a global tamagui `config.immediateSSRTransition`
14
+ if (props?.immediate) {
14
15
  setVal(false)
15
- })
16
- }
17
- }, [])
16
+ } else {
17
+ startTransition(() => {
18
+ setVal(false)
19
+ })
20
+ }
21
+ }, [])
22
+ }
18
23
 
19
24
  return val
20
25
  }
@@ -1,13 +1,13 @@
1
1
  import { useState } from 'react'
2
2
 
3
- import { isSSR, isTouchable, isWeb, useIsomorphicLayoutEffect } from '../constants/platform'
3
+ import { isServer, isTouchable, isWeb, useIsomorphicLayoutEffect } from '../constants/platform'
4
4
 
5
5
  // ssr friendly
6
6
 
7
7
  export const useIsTouchDevice = () => {
8
8
  const [touchOnly, setTouchOnly] = useState(false)
9
9
 
10
- if (isWeb && !isSSR) {
10
+ if (isWeb && !isServer) {
11
11
  // only ever false on web SSR (env), so run effect conditionally
12
12
  // eslint-disable-next-line react-hooks/rules-of-hooks
13
13
  useIsomorphicLayoutEffect(() => {
@@ -1,26 +1,47 @@
1
- import { useMemo, useState } from 'react'
1
+ import { useEffect, useMemo, useState } from 'react'
2
2
 
3
3
  import { useIsomorphicLayoutEffect } from '../constants/platform'
4
4
  import { matchMedia } from '../helpers/matchMedia'
5
5
  import {
6
- ConfigureMediaQueryOptions,
7
6
  MediaQueries,
8
7
  MediaQueryKey,
9
8
  MediaQueryObject,
10
9
  MediaQueryState,
10
+ TamaguiInternalConfig,
11
11
  } from '../types'
12
12
  import { useSafeRef } from './useSafeRef'
13
13
 
14
- export const mediaState: MediaQueryState = {} as any
15
- const mediaQueryListeners: { [key: string]: Set<Function> } = {}
14
+ export const mediaState: MediaQueryState =
15
+ // development time safeguard
16
+ process.env.NODE_ENV === 'development'
17
+ ? new Proxy(
18
+ {},
19
+ {
20
+ get(target, key) {
21
+ if (typeof key === 'string' && key[0] === '$') {
22
+ throw new Error(`Access of mediaState should never include $`)
23
+ }
24
+ return Reflect.get(target, key)
25
+ },
26
+ }
27
+ )
28
+ : ({} as any)
29
+
30
+ type MediaListener = (next: boolean) => void
31
+
32
+ const mediaQueryListeners: { [key: string]: Set<MediaListener> } = {}
16
33
 
17
- export function addMediaQueryListener(key: MediaQueryKey, cb: any) {
34
+ export function addMediaQueryListener(key: MediaQueryKey, cb: MediaListener) {
35
+ if (process.env.NODE_ENV === 'development' && key[0] === '$') {
36
+ // eslint-disable-next-line no-console
37
+ console.warn(`Warning, listening to media queries shouldn't use the "$" prefix`)
38
+ }
18
39
  mediaQueryListeners[key] = mediaQueryListeners[key] || new Set()
19
40
  mediaQueryListeners[key].add(cb)
20
41
  return () => removeMediaQueryListener(key, cb)
21
42
  }
22
43
 
23
- export function removeMediaQueryListener(key: MediaQueryKey, cb: any) {
44
+ export function removeMediaQueryListener(key: MediaQueryKey, cb: MediaListener) {
24
45
  mediaQueryListeners[key]?.delete(cb)
25
46
  }
26
47
 
@@ -35,37 +56,40 @@ const dispose = new Set<Function>()
35
56
  // for SSR capture it at time of startup
36
57
  let initialMediaState: MediaQueryState | null
37
58
 
38
- export const configureMedia = ({
39
- queries,
40
- defaultActive = {},
41
- }: ConfigureMediaQueryOptions = {}) => {
42
- if (!queries) return
43
-
44
- // support hot reload
45
- if (initialMediaState) {
46
- if (process.env.NODE_ENV === 'development') {
47
- if (JSON.stringify(queries) === JSON.stringify(mediaQueryConfig)) {
48
- // hmr avoid update
49
- return
50
- }
51
- }
52
- setupMediaListeners()
53
- }
54
-
55
- Object.assign(mediaQueryConfig, queries)
56
- // start in the initial state
57
- for (const key in queries) {
58
- mediaState[key] = defaultActive[key] || false
59
+ export const configureMedia = (config: TamaguiInternalConfig) => {
60
+ const { media, mediaQueryDefaultActive } = config
61
+ if (!media) return
62
+ for (const key in media) {
63
+ mediaState[key] = mediaQueryDefaultActive?.[key] || false
59
64
  }
65
+ Object.assign(mediaQueryConfig, media)
60
66
  initialMediaState = { ...mediaState }
67
+ if (config.disableSSR) {
68
+ setupMediaListeners()
69
+ }
61
70
  }
62
71
 
72
+ export const getInitialMediaState = () => initialMediaState
73
+
63
74
  function unlisten() {
64
75
  dispose.forEach((cb) => cb())
65
76
  dispose.clear()
66
77
  }
67
78
 
79
+ /**
80
+ * Note: This should *not* set the state on the first render!
81
+ * Because to avoid hydration issues SSR must match the server
82
+ * *and then* re-render with the actual media query state.
83
+ */
84
+ let configuredKey = ''
68
85
  function setupMediaListeners() {
86
+ // avoid setting up more than once per config
87
+ const nextKey = JSON.stringify(mediaQueryConfig)
88
+ if (nextKey === configuredKey) {
89
+ return
90
+ }
91
+ configuredKey = nextKey
92
+
69
93
  // hmr, undo existing before re-binding
70
94
  unlisten()
71
95
 
@@ -79,6 +103,7 @@ function setupMediaListeners() {
79
103
  // react native needs these deprecated apis for now
80
104
  match.addListener(update)
81
105
  dispose.add(() => match.removeListener(update))
106
+
82
107
  update()
83
108
 
84
109
  function update() {
@@ -87,16 +112,18 @@ function setupMediaListeners() {
87
112
  mediaState[key] = next
88
113
  const listeners = mediaQueryListeners[key]
89
114
  if (listeners?.size) {
90
- for (const cb of [...listeners]) {
91
- cb()
92
- }
115
+ listeners.forEach((cb) => cb(next))
93
116
  }
94
117
  }
95
118
  }
96
119
  }
97
120
 
98
- export function useMediaQueryListeners() {
99
- useIsomorphicLayoutEffect(() => {
121
+ export function useMediaQueryListeners(config: TamaguiInternalConfig) {
122
+ if (config.disableSSR) {
123
+ return
124
+ }
125
+
126
+ useEffect(() => {
100
127
  setupMediaListeners()
101
128
  return unlisten
102
129
  }, [])
@@ -105,7 +132,7 @@ export function useMediaQueryListeners() {
105
132
  export function useMedia(): {
106
133
  [key in MediaQueryKey]: boolean
107
134
  } {
108
- const [state, setState] = useState(initialMediaState!)
135
+ const [state, setState] = useState(initialMediaState || {})
109
136
  const keys = useSafeRef({} as Record<string, boolean>)
110
137
 
111
138
  function updateState() {
@@ -120,13 +147,12 @@ export function useMedia(): {
120
147
  }
121
148
 
122
149
  useIsomorphicLayoutEffect(() => {
123
- const listeners: Function[] = []
124
- for (const key in keys.current) {
125
- listeners.push(addMediaQueryListener(key, updateState))
126
- }
150
+ const disposes: Function[] = Object.keys(keys.current).map((key) =>
151
+ addMediaQueryListener(key, updateState)
152
+ )
127
153
  updateState()
128
154
  return () => {
129
- listeners.forEach((cb) => cb())
155
+ disposes.forEach((cb) => cb())
130
156
  }
131
157
  })
132
158
 
@@ -134,8 +160,15 @@ export function useMedia(): {
134
160
  () =>
135
161
  new Proxy(state, {
136
162
  get(_, key: string) {
163
+ if (process.env.NODE_ENV === 'development') {
164
+ if (!initialMediaState) {
165
+ throw new Error(
166
+ `To use useMedia() you must pass in media configuration to createTamagui, none was found.`
167
+ )
168
+ }
169
+ }
137
170
  if (!keys.current[key]) {
138
- keys.current = { ...keys.current, [key]: true }
171
+ keys.current[key] = true
139
172
  }
140
173
  return Reflect.get(state, key)
141
174
  },
@@ -3,7 +3,7 @@ import React, { useContext, useLayoutEffect, useMemo, useRef, useState } from 'r
3
3
 
4
4
  import { getConfig } from '../config'
5
5
  import { isDevTools } from '../constants/isDevTools'
6
- import { isRSC, isSSR, useIsomorphicLayoutEffect } from '../constants/platform'
6
+ import { isRSC, isServer, useIsomorphicLayoutEffect } from '../constants/platform'
7
7
  import { ThemeContext } from '../contexts/ThemeContext'
8
8
  import { areEqualSets } from '../helpers/areEqualSets'
9
9
  import {
@@ -12,7 +12,7 @@ import {
12
12
  ThemeManagerContext,
13
13
  emptyManager,
14
14
  } from '../helpers/ThemeManager'
15
- import { ThemeName, ThemeObject } from '../types'
15
+ import { ThemeName, ThemeParsed } from '../types'
16
16
  import { GetThemeUnwrapped } from './getThemeUnwrapped'
17
17
  import { useConstant } from './useConstant'
18
18
  import { useServerRef } from './useServerHooks'
@@ -38,7 +38,7 @@ export const useTheme = (
38
38
  componentName?: string,
39
39
  props?: ThemeProps,
40
40
  forceUpdate?: any
41
- ): ThemeObject => {
41
+ ): ThemeParsed => {
42
42
  // TODO this can use useChangeThemeEffect almost ready
43
43
  if (isRSC) {
44
44
  const config = getConfig()
@@ -195,10 +195,10 @@ export const useChangeThemeEffect = (
195
195
  forceUpdateProp?: any,
196
196
  uuid?: Object
197
197
  ): {
198
- themes: Record<string, ThemeObject>
198
+ themes: Record<string, ThemeParsed>
199
199
  themeManager: ThemeManager | null
200
200
  name: string
201
- theme: ThemeObject | null
201
+ theme: ThemeParsed | null
202
202
  className?: string
203
203
  } => {
204
204
  const { themes } = getConfig()
@@ -236,10 +236,9 @@ export const useChangeThemeEffect = (
236
236
  return new ThemeManager(next.name, next.className, next.theme, parentManager, reset)
237
237
  })
238
238
 
239
- if (!isSSR) {
239
+ if (!isServer) {
240
240
  useLayoutEffect(() => {
241
241
  themeManager.update(next)
242
-
243
242
  activeThemeManagers.add(themeManager)
244
243
 
245
244
  const disposeParentOnChange = parentManager.onChangeTheme(() => {
package/src/styled.tsx CHANGED
@@ -48,6 +48,7 @@ export function styled<
48
48
  variants?: Variants | undefined
49
49
  // thought i had this typed, but can't get it linked
50
50
  defaultVariants?: { [key: string]: any }
51
+ acceptsClassName?: boolean
51
52
  },
52
53
  staticExtractionOptions?: Partial<StaticConfig>
53
54
  ) {
@@ -73,7 +74,13 @@ export function styled<
73
74
  }
74
75
 
75
76
  if (options) {
76
- const { variants, name, defaultVariants, ...defaultProps } = options
77
+ const {
78
+ variants,
79
+ name,
80
+ defaultVariants,
81
+ acceptsClassName: acceptsClassNameProp,
82
+ ...defaultProps
83
+ } = options
77
84
  if (defaultVariants) {
78
85
  Object.assign(defaultProps, defaultVariants)
79
86
  }
@@ -84,12 +91,7 @@ export function styled<
84
91
  RNComponents.add(Component)
85
92
  isReactNativeWeb = true
86
93
  } else {
87
- if (process.env.NODE_ENV === 'development') {
88
- // eslint-disable-next-line no-console
89
- console.warn(
90
- `Invalid styled() component: Tamagui only accepts react-native Text, View, Image, TextInput, or another styled Tamagui view`
91
- )
92
- }
94
+ // assuming some sort of non-tamagui non-rn view, will always use style={}
93
95
  }
94
96
  }
95
97
  const reactNativeWebComponent = isReactNativeWeb
@@ -107,6 +109,8 @@ export function styled<
107
109
  isInput || staticExtractionOptions?.isText || parentStaticConfig?.isText || Comp === Text
108
110
  )
109
111
 
112
+ const acceptsClassName = acceptsClassNameProp ?? (isTamagui || isReactNativeWeb)
113
+
110
114
  const conf: Partial<StaticConfig> = {
111
115
  ...staticExtractionOptions,
112
116
  ...(!isTamagui && {
@@ -125,10 +129,12 @@ export function styled<
125
129
  isInput,
126
130
  isText,
127
131
  isImage,
132
+ acceptsClassName,
128
133
  }
129
134
 
130
135
  // TODO compiler doesn't have logic to include children, de-opt (see EnsureFlexed for test usage)
131
- if (defaultProps.children) {
136
+ // bail on non className views as well
137
+ if (defaultProps.children || !acceptsClassName) {
132
138
  conf.neverFlatten = true
133
139
  }
134
140
 
package/src/types.tsx CHANGED
@@ -162,7 +162,12 @@ export type CreateTamaguiConfig<
162
162
  fonts: RemoveLanguagePostfixes<F>
163
163
  fontLanguages: GetLanguagePostfixes<F> extends never ? string[] : GetLanguagePostfixes<F>[]
164
164
  tokens: A
165
- themes: B
165
+ // parsed
166
+ themes: {
167
+ [Name in keyof B]: {
168
+ [Key in keyof B[Name]]: Variable
169
+ }
170
+ }
166
171
  shorthands: C
167
172
  media: D
168
173
  animations: AnimationDriver<E>
@@ -230,13 +235,16 @@ export type GenericTamaguiConfig = CreateTamaguiConfig<
230
235
  >
231
236
 
232
237
  // since TamaguiConfig will be re-declared, these will all be typed globally
233
- export type ThemeObject = TamaguiConfig['themes'][keyof TamaguiConfig['themes']]
238
+ export type ThemeDefinition = TamaguiConfig['themes'][keyof TamaguiConfig['themes']]
239
+ export type ThemeKeys = keyof ThemeDefinition
240
+ export type ThemeParsed = {
241
+ [key in ThemeKeys]: Variable
242
+ }
234
243
  export type Tokens = TamaguiConfig['tokens']
235
244
  export type Shorthands = TamaguiConfig['shorthands']
236
245
  export type Media = TamaguiConfig['media']
237
246
  export type Themes = TamaguiConfig['themes']
238
247
  export type ThemeName = GetAltThemeNames<keyof Themes>
239
- export type ThemeKeys = keyof ThemeObject
240
248
  export type ThemeTokens = `$${ThemeKeys}`
241
249
  export type AnimationKeys = Omit<GetAnimationKeys<TamaguiConfig>, number>
242
250
  export type FontLanguages = ArrayIntersection<TamaguiConfig['fontLanguages']>
@@ -264,6 +272,26 @@ export type CreateTamaguiProps = {
264
272
  }
265
273
 
266
274
  defaultTheme?: string
275
+
276
+ /**
277
+ * *Advanced use case* For all CSS extracted views, this has no effect.
278
+ *
279
+ * For SSR compatibility on the web, Tamagui will render once with the settings
280
+ * from `mediaQueryDefaultActive` set for all media queries. Then, it will render
281
+ * again after the initial render using the proper media query values. This is so that
282
+ * hydration will match perfectly with the server.
283
+ *
284
+ * Setting disableSSR will avoid this second render by setting the media query state
285
+ * to the actual browser dimensions on initial load. This is only useful for client-only
286
+ * apps.
287
+ *
288
+ */
289
+ disableSSR?: boolean
290
+
291
+ /**
292
+ * Disable inserting a theme class in the DOM or context, allowing you to manually place it higher.
293
+ * For custom use cases like integration with next-theme.
294
+ */
267
295
  disableRootThemeClass?: boolean
268
296
 
269
297
  defaultProps?: Record<string, any> & {
@@ -314,9 +342,6 @@ export type TamaguiInternalConfig<
314
342
  getCSS: () => string
315
343
  parsed: boolean
316
344
  inverseShorthands: Record<string, string>
317
-
318
- // just passed in from CreateTamaguiProps
319
- themeClassNameOnRoot?: boolean
320
345
  }
321
346
 
322
347
  export type GetAnimationKeys<A extends GenericTamaguiConfig> = keyof A['animations']['animations']
@@ -352,10 +377,6 @@ export type MediaProps<A> = {
352
377
  export type MediaQueries = {
353
378
  [key in MediaQueryKey]: MediaQueryObject
354
379
  }
355
- export type ConfigureMediaQueryOptions = {
356
- queries?: MediaQueries
357
- defaultActive?: Record<MediaQueryKey, boolean>
358
- }
359
380
 
360
381
  export type TransformStyleProps = {
361
382
  x?: number
@@ -423,7 +444,7 @@ export type SizeTokens = GetTokenString<keyof Tokens['size']> | number
423
444
  export type SpaceTokens = GetTokenString<keyof Tokens['space']> | number | boolean
424
445
  export type ColorTokens =
425
446
  | GetTokenString<keyof Tokens['color']>
426
- | GetTokenString<keyof ThemeObject>
447
+ | GetTokenString<keyof ThemeParsed>
427
448
  | CSSColorNames
428
449
  export type ZIndexTokens = GetTokenString<keyof Tokens['zIndex']> | number
429
450
  export type RadiusTokens = GetTokenString<keyof Tokens['radius']> | number
@@ -640,7 +661,7 @@ export type TamaguiProviderProps = Partial<Omit<ThemeProviderProps, 'children'>>
640
661
  export type PropMapper = (
641
662
  key: string,
642
663
  value: any,
643
- theme: ThemeObject,
664
+ theme: ThemeParsed,
644
665
  props: Record<string, any>,
645
666
  state: Partial<SplitStyleState>,
646
667
  languageContext?: FontLanguageProps,
@@ -773,6 +794,13 @@ export type StaticConfig = {
773
794
  * Used insternally to attach default props to names
774
795
  */
775
796
  parentNames?: string[]
797
+
798
+ /**
799
+ * By default if styled() doesn't recognize a parent Tamagui compoent or specific react-native views,
800
+ * it will assume the passed in component only accepts style={} for react-native compatibility.
801
+ * Setting `acceptsClassName: true` indicates Tamagui can pass in className props.
802
+ */
803
+ acceptsClassName?: boolean
776
804
  }
777
805
 
778
806
  /**
@@ -1183,6 +1211,7 @@ export type TamaguiComponentState = {
1183
1211
  pressIn: boolean
1184
1212
  focus: boolean
1185
1213
  mounted: boolean
1214
+ mediaState?: null | Record<string, boolean>
1186
1215
  animation?: null | {
1187
1216
  style?: any
1188
1217
  avoidClasses?: boolean
@@ -1,8 +1,8 @@
1
1
  import { addTheme } from './addTheme'
2
2
  import { activeThemeManagers } from './hooks/useTheme'
3
- import { ThemeObject } from './types'
3
+ import { ThemeDefinition } from './types'
4
4
 
5
- export function updateTheme({ name, theme }: { name: string; theme: ThemeObject }) {
5
+ export function updateTheme({ name, theme }: { name: string; theme: ThemeDefinition }) {
6
6
  const next = addTheme({ name, theme, insertCSS: true, update: true })
7
7
 
8
8
  if (process.env.TAMAGUI_TARGET === 'native') {
@@ -1,7 +1,6 @@
1
- import { useForceUpdate } from '@tamagui/use-force-update'
2
1
  import * as React from 'react'
3
2
 
4
- import { isClient, isRSC, isSSR } from '../constants/platform'
3
+ import { isClient, isRSC, isServer } from '../constants/platform'
5
4
  import { ButtonInsideButtonContext } from '../contexts/ButtonInsideButtonContext'
6
5
  import { TextAncestorProvider } from '../contexts/TextAncestorContext'
7
6
  import { useMediaQueryListeners } from '../hooks/useMedia'
@@ -22,8 +21,8 @@ export function TamaguiProvider({
22
21
  )
23
22
  }
24
23
 
25
- if (!isSSR) {
26
- useMediaQueryListeners()
24
+ if (!isServer) {
25
+ useMediaQueryListeners(config)
27
26
  }
28
27
 
29
28
  if (isClient) {
@@ -1,7 +1,7 @@
1
- import { ThemeObject } from './types';
1
+ import { ThemeDefinition } from './types';
2
2
  export declare function addTheme({ name: themeName, theme: themeIn, insertCSS, update, }: {
3
3
  name: string;
4
- theme: ThemeObject;
4
+ theme: ThemeDefinition;
5
5
  insertCSS?: boolean;
6
6
  update?: boolean;
7
7
  }): {