diff --git a/src/components/entity/ha-entity-state-picker.ts b/src/components/entity/ha-entity-state-picker.ts
index f54a8e5cd8..506d099fcf 100644
--- a/src/components/entity/ha-entity-state-picker.ts
+++ b/src/components/entity/ha-entity-state-picker.ts
@@ -20,6 +20,8 @@ class HaEntityStatePicker extends LitElement {
@property({ attribute: false }) public extraOptions?: any[];
+ @property({ attribute: false }) public excludeOptions?: string[];
+
// eslint-disable-next-line lit/no-native-attributes
@property({ type: Boolean }) public autofocus = false;
@@ -49,7 +51,8 @@ class HaEntityStatePicker extends LitElement {
(changedProps.has("_opened") && this._opened) ||
changedProps.has("entityId") ||
changedProps.has("attribute") ||
- changedProps.has("extraOptions")
+ changedProps.has("extraOptions") ||
+ changedProps.has("excludeOptions")
) {
const stateObj = this.entityId
? this.hass.states[this.entityId]
@@ -68,7 +71,7 @@ class HaEntityStatePicker extends LitElement {
),
}))
: []),
- ];
+ ].filter((item) => !(this.excludeOptions || []).includes(item.value));
}
}
diff --git a/src/components/entity/ha-entity-states-picker.ts b/src/components/entity/ha-entity-states-picker.ts
new file mode 100644
index 0000000000..ae0a192e55
--- /dev/null
+++ b/src/components/entity/ha-entity-states-picker.ts
@@ -0,0 +1,145 @@
+import type { PropertyValues } from "lit";
+import { css, html, LitElement, nothing } from "lit";
+import { customElement, property } from "lit/decorators";
+import { keyed } from "lit/directives/keyed";
+import { repeat } from "lit/directives/repeat";
+import { fireEvent } from "../../common/dom/fire_event";
+import { ensureArray } from "../../common/array/ensure-array";
+import type { HomeAssistant } from "../../types";
+import "./ha-entity-state-picker";
+
+@customElement("ha-entity-states-picker")
+export class HaEntityStatesPicker extends LitElement {
+ @property({ attribute: false }) public hass!: HomeAssistant;
+
+ @property({ attribute: false }) public entityId?: string;
+
+ @property() public attribute?: string;
+
+ @property({ attribute: false }) public extraOptions?: any[];
+
+ @property({ type: Boolean, attribute: "allow-custom-value" })
+ public allowCustomValue;
+
+ @property() public label?: string;
+
+ @property({ type: Array }) public value?: string[];
+
+ @property() public helper?: string;
+
+ @property({ type: Boolean }) public disabled = false;
+
+ @property({ type: Boolean }) public required = false;
+
+ private _keys: string[] = [];
+
+ private _getKey(index: number) {
+ if (!this._keys[index]) {
+ this._keys[index] = Math.random().toString();
+ }
+ return this._keys[index];
+ }
+
+ protected willUpdate(changedProps: PropertyValues): void {
+ super.willUpdate(changedProps);
+ if (changedProps.has("value")) {
+ this.value = ensureArray(this.value);
+ }
+ }
+
+ protected render() {
+ if (!this.hass) {
+ return nothing;
+ }
+
+ const value = this.value || [];
+
+ return html`
+ ${repeat(
+ value,
+ (_state, index) => this._getKey(index),
+ (state, index) => html`
+
+ v !== state)}
+ .allowCustomValue=${this.allowCustomValue}
+ .label=${this.label}
+ .value=${state}
+ .disabled=${this.disabled}
+ .helper=${this.disabled && index === value.length - 1
+ ? this.helper
+ : undefined}
+ @value-changed=${this._valueChanged}
+ >
+
+ `
+ )}
+
+ ${this.disabled && value.length
+ ? nothing
+ : keyed(
+ value.length,
+ html``
+ )}
+
+ `;
+ }
+
+ private _valueChanged(ev: CustomEvent) {
+ ev.stopPropagation();
+ const newState = ev.detail.value;
+ const newValue = [...this.value!];
+ const index = (ev.currentTarget as any)?.index;
+ if (!index) {
+ return;
+ }
+ if (newState === undefined) {
+ newValue.splice(index, 1);
+ this._keys.splice(index, 1);
+ fireEvent(this, "value-changed", {
+ value: newValue,
+ });
+ return;
+ }
+ newValue[index] = newState;
+ fireEvent(this, "value-changed", {
+ value: newValue,
+ });
+ }
+
+ private _addValue(ev: CustomEvent) {
+ ev.stopPropagation();
+ fireEvent(this, "value-changed", {
+ value: [...(this.value || []), ev.detail.value],
+ });
+ }
+
+ static override styles = css`
+ div {
+ margin-top: 8px;
+ }
+ `;
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-entity-states-picker": HaEntityStatesPicker;
+ }
+}
diff --git a/src/components/ha-combo-box.ts b/src/components/ha-combo-box.ts
index ab5ecfe4c3..c80186a341 100644
--- a/src/components/ha-combo-box.ts
+++ b/src/components/ha-combo-box.ts
@@ -208,6 +208,7 @@ export class HaComboBox extends LitElement {
aria-label=${ifDefined(this.hass?.localize("ui.common.clear"))}
class="clear-button"
.path=${mdiClose}
+ ?disabled=${this.disabled}
@click=${this._clearValue}
>`
: ""}
@@ -386,7 +387,8 @@ export class HaComboBox extends LitElement {
:host([opened]) .toggle-button {
color: var(--primary-color);
}
- .toggle-button[disabled] {
+ .toggle-button[disabled],
+ .clear-button[disabled] {
color: var(--disabled-text-color);
pointer-events: none;
}
diff --git a/src/components/ha-form/compute-initial-ha-form-data.ts b/src/components/ha-form/compute-initial-ha-form-data.ts
index f753855b1b..d94a0bfb27 100644
--- a/src/components/ha-form/compute-initial-ha-form-data.ts
+++ b/src/components/ha-form/compute-initial-ha-form-data.ts
@@ -106,6 +106,8 @@ export const computeInitialHaFormData = (
data[field.name] = [];
} else if ("media" in selector || "target" in selector) {
data[field.name] = {};
+ } else if ("state" in selector) {
+ data[field.name] = selector.state?.multiple ? [] : "";
} else {
throw new Error(
`Selector ${Object.keys(selector)[0]} not supported in initial form data`
diff --git a/src/components/ha-selector/ha-selector-selector.ts b/src/components/ha-selector/ha-selector-selector.ts
index af38d908d6..995f36b6e3 100644
--- a/src/components/ha-selector/ha-selector-selector.ts
+++ b/src/components/ha-selector/ha-selector-selector.ts
@@ -112,6 +112,10 @@ const SELECTOR_SCHEMAS = {
name: "entity_id",
selector: { entity: {} },
},
+ {
+ name: "multiple",
+ selector: { boolean: {} },
+ },
] as const,
target: [] as const,
template: [] as const,
diff --git a/src/components/ha-selector/ha-selector-state.ts b/src/components/ha-selector/ha-selector-state.ts
index 8350694a33..8d2b193375 100644
--- a/src/components/ha-selector/ha-selector-state.ts
+++ b/src/components/ha-selector/ha-selector-state.ts
@@ -4,6 +4,7 @@ import type { StateSelector } from "../../data/selector";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../types";
import "../entity/ha-entity-state-picker";
+import "../entity/ha-entity-states-picker";
@customElement("ha-selector-state")
export class HaSelectorState extends SubscribeMixin(LitElement) {
@@ -27,6 +28,24 @@ export class HaSelectorState extends SubscribeMixin(LitElement) {
};
protected render() {
+ if (this.selector.state?.multiple) {
+ return html`
+
+ `;
+ }
return html`