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
JavaScript
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