From 91c0d052dfe3becad666f88ec718bd1e6c5ed2b5 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 9 Oct 2026 10:45:01 +0200 Subject: [PATCH] Convert CSS color names to hex when saving a label (#54738) --- src/common/color/hex.ts | 11 +++++++ .../config/labels/dialog-label-detail.ts | 14 ++++++++- test/common/color/hex.test.ts | 29 ++++++++++++++++++- 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/common/color/hex.ts b/src/common/color/hex.ts index ad5e6e64ae..03357e7321 100644 --- a/src/common/color/hex.ts +++ b/src/common/color/hex.ts @@ -39,3 +39,14 @@ export const hexBlend = (c1: string, c2: string, blend = 50): string => { } return `#${color}`; }; + +/** + * Converts a CSS color, like a color name, to `#rrggbb`. + * @param color - Any CSS color (name, hex, rgb(), hsl() and so on). + * @returns The hex color, or undefined for invalid colors and colors with + * transparency. + */ +export const cssColorToHex = (color: string): string | undefined => { + const parsed = parse(color.trim()); + return parsed && (parsed.alpha ?? 1) === 1 ? formatHex(parsed) : undefined; +}; diff --git a/src/panels/config/labels/dialog-label-detail.ts b/src/panels/config/labels/dialog-label-detail.ts index 970e8ed7df..6767996cfc 100644 --- a/src/panels/config/labels/dialog-label-detail.ts +++ b/src/panels/config/labels/dialog-label-detail.ts @@ -2,6 +2,8 @@ import type { ContextType } from "@lit/context"; import type { CSSResultGroup } from "lit"; import { LitElement, css, html, nothing } from "lit"; import { customElement, state } from "lit/decorators"; +import { THEME_COLORS } from "../../../common/color/compute-color"; +import { cssColorToHex } from "../../../common/color/hex"; import { consume } from "../../../common/decorators/consume"; import "../../../components/ha-alert"; import "../../../components/ha-button"; @@ -189,13 +191,23 @@ class DialogLabelDetail extends DirtyStateProviderMixin()( this._updateDirtyState(this._currentState()); } + // Core stores a theme color or #RRGGBB, but the color picker also accepts + // CSS colors like "gold". Convert those, so they can be saved. + private _labelColor(): string | null { + const color = this._color.trim(); + if (!color || THEME_COLORS.has(color) || /^#[0-9a-f]{6}$/i.test(color)) { + return color || null; + } + return cssColorToHex(color) ?? color; + } + private async _updateEntry() { this._submitting = true; try { const values: LabelRegistryEntryMutableParams = { name: this._name.trim(), icon: this._icon.trim() || null, - color: this._color.trim() || null, + color: this._labelColor(), description: this._description.trim() || null, }; if (this.params!.entry) { diff --git a/test/common/color/hex.test.ts b/test/common/color/hex.test.ts index 08e01f5d03..53c9a9630e 100644 --- a/test/common/color/hex.test.ts +++ b/test/common/color/hex.test.ts @@ -1,5 +1,9 @@ import { describe, it, expect } from "vitest"; -import { expandHex, hexBlend } from "../../../src/common/color/hex"; +import { + cssColorToHex, + expandHex, + hexBlend, +} from "../../../src/common/color/hex"; describe("expandHex", () => { it("should expand a 3-digit hex code to 6 digits", () => { @@ -28,3 +32,26 @@ describe("hexBlend", () => { expect(hexBlend("#ff0000", "#0000ff", 0)).toBe("#0000ff"); }); }); + +describe("cssColorToHex", () => { + it("converts a CSS color name to hex", () => { + expect(cssColorToHex("Gold")).toBe("#ffd700"); + }); + + it("converts other opaque CSS colors to hex", () => { + expect(cssColorToHex("#FFF")).toBe("#ffffff"); + expect(cssColorToHex("rgb(10, 20, 30)")).toBe("#0a141e"); + expect(cssColorToHex("hsl(120 100% 50%)")).toBe("#00ff00"); + }); + + it("rejects invalid colors, theme color names and CSS-wide values", () => { + expect(cssColorToHex("not-a-color")).toBeUndefined(); + expect(cssColorToHex("primary")).toBeUndefined(); + expect(cssColorToHex("inherit")).toBeUndefined(); + }); + + it("rejects colors with transparency", () => { + expect(cssColorToHex("rgba(255, 0, 0, 0.5)")).toBeUndefined(); + expect(cssColorToHex("transparent")).toBeUndefined(); + }); +});