automatic-colour-scheme/color-picker.js

159 lines
4.1 KiB
JavaScript
Raw Normal View History

2025-02-09 20:19:13 +00:00
import colour_names from './colour_names.js';
import { formatHex, parseHex, converter, differenceCiede2000, formatCss } from './culori.mjs';
import * as culori from './culori.mjs';
const COLOUR_WEIGHT = 1.2;
const convertToLab = converter('lab');
const convertToOklab = converter('oklab');
const color_difference = differenceCiede2000();
const weighted_color_difference = (a,b) => {
const d = color_difference(a,b);
return d*Math.pow(COLOUR_WEIGHT,b.weight);
}
const named_colours = colour_names.map(({name, hex, weight}) => {
const color = convertToLab(parseHex(hex));
color.name = name;
color.weight = weight;
return color;
})
export const nearestNamedColors = culori.nearest(named_colours, weighted_color_difference);
export function name_colour(color) {
const closest = nearestNamedColors(color, 3);
return closest;
}
export class ColorPickerElement extends HTMLElement {
static observedAttributes = ['value'];
constructor() {
super();
let template = document.getElementById("color-picker-template");
let templateContent = template.content;
const sheet = new CSSStyleSheet();
sheet.replaceSync(`
* {box-sizing: border-box;}
.color-input {
display: grid;
grid-gap: 0.5em;
grid-template-columns: 1fr 1fr;
}
.color-input label {
grid-column: 1;
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
grid-gap: 1em;
}
.color-input input {
width: 100%;
}
.color-input .color-preview {
display: block;
width: 100%;
height: 100%;
grid-column: 2;
grid-row: 1/5;
background: red;
padding: 1em;
}
.color-input span {
background: white;
padding: 0.2em;
display: inline-block;
}
.color-input .names {
list-style: none;
padding: 0;
}
`);
const shadowRoot = this.attachShadow({ mode: "open" });
shadowRoot.appendChild(templateContent.cloneNode(true));
shadowRoot.adoptedStyleSheets = [sheet];
const colour_name_datalist = shadowRoot.querySelector('#colour-names');
named_colours.forEach(({name}) => {
const option = document.createElement('option');
option.value = option.textContent = name;
colour_name_datalist.append(option);
})
this.name_input = shadowRoot.querySelector('input[name="name"]');
Array.from(shadowRoot.querySelectorAll('.color-input input[type="range"]')).forEach(i => i.addEventListener('input', () => {
this.name_input.value = '';
this.update();
}));
this.inputs = Object.fromEntries(Array.from(this.shadowRoot.querySelector('.color-input').elements).map(e=>[e.name, e]));
this.name_input.addEventListener('input', e => {
this.value = e.target.value.trim();
});
this.value = this.getAttribute('value');
this.update();
}
attributeChangedCallback(name, oldValue, newValue) {
if(name == 'value') {
this.value = newValue;
this.name_input.value = '';
}
}
set value(value) {
const col = culori.parse(value);
if(col) {
this.set_inputs(col);
this.update();
}
}
set_inputs(col) {
const okcol = convertToOklab(col);
this.inputs.L.value = okcol.l;
this.inputs.a.value = okcol.a;
this.inputs.b.value = okcol.b;
}
get value() {
const values = Object.fromEntries(Array.from(this.shadowRoot.querySelector('.color-input').elements).map(e=>[e.name, e.valueAsNumber]));
const {L, a, b} = values;
const col = {mode: 'oklab', l: L, a: a, b: b};
return col;
}
update() {
const col = this.value;
const hex = formatHex(col);
const closest = name_colour(col);
this.shadowRoot.querySelector('output').style.background = formatCss(col);
this.shadowRoot.querySelector('.hex').textContent = hex;
Array.from(this.shadowRoot.querySelectorAll('.names li span')).forEach((e,i) => {
e.textContent = closest[i].name;
})
this.dispatchEvent(new CustomEvent('input',{detail: col}));
}
}
customElements.define(
"color-picker",
ColorPickerElement
);