Skip to content

contrast-color() #1237

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/color-helpers/dist/index.cjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/color-helpers/dist/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -215,4 +215,4 @@ function gam_2020(t){const _=1.09929682680944;return t.map((function(t){const n=
* @license W3C https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
* @copyright This software or document includes material copied from or derived from https://github.com/w3c/csswg-drafts/blob/main/css-color-4/deltaEOK.js. Copyright © 2022 W3C® (MIT, ERCIM, Keio, Beihang).
* @see https://github.com/w3c/csswg-drafts/blob/main/css-color-4/deltaEOK.js
*/function deltaEOK(t,_){const[n,o,e]=t,[r,a,i]=_,l=n-r,u=o-a,c=e-i;return Math.sqrt(l**2+u**2+c**2)}const _=.02,n=1e-5;function binarySearchGamut(t,o,e){const r=t;let a=0,i=r[1];for(;i-a>n;){const t=(a+i)/2;r[1]=t;const n=o(r);if(inGamut(n)){a=t;continue}const l=clip(n);if(deltaEOK(OKLCH_to_OKLab(e(l)),OKLCH_to_OKLab(r))<_)return l;i=t}return clip(o([...r]))}function mapGamut(t,_,n){return binarySearchGamut(t,_,n)}const o={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]};export{HSL_to_XYZ_D50,HWB_to_XYZ_D50,LCH_to_XYZ_D50,Lab_to_XYZ_D50,OKLCH_to_OKLab,OKLCH_to_XYZ_D50,OKLab_to_OKLCH,OKLab_to_XYZ,OKLab_to_XYZ_D50,P3_to_XYZ_D50,ProPhoto_RGB_to_XYZ_D50,XYZ_D50_to_HSL,XYZ_D50_to_HWB,XYZ_D50_to_LCH,XYZ_D50_to_Lab,XYZ_D50_to_OKLCH,XYZ_D50_to_OKLab,XYZ_D50_to_P3,XYZ_D50_to_ProPhoto,XYZ_D50_to_XYZ_D50,XYZ_D50_to_XYZ_D65,XYZ_D50_to_a98_RGB,XYZ_D50_to_lin_sRGB,XYZ_D50_to_rec_2020,XYZ_D50_to_sRGB,XYZ_D65_to_XYZ_D50,XYZ_to_OKLab,XYZ_to_lin_P3,XYZ_to_lin_sRGB,a98_RGB_to_XYZ_D50,clip,gam_P3,gam_sRGB,inGamut,lin_P3,lin_P3_to_XYZ,lin_sRGB,lin_sRGB_to_XYZ,lin_sRGB_to_XYZ_D50,mapGamut,o as namedColors,rec_2020_to_XYZ_D50,sRGB_to_XYZ_D50};
*/function deltaEOK(t,_){const[n,o,e]=t,[r,a,i]=_,l=n-r,u=o-a,s=e-i;return Math.sqrt(l**2+u**2+s**2)}function mapGamut(t,_,n){const o=t;let e=0,r=o[1];for(;r-e>1e-5;){const t=(e+r)/2;o[1]=t;const a=_(o);if(inGamut(a)){e=t;continue}const i=clip(a);if(deltaEOK(OKLCH_to_OKLab(n(i)),OKLCH_to_OKLab(o))<.02)return i;r=t}return clip(_([...o]))}const _={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]};export{HSL_to_XYZ_D50,HWB_to_XYZ_D50,LCH_to_XYZ_D50,Lab_to_XYZ_D50,OKLCH_to_OKLab,OKLCH_to_XYZ_D50,OKLab_to_OKLCH,OKLab_to_XYZ,OKLab_to_XYZ_D50,P3_to_XYZ_D50,ProPhoto_RGB_to_XYZ_D50,XYZ_D50_to_HSL,XYZ_D50_to_HWB,XYZ_D50_to_LCH,XYZ_D50_to_Lab,XYZ_D50_to_OKLCH,XYZ_D50_to_OKLab,XYZ_D50_to_P3,XYZ_D50_to_ProPhoto,XYZ_D50_to_XYZ_D50,XYZ_D50_to_XYZ_D65,XYZ_D50_to_a98_RGB,XYZ_D50_to_lin_sRGB,XYZ_D50_to_rec_2020,XYZ_D50_to_sRGB,XYZ_D65_to_XYZ_D50,XYZ_to_OKLab,XYZ_to_lin_P3,XYZ_to_lin_sRGB,a98_RGB_to_XYZ_D50,clip,gam_P3,gam_sRGB,inGamut,lin_P3,lin_P3_to_XYZ,lin_sRGB,lin_sRGB_to_XYZ,lin_sRGB_to_XYZ_D50,mapGamut,_ as namedColors,rec_2020_to_XYZ_D50,sRGB_to_XYZ_D50};
41 changes: 0 additions & 41 deletions packages/color-helpers/src/calculations/binary-search-gamut.ts

This file was deleted.

41 changes: 38 additions & 3 deletions packages/color-helpers/src/calculations/map-gamut.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,41 @@
import { binarySearchGamut } from '../calculations/binary-search-gamut';
import { clip } from '../utils/clip';
import { OKLCH_to_OKLab } from '../conversions/oklch-to-oklab';
import { deltaEOK } from './delta-EOK';
import type { Color } from '../types/color';
import { inGamut } from '../utils/in-gamut';

export function mapGamut(startOKLCH: Color, toDestination: (x: Color) => Color, fromDestination: (x: Color) => Color): Color {
return binarySearchGamut(startOKLCH, toDestination, fromDestination);
const JND = 0.02;
const EPSILON = 0.00001;

export function mapGamut(
startOKLCH: Color,
toDestination: (x: Color) => Color,
fromDestination: (x: Color) => Color,
): Color {

const current = startOKLCH;

let min = 0.0;
let max = current[1];

while ((max - min) > EPSILON) {
const chroma = (min + max) / 2.0;
current[1] = chroma;

const converted = toDestination(current);
if (inGamut(converted)) {
min = chroma;
continue;
}

const clipped = clip(converted);
const delta_e = deltaEOK(OKLCH_to_OKLab(fromDestination(clipped)), OKLCH_to_OKLab(current));
if (delta_e < JND) {
return clipped;
}

max = chroma;
}

return clip(toDestination([...current]));
}
3 changes: 2 additions & 1 deletion packages/css-color-parser/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Changes to CSS Color Parser

### Unreleased (patch)
### Unreleased (minor)

- Add support for `contrast-color( <color> max )`
- Allow relative color syntax in `rgba` and `hsla` color notations.

### 1.5.2
Expand Down
2 changes: 1 addition & 1 deletion packages/css-color-parser/dist/index.cjs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions packages/css-color-parser/dist/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ export declare enum SyntaxFlag {
RelativeColorSyntax = "relative-color-syntax",
/** Is a mixed color, e.g. `color-mix(in oklch, red, blue)` */
ColorMix = "color-mix",
/** Is a contrasting color, e.g. `contrast-color()` */
ContrastColor = "contrast-color",
/** Is an experimental color syntax */
Experimental = "experimental"
}
Expand Down
2 changes: 1 addition & 1 deletion packages/css-color-parser/dist/index.mjs

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions packages/css-color-parser/docs/css-color-parser.api.json
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,27 @@
"releaseTag": "Public",
"name": "ColorMix"
},
{
"kind": "EnumMember",
"canonicalReference": "@csstools/css-color-parser!SyntaxFlag.ContrastColor:member",
"docComment": "/**\n * Is a contrasting color, e.g. `contrast-color()`\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "ContrastColor = "
},
{
"kind": "Content",
"text": "\"contrast-color\""
}
],
"initializerTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"releaseTag": "Public",
"name": "ContrastColor"
},
{
"kind": "EnumMember",
"canonicalReference": "@csstools/css-color-parser!SyntaxFlag.Experimental:member",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export declare enum SyntaxFlag
| --- | --- | --- |
| ColorKeyword | <code>&quot;color-keyword&quot;</code> | Is a color keyword, e.g. <code>transparent</code>, <code>currentColor</code>, ... |
| ColorMix | <code>&quot;color-mix&quot;</code> | Is a mixed color, e.g. <code>color-mix(in oklch, red, blue)</code> |
| ContrastColor | <code>&quot;contrast-color&quot;</code> | Is a contrasting color, e.g. <code>contrast-color()</code> |
| Experimental | <code>&quot;experimental&quot;</code> | Is an experimental color syntax |
| HasAlpha | <code>&quot;has-alpha&quot;</code> | Has an explicit alpha channel |
| HasDimensionValues | <code>&quot;has-dimension-values&quot;</code> | Has a channel with a dimension value, e.g. <code>50deg</code> |
Expand Down
2 changes: 2 additions & 0 deletions packages/css-color-parser/src/color-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ export enum SyntaxFlag {
RelativeColorSyntax = 'relative-color-syntax',
/** Is a mixed color, e.g. `color-mix(in oklch, red, blue)` */
ColorMix = 'color-mix',
/** Is a contrasting color, e.g. `contrast-color()` */
ContrastColor = 'contrast-color',
/** Is an experimental color syntax */
Experimental = 'experimental',
}
Expand Down
91 changes: 91 additions & 0 deletions packages/css-color-parser/src/functions/constrast-color.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import type { ColorData } from '../color-data';
import type { ColorParser } from '../color-parser';
import type { FunctionNode } from '@csstools/css-parser-algorithms';
import { ColorNotation } from '../color-notation';
import { SyntaxFlag, colorData_to_XYZ_D50 } from '../color-data';
import { isCommentNode, isTokenNode, isWhitespaceNode } from '@csstools/css-parser-algorithms';
import { Color } from '@csstools/color-helpers';
import { XYZ_D50_to_sRGB_Gamut } from '../gamut-mapping/srgb';
import { TokenType } from '@csstools/css-tokenizer';
import { toLowerCaseAZ } from '../util/to-lower-case-a-z';

export function contrastColor(colorMixNode: FunctionNode, colorParser: ColorParser): ColorData | false {
let backgroundColorData: ColorData | false = false;
let maxKeyword: boolean = false;

for (let i = 0; i < colorMixNode.value.length; i++) {
const node = colorMixNode.value[i];
if (isWhitespaceNode(node) || isCommentNode(node)) {
continue;
}

if (!backgroundColorData) {
backgroundColorData = colorParser(node);
if (backgroundColorData) {
continue;
}
}

if (backgroundColorData && !maxKeyword) {
if (
isTokenNode(node) &&
node.value[0] === TokenType.Ident &&
toLowerCaseAZ(node.value[4].value) === 'max'
) {
maxKeyword = true;
continue;
}
}

return false;
}

if (!backgroundColorData || !maxKeyword) {
return false;
}

// Treat missing as zero.
backgroundColorData.channels = backgroundColorData.channels.map((x) => Number.isNaN(x) ? 0 : x) as Color;
backgroundColorData.channels = XYZ_D50_to_sRGB_Gamut(colorData_to_XYZ_D50(backgroundColorData).channels);
backgroundColorData.colorNotation = ColorNotation.sRGB;

const colorData: ColorData = {
colorNotation: ColorNotation.sRGB,
channels: [0, 0, 0],
alpha: 1,
syntaxFlags: new Set([SyntaxFlag.ContrastColor, SyntaxFlag.Experimental]),
};

const contrastWhite = contrastRatio(backgroundColorData.channels, [1, 1, 1]);
const contrastBlack = contrastRatio(backgroundColorData.channels, [0, 0, 0]);

if (contrastWhite > contrastBlack) {
colorData.channels = [1, 1, 1];
} else {
colorData.channels = [0, 0, 0];
}

return colorData;
}

function luminance(color: Color): number {
// https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
const [lumR, lumG, lumB] = color.map(component => {
return component <= 0.03928
? component / 12.92
: Math.pow((component + 0.055) / 1.055, 2.4);
});

return 0.2126 * lumR + 0.7152 * lumG + 0.0722 * lumB;
}

function contrastRatio(color1: Color, color2: Color): number {
// https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
const color1luminance = luminance(color1);
const color2luminance = luminance(color2);

const l1 = Math.max(color1luminance, color2luminance);
const l2 = Math.min(color1luminance, color2luminance);

return (l1 + 0.05) / (l2 + 0.05);
}
3 changes: 3 additions & 0 deletions packages/css-color-parser/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { oklab } from './functions/oklab';
import { oklch } from './functions/oklch';
import { rgb } from './functions/rgb';
import { toLowerCaseAZ } from './util/to-lower-case-a-z';
import { contrastColor } from './functions/constrast-color';

export type { ColorData } from './color-data';
export { ColorNotation } from './color-notation';
Expand Down Expand Up @@ -56,6 +57,8 @@ export function color(colorNode: ComponentValue): ColorData | false {
return colorFn(colorNode, color);
case 'color-mix':
return colorMix(colorNode, color);
case 'contrast-color':
return contrastColor(colorNode, color);
}
}

Expand Down
60 changes: 60 additions & 0 deletions packages/css-color-parser/test/basic/contrast-color-function.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { color } from '@csstools/css-color-parser';
import assert from 'assert';
import { parse } from '../util/parse.mjs';
import { serialize_sRGB_data } from '../util/serialize.mjs';

const tests = [
['contrast-color( black max )', 'rgb(255, 255, 255)'],
['contrast-color(#333/* */max/* */)', 'rgb(255, 255, 255)'],
['contrast-color(grey max)', 'rgb(0, 0, 0)'],
['contrast-color(#ccc max)', 'rgb(0, 0, 0)'],
['contrast-color(white max)', 'rgb(0, 0, 0)'],
['contrast-color(#1234b0 max)', 'rgb(255, 255, 255)'],
['contrast-color(#b012a0 max)', 'rgb(255, 255, 255)'],

['contrast-color(rgb(0 0 0) max)', 'rgb(255, 255, 255)'],
['contrast-color(color(srgb 0 0 0) max)', 'rgb(255, 255, 255)'],
['contrast-color(color(display-p3 0 0 0) max)', 'rgb(255, 255, 255)'],
['contrast-color(rgb(255 255 255) max)', 'rgb(0, 0, 0)'],
['contrast-color(color(srgb 1 1 1) max)', 'rgb(0, 0, 0)'],
['contrast-color(color(display-p3 1 1 1) max)', 'rgb(0, 0, 0)'],

['contrast-color(rgb(0 0 0 / 0) max)', 'rgb(255, 255, 255)'],
['contrast-color(rgb(0 0 0 / 0.5) max)', 'rgb(255, 255, 255)'],
['contrast-color(rgb(255 255 255 / 0) max)', 'rgb(0, 0, 0)'],
['contrast-color(rgb(255 255 255 / 0.5) max)', 'rgb(0, 0, 0)'],

['contrast-color(contrast-color(#b012a0 max) max)', 'rgb(0, 0, 0)'],

['contrast-color(#3b9595 max)', 'rgb(0, 0, 0)'],
['contrast-color(contrast-color(contrast-color(#3b9595 max) max) max)', 'rgb(0, 0, 0)'],

// ignore
['contrast-color( black )', ''],
['contrast-color( black min )', ''],
];

for (const test of tests) {
assert.deepStrictEqual(
serialize_sRGB_data(
color(
parse(test[0]),
{ flags: { experimentalContrastColorFunction: true } },
),
),
test[1],
`"${test[0]}" : ${test[1]}`,
);
}

{
[
'contrast-color(black max)',
'color-mix(in srgb, contrast-color(black max), contrast-color(white max))',
'rgb(from contrast-color(black max) r g b)',
].forEach((testCase) => {
assert.ok(
color(parse(testCase)).syntaxFlags.has('experimental'),
);
});
}
1 change: 1 addition & 0 deletions packages/css-color-parser/test/test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import './basic/basic.mjs';
import './basic/color-function.mjs';
import './basic/color-mix-function-oklch.mjs';
import './basic/color-mix-function.mjs';
import './basic/contrast-color-function.mjs';
import './basic/hwb.mjs';
import './basic/invalid.mjs';
import './basic/keywords.mjs';
Expand Down