1
0
mirror of https://github.com/home-assistant/frontend.git synced 2026-04-02 00:27:49 +01:00

Allow specific entity controls in Area card (#29025)

Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
This commit is contained in:
Petar Petrov
2026-01-19 14:44:10 +01:00
committed by GitHub
parent aaad8e5434
commit e0fc661920
5 changed files with 557 additions and 106 deletions

View File

@@ -5,6 +5,7 @@ import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one";
import { ensureArray } from "../../../common/array/ensure-array";
import { computeDomain } from "../../../common/entity/compute_domain";
import { generateEntityFilter } from "../../../common/entity/entity_filter";
import {
computeGroupEntitiesState,
@@ -15,6 +16,7 @@ import { domainColorProperties } from "../../../common/entity/state_color";
import "../../../components/ha-control-button";
import "../../../components/ha-control-button-group";
import "../../../components/ha-domain-icon";
import "../../../components/ha-state-icon";
import "../../../components/ha-svg-icon";
import type { AreaRegistryEntry } from "../../../data/area/area_registry";
import { forwardHaptic } from "../../../data/haptics";
@@ -23,13 +25,14 @@ import type { HomeAssistant } from "../../../types";
import type { AreaCardFeatureContext } from "../cards/hui-area-card";
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
AreaControl,
AreaControlsCardFeatureConfig,
LovelaceCardFeatureContext,
LovelaceCardFeaturePosition,
import {
AREA_CONTROL_DOMAINS,
type AreaControl,
type AreaControlDomain,
type AreaControlsCardFeatureConfig,
type LovelaceCardFeatureContext,
type LovelaceCardFeaturePosition,
} from "./types";
import { AREA_CONTROLS } from "./types";
interface AreaControlsButton {
filter: {
@@ -38,6 +41,14 @@ interface AreaControlsButton {
};
}
type NormalizedControl =
| { type: "domain"; value: AreaControlDomain }
| { type: "entity"; value: string };
interface ControlButtonElement extends HTMLElement {
control: NormalizedControl;
}
const coverButton = (deviceClass: string) => ({
filter: {
domain: "cover",
@@ -45,7 +56,10 @@ const coverButton = (deviceClass: string) => ({
},
});
export const AREA_CONTROLS_BUTTONS: Record<AreaControl, AreaControlsButton> = {
export const AREA_CONTROLS_BUTTONS: Record<
AreaControlDomain,
AreaControlsButton
> = {
light: {
filter: {
domain: "light",
@@ -82,11 +96,11 @@ export const supportsAreaControlsCardFeature = (
};
export const getAreaControlEntities = (
controls: AreaControl[],
controls: AreaControlDomain[],
areaId: string,
excludeEntities: string[] | undefined,
hass: HomeAssistant
): Record<AreaControl, string[]> =>
): Record<AreaControlDomain, string[]> =>
controls.reduce(
(acc, control) => {
const controlButton = AREA_CONTROLS_BUTTONS[control];
@@ -101,7 +115,7 @@ export const getAreaControlEntities = (
);
return acc;
},
{} as Record<AreaControl, string[]>
{} as Record<AreaControlDomain, string[]>
);
export const MAX_DEFAULT_AREA_CONTROLS = 4;
@@ -129,10 +143,23 @@ class HuiAreaControlsCardFeature
| undefined;
}
private get _controls() {
return (
this._config?.controls || (AREA_CONTROLS as unknown as AreaControl[])
);
private get _controls(): AreaControl[] {
return this._config?.controls || [...AREA_CONTROL_DOMAINS];
}
private _normalizeControl(control: AreaControl): NormalizedControl {
// Handle explicit entity format
if (typeof control === "object" && "entity_id" in control) {
return { type: "entity", value: control.entity_id };
}
// String format: domain control (if valid) or invalid
if (AREA_CONTROL_DOMAINS.includes(control as AreaControlDomain)) {
return { type: "domain", value: control as AreaControlDomain };
}
// Invalid domain string - treat as entity
return { type: "entity", value: control };
}
static getStubConfig(): AreaControlsCardFeatureConfig {
@@ -156,22 +183,37 @@ class HuiAreaControlsCardFeature
private _handleButtonTap(ev: MouseEvent) {
ev.stopPropagation();
if (!this.context?.area_id || !this.hass || !this._config) {
if (!this.hass || !this._config) {
return;
}
const control = (ev.currentTarget as any).control as AreaControl;
const normalized = (ev.currentTarget as ControlButtonElement).control;
if (normalized.type === "entity") {
const entity = this.hass.states[normalized.value];
if (entity) {
forwardHaptic(this, "light");
toggleGroupEntities(this.hass, [entity]);
}
return;
}
if (!this.context?.area_id) {
return;
}
const domainControls = this._domainControls(this._controls);
const controlEntities = this._controlEntities(
this._controls,
domainControls,
this.context.area_id,
this.context.exclude_entities,
this.hass!.entities,
this.hass!.devices,
this.hass!.areas
this.hass.entities,
this.hass.devices,
this.hass.areas
);
const entitiesIds = controlEntities[control];
const entities = entitiesIds
const entities = controlEntities[normalized.value]
.map((entityId) => this.hass!.states[entityId] as HassEntity | undefined)
.filter((v): v is HassEntity => Boolean(v));
@@ -179,9 +221,19 @@ class HuiAreaControlsCardFeature
toggleGroupEntities(this.hass, entities);
}
private _domainControls = memoizeOne((controls: AreaControl[]) =>
controls
.map((c) => this._normalizeControl(c))
.filter(
(n): n is { type: "domain"; value: AreaControlDomain } =>
n.type === "domain"
)
.map((n) => n.value)
);
private _controlEntities = memoizeOne(
(
controls: AreaControl[],
controls: AreaControlDomain[],
areaId: string,
excludeEntities: string[] | undefined,
// needed to update memoized function when entities, devices or areas change
@@ -202,8 +254,15 @@ class HuiAreaControlsCardFeature
return nothing;
}
const normalizedControls = this._controls.map((c) =>
this._normalizeControl(c)
);
// Get domain controls for entity lookup
const domainControls = this._domainControls(this._controls);
const controlEntities = this._controlEntities(
this._controls,
domainControls,
this.context.area_id!,
this.context.exclude_entities,
this.hass!.entities,
@@ -211,13 +270,17 @@ class HuiAreaControlsCardFeature
this.hass!.areas
);
const supportedControls = this._controls.filter(
(control) => controlEntities[control].length > 0
// Filter controls while preserving original order
const allControls = normalizedControls.filter((n) =>
n.type === "domain"
? controlEntities[n.value].length > 0
: this.hass!.states[n.value] &&
!this.context?.exclude_entities?.includes(n.value)
);
const displayControls = this._config.controls
? supportedControls
: supportedControls.slice(0, MAX_DEFAULT_AREA_CONTROLS); // Limit to max if using default controls
? allControls
: allControls.slice(0, MAX_DEFAULT_AREA_CONTROLS); // Limit to max if using default controls
if (!displayControls.length) {
return nothing;
@@ -229,35 +292,54 @@ class HuiAreaControlsCardFeature
"no-stretch": this.position === "inline",
})}
>
${displayControls.map((control) => {
const button = AREA_CONTROLS_BUTTONS[control];
${displayControls.map((normalized) => {
let active: boolean;
let label: string;
let domain: string;
let deviceClass: string | undefined;
let entityState: string;
let entity: HassEntity | undefined;
const entityIds = controlEntities[control];
if (normalized.type === "domain") {
const button = AREA_CONTROLS_BUTTONS[normalized.value];
const controlEntityIds = controlEntities[normalized.value];
const entities = entityIds
.map(
(entityId) =>
this.hass!.states[entityId] as HassEntity | undefined
)
.filter((v): v is HassEntity => Boolean(v));
const entities = controlEntityIds
.map(
(entityId) =>
this.hass!.states[entityId] as HassEntity | undefined
)
.filter((v): v is HassEntity => Boolean(v));
const groupState = computeGroupEntitiesState(entities);
const groupState = computeGroupEntitiesState(entities);
const active = entities[0]
? stateActive(entities[0], groupState)
: false;
active = entities[0] ? stateActive(entities[0], groupState) : false;
label = this.hass!.localize(
`ui.card_features.area_controls.${normalized.value}.${active ? "off" : "on"}`
);
domain = button.filter.domain;
deviceClass = button.filter.device_class
? ensureArray(button.filter.device_class)[0]
: undefined;
entityState = groupState;
} else if (normalized.type === "entity") {
entity = this.hass!.states[normalized.value];
if (!entity) {
return nothing;
}
const label = this.hass!.localize(
`ui.card_features.area_controls.${control}.${active ? "off" : "on"}`
);
const domain = button.filter.domain;
const deviceClass = button.filter.device_class
? ensureArray(button.filter.device_class)[0]
: undefined;
active = stateActive(entity);
label = this.hass!.localize(
`ui.card.common.turn_${active ? "off" : "on"}`
);
domain = computeDomain(entity.entity_id);
entityState = entity.state;
} else {
return nothing;
}
const activeColor = computeCssVariable(
domainColorProperties(domain, deviceClass, groupState, true)
domainColorProperties(domain, deviceClass, entityState, true)
);
return html`
@@ -268,15 +350,20 @@ class HuiAreaControlsCardFeature
.title=${label}
aria-label=${label}
class=${active ? "active" : ""}
.control=${control}
.control=${normalized}
@click=${this._handleButtonTap}
>
<ha-domain-icon
.hass=${this.hass}
.domain=${domain}
.deviceClass=${deviceClass}
.state=${groupState}
></ha-domain-icon>
${normalized.type === "domain"
? html`<ha-domain-icon
.hass=${this.hass}
.domain=${domain}
.deviceClass=${deviceClass}
.state=${entityState}
></ha-domain-icon>`
: html`<ha-state-icon
.hass=${this.hass}
.stateObj=${entity}
></ha-state-icon>`}
</ha-control-button>
`;
})}

View File

@@ -202,7 +202,7 @@ export interface TrendGraphCardFeatureConfig {
detail?: boolean;
}
export const AREA_CONTROLS = [
export const AREA_CONTROL_DOMAINS = [
"light",
"fan",
"cover-shutter",
@@ -218,7 +218,9 @@ export const AREA_CONTROLS = [
"switch",
] as const;
export type AreaControl = (typeof AREA_CONTROLS)[number];
export type AreaControlDomain = (typeof AREA_CONTROL_DOMAINS)[number];
export type AreaControl = AreaControlDomain | { entity_id: string };
export interface AreaControlsCardFeatureConfig {
type: "area-controls";

View File

@@ -1,26 +1,54 @@
import { html, LitElement, nothing } from "lit";
import type { HassEntity } from "home-assistant-js-websocket";
import { mdiDragHorizontalVariant } from "@mdi/js";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { repeat } from "lit/directives/repeat";
import Fuse from "fuse.js";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeEntityNameList } from "../../../../common/entity/compute_entity_name_display";
import { computeStateName } from "../../../../common/entity/compute_state_name";
import { computeRTL } from "../../../../common/util/compute_rtl";
import type { LocalizeFunc } from "../../../../common/translations/localize";
import {
multiTermSortedSearch,
type FuseWeightedKey,
} from "../../../../resources/fuseMultiTerm";
import "../../../../components/ha-combo-box-item";
import "../../../../components/ha-domain-icon";
import "../../../../components/ha-form/ha-form";
import type {
HaFormSchema,
SchemaUnion,
} from "../../../../components/ha-form/types";
import "../../../../components/ha-generic-picker";
import type { PickerComboBoxItem } from "../../../../components/ha-picker-combo-box";
import "../../../../components/chips/ha-input-chip";
import "../../../../components/ha-sortable";
import "../../../../components/ha-state-icon";
import "../../../../components/ha-svg-icon";
import type { HomeAssistant } from "../../../../types";
import {
AREA_CONTROLS_BUTTONS,
getAreaControlEntities,
MAX_DEFAULT_AREA_CONTROLS,
} from "../../card-features/hui-area-controls-card-feature";
import {
AREA_CONTROLS,
AREA_CONTROL_DOMAINS,
type AreaControl,
type AreaControlDomain,
type AreaControlsCardFeatureConfig,
} from "../../card-features/types";
import type { AreaCardFeatureContext } from "../../cards/hui-area-card";
import type { LovelaceCardFeatureEditor } from "../../types";
interface AreaControlPickerItem extends PickerComboBoxItem {
type?: "domain" | "entity";
stateObj?: HassEntity;
domain?: string;
deviceClass?: string;
}
type AreaControlsCardFeatureData = AreaControlsCardFeatureConfig & {
customize_controls: boolean;
};
@@ -40,40 +68,14 @@ export class HuiAreaControlsCardFeatureEditor
this._config = config;
}
private _schema = memoizeOne(
(
localize: LocalizeFunc,
customizeControls: boolean,
compatibleControls: AreaControl[]
) =>
[
{
name: "customize_controls",
selector: {
boolean: {},
},
},
...(customizeControls
? ([
{
name: "controls",
selector: {
select: {
reorder: true,
multiple: true,
options: compatibleControls.map((control) => ({
value: control,
label: localize(
`ui.panel.lovelace.editor.features.types.area-controls.controls_options.${control}`
),
})),
},
},
},
] as const satisfies readonly HaFormSchema[])
: []),
] as const satisfies readonly HaFormSchema[]
);
private _schema = [
{
name: "customize_controls",
selector: {
boolean: {},
},
},
] as const satisfies readonly HaFormSchema[];
private _supportedControls = memoizeOne(
(
@@ -88,7 +90,7 @@ export class HuiAreaControlsCardFeatureEditor
return [];
}
const controlEntities = getAreaControlEntities(
AREA_CONTROLS as unknown as AreaControl[],
AREA_CONTROL_DOMAINS as unknown as AreaControlDomain[],
areaId,
excludeEntities,
this.hass!
@@ -99,6 +101,191 @@ export class HuiAreaControlsCardFeatureEditor
}
);
private _domainSearchKeys: FuseWeightedKey[] = [
{
name: "primary",
weight: 10,
},
];
private _entitySearchKeys: FuseWeightedKey[] = [
{
name: "primary",
weight: 10,
},
{
name: "secondary",
weight: 5,
},
{
name: "id",
weight: 3,
},
];
private _createFuseIndex = (
items: AreaControlPickerItem[],
keys: FuseWeightedKey[]
) => Fuse.createIndex(keys, items);
private _domainFuseIndex = memoizeOne((items: AreaControlPickerItem[]) =>
this._createFuseIndex(items, this._domainSearchKeys)
);
private _entityFuseIndex = memoizeOne((items: AreaControlPickerItem[]) =>
this._createFuseIndex(items, this._entitySearchKeys)
);
private _getItems = memoizeOne(
(
areaId: string,
excludeEntities: string[] | undefined,
currentValue: AreaControl[],
localize: LocalizeFunc,
_entities: HomeAssistant["entities"],
_devices: HomeAssistant["devices"],
_areas: HomeAssistant["areas"]
): ((
searchString?: string,
section?: string
) => (AreaControlPickerItem | string)[]) =>
(searchString?: string, section?: string) => {
if (!this.hass) {
return [];
}
const isSelected = (id: string): boolean =>
currentValue.some((item) =>
typeof item === "string" ? item === id : item.entity_id === id
);
const controlEntities = getAreaControlEntities(
AREA_CONTROL_DOMAINS as unknown as AreaControlDomain[],
areaId,
excludeEntities,
this.hass
);
const items: (AreaControlPickerItem | string)[] = [];
let domainItems: AreaControlPickerItem[] = [];
let entityItems: AreaControlPickerItem[] = [];
if (!section || section === "domain") {
const supportedControls = (
Object.keys(controlEntities) as (keyof typeof controlEntities)[]
).filter((control) => controlEntities[control].length > 0);
supportedControls.forEach((control) => {
if (isSelected(control)) {
return;
}
const label = localize(
`ui.panel.lovelace.editor.features.types.area-controls.controls_options.${control}`
);
const button = AREA_CONTROLS_BUTTONS[control];
const deviceClass = button.filter.device_class
? Array.isArray(button.filter.device_class)
? button.filter.device_class[0]
: button.filter.device_class
: undefined;
domainItems.push({
type: "domain",
id: control,
primary: label,
domain: button.filter.domain,
deviceClass,
});
});
if (searchString) {
const fuseIndex = this._domainFuseIndex(domainItems);
domainItems = multiTermSortedSearch(
domainItems,
searchString,
this._domainSearchKeys,
(item) => item.id,
fuseIndex
);
}
}
if (!section || section === "entity") {
const allEntityIds = Object.values(controlEntities).flat();
const uniqueEntityIds = Array.from(new Set(allEntityIds));
const isRTL = computeRTL(this.hass);
uniqueEntityIds.forEach((entityId) => {
if (isSelected(entityId)) {
return;
}
const stateObj = this.hass!.states[entityId];
if (!stateObj) {
return;
}
const [entityName, deviceName, areaName] = computeEntityNameList(
stateObj,
[{ type: "entity" }, { type: "device" }, { type: "area" }],
this.hass!.entities,
this.hass!.devices,
this.hass!.areas,
this.hass!.floors
);
const primary = entityName || deviceName || entityId;
const secondary = [areaName, entityName ? deviceName : undefined]
.filter(Boolean)
.join(isRTL ? " ◂ " : " ▸ ");
entityItems.push({
type: "entity",
id: entityId,
primary,
secondary,
stateObj,
});
});
if (searchString) {
const fuseIndex = this._entityFuseIndex(entityItems);
entityItems = multiTermSortedSearch(
entityItems,
searchString,
this._entitySearchKeys,
(item) => item.id,
fuseIndex
);
}
}
// Only add section headers if there are items in that section
if (!section) {
if (domainItems.length > 0) {
items.push(
localize(
"ui.panel.lovelace.editor.features.types.area-controls.sections.domain"
)
);
items.push(...domainItems);
}
if (entityItems.length > 0) {
items.push(
localize(
"ui.panel.lovelace.editor.features.types.area-controls.sections.entity"
)
);
items.push(...entityItems);
}
} else {
items.push(...domainItems, ...entityItems);
}
return items;
}
);
protected render() {
if (!this.hass || !this._config || !this.context?.area_id) {
return nothing;
@@ -127,23 +314,179 @@ export class HuiAreaControlsCardFeatureEditor
customize_controls: this._config.controls !== undefined,
};
const schema = this._schema(
this.hass.localize,
data.customize_controls,
supportedControls
);
const value = this._config.controls || [];
return html`
<ha-form
.hass=${this.hass}
.data=${data}
.schema=${schema}
.schema=${this._schema}
.computeLabel=${this._computeLabelCallback}
@value-changed=${this._valueChanged}
></ha-form>
${data.customize_controls
? html`
${value.length
? html`
<ha-sortable
no-style
@item-moved=${this._itemMoved}
handle-selector="button.primary.action"
>
<ha-chip-set>
${repeat(
value,
(item) =>
typeof item === "string" ? item : item.entity_id,
(item, idx) => {
const label = this._getItemLabel(item);
return html`
<ha-input-chip
.idx=${idx}
@remove=${this._removeItem}
.label=${label}
selected
>
<ha-svg-icon
slot="icon"
.path=${mdiDragHorizontalVariant}
></ha-svg-icon>
${label}
</ha-input-chip>
`;
}
)}
</ha-chip-set>
</ha-sortable>
`
: nothing}
<ha-generic-picker
.hass=${this.hass}
.value=${""}
.addButtonLabel=${this.hass.localize(
"ui.panel.lovelace.editor.features.types.area-controls.controls"
)}
.getItems=${this._getItems(
this.context.area_id,
this.context.exclude_entities,
value,
this.hass.localize,
this.hass.entities,
this.hass.devices,
this.hass.areas
)}
.rowRenderer=${this._rowRenderer as any}
.sections=${[
{
id: "domain",
label: this.hass.localize(
"ui.panel.lovelace.editor.features.types.area-controls.sections.domain"
),
},
{
id: "entity",
label: this.hass.localize(
"ui.panel.lovelace.editor.features.types.area-controls.sections.entity"
),
},
]}
@value-changed=${this._controlChanged}
></ha-generic-picker>
`
: nothing}
`;
}
private _rowRenderer = (item: AreaControlPickerItem) => html`
<ha-combo-box-item type="button" compact>
${item.type === "entity" && item.stateObj
? html`<ha-state-icon
slot="start"
.hass=${this.hass}
.stateObj=${item.stateObj}
></ha-state-icon>`
: item.domain
? html`<ha-domain-icon
slot="start"
.hass=${this.hass}
.domain=${item.domain}
.deviceClass=${item.deviceClass}
></ha-domain-icon>`
: nothing}
<span slot="headline">${item.primary}</span>
${item.secondary
? html`<span slot="supporting-text">${item.secondary}</span>`
: nothing}
${item.type === "entity" && item.stateObj
? html`<span slot="supporting-text" class="code">
${item.stateObj.entity_id}
</span>`
: nothing}
</ha-combo-box-item>
`;
private _getItemLabel(item: AreaControl): string {
if (!this.hass) {
return typeof item === "string" ? item : JSON.stringify(item);
}
if (typeof item === "string") {
if (AREA_CONTROL_DOMAINS.includes(item as AreaControlDomain)) {
return this.hass.localize(
`ui.panel.lovelace.editor.features.types.area-controls.controls_options.${item}`
);
}
// Invalid/unknown domain string
return item;
}
if ("entity_id" in item) {
const entityState = this.hass.states[item.entity_id];
if (entityState) {
return computeStateName(entityState);
}
return item.entity_id;
}
return JSON.stringify(item);
}
private _itemMoved(ev: CustomEvent): void {
ev.stopPropagation();
const { oldIndex, newIndex } = ev.detail;
const controls = [...(this._config!.controls || [])];
const item = controls.splice(oldIndex, 1)[0];
controls.splice(newIndex, 0, item);
this._updateControls(controls);
}
private _removeItem(ev: CustomEvent): void {
const index = (ev.currentTarget as any).idx;
const controls = [...(this._config!.controls || [])];
controls.splice(index, 1);
this._updateControls(controls);
}
private _controlChanged(ev: CustomEvent): void {
ev.stopPropagation();
const value = ev.detail.value;
if (!value) {
return;
}
// If it's a domain control (in AREA_CONTROL_DOMAINS), save as string for backwards compatibility
// If it's an entity, save in explicit format
const control = AREA_CONTROL_DOMAINS.includes(value as AreaControlDomain)
? value
: { entity_id: value };
const controls = [...(this._config!.controls || []), control];
this._updateControls(controls);
}
private _updateControls(controls: AreaControl[]): void {
const config = { ...this._config!, controls };
fireEvent(this, "config-changed", { config });
}
private _valueChanged(ev: CustomEvent): void {
const { customize_controls, ...config } = ev.detail
.value as AreaControlsCardFeatureData;
@@ -166,10 +509,9 @@ export class HuiAreaControlsCardFeatureEditor
}
private _computeLabelCallback = (
schema: SchemaUnion<ReturnType<typeof this._schema>>
schema: SchemaUnion<typeof this._schema>
) => {
switch (schema.name) {
case "controls":
case "customize_controls":
return this.hass!.localize(
`ui.panel.lovelace.editor.features.types.area-controls.${schema.name}`
@@ -178,6 +520,19 @@ export class HuiAreaControlsCardFeatureEditor
return "";
}
};
static styles = css`
ha-sortable {
display: block;
margin-bottom: var(--ha-space-2);
}
ha-chip-set {
margin-bottom: var(--ha-space-2);
}
.code {
font-family: var(--ha-font-family-code);
}
`;
}
declare global {

View File

@@ -5,7 +5,10 @@ import type { LovelaceSectionConfig } from "../../../../data/lovelace/config/sec
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
import type { HomeAssistant } from "../../../../types";
import { getAreaControlEntities } from "../../card-features/hui-area-controls-card-feature";
import { AREA_CONTROLS, type AreaControl } from "../../card-features/types";
import {
AREA_CONTROL_DOMAINS,
type AreaControlDomain,
} from "../../card-features/types";
import type { AreaCardConfig, HeadingCardConfig } from "../../cards/types";
import type { EntitiesDisplay } from "./area-view-strategy";
import {
@@ -76,7 +79,7 @@ export class AreasOverviewViewStrategy extends ReactiveElement {
.map((display) => display.hidden || [])
.flat();
const controls: AreaControl[] = AREA_CONTROLS.filter(
const controls: AreaControlDomain[] = AREA_CONTROL_DOMAINS.filter(
(a) => a !== "switch" // Exclude switches control for areas as we don't know what the switches control
);
const controlEntities = getAreaControlEntities(

View File

@@ -8976,6 +8976,10 @@
"label": "Area controls",
"customize_controls": "Customize controls",
"controls": "Controls",
"sections": {
"domain": "Domains",
"entity": "Entities"
},
"controls_options": {
"light": "Lights",
"fan": "Fans",