diff --git a/src/__tests__/native/deferred-declarations.test.tsx b/src/__tests__/native/deferred-declarations.test.tsx
new file mode 100644
index 00000000..cf3a4599
--- /dev/null
+++ b/src/__tests__/native/deferred-declarations.test.tsx
@@ -0,0 +1,111 @@
+import { render } from "@testing-library/react-native";
+import { Text } from "react-native-css/components/Text";
+import { registerCSS, testID } from "react-native-css/jest";
+
+// A second definition keeps each variable from being inlined, so it resolves at runtime
+const decoy = `.decoy { --c: green; --n: 9; --b: 1px solid green; --w: 9px; }`;
+
+function renderText(css: string, className = "my-class") {
+ registerCSS(`${decoy} ${css}`);
+
+ return render().getByTestId(
+ testID,
+ ).props;
+}
+
+// A deferred declaration resolves after every other one in its rule, against the target it was declared on
+describe("a deferred declaration beside a nested one", () => {
+ test("color: var() beside a box-shadow", () => {
+ expect(
+ renderText(
+ `.my-class { --c: red; color: var(--c); box-shadow: 1px 1px blue; }`,
+ ).style,
+ ).toStrictEqual({
+ color: "red",
+ boxShadow: [
+ {
+ color: "#00f",
+ offsetX: 1,
+ offsetY: 1,
+ blurRadius: 0,
+ spreadDistance: 0,
+ },
+ ],
+ });
+ });
+
+ test("color: var() beside a text-shadow", () => {
+ expect(
+ renderText(
+ `.my-class { --c: red; color: var(--c); text-shadow: 1px 1px 2px blue; }`,
+ ).style,
+ ).toStrictEqual({
+ color: "red",
+ textShadowColor: "#00f",
+ textShadowRadius: 2,
+ textShadowOffset: { width: 1, height: 1 },
+ });
+ });
+
+ test("color: var() beside a prop", () => {
+ const props = renderText(
+ `.my-class { --c: red; color: var(--c); -webkit-line-clamp: 2; }`,
+ );
+
+ expect(props.style).toStrictEqual({ color: "red" });
+ expect(props.numberOfLines).toBe(2);
+ });
+
+ test("a deferred prop beside a style", () => {
+ const props = renderText(
+ `.my-class { --n: 2; -webkit-line-clamp: var(--n); --c: red; color: var(--c); }`,
+ );
+
+ expect(props.style).toStrictEqual({ color: "red" });
+ expect(props.numberOfLines).toBe(2);
+ });
+});
+
+// Chromium 153, Firefox 155 and WebKit 26.6: each key belongs to the declaration that set it last, so a later longhand beats an earlier shorthand however late the shorthand resolves
+describe("a deferred shorthand keeps no key a later declaration set", () => {
+ test.each([
+ [
+ "border: var() then border-color, in one rule",
+ ".my-class { --b: 2px solid red; border: var(--b); border-color: blue; }",
+ "my-class",
+ { borderWidth: 2, borderStyle: "solid", borderColor: "#00f" },
+ ],
+ [
+ "border-color then border: var(), in one rule",
+ ".my-class { border-color: blue; --b: 2px solid red; border: var(--b); }",
+ "my-class",
+ { borderWidth: 2, borderStyle: "solid", borderColor: "red" },
+ ],
+ [
+ "border: var() then border-color, across rules",
+ ".earlier { --b: 2px solid red; border: var(--b); } .my-class { border-color: blue; }",
+ "earlier my-class",
+ { borderWidth: 2, borderStyle: "solid", borderColor: "#00f" },
+ ],
+ [
+ "border-color then border: var(), across rules",
+ ".earlier { border-color: blue; } .my-class { --b: 2px solid red; border: var(--b); }",
+ "earlier my-class",
+ { borderWidth: 2, borderStyle: "solid", borderColor: "red" },
+ ],
+ [
+ "border-inline-width: var() then border-inline-start-width",
+ ".my-class { --w: 2px 3px; border-inline-width: var(--w); border-inline-start-width: 5px; }",
+ "my-class",
+ { borderStartWidth: 5, borderEndWidth: 3 },
+ ],
+ [
+ "border-inline-start-width then border-inline-width: var()",
+ ".my-class { border-inline-start-width: 5px; --w: 2px 3px; border-inline-width: var(--w); }",
+ "my-class",
+ { borderStartWidth: 2, borderEndWidth: 3 },
+ ],
+ ] as const)("%s", (_name, css, className, style) => {
+ expect(renderText(css, className).style).toStrictEqual(style);
+ });
+});
diff --git a/src/native/styles/calculate-props.ts b/src/native/styles/calculate-props.ts
index 28b9ed36..af92b8db 100644
--- a/src/native/styles/calculate-props.ts
+++ b/src/native/styles/calculate-props.ts
@@ -13,6 +13,7 @@ import {
type Getter,
type VariableContextValue,
} from "../reactivity";
+import { ShortHandSymbol } from "./constants";
import { transformKeys } from "./defaults";
import { resolveValue } from "./resolve";
@@ -32,6 +33,7 @@ export function calculateProps(
const delayedStyles: (() => void)[] = [];
const transformStyles: (() => void)[] = [];
+ const cascade = createCascade();
for (const rule of rules) {
if (VAR_SYMBOL in rule) {
@@ -75,6 +77,7 @@ export function calculateProps(
guards,
target,
topLevelTarget,
+ cascade,
);
}
}
@@ -94,6 +97,73 @@ export function calculateProps(
};
}
+// The cascade position of the declaration that last set each key of each target, since a deferred declaration resolves after every later one
+interface Cascade {
+ readonly writers: WeakMap