From a9b7498c59b09304d60d4bd991fa660d67c0cd9a Mon Sep 17 00:00:00 2001 From: Dmitry Sharabin Date: Wed, 20 Nov 2024 15:27:48 +0100 Subject: [PATCH 01/62] Basic support for the editable prop --- src/color-scale/color-scale.js | 76 +++++++++++++++++++++++++++++++- src/color-swatch/color-swatch.js | 2 +- 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/src/color-scale/color-scale.js b/src/color-scale/color-scale.js index e874d394..bbcb9b14 100644 --- a/src/color-scale/color-scale.js +++ b/src/color-scale/color-scale.js @@ -21,21 +21,43 @@ const Self = class ColorScale extends ColorElement { connectedCallback () { super.connectedCallback?.(); + this._el.swatches.addEventListener("input", this, {capture: true}); this._el.swatches.addEventListener("colorchange", this, {capture: true}); } disconnectedCallback () { this.#swatches = []; + this._el.swatches.removeEventListener("input", this, {capture: true}); this._el.swatches.removeEventListener("colorchange", this, {capture: true}); } handleEvent (event) { + if (event.type === "input" && (!this.editable?.name || !event.target.matches("input[slot=before]"))) { + return; + } + + let colors = Object.entries(this.colors); + let swatchIndex = this.#swatches.indexOf(event.target.closest("color-swatch")); + if (swatchIndex > -1) { + if (event.type === "colorchange") { + colors[swatchIndex][1] = event.target.color; + } + else { + // Color name changed + colors[swatchIndex][0] = event.target.value; + } + + this.colors = Object.fromEntries(colors); + } + this.dispatchEvent(new event.constructor(event.type, {...event})); } propChangedCallback ({name, prop, detail: change}) { - if (name === "computedColors") { + if ((name === "computedColors" && !this.editable?.name && !this.editable?.color) || name === "editable") { // Re-render swatches + // Only if nothing is being edited, otherwise the input would be lost + // or, e.g., "red" would be converted to "rgb(100%, 0%, 0%)" right after the typing is done this.render(); } } @@ -65,7 +87,26 @@ const Self = class ColorScale extends ColorElement { } swatch.color = color; - swatch.label = name; + if (this.editable?.name || this.editable?.color) { + let html = ""; + + if (this.editable.name) { + html += ``; + } + else { + html += `${ name }`; + } + + if (this.editable.color) { + html += ``; + } + + swatch.innerHTML = html; + } + else { + swatch.textContent = name; + } + if (this.info) { swatch.info = this.info; } @@ -149,6 +190,37 @@ const Self = class ColorScale extends ColorElement { }, additionalDependencies: ["info"], }, + editable: { + parse (value) { + if (value === undefined || value === null || value === false || value === "false") { + return; + } + + if (value === "" || value === true || value === "true") { + // Boolean attribute + return { + name: true, + color: true, + }; + } + + if (typeof value === "string") { + // Convert to object + let entries = value.split(/\s*[,\s]\s*/).map(key => [key.trim(), true]); + return Object.fromEntries(entries); + } + + if (typeof value === "object") { + return value; + } + + console.warn(`The specified value "${ value }" cannot be used as a value of the "editable" property.`); + return; + }, + reflect: { + from: true, + }, + }, info: {}, }; diff --git a/src/color-swatch/color-swatch.js b/src/color-swatch/color-swatch.js index 10694d6d..b8d2d669 100644 --- a/src/color-swatch/color-swatch.js +++ b/src/color-swatch/color-swatch.js @@ -42,7 +42,7 @@ const Self = class ColorSwatch extends ColorElement { #updateStatic () { let previousInput = this._el.input; - let input = this._el.input = this.querySelector("input"); + let input = this._el.input = this.querySelector("input:not([slot])"); this.static = !input; From efcf55cbd71ec38f09bef5475ddd3e3df8767601 Mon Sep 17 00:00:00 2001 From: Dmitry Sharabin Date: Wed, 20 Nov 2024 16:33:45 +0100 Subject: [PATCH 02/62] Allow add colors --- src/color-scale/color-scale.js | 42 ++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/color-scale/color-scale.js b/src/color-scale/color-scale.js index bbcb9b14..58cdab25 100644 --- a/src/color-scale/color-scale.js +++ b/src/color-scale/color-scale.js @@ -59,6 +59,42 @@ const Self = class ColorScale extends ColorElement { // Only if nothing is being edited, otherwise the input would be lost // or, e.g., "red" would be converted to "rgb(100%, 0%, 0%)" right after the typing is done this.render(); + + if (name === "editable") { + let addButton = this._el.addButton; + if (this.editable?.color) { + if (!addButton) { + addButton = this._el.addButton = Object.assign(document.createElement("button"), { + id: "add-button", + part: "add-button", + textContent: "Add color", + }); + + addButton.addEventListener("click", evt => { + let {name, color} = this.defaultColor?.() ?? {}; + [name, color] = [name ?? "New color", color ?? this.computedColors.at(-1).color]; + + if (this.colors[name]) { + // Name already exists + // Append a number to the name + let i = 1; + while (this.colors[`${ name } ${ i }`]) { + i++; + } + name = `${ name } ${ i }`; + } + + this.colors = {...this.colors, [name]: color}; + this.render(); + }); + } + + this._el.swatches.after(addButton); + } + else { + addButton?.remove(); + } + } } } @@ -221,6 +257,12 @@ const Self = class ColorScale extends ColorElement { from: true, }, }, + defaultColor: { + type: { + is: Function, + }, + reflect: false, + }, info: {}, }; From 5a1998a7d1eacdfed63f6e831c0d4a674049c25a Mon Sep 17 00:00:00 2001 From: Dmitry Sharabin Date: Wed, 20 Nov 2024 17:24:34 +0100 Subject: [PATCH 03/62] Allow remove colors --- src/color-scale/color-scale.js | 74 ++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/src/color-scale/color-scale.js b/src/color-scale/color-scale.js index 58cdab25..dd061cfd 100644 --- a/src/color-scale/color-scale.js +++ b/src/color-scale/color-scale.js @@ -23,12 +23,14 @@ const Self = class ColorScale extends ColorElement { super.connectedCallback?.(); this._el.swatches.addEventListener("input", this, {capture: true}); this._el.swatches.addEventListener("colorchange", this, {capture: true}); + this._el.swatches.addEventListener("click", this, {capture: true}); } disconnectedCallback () { this.#swatches = []; this._el.swatches.removeEventListener("input", this, {capture: true}); this._el.swatches.removeEventListener("colorchange", this, {capture: true}); + this._el.swatches.removeEventListener("click", this, {capture: true}); } handleEvent (event) { @@ -42,10 +44,16 @@ const Self = class ColorScale extends ColorElement { if (event.type === "colorchange") { colors[swatchIndex][1] = event.target.color; } - else { + else if (event.type === "input") { // Color name changed colors[swatchIndex][0] = event.target.value; } + else if (event.type === "click" && event.target.matches("[part=remove-button]")) { + colors.splice(swatchIndex, 1); + this.colors = Object.fromEntries(colors); + this.render(); + return; + } this.colors = Object.fromEntries(colors); } @@ -54,47 +62,52 @@ const Self = class ColorScale extends ColorElement { } propChangedCallback ({name, prop, detail: change}) { - if ((name === "computedColors" && !this.editable?.name && !this.editable?.color) || name === "editable") { + if ((name === "computedColors" && !this.editable?.name && !this.editable?.color)) { // Re-render swatches // Only if nothing is being edited, otherwise the input would be lost // or, e.g., "red" would be converted to "rgb(100%, 0%, 0%)" right after the typing is done this.render(); + } - if (name === "editable") { - let addButton = this._el.addButton; - if (this.editable?.color) { - if (!addButton) { - addButton = this._el.addButton = Object.assign(document.createElement("button"), { - id: "add-button", - part: "add-button", - textContent: "Add color", - }); - - addButton.addEventListener("click", evt => { - let {name, color} = this.defaultColor?.() ?? {}; - [name, color] = [name ?? "New color", color ?? this.computedColors.at(-1).color]; - - if (this.colors[name]) { - // Name already exists - // Append a number to the name - let i = 1; - while (this.colors[`${ name } ${ i }`]) { - i++; - } - name = `${ name } ${ i }`; + if (name === "editable") { + let addButton = this._el.addButton; + if (this.editable?.color) { + if (!addButton) { + addButton = this._el.addButton = Object.assign(document.createElement("button"), { + id: "add-button", + part: "add-button", + textContent: "Add color", + }); + + addButton.addEventListener("click", evt => { + let {name, color} = this.defaultColor?.() ?? {}; + [name, color] = [name ?? "New color", color ?? this.computedColors.at(-1)?.color ?? new Self.Color("#f06").to(this.space)]; + + if (this.colors[name]) { + // Name already exists + // Append a number to the name + let i = 1; + while (this.colors[`${ name } ${ i }`]) { + i++; } + name = `${ name } ${ i }`; + } - this.colors = {...this.colors, [name]: color}; - this.render(); - }); - } + this.colors = {...this.colors, [name]: color}; + this.render(); + }); this._el.swatches.after(addButton); } else { - addButton?.remove(); + addButton.style.removeProperty("display"); } } + else { + addButton?.style.setProperty("display", "none"); + } + + this.render(); } } @@ -118,7 +131,7 @@ const Self = class ColorScale extends ColorElement { this.#swatches[i] = swatch = document.createElement("color-swatch"); swatch.setAttribute("size", "large"); swatch.setAttribute("part", "color-swatch"); - swatch.setAttribute("exportparts", "swatch, info, gamut"); + swatch.setAttribute("exportparts", "swatch, info, gamut, remove-button"); newSwatches.push(swatch); } @@ -135,6 +148,7 @@ const Self = class ColorScale extends ColorElement { if (this.editable.color) { html += ``; + html += ``; } swatch.innerHTML = html; From 283445ee1c2faf7904a279a47666f03cfc6c854f Mon Sep 17 00:00:00 2001 From: Dmitry Sharabin Date: Wed, 20 Nov 2024 17:36:04 +0100 Subject: [PATCH 04/62] Improve UX --- src/color-scale/color-scale.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/color-scale/color-scale.js b/src/color-scale/color-scale.js index dd061cfd..680c4fc3 100644 --- a/src/color-scale/color-scale.js +++ b/src/color-scale/color-scale.js @@ -95,6 +95,12 @@ const Self = class ColorScale extends ColorElement { this.colors = {...this.colors, [name]: color}; this.render(); + + // Focus the new color input and select its content + let swatch = this._el.swatches.lastElementChild; + let input = swatch.querySelector("input:not([slot]"); + input.focus(); + input.select(); }); this._el.swatches.after(addButton); From f08d70c45e3027e46a050824f90966478093a120 Mon Sep 17 00:00:00 2001 From: Dmitry Sharabin Date: Wed, 20 Nov 2024 17:36:21 +0100 Subject: [PATCH 05/62] Add an example --- src/color-scale/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/color-scale/README.md b/src/color-scale/README.md index b4e746c3..15a854c2 100644 --- a/src/color-scale/README.md +++ b/src/color-scale/README.md @@ -67,6 +67,15 @@ You can also create compact color scales, by simply setting `--details-style: co Issue: How to make them focusable?? +### The `editable` attribute + +```html + + +``` +