diff --git a/src/__tests__/vendor/tailwind.test.tsx b/src/__tests__/vendor/tailwind.test.tsx
index d02df0f..275fb84 100644
--- a/src/__tests__/vendor/tailwind.test.tsx
+++ b/src/__tests__/vendor/tailwind.test.tsx
@@ -23,7 +23,7 @@ test("transition", () => {
}
`);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
s: [
[
"transition",
@@ -84,10 +84,10 @@ test("transition", () => {
],
],
vr: [
- ["default-transition-duration", [150]],
+ ["default-transition-duration", [[150]]],
[
"default-transition-timing-function",
- [[{}, "cubic-bezier", [0.4, 0, 0.2, 1]]],
+ [[[{}, "cubic-bezier", [0.4, 0, 0.2, 1]]]],
],
],
});
@@ -107,7 +107,7 @@ test("box-shadow", () => {
}
`);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
s: [
[
"shadow-xl",
@@ -215,7 +215,7 @@ test("filter", () => {
}
`);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
s: [
[
"brightness-50",
@@ -286,7 +286,7 @@ test("filter", () => {
],
],
],
- vr: [["drop-shadow-md", [[0, 3, 3, "#0000001f"]]]],
+ vr: [["drop-shadow-md", [[[0, 3, 3, "#0000001f"]]]]],
});
render();
diff --git a/src/compiler/__tests__/@prop.test.tsx b/src/compiler/__tests__/@prop.test.tsx
index 1b124f6..69d0fee 100644
--- a/src/compiler/__tests__/@prop.test.tsx
+++ b/src/compiler/__tests__/@prop.test.tsx
@@ -9,7 +9,7 @@ test("@prop single", () => {
}
`);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
s: [
[
"test",
@@ -39,7 +39,7 @@ test("@prop single, nested value", () => {
}
`);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
s: [
[
"test",
@@ -69,7 +69,7 @@ test("@prop single, top level", () => {
}
`);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
s: [
[
"test",
@@ -99,7 +99,7 @@ test("@prop single, top level, nested", () => {
}
`);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
s: [
[
"test",
@@ -129,7 +129,7 @@ test("@prop single, top level, nested", () => {
}
`);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
s: [
[
"test",
@@ -162,7 +162,7 @@ test("@prop multiple", () => {
}
`);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
s: [
[
"test",
diff --git a/src/compiler/__tests__/compiler.test.tsx b/src/compiler/__tests__/compiler.test.tsx
index ac9434d..eede862 100644
--- a/src/compiler/__tests__/compiler.test.tsx
+++ b/src/compiler/__tests__/compiler.test.tsx
@@ -6,7 +6,7 @@ test("hello world", () => {
color: red;
}`);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
s: [
[
"my-class",
@@ -34,8 +34,8 @@ test("reads global CSS variables", () => {
}
}`);
- expect(compiled).toStrictEqual({
- vr: [["color-red-500", ["#fb2c36"]]],
+ expect(compiled.stylesheet()).toStrictEqual({
+ vr: [["color-red-500", [["#fb2c36"]]]],
});
});
@@ -49,7 +49,7 @@ test.skip("removes unused CSS variables", () => {
}
`);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
s: [
[
"test",
@@ -84,7 +84,7 @@ test.skip("preserves unused CSS variables with preserve-variables", () => {
}
`);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
s: [
[
"test",
@@ -117,7 +117,7 @@ test("multiple rules with same selector", () => {
}
`);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
s: [
[
"redOrGreen",
@@ -157,7 +157,7 @@ test.skip("transitions", () => {
}
`);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
s: [
[
"test",
@@ -195,7 +195,7 @@ test.skip("animations", () => {
}
`);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
k: [
[
"spin",
@@ -242,8 +242,8 @@ test("breaks apart comma separated variables", () => {
}
`);
- expect(compiled).toStrictEqual({
- vr: [["test", [["blue", "green"]]]],
+ expect(compiled.stylesheet()).toStrictEqual({
+ vr: [["test", [[["blue", "green"]]]]],
});
});
@@ -253,7 +253,7 @@ test("light-dark()", () => {
background-color: light-dark(#333b3c, #efefec);
}`);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
s: [
[
"my-class",
@@ -295,20 +295,17 @@ test("media query nested in rules", () => {
@media (min-width: 100px) {
background-color: yellow;
+
}
}`);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
s: [
[
"my-class",
[
{
- d: [
- {
- color: "#f00",
- },
- ],
+ d: [{ color: "#f00" }],
s: [1, 1],
v: [["__rn-css-color", "#f00"]],
},
@@ -323,51 +320,43 @@ test("media query nested in rules", () => {
v: [["__rn-css-color", "#00f"]],
},
{
- d: [
- {
- backgroundColor: "#008000",
- },
+ d: [{ backgroundColor: "#008000" }],
+ m: [
+ [">=", "width", 600],
+ [">=", "width", 400],
],
- m: [[">=", "width", 400]],
s: [3, 1],
},
{
- d: [
- {
- backgroundColor: "#ff0",
- },
- ],
+ d: [{ backgroundColor: "#ff0" }],
m: [[">=", "width", 100]],
s: [4, 1],
},
+ ],
+ ],
+ ],
+ });
+});
+
+test("container queries", () => {
+ const compiled = compile(`
+ @container (width > 400px) {
+ .child {
+ color: blue;
+ }
+ }`);
+
+ expect(compiled.stylesheet()).toStrictEqual({
+ s: [
+ [
+ "child",
+ [
{
- d: [
- {
- color: "#00f",
- },
- ],
- m: [[">=", "width", 600]],
- s: [2, 1, 1],
+ cq: [{ m: [">", "width", 400] }],
+ d: [{ color: "#00f" }],
+ s: [2, 1],
v: [["__rn-css-color", "#00f"]],
},
- {
- d: [
- {
- backgroundColor: "#008000",
- },
- ],
- m: [[">=", "width", 400]],
- s: [3, 1, 1],
- },
- {
- d: [
- {
- backgroundColor: "#ff0",
- },
- ],
- m: [[">=", "width", 100]],
- s: [4, 1, 1],
- },
],
],
],
diff --git a/src/compiler/__tests__/media-query.test.ts b/src/compiler/__tests__/media-query.test.ts
index 3fb5c2f..a9a3ff1 100644
--- a/src/compiler/__tests__/media-query.test.ts
+++ b/src/compiler/__tests__/media-query.test.ts
@@ -8,7 +8,7 @@ describe.skip("platform media queries", () => {
}
`);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
s: [
[
"my-class",
@@ -39,7 +39,7 @@ describe.skip("platform media queries", () => {
}
`);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
s: [
[
"my-class",
diff --git a/src/compiler/__tests__/unstable_animations.test.ts b/src/compiler/__tests__/unstable_animations.test.ts
index 0036feb..7b7e0a9 100644
--- a/src/compiler/__tests__/unstable_animations.test.ts
+++ b/src/compiler/__tests__/unstable_animations.test.ts
@@ -19,7 +19,7 @@ test.skip("test compiler", () => {
`,
);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
k: [
[
"slide-in",
diff --git a/src/compiler/attributes.test.ts b/src/compiler/attributes.test.ts
index 211ea5b..c444b14 100644
--- a/src/compiler/attributes.test.ts
+++ b/src/compiler/attributes.test.ts
@@ -6,7 +6,7 @@ test("multiple classes", () => {
color: red;
}`);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
s: [
[
"test",
diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts
index 19bcb7d..2fa2ffe 100644
--- a/src/compiler/compiler.ts
+++ b/src/compiler/compiler.ts
@@ -1,4 +1,6 @@
/* eslint-disable */
+import { inspect } from "node:util";
+
import { debug } from "debug";
import {
transform as lightningcss,
@@ -12,11 +14,7 @@ import {
} from "lightningcss";
import { maybeMutateReactNativeOptions, parsePropAtRule } from "./atRules";
-import type {
- CompilerOptions,
- ContainerQuery,
- ReactNativeCssStyleSheet,
-} from "./compiler.types";
+import type { CompilerOptions, ContainerQuery } from "./compiler.types";
import { parseContainerCondition } from "./container-query";
import { parseDeclaration } from "./declarations";
import { extractKeyFrames } from "./keyframes";
@@ -33,11 +31,11 @@ const defaultLogger = debug("react-native-css:compiler");
* @param options - Compiler options
* @returns A `ReactNativeCssStyleSheet` that can be passed to `StyleSheet.register` or used with a custom runtime
*/
-export function compile(
- code: Buffer | string,
- options: CompilerOptions = {},
-): ReactNativeCssStyleSheet {
+export function compile(code: Buffer | string, options: CompilerOptions = {}) {
const { logger = defaultLogger } = options;
+ const isLoggerEnabled =
+ "enabled" in logger ? logger.enabled : Boolean(logger);
+
const features = Object.assign({}, options.features);
if (options.selectorPrefix && options.selectorPrefix.startsWith(".")) {
@@ -67,8 +65,12 @@ export function compile(
maybeMutateReactNativeOptions(rule, builder);
},
StyleSheetExit(sheet) {
- logger(`Found ${sheet.rules.length} rules to process`);
- logger(JSON.stringify(sheet.rules, null, 2));
+ if (isLoggerEnabled) {
+ logger(`Found ${sheet.rules.length} rules to process`);
+ logger(
+ inspect(sheet.rules, { depth: null, colors: true, compact: false }),
+ );
+ }
for (const rule of sheet.rules) {
// Extract the style declarations and animations from the current rule
@@ -108,7 +110,10 @@ export function compile(
visitor,
});
- return builder.getNativeStyleSheet();
+ return {
+ stylesheet: () => builder.getNativeStyleSheet(),
+ warnings: () => builder.getWarnings(),
+ };
}
/**
@@ -137,16 +142,16 @@ function extractRule(rule: Rule, builder: StylesheetBuilder) {
const declarationBlock = value.declarations;
if (declarationBlock) {
- if (declarationBlock.declarations) {
- builder.newRuleFork();
+ if (declarationBlock.declarations?.length) {
+ builder.newNestedRule();
for (const declaration of declarationBlock.declarations) {
parseDeclaration(declaration, builder);
}
builder.applyRuleToSelectors();
}
- if (declarationBlock.importantDeclarations) {
- builder.newRuleFork({ important: true });
+ if (declarationBlock.importantDeclarations?.length) {
+ builder.newNestedRule({ important: true });
for (const declaration of declarationBlock.importantDeclarations) {
parseDeclaration(declaration, builder);
}
@@ -283,17 +288,17 @@ function extractContainer(
builder = builder.fork("container");
// Iterate over all rules inside the containerRule and extract their styles using the updated CompilerCollection
- for (const rule of containerRule.rules) {
- const query: ContainerQuery = {
- m: parseContainerCondition(containerRule.condition, builder),
- };
+ const query: ContainerQuery = {
+ m: parseContainerCondition(containerRule.condition, builder),
+ };
- if (containerRule.name) {
- query.n = containerRule.name;
- }
+ if (containerRule.name) {
+ query.n = containerRule.name;
+ }
- builder.addContainerQuery(query);
+ builder.addContainerQuery(query);
+ for (const rule of containerRule.rules) {
extractRule(rule, builder);
}
}
diff --git a/src/compiler/compiler.types.ts b/src/compiler/compiler.types.ts
index 0ba16c8..cfe927d 100644
--- a/src/compiler/compiler.types.ts
+++ b/src/compiler/compiler.types.ts
@@ -35,9 +35,9 @@ export interface ReactNativeCssStyleSheet_V2 {
/** KeyFrames */
k?: Animation_V2[];
/** Root Variables */
- vr?: [string, LightDarkVariable][];
+ vr?: RootVariables;
/** Universal Variables */
- vu?: [string, LightDarkVariable][];
+ vu?: RootVariables;
}
/******************************** Styles ********************************/
@@ -147,10 +147,12 @@ export type StyleFunction =
/****************************** Variables *******************************/
export type VariableDescriptor = [string, StyleDescriptor];
-export type VariableRecord = Record;
-export type LightDarkVariable =
+export type VariableRecord = Record;
+export type VariableValue =
| [StyleDescriptor]
- | [StyleDescriptor, StyleDescriptor];
+ | [StyleDescriptor, MediaCondition[]];
+
+export type RootVariables = [string, VariableValue[]][];
export type InlineVariable = {
[VAR_SYMBOL]: "inline";
diff --git a/src/compiler/declarations.ts b/src/compiler/declarations.ts
index 78210b7..5ac9deb 100644
--- a/src/compiler/declarations.ts
+++ b/src/compiler/declarations.ts
@@ -1272,7 +1272,7 @@ export function parseAngle(angle: Angle | number, builder: StylesheetBuilder) {
case "rad":
return `${angle.value}${angle.type}`;
default:
- builder.addWarning("value", angle.value);
+ builder.addWarning("value", "angle", angle.value);
return undefined;
}
}
@@ -1981,6 +1981,7 @@ export function parseLineHeight(
case "calc":
builder.addWarning(
"value",
+ "line-height",
typeof length.value === "number"
? length.value
: JSON.stringify(length.value),
diff --git a/src/compiler/inheritance.test.ts b/src/compiler/inheritance.test.ts
index 3b1b7d8..021787b 100644
--- a/src/compiler/inheritance.test.ts
+++ b/src/compiler/inheritance.test.ts
@@ -6,7 +6,7 @@ test("nested classes", () => {
color: red;
}`);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
s: [
[
"my-class",
@@ -38,7 +38,7 @@ test("multiple tiers classes", () => {
color: red;
}`);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
s: [
[
"one",
@@ -79,7 +79,7 @@ test("tiers with multiple classes", () => {
color: red;
}`);
- expect(compiled).toStrictEqual({
+ expect(compiled.stylesheet()).toStrictEqual({
s: [
[
"one",
diff --git a/src/compiler/stylesheet.ts b/src/compiler/stylesheet.ts
index 39558f8..0804c85 100644
--- a/src/compiler/stylesheet.ts
+++ b/src/compiler/stylesheet.ts
@@ -17,6 +17,7 @@ import type {
StyleRuleMapping,
StyleRuleSet,
VariableRecord,
+ VariableValue,
} from "./compiler.types";
import { toRNProperty, type NormalizeSelector } from "./selectors";
@@ -56,7 +57,17 @@ export class StylesheetBuilder {
animations?: AnimationRecord;
rem: number;
ruleOrder: number;
- } = { ruleSets: {}, rem: 14, ruleOrder: 0 },
+ warningProperties: string[];
+ warningValues: [string, unknown][];
+ warningFunctions: string[];
+ } = {
+ ruleSets: {},
+ rem: 14,
+ ruleOrder: 0,
+ warningProperties: [],
+ warningValues: [],
+ warningFunctions: [],
+ },
private selectors?: NormalizeSelector[],
) {}
@@ -76,7 +87,7 @@ export class StylesheetBuilder {
);
}
- cloneRule({ ...rule } = this.rule): StyleRule {
+ cloneRule({ ...rule } = this.ruleTemplate): StyleRule {
rule.s = [...rule.s];
rule.aq &&= [...rule.aq];
rule.c &&= [...rule.c];
@@ -128,11 +139,17 @@ export class StylesheetBuilder {
}
if (this.shared.rootVariables) {
- stylesheetOptions.vr = Object.entries(this.shared.rootVariables);
+ stylesheetOptions.vr = Object.entries(this.shared.rootVariables).map(
+ // Reverse these so the most specific variables are first
+ ([key, value]) => [key, value.reverse()] as const,
+ );
}
if (this.shared.universalVariables) {
- stylesheetOptions.vu = Object.entries(this.shared.universalVariables);
+ stylesheetOptions.vu = Object.entries(this.shared.universalVariables).map(
+ // Reverse these so the most specific variables are first
+ ([key, value]) => [key, value.reverse()] as const,
+ );
}
if (this.shared.animations) {
@@ -156,10 +173,29 @@ export class StylesheetBuilder {
}
addWarning(
- _type: "property" | "value" | "function",
- _property: string | number,
+ type: "property" | "value" | "function",
+ property: string,
+ value?: unknown,
): void {
- // TODO
+ switch (type) {
+ case "property":
+ this.shared.warningProperties.push(property);
+ break;
+ case "value":
+ this.shared.warningValues.push([property, value]);
+ break;
+ case "function":
+ this.shared.warningFunctions.push(property);
+ break;
+ }
+ }
+
+ getWarnings() {
+ return {
+ properties: this.shared.warningProperties,
+ values: this.shared.warningValues,
+ functions: this.shared.warningFunctions,
+ };
}
newRule(mapping = this.mapping, { important = false } = {}) {
@@ -171,14 +207,12 @@ export class StylesheetBuilder {
}
}
- newRuleFork({ important = false } = {}) {
- this.rule = this.cloneRule(this.rule);
- this.rule.s[Specificity.Order] = this.shared.ruleOrder;
- if (important) {
- this.rule.s[Specificity.Important] = 1;
- }
+ /** Used by nested declarations (for example @media inside a RuleSet) */
+ newNestedRule({ important = false } = {}) {
+ this.newRule(this.mapping, { important });
}
+ /** Hack for light-dark, which requires adding a new rule without changing the current rule */
addExtraRule(rule: Partial) {
let extraRuleArray = extraRules.get(this.rule);
if (!extraRuleArray) {
@@ -197,8 +231,8 @@ export class StylesheetBuilder {
}
addMediaQuery(condition: MediaCondition) {
- this.rule.m ??= [];
- this.rule.m.push(condition);
+ this.ruleTemplate.m ??= [];
+ this.ruleTemplate.m.push(condition);
}
addContainer(value: string[] | false) {
@@ -355,7 +389,8 @@ export class StylesheetBuilder {
}
for (const selector of selectorList) {
- const rule = this.cloneRule();
+ // We are going to be apply the current rule to n selectors, so we clone the rule
+ const rule = this.cloneRule(this.rule);
if (selector.type === "className") {
const {
@@ -431,16 +466,28 @@ export class StylesheetBuilder {
for (const [name, value] of this.rule.v) {
this.shared[type] ??= {};
- this.shared[type][name] ??= [undefined];
- this.shared[type][name][subtype === "light" ? 0 : 1] = value;
+ this.shared[type][name] ??= [];
+
+ const variableValue: VariableValue =
+ subtype === "light"
+ ? [value]
+ : [value, [["=", "prefers-color-scheme", "dark"]]];
+
+ // Append extra media queries if they exist
+ if (this.rule.m) {
+ variableValue[1] ??= [];
+ variableValue[1].push(...this.rule.m);
+ }
+
+ this.shared[type][name].push(variableValue);
}
}
}
}
addContainerQuery(query: ContainerQuery) {
- this.rule.cq ??= [];
- this.rule.cq.push(query);
+ this.ruleTemplate.cq ??= [];
+ this.ruleTemplate.cq.push(query);
}
newAnimationFrames(name: string) {
diff --git a/src/jest/index.ts b/src/jest/index.ts
index 9d34544..6e933a6 100644
--- a/src/jest/index.ts
+++ b/src/jest/index.ts
@@ -1,6 +1,6 @@
import { Appearance, Dimensions } from "react-native";
-import util from "node:util";
+import { inspect } from "node:util";
import { compile, type CompilerOptions } from "../compiler";
import { StyleCollection } from "../runtime/native/injection";
@@ -37,14 +37,29 @@ export function registerCSS(
...options
}: CompilerOptions & { debug?: boolean } = {},
) {
- const compiled = compile(css, options);
+ const logger = debug
+ ? (text: string) => {
+ // Just log the rules
+ if (text.startsWith("[")) {
+ console.log(`Rules:\n---\n${text}`);
+ }
+ }
+ : undefined;
+
+ const compiled = compile(css, { ...options, logger });
if (debug) {
console.log(
- `Compiled:\n---\n${util.inspect(compiled, { depth: null, colors: true, compact: false })}`,
+ `Compiled:\n---\n${inspect(
+ {
+ stylesheet: compiled.stylesheet(),
+ warnings: compiled.warnings(),
+ },
+ { depth: null, colors: true, compact: false },
+ )}`,
);
}
- StyleCollection.inject(compiled);
+ StyleCollection.inject(compiled.stylesheet());
return compiled;
}
diff --git a/src/runtime/native/__tests__/media-query.test.tsx b/src/runtime/native/__tests__/media-query.test.tsx
index 7ac82e5..bc52e47 100644
--- a/src/runtime/native/__tests__/media-query.test.tsx
+++ b/src/runtime/native/__tests__/media-query.test.tsx
@@ -7,6 +7,37 @@ import { registerCSS, testID } from "react-native-css/jest";
import { colorScheme } from "../api";
import { dimensions } from "../reactivity";
+jest.mock("react-native", () => {
+ const RN = jest.requireActual("react-native");
+ RN.Platform.OS = "ios";
+ return RN as unknown;
+});
+
+test(":root MediaQueries", () => {
+ registerCSS(`
+ :root {
+ @media ios {
+ --my-var: System;
+ }
+ @media android {
+ --my-var: SystemAndroid;
+ }
+ }
+
+ @layer utilities {
+ .my-class {
+ font-family: var(--my-var);
+ }
+ }`);
+
+ render();
+ const component = screen.getByTestId(testID);
+
+ expect(component.props.style).toStrictEqual({
+ fontFamily: "System",
+ });
+});
+
test("color scheme", () => {
registerCSS(`
.my-class { color: blue; }
diff --git a/src/runtime/native/conditions/container-query.ts b/src/runtime/native/conditions/container-query.ts
index 8459b27..a165627 100644
--- a/src/runtime/native/conditions/container-query.ts
+++ b/src/runtime/native/conditions/container-query.ts
@@ -14,7 +14,7 @@ import {
focusFamily,
hoverFamily,
type ContainerContextValue,
- type Effect,
+ type Getter,
} from "../reactivity";
import type { RenderGuard } from "./guards";
@@ -24,10 +24,10 @@ export function testContainerQueries(
queries: ContainerQuery[],
inheritedContainers: ContainerContextValue,
guards: RenderGuard[],
- effect: Effect,
+ get: Getter,
) {
return queries.every((query) => {
- return testContainerQuery(query, inheritedContainers, guards, effect);
+ return testContainerQuery(query, inheritedContainers, guards, get);
});
}
@@ -35,7 +35,7 @@ export function testContainerQuery(
query: ContainerQuery,
inheritedContainers: ContainerContextValue,
guards: RenderGuard[],
- effect?: Effect,
+ get: Getter,
): boolean {
const name = query.n ?? DEFAULT_CONTAINER_NAME;
const container = inheritedContainers[name]!;
@@ -46,11 +46,11 @@ export function testContainerQuery(
return false;
}
- if (query.m && !testContainerMediaCondition(query.m, container, effect)) {
+ if (query.m && !testContainerMediaCondition(query.m, container, get)) {
return false;
}
- if (query.p && !testContainerPseudoCondition(query.p, container, effect)) {
+ if (query.p && !testContainerPseudoCondition(query.p, container, get)) {
return false;
}
@@ -60,15 +60,15 @@ export function testContainerQuery(
function testContainerPseudoCondition(
query: PseudoClassesQuery,
containerKey: WeakKey,
- effect?: Effect,
+ get: Getter,
): boolean {
- if (query.h && !hoverFamily(containerKey).get(effect)) {
+ if (query.h && !get(hoverFamily(containerKey))) {
return false;
}
- if (query.a && !activeFamily(containerKey).get(effect)) {
+ if (query.a && !get(activeFamily(containerKey))) {
return false;
}
- if (query.f && !focusFamily(containerKey).get(effect)) {
+ if (query.f && !get(focusFamily(containerKey))) {
return false;
}
return true;
@@ -77,18 +77,18 @@ function testContainerPseudoCondition(
function testContainerMediaCondition(
condition: MediaCondition,
containerKey: WeakKey,
- effect?: Effect,
+ get: Getter,
): boolean {
switch (condition[0]) {
case "!":
- return !testContainerMediaCondition(condition[1], containerKey, effect);
+ return !testContainerMediaCondition(condition[1], containerKey, get);
case "&":
return condition[1].every((query) => {
- return testContainerMediaCondition(query, containerKey, effect);
+ return testContainerMediaCondition(query, containerKey, get);
});
case "|":
return condition[1].some((query) => {
- return testContainerMediaCondition(query, containerKey, effect);
+ return testContainerMediaCondition(query, containerKey, get);
});
case "!!":
return false;
@@ -99,7 +99,7 @@ function testContainerMediaCondition(
case "<":
case "<=":
case "=": {
- const left = getContainerFeatureValue(condition[1], containerKey, effect);
+ const left = getContainerFeatureValue(condition[1], containerKey, get);
const right = condition[2];
if (condition[0] === "=") {
@@ -133,21 +133,21 @@ function testContainerMediaCondition(
function getContainerFeatureValue(
name: MediaFeatureNameFor_ContainerSizeFeatureId,
containerKey: WeakKey,
- effect?: Effect,
+ get: Getter,
): StyleDescriptor {
switch (name) {
case "width":
- return containerWidthFamily(containerKey).get(effect);
+ return get(containerWidthFamily(containerKey));
case "height":
- return containerHeightFamily(containerKey).get(effect);
+ return get(containerHeightFamily(containerKey));
case "aspect-ratio": {
- const width = containerWidthFamily(containerKey).get(effect);
- const height = containerWidthFamily(containerKey).get(effect);
+ const width = get(containerWidthFamily(containerKey));
+ const height = get(containerHeightFamily(containerKey));
return width / height;
}
case "orientation":
- const width = containerWidthFamily(containerKey).get(effect);
- const height = containerWidthFamily(containerKey).get(effect);
+ const width = get(containerWidthFamily(containerKey));
+ const height = get(containerHeightFamily(containerKey));
return width > height ? "landscape" : "portrait";
case "inline-size":
case "block-size":
diff --git a/src/runtime/native/conditions/guards.ts b/src/runtime/native/conditions/guards.ts
index 558ec70..d3c9dd8 100644
--- a/src/runtime/native/conditions/guards.ts
+++ b/src/runtime/native/conditions/guards.ts
@@ -2,7 +2,6 @@
import type { ComponentState } from "../react/useNativeCss";
import type {
ContainerContextValue,
- Effect,
VariableContextValue,
} from "../reactivity";
@@ -10,7 +9,7 @@ export type RenderGuard =
| ["a", string, any]
| ["d", string, any]
| ["v", string, any]
- | ["c", string, Effect];
+ | ["c", string, WeakKey];
export function testGuards(
state: ComponentState,
diff --git a/src/runtime/native/conditions/index.ts b/src/runtime/native/conditions/index.ts
index f1870d2..4ee0829 100644
--- a/src/runtime/native/conditions/index.ts
+++ b/src/runtime/native/conditions/index.ts
@@ -10,7 +10,7 @@ import {
focusFamily,
hoverFamily,
type ContainerContextValue,
- type Effect,
+ type Getter,
} from "../reactivity";
import { testContainerQueries } from "./container-query";
import type { RenderGuard } from "./guards";
@@ -18,15 +18,15 @@ import { testMediaQuery } from "./media-query";
export function testRule(
rule: StyleRule,
- effect: Effect,
+ get: Getter,
props: Props,
guards: RenderGuard[],
containerContext: ContainerContextValue,
) {
- if (rule.p && !pseudoClasses(rule.p, effect)) {
+ if (rule.p && !pseudoClasses(rule.p, get)) {
return false;
}
- if (rule.m && !testMediaQuery(rule.m, effect)) {
+ if (rule.m && !testMediaQuery(rule.m, get)) {
return false;
}
if (rule.aq && !attributes(rule.aq, props, guards)) {
@@ -34,7 +34,7 @@ export function testRule(
}
if (
rule.cq &&
- !testContainerQueries(rule.cq, containerContext, guards, effect)
+ !testContainerQueries(rule.cq, containerContext, guards, get)
) {
return false;
}
@@ -42,14 +42,14 @@ export function testRule(
return true;
}
-function pseudoClasses(query: PseudoClassesQuery, effect: Effect) {
- if (query.h && !hoverFamily(effect).get(effect)) {
+function pseudoClasses(query: PseudoClassesQuery, get: Getter) {
+ if (query.h && !get(hoverFamily(get))) {
return false;
}
- if (query.a && !activeFamily(effect).get(effect)) {
+ if (query.a && !get(activeFamily(get))) {
return false;
}
- if (query.f && !focusFamily(effect).get(effect)) {
+ if (query.f && !get(focusFamily(get))) {
return false;
}
return true;
diff --git a/src/runtime/native/conditions/media-query.ts b/src/runtime/native/conditions/media-query.ts
index 3011d36..7d4d0c7 100644
--- a/src/runtime/native/conditions/media-query.ts
+++ b/src/runtime/native/conditions/media-query.ts
@@ -2,38 +2,38 @@
import { PixelRatio, Platform } from "react-native";
import type { MediaCondition } from "../../../compiler";
-import { colorScheme, vh, vw, type Effect } from "../reactivity";
+import { colorScheme, vh, vw, type Getter } from "../reactivity";
-export function testMediaQuery(mediaQueries: MediaCondition[], effect: Effect) {
- return mediaQueries.every((query) => test(query, effect));
+export function testMediaQuery(mediaQueries: MediaCondition[], get: Getter) {
+ return mediaQueries.every((query) => test(query, get));
}
-function test(mediaQuery: MediaCondition, effect: Effect): Boolean {
+function test(mediaQuery: MediaCondition, get: Getter): Boolean {
switch (mediaQuery[0]) {
case "[]":
case "!!":
return false;
case "!":
- return !test(mediaQuery[1], effect);
+ return !test(mediaQuery[1], get);
case "&":
return mediaQuery[1].every((query) => {
- return test(query, effect);
+ return test(query, get);
});
case "|":
return mediaQuery[1].some((query) => {
- return test(query, effect);
+ return test(query, get);
});
case ">":
case ">=":
case "<":
case "<=":
case "=": {
- return testComparison(mediaQuery, effect);
+ return testComparison(mediaQuery, get);
}
}
}
-function testComparison(mediaQuery: MediaCondition, effect: Effect): Boolean {
+function testComparison(mediaQuery: MediaCondition, get: Getter): Boolean {
let left: number | undefined;
const right = mediaQuery[2];
@@ -41,22 +41,20 @@ function testComparison(mediaQuery: MediaCondition, effect: Effect): Boolean {
case "platform":
return right === Platform.OS;
case "prefers-color-scheme": {
- return right === colorScheme.get(effect);
+ return right === get(colorScheme);
}
case "display-mode":
return right === "native" || Platform.OS === right;
case "min-width":
- return typeof right === "number" && vw.get(effect) >= right;
+ return typeof right === "number" && get(vw) >= right;
case "max-width":
- return typeof right === "number" && vw.get(effect) <= right;
+ return typeof right === "number" && get(vw) <= right;
case "min-height":
- return typeof right === "number" && vh.get(effect) >= right;
+ return typeof right === "number" && get(vh) >= right;
case "max-height":
- return typeof right === "number" && vh.get(effect) <= right;
+ return typeof right === "number" && get(vh) <= right;
case "orientation":
- return right === "landscape"
- ? vh.get(effect) < vw.get(effect)
- : vh.get(effect) >= vw.get(effect);
+ return right === "landscape" ? get(vh) < get(vw) : get(vh) >= get(vw);
}
if (typeof right !== "number") {
@@ -65,10 +63,10 @@ function testComparison(mediaQuery: MediaCondition, effect: Effect): Boolean {
switch (mediaQuery[1]) {
case "width":
- left = vw.get(effect);
+ left = get(vw);
break;
case "height":
- left = vh.get(effect);
+ left = get(vh);
break;
case "resolution":
left = PixelRatio.get();
diff --git a/src/runtime/native/react/interaction.ts b/src/runtime/native/react/interaction.ts
index ebcba1e..f6e1ad3 100644
--- a/src/runtime/native/react/interaction.ts
+++ b/src/runtime/native/react/interaction.ts
@@ -6,11 +6,11 @@ import {
containerLayoutFamily,
focusFamily,
hoverFamily,
- type Effect,
+ type Getter as WeakKey,
} from "../reactivity";
const mainCache = new WeakMap<
- Effect,
+ WeakKey,
WeakMap void>
>();
@@ -38,14 +38,14 @@ const defaultHandlers: Record = {
};
export function getInteractionHandler(
- effect: Effect,
+ weakKey: WeakKey,
type: InteractionType,
handler = defaultHandlers[type],
) {
- let cache = mainCache.get(effect);
+ let cache = mainCache.get(weakKey);
if (!cache) {
cache = new WeakMap();
- mainCache.set(effect, cache);
+ mainCache.set(weakKey, cache);
}
let cached = cache.get(handler);
@@ -57,29 +57,29 @@ export function getInteractionHandler(
switch (type) {
case "onLayout":
- containerLayoutFamily(effect).set(
+ containerLayoutFamily(weakKey).set(
(event as LayoutChangeEvent).nativeEvent.layout,
);
break;
case "onHoverIn":
- hoverFamily(effect).set(true);
+ hoverFamily(weakKey).set(true);
break;
case "onHoverOut":
- hoverFamily(effect).set(false);
+ hoverFamily(weakKey).set(false);
break;
case "onPress":
break;
case "onPressIn":
- activeFamily(effect).set(true);
+ activeFamily(weakKey).set(true);
break;
case "onPressOut":
- activeFamily(effect).set(false);
+ activeFamily(weakKey).set(false);
break;
case "onFocus":
- focusFamily(effect).set(true);
+ focusFamily(weakKey).set(true);
break;
case "onBlur":
- focusFamily(effect).set(false);
+ focusFamily(weakKey).set(false);
break;
}
};
diff --git a/src/runtime/native/react/rules.ts b/src/runtime/native/react/rules.ts
index 6694d51..cbe33fb 100644
--- a/src/runtime/native/react/rules.ts
+++ b/src/runtime/native/react/rules.ts
@@ -104,7 +104,7 @@ export function updateRules(
if (
!testRule(
rule,
- state.ruleEffect,
+ state.ruleEffectGetter,
currentProps,
guards,
inheritedContainers,
@@ -132,20 +132,20 @@ export function updateRules(
containers = {
...inheritedContainers,
// This container becomes the default container
- [DEFAULT_CONTAINER_NAME]: state.ruleEffect,
+ [DEFAULT_CONTAINER_NAME]: state.ruleEffectGetter,
};
}
// This this component as the named container
for (const name of rule.c) {
- containers![name] = state.ruleEffect;
+ containers![name] = state.ruleEffectGetter;
}
// Enable hover/active/focus/layout handlers
- hoverFamily(state.ruleEffect);
- activeFamily(state.ruleEffect);
- focusFamily(state.ruleEffect);
- containerLayoutFamily(state.ruleEffect);
+ hoverFamily(state.ruleEffectGetter);
+ activeFamily(state.ruleEffectGetter);
+ focusFamily(state.ruleEffectGetter);
+ containerLayoutFamily(state.ruleEffectGetter);
}
if (rule.a) {
@@ -163,7 +163,7 @@ export function updateRules(
if (process.env.NODE_ENV !== "production") {
if (isRerender) {
- let pressable = activeFamily.has(state.ruleEffect);
+ let pressable = activeFamily.has(state.ruleEffectGetter);
if (Boolean(variables) !== Boolean(state.variables)) {
console.log(
@@ -190,7 +190,7 @@ export function updateRules(
let pressable =
process.env.NODE_ENV === "production"
? undefined
- : activeFamily.has(state.ruleEffect);
+ : activeFamily.has(state.ruleEffectGetter);
if (!rules.size && !state.stylesObs && !inlineVariables.size) {
return {
diff --git a/src/runtime/native/react/useNativeCss.ts b/src/runtime/native/react/useNativeCss.ts
index 0132813..da48cf3 100644
--- a/src/runtime/native/react/useNativeCss.ts
+++ b/src/runtime/native/react/useNativeCss.ts
@@ -17,6 +17,7 @@ import {
VariableContext,
type ContainerContextValue,
type Effect,
+ type Getter,
type VariableContextValue,
} from "../reactivity";
import { getStyledProps, stylesFamily } from "../styles";
@@ -35,6 +36,7 @@ export type ComponentState = {
/** Reactive tracking */
ruleEffect: Effect;
+ ruleEffectGetter: Getter;
styleEffect: Effect;
/** The components props */
@@ -94,6 +96,7 @@ export function useNativeCss(
return updateRules(
{
ruleEffect,
+ ruleEffectGetter: (observable) => observable.get(ruleEffect),
styleEffect,
configs,
inheritedContainers,
diff --git a/src/runtime/native/reactivity.ts b/src/runtime/native/reactivity.ts
index 9ed16fb..c80154a 100644
--- a/src/runtime/native/reactivity.ts
+++ b/src/runtime/native/reactivity.ts
@@ -7,7 +7,8 @@ import {
type LayoutRectangle,
} from "react-native";
-import type { LightDarkVariable, StyleDescriptor } from "../../compiler";
+import type { StyleDescriptor, VariableValue } from "../../compiler";
+import { testMediaQuery } from "./conditions/media-query";
export type Effect = {
observers: Set;
@@ -189,24 +190,32 @@ export const VariableContext = createContext({
[VAR_SYMBOL]: true,
});
-const lightDarkFamily = () => {
- return family>(() => {
- const obs = observable(
- (read, lightDark = [undefined, undefined]) => {
- const colorSchemeName =
- read(colorScheme) ?? Appearance.getColorScheme() ?? "light";
+const rootVariableFamily = () => {
+ return family>(() => {
+ const obs = observable(
+ (read, variableValue) => {
+ if (!variableValue) return undefined;
- return colorSchemeName === "dark"
- ? (lightDark[1] ?? lightDark[0])
- : lightDark[0];
+ for (const [value, mediaQuery] of variableValue) {
+ if (!mediaQuery) {
+ return value;
+ }
+
+ if (testMediaQuery(mediaQuery, read)) {
+ return value;
+ }
+ }
+
+ return undefined;
},
);
return obs;
});
};
-export const rootVariables = lightDarkFamily();
-export const universalVariables = lightDarkFamily();
+
+export const rootVariables = rootVariableFamily();
+export const universalVariables = rootVariableFamily();
/** Units *********************************************************************/
@@ -237,7 +246,7 @@ Appearance.addChangeListener((event) => colorScheme.set(event.colorScheme));
/** Containers ****************************************************************/
-export type ContainerContextValue = Record;
+export type ContainerContextValue = Record;
export const ContainerContext = createContext({});
export const containerLayoutFamily = weakFamily(() => {
diff --git a/src/runtime/native/styles/index.ts b/src/runtime/native/styles/index.ts
index e22e2df..0a2f553 100644
--- a/src/runtime/native/styles/index.ts
+++ b/src/runtime/native/styles/index.ts
@@ -55,57 +55,57 @@ export function getStyledProps(
}
// Apply the handlers
- if (hoverFamily.has(state.ruleEffect)) {
+ if (hoverFamily.has(state.ruleEffectGetter)) {
result ??= {};
result.onHoverIn = getInteractionHandler(
- state.ruleEffect,
+ state.ruleEffectGetter,
"onHoverIn",
inline?.onHoverIn,
);
result.onHoverOut = getInteractionHandler(
- state.ruleEffect,
+ state.ruleEffectGetter,
"onHoverOut",
inline?.onHoverOut,
);
}
- if (activeFamily.has(state.ruleEffect)) {
+ if (activeFamily.has(state.ruleEffectGetter)) {
result ??= {};
result.onPress = getInteractionHandler(
- state.ruleEffect,
+ state.ruleEffectGetter,
"onPress",
inline?.onPress,
);
result.onPressIn = getInteractionHandler(
- state.ruleEffect,
+ state.ruleEffectGetter,
"onPressIn",
inline?.onPressIn,
);
result.onPressOut = getInteractionHandler(
- state.ruleEffect,
+ state.ruleEffectGetter,
"onPressOut",
inline?.onPressOut,
);
}
- if (focusFamily.has(state.ruleEffect)) {
+ if (focusFamily.has(state.ruleEffectGetter)) {
result ??= {};
result.onBlur = getInteractionHandler(
- state.ruleEffect,
+ state.ruleEffectGetter,
"onBlur",
inline?.onBlur,
);
result.onFocus = getInteractionHandler(
- state.ruleEffect,
+ state.ruleEffectGetter,
"onFocus",
inline?.onFocus,
);
}
- if (containerLayoutFamily.has(state.ruleEffect)) {
+ if (containerLayoutFamily.has(state.ruleEffectGetter)) {
result ??= {};
result.onLayout = getInteractionHandler(
- state.ruleEffect,
+ state.ruleEffectGetter,
"onLayout",
inline?.onLayout,
);