Skip to content

Commit fb253e7

Browse files
authored
contrast-color() (#1237)
1 parent b14cc06 commit fb253e7

File tree

15 files changed

+226
-50
lines changed

15 files changed

+226
-50
lines changed

packages/color-helpers/dist/index.cjs

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

packages/color-helpers/dist/index.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,4 +215,4 @@ function gam_2020(t){const _=1.09929682680944;return t.map((function(t){const n=
215215
* @license W3C https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
216216
* @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).
217217
* @see https://github.com/w3c/csswg-drafts/blob/main/css-color-4/deltaEOK.js
218-
*/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};
218+
*/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};

packages/color-helpers/src/calculations/binary-search-gamut.ts

Lines changed: 0 additions & 41 deletions
This file was deleted.
Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,41 @@
1-
import { binarySearchGamut } from '../calculations/binary-search-gamut';
1+
import { clip } from '../utils/clip';
2+
import { OKLCH_to_OKLab } from '../conversions/oklch-to-oklab';
3+
import { deltaEOK } from './delta-EOK';
24
import type { Color } from '../types/color';
5+
import { inGamut } from '../utils/in-gamut';
36

4-
export function mapGamut(startOKLCH: Color, toDestination: (x: Color) => Color, fromDestination: (x: Color) => Color): Color {
5-
return binarySearchGamut(startOKLCH, toDestination, fromDestination);
7+
const JND = 0.02;
8+
const EPSILON = 0.00001;
9+
10+
export function mapGamut(
11+
startOKLCH: Color,
12+
toDestination: (x: Color) => Color,
13+
fromDestination: (x: Color) => Color,
14+
): Color {
15+
16+
const current = startOKLCH;
17+
18+
let min = 0.0;
19+
let max = current[1];
20+
21+
while ((max - min) > EPSILON) {
22+
const chroma = (min + max) / 2.0;
23+
current[1] = chroma;
24+
25+
const converted = toDestination(current);
26+
if (inGamut(converted)) {
27+
min = chroma;
28+
continue;
29+
}
30+
31+
const clipped = clip(converted);
32+
const delta_e = deltaEOK(OKLCH_to_OKLab(fromDestination(clipped)), OKLCH_to_OKLab(current));
33+
if (delta_e < JND) {
34+
return clipped;
35+
}
36+
37+
max = chroma;
38+
}
39+
40+
return clip(toDestination([...current]));
641
}

packages/css-color-parser/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Changes to CSS Color Parser
22

3-
### Unreleased (patch)
3+
### Unreleased (minor)
44

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

78
### 1.5.2

packages/css-color-parser/dist/index.cjs

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

packages/css-color-parser/dist/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ export declare enum SyntaxFlag {
153153
RelativeColorSyntax = "relative-color-syntax",
154154
/** Is a mixed color, e.g. `color-mix(in oklch, red, blue)` */
155155
ColorMix = "color-mix",
156+
/** Is a contrasting color, e.g. `contrast-color()` */
157+
ContrastColor = "contrast-color",
156158
/** Is an experimental color syntax */
157159
Experimental = "experimental"
158160
}

packages/css-color-parser/dist/index.mjs

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

packages/css-color-parser/docs/css-color-parser.api.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,6 +1107,27 @@
11071107
"releaseTag": "Public",
11081108
"name": "ColorMix"
11091109
},
1110+
{
1111+
"kind": "EnumMember",
1112+
"canonicalReference": "@csstools/css-color-parser!SyntaxFlag.ContrastColor:member",
1113+
"docComment": "/**\n * Is a contrasting color, e.g. `contrast-color()`\n */\n",
1114+
"excerptTokens": [
1115+
{
1116+
"kind": "Content",
1117+
"text": "ContrastColor = "
1118+
},
1119+
{
1120+
"kind": "Content",
1121+
"text": "\"contrast-color\""
1122+
}
1123+
],
1124+
"initializerTokenRange": {
1125+
"startIndex": 1,
1126+
"endIndex": 2
1127+
},
1128+
"releaseTag": "Public",
1129+
"name": "ContrastColor"
1130+
},
11101131
{
11111132
"kind": "EnumMember",
11121133
"canonicalReference": "@csstools/css-color-parser!SyntaxFlag.Experimental:member",

packages/css-color-parser/docs/css-color-parser.syntaxflag.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export declare enum SyntaxFlag
1616
| --- | --- | --- |
1717
| ColorKeyword | <code>&quot;color-keyword&quot;</code> | Is a color keyword, e.g. <code>transparent</code>, <code>currentColor</code>, ... |
1818
| ColorMix | <code>&quot;color-mix&quot;</code> | Is a mixed color, e.g. <code>color-mix(in oklch, red, blue)</code> |
19+
| ContrastColor | <code>&quot;contrast-color&quot;</code> | Is a contrasting color, e.g. <code>contrast-color()</code> |
1920
| Experimental | <code>&quot;experimental&quot;</code> | Is an experimental color syntax |
2021
| HasAlpha | <code>&quot;has-alpha&quot;</code> | Has an explicit alpha channel |
2122
| HasDimensionValues | <code>&quot;has-dimension-values&quot;</code> | Has a channel with a dimension value, e.g. <code>50deg</code> |

packages/css-color-parser/src/color-data.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ export enum SyntaxFlag {
6565
RelativeColorSyntax = 'relative-color-syntax',
6666
/** Is a mixed color, e.g. `color-mix(in oklch, red, blue)` */
6767
ColorMix = 'color-mix',
68+
/** Is a contrasting color, e.g. `contrast-color()` */
69+
ContrastColor = 'contrast-color',
6870
/** Is an experimental color syntax */
6971
Experimental = 'experimental',
7072
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import type { ColorData } from '../color-data';
2+
import type { ColorParser } from '../color-parser';
3+
import type { FunctionNode } from '@csstools/css-parser-algorithms';
4+
import { ColorNotation } from '../color-notation';
5+
import { SyntaxFlag, colorData_to_XYZ_D50 } from '../color-data';
6+
import { isCommentNode, isTokenNode, isWhitespaceNode } from '@csstools/css-parser-algorithms';
7+
import { Color } from '@csstools/color-helpers';
8+
import { XYZ_D50_to_sRGB_Gamut } from '../gamut-mapping/srgb';
9+
import { TokenType } from '@csstools/css-tokenizer';
10+
import { toLowerCaseAZ } from '../util/to-lower-case-a-z';
11+
12+
export function contrastColor(colorMixNode: FunctionNode, colorParser: ColorParser): ColorData | false {
13+
let backgroundColorData: ColorData | false = false;
14+
let maxKeyword: boolean = false;
15+
16+
for (let i = 0; i < colorMixNode.value.length; i++) {
17+
const node = colorMixNode.value[i];
18+
if (isWhitespaceNode(node) || isCommentNode(node)) {
19+
continue;
20+
}
21+
22+
if (!backgroundColorData) {
23+
backgroundColorData = colorParser(node);
24+
if (backgroundColorData) {
25+
continue;
26+
}
27+
}
28+
29+
if (backgroundColorData && !maxKeyword) {
30+
if (
31+
isTokenNode(node) &&
32+
node.value[0] === TokenType.Ident &&
33+
toLowerCaseAZ(node.value[4].value) === 'max'
34+
) {
35+
maxKeyword = true;
36+
continue;
37+
}
38+
}
39+
40+
return false;
41+
}
42+
43+
if (!backgroundColorData || !maxKeyword) {
44+
return false;
45+
}
46+
47+
// Treat missing as zero.
48+
backgroundColorData.channels = backgroundColorData.channels.map((x) => Number.isNaN(x) ? 0 : x) as Color;
49+
backgroundColorData.channels = XYZ_D50_to_sRGB_Gamut(colorData_to_XYZ_D50(backgroundColorData).channels);
50+
backgroundColorData.colorNotation = ColorNotation.sRGB;
51+
52+
const colorData: ColorData = {
53+
colorNotation: ColorNotation.sRGB,
54+
channels: [0, 0, 0],
55+
alpha: 1,
56+
syntaxFlags: new Set([SyntaxFlag.ContrastColor, SyntaxFlag.Experimental]),
57+
};
58+
59+
const contrastWhite = contrastRatio(backgroundColorData.channels, [1, 1, 1]);
60+
const contrastBlack = contrastRatio(backgroundColorData.channels, [0, 0, 0]);
61+
62+
if (contrastWhite > contrastBlack) {
63+
colorData.channels = [1, 1, 1];
64+
} else {
65+
colorData.channels = [0, 0, 0];
66+
}
67+
68+
return colorData;
69+
}
70+
71+
function luminance(color: Color): number {
72+
// https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
73+
const [lumR, lumG, lumB] = color.map(component => {
74+
return component <= 0.03928
75+
? component / 12.92
76+
: Math.pow((component + 0.055) / 1.055, 2.4);
77+
});
78+
79+
return 0.2126 * lumR + 0.7152 * lumG + 0.0722 * lumB;
80+
}
81+
82+
function contrastRatio(color1: Color, color2: Color): number {
83+
// https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
84+
const color1luminance = luminance(color1);
85+
const color2luminance = luminance(color2);
86+
87+
const l1 = Math.max(color1luminance, color2luminance);
88+
const l2 = Math.min(color1luminance, color2luminance);
89+
90+
return (l1 + 0.05) / (l2 + 0.05);
91+
}

packages/css-color-parser/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { oklab } from './functions/oklab';
1515
import { oklch } from './functions/oklch';
1616
import { rgb } from './functions/rgb';
1717
import { toLowerCaseAZ } from './util/to-lower-case-a-z';
18+
import { contrastColor } from './functions/constrast-color';
1819

1920
export type { ColorData } from './color-data';
2021
export { ColorNotation } from './color-notation';
@@ -56,6 +57,8 @@ export function color(colorNode: ComponentValue): ColorData | false {
5657
return colorFn(colorNode, color);
5758
case 'color-mix':
5859
return colorMix(colorNode, color);
60+
case 'contrast-color':
61+
return contrastColor(colorNode, color);
5962
}
6063
}
6164

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { color } from '@csstools/css-color-parser';
2+
import assert from 'assert';
3+
import { parse } from '../util/parse.mjs';
4+
import { serialize_sRGB_data } from '../util/serialize.mjs';
5+
6+
const tests = [
7+
['contrast-color( black max )', 'rgb(255, 255, 255)'],
8+
['contrast-color(#333/* */max/* */)', 'rgb(255, 255, 255)'],
9+
['contrast-color(grey max)', 'rgb(0, 0, 0)'],
10+
['contrast-color(#ccc max)', 'rgb(0, 0, 0)'],
11+
['contrast-color(white max)', 'rgb(0, 0, 0)'],
12+
['contrast-color(#1234b0 max)', 'rgb(255, 255, 255)'],
13+
['contrast-color(#b012a0 max)', 'rgb(255, 255, 255)'],
14+
15+
['contrast-color(rgb(0 0 0) max)', 'rgb(255, 255, 255)'],
16+
['contrast-color(color(srgb 0 0 0) max)', 'rgb(255, 255, 255)'],
17+
['contrast-color(color(display-p3 0 0 0) max)', 'rgb(255, 255, 255)'],
18+
['contrast-color(rgb(255 255 255) max)', 'rgb(0, 0, 0)'],
19+
['contrast-color(color(srgb 1 1 1) max)', 'rgb(0, 0, 0)'],
20+
['contrast-color(color(display-p3 1 1 1) max)', 'rgb(0, 0, 0)'],
21+
22+
['contrast-color(rgb(0 0 0 / 0) max)', 'rgb(255, 255, 255)'],
23+
['contrast-color(rgb(0 0 0 / 0.5) max)', 'rgb(255, 255, 255)'],
24+
['contrast-color(rgb(255 255 255 / 0) max)', 'rgb(0, 0, 0)'],
25+
['contrast-color(rgb(255 255 255 / 0.5) max)', 'rgb(0, 0, 0)'],
26+
27+
['contrast-color(contrast-color(#b012a0 max) max)', 'rgb(0, 0, 0)'],
28+
29+
['contrast-color(#3b9595 max)', 'rgb(0, 0, 0)'],
30+
['contrast-color(contrast-color(contrast-color(#3b9595 max) max) max)', 'rgb(0, 0, 0)'],
31+
32+
// ignore
33+
['contrast-color( black )', ''],
34+
['contrast-color( black min )', ''],
35+
];
36+
37+
for (const test of tests) {
38+
assert.deepStrictEqual(
39+
serialize_sRGB_data(
40+
color(
41+
parse(test[0]),
42+
{ flags: { experimentalContrastColorFunction: true } },
43+
),
44+
),
45+
test[1],
46+
`"${test[0]}" : ${test[1]}`,
47+
);
48+
}
49+
50+
{
51+
[
52+
'contrast-color(black max)',
53+
'color-mix(in srgb, contrast-color(black max), contrast-color(white max))',
54+
'rgb(from contrast-color(black max) r g b)',
55+
].forEach((testCase) => {
56+
assert.ok(
57+
color(parse(testCase)).syntaxFlags.has('experimental'),
58+
);
59+
});
60+
}

packages/css-color-parser/test/test.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import './basic/basic.mjs';
22
import './basic/color-function.mjs';
33
import './basic/color-mix-function-oklch.mjs';
44
import './basic/color-mix-function.mjs';
5+
import './basic/contrast-color-function.mjs';
56
import './basic/hwb.mjs';
67
import './basic/invalid.mjs';
78
import './basic/keywords.mjs';

0 commit comments

Comments
 (0)