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