diff --git a/cast/src/launcher/layout/hc-cast.ts b/cast/src/launcher/layout/hc-cast.ts
index 2aae14bb98a5..b8c2b9bf7ec4 100644
--- a/cast/src/launcher/layout/hc-cast.ts
+++ b/cast/src/launcher/layout/hc-cast.ts
@@ -1,5 +1,6 @@
import "@material/mwc-button/mwc-button";
-import { ActionDetail } from "@material/mwc-list/mwc-list";
+import "@material/mwc-list/mwc-list";
+import type { ActionDetail } from "@material/mwc-list/mwc-list";
import { mdiCast, mdiCastConnected, mdiViewDashboard } from "@mdi/js";
import { Auth, Connection } from "home-assistant-js-websocket";
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
@@ -89,8 +90,8 @@ class HcCast extends LitElement {
generateDefaultViewConfig({}, {}, {}, {}, () => ""),
]
).map(
- (view, idx) =>
- html` html`
+ `} `
+ >`}
+
+ `
)}
`}
diff --git a/cast/src/receiver/layout/hc-lovelace.ts b/cast/src/receiver/layout/hc-lovelace.ts
index 010979e32d08..6df50c6109bb 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=${viewConfig.theme}
+ >
+
+
`;
}
@@ -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,15 @@ class HcLovelace extends LitElement {
static get styles(): CSSResultGroup {
return css`
- :host {
- min-height: 100vh;
- height: 0;
+ hui-view-container {
display: flex;
- flex-direction: column;
+ position: relative;
+ min-height: 100vh;
box-sizing: border-box;
- background: var(--primary-background-color);
- }
- :host > * {
- flex: 1;
}
hui-view {
- background: var(--lovelace-background, var(--primary-background-color));
+ flex: 1 1 100%;
+ max-width: 100%;
}
`;
}
diff --git a/src/panels/energy/ha-panel-energy.ts b/src/panels/energy/ha-panel-energy.ts
index c16b3842c113..200e3e576ed7 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,23 +394,19 @@ 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));
min-height: 100vh;
box-sizing: border-box;
+ padding-top: calc(var(--header-height) + env(safe-area-inset-top));
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
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..3c749fe1a87f 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,30 +1058,26 @@ 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));
min-height: 100vh;
box-sizing: border-box;
+ padding-top: calc(var(--header-height) + env(safe-area-inset-top));
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
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..73da5a04c1f4
--- /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" && background.image) {
+ 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);