diff --git a/dist/common/bundler/css-processor/processor.js b/dist/common/bundler/css-processor/processor.js
index 7dd369f..48321d1 100644
--- a/dist/common/bundler/css-processor/processor.js
+++ b/dist/common/bundler/css-processor/processor.js
@@ -105,6 +105,64 @@ class ProcessorBuilder {
style.importantProperties.push(declaration.property);
}
}
+ readSelectorVariants(tokens) {
+ const variants = {
+ rtl: null,
+ theme: null,
+ active: null,
+ focus: null,
+ disabled: null,
+ dataAttributes: null
+ };
+ tokens.forEach((token) => {
+ if (token.type === "pseudo-class" && token.kind === "where") {
+ token.selectors.forEach((compound) => {
+ compound.forEach((inner) => {
+ if (inner.type === "class" && this.bundlerConfig.themes.includes(inner.name)) {
+ variants.theme = inner.name;
+ }
+ if (inner.type === "pseudo-class" && inner.kind === "dir") {
+ variants.rtl = inner.direction === "rtl";
+ }
+ });
+ });
+ }
+ if (token.type === "pseudo-class" && token.kind === "active") {
+ variants.active = true;
+ }
+ if (token.type === "pseudo-class" && token.kind === "focus") {
+ variants.focus = true;
+ }
+ if (token.type === "pseudo-class" && token.kind === "disabled") {
+ variants.disabled = true;
+ }
+ if (token.type === "attribute" && token.operation === null && token.name.startsWith("data-")) {
+ variants.dataAttributes ??= {};
+ variants.dataAttributes[token.name] = `"true"`;
+ }
+ if (token.type === "attribute" && token.operation?.operator === "equal" && token.name.startsWith("data-")) {
+ variants.dataAttributes ??= {};
+ variants.dataAttributes[token.name] = `"${token.operation.value}"`;
+ }
+ });
+ const hasVariants = Object.values(variants).some(_utils.isDefined);
+ return { variants, hasVariants };
+ }
+ withSelectorVariants(variants, run) {
+ this.declarationConfig.rtl ??= variants.rtl;
+ this.declarationConfig.theme ??= variants.theme;
+ this.declarationConfig.active ??= variants.active;
+ this.declarationConfig.focus ??= variants.focus;
+ this.declarationConfig.disabled ??= variants.disabled;
+ this.declarationConfig.dataAttributes ??= variants.dataAttributes;
+ run();
+ this.declarationConfig.rtl = null;
+ this.declarationConfig.theme = null;
+ this.declarationConfig.active = null;
+ this.declarationConfig.focus = null;
+ this.declarationConfig.disabled = null;
+ this.declarationConfig.dataAttributes = null;
+ }
parseRuleRec(rule) {
if (this.declarationConfig.className !== null) {
const lastStyle = this.stylesheets[this.declarationConfig.className]?.at(-1);
@@ -120,64 +178,30 @@ class ProcessorBuilder {
this.declarationConfig.className = newClassName;
this.stylesheets[newClassName] ??= [];
this.stylesheets[newClassName].push({});
- rule.value.declarations?.declarations?.forEach(declaration => this.addDeclaration(declaration));
- rule.value.declarations?.importantDeclarations?.forEach(declaration => this.addDeclaration(declaration, true));
- rule.value.rules?.forEach(rule2 => this.parseRuleRec(rule2));
- return;
- }
- let rtl = null;
- let theme = null;
- let active = null;
- let focus = null;
- let disabled = null;
- let dataAttributes = null;
- selector.forEach(selector2 => {
- if (selector2.type === "pseudo-class" && selector2.kind === "where") {
- selector2.selectors.forEach(selector3 => {
- selector3.forEach(selector4 => {
- if (selector4.type === "class" && this.bundlerConfig.themes.includes(selector4.name)) {
- theme = selector4.name;
- }
- if (selector4.type === "pseudo-class" && selector4.kind === "dir") {
- rtl = selector4.direction === "rtl";
- }
- });
- });
- }
- if (selector2.type === "pseudo-class" && selector2.kind === "active") {
- active = true;
+ const trailing = selector.slice(1);
+ const { variants, hasVariants } = this.readSelectorVariants(trailing);
+ if (trailing.length > 0 && !hasVariants) {
+ return;
}
- if (selector2.type === "pseudo-class" && selector2.kind === "focus") {
- focus = true;
- }
- if (selector2.type === "pseudo-class" && selector2.kind === "disabled") {
- disabled = true;
- }
- if (selector2.type === "attribute" && selector2.operation === null && selector2.name.startsWith("data-")) {
- dataAttributes ??= {};
- dataAttributes[selector2.name] = `"true"`;
- }
- if (selector2.type === "attribute" && selector2.operation?.operator === "equal" && selector2.name.startsWith("data-")) {
- dataAttributes ??= {};
- dataAttributes[selector2.name] = `"${selector2.operation.value}"`;
+ const parseClassRule = () => {
+ rule.value.declarations?.declarations?.forEach(declaration => this.addDeclaration(declaration));
+ rule.value.declarations?.importantDeclarations?.forEach(declaration => this.addDeclaration(declaration, true));
+ rule.value.rules?.forEach(rule2 => this.parseRuleRec(rule2));
+ };
+ if (hasVariants) {
+ this.withSelectorVariants(variants, parseClassRule);
+ } else {
+ parseClassRule();
}
- });
- if ([rtl, theme, active, focus, disabled, dataAttributes].some(_utils.isDefined)) {
- this.declarationConfig.rtl ??= rtl;
- this.declarationConfig.theme ??= theme;
- this.declarationConfig.active ??= active;
- this.declarationConfig.focus ??= focus;
- this.declarationConfig.disabled ??= disabled;
- this.declarationConfig.dataAttributes ??= dataAttributes;
- rule.value.declarations?.declarations?.forEach(declaration => this.addDeclaration(declaration));
- rule.value.declarations?.importantDeclarations?.forEach(declaration => this.addDeclaration(declaration, true));
- rule.value.rules?.forEach(rule2 => this.parseRuleRec(rule2));
- this.declarationConfig.rtl = null;
- this.declarationConfig.theme = null;
- this.declarationConfig.active = null;
- this.declarationConfig.focus = null;
- this.declarationConfig.disabled = null;
- this.declarationConfig.dataAttributes = null;
+ return;
+ }
+ const { variants, hasVariants } = this.readSelectorVariants(selector);
+ if (hasVariants) {
+ this.withSelectorVariants(variants, () => {
+ rule.value.declarations?.declarations?.forEach(declaration => this.addDeclaration(declaration));
+ rule.value.declarations?.importantDeclarations?.forEach(declaration => this.addDeclaration(declaration, true));
+ rule.value.rules?.forEach(rule2 => this.parseRuleRec(rule2));
+ });
return;
}
selector.forEach(selectorToken => {
@@ -199,11 +223,16 @@ class ProcessorBuilder {
const {
mediaQueries
} = rule.value.query;
- this.declarationConfig.mediaQueries.push(...mediaQueries);
+ const outerConfig = this.declarationConfig;
+ const blockMediaQueries = [...outerConfig.mediaQueries, ...mediaQueries];
rule.value.rules.forEach(rule2 => {
+ this.declarationConfig = {
+ ...outerConfig,
+ mediaQueries: blockMediaQueries
+ };
this.parseRuleRec(rule2);
- this.declarationConfig = this.getDeclarationConfig();
});
+ this.declarationConfig = outerConfig;
return;
}
if (rule.type === "layer-block") {
diff --git a/dist/metro/transformer.cjs b/dist/metro/transformer.cjs
index d57ca81..83b58d1 100644
--- a/dist/metro/transformer.cjs
+++ b/dist/metro/transformer.cjs
@@ -7,6 +7,7 @@ const culori = require('culori');
const node = require('@tailwindcss/node');
const oxide = require('@tailwindcss/oxide');
const fs = require('fs');
+const node_crypto = require('node:crypto');
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
@@ -1537,6 +1538,64 @@ class ProcessorBuilder {
style.importantProperties.push(declaration.property);
}
}
+ readSelectorVariants(tokens) {
+ const variants = {
+ rtl: null,
+ theme: null,
+ active: null,
+ focus: null,
+ disabled: null,
+ dataAttributes: null
+ };
+ tokens.forEach((token) => {
+ if (token.type === "pseudo-class" && token.kind === "where") {
+ token.selectors.forEach((compound) => {
+ compound.forEach((inner) => {
+ if (inner.type === "class" && this.bundlerConfig.themes.includes(inner.name)) {
+ variants.theme = inner.name;
+ }
+ if (inner.type === "pseudo-class" && inner.kind === "dir") {
+ variants.rtl = inner.direction === "rtl";
+ }
+ });
+ });
+ }
+ if (token.type === "pseudo-class" && token.kind === "active") {
+ variants.active = true;
+ }
+ if (token.type === "pseudo-class" && token.kind === "focus") {
+ variants.focus = true;
+ }
+ if (token.type === "pseudo-class" && token.kind === "disabled") {
+ variants.disabled = true;
+ }
+ if (token.type === "attribute" && token.operation === null && token.name.startsWith("data-")) {
+ variants.dataAttributes ??= {};
+ variants.dataAttributes[token.name] = `"true"`;
+ }
+ if (token.type === "attribute" && token.operation?.operator === "equal" && token.name.startsWith("data-")) {
+ variants.dataAttributes ??= {};
+ variants.dataAttributes[token.name] = `"${token.operation.value}"`;
+ }
+ });
+ const hasVariants = Object.values(variants).some(config.isDefined);
+ return { variants, hasVariants };
+ }
+ withSelectorVariants(variants, run) {
+ this.declarationConfig.rtl ??= variants.rtl;
+ this.declarationConfig.theme ??= variants.theme;
+ this.declarationConfig.active ??= variants.active;
+ this.declarationConfig.focus ??= variants.focus;
+ this.declarationConfig.disabled ??= variants.disabled;
+ this.declarationConfig.dataAttributes ??= variants.dataAttributes;
+ run();
+ this.declarationConfig.rtl = null;
+ this.declarationConfig.theme = null;
+ this.declarationConfig.active = null;
+ this.declarationConfig.focus = null;
+ this.declarationConfig.disabled = null;
+ this.declarationConfig.dataAttributes = null;
+ }
parseRuleRec(rule) {
if (this.declarationConfig.className !== null) {
const lastStyle = this.stylesheets[this.declarationConfig.className]?.at(-1);
@@ -1552,64 +1611,30 @@ class ProcessorBuilder {
this.declarationConfig.className = newClassName;
this.stylesheets[newClassName] ??= [];
this.stylesheets[newClassName].push({});
- rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
- rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
- rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
- return;
- }
- let rtl = null;
- let theme = null;
- let active = null;
- let focus = null;
- let disabled = null;
- let dataAttributes = null;
- selector.forEach((selector2) => {
- if (selector2.type === "pseudo-class" && selector2.kind === "where") {
- selector2.selectors.forEach((selector3) => {
- selector3.forEach((selector4) => {
- if (selector4.type === "class" && this.bundlerConfig.themes.includes(selector4.name)) {
- theme = selector4.name;
- }
- if (selector4.type === "pseudo-class" && selector4.kind === "dir") {
- rtl = selector4.direction === "rtl";
- }
- });
- });
- }
- if (selector2.type === "pseudo-class" && selector2.kind === "active") {
- active = true;
- }
- if (selector2.type === "pseudo-class" && selector2.kind === "focus") {
- focus = true;
+ const trailing = selector.slice(1);
+ const { variants, hasVariants } = this.readSelectorVariants(trailing);
+ if (trailing.length > 0 && !hasVariants) {
+ return;
}
- if (selector2.type === "pseudo-class" && selector2.kind === "disabled") {
- disabled = true;
- }
- if (selector2.type === "attribute" && selector2.operation === null && selector2.name.startsWith("data-")) {
- dataAttributes ??= {};
- dataAttributes[selector2.name] = `"true"`;
- }
- if (selector2.type === "attribute" && selector2.operation?.operator === "equal" && selector2.name.startsWith("data-")) {
- dataAttributes ??= {};
- dataAttributes[selector2.name] = `"${selector2.operation.value}"`;
+ const parseClassRule = () => {
+ rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
+ rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
+ rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
+ };
+ if (hasVariants) {
+ this.withSelectorVariants(variants, parseClassRule);
+ } else {
+ parseClassRule();
}
- });
- if ([rtl, theme, active, focus, disabled, dataAttributes].some(config.isDefined)) {
- this.declarationConfig.rtl ??= rtl;
- this.declarationConfig.theme ??= theme;
- this.declarationConfig.active ??= active;
- this.declarationConfig.focus ??= focus;
- this.declarationConfig.disabled ??= disabled;
- this.declarationConfig.dataAttributes ??= dataAttributes;
- rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
- rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
- rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
- this.declarationConfig.rtl = null;
- this.declarationConfig.theme = null;
- this.declarationConfig.active = null;
- this.declarationConfig.focus = null;
- this.declarationConfig.disabled = null;
- this.declarationConfig.dataAttributes = null;
+ return;
+ }
+ const { variants, hasVariants } = this.readSelectorVariants(selector);
+ if (hasVariants) {
+ this.withSelectorVariants(variants, () => {
+ rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
+ rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
+ rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
+ });
return;
}
selector.forEach((selectorToken) => {
@@ -1629,11 +1654,13 @@ class ProcessorBuilder {
}
if (rule.type === "media") {
const { mediaQueries } = rule.value.query;
- this.declarationConfig.mediaQueries.push(...mediaQueries);
+ const outerConfig = this.declarationConfig;
+ const blockMediaQueries = [...outerConfig.mediaQueries, ...mediaQueries];
rule.value.rules.forEach((rule2) => {
+ this.declarationConfig = { ...outerConfig, mediaQueries: blockMediaQueries };
this.parseRuleRec(rule2);
- this.declarationConfig = this.getDeclarationConfig();
});
+ this.declarationConfig = outerConfig;
return;
}
if (rule.type === "layer-block") {
@@ -1753,10 +1780,11 @@ const transform = async (config$1, projectRoot, filePath, data, options) => {
await bundlerConfig.generateArtifacts(cssArtifactPath);
const virtualCode = await compileCSS(bundlerConfig);
const isWeb = bundlerConfig.platform === config.Platform.Web;
+ const nativeStylesFingerprint = isWeb ? void 0 : node_crypto.createHash("sha256").update(virtualCode).update("\0").update(bundlerConfig.stringifiedThemes).digest("hex");
data = Buffer.from(
isWeb ? virtualCode : [
`const { Uniwind } = require('uniwind');`,
- `Uniwind.__reinit(rt => ${virtualCode}, ${bundlerConfig.stringifiedThemes});`
+ `Uniwind.__reinit(rt => ${virtualCode}, ${bundlerConfig.stringifiedThemes}, '${nativeStylesFingerprint}');`
].join(""),
"utf-8"
);
diff --git a/dist/metro/transformer.mjs b/dist/metro/transformer.mjs
index d3e7475..f92b73b 100644
--- a/dist/metro/transformer.mjs
+++ b/dist/metro/transformer.mjs
@@ -5,6 +5,7 @@ import { converter, parse, formatHex, formatHex8 } from 'culori';
import { compile } from '@tailwindcss/node';
import { Scanner } from '@tailwindcss/oxide';
import fs from 'fs';
+import { createHash } from 'node:crypto';
const toCamelCase = (str) => str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
const pipe = (data) => ((...fns) => fns.reduce((acc, fn) => fn(acc), data));
@@ -1530,6 +1531,64 @@ class ProcessorBuilder {
style.importantProperties.push(declaration.property);
}
}
+ readSelectorVariants(tokens) {
+ const variants = {
+ rtl: null,
+ theme: null,
+ active: null,
+ focus: null,
+ disabled: null,
+ dataAttributes: null
+ };
+ tokens.forEach((token) => {
+ if (token.type === "pseudo-class" && token.kind === "where") {
+ token.selectors.forEach((compound) => {
+ compound.forEach((inner) => {
+ if (inner.type === "class" && this.bundlerConfig.themes.includes(inner.name)) {
+ variants.theme = inner.name;
+ }
+ if (inner.type === "pseudo-class" && inner.kind === "dir") {
+ variants.rtl = inner.direction === "rtl";
+ }
+ });
+ });
+ }
+ if (token.type === "pseudo-class" && token.kind === "active") {
+ variants.active = true;
+ }
+ if (token.type === "pseudo-class" && token.kind === "focus") {
+ variants.focus = true;
+ }
+ if (token.type === "pseudo-class" && token.kind === "disabled") {
+ variants.disabled = true;
+ }
+ if (token.type === "attribute" && token.operation === null && token.name.startsWith("data-")) {
+ variants.dataAttributes ??= {};
+ variants.dataAttributes[token.name] = `"true"`;
+ }
+ if (token.type === "attribute" && token.operation?.operator === "equal" && token.name.startsWith("data-")) {
+ variants.dataAttributes ??= {};
+ variants.dataAttributes[token.name] = `"${token.operation.value}"`;
+ }
+ });
+ const hasVariants = Object.values(variants).some(isDefined);
+ return { variants, hasVariants };
+ }
+ withSelectorVariants(variants, run) {
+ this.declarationConfig.rtl ??= variants.rtl;
+ this.declarationConfig.theme ??= variants.theme;
+ this.declarationConfig.active ??= variants.active;
+ this.declarationConfig.focus ??= variants.focus;
+ this.declarationConfig.disabled ??= variants.disabled;
+ this.declarationConfig.dataAttributes ??= variants.dataAttributes;
+ run();
+ this.declarationConfig.rtl = null;
+ this.declarationConfig.theme = null;
+ this.declarationConfig.active = null;
+ this.declarationConfig.focus = null;
+ this.declarationConfig.disabled = null;
+ this.declarationConfig.dataAttributes = null;
+ }
parseRuleRec(rule) {
if (this.declarationConfig.className !== null) {
const lastStyle = this.stylesheets[this.declarationConfig.className]?.at(-1);
@@ -1545,64 +1604,30 @@ class ProcessorBuilder {
this.declarationConfig.className = newClassName;
this.stylesheets[newClassName] ??= [];
this.stylesheets[newClassName].push({});
- rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
- rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
- rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
- return;
- }
- let rtl = null;
- let theme = null;
- let active = null;
- let focus = null;
- let disabled = null;
- let dataAttributes = null;
- selector.forEach((selector2) => {
- if (selector2.type === "pseudo-class" && selector2.kind === "where") {
- selector2.selectors.forEach((selector3) => {
- selector3.forEach((selector4) => {
- if (selector4.type === "class" && this.bundlerConfig.themes.includes(selector4.name)) {
- theme = selector4.name;
- }
- if (selector4.type === "pseudo-class" && selector4.kind === "dir") {
- rtl = selector4.direction === "rtl";
- }
- });
- });
- }
- if (selector2.type === "pseudo-class" && selector2.kind === "active") {
- active = true;
- }
- if (selector2.type === "pseudo-class" && selector2.kind === "focus") {
- focus = true;
+ const trailing = selector.slice(1);
+ const { variants, hasVariants } = this.readSelectorVariants(trailing);
+ if (trailing.length > 0 && !hasVariants) {
+ return;
}
- if (selector2.type === "pseudo-class" && selector2.kind === "disabled") {
- disabled = true;
- }
- if (selector2.type === "attribute" && selector2.operation === null && selector2.name.startsWith("data-")) {
- dataAttributes ??= {};
- dataAttributes[selector2.name] = `"true"`;
- }
- if (selector2.type === "attribute" && selector2.operation?.operator === "equal" && selector2.name.startsWith("data-")) {
- dataAttributes ??= {};
- dataAttributes[selector2.name] = `"${selector2.operation.value}"`;
+ const parseClassRule = () => {
+ rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
+ rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
+ rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
+ };
+ if (hasVariants) {
+ this.withSelectorVariants(variants, parseClassRule);
+ } else {
+ parseClassRule();
}
- });
- if ([rtl, theme, active, focus, disabled, dataAttributes].some(isDefined)) {
- this.declarationConfig.rtl ??= rtl;
- this.declarationConfig.theme ??= theme;
- this.declarationConfig.active ??= active;
- this.declarationConfig.focus ??= focus;
- this.declarationConfig.disabled ??= disabled;
- this.declarationConfig.dataAttributes ??= dataAttributes;
- rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
- rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
- rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
- this.declarationConfig.rtl = null;
- this.declarationConfig.theme = null;
- this.declarationConfig.active = null;
- this.declarationConfig.focus = null;
- this.declarationConfig.disabled = null;
- this.declarationConfig.dataAttributes = null;
+ return;
+ }
+ const { variants, hasVariants } = this.readSelectorVariants(selector);
+ if (hasVariants) {
+ this.withSelectorVariants(variants, () => {
+ rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
+ rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
+ rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
+ });
return;
}
selector.forEach((selectorToken) => {
@@ -1622,11 +1647,13 @@ class ProcessorBuilder {
}
if (rule.type === "media") {
const { mediaQueries } = rule.value.query;
- this.declarationConfig.mediaQueries.push(...mediaQueries);
+ const outerConfig = this.declarationConfig;
+ const blockMediaQueries = [...outerConfig.mediaQueries, ...mediaQueries];
rule.value.rules.forEach((rule2) => {
+ this.declarationConfig = { ...outerConfig, mediaQueries: blockMediaQueries };
this.parseRuleRec(rule2);
- this.declarationConfig = this.getDeclarationConfig();
});
+ this.declarationConfig = outerConfig;
return;
}
if (rule.type === "layer-block") {
@@ -1746,10 +1773,11 @@ const transform = async (config, projectRoot, filePath, data, options) => {
await bundlerConfig.generateArtifacts(cssArtifactPath);
const virtualCode = await compileCSS(bundlerConfig);
const isWeb = bundlerConfig.platform === Platform.Web;
+ const nativeStylesFingerprint = isWeb ? void 0 : createHash("sha256").update(virtualCode).update("\0").update(bundlerConfig.stringifiedThemes).digest("hex");
data = Buffer.from(
isWeb ? virtualCode : [
`const { Uniwind } = require('uniwind');`,
- `Uniwind.__reinit(rt => ${virtualCode}, ${bundlerConfig.stringifiedThemes});`
+ `Uniwind.__reinit(rt => ${virtualCode}, ${bundlerConfig.stringifiedThemes}, '${nativeStylesFingerprint}');`
].join(""),
"utf-8"
);
diff --git a/dist/module/bundler/css-processor/processor.js b/dist/module/bundler/css-processor/processor.js
index 58c96b6..6849ca7 100644
--- a/dist/module/bundler/css-processor/processor.js
+++ b/dist/module/bundler/css-processor/processor.js
@@ -99,6 +99,64 @@ export class ProcessorBuilder {
style.importantProperties.push(declaration.property);
}
}
+ readSelectorVariants(tokens) {
+ const variants = {
+ rtl: null,
+ theme: null,
+ active: null,
+ focus: null,
+ disabled: null,
+ dataAttributes: null
+ };
+ tokens.forEach((token) => {
+ if (token.type === "pseudo-class" && token.kind === "where") {
+ token.selectors.forEach((compound) => {
+ compound.forEach((inner) => {
+ if (inner.type === "class" && this.bundlerConfig.themes.includes(inner.name)) {
+ variants.theme = inner.name;
+ }
+ if (inner.type === "pseudo-class" && inner.kind === "dir") {
+ variants.rtl = inner.direction === "rtl";
+ }
+ });
+ });
+ }
+ if (token.type === "pseudo-class" && token.kind === "active") {
+ variants.active = true;
+ }
+ if (token.type === "pseudo-class" && token.kind === "focus") {
+ variants.focus = true;
+ }
+ if (token.type === "pseudo-class" && token.kind === "disabled") {
+ variants.disabled = true;
+ }
+ if (token.type === "attribute" && token.operation === null && token.name.startsWith("data-")) {
+ variants.dataAttributes ??= {};
+ variants.dataAttributes[token.name] = `"true"`;
+ }
+ if (token.type === "attribute" && token.operation?.operator === "equal" && token.name.startsWith("data-")) {
+ variants.dataAttributes ??= {};
+ variants.dataAttributes[token.name] = `"${token.operation.value}"`;
+ }
+ });
+ const hasVariants = Object.values(variants).some(isDefined);
+ return { variants, hasVariants };
+ }
+ withSelectorVariants(variants, run) {
+ this.declarationConfig.rtl ??= variants.rtl;
+ this.declarationConfig.theme ??= variants.theme;
+ this.declarationConfig.active ??= variants.active;
+ this.declarationConfig.focus ??= variants.focus;
+ this.declarationConfig.disabled ??= variants.disabled;
+ this.declarationConfig.dataAttributes ??= variants.dataAttributes;
+ run();
+ this.declarationConfig.rtl = null;
+ this.declarationConfig.theme = null;
+ this.declarationConfig.active = null;
+ this.declarationConfig.focus = null;
+ this.declarationConfig.disabled = null;
+ this.declarationConfig.dataAttributes = null;
+ }
parseRuleRec(rule) {
if (this.declarationConfig.className !== null) {
const lastStyle = this.stylesheets[this.declarationConfig.className]?.at(-1);
@@ -114,64 +172,30 @@ export class ProcessorBuilder {
this.declarationConfig.className = newClassName;
this.stylesheets[newClassName] ??= [];
this.stylesheets[newClassName].push({});
- rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
- rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
- rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
- return;
- }
- let rtl = null;
- let theme = null;
- let active = null;
- let focus = null;
- let disabled = null;
- let dataAttributes = null;
- selector.forEach((selector2) => {
- if (selector2.type === "pseudo-class" && selector2.kind === "where") {
- selector2.selectors.forEach((selector3) => {
- selector3.forEach((selector4) => {
- if (selector4.type === "class" && this.bundlerConfig.themes.includes(selector4.name)) {
- theme = selector4.name;
- }
- if (selector4.type === "pseudo-class" && selector4.kind === "dir") {
- rtl = selector4.direction === "rtl";
- }
- });
- });
- }
- if (selector2.type === "pseudo-class" && selector2.kind === "active") {
- active = true;
+ const trailing = selector.slice(1);
+ const { variants, hasVariants } = this.readSelectorVariants(trailing);
+ if (trailing.length > 0 && !hasVariants) {
+ return;
}
- if (selector2.type === "pseudo-class" && selector2.kind === "focus") {
- focus = true;
- }
- if (selector2.type === "pseudo-class" && selector2.kind === "disabled") {
- disabled = true;
- }
- if (selector2.type === "attribute" && selector2.operation === null && selector2.name.startsWith("data-")) {
- dataAttributes ??= {};
- dataAttributes[selector2.name] = `"true"`;
- }
- if (selector2.type === "attribute" && selector2.operation?.operator === "equal" && selector2.name.startsWith("data-")) {
- dataAttributes ??= {};
- dataAttributes[selector2.name] = `"${selector2.operation.value}"`;
+ const parseClassRule = () => {
+ rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
+ rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
+ rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
+ };
+ if (hasVariants) {
+ this.withSelectorVariants(variants, parseClassRule);
+ } else {
+ parseClassRule();
}
- });
- if ([rtl, theme, active, focus, disabled, dataAttributes].some(isDefined)) {
- this.declarationConfig.rtl ??= rtl;
- this.declarationConfig.theme ??= theme;
- this.declarationConfig.active ??= active;
- this.declarationConfig.focus ??= focus;
- this.declarationConfig.disabled ??= disabled;
- this.declarationConfig.dataAttributes ??= dataAttributes;
- rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
- rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
- rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
- this.declarationConfig.rtl = null;
- this.declarationConfig.theme = null;
- this.declarationConfig.active = null;
- this.declarationConfig.focus = null;
- this.declarationConfig.disabled = null;
- this.declarationConfig.dataAttributes = null;
+ return;
+ }
+ const { variants, hasVariants } = this.readSelectorVariants(selector);
+ if (hasVariants) {
+ this.withSelectorVariants(variants, () => {
+ rule.value.declarations?.declarations?.forEach((declaration) => this.addDeclaration(declaration));
+ rule.value.declarations?.importantDeclarations?.forEach((declaration) => this.addDeclaration(declaration, true));
+ rule.value.rules?.forEach((rule2) => this.parseRuleRec(rule2));
+ });
return;
}
selector.forEach((selectorToken) => {
@@ -191,11 +215,13 @@ export class ProcessorBuilder {
}
if (rule.type === "media") {
const { mediaQueries } = rule.value.query;
- this.declarationConfig.mediaQueries.push(...mediaQueries);
+ const outerConfig = this.declarationConfig;
+ const blockMediaQueries = [...outerConfig.mediaQueries, ...mediaQueries];
rule.value.rules.forEach((rule2) => {
+ this.declarationConfig = { ...outerConfig, mediaQueries: blockMediaQueries };
this.parseRuleRec(rule2);
- this.declarationConfig = this.getDeclarationConfig();
});
+ this.declarationConfig = outerConfig;
return;
}
if (rule.type === "layer-block") {
diff --git a/src/bundler/adapters/metro/transformer.ts b/src/bundler/adapters/metro/transformer.ts
index 08059b0..05c0515 100644
--- a/src/bundler/adapters/metro/transformer.ts
+++ b/src/bundler/adapters/metro/transformer.ts
@@ -5,6 +5,7 @@ import { Platform } from '@/common/consts'
import type * as ExpoMetroConfig from '@expo/metro-config'
import type * as MetroTransformWorker from 'metro-transform-worker'
import type { JsTransformerConfig, JsTransformOptions } from 'metro-transform-worker'
+import { createHash } from 'node:crypto'
import path from 'path'
const cssArtifactPath = path.resolve(__dirname, '../../uniwind.css')
@@ -69,13 +70,20 @@ export const transform = async (
await bundlerConfig.generateArtifacts(cssArtifactPath)
const virtualCode = await compileCSS(bundlerConfig)
const isWeb = bundlerConfig.platform === Platform.Web
+ const nativeStylesFingerprint = isWeb
+ ? undefined
+ : createHash('sha256')
+ .update(virtualCode)
+ .update('\0')
+ .update(bundlerConfig.stringifiedThemes)
+ .digest('hex')
data = Buffer.from(
isWeb
? virtualCode
: [
`const { Uniwind } = require('uniwind');`,
- `Uniwind.__reinit(rt => ${virtualCode}, ${bundlerConfig.stringifiedThemes});`,
+ `Uniwind.__reinit(rt => ${virtualCode}, ${bundlerConfig.stringifiedThemes}, '${nativeStylesFingerprint}');`,
].join(''),
'utf-8',
)
diff --git a/src/bundler/css-processor/processor.ts b/src/bundler/css-processor/processor.ts
index c8b3bb8..4af4199 100644
--- a/src/bundler/css-processor/processor.ts
+++ b/src/bundler/css-processor/processor.ts
@@ -123,6 +123,77 @@ export class ProcessorBuilder {
}
}
+ private readSelectorVariants(tokens: ReadonlyArray<any>) {
+ const variants = {
+ rtl: null as boolean | null,
+ theme: null as string | null,
+ active: null as boolean | null,
+ focus: null as boolean | null,
+ disabled: null as boolean | null,
+ dataAttributes: null as Record<string, string> | null,
+ }
+
+ tokens.forEach(token => {
+ if (token.type === 'pseudo-class' && token.kind === 'where') {
+ token.selectors.forEach((compound: Array<any>) => {
+ compound.forEach(inner => {
+ if (inner.type === 'class' && this.bundlerConfig.themes.includes(inner.name)) {
+ variants.theme = inner.name
+ }
+
+ if (inner.type === 'pseudo-class' && inner.kind === 'dir') {
+ variants.rtl = inner.direction === 'rtl'
+ }
+ })
+ })
+ }
+
+ if (token.type === 'pseudo-class' && token.kind === 'active') {
+ variants.active = true
+ }
+
+ if (token.type === 'pseudo-class' && token.kind === 'focus') {
+ variants.focus = true
+ }
+
+ if (token.type === 'pseudo-class' && token.kind === 'disabled') {
+ variants.disabled = true
+ }
+
+ // data-x
+ if (token.type === 'attribute' && token.operation === null && token.name.startsWith('data-')) {
+ variants.dataAttributes ??= {}
+ variants.dataAttributes[token.name] = `"true"`
+ }
+
+ // data-x=
+ if (token.type === 'attribute' && token.operation?.operator === 'equal' && token.name.startsWith('data-')) {
+ variants.dataAttributes ??= {}
+ variants.dataAttributes[token.name] = `"${token.operation.value}"`
+ }
+ })
+
+ return { variants, hasVariants: Object.values(variants).some(isDefined) }
+ }
+
+ private withSelectorVariants(variants: ReturnType<ProcessorBuilder['readSelectorVariants']>['variants'], run: () => void) {
+ this.declarationConfig.rtl ??= variants.rtl
+ this.declarationConfig.theme ??= variants.theme
+ this.declarationConfig.active ??= variants.active
+ this.declarationConfig.focus ??= variants.focus
+ this.declarationConfig.disabled ??= variants.disabled
+ this.declarationConfig.dataAttributes ??= variants.dataAttributes
+
+ run()
+
+ this.declarationConfig.rtl = null
+ this.declarationConfig.theme = null
+ this.declarationConfig.active = null
+ this.declarationConfig.focus = null
+ this.declarationConfig.disabled = null
+ this.declarationConfig.dataAttributes = null
+ }
+
private parseRuleRec(rule: Rule<Declaration, MediaQuery>) {
if (this.declarationConfig.className !== null) {
const lastStyle = this.stylesheets[this.declarationConfig.className]?.at(-1)
@@ -142,78 +213,41 @@ export class ProcessorBuilder {
this.stylesheets[newClassName] ??= []
this.stylesheets[newClassName].push({})
- rule.value.declarations?.declarations?.forEach(declaration => this.addDeclaration(declaration))
- rule.value.declarations?.importantDeclarations?.forEach(declaration => this.addDeclaration(declaration, true))
- rule.value.rules?.forEach(rule => this.parseRuleRec(rule))
+ // Tailwind >= 4.3.3 emits `.active\:x:active {}` instead of
+ // nesting `&:active` under the class, so the variant tokens
+ // sit right after the class token. A compound the runtime
+ // cannot express (e.g. `[aria-disabled="true"]`) used to be
+ // an empty nested rule and must not become unconditional.
+ const trailing = selector.slice(1)
+ const { variants, hasVariants } = this.readSelectorVariants(trailing)
- return
- }
-
- let rtl = null as boolean | null
- let theme = null as string | null
- let active = null as boolean | null
- let focus = null as boolean | null
- let disabled = null as boolean | null
- let dataAttributes = null as Record<string, string> | null
-
- selector.forEach(selector => {
- if (selector.type === 'pseudo-class' && selector.kind === 'where') {
- selector.selectors.forEach(selector => {
- selector.forEach(selector => {
- if (selector.type === 'class' && this.bundlerConfig.themes.includes(selector.name)) {
- theme = selector.name
- }
-
- if (selector.type === 'pseudo-class' && selector.kind === 'dir') {
- rtl = selector.direction === 'rtl'
- }
- })
- })
+ if (trailing.length > 0 && !hasVariants) {
+ return
}
- if (selector.type === 'pseudo-class' && selector.kind === 'active') {
- active = true
- }
-
- if (selector.type === 'pseudo-class' && selector.kind === 'focus') {
- focus = true
+ const parseClassRule = () => {
+ rule.value.declarations?.declarations?.forEach(declaration => this.addDeclaration(declaration))
+ rule.value.declarations?.importantDeclarations?.forEach(declaration => this.addDeclaration(declaration, true))
+ rule.value.rules?.forEach(rule => this.parseRuleRec(rule))
}
- if (selector.type === 'pseudo-class' && selector.kind === 'disabled') {
- disabled = true
+ if (hasVariants) {
+ this.withSelectorVariants(variants, parseClassRule)
+ } else {
+ parseClassRule()
}
- // data-x
- if (selector.type === 'attribute' && selector.operation === null && selector.name.startsWith('data-')) {
- dataAttributes ??= {}
- dataAttributes[selector.name] = `"true"`
- }
+ return
+ }
- // data-x=
- if (selector.type === 'attribute' && selector.operation?.operator === 'equal' && selector.name.startsWith('data-')) {
- dataAttributes ??= {}
- dataAttributes[selector.name] = `"${selector.operation.value}"`
- }
- })
+ const { variants, hasVariants } = this.readSelectorVariants(selector)
- if ([rtl, theme, active, focus, disabled, dataAttributes].some(isDefined)) {
- this.declarationConfig.rtl ??= rtl
- this.declarationConfig.theme ??= theme
- this.declarationConfig.active ??= active
- this.declarationConfig.focus ??= focus
- this.declarationConfig.disabled ??= disabled
- this.declarationConfig.dataAttributes ??= dataAttributes
-
- rule.value.declarations?.declarations?.forEach(declaration => this.addDeclaration(declaration))
- rule.value.declarations?.importantDeclarations?.forEach(declaration => this.addDeclaration(declaration, true))
- rule.value.rules?.forEach(rule => this.parseRuleRec(rule))
-
- this.declarationConfig.rtl = null
- this.declarationConfig.theme = null
- this.declarationConfig.active = null
- this.declarationConfig.focus = null
- this.declarationConfig.disabled = null
- this.declarationConfig.dataAttributes = null
+ if (hasVariants) {
+ this.withSelectorVariants(variants, () => {
+ rule.value.declarations?.declarations?.forEach(declaration => this.addDeclaration(declaration))
+ rule.value.declarations?.importantDeclarations?.forEach(declaration => this.addDeclaration(declaration, true))
+ rule.value.rules?.forEach(rule => this.parseRuleRec(rule))
+ })
return
}
@@ -241,13 +275,21 @@ export class ProcessorBuilder {
if (rule.type === 'media') {
const { mediaQueries } = rule.value.query
+ // Media queries have to outlive the reset between the block's sibling
+ // rules: resetting only after each parse left every rule past the first
+ // without the block's queries, shipping ios:/android: variants and later
+ // utilities of a width breakpoint unguarded to every platform. Reset to
+ // the outer config instead of a fresh one so media nested inside a class
+ // rule keeps writing into that class.
+ const outerConfig = this.declarationConfig
+ const blockMediaQueries = [...outerConfig.mediaQueries, ...mediaQueries]
- this.declarationConfig.mediaQueries.push(...mediaQueries)
rule.value.rules.forEach(rule => {
+ this.declarationConfig = { ...outerConfig, mediaQueries: blockMediaQueries }
this.parseRuleRec(rule)
- this.declarationConfig = this.getDeclarationConfig()
})
+ this.declarationConfig = outerConfig
return
}
diff --git a/src/core/config/config.native.ts b/src/core/config/config.native.ts
index 5e0832b..01aed1b 100644
--- a/src/core/config/config.native.ts
+++ b/src/core/config/config.native.ts
@@ -8,6 +8,8 @@ import type { CSSVariables, GenerateStyleSheetsCallback, ThemeName } from '../ty
import { UniwindConfigBuilder as UniwindConfigBuilderBase } from './config.common'
class UniwindConfigBuilder extends UniwindConfigBuilderBase {
+ private stylesFingerprint: string | undefined
+
constructor() {
super()
}
@@ -35,9 +37,18 @@ class UniwindConfigBuilder extends UniwindConfigBuilderBase {
UniwindListener.notify([StyleDependency.Insets])
}
- protected __reinit(generateStyleSheetCallback: GenerateStyleSheetsCallback, themes: Array<string>) {
+ protected __reinit(
+ generateStyleSheetCallback: GenerateStyleSheetsCallback,
+ themes: Array<string>,
+ stylesFingerprint?: string,
+ ) {
+ if (__DEV__ && stylesFingerprint !== undefined && stylesFingerprint === this.stylesFingerprint) {
+ return
+ }
+
super.__reinit(generateStyleSheetCallback, themes)
UniwindStore.reinit(generateStyleSheetCallback, themes)
+ this.stylesFingerprint = stylesFingerprint
}
protected onThemeChange() {