From af01ba31f790d0e1028cb287c5dd938abb9e20af Mon Sep 17 00:00:00 2001 From: Dmitry Sharabin Date: Wed, 16 Oct 2024 14:11:04 +0200 Subject: [PATCH 1/7] [color-swatch] Add support for color difference (Delta E) and contrast --- src/color-swatch/README.md | 24 +++- src/color-swatch/color-swatch.css | 29 ++++- src/color-swatch/color-swatch.js | 185 +++++++++++++++++++++++++++--- 3 files changed, 216 insertions(+), 22 deletions(-) diff --git a/src/color-swatch/README.md b/src/color-swatch/README.md index d95c4182..6706cf41 100644 --- a/src/color-swatch/README.md +++ b/src/color-swatch/README.md @@ -189,7 +189,7 @@ If used as a property and is not defined via the `label` attribute, its value is ### The `info` attribute You can use the `info` attribute to show information about the color. -Currently, the only type of information supported is color coords (in any color space), but more will be added in the future. +Currently, the types of information supported are color coords (in any color space), the difference (delta) and contrast between the current color and another one (specified via [the `vs` attribute](./#the-vs-attribute)). ```html @@ -213,6 +213,25 @@ The `info` attribute plays quite nicely with the `--details-style: compact` styl oklch(70% 0.25 138) ``` +### The `vs` attribute + +You can calculate the difference (delta) and contrast between the current color and another one. +To do so, provide the new color via the `vs` attribute and specify one of the [supported algorithms for calculating the difference](https://colorjs.io/docs/color-difference#delta-e-e) ([contrast](https://colorjs.io/docs/contrast) or both) between two colors inside [the `info` attribute](./#the-info-attribute). + +```html + + oklch(70% 0.25 138) + +``` + +If color coords are also specified, the deltas on a coord-by-coord basis will be shown: + +```html + + oklch(70% 0.25 138) + +``` + ### With slot content Before and after: @@ -316,6 +335,7 @@ If you don’t, the `` element will be used. | `value` | `value` | `string` | - | The current value of the swatch. | | `label` | `label` | `string` | - | The label of the swatch (e.g., color name). Defaults to the element text content. | | `size` | - | `large` | - | The size of the swatch. Currently, it is used only to make a large swatch. | +| `vs` | `vs` | `Color` | `string` | - | The second color to use when calculating the difference (delta) and contrast with the current color. | | `property` | `property` | `string` | - | CSS property to bind to. | | `scope` | `scope` | `string` | `:root` | CSS selector to use as the scope for the specified CSS property. | | `gamuts` | `gamuts` | `string` | `srgb, p3, rec2020: P3+, prophoto: PP` | Comma-separated list of gamuts to be used by the gamut indicator. | @@ -338,6 +358,8 @@ These properties are read-only. | `--transparency-cell-size` | `` | The size of the cells of the transparency gradient. | | `--transparcency-background` | `` | The background color of the transparency gradient. | | `--transparency-darkness` | `` | The opacity of the black color used for dark parts of the transparency gradient. | +| `--positive-deltaE-color` | `` | The color used for the positive color difference in color coords. | +| `--negative-deltaE-color` | `` | The color used for the negative color difference in color coords. | ### Parts diff --git a/src/color-swatch/color-swatch.css b/src/color-swatch/color-swatch.css index 6ae2ddb5..547714b6 100644 --- a/src/color-swatch/color-swatch.css +++ b/src/color-swatch/color-swatch.css @@ -7,6 +7,8 @@ 0 0 / calc(2 * var(--_transparency-cell-size)) calc(2 * var(--_transparency-cell-size)) content-box border-box var(--_transparency-background) ); + --_positive-deltaE-color: var(--positive-deltaE-color, hsl(120, 80%, 25%)); + --_negative-deltaE-color: var(--negative-deltaE-color, hsl(0, 85%, 40%)); position: relative; display: inline-flex; @@ -64,22 +66,29 @@ slot { [part="info"] { margin: 0; display: inline-flex; - display: none; gap: .5em; &:is(:host([size="large"]) &) { display: grid; - grid-template-columns: max-content auto; + grid-template-columns: repeat(3, max-content); gap: .1em .2em; font-size: max(9px, 80%); justify-content: start; - .coord { + & > * { + grid-column: 1 / -1; + } + + .data { display: contents; + + dd:not(.deltaE, :has(~ dd)) { + grid-column: span 2; + } } } - .coord { + .data { display: flex; gap: .2em; @@ -87,6 +96,18 @@ slot { margin: 0; font-weight: bold; font-variant-numeric: tabular-nums; + + &.deltaE { + color: var(--_deltaE-color); + + &.positive { + --_deltaE-color: var(--_positive-deltaE-color); + } + + &.negative { + --_deltaE-color: var(--_negative-deltaE-color); + } + } } } } diff --git a/src/color-swatch/color-swatch.js b/src/color-swatch/color-swatch.js index 10694d6d..29420b0e 100644 --- a/src/color-swatch/color-swatch.js +++ b/src/color-swatch/color-swatch.js @@ -130,7 +130,7 @@ const Self = class ColorSwatch extends ColorElement { this.style.setProperty("--color", colorString); } - if (name === "colorInfo") { + if (name === "colorInfo" || name === "vsInfo") { if (!this.colorInfo) { return; } @@ -140,21 +140,35 @@ const Self = class ColorSwatch extends ColorElement { this._el.colorWrapper.after(this._el.info); } - let info = []; - for (let coord of this.info) { - let [label, channel] = Object.entries(coord)[0]; + let html = []; + for (let data of this.info) { + let [label, key] = Object.entries(data)[0]; - let value = this.colorInfo[channel]; - if (value === undefined) { + let rawValue = this.colorInfo[key] ?? this.vsInfo?.[key]; + if (rawValue === undefined) { continue; } - value = typeof value === "number" ? Number(value.toPrecision(4)) : value; + let value = typeof rawValue === "number" ? Number(rawValue.toPrecision(4)) : rawValue; + let ret = `
${ label }
${ value }
`; - info.push(`
${ label }
${ value }
`); + if (this.colorDeltas?.[key] && this.infoCoordsResolved?.[key]) { + let delta = this.colorDeltas[key]; + let sign = delta > 0 ? "+" : ""; + let className = delta > 0 ? "positive" : "negative"; + let isAngle = this.infoCoordsResolved[key]?.type === "angle"; + + delta = isAngle ? delta : delta / rawValue * 100; + delta = typeof delta === "number" ? Number(delta.toPrecision(4)) : delta; + ret += `
(${ sign }${ delta }${ !isAngle ? "%" : ""})
`; + } + + ret += "
"; + + html.push(ret); } - this._el.info.innerHTML = info.join("\n"); + this._el.info.innerHTML = html.join("\n"); } } @@ -188,17 +202,17 @@ const Self = class ColorSwatch extends ColorElement { }, color: { get type () { - return ColorSwatch.Color; + return Self.Color; }, get () { if (!this.value) { return null; } - return ColorSwatch.Color.get(this.value); + return Self.Color.get(this.value); }, set (value) { - this.value = ColorSwatch.Color.get(value)?.display(); + this.value = Self.Color.get(value)?.display(); }, reflect: false, }, @@ -207,7 +221,18 @@ const Self = class ColorSwatch extends ColorElement { is: Array, values: { is: Object, - defaultKey: (coord, i) => ColorSwatch.Color.Space.resolveCoord(coord)?.name, + defaultKey: (value, i) => { + if (value.includes(".")) { + return Self.Color.Space.resolveCoord(value)?.name; + } + else if (value.startsWith("deltaE")) { + let algorithm = value.replace("deltaE", ""); + return "ΔE " + algorithm; + } + else { + return value; + } + }, }, }, default: [], @@ -215,23 +240,149 @@ const Self = class ColorSwatch extends ColorElement { from: true, }, }, + infoCoords: { + get () { + if (!this.info.length) { + return; + } + + let ret = []; + for (let data of this.info) { + let [key, value] = Object.entries(data)[0]; + if (value.includes(".")) { + ret.push(value); + } + } + + return ret; + }, + }, + infoCoordsResolved: { + get () { + if (!this.infoCoords) { + return; + } + + let ret = {}; + for (let coord of this.infoCoords) { + try { + let { space, index } = Self.Color.Space.resolveCoord(coord); + ret[coord] = Object.values(space.coords)[index]; + } + catch (e) { + console.error(e); + } + } + + return ret; + }, + }, + infoOther: { // DeltaE, contrast, etc. + get () { + if (!this.info.length) { + return; + } + + let ret = []; + for (let data of this.info) { + let [key, value] = Object.entries(data)[0]; + if (!value.includes(".")) { + ret.push(value); + } + } + + return ret; + }, + }, colorInfo: { get () { - if (!this.info.length || !this.color) { + if (!this.color || !this.infoCoords) { return; } let ret = {}; - for (let coord of this.info) { - let [label, channel] = Object.entries(coord)[0]; + for (let coord of this.infoCoords) { try { - ret[channel] = this.color.get(channel); + ret[coord] = this.color.get(coord); } catch (e) { console.error(e); } } + return ret; + }, + }, + colorDeltas: { + get () { + if (!this.infoCoordsResolved || !this.vsInfo || !this.infoOther?.some(value => value.startsWith("deltaE"))) { + return; + } + + let ret = {}; + for (let coord of this.infoCoords) { + let value = this.colorInfo[coord]; + let vsValue = this.vsInfo[coord]; + + let isAngle = this.infoCoordsResolved[coord]?.type === "angle"; + if (isAngle) { + // Constrain angles (shorter arc) + [value, vsValue] = [value, vsValue].map(v => ((v % 360) + 360) % 360); + let angleDiff = vsValue - value; + if (angleDiff > 180) { + value += 360; + } + else if (angleDiff < -180) { + vsValue += 360; + } + } + + ret[coord] = value - vsValue; + } + + return ret; + }, + }, + vs: { + get type () { + return Self.Color; + }, + dependencies: ["color"], + }, + vsInfo: { + get () { + if (!this.color || !this.vs || !this.info.length) { + return; + } + + let ret = {}; + + for (let coord of this.infoCoords) { + try { + ret[coord] = this.vs.get(coord); + } + catch (e) { + console.error(e); + } + } + + for (let data of this.infoOther) { + let regexp = /(^(?deltaE)(?.+))|((?.+)\s+(?contrast)$)/; + let { deltaE, deltaE_algorithm, contrast, contrast_algorithm } = regexp.exec(data)?.groups ?? {}; + + let method = deltaE || contrast; + let algorithm = deltaE_algorithm || contrast_algorithm; + + if (method && algorithm) { + try { + ret[data] = this.color[method](this.vs, algorithm); + } + catch (e) { + console.error(e); + } + } + } + return ret; }, }, From a8c44fdd8155a463a06ad1780844e485b1599af1 Mon Sep 17 00:00:00 2001 From: Dmitry Sharabin Date: Wed, 16 Oct 2024 15:51:39 +0200 Subject: [PATCH 2/7] [color-swatch] Remove redundant dependency --- src/color-swatch/color-swatch.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/color-swatch/color-swatch.js b/src/color-swatch/color-swatch.js index 29420b0e..176fda8b 100644 --- a/src/color-swatch/color-swatch.js +++ b/src/color-swatch/color-swatch.js @@ -347,7 +347,6 @@ const Self = class ColorSwatch extends ColorElement { get type () { return Self.Color; }, - dependencies: ["color"], }, vsInfo: { get () { From 450275c0e45b52a8bc5fb173a02c2a7139b027f9 Mon Sep 17 00:00:00 2001 From: Dmitry Sharabin Date: Wed, 16 Oct 2024 17:41:45 +0200 Subject: [PATCH 3/7] =?UTF-8?q?Rename:=20`deltaE`=20=E2=86=92=20`delta`=20?= =?UTF-8?q?(class=20name=20and=20CSS=20variable)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/color-swatch/README.md | 4 ++-- src/color-swatch/color-swatch.css | 14 +++++++------- src/color-swatch/color-swatch.js | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/color-swatch/README.md b/src/color-swatch/README.md index 6706cf41..09208059 100644 --- a/src/color-swatch/README.md +++ b/src/color-swatch/README.md @@ -358,8 +358,8 @@ These properties are read-only. | `--transparency-cell-size` | `` | The size of the cells of the transparency gradient. | | `--transparcency-background` | `` | The background color of the transparency gradient. | | `--transparency-darkness` | `` | The opacity of the black color used for dark parts of the transparency gradient. | -| `--positive-deltaE-color` | `` | The color used for the positive color difference in color coords. | -| `--negative-deltaE-color` | `` | The color used for the negative color difference in color coords. | +| `--positive-delta-color` | `` | The color used for the positive color difference in color coords. | +| `--negative-delta-color` | `` | The color used for the negative color difference in color coords. | ### Parts diff --git a/src/color-swatch/color-swatch.css b/src/color-swatch/color-swatch.css index 547714b6..f353a87f 100644 --- a/src/color-swatch/color-swatch.css +++ b/src/color-swatch/color-swatch.css @@ -7,8 +7,8 @@ 0 0 / calc(2 * var(--_transparency-cell-size)) calc(2 * var(--_transparency-cell-size)) content-box border-box var(--_transparency-background) ); - --_positive-deltaE-color: var(--positive-deltaE-color, hsl(120, 80%, 25%)); - --_negative-deltaE-color: var(--negative-deltaE-color, hsl(0, 85%, 40%)); + --_positive-delta-color: var(--positive-delta-color, hsl(120, 80%, 25%)); + --_negative-delta-color: var(--negative-delta-color, hsl(0, 85%, 40%)); position: relative; display: inline-flex; @@ -82,7 +82,7 @@ slot { .data { display: contents; - dd:not(.deltaE, :has(~ dd)) { + dd:not(.delta, :has(~ dd)) { grid-column: span 2; } } @@ -97,15 +97,15 @@ slot { font-weight: bold; font-variant-numeric: tabular-nums; - &.deltaE { - color: var(--_deltaE-color); + &.delta { + color: var(--_delta-color); &.positive { - --_deltaE-color: var(--_positive-deltaE-color); + --_delta-color: var(--_positive-delta-color); } &.negative { - --_deltaE-color: var(--_negative-deltaE-color); + --_delta-color: var(--_negative-delta-color); } } } diff --git a/src/color-swatch/color-swatch.js b/src/color-swatch/color-swatch.js index 176fda8b..95dcd69c 100644 --- a/src/color-swatch/color-swatch.js +++ b/src/color-swatch/color-swatch.js @@ -160,7 +160,7 @@ const Self = class ColorSwatch extends ColorElement { delta = isAngle ? delta : delta / rawValue * 100; delta = typeof delta === "number" ? Number(delta.toPrecision(4)) : delta; - ret += `
(${ sign }${ delta }${ !isAngle ? "%" : ""})
`; + ret += `
(${ sign }${ delta }${ !isAngle ? "%" : ""})
`; } ret += ""; From 846ef80d8ef572da1b8b69a7063ff188f8dce94e Mon Sep 17 00:00:00 2001 From: Dmitry Sharabin Date: Fri, 18 Oct 2024 16:22:58 +0200 Subject: [PATCH 4/7] Provide DeltaE and contrast algorithms similar to color coords (i.e., `method.algorithm`) --- src/color-swatch/README.md | 4 ++-- src/color-swatch/color-swatch.js | 23 ++++++++++------------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/color-swatch/README.md b/src/color-swatch/README.md index 09208059..dd18d464 100644 --- a/src/color-swatch/README.md +++ b/src/color-swatch/README.md @@ -219,7 +219,7 @@ You can calculate the difference (delta) and contrast between the current color To do so, provide the new color via the `vs` attribute and specify one of the [supported algorithms for calculating the difference](https://colorjs.io/docs/color-difference#delta-e-e) ([contrast](https://colorjs.io/docs/contrast) or both) between two colors inside [the `info` attribute](./#the-info-attribute). ```html - + oklch(70% 0.25 138) ``` @@ -227,7 +227,7 @@ To do so, provide the new color via the `vs` attribute and specify one of the [s If color coords are also specified, the deltas on a coord-by-coord basis will be shown: ```html - + oklch(70% 0.25 138) ``` diff --git a/src/color-swatch/color-swatch.js b/src/color-swatch/color-swatch.js index 95dcd69c..1d375e8f 100644 --- a/src/color-swatch/color-swatch.js +++ b/src/color-swatch/color-swatch.js @@ -222,12 +222,13 @@ const Self = class ColorSwatch extends ColorElement { values: { is: Object, defaultKey: (value, i) => { - if (value.includes(".")) { - return Self.Color.Space.resolveCoord(value)?.name; + if (value.startsWith("deltaE.") || value.startsWith("contrast.")) { + let [method, algorithm] = value.split("."); + let label = method === "deltaE" ? `ΔE ${algorithm}` : `${algorithm} Contrast`; + return label; } - else if (value.startsWith("deltaE")) { - let algorithm = value.replace("deltaE", ""); - return "ΔE " + algorithm; + else if (value.includes(".")) { + return Self.Color.Space.resolveCoord(value)?.name; } else { return value; @@ -249,7 +250,7 @@ const Self = class ColorSwatch extends ColorElement { let ret = []; for (let data of this.info) { let [key, value] = Object.entries(data)[0]; - if (value.includes(".")) { + if (value.includes(".") && !value.startsWith("deltaE") && !value.startsWith("contrast")) { ret.push(value); } } @@ -286,7 +287,7 @@ const Self = class ColorSwatch extends ColorElement { let ret = []; for (let data of this.info) { let [key, value] = Object.entries(data)[0]; - if (!value.includes(".")) { + if (!this.infoCoords.includes(value)) { ret.push(value); } } @@ -315,7 +316,7 @@ const Self = class ColorSwatch extends ColorElement { }, colorDeltas: { get () { - if (!this.infoCoordsResolved || !this.vsInfo || !this.infoOther?.some(value => value.startsWith("deltaE"))) { + if (!this.infoCoordsResolved || !this.vsInfo) { return; } @@ -366,11 +367,7 @@ const Self = class ColorSwatch extends ColorElement { } for (let data of this.infoOther) { - let regexp = /(^(?deltaE)(?.+))|((?.+)\s+(?contrast)$)/; - let { deltaE, deltaE_algorithm, contrast, contrast_algorithm } = regexp.exec(data)?.groups ?? {}; - - let method = deltaE || contrast; - let algorithm = deltaE_algorithm || contrast_algorithm; + let [method, algorithm] = data.split("."); if (method && algorithm) { try { From 6e56efdbea371f438ee201ee668a632ba50fb5df Mon Sep 17 00:00:00 2001 From: Dmitry Sharabin Date: Thu, 24 Oct 2024 14:53:50 +0200 Subject: [PATCH 5/7] Move generated content to CSS Weird formatting issue --- src/color-swatch/color-swatch.css | 16 ++++++++++++++++ src/color-swatch/color-swatch.js | 14 +++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/color-swatch/color-swatch.css b/src/color-swatch/color-swatch.css index f353a87f..395898cf 100644 --- a/src/color-swatch/color-swatch.css +++ b/src/color-swatch/color-swatch.css @@ -100,13 +100,29 @@ slot { &.delta { color: var(--_delta-color); + &::before { + content: "("; + } + + &::after { + content: ")"; + } + &.positive { --_delta-color: var(--_positive-delta-color); + + &::before { + content: "(+"; + } } &.negative { --_delta-color: var(--_negative-delta-color); } + + &:not(.angle)::after { + content: "%)"; + } } } } diff --git a/src/color-swatch/color-swatch.js b/src/color-swatch/color-swatch.js index 1d375e8f..7ce9025e 100644 --- a/src/color-swatch/color-swatch.js +++ b/src/color-swatch/color-swatch.js @@ -154,13 +154,17 @@ const Self = class ColorSwatch extends ColorElement { if (this.colorDeltas?.[key] && this.infoCoordsResolved?.[key]) { let delta = this.colorDeltas[key]; - let sign = delta > 0 ? "+" : ""; - let className = delta > 0 ? "positive" : "negative"; - let isAngle = this.infoCoordsResolved[key]?.type === "angle"; + let classes = delta > 0 ? "positive" : "negative"; + + if (this.infoCoordsResolved[key]?.type === "angle") { + classes += " angle"; + } + else { + delta = delta / rawValue * 100; + } - delta = isAngle ? delta : delta / rawValue * 100; delta = typeof delta === "number" ? Number(delta.toPrecision(4)) : delta; - ret += `
(${ sign }${ delta }${ !isAngle ? "%" : ""})
`; + ret += `
${ delta }
`; } ret += ""; From bd99c903a19205dd9b525118d2044a4bdd30fb96 Mon Sep 17 00:00:00 2001 From: Dmitry Sharabin Date: Tue, 26 Nov 2024 13:59:47 +0100 Subject: [PATCH 6/7] Fix the docs Co-authored-by: Lea Verou --- src/color-swatch/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/color-swatch/README.md b/src/color-swatch/README.md index dd18d464..eda39845 100644 --- a/src/color-swatch/README.md +++ b/src/color-swatch/README.md @@ -189,7 +189,7 @@ If used as a property and is not defined via the `label` attribute, its value is ### The `info` attribute You can use the `info` attribute to show information about the color. -Currently, the types of information supported are color coords (in any color space), the difference (delta) and contrast between the current color and another one (specified via [the `vs` attribute](./#the-vs-attribute)). +Currently, the types of information supported are color coords (in any color space), the difference (deltaE) and contrast between the current color and another one (specified via [the `vs` attribute](./#the-vs-attribute)). ```html @@ -215,7 +215,7 @@ The `info` attribute plays quite nicely with the `--details-style: compact` styl ### The `vs` attribute -You can calculate the difference (delta) and contrast between the current color and another one. +You can calculate the difference (deltaE) and contrast between the current color and another one. To do so, provide the new color via the `vs` attribute and specify one of the [supported algorithms for calculating the difference](https://colorjs.io/docs/color-difference#delta-e-e) ([contrast](https://colorjs.io/docs/contrast) or both) between two colors inside [the `info` attribute](./#the-info-attribute). ```html From a8399108f6fb82687e4cba474226800c72fbcc06 Mon Sep 17 00:00:00 2001 From: Dmitry Sharabin Date: Mon, 16 Dec 2024 19:21:00 +0100 Subject: [PATCH 7/7] Add comments --- src/color-swatch/color-swatch.js | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/color-swatch/color-swatch.js b/src/color-swatch/color-swatch.js index 7ce9025e..ddc479c3 100644 --- a/src/color-swatch/color-swatch.js +++ b/src/color-swatch/color-swatch.js @@ -245,6 +245,10 @@ const Self = class ColorSwatch extends ColorElement { from: true, }, }, + /** + * Specified coords + * @example ["oklch.h", "oklch.c", "oklch.l"] + */ infoCoords: { get () { if (!this.info.length) { @@ -262,6 +266,7 @@ const Self = class ColorSwatch extends ColorElement { return ret; }, }, + // We need this to correctly work (calculate and show in the UI) with coords of type "angle" infoCoordsResolved: { get () { if (!this.infoCoords) { @@ -282,7 +287,11 @@ const Self = class ColorSwatch extends ColorElement { return ret; }, }, - infoOther: { // DeltaE, contrast, etc. + /** + * Specified deltaE and contrast + * @example ["deltaE.2000", "contrast.WCAG21"] + */ + infoOther: { get () { if (!this.info.length) { return; @@ -299,6 +308,10 @@ const Self = class ColorSwatch extends ColorElement { return ret; }, }, + /** + * Coords for `this.color` + * @example {"oklch.l": 0.7,"oklch.c": 0.25, "oklch.h": 138} + */ colorInfo: { get () { if (!this.color || !this.infoCoords) { @@ -318,12 +331,17 @@ const Self = class ColorSwatch extends ColorElement { return ret; }, }, + /** + * Color deltas (between `this.color` and `this.vs`) + * @example {"oklch.l": -0.3,"oklch.c": 0.35, "oklch.h": 108} + */ colorDeltas: { get () { if (!this.infoCoordsResolved || !this.vsInfo) { return; } + // TODO: Use Color.js deltas() instead (when v0.6.0 is released) let ret = {}; for (let coord of this.infoCoords) { let value = this.colorInfo[coord]; @@ -348,11 +366,18 @@ const Self = class ColorSwatch extends ColorElement { return ret; }, }, + /** + * Color to compare `this.color` with + */ vs: { get type () { return Self.Color; }, }, + /** + * Coords, deltaE, contrast for `this.vs` + * @example {"oklch.l": 1, "oklch.c": 0, "oklch.h": null, "deltaE.2000": 37.69, "contrast.WCAG21": 2.46} + */ vsInfo: { get () { if (!this.color || !this.vs || !this.info.length) {