diff --git a/cast/src/receiver/layout/hc-lovelace.ts b/cast/src/receiver/layout/hc-lovelace.ts index 010979e32d08..23b27d9ed991 100644 --- a/cast/src/receiver/layout/hc-lovelace.ts +++ b/cast/src/receiver/layout/hc-lovelace.ts @@ -1,10 +1,11 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; -import { customElement, property, query } from "lit/decorators"; +import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../../src/common/dom/fire_event"; import { LovelaceConfig } from "../../../../src/data/lovelace/config/types"; import { getPanelTitleFromUrlPath } from "../../../../src/data/panel"; import { Lovelace } from "../../../../src/panels/lovelace/types"; import "../../../../src/panels/lovelace/views/hui-view"; +import "../../../../src/panels/lovelace/views/hui-view-container"; import { HomeAssistant } from "../../../../src/types"; import "./hc-launch-screen"; @@ -22,8 +23,6 @@ class HcLovelace extends LitElement { @property() public urlPath: string | null = null; - @query("hui-view") private _huiView?: HTMLElement; - protected render(): TemplateResult { const index = this._viewIndex; if (index === undefined) { @@ -47,12 +46,22 @@ class HcLovelace extends LitElement { setEditMode: () => undefined, showToast: () => undefined, }; + + const viewConfig = this.lovelaceConfig.views[index]; + const background = viewConfig.background || this.lovelaceConfig.background; + return html` - + .background=${background} + .theme=${this.lovelaceConfig} + > + + `; } @@ -82,26 +91,6 @@ class HcLovelace extends LitElement { }${viewTitle || ""}` : undefined, }); - - const configBackground = - this.lovelaceConfig.views[index].background || - this.lovelaceConfig.background; - - const backgroundStyle = - typeof configBackground === "string" - ? configBackground - : configBackground?.image - ? `center / cover no-repeat url('${configBackground.image}')` - : undefined; - - if (backgroundStyle) { - this._huiView!.style.setProperty( - "--lovelace-background", - backgroundStyle - ); - } else { - this._huiView!.style.removeProperty("--lovelace-background"); - } } } } @@ -125,19 +114,16 @@ class HcLovelace extends LitElement { static get styles(): CSSResultGroup { return css` - :host { + hui-view-container { + position: relative; min-height: 100vh; height: 0; display: flex; flex-direction: column; box-sizing: border-box; - background: var(--primary-background-color); - } - :host > * { - flex: 1; } hui-view { - background: var(--lovelace-background, var(--primary-background-color)); + flex: 1; } `; } diff --git a/src/panels/energy/ha-panel-energy.ts b/src/panels/energy/ha-panel-energy.ts index c16b3842c113..3085574006b8 100644 --- a/src/panels/energy/ha-panel-energy.ts +++ b/src/panels/energy/ha-panel-energy.ts @@ -18,6 +18,7 @@ import { HomeAssistant } from "../../types"; import "../lovelace/components/hui-energy-period-selector"; import { Lovelace } from "../lovelace/types"; import "../lovelace/views/hui-view"; +import "../lovelace/views/hui-view-container"; import { navigate } from "../../common/navigate"; import { getEnergyDataCollection, @@ -108,14 +109,18 @@ class PanelEnergy extends LitElement { -
+ + -
+ `; } @@ -389,7 +394,7 @@ class PanelEnergy extends LitElement { line-height: 20px; flex-grow: 1; } - #view { + hui-view-container { position: relative; display: flex; padding-top: calc(var(--header-height) + env(safe-area-inset-top)); @@ -400,12 +405,8 @@ class PanelEnergy extends LitElement { padding-inline-start: env(safe-area-inset-left); padding-inline-end: env(safe-area-inset-right); padding-bottom: env(safe-area-inset-bottom); - background: var( - --lovelace-background, - var(--primary-background-color) - ); } - #view > * { + hui-view { flex: 1 1 100%; max-width: 100%; } diff --git a/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts b/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts index dadd34449ea2..ef0a34233356 100644 --- a/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts +++ b/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts @@ -167,7 +167,6 @@ export class HuiUnusedEntities extends LitElement { static get styles(): CSSResultGroup { return css` :host { - background: var(--lovelace-background); overflow: hidden; } .container { diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts index 939d063e8203..3202d6e7aa1b 100644 --- a/src/panels/lovelace/hui-root.ts +++ b/src/panels/lovelace/hui-root.ts @@ -62,6 +62,7 @@ import { fetchDashboards, updateDashboard, } from "../../data/lovelace/dashboard"; +import { getPanelTitle } from "../../data/panel"; import { showAlertDialog, showConfirmationDialog, @@ -80,8 +81,8 @@ import { getLovelaceStrategy } from "./strategies/get-strategy"; import { isLegacyStrategyConfig } from "./strategies/legacy-strategy"; import type { Lovelace } from "./types"; import "./views/hui-view"; +import "./views/hui-view-container"; import type { HUIView } from "./views/hui-view"; -import { getPanelTitle } from "../../data/panel"; @customElement("hui-root") class HUIRoot extends LitElement { @@ -291,6 +292,8 @@ class HUIRoot extends LitElement { ? getPanelTitle(this.hass, this.panel) : undefined; + const background = curViewConfig?.background || this.config.background; + return html`
-
+ +
`; } @@ -937,21 +947,6 @@ class HUIRoot extends LitElement { view.hass = this.hass; view.narrow = this.narrow; - const configBackground = viewConfig.background || this.config.background; - - const backgroundStyle = - typeof configBackground === "string" - ? configBackground - : configBackground?.image - ? `center / cover no-repeat url('${configBackground.image}')` - : undefined; - - if (backgroundStyle) { - root.style.setProperty("--lovelace-background", backgroundStyle); - } else { - root.style.removeProperty("--lovelace-background"); - } - root.appendChild(view); } @@ -1063,7 +1058,7 @@ class HUIRoot extends LitElement { mwc-button.warning:not([disabled]) { color: var(--error-color); } - #view { + hui-view-container { position: relative; display: flex; padding-top: calc(var(--header-height) + env(safe-area-inset-top)); @@ -1074,19 +1069,15 @@ class HUIRoot extends LitElement { padding-inline-start: env(safe-area-inset-left); padding-inline-end: env(safe-area-inset-right); padding-bottom: env(safe-area-inset-bottom); - background: var( - --lovelace-background, - var(--primary-background-color) - ); } - #view > * { + hui-view-container > * { flex: 1 1 100%; max-width: 100%; } /** * In edit mode we have the tab bar on a new line * */ - .edit-mode #view { + .edit-mode hui-view-container { padding-top: calc( var(--header-height) + 48px + env(safe-area-inset-top) ); diff --git a/src/panels/lovelace/views/hui-view-container.ts b/src/panels/lovelace/views/hui-view-container.ts new file mode 100644 index 000000000000..9947dcef9868 --- /dev/null +++ b/src/panels/lovelace/views/hui-view-container.ts @@ -0,0 +1,142 @@ +import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; +import { listenMediaQuery } from "../../../common/dom/media_query"; +import type { LovelaceViewConfig } from "../../../data/lovelace/config/view"; +import type { HomeAssistant } from "../../../types"; + +type BackgroundConfig = LovelaceViewConfig["background"]; + +@customElement("hui-view-container") +class HuiViewContainer extends LitElement { + @property({ attribute: false }) hass?: HomeAssistant; + + @property({ attribute: false }) background?: BackgroundConfig; + + @property({ attribute: false }) theme?: LovelaceViewConfig["theme"]; + + @state() themeBackground?: string; + + private _unsubMediaQuery?: () => void; + + public connectedCallback(): void { + super.connectedCallback(); + this._setUpMediaQuery(); + this._applyTheme(); + } + + public disconnectedCallback(): void { + super.disconnectedCallback(); + this._clearmediaQuery(); + } + + private _clearmediaQuery() { + if (this._unsubMediaQuery) { + this._unsubMediaQuery(); + this._unsubMediaQuery = undefined; + } + } + + private _setUpMediaQuery() { + this._unsubMediaQuery = listenMediaQuery( + "(prefers-color-scheme: dark)", + this._applyTheme.bind(this) + ); + } + + private _isFixedBackground(background?: BackgroundConfig) { + if (typeof background === "string") { + return background.includes(" fixed"); + } + return false; + } + + private _computeBackgroundProperty(background?: BackgroundConfig) { + if (typeof background === "object") { + return `center / cover no-repeat url('${background.image}')`; + } + if (typeof background === "string") { + return background; + } + return null; + } + + protected willUpdate(changedProperties: PropertyValues) { + super.willUpdate(changedProperties); + if (changedProperties.has("hass") && this.hass) { + const oldHass = changedProperties.get("hass"); + if ( + !oldHass || + this.hass.themes !== oldHass.themes || + this.hass.selectedTheme !== oldHass.selectedTheme + ) { + this._applyTheme(); + return; + } + } + + if (changedProperties.has("theme") || changedProperties.has("background")) { + this._applyTheme(); + } + } + + render() { + return html``; + } + + private _applyTheme() { + if (this.hass) { + applyThemesOnElement(this, this.hass?.themes, this.theme); + } + + const computedStyles = getComputedStyle(this); + const themeBackground = computedStyles.getPropertyValue( + "--lovelace-background" + ); + + const fixedBackground = this._isFixedBackground( + this.background || themeBackground + ); + const viewBackground = this._computeBackgroundProperty(this.background); + this.toggleAttribute("fixed-background", fixedBackground); + this.style.setProperty("--view-background", viewBackground); + } + + static get styles(): CSSResultGroup { + return css` + :host { + display: relative; + } + /* Fixed background hack for Safari iOS */ + :host([fixed-background]) ::slotted(*):before { + display: block; + content: ""; + z-index: -1; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + height: 100%; + width: 100%; + background: var( + --view-background, + var(--lovelace-background, var(--primary-background-color)) + ); + background-attachment: scroll !important; + } + :host(:not(fixed-background)) { + background: var( + --view-background, + var(--lovelace-background, var(--primary-background-color)) + ); + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-view-container": HuiViewContainer; + } +} diff --git a/src/panels/lovelace/views/hui-view.ts b/src/panels/lovelace/views/hui-view.ts index 397be95d51bd..12aa24eb8e60 100644 --- a/src/panels/lovelace/views/hui-view.ts +++ b/src/panels/lovelace/views/hui-view.ts @@ -1,6 +1,5 @@ import { PropertyValues, ReactiveElement } from "lit"; import { customElement, property, state } from "lit/decorators"; -import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { HASSDomEvent } from "../../../common/dom/fire_event"; import "../../../components/entity/ha-state-label-badge"; import "../../../components/ha-svg-icon"; @@ -84,8 +83,6 @@ export class HUIView extends ReactiveElement { private _layoutElement?: LovelaceViewElement; - private _viewConfigTheme?: string; - private _createCardElement(cardConfig: LovelaceCardConfig) { const element = document.createElement("hui-card"); element.hass = this.hass; @@ -138,11 +135,6 @@ export class HUIView extends ReactiveElement { return this; } - public connectedCallback(): void { - super.connectedCallback(); - this._applyTheme(); - } - public willUpdate(changedProperties: PropertyValues): void { super.willUpdate(changedProperties); @@ -194,18 +186,6 @@ export class HUIView extends ReactiveElement { }); this._layoutElement.hass = this.hass; - - const oldHass = changedProperties.get("hass") as - | this["hass"] - | undefined; - - if ( - !oldHass || - this.hass.themes !== oldHass.themes || - this.hass.selectedTheme !== oldHass.selectedTheme - ) { - this._applyTheme(); - } } if (changedProperties.has("narrow")) { this._layoutElement.narrow = this.narrow; @@ -237,28 +217,6 @@ export class HUIView extends ReactiveElement { } } - private _applyTheme() { - applyThemesOnElement(this, this.hass.themes, this._viewConfigTheme); - if (this._viewConfigTheme) { - // Set lovelace background color to root element, so it will be placed under the header too - const computedStyles = getComputedStyle(this); - let lovelaceBackground = computedStyles.getPropertyValue( - "--lovelace-background" - ); - if (!lovelaceBackground) { - lovelaceBackground = computedStyles.getPropertyValue( - "--primary-background-color" - ); - } - if (lovelaceBackground) { - this.parentElement?.style.setProperty( - "--lovelace-background", - lovelaceBackground - ); - } - } - } - private async _initializeConfig() { let viewConfig = this.lovelace.config.views[this.index]; let isStrategy = false; @@ -296,9 +254,6 @@ export class HUIView extends ReactiveElement { this._layoutElement!.badges = this._badges; this._layoutElement!.sections = this._sections; - applyThemesOnElement(this, this.hass.themes, viewConfig.theme); - this._viewConfigTheme = viewConfig.theme; - if (addLayoutElement) { while (this.lastChild) { this.removeChild(this.lastChild);