authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-02 23:41:36-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-02 23:41:36-07:00
log1f7a3c11ca864898b136a35011ad10777ea07544
tree325d4fa9f92179d500c2d54c2716c655ef5c1a44
parentcf0bb4c3571badbc5aaa8189cd5cbe3563ec73c1
signature Signed by PGP key B5690EEEBB952194

fix(mobile): pressed and disabled styles no longer apply unconditionally (#9355)

Co-authored-by: Claude Code <noreply@anthropic.com>

2 files changed, 812 insertions(+), 9 deletions(-)

patches/uniwind@1.11.0.patch+809-6
...@@ -1,5 +1,161 @@...@@ -1,5 +1,161 @@
1diff --git a/dist/common/bundler/css-processor/processor.js b/dist/common/bundler/css-processor/processor.js
2index 7dd369fa3197dbd622832c89ac3854c401e55c3e..ab3ea1a9520907321f9c21c2a3a859262fe5075b 100644
3--- a/dist/common/bundler/css-processor/processor.js
4+++ b/dist/common/bundler/css-processor/processor.js
5@@ -105,6 +105,64 @@ class ProcessorBuilder {
6 style.importantProperties.push(declaration.property);
7 }
8 }
9+ readSelectorVariants(tokens) {
10+ const variants = {
11+ rtl: null,
12+ theme: null,
13+ active: null,
14+ focus: null,
15+ disabled: null,
16+ dataAttributes: null
17+ };
18+ tokens.forEach((token) => {
19+ if (token.type === "pseudo-class" && token.kind === "where") {
20+ token.selectors.forEach((compound) => {
21+ compound.forEach((inner) => {
22+ if (inner.type === "class" && this.bundlerConfig.themes.includes(inner.name)) {
23+ variants.theme = inner.name;
24+ }
25+ if (inner.type === "pseudo-class" && inner.kind === "dir") {
26+ variants.rtl = inner.direction === "rtl";
27+ }
28+ });
29+ });
30+ }
31+ if (token.type === "pseudo-class" && token.kind === "active") {
32+ variants.active = true;
33+ }
34+ if (token.type === "pseudo-class" && token.kind === "focus") {
35+ variants.focus = true;
36+ }
37+ if (token.type === "pseudo-class" && token.kind === "disabled") {
38+ variants.disabled = true;
39+ }
40+ if (token.type === "attribute" && token.operation === null && token.name.startsWith("data-")) {
41+ variants.dataAttributes ??= {};
42+ variants.dataAttributes[token.name] = `"true"`;
43+ }
44+ if (token.type === "attribute" && token.operation?.operator === "equal" && token.name.startsWith("data-")) {
45+ variants.dataAttributes ??= {};
46+ variants.dataAttributes[token.name] = `"${token.operation.value}"`;
47+ }
48+ });
49+ const hasVariants = Object.values(variants).some(_utils.isDefined);
50+ return { variants, hasVariants };
51+ }
52+ withSelectorVariants(variants, run) {
53+ this.declarationConfig.rtl ??= variants.rtl;
54+ this.declarationConfig.theme ??= variants.theme;
55+ this.declarationConfig.active ??= variants.active;
56+ this.declarationConfig.focus ??= variants.focus;
57+ this.declarationConfig.disabled ??= variants.disabled;
58+ this.declarationConfig.dataAttributes ??= variants.dataAttributes;
59+ run();
60+ this.declarationConfig.rtl = null;
61+ this.declarationConfig.theme = null;
62+ this.declarationConfig.active = null;
63+ this.declarationConfig.focus = null;
64+ this.declarationConfig.disabled = null;
65+ this.declarationConfig.dataAttributes = null;
66+ }
67 parseRuleRec(rule) {
68 if (this.declarationConfig.className !== null) {
69 const lastStyle = this.stylesheets[this.declarationConfig.className]?.at(-1);
70@@ -120,64 +178,30 @@ class ProcessorBuilder {
71 this.declarationConfig.className = newClassName;
72 this.stylesheets[newClassName] ??= [];
73 this.stylesheets[newClassName].push({});
74- rule.value.declarations?.declarations?.forEach(declaration => this.addDeclaration(declaration));
75- rule.value.declarations?.importantDeclarations?.forEach(declaration => this.addDeclaration(declaration, true));
76- rule.value.rules?.forEach(rule2 => this.parseRuleRec(rule2));
77- return;
78- }
79- let rtl = null;
80- let theme = null;
81- let active = null;
82- let focus = null;
83- let disabled = null;
84- let dataAttributes = null;
85- selector.forEach(selector2 => {
86- if (selector2.type === "pseudo-class" && selector2.kind === "where") {
87- selector2.selectors.forEach(selector3 => {
88- selector3.forEach(selector4 => {
89- if (selector4.type === "class" && this.bundlerConfig.themes.includes(selector4.name)) {
90- theme = selector4.name;
91- }
92- if (selector4.type === "pseudo-class" && selector4.kind === "dir") {
93- rtl = selector4.direction === "rtl";
94- }
95- });
96- });
97- }
98- if (selector2.type === "pseudo-class" && selector2.kind === "active") {
99- active = true;
100+ const trailing = selector.slice(1);
101+ const { variants, hasVariants } = this.readSelectorVariants(trailing);
102+ if (trailing.length > 0 && !hasVariants) {
103+ return;
104 }
105- if (selector2.type === "pseudo-class" && selector2.kind === "focus") {
106- focus = true;
107- }
108- if (selector2.type === "pseudo-class" && selector2.kind === "disabled") {
109- disabled = true;
110- }
111- if (selector2.type === "attribute" && selector2.operation === null && selector2.name.startsWith("data-")) {
112- dataAttributes ??= {};
113- dataAttributes[selector2.name] = `"true"`;
114- }
115- if (selector2.type === "attribute" && selector2.operation?.operator === "equal" && selector2.name.startsWith("data-")) {
116- dataAttributes ??= {};
117- dataAttributes[selector2.name] = `"${selector2.operation.value}"`;
118+ const parseClassRule = () => {
119+ rule.value.declarations?.declarations?.forEach(declaration => this.addDeclaration(declaration));
120+ rule.value.declarations?.importantDeclarations?.forEach(declaration => this.addDeclaration(declaration, true));
121+ rule.value.rules?.forEach(rule2 => this.parseRuleRec(rule2));
122+ };
123+ if (hasVariants) {
124+ this.withSelectorVariants(variants, parseClassRule);
125+ } else {
126+ parseClassRule();
127 }
128- });
129- if ([rtl, theme, active, focus, disabled, dataAttributes].some(_utils.isDefined)) {
130- this.declarationConfig.rtl ??= rtl;
131- this.declarationConfig.theme ??= theme;
132- this.declarationConfig.active ??= active;
133- this.declarationConfig.focus ??= focus;
134- this.declarationConfig.disabled ??= disabled;
135- this.declarationConfig.dataAttributes ??= dataAttributes;
136- rule.value.declarations?.declarations?.forEach(declaration => this.addDeclaration(declaration));
137- rule.value.declarations?.importantDeclarations?.forEach(declaration => this.addDeclaration(declaration, true));
138- rule.value.rules?.forEach(rule2 => this.parseRuleRec(rule2));
139- this.declarationConfig.rtl = null;
140- this.declarationConfig.theme = null;
141- this.declarationConfig.active = null;
142- this.declarationConfig.focus = null;
143- this.declarationConfig.disabled = null;
144- this.declarationConfig.dataAttributes = null;
145+ return;
146+ }
147+ const { variants, hasVariants } = this.readSelectorVariants(selector);
148+ if (hasVariants) {
149+ this.withSelectorVariants(variants, () => {
150+ rule.value.declarations?.declarations?.forEach(declaration => this.addDeclaration(declaration));
151+ rule.value.declarations?.importantDeclarations?.forEach(declaration => this.addDeclaration(declaration, true));
152+ rule.value.rules?.forEach(rule2 => this.parseRuleRec(rule2));
153+ });
154 return;
155 }
156 selector.forEach(selectorToken => {
1diff --git a/dist/metro/transformer.cjs b/dist/metro/transformer.cjs157diff --git a/dist/metro/transformer.cjs b/dist/metro/transformer.cjs
2index d57ca81..fd8693c 100644158index d57ca8161f411f0dc7df827132586e428cafdf1b..3650a353b0db269ad4fc51b70a7158885c992e36 100644
3--- a/dist/metro/transformer.cjs159--- a/dist/metro/transformer.cjs
4+++ b/dist/metro/transformer.cjs160+++ b/dist/metro/transformer.cjs
5@@ -7,6 +7,7 @@ const culori = require('culori');161@@ -7,6 +7,7 @@ const culori = require('culori');
...@@ -10,7 +166,159 @@ index d57ca81..fd8693c 100644...@@ -10,7 +166,159 @@ index d57ca81..fd8693c 100644
10 166
11 function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }167 function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
12 168
13@@ -1753,10 +1754,11 @@ const transform = async (config$1, projectRoot, filePath, data, options) => {169@@ -1537,6 +1538,64 @@ class ProcessorBuilder {
170 style.importantProperties.push(declaration.property);
171 }
172 }
173+ readSelectorVariants(tokens) {
174+ const variants = {
175+ rtl: null,
176+ theme: null,
177+ active: null,
178+ focus: null,
179+ disabled: null,
180+ dataAttributes: null
181+ };
182+ tokens.forEach((token) => {
183+ if (token.type === "pseudo-class" && token.kind === "where") {
184+ token.selectors.forEach((compound) => {
185+ compound.forEach((inner) => {
186+ if (inner.type === "class" && this.bundlerConfig.themes.includes(inner.name)) {
187+ variants.theme = inner.name;
188+ }
189+ if (inner.type === "pseudo-class" && inner.kind === "dir") {
190+ variants.rtl = inner.direction === "rtl";
191+ }
192+ });
193+ });
194+ }
195+ if (token.type === "pseudo-class" && token.kind === "active") {
196+ variants.active = true;
197+ }
198+ if (token.type === "pseudo-class" && token.kind === "focus") {
199+ variants.focus = true;
200+ }
201+ if (token.type === "pseudo-class" && token.kind === "disabled") {
202+ variants.disabled = true;
203+ }
204+ if (token.type === "attribute" && token.operation === null && token.name.startsWith("data-")) {
205+ variants.dataAttributes ??= {};
206+ variants.dataAttributes[token.name] = `"true"`;
207+ }
208+ if (token.type === "attribute" && token.operation?.operator === "equal" && token.name.startsWith("data-")) {
209+ variants.dataAttributes ??= {};
210+ variants.dataAttributes[token.name] = `"${token.operation.value}"`;
211+ }
212+ });
213+ const hasVariants = Object.values(variants).some(config.isDefined);
214+ return { variants, hasVariants };
215+ }
216+ withSelectorVariants(variants, run) {
217+ this.declarationConfig.rtl ??= variants.rtl;
218+ this.declarationConfig.theme ??= variants.theme;
219+ this.declarationConfig.active ??= variants.active;
220+ this.declarationConfig.focus ??= variants.focus;
221+ this.declarationConfig.disabled ??= variants.disabled;
222+ this.declarationConfig.dataAttributes ??= variants.dataAttributes;
223+ run();
224+ this.declarationConfig.rtl = null;
225+ this.declarationConfig.theme = null;
226+ this.declarationConfig.active = null;
227+ this.declarationConfig.focus = null;
228+ this.declarationConfig.disabled = null;
229+ this.declarationConfig.dataAttributes = null;
230+ }
231 parseRuleRec(rule) {
232 if (this.declarationConfig.className !== null) {
233 const lastStyle = this.stylesheets[this.declarationConfig.className]?.at(-1);
234@@ -1552,64 +1611,30 @@ class ProcessorBuilder {
235 this.declarationConfig.className = newClassName;
236 this.stylesheets[newClassName] ??= [];
237 this.stylesheets[newClassName].push({});
238- rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
239- rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
240- rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
241- return;
242- }
243- let rtl = null;
244- let theme = null;
245- let active = null;
246- let focus = null;
247- let disabled = null;
248- let dataAttributes = null;
249- selector.forEach((selector2) => {
250- if (selector2.type === "pseudo-class" && selector2.kind === "where") {
251- selector2.selectors.forEach((selector3) => {
252- selector3.forEach((selector4) => {
253- if (selector4.type === "class" && this.bundlerConfig.themes.includes(selector4.name)) {
254- theme = selector4.name;
255- }
256- if (selector4.type === "pseudo-class" && selector4.kind === "dir") {
257- rtl = selector4.direction === "rtl";
258- }
259- });
260- });
261- }
262- if (selector2.type === "pseudo-class" && selector2.kind === "active") {
263- active = true;
264- }
265- if (selector2.type === "pseudo-class" && selector2.kind === "focus") {
266- focus = true;
267+ const trailing = selector.slice(1);
268+ const { variants, hasVariants } = this.readSelectorVariants(trailing);
269+ if (trailing.length > 0 && !hasVariants) {
270+ return;
271 }
272- if (selector2.type === "pseudo-class" && selector2.kind === "disabled") {
273- disabled = true;
274- }
275- if (selector2.type === "attribute" && selector2.operation === null && selector2.name.startsWith("data-")) {
276- dataAttributes ??= {};
277- dataAttributes[selector2.name] = `"true"`;
278- }
279- if (selector2.type === "attribute" && selector2.operation?.operator === "equal" && selector2.name.startsWith("data-")) {
280- dataAttributes ??= {};
281- dataAttributes[selector2.name] = `"${selector2.operation.value}"`;
282+ const parseClassRule = () => {
283+ rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
284+ rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
285+ rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
286+ };
287+ if (hasVariants) {
288+ this.withSelectorVariants(variants, parseClassRule);
289+ } else {
290+ parseClassRule();
291 }
292- });
293- if ([rtl, theme, active, focus, disabled, dataAttributes].some(config.isDefined)) {
294- this.declarationConfig.rtl ??= rtl;
295- this.declarationConfig.theme ??= theme;
296- this.declarationConfig.active ??= active;
297- this.declarationConfig.focus ??= focus;
298- this.declarationConfig.disabled ??= disabled;
299- this.declarationConfig.dataAttributes ??= dataAttributes;
300- rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
301- rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
302- rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
303- this.declarationConfig.rtl = null;
304- this.declarationConfig.theme = null;
305- this.declarationConfig.active = null;
306- this.declarationConfig.focus = null;
307- this.declarationConfig.disabled = null;
308- this.declarationConfig.dataAttributes = null;
309+ return;
310+ }
311+ const { variants, hasVariants } = this.readSelectorVariants(selector);
312+ if (hasVariants) {
313+ this.withSelectorVariants(variants, () => {
314+ rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
315+ rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
316+ rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
317+ });
318 return;
319 }
320 selector.forEach((selectorToken) => {
321@@ -1753,10 +1778,11 @@ const transform = async (config$1, projectRoot, filePath, data, options) => {
14 await bundlerConfig.generateArtifacts(cssArtifactPath);322 await bundlerConfig.generateArtifacts(cssArtifactPath);
15 const virtualCode = await compileCSS(bundlerConfig);323 const virtualCode = await compileCSS(bundlerConfig);
16 const isWeb = bundlerConfig.platform === config.Platform.Web;324 const isWeb = bundlerConfig.platform === config.Platform.Web;
...@@ -24,7 +332,7 @@ index d57ca81..fd8693c 100644...@@ -24,7 +332,7 @@ index d57ca81..fd8693c 100644
24 "utf-8"332 "utf-8"
25 );333 );
26diff --git a/dist/metro/transformer.mjs b/dist/metro/transformer.mjs334diff --git a/dist/metro/transformer.mjs b/dist/metro/transformer.mjs
27index d3e7475..bf63587 100644335index d3e7475f8f0aaba510b318657dc8374e6c6097cb..6ff8cffc4bb703521b6d86f0ac14be054ba5b9ce 100644
28--- a/dist/metro/transformer.mjs336--- a/dist/metro/transformer.mjs
29+++ b/dist/metro/transformer.mjs337+++ b/dist/metro/transformer.mjs
30@@ -5,6 +5,7 @@ import { converter, parse, formatHex, formatHex8 } from 'culori';338@@ -5,6 +5,7 @@ import { converter, parse, formatHex, formatHex8 } from 'culori';
...@@ -35,7 +343,159 @@ index d3e7475..bf63587 100644...@@ -35,7 +343,159 @@ index d3e7475..bf63587 100644
35 343
36 const toCamelCase = (str) => str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());344 const toCamelCase = (str) => str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
37 const pipe = (data) => ((...fns) => fns.reduce((acc, fn) => fn(acc), data));345 const pipe = (data) => ((...fns) => fns.reduce((acc, fn) => fn(acc), data));
38@@ -1746,10 +1747,11 @@ const transform = async (config, projectRoot, filePath, data, options) => {346@@ -1530,6 +1531,64 @@ class ProcessorBuilder {
347 style.importantProperties.push(declaration.property);
348 }
349 }
350+ readSelectorVariants(tokens) {
351+ const variants = {
352+ rtl: null,
353+ theme: null,
354+ active: null,
355+ focus: null,
356+ disabled: null,
357+ dataAttributes: null
358+ };
359+ tokens.forEach((token) => {
360+ if (token.type === "pseudo-class" && token.kind === "where") {
361+ token.selectors.forEach((compound) => {
362+ compound.forEach((inner) => {
363+ if (inner.type === "class" && this.bundlerConfig.themes.includes(inner.name)) {
364+ variants.theme = inner.name;
365+ }
366+ if (inner.type === "pseudo-class" && inner.kind === "dir") {
367+ variants.rtl = inner.direction === "rtl";
368+ }
369+ });
370+ });
371+ }
372+ if (token.type === "pseudo-class" && token.kind === "active") {
373+ variants.active = true;
374+ }
375+ if (token.type === "pseudo-class" && token.kind === "focus") {
376+ variants.focus = true;
377+ }
378+ if (token.type === "pseudo-class" && token.kind === "disabled") {
379+ variants.disabled = true;
380+ }
381+ if (token.type === "attribute" && token.operation === null && token.name.startsWith("data-")) {
382+ variants.dataAttributes ??= {};
383+ variants.dataAttributes[token.name] = `"true"`;
384+ }
385+ if (token.type === "attribute" && token.operation?.operator === "equal" && token.name.startsWith("data-")) {
386+ variants.dataAttributes ??= {};
387+ variants.dataAttributes[token.name] = `"${token.operation.value}"`;
388+ }
389+ });
390+ const hasVariants = Object.values(variants).some(isDefined);
391+ return { variants, hasVariants };
392+ }
393+ withSelectorVariants(variants, run) {
394+ this.declarationConfig.rtl ??= variants.rtl;
395+ this.declarationConfig.theme ??= variants.theme;
396+ this.declarationConfig.active ??= variants.active;
397+ this.declarationConfig.focus ??= variants.focus;
398+ this.declarationConfig.disabled ??= variants.disabled;
399+ this.declarationConfig.dataAttributes ??= variants.dataAttributes;
400+ run();
401+ this.declarationConfig.rtl = null;
402+ this.declarationConfig.theme = null;
403+ this.declarationConfig.active = null;
404+ this.declarationConfig.focus = null;
405+ this.declarationConfig.disabled = null;
406+ this.declarationConfig.dataAttributes = null;
407+ }
408 parseRuleRec(rule) {
409 if (this.declarationConfig.className !== null) {
410 const lastStyle = this.stylesheets[this.declarationConfig.className]?.at(-1);
411@@ -1545,64 +1604,30 @@ class ProcessorBuilder {
412 this.declarationConfig.className = newClassName;
413 this.stylesheets[newClassName] ??= [];
414 this.stylesheets[newClassName].push({});
415- rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
416- rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
417- rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
418- return;
419- }
420- let rtl = null;
421- let theme = null;
422- let active = null;
423- let focus = null;
424- let disabled = null;
425- let dataAttributes = null;
426- selector.forEach((selector2) => {
427- if (selector2.type === "pseudo-class" && selector2.kind === "where") {
428- selector2.selectors.forEach((selector3) => {
429- selector3.forEach((selector4) => {
430- if (selector4.type === "class" && this.bundlerConfig.themes.includes(selector4.name)) {
431- theme = selector4.name;
432- }
433- if (selector4.type === "pseudo-class" && selector4.kind === "dir") {
434- rtl = selector4.direction === "rtl";
435- }
436- });
437- });
438- }
439- if (selector2.type === "pseudo-class" && selector2.kind === "active") {
440- active = true;
441- }
442- if (selector2.type === "pseudo-class" && selector2.kind === "focus") {
443- focus = true;
444+ const trailing = selector.slice(1);
445+ const { variants, hasVariants } = this.readSelectorVariants(trailing);
446+ if (trailing.length > 0 && !hasVariants) {
447+ return;
448 }
449- if (selector2.type === "pseudo-class" && selector2.kind === "disabled") {
450- disabled = true;
451- }
452- if (selector2.type === "attribute" && selector2.operation === null && selector2.name.startsWith("data-")) {
453- dataAttributes ??= {};
454- dataAttributes[selector2.name] = `"true"`;
455- }
456- if (selector2.type === "attribute" && selector2.operation?.operator === "equal" && selector2.name.startsWith("data-")) {
457- dataAttributes ??= {};
458- dataAttributes[selector2.name] = `"${selector2.operation.value}"`;
459+ const parseClassRule = () => {
460+ rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
461+ rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
462+ rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
463+ };
464+ if (hasVariants) {
465+ this.withSelectorVariants(variants, parseClassRule);
466+ } else {
467+ parseClassRule();
468 }
469- });
470- if ([rtl, theme, active, focus, disabled, dataAttributes].some(isDefined)) {
471- this.declarationConfig.rtl ??= rtl;
472- this.declarationConfig.theme ??= theme;
473- this.declarationConfig.active ??= active;
474- this.declarationConfig.focus ??= focus;
475- this.declarationConfig.disabled ??= disabled;
476- this.declarationConfig.dataAttributes ??= dataAttributes;
477- rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
478- rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
479- rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
480- this.declarationConfig.rtl = null;
481- this.declarationConfig.theme = null;
482- this.declarationConfig.active = null;
483- this.declarationConfig.focus = null;
484- this.declarationConfig.disabled = null;
485- this.declarationConfig.dataAttributes = null;
486+ return;
487+ }
488+ const { variants, hasVariants } = this.readSelectorVariants(selector);
489+ if (hasVariants) {
490+ this.withSelectorVariants(variants, () => {
491+ rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
492+ rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
493+ rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
494+ });
495 return;
496 }
497 selector.forEach((selectorToken) => {
498@@ -1746,10 +1771,11 @@ const transform = async (config, projectRoot, filePath, data, options) => {
39 await bundlerConfig.generateArtifacts(cssArtifactPath);499 await bundlerConfig.generateArtifacts(cssArtifactPath);
40 const virtualCode = await compileCSS(bundlerConfig);500 const virtualCode = await compileCSS(bundlerConfig);
41 const isWeb = bundlerConfig.platform === Platform.Web;501 const isWeb = bundlerConfig.platform === Platform.Web;
...@@ -48,8 +508,164 @@ index d3e7475..bf63587 100644...@@ -48,8 +508,164 @@ index d3e7475..bf63587 100644
48 ].join(""),508 ].join(""),
49 "utf-8"509 "utf-8"
50 );510 );
511diff --git a/dist/module/bundler/css-processor/processor.js b/dist/module/bundler/css-processor/processor.js
512index 58c96b6ab642c2b724d1868d0d48b77bad33be6f..70b759b04913a0e9028c936827b4578d628b4728 100644
513--- a/dist/module/bundler/css-processor/processor.js
514+++ b/dist/module/bundler/css-processor/processor.js
515@@ -99,6 +99,64 @@ export class ProcessorBuilder {
516 style.importantProperties.push(declaration.property);
517 }
518 }
519+ readSelectorVariants(tokens) {
520+ const variants = {
521+ rtl: null,
522+ theme: null,
523+ active: null,
524+ focus: null,
525+ disabled: null,
526+ dataAttributes: null
527+ };
528+ tokens.forEach((token) => {
529+ if (token.type === "pseudo-class" && token.kind === "where") {
530+ token.selectors.forEach((compound) => {
531+ compound.forEach((inner) => {
532+ if (inner.type === "class" && this.bundlerConfig.themes.includes(inner.name)) {
533+ variants.theme = inner.name;
534+ }
535+ if (inner.type === "pseudo-class" && inner.kind === "dir") {
536+ variants.rtl = inner.direction === "rtl";
537+ }
538+ });
539+ });
540+ }
541+ if (token.type === "pseudo-class" && token.kind === "active") {
542+ variants.active = true;
543+ }
544+ if (token.type === "pseudo-class" && token.kind === "focus") {
545+ variants.focus = true;
546+ }
547+ if (token.type === "pseudo-class" && token.kind === "disabled") {
548+ variants.disabled = true;
549+ }
550+ if (token.type === "attribute" && token.operation === null && token.name.startsWith("data-")) {
551+ variants.dataAttributes ??= {};
552+ variants.dataAttributes[token.name] = `"true"`;
553+ }
554+ if (token.type === "attribute" && token.operation?.operator === "equal" && token.name.startsWith("data-")) {
555+ variants.dataAttributes ??= {};
556+ variants.dataAttributes[token.name] = `"${token.operation.value}"`;
557+ }
558+ });
559+ const hasVariants = Object.values(variants).some(isDefined);
560+ return { variants, hasVariants };
561+ }
562+ withSelectorVariants(variants, run) {
563+ this.declarationConfig.rtl ??= variants.rtl;
564+ this.declarationConfig.theme ??= variants.theme;
565+ this.declarationConfig.active ??= variants.active;
566+ this.declarationConfig.focus ??= variants.focus;
567+ this.declarationConfig.disabled ??= variants.disabled;
568+ this.declarationConfig.dataAttributes ??= variants.dataAttributes;
569+ run();
570+ this.declarationConfig.rtl = null;
571+ this.declarationConfig.theme = null;
572+ this.declarationConfig.active = null;
573+ this.declarationConfig.focus = null;
574+ this.declarationConfig.disabled = null;
575+ this.declarationConfig.dataAttributes = null;
576+ }
577 parseRuleRec(rule) {
578 if (this.declarationConfig.className !== null) {
579 const lastStyle = this.stylesheets[this.declarationConfig.className]?.at(-1);
580@@ -114,64 +172,30 @@ export class ProcessorBuilder {
581 this.declarationConfig.className = newClassName;
582 this.stylesheets[newClassName] ??= [];
583 this.stylesheets[newClassName].push({});
584- rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
585- rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
586- rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
587- return;
588- }
589- let rtl = null;
590- let theme = null;
591- let active = null;
592- let focus = null;
593- let disabled = null;
594- let dataAttributes = null;
595- selector.forEach((selector2) => {
596- if (selector2.type === "pseudo-class" && selector2.kind === "where") {
597- selector2.selectors.forEach((selector3) => {
598- selector3.forEach((selector4) => {
599- if (selector4.type === "class" && this.bundlerConfig.themes.includes(selector4.name)) {
600- theme = selector4.name;
601- }
602- if (selector4.type === "pseudo-class" && selector4.kind === "dir") {
603- rtl = selector4.direction === "rtl";
604- }
605- });
606- });
607- }
608- if (selector2.type === "pseudo-class" && selector2.kind === "active") {
609- active = true;
610+ const trailing = selector.slice(1);
611+ const { variants, hasVariants } = this.readSelectorVariants(trailing);
612+ if (trailing.length > 0 && !hasVariants) {
613+ return;
614 }
615- if (selector2.type === "pseudo-class" && selector2.kind === "focus") {
616- focus = true;
617- }
618- if (selector2.type === "pseudo-class" && selector2.kind === "disabled") {
619- disabled = true;
620- }
621- if (selector2.type === "attribute" && selector2.operation === null && selector2.name.startsWith("data-")) {
622- dataAttributes ??= {};
623- dataAttributes[selector2.name] = `"true"`;
624- }
625- if (selector2.type === "attribute" && selector2.operation?.operator === "equal" && selector2.name.startsWith("data-")) {
626- dataAttributes ??= {};
627- dataAttributes[selector2.name] = `"${selector2.operation.value}"`;
628+ const parseClassRule = () => {
629+ rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
630+ rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
631+ rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
632+ };
633+ if (hasVariants) {
634+ this.withSelectorVariants(variants, parseClassRule);
635+ } else {
636+ parseClassRule();
637 }
638- });
639- if ([rtl, theme, active, focus, disabled, dataAttributes].some(isDefined)) {
640- this.declarationConfig.rtl ??= rtl;
641- this.declarationConfig.theme ??= theme;
642- this.declarationConfig.active ??= active;
643- this.declarationConfig.focus ??= focus;
644- this.declarationConfig.disabled ??= disabled;
645- this.declarationConfig.dataAttributes ??= dataAttributes;
646- rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
647- rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
648- rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
649- this.declarationConfig.rtl = null;
650- this.declarationConfig.theme = null;
651- this.declarationConfig.active = null;
652- this.declarationConfig.focus = null;
653- this.declarationConfig.disabled = null;
654- this.declarationConfig.dataAttributes = null;
655+ return;
656+ }
657+ const { variants, hasVariants } = this.readSelectorVariants(selector);
658+ if (hasVariants) {
659+ this.withSelectorVariants(variants, () => {
660+ rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
661+ rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
662+ rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
663+ });
664 return;
665 }
666 selector.forEach((selectorToken) => {
51diff --git a/src/bundler/adapters/metro/transformer.ts b/src/bundler/adapters/metro/transformer.ts667diff --git a/src/bundler/adapters/metro/transformer.ts b/src/bundler/adapters/metro/transformer.ts
52index 08059b0..05c0515 100644668index 08059b0fb2394effd2553086216d8933a53fad84..05c051595bfffd313f1d731fcedc1e991af720e1 100644
53--- a/src/bundler/adapters/metro/transformer.ts669--- a/src/bundler/adapters/metro/transformer.ts
54+++ b/src/bundler/adapters/metro/transformer.ts670+++ b/src/bundler/adapters/metro/transformer.ts
55@@ -5,6 +5,7 @@ import { Platform } from '@/common/consts'671@@ -5,6 +5,7 @@ import { Platform } from '@/common/consts'
...@@ -82,8 +698,195 @@ index 08059b0..05c0515 100644...@@ -82,8 +698,195 @@ index 08059b0..05c0515 100644
82 ].join(''),698 ].join(''),
83 'utf-8',699 'utf-8',
84 )700 )
701diff --git a/src/bundler/css-processor/processor.ts b/src/bundler/css-processor/processor.ts
702index c8b3bb86e2a50c16ebc3f0404f4d9ac050aba61d..b5e185469733aca4289b7b07bc76dbfcd8fd5788 100644
703--- a/src/bundler/css-processor/processor.ts
704+++ b/src/bundler/css-processor/processor.ts
705@@ -123,6 +123,77 @@ export class ProcessorBuilder {
706 }
707 }
708
709+ private readSelectorVariants(tokens: ReadonlyArray<any>) {
710+ const variants = {
711+ rtl: null as boolean | null,
712+ theme: null as string | null,
713+ active: null as boolean | null,
714+ focus: null as boolean | null,
715+ disabled: null as boolean | null,
716+ dataAttributes: null as Record<string, string> | null,
717+ }
718+
719+ tokens.forEach(token => {
720+ if (token.type === 'pseudo-class' && token.kind === 'where') {
721+ token.selectors.forEach((compound: Array<any>) => {
722+ compound.forEach(inner => {
723+ if (inner.type === 'class' && this.bundlerConfig.themes.includes(inner.name)) {
724+ variants.theme = inner.name
725+ }
726+
727+ if (inner.type === 'pseudo-class' && inner.kind === 'dir') {
728+ variants.rtl = inner.direction === 'rtl'
729+ }
730+ })
731+ })
732+ }
733+
734+ if (token.type === 'pseudo-class' && token.kind === 'active') {
735+ variants.active = true
736+ }
737+
738+ if (token.type === 'pseudo-class' && token.kind === 'focus') {
739+ variants.focus = true
740+ }
741+
742+ if (token.type === 'pseudo-class' && token.kind === 'disabled') {
743+ variants.disabled = true
744+ }
745+
746+ // data-x
747+ if (token.type === 'attribute' && token.operation === null && token.name.startsWith('data-')) {
748+ variants.dataAttributes ??= {}
749+ variants.dataAttributes[token.name] = `"true"`
750+ }
751+
752+ // data-x=
753+ if (token.type === 'attribute' && token.operation?.operator === 'equal' && token.name.startsWith('data-')) {
754+ variants.dataAttributes ??= {}
755+ variants.dataAttributes[token.name] = `"${token.operation.value}"`
756+ }
757+ })
758+
759+ return { variants, hasVariants: Object.values(variants).some(isDefined) }
760+ }
761+
762+ private withSelectorVariants(variants: ReturnType<ProcessorBuilder['readSelectorVariants']>['variants'], run: () => void) {
763+ this.declarationConfig.rtl ??= variants.rtl
764+ this.declarationConfig.theme ??= variants.theme
765+ this.declarationConfig.active ??= variants.active
766+ this.declarationConfig.focus ??= variants.focus
767+ this.declarationConfig.disabled ??= variants.disabled
768+ this.declarationConfig.dataAttributes ??= variants.dataAttributes
769+
770+ run()
771+
772+ this.declarationConfig.rtl = null
773+ this.declarationConfig.theme = null
774+ this.declarationConfig.active = null
775+ this.declarationConfig.focus = null
776+ this.declarationConfig.disabled = null
777+ this.declarationConfig.dataAttributes = null
778+ }
779+
780 private parseRuleRec(rule: Rule<Declaration, MediaQuery>) {
781 if (this.declarationConfig.className !== null) {
782 const lastStyle = this.stylesheets[this.declarationConfig.className]?.at(-1)
783@@ -142,78 +213,41 @@ export class ProcessorBuilder {
784 this.stylesheets[newClassName] ??= []
785 this.stylesheets[newClassName].push({})
786
787- rule.value.declarations?.declarations?.forEach(declaration => this.addDeclaration(declaration))
788- rule.value.declarations?.importantDeclarations?.forEach(declaration => this.addDeclaration(declaration, true))
789- rule.value.rules?.forEach(rule => this.parseRuleRec(rule))
790+ // Tailwind >= 4.3.3 emits `.active\:x:active {}` instead of
791+ // nesting `&:active` under the class, so the variant tokens
792+ // sit right after the class token. A compound the runtime
793+ // cannot express (e.g. `[aria-disabled="true"]`) used to be
794+ // an empty nested rule and must not become unconditional.
795+ const trailing = selector.slice(1)
796+ const { variants, hasVariants } = this.readSelectorVariants(trailing)
797
798- return
799- }
800-
801- let rtl = null as boolean | null
802- let theme = null as string | null
803- let active = null as boolean | null
804- let focus = null as boolean | null
805- let disabled = null as boolean | null
806- let dataAttributes = null as Record<string, string> | null
807-
808- selector.forEach(selector => {
809- if (selector.type === 'pseudo-class' && selector.kind === 'where') {
810- selector.selectors.forEach(selector => {
811- selector.forEach(selector => {
812- if (selector.type === 'class' && this.bundlerConfig.themes.includes(selector.name)) {
813- theme = selector.name
814- }
815-
816- if (selector.type === 'pseudo-class' && selector.kind === 'dir') {
817- rtl = selector.direction === 'rtl'
818- }
819- })
820- })
821+ if (trailing.length > 0 && !hasVariants) {
822+ return
823 }
824
825- if (selector.type === 'pseudo-class' && selector.kind === 'active') {
826- active = true
827- }
828-
829- if (selector.type === 'pseudo-class' && selector.kind === 'focus') {
830- focus = true
831+ const parseClassRule = () => {
832+ rule.value.declarations?.declarations?.forEach(declaration => this.addDeclaration(declaration))
833+ rule.value.declarations?.importantDeclarations?.forEach(declaration => this.addDeclaration(declaration, true))
834+ rule.value.rules?.forEach(rule => this.parseRuleRec(rule))
835 }
836
837- if (selector.type === 'pseudo-class' && selector.kind === 'disabled') {
838- disabled = true
839+ if (hasVariants) {
840+ this.withSelectorVariants(variants, parseClassRule)
841+ } else {
842+ parseClassRule()
843 }
844
845- // data-x
846- if (selector.type === 'attribute' && selector.operation === null && selector.name.startsWith('data-')) {
847- dataAttributes ??= {}
848- dataAttributes[selector.name] = `"true"`
849- }
850+ return
851+ }
852
853- // data-x=
854- if (selector.type === 'attribute' && selector.operation?.operator === 'equal' && selector.name.startsWith('data-')) {
855- dataAttributes ??= {}
856- dataAttributes[selector.name] = `"${selector.operation.value}"`
857- }
858- })
859+ const { variants, hasVariants } = this.readSelectorVariants(selector)
860
861- if ([rtl, theme, active, focus, disabled, dataAttributes].some(isDefined)) {
862- this.declarationConfig.rtl ??= rtl
863- this.declarationConfig.theme ??= theme
864- this.declarationConfig.active ??= active
865- this.declarationConfig.focus ??= focus
866- this.declarationConfig.disabled ??= disabled
867- this.declarationConfig.dataAttributes ??= dataAttributes
868-
869- rule.value.declarations?.declarations?.forEach(declaration => this.addDeclaration(declaration))
870- rule.value.declarations?.importantDeclarations?.forEach(declaration => this.addDeclaration(declaration, true))
871- rule.value.rules?.forEach(rule => this.parseRuleRec(rule))
872-
873- this.declarationConfig.rtl = null
874- this.declarationConfig.theme = null
875- this.declarationConfig.active = null
876- this.declarationConfig.focus = null
877- this.declarationConfig.disabled = null
878- this.declarationConfig.dataAttributes = null
879+ if (hasVariants) {
880+ this.withSelectorVariants(variants, () => {
881+ rule.value.declarations?.declarations?.forEach(declaration => this.addDeclaration(declaration))
882+ rule.value.declarations?.importantDeclarations?.forEach(declaration => this.addDeclaration(declaration, true))
883+ rule.value.rules?.forEach(rule => this.parseRuleRec(rule))
884+ })
885
886 return
887 }
85diff --git a/src/core/config/config.native.ts b/src/core/config/config.native.ts888diff --git a/src/core/config/config.native.ts b/src/core/config/config.native.ts
86index 5e0832b..01aed1b 100644889index 5e0832b5913df6f02604020d7ff5caf3b6a8d912..01aed1be25ca928f3a802996f41cb6b841aaed86 100644
87--- a/src/core/config/config.native.ts890--- a/src/core/config/config.native.ts
88+++ b/src/core/config/config.native.ts891+++ b/src/core/config/config.native.ts
89@@ -8,6 +8,8 @@ import type { CSSVariables, GenerateStyleSheetsCallback, ThemeName } from '../ty892@@ -8,6 +8,8 @@ import type { CSSVariables, GenerateStyleSheetsCallback, ThemeName } from '../ty
pnpm-lock.yaml+3-3
...@@ -103,7 +103,7 @@ patchedDependencies:...@@ -103,7 +103,7 @@ patchedDependencies:
103 react-native-keyboard-controller@1.21.13: 6e4339347bc5bb3c9ea67d85ff5c814058b211c5750f247aba59d07869a2e787103 react-native-keyboard-controller@1.21.13: 6e4339347bc5bb3c9ea67d85ff5c814058b211c5750f247aba59d07869a2e787
104 react-native-nitro-modules@0.35.9: 825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675104 react-native-nitro-modules@0.35.9: 825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675
105 react-native-screens@4.26.2: 149bef30a66351ea9b26b42f87b78c539cb52b880f2387bb323ec80dcac84006105 react-native-screens@4.26.2: 149bef30a66351ea9b26b42f87b78c539cb52b880f2387bb323ec80dcac84006
106 uniwind@1.11.0: 329a77525509623d763b738152dbd00ab392cdcdd9fb6ed2b4a20e086e437196106 uniwind@1.11.0: 17d92be2eec71bb6396b402e8d034968e54b28746876d7977cb3139655f42b90
107107
108importers:108importers:
109109
...@@ -449,7 +449,7 @@ importers:...@@ -449,7 +449,7 @@ importers:
449 version: 3.6.0449 version: 3.6.0
450 uniwind:450 uniwind:
451 specifier: 1.11.0451 specifier: 1.11.0
452 version: 1.11.0(patch_hash=329a77525509623d763b738152dbd00ab392cdcdd9fb6ed2b4a20e086e437196)(@expo/metro-config@57.0.12(patch_hash=96f1a75347e6ea02dc4b7034ace815d8ee39e18b8166ebfb573d9e58328f0dc2)(bufferutil@4.1.0)(expo@57.0.18)(typescript@6.0.3)(utf-8-validate@6.0.6))(metro-cache@0.84.5)(metro-transform-worker@0.84.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(metro@0.84.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(tailwindcss@4.3.3)452 version: 1.11.0(patch_hash=17d92be2eec71bb6396b402e8d034968e54b28746876d7977cb3139655f42b90)(@expo/metro-config@57.0.12(patch_hash=96f1a75347e6ea02dc4b7034ace815d8ee39e18b8166ebfb573d9e58328f0dc2)(bufferutil@4.1.0)(expo@57.0.18)(typescript@6.0.3)(utf-8-validate@6.0.6))(metro-cache@0.84.5)(metro-transform-worker@0.84.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(metro@0.84.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(tailwindcss@4.3.3)
453 devDependencies:453 devDependencies:
454 '@effect/vitest':454 '@effect/vitest':
455 specifier: 4.0.0-beta.103455 specifier: 4.0.0-beta.103
...@@ -20640,7 +20640,7 @@ snapshots:...@@ -20640,7 +20640,7 @@ snapshots:
2064020640
20641 universalify@2.0.1: {}20641 universalify@2.0.1: {}
2064220642
20643 uniwind@1.11.0(patch_hash=329a77525509623d763b738152dbd00ab392cdcdd9fb6ed2b4a20e086e437196)(@expo/metro-config@57.0.12(patch_hash=96f1a75347e6ea02dc4b7034ace815d8ee39e18b8166ebfb573d9e58328f0dc2)(bufferutil@4.1.0)(expo@57.0.18)(typescript@6.0.3)(utf-8-validate@6.0.6))(metro-cache@0.84.5)(metro-transform-worker@0.84.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(metro@0.84.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(tailwindcss@4.3.3):20643 uniwind@1.11.0(patch_hash=17d92be2eec71bb6396b402e8d034968e54b28746876d7977cb3139655f42b90)(@expo/metro-config@57.0.12(patch_hash=96f1a75347e6ea02dc4b7034ace815d8ee39e18b8166ebfb573d9e58328f0dc2)(bufferutil@4.1.0)(expo@57.0.18)(typescript@6.0.3)(utf-8-validate@6.0.6))(metro-cache@0.84.5)(metro-transform-worker@0.84.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(metro@0.84.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(tailwindcss@4.3.3):
20644 dependencies:20644 dependencies:
20645 '@tailwindcss/node': 4.3.320645 '@tailwindcss/node': 4.3.3
20646 '@tailwindcss/oxide': 4.3.320646 '@tailwindcss/oxide': 4.3.3