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

Cover tilt presets card feature (#29989)

This commit is contained in:
Aidan Timson
2026-03-05 15:23:28 +00:00
committed by GitHub
parent aeddeb3bd7
commit 8f704d8c0a
6 changed files with 270 additions and 0 deletions

View File

@@ -0,0 +1,172 @@
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 { computeCssColor } from "../../../common/color/compute-color";
import { computeDomain } from "../../../common/entity/compute_domain";
import { stateColorCss } from "../../../common/entity/state_color";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-control-select";
import type { CoverEntity } from "../../../data/cover";
import { CoverEntityFeature } from "../../../data/cover";
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 type {
CoverTiltPresetCardFeatureConfig,
LovelaceCardFeatureContext,
} from "./types";
export const DEFAULT_COVER_TILT_PRESETS = [0, 25, 75, 100];
export const supportsCoverTiltPresetCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
) => {
const stateObj = context.entity_id
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
const domain = computeDomain(stateObj.entity_id);
return (
domain === "cover" &&
supportsFeature(stateObj, CoverEntityFeature.SET_TILT_POSITION)
);
};
@customElement("hui-cover-tilt-preset-card-feature")
class HuiCoverTiltPresetCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@property({ attribute: false }) public color?: string;
@state() private _config?: CoverTiltPresetCardFeatureConfig;
@state() private _currentTiltPosition?: number;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as CoverEntity | undefined;
}
static getStubConfig(): CoverTiltPresetCardFeatureConfig {
return {
type: "cover-tilt-preset",
positions: DEFAULT_COVER_TILT_PRESETS,
};
}
public static async getConfigElement(): Promise<LovelaceCardFeatureEditor> {
await import("../editor/config-elements/hui-cover-tilt-preset-card-feature-editor");
return document.createElement("hui-cover-tilt-preset-card-feature-editor");
}
public setConfig(config: CoverTiltPresetCardFeatureConfig): 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._currentTiltPosition =
this._stateObj.attributes.current_tilt_position ?? undefined;
}
}
}
private async _valueChanged(
ev: CustomEvent<{ value?: string; item?: { value: string } }>
) {
const value = ev.detail.value ?? ev.detail.item?.value;
if (value == null) return;
const tiltPosition = Number(value);
if (isNaN(tiltPosition)) return;
const oldTiltPosition = this._stateObj!.attributes.current_tilt_position;
if (tiltPosition === oldTiltPosition) return;
this._currentTiltPosition = tiltPosition;
try {
await this.hass!.callService("cover", "set_cover_tilt_position", {
entity_id: this._stateObj!.entity_id,
tilt_position: tiltPosition,
});
} catch (_err) {
this._currentTiltPosition = oldTiltPosition ?? undefined;
}
}
protected render(): TemplateResult | null {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsCoverTiltPresetCardFeature(this.hass, this.context)
) {
return null;
}
const positions = this._config.positions ?? DEFAULT_COVER_TILT_PRESETS;
const options = positions.map((position) => ({
value: String(position),
label: `${position}%`,
}));
const currentValue =
this._currentTiltPosition != null
? String(this._currentTiltPosition)
: undefined;
const color = this.color
? computeCssColor(this.color)
: stateColorCss(this._stateObj);
const style = {
"--feature-color": color,
};
return html`
<ha-control-select
style=${styleMap(style)}
.options=${options}
.value=${currentValue}
@value-changed=${this._valueChanged}
.label=${this.hass.localize(
"ui.panel.lovelace.editor.features.types.cover-tilt-preset.label"
)}
.disabled=${this._stateObj!.state === UNAVAILABLE}
>
</ha-control-select>
`;
}
static get styles() {
return cardFeatureStyles;
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-cover-tilt-preset-card-feature": HuiCoverTiltPresetCardFeature;
}
}

View File

@@ -31,6 +31,11 @@ export interface CoverPositionPresetCardFeatureConfig {
positions?: number[];
}
export interface CoverTiltPresetCardFeatureConfig {
type: "cover-tilt-preset";
positions?: number[];
}
export interface LightBrightnessCardFeatureConfig {
type: "light-brightness";
}
@@ -252,6 +257,7 @@ export type LovelaceCardFeatureConfig =
| CoverOpenCloseCardFeatureConfig
| CoverPositionCardFeatureConfig
| CoverPositionPresetCardFeatureConfig
| CoverTiltPresetCardFeatureConfig
| CoverTiltPositionCardFeatureConfig
| CoverTiltCardFeatureConfig
| DateSetCardFeatureConfig

View File

@@ -10,6 +10,7 @@ import "../card-features/hui-cover-open-close-card-feature";
import "../card-features/hui-cover-position-card-feature";
import "../card-features/hui-cover-position-preset-card-feature";
import "../card-features/hui-cover-tilt-card-feature";
import "../card-features/hui-cover-tilt-preset-card-feature";
import "../card-features/hui-cover-tilt-position-card-feature";
import "../card-features/hui-date-set-card-feature";
import "../card-features/hui-fan-direction-card-feature";
@@ -60,6 +61,7 @@ const TYPES = new Set<LovelaceCardFeatureConfig["type"]>([
"cover-open-close",
"cover-position",
"cover-position-preset",
"cover-tilt-preset",
"cover-tilt-position",
"cover-tilt",
"date-set",

View File

@@ -38,6 +38,7 @@ import { supportsCoverOpenCloseCardFeature } from "../../card-features/hui-cover
import { supportsCoverPositionCardFeature } from "../../card-features/hui-cover-position-card-feature";
import { supportsCoverPositionPresetCardFeature } from "../../card-features/hui-cover-position-preset-card-feature";
import { supportsCoverTiltCardFeature } from "../../card-features/hui-cover-tilt-card-feature";
import { supportsCoverTiltPresetCardFeature } from "../../card-features/hui-cover-tilt-preset-card-feature";
import { supportsCoverTiltPositionCardFeature } from "../../card-features/hui-cover-tilt-position-card-feature";
import { supportsDateSetCardFeature } from "../../card-features/hui-date-set-card-feature";
import { supportsFanDirectionCardFeature } from "../../card-features/hui-fan-direction-card-feature";
@@ -92,6 +93,7 @@ const UI_FEATURE_TYPES = [
"cover-open-close",
"cover-position",
"cover-position-preset",
"cover-tilt-preset",
"cover-tilt-position",
"cover-tilt",
"date-set",
@@ -136,6 +138,7 @@ const EDITABLES_FEATURE_TYPES = new Set<UiFeatureTypes>([
"climate-swing-horizontal-modes",
"counter-actions",
"cover-position-preset",
"cover-tilt-preset",
"fan-preset-modes",
"humidifier-modes",
"lawn-mower-commands",
@@ -166,6 +169,7 @@ const SUPPORTS_FEATURE_TYPES: Record<
"cover-open-close": supportsCoverOpenCloseCardFeature,
"cover-position": supportsCoverPositionCardFeature,
"cover-position-preset": supportsCoverPositionPresetCardFeature,
"cover-tilt-preset": supportsCoverTiltPresetCardFeature,
"cover-tilt-position": supportsCoverTiltPositionCardFeature,
"cover-tilt": supportsCoverTiltCardFeature,
"date-set": supportsDateSetCardFeature,

View File

@@ -0,0 +1,80 @@
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-multi-textfield";
import type { HomeAssistant } from "../../../../types";
import { DEFAULT_COVER_TILT_PRESETS } from "../../card-features/hui-cover-tilt-preset-card-feature";
import type {
CoverTiltPresetCardFeatureConfig,
LovelaceCardFeatureContext,
} from "../../card-features/types";
import type { LovelaceCardFeatureEditor } from "../../types";
@customElement("hui-cover-tilt-preset-card-feature-editor")
export class HuiCoverTiltPresetCardFeatureEditor
extends LitElement
implements LovelaceCardFeatureEditor
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state() private _config?: CoverTiltPresetCardFeatureConfig;
public setConfig(config: CoverTiltPresetCardFeatureConfig): void {
this._config = config;
}
protected render() {
if (!this.hass || !this._config) {
return nothing;
}
const positions = this._config.positions ?? DEFAULT_COVER_TILT_PRESETS;
const stringValues = positions.map((p) => String(p));
return html`
<ha-multi-textfield
.hass=${this.hass}
.value=${stringValues}
.max=${6}
.label=${this.hass.localize(
"ui.panel.lovelace.editor.features.types.cover-tilt-preset.position"
)}
.inputType=${"number"}
.inputSuffix=${"%"}
.addLabel=${this.hass.localize(
"ui.panel.lovelace.editor.features.types.cover-tilt-preset.add_position"
)}
.removeLabel=${this.hass.localize(
"ui.panel.lovelace.editor.features.types.cover-tilt-preset.remove_position"
)}
@value-changed=${this._valueChanged}
></ha-multi-textfield>
`;
}
private _valueChanged(ev: CustomEvent): void {
ev.stopPropagation();
const stringValues = ev.detail.value as (string | null | undefined)[];
const positions = stringValues
.filter((v): v is string => !!v && !isNaN(Number(v)))
.map((v) => Math.min(100, Math.max(0, Number(v))));
const config: CoverTiltPresetCardFeatureConfig = {
...this._config!,
positions,
};
this._config = config;
fireEvent(this, "config-changed", { config });
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-cover-tilt-preset-card-feature-editor": HuiCoverTiltPresetCardFeatureEditor;
}
}

View File

@@ -9359,6 +9359,12 @@
"cover-tilt": {
"label": "Cover tilt"
},
"cover-tilt-preset": {
"label": "Cover tilt preset",
"position": "Position",
"add_position": "Add position",
"remove_position": "Remove position"
},
"cover-tilt-position": {
"label": "Cover tilt position"
},