Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e13ef18
[color-swatch] Add support for deltas from other color
DmitrySharabin Jun 11, 2024
d6c7f7f
[color-scale] Add support for deltas from other color
DmitrySharabin Jun 11, 2024
8b85dff
Merge branch 'main' into color-swatch-deltas
DmitrySharabin Jun 11, 2024
4525cff
Move vars to the block where they are used
DmitrySharabin Jun 11, 2024
e642891
Don't show the delta if it's zero
DmitrySharabin Jun 11, 2024
85428d8
Minor refactor
DmitrySharabin Jun 11, 2024
6242c06
Support different precision for angles and numbers
DmitrySharabin Jun 11, 2024
9c88652
Add support for the primary use case
DmitrySharabin Jun 11, 2024
bd4df34
🤦‍♂️
DmitrySharabin Jun 11, 2024
5ddf3a1
Increase precision for percents
DmitrySharabin Jun 11, 2024
fea82ad
[color-swatch] Add docs on the `vs` prop (attribute)
DmitrySharabin Jun 12, 2024
5cfe1b5
[color-scale] Add docs on the `coords`, `deltas`, and `vs` attributes
DmitrySharabin Jun 12, 2024
58974c4
Address feedback
DmitrySharabin Jun 12, 2024
84ff6d3
Address feedback
DmitrySharabin Jun 12, 2024
6746cd4
Oops
DmitrySharabin Jun 12, 2024
bdc2ac9
Add links to the page with ΔE algorithms
DmitrySharabin Jun 12, 2024
43fb65b
Update the docs
DmitrySharabin Jun 12, 2024
d278303
Manually calculate deltas
DmitrySharabin Jun 12, 2024
89fdb92
Move to a more appropriate place
DmitrySharabin Jun 12, 2024
5ca9abd
Merge branch 'main' into color-swatch-deltas
DmitrySharabin Jun 13, 2024
1aa5bbe
Merge branch 'main' into color-swatch-deltas
DmitrySharabin Jun 17, 2024
fdcfb61
Leftovers from merging the main branch
DmitrySharabin Jun 17, 2024
9482bcf
Add support for deltaE and contrast
DmitrySharabin Jun 17, 2024
de58fc4
Fix comment
DmitrySharabin Jun 17, 2024
5dfb4a6
Update README.md
DmitrySharabin Jun 17, 2024
a151249
Merge branch 'main' into color-swatch-deltas
DmitrySharabin Jun 17, 2024
09f4136
Update API
DmitrySharabin Jun 17, 2024
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
22 changes: 22 additions & 0 deletions src/color-scale/color-scale.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,19 @@ const Self = class ColorScale extends NudeElement {
swatch.textContent = name;
if (this.info) {
swatch.info = this.info;

if (this.vs && !["previous", "next"].includes(this.vs)) {
// If there is a vs color, use it as the reference for the deltas
swatch.vs = this.vs;
}
else if (this.vs === "previous" && i > 0) {
// Otherwise, use the previous color
swatch.vs = colors[i - 1].color;
}
else if (this.vs === "next" && i < colors.length - 1) {
// Or the next color
swatch.vs = colors[i + 1].color;
}
}
i++;
}
Expand Down Expand Up @@ -155,6 +168,15 @@ const Self = class ColorScale extends NudeElement {
additionalDependencies: ["info"],
},
info: {},
vs: {
parse (value) {
if (value instanceof Color || value === "previous" || value === "next" || value === null || value === undefined) {
return value;
}

return new Color(value);
},
},
};
}

Expand Down
1 change: 1 addition & 0 deletions src/color-swatch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ If you don’t, the `<html>` element will be used.
| `info` | `info` | `string` | - | Comma-separated list of coords of the current color to be shown. |
| `value` | `value` | `string` | - | The current value of the swatch. |
| `size` | - | `large` | - | The size of the swatch. Currently, it is used only to make a large swatch. |
| `vs` | `vs` | `Color` &#124; `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. |
Expand Down
47 changes: 37 additions & 10 deletions src/color-swatch/color-swatch.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -67,26 +69,39 @@ slot {
display: none;
gap: .5em;

dd {
margin: 0;
font-weight: bold;
font-variant-numeric: tabular-nums;
}

&:is(:host([size="large"]) &) {
display: grid;
grid-template-columns: max-content auto;
gap: .1em .2em;
font-size: max(9px, 80%);
justify-content: start;
grid-template-columns: subgrid;

.coord {
.info {
display: contents;

dd:not(.deltaE, :has(~ dd)) {
grid-column: span 2;
}
}
}

.coord {
.info {
display: flex;
gap: .2em;

dd {
margin: 0;
font-weight: bold;
font-variant-numeric: tabular-nums;
dd.deltaE {
color: var(--_deltaE-color);

&.positive {
--_deltaE-color: var(--_positive-deltaE-color);
}

&.negative {
--_deltaE-color: var(--_negative-deltaE-color);
}
}
}
}
Expand All @@ -110,6 +125,18 @@ slot {
font-size: 80%;
}

&:is(:host([size="large"]) &) {
display: grid;
grid-template-columns: repeat(3, max-content);
gap: .1em .2em;
font-size: max(9px, 80%);
justify-content: start;

& > * {
grid-column: 1 / -1;
}
}

@container style(--details-style: compact) {
--_border-color: var(--border-color, color-mix(in oklab, buttonborder 20%, oklab(none none none / 0%)));
--_pointer-height: var(--pointer-height, .5em);
Expand Down
132 changes: 111 additions & 21 deletions src/color-swatch/color-swatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const Self = class ColorSwatch extends NudeElement {
part: "gamut",
exportparts: "label: gamutLabel",
gamuts: this.gamuts,
color: this.color
color: this.color,
});

this.shadowRoot.append(this._el.gamutIndicator);
Expand Down Expand Up @@ -116,27 +116,103 @@ const Self = class ColorSwatch extends NudeElement {
this.style.setProperty("--color", colorString);
}

if (name === "info") {
if (!this.info.length) {
return;
}
if (name === "info" || name === "vs") {
let infoHTML = [];
let coords = [];
let other = []; // DeltaE and contrast

this._el.info ??= Object.assign(document.createElement("dl"), {part: "info"});
if (!this._el.info.parentNode) {
this._el.colorWrapper.after(this._el.info);
}
if (this.info.length || this.vs) {
this._el.info ??= Object.assign(document.createElement("dl"), {part: "info"});
if (!this._el.info.parentNode) {
this._el.colorWrapper.after(this._el.info);
}

let info = [];
for (let coord of this.info) {
let [label, channel] = Object.entries(coord)[0];
for (let item of this.info) {
let [label, data] = Object.entries(item)[0];
if (label === "deltaE" || label === "contrast") {
let method = label;
let algorithm = data.replace(/(^deltaE)|(\s+contrast$)/, "");
label = algorithm;

if (method === "deltaE") {
label = "ΔE " + label;
}

other.push({method, label, algorithm});
}
else {
// Color coord
coords.push([label, data]);
}
}
}

let value = this.color.get(channel);
value = typeof value === "number" ? Number(value.toPrecision(4)) : value;
if (coords.length) {
for (let [label, channel] of coords) {
let value = this.color.get(channel);

let deltaString;
if (this.vs) {
let vsValue = this.vs.get(channel);

let hasDelta = typeof value === "number" && !Number.isNaN(value) &&
typeof vsValue === "number" && !Number.isNaN(vsValue);
if (hasDelta) {
let delta;

let {space, index} = Color.Space.resolveCoord(channel);
let spaceCoord = Object.values(space.coords)[index];

let isAngle = spaceCoord.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;
}

delta = value - vsValue;
}
else {
delta = (value - vsValue) / value * 100;
}

delta = Number(delta.toPrecision(isAngle ? 4 : 2));
if (delta !== 0) {
let sign = delta > 0 ? "+" : "";
let className = delta > 0 ? "positive" : "negative";
deltaString = `<dd class="deltaE ${ className }">(${ sign }${ delta }${ !isAngle ? "%" : ""})</dd>`;
}
}
}

value = typeof value === "number" ? Number(value.toPrecision(4)) : value;
let html = `<dt>${ label }</dt><dd>${ value }</dd>`;
if (deltaString) {
html += deltaString;
}

infoHTML.push(`<div class="info">${ html }</div>`);
}
}

info.push(`<div class="coord"><dt>${ label }</dt><dd>${ value }</dd></div>`);
if (this.vs) {
for (let {label, algorithm, method} of other) {
if (algorithm) {
let value = this.color[method](this.vs, algorithm);
value = typeof value === "number" ? Number(value.toPrecision(4)) : value;
infoHTML.push(`<div class="info"><dt>${ label ?? algorithm }</dt><dd>${ value }</dd></div>`);
}
}
}

this._el.info.innerHTML = info.join("\n");
if (infoHTML.length) {
this._el.info.innerHTML = infoHTML.join("\n");
}
}
}

Expand All @@ -156,7 +232,7 @@ const Self = class ColorSwatch extends NudeElement {
},
reflect: {
from: "color",
}
},
},
color: {
type: Color,
Expand All @@ -177,7 +253,17 @@ const Self = class ColorSwatch extends NudeElement {
is: Array,
values: {
is: Object,
defaultKey: (coord, i) => Color.Space.resolveCoord(coord)?.name,
defaultKey: (coord, i) => {
if (coord.includes(".")) {
return Color.Space.resolveCoord(coord)?.name;
}
else if (coord.startsWith("deltaE")) {
return "deltaE";
}
else if (coord.endsWith("contrast")) {
return "contrast";
}
},
},
},
default: [],
Expand All @@ -188,7 +274,11 @@ const Self = class ColorSwatch extends NudeElement {
},
size: {},
open: {},
}
vs: {
type: Color,
dependencies: ["color"],
},
};

static events = {
colorchange: {
Expand All @@ -198,8 +288,8 @@ const Self = class ColorSwatch extends NudeElement {
propchange: "value",
},
};
}
};

customElements.define(Self.tagName, Self);

export default Self;
export default Self;