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,
mdiCreation,
mdiFileWordBox,
mdiLightbulb,
mdiLightbulbOff,
mdiLightbulbOn,
mdiPower,
} from "@mdi/js";
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 { stopPropagation } from "../../../common/dom/stop_propagation";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attributes";
import "../../../components/ha-attribute-icon";
import "../../../components/ha-attributes";
import "../../../components/ha-control-select-menu";
import "../../../components/ha-icon-button-group";
import "../../../components/ha-icon-button-toggle";
@ -121,7 +121,7 @@ class MoreInfoLight extends LitElement {
<ha-state-control-toggle
.stateObj=${this.stateObj}
.hass=${this.hass}
.iconPathOn=${mdiLightbulb}
.iconPathOn=${mdiLightbulbOn}
.iconPathOff=${mdiLightbulbOff}
></ha-state-control-toggle>
`

View File

@ -27,4 +27,12 @@ export const cardFeatureStyles = css`
--control-slider-thickness: var(--feature-height);
--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 { TemplateResult } from "lit";
import { LitElement, html } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map";
import { computeDomain } from "../../../common/entity/compute_domain";
import { stateColorCss } from "../../../common/entity/state_color";
import "../../../components/ha-control-select";
import type { ControlSelectOption } from "../../../components/ha-control-select";
import { UNAVAILABLE } from "../../../data/entity";
import "../../../components/ha-control-button";
import "../../../components/ha-control-button-group";
import "../../../components/ha-control-switch";
import { UNAVAILABLE, UNKNOWN } from "../../../data/entity";
import { forwardHaptic } from "../../../data/haptics";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type { ToggleCardFeatureConfig } from "./types";
import { showToast } from "../../../util/toast";
export const supportsToggleCardFeature = (stateObj: HassEntity) => {
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")
@ -41,67 +68,120 @@ class HuiToggleCardFeature extends LitElement implements LovelaceCardFeature {
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 (
!this._config ||
!this.hass ||
!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) => ({
value: entityState,
label: this.hass!.formatEntityState(this.stateObj!, entityState),
path: entityState === "on" ? mdiPower : mdiPowerOff,
}));
const isOn = this.stateObj.state === "on";
const isOff = this.stateObj.state === "off";
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`
<ha-control-select
.options=${options}
.value=${this.stateObj.state}
@value-changed=${this._valueChanged}
hide-label
.ariaLabel=${this.hass.localize("ui.card.humidifier.state")}
style=${styleMap({
"--control-select-color": color,
})}
.disabled=${this.stateObj!.state === UNAVAILABLE}
<ha-control-switch
touch-action="none"
.pathOn=${onIcon}
.pathOff=${offIcon}
.checked=${isOn}
@change=${this._valueChanged}
.ariaLabel=${this.hass.localize("ui.card.common.toggle")}
.disabled=${this.stateObj.state === UNAVAILABLE}
>
</ha-control-select>
</ha-control-switch>
`;
}
private async _valueChanged(ev: CustomEvent) {
const newState = (ev.detail as any).value;
if (
newState === this.stateObj!.state &&
!this.stateObj!.attributes.assumed_state
)
return;
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 get styles() {
return [
cardFeatureStyles,
css`
ha-control-button.active {
--control-button-icon-color: white;
--control-button-background-color: var(--color);
--control-button-background-opacity: 1;
}
`,
];
}
static styles = cardFeatureStyles;
}
declare global {