UNPKG

primeng

Version:

PrimeNG is a premium UI library for Angular featuring a rich set of 90+ components, a theme designer, various theme alternatives such as Material, Bootstrap, Tailwind, premium templates and professional support. In addition, it integrates with PrimeBlock,

1,009 lines (1,006 loc) 61.9 kB
export * from 'primeng/types/toast'; import * as i0 from '@angular/core'; import { Injectable, input, numberAttribute, output, inject, signal, computed, effect, ChangeDetectionStrategy, ViewEncapsulation, Component, InjectionToken, booleanAttribute, contentChild, NgModule } from '@angular/core'; import { uuid, isEmpty, setAttribute } from '@primeuix/utils'; import { SharedModule, MessageService } from 'primeng/api'; import { BaseComponent, PARENT_INSTANCE } from 'primeng/basecomponent'; import * as i1$1 from 'primeng/bind'; import { Bind } from 'primeng/bind'; import { ZIndexUtils } from 'primeng/utils'; import { style } from '@primeuix/styles/toast'; import { BaseStyle } from 'primeng/base'; import { NgTemplateOutlet } from '@angular/common'; import { Check } from '@primeicons/angular/check'; import { InfoCircle } from '@primeicons/angular/info-circle'; import { TimesCircle } from '@primeicons/angular/times-circle'; import { ExclamationTriangle } from '@primeicons/angular/exclamation-triangle'; import { Times } from '@primeicons/angular/times'; import * as i1 from 'primeng/motion'; import { MotionModule } from 'primeng/motion'; // Position const inlineStyles = { root: ({ instance }) => { const position = instance.position(); return { position: 'fixed', top: position === 'top-right' || position === 'top-left' || position === 'top-center' ? '20px' : position === 'center' ? '50%' : null, right: (position === 'top-right' || position === 'bottom-right') && '20px', bottom: (position === 'bottom-left' || position === 'bottom-right' || position === 'bottom-center') && '20px', left: position === 'top-left' || position === 'bottom-left' ? '20px' : position === 'center' || position === 'top-center' || position === 'bottom-center' ? '50%' : null }; } }; const classes = { root: ({ instance }) => ['p-toast p-component', `p-toast-${instance.position()}`], message: ({ instance }) => ({ 'p-toast-message': true, 'p-toast-message-normal': instance.message().severity === 'normal' || instance.message().severity === undefined, 'p-toast-message-info': instance.message().severity === 'info', 'p-toast-message-warn': instance.message().severity === 'warn', 'p-toast-message-error': instance.message().severity === 'error', 'p-toast-message-success': instance.message().severity === 'success', 'p-toast-message-secondary': instance.message().severity === 'secondary', 'p-toast-message-contrast': instance.message().severity === 'contrast' }), messageContent: 'p-toast-message-content', messageIcon: ({ instance }) => ({ 'p-toast-message-icon': true, [`pi ${instance.message().icon}`]: !!instance.message().icon }), messageText: 'p-toast-message-text', summary: 'p-toast-summary', detail: 'p-toast-detail', closeButton: 'p-toast-close-button', closeIcon: ({ instance }) => ({ 'p-toast-close-icon': true, [`pi ${instance.message().closeIcon}`]: !!instance.message().closeIcon }) }; class ToastStyle extends BaseStyle { name = 'toast'; style = style; classes = classes; inlineStyles = inlineStyles; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ToastStyle, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ToastStyle }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ToastStyle, decorators: [{ type: Injectable }] }); /** * * Toast is used to display messages in an overlay. * * [Live Demo](https://www.primeng.org/toast/) * * @module toaststyle * */ var ToastClasses; (function (ToastClasses) { /** * Class name of the root element */ ToastClasses["root"] = "p-toast"; /** * Class name of the message element */ ToastClasses["message"] = "p-toast-message"; /** * Class name of the message content element */ ToastClasses["messageContent"] = "p-toast-message-content"; /** * Class name of the message icon element */ ToastClasses["messageIcon"] = "p-toast-message-icon"; /** * Class name of the message text element */ ToastClasses["messageText"] = "p-toast-message-text"; /** * Class name of the summary element */ ToastClasses["summary"] = "p-toast-summary"; /** * Class name of the detail element */ ToastClasses["detail"] = "p-toast-detail"; /** * Class name of the close button element */ ToastClasses["closeButton"] = "p-toast-close-button"; /** * Class name of the close icon element */ ToastClasses["closeIcon"] = "p-toast-close-icon"; })(ToastClasses || (ToastClasses = {})); const SWIPE_THRESHOLD = 50; const VELOCITY_THRESHOLD = 0.11; const SWIPE_OUT_DURATION = 500; class ToastItem extends BaseComponent { /** * Message instance. * @group Props */ message = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "message" }] : /* istanbul ignore next */ [])); /** * Index of the message. * @group Props */ index = input(undefined, { ...(ngDevMode ? { debugName: "index" } : /* istanbul ignore next */ {}), transform: numberAttribute }); /** * Life duration in milliseconds. * @group Props */ life = input(undefined, { ...(ngDevMode ? { debugName: "life" } : /* istanbul ignore next */ {}), transform: numberAttribute }); /** * Custom message template. * @group Props */ template = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "template" }] : /* istanbul ignore next */ [])); /** * Custom headless template. * @group Props */ headlessTemplate = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "headlessTemplate" }] : /* istanbul ignore next */ [])); /** * The motion options. * @group Props */ motionOptions = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "motionOptions" }] : /* istanbul ignore next */ [])); /** * Clear all trigger. * @group Props */ clearAll = input(null, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "clearAll" }] : /* istanbul ignore next */ [])); /** * Whether the stack is expanded. * @group Props */ stackExpanded = input(false, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "stackExpanded" }] : /* istanbul ignore next */ [])); /** * Whether the toast container is currently hovered or focused. * @group Props */ stackIsHovered = input(false, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "stackIsHovered" }] : /* istanbul ignore next */ [])); /** * Index of this toast in the stack. * @group Props */ stackIndex = input(0, { ...(ngDevMode ? { debugName: "stackIndex" } : /* istanbul ignore next */ {}), transform: numberAttribute }); /** * Total number of toasts in the stack. * @group Props */ stackTotal = input(0, { ...(ngDevMode ? { debugName: "stackTotal" } : /* istanbul ignore next */ {}), transform: numberAttribute }); /** * Cumulative offset for expanded state in pixels. * @group Props */ stackOffset = input(0, { ...(ngDevMode ? { debugName: "stackOffset" } : /* istanbul ignore next */ {}), transform: numberAttribute }); /** * Whether this toast is within the visible limit. * @group Props */ stackIsVisible = input(false, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "stackIsVisible" }] : /* istanbul ignore next */ [])); /** * Whether the user is interacting with the toast container. * @group Props */ stackIsInteracting = input(false, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "stackIsInteracting" }] : /* istanbul ignore next */ [])); /** * Position of the toast container. * @group Props */ position = input('top-right', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ [])); /** * Emits when animation starts. * @group Emits */ onAnimationStart = output(); /** * Emits when animation ends. * @group Emits */ onAnimationEnd = output(); /** * Emits when message is closed. * @group Emits */ onClose = output(); /** * Emits when toast height is measured (for stack mode). * @group Emits */ onHeightChange = output(); _componentStyle = inject(ToastStyle); timeout = null; visible = signal(undefined, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ [])); showCloseButton = computed(() => this.message()?.closable !== false, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "showCloseButton" }] : /* istanbul ignore next */ [])); static severityIcons = { success: 'check', info: 'info-circle', error: 'times-circle', warn: 'exclamation-triangle', secondary: 'info-circle', contrast: 'info-circle' }; severityIcon = computed(() => ToastItem.severityIcons[this.message()?.severity] ?? null, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "severityIcon" }] : /* istanbul ignore next */ [])); isDestroyed = false; mounted = signal(false, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "mounted" }] : /* istanbul ignore next */ [])); measuredHeight = signal(0, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "measuredHeight" }] : /* istanbul ignore next */ [])); removed = signal(false, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "removed" }] : /* istanbul ignore next */ [])); offsetBeforeRemove = signal(0, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "offsetBeforeRemove" }] : /* istanbul ignore next */ [])); swiping = signal(false, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "swiping" }] : /* istanbul ignore next */ [])); isSwiped = signal(false, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "isSwiped" }] : /* istanbul ignore next */ [])); swipeOut = signal(false, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "swipeOut" }] : /* istanbul ignore next */ [])); swipeDirection = signal(null, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "swipeDirection" }] : /* istanbul ignore next */ [])); swipeOutDirection = signal(null, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "swipeOutDirection" }] : /* istanbul ignore next */ [])); swipeAmountX = signal(0, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "swipeAmountX" }] : /* istanbul ignore next */ [])); swipeAmountY = signal(0, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "swipeAmountY" }] : /* istanbul ignore next */ [])); pointerStartPosition = null; swipeStartTime = 0; dataMounted = computed(() => (this.mounted() ? '' : null), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "dataMounted" }] : /* istanbul ignore next */ [])); dataFront = computed(() => (this.stackIndex() === 0 ? '' : null), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "dataFront" }] : /* istanbul ignore next */ [])); dataExpanded = computed(() => (this.stackExpanded() ? '' : null), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "dataExpanded" }] : /* istanbul ignore next */ [])); dataVisible = computed(() => (this.stackIsVisible() ? '' : null), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "dataVisible" }] : /* istanbul ignore next */ [])); dataRemoved = computed(() => (this.removed() ? '' : null), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "dataRemoved" }] : /* istanbul ignore next */ [])); dataSwiping = computed(() => (this.swiping() ? '' : null), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "dataSwiping" }] : /* istanbul ignore next */ [])); dataSwiped = computed(() => (this.isSwiped() ? '' : null), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "dataSwiped" }] : /* istanbul ignore next */ [])); dataSwipeOut = computed(() => (this.swipeOut() ? '' : null), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "dataSwipeOut" }] : /* istanbul ignore next */ [])); dataSwipeDirection = computed(() => (this.swipeOutDirection() ? this.swipeOutDirection() : null), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "dataSwipeDirection" }] : /* istanbul ignore next */ [])); dataDismissible = computed(() => String(this.message()?.closable !== false), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "dataDismissible" }] : /* istanbul ignore next */ [])); stackStyles = computed(() => { const idx = this.stackIndex(); const total = this.stackTotal(); return { '--px-toast-index': this.removed() ? this.stackIndex() : idx, '--px-toast-z-index': total - idx, '--px-initial-height': this.measuredHeight() + 'px', '--px-toast-offset': (this.removed() ? this.offsetBeforeRemove() : this.stackOffset()) + 'px', '--px-swipe-amount-x': this.swipeAmountX() + 'px', '--px-swipe-amount-y': this.swipeAmountY() + 'px', 'z-index': total - idx }; }, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "stackStyles" }] : /* istanbul ignore next */ [])); constructor() { super(); effect(() => { if (this.clearAll()) { this.visible.set(false); } }); effect(() => { // Pause auto-dismiss only while hovered/focused, interacting or swiping — not in expanded mode // (expanded just lays the stack out open; toasts still dismiss on their own). Mirrors React's useToast. const hovered = this.stackIsHovered(); const interacting = this.stackIsInteracting(); const swiping = this.swiping(); if (hovered || interacting || swiping) { this.pauseStackTimer(); } else { this.startStackTimer(); } }); } onBeforeEnter(event) { this.onAnimationStart.emit(event.element); } onAfterEnter() { this.measureStackHeight(); } onAfterLeave(event) { if (!this.visible() && !this.isDestroyed) { this.onClose.emit({ index: this.index(), message: this.message() }); if (!this.isDestroyed) { this.onAnimationEnd.emit(event.element); } } } onAfterViewInit() { this.visible.set(true); this.measureStackHeight(); } measureStackHeight() { if (this.mounted()) return; const el = this.el.nativeElement.querySelector('[data-stack]'); if (!el) return; const orig = el.style.height; el.style.height = 'auto'; const height = el.getBoundingClientRect().height; el.style.height = orig; this.measuredHeight.set(height); this.onHeightChange.emit({ index: this.index(), height }); this.mounted.set(true); } remainingTime = 0; timerStartTime = 0; startStackTimer() { const msg = this.message(); if (msg?.sticky) return; this.clearTimeout(); if (this.remainingTime <= 0) { this.remainingTime = msg?.life || this.life() || 3000; } this.timerStartTime = Date.now(); this.timeout = setTimeout(() => { this.handleFocusOnRemove(); this.closeStack(); }, this.remainingTime); } pauseStackTimer() { if (this.timerStartTime > 0 && this.timeout) { const elapsed = Date.now() - this.timerStartTime; this.remainingTime = Math.max(0, this.remainingTime - elapsed); } this.clearTimeout(); } clearTimeout() { if (this.timeout) { clearTimeout(this.timeout); this.timeout = null; } } onCloseIconClick = (event) => { this.clearTimeout(); this.handleFocusOnRemove(); this.closeStack(); event?.preventDefault(); }; closeStack() { this.markRemoved(); this.visible.set(false); } isDismissible() { return this.message()?.closable !== false; } markRemoved() { // A pending close/transition callback can reach this after teardown; bail so we don't // emit on a destroyed OutputRef. if (this.isDestroyed) return; this.offsetBeforeRemove.set(this.stackOffset()); this.removed.set(true); this.onHeightChange.emit({ index: this.index(), height: 0, removed: true }); } onPointerDown = (event) => { if (event.button !== 0) return; if (!this.isDismissible()) return; this.swipeStartTime = Date.now(); this.offsetBeforeRemove.set(this.stackOffset()); try { event.target.setPointerCapture(event.pointerId); } catch (_) { // ignore — some elements don't support pointer capture } this.swiping.set(true); this.pointerStartPosition = { x: event.clientX, y: event.clientY }; }; onPointerMove = (event) => { if (!this.pointerStartPosition || !this.isDismissible()) return; if ((window.getSelection()?.toString().length ?? 0) > 0) return; const yDelta = event.clientY - this.pointerStartPosition.y; const xDelta = event.clientX - this.pointerStartPosition.x; const isRealSwipe = Math.abs(xDelta) > 1 || Math.abs(yDelta) > 1; const positionParts = (this.position() ?? 'top-right').split('-'); const side = positionParts[0]; const align = positionParts[1]; if (!this.swipeDirection() && isRealSwipe) { this.swipeDirection.set(Math.abs(xDelta) > Math.abs(yDelta) ? 'x' : 'y'); } let nextX = 0; let nextY = 0; if (this.swipeDirection() === 'x') { const naturalDirection = (align === 'left' && xDelta < 0) || (align === 'right' && xDelta > 0); nextX = naturalDirection ? xDelta : this.applyDampening(xDelta); } else if (this.swipeDirection() === 'y') { const naturalDirection = (side === 'top' && yDelta < 0) || (side === 'bottom' && yDelta > 0); nextY = naturalDirection ? yDelta : this.applyDampening(yDelta); } if (Math.abs(nextX) > 0 || Math.abs(nextY) > 0) { this.isSwiped.set(true); } this.swipeAmountX.set(nextX); this.swipeAmountY.set(nextY); }; onPointerUp = () => { if (this.swipeOut() || !this.isDismissible()) return; this.swiping.set(false); this.pointerStartPosition = null; const swipeAmount = this.swipeDirection() === 'x' ? this.swipeAmountX() : this.swipeAmountY(); const elapsed = Date.now() - (this.swipeStartTime || Date.now()); const velocity = elapsed > 0 ? Math.abs(swipeAmount) / elapsed : 0; if (Math.abs(swipeAmount) >= SWIPE_THRESHOLD || velocity > VELOCITY_THRESHOLD) { this.offsetBeforeRemove.set(this.stackOffset()); if (this.swipeDirection() === 'x') { this.swipeOutDirection.set(this.swipeAmountX() > 0 ? 'right' : 'left'); } else { this.swipeOutDirection.set(this.swipeAmountY() > 0 ? 'down' : 'up'); } this.swipeOut.set(true); this.markRemoved(); this.scheduleSwipeOutClose(); return; } this.swipeAmountX.set(0); this.swipeAmountY.set(0); this.isSwiped.set(false); this.swipeDirection.set(null); }; onDragEnd = () => { this.swiping.set(false); this.swipeDirection.set(null); this.pointerStartPosition = null; }; applyDampening(delta) { const factor = Math.abs(delta) / 20; const dampenedDelta = delta * (1 / (1.5 + factor)); return Math.abs(dampenedDelta) < Math.abs(delta) ? dampenedDelta : delta; } scheduleSwipeOutClose() { this.clearTimeout(); this.timeout = setTimeout(() => { this.visible.set(false); }, SWIPE_OUT_DURATION); } handleFocusOnRemove() { const el = this.el.nativeElement; const activeEl = this.document.activeElement; if (!el?.contains(activeEl)) return; const next = el.nextElementSibling?.querySelector('[data-pc-section="closebutton"]'); const prev = el.previousElementSibling?.querySelector('[data-pc-section="closebutton"]'); requestAnimationFrame(() => { if (next) next.focus({ preventScroll: true }); else if (prev) prev.focus({ preventScroll: true }); }); } get closeAriaLabel() { return this.config.translation.aria ? this.config.translation.aria.close : undefined; } onDestroy() { this.isDestroyed = true; this.clearTimeout(); this.visible.set(false); } headlessContext = computed(() => ({ $implicit: this.message(), closeFn: this.onCloseIconClick }), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "headlessContext" }] : /* istanbul ignore next */ [])); messageContext = computed(() => ({ $implicit: this.message(), closeFn: this.onCloseIconClick }), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "messageContext" }] : /* istanbul ignore next */ [])); dataP = computed(() => { const msg = this.message(); return this.cn({ [msg?.severity]: msg?.severity }); }, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "dataP" }] : /* istanbul ignore next */ [])); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ToastItem, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: ToastItem, isStandalone: true, selector: "p-toast-item", inputs: { message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: false, transformFunction: null }, life: { classPropertyName: "life", publicName: "life", isSignal: true, isRequired: false, transformFunction: null }, template: { classPropertyName: "template", publicName: "template", isSignal: true, isRequired: false, transformFunction: null }, headlessTemplate: { classPropertyName: "headlessTemplate", publicName: "headlessTemplate", isSignal: true, isRequired: false, transformFunction: null }, motionOptions: { classPropertyName: "motionOptions", publicName: "motionOptions", isSignal: true, isRequired: false, transformFunction: null }, clearAll: { classPropertyName: "clearAll", publicName: "clearAll", isSignal: true, isRequired: false, transformFunction: null }, stackExpanded: { classPropertyName: "stackExpanded", publicName: "stackExpanded", isSignal: true, isRequired: false, transformFunction: null }, stackIsHovered: { classPropertyName: "stackIsHovered", publicName: "stackIsHovered", isSignal: true, isRequired: false, transformFunction: null }, stackIndex: { classPropertyName: "stackIndex", publicName: "stackIndex", isSignal: true, isRequired: false, transformFunction: null }, stackTotal: { classPropertyName: "stackTotal", publicName: "stackTotal", isSignal: true, isRequired: false, transformFunction: null }, stackOffset: { classPropertyName: "stackOffset", publicName: "stackOffset", isSignal: true, isRequired: false, transformFunction: null }, stackIsVisible: { classPropertyName: "stackIsVisible", publicName: "stackIsVisible", isSignal: true, isRequired: false, transformFunction: null }, stackIsInteracting: { classPropertyName: "stackIsInteracting", publicName: "stackIsInteracting", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onAnimationStart: "onAnimationStart", onAnimationEnd: "onAnimationEnd", onClose: "onClose", onHeightChange: "onHeightChange" }, providers: [ToastStyle], usesInheritance: true, ngImport: i0, template: ` <div #container [pMotion]="visible()" [pMotionAppear]="true" [pMotionOptions]="motionOptions()" (pMotionOnBeforeEnter)="onBeforeEnter($event)" (pMotionOnAfterEnter)="onAfterEnter()" (pMotionOnAfterLeave)="onAfterLeave($event)" [attr.id]="message()?.id" [pBind]="ptm('message')" [class]="cn(cx('message'), message()?.styleClass)" [style]="stackStyles()" (pointerdown)="onPointerDown($event)" (pointermove)="onPointerMove($event)" (pointerup)="onPointerUp()" (dragend)="onDragEnd()" role="alert" aria-live="assertive" aria-atomic="true" [attr.data-p]="dataP()" data-stack="" [attr.data-mounted]="dataMounted()" [attr.data-removed]="dataRemoved()" [attr.data-front]="dataFront()" [attr.data-expanded]="dataExpanded()" [attr.data-visible]="dataVisible()" [attr.data-swiping]="dataSwiping()" [attr.data-swiped]="dataSwiped()" [attr.data-swipe-out]="dataSwipeOut()" [attr.data-swipe-direction]="dataSwipeDirection()" [attr.data-dismissible]="dataDismissible()" > @if (headlessTemplate()) { <ng-container *ngTemplateOutlet="headlessTemplate(); context: headlessContext()"></ng-container> } @else { <div [pBind]="ptm('messageContent')" [class]="cn(cx('messageContent'), message()?.contentStyleClass)"> @if (!template()) { @if (message()?.icon) { <span [pBind]="ptm('messageIcon')" [class]="cn(cx('messageIcon'), message()?.icon)"></span> } @else if (severityIcon()) { @switch (severityIcon()) { @case ('check') { <svg data-p-icon="check" [pBind]="ptm('messageIcon')" [class]="cx('messageIcon')" [attr.aria-hidden]="true" /> } @case ('times-circle') { <svg data-p-icon="times-circle" [pBind]="ptm('messageIcon')" [class]="cx('messageIcon')" [attr.aria-hidden]="true" /> } @case ('exclamation-triangle') { <svg data-p-icon="exclamation-triangle" [pBind]="ptm('messageIcon')" [class]="cx('messageIcon')" [attr.aria-hidden]="true" /> } @case ('info-circle') { <svg data-p-icon="info-circle" [pBind]="ptm('messageIcon')" [class]="cx('messageIcon')" [attr.aria-hidden]="true" /> } } } <div [pBind]="ptm('messageText')" [class]="cx('messageText')" [attr.data-p]="dataP()"> <div [pBind]="ptm('summary')" [class]="cx('summary')" [attr.data-p]="dataP()"> {{ message()?.summary }} </div> <div [pBind]="ptm('detail')" [class]="cx('detail')" [attr.data-p]="dataP()">{{ message()?.detail }}</div> </div> } @if (template()) { <ng-container *ngTemplateOutlet="template(); context: messageContext()"></ng-container> } @if (showCloseButton()) { <div> <button [pBind]="ptm('closeButton')" type="button" [attr.class]="cx('closeButton')" (click)="onCloseIconClick($event)" (keydown.enter)="onCloseIconClick($event)" [attr.aria-label]="closeAriaLabel" autofocus [attr.data-p]="dataP()" > @if (message()?.closeIcon) { <span [pBind]="ptm('closeIcon')" [class]="cn(cx('closeIcon'), message()?.closeIcon)"></span> } @else { <svg [pBind]="ptm('closeIcon')" data-p-icon="times" [class]="cx('closeIcon')" [attr.aria-hidden]="true" /> } </button> </div> } </div> } </div> `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Check, selector: "svg[data-p-icon=\"check\"]" }, { kind: "component", type: InfoCircle, selector: "svg[data-p-icon=\"info-circle\"]" }, { kind: "component", type: TimesCircle, selector: "svg[data-p-icon=\"times-circle\"]" }, { kind: "component", type: ExclamationTriangle, selector: "svg[data-p-icon=\"exclamation-triangle\"]" }, { kind: "component", type: Times, selector: "svg[data-p-icon=\"times\"]" }, { kind: "ngmodule", type: SharedModule }, { kind: "directive", type: Bind, selector: "[pBind]", inputs: ["pBind"] }, { kind: "ngmodule", type: MotionModule }, { kind: "directive", type: i1.MotionDirective, selector: "[pMotion]", inputs: ["pMotion", "pMotionName", "pMotionType", "pMotionSafe", "pMotionDisabled", "pMotionAppear", "pMotionEnter", "pMotionLeave", "pMotionDuration", "pMotionHideStrategy", "pMotionEnterFromClass", "pMotionEnterToClass", "pMotionEnterActiveClass", "pMotionLeaveFromClass", "pMotionLeaveToClass", "pMotionLeaveActiveClass", "pMotionOptions"], outputs: ["pMotionOnBeforeEnter", "pMotionOnEnter", "pMotionOnAfterEnter", "pMotionOnEnterCancelled", "pMotionOnBeforeLeave", "pMotionOnLeave", "pMotionOnAfterLeave", "pMotionOnLeaveCancelled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ToastItem, decorators: [{ type: Component, args: [{ selector: 'p-toast-item', standalone: true, imports: [NgTemplateOutlet, Check, InfoCircle, TimesCircle, ExclamationTriangle, Times, SharedModule, Bind, MotionModule], template: ` <div #container [pMotion]="visible()" [pMotionAppear]="true" [pMotionOptions]="motionOptions()" (pMotionOnBeforeEnter)="onBeforeEnter($event)" (pMotionOnAfterEnter)="onAfterEnter()" (pMotionOnAfterLeave)="onAfterLeave($event)" [attr.id]="message()?.id" [pBind]="ptm('message')" [class]="cn(cx('message'), message()?.styleClass)" [style]="stackStyles()" (pointerdown)="onPointerDown($event)" (pointermove)="onPointerMove($event)" (pointerup)="onPointerUp()" (dragend)="onDragEnd()" role="alert" aria-live="assertive" aria-atomic="true" [attr.data-p]="dataP()" data-stack="" [attr.data-mounted]="dataMounted()" [attr.data-removed]="dataRemoved()" [attr.data-front]="dataFront()" [attr.data-expanded]="dataExpanded()" [attr.data-visible]="dataVisible()" [attr.data-swiping]="dataSwiping()" [attr.data-swiped]="dataSwiped()" [attr.data-swipe-out]="dataSwipeOut()" [attr.data-swipe-direction]="dataSwipeDirection()" [attr.data-dismissible]="dataDismissible()" > @if (headlessTemplate()) { <ng-container *ngTemplateOutlet="headlessTemplate(); context: headlessContext()"></ng-container> } @else { <div [pBind]="ptm('messageContent')" [class]="cn(cx('messageContent'), message()?.contentStyleClass)"> @if (!template()) { @if (message()?.icon) { <span [pBind]="ptm('messageIcon')" [class]="cn(cx('messageIcon'), message()?.icon)"></span> } @else if (severityIcon()) { @switch (severityIcon()) { @case ('check') { <svg data-p-icon="check" [pBind]="ptm('messageIcon')" [class]="cx('messageIcon')" [attr.aria-hidden]="true" /> } @case ('times-circle') { <svg data-p-icon="times-circle" [pBind]="ptm('messageIcon')" [class]="cx('messageIcon')" [attr.aria-hidden]="true" /> } @case ('exclamation-triangle') { <svg data-p-icon="exclamation-triangle" [pBind]="ptm('messageIcon')" [class]="cx('messageIcon')" [attr.aria-hidden]="true" /> } @case ('info-circle') { <svg data-p-icon="info-circle" [pBind]="ptm('messageIcon')" [class]="cx('messageIcon')" [attr.aria-hidden]="true" /> } } } <div [pBind]="ptm('messageText')" [class]="cx('messageText')" [attr.data-p]="dataP()"> <div [pBind]="ptm('summary')" [class]="cx('summary')" [attr.data-p]="dataP()"> {{ message()?.summary }} </div> <div [pBind]="ptm('detail')" [class]="cx('detail')" [attr.data-p]="dataP()">{{ message()?.detail }}</div> </div> } @if (template()) { <ng-container *ngTemplateOutlet="template(); context: messageContext()"></ng-container> } @if (showCloseButton()) { <div> <button [pBind]="ptm('closeButton')" type="button" [attr.class]="cx('closeButton')" (click)="onCloseIconClick($event)" (keydown.enter)="onCloseIconClick($event)" [attr.aria-label]="closeAriaLabel" autofocus [attr.data-p]="dataP()" > @if (message()?.closeIcon) { <span [pBind]="ptm('closeIcon')" [class]="cn(cx('closeIcon'), message()?.closeIcon)"></span> } @else { <svg [pBind]="ptm('closeIcon')" data-p-icon="times" [class]="cx('closeIcon')" [attr.aria-hidden]="true" /> } </button> </div> } </div> } </div> `, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, providers: [ToastStyle] }] }], ctorParameters: () => [], propDecorators: { message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], index: [{ type: i0.Input, args: [{ isSignal: true, alias: "index", required: false }] }], life: [{ type: i0.Input, args: [{ isSignal: true, alias: "life", required: false }] }], template: [{ type: i0.Input, args: [{ isSignal: true, alias: "template", required: false }] }], headlessTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "headlessTemplate", required: false }] }], motionOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "motionOptions", required: false }] }], clearAll: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearAll", required: false }] }], stackExpanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "stackExpanded", required: false }] }], stackIsHovered: [{ type: i0.Input, args: [{ isSignal: true, alias: "stackIsHovered", required: false }] }], stackIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "stackIndex", required: false }] }], stackTotal: [{ type: i0.Input, args: [{ isSignal: true, alias: "stackTotal", required: false }] }], stackOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "stackOffset", required: false }] }], stackIsVisible: [{ type: i0.Input, args: [{ isSignal: true, alias: "stackIsVisible", required: false }] }], stackIsInteracting: [{ type: i0.Input, args: [{ isSignal: true, alias: "stackIsInteracting", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], onAnimationStart: [{ type: i0.Output, args: ["onAnimationStart"] }], onAnimationEnd: [{ type: i0.Output, args: ["onAnimationEnd"] }], onClose: [{ type: i0.Output, args: ["onClose"] }], onHeightChange: [{ type: i0.Output, args: ["onHeightChange"] }] } }); const TOAST_INSTANCE = new InjectionToken('TOAST_INSTANCE'); /** * Toast is used to display messages in an overlay. * @group Components */ class Toast extends BaseComponent { componentName = 'Toast'; $pcToast = inject(TOAST_INSTANCE, { optional: true, skipSelf: true }) ?? undefined; bindDirectiveInstance = inject(Bind, { self: true }); onAfterViewChecked() { this.bindDirectiveInstance.setAttrs(this.ptms(['host', 'root'])); } /** * Key of the message in case message is targeted to a specific toast component. * @group Props */ key = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "key" }] : /* istanbul ignore next */ [])); /** * Whether to automatically manage layering. * @group Props */ autoZIndex = input(true, { ...(ngDevMode ? { debugName: "autoZIndex" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); /** * Base zIndex value to use in layering. * @group Props */ baseZIndex = input(0, { ...(ngDevMode ? { debugName: "baseZIndex" } : /* istanbul ignore next */ {}), transform: numberAttribute }); /** * The default time to display messages for in milliseconds. * @group Props */ life = input(3000, { ...(ngDevMode ? { debugName: "life" } : /* istanbul ignore next */ {}), transform: numberAttribute }); /** * Position of the toast in viewport. * @group Props */ position = input('top-right', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ [])); /** * Layout mode of the toast. `stacked` collapses toasts into a stack that expands on hover/focus; `expanded` always renders every toast expanded. * @defaultValue 'stacked' * @group Props */ mode = input('stacked', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ [])); /** * Gap between stacked toast items in pixels. * @group Props */ stackGap = input(8, { ...(ngDevMode ? { debugName: "stackGap" } : /* istanbul ignore next */ {}), transform: numberAttribute }); /** * Maximum number of visible toasts in the stack. * @group Props */ stackVisibleLimit = input(3, { ...(ngDevMode ? { debugName: "stackVisibleLimit" } : /* istanbul ignore next */ {}), transform: numberAttribute }); /** * It does not add the new message if there is already a toast displayed with the same content * @group Props */ preventOpenDuplicates = input(false, { ...(ngDevMode ? { debugName: "preventOpenDuplicates" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); /** * Displays only once a message with the same content. * @group Props */ preventDuplicates = input(false, { ...(ngDevMode ? { debugName: "preventDuplicates" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); /** * The motion options. * @group Props */ motionOptions = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "motionOptions" }] : /* istanbul ignore next */ [])); computedMotionOptions = computed(() => { return { ...this.ptm('motion'), ...this.motionOptions() }; }, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "computedMotionOptions" }] : /* istanbul ignore next */ [])); /** * Object literal to define styles per screen size. * @group Props */ breakpoints = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "breakpoints" }] : /* istanbul ignore next */ [])); /** * Callback to invoke when a message is closed. * @param {ToastCloseEvent} event - custom close event. * @group Emits */ onClose = output(); /** * Custom message template. * @param {ToastMessageTemplateContext} context - message context. * @see {@link ToastMessageTemplateContext} * @group Templates */ messageTemplate = contentChild('message', { ...(ngDevMode ? { debugName: "messageTemplate" } : /* istanbul ignore next */ {}), descendants: false }); /** * Custom headless template. * @param {ToastHeadlessTemplateContext} context - headless context. * @see {@link ToastHeadlessTemplateContext} * @group Templates */ headlessTemplate = contentChild('headless', { ...(ngDevMode ? { debugName: "headlessTemplate" } : /* istanbul ignore next */ {}), descendants: false }); messageSubscription; clearSubscription; messages; messageArchive; messageService = inject(MessageService); _componentStyle = inject(ToastStyle); styleElement = null; id = uuid('pn_id_'); clearAllTrigger = signal(null, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "clearAllTrigger" }] : /* istanbul ignore next */ [])); hovered = signal(false, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "hovered" }] : /* istanbul ignore next */ [])); isInteracting = signal(false, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "isInteracting" }] : /* istanbul ignore next */ [])); heights = signal([], /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "heights" }] : /* istanbul ignore next */ [])); // Single sorted source — all stack computeds derive from this sortedHeights = computed(() => [...this.heights()].sort((a, b) => b.index - a.index), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "sortedHeights" }] : /* istanbul ignore next */ [])); frontToastHeight = computed(() => this.sortedHeights()[0]?.height ?? 0, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "frontToastHeight" }] : /* istanbul ignore next */ [])); stackOffsets = computed(() => { const sorted = this.sortedHeights(); const offsets = [0]; for (let i = 1; i < sorted.length; i++) { offsets[i] = offsets[i - 1] + sorted[i - 1].height; } return offsets; }, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "stackOffsets" }] : /* istanbul ignore next */ [])); visualStackIndices = computed(() => { const map = new Map(); this.sortedHeights().forEach((entry, idx) => map.set(entry.index, idx)); return map; }, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "visualStackIndices" }] : /* istanbul ignore next */ [])); visibleIndices = computed(() => new Set(this.sortedHeights() .slice(0, this.stackVisibleLimit()) .map((x) => x.index)), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "visibleIndices" }] : /* istanbul ignore next */ [])); raiseFactor = computed(() => { const pos = this.position(); return pos.startsWith('bottom') ? -1 : 1; }, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "raiseFactor" }] : /* istanbul ignore next */ [])); isExpanded = computed(() => this.mode() === 'expanded' || this.hovered(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "isExpanded" }] : /* istanbul ignore next */ [])); hostDataExpanded = computed(() => (this.isExpanded() ? '' : null), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "hostDataExpanded" }] : /* istanbul ignore next */ [])); stackTotal = computed(() => this.messages?.length ?? 0, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "stackTotal" }] : /* istanbul ignore next */ [])); getStackIndex(domIndex) { return this.visualStackIndices().get(domIndex) ?? (this.messages?.length ?? 0) - 1 - domIndex; } getStackOffset(domIndex) { const visualIdx = this.visualStackIndices().get(domIndex) ?? 0; return this.stackOffsets()[visualIdx] ?? 0; } isStackVisible(domIndex) { return this.visibleIndices().has(domIndex); } dataP = computed(() => { const pos = this.position(); return this.cn({ [pos]: pos }); }, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "dataP" }] : /* istanbul ignore next */ [])); onInit() { this.messageSubscription = this.messageService.messageObserver.subscribe((messages) => { if (messages) { if (Array.isArray(messages)) { const filteredMessages = messages.filter((m) => this.canAdd(m)); this.add(filteredMessages); } else if (this.canAdd(messages)) { this.add([messages]); } } }); this.clearSubscription = this.messageService.clearObserver.subscribe((key) => { if (key) { if (this.key() === key) { this.clearAll(); } } else { this.clearAll(); } this.cd.markForCheck(); }); } clearAll() { this.clearAllTrigger.set({}); this.heights.set([]); this.hovered.set(false); this.isInteracting.set(false); this.messageArchive = undefined; } onAfterViewInit() { if (this.breakpoints()) { this.createStyle(); } } add(messages) { this.messages = this.messages ? [...this.messages, ...messages] : [...messages]; if (this.preventDuplicates()) { this.messageArchive = this.messageArchive ? [...this.messageArchive, ...messages] : [...messages]; } this.cd.markForCheck(); } canAdd(message) { let allow = this.key() === message.key; if (allow && this.preventOpenDuplicates()) { allow = !this.containsMessage(this.messages ?? [], message); } if (allow && this.preventDuplicates()) { allow = !this.containsMessage(this.messageArchive ?? [], message); } return allow; } containsMessage(collection, message) { if (!collection) { return false; } return (collection.find((m) => { return m.summary === message.summary && m.detail == message.detail && m.severity === message.severity; }) != null); } onMessageClose(event) { this.messages?.splice(event.index, 1); this.heights.update((h) => h.filter((x) => x.index !== event.index).map((x) => (x.index > event.index ? { ...x, index: x.index - 1 } : x))); if ((this.messages?.length ?? 0) <= 1) { this.hovered.set(false); } this.onClose.emit({ message: event.message }); this.onAnimationEnd(); this.cd.detectChanges(); } onAnimationStart() { this.renderer.setAttribute(this.el?.nativeElement, this.id, ''); if (this.autoZIndex() && this.el?.nativeElement.style.zIndex === '') { ZIndexUtils.set('modal', this.el?.nativeElement, this.baseZIndex() || this.config.zIndex.modal); } } onAnimationEnd() { if (this.autoZIndex() && isEmpty(this.messages)) { ZIndexUtils.clear(this.el?.nativeElement); } } onContainerMouseEnter() { this.hovered.set(true); } onContainerMouseLeave(event) { if (this.isInteracting()) return; const container = this.el?.nativeElement; const relatedTarget = event.relatedTarget; if (relatedTarget && container?.contains(relatedTarget)) { return; } this.hovered.set(false); } onContainerPointerDown(event) { const target = event.target; if (target && target.closest('[data-dismissible="false"]')) return; this.isInteracting.set(true); } onContainerPointerUp() { this.isInteract