UNPKG

@ngxpert/hot-toast

Version:

Smoking hot Notifications for Angular. Lightweight, customizable and beautiful by default.

1 lines 141 kB
{"version":3,"file":"ngxpert-hot-toast.mjs","sources":["../../../../projects/ngxpert/hot-toast/src/lib/constants.ts","../../../../projects/ngxpert/hot-toast/src/lib/hot-toast-ref.ts","../../../../projects/ngxpert/hot-toast/src/lib/utils.ts","../../../../projects/ngxpert/hot-toast/src/lib/components/indicator/icons/loader/loader.component.ts","../../../../projects/ngxpert/hot-toast/src/lib/components/indicator/icons/loader/loader.component.html","../../../../projects/ngxpert/hot-toast/src/lib/components/indicator/icons/error/error.component.ts","../../../../projects/ngxpert/hot-toast/src/lib/components/indicator/icons/error/error.component.html","../../../../projects/ngxpert/hot-toast/src/lib/components/indicator/icons/checkmark/checkmark.component.ts","../../../../projects/ngxpert/hot-toast/src/lib/components/indicator/icons/checkmark/checkmark.component.html","../../../../projects/ngxpert/hot-toast/src/lib/components/indicator/icons/warning/warning.component.ts","../../../../projects/ngxpert/hot-toast/src/lib/components/indicator/icons/warning/warning.component.html","../../../../projects/ngxpert/hot-toast/src/lib/components/indicator/icons/info/info.component.ts","../../../../projects/ngxpert/hot-toast/src/lib/components/indicator/icons/info/info.component.html","../../../../projects/ngxpert/hot-toast/src/lib/components/indicator/indicator.component.ts","../../../../projects/ngxpert/hot-toast/src/lib/components/indicator/indicator.component.html","../../../../projects/ngxpert/hot-toast/src/lib/components/animated-icon/animated-icon.component.ts","../../../../projects/ngxpert/hot-toast/src/lib/components/animated-icon/animated-icon.component.html","../../../../projects/ngxpert/hot-toast/src/lib/components/hot-toast-group-item/hot-toast-group-item.component.ts","../../../../projects/ngxpert/hot-toast/src/lib/components/hot-toast-group-item/hot-toast-group-item.component.html","../../../../projects/ngxpert/hot-toast/src/lib/components/hot-toast/hot-toast.component.ts","../../../../projects/ngxpert/hot-toast/src/lib/components/hot-toast/hot-toast.component.html","../../../../projects/ngxpert/hot-toast/src/lib/components/hot-toast-container/hot-toast-container.component.ts","../../../../projects/ngxpert/hot-toast/src/lib/components/hot-toast-container/hot-toast-container.component.html","../../../../projects/ngxpert/hot-toast/src/lib/hot-toast.model.ts","../../../../projects/ngxpert/hot-toast/src/lib/tokens.ts","../../../../projects/ngxpert/hot-toast/src/lib/hot-toast.service.ts","../../../../projects/ngxpert/hot-toast/src/lib/hot-toast.provide.ts","../../../../projects/ngxpert/hot-toast/src/lib/hot-toast-http-interceptor.model.ts","../../../../projects/ngxpert/hot-toast/src/lib/hot-toast-http-interceptor.ts","../../../../projects/ngxpert/hot-toast/src/lib/hot-toast-builder.ts","../../../../projects/ngxpert/hot-toast/src/public-api.ts","../../../../projects/ngxpert/hot-toast/src/ngxpert-hot-toast.ts"],"sourcesContent":["import { ToastType } from './hot-toast.model';\n\nexport const HOT_TOAST_DEFAULT_TIMEOUTS: {\n [key in ToastType]: number;\n} = {\n blank: 4000,\n error: 4000,\n success: 4000,\n loading: 30000,\n warning: 4000,\n info: 4000,\n};\n\nexport const EXIT_ANIMATION_DURATION = 800;\nexport const ENTER_ANIMATION_DURATION = 350;\n\nexport const HOT_TOAST_MARGIN = 8;\n\nexport const HOT_TOAST_DEPTH_SCALE = 0.05;\nexport const HOT_TOAST_DEPTH_SCALE_ADD = 1;","import { Content } from '@ngneat/overview';\nimport { Observable, race, Subject } from 'rxjs';\n\n// This should be a `type` import since it causes `ng-packagr` compilation to fail because of a cyclic dependency.\nimport type { HotToastContainerComponent } from './components/hot-toast-container/hot-toast-container.component';\nimport {\n HotToastClose,\n Toast,\n UpdateToastOptions,\n HotToastRefProps,\n DefaultDataType,\n CreateHotToastRef,\n HotToastGroupEvent,\n} from './hot-toast.model';\n\nexport class HotToastRef<DataType = DefaultDataType> implements HotToastRefProps<DataType> {\n updateMessage: (message: Content) => void;\n updateToast: (options: UpdateToastOptions<DataType>) => void;\n afterClosed: Observable<HotToastClose>;\n afterGroupToggled: Observable<HotToastGroupEvent>;\n afterGroupRefsAttached: Observable<CreateHotToastRef<unknown>[]>;\n groupRefs: CreateHotToastRef<unknown>[] = [];\n groupExpanded = false;\n\n private _dispose: () => void;\n\n /** Subject for notifying the user that the toast has been closed. */\n private _onClosed = new Subject<HotToastClose>();\n\n /** Subject for notifying the user that the toast has been closed. */\n private _onGroupToggle = new Subject<HotToastGroupEvent>();\n\n constructor(private toast: Toast<DataType>) {}\n\n set data(data: DataType) {\n this.toast.data = data;\n }\n\n get data() {\n return this.toast.data;\n }\n\n set dispose(value: () => void) {\n this._dispose = value;\n }\n\n getToast() {\n return this.toast;\n }\n\n /**\n * Used for internal purpose\n * Attach ToastRef to container\n */\n appendTo(container: HotToastContainerComponent, skipAttachToParent?: boolean) {\n const { dispose, updateMessage, updateToast, afterClosed, afterGroupToggled, afterGroupRefsAttached } =\n container.addToast(this, skipAttachToParent);\n\n this.dispose = dispose;\n this.updateMessage = updateMessage;\n this.updateToast = updateToast;\n this.afterClosed = race(this._onClosed.asObservable(), afterClosed);\n this.afterGroupToggled = race(this._onGroupToggle.asObservable(), afterGroupToggled);\n this.afterGroupRefsAttached = afterGroupRefsAttached;\n return this;\n }\n\n /**\n * Closes the toast\n *\n * @param [closeData={ dismissedByAction: false }] -\n * Make sure to pass { dismissedByAction: true } when closing from template\n * @memberof HotToastRef\n */\n close(closeData: { dismissedByAction: boolean } = { dismissedByAction: false }) {\n this.groupRefs.forEach((ref) => ref.close());\n this._dispose();\n this._onClosed.next({ dismissedByAction: closeData.dismissedByAction, id: this.toast.id });\n this._onClosed.complete();\n }\n\n toggleGroup(eventData: { byAction: boolean } = { byAction: false }) {\n this.groupExpanded = !this.groupExpanded;\n this._onGroupToggle.next({\n byAction: eventData.byAction,\n id: this.toast.id,\n event: this.groupExpanded ? 'expand' : 'collapse',\n });\n }\n\n show() {\n this.toast.visible = true;\n }\n}\n","import { Renderer2 } from '@angular/core';\n\nexport const animate = (renderer: Renderer2, element: HTMLElement, animation: string) => {\n renderer.setStyle(element, 'animation', animation);\n};\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\n\nimport { IconTheme } from '../../../../hot-toast.model';\n\n@Component({\n selector: 'hot-toast-loader',\n templateUrl: './loader.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class LoaderComponent {\n readonly theme = input<IconTheme>();\n}\n","<div\n class=\"hot-toast-loader-icon\"\n [style.border-color]=\"theme()?.primary\"\n [style.border-right-color]=\"theme()?.secondary\"\n></div>\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\nimport { IconTheme } from '../../../../hot-toast.model';\n\n@Component({\n selector: 'hot-toast-error',\n templateUrl: './error.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n standalone: true,\n})\nexport class ErrorComponent {\n readonly theme = input<IconTheme>();\n}\n","<div\n class=\"hot-toast-error-icon\"\n [style.--error-primary]=\"theme()?.primary\"\n [style.--error-secondary]=\"theme()?.secondary\"\n></div>\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\nimport { IconTheme } from '../../../../hot-toast.model';\n\n@Component({\n selector: 'hot-toast-checkmark',\n templateUrl: './checkmark.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n standalone: true,\n})\nexport class CheckMarkComponent {\n readonly theme = input<IconTheme>();\n}\n","<div\n class=\"hot-toast-checkmark-icon\"\n [style.--check-primary]=\"theme()?.primary\"\n [style.--check-secondary]=\"theme()?.secondary\"\n></div>\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\nimport { IconTheme } from '../../../../hot-toast.model';\n\n@Component({\n selector: 'hot-toast-warning',\n templateUrl: './warning.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n standalone: true,\n})\nexport class WarningComponent {\n readonly theme = input<IconTheme>();\n}\n","<div\n class=\"hot-toast-warning-icon\"\n [style.--warn-primary]=\"theme()?.primary\"\n [style.--warn-secondary]=\"theme()?.secondary\"\n></div>\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\nimport { IconTheme } from '../../../../hot-toast.model';\n\n@Component({\n selector: 'hot-toast-info',\n templateUrl: './info.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n standalone: true,\n})\nexport class InfoComponent {\n readonly theme = input<IconTheme>();\n}\n","<div\n class=\"hot-toast-info-icon\"\n [style.--info-primary]=\"theme()?.primary\"\n [style.--info-secondary]=\"theme()?.secondary\"\n></div>\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\n\nimport { IconTheme, ToastType } from '../../hot-toast.model';\nimport { LoaderComponent } from './icons/loader/loader.component';\nimport { ErrorComponent } from './icons/error/error.component';\nimport { CheckMarkComponent } from './icons/checkmark/checkmark.component';\nimport { WarningComponent } from './icons/warning/warning.component';\nimport { InfoComponent } from './icons/info/info.component';\n\n@Component({\n selector: 'hot-toast-indicator',\n templateUrl: 'indicator.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [LoaderComponent, ErrorComponent, CheckMarkComponent, WarningComponent, InfoComponent],\n})\nexport class IndicatorComponent {\n readonly theme = input<IconTheme>();\n readonly type = input<ToastType>();\n}\n","@if (type() !== 'blank') {\n<div class=\"hot-toast-indicator-wrapper\">\n @if (type() === 'loading') {\n <hot-toast-loader [theme]=\"theme()\" />\n } @if (type() !== 'loading') {\n <div class=\"hot-toast-status-wrapper\">\n <div>\n @switch (type()) { @case ('error') {\n <div>\n <hot-toast-error [theme]=\"theme()\" />\n </div>\n } @case ('success') {\n <div>\n <hot-toast-checkmark [theme]=\"theme()\" />\n </div>\n } @case ('warning') {\n <div>\n <hot-toast-warning [theme]=\"theme()\" />\n </div>\n } @case ('info') {\n <div>\n <hot-toast-info [theme]=\"theme()\" />\n </div>\n } }\n </div>\n </div>\n }\n</div>\n}\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\nimport { IconTheme } from '../../hot-toast.model';\nimport { Content, DynamicViewDirective } from '@ngneat/overview';\n\n@Component({\n selector: 'hot-toast-animated-icon',\n templateUrl: './animated-icon.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n standalone: true,\n imports: [DynamicViewDirective],\n})\nexport class AnimatedIconComponent {\n readonly iconTheme = input<IconTheme>();\n readonly icon = input<Content>();\n}\n","<div class=\"hot-toast-animated-icon\" [style.color]=\"iconTheme()?.primary\">\n <ng-container *dynamicView=\"icon()\" />\n</div>\n","import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n Injector,\n Input,\n NgZone,\n Renderer2,\n SimpleChanges,\n ViewChild,\n OnChanges,\n OnInit,\n AfterViewInit,\n OnDestroy,\n signal,\n ChangeDetectorRef,\n inject,\n input,\n output\n} from '@angular/core';\nimport { AnimatedIconComponent } from '../animated-icon/animated-icon.component';\nimport { IndicatorComponent } from '../indicator/indicator.component';\nimport { DynamicViewDirective, isComponent, isTemplateRef } from '@ngneat/overview';\nimport { ENTER_ANIMATION_DURATION, EXIT_ANIMATION_DURATION, HOT_TOAST_DEPTH_SCALE } from '../../constants';\nimport { HotToastRef } from '../../hot-toast-ref';\nimport { Toast, ToastConfig, CreateHotToastRef, HotToastClose, HotToastGroupEvent } from '../../hot-toast.model';\nimport { animate } from '../../utils';\n\n@Component({\n selector: 'hot-toast-group-item',\n templateUrl: 'hot-toast-group-item.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [AnimatedIconComponent, IndicatorComponent, DynamicViewDirective],\n})\nexport class HotToastGroupItemComponent implements OnChanges, OnInit, AfterViewInit, OnDestroy {\n private _toast: Toast<unknown>;\n @Input()\n set toast(value: Toast<unknown>) {\n this._toast = value;\n const ogStyle = this.toastBarBaseStylesSignal();\n const newStyle: Record<string, string> = { ...value.style };\n\n if (ogStyle['animation']?.includes('hotToastExitAnimation')) {\n // if toast is set for exit, we don't need want set the enter animation\n newStyle['animation'] = ogStyle['animation'];\n } else {\n const top = value.position.includes('top');\n const enterAnimation = `hotToastEnterAnimation${\n top ? 'Negative' : 'Positive'\n } ${ENTER_ANIMATION_DURATION}ms cubic-bezier(0.21, 1.02, 0.73, 1) forwards`;\n newStyle['animation'] = enterAnimation;\n }\n\n this.toastBarBaseStylesSignal.set(newStyle);\n }\n get toast() {\n return this._toast;\n }\n readonly offset = input(0);\n readonly defaultConfig = input<ToastConfig>();\n readonly toastRef = input<CreateHotToastRef<unknown>>();\n\n private _toastsAfter = 0;\n get toastsAfter() {\n return this._toastsAfter;\n }\n @Input()\n set toastsAfter(value) {\n this._toastsAfter = value;\n }\n\n readonly isShowingAllToasts = input(false);\n\n readonly height = output<number>();\n readonly beforeClosed = output();\n readonly afterClosed = output<HotToastClose>();\n readonly showAllToasts = output<boolean>();\n readonly toggleGroup = output<HotToastGroupEvent>();\n\n @ViewChild('hotToastBarBase', { static: true }) protected toastBarBase: ElementRef<HTMLElement>;\n\n isManualClose = false;\n context: Record<string, unknown>;\n toastComponentInjector: Injector;\n toastBarBaseStylesSignal = signal({});\n\n private unlisteners: VoidFunction[] = [];\n protected softClosed = false;\n\n private injector = inject(Injector);\n private renderer = inject(Renderer2);\n private ngZone = inject(NgZone);\n private cdr = inject(ChangeDetectorRef);\n\n get toastBarBaseHeight() {\n return this.toastBarBase.nativeElement.offsetHeight;\n }\n\n get scale() {\n return this.defaultConfig().stacking !== 'vertical' && !this.isShowingAllToasts()\n ? this.toastsAfter * -HOT_TOAST_DEPTH_SCALE + 1\n : 1;\n }\n\n get translateY() {\n return this.offset() * (this.top ? 1 : -1) + 'px';\n }\n\n get exitAnimationDelay() {\n return this.toast.duration + 'ms';\n }\n\n get top() {\n return this.toast.position.includes('top');\n }\n\n get containerPositionStyle() {\n const verticalStyle = this.top ? { top: 0 } : { bottom: 0 };\n const transform = `translateY(var(--hot-toast-translate-y)) scale(var(--hot-toast-scale))`;\n\n const horizontalStyle = this.toast.position.includes('left')\n ? {\n left: 0,\n }\n : this.toast.position.includes('right')\n ? {\n right: 0,\n }\n : {\n left: 0,\n right: 0,\n justifyContent: 'center',\n };\n return {\n transform,\n ...verticalStyle,\n ...horizontalStyle,\n };\n }\n\n get isIconString() {\n return typeof this.toast.icon === 'string';\n }\n\n get groupChildrenToastRefs() {\n return this.toastRef().groupRefs.filter((ref) => !!ref);\n }\n set groupChildrenToastRefs(value: CreateHotToastRef<unknown>[]) {\n (this.toastRef() as { groupRefs: CreateHotToastRef<unknown>[] }).groupRefs = value;\n }\n\n get groupChildrenToasts() {\n return this.groupChildrenToastRefs.map((ref) => ref.getToast());\n }\n\n get groupHeight() {\n return this.visibleToasts.map((t) => t.height).reduce((prev, curr) => prev + curr, 0);\n }\n\n get isExpanded() {\n return this.toastRef().groupExpanded;\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes.toast && !changes.toast.firstChange && changes.toast.currentValue?.message) {\n requestAnimationFrame(() => {\n this.height.emit(this.toastBarBase.nativeElement.offsetHeight);\n });\n }\n }\n\n ngOnInit() {\n if (isTemplateRef(this.toast.message)) {\n this.context = { $implicit: this.toastRef() };\n }\n if (isComponent(this.toast.message)) {\n this.toastComponentInjector = Injector.create({\n providers: [\n {\n provide: HotToastRef,\n useValue: this.toastRef(),\n },\n ],\n parent: this.toast.injector || this.injector,\n });\n }\n\n const nativeElement = this.toastBarBase.nativeElement;\n // Caretaker note: `animationstart` and `animationend` events are event tasks that trigger change detection.\n // We'd want to trigger the change detection only if it's an exit animation.\n this.ngZone.runOutsideAngular(() => {\n this.unlisteners.push(\n // Caretaker note: we have to remove these event listeners at the end (even if the element is removed from DOM).\n this.renderer.listen(nativeElement, 'animationstart', (event: AnimationEvent) => {\n if (this.isExitAnimation(event)) {\n this.ngZone.run(() => {\n this.renderer.setStyle(nativeElement, 'pointer-events', 'none');\n this.renderer.setStyle(nativeElement.parentElement, 'pointer-events', 'none');\n this.beforeClosed.emit();\n });\n }\n }),\n this.renderer.listen(nativeElement, 'animationend', (event: AnimationEvent) => {\n if (this.isEnterAnimation(event)) {\n this.ngZone.run(() => {\n if (this.toast.autoClose) {\n const exitAnimation = `hotToastExitAnimation${\n this.top ? 'Negative' : 'Positive'\n } ${EXIT_ANIMATION_DURATION}ms forwards cubic-bezier(0.06, 0.71, 0.55, 1) var(--hot-toast-exit-animation-delay) var(--hot-toast-exit-animation-state)`;\n this.toastBarBaseStylesSignal.set({ ...this.toast.style, animation: exitAnimation });\n }\n });\n }\n if (this.isExitAnimation(event)) {\n this.ngZone.run(() => this.afterClosed.emit({ dismissedByAction: this.isManualClose, id: this.toast.id }));\n }\n }),\n );\n });\n }\n\n ngAfterViewInit() {\n const nativeElement = this.toastBarBase.nativeElement;\n // Caretaker note: accessing `offsetHeight` triggers the whole layout update.\n // Macro tasks (like `setTimeout`) might be executed within the current rendering frame and cause a frame drop.\n requestAnimationFrame(() => {\n this.height.emit(nativeElement.offsetHeight);\n });\n\n this.setToastAttributes();\n }\n\n softClose() {\n const exitAnimation = `hotToastExitSoftAnimation${\n this.top ? 'Negative' : 'Positive'\n } ${EXIT_ANIMATION_DURATION}ms forwards cubic-bezier(0.06, 0.71, 0.55, 1)`;\n\n const nativeElement = this.toastBarBase.nativeElement;\n\n animate(this.renderer, nativeElement, exitAnimation);\n this.softClosed = true;\n }\n softOpen() {\n const softEnterAnimation = `hotToastEnterSoftAnimation${\n top ? 'Negative' : 'Positive'\n } ${ENTER_ANIMATION_DURATION}ms cubic-bezier(0.21, 1.02, 0.73, 1) forwards`;\n\n const nativeElement = this.toastBarBase.nativeElement;\n\n animate(this.renderer, nativeElement, softEnterAnimation);\n this.softClosed = false;\n }\n\n close() {\n this.isManualClose = true;\n this.cdr.markForCheck();\n\n const exitAnimation = `hotToastExitAnimation${\n this.top ? 'Negative' : 'Positive'\n } ${EXIT_ANIMATION_DURATION}ms forwards cubic-bezier(0.06, 0.71, 0.55, 1)`;\n this.toastBarBaseStylesSignal.set({ ...this.toast.style, animation: exitAnimation });\n }\n\n handleMouseEnter() {\n this.showAllToasts.emit(true);\n }\n handleMouseLeave() {\n this.showAllToasts.emit(false);\n }\n\n ngOnDestroy() {\n this.close();\n while (this.unlisteners.length) {\n this.unlisteners.pop()();\n }\n }\n\n private isExitAnimation(ev: AnimationEvent) {\n return ev.animationName.includes('hotToastExitAnimation');\n }\n\n private isEnterAnimation(ev: AnimationEvent) {\n return ev.animationName.includes('hotToastEnterAnimation');\n }\n\n private setToastAttributes() {\n const toastAttributes: Record<string, string> = this.toast.attributes;\n for (const [key, value] of Object.entries(toastAttributes)) {\n this.renderer.setAttribute(this.toastBarBase.nativeElement, key, value);\n }\n }\n\n get visibleToasts() {\n return this.groupChildrenToasts.filter((t) => t.visible);\n }\n}\n","<div\n class=\"hot-toast-bar-base-container\"\n [style]=\"containerPositionStyle\"\n [class]=\"'hot-toast-theme-' + toast.theme\"\n [style.--hot-toast-scale]=\"scale\"\n [style.--hot-toast-translate-y]=\"translateY\"\n>\n <div class=\"hot-toast-bar-base-wrapper\" (mouseenter)=\"handleMouseEnter()\" (mouseleave)=\"handleMouseLeave()\">\n <div\n class=\"hot-toast-bar-base\"\n #hotToastBarBase\n [style]=\"toastBarBaseStylesSignal()\"\n [class]=\"toast.className\"\n [style.--hot-toast-animation-state]=\"isManualClose ? 'running' : 'paused'\"\n [style.--hot-toast-exit-animation-state]=\"isShowingAllToasts() ? 'paused' : 'running'\"\n [style.--hot-toast-exit-animation-delay]=\"exitAnimationDelay\"\n [attr.aria-live]=\"toast.ariaLive\"\n [attr.role]=\"toast.role\"\n >\n <div class=\"hot-toast-icon\" aria-hidden=\"true\">\n @if (toast.icon !== undefined) { @if (isIconString) {\n <hot-toast-animated-icon [iconTheme]=\"toast.iconTheme\">{{ toast.icon }}</hot-toast-animated-icon>\n } @else {\n <div>\n <ng-container *dynamicView=\"toast.icon\" />\n </div>\n } } @else {\n <hot-toast-indicator [theme]=\"toast.iconTheme\" [type]=\"toast.type\" />\n }\n </div>\n <div class=\"hot-toast-message\">\n <ng-container *dynamicView=\"toast.message; context: context; injector: toastComponentInjector\" />\n </div>\n @if (toast.dismissible) {\n <button\n (click)=\"close()\"\n type=\"button\"\n class=\"hot-toast-close-btn\"\n aria-label=\"Close\"\n [style]=\"toast.closeStyle\"\n ></button>\n }\n </div>\n </div>\n</div>\n","import {\n AfterViewInit,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n DoCheck,\n ElementRef,\n inject,\n Injector,\n Input,\n NgZone,\n OnChanges,\n OnDestroy,\n OnInit,\n Renderer2,\n signal,\n SimpleChanges,\n ViewChild,\n input,\n output,\n} from '@angular/core';\nimport { DynamicViewDirective, isComponent, isTemplateRef } from '@ngneat/overview';\n\nimport { ENTER_ANIMATION_DURATION, EXIT_ANIMATION_DURATION, HOT_TOAST_DEPTH_SCALE } from '../../constants';\nimport { HotToastRef } from '../../hot-toast-ref';\nimport { CreateHotToastRef, HotToastClose, HotToastGroupEvent, Toast, ToastConfig } from '../../hot-toast.model';\nimport { animate } from '../../utils';\nimport { IndicatorComponent } from '../indicator/indicator.component';\nimport { AnimatedIconComponent } from '../animated-icon/animated-icon.component';\nimport { HotToastGroupItemComponent } from '../hot-toast-group-item/hot-toast-group-item.component';\n\n@Component({\n selector: 'hot-toast-component',\n templateUrl: 'hot-toast.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [DynamicViewDirective, IndicatorComponent, AnimatedIconComponent, HotToastGroupItemComponent],\n})\nexport class HotToastComponent implements OnInit, AfterViewInit, OnDestroy, OnChanges, DoCheck {\n private _toast: Toast<unknown>;\n @Input()\n set toast(value: Toast<unknown>) {\n this._toast = value;\n const ogStyle = this.toastBarBaseStylesSignal();\n const newStyle: Record<string, string> = { ...value.style };\n\n if (ogStyle['animation']?.includes('hotToastExitAnimation')) {\n // if toast is set for exit, we don't need want set the enter animation\n newStyle['animation'] = ogStyle['animation'];\n } else {\n const top = value.position.includes('top');\n const enterAnimation = `hotToastEnterAnimation${\n top ? 'Negative' : 'Positive'\n } ${ENTER_ANIMATION_DURATION}ms cubic-bezier(0.21, 1.02, 0.73, 1) forwards`;\n newStyle['animation'] = enterAnimation;\n }\n\n this.toastBarBaseStylesSignal.set(newStyle);\n }\n get toast() {\n return this._toast;\n }\n readonly offset = input(0);\n readonly defaultConfig = input<ToastConfig>();\n readonly toastRef = input<CreateHotToastRef<unknown>>();\n\n private _toastsAfter = 0;\n get toastsAfter() {\n return this._toastsAfter;\n }\n @Input()\n set toastsAfter(value) {\n this._toastsAfter = value;\n const defaultConfig = this.defaultConfig();\n if (defaultConfig?.visibleToasts > 0) {\n if (this.toast.autoClose) {\n // if (value >= this.defaultConfig?.visibleToasts) {\n // this.close();\n // }\n } else {\n if (value >= defaultConfig?.visibleToasts) {\n this.softClose();\n } else if (this.softClosed) {\n this.softOpen();\n }\n }\n }\n }\n\n readonly isShowingAllToasts = input(false);\n\n readonly height = output<number>();\n readonly beforeClosed = output();\n readonly afterClosed = output<HotToastClose>();\n readonly showAllToasts = output<boolean>();\n readonly toggleGroup = output<HotToastGroupEvent>();\n\n @ViewChild('hotToastBarBase', { static: true }) private toastBarBase: ElementRef<HTMLElement>;\n\n isManualClose = false;\n context: Record<string, unknown>;\n toastComponentInjector: Injector;\n isExpanded = false;\n toastBarBaseStylesSignal = signal<Record<string, string>>({});\n\n private unlisteners: VoidFunction[] = [];\n private softClosed = false;\n private groupRefs: CreateHotToastRef<unknown>[] = [];\n\n private injector = inject(Injector);\n private renderer = inject(Renderer2);\n private ngZone = inject(NgZone);\n private cdr = inject(ChangeDetectorRef);\n\n get toastBarBaseHeight() {\n return this.toastBarBase.nativeElement.offsetHeight;\n }\n\n get scale() {\n return this.defaultConfig().stacking !== 'vertical' && !this.isShowingAllToasts()\n ? this.toastsAfter * -HOT_TOAST_DEPTH_SCALE + 1\n : 1;\n }\n\n get translateY() {\n return this.offset() * (this.top ? 1 : -1) + 'px';\n }\n\n get exitAnimationDelay() {\n return this.toast.duration + 'ms';\n }\n\n get top() {\n return this.toast.position.includes('top');\n }\n\n get containerPositionStyle() {\n const verticalStyle = this.top ? { top: 0 } : { bottom: 0 };\n const transform = `translateY(var(--hot-toast-translate-y)) scale(var(--hot-toast-scale))`;\n\n const horizontalStyle = this.toast.position.includes('left')\n ? {\n left: 0,\n }\n : this.toast.position.includes('right')\n ? {\n right: 0,\n }\n : {\n left: 0,\n right: 0,\n justifyContent: 'center',\n };\n return {\n transform,\n ...verticalStyle,\n ...horizontalStyle,\n };\n }\n\n get isIconString() {\n return typeof this.toast.icon === 'string';\n }\n\n get groupChildrenToastRefs() {\n return this.groupRefs.filter((ref) => !!ref);\n }\n set groupChildrenToastRefs(value: CreateHotToastRef<unknown>[]) {\n this.groupRefs = value;\n\n (this.toastRef() as { groupRefs: CreateHotToastRef<unknown>[] }).groupRefs = value;\n }\n\n get groupChildrenToasts() {\n return this.groupChildrenToastRefs.map((ref) => ref.getToast());\n }\n\n get groupHeight() {\n return this.visibleToasts\n .slice(-this.defaultConfig().visibleToasts)\n .map((t) => t.height)\n .reduce((prev, curr) => prev + curr, 0);\n }\n\n get visibleToasts() {\n return this.groupChildrenToasts.filter((t) => t.visible);\n }\n\n ngDoCheck() {\n const toastRef = this.toastRef();\n if (toastRef.groupRefs.length !== this.groupRefs.length) {\n this.groupRefs = toastRef.groupRefs.slice();\n this.cdr.markForCheck();\n\n this.emiHeightWithGroup(this.isExpanded);\n }\n if (toastRef.groupExpanded !== this.isExpanded) {\n this.isExpanded = toastRef.groupExpanded;\n this.cdr.markForCheck();\n\n this.emiHeightWithGroup(this.isExpanded);\n }\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes.toast && !changes.toast.firstChange && changes.toast.currentValue?.message) {\n (this, this.emiHeightWithGroup(this.isExpanded));\n }\n }\n\n ngOnInit() {\n if (isTemplateRef(this.toast.message)) {\n this.context = { $implicit: this.toastRef() };\n }\n if (isComponent(this.toast.message)) {\n this.toastComponentInjector = Injector.create({\n providers: [\n {\n provide: HotToastRef,\n useValue: this.toastRef(),\n },\n ],\n parent: this.toast.injector || this.injector,\n });\n }\n\n const nativeElement = this.toastBarBase.nativeElement;\n // Caretaker note: `animationstart` and `animationend` events are event tasks that trigger change detection.\n // We'd want to trigger the change detection only if it's an exit animation.\n this.ngZone.runOutsideAngular(() => {\n this.unlisteners.push(\n // Caretaker note: we have to remove these event listeners at the end (even if the element is removed from DOM).\n this.renderer.listen(nativeElement, 'animationstart', (event: AnimationEvent) => {\n if (this.isExitAnimation(event)) {\n this.ngZone.run(() => {\n this.renderer.setStyle(nativeElement, 'pointer-events', 'none');\n this.renderer.setStyle(nativeElement.parentElement, 'pointer-events', 'none');\n this.beforeClosed.emit();\n });\n }\n }),\n this.renderer.listen(nativeElement, 'animationend', (event: AnimationEvent) => {\n if (this.isEnterAnimation(event)) {\n this.ngZone.run(() => {\n if (this.toast.autoClose) {\n const exitAnimation = `hotToastExitAnimation${\n this.top ? 'Negative' : 'Positive'\n } ${EXIT_ANIMATION_DURATION}ms forwards cubic-bezier(0.06, 0.71, 0.55, 1) var(--hot-toast-exit-animation-delay) var(--hot-toast-exit-animation-state)`;\n this.toastBarBaseStylesSignal.set({ ...this.toast.style, animation: exitAnimation });\n }\n });\n }\n if (this.isExitAnimation(event)) {\n this.ngZone.run(() => this.afterClosed.emit({ dismissedByAction: this.isManualClose, id: this.toast.id }));\n }\n }),\n );\n });\n }\n\n ngAfterViewInit() {\n const nativeElement = this.toastBarBase.nativeElement;\n // Caretaker note: accessing `offsetHeight` triggers the whole layout update.\n // Macro tasks (like `setTimeout`) might be executed within the current rendering frame and cause a frame drop.\n requestAnimationFrame(() => {\n this.height.emit(nativeElement.offsetHeight);\n });\n\n this.setToastAttributes();\n }\n\n softClose() {\n const exitAnimation = `hotToastExitSoftAnimation${\n this.top ? 'Negative' : 'Positive'\n } ${EXIT_ANIMATION_DURATION}ms forwards cubic-bezier(0.06, 0.71, 0.55, 1)`;\n\n const nativeElement = this.toastBarBase.nativeElement;\n\n animate(this.renderer, nativeElement, exitAnimation);\n this.softClosed = true;\n\n if (this.isExpanded) {\n this.toggleToastGroup();\n }\n }\n\n softOpen() {\n const softEnterAnimation = `hotToastEnterSoftAnimation${\n top ? 'Negative' : 'Positive'\n } ${ENTER_ANIMATION_DURATION}ms cubic-bezier(0.21, 1.02, 0.73, 1) forwards`;\n\n const nativeElement = this.toastBarBase.nativeElement;\n\n animate(this.renderer, nativeElement, softEnterAnimation);\n this.softClosed = false;\n }\n\n close() {\n this.isManualClose = true;\n this.cdr.markForCheck();\n\n const exitAnimation = `hotToastExitAnimation${\n this.top ? 'Negative' : 'Positive'\n } ${EXIT_ANIMATION_DURATION}ms forwards cubic-bezier(0.06, 0.71, 0.55, 1)`;\n\n this.toastBarBaseStylesSignal.set({ ...this.toast.style, animation: exitAnimation });\n }\n\n handleMouseEnter() {\n this.showAllToasts.emit(true);\n }\n handleMouseLeave() {\n this.showAllToasts.emit(false);\n }\n\n ngOnDestroy() {\n this.close();\n while (this.unlisteners.length) {\n this.unlisteners.pop()();\n }\n }\n\n private isExitAnimation(ev: AnimationEvent) {\n return ev.animationName.includes('hotToastExitAnimation');\n }\n\n private isEnterAnimation(ev: AnimationEvent) {\n return ev.animationName.includes('hotToastEnterAnimation');\n }\n\n private setToastAttributes() {\n const toastAttributes: Record<string, string> = this.toast.attributes;\n for (const [key, value] of Object.entries(toastAttributes)) {\n this.renderer.setAttribute(this.toastBarBase.nativeElement, key, value);\n }\n }\n\n calculateOffset(toastId: string) {\n const visibleToasts = this.visibleToasts;\n const index = visibleToasts.findIndex((toast) => toast.id === toastId);\n const offset =\n index !== -1\n ? visibleToasts.slice(...(this.defaultConfig().reverseOrder ? [index + 1] : [0, index])).reduce((acc, t, i) => {\n const defaultConfig = this.defaultConfig();\n return defaultConfig.visibleToasts !== 0 && i < visibleToasts.length - defaultConfig.visibleToasts\n ? 0\n : acc + (t.height || 0);\n }, 0)\n : 0;\n return offset;\n }\n\n updateHeight(height: number, toast: Toast<unknown>) {\n toast.height = height;\n this.cdr.markForCheck();\n }\n\n beforeClosedGroupItem(toast: Toast<unknown>) {\n toast.visible = false;\n this.cdr.markForCheck();\n if (this.visibleToasts.length === 0 && this.isExpanded) {\n this.toggleToastGroup();\n } else {\n this.emiHeightWithGroup(this.isExpanded);\n }\n }\n\n afterClosedGroupItem(closeToast: HotToastClose) {\n const toastIndex = this.groupChildrenToasts.findIndex((t) => t.id === closeToast.id);\n if (toastIndex > -1) {\n this.groupChildrenToastRefs = this.groupChildrenToastRefs.filter((t) => t.getToast().id !== closeToast.id);\n this.cdr.markForCheck();\n }\n }\n\n toggleToastGroup() {\n const event = this.isExpanded ? 'collapse' : 'expand';\n this.toggleGroup.emit({\n byAction: true,\n event,\n id: this.toast.id,\n });\n this.emiHeightWithGroup(event === 'expand');\n }\n\n private emiHeightWithGroup(isExpanded: boolean) {\n if (isExpanded) {\n requestAnimationFrame(() => {\n this.height.emit(this.toastBarBase.nativeElement.offsetHeight + this.groupHeight);\n });\n } else {\n requestAnimationFrame(() => {\n this.height.emit(this.toastBarBase.nativeElement.offsetHeight);\n });\n }\n }\n}\n","<div\n class=\"hot-toast-bar-base-container\"\n [style]=\"containerPositionStyle\"\n [class]=\"'hot-toast-theme-' + toast.theme\"\n [style.--hot-toast-scale]=\"scale\"\n [style.--hot-toast-translate-y]=\"translateY\"\n>\n <div\n class=\"hot-toast-bar-base-wrapper\"\n [class.expanded]=\"isExpanded\"\n (mouseenter)=\"handleMouseEnter()\"\n (mouseleave)=\"handleMouseLeave()\"\n >\n <div\n class=\"hot-toast-bar-base\"\n #hotToastBarBase\n [style]=\"toastBarBaseStylesSignal()\"\n [class]=\"toast.className\"\n [style.--hot-toast-animation-state]=\"isManualClose ? 'running' : 'paused'\"\n [style.--hot-toast-exit-animation-state]=\"isShowingAllToasts() ? 'paused' : 'running'\"\n [style.--hot-toast-exit-animation-delay]=\"exitAnimationDelay\"\n [attr.aria-live]=\"toast.ariaLive\"\n [attr.role]=\"toast.role\"\n >\n <div class=\"hot-toast-icon\" aria-hidden=\"true\">\n @if (toast.icon !== undefined) { @if (isIconString) {\n <hot-toast-animated-icon [iconTheme]=\"toast.iconTheme\" [icon]=\"toast.icon\" />\n } @else {\n <div>\n <ng-container *dynamicView=\"toast.icon\" />\n </div>\n } } @else {\n <hot-toast-indicator [theme]=\"toast.iconTheme\" [type]=\"toast.type\" />\n }\n </div>\n\n <div class=\"hot-toast-message\">\n <ng-container *dynamicView=\"toast.message; context: context; injector: toastComponentInjector\" />\n </div>\n\n @if (toast.group?.expandAndCollapsible && toast.group?.children && visibleToasts.length > 0) {\n <button\n (click)=\"toggleToastGroup()\"\n type=\"button\"\n class=\"hot-toast-group-btn\"\n [class.expanded]=\"isExpanded\"\n [attr.aria-label]=\"isExpanded ? 'Collapse' : 'Expand'\"\n [style]=\"toast.group.btnStyle\"\n ></button>\n } @if (toast.dismissible) {\n <button\n (click)=\"close()\"\n type=\"button\"\n class=\"hot-toast-close-btn\"\n aria-label=\"Close\"\n [style]=\"toast.closeStyle\"\n ></button>\n }\n </div>\n\n @if (toast.visible) {\n <div\n role=\"list\"\n class=\"hot-toast-bar-base-group\"\n [class]=\"toast.group?.className\"\n [style.--hot-toast-group-height]=\"groupHeight + 'px'\"\n >\n @for (item of groupChildrenToasts; track item.id) {\n <hot-toast-group-item [toast]=\"item\"\n [offset]=\"calculateOffset(item.id)\"\n [toastRef]=\"toastRef().groupRefs[$index]\"\n [toastsAfter]=\"(item.autoClose ? groupChildrenToasts.length : visibleToasts.length) - 1 - $index\"\n [defaultConfig]=\"defaultConfig()\"\n [isShowingAllToasts]=\"isShowingAllToasts()\"\n (height)=\"updateHeight($event, item)\"\n (beforeClosed)=\"beforeClosedGroupItem(item)\"\n (afterClosed)=\"afterClosedGroupItem($event)\"\n />\n }\n </div>\n }\n </div>\n</div>\n","import {\n Component,\n ChangeDetectionStrategy,\n inject,\n QueryList,\n ViewChildren,\n ChangeDetectorRef,\n input,\n afterNextRender,\n ElementRef,\n OnDestroy,\n isDevMode,\n} from '@angular/core';\nimport { Subject } from 'rxjs';\nimport {\n HotToastClose,\n Toast,\n ToastConfig,\n ToastPosition,\n UpdateToastOptions,\n AddToastRef,\n CreateHotToastRef,\n HotToastGroupEvent,\n} from '../../hot-toast.model';\nimport { HotToastRef } from '../../hot-toast-ref';\nimport { filter, map } from 'rxjs/operators';\nimport { Content } from '@ngneat/overview';\nimport { HotToastComponent } from '../hot-toast/hot-toast.component';\nimport { HOT_TOAST_DEPTH_SCALE, HOT_TOAST_DEPTH_SCALE_ADD, HOT_TOAST_MARGIN } from '../../constants';\nimport { HotToastService } from '../../hot-toast.service';\n\n@Component({\n selector: 'hot-toast-container',\n templateUrl: './hot-toast-container.component.html',\n styleUrl: './hot-toast-container.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [HotToastComponent],\n host: {\n '[attr.popover]': 'defaultConfig().usePopover ? \"manual\" : undefined',\n '[class.hot-toast-container-overlay-popover]': 'defaultConfig().usePopover',\n },\n})\nexport class HotToastContainerComponent implements OnDestroy {\n readonly defaultConfig = input<ToastConfig>();\n\n @ViewChildren(HotToastComponent) hotToastComponentList: QueryList<HotToastComponent>;\n\n toasts: Toast<unknown>[] = [];\n toastRefs: CreateHotToastRef<unknown>[] = [];\n isShowingAllToasts = false;\n\n /** Subject for notifying the user that the toast has been closed. */\n private _onClosed = new Subject<HotToastClose>();\n\n /** Subject for notifying the user that the toast has been expanded or collapsed. */\n private _onGroupToggle = new Subject<HotToastGroupEvent>();\n\n /** Subject for notifying the user that the group refs have been attached to toast. */\n private _onGroupRefAttached = new Subject<{ groupRefs: CreateHotToastRef<unknown>[]; id: string }>();\n\n private onClosed$ = this._onClosed.asObservable();\n private onGroupToggle$ = this._onGroupToggle.asObservable();\n private onGroupRefAttached$ = this._onGroupRefAttached.asObservable();\n\n private cdr = inject(ChangeDetectorRef);\n private toastService = inject(HotToastService);\n private host = inject(ElementRef);\n\n constructor() {\n afterNextRender(() => {\n if (this.defaultConfig().usePopover) {\n // We need the try/catch because the browser will throw if the\n // host or any of the parents are outside the DOM. Also note\n // the string access which is there for compatibility with Closure.\n try {\n this.host.nativeElement['showPopover']();\n } catch (error) {\n if (isDevMode()) {\n console.error('Error showing popover');\n console.error(error);\n }\n }\n }\n });\n }\n\n trackById(index: number, toast: Toast<unknown>) {\n return toast.id;\n }\n\n getVisibleToasts(position: ToastPosition) {\n return this.unGroupedToasts.filter((t) => t.visible && t.position === position);\n }\n\n get unGroupedToasts() {\n return this.toasts.filter(\n (t) => t.group?.parent === undefined || t.group?.children === undefined || t.group?.children.length === 0,\n );\n }\n\n calculateOffset(toastId: string, position: ToastPosition) {\n const visibleToasts = this.getVisibleToasts(position);\n const index = visibleToasts.findIndex((toast) => toast.id === toastId);\n const offset =\n index !== -1\n ? visibleToasts.slice(...(this.defaultConfig().reverseOrder ? [index + 1] : [0, index])).reduce((acc, t, i) => {\n const toastsAfter = visibleToasts.length - 1 - i;\n return this.defaultConfig().visibleToasts !== 0 &&\n i < visibleToasts.length - this.defaultConfig().visibleToasts\n ? 0\n : acc +\n (this.defaultConfig().stacking === 'vertical' || this.isShowingAllToasts\n ? t.height || 0\n : toastsAfter * HOT_TOAST_DEPTH_SCALE + HOT_TOAST_DEPTH_SCALE_ADD) +\n HOT_TOAST_MARGIN;\n }, 0)\n : 0;\n return offset;\n }\n\n updateHeight(height: number, toast: Toast<unknown>) {\n toast.height = height;\n this.cdr.markForCheck();\n }\n\n addToast<DataType>(ref: HotToastRef<DataType>, skipAttachToParent?: boolean): AddToastRef<DataType> {\n this.toastRefs.push(ref);\n\n let toast = ref.getToast();\n\n this.toasts.push(ref.getToast());\n\n if (this.defaultConfig().visibleToasts !== 0 && this.unGroupedToasts.length > this.defaultConfig().visibleToasts) {\n const closeToasts = this.toasts.slice(0, this.toasts.length - this.defaultConfig().visibleToasts);\n closeToasts.forEach((t) => {\n if (t.autoClose) {\n this.closeToast(t.id);\n }\n });\n }\n\n this.cdr.markForCheck();\n\n this.attachGroupRefs<DataType>(toast, ref, skipAttachToParent);\n\n return {\n dispose: () => {\n this.closeToast(toast.id);\n },\n updateMessage: (message: Content) => {\n toast.message = message;\n this.updateToasts(toast);\n this.cdr.markForCheck();\n },\n updateToast: (options: UpdateToastOptions<DataType>) => {\n toast = { ...toast, ...options };\n this.updateToasts(toast, options);\n this.cdr.markForCheck();\n },\n afterClosed: this.getAfterClosed(toast),\n afterGroupToggled: this.getAfterGroupToggled(toast),\n afterGroupRefsAttached: this.getAfterGroupRefsAttached(toast).pipe(map((v) => v.groupRefs)),\n };\n }\n\n private async attachGroupRefs<DataType>(\n toast: Toast<DataType>,\n ref: HotToastRef<DataType>,\n skipAttachToParent?: boolean,\n ) {\n let groupRefs: CreateHotToastRef<unknown>[] = [];\n\n if (toast.group) {\n if (toast.group.children) {\n groupRefs = await this.createGroupRefs(toast, ref);\n const toastIndex = this.toastRefs.findIndex((t) => t.getToast().id === toast.id);\n\n if (toastIndex > -1) {\n (this.toastRefs[toastIndex] as { groupRefs: CreateHotToastRef<unknown>[] }).groupRefs = groupRefs;\n\n this.cdr.markForCheck();\n this._onGroupRefAttached.next({ groupRefs, id: toast.id });\n }\n } else if (toast.group.parent && !skipAttachToParent) {\n const parentToastRef = toast.group.parent;\n const parentToast = parentToastRef.getToast();\n\n const parentToastRefIndex = this.toastRefs.findIndex((t) => t.getToast().id === parentToast.id);\n const parentToastIndex = this.toasts.findIndex((t) => t.id === parentToast.id);\n\n if (parentToastRefIndex > -1 && parentToastIndex > -1) {\n this.toastRefs[parentToastRefIndex].groupRefs.push(ref);\n\n const existingGroup = this.toasts[parentToastRefIndex].group ?? {};\n const existingChildren = this.toasts[parentToastRefIndex].group?.children ?? [];\n\n existingChildren.push({ options: { ...toast, type: toast.type, message: toast.message } });\n existingGroup.children = existingChildren;\n\n this.toasts[parentToastRefIndex].group = { ...existingGroup };\n\n this.cdr.markForCheck();\n\n this._onGroupRefAttached.next({ groupRefs, id: parentToast.id });\n }\n }\n }\n }\n\n private createGroupRefs<DataType>(toast: Toast<DataType>, ref: HotToastRef<DataType>) {\n const skipAttachToParent = true;\n return new Promise<CreateHotToastRef<unknown>[]>((resolve) => {\n const items = toast.group.children;\n const allPromises: Promise<CreateHotToastRef<unknown>>[] = items.map((item) => {\n return new Promise((innerResolve) => {\n item.options.group = { parent: ref };\n // We need to give a tick's delay so that IDs are generated properly\n setTimeout(() => {\n try {\n const itemRef = this.toastService.show(item.options.message, item.options, skipAttachToParent);\n innerResolve(itemRef);\n } catch (error) {\n console.error('Error creating toast', error);\n innerResolve(null);\n }\n });\n });\n });\n Promise.all(allPromises).then((refs) => resolve(refs));\n });\n }\n\n closeToast(id?: string) {\n if (id) {\n const comp = this.hotToastComponentList.find((item) => item.toast.id === id);\n if (comp) {\n comp.close();\n this.cdr.markForCheck();\n }\n } else {\n this.hotToastComponentList.forEach((comp) => comp.close());\n this.cdr.markForCheck();\n }\n }\n\n beforeClosed(toast: Toast<unknown>) {\n toast.visible = false;\n this.cdr.markForCheck();\n }\n\n afterClosed(closeToast: HotToastClose) {\n const toastIndex = this.toasts.findIndex((t) => t.id === closeToast.id);\n if (toastIndex > -1) {\n this._onClosed.next(closeToast);\n this.toasts = this.toasts.filter((t) => t.id !== closeToast.id);\n this.toastRefs = this.toastRefs.filter((t) => t.getToast().id !== closeToast.id);\n this.cdr.markForCheck();\n }\n }\n\n toggleGroup(groupEvent: HotToastGroupEvent) {\n const toastIndex = this.toastRefs.findIndex((t) => t.getToast().id === groupEvent.id);\n if (toastIndex > -1) {\n this._onGroupToggle.next(groupEvent);\n (this.toastRefs[toastIndex] as { groupExpanded: boolean }).groupExpanded = groupEvent.event === 'expand';\n this.cdr.markForCheck();\n }\n }\n\n hasToast(id: string) {\n return this.toasts.findIndex((t) => t.id === id) > -1;\n }\n\n showAllToasts(show: boolean) {\n this.isShowingAllToasts = show;\n }\n\n ngOnDestroy() {\n if (this.defaultConfig().usePopover) {\n // We need the try/catch because the browser will throw if the\n // host or any of the parents are outside the DOM. Also note\n // the string access which is there for compatibility with Closure.\n try {\n this.host.nativeElement['hidePopover']();\n } catch (error) {\n if (isDevMode()) {\n console.error('Error hiding popover');\n console.error(error);\n }\n }\n }\n }\n\n private getAfterClosed(toast: Toast<unknown>) {\n return this.onClosed$.pipe(filter((v) => v.id === toast.id));\n }\n\n private getAfterGroupToggled(toast: Toast<unknown>) {\n return this.onGroupToggle$.pipe(filter((v) => v.id === toast.id));\n }\n\n private getAfterGroupRefsAttached(toast: Toast<unknown>) {\n return this.onGroupRefAttached$.pipe(filter((v) => v.id === toast.id));\n }\n\n private updateToasts(toast: Toast<unknown>, options?: UpdateToastOptions<unknown>) {\n this.toasts = this.toasts.map((t) => ({ ...t, ...(t.id === toast.id && { ...toast, ...options }) }));\n this.cdr.markForCheck();\n }\n}\n","<div class=\"hot-toast-container-overlay\">\n <div class=\"hot-toast-container-wrapper\">\n <div>\n @for (toast of toasts; track trackById($index, toast)) {\n @if (toast.group?.parent)