UNPKG

hot-toast-bokzor

Version:

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

1 lines 149 kB
{"version":3,"file":"hot-toast-bokzor.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-builder.ts","../../../projects/ngxpert/hot-toast/src/public-api.ts","../../../projects/ngxpert/hot-toast/src/hot-toast-bokzor.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 @Input() theme: 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 @Input() theme: 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 @Input() theme: 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 @Input() theme: 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 @Input() theme: 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 @Input() theme: IconTheme;\n @Input() type: ToastType;\n}\n","@if (type !== 'blank') {\n<div class=\"hot-toast-indicator-wrapper\">\n @if (type === 'loading') {\n <hot-toast-loader [theme]=\"theme\"></hot-toast-loader>\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\"></hot-toast-error>\n </div>\n } @case ('success') {\n <div>\n <hot-toast-checkmark [theme]=\"theme\"></hot-toast-checkmark>\n </div>\n } @case ('warning') {\n <div>\n <hot-toast-warning [theme]=\"theme\"></hot-toast-warning>\n </div>\n } @case ('info') {\n <div>\n <hot-toast-info [theme]=\"theme\"></hot-toast-info>\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 @Input() iconTheme: IconTheme;\n @Input() icon: Content;\n}\n\n","<div class=\"hot-toast-animated-icon\" [style.color]=\"iconTheme?.primary\">\n <ng-container *dynamicView=\"icon\"></ng-container>\n</div>\n","import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n EventEmitter,\n Injector,\n Input,\n NgZone,\n Output,\n Renderer2,\n SimpleChanges,\n ViewChild,\n OnChanges,\n OnInit,\n AfterViewInit,\n OnDestroy,\n signal,\n ChangeDetectorRef,\n inject,\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 @Input() offset = 0;\n @Input() defaultConfig: ToastConfig;\n @Input() toastRef: 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 @Input() isShowingAllToasts = false;\n\n @Output() height = new EventEmitter<number>();\n @Output() beforeClosed = new EventEmitter();\n @Output() afterClosed = new EventEmitter<HotToastClose>();\n @Output() showAllToasts = new EventEmitter<boolean>();\n @Output() toggleGroup = new EventEmitter<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 // zone.js stores its `ZoneTask`s within the `nativeElement[Zone.__symbol__('animationstart') + 'false']` property\n // with callback that capture `this`.\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 this.attachSwipeListeners();\n }\n\n // Swipe-to-dismiss implementation (vertical: bottom -> top)\n private drag = {\n active: false,\n locked: false,\n startX: 0,\n startY: 0,\n lastY: 0,\n lastT: 0,\n vy: 0,\n dy: 0,\n };\n\n private rubberband(x: number) {\n const a = 0.55;\n const ax = Math.abs(x);\n const eased = 1 - Math.pow(1 - Math.min(1, ax), a);\n return Math.sign(x) * eased;\n }\n\n private decideVerticalDismiss(el: HTMLElement, dy: number, vy: number) {\n const height = el.offsetHeight || 1;\n const distanceDismiss = dy < 0 && Math.abs(dy) > height * 0.35; // upward only\n const velocityDismiss = vy < 0 && Math.abs(vy) > 800; // px/s upward\n return distanceDismiss || velocityDismiss;\n }\n\n private attachSwipeListeners() {\n const el = this.toastBarBase.nativeElement;\n this.ngZone.runOutsideAngular(() => {\n const downUn = this.renderer.listen(el, 'pointerdown', (e: PointerEvent) => {\n if ((e as any).button !== undefined && (e as any).button !== 0) return;\n this.drag.active = true;\n this.drag.locked = false;\n this.drag.startX = e.clientX;\n this.drag.startY = e.clientY;\n this.drag.lastY = e.clientY;\n this.drag.lastT = performance.now();\n (el as any).setPointerCapture?.((e as any).pointerId);\n this.ngZone.run(() => this.showAllToasts.emit(true));\n this.renderer.setStyle(el, 'will-change', 'transform, opacity');\n this.renderer.setStyle(el, 'touch-action', 'pan-x');\n this.renderer.setStyle(el, 'cursor', 'grabbing');\n });\n\n const moveUn = this.renderer.listen(el, 'pointermove', (e: PointerEvent) => {\n if (!this.drag.active) return;\n const dxRaw = e.clientX - this.drag.startX;\n const dyRaw = e.clientY - this.drag.startY;\n if (!this.drag.locked) {\n const slop = 8;\n if (Math.abs(dxRaw) < slop && Math.abs(dyRaw) < slop) return;\n if (Math.abs(dyRaw) > Math.abs(dxRaw) && dyRaw < 0) {\n this.drag.locked = true; // commit to upward vertical gesture\n } else {\n cancelDrag();\n return;\n }\n }\n const height = el.offsetHeight || 1;\n const eased = this.rubberband(dyRaw / height) * height;\n this.drag.dy = eased;\n const opacity = 1 - Math.min(1, Math.abs(eased) / height);\n this.renderer.setStyle(el, 'transform', `translate3d(0,${eased}px,0)`);\n this.renderer.setStyle(el, 'opacity', String(opacity));\n const now = performance.now();\n const dt = Math.max(1, now - this.drag.lastT);\n this.drag.vy = ((e.clientY - this.drag.lastY) / dt) * 1000;\n this.drag.lastY = e.clientY;\n this.drag.lastT = now;\n });\n\n const upHandler = () => {\n if (!this.drag.active) return;\n const shouldDismiss = this.decideVerticalDismiss(el, this.drag.dy, this.drag.vy);\n if (shouldDismiss) {\n const height = el.offsetHeight || 1;\n const target = -1 * (window.innerHeight + height); // always upward\n const current = getComputedStyle(el);\n (el as any)\n .animate(\n [\n { transform: `translate3d(0,${this.drag.dy}px,0)`, opacity: Number(current.opacity) || 1 },\n { transform: `translate3d(0,${target}px,0)`, opacity: 0 },\n ],\n { duration: 220, easing: 'cubic-bezier(.22,.61,.36,1)' }\n )\n .finished.finally(() => {\n this.ngZone.run(() => {\n this.beforeClosed.emit();\n this.afterClosed.emit({ dismissedByAction: true, id: this.toast.id });\n });\n (navigator as any).vibrate?.(10);\n });\n } else {\n const current = getComputedStyle(el);\n (el as any)\n .animate(\n [\n { transform: current.transform, opacity: Number(current.opacity) },\n { transform: 'translate3d(0,0,0)', opacity: 1 },\n ],\n { duration: 240, easing: 'cubic-bezier(.17,.89,.32,1.27)' }\n )\n .finished.finally(() => {\n this.renderer.removeStyle(el, 'transform');\n this.renderer.removeStyle(el, 'opacity');\n this.ngZone.run(() => this.showAllToasts.emit(false));\n });\n }\n finishDrag();\n };\n\n const upUn = this.renderer.listen(el, 'pointerup', upHandler);\n const cancelUn = this.renderer.listen(el, 'pointercancel', upHandler);\n\n const cancelDrag = () => {\n this.drag.active = false;\n this.drag.locked = false;\n this.ngZone.run(() => this.showAllToasts.emit(false));\n this.renderer.removeStyle(el, 'cursor');\n this.renderer.removeStyle(el, 'will-change');\n };\n\n const finishDrag = () => {\n this.drag.active = false;\n this.drag.locked = false;\n this.drag.dy = 0;\n this.drag.vy = 0;\n this.renderer.removeStyle(el, 'cursor');\n this.renderer.removeStyle(el, 'will-change');\n };\n\n this.unlisteners.push(downUn, moveUn, upUn, cancelUn);\n });\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\"></ng-container>\n </div>\n } } @else {\n <hot-toast-indicator [theme]=\"toast.iconTheme\" [type]=\"toast.type\"></hot-toast-indicator>\n }\n </div>\n <div class=\"hot-toast-message\">\n <ng-container *dynamicView=\"toast.message; context: context; injector: toastComponentInjector\"></ng-container>\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 EventEmitter,\n inject,\n Injector,\n Input,\n NgZone,\n OnChanges,\n OnDestroy,\n OnInit,\n Output,\n Renderer2,\n signal,\n SimpleChanges,\n ViewChild,\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',\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 @Input() offset = 0;\n @Input() defaultConfig: ToastConfig;\n @Input() toastRef: 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 if (this.defaultConfig?.visibleToasts > 0) {\n if (this.toast.autoClose) {\n // if (value >= this.defaultConfig?.visibleToasts) {\n // this.close();\n // }\n } else {\n if (value >= this.defaultConfig?.visibleToasts) {\n this.softClose();\n } else if (this.softClosed) {\n this.softOpen();\n }\n }\n }\n }\n\n @Input() isShowingAllToasts = false;\n\n @Output() height = new EventEmitter<number>();\n @Output() beforeClosed = new EventEmitter();\n @Output() afterClosed = new EventEmitter<HotToastClose>();\n @Output() showAllToasts = new EventEmitter<boolean>();\n @Output() toggleGroup = new EventEmitter<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 if (this.toastRef.groupRefs.length !== this.groupRefs.length) {\n this.groupRefs = this.toastRef.groupRefs.slice();\n this.cdr.markForCheck();\n\n this.emiHeightWithGroup(this.isExpanded);\n }\n if (this.toastRef.groupExpanded !== this.isExpanded) {\n this.isExpanded = this.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 // zone.js stores its `ZoneTask`s within the `nativeElement[Zone.__symbol__('animationstart') + 'false']` property\n // with callback that capture `this`.\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 this.attachSwipeListeners();\n }\n\n // Swipe-to-dismiss implementation (vertical: bottom -> top)\n private drag = {\n active: false,\n locked: false,\n startX: 0,\n startY: 0,\n lastY: 0,\n lastT: 0,\n vy: 0,\n dy: 0,\n };\n\n private rubberband(x: number) {\n const a = 0.55;\n const ax = Math.abs(x);\n const eased = 1 - Math.pow(1 - Math.min(1, ax), a);\n return Math.sign(x) * eased;\n }\n\n private decideVerticalDismiss(el: HTMLElement, dy: number, vy: number) {\n const height = el.offsetHeight || 1;\n const distanceDismiss = dy < 0 && Math.abs(dy) > height * 0.35; // upward only\n const velocityDismiss = vy < 0 && Math.abs(vy) > 800; // px/s upward\n return distanceDismiss || velocityDismiss;\n }\n\n private attachSwipeListeners() {\n const el = this.toastBarBase.nativeElement;\n this.ngZone.runOutsideAngular(() => {\n const downUn = this.renderer.listen(el, 'pointerdown', (e: PointerEvent) => {\n if ((e as any).button !== undefined && (e as any).button !== 0) return;\n this.drag.active = true;\n this.drag.locked = false;\n this.drag.startX = e.clientX;\n this.drag.startY = e.clientY;\n this.drag.lastY = e.clientY;\n this.drag.lastT = performance.now();\n (el as any).setPointerCapture?.((e as any).pointerId);\n this.ngZone.run(() => this.showAllToasts.emit(true));\n this.renderer.setStyle(el, 'will-change', 'transform, opacity');\n this.renderer.setStyle(el, 'touch-action', 'pan-x');\n this.renderer.setStyle(el, 'cursor', 'grabbing');\n });\n\n const moveUn = this.renderer.listen(el, 'pointermove', (e: PointerEvent) => {\n if (!this.drag.active) return;\n const dxRaw = e.clientX - this.drag.startX;\n const dyRaw = e.clientY - this.drag.startY;\n if (!this.drag.locked) {\n const slop = 8;\n if (Math.abs(dxRaw) < slop && Math.abs(dyRaw) < slop) return;\n if (Math.abs(dyRaw) > Math.abs(dxRaw) && dyRaw < 0) {\n this.drag.locked = true; // commit to upward vertical gesture\n } else {\n cancelDrag();\n return;\n }\n }\n const height = el.offsetHeight || 1;\n const eased = this.rubberband(dyRaw / height) * height;\n this.drag.dy = eased;\n const opacity = 1 - Math.min(1, Math.abs(eased) / height);\n this.renderer.setStyle(el, 'transform', `translate3d(0,${eased}px,0)`);\n this.renderer.setStyle(el, 'opacity', String(opacity));\n const now = performance.now();\n const dt = Math.max(1, now - this.drag.lastT);\n this.drag.vy = ((e.clientY - this.drag.lastY) / dt) * 1000;\n this.drag.lastY = e.clientY;\n this.drag.lastT = now;\n });\n\n const upHandler = () => {\n if (!this.drag.active) return;\n const shouldDismiss = this.decideVerticalDismiss(el, this.drag.dy, this.drag.vy);\n if (shouldDismiss) {\n const height = el.offsetHeight || 1;\n const target = -1 * (window.innerHeight + height); // always upward\n const current = getComputedStyle(el);\n (el as any)\n .animate(\n [\n { transform: `translate3d(0,${this.drag.dy}px,0)`, opacity: Number(current.opacity) || 1 },\n { transform: `translate3d(0,${target}px,0)`, opacity: 0 },\n ],\n { duration: 220, easing: 'cubic-bezier(.22,.61,.36,1)' }\n )\n .finished.finally(() => {\n this.ngZone.run(() => {\n this.beforeClosed.emit();\n this.afterClosed.emit({ dismissedByAction: true, id: this.toast.id });\n });\n (navigator as any).vibrate?.(10);\n });\n } else {\n const current = getComputedStyle(el);\n (el as any)\n .animate(\n [\n { transform: current.transform, opacity: Number(current.opacity) },\n { transform: 'translate3d(0,0,0)', opacity: 1 },\n ],\n { duration: 240, easing: 'cubic-bezier(.17,.89,.32,1.27)' }\n )\n .finished.finally(() => {\n this.renderer.removeStyle(el, 'transform');\n this.renderer.removeStyle(el, 'opacity');\n this.ngZone.run(() => this.showAllToasts.emit(false));\n });\n }\n finishDrag();\n };\n\n const upUn = this.renderer.listen(el, 'pointerup', upHandler);\n const cancelUn = this.renderer.listen(el, 'pointercancel', upHandler);\n\n const cancelDrag = () => {\n this.drag.active = false;\n this.drag.locked = false;\n this.ngZone.run(() => this.showAllToasts.emit(false));\n this.renderer.removeStyle(el, 'cursor');\n this.renderer.removeStyle(el, 'will-change');\n };\n\n const finishDrag = () => {\n this.drag.active = false;\n this.drag.locked = false;\n this.drag.dy = 0;\n this.drag.vy = 0;\n this.renderer.removeStyle(el, 'cursor');\n this.renderer.removeStyle(el, 'will-change');\n };\n\n this.unlisteners.push(downUn, moveUn, upUn, cancelUn);\n });\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 return this.defaultConfig.visibleToasts !== 0 && i < visibleToasts.length - this.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\"></hot-toast-animated-icon>\n } @else {\n <div>\n <ng-container *dynamicView=\"toast.icon\"></ng-container>\n </div>\n } } @else {\n <hot-toast-indicator [theme]=\"toast.iconTheme\" [type]=\"toast.type\"></hot-toast-indicator>\n }\n </div>\n\n <div class=\"hot-toast-message\">\n <ng-container *dynamicView=\"toast.message; context: context; injector: toastComponentInjector\"></ng-container>\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\n [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 ></hot-toast-group-item>\n }\n </div>\n }\n </div>\n</div>\n","import {\n Component,\n ChangeDetectionStrategy,\n inject,\n Input,\n QueryList,\n ViewChildren,\n ChangeDetectorRef,\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 } fro