diff --git a/src/__tests__/native/color-mix.test.tsx b/src/__tests__/native/color-mix.test.tsx index bed3cea..f581d1f 100644 --- a/src/__tests__/native/color-mix.test.tsx +++ b/src/__tests__/native/color-mix.test.tsx @@ -45,3 +45,24 @@ test("color-mix() - oklch", () => { backgroundColor: "rgba(231, 0, 11, 0.5)", }); }); + +test("color-mix() - black with transparent (NaN oklab channels)", () => { + // lightningcss resolves this at compile time to oklab(0 NaN NaN / 0.5): + // black is oklab [l=0, a=0, b=0] and transparent has no chromaticity, so the + // a/b channels degenerate to NaN. Without coercing NaN to 0 the color + // serializes to "#NaNNaNNaN80", which React Native silently discards. + // This is what Tailwind's `bg-black/50` compiles to. + registerCSS( + `.test { + background-color: color-mix(in oklab, #000 50%, transparent); + } + `, + ); + + render(); + const component = screen.getByTestId(testID); + + expect(component.props.style).toStrictEqual({ + backgroundColor: "#00000080", + }); +}); diff --git a/src/compiler/declarations.ts b/src/compiler/declarations.ts index 4031778..1301364 100644 --- a/src/compiler/declarations.ts +++ b/src/compiler/declarations.ts @@ -1612,6 +1612,18 @@ export function parseColorDeclaration( ); } +/** + * Lab/LCH/OKLab/OKLCH channels can be `NaN` when lightningcss resolves a + * degenerate `color-mix()` at compile time (e.g. mixing black with + * `transparent` in oklab yields `NaN` for the a/b chromaticity channels). + * Passing `NaN` to colorjs.io produces an invalid string such as + * `#NaNNaNNaN80`, which React Native silently discards. Per CSS Color 4 a + * missing component is treated as `0`, so coerce `NaN` to `0`. + */ +function nanToZero(value: number): number { + return Number.isNaN(value) ? 0 : value; +} + export function parseColor(cssColor: CssColor, builder: StylesheetBuilder) { if (typeof cssColor === "string") { if (namedColors.has(cssColor)) { @@ -1666,28 +1678,44 @@ export function parseColor(cssColor: CssColor, builder: StylesheetBuilder) { case "lab": color = new Color({ space: cssColor.type, - coords: [cssColor.l, cssColor.a, cssColor.b], + coords: [ + nanToZero(cssColor.l), + nanToZero(cssColor.a), + nanToZero(cssColor.b), + ], alpha: cssColor.alpha, }); break; case "lch": color = new Color({ space: cssColor.type, - coords: [cssColor.l, cssColor.c, cssColor.h], + coords: [ + nanToZero(cssColor.l), + nanToZero(cssColor.c), + nanToZero(cssColor.h), + ], alpha: cssColor.alpha, }); break; case "oklab": color = new Color({ space: cssColor.type, - coords: [cssColor.l, cssColor.a, cssColor.b], + coords: [ + nanToZero(cssColor.l), + nanToZero(cssColor.a), + nanToZero(cssColor.b), + ], alpha: cssColor.alpha, }); break; case "oklch": color = new Color({ space: cssColor.type, - coords: [cssColor.l, cssColor.c, cssColor.h], + coords: [ + nanToZero(cssColor.l), + nanToZero(cssColor.c), + nanToZero(cssColor.h), + ], alpha: cssColor.alpha, }); break;