diff --git a/src/components/ha-dialog.ts b/src/components/ha-dialog.ts
index e326e6b19b..f36d93476b 100644
--- a/src/components/ha-dialog.ts
+++ b/src/components/ha-dialog.ts
@@ -139,6 +139,7 @@ export class HaDialog extends DialogBase {
:host([flexContent]) .mdc-dialog .mdc-dialog__content {
display: flex;
flex-direction: column;
+ scrollbar-color: var(--scrollbar-thumb-color) transparent;
}
.header_title {
display: flex;
diff --git a/src/data/lovelace.ts b/src/data/lovelace.ts
index 98ed39e912..e64430832b 100644
--- a/src/data/lovelace.ts
+++ b/src/data/lovelace.ts
@@ -3,7 +3,7 @@ import { getCollection } from "home-assistant-js-websocket";
import type { HuiBadge } from "../panels/lovelace/badges/hui-badge";
import type { HuiCard } from "../panels/lovelace/cards/hui-card";
import type { HuiSection } from "../panels/lovelace/sections/hui-section";
-import type { Lovelace } from "../panels/lovelace/types";
+import type { Lovelace, LovelaceDialogSize } from "../panels/lovelace/types";
import type { HomeAssistant } from "../types";
import type { LovelaceSectionConfig } from "./lovelace/config/section";
import type { LegacyLovelaceConfig } from "./lovelace/config/types";
@@ -24,6 +24,7 @@ export interface LovelaceViewElement extends HTMLElement {
sections?: HuiSection[];
isStrategy: boolean;
setConfig(config: LovelaceViewConfig): void;
+ getDialogSize?: () => LovelaceDialogSize;
}
export interface LovelaceSectionElement extends HTMLElement {
diff --git a/src/data/lovelace/config/action.ts b/src/data/lovelace/config/action.ts
index 6aa0a8ee88..0a836299df 100644
--- a/src/data/lovelace/config/action.ts
+++ b/src/data/lovelace/config/action.ts
@@ -45,6 +45,12 @@ export interface CustomActionConfig extends BaseActionConfig {
action: "fire-dom-event";
}
+export interface OpenDialogActionConfig extends BaseActionConfig {
+ action: "open-dialog";
+ dashboard_path?: string;
+ view_path: string;
+}
+
export interface BaseActionConfig {
action: string;
confirmation?: ConfirmationRestrictionConfig;
@@ -60,6 +66,7 @@ export interface RestrictionConfig {
}
export type ActionConfig =
+ | OpenDialogActionConfig
| ToggleActionConfig
| CallServiceActionConfig
| NavigateActionConfig
diff --git a/src/panels/lovelace/common/handle-action.ts b/src/panels/lovelace/common/handle-action.ts
index 7434f75f5d..e5c3b61f87 100644
--- a/src/panels/lovelace/common/handle-action.ts
+++ b/src/panels/lovelace/common/handle-action.ts
@@ -7,6 +7,7 @@ import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box
import { showVoiceCommandDialog } from "../../../dialogs/voice-command-dialog/show-ha-voice-command-dialog";
import type { HomeAssistant } from "../../../types";
import { showToast } from "../../../util/toast";
+import { showViewPopupDialog } from "../views/view-popup/show-view-popup-dialog";
import { toggleEntity } from "./entity/toggle-entity";
declare global {
@@ -125,6 +126,19 @@ export const handleAction = async (
forwardHaptic("failure");
}
break;
+ case "open-dialog":
+ if (actionConfig.view_path) {
+ showViewPopupDialog(node, {
+ dashboard_path: actionConfig.dashboard_path,
+ view_path: actionConfig.view_path,
+ });
+ } else {
+ showToast(node, {
+ message: "No dashboard path and view path provided",
+ });
+ forwardHaptic("failure");
+ }
+ break;
case "url": {
if (actionConfig.url_path) {
window.open(actionConfig.url_path);
diff --git a/src/panels/lovelace/components/hui-action-editor.ts b/src/panels/lovelace/components/hui-action-editor.ts
index cd1f466840..e038e49301 100644
--- a/src/panels/lovelace/components/hui-action-editor.ts
+++ b/src/panels/lovelace/components/hui-action-editor.ts
@@ -16,6 +16,7 @@ import type {
ActionConfig,
CallServiceActionConfig,
NavigateActionConfig,
+ OpenDialogActionConfig,
UrlActionConfig,
} from "../../../data/lovelace/config/action";
import type { ServiceAction } from "../../../data/script";
@@ -30,6 +31,7 @@ const DEFAULT_ACTIONS: UiAction[] = [
"toggle",
"navigate",
"url",
+ "open-dialog",
"perform-action",
"assist",
"none",
@@ -93,6 +95,16 @@ export class HuiActionEditor extends LitElement {
return config?.url_path || "";
}
+ get _view_path(): string {
+ const config = this.config as OpenDialogActionConfig | undefined;
+ return config?.view_path || "";
+ }
+
+ get _dashboard_path(): string {
+ const config = this.config as OpenDialogActionConfig | undefined;
+ return config?.dashboard_path || "";
+ }
+
get _service(): string {
const config = this.config as CallServiceActionConfig;
return config?.perform_action || config?.service || "";
@@ -191,6 +203,26 @@ export class HuiActionEditor extends LitElement {
>
`
: nothing}
+ ${this.config?.action === "url"
+ ? html`
+
+
+ `
+ : nothing}
${this.config?.action === "call-service" ||
this.config?.action === "perform-action"
? html`
diff --git a/src/panels/lovelace/editor/structs/action-struct.ts b/src/panels/lovelace/editor/structs/action-struct.ts
index b6b7c75a27..f10bd3edc2 100644
--- a/src/panels/lovelace/editor/structs/action-struct.ts
+++ b/src/panels/lovelace/editor/structs/action-struct.ts
@@ -55,6 +55,12 @@ const actionConfigStructNavigate = object({
confirmation: optional(actionConfigStructConfirmation),
});
+const actionConfigStructOpenDialog = object({
+ action: literal("open-dialog"),
+ dashboard_path: optional(string()),
+ view_path: optional(string()),
+});
+
const actionConfigStructAssist = type({
action: literal("assist"),
pipeline_id: optional(string()),
@@ -101,6 +107,9 @@ export const actionConfigStruct = dynamic((value) => {
case "more-info": {
return actionConfigStructMoreInfo;
}
+ case "open-dialog": {
+ return actionConfigStructOpenDialog;
+ }
}
}
diff --git a/src/panels/lovelace/types.ts b/src/panels/lovelace/types.ts
index c3810265d9..dc1b397957 100644
--- a/src/panels/lovelace/types.ts
+++ b/src/panels/lovelace/types.ts
@@ -61,6 +61,11 @@ export interface LovelaceGridOptions {
max_rows?: number;
}
+export interface LovelaceDialogSize {
+ width?: number | "full" | "auto";
+ height?: number | "full" | "auto";
+}
+
export interface LovelaceCard extends HTMLElement {
hass?: HomeAssistant;
preview?: boolean;
diff --git a/src/panels/lovelace/views/hui-masonry-view.ts b/src/panels/lovelace/views/hui-masonry-view.ts
index dc90245e1d..c5785ef5e0 100644
--- a/src/panels/lovelace/views/hui-masonry-view.ts
+++ b/src/panels/lovelace/views/hui-masonry-view.ts
@@ -13,7 +13,7 @@ import type { HuiBadge } from "../badges/hui-badge";
import "../badges/hui-view-badges";
import type { HuiCard } from "../cards/hui-card";
import { computeCardSize } from "../common/compute-card-size";
-import type { Lovelace } from "../types";
+import type { Lovelace, LovelaceDialogSize } from "../types";
// Find column with < 5 size, else smallest column
const getColumnIndex = (columnSizes: number[], size: number) => {
@@ -113,6 +113,14 @@ export class MasonryView extends LitElement implements LovelaceViewElement {
});
}
+ public getDialogSize(): LovelaceDialogSize {
+ this._createColumns();
+
+ return {
+ width: "auto",
+ };
+ }
+
private get mqls(): MediaQueryList[] {
if (!this._mqls) {
this._initMqls();
diff --git a/src/panels/lovelace/views/hui-panel-view.ts b/src/panels/lovelace/views/hui-panel-view.ts
index 17b091a3e3..0e32b3ea36 100644
--- a/src/panels/lovelace/views/hui-panel-view.ts
+++ b/src/panels/lovelace/views/hui-panel-view.ts
@@ -11,7 +11,7 @@ import type { HomeAssistant } from "../../../types";
import type { HuiCard } from "../cards/hui-card";
import type { HuiCardOptions } from "../components/hui-card-options";
import type { HuiWarning } from "../components/hui-warning";
-import type { Lovelace } from "../types";
+import type { Lovelace, LovelaceDialogSize } from "../types";
let editCodeLoaded = false;
@@ -28,6 +28,13 @@ export class PanelView extends LitElement implements LovelaceViewElement {
@state() private _card?: HuiCard | HuiWarning | HuiCardOptions;
+ public getDialogSize(): LovelaceDialogSize {
+ return {
+ height: "full",
+ width: "full",
+ };
+ }
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
public setConfig(_config: LovelaceViewConfig): void {}
diff --git a/src/panels/lovelace/views/hui-sections-view.ts b/src/panels/lovelace/views/hui-sections-view.ts
index 58d45edba8..5de61775b6 100644
--- a/src/panels/lovelace/views/hui-sections-view.ts
+++ b/src/panels/lovelace/views/hui-sections-view.ts
@@ -43,7 +43,7 @@ import {
} from "../editor/lovelace-path";
import { showEditSectionDialog } from "../editor/section-editor/show-edit-section-dialog";
import type { HuiSection } from "../sections/hui-section";
-import type { Lovelace } from "../types";
+import type { Lovelace, LovelaceDialogSize } from "../types";
export const DEFAULT_MAX_COLUMNS = 4;
@@ -101,6 +101,21 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
this._config = config;
}
+ public getDialogSize(): LovelaceDialogSize {
+ if (!this._config?.sections) {
+ return {
+ width: 400,
+ };
+ }
+ const size = this._config.sections
+ .map((config) => config.column_span ?? 1)
+ .reduce((acc, val) => acc + val, 0);
+
+ return {
+ width: Math.min(size, 3) * 400,
+ };
+ }
+
private _sectionConfigKeys = new WeakMap();
private _getSectionKey(section: HuiSection) {
diff --git a/src/panels/lovelace/views/hui-view.ts b/src/panels/lovelace/views/hui-view.ts
index 12d10e3214..354c1b767c 100644
--- a/src/panels/lovelace/views/hui-view.ts
+++ b/src/panels/lovelace/views/hui-view.ts
@@ -3,7 +3,7 @@ import type { PropertyValues } from "lit";
import { ReactiveElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { storage } from "../../../common/decorators/storage";
-import type { HASSDomEvent } from "../../../common/dom/fire_event";
+import { fireEvent, type HASSDomEvent } from "../../../common/dom/fire_event";
import "../../../components/entity/ha-state-label-badge";
import "../../../components/ha-svg-icon";
import type { LovelaceViewElement } from "../../../data/lovelace";
@@ -38,12 +38,13 @@ import { createErrorSectionConfig } from "../sections/hui-error-section";
import "../sections/hui-section";
import type { HuiSection } from "../sections/hui-section";
import { generateLovelaceViewStrategy } from "../strategies/get-strategy";
-import type { Lovelace } from "../types";
+import type { Lovelace, LovelaceDialogSize } from "../types";
import { getViewType } from "./get-view-type";
declare global {
// for fire event
interface HASSDomEvents {
+ "view-updated": undefined;
"ll-create-card": { suggested?: string[] } | undefined;
"ll-edit-card": { path: LovelaceCardPath };
"ll-delete-card": DeleteCardParams;
@@ -54,6 +55,7 @@ declare global {
"ll-delete-badge": DeleteBadgeParams;
}
interface HTMLElementEventMap {
+ "view-updated": HASSDomEvent;
"ll-create-card": HASSDomEvent;
"ll-edit-card": HASSDomEvent;
"ll-delete-card": HASSDomEvent;
@@ -93,6 +95,16 @@ export class HUIView extends ReactiveElement {
})
protected _clipboard?: LovelaceCardConfig;
+ public getDialogSize(): LovelaceDialogSize | undefined {
+ if (!this._layoutElement) {
+ return undefined;
+ }
+ if (this._layoutElement.getDialogSize) {
+ return this._layoutElement.getDialogSize();
+ }
+ return undefined;
+ }
+
private _createCardElement(cardConfig: LovelaceCardConfig) {
const element = document.createElement("hui-card");
element.hass = this.hass;
@@ -271,6 +283,14 @@ export class HUIView extends ReactiveElement {
private _createLayoutElement(config: LovelaceViewConfig): void {
this._layoutElement = createViewElement(config) as LovelaceViewElement;
+ this._layoutElement.addEventListener(
+ "ll-upgrade",
+ (ev: Event) => {
+ ev.stopPropagation();
+ fireEvent(this, "view-updated");
+ },
+ { once: true }
+ );
this._layoutElementType = config.type;
this._layoutElement.addEventListener("ll-create-card", (ev) => {
showCreateCardDialog(this, {
diff --git a/src/panels/lovelace/views/view-popup/hui-dialog-view-popup.ts b/src/panels/lovelace/views/view-popup/hui-dialog-view-popup.ts
new file mode 100644
index 0000000000..25b755a6b9
--- /dev/null
+++ b/src/panels/lovelace/views/view-popup/hui-dialog-view-popup.ts
@@ -0,0 +1,206 @@
+import { mdiClose } from "@mdi/js";
+import type { PropertyValues } from "lit";
+import { css, html, LitElement, nothing } from "lit";
+import { customElement, property, state } from "lit/decorators";
+import { styleMap } from "lit/directives/style-map";
+import { fireEvent } from "../../../../common/dom/fire_event";
+import "../../../../components/ha-dialog-header";
+import "../../../../components/ha-icon-button";
+import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
+import {
+ fetchConfig,
+ isStrategyDashboard,
+} from "../../../../data/lovelace/config/types";
+import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
+import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
+import { haStyleDialog } from "../../../../resources/styles";
+import type { HomeAssistant } from "../../../../types";
+import { generateLovelaceDashboardStrategy } from "../../strategies/get-strategy";
+import type { Lovelace, LovelaceDialogSize } from "../../types";
+import "../hui-view";
+import type { HUIView } from "../hui-view";
+import type { ViewPopupDialogParams } from "./show-view-popup-dialog";
+
+@customElement("hui-dialog-view-popup")
+export class DialogViewPopup
+ extends LitElement
+ implements HassDialog
+{
+ @property({ attribute: false }) public hass!: HomeAssistant;
+
+ @state() private _params?: ViewPopupDialogParams;
+
+ @state() private _viewIndex?: number;
+
+ @state() private _view?: HUIView;
+
+ @state() private _viewSize?: LovelaceDialogSize;
+
+ @state() private _viewConfig?: LovelaceViewConfig;
+
+ protected updated(changedProperties: PropertyValues): void {
+ super.updated(changedProperties);
+ if (changedProperties.has("hass") && this._view) {
+ this._view.hass = this.hass;
+ }
+ }
+
+ public showDialog(params: ViewPopupDialogParams): void {
+ this._params = params;
+ this.fetchConfig();
+ }
+
+ public closeDialog() {
+ this._params = undefined;
+ this._view = undefined;
+ this._viewConfig = undefined;
+ this._viewSize = undefined;
+ this._viewIndex = undefined;
+ fireEvent(this, "dialog-closed", { dialog: this.localName });
+ return true;
+ }
+
+ public async fetchConfig() {
+ if (!this._params) {
+ return;
+ }
+
+ const dashboardPath = this._params.dashboard_path ?? null;
+
+ const rawConfig = await fetchConfig(
+ this.hass.connection,
+ this._params?.dashboard_path ?? null,
+ false
+ );
+
+ let config: LovelaceConfig;
+
+ if (isStrategyDashboard(rawConfig)) {
+ config = await generateLovelaceDashboardStrategy(rawConfig, this.hass);
+ } else {
+ config = rawConfig;
+ }
+
+ const lovelace: Lovelace = {
+ config: config,
+ rawConfig: rawConfig,
+ editMode: false,
+ urlPath: dashboardPath,
+ enableFullEditMode: () => undefined,
+ mode: "storage",
+ locale: this.hass.locale,
+ saveConfig: async () => undefined,
+ deleteConfig: async () => undefined,
+ setEditMode: () => undefined,
+ showToast: () => undefined,
+ };
+
+ this._viewIndex = config.views.findIndex(
+ (v) => v.path === this._params?.view_path
+ );
+
+ const view = document.createElement("hui-view");
+ view.lovelace = lovelace;
+ view.hass = this.hass;
+ view.index = this._viewIndex;
+ this._view = view;
+ this._view.addEventListener(
+ "view-updated",
+ (ev) => {
+ ev.stopPropagation();
+ this._viewSize = this._view?.getDialogSize();
+ },
+ { once: true }
+ );
+ this._viewConfig = config.views[this._viewIndex!];
+ await this.updateComplete;
+
+ this._viewSize = this._view.getDialogSize();
+ }
+
+ protected render() {
+ if (!this._params || !this._view) {
+ return nothing;
+ }
+
+ const width = this._viewSize?.width ?? "auto";
+ const height = this._viewSize?.height ?? "auto";
+ const dialogMinWidth =
+ width === "full"
+ ? "100vw"
+ : typeof width === "number"
+ ? `${width}px`
+ : undefined;
+ const dialogMinHeight =
+ height === "full"
+ ? "100vh"
+ : typeof height === "number"
+ ? `${height}px`
+ : undefined;
+
+ return html`
+
+
+
+ ${this._viewConfig?.title}
+
+ ${this._view}
+
+ `;
+ }
+
+ static get styles() {
+ return [
+ haStyleDialog,
+ css`
+ ha-dialog {
+ --dialog-content-padding: 0;
+ --mdc-dialog-max-width: 90vw;
+ --mdc-dialog-max-height: 90vw;
+ --mdc-dialog-min-width: min(var(--dialog-width, none), 90vw);
+ --mdc-dialog-min-height: min(var(--dialog-height, none), 90vh);
+ }
+
+ .content {
+ display: block;
+ flex: 1 1 0;
+ width: auto;
+ }
+
+ @media all and (max-width: 450px), all and (max-height: 500px) {
+ ha-dialog {
+ --mdc-dialog-min-width: calc(
+ 100vw - env(safe-area-inset-right) - env(safe-area-inset-left)
+ );
+ --mdc-dialog-max-width: calc(
+ 100vw - env(safe-area-inset-right) - env(safe-area-inset-left)
+ );
+ --mdc-dialog-min-height: 100%;
+ --mdc-dialog-max-height: 100%;
+ }
+ }
+ `,
+ ];
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "hui-dialog-view-popup": DialogViewPopup;
+ }
+}
diff --git a/src/panels/lovelace/views/view-popup/show-view-popup-dialog.ts b/src/panels/lovelace/views/view-popup/show-view-popup-dialog.ts
new file mode 100644
index 0000000000..99b8b88967
--- /dev/null
+++ b/src/panels/lovelace/views/view-popup/show-view-popup-dialog.ts
@@ -0,0 +1,17 @@
+import { fireEvent } from "../../../../common/dom/fire_event";
+
+export interface ViewPopupDialogParams {
+ dashboard_path?: string;
+ view_path: string;
+}
+
+export const showViewPopupDialog = (
+ element: HTMLElement,
+ dialogParams: ViewPopupDialogParams
+): void => {
+ fireEvent(element, "show-dialog", {
+ dialogTag: "hui-dialog-view-popup",
+ dialogImport: () => import("./hui-dialog-view-popup"),
+ dialogParams,
+ });
+};