178 lines
4.6 KiB
TypeScript
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;
|
|
}
|
|
}
|