Skip to content

Commit 08dfca1

Browse files
fix: coerce NaN Lab/OKLab channels to 0 in parseColor
color-mix() resolved at compile time by lightningcss can yield NaN chromaticity channels for degenerate mixes (e.g. mixing black with transparent in oklab). Passing NaN to colorjs.io produced an invalid color string like "#NaNNaNNaN80" that React Native silently discards, so utilities such as Tailwind's bg-black/50 rendered with no background. Treat NaN channels as 0 (a missing component per CSS Color 4) for the lab/lch/oklab/oklch cases, so the example resolves to #80. Fixes #317
1 parent 5b7ad9d commit 08dfca1

2 files changed

Lines changed: 53 additions & 4 deletions

File tree

src/__tests__/native/color-mix.test.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,24 @@ test("color-mix() - oklch", () => {
4545
backgroundColor: "rgba(231, 0, 11, 0.5)",
4646
});
4747
});
48+
49+
test("color-mix() - black with transparent (NaN oklab channels)", () => {
50+
// lightningcss resolves this at compile time to oklab(0 NaN NaN / 0.5):
51+
// black is oklab [l=0, a=0, b=0] and transparent has no chromaticity, so the
52+
// a/b channels degenerate to NaN. Without coercing NaN to 0 the color
53+
// serializes to "#NaNNaNNaN80", which React Native silently discards.
54+
// This is what Tailwind's `bg-black/50` compiles to.
55+
registerCSS(
56+
`.test {
57+
background-color: color-mix(in oklab, #000 50%, transparent);
58+
}
59+
`,
60+
);
61+
62+
render(<View testID={testID} className="test" />);
63+
const component = screen.getByTestId(testID);
64+
65+
expect(component.props.style).toStrictEqual({
66+
backgroundColor: "#00000080",
67+
});
68+
});

src/compiler/declarations.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1612,6 +1612,18 @@ export function parseColorDeclaration(
16121612
);
16131613
}
16141614

1615+
/**
1616+
* Lab/LCH/OKLab/OKLCH channels can be `NaN` when lightningcss resolves a
1617+
* degenerate `color-mix()` at compile time (e.g. mixing black with
1618+
* `transparent` in oklab yields `NaN` for the a/b chromaticity channels).
1619+
* Passing `NaN` to colorjs.io produces an invalid string such as
1620+
* `#NaNNaNNaN80`, which React Native silently discards. Per CSS Color 4 a
1621+
* missing component is treated as `0`, so coerce `NaN` to `0`.
1622+
*/
1623+
function nanToZero(value: number): number {
1624+
return Number.isNaN(value) ? 0 : value;
1625+
}
1626+
16151627
export function parseColor(cssColor: CssColor, builder: StylesheetBuilder) {
16161628
if (typeof cssColor === "string") {
16171629
if (namedColors.has(cssColor)) {
@@ -1666,28 +1678,44 @@ export function parseColor(cssColor: CssColor, builder: StylesheetBuilder) {
16661678
case "lab":
16671679
color = new Color({
16681680
space: cssColor.type,
1669-
coords: [cssColor.l, cssColor.a, cssColor.b],
1681+
coords: [
1682+
nanToZero(cssColor.l),
1683+
nanToZero(cssColor.a),
1684+
nanToZero(cssColor.b),
1685+
],
16701686
alpha: cssColor.alpha,
16711687
});
16721688
break;
16731689
case "lch":
16741690
color = new Color({
16751691
space: cssColor.type,
1676-
coords: [cssColor.l, cssColor.c, cssColor.h],
1692+
coords: [
1693+
nanToZero(cssColor.l),
1694+
nanToZero(cssColor.c),
1695+
nanToZero(cssColor.h),
1696+
],
16771697
alpha: cssColor.alpha,
16781698
});
16791699
break;
16801700
case "oklab":
16811701
color = new Color({
16821702
space: cssColor.type,
1683-
coords: [cssColor.l, cssColor.a, cssColor.b],
1703+
coords: [
1704+
nanToZero(cssColor.l),
1705+
nanToZero(cssColor.a),
1706+
nanToZero(cssColor.b),
1707+
],
16841708
alpha: cssColor.alpha,
16851709
});
16861710
break;
16871711
case "oklch":
16881712
color = new Color({
16891713
space: cssColor.type,
1690-
coords: [cssColor.l, cssColor.c, cssColor.h],
1714+
coords: [
1715+
nanToZero(cssColor.l),
1716+
nanToZero(cssColor.c),
1717+
nanToZero(cssColor.h),
1718+
],
16911719
alpha: cssColor.alpha,
16921720
});
16931721
break;

0 commit comments

Comments
 (0)