ha-frontend/src/panels/lovelace/components/hui-conditional-base.ts

178 lines
4.6 KiB
TypeScript

import { PropertyValues, ReactiveElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { listenMediaQuery } from "../../../common/dom/media_query";
import { deepEqual } from "../../../common/util/deep-equal";
import { HomeAssistant } from "../../../types";
import { ConditionalCardConfig } from "../cards/types";
import {
Condition,
LegacyCondition,
checkConditionsMet,
validateConditionalConfig,
} from "../common/validate-condition";
import { ConditionalRowConfig, LovelaceRow } from "../entity-rows/types";
import { LovelaceCard, LovelaceCardFeature } from "../types";
import { ConditionalCardFeatureConfig } from "../card-features/types";
function extractMediaQueries(
conditions: (Condition | LegacyCondition)[]
): string[] {
return conditions.reduce<string[]>((array, c) => {
if ("conditions" in c && c.conditions) {
array.push(...extractMediaQueries(c.conditions));
}
if ("condition" in c && c.condition === "screen" && c.media_query) {
array.push(c.media_query);
}
return array;
}, []);
}
@customElement("hui-conditional-base")
export class HuiConditionalBase extends ReactiveElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public editMode = false;
@state() protected _config?:
| ConditionalCardConfig
| ConditionalRowConfig
| ConditionalCardFeatureConfig;
protected _element?: LovelaceCard | LovelaceRow | LovelaceCardFeature;
private _mediaQueriesListeners: Array<() => void> = [];
private _mediaQueries: string[] = [];
protected createRenderRoot() {
return this;
}
protected validateConfig(
config:
| ConditionalCardConfig
| ConditionalRowConfig
| ConditionalCardFeatureConfig
): void {
if (!config.conditions) {
throw new Error("No conditions configured");
}
if (!Array.isArray(config.conditions)) {
throw new Error("Conditions need to be an array");
}
if (!validateConditionalConfig(config.conditions)) {
throw new Error("Conditions are invalid");
}
if (this.lastChild) {
this.removeChild(this.lastChild);
}
this._config = config;
}
public disconnectedCallback() {
super.disconnectedCallback();
this._clearMediaQueries();
}
public connectedCallback() {
super.connectedCallback();
this._listenMediaQueries();
this._updateVisibility();
}
private _clearMediaQueries() {
this._mediaQueries = [];
while (this._mediaQueriesListeners.length) {
this._mediaQueriesListeners.pop()!();
}
}
private _listenMediaQueries() {
if (!this._config) {
return;
}
const mediaQueries = extractMediaQueries(this._config.conditions);
if (deepEqual(mediaQueries, this._mediaQueries)) return;
this._mediaQueries = mediaQueries;
while (this._mediaQueriesListeners.length) {
this._mediaQueriesListeners.pop()!();
}
mediaQueries.forEach((query) => {
const listener = listenMediaQuery(query, (matches) => {
// For performance, if there is only one condition and it's a screen condition, set the visibility directly
if (
this._config!.conditions.length === 1 &&
"condition" in this._config!.conditions[0] &&
this._config!.conditions[0].condition === "screen"
) {
this._setVisibility(matches);
return;
}
this._updateVisibility();
});
this._mediaQueriesListeners.push(listener);
});
}
protected update(changed: PropertyValues): void {
super.update(changed);
if (
changed.has("_element") ||
changed.has("_config") ||
changed.has("hass")
) {
this._listenMediaQueries();
this._updateVisibility();
}
}
private _updateVisibility() {
if (!this._element || !this.hass || !this._config) {
return;
}
this._element.editMode = this.editMode;
const conditionMet = checkConditionsMet(
this._config!.conditions,
this.hass!
);
this._setVisibility(conditionMet);
}
private _setVisibility(conditionMet: boolean) {
if (!this._element || !this.hass) {
return;
}
const visible = this.editMode || conditionMet;
this.toggleAttribute("hidden", !visible);
this.style.setProperty("display", visible ? "" : "none");
if (visible) {
this._element.hass = this.hass;
if (!this._element!.parentElement) {
this.appendChild(this._element!);
}
} else if (this._element.parentElement) {
this.removeChild(this._element!);
}
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-conditional-base": HuiConditionalBase;
}
}