Add cover controls to area card and improve areas dashboard (#25892)

This commit is contained in:
Paul Bottein 2025-06-25 15:14:41 +02:00 committed by GitHub
parent 5c1a8029bf
commit 174d54396f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 336 additions and 71 deletions

View File

@ -0,0 +1,68 @@
import { callService, type HassEntity } from "home-assistant-js-websocket";
import { computeStateDomain } from "./compute_state_domain";
import { isUnavailableState, UNAVAILABLE } from "../../data/entity";
import type { HomeAssistant } from "../../types";
export const computeGroupEntitiesState = (states: HassEntity[]): string => {
if (!states.length) {
return UNAVAILABLE;
}
const validState = states.filter((stateObj) => isUnavailableState(stateObj));
if (!validState) {
return UNAVAILABLE;
}
// Use the first state to determine the domain
// This assumes all states in the group have the same domain
const domain = computeStateDomain(states[0]);
if (domain === "cover") {
for (const s of ["opening", "closing", "open"]) {
if (states.some((stateObj) => stateObj.state === s)) {
return s;
}
}
return "closed";
}
if (states.some((stateObj) => stateObj.state === "on")) {
return "on";
}
return "off";
};
export const toggleGroupEntities = (
hass: HomeAssistant,
states: HassEntity[]
) => {
if (!states.length) {
return;
}
// Use the first state to determine the domain
// This assumes all states in the group have the same domain
const domain = computeStateDomain(states[0]);
const state = computeGroupEntitiesState(states);
const isOn = state === "on" || state === "open";
let service = isOn ? "turn_off" : "turn_on";
if (domain === "cover") {
if (state === "opening" || state === "closing") {
// If the cover is opening or closing, we toggle it to stop it
service = "stop_cover";
} else {
// For covers, we use the open/close service
service = isOn ? "close_cover" : "open_cover";
}
}
const entitiesIds = states.map((stateObj) => stateObj.entity_id);
callService(hass.connection, domain, service, {
entity_id: entitiesIds,
});
};

View File

@ -64,15 +64,27 @@ export const domainStateColorProperties = (
const compareState = state !== undefined ? state : stateObj.state; const compareState = state !== undefined ? state : stateObj.state;
const active = stateActive(stateObj, state); const active = stateActive(stateObj, state);
return domainColorProperties(
domain,
stateObj.attributes.device_class,
compareState,
active
);
};
export const domainColorProperties = (
domain: string,
deviceClass: string | undefined,
state: string,
active: boolean
) => {
const properties: string[] = []; const properties: string[] = [];
const stateKey = slugify(compareState, "_"); const stateKey = slugify(state, "_");
const activeKey = active ? "active" : "inactive"; const activeKey = active ? "active" : "inactive";
const dc = stateObj.attributes.device_class; if (deviceClass) {
properties.push(`--state-${domain}-${deviceClass}-${stateKey}-color`);
if (dc) {
properties.push(`--state-${domain}-${dc}-${stateKey}-color`);
} }
properties.push( properties.push(

View File

@ -26,6 +26,7 @@ export class HaControlButtonGroup extends LitElement {
.container { .container {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: var(--control-button-group-alignment, start);
width: 100%; width: 100%;
height: 100%; height: 100%;
} }

View File

@ -18,6 +18,8 @@ export class HaDomainIcon extends LitElement {
@property({ attribute: false }) public deviceClass?: string; @property({ attribute: false }) public deviceClass?: string;
@property({ attribute: false }) public state?: string;
@property() public icon?: string; @property() public icon?: string;
@property({ attribute: "brand-fallback", type: Boolean }) @property({ attribute: "brand-fallback", type: Boolean })
@ -36,14 +38,17 @@ export class HaDomainIcon extends LitElement {
return this._renderFallback(); return this._renderFallback();
} }
const icon = domainIcon(this.hass, this.domain, this.deviceClass).then( const icon = domainIcon(
(icn) => { this.hass,
if (icn) { this.domain,
return html`<ha-icon .icon=${icn}></ha-icon>`; this.deviceClass,
} this.state
return this._renderFallback(); ).then((icn) => {
if (icn) {
return html`<ha-icon .icon=${icn}></ha-icon>`;
} }
); return this._renderFallback();
});
return html`${until(icon)}`; return html`${until(icon)}`;
} }

View File

@ -504,14 +504,25 @@ export const serviceSectionIcon = async (
export const domainIcon = async ( export const domainIcon = async (
hass: HomeAssistant, hass: HomeAssistant,
domain: string, domain: string,
deviceClass?: string deviceClass?: string,
state?: string
): Promise<string | undefined> => { ): Promise<string | undefined> => {
const entityComponentIcons = await getComponentIcons(hass, domain); const entityComponentIcons = await getComponentIcons(hass, domain);
if (entityComponentIcons) { if (entityComponentIcons) {
const translations = const translations =
(deviceClass && entityComponentIcons[deviceClass]) || (deviceClass && entityComponentIcons[deviceClass]) ||
entityComponentIcons._; entityComponentIcons._;
return translations?.default; // First check for exact state match
if (state && translations.state?.[state]) {
return translations.state[state];
}
// Then check for range-based icons if we have a numeric state
if (state !== undefined && translations.range && !isNaN(Number(state))) {
return getIconFromRange(Number(state), translations.range);
}
// Fallback to default icon
return translations.default;
} }
return undefined; return undefined;
}; };

View File

@ -25,6 +25,9 @@ export const cardFeatureStyles = css`
flex-basis: 20px; flex-basis: 20px;
--control-button-padding: 0px; --control-button-padding: 0px;
} }
ha-control-button-group[no-stretch] > ha-control-button {
max-width: 48px;
}
ha-control-button { ha-control-button {
--control-button-focus-color: var(--feature-color); --control-button-focus-color: var(--feature-color);
} }

View File

@ -1,17 +1,22 @@
import { mdiFan, mdiLightbulb, mdiToggleSwitch } from "@mdi/js"; import type { HassEntity } from "home-assistant-js-websocket";
import { callService, type HassEntity } from "home-assistant-js-websocket"; import { css, html, LitElement, nothing } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { ensureArray } from "../../../common/array/ensure-array";
import { generateEntityFilter } from "../../../common/entity/entity_filter";
import { import {
generateEntityFilter, computeGroupEntitiesState,
type EntityFilter, toggleGroupEntities,
} from "../../../common/entity/entity_filter"; } from "../../../common/entity/group_entities";
import { stateActive } from "../../../common/entity/state_active"; import { stateActive } from "../../../common/entity/state_active";
import { domainColorProperties } from "../../../common/entity/state_color";
import "../../../components/ha-control-button"; import "../../../components/ha-control-button";
import "../../../components/ha-control-button-group"; import "../../../components/ha-control-button-group";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import type { AreaRegistryEntry } from "../../../data/area_registry"; import type { AreaRegistryEntry } from "../../../data/area_registry";
import { forwardHaptic } from "../../../data/haptics";
import { computeCssVariable } from "../../../resources/css-variables";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles"; import { cardFeatureStyles } from "./common/card-feature-styles";
@ -19,41 +24,55 @@ import type {
AreaControl, AreaControl,
AreaControlsCardFeatureConfig, AreaControlsCardFeatureConfig,
LovelaceCardFeatureContext, LovelaceCardFeatureContext,
LovelaceCardFeaturePosition,
} from "./types"; } from "./types";
import { AREA_CONTROLS } from "./types"; import { AREA_CONTROLS } from "./types";
interface AreaControlsButton { interface AreaControlsButton {
iconPath: string; offIcon?: string;
onService: string; onIcon?: string;
offService: string; filter: {
filter: EntityFilter; domain: string;
device_class?: string;
};
} }
const coverButton = (deviceClass: string) => ({
filter: {
domain: "cover",
device_class: deviceClass,
},
});
export const AREA_CONTROLS_BUTTONS: Record<AreaControl, AreaControlsButton> = { export const AREA_CONTROLS_BUTTONS: Record<AreaControl, AreaControlsButton> = {
light: { light: {
iconPath: mdiLightbulb, // Overrides the icons for lights
offIcon: "mdi:lightbulb-off",
onIcon: "mdi:lightbulb",
filter: { filter: {
domain: "light", domain: "light",
}, },
onService: "light.turn_on",
offService: "light.turn_off",
}, },
fan: { fan: {
iconPath: mdiFan,
filter: { filter: {
domain: "fan", domain: "fan",
}, },
onService: "fan.turn_on",
offService: "fan.turn_off",
}, },
switch: { switch: {
iconPath: mdiToggleSwitch,
filter: { filter: {
domain: "switch", domain: "switch",
}, },
onService: "switch.turn_on",
offService: "switch.turn_off",
}, },
"cover-blind": coverButton("blind"),
"cover-curtain": coverButton("curtain"),
"cover-damper": coverButton("damper"),
"cover-awning": coverButton("awning"),
"cover-door": coverButton("door"),
"cover-garage": coverButton("garage"),
"cover-gate": coverButton("gate"),
"cover-shade": coverButton("shade"),
"cover-shutter": coverButton("shutter"),
"cover-window": coverButton("window"),
}; };
export const supportsAreaControlsCardFeature = ( export const supportsAreaControlsCardFeature = (
@ -87,6 +106,8 @@ export const getAreaControlEntities = (
{} as Record<AreaControl, string[]> {} as Record<AreaControl, string[]>
); );
export const MAX_DEFAULT_AREA_CONTROLS = 4;
@customElement("hui-area-controls-card-feature") @customElement("hui-area-controls-card-feature")
class HuiAreaControlsCardFeature class HuiAreaControlsCardFeature
extends LitElement extends LitElement
@ -96,6 +117,9 @@ class HuiAreaControlsCardFeature
@property({ attribute: false }) public context?: LovelaceCardFeatureContext; @property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@property({ attribute: false })
public position?: LovelaceCardFeaturePosition;
@state() private _config?: AreaControlsCardFeatureConfig; @state() private _config?: AreaControlsCardFeatureConfig;
private get _area() { private get _area() {
@ -151,17 +175,12 @@ class HuiAreaControlsCardFeature
); );
const entitiesIds = controlEntities[control]; const entitiesIds = controlEntities[control];
const { onService, offService } = AREA_CONTROLS_BUTTONS[control]; const entities = entitiesIds
.map((entityId) => this.hass!.states[entityId] as HassEntity | undefined)
.filter((v): v is HassEntity => Boolean(v));
const isOn = entitiesIds.some((entityId) => forwardHaptic("light");
stateActive(this.hass!.states[entityId] as HassEntity) toggleGroupEntities(this.hass, entities);
);
const [domain, service] = (isOn ? offService : onService).split(".");
callService(this.hass!.connection, domain, service, {
entity_id: entitiesIds,
});
} }
private _controlEntities = memoizeOne( private _controlEntities = memoizeOne(
@ -200,33 +219,67 @@ class HuiAreaControlsCardFeature
(control) => controlEntities[control].length > 0 (control) => controlEntities[control].length > 0
); );
if (!supportedControls.length) { const displayControls = this._config.controls
? supportedControls
: supportedControls.slice(0, MAX_DEFAULT_AREA_CONTROLS); // Limit to max if using default controls
if (!displayControls.length) {
return nothing; return nothing;
} }
return html` return html`
<ha-control-button-group> <ha-control-button-group ?no-stretch=${this.position === "inline"}>
${supportedControls.map((control) => { ${displayControls.map((control) => {
const button = AREA_CONTROLS_BUTTONS[control]; const button = AREA_CONTROLS_BUTTONS[control];
const entities = controlEntities[control]; const entityIds = controlEntities[control];
const active = entities.some((entityId) => {
const stateObj = this.hass!.states[entityId] as const entities = entityIds
| HassEntity .map(
| undefined; (entityId) =>
if (!stateObj) { this.hass!.states[entityId] as HassEntity | undefined
return false; )
} .filter((v): v is HassEntity => Boolean(v));
return stateActive(stateObj);
}); const groupState = computeGroupEntitiesState(entities);
const active = entities[0]
? stateActive(entities[0], groupState)
: false;
const label = this.hass!.localize(
`ui.card_features.area_controls.${control}.${active ? "off" : "on"}`
);
const icon = active ? button.onIcon : button.offIcon;
const domain = button.filter.domain;
const deviceClass = button.filter.device_class
? ensureArray(button.filter.device_class)[0]
: undefined;
const activeColor = computeCssVariable(
domainColorProperties(domain, deviceClass, groupState, true)
);
return html` return html`
<ha-control-button <ha-control-button
style=${styleMap({
"--active-color": activeColor,
})}
.title=${label}
aria-label=${label}
class=${active ? "active" : ""} class=${active ? "active" : ""}
.control=${control} .control=${control}
@click=${this._handleButtonTap} @click=${this._handleButtonTap}
> >
<ha-svg-icon .path=${button.iconPath}></ha-svg-icon> <ha-domain-icon
.hass=${this.hass}
.icon=${icon}
.domain=${domain}
.deviceClass=${deviceClass}
.state=${groupState}
></ha-domain-icon>
</ha-control-button> </ha-control-button>
`; `;
})} })}
@ -238,6 +291,9 @@ class HuiAreaControlsCardFeature
return [ return [
cardFeatureStyles, cardFeatureStyles,
css` css`
ha-control-button-group {
--control-button-group-alignment: flex-end;
}
ha-control-button { ha-control-button {
--active-color: var(--state-active-color); --active-color: var(--state-active-color);
--control-button-focus-color: var(--state-active-color); --control-button-focus-color: var(--state-active-color);

View File

@ -7,6 +7,7 @@ import type { LovelaceCardFeature } from "../types";
import type { import type {
LovelaceCardFeatureConfig, LovelaceCardFeatureConfig,
LovelaceCardFeatureContext, LovelaceCardFeatureContext,
LovelaceCardFeaturePosition,
} from "./types"; } from "./types";
@customElement("hui-card-feature") @customElement("hui-card-feature")
@ -19,6 +20,9 @@ export class HuiCardFeature extends LitElement {
@property({ attribute: false }) public color?: string; @property({ attribute: false }) public color?: string;
@property({ attribute: false })
public position?: LovelaceCardFeaturePosition;
private _element?: LovelaceCardFeature | HuiErrorCard; private _element?: LovelaceCardFeature | HuiErrorCard;
private _getFeatureElement(feature: LovelaceCardFeatureConfig) { private _getFeatureElement(feature: LovelaceCardFeatureConfig) {
@ -41,6 +45,7 @@ export class HuiCardFeature extends LitElement {
element.hass = this.hass; element.hass = this.hass;
element.context = this.context; element.context = this.context;
element.color = this.color; element.color = this.color;
element.position = this.position;
// Backwards compatibility from custom card features // Backwards compatibility from custom card features
if (this.context.entity_id) { if (this.context.entity_id) {
const stateObj = this.hass.states[this.context.entity_id]; const stateObj = this.hass.states[this.context.entity_id];

View File

@ -5,6 +5,7 @@ import "./hui-card-feature";
import type { import type {
LovelaceCardFeatureConfig, LovelaceCardFeatureConfig,
LovelaceCardFeatureContext, LovelaceCardFeatureContext,
LovelaceCardFeaturePosition,
} from "./types"; } from "./types";
@customElement("hui-card-features") @customElement("hui-card-features")
@ -17,6 +18,9 @@ export class HuiCardFeatures extends LitElement {
@property({ attribute: false }) public color?: string; @property({ attribute: false }) public color?: string;
@property({ attribute: false })
public position?: LovelaceCardFeaturePosition;
protected render() { protected render() {
if (!this.features) { if (!this.features) {
return nothing; return nothing;
@ -29,6 +33,7 @@ export class HuiCardFeatures extends LitElement {
.context=${this.context} .context=${this.context}
.color=${this.color} .color=${this.color}
.feature=${feature} .feature=${feature}
.position=${this.position}
></hui-card-feature> ></hui-card-feature>
` `
)} )}

View File

@ -158,7 +158,21 @@ export interface UpdateActionsCardFeatureConfig {
backup?: "yes" | "no" | "ask"; backup?: "yes" | "no" | "ask";
} }
export const AREA_CONTROLS = ["light", "fan", "switch"] as const; export const AREA_CONTROLS = [
"light",
"fan",
"cover-shutter",
"cover-blind",
"cover-curtain",
"cover-shade",
"cover-awning",
"cover-garage",
"cover-gate",
"cover-door",
"cover-window",
"cover-damper",
"switch",
] as const;
export type AreaControl = (typeof AREA_CONTROLS)[number]; export type AreaControl = (typeof AREA_CONTROLS)[number];
@ -168,6 +182,8 @@ export interface AreaControlsCardFeatureConfig {
exclude_entities?: string[]; exclude_entities?: string[];
} }
export type LovelaceCardFeaturePosition = "bottom" | "inline";
export type LovelaceCardFeatureConfig = export type LovelaceCardFeatureConfig =
| AlarmModesCardFeatureConfig | AlarmModesCardFeatureConfig
| ClimateFanModesCardFeatureConfig | ClimateFanModesCardFeatureConfig

View File

@ -514,6 +514,7 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
.context=${this._featureContext} .context=${this._featureContext}
.color=${this._config.color} .color=${this._config.color}
.features=${features} .features=${features}
.position=${featurePosition}
></hui-card-features> ></hui-card-features>
` `
: nothing} : nothing}

View File

@ -9,7 +9,10 @@ import type {
ThemeMode, ThemeMode,
TranslationDict, TranslationDict,
} from "../../../types"; } from "../../../types";
import type { LovelaceCardFeatureConfig } from "../card-features/types"; import type {
LovelaceCardFeatureConfig,
LovelaceCardFeaturePosition,
} from "../card-features/types";
import type { LegacyStateFilter } from "../common/evaluate-filter"; import type { LegacyStateFilter } from "../common/evaluate-filter";
import type { Condition, LegacyCondition } from "../common/validate-condition"; import type { Condition, LegacyCondition } from "../common/validate-condition";
import type { HuiImage } from "../components/hui-image"; import type { HuiImage } from "../components/hui-image";
@ -113,7 +116,7 @@ export interface AreaCardConfig extends LovelaceCardConfig {
sensor_classes?: string[]; sensor_classes?: string[];
alert_classes?: string[]; alert_classes?: string[];
features?: LovelaceCardFeatureConfig[]; features?: LovelaceCardFeatureConfig[];
features_position?: "bottom" | "inline"; features_position?: LovelaceCardFeaturePosition;
} }
export interface ButtonCardConfig extends LovelaceCardConfig { export interface ButtonCardConfig extends LovelaceCardConfig {
@ -564,7 +567,7 @@ export interface TileCardConfig extends LovelaceCardConfig {
icon_hold_action?: ActionConfig; icon_hold_action?: ActionConfig;
icon_double_tap_action?: ActionConfig; icon_double_tap_action?: ActionConfig;
features?: LovelaceCardFeatureConfig[]; features?: LovelaceCardFeatureConfig[];
features_position?: "bottom" | "inline"; features_position?: LovelaceCardFeaturePosition;
} }
export interface HeadingCardConfig extends LovelaceCardConfig { export interface HeadingCardConfig extends LovelaceCardConfig {

View File

@ -9,7 +9,10 @@ import type {
SchemaUnion, SchemaUnion,
} from "../../../../components/ha-form/types"; } from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import { getAreaControlEntities } from "../../card-features/hui-area-controls-card-feature"; import {
getAreaControlEntities,
MAX_DEFAULT_AREA_CONTROLS,
} from "../../card-features/hui-area-controls-card-feature";
import { import {
AREA_CONTROLS, AREA_CONTROLS,
type AreaControl, type AreaControl,
@ -72,7 +75,7 @@ export class HuiAreaControlsCardFeatureEditor
] as const satisfies readonly HaFormSchema[] ] as const satisfies readonly HaFormSchema[]
); );
private _compatibleControls = memoizeOne( private _supportedControls = memoizeOne(
( (
areaId: string, areaId: string,
// needed to update memoized function when entities, devices or areas change // needed to update memoized function when entities, devices or areas change
@ -99,14 +102,14 @@ export class HuiAreaControlsCardFeatureEditor
return nothing; return nothing;
} }
const compatibleControls = this._compatibleControls( const supportedControls = this._supportedControls(
this.context.area_id, this.context.area_id,
this.hass.entities, this.hass.entities,
this.hass.devices, this.hass.devices,
this.hass.areas this.hass.areas
); );
if (compatibleControls.length === 0) { if (supportedControls.length === 0) {
return html` return html`
<ha-alert alert-type="warning"> <ha-alert alert-type="warning">
${this.hass.localize( ${this.hass.localize(
@ -124,7 +127,7 @@ export class HuiAreaControlsCardFeatureEditor
const schema = this._schema( const schema = this._schema(
this.hass.localize, this.hass.localize,
data.customize_controls, data.customize_controls,
compatibleControls supportedControls
); );
return html` return html`
@ -143,12 +146,12 @@ export class HuiAreaControlsCardFeatureEditor
.value as AreaControlsCardFeatureData; .value as AreaControlsCardFeatureData;
if (customize_controls && !config.controls) { if (customize_controls && !config.controls) {
config.controls = this._compatibleControls( config.controls = this._supportedControls(
this.context!.area_id!, this.context!.area_id!,
this.hass!.entities, this.hass!.entities,
this.hass!.devices, this.hass!.devices,
this.hass!.areas this.hass!.areas
).concat(); ).slice(0, MAX_DEFAULT_AREA_CONTROLS); // Limit to max default controls
} }
if (!customize_controls && config.controls) { if (!customize_controls && config.controls) {

View File

@ -6,7 +6,7 @@ import type { LovelaceSectionConfig } from "../../../../data/lovelace/config/sec
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view"; import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import { getAreaControlEntities } from "../../card-features/hui-area-controls-card-feature"; import { getAreaControlEntities } from "../../card-features/hui-area-controls-card-feature";
import type { AreaControl } from "../../card-features/types"; import { AREA_CONTROLS, type AreaControl } from "../../card-features/types";
import type { AreaCardConfig, HeadingCardConfig } from "../../cards/types"; import type { AreaCardConfig, HeadingCardConfig } from "../../cards/types";
import type { EntitiesDisplay } from "./area-view-strategy"; import type { EntitiesDisplay } from "./area-view-strategy";
import { computeAreaPath, getAreas } from "./helpers/areas-strategy-helper"; import { computeAreaPath, getAreas } from "./helpers/areas-strategy-helper";
@ -77,7 +77,9 @@ export class AreasOverviewViewStrategy extends ReactiveElement {
.map((display) => display.hidden || []) .map((display) => display.hidden || [])
.flat(); .flat();
const controls: AreaControl[] = ["light", "fan"]; const controls: AreaControl[] = AREA_CONTROLS.filter(
(a) => a !== "switch" // Exclude switches control for areas as we don't know what the switches control
);
const controlEntities = getAreaControlEntities( const controlEntities = getAreaControlEntities(
controls, controls,
area.area_id, area.area_id,
@ -112,6 +114,11 @@ export class AreasOverviewViewStrategy extends ReactiveElement {
}, },
] ]
: [], : [],
grid_options: {
rows: 1,
columns: 12,
},
features_position: "inline",
navigation_path: path, navigation_path: path,
}; };
}); });

View File

@ -13,6 +13,7 @@ import type { Constructor, HomeAssistant } from "../../types";
import type { import type {
LovelaceCardFeatureConfig, LovelaceCardFeatureConfig,
LovelaceCardFeatureContext, LovelaceCardFeatureContext,
LovelaceCardFeaturePosition,
} from "./card-features/types"; } from "./card-features/types";
import type { LovelaceElement, LovelaceElementConfig } from "./elements/types"; import type { LovelaceElement, LovelaceElementConfig } from "./elements/types";
import type { LovelaceRow, LovelaceRowConfig } from "./entity-rows/types"; import type { LovelaceRow, LovelaceRowConfig } from "./entity-rows/types";
@ -179,6 +180,7 @@ export interface LovelaceCardFeature extends HTMLElement {
context?: LovelaceCardFeatureContext; context?: LovelaceCardFeatureContext;
setConfig(config: LovelaceCardFeatureConfig); setConfig(config: LovelaceCardFeatureConfig);
color?: string; color?: string;
position?: LovelaceCardFeaturePosition;
} }
export interface LovelaceCardFeatureConstructor export interface LovelaceCardFeatureConstructor

View File

@ -325,6 +325,62 @@
"low": "Low" "low": "Low"
} }
}, },
"card_features": {
"area_controls": {
"light": {
"on": "Turn on area lights",
"off": "Turn off area lights"
},
"fan": {
"on": "Turn on area fans",
"off": "Turn off area fans"
},
"switch": {
"on": "Turn on area switches",
"off": "Turn off area switches"
},
"cover-awning": {
"on": "Open area awnings",
"off": "Close area awnings"
},
"cover-blind": {
"on": "Open area blinds",
"off": "Close area blinds"
},
"cover-curtain": {
"on": "Open area curtains",
"off": "Close area curtains"
},
"cover-damper": {
"on": "Open area dampers",
"off": "Close area dampers"
},
"cover-door": {
"on": "Open area doors",
"off": "Close area doors"
},
"cover-garage": {
"on": "Open garage door",
"off": "Close garage door"
},
"cover-gate": {
"on": "Open area gates",
"off": "Close area gates"
},
"cover-shade": {
"on": "Open area shades",
"off": "Close area shades"
},
"cover-shutter": {
"on": "Open area shutters",
"off": "Close area shutters"
},
"cover-window": {
"on": "Open area windows",
"off": "Close area windows"
}
}
},
"common": { "common": {
"and": "and", "and": "and",
"continue": "Continue", "continue": "Continue",
@ -383,6 +439,7 @@
"markdown": "Markdown", "markdown": "Markdown",
"suggest_ai": "Suggest with AI" "suggest_ai": "Suggest with AI"
}, },
"components": { "components": {
"selectors": { "selectors": {
"media": { "media": {
@ -7857,7 +7914,17 @@
"controls_options": { "controls_options": {
"light": "Lights", "light": "Lights",
"fan": "Fans", "fan": "Fans",
"switch": "Switches" "switch": "Switches",
"cover-awning": "Awnings",
"cover-blind": "Blinds",
"cover-curtain": "Curtains",
"cover-damper": "Dampers",
"cover-door": "Doors",
"cover-garage": "Garage doors",
"cover-gate": "Gates",
"cover-shade": "Shades",
"cover-shutter": "Shutters",
"cover-window": "Windows"
}, },
"no_compatible_controls": "No compatible controls available for this area" "no_compatible_controls": "No compatible controls available for this area"
} }