mirror of
https://github.com/home-assistant/frontend.git
synced 2026-04-02 00:27:49 +01:00
Migrate ha-toast to wa-popup instead of wa-popover (#30327)
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import "@home-assistant/webawesome/dist/components/popover/popover";
|
||||
import type WaPopover from "@home-assistant/webawesome/dist/components/popover/popover";
|
||||
import "@home-assistant/webawesome/dist/components/popup/popup";
|
||||
import type WaPopup from "@home-assistant/webawesome/dist/components/popup/popup";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { nextRender } from "../common/util/render-status";
|
||||
|
||||
export type ToastCloseReason =
|
||||
| "dismiss"
|
||||
@@ -19,23 +22,100 @@ export class HaToast extends LitElement {
|
||||
|
||||
@property({ type: Number, attribute: "timeout-ms" }) public timeoutMs = 4000;
|
||||
|
||||
@query("wa-popover")
|
||||
private _popover?: WaPopover;
|
||||
@query("wa-popup")
|
||||
private _popup?: WaPopup;
|
||||
|
||||
@query(".toast")
|
||||
private _toast?: HTMLDivElement;
|
||||
|
||||
@state() private _active = false;
|
||||
|
||||
@state() private _visible = false;
|
||||
|
||||
private _dismissTimer?: ReturnType<typeof setTimeout>;
|
||||
|
||||
private _closeReason: ToastCloseReason = "programmatic";
|
||||
|
||||
private _transitionId = 0;
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
clearTimeout(this._dismissTimer);
|
||||
this._transitionId += 1;
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
public async show(): Promise<void> {
|
||||
await this.updateComplete;
|
||||
await this._popover?.show();
|
||||
|
||||
clearTimeout(this._dismissTimer);
|
||||
|
||||
if (this._active && this._visible) {
|
||||
this._popup?.reposition();
|
||||
this._setDismissTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
const transitionId = ++this._transitionId;
|
||||
|
||||
this._active = true;
|
||||
await this.updateComplete;
|
||||
|
||||
if (transitionId !== this._transitionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._popup?.reposition();
|
||||
await nextRender();
|
||||
|
||||
if (transitionId !== this._transitionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._visible = true;
|
||||
await this.updateComplete;
|
||||
await this._waitForTransitionEnd();
|
||||
|
||||
if (transitionId !== this._transitionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._setDismissTimer();
|
||||
}
|
||||
|
||||
public async hide(reason: ToastCloseReason = "programmatic"): Promise<void> {
|
||||
clearTimeout(this._dismissTimer);
|
||||
this._closeReason = reason;
|
||||
|
||||
if (!this._active) {
|
||||
return;
|
||||
}
|
||||
|
||||
const transitionId = ++this._transitionId;
|
||||
const wasVisible = this._visible;
|
||||
|
||||
this._visible = false;
|
||||
await this.updateComplete;
|
||||
|
||||
if (wasVisible) {
|
||||
await this._waitForTransitionEnd();
|
||||
}
|
||||
|
||||
if (transitionId !== this._transitionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._active = false;
|
||||
await this.updateComplete;
|
||||
|
||||
fireEvent(this, "toast-closed", {
|
||||
reason: this._closeReason,
|
||||
});
|
||||
this._closeReason = "programmatic";
|
||||
}
|
||||
|
||||
public close(reason: ToastCloseReason = "programmatic"): void {
|
||||
this.hide(reason);
|
||||
}
|
||||
|
||||
private _setDismissTimer() {
|
||||
if (this.timeoutMs > 0) {
|
||||
this._dismissTimer = setTimeout(() => {
|
||||
this.hide("timeout");
|
||||
@@ -43,53 +123,53 @@ export class HaToast extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
public async hide(reason: ToastCloseReason = "programmatic"): Promise<void> {
|
||||
clearTimeout(this._dismissTimer);
|
||||
this._closeReason = reason;
|
||||
await this._popover?.hide();
|
||||
}
|
||||
private async _waitForTransitionEnd(): Promise<void> {
|
||||
const toastEl = this._toast;
|
||||
if (!toastEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
public close(reason: ToastCloseReason = "programmatic"): void {
|
||||
this.hide(reason);
|
||||
}
|
||||
const animations = toastEl.getAnimations({ subtree: true });
|
||||
if (animations.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
private _handleAfterHide() {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent<ToastClosedEventDetail>("toast-closed", {
|
||||
detail: { reason: this._closeReason },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
);
|
||||
this._closeReason = "programmatic";
|
||||
await Promise.allSettled(animations.map((animation) => animation.finished));
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<div id="toast-anchor" aria-hidden="true"></div>
|
||||
<wa-popover
|
||||
for="toast-anchor"
|
||||
<wa-popup
|
||||
placement="top"
|
||||
distance="16"
|
||||
.active=${this._active}
|
||||
.distance=${16}
|
||||
skidding="0"
|
||||
without-arrow
|
||||
@wa-after-hide=${this._handleAfterHide}
|
||||
flip
|
||||
shift
|
||||
>
|
||||
<div class="toast" role="status" aria-live="polite">
|
||||
<div id="toast-anchor" slot="anchor" aria-hidden="true"></div>
|
||||
<div
|
||||
class=${classMap({
|
||||
toast: true,
|
||||
visible: this._visible,
|
||||
})}
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
>
|
||||
<span class="message">${this.labelText}</span>
|
||||
<div class="actions">
|
||||
<slot name="action"></slot>
|
||||
<slot name="dismiss"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</wa-popover>
|
||||
</wa-popup>
|
||||
`;
|
||||
}
|
||||
|
||||
static override styles = css`
|
||||
#toast-anchor {
|
||||
position: fixed;
|
||||
bottom: calc(8px + var(--safe-area-inset-bottom));
|
||||
bottom: calc(var(--ha-space-2) + var(--safe-area-inset-bottom));
|
||||
inset-inline-start: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 1px;
|
||||
@@ -98,22 +178,11 @@ export class HaToast extends LitElement {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
wa-popover {
|
||||
--arrow-size: 0;
|
||||
--max-width: min(
|
||||
650px,
|
||||
calc(
|
||||
100vw -
|
||||
16px - var(--safe-area-inset-left) - var(--safe-area-inset-right)
|
||||
)
|
||||
);
|
||||
--show-duration: var(--ha-animation-duration-fast, 150ms);
|
||||
--hide-duration: var(--ha-animation-duration-fast, 150ms);
|
||||
}
|
||||
|
||||
wa-popover::part(body) {
|
||||
wa-popup::part(popup) {
|
||||
padding: 0;
|
||||
border-radius: 4px;
|
||||
border-radius: var(--ha-border-radius-sm);
|
||||
box-shadow: var(--wa-shadow-l);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.toast {
|
||||
@@ -121,8 +190,9 @@ export class HaToast extends LitElement {
|
||||
min-width: min(
|
||||
350px,
|
||||
calc(
|
||||
100vw -
|
||||
16px - var(--safe-area-inset-left) - var(--safe-area-inset-right)
|
||||
100vw - var(--ha-space-4) - var(--safe-area-inset-left) - var(
|
||||
--safe-area-inset-right
|
||||
)
|
||||
)
|
||||
);
|
||||
max-width: 650px;
|
||||
@@ -131,8 +201,19 @@ export class HaToast extends LitElement {
|
||||
align-items: center;
|
||||
gap: var(--ha-space-2);
|
||||
padding: var(--ha-space-2) var(--ha-space-3);
|
||||
color: var(--inverse-primary-text-color);
|
||||
background-color: var(--inverse-surface-color);
|
||||
color: var(--ha-color-on-neutral-loud);
|
||||
background-color: var(--ha-color-neutral-10);
|
||||
border-radius: var(--ha-border-radius-sm);
|
||||
opacity: 0;
|
||||
transform: translateY(var(--ha-space-2));
|
||||
transition:
|
||||
opacity var(--ha-animation-duration-fast, 150ms) ease,
|
||||
transform var(--ha-animation-duration-fast, 150ms) ease;
|
||||
}
|
||||
|
||||
.toast.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.message {
|
||||
@@ -144,23 +225,27 @@ export class HaToast extends LitElement {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--ha-space-2);
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
color: var(--ha-color-on-neutral-loud);
|
||||
}
|
||||
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
wa-popup::part(popup) {
|
||||
border-radius: var(--ha-border-radius-square);
|
||||
}
|
||||
|
||||
.toast {
|
||||
min-width: calc(
|
||||
100vw - var(--safe-area-inset-left) - var(--safe-area-inset-right)
|
||||
);
|
||||
border-radius: 0;
|
||||
border-radius: var(--ha-border-radius-square);
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementEventMap {
|
||||
"toast-closed": CustomEvent<ToastClosedEventDetail>;
|
||||
interface HASSDomEvents {
|
||||
"toast-closed": ToastClosedEventDetail;
|
||||
}
|
||||
|
||||
interface HTMLElementTagNameMap {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import type { HASSDomEvent } from "../common/dom/fire_event";
|
||||
import type { LocalizeKeys } from "../common/translations/localize";
|
||||
import "../components/ha-button";
|
||||
import "../components/ha-icon-button";
|
||||
import "../components/ha-toast";
|
||||
import type { ToastClosedEventDetail } from "../components/ha-toast";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export interface ShowToastParams {
|
||||
@@ -39,7 +41,7 @@ class NotificationManager extends LitElement {
|
||||
await this._toast?.hide();
|
||||
}
|
||||
|
||||
if (!parameters || parameters.duration === 0) {
|
||||
if (parameters.duration === 0) {
|
||||
this._parameters = undefined;
|
||||
return;
|
||||
}
|
||||
@@ -57,7 +59,7 @@ class NotificationManager extends LitElement {
|
||||
this._toast?.show();
|
||||
}
|
||||
|
||||
private _toastClosed(_ev: HTMLElementEventMap["toast-closed"]) {
|
||||
private _toastClosed(_ev: HASSDomEvent<ToastClosedEventDetail>) {
|
||||
this._parameters = undefined;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user