Compare commits

...

1 Commits

8 changed files with 124 additions and 56 deletions

View File

@@ -614,8 +614,8 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
private _dialogManager = (e) => {
const { dialogTag, dialogImport, dialogParams, addHistory } = e.detail;
showDialog(
e.target as LitElement,
this,
this.shadowRoot!,
dialogTag,
dialogParams,
dialogImport,

View File

@@ -118,7 +118,7 @@ class HaLandingPage extends LandingPageBaseElement {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
makeDialogManager(this, this.shadowRoot!);
makeDialogManager(this);
if (window.innerWidth > 450) {
import("../../src/resources/particles");

View File

@@ -0,0 +1,46 @@
import type { LitElement } from "lit";
import { fireEvent } from "../common/dom/fire_event";
import type { HaDialog } from "../components/ha-dialog";
import type { Constructor } from "../types";
import type { HassDialogNext } from "./make-dialog-manager";
export const DialogMixin = <T extends Constructor<LitElement>>(superClass: T) =>
class extends superClass implements HassDialogNext {
private _closePromise?: Promise<boolean>;
private _closeResolve?: (value: boolean) => void;
public closeDialog(_historyState?: any): Promise<boolean> | boolean {
if (this._closePromise) {
return this._closePromise;
}
const dialogElement = this.shadowRoot?.querySelector(
"ha-dialog"
) as HaDialog | null;
if (dialogElement) {
this._closePromise = new Promise<boolean>((resolve) => {
this._closeResolve = resolve;
});
dialogElement.open = false;
}
return this._closePromise || true;
}
private _removeDialog = () => {
this._closeResolve?.(true);
this._closePromise = undefined;
this._closeResolve = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
};
connectedCallback() {
super.connectedCallback();
this.addEventListener("closed", this._removeDialog, { once: true });
}
disconnectedCallback() {
this.removeEventListener("closed", this._removeDialog);
super.disconnectedCallback();
}
};

View File

@@ -1,3 +1,4 @@
import type { LitElement } from "lit";
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";
@@ -23,7 +24,14 @@ export interface HassDialog<
T = HASSDomEvents[ValidHassDomEvent],
> extends HTMLElement {
showDialog(params: T);
closeDialog?: (historyState?: any) => boolean;
closeDialog?: (historyState?: any) => Promise<boolean> | boolean;
}
export interface HassDialogNext<
T = HASSDomEvents[ValidHassDomEvent],
> extends HTMLElement {
params?: T;
closeDialog?: (historyState?: any) => Promise<boolean> | boolean;
}
interface ShowDialogParams<T> {
@@ -39,7 +47,6 @@ export interface DialogClosedParams {
export interface DialogState {
element: HTMLElement & ProvideHassElement;
root: ShadowRoot | HTMLElement;
dialogTag: string;
dialogParams: unknown;
dialogImport?: () => Promise<unknown>;
@@ -47,7 +54,7 @@ export interface DialogState {
}
interface LoadedDialogInfo {
element: Promise<HassDialog>;
element: Promise<HassDialogNext | HassDialog> | null;
closedFocusTargets?: Set<Element>;
}
@@ -58,8 +65,8 @@ const OPEN_DIALOG_STACK: DialogState[] = [];
export const FOCUS_TARGET = Symbol.for("HA focus target");
export const showDialog = async (
eventTarget: LitElement,
element: HTMLElement & ProvideHassElement,
root: ShadowRoot | HTMLElement,
dialogTag: string,
dialogParams: unknown,
dialogImport?: () => Promise<unknown>,
@@ -77,10 +84,18 @@ export const showDialog = async (
}
LOADED[dialogTag] = {
element: dialogImport().then(() => {
const dialogEl = document.createElement(dialogTag) as HassDialog;
element.provideHass(dialogEl);
const dialogEl = document.createElement(dialogTag) as
| HassDialogNext
| HassDialog;
if ("showDialog" in dialogEl) {
// provide hass for legacy persistent dialogs
element.provideHass(dialogEl);
}
dialogEl.addEventListener("dialog-closed", _handleClosed);
dialogEl.addEventListener("dialog-closed", _handleClosedFocus);
return dialogEl;
}),
};
@@ -95,8 +110,8 @@ export const showDialog = async (
setTimeout(resolve);
});
return showDialog(
eventTarget,
element,
root,
dialogTag,
dialogParams,
dialogImport,
@@ -111,7 +126,6 @@ export const showDialog = async (
}
OPEN_DIALOG_STACK.push({
element,
root,
dialogTag,
dialogParams,
dialogImport,
@@ -134,12 +148,27 @@ export const showDialog = async (
FOCUS_TARGET
);
const dialogElement = await LOADED[dialogTag].element;
let dialogElement: HassDialogNext | HassDialog | null;
if (LOADED[dialogTag] && LOADED[dialogTag].element === null) {
dialogElement = document.createElement(dialogTag) as HassDialogNext;
dialogElement.addEventListener("dialog-closed", _handleClosed);
dialogElement.addEventListener("dialog-closed", _handleClosedFocus);
LOADED[dialogTag].element = Promise.resolve(dialogElement);
} else {
dialogElement = await LOADED[dialogTag].element;
}
// Append it again so it's the last element in the root,
// so it's guaranteed to be on top of the other elements
root.appendChild(dialogElement);
dialogElement.showDialog(dialogParams);
eventTarget.shadowRoot!.appendChild(dialogElement!);
if ("showDialog" in dialogElement!) {
dialogElement.showDialog(dialogParams);
} else {
dialogElement!.params = dialogParams;
}
return true;
};
@@ -152,7 +181,7 @@ export const closeDialog = async (
return true;
}
const dialogElement = await LOADED[dialogTag].element;
if (dialogElement.closeDialog) {
if (dialogElement && dialogElement.closeDialog) {
return dialogElement.closeDialog(historyState) !== false;
}
return true;
@@ -214,19 +243,27 @@ const _handleClosed = (ev: HASSDomEvent<DialogClosedParams>) => {
mainWindow.history.back();
}
}
// cleanup element
if (ev.currentTarget && "params" in ev.currentTarget) {
const dialogElement = ev.currentTarget as HassDialogNext;
dialogElement.removeEventListener("dialog-closed", _handleClosed);
dialogElement.removeEventListener("dialog-closed", _handleClosedFocus);
dialogElement.remove();
LOADED[ev.detail.dialog].element = null;
}
};
export const makeDialogManager = (
element: HTMLElement & ProvideHassElement,
root: ShadowRoot | HTMLElement
element: HTMLElement & ProvideHassElement
) => {
element.addEventListener(
"show-dialog",
(e: HASSDomEvent<ShowDialogParams<unknown>>) => {
const { dialogTag, dialogImport, dialogParams, addHistory } = e.detail;
showDialog(
e.target as LitElement,
element,
root,
dialogTag,
dialogParams,
dialogImport,

View File

@@ -1,12 +1,14 @@
import { consume } from "@lit/context";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { customElement, state } from "lit/decorators";
import type { LocalizeKeys } from "../../common/translations/localize";
import "../../components/ha-alert";
import "../../components/ha-svg-icon";
import "../../components/ha-dialog";
import "../../components/ha-svg-icon";
import { localizeContext } from "../../data/context";
import type { HomeAssistant } from "../../types";
import { isMac } from "../../util/is_mac";
import { DialogMixin } from "../dialog-mixin";
interface Text {
textTranslationKey: LocalizeKeys;
@@ -165,24 +167,10 @@ const _SHORTCUTS: Section[] = [
];
@customElement("dialog-shortcuts")
class DialogShortcuts extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _open = false;
public async showDialog(): Promise<void> {
this._open = true;
}
private _dialogClosed() {
this._open = false;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
public async closeDialog() {
this._open = false;
return true;
}
class DialogShortcuts extends DialogMixin(LitElement) {
@state()
@consume({ context: localizeContext, subscribe: true })
private localize!: HomeAssistant["localize"];
private _renderShortcut(
shortcutKeys: ShortcutString[],
@@ -196,15 +184,13 @@ class DialogShortcuts extends LitElement {
>${shortcutKey === CTRL_CMD
? isMac
? "⌘"
: this.hass.localize("ui.dialogs.shortcuts.keys.ctrl")
: this.localize("ui.dialogs.shortcuts.keys.ctrl")
: typeof shortcutKey === "string"
? shortcutKey
: this.hass.localize(
shortcutKey.shortcutTranslationKey
)}</span
: this.localize(shortcutKey.shortcutTranslationKey)}</span
>`
)}
${this.hass.localize(descriptionKey)}
${this.localize(descriptionKey)}
</div>
`;
}
@@ -212,14 +198,13 @@ class DialogShortcuts extends LitElement {
protected render() {
return html`
<ha-dialog
.open=${this._open}
@closed=${this._dialogClosed}
.headerTitle=${this.hass.localize("ui.dialogs.shortcuts.title")}
open
.headerTitle=${this.localize("ui.dialogs.shortcuts.title")}
>
<div class="content">
${_SHORTCUTS.map(
(section) => html`
<h3>${this.hass.localize(section.titleTranslationKey)}</h3>
<h3>${this.localize(section.titleTranslationKey)}</h3>
<div class="items">
${section.items.map((item) => {
if ("shortcut" in item) {
@@ -229,7 +214,7 @@ class DialogShortcuts extends LitElement {
);
}
return html`<p>
${this.hass.localize((item as Text).textTranslationKey)}
${this.localize((item as Text).textTranslationKey)}
</p>`;
})}
</div>
@@ -238,9 +223,9 @@ class DialogShortcuts extends LitElement {
</div>
<ha-alert slot="footer">
${this.hass.localize("ui.dialogs.shortcuts.enable_shortcuts_hint", {
${this.localize("ui.dialogs.shortcuts.enable_shortcuts_hint", {
user_profile: html`<a href="/profile/general#shortcuts"
>${this.hass.localize(
>${this.localize(
"ui.dialogs.shortcuts.enable_shortcuts_hint_user_profile"
)}</a
>`,

View File

@@ -226,7 +226,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
) {
import("../resources/particles");
}
makeDialogManager(this, this.shadowRoot!);
makeDialogManager(this);
import("../components/ha-language-picker");
}

View File

@@ -1,4 +1,4 @@
import type { PropertyValues } from "lit";
import type { LitElement, PropertyValues } from "lit";
import type { HASSDomEvent } from "../common/dom/fire_event";
import { makeDialogManager, showDialog } from "../dialogs/make-dialog-manager";
import type { Constructor } from "../types";
@@ -32,7 +32,7 @@ export const dialogManagerMixin = <T extends Constructor<HassBaseEl>>(
this.addEventListener("register-dialog", (e) =>
this.registerDialog(e.detail)
);
makeDialogManager(this, this.shadowRoot!);
makeDialogManager(this);
}
protected registerDialog({
@@ -43,8 +43,8 @@ export const dialogManagerMixin = <T extends Constructor<HassBaseEl>>(
}: RegisterDialogParams) {
this.addEventListener(dialogShowEvent, (showEv) => {
showDialog(
showEv.target as LitElement,
this,
this.shadowRoot!,
dialogTag,
(showEv as HASSDomEvent<unknown>).detail,
dialogImport,

View File

@@ -1,4 +1,4 @@
import type { PropertyValues } from "lit";
import type { LitElement, PropertyValues } from "lit";
import type { HASSDomEvent } from "../common/dom/fire_event";
import { computeDomain } from "../common/entity/compute_domain";
import { showDialog } from "../dialogs/make-dialog-manager";
@@ -27,8 +27,8 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
private async _handleMoreInfo(ev: HASSDomEvent<MoreInfoDialogParams>) {
showDialog(
ev.target as LitElement,
this,
this.shadowRoot!,
"ha-more-info-dialog",
{
entityId: ev.detail.entityId,