diff --git a/src/common/navigate.ts b/src/common/navigate.ts index 53a9497bd621..7bd9c31673e4 100644 --- a/src/common/navigate.ts +++ b/src/common/navigate.ts @@ -1,4 +1,3 @@ -import { historyPromise } from "../state/url-sync-mixin"; import { fireEvent } from "./dom/fire_event"; import { mainWindow } from "./dom/get_main_window"; @@ -17,11 +16,6 @@ export interface NavigateOptions { export const navigate = (path: string, options?: NavigateOptions) => { const replace = options?.replace || false; - if (historyPromise) { - historyPromise.then(() => navigate(path, options)); - return; - } - if (__DEMO__) { if (replace) { mainWindow.history.replaceState( diff --git a/src/dialogs/generic/show-dialog-box.ts b/src/dialogs/generic/show-dialog-box.ts index b780ddf986a9..77092e35f6fd 100644 --- a/src/dialogs/generic/show-dialog-box.ts +++ b/src/dialogs/generic/show-dialog-box.ts @@ -46,6 +46,7 @@ const showDialogHelper = ( extra?: { confirmation?: DialogBoxParams["confirmation"]; prompt?: DialogBoxParams["prompt"]; + alert?: boolean; } ) => new Promise((resolve) => { @@ -71,13 +72,15 @@ const showDialogHelper = ( } }, }, + // simple alerts don't need to be added to history + addHistory: extra?.alert ? false : undefined, }); }); export const showAlertDialog = ( element: HTMLElement, dialogParams: AlertDialogParams -) => showDialogHelper(element, dialogParams); +) => showDialogHelper(element, dialogParams, { alert: true }); export const showConfirmationDialog = ( element: HTMLElement, diff --git a/src/dialogs/make-dialog-manager.ts b/src/dialogs/make-dialog-manager.ts index 49da32e15fa7..80555b7dc610 100644 --- a/src/dialogs/make-dialog-manager.ts +++ b/src/dialogs/make-dialog-manager.ts @@ -39,8 +39,8 @@ export interface DialogClosedParams { export interface DialogState { dialog: string; open: boolean; - oldState: null | DialogState; dialogParams?: unknown; + nextState?: DialogState; } interface LoadedDialogInfo { @@ -82,42 +82,21 @@ export const showDialog = async ( }; } - // Get the focus targets after the dialog closes, but keep the original if dialog is being replaced - if (mainWindow.history.state?.replaced) { - LOADED[dialogTag].closedFocusTargets = - LOADED[mainWindow.history.state.dialog].closedFocusTargets; - delete LOADED[mainWindow.history.state.dialog].closedFocusTargets; - } else { - LOADED[dialogTag].closedFocusTargets = ancestorsWithProperty( - deepActiveElement(), - FOCUS_TARGET - ); - } + LOADED[dialogTag].closedFocusTargets = ancestorsWithProperty( + deepActiveElement(), + FOCUS_TARGET + ); - if (addHistory) { - mainWindow.history.replaceState( - { - dialog: dialogTag, - open: false, - oldState: - mainWindow.history.state?.open && - mainWindow.history.state?.dialog !== dialogTag - ? mainWindow.history.state - : null, - }, - "" - ); + const { state } = mainWindow.history; + // if the same dialog is already open, don't push state + if (addHistory && state?.dialog !== dialogTag) { + const nextState = { dialog: dialogTag, dialogParams, open: true }; + mainWindow.history.replaceState({ ...state, nextState }, ""); try { - mainWindow.history.pushState( - { dialog: dialogTag, dialogParams: dialogParams, open: true }, - "" - ); + mainWindow.history.pushState(nextState, ""); } catch (err: any) { // dialogParams could not be cloned, probably contains callback - mainWindow.history.pushState( - { dialog: dialogTag, dialogParams: null, open: true }, - "" - ); + mainWindow.history.pushState({ ...nextState, dialogParams: null }, ""); } } @@ -132,14 +111,6 @@ export const showDialog = async ( return true; }; -export const replaceDialog = (dialogElement: HassDialog) => { - mainWindow.history.replaceState( - { ...mainWindow.history.state, replaced: true }, - "" - ); - dialogElement.removeEventListener("dialog-closed", _handleClosedFocus); -}; - export const closeDialog = async (dialogTag: string): Promise => { if (!(dialogTag in LOADED)) { return true; diff --git a/src/state/url-sync-mixin.ts b/src/state/url-sync-mixin.ts index b9b47d238a47..2962e35dc2c8 100644 --- a/src/state/url-sync-mixin.ts +++ b/src/state/url-sync-mixin.ts @@ -12,11 +12,6 @@ import type { Constructor } from "../types"; const DEBUG = false; -// eslint-disable-next-line import/no-mutable-exports -export let historyPromise: Promise | undefined; - -let historyResolve: undefined | (() => void); - export const urlSyncMixin = < T extends Constructor, >( @@ -26,8 +21,6 @@ export const urlSyncMixin = < __DEMO__ ? superClass : class extends superClass { - private _ignoreNextPopState = false; - public connectedCallback(): void { super.connectedCallback(); if (mainWindow.history.length === 1) { @@ -72,57 +65,30 @@ export const urlSyncMixin = < } // If not closed by navigating back, and not a new dialog is open, remove the open state from history if ( + mainWindow.history.length > 1 && mainWindow.history.state?.open && mainWindow.history.state?.dialog === ev.detail.dialog ) { if (DEBUG) { console.log("remove state", ev.detail.dialog); } - if (mainWindow.history.length) { - this._ignoreNextPopState = true; - historyPromise = new Promise((resolve) => { - historyResolve = () => { - resolve(); - historyResolve = undefined; - historyPromise = undefined; - }; - mainWindow.history.back(); - }); - } + mainWindow.history.back(); } }; private _popstateChangeListener = (ev: PopStateEvent) => { - if (this._ignoreNextPopState) { - if ( - history.length && - (ev.state?.oldState?.replaced || - ev.state?.oldState?.dialogParams === null) - ) { - // if the previous dialog was replaced, or we could not copy the params, and the current dialog is closed, we should also remove the previous dialog from history - if (DEBUG) { - console.log("remove old state", ev.state.oldState); - } - mainWindow.history.back(); - return; - } + if (ev.state) { if (DEBUG) { - console.log("ignore popstate"); + console.log("popstate", ev); } - this._ignoreNextPopState = false; - if (historyResolve) { - historyResolve(); + if ("nextState" in ev.state) { + // coming back from a dialog + this._closeDialog(ev.state.nextState); } - return; - } - if (ev.state && "dialog" in ev.state) { - if (DEBUG) { - console.log("popstate", ev); + if ("dialog" in ev.state) { + // coming to a dialog + this._handleDialogStateChange(ev.state); } - this._handleDialogStateChange(ev.state); - } - if (historyResolve) { - historyResolve(); } }; @@ -130,47 +96,26 @@ export const urlSyncMixin = < if (DEBUG) { console.log("handle state", state); } - if (!state.open) { - const closed = await closeDialog(state.dialog); - if (!closed) { - if (DEBUG) { - console.log("dialog could not be closed"); - } - // dialog could not be closed, push state again - mainWindow.history.pushState( - { - dialog: state.dialog, - open: true, - dialogParams: null, - oldState: null, - }, - "" - ); - return; - } - if (state.oldState) { - if (DEBUG) { - console.log("handle old state"); - } - this._handleDialogStateChange(state.oldState); - } - return; - } - let shown = false; if (state.open && state.dialogParams !== null) { - shown = await showDialog( + await showDialog( this, this.shadowRoot!, state.dialog, - state.dialogParams + state.dialogParams, + undefined, + false ); } - if (!shown) { - // can't open dialog, update state - mainWindow.history.replaceState( - { ...mainWindow.history.state, open: false }, - "" - ); + } + + private async _closeDialog(dialogState: DialogState) { + const closed = await closeDialog(dialogState.dialog); + if (!closed) { + if (DEBUG) { + console.log("dialog could not be closed"); + } + // dialog could not be closed, push state again + mainWindow.history.pushState(dialogState, ""); } } };