diff --git a/src/panels/lovelace/card-features/hui-select-options-card-feature.ts b/src/panels/lovelace/card-features/hui-select-options-card-feature.ts index 5cb20609ce..60b99116e4 100644 --- a/src/panels/lovelace/card-features/hui-select-options-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-select-options-card-feature.ts @@ -9,8 +9,9 @@ import { UNAVAILABLE } from "../../../data/entity"; import { InputSelectEntity } from "../../../data/input_select"; import { SelectEntity } from "../../../data/select"; import { HomeAssistant } from "../../../types"; -import { LovelaceCardFeature } from "../types"; +import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; import { SelectOptionsCardFeatureConfig } from "./types"; +import { filterModes } from "./common/filter-modes"; export const supportsSelectOptionsCardFeature = (stateObj: HassEntity) => { const domain = computeDomain(stateObj.entity_id); @@ -41,6 +42,13 @@ class HuiSelectOptionsCardFeature }; } + public static async getConfigElement(): Promise { + await import( + "../editor/config-elements/hui-select-options-card-feature-editor" + ); + return document.createElement("hui-select-options-card-feature-editor"); + } + public setConfig(config: SelectOptionsCardFeatureConfig): void { if (!config) { throw new Error("Invalid configuration"); @@ -105,6 +113,11 @@ class HuiSelectOptionsCardFeature const stateObj = this.stateObj; + const options = filterModes( + this.stateObj.attributes.options, + this._config.options + ); + return html`
- ${stateObj.attributes.options!.map( + ${options.map( (option) => html` ${this.hass!.formatEntityState(stateObj, option)} diff --git a/src/panels/lovelace/card-features/types.ts b/src/panels/lovelace/card-features/types.ts index 82fa311876..04a0f1df5f 100644 --- a/src/panels/lovelace/card-features/types.ts +++ b/src/panels/lovelace/card-features/types.ts @@ -75,6 +75,7 @@ export interface ClimatePresetModesCardFeatureConfig { export interface SelectOptionsCardFeatureConfig { type: "select-options"; + options?: string[]; } export interface NumericInputCardFeatureConfig { diff --git a/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts b/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts index c4b938230a..288a6976b8 100644 --- a/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts @@ -21,9 +21,9 @@ import { import { HomeAssistant } from "../../../../types"; import { supportsAlarmModesCardFeature } from "../../card-features/hui-alarm-modes-card-feature"; import { supportsClimateFanModesCardFeature } from "../../card-features/hui-climate-fan-modes-card-feature"; -import { supportsClimateSwingModesCardFeature } from "../../card-features/hui-climate-swing-modes-card-feature"; import { supportsClimateHvacModesCardFeature } from "../../card-features/hui-climate-hvac-modes-card-feature"; import { supportsClimatePresetModesCardFeature } from "../../card-features/hui-climate-preset-modes-card-feature"; +import { supportsClimateSwingModesCardFeature } from "../../card-features/hui-climate-swing-modes-card-feature"; import { supportsCoverOpenCloseCardFeature } from "../../card-features/hui-cover-open-close-card-feature"; import { supportsCoverPositionCardFeature } from "../../card-features/hui-cover-position-card-feature"; import { supportsCoverTiltCardFeature } from "../../card-features/hui-cover-tilt-card-feature"; @@ -53,13 +53,13 @@ type SupportsFeature = (stateObj: HassEntity) => boolean; const UI_FEATURE_TYPES = [ "alarm-modes", "climate-fan-modes", - "climate-swing-modes", "climate-hvac-modes", "climate-preset-modes", + "climate-swing-modes", "cover-open-close", "cover-position", - "cover-tilt", "cover-tilt-position", + "cover-tilt", "fan-preset-modes", "fan-speed", "humidifier-modes", @@ -82,14 +82,15 @@ type UiFeatureTypes = (typeof UI_FEATURE_TYPES)[number]; const EDITABLES_FEATURE_TYPES = new Set([ "alarm-modes", - "climate-hvac-modes", "climate-fan-modes", - "climate-swing-modes", + "climate-hvac-modes", "climate-preset-modes", + "climate-swing-modes", "fan-preset-modes", "humidifier-modes", "lawn-mower-commands", "numeric-input", + "select-options", "update-actions", "vacuum-commands", "water-heater-operation-modes", diff --git a/src/panels/lovelace/editor/config-elements/hui-select-options-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-select-options-card-feature-editor.ts new file mode 100644 index 0000000000..65b4dc6faf --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-select-options-card-feature-editor.ts @@ -0,0 +1,140 @@ +import { HassEntity } from "home-assistant-js-websocket"; +import { 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 { FormatEntityStateFunc } from "../../../../common/translations/entity-state"; +import "../../../../components/ha-form/ha-form"; +import type { + HaFormSchema, + SchemaUnion, +} from "../../../../components/ha-form/types"; +import type { HomeAssistant } from "../../../../types"; +import { + LovelaceCardFeatureContext, + SelectOptionsCardFeatureConfig, +} from "../../card-features/types"; +import type { LovelaceCardFeatureEditor } from "../../types"; + +type SelectOptionsCardFeatureData = SelectOptionsCardFeatureConfig & { + customize_options: boolean; +}; + +@customElement("hui-select-options-card-feature-editor") +export class HuiSelectOptionsCardFeatureEditor + extends LitElement + implements LovelaceCardFeatureEditor +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @property({ attribute: false }) public context?: LovelaceCardFeatureContext; + + @state() private _config?: SelectOptionsCardFeatureConfig; + + public setConfig(config: SelectOptionsCardFeatureConfig): void { + this._config = config; + } + + private _schema = memoizeOne( + ( + formatEntityState: FormatEntityStateFunc, + stateObj: HassEntity | undefined, + customizeOptions: boolean + ) => + [ + { + name: "customize_options", + selector: { + boolean: {}, + }, + }, + ...(customizeOptions + ? ([ + { + name: "options", + selector: { + select: { + multiple: true, + reorder: true, + options: + stateObj?.attributes.options?.map((option) => ({ + value: option, + label: formatEntityState(stateObj, option), + })) || [], + }, + }, + }, + ] as const satisfies readonly HaFormSchema[]) + : []), + ] as const satisfies readonly HaFormSchema[] + ); + + protected render() { + if (!this.hass || !this._config) { + return nothing; + } + + const stateObj = this.context?.entity_id + ? this.hass.states[this.context?.entity_id] + : undefined; + + const data: SelectOptionsCardFeatureData = { + ...this._config, + customize_options: this._config.options !== undefined, + }; + + const schema = this._schema( + this.hass.formatEntityState, + stateObj, + data.customize_options + ); + + return html` + + `; + } + + private _valueChanged(ev: CustomEvent): void { + const { customize_options, ...config } = ev.detail + .value as SelectOptionsCardFeatureData; + + const stateObj = this.context?.entity_id + ? this.hass!.states[this.context?.entity_id] + : undefined; + + if (customize_options && !config.options) { + config.options = stateObj?.attributes.options || []; + } + if (!customize_options && config.options) { + delete config.options; + } + + fireEvent(this, "config-changed", { config: config }); + } + + private _computeLabelCallback = ( + schema: SchemaUnion> + ) => { + switch (schema.name) { + case "options": + case "customize_options": + return this.hass!.localize( + `ui.panel.lovelace.editor.features.types.select-options.${schema.name}` + ); + default: + return ""; + } + }; +} + +declare global { + interface HTMLElementTagNameMap { + "hui-select-options-card-feature-editor": HuiSelectOptionsCardFeatureEditor; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index a92e37fc5a..f7365a2c70 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -6053,7 +6053,9 @@ "modes": "Modes" }, "select-options": { - "label": "Select options" + "label": "Select options", + "options": "Options", + "customize_options": "Customize options" }, "numeric-input": { "label": "Numeric input",