@tamagui/web 1.55.2 → 1.56.0
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.
- package/dist/cjs/config.js +1 -1
- package/dist/cjs/config.js.map +1 -1
- package/dist/cjs/createComponent.js +79 -21
- package/dist/cjs/createComponent.js.map +2 -2
- package/dist/cjs/helpers/createMediaStyle.js +47 -39
- package/dist/cjs/helpers/createMediaStyle.js.map +1 -1
- package/dist/cjs/helpers/createShallowSetState.js +6 -4
- package/dist/cjs/helpers/createShallowSetState.js.map +1 -1
- package/dist/cjs/helpers/getGroupPropParts.js +41 -0
- package/dist/cjs/helpers/getGroupPropParts.js.map +6 -0
- package/dist/cjs/helpers/getSplitStyles.js +49 -18
- package/dist/cjs/helpers/getSplitStyles.js.map +2 -2
- package/dist/cjs/helpers/insertStyleRule.js +9 -2
- package/dist/cjs/helpers/insertStyleRule.js.map +1 -1
- package/dist/cjs/helpers/matchMedia.js +1 -0
- package/dist/cjs/helpers/matchMedia.js.map +1 -1
- package/dist/cjs/helpers/matchMedia.native.js +1 -0
- package/dist/cjs/helpers/matchMedia.native.js.map +1 -1
- package/dist/cjs/hooks/useMedia.js +31 -3
- package/dist/cjs/hooks/useMedia.js.map +1 -1
- package/dist/cjs/index.js +0 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/views/Slot.js +4 -11
- package/dist/cjs/views/Slot.js.map +1 -1
- package/dist/esm/config.js +1 -1
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/createComponent.js +85 -24
- package/dist/esm/createComponent.js.map +2 -2
- package/dist/esm/helpers/createMediaStyle.js +47 -39
- package/dist/esm/helpers/createMediaStyle.js.map +1 -1
- package/dist/esm/helpers/createShallowSetState.js +4 -3
- package/dist/esm/helpers/createShallowSetState.js.map +1 -1
- package/dist/esm/helpers/getGroupPropParts.js +17 -0
- package/dist/esm/helpers/getGroupPropParts.js.map +6 -0
- package/dist/esm/helpers/getSplitStyles.js +50 -18
- package/dist/esm/helpers/getSplitStyles.js.map +2 -2
- package/dist/esm/helpers/insertStyleRule.js +9 -2
- package/dist/esm/helpers/insertStyleRule.js.map +1 -1
- package/dist/esm/helpers/matchMedia.js +1 -0
- package/dist/esm/helpers/matchMedia.js.map +1 -1
- package/dist/esm/helpers/matchMedia.native.js +1 -0
- package/dist/esm/helpers/matchMedia.native.js.map +1 -1
- package/dist/esm/hooks/useMedia.js +29 -3
- package/dist/esm/hooks/useMedia.js.map +1 -1
- package/dist/esm/index.js +0 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/views/Slot.js +3 -9
- package/dist/esm/views/Slot.js.map +1 -1
- package/package.json +9 -10
- package/src/config.ts +1 -1
- package/src/createComponent.tsx +102 -25
- package/src/helpers/createMediaStyle.ts +59 -45
- package/src/helpers/createShallowSetState.tsx +2 -2
- package/src/helpers/getGroupPropParts.ts +14 -0
- package/src/helpers/getSplitStyles.tsx +60 -24
- package/src/helpers/insertStyleRule.tsx +17 -5
- package/src/helpers/matchMedia.native.ts +1 -0
- package/src/helpers/matchMedia.ts +1 -0
- package/src/hooks/useMedia.tsx +34 -3
- package/src/index.ts +0 -1
- package/src/types.tsx +43 -6
- package/src/views/Slot.tsx +2 -8
- package/types/createComponent.d.ts.map +1 -1
- package/types/helpers/createMediaStyle.d.ts +1 -1
- package/types/helpers/createMediaStyle.d.ts.map +1 -1
- package/types/helpers/createShallowSetState.d.ts +1 -0
- package/types/helpers/createShallowSetState.d.ts.map +1 -1
- package/types/helpers/getGroupPropParts.d.ts +6 -0
- package/types/helpers/getGroupPropParts.d.ts.map +1 -0
- package/types/helpers/getSplitStyles.d.ts.map +1 -1
- package/types/helpers/insertStyleRule.d.ts.map +1 -1
- package/types/helpers/matchMedia.d.ts.map +1 -1
- package/types/helpers/matchMedia.native.d.ts.map +1 -1
- package/types/hooks/useMedia.d.ts +6 -1
- package/types/hooks/useMedia.d.ts.map +1 -1
- package/types/index.d.ts +0 -1
- package/types/index.d.ts.map +1 -1
- package/types/types.d.ts +38 -4
- package/types/types.d.ts.map +1 -1
- package/types/views/Slot.d.ts +0 -1
- package/types/views/Slot.d.ts.map +1 -1
package/src/createComponent.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useComposedRefs } from '@tamagui/compose-refs'
|
|
2
2
|
import { isClient, isServer, isWeb } from '@tamagui/constants'
|
|
3
|
-
import { validStyles } from '@tamagui/helpers'
|
|
3
|
+
import { composeEventHandlers, validStyles } from '@tamagui/helpers'
|
|
4
4
|
import { useDidFinishSSR } from '@tamagui/use-did-finish-ssr'
|
|
5
5
|
import React, {
|
|
6
6
|
Children,
|
|
@@ -21,18 +21,23 @@ import { getConfig, onConfiguredOnce } from './config'
|
|
|
21
21
|
import { stackDefaultStyles } from './constants/constants'
|
|
22
22
|
import { ComponentContext } from './contexts/ComponentContext'
|
|
23
23
|
import { didGetVariableValue, setDidGetVariableValue } from './createVariable'
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
createShallowSetState,
|
|
26
|
+
mergeIfNotShallowEqual,
|
|
27
|
+
} from './helpers/createShallowSetState'
|
|
25
28
|
import { useSplitStyles } from './helpers/getSplitStyles'
|
|
26
29
|
import { mergeProps } from './helpers/mergeProps'
|
|
27
30
|
import { proxyThemeVariables } from './helpers/proxyThemeVariables'
|
|
28
31
|
import { themeable } from './helpers/themeable'
|
|
29
|
-
import { setMediaShouldUpdate, useMedia } from './hooks/useMedia'
|
|
32
|
+
import { mediaKeyMatch, setMediaShouldUpdate, useMedia } from './hooks/useMedia'
|
|
30
33
|
import { useThemeWithState } from './hooks/useTheme'
|
|
31
34
|
import { hooks } from './setupHooks'
|
|
32
35
|
import {
|
|
36
|
+
ComponentContextI,
|
|
33
37
|
DebugProp,
|
|
34
38
|
DisposeFn,
|
|
35
|
-
|
|
39
|
+
GroupState,
|
|
40
|
+
LayoutEvent,
|
|
36
41
|
SpaceDirection,
|
|
37
42
|
SpaceValue,
|
|
38
43
|
SpacerProps,
|
|
@@ -235,6 +240,7 @@ export function createComponent<
|
|
|
235
240
|
// [animated, inversed]
|
|
236
241
|
const stateRef = useRef(
|
|
237
242
|
undefined as any as {
|
|
243
|
+
hasMeasured?: boolean
|
|
238
244
|
hasAnimated?: boolean
|
|
239
245
|
themeShallow?: boolean
|
|
240
246
|
isListeningToTheme?: boolean
|
|
@@ -291,13 +297,17 @@ export function createComponent<
|
|
|
291
297
|
let setStateShallow = createShallowSetState(setState)
|
|
292
298
|
|
|
293
299
|
const groupName = props.group as any as string
|
|
300
|
+
const groupClassName = groupName ? `t_group_${props.group}` : ''
|
|
301
|
+
|
|
294
302
|
if (groupName) {
|
|
295
303
|
// when we set state we also set our group state and emit an event for children listening:
|
|
296
304
|
const groupContextState = componentContext.groups.state
|
|
297
305
|
const og = setStateShallow
|
|
298
306
|
setStateShallow = (state) => {
|
|
299
307
|
og(state)
|
|
300
|
-
componentContext.groups.emit(groupName,
|
|
308
|
+
componentContext.groups.emit(groupName, {
|
|
309
|
+
pseudo: state,
|
|
310
|
+
})
|
|
301
311
|
// and mutate the current since its concurrent safe (children throw it in useState on mount)
|
|
302
312
|
const next = {
|
|
303
313
|
...groupContextState[groupName],
|
|
@@ -502,6 +512,11 @@ export function createComponent<
|
|
|
502
512
|
debugProp
|
|
503
513
|
)
|
|
504
514
|
|
|
515
|
+
// hide strategy will set this opacity = 0 until measured
|
|
516
|
+
if (props.group && props.untilMeasured === 'hide' && !stateRef.current.hasMeasured) {
|
|
517
|
+
splitStyles.style.opacity = 0
|
|
518
|
+
}
|
|
519
|
+
|
|
505
520
|
if (process.env.NODE_ENV === 'development' && time) time`split-styles`
|
|
506
521
|
|
|
507
522
|
stateRef.current.isListeningToTheme = splitStyles.dynamicThemeAccess
|
|
@@ -616,6 +631,12 @@ export function createComponent<
|
|
|
616
631
|
...nonTamaguiProps
|
|
617
632
|
} = viewPropsIn
|
|
618
633
|
|
|
634
|
+
if (process.env.NODE_ENV === 'development' && props.untilMeasured && !props.group) {
|
|
635
|
+
console.warn(
|
|
636
|
+
`You set the untilMeasured prop without setting group. This doesn't work, be sure to set untilMeasured on the parent that sets group, not the children that use the $group- prop.\n\nIf you meant to do this, you can disable this warning - either change untilMeasured and group at the same time, or do group={conditional ? 'name' : undefined}`
|
|
637
|
+
)
|
|
638
|
+
}
|
|
639
|
+
|
|
619
640
|
if (process.env.NODE_ENV === 'development' && time) time`destructure`
|
|
620
641
|
|
|
621
642
|
const disabled =
|
|
@@ -631,6 +652,24 @@ export function createComponent<
|
|
|
631
652
|
viewProps.theme = _themeProp
|
|
632
653
|
}
|
|
633
654
|
|
|
655
|
+
if (groupName) {
|
|
656
|
+
nonTamaguiProps.onLayout = composeEventHandlers(
|
|
657
|
+
nonTamaguiProps.onLayout,
|
|
658
|
+
(e: LayoutEvent) => {
|
|
659
|
+
componentContext.groups.emit(groupName, {
|
|
660
|
+
layout: e.nativeEvent.layout,
|
|
661
|
+
})
|
|
662
|
+
|
|
663
|
+
// force re-render if measure strategy is hide
|
|
664
|
+
if (!stateRef.current.hasMeasured && props.untilMeasured === 'hide') {
|
|
665
|
+
setState((prev) => ({ ...prev }))
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
stateRef.current.hasMeasured = true
|
|
669
|
+
}
|
|
670
|
+
)
|
|
671
|
+
}
|
|
672
|
+
|
|
634
673
|
// if react-native-web view just pass all props down
|
|
635
674
|
if (
|
|
636
675
|
process.env.TAMAGUI_TARGET === 'web' &&
|
|
@@ -672,7 +711,8 @@ export function createComponent<
|
|
|
672
711
|
// for example css driver needs to render once with the first styles, then again with the next
|
|
673
712
|
// if its a layout effect it will just skip that first render output
|
|
674
713
|
const shouldSetMounted = needsMount && state.unmounted
|
|
675
|
-
const { pseudoGroups } = splitStyles
|
|
714
|
+
const { pseudoGroups, mediaGroups } = splitStyles
|
|
715
|
+
|
|
676
716
|
useEffect(() => {
|
|
677
717
|
if (shouldSetMounted) {
|
|
678
718
|
const unmounted =
|
|
@@ -686,22 +726,36 @@ export function createComponent<
|
|
|
686
726
|
|
|
687
727
|
// parent group pseudo listening
|
|
688
728
|
let disposeGroupsListener: DisposeFn | undefined
|
|
689
|
-
if (pseudoGroups) {
|
|
690
|
-
const current = {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
729
|
+
if (pseudoGroups || mediaGroups) {
|
|
730
|
+
const current = {
|
|
731
|
+
pseudo: {},
|
|
732
|
+
media: {},
|
|
733
|
+
} satisfies GroupState
|
|
734
|
+
disposeGroupsListener = componentContext.groups.subscribe(
|
|
735
|
+
(name, { layout, pseudo }) => {
|
|
736
|
+
if (pseudo && pseudoGroups?.has(name)) {
|
|
737
|
+
// we emit a partial so merge it + change reference so mergeIfNotShallowEqual runs
|
|
738
|
+
Object.assign(current.pseudo, pseudo)
|
|
739
|
+
persist()
|
|
740
|
+
} else if (layout && mediaGroups) {
|
|
741
|
+
const mediaState = getMediaState(mediaGroups, layout)
|
|
742
|
+
const next = mergeIfNotShallowEqual(current.media, mediaState)
|
|
743
|
+
if (next !== current.media) {
|
|
744
|
+
Object.assign(current.media, next)
|
|
745
|
+
persist()
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
function persist() {
|
|
749
|
+
setStateShallow({
|
|
750
|
+
// force it to be referentially different so it always updates
|
|
751
|
+
group: {
|
|
752
|
+
...state.group,
|
|
753
|
+
[name]: current,
|
|
754
|
+
},
|
|
755
|
+
})
|
|
698
756
|
}
|
|
699
|
-
setStateShallow({
|
|
700
|
-
// force it to be referentially different so it always updates
|
|
701
|
-
group,
|
|
702
|
-
})
|
|
703
757
|
}
|
|
704
|
-
|
|
758
|
+
)
|
|
705
759
|
}
|
|
706
760
|
|
|
707
761
|
return () => {
|
|
@@ -712,6 +766,7 @@ export function createComponent<
|
|
|
712
766
|
shouldSetMounted,
|
|
713
767
|
state.unmounted,
|
|
714
768
|
pseudoGroups ? Object.keys([...pseudoGroups]).join('') : 0,
|
|
769
|
+
mediaGroups ? Object.keys([...mediaGroups]).join('') : 0,
|
|
715
770
|
])
|
|
716
771
|
|
|
717
772
|
const avoidAnimationStyle = keepStyleSSR && state.unmounted === true
|
|
@@ -735,7 +790,7 @@ export function createComponent<
|
|
|
735
790
|
componentName ? componentClassName : '',
|
|
736
791
|
fontFamilyClassName,
|
|
737
792
|
classNames ? Object.values(classNames).join(' ') : '',
|
|
738
|
-
|
|
793
|
+
groupClassName,
|
|
739
794
|
]
|
|
740
795
|
className = classList.join(' ')
|
|
741
796
|
|
|
@@ -934,16 +989,24 @@ export function createComponent<
|
|
|
934
989
|
|
|
935
990
|
// must override context so siblings don't clobber initial state
|
|
936
991
|
const subGroupContext = useMemo(() => {
|
|
937
|
-
if (!groupName) return
|
|
992
|
+
if (!groupName) return
|
|
938
993
|
// change reference so context value updates
|
|
939
994
|
return {
|
|
940
995
|
...componentContext.groups,
|
|
941
996
|
// change reference so as we mutate it doesn't affect siblings etc
|
|
942
997
|
state: {
|
|
943
998
|
...componentContext.groups.state,
|
|
944
|
-
[groupName]:
|
|
999
|
+
[groupName]: {
|
|
1000
|
+
pseudo: initialState,
|
|
1001
|
+
// capture just initial width and height if they exist
|
|
1002
|
+
// will have top, left, width, height (not x, y)
|
|
1003
|
+
layout: {
|
|
1004
|
+
width: fromPx(splitStyles.style.width as any),
|
|
1005
|
+
height: fromPx(splitStyles.style.height as any),
|
|
1006
|
+
} as any,
|
|
1007
|
+
},
|
|
945
1008
|
},
|
|
946
|
-
}
|
|
1009
|
+
} satisfies ComponentContextI['groups']
|
|
947
1010
|
}, [groupName])
|
|
948
1011
|
|
|
949
1012
|
if (groupName && subGroupContext) {
|
|
@@ -971,7 +1034,7 @@ export function createComponent<
|
|
|
971
1034
|
if (events || isAnimatedReactNativeWeb) {
|
|
972
1035
|
content = (
|
|
973
1036
|
<span
|
|
974
|
-
className={`${isAnimatedReactNativeWeb ? className : ''}
|
|
1037
|
+
className={`${isAnimatedReactNativeWeb ? className : ''} _dsp_contents`}
|
|
975
1038
|
{...(events && {
|
|
976
1039
|
onMouseEnter: events.onMouseEnter,
|
|
977
1040
|
onMouseLeave: events.onMouseLeave,
|
|
@@ -1320,3 +1383,17 @@ function hasAnimatedStyleValue(style: Object) {
|
|
|
1320
1383
|
return val && typeof val === 'object' && '_animation' in val
|
|
1321
1384
|
})
|
|
1322
1385
|
}
|
|
1386
|
+
|
|
1387
|
+
function getMediaState(
|
|
1388
|
+
mediaGroups: Set<string>,
|
|
1389
|
+
layout: LayoutEvent['nativeEvent']['layout']
|
|
1390
|
+
) {
|
|
1391
|
+
return Object.fromEntries(
|
|
1392
|
+
[...mediaGroups].map((mediaKey) => {
|
|
1393
|
+
return [mediaKey, mediaKeyMatch(mediaKey, layout as any)]
|
|
1394
|
+
})
|
|
1395
|
+
)
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
const fromPx = (val?: number | string) =>
|
|
1399
|
+
typeof val !== 'string' ? val : +val.replace('px', '')
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getConfig } from '../config'
|
|
2
2
|
import { mediaObjectToString } from '../hooks/useMedia'
|
|
3
3
|
import type { MediaQueries, MediaStyleObject, StyleObject } from '../types'
|
|
4
|
+
import { getGroupPropParts } from './getGroupPropParts'
|
|
4
5
|
|
|
5
6
|
// TODO have this be used by extractMediaStyle in tamagui static
|
|
6
7
|
// not synced to static/constants for now
|
|
@@ -9,47 +10,60 @@ export const MEDIA_SEP = '_'
|
|
|
9
10
|
let prefixes: Record<string, string> | null = null
|
|
10
11
|
let selectors: Record<string, string> | null = null
|
|
11
12
|
|
|
12
|
-
const
|
|
13
|
+
const groupPseudoToPseudoCSSMap = {
|
|
13
14
|
press: 'active',
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export const createMediaStyle = (
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
styleObject: StyleObject,
|
|
19
|
+
mediaKeyIn: string,
|
|
19
20
|
mediaQueries: MediaQueries,
|
|
20
21
|
negate?: boolean,
|
|
21
22
|
priority?: number
|
|
22
23
|
): MediaStyleObject => {
|
|
24
|
+
const { property, identifier, rules } = styleObject
|
|
23
25
|
const conf = getConfig()
|
|
24
26
|
const enableMediaPropOrder = conf.settings.mediaPropOrder
|
|
25
|
-
const isThemeMedia =
|
|
26
|
-
const isPlatformMedia = !isThemeMedia &&
|
|
27
|
-
const
|
|
28
|
-
const isNonWindowMedia = isThemeMedia || isPlatformMedia ||
|
|
27
|
+
const isThemeMedia = mediaKeyIn.startsWith('theme-')
|
|
28
|
+
const isPlatformMedia = !isThemeMedia && mediaKeyIn.startsWith('platform-')
|
|
29
|
+
const isGroup = !isThemeMedia && !isPlatformMedia && mediaKeyIn.startsWith('group-')
|
|
30
|
+
const isNonWindowMedia = isThemeMedia || isPlatformMedia || isGroup
|
|
29
31
|
const negKey = negate ? '0' : ''
|
|
30
32
|
const ogPrefix = identifier.slice(0, identifier.indexOf('-') + 1)
|
|
33
|
+
const id = `${ogPrefix}${MEDIA_SEP}${mediaKeyIn.replace('-', '')}${negKey}${MEDIA_SEP}`
|
|
31
34
|
|
|
32
|
-
let styleRule
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
let styleRule = ''
|
|
36
|
+
let groupMediaKey: string | undefined
|
|
37
|
+
let containerName: string | undefined
|
|
38
|
+
let nextIdentifier = identifier.replace(ogPrefix, id)
|
|
39
|
+
let styleInner = rules.map((rule) => rule.replace(identifier, nextIdentifier)).join(';')
|
|
36
40
|
|
|
37
41
|
if (isNonWindowMedia) {
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
.
|
|
41
|
-
|
|
42
|
+
const precedenceImportancePrefix = new Array((priority || 0) + (isGroup ? 1 : 0))
|
|
43
|
+
.fill(':root')
|
|
44
|
+
.join('')
|
|
45
|
+
|
|
46
|
+
if (isThemeMedia || isGroup) {
|
|
47
|
+
const groupInfo = getGroupPropParts(mediaKeyIn)
|
|
48
|
+
const mediaName = groupInfo?.name
|
|
49
|
+
groupMediaKey = groupInfo?.media
|
|
50
|
+
if (isGroup) {
|
|
51
|
+
containerName = mediaName
|
|
52
|
+
}
|
|
53
|
+
const name = (isGroup ? 'group_' : '') + mediaName
|
|
42
54
|
|
|
43
|
-
if (isThemeMedia || isGroupMedia) {
|
|
44
|
-
const [_, groupName, groupPseudo] = mediaKey.split('-')
|
|
45
|
-
const name = (isGroupMedia ? 'group_' : '') + groupName
|
|
46
55
|
const selectorStart = styleInner.indexOf(':root')
|
|
47
56
|
const selectorEnd = styleInner.lastIndexOf('{')
|
|
48
57
|
const selector = styleInner.slice(selectorStart, selectorEnd)
|
|
49
58
|
const precedenceSpace = conf.themeClassNameOnRoot ? '' : ' '
|
|
50
|
-
const pseudoSelectorName =
|
|
59
|
+
const pseudoSelectorName = groupInfo.pseudo
|
|
60
|
+
? groupPseudoToPseudoCSSMap[groupInfo.pseudo] || groupInfo.pseudo
|
|
61
|
+
: undefined
|
|
62
|
+
|
|
51
63
|
const pseudoSelector = pseudoSelectorName ? `:${pseudoSelectorName}` : ''
|
|
52
|
-
const
|
|
64
|
+
const presedencePrefix = `:root${precedenceImportancePrefix}${precedenceSpace}`
|
|
65
|
+
const mediaSelector = `.t_${name}${pseudoSelector}`
|
|
66
|
+
const nextSelector = `${presedencePrefix}${mediaSelector} ${selector.replace(
|
|
53
67
|
':root',
|
|
54
68
|
''
|
|
55
69
|
)}`
|
|
@@ -57,48 +71,48 @@ export const createMediaStyle = (
|
|
|
57
71
|
// add back in the { we used to split
|
|
58
72
|
styleRule = styleInner.replace(selector, nextSelector)
|
|
59
73
|
} else {
|
|
60
|
-
styleRule = `${
|
|
74
|
+
styleRule = `${precedenceImportancePrefix}${styleInner}`
|
|
61
75
|
}
|
|
62
|
-
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!isNonWindowMedia || groupMediaKey) {
|
|
79
|
+
// one time cost:
|
|
80
|
+
// TODO MOVE THIS INTO SETUP AREA AND EXPORT IT
|
|
63
81
|
if (!selectors) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
} else {
|
|
70
|
-
const mediaKeys = Object.keys(mediaQueries)
|
|
82
|
+
const mediaKeys = Object.keys(mediaQueries)
|
|
83
|
+
selectors = Object.fromEntries(
|
|
84
|
+
mediaKeys.map((key) => [key, mediaObjectToString(mediaQueries[key])])
|
|
85
|
+
)
|
|
86
|
+
if (!enableMediaPropOrder) {
|
|
71
87
|
prefixes = Object.fromEntries(
|
|
72
|
-
mediaKeys.map((
|
|
73
|
-
key,
|
|
74
|
-
new Array(index + 1).fill(':root').join(''),
|
|
75
|
-
])
|
|
76
|
-
)
|
|
77
|
-
selectors = Object.fromEntries(
|
|
78
|
-
mediaKeys.map((key) => [key, mediaObjectToString(mediaQueries[key])])
|
|
88
|
+
mediaKeys.map((k, index) => [k, new Array(index + 1).fill(':root').join('')])
|
|
79
89
|
)
|
|
80
90
|
}
|
|
81
91
|
}
|
|
82
92
|
|
|
83
|
-
const
|
|
93
|
+
const mediaKey = groupMediaKey || mediaKeyIn
|
|
94
|
+
const mediaSelector = selectors[mediaKey]
|
|
95
|
+
const screenStr = negate ? 'not all and' : ''
|
|
96
|
+
const mediaQuery = `${screenStr} ${mediaSelector}`
|
|
97
|
+
const precedenceImportancePrefix = groupMediaKey
|
|
98
|
+
? ''
|
|
99
|
+
: enableMediaPropOrder
|
|
84
100
|
? // this new array should be cached
|
|
85
101
|
new Array(priority).fill(':root').join('')
|
|
86
102
|
: // @ts-ignore
|
|
87
103
|
prefixes[mediaKey]
|
|
104
|
+
const prefix = groupMediaKey ? `@container ${containerName}` : '@media'
|
|
88
105
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const styleInner = rules
|
|
93
|
-
.map((rule) => rule.replace(identifier, nextIdentifier))
|
|
94
|
-
.join(';')
|
|
106
|
+
if (groupMediaKey) {
|
|
107
|
+
styleInner = styleRule
|
|
108
|
+
}
|
|
95
109
|
|
|
96
110
|
// combines media queries if they already exist
|
|
97
|
-
if (styleInner.includes(
|
|
111
|
+
if (styleInner.includes(prefix)) {
|
|
98
112
|
// combine
|
|
99
113
|
styleRule = styleInner.replace('{', ` and ${mediaQuery} {`)
|
|
100
114
|
} else {
|
|
101
|
-
styleRule =
|
|
115
|
+
styleRule = `${prefix} ${mediaQuery}{${precedenceImportancePrefix}${styleInner}}`
|
|
102
116
|
}
|
|
103
117
|
}
|
|
104
118
|
|
|
@@ -5,10 +5,10 @@ import { TamaguiComponentState } from '../types'
|
|
|
5
5
|
export function createShallowSetState<State extends TamaguiComponentState>(
|
|
6
6
|
setter: React.Dispatch<React.SetStateAction<State>>
|
|
7
7
|
) {
|
|
8
|
-
return (next: Partial<State>) => setter((prev) =>
|
|
8
|
+
return (next: Partial<State>) => setter((prev) => mergeIfNotShallowEqual(prev, next))
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
function
|
|
11
|
+
export function mergeIfNotShallowEqual(prev, next) {
|
|
12
12
|
for (const key in next) {
|
|
13
13
|
if (prev[key] !== next[key]) {
|
|
14
14
|
return { ...prev, ...next }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { getMedia } from '../hooks/useMedia'
|
|
2
|
+
|
|
3
|
+
export function getGroupPropParts(groupProp: string) {
|
|
4
|
+
const mediaQueries = getMedia()
|
|
5
|
+
const [_, name, part3, part4] = groupProp.split('-')
|
|
6
|
+
let pseudo: string | undefined
|
|
7
|
+
const media = part3 in mediaQueries ? part3 : undefined
|
|
8
|
+
if (!media) {
|
|
9
|
+
pseudo = part3
|
|
10
|
+
} else {
|
|
11
|
+
pseudo = part4
|
|
12
|
+
}
|
|
13
|
+
return { name, pseudo, media }
|
|
14
|
+
}
|
|
@@ -28,6 +28,8 @@ import {
|
|
|
28
28
|
getMediaImportanceIfMoreImportant,
|
|
29
29
|
mediaState as globalMediaState,
|
|
30
30
|
isMediaKey,
|
|
31
|
+
mediaKeyMatch,
|
|
32
|
+
mediaKeyToQuery,
|
|
31
33
|
mediaQueryConfig,
|
|
32
34
|
mergeMediaByImportance,
|
|
33
35
|
} from '../hooks/useMedia'
|
|
@@ -54,6 +56,7 @@ import type {
|
|
|
54
56
|
import type { LanguageContextType } from '../views/FontLanguage.types'
|
|
55
57
|
import { createMediaStyle } from './createMediaStyle'
|
|
56
58
|
import { fixStyles } from './expandStyles'
|
|
59
|
+
import { getGroupPropParts } from './getGroupPropParts'
|
|
57
60
|
import { generateAtomicStyles, getStylesAtomic, styleToCSS } from './getStylesAtomic'
|
|
58
61
|
import {
|
|
59
62
|
insertStyleRules,
|
|
@@ -145,27 +148,27 @@ export const getSplitStyles: StyleSplitter = (
|
|
|
145
148
|
} = staticConfig
|
|
146
149
|
const validStyleProps = isText ? stylePropsText : validStyles
|
|
147
150
|
const viewProps: GetStyleResult['viewProps'] = {}
|
|
148
|
-
let pseudos: PseudoStyles | null = null
|
|
149
151
|
const mediaState = styleProps.mediaState || globalMediaState
|
|
150
152
|
const usedKeys: Record<string, number> = {}
|
|
151
|
-
let space: SpaceTokens | null = props.space
|
|
152
|
-
let hasMedia: boolean | string[] = false
|
|
153
|
-
let dynamicThemeAccess: boolean | undefined
|
|
154
|
-
let pseudoGroups: Set<string> | undefined
|
|
155
153
|
const shouldDoClasses = acceptsClassName && isWeb && !styleProps.noClassNames
|
|
156
|
-
|
|
157
|
-
let style: ViewStyleWithPseudos = {}
|
|
158
154
|
const rulesToInsert: RulesToInsert = []
|
|
159
155
|
const classNames: ClassNamesObject = {}
|
|
160
|
-
let className = '' // existing classNames
|
|
161
156
|
// we need to gather these specific to each media query / pseudo
|
|
162
157
|
// value is [hash, val], so ["-jnjad-asdnjk", "scaleX(1) rotate(10deg)"]
|
|
163
158
|
const transforms: Record<TransformNamespaceKey, [string, string]> = {}
|
|
164
159
|
|
|
160
|
+
let pseudos: PseudoStyles | null = null
|
|
161
|
+
let space: SpaceTokens | null = props.space
|
|
162
|
+
let hasMedia: boolean | string[] = false
|
|
163
|
+
let dynamicThemeAccess: boolean | undefined
|
|
164
|
+
let pseudoGroups: Set<string> | undefined
|
|
165
|
+
let mediaGroups: Set<string> | undefined
|
|
166
|
+
let style: ViewStyleWithPseudos = {}
|
|
167
|
+
let className = '' // existing classNames
|
|
165
168
|
let mediaStylesSeen = 0
|
|
166
169
|
|
|
167
170
|
/**
|
|
168
|
-
* Not the biggest fan of creating
|
|
171
|
+
* Not the biggest fan of creating an object but it is a nice API
|
|
169
172
|
*/
|
|
170
173
|
const styleState: GetStyleState = {
|
|
171
174
|
curProps: Object.assign({}, props),
|
|
@@ -246,7 +249,21 @@ export const getSplitStyles: StyleSplitter = (
|
|
|
246
249
|
|
|
247
250
|
if (keyInit === 'className') continue // handled above
|
|
248
251
|
if (keyInit in usedKeys) continue
|
|
249
|
-
if (keyInit in skipProps && !isHOC)
|
|
252
|
+
if (keyInit in skipProps && !isHOC) {
|
|
253
|
+
if (keyInit === 'group') {
|
|
254
|
+
// add container style
|
|
255
|
+
const identifier = `t_group_${valInit}`
|
|
256
|
+
const containerCSS = {
|
|
257
|
+
identifier,
|
|
258
|
+
property: 'container',
|
|
259
|
+
rules: [
|
|
260
|
+
`.${identifier} { container-name: ${valInit}; container-type: inline-size; }`,
|
|
261
|
+
],
|
|
262
|
+
}
|
|
263
|
+
addStyleToInsertRules(rulesToInsert, containerCSS)
|
|
264
|
+
}
|
|
265
|
+
continue
|
|
266
|
+
}
|
|
250
267
|
|
|
251
268
|
styleState.curProps[keyInit] = valInit
|
|
252
269
|
|
|
@@ -669,6 +686,7 @@ export const getSplitStyles: StyleSplitter = (
|
|
|
669
686
|
if (!val) continue
|
|
670
687
|
|
|
671
688
|
// TODO can avoid processing this if !shouldDoClasses + state is off
|
|
689
|
+
// (note: can't because we need to set defaults on enter/exit or else enforce that they should)
|
|
672
690
|
const pseudoStyleObject = getSubStyle(
|
|
673
691
|
styleState,
|
|
674
692
|
key,
|
|
@@ -865,6 +883,7 @@ export const getSplitStyles: StyleSplitter = (
|
|
|
865
883
|
if (shouldDoClasses) {
|
|
866
884
|
if (hasSpace) {
|
|
867
885
|
delete mediaStyle['space']
|
|
886
|
+
// TODO group/theme/platform + space support (or just make it official not supported in favor of gap)
|
|
868
887
|
if (mediaState[mediaKeyShort]) {
|
|
869
888
|
const importance = getMediaImportanceIfMoreImportant(
|
|
870
889
|
mediaKeyShort,
|
|
@@ -923,31 +942,46 @@ export const getSplitStyles: StyleSplitter = (
|
|
|
923
942
|
continue
|
|
924
943
|
}
|
|
925
944
|
} else if (isGroupMedia) {
|
|
926
|
-
const
|
|
945
|
+
const groupInfo = getGroupPropParts(mediaKeyShort)
|
|
946
|
+
const groupName = groupInfo.name
|
|
927
947
|
|
|
928
948
|
// $group-x
|
|
929
|
-
|
|
949
|
+
const groupContext = context?.groups.state[groupName]
|
|
950
|
+
if (!groupContext) {
|
|
930
951
|
if (process.env.NODE_ENV === 'development' && debug) {
|
|
931
952
|
console.warn(`No parent with group prop, skipping styles: ${groupName}`)
|
|
932
953
|
}
|
|
933
954
|
continue
|
|
934
955
|
}
|
|
935
956
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
957
|
+
const groupPseudoKey = groupInfo.pseudo
|
|
958
|
+
const groupMediaKey = groupInfo.media
|
|
959
|
+
const componentGroupState = componentState.group?.[groupName]
|
|
960
|
+
|
|
961
|
+
if (groupMediaKey) {
|
|
962
|
+
mediaGroups ||= new Set()
|
|
963
|
+
mediaGroups.add(groupMediaKey)
|
|
964
|
+
const mediaState = componentGroupState?.media
|
|
965
|
+
let isActive = mediaState?.[groupMediaKey]
|
|
966
|
+
// use parent styles if width and height hardcoded we can do an inline media match and avoid double render
|
|
967
|
+
if (!mediaState && groupContext.layout) {
|
|
968
|
+
isActive = mediaKeyMatch(groupMediaKey, groupContext.layout)
|
|
969
|
+
}
|
|
970
|
+
if (!isActive) continue
|
|
971
|
+
importanceBump = 2
|
|
972
|
+
}
|
|
973
|
+
|
|
939
974
|
if (groupPseudoKey) {
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
975
|
+
pseudoGroups ||= new Set()
|
|
976
|
+
pseudoGroups.add(groupName)
|
|
977
|
+
const componentGroupPseudoState = (
|
|
978
|
+
componentGroupState ||
|
|
943
979
|
// fallback to context initially
|
|
944
980
|
context.groups.state[groupName]
|
|
945
|
-
|
|
946
|
-
const isActive =
|
|
947
|
-
if (!isActive)
|
|
948
|
-
|
|
949
|
-
}
|
|
950
|
-
const priority = pseudoPriorities[groupPseudoKeyShort]
|
|
981
|
+
).pseudo
|
|
982
|
+
const isActive = componentGroupPseudoState?.[groupPseudoKey]
|
|
983
|
+
if (!isActive) continue
|
|
984
|
+
const priority = pseudoPriorities[groupPseudoKey]
|
|
951
985
|
importanceBump = priority
|
|
952
986
|
}
|
|
953
987
|
}
|
|
@@ -1198,6 +1232,7 @@ export const getSplitStyles: StyleSplitter = (
|
|
|
1198
1232
|
rulesToInsert,
|
|
1199
1233
|
dynamicThemeAccess,
|
|
1200
1234
|
pseudoGroups,
|
|
1235
|
+
mediaGroups,
|
|
1201
1236
|
}
|
|
1202
1237
|
|
|
1203
1238
|
// native: swap out the right family based on weight/style
|
|
@@ -1417,6 +1452,7 @@ const mapTransformKeys = {
|
|
|
1417
1452
|
}
|
|
1418
1453
|
|
|
1419
1454
|
const skipProps = {
|
|
1455
|
+
untilMeasured: 1,
|
|
1420
1456
|
animation: 1,
|
|
1421
1457
|
space: 1,
|
|
1422
1458
|
animateOnly: 1,
|
|
@@ -9,6 +9,10 @@ import type {
|
|
|
9
9
|
TokensParsed,
|
|
10
10
|
} from '../types'
|
|
11
11
|
|
|
12
|
+
// only cache tamagui styles
|
|
13
|
+
// TODO merge totalSelectorsInserted and allSelectors?
|
|
14
|
+
const scannedCache = new WeakMap<CSSStyleSheet, string>()
|
|
15
|
+
const totalSelectorsInserted = new Map<string, number>()
|
|
12
16
|
const allSelectors: Record<string, string> = {}
|
|
13
17
|
const allRules: Record<string, string> = {}
|
|
14
18
|
export const insertedTransforms = {}
|
|
@@ -44,10 +48,6 @@ function addTransform(identifier: string, css: string, rule?: CSSRule) {
|
|
|
44
48
|
|
|
45
49
|
// multiple sheets could have the same ids so we have to count
|
|
46
50
|
|
|
47
|
-
// only cache tamagui styles
|
|
48
|
-
const scannedCache = new WeakMap<CSSStyleSheet, string>()
|
|
49
|
-
const totalSelectorsInserted = new Map<string, number>()
|
|
50
|
-
|
|
51
51
|
export function listenForSheetChanges() {
|
|
52
52
|
if (!isClient) return
|
|
53
53
|
|
|
@@ -378,6 +378,18 @@ export function shouldInsertStyleRules(identifier: string) {
|
|
|
378
378
|
return true
|
|
379
379
|
}
|
|
380
380
|
const total = totalSelectorsInserted.get(identifier)
|
|
381
|
-
|
|
381
|
+
|
|
382
|
+
if (process.env.NODE_ENV === 'development') {
|
|
383
|
+
if (
|
|
384
|
+
totalSelectorsInserted.size >
|
|
385
|
+
+(process.env.TAMAGUI_STYLE_INSERTION_WARNING_LIMIT || 50000)
|
|
386
|
+
) {
|
|
387
|
+
console.warn(
|
|
388
|
+
`Warning: inserting many CSS rules, you may be animating something and generating many CSS insertions, which can degrade performance. Instead, try using the "disableClassName" property on elements that change styles often. To disable this warning set TAMAGUI_STYLE_INSERTION_WARNING_LIMIT from 50000 to something higher`
|
|
389
|
+
)
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// note we are being conservative allowing duplicates
|
|
382
394
|
return total === undefined || total < 2
|
|
383
395
|
}
|