Use switch and add support for light, fan and valve (#24426)

* Use switch and add support for light and fan

* Add icon per domains
This commit is contained in:
Paul Bottein 2025-02-27 15:35:12 +01:00 committed by GitHub
parent 51193cf441
commit 8848911b34
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 144 additions and 56 deletions

View File

@ -3,8 +3,8 @@ import {
mdiBrightness6, mdiBrightness6,
mdiCreation, mdiCreation,
mdiFileWordBox, mdiFileWordBox,
mdiLightbulb,
mdiLightbulbOff, mdiLightbulbOff,
mdiLightbulbOn,
mdiPower, mdiPower,
} from "@mdi/js"; } from "@mdi/js";
import type { CSSResultGroup, PropertyValues } from "lit"; import type { CSSResultGroup, PropertyValues } from "lit";
@ -12,8 +12,8 @@ import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { stopPropagation } from "../../../common/dom/stop_propagation"; import { stopPropagation } from "../../../common/dom/stop_propagation";
import { supportsFeature } from "../../../common/entity/supports-feature"; import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attributes";
import "../../../components/ha-attribute-icon"; import "../../../components/ha-attribute-icon";
import "../../../components/ha-attributes";
import "../../../components/ha-control-select-menu"; import "../../../components/ha-control-select-menu";
import "../../../components/ha-icon-button-group"; import "../../../components/ha-icon-button-group";
import "../../../components/ha-icon-button-toggle"; import "../../../components/ha-icon-button-toggle";
@ -121,7 +121,7 @@ class MoreInfoLight extends LitElement {
<ha-state-control-toggle <ha-state-control-toggle
.stateObj=${this.stateObj} .stateObj=${this.stateObj}
.hass=${this.hass} .hass=${this.hass}
.iconPathOn=${mdiLightbulb} .iconPathOn=${mdiLightbulbOn}
.iconPathOff=${mdiLightbulbOff} .iconPathOff=${mdiLightbulbOff}
></ha-state-control-toggle> ></ha-state-control-toggle>
` `

View File

@ -27,4 +27,12 @@ export const cardFeatureStyles = css`
--control-slider-thickness: var(--feature-height); --control-slider-thickness: var(--feature-height);
--control-slider-border-radius: var(--feature-border-radius); --control-slider-border-radius: var(--feature-border-radius);
} }
ha-control-switch {
--control-switch-on-color: var(--feature-color);
--control-switch-off-color: var(--feature-color);
--control-switch-background-opacity: 0.2;
--control-switch-thickness: var(--feature-height);
--control-switch-border-radius: var(--feature-border-radius);
--control-switch-padding: 0px;
}
`; `;

View File

@ -1,23 +1,50 @@
import { mdiPowerOff, mdiPower } from "@mdi/js"; import {
mdiFan,
mdiFanOff,
mdiLightbulbOff,
mdiLightbulbOn,
mdiPower,
mdiPowerOff,
mdiVolumeHigh,
mdiVolumeOff,
} from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket"; import type { HassEntity } from "home-assistant-js-websocket";
import type { TemplateResult } from "lit"; import { LitElement, css, html, nothing } from "lit";
import { LitElement, html } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import { computeDomain } from "../../../common/entity/compute_domain"; import { computeDomain } from "../../../common/entity/compute_domain";
import { stateColorCss } from "../../../common/entity/state_color"; import { stateColorCss } from "../../../common/entity/state_color";
import "../../../components/ha-control-select"; import "../../../components/ha-control-button";
import type { ControlSelectOption } from "../../../components/ha-control-select"; import "../../../components/ha-control-button-group";
import { UNAVAILABLE } from "../../../data/entity"; import "../../../components/ha-control-switch";
import { UNAVAILABLE, UNKNOWN } from "../../../data/entity";
import { forwardHaptic } from "../../../data/haptics";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types"; import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles"; import { cardFeatureStyles } from "./common/card-feature-styles";
import type { ToggleCardFeatureConfig } from "./types"; import type { ToggleCardFeatureConfig } from "./types";
import { showToast } from "../../../util/toast";
export const supportsToggleCardFeature = (stateObj: HassEntity) => { export const supportsToggleCardFeature = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id); const domain = computeDomain(stateObj.entity_id);
return ["switch", "input_boolean"].includes(domain); return ["switch", "input_boolean", "light", "fan", "siren", "valve"].includes(
domain
);
};
const DOMAIN_ICONS: Record<string, { on: string; off: string }> = {
siren: {
on: mdiVolumeHigh,
off: mdiVolumeOff,
},
light: {
on: mdiLightbulbOn,
off: mdiLightbulbOff,
},
fan: {
on: mdiFan,
off: mdiFanOff,
},
}; };
@customElement("hui-toggle-card-feature") @customElement("hui-toggle-card-feature")
@ -41,67 +68,120 @@ class HuiToggleCardFeature extends LitElement implements LovelaceCardFeature {
this._config = config; this._config = config;
} }
protected render(): TemplateResult | null { private _valueChanged(ev) {
const checked = ev.target.checked as boolean;
if (checked) {
this._turnOn();
} else {
this._turnOff();
}
}
private _turnOn() {
this._callService(true);
}
private _turnOff() {
this._callService(false);
}
private async _callService(turnOn): Promise<void> {
if (!this.hass || !this.stateObj) {
return;
}
forwardHaptic("light");
const stateDomain = computeDomain(this.stateObj.entity_id);
const serviceDomain = stateDomain;
const service = turnOn ? "turn_on" : "turn_off";
await this.hass.callService(serviceDomain, service, {
entity_id: this.stateObj.entity_id,
});
}
protected render() {
if ( if (
!this._config || !this._config ||
!this.hass || !this.hass ||
!this.stateObj || !this.stateObj ||
!supportsToggleCardFeature(this.stateObj) !supportsToggleCardFeature(this.stateObj)
) { ) {
return null; return nothing;
} }
const color = stateColorCss(this.stateObj); const onColor = "var(--feature-color)";
const offColor = stateColorCss(this.stateObj, "off");
const options = ["off", "on"].map<ControlSelectOption>((entityState) => ({ const isOn = this.stateObj.state === "on";
value: entityState, const isOff = this.stateObj.state === "off";
label: this.hass!.formatEntityState(this.stateObj!, entityState),
path: entityState === "on" ? mdiPower : mdiPowerOff, const domain = computeDomain(this.stateObj.entity_id);
})); const onIcon = DOMAIN_ICONS[domain]?.on || mdiPower;
const offIcon = DOMAIN_ICONS[domain]?.off || mdiPowerOff;
if (
this.stateObj.attributes.assumed_state ||
this.stateObj.state === UNKNOWN
) {
return html`
<ha-control-button-group>
<ha-control-button
.label=${this.hass.localize("ui.card.common.turn_off")}
@click=${this._turnOff}
.disabled=${this.stateObj.state === UNAVAILABLE}
class=${classMap({
active: isOff,
})}
style=${styleMap({
"--color": offColor,
})}
>
<ha-svg-icon .path=${offIcon}></ha-svg-icon>
</ha-control-button>
<ha-control-button
.label=${this.hass.localize("ui.card.common.turn_on")}
@click=${this._turnOn}
.disabled=${this.stateObj.state === UNAVAILABLE}
class=${classMap({
active: isOn,
})}
style=${styleMap({
"--color": onColor,
})}
>
<ha-svg-icon .path=${onIcon}></ha-svg-icon>
</ha-control-button>
</ha-control-button-group>
`;
}
return html` return html`
<ha-control-select <ha-control-switch
.options=${options} touch-action="none"
.value=${this.stateObj.state} .pathOn=${onIcon}
@value-changed=${this._valueChanged} .pathOff=${offIcon}
hide-label .checked=${isOn}
.ariaLabel=${this.hass.localize("ui.card.humidifier.state")} @change=${this._valueChanged}
style=${styleMap({ .ariaLabel=${this.hass.localize("ui.card.common.toggle")}
"--control-select-color": color, .disabled=${this.stateObj.state === UNAVAILABLE}
})}
.disabled=${this.stateObj!.state === UNAVAILABLE}
> >
</ha-control-select> </ha-control-switch>
`; `;
} }
private async _valueChanged(ev: CustomEvent) { static get styles() {
const newState = (ev.detail as any).value; return [
cardFeatureStyles,
if ( css`
newState === this.stateObj!.state && ha-control-button.active {
!this.stateObj!.attributes.assumed_state --control-button-icon-color: white;
) --control-button-background-color: var(--color);
return; --control-button-background-opacity: 1;
const service = newState === "on" ? "turn_on" : "turn_off"; }
const domain = computeDomain(this.stateObj!.entity_id); `,
];
try {
await this.hass!.callService(domain, service, {
entity_id: this.stateObj!.entity_id,
});
} catch (_err) {
showToast(this, {
message: this.hass!.localize("ui.notification_toast.action_failed", {
service: domain + "." + service,
}),
duration: 5000,
dismissable: true,
});
}
} }
static styles = cardFeatureStyles;
} }
declare global { declare global {