@niibase/uniwind 1.5.0 → 1.6.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.
Files changed (108) hide show
  1. package/CHANGELOG.md +1197 -0
  2. package/dist/common/components/web/ActivityIndicator.js +3 -1
  3. package/dist/common/components/web/Button.js +3 -1
  4. package/dist/common/components/web/FlatList.js +3 -1
  5. package/dist/common/components/web/Image.js +3 -1
  6. package/dist/common/components/web/ImageBackground.js +3 -1
  7. package/dist/common/components/web/KeyboardAvoidingView.js +3 -1
  8. package/dist/common/components/web/Modal.js +3 -1
  9. package/dist/common/components/web/Pressable.js +3 -1
  10. package/dist/common/components/web/RefreshControl.js +3 -1
  11. package/dist/common/components/web/SafeAreaView.js +3 -1
  12. package/dist/common/components/web/ScrollView.js +3 -1
  13. package/dist/common/components/web/SectionList.js +3 -1
  14. package/dist/common/components/web/Switch.js +3 -1
  15. package/dist/common/components/web/Text.js +3 -1
  16. package/dist/common/components/web/TextInput.js +3 -1
  17. package/dist/common/components/web/TouchableHighlight.js +3 -1
  18. package/dist/common/components/web/TouchableOpacity.js +3 -1
  19. package/dist/common/components/web/TouchableWithoutFeedback.js +3 -1
  20. package/dist/common/components/web/View.js +2 -0
  21. package/dist/common/components/web/VirtualizedList.js +3 -1
  22. package/dist/common/components/web/generateDataSet.js +18 -0
  23. package/dist/common/core/config/config.common.js +6 -3
  24. package/dist/common/core/web/cssListener.js +58 -6
  25. package/dist/common/core/web/getWebStyles.js +20 -18
  26. package/dist/common/css/extraUtilities.js +19 -0
  27. package/dist/common/css/index.js +2 -3
  28. package/dist/common/css/insets.js +2 -2
  29. package/dist/common/css/overwrite.js +2 -2
  30. package/dist/common/css/variants.js +2 -2
  31. package/dist/metro/metro-transformer.cjs +41 -7
  32. package/dist/metro/metro-transformer.mjs +41 -7
  33. package/dist/module/components/web/ActivityIndicator.js +3 -1
  34. package/dist/module/components/web/Button.js +3 -1
  35. package/dist/module/components/web/FlatList.js +3 -1
  36. package/dist/module/components/web/Image.js +3 -1
  37. package/dist/module/components/web/ImageBackground.js +3 -1
  38. package/dist/module/components/web/KeyboardAvoidingView.js +3 -1
  39. package/dist/module/components/web/Modal.js +3 -1
  40. package/dist/module/components/web/Pressable.js +3 -1
  41. package/dist/module/components/web/RefreshControl.js +3 -1
  42. package/dist/module/components/web/SafeAreaView.js +3 -1
  43. package/dist/module/components/web/ScrollView.js +3 -1
  44. package/dist/module/components/web/SectionList.js +3 -1
  45. package/dist/module/components/web/Switch.js +3 -1
  46. package/dist/module/components/web/Text.js +3 -1
  47. package/dist/module/components/web/TextInput.js +3 -1
  48. package/dist/module/components/web/TouchableHighlight.js +3 -1
  49. package/dist/module/components/web/TouchableOpacity.js +3 -1
  50. package/dist/module/components/web/TouchableWithoutFeedback.js +3 -1
  51. package/dist/module/components/web/View.js +2 -0
  52. package/dist/module/components/web/VirtualizedList.js +3 -1
  53. package/dist/module/components/web/generateDataSet.d.ts +32 -0
  54. package/dist/module/components/web/generateDataSet.js +9 -0
  55. package/dist/module/core/config/config.common.d.ts +2 -1
  56. package/dist/module/core/config/config.common.js +6 -3
  57. package/dist/module/core/web/cssListener.d.ts +6 -1
  58. package/dist/module/core/web/cssListener.js +58 -6
  59. package/dist/module/core/web/getWebStyles.js +21 -18
  60. package/dist/module/css/extraUtilities.d.ts +1 -0
  61. package/dist/module/css/extraUtilities.js +20 -0
  62. package/dist/module/css/index.js +8 -8
  63. package/dist/module/css/insets.d.ts +1 -1
  64. package/dist/module/css/insets.js +2 -1
  65. package/dist/module/css/overwrite.d.ts +1 -1
  66. package/dist/module/css/overwrite.js +1 -1
  67. package/dist/module/css/variants.d.ts +1 -1
  68. package/dist/module/css/variants.js +2 -1
  69. package/dist/module/index.d.ts +1 -0
  70. package/dist/shared/{uniwind.DTMo4epG.cjs → uniwind.BZyFsest.cjs} +28 -6
  71. package/dist/shared/{uniwind.BWb5KNML.mjs → uniwind.C-rHhHOg.mjs} +28 -6
  72. package/dist/vite/index.cjs +1 -1
  73. package/dist/vite/index.mjs +1 -1
  74. package/package.json +4 -2
  75. package/src/components/web/ActivityIndicator.tsx +2 -0
  76. package/src/components/web/Button.tsx +2 -0
  77. package/src/components/web/FlatList.tsx +2 -0
  78. package/src/components/web/Image.tsx +2 -0
  79. package/src/components/web/ImageBackground.tsx +2 -0
  80. package/src/components/web/KeyboardAvoidingView.tsx +2 -0
  81. package/src/components/web/Modal.tsx +2 -0
  82. package/src/components/web/Pressable.tsx +2 -0
  83. package/src/components/web/RefreshControl.tsx +2 -0
  84. package/src/components/web/SafeAreaView.tsx +2 -0
  85. package/src/components/web/ScrollView.tsx +2 -0
  86. package/src/components/web/SectionList.tsx +2 -0
  87. package/src/components/web/Switch.tsx +2 -0
  88. package/src/components/web/Text.tsx +2 -0
  89. package/src/components/web/TextInput.tsx +2 -0
  90. package/src/components/web/TouchableHighlight.tsx +2 -0
  91. package/src/components/web/TouchableOpacity.tsx +2 -0
  92. package/src/components/web/TouchableWithoutFeedback.tsx +2 -0
  93. package/src/components/web/View.tsx +2 -0
  94. package/src/components/web/VirtualizedList.tsx +2 -0
  95. package/src/components/web/generateDataSet.ts +52 -0
  96. package/src/components/web/rnw.ts +1 -1
  97. package/src/core/config/config.common.ts +7 -3
  98. package/src/core/web/cssListener.ts +73 -6
  99. package/src/core/web/getWebStyles.ts +26 -25
  100. package/src/css/extraUtilities.ts +26 -0
  101. package/src/css/index.ts +8 -8
  102. package/src/css/insets.ts +3 -1
  103. package/src/css/overwrite.ts +1 -1
  104. package/src/css/variants.ts +3 -1
  105. package/src/index.ts +1 -0
  106. package/src/metro/processor/css.ts +35 -0
  107. package/src/metro/utils/serialize.ts +13 -8
  108. package/uniwind.css +8 -0
@@ -1,5 +1,6 @@
1
1
  import { SafeAreaView as RNSafeAreaView, ViewProps } from 'react-native'
2
2
  import { copyComponentProperties } from '../utils'
3
+ import { generateDataSet } from './generateDataSet'
3
4
  import { toRNWClassName } from './rnw'
4
5
 
5
6
  export const SafeAreaView = copyComponentProperties(RNSafeAreaView, (props: ViewProps) => {
@@ -7,6 +8,7 @@ export const SafeAreaView = copyComponentProperties(RNSafeAreaView, (props: View
7
8
  <RNSafeAreaView
8
9
  {...props}
9
10
  style={[toRNWClassName(props.className), props.style]}
11
+ dataSet={generateDataSet(props)}
10
12
  />
11
13
  )
12
14
  })
@@ -1,5 +1,6 @@
1
1
  import { ScrollView as RNScrollView, ScrollViewProps } from 'react-native'
2
2
  import { copyComponentProperties } from '../utils'
3
+ import { generateDataSet } from './generateDataSet'
3
4
  import { toRNWClassName } from './rnw'
4
5
 
5
6
  export const ScrollView = copyComponentProperties(RNScrollView, (props: ScrollViewProps) => {
@@ -8,6 +9,7 @@ export const ScrollView = copyComponentProperties(RNScrollView, (props: ScrollVi
8
9
  {...props}
9
10
  style={[toRNWClassName(props.className), props.style]}
10
11
  contentContainerStyle={[toRNWClassName(props.contentContainerClassName), props.contentContainerStyle]}
12
+ dataSet={generateDataSet(props)}
11
13
  />
12
14
  )
13
15
  })
@@ -1,5 +1,6 @@
1
1
  import { SectionList as RNSectionList, SectionListProps } from 'react-native'
2
2
  import { copyComponentProperties } from '../utils'
3
+ import { generateDataSet } from './generateDataSet'
3
4
  import { toRNWClassName } from './rnw'
4
5
 
5
6
  export const SectionList = copyComponentProperties(RNSectionList, (props: SectionListProps<unknown, unknown>) => {
@@ -10,6 +11,7 @@ export const SectionList = copyComponentProperties(RNSectionList, (props: Sectio
10
11
  contentContainerStyle={[toRNWClassName(props.contentContainerClassName), props.contentContainerStyle]}
11
12
  ListFooterComponentStyle={[toRNWClassName(props.ListFooterComponentClassName), props.ListFooterComponentStyle]}
12
13
  ListHeaderComponentStyle={[toRNWClassName(props.ListHeaderComponentClassName), props.ListHeaderComponentStyle]}
14
+ dataSet={generateDataSet(props)}
13
15
  />
14
16
  )
15
17
  })
@@ -1,6 +1,7 @@
1
1
  import { Switch as RNSwitch, SwitchProps } from 'react-native'
2
2
  import { useUniwindAccent } from '../../hooks'
3
3
  import { copyComponentProperties } from '../utils'
4
+ import { generateDataSet } from './generateDataSet'
4
5
  import { toRNWClassName } from './rnw'
5
6
 
6
7
  export const Switch = copyComponentProperties(RNSwitch, (props: SwitchProps) => {
@@ -14,6 +15,7 @@ export const Switch = copyComponentProperties(RNSwitch, (props: SwitchProps) =>
14
15
  style={[toRNWClassName(props.className), props.style]}
15
16
  thumbColor={props.thumbColor ?? thumbColor}
16
17
  trackColor={{ true: props.trackColor?.true ?? trackColorOn, false: props.trackColor?.false ?? trackColorOff }}
18
+ dataSet={generateDataSet(props)}
17
19
  />
18
20
  )
19
21
  })
@@ -1,5 +1,6 @@
1
1
  import { Text as RNText, TextProps } from 'react-native'
2
2
  import { copyComponentProperties } from '../utils'
3
+ import { generateDataSet } from './generateDataSet'
3
4
  import { toRNWClassName } from './rnw'
4
5
 
5
6
  export const Text = copyComponentProperties(RNText, (props: TextProps) => {
@@ -7,6 +8,7 @@ export const Text = copyComponentProperties(RNText, (props: TextProps) => {
7
8
  <RNText
8
9
  {...props}
9
10
  style={[toRNWClassName(props.className), props.style]}
11
+ dataSet={generateDataSet(props)}
10
12
  />
11
13
  )
12
14
  })
@@ -1,6 +1,7 @@
1
1
  import { TextInput as RNTextInput, TextInputProps } from 'react-native'
2
2
  import { useUniwindAccent } from '../../hooks'
3
3
  import { copyComponentProperties } from '../utils'
4
+ import { generateDataSet } from './generateDataSet'
4
5
  import { toRNWClassName } from './rnw'
5
6
 
6
7
  export const TextInput = copyComponentProperties(RNTextInput, (props: TextInputProps) => {
@@ -11,6 +12,7 @@ export const TextInput = copyComponentProperties(RNTextInput, (props: TextInputP
11
12
  {...props}
12
13
  style={[toRNWClassName(props.className), props.style]}
13
14
  placeholderTextColor={props.placeholderTextColor ?? placeholderTextColor}
15
+ dataSet={generateDataSet(props)}
14
16
  />
15
17
  )
16
18
  })
@@ -1,6 +1,7 @@
1
1
  import { TouchableHighlight as RNTouchableHighlight, TouchableHighlightProps } from 'react-native'
2
2
  import { useUniwindAccent } from '../../hooks'
3
3
  import { copyComponentProperties } from '../utils'
4
+ import { generateDataSet } from './generateDataSet'
4
5
  import { toRNWClassName } from './rnw'
5
6
 
6
7
  export const TouchableHighlight = copyComponentProperties(RNTouchableHighlight, (props: TouchableHighlightProps) => {
@@ -11,6 +12,7 @@ export const TouchableHighlight = copyComponentProperties(RNTouchableHighlight,
11
12
  {...props}
12
13
  style={[toRNWClassName(props.className), props.style]}
13
14
  underlayColor={props.underlayColor ?? underlayColor}
15
+ dataSet={generateDataSet(props)}
14
16
  />
15
17
  )
16
18
  })
@@ -1,5 +1,6 @@
1
1
  import { TouchableOpacity as RNTouchableOpacity, TouchableOpacityProps } from 'react-native'
2
2
  import { copyComponentProperties } from '../utils'
3
+ import { generateDataSet } from './generateDataSet'
3
4
  import { toRNWClassName } from './rnw'
4
5
 
5
6
  export const TouchableOpacity = copyComponentProperties(RNTouchableOpacity, (props: TouchableOpacityProps) => {
@@ -7,6 +8,7 @@ export const TouchableOpacity = copyComponentProperties(RNTouchableOpacity, (pro
7
8
  <RNTouchableOpacity
8
9
  {...props}
9
10
  style={[toRNWClassName(props.className), props.style]}
11
+ dataSet={generateDataSet(props)}
10
12
  />
11
13
  )
12
14
  })
@@ -1,5 +1,6 @@
1
1
  import { TouchableWithoutFeedback as RNTouchableWithoutFeedback, TouchableWithoutFeedbackProps } from 'react-native'
2
2
  import { copyComponentProperties } from '../utils'
3
+ import { generateDataSet } from './generateDataSet'
3
4
  import { toRNWClassName } from './rnw'
4
5
 
5
6
  export const TouchableWithoutFeedback = copyComponentProperties(RNTouchableWithoutFeedback, (props: TouchableWithoutFeedbackProps) => {
@@ -7,6 +8,7 @@ export const TouchableWithoutFeedback = copyComponentProperties(RNTouchableWitho
7
8
  <RNTouchableWithoutFeedback
8
9
  {...props}
9
10
  style={[toRNWClassName(props.className), props.style]}
11
+ dataSet={generateDataSet(props)}
10
12
  />
11
13
  )
12
14
  })
@@ -1,11 +1,13 @@
1
1
  import { View as RNView, ViewProps } from 'react-native'
2
2
  import { copyComponentProperties } from '../utils'
3
+ import { generateDataSet } from './generateDataSet'
3
4
  import { toRNWClassName } from './rnw'
4
5
 
5
6
  export const View = copyComponentProperties(RNView, (props: ViewProps) => {
6
7
  return (
7
8
  <RNView
8
9
  {...props}
10
+ dataSet={generateDataSet(props)}
9
11
  style={[toRNWClassName(props.className), props.style]}
10
12
  />
11
13
  )
@@ -1,5 +1,6 @@
1
1
  import { VirtualizedList as RNVirtualizedList, VirtualizedListProps } from 'react-native'
2
2
  import { copyComponentProperties } from '../utils'
3
+ import { generateDataSet } from './generateDataSet'
3
4
  import { toRNWClassName } from './rnw'
4
5
 
5
6
  export const VirtualizedList = copyComponentProperties(RNVirtualizedList, (props: VirtualizedListProps<unknown>) => {
@@ -10,6 +11,7 @@ export const VirtualizedList = copyComponentProperties(RNVirtualizedList, (props
10
11
  contentContainerStyle={[toRNWClassName(props.contentContainerClassName), props.contentContainerStyle]}
11
12
  ListFooterComponentStyle={[toRNWClassName(props.ListFooterComponentClassName), props.ListFooterComponentStyle]}
12
13
  ListHeaderComponentStyle={[toRNWClassName(props.ListHeaderComponentClassName), props.ListHeaderComponentStyle]}
14
+ dataSet={generateDataSet(props)}
13
15
  />
14
16
  )
15
17
  })
@@ -0,0 +1,52 @@
1
+ export const generateDataSet = (props: Record<PropertyKey, any>) => {
2
+ const dataSet: DataSet = props.dataSet !== undefined ? { ...props.dataSet } : {}
3
+
4
+ Object.entries(props).forEach(([key, value]) => {
5
+ if (key.startsWith('data-')) {
6
+ // Remove data- prefix
7
+ dataSet[key.slice(5)] = value
8
+ }
9
+ })
10
+
11
+ return dataSet
12
+ }
13
+
14
+ type DataSet = Record<string, string | boolean>
15
+
16
+ declare module 'react-native' {
17
+ interface SwitchProps {
18
+ dataSet?: DataSet
19
+ }
20
+
21
+ interface TextProps {
22
+ dataSet?: DataSet
23
+ }
24
+
25
+ interface TouchableWithoutFeedbackProps {
26
+ dataSet?: DataSet
27
+ }
28
+
29
+ interface ViewProps {
30
+ dataSet?: DataSet
31
+ }
32
+
33
+ interface PressableProps {
34
+ dataSet?: DataSet
35
+ }
36
+
37
+ interface TextInputProps {
38
+ dataSet?: DataSet
39
+ }
40
+
41
+ interface ImagePropsBase {
42
+ dataSet?: DataSet
43
+ }
44
+
45
+ interface InputAccessoryViewProps {
46
+ dataSet?: DataSet
47
+ }
48
+
49
+ interface ButtonProps {
50
+ dataSet?: DataSet
51
+ }
52
+ }
@@ -4,7 +4,7 @@ import { StyleDependency } from '../../types'
4
4
  import './metro-injected'
5
5
 
6
6
  type UniwindWithThemes = {
7
- themes: typeof Uniwind['themes']
7
+ themes: typeof Uniwind['_themes']
8
8
  }
9
9
 
10
10
  const addClassNameToRoot = () => {
@@ -13,7 +13,7 @@ const RN_VERSION = Platform.constants?.reactNativeVersion?.minor ?? 0
13
13
  const UNSPECIFIED_THEME = RN_VERSION >= 82 ? 'unspecified' : undefined
14
14
 
15
15
  export class UniwindConfigBuilder {
16
- protected themes = ['light', 'dark']
16
+ protected _themes = ['light', 'dark']
17
17
  #hasAdaptiveThemes = true
18
18
  #currentTheme = this.colorScheme as ThemeName
19
19
 
@@ -33,6 +33,10 @@ export class UniwindConfigBuilder {
33
33
  })
34
34
  }
35
35
 
36
+ get themes() {
37
+ return this._themes as Array<ThemeName>
38
+ }
39
+
36
40
  get hasAdaptiveThemes() {
37
41
  return this.#hasAdaptiveThemes
38
42
  }
@@ -77,7 +81,7 @@ export class UniwindConfigBuilder {
77
81
  return
78
82
  }
79
83
 
80
- if (!this.themes.includes(theme)) {
84
+ if (!this._themes.includes(theme)) {
81
85
  throw new Error(`Uniwind: You're trying to setTheme to '${theme}', but it was not registered.`)
82
86
  }
83
87
 
@@ -116,7 +120,7 @@ export class UniwindConfigBuilder {
116
120
  }
117
121
 
118
122
  protected __reinit(_: GenerateStyleSheetsCallback, themes: Array<string>) {
119
- this.themes = themes
123
+ this._themes = themes
120
124
  }
121
125
 
122
126
  protected onThemeChange() {
@@ -2,9 +2,10 @@ import { StyleDependency } from '../../types'
2
2
  import { UniwindListener } from '../listener'
3
3
 
4
4
  class CSSListenerBuilder {
5
+ activeRules = new Set<CSSStyleRule>()
5
6
  private classNameMediaQueryListeners = new Map<string, MediaQueryList>()
6
7
  private listeners = new Map<MediaQueryList, Set<VoidFunction>>()
7
- private registeredRules = new Map<string, MediaQueryList>()
8
+ private registeredRulesMediaQueries = new Map<string, MediaQueryList>()
8
9
  private processedStyleSheets = new WeakSet<CSSStyleSheet>()
9
10
  private pendingInitialization: number | undefined = undefined
10
11
 
@@ -15,6 +16,22 @@ class CSSListenerBuilder {
15
16
 
16
17
  const observer = new MutationObserver(mutations => {
17
18
  for (const mutation of mutations) {
19
+ if (mutation.type === 'attributes') {
20
+ const el = mutation.target as HTMLLinkElement | HTMLStyleElement
21
+
22
+ if (!('sheet' in el)) {
23
+ continue
24
+ }
25
+
26
+ const sheet = el.sheet
27
+
28
+ if (sheet) {
29
+ this.processedStyleSheets.delete(sheet)
30
+ }
31
+
32
+ this.scheduleInitialization()
33
+ }
34
+
18
35
  if (mutation.type === 'childList') {
19
36
  this.scheduleInitialization()
20
37
  }
@@ -83,8 +100,19 @@ class CSSListenerBuilder {
83
100
  }
84
101
  }
85
102
 
103
+ private pruneStaleRules() {
104
+ const activeSheets = new Set(Array.from(document.styleSheets))
105
+
106
+ for (const rule of this.activeRules) {
107
+ if (!rule.parentStyleSheet || !activeSheets.has(rule.parentStyleSheet)) {
108
+ this.activeRules.delete(rule)
109
+ }
110
+ }
111
+ }
112
+
86
113
  private initialize() {
87
114
  this.pendingInitialization = undefined
115
+ this.pruneStaleRules()
88
116
 
89
117
  for (const sheet of Array.from(document.styleSheets)) {
90
118
  // Skip already processed stylesheets
@@ -121,6 +149,10 @@ class CSSListenerBuilder {
121
149
  return rule.constructor.name === 'CSSMediaRule'
122
150
  }
123
151
 
152
+ private isSupportsRule(rule: CSSRule): rule is CSSSupportsRule {
153
+ return rule.constructor.name === 'CSSSupportsRule'
154
+ }
155
+
124
156
  private collectParentMediaQueries(rule: CSSRule, acc = [] as Array<CSSMediaRule>): Array<CSSMediaRule> {
125
157
  const { parentRule } = rule
126
158
 
@@ -144,13 +176,25 @@ class CSSListenerBuilder {
144
176
  if (this.isStyleRule(rule)) {
145
177
  const mediaQueries = this.collectParentMediaQueries(rule)
146
178
 
179
+ this.activeRules.add(rule)
180
+
147
181
  if (mediaQueries.length > 0) {
148
- this.addMediaQuery(mediaQueries, rule.selectorText)
182
+ this.addMediaQuery(mediaQueries, rule)
149
183
  }
150
184
 
151
185
  continue
152
186
  }
153
187
 
188
+ if (this.isSupportsRule(rule)) {
189
+ if (!CSS.supports(rule.conditionText)) {
190
+ continue
191
+ }
192
+
193
+ this.addMediaQueriesDeep(rule.cssRules)
194
+
195
+ continue
196
+ }
197
+
154
198
  if ('cssRules' in rule && rule.cssRules instanceof CSSRuleList) {
155
199
  this.addMediaQueriesDeep(rule.cssRules)
156
200
 
@@ -159,27 +203,50 @@ class CSSListenerBuilder {
159
203
  }
160
204
  }
161
205
 
162
- private addMediaQuery(mediaQueries: Array<CSSMediaRule>, className: string) {
206
+ private addMediaQuery(mediaQueries: Array<CSSMediaRule>, rule: CSSStyleRule) {
207
+ const className = rule.selectorText
163
208
  const rules = mediaQueries.map(mediaQuery => mediaQuery.conditionText).sort().join(' and ')
164
209
  const parsedClassName = className.replace('.', '').replace('\\', '')
165
- const cachedMediaQueryList = this.registeredRules.get(rules)
210
+ const cachedMediaQueryList = this.registeredRulesMediaQueries.get(rules)
166
211
 
167
212
  if (cachedMediaQueryList) {
168
213
  this.classNameMediaQueryListeners.set(parsedClassName, cachedMediaQueryList)
214
+ this.toggleRule(cachedMediaQueryList, rule)
215
+
216
+ cachedMediaQueryList.addEventListener('change', () => {
217
+ this.toggleRule(cachedMediaQueryList, rule)
218
+ })
169
219
 
170
220
  return
171
221
  }
172
222
 
173
223
  const mediaQueryList = window.matchMedia(rules)
174
224
 
175
- this.registeredRules.set(rules, mediaQueryList)
225
+ this.toggleRule(mediaQueryList, rule)
226
+ this.registeredRulesMediaQueries.set(rules, mediaQueryList)
176
227
  this.listeners.set(mediaQueryList, new Set())
177
228
  this.classNameMediaQueryListeners.set(parsedClassName, mediaQueryList)
178
229
 
179
230
  mediaQueryList.addEventListener('change', () => {
180
- this.listeners.get(mediaQueryList)!.forEach(listener => listener())
231
+ this.listeners.get(mediaQueryList)!.forEach(listener => {
232
+ listener()
233
+ })
234
+ this.toggleRule(mediaQueryList, rule)
181
235
  })
182
236
  }
237
+
238
+ private isRuleLive(rule: CSSStyleRule) {
239
+ const sheet = rule.parentStyleSheet
240
+ return sheet !== null && Array.from(document.styleSheets).includes(sheet)
241
+ }
242
+
243
+ private toggleRule(mqList: MediaQueryList, rule: CSSStyleRule) {
244
+ if (mqList.matches && this.isRuleLive(rule)) {
245
+ this.activeRules.add(rule)
246
+ } else {
247
+ this.activeRules.delete(rule)
248
+ }
249
+ }
183
250
  }
184
251
 
185
252
  export const CSSListener = new CSSListenerBuilder()
@@ -1,4 +1,5 @@
1
1
  import { RNStyle, UniwindContextType } from '../types'
2
+ import { CSSListener } from './cssListener'
2
3
  import { parseCSSValue } from './parseCSSValue'
3
4
 
4
5
  const dummyParent = typeof document !== 'undefined'
@@ -15,40 +16,40 @@ if (dummyParent && dummy) {
15
16
  dummyParent.appendChild(dummy)
16
17
  }
17
18
 
18
- const getComputedStyles = () => {
19
+ const getActiveStylesForClass = (className: string) => {
20
+ const extractedStyles = {} as Record<string, string>
21
+
19
22
  if (!dummy) {
20
- return {} as CSSStyleDeclaration
23
+ return extractedStyles
21
24
  }
22
25
 
26
+ const classNames = className.split(/\s+/).filter(Boolean)
23
27
  const computedStyles = window.getComputedStyle(dummy)
24
- const styles = {} as CSSStyleDeclaration
25
-
26
- // eslint-disable-next-line @typescript-eslint/prefer-for-of
27
- for (let i = 0; i < computedStyles.length; i++) {
28
- // Typescript is unable to infer it properly
29
- const prop = computedStyles[i] as any
30
28
 
31
- styles[prop] = computedStyles.getPropertyValue(prop)
32
- }
29
+ CSSListener.activeRules.forEach(rule => {
30
+ const selector = rule.selectorText
31
+ const mightMatch = classNames.some((cls) => selector.includes(`.${CSS.escape(cls)}`))
33
32
 
34
- return styles
35
- }
36
-
37
- const initialStyles = typeof document !== 'undefined'
38
- ? getComputedStyles()
39
- : {} as CSSStyleDeclaration
40
-
41
- const getObjectDifference = <T extends object>(obj1: T, obj2: T): T => {
42
- const diff = {} as T
43
- const keys = Object.keys(obj2) as Array<keyof T>
33
+ if (!mightMatch) {
34
+ return
35
+ }
44
36
 
45
- keys.forEach(key => {
46
- if (obj2[key] !== obj1[key]) {
47
- diff[key] = obj2[key]
37
+ // element.matches() throws errors if it sees pseudo-elements like ::before
38
+ // So we strip them out safely just for the matching test
39
+ const safeSelector = selector.replace(/::[a-z-]+/gi, '')
40
+
41
+ try {
42
+ if (safeSelector !== '' && dummy.matches(safeSelector)) {
43
+ for (const propertyName of rule.style) {
44
+ extractedStyles[propertyName] = computedStyles.getPropertyValue(propertyName)
45
+ }
46
+ }
47
+ } catch {
48
+ // Failsafe for unparseable selectors
48
49
  }
49
50
  })
50
51
 
51
- return diff
52
+ return extractedStyles
52
53
  }
53
54
 
54
55
  export const getWebStyles = (className: string | undefined, uniwindContext: UniwindContextType): RNStyle => {
@@ -68,7 +69,7 @@ export const getWebStyles = (className: string | undefined, uniwindContext: Uniw
68
69
 
69
70
  dummy.className = className
70
71
 
71
- const computedStyles = getObjectDifference(initialStyles, getComputedStyles())
72
+ const computedStyles = getActiveStylesForClass(className)
72
73
 
73
74
  return Object.fromEntries(
74
75
  Object.entries(computedStyles)
@@ -0,0 +1,26 @@
1
+ import { RNStyle } from '../core/types'
2
+
3
+ const toKebabCase = (str: string) => str.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`)
4
+
5
+ const generateExtraUtilities = (map: Record<string, RNStyle>) => {
6
+ return Object.entries(map)
7
+ .map(([name, style]) =>
8
+ [
9
+ `@utility ${name} {`,
10
+ ...Object.entries(style).map(([key, value]) => ` ${toKebabCase(key)}: ${value};`),
11
+ `}`,
12
+ '',
13
+ ].join('\n')
14
+ ).join('\n')
15
+ }
16
+
17
+ const EXTRA_UTILITIES_MAP = {
18
+ 'border-continuous': {
19
+ borderCurve: 'continuous',
20
+ },
21
+ 'border-circular': {
22
+ borderCurve: 'circular',
23
+ },
24
+ } satisfies Record<string, RNStyle>
25
+
26
+ export const EXTRA_UTILITIES_CSS = generateExtraUtilities(EXTRA_UTILITIES_MAP)
package/src/css/index.ts CHANGED
@@ -1,15 +1,14 @@
1
1
  import fs from 'fs'
2
2
  import path from 'path'
3
- import { generateCSSForInsets } from './insets'
4
- import { overwrite } from './overwrite'
3
+ import { EXTRA_UTILITIES_CSS } from './extraUtilities'
4
+ import { INSETS_CSS } from './insets'
5
+ import { OVERWRITE_CSS } from './overwrite'
5
6
  import { generateCSSForThemes } from './themes'
6
- import { generateCSSForVariants } from './variants'
7
+ import { VARIANTS_CSS } from './variants'
7
8
 
8
9
  const dirname = typeof __dirname !== 'undefined' ? __dirname : import.meta.dirname
9
10
 
10
11
  export const buildCSS = async (themes: Array<string>, input: string) => {
11
- const variants = generateCSSForVariants()
12
- const insets = generateCSSForInsets()
13
12
  const themesCSS = await generateCSSForThemes(themes, input)
14
13
  const cssFilePath = path.join(dirname, '../../uniwind.css')
15
14
  const oldCSSFile = fs.existsSync(cssFilePath)
@@ -17,9 +16,10 @@ export const buildCSS = async (themes: Array<string>, input: string) => {
17
16
  : ''
18
17
 
19
18
  const newCssFile = [
20
- variants,
21
- insets,
22
- overwrite,
19
+ VARIANTS_CSS,
20
+ INSETS_CSS,
21
+ OVERWRITE_CSS,
22
+ EXTRA_UTILITIES_CSS,
23
23
  themesCSS,
24
24
  ].join('\n')
25
25
 
package/src/css/insets.ts CHANGED
@@ -9,7 +9,7 @@ type TypeName = (typeof types)[number]
9
9
  type SafeAreaType = (typeof safeAreaTypes)[number]
10
10
  type Inset = 'top' | 'bottom' | 'left' | 'right'
11
11
 
12
- export const generateCSSForInsets = () => {
12
+ const generateCSSForInsets = () => {
13
13
  let css = `@utility h-screen-safe {
14
14
  height: calc(100vh - (env(safe-area-inset-top) + env(safe-area-inset-bottom)));
15
15
  }\n\n`
@@ -104,3 +104,5 @@ export const generateCSSForInsets = () => {
104
104
  // Remove the last newline character
105
105
  return css.slice(0, -1)
106
106
  }
107
+
108
+ export const INSETS_CSS = generateCSSForInsets()
@@ -13,4 +13,4 @@ const overwriteDisabled = `@custom-variant disabled {
13
13
  }
14
14
  `
15
15
 
16
- export const overwrite = overwriteDisabled
16
+ export const OVERWRITE_CSS = overwriteDisabled
@@ -1,6 +1,6 @@
1
1
  const variants = ['ios', 'android', 'web', 'native', 'tv', 'android-tv', 'apple-tv']
2
2
 
3
- export const generateCSSForVariants = () => {
3
+ const generateCSSForVariants = () => {
4
4
  let css = ''
5
5
 
6
6
  variants.forEach(variant => {
@@ -9,3 +9,5 @@ export const generateCSSForVariants = () => {
9
9
 
10
10
  return css
11
11
  }
12
+
13
+ export const VARIANTS_CSS = generateCSSForVariants()
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './components/ScopedTheme'
2
2
  export { Uniwind } from './core'
3
+ export type { ThemeName } from './core/types'
3
4
  export { withUniwind } from './hoc'
4
5
  export type { ApplyUniwind, ApplyUniwindOptions } from './hoc/types'
5
6
  export { useCSSVariable, useResolveClassNames, useUniwind } from './hooks'
@@ -434,6 +434,14 @@ export class CSS {
434
434
  return this.processValue(declarationValue[0]!)
435
435
  }
436
436
 
437
+ if (this.isTransformArray(declarationValue)) {
438
+ return declarationValue.flatMap(value => {
439
+ const result = this.processValue(value)
440
+
441
+ return Array.isArray(result) ? result : [result]
442
+ })
443
+ }
444
+
437
445
  return this.addComaBetweenTokens(declarationValue).reduce<string | number>((acc, value, index, array) => {
438
446
  if (typeof value === 'object') {
439
447
  const nextValue = array.at(index + 1)
@@ -580,6 +588,33 @@ export class CSS {
580
588
  return undefined
581
589
  }
582
590
 
591
+ // eslint-disable-next-line @typescript-eslint/member-ordering
592
+ private static readonly TRANSFORM_TYPES = new Set([
593
+ 'translate',
594
+ 'translateX',
595
+ 'translateY',
596
+ 'translateZ',
597
+ 'rotate',
598
+ 'rotateX',
599
+ 'rotateY',
600
+ 'rotateZ',
601
+ 'scale',
602
+ 'scaleX',
603
+ 'scaleY',
604
+ 'scaleZ',
605
+ 'skew',
606
+ 'skewX',
607
+ 'skewY',
608
+ 'matrix',
609
+ 'perspective',
610
+ ])
611
+
612
+ private isTransformArray(values: Array<any>) {
613
+ return values.every(
614
+ value => typeof value === 'object' && value !== null && 'type' in value && CSS.TRANSFORM_TYPES.has(value.type),
615
+ )
616
+ }
617
+
583
618
  private isOverflow(value: any): value is { x: OverflowKeyword; y: OverflowKeyword } {
584
619
  return typeof value === 'object' && 'x' in value && ['hidden', 'visible'].includes(value.x)
585
620
  }