UNPKG

color-elements

Version:

A set of web components for working with color. A Color.js project.

255 lines (216 loc) 5.66 kB
import ColorElement from "../common/color-element.js"; import "../gamut-badge/gamut-badge.js"; let importIncrementable; const Self = class ColorSwatch extends ColorElement { static tagName = "color-swatch"; static url = import.meta.url; static dependencies = new Set(["gamut-badge"]); static styles = "./color-swatch.css"; static shadowTemplate = ` <slot name="swatch"> <div id="swatch" part="swatch"> <slot name="swatch-content"></slot> </div> </slot> <div id="wrapper" part="details"> <slot name="before"></slot> <div part="label"></div> <div part="color"> <slot></slot> </div> <slot name="after"></slot> </div>`; constructor () { super(); this._el = { wrapper: this.shadowRoot.querySelector("#wrapper"), label: this.shadowRoot.querySelector("[part=label]"), colorWrapper: this.shadowRoot.querySelector("[part=color]"), }; this._slots = { default: this.shadowRoot.querySelector("slot:not([name])"), }; this.#updateStatic(); this._slots.default.addEventListener("slotchange", evt => this.#updateStatic()); } #updateStatic () { let previousInput = this._el.input; let input = this._el.input = this.querySelector("input"); this.static = !input; // This should eventually be a custom state this._el.wrapper.classList.toggle("static", this.static); if (input && input !== previousInput) { importIncrementable ??= import("https://incrementable.verou.me/incrementable.mjs").then(m => m.default); importIncrementable?.then(Incrementable => new Incrementable(input)); input.addEventListener("input", evt => { this.value = evt.target.value; }); } } get gamut () { return this._el.gamutIndicator.gamut; } get swatchTextContent () { // Children that are not assigned to another slot return [...this.childNodes].filter(n => !n.slot).map(n => n.textContent).join("").trim(); } propChangedCallback ({name, prop, detail: change}) { let input = this._el.input; if (name === "gamuts") { if (this.gamuts === "none") { this._el.gamutIndicator?.remove(); this._el.gamutIndicator = null; } else if (this.gamuts) { if (!this._el.gamutIndicator) { this._el.gamutIndicator = Object.assign(document.createElement("gamut-badge"), { id: "gamut", part: "gamut", exportparts: "label: gamutLabel", gamuts: this.gamuts, color: this.color, }); this.shadowRoot.append(this._el.gamutIndicator); this._el.gamutIndicator.addEventListener("gamutchange", evt => { let gamut = this._el.gamutIndicator.gamut; this.setAttribute("gamut", gamut); this.dispatchEvent(new CustomEvent("gamutchange", { detail: gamut, })); }); } else { this._el.gamutIndicator.gamuts = this.gamuts; } } } if (name === "value") { if (input && (!input.value || input.value !== this.value)) { input.value = this.value; } } if (name === "label") { if (this.label.length && this.label !== this.swatchTextContent) { this._el.label.textContent = this.label; this._el.label.title = this.label; } else { this._el.label.textContent = ""; this._el.label.title = ""; } } if (name === "color") { let isValid = this.color !== null || !this.value; input?.setCustomValidity(isValid ? "" : "Invalid color"); if (this._el.gamutIndicator) { this._el.gamutIndicator.color = this.color; } let colorString = this.color?.display(); this.style.setProperty("--color", colorString); } if (name === "colorInfo") { if (!this.colorInfo) { return; } 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]; let value = this.colorInfo[channel]; if (value === undefined) { continue; } value = typeof value === "number" ? Number(value.toPrecision(4)) : value; info.push(`<div class="coord"><dt>${ label }</dt><dd>${ value }</dd></div>`); } this._el.info.innerHTML = info.join("\n"); } } static props = { size: {}, open: {}, gamuts: { default: "srgb, p3, rec2020: P3+, prophoto: PP", }, value: { type: String, default () { if (this._el.input) { return this._el.input.value; } return this.swatchTextContent; }, reflect: { from: true, }, }, label: { type: String, default () { return this.swatchTextContent; }, convert (value) { return value.trim(); }, }, color: { get type () { return ColorSwatch.Color; }, get () { if (!this.value) { return null; } return ColorSwatch.Color.get(this.value); }, set (value) { this.value = ColorSwatch.Color.get(value)?.display(); }, reflect: false, }, info: { type: { is: Array, values: { is: Object, defaultKey: (coord, i) => ColorSwatch.Color.Space.resolveCoord(coord)?.name, }, }, default: [], reflect: { from: true, }, }, colorInfo: { get () { if (!this.info.length || !this.color) { return; } let ret = {}; for (let coord of this.info) { let [label, channel] = Object.entries(coord)[0]; try { ret[channel] = this.color.get(channel); } catch (e) { console.error(e); } } return ret; }, }, }; static events = { colorchange: { propchange: "color", }, valuechange: { propchange: "value", }, }; }; Self.define(); export default Self;