diff --git a/src/__tests__/compiler/@prop.test.tsx b/src/__tests__/compiler/@prop.test.tsx index 89ba59f7..63f8793e 100644 --- a/src/__tests__/compiler/@prop.test.tsx +++ b/src/__tests__/compiler/@prop.test.tsx @@ -61,15 +61,13 @@ test("@prop target unparsed", () => { "my-class", [ { - d: [[[{}, "var", "color-black", 1], ["test"], 1]], - dv: 1, - s: [2, 1], - v: [["__rn-css-current-color", [{}, "var", "color-black", 1]]], + d: [["#000", ["test"]]], + s: [1, 1], + v: [["__rn-css-color", "#000"]], }, ], ], ], - vr: [["color-black", [["#000"]]]], }); render(); diff --git a/src/__tests__/compiler/compiler.test.tsx b/src/__tests__/compiler/compiler.test.tsx index 0623c8d3..5e5775b5 100644 --- a/src/__tests__/compiler/compiler.test.tsx +++ b/src/__tests__/compiler/compiler.test.tsx @@ -27,12 +27,17 @@ test("hello world", () => { }); test("reads global CSS variables", () => { - const compiled = compile(` + const compiled = compile( + ` @layer theme { :root, :host { --color-red-500: oklch(63.7% 0.237 25.331); } -}`); +}`, + { + inlineVariables: false, + }, + ); expect(compiled.stylesheet()).toStrictEqual({ vr: [["color-red-500", [["#fb2c36"]]]], @@ -236,11 +241,16 @@ test.skip("animations", () => { }); test("breaks apart comma separated variables", () => { - const compiled = compile(` + const compiled = compile( + ` :root { --test: blue, green; } - `); + `, + { + inlineVariables: false, + }, + ); expect(compiled.stylesheet()).toStrictEqual({ vr: [["test", [[["blue", "green"]]]]], diff --git a/src/__tests__/native/box-shadow.test.tsx b/src/__tests__/native/box-shadow.test.tsx index 6d62135a..48a387a4 100644 --- a/src/__tests__/native/box-shadow.test.tsx +++ b/src/__tests__/native/box-shadow.test.tsx @@ -49,7 +49,7 @@ test("shadow values - multiple nested variables", () => { boxShadow: [ { blurRadius: 0, - color: "red", + color: "#f00", offsetX: 0, offsetY: 20, spreadDistance: 0, @@ -57,35 +57,35 @@ test("shadow values - multiple nested variables", () => { { blurRadius: 0, - color: "green", + color: "#008000", offsetX: 0, offsetY: 30, spreadDistance: 0, }, { blurRadius: 0, - color: "purple", + color: "#800080", offsetX: 0, offsetY: 40, spreadDistance: 0, }, { blurRadius: 0, - color: "yellow", + color: "#ff0", offsetX: 0, offsetY: 50, spreadDistance: 0, }, { blurRadius: 0, - color: "orange", + color: "#ffa500", offsetX: 0, offsetY: 60, spreadDistance: 0, }, { blurRadius: 0, - color: "gray", + color: "#808080", offsetX: 0, offsetY: 70, spreadDistance: 0, diff --git a/src/__tests__/native/calc.test.tsx b/src/__tests__/native/calc.test.tsx index 30cd63b3..b26cfbad 100644 --- a/src/__tests__/native/calc.test.tsx +++ b/src/__tests__/native/calc.test.tsx @@ -139,7 +139,6 @@ test("calc(var(--variable) + 20%)", () => { expect(component.type).toBe("View"); expect(component.props).toStrictEqual({ children: undefined, - style: {}, testID, }); }); @@ -159,7 +158,6 @@ test("calc(var(--percent) + 20px)", () => { expect(component.type).toBe("View"); expect(component.props).toStrictEqual({ children: undefined, - style: {}, testID, }); }); @@ -185,7 +183,7 @@ test("calc & colors", () => { expect(component.props).toStrictEqual({ children: undefined, style: { - backgroundColor: "hsl(120, 90%, 80%)", + backgroundColor: "#9efa9e", }, testID, }); diff --git a/src/__tests__/native/colors.test.tsx b/src/__tests__/native/colors.test.tsx index 384379b0..6a8c7255 100644 --- a/src/__tests__/native/colors.test.tsx +++ b/src/__tests__/native/colors.test.tsx @@ -45,7 +45,7 @@ describe("hsl", () => { expect(component.type).toBe("View"); expect(component.props).toStrictEqual({ children: undefined, - style: { color: "hsl(0, 84.2%, 60.2%)" }, + style: { color: "#ef4444" }, testID, }); }); @@ -62,7 +62,7 @@ describe("hsl", () => { expect(component.type).toBe("View"); expect(component.props).toStrictEqual({ children: undefined, - style: { color: "hsl(0, 84.2%, 60.2%)" }, + style: { color: "#ef4444" }, testID, }); }); @@ -113,7 +113,7 @@ describe("hsla", () => { expect(component.type).toBe("View"); expect(component.props).toStrictEqual({ children: undefined, - style: { color: "hsla(0, 84.2%, 60.2%, 60%)" }, + style: { color: "#ef444499" }, testID, }); }); @@ -130,7 +130,7 @@ describe("hsla", () => { expect(component.type).toBe("View"); expect(component.props).toStrictEqual({ children: undefined, - style: { color: "hsla(0, 84.2%, 60.2%, 60%)" }, + style: { color: "#ef444499" }, testID, }); }); @@ -160,7 +160,7 @@ describe("currentcolor", () => { expect(component.type).toBe("View"); expect(component.props).toStrictEqual({ children: undefined, - style: { color: "red", backgroundColor: "red" }, + style: { color: "#f00", backgroundColor: "#f00" }, testID, }); }); diff --git a/src/__tests__/native/text-shadow.test.ios.tsx b/src/__tests__/native/text-shadow.test.ios.tsx index 8b45ea72..07281ee7 100644 --- a/src/__tests__/native/text-shadow.test.ios.tsx +++ b/src/__tests__/native/text-shadow.test.ios.tsx @@ -5,13 +5,18 @@ import { registerCSS, testID } from "react-native-css/jest"; describe("text-shadow", () => { test(" ", () => { registerCSS( - `.my-class { --my-var: 10px 10px; text-shadow: var(--my-var); }`, + `.my-class { + --my-var: 10px 10px; + text-shadow: var(--my-var); + }`, ); render(); expect(screen.getByTestId(testID).props.style).toStrictEqual({ - textShadowColor: "black", + textShadowColor: { + semantic: ["label", "labelColor"], + }, textShadowOffset: { height: 10, width: 10, @@ -28,7 +33,7 @@ describe("text-shadow", () => { render(); expect(screen.getByTestId(testID).props.style).toStrictEqual({ - textShadowColor: "red", + textShadowColor: "#f00", textShadowOffset: { height: 10, width: 10, @@ -45,7 +50,7 @@ describe("text-shadow", () => { render(); expect(screen.getByTestId(testID).props.style).toStrictEqual({ - textShadowColor: "red", + textShadowColor: "#f00", textShadowOffset: { height: 10, width: 10, diff --git a/src/__tests__/native/transform.test.tsx b/src/__tests__/native/transform.test.tsx index 6ec3c08b..b7a08fca 100644 --- a/src/__tests__/native/transform.test.tsx +++ b/src/__tests__/native/transform.test.tsx @@ -56,7 +56,7 @@ describe("scale", () => { ).getByTestId(testID); expect(component.props.style).toStrictEqual({ - transform: [{ scale: "2%" }], + transform: [{ scaleX: "2%" }, { scaleY: "2%" }], }); }); diff --git a/src/__tests__/native/variables.test.tsx b/src/__tests__/native/variables.test.tsx index dbdf150b..48971465 100644 --- a/src/__tests__/native/variables.test.tsx +++ b/src/__tests__/native/variables.test.tsx @@ -20,7 +20,7 @@ test("inline variable", () => { }); }); -test("combined inline variable", () => { +test("combined inline variables", () => { registerCSS(` .my-class-1 { width: var(--my-var); } .my-class-2 { --my-var: 10px; } @@ -142,7 +142,7 @@ test(":root variables", () => { , ).getByTestId(testID); - expect(component.props.style).toStrictEqual({ color: "red" }); + expect(component.props.style).toStrictEqual({ color: "#f00" }); }); test("can apply and set new variables", () => { @@ -166,13 +166,13 @@ test("can apply and set new variables", () => { ); expect(screen.getByTestId(testIDs.one).props.style).toStrictEqual({ - color: "red", + color: "#f00", }); expect(screen.getByTestId(testIDs.two).props.style).toStrictEqual({ - color: "red", + color: "#f00", }); expect(screen.getByTestId(testIDs.three).props.style).toStrictEqual({ - color: "green", + color: "#008000", }); }); @@ -199,7 +199,7 @@ test("variables will be inherited", () => { ); expect(screen.getByTestId(testIDs.three).props.style).toStrictEqual({ - color: "green", + color: "#008000", }); }); @@ -212,7 +212,7 @@ test("useUnsafeVariable", () => { render(); const component = screen.getByTestId(testID); - expect(component.props.style).toStrictEqual({ color: "red" }); + expect(component.props.style).toStrictEqual({ color: "#f00" }); }); test("ratio values", () => { @@ -224,7 +224,7 @@ test("ratio values", () => { render(); const component = screen.getByTestId(testID); - expect(component.props.style).toStrictEqual({ aspectRatio: "16 / 9" }); + expect(component.props.style).toStrictEqual({ aspectRatio: "16/9" }); }); test("VariableContextProvider", () => { @@ -269,5 +269,5 @@ test("variable overriding with classes", () => { ); const component = screen.getByTestId(testID); - expect(component.props.style).toStrictEqual({ color: "red" }); + expect(component.props.style).toStrictEqual({ color: "#f00" }); }); diff --git a/src/__tests__/vendor/tailwind.test.tsx b/src/__tests__/vendor/tailwind.test.tsx deleted file mode 100644 index 446fd662..00000000 --- a/src/__tests__/vendor/tailwind.test.tsx +++ /dev/null @@ -1,414 +0,0 @@ -import { render, screen } from "@testing-library/react-native"; -import { View } from "react-native-css/components/View"; -import { registerCSS, testID } from "react-native-css/jest"; - -/** - * Tailwind CSS utilities - * - * These tests are designed to ensure that complex Tailwind CSS utilities are compiled correctly. - * For the full Tailwind CSS test suite, see the Nativewind repository. - */ - -test("transition", () => { - const compiled = registerCSS(` -:root, :host { - --default-transition-duration: 150ms; - --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); -} - -.transition { - transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, visibility, content-visibility, overlay, pointer-events; - transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); - transition-duration: var(--tw-duration, var(--default-transition-duration)); -} - `); - - expect(compiled.stylesheet()).toStrictEqual({ - s: [ - [ - "transition", - [ - { - a: true, - d: [ - { - transitionProperty: [ - "color", - "backgroundColor", - "borderColor", - "outlineColor", - "textDecorationColor", - "fill", - "stroke", - "opacity", - "boxShadow", - "transform", - "translate", - "scale", - "rotate", - "filter", - "display", - "pointerEvents", - ], - }, - [ - [ - {}, - "var", - [ - "tw-ease", - [{}, "var", "default-transition-timing-function", 1], - ], - 1, - ], - "transitionTimingFunction", - 1, - ], - [ - [ - {}, - "var", - [ - "tw-duration", - [{}, "var", "default-transition-duration", 1], - ], - 1, - ], - "transitionDuration", - 1, - ], - ], - dv: 1, - s: [2, 1], - }, - ], - ], - ], - vr: [ - ["default-transition-duration", [[150]]], - [ - "default-transition-timing-function", - [[[{}, "cubicBezier", [0.4, 0, 0.2, 1]]]], - ], - ], - }); -}); - -test("box-shadow", () => { - const compiled = registerCSS(` -.shadow-xl { - --tw-shadow: 0 20px 25px -5px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 8px 10px -6px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); - box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); -} -.shadow-red-500 { - --tw-shadow-color: oklch(63.7% 0.237 25.331); - @supports (color: color-mix(in lab, red, red)) { - --tw-shadow-color: color-mix(in oklab, var(--color-red-500) var(--tw-shadow-alpha), transparent); - } -} - `); - - expect(compiled.stylesheet()).toStrictEqual({ - s: [ - [ - "shadow-xl", - [ - { - d: [ - [ - [ - {}, - "boxShadow", - [ - [{}, "var", "tw-inset-shadow", 1], - [{}, "var", "tw-inset-ring-shadow", 1], - [{}, "var", "tw-ring-offset-shadow", 1], - [{}, "var", "tw-ring-shadow", 1], - [{}, "var", "tw-shadow", 1], - ], - 1, - ], - "boxShadow", - 1, - ], - ], - dv: 1, - s: [1, 1], - v: [ - [ - "tw-shadow", - [ - [ - 0, - 20, - 25, - -5, - [{}, "var", ["tw-shadow-color", "#0000001a"], 1], - ], - [ - 0, - 8, - 10, - -6, - [{}, "var", ["tw-shadow-color", "#0000001a"], 1], - ], - ], - ], - ], - }, - ], - ], - [ - "shadow-red-500", - [ - { - s: [2, 1], - v: [["tw-shadow-color", "#fb2c36"]], - }, - ], - ], - ], - }); - - render(); - const component = screen.getByTestId(testID); - - expect(component.type).toBe("View"); - expect(component.props).toStrictEqual({ - children: undefined, - style: { - boxShadow: [ - { - blurRadius: 25, - color: "#fb2c36", - offsetX: 0, - offsetY: 20, - spreadDistance: -5, - }, - { - blurRadius: 10, - color: "#fb2c36", - offsetX: 0, - offsetY: 8, - spreadDistance: -6, - }, - ], - }, - testID, - }); -}); - -test("filter", () => { - const compiled = registerCSS(` -:root, :host { - --drop-shadow-md: 0 3px 3px rgb(0 0 0 / 0.12); -} - -.brightness-50 { - --tw-brightness: brightness(50%); - filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); -} - -.drop-shadow-md { - --tw-drop-shadow-size: drop-shadow(0 3px 3px var(--tw-drop-shadow-color, rgb(0 0 0 / 0.12))); - --tw-drop-shadow: drop-shadow(var(--drop-shadow-md)); - filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); -} - `); - - expect(compiled.stylesheet()).toStrictEqual({ - s: [ - [ - "brightness-50", - [ - { - d: [ - [ - [ - [{}, "var", "tw-blur", 1], - [{}, "var", "tw-brightness", 1], - [{}, "var", "tw-contrast", 1], - [{}, "var", "tw-grayscale", 1], - [{}, "var", "tw-hue-rotate", 1], - [{}, "var", "tw-invert", 1], - [{}, "var", "tw-saturate", 1], - [{}, "var", "tw-sepia", 1], - [{}, "var", "tw-drop-shadow", 1], - ], - "filter", - ], - ], - s: [2, 1], - v: [["tw-brightness", [{}, "brightness", "50%"]]], - }, - ], - ], - [ - "drop-shadow-md", - [ - { - d: [ - [ - [ - [{}, "var", "tw-blur", 1], - [{}, "var", "tw-brightness", 1], - [{}, "var", "tw-contrast", 1], - [{}, "var", "tw-grayscale", 1], - [{}, "var", "tw-hue-rotate", 1], - [{}, "var", "tw-invert", 1], - [{}, "var", "tw-saturate", 1], - [{}, "var", "tw-sepia", 1], - [{}, "var", "tw-drop-shadow", 1], - ], - "filter", - ], - ], - s: [3, 1], - v: [ - [ - "tw-drop-shadow-size", - [ - {}, - "dropShadow", - [ - 0, - 3, - 3, - [{}, "var", ["tw-drop-shadow-color", "#0000001f"], 1], - ], - ], - ], - [ - "tw-drop-shadow", - [{}, "dropShadow", [{}, "var", "drop-shadow-md", 1]], - ], - ], - }, - ], - ], - ], - vr: [["drop-shadow-md", [[[0, 3, 3, "#0000001f"]]]]], - }); - - render(); - const component = screen.getByTestId(testID); - - expect(component.type).toBe("View"); - expect(component.props).toStrictEqual({ - children: undefined, - style: { - filter: [ - { brightness: "50%" }, - { - dropShadow: { - blurRadius: 3, - color: "#0000001f", - offsetX: 0, - offsetY: 3, - }, - }, - ], - }, - testID, - }); -}); - -test("line-clamp", () => { - const compiled = registerCSS(` - .my-class { - overflow: hidden; - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 1 - } - `); - - expect(compiled.stylesheet()).toStrictEqual({ - s: [ - [ - "my-class", - [ - { - d: [ - [1, ["numberOfLines"]], - { - overflow: "hidden", - }, - ], - s: [1, 1], - }, - ], - ], - ], - }); - - render(); - const component = screen.getByTestId(testID); - - expect(component.props).toStrictEqual({ - children: undefined, - numberOfLines: 1, - style: { - overflow: "hidden", - }, - testID, - }); -}); - -test("blur-xl", () => { - const compiled = registerCSS(` - @layer theme { - :root, :host { - --blur-xs: 4px; - } - } - @layer utilities { - .blur-xs { - --tw-blur: blur(var(--blur-xs)); - filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); - } - } - `); - - expect(compiled.stylesheet()).toStrictEqual({ - s: [ - [ - "blur-xs", - [ - { - d: [ - [ - [ - [{}, "var", "tw-blur", 1], - [{}, "var", "tw-brightness", 1], - [{}, "var", "tw-contrast", 1], - [{}, "var", "tw-grayscale", 1], - [{}, "var", "tw-hue-rotate", 1], - [{}, "var", "tw-invert", 1], - [{}, "var", "tw-saturate", 1], - [{}, "var", "tw-sepia", 1], - [{}, "var", "tw-drop-shadow", 1], - ], - "filter", - ], - ], - s: [2, 1], - v: [["tw-blur", [{}, "blur", [{}, "var", "blur-xs", 1]]]], - }, - ], - ], - ], - vr: [["blur-xs", [[4]]]], - }); - - render(); - const component = screen.getByTestId(testID); - - expect(component.props).toStrictEqual({ - children: undefined, - style: { - filter: [{ blur: 4 }], - }, - testID, - }); -}); diff --git a/src/__tests__/vendor/tailwind/_tailwind.tsx b/src/__tests__/vendor/tailwind/_tailwind.tsx index 6f5cd5a6..bff42224 100644 --- a/src/__tests__/vendor/tailwind/_tailwind.tsx +++ b/src/__tests__/vendor/tailwind/_tailwind.tsx @@ -1,7 +1,5 @@ import type { PropsWithChildren, ReactElement } from "react"; -import { inspect } from "node:util"; - import tailwind from "@tailwindcss/postcss"; import { screen, @@ -88,16 +86,6 @@ export async function render( const compiled = registerCSS(output, { debug: Boolean(debug) }); - if (debug) { - console.log( - inspect(compiled.stylesheet(), { - colors: true, - compact: false, - depth: null, - }), - ); - } - return Object.assign( {}, tlRender(component, { diff --git a/src/__tests__/vendor/tailwind/flexbox-grid.test.tsx b/src/__tests__/vendor/tailwind/flexbox-grid.test.tsx index b73d6648..cc825129 100644 --- a/src/__tests__/vendor/tailwind/flexbox-grid.test.tsx +++ b/src/__tests__/vendor/tailwind/flexbox-grid.test.tsx @@ -402,7 +402,7 @@ describe("Flexbox & Grid - Gap", () => { }); test("gap-px", async () => { expect(await renderCurrentTest()).toStrictEqual({ - props: { style: { columnGap: 1, rowGap: 1 } }, + props: { style: { gap: 1 } }, }); }); test("gap-x-1", async () => { diff --git a/src/__tests__/vendor/tailwind/interactivity.test.tsx b/src/__tests__/vendor/tailwind/interactivity.test.tsx index e4a99fe3..dd2082f6 100644 --- a/src/__tests__/vendor/tailwind/interactivity.test.tsx +++ b/src/__tests__/vendor/tailwind/interactivity.test.tsx @@ -60,7 +60,12 @@ describe("Interactivity - Caret Color", () => { }); test("caret-current", async () => { expect(await renderCurrentTest()).toStrictEqual({ - props: { style: {} }, + props: { + cursorColor: { + semantic: ["label", "labelColor"], + }, + style: {}, + }, }); }); test("caret-white", async () => { diff --git a/src/__tests__/vendor/tailwind/layout.test.tsx b/src/__tests__/vendor/tailwind/layout.test.tsx index 99948b3b..e38a1cec 100644 --- a/src/__tests__/vendor/tailwind/layout.test.tsx +++ b/src/__tests__/vendor/tailwind/layout.test.tsx @@ -8,7 +8,7 @@ describe("Layout - Aspect Ratio", () => { }); test("aspect-video", async () => { expect(await renderCurrentTest()).toStrictEqual({ - props: { style: { aspectRatio: "16 / 9" } }, + props: { style: { aspectRatio: "16/9" } }, }); }); test("aspect-[4/3]", async () => { diff --git a/src/__tests__/vendor/tailwind/transform.test.ts b/src/__tests__/vendor/tailwind/transform.test.ts index b2198afb..a056663f 100644 --- a/src/__tests__/vendor/tailwind/transform.test.ts +++ b/src/__tests__/vendor/tailwind/transform.test.ts @@ -95,7 +95,7 @@ describe("Transforms - Translate", () => { expect(await renderCurrentTest()).toStrictEqual({ props: { style: { - transform: [{ translateX: 0 }], + transform: [{ translateX: 0 }, { translateY: 0 }], }, }, }); @@ -104,7 +104,7 @@ describe("Transforms - Translate", () => { expect(await renderCurrentTest()).toStrictEqual({ props: { style: { - transform: [{ translateY: 0 }], + transform: [{ translateX: 0 }, { translateY: 0 }], }, }, }); @@ -113,7 +113,7 @@ describe("Transforms - Translate", () => { expect(await renderCurrentTest()).toStrictEqual({ props: { style: { - transform: [{ translateX: 1 }], + transform: [{ translateX: 1 }, { translateY: 0 }], }, }, }); @@ -122,7 +122,7 @@ describe("Transforms - Translate", () => { expect(await renderCurrentTest()).toStrictEqual({ props: { style: { - transform: [{ translateY: 1 }], + transform: [{ translateX: 0 }, { translateY: 1 }], }, }, }); @@ -131,7 +131,7 @@ describe("Transforms - Translate", () => { expect(await renderCurrentTest()).toStrictEqual({ props: { style: { - transform: [{ translateX: 3.5 }], + transform: [{ translateX: 3.5 }, { translateY: 0 }], }, }, }); @@ -140,7 +140,7 @@ describe("Transforms - Translate", () => { expect(await renderCurrentTest()).toStrictEqual({ props: { style: { - transform: [{ translateY: 3.5 }], + transform: [{ translateX: 0 }, { translateY: 3.5 }], }, }, }); @@ -150,7 +150,9 @@ describe("Transforms - Translate", () => { describe("Transforms - Translate (%)", () => { test("translate-x-1/2", async () => { expect(await renderCurrentTest()).toStrictEqual({ - props: { style: { transform: [{ translateX: "50%" }] } }, + props: { + style: { transform: [{ translateX: "50%" }, { translateY: 0 }] }, + }, }); }); @@ -158,7 +160,7 @@ describe("Transforms - Translate (%)", () => { expect(await renderCurrentTest()).toStrictEqual({ props: { style: { - transform: [{ translateY: "50%" }], + transform: [{ translateX: 0 }, { translateY: "50%" }], }, }, }); @@ -167,7 +169,7 @@ describe("Transforms - Translate (%)", () => { expect(await renderCurrentTest()).toStrictEqual({ props: { style: { - transform: [{ translateX: "100%" }], + transform: [{ translateX: "100%" }, { translateY: 0 }], }, }, }); @@ -176,7 +178,7 @@ describe("Transforms - Translate (%)", () => { expect(await renderCurrentTest()).toStrictEqual({ props: { style: { - transform: [{ translateY: "100%" }], + transform: [{ translateX: 0 }, { translateY: "100%" }], }, }, }); @@ -234,6 +236,7 @@ describe("Transforms - Mixed", () => { transform: [ { skewY: "1deg" }, { translateX: 3.5 }, + { translateY: 0 }, { rotateZ: "90deg" }, ], }, diff --git a/src/__tests__/vendor/tailwind/typography.test.tsx b/src/__tests__/vendor/tailwind/typography.test.tsx index 5986d5b4..00d184b7 100644 --- a/src/__tests__/vendor/tailwind/typography.test.tsx +++ b/src/__tests__/vendor/tailwind/typography.test.tsx @@ -313,7 +313,11 @@ describe("Typography - Text Color", () => { test("text-current", async () => { expect(await renderCurrentTest()).toStrictEqual({ props: { - style: {}, + style: { + color: { + semantic: ["label", "labelColor"], + }, + }, }, }); }); diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts index 52018dc1..f403ea6d 100644 --- a/src/compiler/compiler.ts +++ b/src/compiler/compiler.ts @@ -14,9 +14,14 @@ import { } from "lightningcss"; import { maybeMutateReactNativeOptions, parsePropAtRule } from "./atRules"; -import type { CompilerOptions, ContainerQuery } from "./compiler.types"; +import type { + CompilerOptions, + ContainerQuery, + UniqueVarInfo, +} from "./compiler.types"; import { parseContainerCondition } from "./container-query"; import { parseDeclaration, round } from "./declarations"; +import { inlineVariables } from "./inline-variables"; import { extractKeyFrames } from "./keyframes"; import { parseMediaQuery } from "./media-query"; import { StylesheetBuilder } from "./stylesheet"; @@ -64,22 +69,54 @@ export function compile(code: Buffer | string, options: CompilerOptions = {}) { * * Due to the above issue, we run lightningcss twice */ + + const vars = new Map(); + + const firstPassVisitor: Visitor = {}; + + if (options.inlineRem !== false) { + firstPassVisitor.Length = (length) => { + if (length.unit !== "rem" || options.inlineRem === false) { + return length; + } + + return { + unit: "px", + value: round(length.value * (options.inlineRem ?? 14)), + }; + }; + } + + if (options.inlineVariables !== false) { + const exclusionList: string[] = options.inlineVariables?.exclude ?? []; + + firstPassVisitor.Declaration = (decl) => { + if ( + decl.property === "custom" && + decl.value.name.startsWith("--") && + !exclusionList.includes(decl.value.name) + ) { + const entry = vars.get(decl.value.name) ?? { + count: 0, + value: [ + ...decl.value.value, + { type: "token", value: { type: "white-space", value: " " } }, + ], + }; + entry.count++; + vars.set(decl.value.name, entry); + } + }; + firstPassVisitor.StyleSheetExit = (sheet) => { + return inlineVariables(sheet, vars); + }; + } + const { code: firstPass } = lightningcss({ code: typeof code === "string" ? new TextEncoder().encode(code) : code, include: Features.DoublePositionGradients | Features.ColorFunction, exclude: Features.VendorPrefixes, - visitor: { - Length(length) { - if (length.unit !== "rem" || options.inlineRem === false) { - return length; - } - - return { - unit: "px", - value: round(length.value * (options.inlineRem ?? 14)), - }; - }, - }, + visitor: firstPassVisitor, filename: options.filename ?? "style.css", projectRoot: options.projectRoot ?? process.cwd(), }); diff --git a/src/compiler/compiler.types.ts b/src/compiler/compiler.types.ts index 9f71729d..f1cecb41 100644 --- a/src/compiler/compiler.types.ts +++ b/src/compiler/compiler.types.ts @@ -1,6 +1,9 @@ /* eslint-disable */ import type { Debugger } from "debug"; -import type { MediaFeatureNameFor_MediaFeatureId } from "lightningcss"; +import type { + MediaFeatureNameFor_MediaFeatureId, + TokenOrValue, +} from "lightningcss"; import { VAR_SYMBOL } from "../runtime/native/reactivity"; @@ -8,6 +11,7 @@ export interface CompilerOptions { filename?: string; projectRoot?: string; inlineRem?: number | false; + inlineVariables?: false | InlineVariableOptions; selectorPrefix?: string; stylesheetOrder?: number; features?: FeatureFlagRecord; @@ -16,6 +20,10 @@ export interface CompilerOptions { colorPrecision?: number; } +export interface InlineVariableOptions { + exclude?: `--${string}`[]; +} + /** * A `react-native-css` StyleSheet */ @@ -151,6 +159,12 @@ export type InlineVariable = { [key: string]: unknown | undefined; }; +export type UniqueVarInfo = { + count: number; + value: TokenOrValue[] | undefined; + flat?: true; +}; + /****************************** Animations ******************************/ export type Animation = [string, AnimationKeyframes[]]; diff --git a/src/compiler/declarations.ts b/src/compiler/declarations.ts index 47540aac..2e20bd9a 100644 --- a/src/compiler/declarations.ts +++ b/src/compiler/declarations.ts @@ -36,6 +36,7 @@ import type { UnresolvedColor, } from "lightningcss"; +import { isStyleFunction } from "../runtime/utils"; import type { StyleDescriptor, StyleFunction, @@ -369,35 +370,26 @@ function parseBorderColor( >, builder: StylesheetBuilder, ) { - switch (declaration.property) { - case "border-color": - builder.addShorthand("border-color", { - "border-top-color": parseColor(declaration.value.top, builder), - "border-bottom-color": parseColor(declaration.value.bottom, builder), - "border-left-color": parseColor(declaration.value.left, builder), - "border-right-color": parseColor(declaration.value.right, builder), - }); - break; - case "border-block-color": - builder.addDescriptor( - "border-top-color", - parseColor(declaration.value.start, builder), - ); - builder.addDescriptor( - "border-bottom-color", - parseColor(declaration.value.end, builder), - ); - break; - case "border-inline-color": - builder.addDescriptor( - "border-left-color", - parseColor(declaration.value.start, builder), - ); - builder.addDescriptor( - "border-right-color", - parseColor(declaration.value.end, builder), - ); - break; + if (declaration.property === "border-color") { + builder.addShorthand("border-color", { + "border-top-color": parseColor(declaration.value.top, builder), + "border-bottom-color": parseColor(declaration.value.bottom, builder), + "border-left-color": parseColor(declaration.value.left, builder), + "border-right-color": parseColor(declaration.value.right, builder), + }); + } else { + const start = parseColor(declaration.value.start, builder); + const end = parseColor(declaration.value.end, builder); + + if (start === end) { + builder.addDescriptor(declaration.property, start); + } else if (declaration.property === "border-block-color") { + builder.addDescriptor("border-top-color", start); + builder.addDescriptor("border-bottom-color", end); + } else { + builder.addDescriptor("border-left-color", start); + builder.addDescriptor("border-right-color", end); + } } } @@ -859,17 +851,8 @@ function parseLetterSpacing( if (value.type === "normal") { return; } - const descriptor = parseLength(value.value, builder); - if ( - Array.isArray(descriptor) && - descriptor[1] === "em" && - typeof descriptor[2] === "number" - ) { - return descriptor[2]; - } - - return descriptor; + return parseLength(value.value, builder); } function parseTextDecoration( @@ -966,7 +949,13 @@ export function parseUnparsedDeclaration( } if (property === "color") { - builder.addDescriptor("--__rn-css-current-color", value); + if ( + !isStyleFunction(value) || + value[1] !== "var" || + value[2] !== "-css-color" + ) { + builder.addDescriptor("--__rn-css-color", value); + } } } } @@ -1125,7 +1114,7 @@ export function parseUnparsed( } else if (tokenOrValue === "false") { return false; } else if (tokenOrValue === "currentcolor") { - return [{}, "var", "__rn-css-current-color"] as const; + return [{}, "var", "__rn-css-color"] as const; } else { return tokenOrValue; } @@ -1143,8 +1132,16 @@ export function parseUnparsed( allowAuto, ); if (!args) return; - if (Array.isArray(args) && args.length === 1) return args[0]; - return args; + if (Array.isArray(args) && args.length === 1) { + return args[0]; + } else if ( + (property === "filter" || property === "transform") && + isStyleFunction(args) + ) { + return [args]; + } else { + return args; + } } switch (tokenOrValue.type) { @@ -1255,7 +1252,7 @@ export function parseUnparsed( builder.addWarning("value", value); return; } else if (value === "currentcolor") { - return [{}, "var", "__rn-css-current-color"] as const; + return [{}, "var", "__rn-css-color"] as const; } if (value === "true") { @@ -1579,10 +1576,15 @@ export function parseFontColorDeclaration( ) { parseColorDeclaration(declaration, builder); - builder.addDescriptor( - "--__rn-css-color", - parseColor(declaration.value, builder), - ); + if ( + typeof declaration.value !== "object" || + declaration.value.type !== "currentcolor" + ) { + builder.addDescriptor( + "--__rn-css-color", + parseColor(declaration.value, builder), + ); + } } export function parseColorDeclaration( @@ -1609,7 +1611,7 @@ export function parseColor(cssColor: CssColor, builder: StylesheetBuilder) { switch (cssColor.type) { case "currentcolor": - return [{}, "var", "__rn-css-current-color"] as const; + return [{}, "var", "__rn-css-color"] as const; case "light-dark": { const extraRule: StyleRule = { s: [], @@ -1952,7 +1954,7 @@ export function parseFontWeight( switch (fontWeight.type) { case "absolute": if (fontWeight.value.type === "weight") { - return fontWeight.value.value.toString(); + return fontWeight.value.value; } else { return fontWeight.value.type; } @@ -1982,11 +1984,11 @@ export function parseTextShadow( parseColor(textShadow.color, builder), ); builder.addDescriptor( - "textShadowOffset.width", + "*.textShadowOffset.width", parseLength(textShadow.xOffset, builder), ); builder.addDescriptor( - "textShadowOffset.height", + "*.textShadowOffset.height", parseLength(textShadow.yOffset, builder), ); builder.addDescriptor( @@ -2324,24 +2326,22 @@ export function parseGap( builder: StylesheetBuilder, ) { if ("column" in declaration.value) { - builder.addDescriptor( - "row-gap", - parseGapValue(declaration.value.row, builder), - ); - builder.addDescriptor( - "column-gap", - parseGapValue(declaration.value.column, builder), - ); + const row = parseGapValue(declaration.value.row, builder); + const column = parseGapValue(declaration.value.column, builder); - return; - } else { - if (declaration.value.type === "normal") { - builder.addWarning("value", declaration.value.type); - return; + if (row !== column) { + builder.addDescriptor("row-gap", row); + builder.addDescriptor("column-gap", column); + } else { + builder.addDescriptor("gap", row); } - + } else if (declaration.value.type === "normal") { + builder.addWarning("value", declaration.value.type); + } else { return parseLength(declaration.value.value, builder); } + + return; } function parseGapValue( @@ -2374,27 +2374,27 @@ export function parseBoxShadow( ) { for (const [index, shadow] of value.entries()) { builder.addDescriptor( - `boxShadow.[${index}].color`, + `*.boxShadow.[${index}].color`, parseColor(shadow.color, builder), ); builder.addDescriptor( - `boxShadow.[${index}].offsetX`, + `*.boxShadow.[${index}].offsetX`, parseLength(shadow.xOffset, builder), ); builder.addDescriptor( - `boxShadow.[${index}].offsetY`, + `*.boxShadow.[${index}].offsetY`, parseLength(shadow.yOffset, builder), ); builder.addDescriptor( - `boxShadow.[${index}].blurRadius`, + `*.boxShadow.[${index}].blurRadius`, parseLength(shadow.blur, builder), ); builder.addDescriptor( - `boxShadow.[${index}].spreadDistance`, + `*.boxShadow.[${index}].spreadDistance`, parseLength(shadow.spread, builder), ); builder.addDescriptor( - `boxShadow.[${index}].inset`, + `*.boxShadow.[${index}].inset`, shadow.inset ? true : undefined, ); } diff --git a/src/compiler/inline-variables.ts b/src/compiler/inline-variables.ts new file mode 100644 index 00000000..609760c1 --- /dev/null +++ b/src/compiler/inline-variables.ts @@ -0,0 +1,209 @@ +import type { + Declaration, + DeclarationBlock, + StyleSheet, + TokenOrValue, +} from "lightningcss"; + +import type { UniqueVarInfo } from "./compiler.types"; + +export function inlineVariables( + stylesheet: StyleSheet, + vars: Map, +) { + for (const [name, info] of [...vars]) { + if (info.count !== 1) { + vars.delete(name); + } else { + flattenVar(name, vars); + } + } + + stylesheet.rules = stylesheet.rules.map(function checkRule(rule) { + switch (rule.type) { + case "custom": + case "font-face": + case "font-palette-values": + case "font-feature-values": + case "namespace": + case "layer-statement": + case "property": + case "view-transition": + case "ignored": + case "unknown": + case "import": + case "page": + case "counter-style": + case "moz-document": + case "nesting": + case "viewport": + case "custom-media": + case "scope": + case "starting-style": + return rule; + + case "media": + rule.value.rules = rule.value.rules.map((rule) => checkRule(rule)); + return rule; + case "keyframes": + rule.value.keyframes = rule.value.keyframes.map((keyframe) => { + keyframe.declarations = + replaceDeclarationBlock(keyframe.declarations, vars) ?? + keyframe.declarations; + + return keyframe; + }); + return rule; + case "style": + rule.value.declarations = replaceDeclarationBlock( + rule.value.declarations, + vars, + ); + + rule.value.rules = rule.value.rules?.flatMap((rule) => checkRule(rule)); + + return rule; + case "nested-declarations": + rule.value.declarations = + replaceDeclarationBlock(rule.value.declarations, vars) ?? {}; + return rule; + case "supports": + rule.value.rules = rule.value.rules.flatMap((rule) => checkRule(rule)); + return rule; + case "layer-block": + rule.value.rules = rule.value.rules.flatMap((rule) => checkRule(rule)); + return rule; + case "container": + rule.value.rules = rule.value.rules.flatMap((rule) => checkRule(rule)); + return rule; + } + }); + + return stylesheet; +} + +function replaceDeclarationBlock( + block: DeclarationBlock | undefined, + vars: Map, +) { + if (!block) return; + + block.declarations = block.declarations + ?.map((decl) => { + return replaceDeclaration(decl, vars); + }) + .filter((d) => !!d); + + block.importantDeclarations = block.importantDeclarations + ?.map((decl) => { + return replaceDeclaration(decl, vars); + }) + .filter((d) => !!d); + + return block; +} + +function replaceDeclaration( + declaration: Declaration, + vars: Map, +) { + if ( + declaration.property !== "unparsed" && + declaration.property !== "custom" + ) { + return declaration; + } + + if (declaration.property === "custom" && vars.has(declaration.value.name)) { + return; + } + + declaration.value.value = declaration.value.value.flatMap((part) => { + return flattenPart(part, vars); + }); + + return declaration; +} + +function flattenPart( + part: TokenOrValue, + vars: Map, +): TokenOrValue | TokenOrValue[] { + if (part.type === "var") { + const varInfo = vars.get(part.value.name.ident); + + if (!varInfo) { + part.value.fallback = part.value.fallback?.flatMap((arg) => { + return flattenPart(arg, vars); + }); + + return part; + } else if (varInfo.value === undefined) { + const fallback = part.value.fallback?.flatMap((arg) => { + return flattenPart(arg, vars); + }); + + return fallback ?? []; + } + + return varInfo.value; + } else if (part.type === "function") { + part.value.arguments = part.value.arguments.flatMap((arg) => { + return flattenPart(arg, vars); + }); + } + + return part; +} + +function flattenVar( + name: string, + vars: Map, + seen = new Set(), +) { + if (seen.has(name)) { + vars.delete(name); + } + + seen.add(name); + + let varInfo = vars.get(name); + + if (!varInfo || varInfo.flat) { + return; + } + + let varInfoValue = varInfo.value?.flatMap((part) => { + if (part.type === "var") { + const name = part.value.name.ident; + + flattenVar(name, vars, seen); + + const nestedVarInfo = vars.get(part.value.name.ident); + if (nestedVarInfo?.value) { + return nestedVarInfo.value; + } + } + return flattenPart(part, vars); + }); + + // If the variable is shorthand for "initial", substitute it for undefined + if ( + varInfoValue?.length === 2 && + varInfoValue[0]?.type === "token" && + varInfoValue[0].value.type === "ident" && + varInfoValue[0].value.value === "initial" && + varInfoValue[1]?.type === "token" && + varInfoValue[1].value.type === "white-space" + ) { + varInfoValue = undefined; + } + + varInfo = { + count: 1, + flat: true, + value: varInfoValue, + }; + + vars.set(name, varInfo); +} diff --git a/src/compiler/stylesheet.ts b/src/compiler/stylesheet.ts index a23514a8..8d8d51ef 100644 --- a/src/compiler/stylesheet.ts +++ b/src/compiler/stylesheet.ts @@ -391,6 +391,10 @@ export class StylesheetBuilder { let propPath: string | string[] | undefined = this.mapping[rawProperty] ?? this.mapping[property] ?? this.mapping["*"]; + if (typeof property === "string" && property.includes(".")) { + propPath = property.split("."); + } + if (Array.isArray(propPath)) { const [first, second] = propPath; diff --git a/src/jest/index.ts b/src/jest/index.ts index fd57173b..26beaf6e 100644 --- a/src/jest/index.ts +++ b/src/jest/index.ts @@ -59,12 +59,12 @@ export function compileWithAutoDebug( { debug = debugDefault, ...options - }: CompilerOptions & { debug?: boolean } = {}, + }: CompilerOptions & { debug?: boolean | "verbose" } = {}, ) { const logger = debug ? (text: string) => { // Just log the rules - if (text.startsWith("[")) { + if (text.startsWith("[") && debug === "verbose") { console.log(`Rules:\n---\n${text}`); } } diff --git a/src/runtime/native/styles/calculate-props.ts b/src/runtime/native/styles/calculate-props.ts index c8f53a8f..a24bd77e 100644 --- a/src/runtime/native/styles/calculate-props.ts +++ b/src/runtime/native/styles/calculate-props.ts @@ -115,7 +115,7 @@ export function applyDeclarations( // Dynamic styles let value: any = declaration[0]; let propPath = declaration[1]; - let prop: string; + let prop: string | number; if (Array.isArray(propPath)) { const [first, ...rest] = propPath; @@ -132,7 +132,21 @@ export function applyDeclarations( target = topLevelTarget[first]; } + let previousProp: string | number = first; + let previousTarget = topLevelTarget; + for (prop of rest) { + if (prop.startsWith("[") && prop.endsWith("]")) { + prop = Number(prop.slice(1, -1)); + + if (!Array.isArray(previousTarget[previousProp])) { + previousTarget[previousProp] = []; + target = previousTarget[previousProp]; + } + } + previousTarget = target; + previousProp = prop; + target[prop] ??= {}; target = target[prop]; } diff --git a/src/runtime/native/styles/shorthands/box-shadow.ts b/src/runtime/native/styles/shorthands/box-shadow.ts index fa6c4691..f3f814bb 100644 --- a/src/runtime/native/styles/shorthands/box-shadow.ts +++ b/src/runtime/native/styles/shorthands/box-shadow.ts @@ -10,32 +10,11 @@ const blurRadius = ["blurRadius", "number"] as const; const spreadDistance = ["spreadDistance", "number"] as const; // const inset = ["inset", "string"] as const; -function deepFlattenToArrayOfStyleDescriptors( - arr: StyleDescriptor[], -): StyleDescriptor[] { - const result: StyleDescriptor[] = []; - const stack = [arr]; - while (stack.length > 0) { - const current = stack.pop(); - if (Array.isArray(current)) { - if (current.length > 0 && Array.isArray(current[0])) { - for (let i = current.length - 1; i >= 0; i--) { - const elem = current[i]; - if (isStyleDescriptorArray(elem)) stack.push(elem); - } - } else { - result.push(current); - } - } - } - return result; -} - const handler = shorthandHandler( [ + [offsetX, offsetY, blurRadius, spreadDistance], [offsetX, offsetY, blurRadius, spreadDistance, color], [color, offsetX, offsetY], - [color, offsetX, offsetY, blurRadius], [color, offsetX, offsetY, blurRadius, spreadDistance], [offsetX, offsetY, color], [offsetX, offsetY, blurRadius, color], @@ -55,10 +34,11 @@ export const boxShadow: StyleFunctionResolver = ( if (!isStyleDescriptorArray(args)) { return args; } else { - return deepFlattenToArrayOfStyleDescriptors(args) + return args + .flatMap(flattenShadowDescriptor) .map((shadows) => { if (shadows === undefined) { - return []; + return; } else { return omitTransparentShadows( handler(resolveValue, shadows, get, options), @@ -69,6 +49,16 @@ export const boxShadow: StyleFunctionResolver = ( } }; +function flattenShadowDescriptor(arg: StyleDescriptor): StyleDescriptor[] { + if (isStyleDescriptorArray(arg) && isStyleDescriptorArray(arg[0])) { + return arg.map((arg) => { + return flattenShadowDescriptor(arg); + }); + } + + return [arg]; +} + function omitTransparentShadows(style: unknown) { if (typeof style === "object" && style && "color" in style) { if (style.color === "#0000" || style.color === "transparent") { diff --git a/src/style-collection/root.ts b/src/style-collection/root.ts index 67f28f7c..3c59dd77 100644 --- a/src/style-collection/root.ts +++ b/src/style-collection/root.ts @@ -1,3 +1,5 @@ +import { Platform, PlatformColor } from "react-native"; + import type { StyleDescriptor, VariableValue } from "react-native-css/compiler"; import { testMediaQuery } from "../runtime/native/conditions/media-query"; @@ -35,3 +37,12 @@ export const rootVariables = rootVariableFamily(); export const universalVariables = rootVariableFamily(); rootVariables("__rn-css-rem").set([[14]]); +// eslint-disable-next-line @typescript-eslint/no-unsafe-argument +rootVariables("__rn-css-color").set([ + [ + Platform.OS === "ios" + ? PlatformColor("label", "labelColor") + : PlatformColor("?attr/textColorPrimary", "SystemBaseHighColor"), + ], + // eslint-disable-next-line @typescript-eslint/no-explicit-any +] as any);