diff --git a/gallery/src/pages/components/ha-selector.ts b/gallery/src/pages/components/ha-selector.ts
index 9eb13bdee2..2f0fedf1e4 100644
--- a/gallery/src/pages/components/ha-selector.ts
+++ b/gallery/src/pages/components/ha-selector.ts
@@ -40,6 +40,9 @@ const ENTITIES = [
getEntity("switch", "coffee", "off", {
friendly_name: "Coffee",
}),
+ getEntity("number", "number", 5, {
+ friendly_name: "Number",
+ }),
];
const DEVICES: DeviceRegistryEntry[] = [
@@ -377,6 +380,33 @@ const SCHEMAS: {
name: "Constant",
selector: { constant: { value: true, label: "Yes!" } },
},
+ choose: {
+ name: "Choose",
+ selector: {
+ choose: {
+ choices: {
+ number: {
+ selector: {
+ number: {
+ min: 0,
+ max: 100,
+ step: 0.1,
+ },
+ },
+ },
+ entity: {
+ selector: {
+ entity: {
+ filter: {
+ domain: "number",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
},
},
{
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..5b86712b74
--- /dev/null
+++ b/src/components/ha-selector/ha-selector-choose.ts
@@ -0,0 +1,202 @@
+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 { isTemplate } from "../../common/string/has-template";
+import type { ChooseSelector, Selector } from "../../data/selector";
+import type { HomeAssistant } from "../../types";
+import "../ha-button-toggle-group";
+import "./ha-selector";
+
+@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({ attribute: false })
+ public localizeValue?: (key: string) => 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`
+ `;
+ }
+
+ private _toggleButtons = memoizeOne(
+ (choices: ChooseSelector["choose"]["choices"], translationKey?: string) =>
+ Object.keys(choices).map((choice) => ({
+ label:
+ this.localizeValue && translationKey
+ ? this.localizeValue(`${translationKey}.choices.${choice}`)
+ : 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/components/ha-service-control.ts b/src/components/ha-service-control.ts
index 67733abf38..f5a51a6dbf 100644
--- a/src/components/ha-service-control.ts
+++ b/src/components/ha-service-control.ts
@@ -726,6 +726,7 @@ export class HaServiceControl extends LitElement {
: undefined}
.placeholder=${dataField.default}
.localizeValue=${this._localizeValueCallback}
+ .required=${dataField.required}
>
`
: "";
diff --git a/src/data/selector.ts b/src/data/selector.ts
index cf1398b1fd..4a662490e9 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,13 @@ export interface ButtonToggleSelector {
} | null;
}
+export interface ChooseSelector {
+ choose: {
+ choices: Record;
+ translation_key?: string;
+ };
+}
+
export interface ColorRGBSelector {
color_rgb: {} | null;
}
diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-platform.ts b/src/panels/config/automation/condition/types/ha-automation-condition-platform.ts
index c1e15b3a16..d425048f18 100644
--- a/src/panels/config/automation/condition/types/ha-automation-condition-platform.ts
+++ b/src/panels/config/automation/condition/types/ha-automation-condition-platform.ts
@@ -256,6 +256,7 @@ export class HaPlatformCondition extends LitElement {
: undefined}
.placeholder=${dataField.default}
.localizeValue=${this._localizeValueCallback}
+ .required=${dataField.required}
>
`
: nothing;
diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-platform.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-platform.ts
index 58b6287c73..c61ad3a704 100644
--- a/src/panels/config/automation/trigger/types/ha-automation-trigger-platform.ts
+++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-platform.ts
@@ -292,6 +292,7 @@ export class HaPlatformTrigger extends LitElement {
: undefined}
.placeholder=${dataField.default}
.localizeValue=${this._localizeValueCallback}
+ .required=${dataField.required}
>
`
: nothing;