1
0
mirror of https://github.com/home-assistant/frontend.git synced 2026-04-01 16:17:21 +01:00

Create shared select card feature base class (#51333)

* Create shared select card feature base class

* Add sound mode and source features

* Remove serviceValueKey as its the same as attribute

* Migrate more

* Migrate select options

* Add fan direction

* Remove default usages
This commit is contained in:
Aidan Timson
2026-04-01 15:58:11 +01:00
committed by GitHub
parent 3b8f219800
commit a8ad921efd
13 changed files with 626 additions and 1547 deletions

View File

@@ -1,21 +1,12 @@
import { mdiFan } from "@mdi/js";
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attribute-icon";
import "../../../components/ha-control-select";
import type { ControlSelectOption } from "../../../components/ha-control-select";
import "../../../components/ha-control-select-menu";
import "../../../components/ha-list-item";
import type { ClimateEntity } from "../../../data/climate";
import { ClimateEntityFeature } from "../../../data/climate";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import { filterModes } from "./common/filter-modes";
import { HuiModeSelectCardFeatureBase } from "./hui-mode-select-card-feature-base";
import type {
ClimateFanModesCardFeatureConfig,
LovelaceCardFeatureContext,
@@ -38,34 +29,26 @@ export const supportsClimateFanModesCardFeature = (
@customElement("hui-climate-fan-modes-card-feature")
class HuiClimateFanModesCardFeature
extends LitElement
extends HuiModeSelectCardFeatureBase<
ClimateEntity,
ClimateFanModesCardFeatureConfig
>
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
protected readonly _attribute = "fan_mode";
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
protected readonly _modesAttribute = "fan_modes";
@state() private _config?: ClimateFanModesCardFeatureConfig;
@state() _currentFanMode?: string;
private _renderFanModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this._stateObj}
attribute="fan_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as
| ClimateEntity
| undefined;
protected get _configuredModes() {
return this._config?.fan_modes;
}
protected readonly _dropdownIconPath = mdiFan;
protected readonly _serviceDomain = "climate";
protected readonly _serviceAction = "set_fan_mode";
static getStubConfig(): ClimateFanModesCardFeatureConfig {
return {
type: "climate-fan-modes",
@@ -78,120 +61,12 @@ class HuiClimateFanModesCardFeature
return document.createElement("hui-climate-fan-modes-card-feature-editor");
}
public setConfig(config: ClimateFanModesCardFeatureConfig): void {
if (!config) {
throw new Error("Invalid configuration");
}
this._config = config;
}
protected willUpdate(changedProp: PropertyValues): void {
super.willUpdate(changedProp);
if (
(changedProp.has("hass") || changedProp.has("context")) &&
this._stateObj
) {
const oldHass = changedProp.get("hass") as HomeAssistant | undefined;
const oldStateObj = oldHass?.states[this.context!.entity_id!];
if (oldStateObj !== this._stateObj) {
this._currentFanMode = this._stateObj.attributes.fan_mode;
}
}
}
private async _valueChanged(
ev: CustomEvent<{ value?: string; item?: { value: string } }>
) {
const fanMode = ev.detail.value ?? ev.detail.item?.value;
const oldFanMode = this._stateObj!.attributes.fan_mode;
if (fanMode === oldFanMode || !fanMode) {
return;
}
this._currentFanMode = fanMode;
try {
await this._setMode(fanMode);
} catch (_err) {
this._currentFanMode = oldFanMode;
}
}
private async _setMode(mode: string) {
await this.hass!.callService("climate", "set_fan_mode", {
entity_id: this._stateObj!.entity_id,
fan_mode: mode,
});
}
protected render(): TemplateResult | null {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsClimateFanModesCardFeature(this.hass, this.context)
) {
return null;
}
const stateObj = this._stateObj;
const options = filterModes(
stateObj.attributes.fan_modes,
this._config!.fan_modes
).map<ControlSelectOption>((mode) => ({
value: mode,
label: this.hass!.formatEntityAttributeValue(
this._stateObj!,
"fan_mode",
mode
),
}));
if (this._config.style === "icons") {
return html`
<ha-control-select
.options=${options.map((option) => ({
...option,
icon: html`<ha-attribute-icon
slot="graphic"
.hass=${this.hass}
.stateObj=${stateObj}
attribute="fan_mode"
.attributeValue=${option.value}
></ha-attribute-icon>`,
}))}
.value=${this._currentFanMode}
@value-changed=${this._valueChanged}
hide-option-label
.label=${this.hass!.formatEntityAttributeName(stateObj, "fan_mode")}
.disabled=${this._stateObj!.state === UNAVAILABLE}
>
</ha-control-select>
`;
}
return html`
<ha-control-select-menu
.hass=${this.hass}
show-arrow
hide-label
.label=${this.hass!.formatEntityAttributeName(stateObj, "fan_mode")}
.value=${this._currentFanMode}
.disabled=${this._stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
.options=${options}
.renderIcon=${this._renderFanModeIcon}
><ha-svg-icon slot="icon" .path=${mdiFan}></ha-svg-icon>
</ha-control-select-menu>
`;
}
static get styles() {
return cardFeatureStyles;
protected _isSupported(): boolean {
return !!(
this.hass &&
this.context &&
supportsClimateFanModesCardFeature(this.hass, this.context)
);
}
}

View File

@@ -1,28 +1,30 @@
import { mdiThermostat } from "@mdi/js";
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import type { TemplateResult } from "lit";
import { html } from "lit";
import { customElement } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import { stateColorCss } from "../../../common/entity/state_color";
import "../../../components/ha-control-select";
import "../../../components/ha-control-select-menu";
import "../../../components/ha-list-item";
import type { ClimateEntity, HvacMode } from "../../../data/climate";
import type { ClimateEntity } from "../../../data/climate";
import {
climateHvacModeIcon,
compareClimateHvacModes,
} from "../../../data/climate";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import { filterModes } from "./common/filter-modes";
import {
HuiModeSelectCardFeatureBase,
type HuiModeSelectOption,
} from "./hui-mode-select-card-feature-base";
import type {
ClimateHvacModesCardFeatureConfig,
LovelaceCardFeatureContext,
} from "./types";
interface HvacModeOption extends HuiModeSelectOption {
iconPath: string;
}
export const supportsClimateHvacModesCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -37,24 +39,44 @@ export const supportsClimateHvacModesCardFeature = (
@customElement("hui-climate-hvac-modes-card-feature")
class HuiClimateHvacModesCardFeature
extends LitElement
extends HuiModeSelectCardFeatureBase<
ClimateEntity,
ClimateHvacModesCardFeatureConfig
>
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
protected readonly _attribute = "hvac_mode";
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
protected readonly _modesAttribute = "hvac_modes";
@state() private _config?: ClimateHvacModesCardFeatureConfig;
protected get _configuredModes() {
return this._config?.hvac_modes;
}
@state() _currentHvacMode?: HvacMode;
protected readonly _dropdownIconPath = mdiThermostat;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
protected readonly _serviceDomain = "climate";
protected readonly _serviceAction = "set_hvac_mode";
protected get _label(): string {
return this.hass!.localize("ui.card.climate.mode");
}
protected readonly _showDropdownOptionIcons = false;
protected readonly _defaultStyle = "icons";
protected get _controlSelectStyle():
| Record<string, string | undefined>
| undefined {
if (!this._stateObj) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as
| ClimateEntity
| undefined;
return {
"--control-select-color": stateColorCss(this._stateObj),
};
}
static getStubConfig(): ClimateHvacModesCardFeatureConfig {
@@ -68,119 +90,42 @@ class HuiClimateHvacModesCardFeature
return document.createElement("hui-climate-hvac-modes-card-feature-editor");
}
public setConfig(config: ClimateHvacModesCardFeatureConfig): void {
if (!config) {
throw new Error("Invalid configuration");
}
this._config = config;
protected _getValue(stateObj: ClimateEntity): string | undefined {
return stateObj.state;
}
protected willUpdate(changedProp: PropertyValues): void {
super.willUpdate(changedProp);
if (
(changedProp.has("hass") || changedProp.has("context")) &&
this._stateObj
) {
const oldHass = changedProp.get("hass") as HomeAssistant | undefined;
const oldStateObj = oldHass?.states[this.context!.entity_id!];
if (oldStateObj !== this._stateObj) {
this._currentHvacMode = this._stateObj.state as HvacMode;
}
}
}
private async _valueChanged(
ev: CustomEvent<{ value?: string; item?: { value: string } }>
) {
const mode = ev.detail.value ?? ev.detail.item?.value;
if (mode === this._stateObj!.state || !mode) {
return;
protected _getOptions(): HvacModeOption[] {
if (!this._stateObj || !this.hass) {
return [];
}
const oldMode = this._stateObj!.state as HvacMode;
this._currentHvacMode = mode as HvacMode;
try {
await this._setMode(this._currentHvacMode);
} catch (_err) {
this._currentHvacMode = oldMode;
}
}
private async _setMode(mode: HvacMode) {
await this.hass!.callService("climate", "set_hvac_mode", {
entity_id: this._stateObj!.entity_id,
hvac_mode: mode,
});
}
protected render(): TemplateResult | null {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsClimateHvacModesCardFeature(this.hass, this.context)
) {
return null;
}
const color = stateColorCss(this._stateObj);
const ordererHvacModes = (this._stateObj.attributes.hvac_modes || [])
const orderedHvacModes = (this._stateObj.attributes.hvac_modes || [])
.concat()
.sort(compareClimateHvacModes)
.reverse();
const options = filterModes(ordererHvacModes, this._config.hvac_modes).map(
return filterModes(orderedHvacModes, this._config?.hvac_modes).map(
(mode) => ({
value: mode as string,
value: mode,
label: this.hass!.formatEntityState(this._stateObj!, mode),
iconPath: climateHvacModeIcon(mode),
})
);
if (this._config.style === "dropdown") {
return html`
<ha-control-select-menu
show-arrow
hide-label
.label=${this.hass.localize("ui.card.climate.mode")}
.value=${this._currentHvacMode}
.disabled=${this._stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
.options=${options}
>
<ha-svg-icon slot="icon" .path=${mdiThermostat}></ha-svg-icon>
</ha-control-select-menu>
`;
}
return html`
<ha-control-select
.options=${options.map((option) => ({
...option,
icon: html`<ha-svg-icon
slot="graphic"
.path=${option.iconPath}
></ha-svg-icon>`,
}))}
.value=${this._currentHvacMode}
@value-changed=${this._valueChanged}
hide-option-label
.label=${this.hass.localize("ui.card.climate.mode")}
style=${styleMap({
"--control-select-color": color,
})}
.disabled=${this._stateObj!.state === UNAVAILABLE}
>
</ha-control-select>
`;
}
static get styles() {
return cardFeatureStyles;
protected _renderOptionIcon(option: HvacModeOption): TemplateResult<1> {
return html`<ha-svg-icon
slot="graphic"
.path=${option.iconPath}
></ha-svg-icon>`;
}
protected _isSupported(): boolean {
return !!(
this.hass &&
this.context &&
supportsClimateHvacModesCardFeature(this.hass, this.context)
);
}
}

View File

@@ -1,20 +1,12 @@
import { mdiTuneVariant } from "@mdi/js";
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attribute-icon";
import "../../../components/ha-control-select";
import "../../../components/ha-control-select-menu";
import "../../../components/ha-list-item";
import type { ClimateEntity } from "../../../data/climate";
import { ClimateEntityFeature } from "../../../data/climate";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import { filterModes } from "./common/filter-modes";
import { HuiModeSelectCardFeatureBase } from "./hui-mode-select-card-feature-base";
import type {
ClimatePresetModesCardFeatureConfig,
LovelaceCardFeatureContext,
@@ -37,34 +29,26 @@ export const supportsClimatePresetModesCardFeature = (
@customElement("hui-climate-preset-modes-card-feature")
class HuiClimatePresetModesCardFeature
extends LitElement
extends HuiModeSelectCardFeatureBase<
ClimateEntity,
ClimatePresetModesCardFeatureConfig
>
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
protected readonly _attribute = "preset_mode";
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
protected readonly _modesAttribute = "preset_modes";
@state() private _config?: ClimatePresetModesCardFeatureConfig;
@state() _currentPresetMode?: string;
private _renderPresetModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this._stateObj}
attribute="preset_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as
| ClimateEntity
| undefined;
protected get _configuredModes() {
return this._config?.preset_modes;
}
protected readonly _dropdownIconPath = mdiTuneVariant;
protected readonly _serviceDomain = "climate";
protected readonly _serviceAction = "set_preset_mode";
static getStubConfig(): ClimatePresetModesCardFeatureConfig {
return {
type: "climate-preset-modes",
@@ -79,124 +63,12 @@ class HuiClimatePresetModesCardFeature
);
}
public setConfig(config: ClimatePresetModesCardFeatureConfig): void {
if (!config) {
throw new Error("Invalid configuration");
}
this._config = config;
}
protected willUpdate(changedProp: PropertyValues): void {
super.willUpdate(changedProp);
if (
(changedProp.has("hass") || changedProp.has("context")) &&
this._stateObj
) {
const oldHass = changedProp.get("hass") as HomeAssistant | undefined;
const oldStateObj = oldHass?.states[this.context!.entity_id!];
if (oldStateObj !== this._stateObj) {
this._currentPresetMode = this._stateObj.attributes.preset_mode;
}
}
}
private async _valueChanged(
ev: CustomEvent<{ value?: string; item?: { value: string } }>
) {
const presetMode = ev.detail.value ?? ev.detail.item?.value;
const oldPresetMode = this._stateObj!.attributes.preset_mode;
if (presetMode === oldPresetMode || !presetMode) {
return;
}
this._currentPresetMode = presetMode;
try {
await this._setMode(presetMode);
} catch (_err) {
this._currentPresetMode = oldPresetMode;
}
}
private async _setMode(mode: string) {
await this.hass!.callService("climate", "set_preset_mode", {
entity_id: this._stateObj!.entity_id,
preset_mode: mode,
});
}
protected render(): TemplateResult | null {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsClimatePresetModesCardFeature(this.hass, this.context)
) {
return null;
}
const stateObj = this._stateObj;
const options = filterModes(
stateObj.attributes.preset_modes,
this._config!.preset_modes
).map((mode) => ({
value: mode,
label: this.hass!.formatEntityAttributeValue(
this._stateObj!,
"preset_mode",
mode
),
}));
if (this._config.style === "icons") {
return html`
<ha-control-select
.options=${options.map((option) => ({
...option,
icon: html`<ha-attribute-icon
slot="graphic"
.hass=${this.hass}
.stateObj=${stateObj}
attribute="preset_mode"
.attributeValue=${option.value}
></ha-attribute-icon>`,
}))}
.value=${this._currentPresetMode}
@value-changed=${this._valueChanged}
hide-option-label
.label=${this.hass!.formatEntityAttributeName(
stateObj,
"preset_mode"
)}
.disabled=${this._stateObj!.state === UNAVAILABLE}
>
</ha-control-select>
`;
}
return html`
<ha-control-select-menu
.hass=${this.hass}
show-arrow
hide-label
.label=${this.hass!.formatEntityAttributeName(stateObj, "preset_mode")}
.value=${this._currentPresetMode}
.disabled=${this._stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
.options=${options}
.renderIcon=${this._renderPresetModeIcon}
>
<ha-svg-icon slot="icon" .path=${mdiTuneVariant}></ha-svg-icon>
</ha-control-select-menu>
`;
}
static get styles() {
return cardFeatureStyles;
protected _isSupported(): boolean {
return !!(
this.hass &&
this.context &&
supportsClimatePresetModesCardFeature(this.hass, this.context)
);
}
}

View File

@@ -1,20 +1,12 @@
import { mdiArrowOscillating } from "@mdi/js";
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attribute-icon";
import "../../../components/ha-control-select";
import "../../../components/ha-control-select-menu";
import "../../../components/ha-list-item";
import type { ClimateEntity } from "../../../data/climate";
import { ClimateEntityFeature } from "../../../data/climate";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import { filterModes } from "./common/filter-modes";
import { HuiModeSelectCardFeatureBase } from "./hui-mode-select-card-feature-base";
import type {
ClimateSwingHorizontalModesCardFeatureConfig,
LovelaceCardFeatureContext,
@@ -37,34 +29,26 @@ export const supportsClimateSwingHorizontalModesCardFeature = (
@customElement("hui-climate-swing-horizontal-modes-card-feature")
class HuiClimateSwingHorizontalModesCardFeature
extends LitElement
extends HuiModeSelectCardFeatureBase<
ClimateEntity,
ClimateSwingHorizontalModesCardFeatureConfig
>
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
protected readonly _attribute = "swing_horizontal_mode";
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
protected readonly _modesAttribute = "swing_horizontal_modes";
@state() private _config?: ClimateSwingHorizontalModesCardFeatureConfig;
@state() _currentSwingHorizontalMode?: string;
private _renderSwingHorizontalModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this._stateObj}
attribute="swing_horizontal_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as
| ClimateEntity
| undefined;
protected get _configuredModes() {
return this._config?.swing_horizontal_modes;
}
protected readonly _dropdownIconPath = mdiArrowOscillating;
protected readonly _serviceDomain = "climate";
protected readonly _serviceAction = "set_swing_horizontal_mode";
static getStubConfig(): ClimateSwingHorizontalModesCardFeatureConfig {
return {
type: "climate-swing-horizontal-modes",
@@ -79,132 +63,12 @@ class HuiClimateSwingHorizontalModesCardFeature
);
}
public setConfig(config: ClimateSwingHorizontalModesCardFeatureConfig): void {
if (!config) {
throw new Error("Invalid configuration");
}
this._config = config;
}
protected willUpdate(changedProp: PropertyValues): void {
super.willUpdate(changedProp);
if (
(changedProp.has("hass") || changedProp.has("context")) &&
this._stateObj
) {
const oldHass = changedProp.get("hass") as HomeAssistant | undefined;
const oldStateObj = oldHass?.states[this.context!.entity_id!];
if (oldStateObj !== this._stateObj) {
this._currentSwingHorizontalMode =
this._stateObj.attributes.swing_horizontal_mode;
}
}
}
private async _valueChanged(
ev: CustomEvent<{ value?: string; item?: { value: string } }>
) {
const swingHorizontalMode = ev.detail.value ?? ev.detail.item?.value;
const oldSwingHorizontalMode =
this._stateObj!.attributes.swing_horizontal_mode;
if (
swingHorizontalMode === oldSwingHorizontalMode ||
!swingHorizontalMode
) {
return;
}
this._currentSwingHorizontalMode = swingHorizontalMode;
try {
await this._setMode(swingHorizontalMode);
} catch (_err) {
this._currentSwingHorizontalMode = oldSwingHorizontalMode;
}
}
private async _setMode(mode: string) {
await this.hass!.callService("climate", "set_swing_horizontal_mode", {
entity_id: this._stateObj!.entity_id,
swing_horizontal_mode: mode,
});
}
protected render(): TemplateResult | null {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsClimateSwingHorizontalModesCardFeature(this.hass, this.context)
) {
return null;
}
const stateObj = this._stateObj;
const options = filterModes(
stateObj.attributes.swing_horizontal_modes,
this._config!.swing_horizontal_modes
).map((mode) => ({
value: mode,
label: this.hass!.formatEntityAttributeValue(
this._stateObj!,
"swing_horizontal_mode",
mode
),
}));
if (this._config.style === "icons") {
return html`
<ha-control-select
.options=${options.map((option) => ({
...option,
icon: html`<ha-attribute-icon
slot="graphic"
.hass=${this.hass}
.stateObj=${stateObj}
attribute="swing_horizontal_mode"
.attributeValue=${option.value}
></ha-attribute-icon>`,
}))}
.value=${this._currentSwingHorizontalMode}
@value-changed=${this._valueChanged}
hide-option-label
.label=${this.hass!.formatEntityAttributeName(
stateObj,
"swing_horizontal_mode"
)}
.disabled=${this._stateObj!.state === UNAVAILABLE}
>
</ha-control-select>
`;
}
return html`
<ha-control-select-menu
.hass=${this.hass}
show-arrow
hide-label
.label=${this.hass!.formatEntityAttributeName(
stateObj,
"swing_horizontal_mode"
)}
.value=${this._currentSwingHorizontalMode}
.disabled=${this._stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
.options=${options}
.renderIcon=${this._renderSwingHorizontalModeIcon}
>
<ha-svg-icon slot="icon" .path=${mdiArrowOscillating}></ha-svg-icon>
</ha-control-select-menu>
`;
}
static get styles() {
return cardFeatureStyles;
protected _isSupported(): boolean {
return !!(
this.hass &&
this.context &&
supportsClimateSwingHorizontalModesCardFeature(this.hass, this.context)
);
}
}

View File

@@ -1,20 +1,12 @@
import { mdiArrowOscillating } from "@mdi/js";
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attribute-icon";
import "../../../components/ha-control-select";
import "../../../components/ha-control-select-menu";
import "../../../components/ha-list-item";
import type { ClimateEntity } from "../../../data/climate";
import { ClimateEntityFeature } from "../../../data/climate";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import { filterModes } from "./common/filter-modes";
import { HuiModeSelectCardFeatureBase } from "./hui-mode-select-card-feature-base";
import type {
ClimateSwingModesCardFeatureConfig,
LovelaceCardFeatureContext,
@@ -37,34 +29,26 @@ export const supportsClimateSwingModesCardFeature = (
@customElement("hui-climate-swing-modes-card-feature")
class HuiClimateSwingModesCardFeature
extends LitElement
extends HuiModeSelectCardFeatureBase<
ClimateEntity,
ClimateSwingModesCardFeatureConfig
>
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
protected readonly _attribute = "swing_mode";
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
protected readonly _modesAttribute = "swing_modes";
@state() private _config?: ClimateSwingModesCardFeatureConfig;
@state() _currentSwingMode?: string;
private _renderSwingModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this._stateObj}
attribute="swing_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as
| ClimateEntity
| undefined;
protected get _configuredModes() {
return this._config?.swing_modes;
}
protected readonly _dropdownIconPath = mdiArrowOscillating;
protected readonly _serviceDomain = "climate";
protected readonly _serviceAction = "set_swing_mode";
static getStubConfig(): ClimateSwingModesCardFeatureConfig {
return {
type: "climate-swing-modes",
@@ -79,123 +63,12 @@ class HuiClimateSwingModesCardFeature
);
}
public setConfig(config: ClimateSwingModesCardFeatureConfig): void {
if (!config) {
throw new Error("Invalid configuration");
}
this._config = config;
}
protected willUpdate(changedProp: PropertyValues): void {
super.willUpdate(changedProp);
if (
(changedProp.has("hass") || changedProp.has("context")) &&
this._stateObj
) {
const oldHass = changedProp.get("hass") as HomeAssistant | undefined;
const oldStateObj = oldHass?.states[this.context!.entity_id!];
if (oldStateObj !== this._stateObj) {
this._currentSwingMode = this._stateObj.attributes.swing_mode;
}
}
}
private async _valueChanged(
ev: CustomEvent<{ value?: string; item?: { value: string } }>
) {
const swingMode = ev.detail.value ?? ev.detail.item?.value;
const oldSwingMode = this._stateObj!.attributes.swing_mode;
if (swingMode === oldSwingMode || !swingMode) {
return;
}
this._currentSwingMode = swingMode;
try {
await this._setMode(swingMode);
} catch (_err) {
this._currentSwingMode = oldSwingMode;
}
}
private async _setMode(mode: string) {
await this.hass!.callService("climate", "set_swing_mode", {
entity_id: this._stateObj!.entity_id,
swing_mode: mode,
});
}
protected render(): TemplateResult | null {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsClimateSwingModesCardFeature(this.hass, this.context)
) {
return null;
}
const stateObj = this._stateObj;
const options = filterModes(
stateObj.attributes.swing_modes,
this._config!.swing_modes
).map((mode) => ({
value: mode,
label: this.hass!.formatEntityAttributeValue(
this._stateObj!,
"swing_mode",
mode
),
}));
if (this._config.style === "icons") {
return html`
<ha-control-select
.options=${options.map((option) => ({
...option,
icon: html`<ha-attribute-icon
slot="graphic"
.hass=${this.hass}
.stateObj=${stateObj}
attribute="swing_mode"
.attributeValue=${option.value}
></ha-attribute-icon>`,
}))}
.value=${this._currentSwingMode}
@value-changed=${this._valueChanged}
hide-option-label
.ariaLabel=${this.hass!.formatEntityAttributeName(
stateObj,
"swing_mode"
)}
.disabled=${this._stateObj!.state === UNAVAILABLE}
>
</ha-control-select>
`;
}
return html`
<ha-control-select-menu
.hass=${this.hass}
show-arrow
hide-label
.label=${this.hass!.formatEntityAttributeName(stateObj, "swing_mode")}
.value=${this._currentSwingMode}
.disabled=${this._stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
.options=${options}
.renderIcon=${this._renderSwingModeIcon}
><ha-svg-icon slot="icon" .path=${mdiArrowOscillating}></ha-svg-icon>
</ha-control-select-menu>
`;
}
static get styles() {
return cardFeatureStyles;
protected _isSupported(): boolean {
return !!(
this.hass &&
this.context &&
supportsClimateSwingModesCardFeature(this.hass, this.context)
);
}
}

View File

@@ -1,22 +1,21 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attribute-icon";
import "../../../components/ha-control-select";
import type { ControlSelectOption } from "../../../components/ha-control-select";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type { FanDirection, FanEntity } from "../../../data/fan";
import { FanEntityFeature } from "../../../data/fan";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import {
HuiModeSelectCardFeatureBase,
type HuiModeSelectOption,
} from "./hui-mode-select-card-feature-base";
import type {
FanDirectionCardFeatureConfig,
LovelaceCardFeatureContext,
} from "./types";
const FAN_DIRECTIONS: FanDirection[] = ["forward", "reverse"];
export const supportsFanDirectionCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -33,23 +32,18 @@ export const supportsFanDirectionCardFeature = (
@customElement("hui-fan-direction-card-feature")
class HuiFanDirectionCardFeature
extends LitElement
extends HuiModeSelectCardFeatureBase<FanEntity, FanDirectionCardFeatureConfig>
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
protected readonly _attribute = "direction";
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
protected readonly _modesAttribute = "direction";
@state() private _config?: FanDirectionCardFeatureConfig;
protected readonly _serviceDomain = "fan";
@state() _currentDirection?: FanDirection;
protected readonly _serviceAction = "set_direction";
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as FanEntity | undefined;
}
protected readonly _defaultStyle = "icons";
static getStubConfig(): FanDirectionCardFeatureConfig {
return {
@@ -57,90 +51,23 @@ class HuiFanDirectionCardFeature
};
}
public setConfig(config: FanDirectionCardFeatureConfig): void {
if (!config) {
throw new Error("Invalid configuration");
}
this._config = config;
}
protected willUpdate(changedProp: PropertyValues): void {
if (
(changedProp.has("hass") || changedProp.has("context")) &&
this._stateObj
) {
const oldHass = changedProp.get("hass") as HomeAssistant | undefined;
const oldStateObj = oldHass?.states[this.context!.entity_id!];
if (oldStateObj !== this._stateObj) {
this._currentDirection = this._stateObj.attributes
.direction as FanDirection;
}
}
}
private async _valueChanged(ev: CustomEvent) {
const newDirection = (ev.detail as any).value as FanDirection;
if (newDirection === this._stateObj!.attributes.direction) return;
const oldDirection = this._stateObj!.attributes.direction as FanDirection;
this._currentDirection = newDirection;
try {
await this._setDirection(newDirection);
} catch (_err) {
this._currentDirection = oldDirection;
}
}
private async _setDirection(direction: string) {
await this.hass!.callService("fan", "set_direction", {
entity_id: this._stateObj!.entity_id,
direction: direction,
});
}
protected render(): TemplateResult | null {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsFanDirectionCardFeature(this.hass, this.context)
) {
return null;
protected _getOptions(): HuiModeSelectOption[] {
if (!this.hass) {
return [];
}
const stateObj = this._stateObj;
const FAN_DIRECTION_MAP: FanDirection[] = ["forward", "reverse"];
const options = FAN_DIRECTION_MAP.map<ControlSelectOption>((direction) => ({
return FAN_DIRECTIONS.map((direction) => ({
value: direction,
label: this.hass!.localize(`ui.card.fan.${direction}`),
icon: html`<ha-attribute-icon
slot="graphic"
.hass=${this.hass}
.stateObj=${stateObj}
attribute="direction"
.attributeValue=${direction}
></ha-attribute-icon>`,
}));
return html`
<ha-control-select
.options=${options}
.value=${this._currentDirection}
@value-changed=${this._valueChanged}
hide-option-label
.label=${this.hass!.formatEntityAttributeName(stateObj, "direction")}
.disabled=${this._stateObj!.state === UNAVAILABLE}
>
</ha-control-select>
`;
}
static get styles() {
return cardFeatureStyles;
protected _isSupported(): boolean {
return !!(
this.hass &&
this.context &&
supportsFanDirectionCardFeature(this.hass, this.context)
);
}
}

View File

@@ -1,20 +1,12 @@
import { mdiTuneVariant } from "@mdi/js";
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attribute-icon";
import "../../../components/ha-control-select";
import "../../../components/ha-control-select-menu";
import "../../../components/ha-list-item";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type { FanEntity } from "../../../data/fan";
import { FanEntityFeature } from "../../../data/fan";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import { filterModes } from "./common/filter-modes";
import { HuiModeSelectCardFeatureBase } from "./hui-mode-select-card-feature-base";
import type {
FanPresetModesCardFeatureConfig,
LovelaceCardFeatureContext,
@@ -36,32 +28,26 @@ export const supportsFanPresetModesCardFeature = (
@customElement("hui-fan-preset-modes-card-feature")
class HuiFanPresetModesCardFeature
extends LitElement
extends HuiModeSelectCardFeatureBase<
FanEntity,
FanPresetModesCardFeatureConfig
>
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
protected readonly _attribute = "preset_mode";
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
protected readonly _modesAttribute = "preset_modes";
@state() private _config?: FanPresetModesCardFeatureConfig;
@state() _currentPresetMode?: string;
private _renderPresetModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this._stateObj}
attribute="preset_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as FanEntity | undefined;
protected get _configuredModes() {
return this._config?.preset_modes;
}
protected readonly _dropdownIconPath = mdiTuneVariant;
protected readonly _serviceDomain = "fan";
protected readonly _serviceAction = "set_preset_mode";
static getStubConfig(): FanPresetModesCardFeatureConfig {
return {
type: "fan-preset-modes",
@@ -74,123 +60,12 @@ class HuiFanPresetModesCardFeature
return document.createElement("hui-fan-preset-modes-card-feature-editor");
}
public setConfig(config: FanPresetModesCardFeatureConfig): void {
if (!config) {
throw new Error("Invalid configuration");
}
this._config = config;
}
protected willUpdate(changedProp: PropertyValues): void {
if (
(changedProp.has("hass") || changedProp.has("context")) &&
this._stateObj
) {
const oldHass = changedProp.get("hass") as HomeAssistant | undefined;
const oldStateObj = oldHass?.states[this.context!.entity_id!];
if (oldStateObj !== this._stateObj) {
this._currentPresetMode = this._stateObj.attributes.preset_mode;
}
}
}
private async _valueChanged(
ev: CustomEvent<{ value?: string; item?: { value: string } }>
) {
const presetMode = ev.detail.value ?? ev.detail.item?.value;
const oldPresetMode = this._stateObj!.attributes.preset_mode;
if (presetMode === oldPresetMode || !presetMode) {
return;
}
this._currentPresetMode = presetMode;
try {
await this._setMode(presetMode);
} catch (_err) {
this._currentPresetMode = oldPresetMode;
}
}
private async _setMode(mode: string) {
await this.hass!.callService("fan", "set_preset_mode", {
entity_id: this._stateObj!.entity_id,
preset_mode: mode,
});
}
protected render(): TemplateResult | null {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsFanPresetModesCardFeature(this.hass, this.context)
) {
return null;
}
const stateObj = this._stateObj;
const options = filterModes(
stateObj.attributes.preset_modes,
this._config!.preset_modes
).map((mode) => ({
value: mode,
label: this.hass!.formatEntityAttributeValue(
this._stateObj!,
"preset_mode",
mode
),
}));
if (this._config.style === "icons") {
return html`
<ha-control-select
.options=${options.map((option) => ({
...option,
icon: html`<ha-attribute-icon
slot="graphic"
.hass=${this.hass}
.stateObj=${stateObj}
attribute="preset_mode"
.attributeValue=${option.value}
></ha-attribute-icon>`,
}))}
.value=${this._currentPresetMode}
@value-changed=${this._valueChanged}
hide-option-label
.label=${this.hass!.formatEntityAttributeName(
stateObj,
"preset_mode"
)}
.disabled=${this._stateObj!.state === UNAVAILABLE}
>
</ha-control-select>
`;
}
return html`
<ha-control-select-menu
.hass=${this.hass}
show-arrow
hide-label
.label=${this.hass!.formatEntityAttributeName(stateObj, "preset_mode")}
.value=${this._currentPresetMode}
.disabled=${this._stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
.options=${options}
.renderIcon=${this._renderPresetModeIcon}
>
<ha-svg-icon slot="icon" .path=${mdiTuneVariant}></ha-svg-icon>
</ha-control-select-menu>
`;
}
static get styles() {
return cardFeatureStyles;
protected _isSupported(): boolean {
return !!(
this.hass &&
this.context &&
supportsFanPresetModesCardFeature(this.hass, this.context)
);
}
}

View File

@@ -1,20 +1,12 @@
import { mdiTuneVariant } from "@mdi/js";
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attribute-icon";
import "../../../components/ha-control-select";
import "../../../components/ha-control-select-menu";
import "../../../components/ha-list-item";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type { HumidifierEntity } from "../../../data/humidifier";
import { HumidifierEntityFeature } from "../../../data/humidifier";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import { filterModes } from "./common/filter-modes";
import { HuiModeSelectCardFeatureBase } from "./hui-mode-select-card-feature-base";
import type {
HumidifierModesCardFeatureConfig,
LovelaceCardFeatureContext,
@@ -37,34 +29,26 @@ export const supportsHumidifierModesCardFeature = (
@customElement("hui-humidifier-modes-card-feature")
class HuiHumidifierModesCardFeature
extends LitElement
extends HuiModeSelectCardFeatureBase<
HumidifierEntity,
HumidifierModesCardFeatureConfig
>
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
protected readonly _attribute = "mode";
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
protected readonly _modesAttribute = "available_modes";
@state() private _config?: HumidifierModesCardFeatureConfig;
@state() _currentMode?: string;
private _renderModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this._stateObj}
attribute="mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as
| HumidifierEntity
| undefined;
protected get _configuredModes() {
return this._config?.modes;
}
protected readonly _dropdownIconPath = mdiTuneVariant;
protected readonly _serviceDomain = "humidifier";
protected readonly _serviceAction = "set_mode";
static getStubConfig(): HumidifierModesCardFeatureConfig {
return {
type: "humidifier-modes",
@@ -77,121 +61,12 @@ class HuiHumidifierModesCardFeature
return document.createElement("hui-humidifier-modes-card-feature-editor");
}
public setConfig(config: HumidifierModesCardFeatureConfig): void {
if (!config) {
throw new Error("Invalid configuration");
}
this._config = config;
}
protected willUpdate(changedProp: PropertyValues): void {
super.willUpdate(changedProp);
if (
(changedProp.has("hass") || changedProp.has("context")) &&
this._stateObj
) {
const oldHass = changedProp.get("hass") as HomeAssistant | undefined;
const oldStateObj = oldHass?.states[this.context!.entity_id!];
if (oldStateObj !== this._stateObj) {
this._currentMode = this._stateObj.attributes.mode;
}
}
}
private async _valueChanged(
ev: CustomEvent<{ value?: string; item?: { value: string } }>
) {
const mode = ev.detail.value ?? ev.detail.item?.value;
const oldMode = this._stateObj!.attributes.mode;
if (mode === oldMode || !mode) {
return;
}
this._currentMode = mode;
try {
await this._setMode(mode);
} catch (_err) {
this._currentMode = oldMode;
}
}
private async _setMode(mode: string) {
await this.hass!.callService("humidifier", "set_mode", {
entity_id: this._stateObj!.entity_id,
mode: mode,
});
}
protected render(): TemplateResult | null {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsHumidifierModesCardFeature(this.hass, this.context)
) {
return null;
}
const stateObj = this._stateObj;
const options = filterModes(
stateObj.attributes.available_modes,
this._config!.modes
).map((mode) => ({
value: mode,
label: this.hass!.formatEntityAttributeValue(
this._stateObj!,
"mode",
mode
),
}));
if (this._config.style === "icons") {
return html`
<ha-control-select
.options=${options.map((option) => ({
...option,
icon: html`<ha-attribute-icon
slot="graphic"
.hass=${this.hass}
.stateObj=${stateObj}
attribute="mode"
.attributeValue=${option.value}
></ha-attribute-icon>`,
}))}
.value=${this._currentMode}
@value-changed=${this._valueChanged}
hide-option-label
.label=${this.hass!.formatEntityAttributeName(stateObj, "mode")}
.disabled=${this._stateObj!.state === UNAVAILABLE}
>
</ha-control-select>
`;
}
return html`
<ha-control-select-menu
.hass=${this.hass}
show-arrow
hide-label
.label=${this.hass!.formatEntityAttributeName(stateObj, "mode")}
.value=${this._currentMode}
.disabled=${this._stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
.options=${options}
.renderIcon=${this._renderModeIcon}
>
<ha-svg-icon slot="icon" .path=${mdiTuneVariant}></ha-svg-icon>
</ha-control-select-menu>
`;
}
static get styles() {
return cardFeatureStyles;
protected _isSupported(): boolean {
return !!(
this.hass &&
this.context &&
supportsHumidifierModesCardFeature(this.hass, this.context)
);
}
}

View File

@@ -1,12 +1,7 @@
import type { PropertyValues } from "lit";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
import "../../../components/ha-control-select-menu";
import "../../../components/ha-list-item";
import { UNAVAILABLE } from "../../../data/entity/entity";
import {
MediaPlayerEntityFeature,
type MediaPlayerEntity,
@@ -14,7 +9,7 @@ import {
import type { HomeAssistant } from "../../../types";
import { hasConfigChanged } from "../common/has-changed";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import { HuiModeSelectCardFeatureBase } from "./hui-mode-select-card-feature-base";
import type {
LovelaceCardFeatureContext,
MediaPlayerSoundModeCardFeatureConfig,
@@ -38,59 +33,42 @@ export const supportsMediaPlayerSoundModeCardFeature = (
@customElement("hui-media-player-sound-mode-card-feature")
class HuiMediaPlayerSoundModeCardFeature
extends LitElement
extends HuiModeSelectCardFeatureBase<
MediaPlayerEntity,
MediaPlayerSoundModeCardFeatureConfig
>
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
protected readonly _attribute = "sound_mode";
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
protected readonly _modesAttribute = "sound_mode_list";
@state() private _config?: MediaPlayerSoundModeCardFeatureConfig;
protected readonly _serviceDomain = "media_player";
@state() private _currentSoundMode?: string;
protected readonly _serviceAction = "select_sound_mode";
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as
| MediaPlayerEntity
| undefined;
protected get _label(): string {
return this.hass!.localize("ui.card.media_player.sound_mode");
}
protected readonly _hideLabel = false;
protected readonly _showDropdownOptionIcons = false;
protected readonly _allowIconsStyle = false;
static getStubConfig(): MediaPlayerSoundModeCardFeatureConfig {
return {
type: "media-player-sound-mode",
};
}
public setConfig(config: MediaPlayerSoundModeCardFeatureConfig): void {
if (!config) {
throw new Error("Invalid configuration");
}
this._config = config;
}
protected willUpdate(changedProps: PropertyValues): void {
super.willUpdate(changedProps);
if (
(changedProps.has("hass") || changedProps.has("context")) &&
this._stateObj
) {
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
const oldStateObj = oldHass?.states[this.context!.entity_id!];
if (oldStateObj !== this._stateObj) {
this._currentSoundMode = this._stateObj.attributes.sound_mode;
}
}
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
const entityId = this.context?.entity_id;
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
return (
changedProps.has("_currentSoundMode") ||
changedProps.has("_currentValue") ||
changedProps.has("context") ||
hasConfigChanged(this, changedProps) ||
(changedProps.has("hass") &&
@@ -100,64 +78,12 @@ class HuiMediaPlayerSoundModeCardFeature
);
}
private async _valueChanged(ev: HaDropdownSelectEvent) {
const soundMode = ev.detail.item?.value;
const oldSoundMode = this._stateObj!.attributes.sound_mode;
if (soundMode === oldSoundMode || !soundMode) {
return;
}
this._currentSoundMode = soundMode;
try {
await this.hass!.callService("media_player", "select_sound_mode", {
entity_id: this._stateObj!.entity_id,
sound_mode: soundMode,
});
} catch (_err) {
this._currentSoundMode = oldSoundMode;
}
}
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsMediaPlayerSoundModeCardFeature(this.hass, this.context)
) {
return nothing;
}
const options = this._stateObj.attributes.sound_mode_list!.map(
(soundMode) => ({
value: soundMode,
label: this.hass!.formatEntityAttributeValue(
this._stateObj!,
"sound_mode",
soundMode
),
})
protected _isSupported(): boolean {
return !!(
this.hass &&
this.context &&
supportsMediaPlayerSoundModeCardFeature(this.hass, this.context)
);
return html`
<ha-control-select-menu
.hass=${this.hass}
show-arrow
.label=${this.hass.localize("ui.card.media_player.sound_mode")}
.value=${this._currentSoundMode}
.disabled=${this._stateObj.state === UNAVAILABLE}
.options=${options}
@wa-select=${this._valueChanged}
>
</ha-control-select-menu>
`;
}
static get styles() {
return cardFeatureStyles;
}
}

View File

@@ -1,11 +1,7 @@
import type { PropertyValues } from "lit";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
import "../../../components/ha-control-select-menu";
import { UNAVAILABLE } from "../../../data/entity/entity";
import {
MediaPlayerEntityFeature,
type MediaPlayerEntity,
@@ -13,7 +9,7 @@ import {
import type { HomeAssistant } from "../../../types";
import { hasConfigChanged } from "../common/has-changed";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import { HuiModeSelectCardFeatureBase } from "./hui-mode-select-card-feature-base";
import type {
LovelaceCardFeatureContext,
MediaPlayerSourceCardFeatureConfig,
@@ -37,59 +33,42 @@ export const supportsMediaPlayerSourceCardFeature = (
@customElement("hui-media-player-source-card-feature")
class HuiMediaPlayerSourceCardFeature
extends LitElement
extends HuiModeSelectCardFeatureBase<
MediaPlayerEntity,
MediaPlayerSourceCardFeatureConfig
>
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
protected readonly _attribute = "source";
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
protected readonly _modesAttribute = "source_list";
@state() private _config?: MediaPlayerSourceCardFeatureConfig;
protected readonly _serviceDomain = "media_player";
@state() private _currentSource?: string;
protected readonly _serviceAction = "select_source";
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as
| MediaPlayerEntity
| undefined;
protected get _label(): string {
return this.hass!.localize("ui.card.media_player.source");
}
protected readonly _hideLabel = false;
protected readonly _showDropdownOptionIcons = false;
protected readonly _allowIconsStyle = false;
static getStubConfig(): MediaPlayerSourceCardFeatureConfig {
return {
type: "media-player-source",
};
}
public setConfig(config: MediaPlayerSourceCardFeatureConfig): void {
if (!config) {
throw new Error("Invalid configuration");
}
this._config = config;
}
protected willUpdate(changedProps: PropertyValues): void {
super.willUpdate(changedProps);
if (
(changedProps.has("hass") || changedProps.has("context")) &&
this._stateObj
) {
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
const oldStateObj = oldHass?.states[this.context!.entity_id!];
if (oldStateObj !== this._stateObj) {
this._currentSource = this._stateObj.attributes.source;
}
}
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
const entityId = this.context?.entity_id;
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
return (
changedProps.has("_currentSource") ||
changedProps.has("_currentValue") ||
changedProps.has("context") ||
hasConfigChanged(this, changedProps) ||
(changedProps.has("hass") &&
@@ -99,61 +78,12 @@ class HuiMediaPlayerSourceCardFeature
);
}
private async _valueChanged(ev: HaDropdownSelectEvent) {
const source = ev.detail.item?.value;
const oldSource = this._stateObj!.attributes.source;
if (source === oldSource || !source) {
return;
}
this._currentSource = source;
try {
await this.hass!.callService("media_player", "select_source", {
entity_id: this._stateObj!.entity_id,
source: source,
});
} catch (_err) {
this._currentSource = oldSource;
}
}
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsMediaPlayerSourceCardFeature(this.hass, this.context)
) {
return nothing;
}
const options = this._stateObj.attributes.source_list!.map((source) => ({
value: source,
label: this.hass!.formatEntityAttributeValue(
this._stateObj!,
"source",
source
),
}));
return html`
<ha-control-select-menu
show-arrow
.label=${this.hass.localize("ui.card.media_player.source")}
.value=${this._currentSource}
.disabled=${this._stateObj.state === UNAVAILABLE}
.options=${options}
@wa-select=${this._valueChanged}
>
</ha-control-select-menu>
`;
}
static get styles() {
return cardFeatureStyles;
protected _isSupported(): boolean {
return !!(
this.hass &&
this.context &&
supportsMediaPlayerSourceCardFeature(this.hass, this.context)
);
}
}

View File

@@ -0,0 +1,263 @@
import type { HassEntity } from "home-assistant-js-websocket";
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement, nothing } from "lit";
import { property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import "../../../components/ha-attribute-icon";
import "../../../components/ha-control-select";
import "../../../components/ha-control-select-menu";
import "../../../components/ha-svg-icon";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import { filterModes } from "./common/filter-modes";
import type {
LovelaceCardFeatureConfig,
LovelaceCardFeatureContext,
} from "./types";
type AttributeModeChangeEvent = CustomEvent<{
value?: string;
item?: { value: string };
}>;
type AttributeModeCardFeatureConfig = LovelaceCardFeatureConfig & {
style?: "dropdown" | "icons";
};
export interface HuiModeSelectOption {
value: string;
label: string;
}
export abstract class HuiModeSelectCardFeatureBase<
TEntity extends HassEntity,
TConfig extends AttributeModeCardFeatureConfig,
>
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state() protected _config?: TConfig;
@state() protected _currentValue?: string;
protected abstract readonly _attribute: string;
protected abstract readonly _modesAttribute: string;
protected get _configuredModes(): string[] | undefined {
return undefined;
}
protected readonly _dropdownIconPath?: string;
protected abstract readonly _serviceDomain: string;
protected abstract readonly _serviceAction: string;
protected abstract _isSupported(): boolean;
protected get _label(): string {
return this.hass!.formatEntityAttributeName(
this._stateObj!,
this._attribute
);
}
protected readonly _hideLabel: boolean = true;
protected readonly _showDropdownOptionIcons: boolean = true;
protected readonly _allowIconsStyle: boolean = true;
protected readonly _defaultStyle: "dropdown" | "icons" = "dropdown";
protected get _controlSelectStyle():
| Record<string, string | undefined>
| undefined {
return undefined;
}
protected _getServiceDomain(_stateObj: TEntity): string {
return this._serviceDomain;
}
protected _isValueValid(_value: string, _stateObj: TEntity): boolean {
return true;
}
protected get _stateObj(): TEntity | undefined {
if (!this.hass || !this.context?.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id] as TEntity | undefined;
}
public setConfig(config: TConfig): void {
if (!config) {
throw new Error("Invalid configuration");
}
this._config = config;
}
protected willUpdate(changedProps: PropertyValues): void {
super.willUpdate(changedProps);
if (
(changedProps.has("hass") || changedProps.has("context")) &&
this._stateObj
) {
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
const oldStateObj = this.context?.entity_id
? (oldHass?.states[this.context.entity_id] as TEntity | undefined)
: undefined;
if (oldStateObj !== this._stateObj) {
this._currentValue = this._getValue(this._stateObj);
}
}
}
protected render(): TemplateResult | null {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!this._isSupported()
) {
return null;
}
const stateObj = this._stateObj;
const options = this._getOptions();
const label = this._label;
const renderIcons =
this._allowIconsStyle &&
(this._config.style === "icons" ||
(this._config.style === undefined && this._defaultStyle === "icons"));
if (renderIcons) {
return html`
<ha-control-select
.options=${options.map((option) => ({
...option,
icon: this._renderOptionIcon(option),
}))}
.value=${this._currentValue}
@value-changed=${this._valueChanged}
hide-option-label
.label=${label}
style=${styleMap(this._controlSelectStyle ?? {})}
.disabled=${stateObj.state === UNAVAILABLE}
>
</ha-control-select>
`;
}
return html`
<ha-control-select-menu
show-arrow
?hide-label=${this._hideLabel}
.label=${label}
.value=${this._currentValue}
.disabled=${stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
.options=${options}
.renderIcon=${this._showDropdownOptionIcons
? this._renderMenuIcon
: undefined}
>
${this._dropdownIconPath
? html`<ha-svg-icon
slot="icon"
.path=${this._dropdownIconPath}
></ha-svg-icon>`
: nothing}
</ha-control-select-menu>
`;
}
protected _getValue(stateObj: TEntity): string | undefined {
return stateObj.attributes[this._attribute] as string | undefined;
}
protected _getOptions(): HuiModeSelectOption[] {
if (!this._stateObj || !this.hass) {
return [];
}
return filterModes(
this._stateObj.attributes[this._modesAttribute] as string[] | undefined,
this._configuredModes
).map((mode) => ({
value: mode,
label: this.hass!.formatEntityAttributeValue(
this._stateObj!,
this._attribute,
mode
),
}));
}
protected _renderOptionIcon(option: HuiModeSelectOption): TemplateResult<1> {
return html`<ha-attribute-icon
slot="graphic"
.hass=${this.hass!}
.stateObj=${this._stateObj}
.attribute=${this._attribute}
.attributeValue=${option.value}
></ha-attribute-icon>`;
}
private _renderMenuIcon = (value: string): TemplateResult<1> =>
html`<ha-attribute-icon
.hass=${this.hass!}
.stateObj=${this._stateObj}
.attribute=${this._attribute}
.attributeValue=${value}
></ha-attribute-icon>`;
private async _valueChanged(ev: AttributeModeChangeEvent) {
if (!this.hass || !this._stateObj) {
return;
}
const value = ev.detail.value ?? ev.detail.item?.value;
const oldValue = this._getValue(this._stateObj);
if (
value === oldValue ||
!value ||
!this._isValueValid(value, this._stateObj)
) {
return;
}
this._currentValue = value;
try {
await this.hass.callService(
this._getServiceDomain(this._stateObj),
this._serviceAction,
{
entity_id: this._stateObj.entity_id,
[this._attribute]: value,
}
);
} catch (_err) {
this._currentValue = oldValue;
}
}
static get styles() {
return cardFeatureStyles;
}
}

View File

@@ -1,22 +1,20 @@
import type { PropertyValues } from "lit";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { customElement } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import "../../../components/ha-control-select-menu";
import "../../../components/ha-list-item";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type { InputSelectEntity } from "../../../data/input_select";
import type { SelectEntity } from "../../../data/select";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import { filterModes } from "./common/filter-modes";
import {
HuiModeSelectCardFeatureBase,
type HuiModeSelectOption,
} from "./hui-mode-select-card-feature-base";
import type {
LovelaceCardFeatureContext,
SelectOptionsCardFeatureConfig,
} from "./types";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
type SelectOptionEntity = SelectEntity | InputSelectEntity;
export const supportsSelectOptionsCardFeature = (
hass: HomeAssistant,
@@ -32,27 +30,32 @@ export const supportsSelectOptionsCardFeature = (
@customElement("hui-select-options-card-feature")
class HuiSelectOptionsCardFeature
extends LitElement
extends HuiModeSelectCardFeatureBase<
SelectOptionEntity,
SelectOptionsCardFeatureConfig
>
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
protected readonly _attribute = "option";
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
protected readonly _modesAttribute = "options";
@state() private _config?: SelectOptionsCardFeatureConfig;
@state() _currentOption?: string;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as
| SelectEntity
| InputSelectEntity
| undefined;
protected get _configuredModes() {
return this._config?.options;
}
protected readonly _serviceDomain = "select";
protected readonly _serviceAction = "select_option";
protected get _label(): string {
return this.hass!.localize("ui.card.select.option");
}
protected readonly _allowIconsStyle = false;
protected readonly _showDropdownOptionIcons = false;
static getStubConfig(): SelectOptionsCardFeatureConfig {
return {
type: "select-options",
@@ -64,98 +67,41 @@ class HuiSelectOptionsCardFeature
return document.createElement("hui-select-options-card-feature-editor");
}
public setConfig(config: SelectOptionsCardFeatureConfig): void {
if (!config) {
throw new Error("Invalid configuration");
}
this._config = config;
protected _getValue(stateObj: SelectOptionEntity): string | undefined {
return stateObj.state;
}
protected willUpdate(changedProp: PropertyValues): void {
super.willUpdate(changedProp);
if (
(changedProp.has("hass") || changedProp.has("context")) &&
this._stateObj
) {
const oldHass = changedProp.get("hass") as HomeAssistant | undefined;
const oldStateObj = oldHass?.states[this.context!.entity_id!];
if (oldStateObj !== this._stateObj) {
this._currentOption = this._stateObj.state;
}
}
}
private async _valueChanged(ev: HaDropdownSelectEvent) {
const option = ev.detail.item?.value;
const oldOption = this._stateObj!.state;
if (
option === oldOption ||
!this._stateObj!.attributes.options.includes(option)
) {
return;
protected _getOptions(): HuiModeSelectOption[] {
if (!this._stateObj || !this.hass) {
return [];
}
this._currentOption = option;
try {
await this._setOption(option);
} catch (_err) {
this._currentOption = oldOption;
}
}
private async _setOption(option: string) {
const domain = computeDomain(this._stateObj!.entity_id);
await this.hass!.callService(domain, "select_option", {
entity_id: this._stateObj!.entity_id,
option: option,
});
}
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsSelectOptionsCardFeature(this.hass, this.context)
) {
return nothing;
}
const stateObj = this._stateObj;
const options = this._getOptions(
return filterModes(
this._stateObj.attributes.options,
this._config.options
);
return html`
<ha-control-select-menu
show-arrow
hide-label
.label=${this.hass.localize("ui.card.select.option")}
.value=${stateObj.state}
.options=${options}
.disabled=${this._stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
>
</ha-control-select-menu>
`;
this._config?.options
).map((option) => ({
value: option,
label: this.hass!.formatEntityState(this._stateObj!, option),
}));
}
private _getOptions = memoizeOne(
(attributeOptions: string[], configOptions: string[] | undefined) =>
filterModes(attributeOptions, configOptions).map((option) => ({
value: option,
label: this.hass!.formatEntityState(this._stateObj!, option),
}))
);
protected _getServiceDomain(stateObj: SelectOptionEntity): string {
return computeDomain(stateObj.entity_id);
}
static get styles() {
return cardFeatureStyles;
protected _isValueValid(
value: string,
stateObj: SelectOptionEntity
): boolean {
return stateObj.attributes.options.includes(value);
}
protected _isSupported(): boolean {
return !!(
this.hass &&
this.context &&
supportsSelectOptionsCardFeature(this.hass, this.context)
);
}
}

View File

@@ -1,24 +1,13 @@
import { mdiWaterBoiler } from "@mdi/js";
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import { customElement } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import { stateColorCss } from "../../../common/entity/state_color";
import "../../../components/ha-attribute-icon";
import "../../../components/ha-control-select";
import "../../../components/ha-control-select-menu";
import "../../../components/ha-list-item";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type {
OperationMode,
WaterHeaterEntity,
} from "../../../data/water_heater";
import type { WaterHeaterEntity } from "../../../data/water_heater";
import { compareWaterHeaterOperationMode } from "../../../data/water_heater";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import { filterModes } from "./common/filter-modes";
import { HuiModeSelectCardFeatureBase } from "./hui-mode-select-card-feature-base";
import type {
LovelaceCardFeatureContext,
WaterHeaterOperationModesCardFeatureConfig,
@@ -38,32 +27,42 @@ export const supportsWaterHeaterOperationModesCardFeature = (
@customElement("hui-water-heater-operation-modes-card-feature")
class HuiWaterHeaterOperationModeCardFeature
extends LitElement
extends HuiModeSelectCardFeatureBase<
WaterHeaterEntity,
WaterHeaterOperationModesCardFeatureConfig
>
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
protected readonly _attribute = "operation_mode";
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
protected readonly _modesAttribute = "operation_list";
@state() private _config?: WaterHeaterOperationModesCardFeatureConfig;
protected get _configuredModes() {
return this._config?.operation_modes;
}
@state() _currentOperationMode?: OperationMode;
protected readonly _dropdownIconPath = mdiWaterBoiler;
private _renderOperationModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this._stateObj}
attribute="operation_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
protected readonly _serviceDomain = "water_heater";
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
protected readonly _serviceAction = "set_operation_mode";
protected get _label(): string {
return this.hass!.localize("ui.card.water_heater.mode");
}
protected readonly _defaultStyle = "icons";
protected get _controlSelectStyle():
| Record<string, string | undefined>
| undefined {
if (!this._stateObj) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as
| WaterHeaterEntity
| undefined;
return {
"--control-select-color": stateColorCss(this._stateObj),
};
}
static getStubConfig(): WaterHeaterOperationModesCardFeatureConfig {
@@ -79,125 +78,34 @@ class HuiWaterHeaterOperationModeCardFeature
);
}
public setConfig(config: WaterHeaterOperationModesCardFeatureConfig): void {
if (!config) {
throw new Error("Invalid configuration");
}
this._config = config;
protected _getValue(stateObj: WaterHeaterEntity): string | undefined {
return stateObj.state;
}
protected willUpdate(changedProp: PropertyValues): void {
super.willUpdate(changedProp);
if (
(changedProp.has("hass") || changedProp.has("context")) &&
this._stateObj
) {
const oldHass = changedProp.get("hass") as HomeAssistant | undefined;
const oldStateObj = oldHass?.states[this.context!.entity_id!];
if (oldStateObj !== this._stateObj) {
this._currentOperationMode = this._stateObj.state as OperationMode;
}
protected _getOptions() {
if (!this._stateObj || !this.hass) {
return [];
}
}
private async _valueChanged(
ev: CustomEvent<{ value?: string; item?: { value: string } }>
) {
const mode = ev.detail.value ?? ev.detail.item?.value;
if (mode === this._stateObj!.state || !mode) {
return;
}
const oldMode = this._stateObj!.state as OperationMode;
this._currentOperationMode = mode as OperationMode;
try {
await this._setMode(this._currentOperationMode);
} catch (_err) {
this._currentOperationMode = oldMode;
}
}
private async _setMode(mode: OperationMode) {
await this.hass!.callService("water_heater", "set_operation_mode", {
entity_id: this._stateObj!.entity_id,
operation_mode: mode,
});
}
protected render(): TemplateResult | null {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsWaterHeaterOperationModesCardFeature(this.hass, this.context)
) {
return null;
}
const color = stateColorCss(this._stateObj);
const orderedModes = (this._stateObj.attributes.operation_list || [])
.concat()
.sort(compareWaterHeaterOperationMode)
.reverse();
const options = filterModes(orderedModes, this._config.operation_modes).map(
return filterModes(orderedModes, this._config?.operation_modes).map(
(mode) => ({
value: mode,
label: this.hass!.formatEntityState(this._stateObj!, mode),
})
);
if (this._config.style === "dropdown") {
return html`
<ha-control-select-menu
.hass=${this.hass}
show-arrow
hide-label
.label=${this.hass.localize("ui.card.water_heater.mode")}
.value=${this._currentOperationMode}
.disabled=${this._stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
.options=${options}
.renderIcon=${this._renderOperationModeIcon}
>
<ha-svg-icon slot="icon" .path=${mdiWaterBoiler}></ha-svg-icon>
</ha-control-select-menu>
`;
}
return html`
<ha-control-select
.options=${options.map((option) => ({
...option,
icon: html`
<ha-attribute-icon
slot="graphic"
.hass=${this.hass}
.stateObj=${this._stateObj}
attribute="operation_mode"
.attributeValue=${option.value}
></ha-attribute-icon>
`,
}))}
.value=${this._currentOperationMode}
@value-changed=${this._valueChanged}
hide-option-label
.label=${this.hass.localize("ui.card.water_heater.mode")}
style=${styleMap({
"--control-select-color": color,
})}
.disabled=${this._stateObj!.state === UNAVAILABLE}
>
</ha-control-select>
`;
}
static get styles() {
return cardFeatureStyles;
protected _isSupported(): boolean {
return !!(
this.hass &&
this.context &&
supportsWaterHeaterOperationModesCardFeature(this.hass, this.context)
);
}
}