Skip to content
Open
Show file tree
Hide file tree
Changes from 25 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
16 changes: 16 additions & 0 deletions src/color-scale/color-scale.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const Self = class ColorScale extends NudeElement {
slot: this.shadowRoot.querySelector("slot"),
swatches: this.shadowRoot.getElementById("swatches"),
};

this.#hasVs = this.hasAttribute("vs");
}

connectedCallback() {
Expand All @@ -43,6 +45,7 @@ const Self = class ColorScale extends NudeElement {
}
}

#hasVs;
#swatches = [];

render () {
Expand Down Expand Up @@ -71,6 +74,16 @@ const Self = class ColorScale extends NudeElement {
swatch.textContent = name;
if (this.info) {
swatch.info = this.info;

// Deltas and contrast don't make sense without coords
if (this.vs) {
// If there is a vs color, use it as the reference for the deltas
swatch.vs = this.vs;
}
else if (this.#hasVs && i > 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would you check this and not this.vs !== undefined or something?

// Otherwise, use the previous color
swatch.vs = colors[i - 1].color;
}
}
i++;
}
Expand Down Expand Up @@ -153,6 +166,9 @@ const Self = class ColorScale extends NudeElement {
additionalDependencies: ["info"],
},
info: {},
vs: {
type: Color,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see why you're doing the #hasVs thing. That’s not the right way; it means that if someone sets it via the property you get a different behavior. You want two properties: one for the raw value, and one for the parsed value, which is either true (previous color), false (no vs), or a Color instance.

And yes, this is another case of rawProp.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'm doing this precisely because I can't see another way to distinguish different cases: color, no color, no vs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option would be to use a custom parse function. Up to you.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it may be useful to provide a way to do vs with the previous or next color. In that case we could have vs="previous" and vs="next" which may be more explanatory than a bare vs with no value.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option would be to use a custom parse function.

Yeah, I was going to try this later. Glad it makes sense. :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it may be useful to provide a way to do vs with the previous or next color. In that case we could have vs="previous" and vs="next" which may be more explanatory than a bare vs with no value.

I like it

},
};
}

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 @@ -75,7 +75,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 @@ -113,27 +113,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 @@ -153,7 +229,7 @@ const Self = class ColorSwatch extends NudeElement {
},
reflect: {
from: "color",
}
},
},
color: {
type: Color,
Expand All @@ -174,7 +250,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 @@ -185,7 +271,11 @@ const Self = class ColorSwatch extends NudeElement {
},
size: {},
open: {},
}
vs: {
type: Color,
dependencies: ["color"],
},
};

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

customElements.define(Self.tagName, Self);

export default Self;
export default Self;