Dashboard Studio 2.3.1: light colours behind a palette button

- The light card's colours wait behind a palette button next to the
  brightness; picking a colour folds them away again

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-27 11:47:13 +01:00
parent 2e929b55bb
commit 095b23481e
7 changed files with 33 additions and 7 deletions
+10
View File
@@ -2094,7 +2094,9 @@
else if (act === "light-color") {
const [kind, val] = a.dataset.v.split(":");
ha.callService("light", "turn_on", kind === "k" ? { color_temp_kelvin: Number(val) } : { rgb_color: val.split(",").map(Number) }, eid);
if (caches[a.closest(".w[data-id]")?.dataset.id]?.colors) toggleLightColors(a); // a colour picked: the row closes again
}
else if (act === "light-colors") toggleLightColors(a);
else if (act === "light-popup") lightPopup(eid);
else if (act === "todo-add") todoAdd(a.closest(".todo-add"));
else if (act === "alarm-key") {
@@ -2183,6 +2185,14 @@
if (t.dataset.act === "cover-tilt") ha.callService("cover", "set_cover_tilt_position", { tilt_position: Number(t.value) }, eid);
});
/** The light card's colours, behind their button: open or closed, kept on this device and not saved. */
function toggleLightColors(btn) {
const box = btn.closest(".w[data-id]"), id = box?.dataset.id, cache = (caches[id] ||= {});
cache.colors = !cache.colors;
const w = findWidget(id); // also finds a light card inside a scroll or swipe card
if (w) renderWidget(w, box, { child: box.classList.contains("child") });
}
// ------------------------------------------------------------ dialogs
let modalClose = null;
function openModal(html, onClick) {
+2
View File
@@ -783,6 +783,8 @@
"are open": "staan open",
"is open": "staat open",
"Hide these entities": "Deze entiteiten verbergen",
"Close colors": "Kleuren sluiten",
"Colors for {0}": "Kleuren voor {0}",
"Today": "Vandaag", "Tomorrow": "Morgen", "All day": "Hele dag", "Busy": "Bezet",
};
+7 -2
View File
@@ -1955,8 +1955,6 @@ button.fader-input { cursor: pointer; }
.lf-row.on .lf-pct { color: inherit; }
.lf-row .switch { transform: scale(.85); }
/* not much room: the colours go (the bulb opens them) */
@container (max-height: 150px) { .lt-foot .swatches { display: none; } }
@container (max-width: 260px) { .lt-foot .swatches { display: none; } }
/* room: the name and what's going on, the climate in large figures, then its devices as tiles that light up */
.rm { display: flex; flex-direction: column; gap: 12px; height: 100%; min-height: 0; }
@@ -2001,3 +1999,10 @@ button.fader-input { cursor: pointer; }
.rm-head .rm-ico { display: none; }
}
@container (max-width: 200px) and (max-height: 190px) { .lt-sub { display: none; } .lt-big { font-size: 28px; } .lt-main { gap: 4px; } }
/* the light's colours, behind a palette button */
.lt-pal { flex: none; width: var(--c-ctl); height: var(--c-ctl); border-radius: 50%; display: grid; place-items: center; font-size: 18px; border: 1.5px solid color-mix(in srgb, currentColor 28%, transparent); box-sizing: border-box; }
.lt.on .lt-pal { color: var(--lit-text); }
.lt-colors { flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px; }
.lt-colors .swatches { flex: 1; min-width: 0; margin: 0; justify-content: flex-start; }
.lt-colors .swatch { width: 28px; height: 28px; }
+8 -3
View File
@@ -259,14 +259,19 @@
entities: (c, ha) => [c.entity, ...(ha?.states[c.entity]?.attributes.entity_id || [])].filter(Boolean),
// The card is the lamp: when it's on, the brightness fills the card from the left in the lamp's colour,
// and dragging anywhere across it dims. A group adds a row per light, each its own little fader.
render(c, { ha }) {
render(c, { ha, cache }) {
const s = ha.states[c.entity];
if (!s) return missing(c.entity, "a light");
const members = c.members !== false && Array.isArray(s.attributes.entity_id) ? s.attributes.entity_id.filter((e) => ha.states[e]) : [];
const name = friendly(s, c.name);
const onCount = members.filter((e) => ha.states[e].state === "on").length;
const sub = members.length ? (onCount ? t("{0} of {1} on", onCount, members.length) : t("All off")) : lightNote(s);
const colors = c.colors !== false && !members.length ? swatches(c.entity, s, { compact: true }) : "";
// The colours wait behind a palette button; opened, they take the figure's place until one is picked.
const hasColors = c.colors !== false && (canTemp(s) || canColor(s));
const open = hasColors && cache.colors;
const foot = open
? `<div class="lt-colors">${swatches(c.entity, s)}<button class="lt-pal wbtn" data-act="light-colors" aria-expanded="true" aria-label="${t("Close colors")}">${Icons.svg("close")}</button></div>`
: `<span class="lt-big num">${esc(bigText(s))}</span>${hasColors ? `<button class="lt-pal wbtn" data-act="light-colors" aria-expanded="false" aria-label="${esc(t("Colors for {0}", name))}">${Icons.svg("palette")}</button>` : ""}`;
const rows = members.map((eid) => {
const m = ha.states[eid];
@@ -281,7 +286,7 @@
<span class="lt-title"><span class="name">${esc(name)}</span><span class="lt-sub">${esc(sub)}</span></span>
<button class="light-switch wbtn" role="switch" aria-checked="${isOn(s)}" data-act="toggle" data-e="${esc(c.entity)}" aria-label="${esc(name)}"><span class="switch"></span></button>
</div>
<div class="lt-foot"><span class="lt-big num">${esc(bigText(s))}</span>${colors}</div>`, "lt-main");
<div class="lt-foot">${foot}</div>`, "lt-main");
return `<div class="lt ${members.length ? "group" : ""} ${isOn(s) ? "on" : ""}">${main}${rows ? `<div class="lt-rows" role="group" aria-label="${t("Lights in {0}", esc(name))}">${rows}</div>` : ""}</div>`;
},
lit: (c, { ha }) => litKind(ha.states[c.entity]),