Skip to content

Commit fbeb2f8

Browse files
authored
fix: patch rnw to wrap default styles with css layer (#337)
* feat: replace createOrderedCSSStyleSheet so default rnw styles are inside separated layer * feat: extend color scheme variants * chore: different implementation * chore: dual ordered stylesheet * chore: apply patch in vite * chore: apply patch in vite * chore: update uniwind.css * chore: minor corrections
1 parent fb82b99 commit fbeb2f8

7 files changed

Lines changed: 131 additions & 28 deletions

File tree

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// @ts-expect-error - Importing internal react-native-web module
2+
import originalCreateOrderedCSSStyleSheet from 'react-native-web/dist/exports/StyleSheet/dom/createOrderedCSSStyleSheet'
3+
4+
type OrderedCSSStyleSheet = {
5+
getTextContent: () => string
6+
insert: (cssText: string, groupValue: number) => void
7+
}
8+
9+
/**
10+
* Custom createOrderedCSSStyleSheet that wraps RNW rules in @layer rnw
11+
* This ensures proper CSS cascade ordering with Tailwind v4 styles.
12+
*/
13+
const createOrderedCSSStyleSheet = (sheet: CSSStyleSheet | null) => {
14+
let layerRule = null
15+
let fakeSheet = null
16+
17+
if (sheet !== null) {
18+
// Use existing layer rule if it already exists
19+
if (typeof CSSLayerBlockRule !== 'undefined' && sheet.cssRules[0] instanceof CSSLayerBlockRule) {
20+
layerRule = sheet.cssRules[0]
21+
} else {
22+
// otherwise insert a layer rule
23+
const layerIndex = sheet.insertRule('@layer rnw {}', 0)
24+
layerRule = sheet.cssRules[layerIndex]
25+
}
26+
27+
// Create a fake sheet that excludes the layer rule
28+
fakeSheet = {
29+
// Increment index by 1 to skip the layer rule
30+
insertRule: (text: string, index?: number) => sheet.insertRule(text, index === undefined ? 1 : index + 1),
31+
get cssRules() {
32+
return Array.from(sheet.cssRules).slice(1)
33+
},
34+
}
35+
}
36+
37+
const originalLayered: OrderedCSSStyleSheet = originalCreateOrderedCSSStyleSheet(layerRule)
38+
const original: OrderedCSSStyleSheet = originalCreateOrderedCSSStyleSheet(fakeSheet)
39+
40+
return {
41+
getTextContent: () => {
42+
return `@layer rnw { ${originalLayered.getTextContent()} }\n${original.getTextContent()}`
43+
},
44+
insert: (cssText: string, groupValue: number) => {
45+
if (groupValue <= 1) {
46+
originalLayered.insert(cssText, groupValue)
47+
48+
return
49+
}
50+
51+
original.insert(cssText, groupValue)
52+
},
53+
}
54+
}
55+
56+
export default createOrderedCSSStyleSheet

packages/uniwind/src/components/web/rnw.ts

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,6 @@ import { UniwindListener } from '../../core/listener'
33
import { StyleDependency } from '../../types'
44
import './metro-injected'
55

6-
const moveCssRulesToUtilitiesLayer = (sourceSheet: CSSStyleSheet) => {
7-
const layerRuleIndex = sourceSheet.insertRule(
8-
'@layer rnw {}',
9-
0,
10-
)
11-
const layerRule = sourceSheet.cssRules[layerRuleIndex] as CSSGroupingRule
12-
13-
while (sourceSheet.cssRules.length > 1) {
14-
const nextRule = sourceSheet.cssRules[1]!
15-
16-
layerRule.insertRule(nextRule.cssText, layerRule.cssRules.length)
17-
sourceSheet.deleteRule(1)
18-
}
19-
}
20-
21-
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
22-
const rnwStyleSheet = document.querySelector<HTMLStyleElement>('#react-native-stylesheet')
23-
24-
if (rnwStyleSheet?.sheet) {
25-
moveCssRulesToUtilitiesLayer(rnwStyleSheet.sheet)
26-
}
27-
}
28-
296
type UniwindWithThemes = {
307
themes: typeof Uniwind['themes']
318
}

packages/uniwind/src/core/config/config.common.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import { UniwindListener } from '../listener'
44
import { CSSVariables, GenerateStyleSheetsCallback, ThemeName } from '../types'
55

66
const SYSTEM_THEME = 'system' as const
7-
const RN_VERSION = Platform.constants.reactNativeVersion.minor
7+
// Platform.constants is not defined in RNW
8+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
9+
const RN_VERSION = Platform.constants?.reactNativeVersion?.minor ?? 0
810
const UNSPECIFIED_THEME = RN_VERSION >= 82 ? 'unspecified' : undefined
911

1012
export class UniwindConfigBuilder {

packages/uniwind/src/css/themes.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,28 @@ export const generateCSSForThemes = async (themes: Array<string>, input: string)
122122
]
123123
: []
124124
const uniwindCSS = [
125-
...themes.map(theme => `@custom-variant ${theme} (&:where(.${theme}, .${theme} *));`),
125+
...themes.map(theme => {
126+
const notOtherThemes = themes.map(t => `.${t}, .${t} *`)
127+
128+
if (theme === 'dark' || theme === 'light') {
129+
return [
130+
`@custom-variant ${theme} {`,
131+
` &:where(.${theme}, .${theme} *) {`,
132+
' @slot;',
133+
' }',
134+
'',
135+
` @media (prefers-color-scheme: ${theme}) {`,
136+
` &:not(:where(${notOtherThemes.join(', ')})) {`,
137+
' @slot;',
138+
' }',
139+
' }',
140+
'}',
141+
'',
142+
].join('\n')
143+
}
144+
145+
return `@custom-variant ${theme} (&:where(.${theme}, .${theme} *));`
146+
}),
126147
...variablesCSS,
127148
].join('\n')
128149

packages/uniwind/src/metro/resolvers.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const SUPPORTED_COMPONENTS = [
3333
'TouchableWithoutFeedback',
3434
'View',
3535
'VirtualizedList',
36+
'createOrderedCSSStyleSheet',
3637
]
3738

3839
export const nativeResolver = ({
@@ -109,9 +110,15 @@ export const webResolver = ({
109110
}
110111

111112
const segments = resolution.filePath.split(sep)
112-
const isIndex = segments.at(-1)?.startsWith('index.')
113+
const filename = segments.at(-1) ?? ''
114+
const isIndex = filename.startsWith('index.')
113115
const module = segments.at(-2)
114116

117+
// Handle createOrderedCSSStyleSheet which is in StyleSheet/dom/ subdirectory
118+
if (filename.startsWith('createOrderedCSSStyleSheet.')) {
119+
return resolver(context, `uniwind/components/createOrderedCSSStyleSheet`, platform)
120+
}
121+
115122
if (!isIndex || module === undefined || !SUPPORTED_COMPONENTS.includes(module) || context.originModulePath.endsWith(`${module}${sep}index.js`)) {
116123
return resolution
117124
}

packages/uniwind/src/vite/vite.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ const componentPath = path.resolve(
1919
dirname,
2020
'../module/components/web/index.js',
2121
)
22+
const styleSheetPath = path.resolve(
23+
dirname,
24+
'../module/components/web/createOrderedCSSStyleSheet.js',
25+
)
2226

2327
export const uniwind = ({
2428
cssEntryFile,
@@ -45,6 +49,21 @@ export const uniwind = ({
4549
},
4650
optimizeDeps: {
4751
exclude: [UNIWIND_PACKAGE_NAME, 'react-native'],
52+
esbuildOptions: {
53+
plugins: [{
54+
name: 'uniwind-esbuild-plugin',
55+
setup: build => {
56+
build.onResolve(
57+
{ filter: /^\.\/createOrderedCSSStyleSheet$/ },
58+
args => {
59+
if (normalizePath(args.importer).includes('react-native-web/dist/exports/StyleSheet')) {
60+
return { path: styleSheetPath }
61+
}
62+
},
63+
)
64+
},
65+
}],
66+
},
4867
},
4968
resolve: {
5069
alias: [

packages/uniwind/uniwind.css

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,5 +376,26 @@
376376
right: calc(env(safe-area-inset-right) + --value([length], --spacing-*));
377377
}
378378

379-
@custom-variant light (&:where(.light, .light *));
380-
@custom-variant dark (&:where(.dark, .dark *));
379+
@custom-variant light {
380+
&:where(.light, .light *) {
381+
@slot;
382+
}
383+
384+
@media (prefers-color-scheme: light) {
385+
&:not(:where(.light, .light *, .dark, .dark *, .premium, .premium *)) {
386+
@slot;
387+
}
388+
}
389+
}
390+
391+
@custom-variant dark {
392+
&:where(.dark, .dark *) {
393+
@slot;
394+
}
395+
396+
@media (prefers-color-scheme: dark) {
397+
&:not(:where(.light, .light *, .dark, .dark *, .premium, .premium *)) {
398+
@slot;
399+
}
400+
}
401+
}

0 commit comments

Comments
 (0)