159 lines
4.1 KiB
JavaScript
159 lines
4.1 KiB
JavaScript
![]() |
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
|
||
|
);
|