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`