From 999c671cb9e500d2bc9471207af8bf94a8b581ca Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 19 Dec 2025 00:20:38 +0100 Subject: [PATCH] Add choose selector --- .../ha-selector/ha-selector-choose.ts | 198 ++++++++++++++++++ src/components/ha-selector/ha-selector.ts | 1 + src/data/selector.ts | 7 + 3 files changed, 206 insertions(+) create mode 100644 src/components/ha-selector/ha-selector-choose.ts diff --git a/src/components/ha-selector/ha-selector-choose.ts b/src/components/ha-selector/ha-selector-choose.ts new file mode 100644 index 0000000000..253b36efcc --- /dev/null +++ b/src/components/ha-selector/ha-selector-choose.ts @@ -0,0 +1,198 @@ +import type { PropertyValues } from "lit"; +import { css, html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import { fireEvent } from "../../common/dom/fire_event"; +import type { ChooseSelector, Selector } from "../../data/selector"; +import type { HomeAssistant } from "../../types"; +import "./ha-selector"; +import { isTemplate } from "../../common/string/has-template"; + +@customElement("ha-selector-choose") +export class HaChooseSelector extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public selector!: ChooseSelector; + + @property() public value?: any; + + @property() public label?: string; + + @property() public helper?: string; + + @property({ type: Boolean }) public disabled = false; + + @property({ type: Boolean }) public required = true; + + @state() public _activeChoice?: string; + + protected willUpdate(changedProperties: PropertyValues): void { + if ( + changedProperties.has("selector") && + (!this._activeChoice || + !(this._activeChoice in this.selector.choose.choices)) + ) { + this._setActiveChoice(); + } + } + + protected render() { + if (!this._activeChoice) { + return nothing; + } + + const selector = this._selector(this._activeChoice); + const value = this._value(this._activeChoice); + + return html`
+ ${this.label}${this.required ? "*" : ""} + +
+ `; + } + + private _toggleButtons = memoizeOne( + ( + choices: ChooseSelector["choose"]["choices"], + localize: HomeAssistant["localize"] + ) => + Object.keys(choices).map((choice) => ({ + label: localize("") || choice, + value: choice, + })) + ); + + private _choiceChanged(ev) { + ev.stopPropagation(); + const value = + typeof this.value === "object" + ? this.value + : { + [this._activeChoice!]: this.value, + }; + this._activeChoice = ev.detail?.value || ev.target.value; + fireEvent(this, "value-changed", { + value: { + ...value, + active_choice: this._activeChoice, + }, + }); + } + + private _handleValueChanged(ev: CustomEvent) { + ev.stopPropagation(); + const value = typeof this.value === "object" ? this.value : {}; + fireEvent(this, "value-changed", { + value: { + ...value, + [this._activeChoice!]: ev.detail.value, + active_choice: this._activeChoice, + }, + }); + } + + private _selector(choice?: string): Selector { + const choices = this.selector.choose.choices; + + choice = choice || this.value?.active_choice; + + if (choice && choice in choices) { + return choices[choice].selector; + } + + return choices[Object.keys(choices)[0]].selector; + } + + private _value(choice?: string): any { + if (!this.value) { + return undefined; + } + return typeof this.value === "object" + ? this.value[choice || this.value.active_choice] + : this.value; + } + + private _setActiveChoice() { + if (this.value) { + if (typeof this.value === "object") { + if (this.value.active_choice in this.selector.choose.choices) { + this._activeChoice = this.value.active_choice; + return; + } + } else { + const typeofValue = typeof this.value; + const selectorTypes = Object.values(this.selector.choose.choices).map( + (choice) => Object.keys(choice.selector)[0] + ); + if (typeofValue === "number" && selectorTypes.includes("number")) { + this._activeChoice = Object.keys(this.selector.choose.choices)[ + selectorTypes.indexOf("number") + ]; + return; + } + if ( + typeofValue === "string" && + isTemplate(this.value) && + selectorTypes.includes("template") + ) { + this._activeChoice = Object.keys(this.selector.choose.choices)[ + selectorTypes.indexOf("template") + ]; + return; + } + if ( + typeofValue === "string" && + this.value.includes(".") && + selectorTypes.includes("entity") + ) { + this._activeChoice = Object.keys(this.selector.choose.choices)[ + selectorTypes.indexOf("entity") + ]; + return; + } + if (typeofValue === "string" && selectorTypes.includes("text")) { + this._activeChoice = Object.keys(this.selector.choose.choices)[ + selectorTypes.indexOf("text") + ]; + return; + } + } + } + this._activeChoice = Object.keys(this.selector.choose.choices)[0]; + } + + static styles = css` + .multi-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--ha-space-2); + } + ha-button-toggle-group { + display: block; + justify-self: end; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-selector-choose": HaChooseSelector; + } +} diff --git a/src/components/ha-selector/ha-selector.ts b/src/components/ha-selector/ha-selector.ts index 6fb5f56344..3487a6b21d 100644 --- a/src/components/ha-selector/ha-selector.ts +++ b/src/components/ha-selector/ha-selector.ts @@ -18,6 +18,7 @@ const LOAD_ELEMENTS = { attribute: () => import("./ha-selector-attribute"), assist_pipeline: () => import("./ha-selector-assist-pipeline"), boolean: () => import("./ha-selector-boolean"), + choose: () => import("./ha-selector-choose"), color_rgb: () => import("./ha-selector-color-rgb"), condition: () => import("./ha-selector-condition"), config_entry: () => import("./ha-selector-config-entry"), diff --git a/src/data/selector.ts b/src/data/selector.ts index cf1398b1fd..c9e707db08 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -27,6 +27,7 @@ export type Selector = | AttributeSelector | BooleanSelector | ButtonToggleSelector + | ChooseSelector | ColorRGBSelector | ColorTempSelector | ConditionSelector @@ -116,6 +117,12 @@ export interface ButtonToggleSelector { } | null; } +export interface ChooseSelector { + choose: { + choices: Record; + }; +} + export interface ColorRGBSelector { color_rgb: {} | null; }