diff --git a/src/dialogs/make-dialog-manager.ts b/src/dialogs/make-dialog-manager.ts index 50d0e32977..dabbfe6122 100644 --- a/src/dialogs/make-dialog-manager.ts +++ b/src/dialogs/make-dialog-manager.ts @@ -1,9 +1,9 @@ -import type { HASSDomEvent, ValidHassDomEvent } from "../common/dom/fire_event"; -import { mainWindow } from "../common/dom/get_main_window"; -import type { ProvideHassElement } from "../mixins/provide-hass-lit-mixin"; import { ancestorsWithProperty } from "../common/dom/ancestors-with-property"; import { deepActiveElement } from "../common/dom/deep-active-element"; +import type { HASSDomEvent, ValidHassDomEvent } from "../common/dom/fire_event"; +import { mainWindow } from "../common/dom/get_main_window"; import { nextRender } from "../common/util/render-status"; +import type { ProvideHassElement } from "../mixins/provide-hass-lit-mixin"; declare global { // for fire event @@ -23,7 +23,7 @@ export interface HassDialog< T = HASSDomEvents[ValidHassDomEvent], > extends HTMLElement { showDialog(params: T); - closeDialog?: () => boolean; + closeDialog?: (historyState?: any) => boolean; } interface ShowDialogParams { @@ -144,27 +144,32 @@ export const showDialog = async ( return true; }; -export const closeDialog = async (dialogTag: string): Promise => { +export const closeDialog = async ( + dialogTag: string, + historyState?: any +): Promise => { if (!(dialogTag in LOADED)) { return true; } const dialogElement = await LOADED[dialogTag].element; if (dialogElement.closeDialog) { - return dialogElement.closeDialog() !== false; + return dialogElement.closeDialog(historyState) !== false; } return true; }; // called on back() -export const closeLastDialog = async () => { +export const closeLastDialog = async (historyState?: any) => { if (OPEN_DIALOG_STACK.length) { - const lastDialog = OPEN_DIALOG_STACK.pop(); - const closed = await closeDialog(lastDialog!.dialogTag); + const lastDialog = OPEN_DIALOG_STACK.pop() as DialogState; + const closed = await closeDialog(lastDialog.dialogTag, historyState); if (!closed) { // if the dialog was not closed, put it back on the stack - OPEN_DIALOG_STACK.push(lastDialog!); - } - if (OPEN_DIALOG_STACK.length && mainWindow.history.state?.opensDialog) { + OPEN_DIALOG_STACK.push(lastDialog); + } else if ( + OPEN_DIALOG_STACK.length && + mainWindow.history.state?.opensDialog + ) { // if there are more dialogs open, push a new state so back() will close the next top dialog mainWindow.history.pushState( { dialog: OPEN_DIALOG_STACK[OPEN_DIALOG_STACK.length - 1].dialogTag }, diff --git a/src/panels/config/automation/add-automation-element-dialog.ts b/src/panels/config/automation/add-automation-element-dialog.ts index fa666bb444..4b8ca3f54c 100644 --- a/src/panels/config/automation/add-automation-element-dialog.ts +++ b/src/panels/config/automation/add-automation-element-dialog.ts @@ -16,6 +16,7 @@ import { classMap } from "lit/directives/class-map"; import { repeat } from "lit/directives/repeat"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../common/dom/fire_event"; +import { mainWindow } from "../../../common/dom/get_main_window"; import { computeAreaName } from "../../../common/entity/compute_area_name"; import { computeDeviceName } from "../../../common/entity/compute_device_name"; import { computeDomain } from "../../../common/entity/compute_domain"; @@ -118,7 +119,6 @@ import type { HomeAssistant } from "../../../types"; import { isMac } from "../../../util/is_mac"; import { showToast } from "../../../util/toast"; import "./add-automation-element/ha-automation-add-from-target"; -import type HaAutomationAddFromTarget from "./add-automation-element/ha-automation-add-from-target"; import "./add-automation-element/ha-automation-add-items"; import "./add-automation-element/ha-automation-add-search"; import type { AddAutomationElementDialogParams } from "./show-add-automation-element-dialog"; @@ -216,10 +216,6 @@ class DialogAddAutomationElement // #endregion state // #region queries - - @query("ha-automation-add-from-target") - private _targetPickerElement?: HaAutomationAddFromTarget; - @query("ha-automation-add-items") private _itemsListElement?: HTMLDivElement; @@ -298,6 +294,14 @@ class DialogAddAutomationElement } ); + // add initial dialog view state to history + mainWindow.history.pushState( + { + dialogData: {}, + }, + "" + ); + if (this._params?.type === "action") { this.hass.loadBackendTranslation("services"); getServiceIcons(this.hass); @@ -318,7 +322,41 @@ class DialogAddAutomationElement this._bottomSheetMode = this._narrow; } - public closeDialog() { + public closeDialog(historyState?: any) { + // prevent closing when come from popstate event and root level isn't active + if ( + this._open && + historyState && + (this._selectedTarget || this._selectedGroup) + ) { + if (historyState.dialogData?.target) { + this._selectedTarget = historyState.dialogData.target; + this._getItemsByTarget(); + this._tab = "targets"; + return false; + } + if (historyState.dialogData?.group) { + this._selectedCollectionIndex = historyState.dialogData.collectionIndex; + this._selectedGroup = historyState.dialogData.group; + this._tab = "groups"; + return false; + } + + // return to home on mobile + if (this._narrow) { + this._selectedTarget = undefined; + this._selectedGroup = undefined; + return false; + } + } + + // if dialog is closed, but root level isn't active, clean up history state + if (mainWindow.history.state?.dialogData) { + this._open = false; + mainWindow.history.back(); + return false; + } + this.removeKeyboardShortcuts(); this._unsubscribe(); if (this._params) { @@ -405,7 +443,7 @@ class DialogAddAutomationElement return html` ${this._renderContent()} @@ -417,7 +455,7 @@ class DialogAddAutomationElement ${this._renderContent()} @@ -1659,11 +1697,7 @@ class DialogAddAutomationElement } private _back() { - if (this._selectedTarget) { - this._targetPickerElement?.navigateBack(); - return; - } - this._selectedGroup = undefined; + mainWindow.history.back(); } private _groupSelected(ev) { @@ -1675,6 +1709,16 @@ class DialogAddAutomationElement } this._selectedGroup = group.value; this._selectedCollectionIndex = ev.currentTarget.index; + + mainWindow.history.pushState( + { + dialogData: { + group: this._selectedGroup, + collectionIndex: this._selectedCollectionIndex, + }, + }, + "" + ); requestAnimationFrame(() => { this._itemsListElement?.scrollTo(0, 0); }); @@ -1704,6 +1748,14 @@ class DialogAddAutomationElement this._targetItems = undefined; this._loadItemsError = false; this._selectedTarget = ev.detail.value; + mainWindow.history.pushState( + { + dialogData: { + target: this._selectedTarget, + }, + }, + "" + ); requestAnimationFrame(() => { if (this._narrow) { @@ -1823,6 +1875,10 @@ class DialogAddAutomationElement this._tab = "targets"; } + private _handleClosed() { + this.closeDialog(); + } + // #region interaction // #region render helpers diff --git a/src/panels/config/automation/add-automation-element/ha-automation-add-from-target.ts b/src/panels/config/automation/add-automation-element/ha-automation-add-from-target.ts index 5808779391..9986f2f8ce 100644 --- a/src/panels/config/automation/add-automation-element/ha-automation-add-from-target.ts +++ b/src/panels/config/automation/add-automation-element/ha-automation-add-from-target.ts @@ -1383,92 +1383,6 @@ export default class HaAutomationAddFromTarget extends LitElement { ); } - public navigateBack() { - if (!this.value) { - return; - } - - const valueType = Object.keys(this.value)[0].replace("_id", ""); - const valueId = this.value[`${valueType}_id`]; - - if ( - valueType === "floor" || - valueType === "label" || - (!valueId && - (valueType === "device" || - valueType === "helper" || - valueType === "service" || - valueType === "area")) - ) { - fireEvent(this, "value-changed", { value: undefined }); - return; - } - - if (valueType === "area") { - fireEvent(this, "value-changed", { - value: { floor_id: this.areas[valueId].floor_id || undefined }, - }); - return; - } - - if (valueType === "device") { - if ( - !this.devices[valueId].area_id && - this.devices[valueId].entry_type === "service" - ) { - fireEvent(this, "value-changed", { - value: { service_id: undefined }, - }); - return; - } - - fireEvent(this, "value-changed", { - value: { area_id: this.devices[valueId].area_id || undefined }, - }); - return; - } - - if (valueType === "entity" && valueId) { - const deviceId = this.entities[valueId].device_id; - if (deviceId) { - fireEvent(this, "value-changed", { - value: { device_id: deviceId }, - }); - return; - } - - const areaId = this.entities[valueId].area_id; - if (areaId) { - fireEvent(this, "value-changed", { - value: { area_id: areaId }, - }); - return; - } - - const domain = valueId.split(".", 2)[0]; - const manifest = this.manifests ? this.manifests[domain] : undefined; - if (manifest?.integration_type === "helper") { - fireEvent(this, "value-changed", { - value: { [`helper_${domain}_id`]: undefined }, - }); - return; - } - - fireEvent(this, "value-changed", { - value: { [`entity_${domain}_id`]: undefined }, - }); - } - - if (valueType.startsWith("helper_") || valueType.startsWith("entity_")) { - fireEvent(this, "value-changed", { - value: { - [`${valueType.startsWith("helper_") ? "helper" : "device"}_id`]: - undefined, - }, - }); - } - } - private _expandHeight() { this._fullHeight = true; this.style.setProperty("--max-height", "none"); diff --git a/src/state/url-sync-mixin.ts b/src/state/url-sync-mixin.ts index 2ed322f74d..ba36aea1de 100644 --- a/src/state/url-sync-mixin.ts +++ b/src/state/url-sync-mixin.ts @@ -56,7 +56,11 @@ export const urlSyncMixin = < // if we are instead navigating forward, the dialogs are already closed closeLastDialog(); } - if ("dialog" in ev.state) { + if ("dialogData" in ev.state) { + // if we have dialog data we are closing a dialog with appended state + // so dialog has the change to navigate back to the previous state + closeLastDialog(ev.state); + } else if ("dialog" in ev.state) { // coming to a dialog // the dialog stack must be empty in this case so this state should be cleaned up mainWindow.history.back();