@tamagui/core 1.0.1-beta.216 → 1.0.1-beta.217

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 (39) hide show
  1. package/dist/cjs/createComponent.js +37 -86
  2. package/dist/cjs/createComponent.js.map +2 -2
  3. package/dist/cjs/createTamagui.js +2 -2
  4. package/dist/cjs/createTamagui.js.map +1 -1
  5. package/dist/cjs/helpers/getSplitStyles.js +44 -38
  6. package/dist/cjs/helpers/getSplitStyles.js.map +3 -3
  7. package/dist/cjs/helpers/mergeProps.js.map +2 -2
  8. package/dist/cjs/helpers/normalizeValueWithProperty.js +7 -2
  9. package/dist/cjs/helpers/normalizeValueWithProperty.js.map +2 -2
  10. package/dist/cjs/helpers/useShallowSetState.js.map +2 -2
  11. package/dist/cjs/hooks/useMedia.js +43 -38
  12. package/dist/cjs/hooks/useMedia.js.map +2 -2
  13. package/dist/cjs/views/ThemeInverse.js +1 -2
  14. package/dist/cjs/views/ThemeInverse.js.map +2 -2
  15. package/dist/esm/createComponent.js +39 -92
  16. package/dist/esm/createComponent.js.map +2 -2
  17. package/dist/esm/createTamagui.js +2 -2
  18. package/dist/esm/createTamagui.js.map +1 -1
  19. package/dist/esm/helpers/getSplitStyles.js +44 -38
  20. package/dist/esm/helpers/getSplitStyles.js.map +3 -3
  21. package/dist/esm/helpers/mergeProps.js.map +2 -2
  22. package/dist/esm/helpers/normalizeValueWithProperty.js +7 -2
  23. package/dist/esm/helpers/normalizeValueWithProperty.js.map +2 -2
  24. package/dist/esm/helpers/useShallowSetState.js.map +2 -2
  25. package/dist/esm/hooks/useMedia.js +44 -39
  26. package/dist/esm/hooks/useMedia.js.map +2 -2
  27. package/dist/esm/views/ThemeInverse.js +1 -2
  28. package/dist/esm/views/ThemeInverse.js.map +2 -2
  29. package/package.json +16 -16
  30. package/src/createComponent.tsx +50 -109
  31. package/src/createTamagui.ts +2 -2
  32. package/src/helpers/getSplitStyles.tsx +44 -37
  33. package/src/helpers/mergeProps.ts +7 -7
  34. package/src/helpers/normalizeValueWithProperty.ts +9 -2
  35. package/src/helpers/useShallowSetState.tsx +3 -1
  36. package/src/hooks/useMedia.tsx +49 -41
  37. package/src/views/ThemeInverse.tsx +1 -2
  38. package/types/helpers/mergeProps.d.ts +2 -2
  39. package/types/helpers/useShallowSetState.d.ts +2 -1
@@ -1,5 +1,5 @@
1
1
  import { useComposedRefs } from '@tamagui/compose-refs'
2
- import { isClient, isRSC, isWeb, useIsomorphicLayoutEffect } from '@tamagui/constants'
2
+ import { isClient, isRSC, isServer, isWeb, useIsomorphicLayoutEffect } from '@tamagui/constants'
3
3
  /* eslint-disable react-hooks/rules-of-hooks */
4
4
  import {
5
5
  composeEventHandlers,
@@ -29,23 +29,18 @@ import { stackDefaultStyles } from './constants/constants'
29
29
  import { FontLanguageContext } from './contexts/FontLanguageContext'
30
30
  import { TextAncestorContext } from './contexts/TextAncestorContext'
31
31
  import { extendStaticConfig, parseStaticConfig } from './helpers/extendStaticConfig'
32
- import {
33
- SplitStyleResult,
34
- getSubStyle,
35
- insertSplitStyles,
36
- useSplitStyles,
37
- } from './helpers/getSplitStyles'
32
+ import { getSubStyle, useSplitStyles } from './helpers/getSplitStyles'
38
33
  import { getAllSelectors } from './helpers/insertStyleRule'
39
34
  import { mergeProps } from './helpers/mergeProps'
40
35
  import { mergeTransform } from './helpers/mergeTransform'
41
36
  import { proxyThemeVariables } from './helpers/proxyThemeVariables'
42
- import { pseudoDescriptors } from './helpers/pseudoDescriptors'
43
37
  import { useShallowSetState } from './helpers/useShallowSetState'
44
38
  import { getRect, measureLayout, useElementLayout } from './hooks/useElementLayout'
45
39
  import { addMediaQueryListener, getInitialMediaState } from './hooks/useMedia'
46
40
  import { useServerRef, useServerState } from './hooks/useServerHooks'
47
41
  import { getThemeManager, useTheme } from './hooks/useTheme'
48
42
  import {
43
+ DebugProp,
49
44
  SpaceDirection,
50
45
  SpaceTokens,
51
46
  SpacerProps,
@@ -140,7 +135,6 @@ export function createComponent<
140
135
  let defaultNativeStyle: any
141
136
  let tamaguiDefaultProps: any
142
137
  let defaultTag: string | undefined
143
- let initialSplitStyles: SplitStyleResult
144
138
 
145
139
  // see onConfiguredOnce below which attaches a name then to this component
146
140
  const component = forwardRef<Ref, ComponentPropTypes>((propsIn: any, forwardedRef) => {
@@ -157,7 +151,7 @@ export function createComponent<
157
151
 
158
152
  // time`mergeProps`
159
153
 
160
- const debugProp = props['debug']
154
+ const debugProp = props['debug'] as DebugProp
161
155
  const { Component, isText, isZStack } = staticConfig
162
156
  const componentName = props.componentName || staticConfig.componentName
163
157
  const componentClassName = props.asChild
@@ -194,7 +188,7 @@ export function createComponent<
194
188
  )
195
189
 
196
190
  if (process.env.NODE_ENV === 'development') {
197
- if (props['debug']) {
191
+ if (debugProp) {
198
192
  const name = `${
199
193
  componentName || Component?.displayName || Component?.name || '[Unnamed Component]'
200
194
  }`
@@ -203,8 +197,18 @@ export function createComponent<
203
197
  const banner = `${name}${dataIs ? ` ${dataIs}` : ''} ${type}`
204
198
  // eslint-disable-next-line no-console
205
199
  console.group(`%c ${banner}`, 'background: yellow;')
206
- // eslint-disable-next-line no-console
207
- console.log(`state`, state)
200
+ if (!isServer) {
201
+ // eslint-disable-next-line no-console
202
+ console.log(`state`, state)
203
+ // eslint-disable-next-line no-console
204
+ console.groupCollapsed('props')
205
+ // eslint-disable-next-line no-console
206
+ console.log(props)
207
+ // eslint-disable-next-line no-console
208
+ console.log('order', Object.keys(props))
209
+ // eslint-disable-next-line no-console
210
+ console.groupEnd()
211
+ }
208
212
  }
209
213
  }
210
214
 
@@ -254,19 +258,18 @@ export function createComponent<
254
258
 
255
259
  // time`setupStateConf`
256
260
 
257
- const splitStyleState: SplitStyleState = {
258
- ...state,
259
- noClassNames,
260
- dynamicStylesInline: noClassNames,
261
- hasTextAncestor,
262
- resolveVariablesAs: 'value',
263
- }
264
261
  const splitStyles = useSplitStyles(
265
262
  props,
266
263
  staticConfig,
267
264
  theme,
268
- splitStyleState,
269
- props.asChild ? null : initialSplitStyles,
265
+ {
266
+ ...state,
267
+ noClassNames,
268
+ dynamicStylesInline: noClassNames,
269
+ hasTextAncestor,
270
+ resolveVariablesAs: 'value',
271
+ },
272
+ null,
270
273
  languageContext || undefined,
271
274
  elementType,
272
275
  debugProp
@@ -593,7 +596,7 @@ export function createComponent<
593
596
  }, [unPress])
594
597
  }
595
598
 
596
- let styles: any[]
599
+ let styles: Record<string, any>[]
597
600
 
598
601
  if (isStringElement && shouldAvoidClasses && !shouldForcePseudo) {
599
602
  styles = {
@@ -607,11 +610,7 @@ export function createComponent<
607
610
  // ugly but for now...
608
611
  if (shouldForcePseudo) {
609
612
  const next = {}
610
- for (const style of styles) {
611
- if (style) {
612
- Object.assign(next, style)
613
- }
614
- }
613
+ styles.forEach((style) => Object.assign(next, style))
615
614
  // @ts-ignore
616
615
  Object.assign(splitStyles.style, next)
617
616
  }
@@ -687,16 +686,11 @@ export function createComponent<
687
686
  viewProps.style = styles
688
687
  }
689
688
 
690
- // TODO need to loop active variants and see if they have matchin pseudos and apply as well
691
- const initialPseudos = initialSplitStyles?.pseudos
692
- const runtimePressStyle = noClassNames && (pseudos?.pressStyle || initialPseudos?.pressStyle)
689
+ const runtimePressStyle = !disabled && noClassNames && pseudos?.pressStyle
693
690
  const attachPress = !!(runtimePressStyle || onPress || onPressOut || onPressIn || onClick)
694
-
695
- const isHoverable = isWeb
696
- const runtimeHoverStyle = noClassNames && (pseudos?.hoverStyle || initialPseudos.hoverStyle)
697
- const attachHover =
698
- isHoverable &&
699
- !!(runtimeHoverStyle || onHoverIn || onHoverOut || onMouseEnter || onMouseLeave)
691
+ const runtimeHoverStyle = !disabled && noClassNames && pseudos?.hoverStyle
692
+ const isHoverable =
693
+ isWeb && !!(runtimeHoverStyle || onHoverIn || onHoverOut || onMouseEnter || onMouseLeave)
700
694
 
701
695
  const handlesPressEvents = !isWeb && !asChild
702
696
 
@@ -706,7 +700,7 @@ export function createComponent<
706
700
  ? false
707
701
  : Boolean(
708
702
  attachPress ||
709
- attachHover ||
703
+ isHoverable ||
710
704
  'pressStyle' in props ||
711
705
  'onPress' in props ||
712
706
  'onPressIn' in props ||
@@ -729,28 +723,28 @@ export function createComponent<
729
723
  onMouseUp?.(e)
730
724
  }
731
725
  : undefined,
732
- ...(attachHover && {
733
- onMouseEnter: attachHover
726
+ ...(isHoverable && {
727
+ onMouseEnter: isHoverable
734
728
  ? (e) => {
735
729
  const next: Partial<typeof state> = {}
736
- if (attachHover) {
730
+ if (isHoverable) {
737
731
  next.hover = true
738
732
  }
739
733
  if (state.pressIn) {
740
734
  next.press = true
741
735
  }
742
- if (attachHover || state.pressIn) {
736
+ if (isHoverable || state.pressIn) {
743
737
  setStateShallow(next)
744
738
  }
745
739
  onHoverIn?.(e)
746
740
  onMouseEnter?.(e)
747
741
  }
748
742
  : undefined,
749
- onMouseLeave: attachHover
743
+ onMouseLeave: isHoverable
750
744
  ? (e) => {
751
745
  const next: Partial<typeof state> = {}
752
746
  mouseUps.add(unPress)
753
- if (attachHover) {
747
+ if (isHoverable) {
754
748
  next.hover = false
755
749
  }
756
750
  if (state.pressIn) {
@@ -855,7 +849,7 @@ export function createComponent<
855
849
  // EVENTS native
856
850
  if (process.env.TAMAGUI_TARGET === 'native') {
857
851
  // add focus events
858
- const attachFocus = !!(pseudos.focusStyle || (initialPseudos && initialPseudos.focusStyle))
852
+ const attachFocus = !!pseudos.focusStyle
859
853
  if (attachFocus) {
860
854
  viewProps.onFocus = mergeEvent(viewProps.onFocus, () => {
861
855
  setStateShallow({ focus: true })
@@ -917,7 +911,7 @@ export function createComponent<
917
911
  if (typeof window !== 'undefined') {
918
912
  // prettier-ignore
919
913
  // eslint-disable-next-line no-console
920
- console.log({ state, shouldProvideThemeManager, isAnimated, isAnimatedReactNativeWeb, tamaguiDefaultProps, viewProps, splitStyles, animationStyles, handlesPressEvents, isStringElement, classNamesIn: props.className?.split(' '), classNamesOut: viewProps.className?.split(' '), events, shouldAttach, styles, pseudos, content, childEls, shouldAvoidClasses, avoidClasses: avoidClassesWhileAnimating, animation: props.animation, style: splitStylesStyle, defaultNativeStyle, initialSplitStyles, ...(typeof window !== 'undefined' ? { theme, themeClassName: theme.className, staticConfig, tamaguiConfig, events, shouldAvoidClasses, shouldForcePseudo, classNames: Object.fromEntries(Object.entries(classNames).map(([k, v]) => [v, getAllSelectors()[v]])) } : null) })
914
+ console.log({ state, shouldProvideThemeManager, isAnimated, isAnimatedReactNativeWeb, tamaguiDefaultProps, viewProps, splitStyles, animationStyles, handlesPressEvents, isStringElement, classNamesIn: props.className?.split(' '), classNamesOut: viewProps.className?.split(' '), events, shouldAttach, styles, pseudos, content, childEls, shouldAvoidClasses, avoidClasses: avoidClassesWhileAnimating, animation: props.animation, style: splitStylesStyle, defaultNativeStyle, ...(typeof window !== 'undefined' ? { theme, themeClassName: theme.className, staticConfig, tamaguiConfig, events, shouldAvoidClasses, shouldForcePseudo, classNames: Object.fromEntries(Object.entries(classNames).map(([k, v]) => [v, getAllSelectors()[v]])) } : null) })
921
915
  }
922
916
  // eslint-disable-next-line no-console
923
917
  console.groupEnd()
@@ -994,50 +988,22 @@ export function createComponent<
994
988
  const debug = defaultPropsIn['debug']
995
989
 
996
990
  // remove all classNames
997
- const [ourProps, ourClassNames] = mergeProps(defaultPropsIn, {}, true)
991
+ const [ourProps, ourClassNames] = mergeProps(defaultPropsIn, {})
998
992
 
999
993
  if (ourProps.tag) {
1000
994
  defaultTag = ourProps.tag
1001
995
  }
1002
996
 
1003
997
  const noClassNames = !staticConfig.acceptsClassName
1004
-
1005
- initialSplitStyles = insertSplitStyles(
1006
- ourProps,
1007
- staticConfig,
1008
- initialTheme,
1009
- {
1010
- unmounted: false,
1011
- hover: false,
1012
- press: false,
1013
- pressIn: false,
1014
- focus: false,
1015
- resolveVariablesAs: 'both',
1016
- noClassNames,
1017
- },
1018
- undefined,
1019
- undefined,
1020
- debug
1021
- )
998
+ const { name, variants, defaultVariants, ...restProps } = ourProps
1022
999
 
1023
1000
  // must preserve prop order
1024
1001
  // leave out className because we handle that already with initialSplitStyles.classNames
1025
1002
  // otherwise it confuses variant functions getting className props
1026
- const [defaults, defaultsClassnames] = mergeProps(
1027
- component.defaultProps as any,
1028
- isWeb
1029
- ? { ...initialSplitStyles.pseudos, ...initialSplitStyles.viewProps }
1030
- : initialSplitStyles.viewProps,
1031
- true
1032
- // conf.inverseShorthands
1033
- )
1034
-
1035
- // avoid passing className props to variants
1036
- initialSplitStyles.classNames = {
1037
- ...defaultsClassnames,
1038
- ...initialSplitStyles.classNames,
1039
- ...ourClassNames,
1040
- }
1003
+ const [defaults, defaultsClassnames] = mergeProps(component.defaultProps as any, {
1004
+ ...defaultVariants,
1005
+ ...restProps,
1006
+ })
1041
1007
 
1042
1008
  defaultNativeStyle = {}
1043
1009
 
@@ -1056,40 +1022,15 @@ export function createComponent<
1056
1022
  }
1057
1023
  }
1058
1024
 
1059
- if (Object.keys(defaults).length) {
1060
- tamaguiDefaultProps = defaults
1061
-
1062
- // temp bugfix, splitStyles isn't returning variables when resolveVariablesAs == 'both'
1063
- if (!isWeb) {
1064
- Object.keys(pseudoDescriptors).forEach((k) => {
1065
- if (ourProps[k]) {
1066
- // @ts-ignore
1067
- initialSplitStyles.pseudos[k] = null
1068
- tamaguiDefaultProps[k] = ourProps[k]
1069
- }
1070
- })
1071
- }
1072
- }
1025
+ // set to global
1026
+ tamaguiDefaultProps = defaults
1073
1027
 
1074
1028
  // add debug logs
1075
1029
  if (process.env.NODE_ENV === 'development' && debug) {
1076
1030
  if (process.env.IS_STATIC !== 'is_static') {
1031
+ // prettier-ignore
1077
1032
  // eslint-disable-next-line no-console
1078
- console.log(`🐛 [${staticConfig.componentName || 'Component'}]`, {
1079
- staticConfig,
1080
- initialSplitStyles,
1081
- tamaguiDefaultProps,
1082
- defaultNativeStyle,
1083
- defaults,
1084
- defaultPropsIn,
1085
- defaultPropsKeyOrder: Object.keys(staticConfig.defaultProps),
1086
- defaultPropsInKeyOrder: Object.keys(defaultPropsIn).map((k) => [k, defaultPropsIn[k]]),
1087
- ourProps,
1088
- ourClassNames,
1089
- defaultsClassnames,
1090
- defaultTag,
1091
- noClassNames,
1092
- })
1033
+ console.log(`🐛 [${staticConfig.componentName || 'Component'}]`, { staticConfig, tamaguiDefaultProps, defaultNativeStyle, defaults, defaultPropsIn, defaultPropsKeyOrder: Object.keys(staticConfig.defaultProps), defaultPropsInKeyOrder: Object.keys(defaultPropsIn).map((k) => [k, defaultPropsIn[k]]), ourProps, ourClassNames, defaultsClassnames, defaultTag, noClassNames, })
1093
1034
  }
1094
1035
  }
1095
1036
  })
@@ -195,8 +195,8 @@ export function createTamagui<Conf extends CreateTamaguiProps>(
195
195
  tokensParsed,
196
196
  parsed: true,
197
197
  getCSS: () => `
198
- .is_Text .is_Text { display: inline-flex; }
199
- ._dsp_contents { display: contents; }
198
+ .is_Text .is_Text {display:inline-flex;}
199
+ ._dsp_contents {display:contents;}
200
200
  ${themeConfig.css}
201
201
  ${getAllRules().join('\n')}
202
202
  `,
@@ -196,21 +196,9 @@ export const getSplitStyles: StyleSplitter = (
196
196
  const shouldDoClasses =
197
197
  staticConfig.acceptsClassName && (isWeb || IS_STATIC) && !state.noClassNames
198
198
 
199
- let style: ViewStyle = {}
199
+ const style: ViewStyle = {}
200
200
  let flatTransforms: Record<string, any> | undefined
201
201
 
202
- function mergeStyle(key: string, val: any) {
203
- usedKeys[key] = usedKeys[key] || 1
204
- if (val && val[0] === '_') {
205
- classNames[key] = val
206
- } else if (key in stylePropsTransform) {
207
- flatTransforms ||= {}
208
- flatTransforms[key] = val
209
- } else {
210
- style[key] = normalizeValueWithProperty(val, key)
211
- }
212
- }
213
-
214
202
  const len = propKeys.length
215
203
  const rulesToInsert: RulesToInsert = []
216
204
  const classNames: ClassNamesObject = {}
@@ -225,6 +213,18 @@ export const getSplitStyles: StyleSplitter = (
225
213
  let keyInit = propKeys[i]
226
214
  const valInit = props[keyInit]
227
215
 
216
+ if (validStyleProps[keyInit] || keyInit.includes('-')) {
217
+ if (valInit && valInit[0] === '_') {
218
+ if (shouldDoClasses) {
219
+ classNames[keyInit] = valInit
220
+ } else {
221
+ style[keyInit] = reverseMapClassNameToValue(keyInit, valInit)
222
+ }
223
+ usedKeys[keyInit] = 1
224
+ continue
225
+ }
226
+ }
227
+
228
228
  // normalize shorthands up front
229
229
  const expandedKey = conf.shorthands[keyInit]
230
230
  if (expandedKey) {
@@ -251,6 +251,18 @@ export const getSplitStyles: StyleSplitter = (
251
251
  continue
252
252
  }
253
253
 
254
+ function mergeStyle(key: string, val: any) {
255
+ usedKeys[key] = usedKeys[key] || 1
256
+ if (val && val[0] === '_') {
257
+ classNames[key] = val
258
+ } else if (key in stylePropsTransform) {
259
+ flatTransforms ||= {}
260
+ flatTransforms[key] = val
261
+ } else {
262
+ style[key] = normalizeValueWithProperty(val, key)
263
+ }
264
+ }
265
+
254
266
  if (process.env.TAMAGUI_TARGET === 'web') {
255
267
  /**
256
268
  * Copying in the accessibility/prop handling from react-native-web here
@@ -416,6 +428,7 @@ export const getSplitStyles: StyleSplitter = (
416
428
  !isValidClassName &&
417
429
  keyInit.includes(PROP_SPLIT) &&
418
430
  validStyles[keyInit.split(PROP_SPLIT)[0]]
431
+
419
432
  if (isValidClassName || isMediaOrPseudo) {
420
433
  usedKeys[keyInit] = 1
421
434
  mergeClassName(transforms, classNames, keyInit, valInit, isMediaOrPseudo)
@@ -479,13 +492,9 @@ export const getSplitStyles: StyleSplitter = (
479
492
  }
480
493
 
481
494
  if (process.env.NODE_ENV === 'development' && debug === 'verbose') {
482
- if (!isServer) {
483
- // eslint-disable-next-line no-console
484
- console.log('expanded', expanded)
485
- // eslint-disable-next-line no-console
486
- console.log('usedKeys', usedKeys)
495
+ if (!isServer && isWeb) {
487
496
  // eslint-disable-next-line no-console
488
- console.log('current', { ...style })
497
+ console.log('expanded', expanded, '\nusedKeys', usedKeys, '\ncurrent', { ...style })
489
498
  }
490
499
  // eslint-disable-next-line no-console
491
500
  console.groupEnd()
@@ -677,31 +686,32 @@ export const getSplitStyles: StyleSplitter = (
677
686
 
678
687
  // add in defaults if not set:
679
688
  if (parentSplitStyles) {
680
- if (process.env.NODE_ENV === 'development') {
681
- if (debug === 'verbose') {
682
- // eslint-disable-next-line no-console
683
- console.log(` add in styled() parent styles`, { parentSplitStyles, shouldDoClasses })
684
- }
685
- }
686
689
  if (process.env.TAMAGUI_TARGET === 'web') {
687
- for (const key in parentSplitStyles.classNames) {
688
- if (key in classNames) continue
689
- if (key in style) continue
690
- const val = parentSplitStyles.classNames[key]
691
- if (!shouldDoClasses) {
692
- style[key] = reverseMapClassNameToValue(key, val)
693
- } else {
690
+ if (shouldDoClasses) {
691
+ for (const key in parentSplitStyles.classNames) {
692
+ const val = parentSplitStyles.classNames[key]
693
+ if (key in style || key in classNames) continue
694
694
  classNames[key] = val
695
695
  }
696
696
  }
697
697
  }
698
698
  if (!shouldDoClasses) {
699
699
  for (const key in parentSplitStyles.style) {
700
- if (key in classNames) continue
701
- if (key in style) continue
700
+ if (key in classNames || key in style) continue
702
701
  style[key] = parentSplitStyles.style[key]
703
702
  }
704
703
  }
704
+ if (process.env.NODE_ENV === 'development') {
705
+ if (debug === 'verbose') {
706
+ // eslint-disable-next-line no-console
707
+ console.log(` add in parent styled()`, {
708
+ parentSplitStyles,
709
+ shouldDoClasses,
710
+ style: { ...style },
711
+ classNames: { ...classNames },
712
+ })
713
+ }
714
+ }
705
715
  }
706
716
 
707
717
  if (process.env.TAMAGUI_TARGET === 'web') {
@@ -715,9 +725,6 @@ export const getSplitStyles: StyleSplitter = (
715
725
  addStyleToInsertRules(rulesToInsert, atomicStyle)
716
726
  mergeClassName(transforms, classNames, key, atomicStyle.identifier)
717
727
  }
718
- if (!IS_STATIC) {
719
- style = emptyObject
720
- }
721
728
  }
722
729
  }
723
730
 
@@ -12,16 +12,16 @@
12
12
  * - shorthands can be expanded before merging
13
13
  */
14
14
 
15
- type StringRecord = Record<string, string>
15
+ type AnyRecord = Record<string, any>
16
16
 
17
17
  export const mergeProps = (
18
18
  a: Object,
19
19
  b: Object,
20
20
  leaveOutClassNames = false,
21
- inverseShorthands?: StringRecord
21
+ inverseShorthands?: AnyRecord
22
22
  ) => {
23
- const out: StringRecord = {}
24
- const outCns: StringRecord = leaveOutClassNames ? {} : (null as any)
23
+ const out: AnyRecord = {}
24
+ const outCns: AnyRecord = leaveOutClassNames ? {} : (null as any)
25
25
  for (const key in a) {
26
26
  mergeProp(out, outCns, a, b, key, leaveOutClassNames, inverseShorthands)
27
27
  }
@@ -32,13 +32,13 @@ export const mergeProps = (
32
32
  }
33
33
 
34
34
  function mergeProp(
35
- out: StringRecord,
36
- outCns: StringRecord,
35
+ out: AnyRecord,
36
+ outCns: AnyRecord,
37
37
  a: Object,
38
38
  b: Object | undefined,
39
39
  key: string,
40
40
  leaveOutClassNames: boolean,
41
- inverseShorthands?: StringRecord
41
+ inverseShorthands?: AnyRecord
42
42
  ) {
43
43
  const val = a[key]
44
44
  const shorthand = inverseShorthands ? inverseShorthands[key] : null
@@ -3,6 +3,8 @@
3
3
  * Copyright (c) Nicolas Gallagher licensed under the MIT license.
4
4
  */
5
5
 
6
+ import { isWeb } from '@tamagui/constants'
7
+
6
8
  import { getAllSelectors } from './insertStyleRule'
7
9
  import { normalizeColor } from './normalizeColor'
8
10
 
@@ -20,9 +22,11 @@ export function normalizeValueWithProperty(value: any, property?: string): any {
20
22
  if (
21
23
  process.env.TAMAGUI_TARGET === 'web' &&
22
24
  typeof value === 'number' &&
23
- (property === undefined || !unitlessNumbers[property])
25
+ (property === undefined || (!unitlessNumbers[property] && !stringNumbers[property]))
24
26
  ) {
25
27
  res = `${value}px`
28
+ } else if (isWeb && property !== undefined && stringNumbers[property]) {
29
+ res = `${res}`
26
30
  } else if (property && colorProps[property]) {
27
31
  res = normalizeColor(value)
28
32
  cache[value] = res
@@ -45,6 +49,10 @@ const colorProps = {
45
49
  textShadowColor: true,
46
50
  }
47
51
 
52
+ const stringNumbers = {
53
+ zIndex: true,
54
+ }
55
+
48
56
  const unitlessNumbers = {
49
57
  WebkitLineClamp: true,
50
58
  animationIterationCount: true,
@@ -74,7 +82,6 @@ const unitlessNumbers = {
74
82
  orphans: true,
75
83
  tabSize: true,
76
84
  widows: true,
77
- zIndex: true,
78
85
  zoom: true,
79
86
  scale: true,
80
87
  scaleX: true,
@@ -1,8 +1,10 @@
1
1
  import React, { useCallback } from 'react'
2
2
 
3
+ import { DebugProp } from '../types'
4
+
3
5
  export function useShallowSetState<State extends Object>(
4
6
  setter: React.Dispatch<React.SetStateAction<State>>,
5
- debug?: boolean | 'verbose',
7
+ debug?: DebugProp,
6
8
  debugName?: string
7
9
  ) {
8
10
  return useCallback(
@@ -1,5 +1,5 @@
1
1
  import { useIsomorphicLayoutEffect } from '@tamagui/constants'
2
- import { MutableRefObject, useEffect, useMemo, useState } from 'react'
2
+ import { MutableRefObject, useEffect, useMemo, useState, useSyncExternalStore } from 'react'
3
3
 
4
4
  import { getConfig } from '../config'
5
5
  import { createProxy } from '../helpers/createProxy'
@@ -54,15 +54,12 @@ export function removeMediaQueryListener(key: MediaQueryKey, cb: MediaListener)
54
54
 
55
55
  export const mediaQueryConfig: MediaQueries = {}
56
56
 
57
- export const getMedia = () => {
58
- return mediaState
59
- }
57
+ export const getMedia = () => mediaState
60
58
 
61
59
  // for SSR capture it at time of startup
62
- let curState: MediaQueryState
63
60
  let initState: MediaQueryState
64
61
  export const getInitialMediaState = () => {
65
- return (getConfig().disableSSR ? curState : initState) || {}
62
+ return (getConfig().disableSSR ? mediaState : initState) || {}
66
63
  }
67
64
 
68
65
  let mediaKeysOrdered: string[]
@@ -79,8 +76,8 @@ export const configureMedia = (config: TamaguiInternalConfig) => {
79
76
  mediaState[key] = mediaQueryDefaultActive?.[key] || false
80
77
  }
81
78
  Object.assign(mediaQueryConfig, media)
82
- curState = { ...mediaState }
83
- initState = curState
79
+ initState = { ...mediaState }
80
+ updateCurrentState()
84
81
  mediaKeysOrdered = Object.keys(media)
85
82
  if (config.disableSSR) {
86
83
  setupMediaListeners()
@@ -126,7 +123,7 @@ function setupMediaListeners() {
126
123
  const next = !!getMatch().matches
127
124
  if (next === mediaState[key]) return
128
125
  mediaState[key] = next
129
- curState = { ...mediaState }
126
+ updateCurrentState()
130
127
  const cur = listeners[key]
131
128
  if (cur?.size) {
132
129
  cur.forEach((cb) => cb(next))
@@ -143,45 +140,56 @@ export function useMediaListeners(config: TamaguiInternalConfig) {
143
140
  }, [])
144
141
  }
145
142
 
143
+ const nextTick = process.nextTick || setImmediate
144
+ const currentStateListeners = new Set<any>()
145
+ let isUpdating = false
146
+ function updateCurrentState() {
147
+ if (isUpdating) return
148
+ isUpdating = true
149
+ nextTick(() => {
150
+ currentStateListeners.forEach((cb) => cb(mediaState))
151
+ isUpdating = false
152
+ })
153
+ }
154
+ function subscribe(subscriber: any) {
155
+ currentStateListeners.add(subscriber)
156
+ return () => currentStateListeners.delete(subscriber)
157
+ }
158
+
159
+ type MediaKeysState = {
160
+ [key: string]: any
161
+ }
162
+
146
163
  export function useMedia(): {
147
164
  [key in MediaQueryKey]: boolean
148
165
  } {
149
- const keys = useSafeRef({} as Record<string, boolean>)
150
- const [state, setState] = useState<MediaQueryState>(() => getMediaState(initState || {}, keys))
166
+ const keys = useSafeRef<MediaKeysState>({ prev: initState })
151
167
 
152
- function updateState() {
153
- setState((prev) => {
154
- for (const key in keys.current) {
155
- if (prev[key] !== mediaState[key]) {
156
- return getMediaState({ ...mediaState }, keys)
157
- }
168
+ const state = useSyncExternalStore<MediaQueryState>(
169
+ subscribe,
170
+ () => {
171
+ const { prev, ...curKeys } = keys.current
172
+ const shouldUpdate = !prev || Object.keys(curKeys).some((k) => mediaState[k] !== prev[k])
173
+ if (shouldUpdate) {
174
+ const next = { ...mediaState }
175
+ keys.current.prev = next
176
+ return next
158
177
  }
159
178
  return prev
160
- })
161
- }
162
-
163
- useIsomorphicLayoutEffect(() => {
164
- updateState()
165
- const disposers = Object.keys(keys.current).map((key) =>
166
- addMediaQueryListener(key, updateState)
167
- )
168
- return () => {
169
- disposers.forEach((cb) => cb())
170
- }
171
- }, [keys.current])
172
-
173
- return state
174
- }
175
-
176
- function getMediaState(state: MediaQueryState, keys: MutableRefObject<Record<string, boolean>>) {
177
- return new Proxy(state, {
178
- get(_, key: string) {
179
- if (!keys.current[key]) {
180
- keys.current[key] = true
181
- }
182
- return Reflect.get(state, key)
183
179
  },
184
- })
180
+ () => initState
181
+ )
182
+
183
+ return useMemo(() => {
184
+ return new Proxy(state, {
185
+ get(_, key: string) {
186
+ if (!keys.current[key]) {
187
+ keys.current[key] = true
188
+ }
189
+ return Reflect.get(state, key)
190
+ },
191
+ })
192
+ }, [state])
185
193
  }
186
194
 
187
195
  /**