1
0
mirror of https://github.com/home-assistant/frontend.git synced 2025-12-19 18:28:42 +00:00

Use history to manage back button click in automations add TCA (#28289)

This commit is contained in:
Wendelin
2025-12-02 15:43:13 +01:00
committed by GitHub
parent c5642c15b8
commit 0d51648de1
4 changed files with 91 additions and 112 deletions

View File

@@ -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<T> {
@@ -144,27 +144,32 @@ export const showDialog = async (
return true;
};
export const closeDialog = async (dialogTag: string): Promise<boolean> => {
export const closeDialog = async (
dialogTag: string,
historyState?: any
): Promise<boolean> => {
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 },

View File

@@ -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`
<ha-bottom-sheet
.open=${this._open}
@closed=${this.closeDialog}
@closed=${this._handleClosed}
flexcontent
>
${this._renderContent()}
@@ -417,7 +455,7 @@ class DialogAddAutomationElement
<ha-wa-dialog
width="large"
.open=${this._open}
@closed=${this.closeDialog}
@closed=${this._handleClosed}
flexcontent
>
${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

View File

@@ -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");

View File

@@ -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();