Skip to content
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: 4 additions & 0 deletions packages/css-color-parser/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changes to CSS Color Parser

### Unreleased (patch)

- Drop the `max` keyword for `contrast-color( <color> )`

### 3.0.8

_February 23, 2025_
Expand Down
2 changes: 1 addition & 1 deletion packages/css-color-parser/dist/index.cjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/css-color-parser/dist/index.mjs

Large diffs are not rendered by default.

18 changes: 2 additions & 16 deletions packages/css-color-parser/src/functions/contrast-color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,12 @@ 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, convertNaNToZero } from '../color-data';
import { isCommentNode, isTokenNode, isWhitespaceNode } from '@csstools/css-parser-algorithms';
import { isCommentNode, isWhitespaceNode } from '@csstools/css-parser-algorithms';
import { XYZ_D50_to_sRGB_Gamut } from '../gamut-mapping/srgb';
import { isTokenIdent } from '@csstools/css-tokenizer';
import { toLowerCaseAZ } from '../util/to-lower-case-a-z';
import { contrast_ratio_wcag_2_1 } from '@csstools/color-helpers';

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];
Expand All @@ -26,21 +23,10 @@ export function contrastColor(colorMixNode: FunctionNode, colorParser: ColorPars
}
}

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

return false;
}

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

Expand Down
58 changes: 29 additions & 29 deletions packages/css-color-parser/test/basic/contrast-color-function.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,34 @@ 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)'],
['contrast-color( black )', 'rgb(255, 255, 255)'],
['contrast-color(#333/* */)', 'rgb(255, 255, 255)'],
['contrast-color(grey)', 'rgb(0, 0, 0)'],
['contrast-color(#ccc)', 'rgb(0, 0, 0)'],
['contrast-color(white)', 'rgb(0, 0, 0)'],
['contrast-color(#1234b0)', 'rgb(255, 255, 255)'],
['contrast-color(#b012a0)', 'rgb(255, 255, 255)'],

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

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

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

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

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

for (const test of tests) {
Expand All @@ -49,9 +49,9 @@ for (const test of tests) {

{
[
'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)',
'contrast-color(black)',
'color-mix(in srgb, contrast-color(black), contrast-color(white))',
'rgb(from contrast-color(black) r g b)',
].forEach((testCase) => {
assert.ok(
color(parse(testCase)).syntaxFlags.has('experimental'),
Expand Down
4 changes: 4 additions & 0 deletions plugins/postcss-contrast-color-function/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changes to PostCSS Contrast Color Function

### Unreleased (patch)

- Drop the `max` keyword for `contrast-color( <color> )`

### 2.0.8

_February 23, 2025_
Expand Down
41 changes: 5 additions & 36 deletions plugins/postcss-contrast-color-function/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,15 @@ npm install @csstools/postcss-contrast-color-function --save-dev
[PostCSS Contrast Color Function] lets you dynamically specify a text color with adequate contrast following the [CSS Color 5 Specification].

```css
.dynamic {
.color {
color: contrast-color(oklch(82% 0.2 330));
}

.max {
color: contrast-color(oklch(30% 0.2 79) max);
}

/* becomes */

.dynamic {
color: color(display-p3 0.15433 0 0.15992);
color: contrast-color(oklch(82% 0.2 330));
}@supports not (color: contrast-color(red max)) {@media (prefers-contrast: more) {.dynamic {
.color {
color: rgb(0, 0, 0);
}
}
}@supports not (color: contrast-color(red max)) {@media (prefers-contrast: less) {.dynamic {
color: color(display-p3 0.2925 0 0.30177);
}
}
}

.max {
color: rgb(255, 255, 255);
color: contrast-color(oklch(30% 0.2 79) max);
color: contrast-color(oklch(82% 0.2 330));
}
```

Expand Down Expand Up @@ -80,29 +63,15 @@ postcssContrastColorFunction({ preserve: false })
```

```css
.dynamic {
.color {
color: contrast-color(oklch(82% 0.2 330));
}

.max {
color: contrast-color(oklch(30% 0.2 79) max);
}

/* becomes */

.dynamic {
color: color(display-p3 0.15433 0 0.15992);
}@media (prefers-contrast: more) {.dynamic {
.color {
color: rgb(0, 0, 0);
}
}@media (prefers-contrast: less) {.dynamic {
color: color(display-p3 0.2925 0 0.30177);
}
}

.max {
color: rgb(255, 255, 255);
}
```

[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test
Expand Down
2 changes: 1 addition & 1 deletion plugins/postcss-contrast-color-function/dist/index.cjs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
"use strict";var e=require("@csstools/postcss-progressive-custom-properties"),o=require("@csstools/utilities"),r=require("@csstools/css-tokenizer"),s=require("@csstools/css-parser-algorithms"),t=require("@csstools/css-color-parser"),n=require("@csstools/color-helpers");const a=/^contrast-color$/i;function parseContrastColor(e){if(!s.isFunctionNode(e)||!a.test(e.getName()))return!1;const o=e.value.filter((e=>!s.isWhitespaceNode(e)&&!s.isCommentNode(e)));if(o.length>2)return!1;const t=o[0],n=o[1];return!!t&&(n?!(!s.isTokenNode(n)||!r.isTokenIdent(n.value)||"max"!==n.value[4].value.toLowerCase())&&[t,"max"]:[t])}var c;!function(e){e[e.MORE=0]="MORE",e[e.LESS=1]="LESS",e[e.NO_PREFERENCE=2]="NO_PREFERENCE"}(c||(c={}));const l=/\bcontrast-color\(/i;function transformContrastColor(e,o,a=0){const i=s.replaceComponentValues(s.parseCommaSeparatedListOfComponentValues(r.tokenize({css:e})),(e=>{const a=parseContrastColor(e);if(!a)return;const[l,i]=a;if("max"===i){const o=t.color(e);if(!o)return;return t.serializeRGB(o,!0)}if(i)return;const u=t.color(new s.FunctionNode([r.TokenType.Function,"contrast-color(",-1,-1,{value:"contrast-color"}],[r.TokenType.CloseParen,")",-1,-1,void 0],[l,new s.TokenNode([r.TokenType.Ident,"max",-1,-1,{value:"max"}])]));if(!u)return;if(o===c.MORE)return t.serializeRGB(u,!0);const p=t.color(l);if(!p)return;let f=0;const m=t.color(t.serializeRGB(p,!0));if(!m)return;{const e=n.contrast_ratio_wcag_2_1(m.channels,[0,0,0]),r=n.contrast_ratio_wcag_2_1(m.channels,[1,1,1]);f=o===c.LESS?e>=r?.3:.9:e>=r?.2:.95}const v=t.color(new s.FunctionNode([r.TokenType.Function,"oklch(",-1,-1,{value:"oklch"}],[r.TokenType.CloseParen,")",-1,-1,void 0],[new s.TokenNode([r.TokenType.Ident,"from",-1,-1,{value:"from"}]),l,new s.TokenNode([r.TokenType.Number,f.toString(),-1,-1,{value:f,type:r.NumberType.Number}]),new s.TokenNode([r.TokenType.Ident,"c",-1,-1,{value:"c"}]),new s.TokenNode([r.TokenType.Ident,"h",-1,-1,{value:"h"}])]));if(!v)return;const d=t.color(t.serializeRGB(v,!0));if(!d)return;return n.contrast_ratio_wcag_2_1(m.channels,d.channels)<4.5?t.serializeRGB(u,!0):t.serializeP3(v,!0)})),u=s.stringify(i);return u===e?e:a>10?u:l.test(u)?transformContrastColor(u,o,a+1):u}const basePlugin=e=>({postcssPlugin:"postcss-contrast-color-function",prepare:()=>({postcssPlugin:"postcss-contrast-color-function",Declaration(r,{atRule:s}){const t=r.parent;if(!t)return;if(!l.test(r.value))return;if(o.hasFallback(r))return;if(o.hasSupportsAtRuleAncestor(r,l))return;const n=transformContrastColor(r.value,c.NO_PREFERENCE);if(n===r.value)return;const a=transformContrastColor(r.value,c.LESS);if(a===r.value)return;const i=transformContrastColor(r.value,c.MORE);if(i!==r.value){if(r.cloneBefore({value:n}),n!==a){const o=t.clone();o.removeAll(),o.append(r.clone({value:a}));const n=s({name:"media",params:"(prefers-contrast: less)",source:t.source});if(n.append(o),e?.preserve){const e=s({name:"supports",params:"not (color: contrast-color(red max))",source:t.source});e.append(n),t.after(e)}else t.after(n)}if(n!==i){const o=t.clone();o.removeAll(),o.append(r.clone({value:i}));const n=s({name:"media",params:"(prefers-contrast: more)",source:t.source});if(n.append(o),e?.preserve){const e=s({name:"supports",params:"not (color: contrast-color(red max))",source:t.source});e.append(n),t.after(e)}else t.after(n)}e?.preserve||r.remove()}}})});basePlugin.postcss=!0;const postcssPlugin=o=>{const r=Object.assign({enableProgressiveCustomProperties:!0,preserve:!0},o);return r.enableProgressiveCustomProperties&&r.preserve?{postcssPlugin:"postcss-contrast-color-function",plugins:[e(),basePlugin(r)]}:basePlugin(r)};postcssPlugin.postcss=!0,module.exports=postcssPlugin;
"use strict";var s=require("@csstools/postcss-progressive-custom-properties"),e=require("@csstools/utilities"),o=require("@csstools/css-tokenizer"),r=require("@csstools/css-parser-algorithms"),t=require("@csstools/css-color-parser");const c=/\bcontrast-color\(/i,n=/^contrast-color$/i,basePlugin=s=>({postcssPlugin:"postcss-contrast-color-function",prepare:()=>({postcssPlugin:"postcss-contrast-color-function",Declaration(i){const a=i.value;if(!c.test(a))return;if(e.hasFallback(i))return;if(e.hasSupportsAtRuleAncestor(i,c))return;const l=o.tokenize({css:a}),u=r.replaceComponentValues(r.parseCommaSeparatedListOfComponentValues(l),(s=>{if(!r.isFunctionNode(s)||!n.test(s.getName()))return;const e=t.color(s);return e&&!e.syntaxFlags.has(t.SyntaxFlag.HasNoneKeywords)?t.serializeRGB(e):void 0})),p=r.stringify(u);p!==a&&(i.cloneBefore({value:p}),s?.preserve||i.remove())}})});basePlugin.postcss=!0;const postcssPlugin=e=>{const o=Object.assign({enableProgressiveCustomProperties:!0,preserve:!0},e);return o.enableProgressiveCustomProperties&&o.preserve?{postcssPlugin:"postcss-contrast-color-function",plugins:[s(),basePlugin(o)]}:basePlugin(o)};postcssPlugin.postcss=!0,module.exports=postcssPlugin;
2 changes: 1 addition & 1 deletion plugins/postcss-contrast-color-function/dist/index.mjs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
import r from"@csstools/postcss-progressive-custom-properties";import{hasFallback as o,hasSupportsAtRuleAncestor as e}from"@csstools/utilities";import{isTokenIdent as s,tokenize as t,TokenType as n,NumberType as c}from"@csstools/css-tokenizer";import{isFunctionNode as a,isWhitespaceNode as l,isCommentNode as u,isTokenNode as i,replaceComponentValues as p,parseCommaSeparatedListOfComponentValues as f,FunctionNode as m,TokenNode as v,stringify as C}from"@csstools/css-parser-algorithms";import{color as E,serializeRGB as d,serializeP3 as g}from"@csstools/css-color-parser";import{contrast_ratio_wcag_2_1 as P}from"@csstools/color-helpers";const h=/^contrast-color$/i;function parseContrastColor(r){if(!a(r)||!h.test(r.getName()))return!1;const o=r.value.filter((r=>!l(r)&&!u(r)));if(o.length>2)return!1;const e=o[0],t=o[1];return!!e&&(t?!(!i(t)||!s(t.value)||"max"!==t.value[4].value.toLowerCase())&&[e,"max"]:[e])}var R;!function(r){r[r.MORE=0]="MORE",r[r.LESS=1]="LESS",r[r.NO_PREFERENCE=2]="NO_PREFERENCE"}(R||(R={}));const N=/\bcontrast-color\(/i;function transformContrastColor(r,o,e=0){const s=p(f(t({css:r})),(r=>{const e=parseContrastColor(r);if(!e)return;const[s,t]=e;if("max"===t){const o=E(r);if(!o)return;return d(o,!0)}if(t)return;const a=E(new m([n.Function,"contrast-color(",-1,-1,{value:"contrast-color"}],[n.CloseParen,")",-1,-1,void 0],[s,new v([n.Ident,"max",-1,-1,{value:"max"}])]));if(!a)return;if(o===R.MORE)return d(a,!0);const l=E(s);if(!l)return;let u=0;const i=E(d(l,!0));if(!i)return;{const r=P(i.channels,[0,0,0]),e=P(i.channels,[1,1,1]);u=o===R.LESS?r>=e?.3:.9:r>=e?.2:.95}const p=E(new m([n.Function,"oklch(",-1,-1,{value:"oklch"}],[n.CloseParen,")",-1,-1,void 0],[new v([n.Ident,"from",-1,-1,{value:"from"}]),s,new v([n.Number,u.toString(),-1,-1,{value:u,type:c.Number}]),new v([n.Ident,"c",-1,-1,{value:"c"}]),new v([n.Ident,"h",-1,-1,{value:"h"}])]));if(!p)return;const f=E(d(p,!0));if(!f)return;return P(i.channels,f.channels)<4.5?d(a,!0):g(p,!0)})),a=C(s);return a===r?r:e>10?a:N.test(a)?transformContrastColor(a,o,e+1):a}const basePlugin=r=>({postcssPlugin:"postcss-contrast-color-function",prepare:()=>({postcssPlugin:"postcss-contrast-color-function",Declaration(s,{atRule:t}){const n=s.parent;if(!n)return;if(!N.test(s.value))return;if(o(s))return;if(e(s,N))return;const c=transformContrastColor(s.value,R.NO_PREFERENCE);if(c===s.value)return;const a=transformContrastColor(s.value,R.LESS);if(a===s.value)return;const l=transformContrastColor(s.value,R.MORE);if(l!==s.value){if(s.cloneBefore({value:c}),c!==a){const o=n.clone();o.removeAll(),o.append(s.clone({value:a}));const e=t({name:"media",params:"(prefers-contrast: less)",source:n.source});if(e.append(o),r?.preserve){const r=t({name:"supports",params:"not (color: contrast-color(red max))",source:n.source});r.append(e),n.after(r)}else n.after(e)}if(c!==l){const o=n.clone();o.removeAll(),o.append(s.clone({value:l}));const e=t({name:"media",params:"(prefers-contrast: more)",source:n.source});if(e.append(o),r?.preserve){const r=t({name:"supports",params:"not (color: contrast-color(red max))",source:n.source});r.append(e),n.after(r)}else n.after(e)}r?.preserve||s.remove()}}})});basePlugin.postcss=!0;const postcssPlugin=o=>{const e=Object.assign({enableProgressiveCustomProperties:!0,preserve:!0},o);return e.enableProgressiveCustomProperties&&e.preserve?{postcssPlugin:"postcss-contrast-color-function",plugins:[r(),basePlugin(e)]}:basePlugin(e)};postcssPlugin.postcss=!0;export{postcssPlugin as default};
import s from"@csstools/postcss-progressive-custom-properties";import{hasFallback as o,hasSupportsAtRuleAncestor as r}from"@csstools/utilities";import{tokenize as t}from"@csstools/css-tokenizer";import{replaceComponentValues as e,parseCommaSeparatedListOfComponentValues as c,isFunctionNode as n,stringify as i}from"@csstools/css-parser-algorithms";import{color as p,SyntaxFlag as l,serializeRGB as a}from"@csstools/css-color-parser";const u=/\bcontrast-color\(/i,m=/^contrast-color$/i,basePlugin=s=>({postcssPlugin:"postcss-contrast-color-function",prepare:()=>({postcssPlugin:"postcss-contrast-color-function",Declaration(f){const g=f.value;if(!u.test(g))return;if(o(f))return;if(r(f,u))return;const v=t({css:g}),P=e(c(v),(s=>{if(!n(s)||!m.test(s.getName()))return;const o=p(s);return o&&!o.syntaxFlags.has(l.HasNoneKeywords)?a(o):void 0})),b=i(P);b!==g&&(f.cloneBefore({value:b}),s?.preserve||f.remove())}})});basePlugin.postcss=!0;const postcssPlugin=o=>{const r=Object.assign({enableProgressiveCustomProperties:!0,preserve:!0},o);return r.enableProgressiveCustomProperties&&r.preserve?{postcssPlugin:"postcss-contrast-color-function",plugins:[s(),basePlugin(r)]}:basePlugin(r)};postcssPlugin.postcss=!0;export{postcssPlugin as default};
1 change: 0 additions & 1 deletion plugins/postcss-contrast-color-function/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
"dist"
],
"dependencies": {
"@csstools/color-helpers": "^5.0.2",
"@csstools/css-color-parser": "^3.0.8",
"@csstools/css-parser-algorithms": "^3.0.4",
"@csstools/css-tokenizer": "^3.0.3",
Expand Down
92 changes: 33 additions & 59 deletions plugins/postcss-contrast-color-function/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import postcssProgressiveCustomProperties from '@csstools/postcss-progressive-custom-properties';
import type { Plugin, PluginCreator } from 'postcss';
import { hasFallback, hasSupportsAtRuleAncestor } from '@csstools/utilities';
import { CONTRAST_COLOR_FUNCTION_REGEX, PREFERS_CONTRAST, transformContrastColor } from './transform-contrast-color';
import { tokenize } from '@csstools/css-tokenizer';
import { isFunctionNode, parseCommaSeparatedListOfComponentValues, replaceComponentValues, stringify } from '@csstools/css-parser-algorithms';
import { color, serializeRGB, SyntaxFlag } from '@csstools/css-color-parser';

const CONTRAST_COLOR_FUNCTION_REGEX = /\bcontrast-color\(/i;
const CONTRAST_COLOR_NAME_REGEX = /^contrast-color$/i;

const basePlugin: PluginCreator<pluginOptions> = (opts) => {
return {
postcssPlugin: 'postcss-contrast-color-function',
prepare(): Plugin {
return {
postcssPlugin: 'postcss-contrast-color-function',
Declaration(decl, { atRule }): void {
const parent = decl.parent;
if (!parent) {
return;
}

if (!CONTRAST_COLOR_FUNCTION_REGEX.test(decl.value)) {
Declaration(decl): void {
const originalValue = decl.value;
if (!(CONTRAST_COLOR_FUNCTION_REGEX.test(originalValue))) {
return;
}

Expand All @@ -27,60 +28,33 @@ const basePlugin: PluginCreator<pluginOptions> = (opts) => {
return;
}

const modifiedNoPreference = transformContrastColor(decl.value, PREFERS_CONTRAST.NO_PREFERENCE);
if (modifiedNoPreference === decl.value) {
const tokens = tokenize({ css: originalValue });
const replacedRGB = replaceComponentValues(
parseCommaSeparatedListOfComponentValues(tokens),
(componentValue) => {
if (!isFunctionNode(componentValue) || !CONTRAST_COLOR_NAME_REGEX.test(componentValue.getName())) {
return;
}

const colorData = color(componentValue);
if (!colorData) {
return;
}

if (colorData.syntaxFlags.has(SyntaxFlag.HasNoneKeywords)) {
return;
}

return serializeRGB(colorData);
},
);

const modified = stringify(replacedRGB);
if (modified === originalValue) {
return;
}

const modifiedLess = transformContrastColor(decl.value, PREFERS_CONTRAST.LESS);
if (modifiedLess === decl.value) {
return;
}

const modifiedMore = transformContrastColor(decl.value, PREFERS_CONTRAST.MORE);
if (modifiedMore === decl.value) {
return;
}

decl.cloneBefore({ value: modifiedNoPreference });

if (modifiedNoPreference !== modifiedLess) {
const parentClone = parent.clone();
parentClone.removeAll();

parentClone.append(decl.clone({ value: modifiedLess }));

const prefers = atRule({ name: 'media', params: '(prefers-contrast: less)', source: parent.source });
prefers.append(parentClone);

if (!opts?.preserve) {
parent.after(prefers);
} else {
const supports = atRule({ name: 'supports', params: 'not (color: contrast-color(red max))', source: parent.source });
supports.append(prefers);

parent.after(supports);
}
}

if (modifiedNoPreference !== modifiedMore) {
const parentClone = parent.clone();
parentClone.removeAll();

parentClone.append(decl.clone({ value: modifiedMore }));

const prefers = atRule({ name: 'media', params: '(prefers-contrast: more)', source: parent.source });
prefers.append(parentClone);

if (!opts?.preserve) {
parent.after(prefers);
} else {
const supports = atRule({ name: 'supports', params: 'not (color: contrast-color(red max))', source: parent.source });
supports.append(prefers);

parent.after(supports);
}
}
decl.cloneBefore({ value: modified });

if (!opts?.preserve) {
decl.remove();
Expand Down
Loading