ng-angular-popup
Version:
A modern, lightweight, and customizable toast notification library for Angular applications
629 lines (612 loc) • 60.8 kB
JavaScript
import * as i0 from '@angular/core';
import { signal, Injectable, input, inject, ElementRef, Directive, computed, effect, Component, NgModule } from '@angular/core';
import * as i2 from '@angular/common';
import { CommonModule } from '@angular/common';
import { trigger, transition, style, animate } from '@angular/animations';
import * as i3 from '@angular/forms';
import { FormsModule } from '@angular/forms';
/**
* Represents a toast notification message with various configuration options.
*/
class ToastMessage {
/**
* Creates a new toast message instance.
*
* @param message The content of the toast message
* @param type The visual type/style of the toast
* @param title Optional title for the toast message
* @param duration Time in milliseconds before auto-dismissal (0 for no auto-dismiss)
* @param showProgress Whether to show the progress countdown bar
* @param dismissible Whether the toast can be manually dismissed
*/
constructor(message, type, title, duration = 2000, showProgress, dismissible) {
this.message = message;
this.type = type;
this.title = title;
this.duration = duration;
/** Whether to show the progress bar for auto-dismiss countdown */
this.showProgress = true;
/** Whether the toast can be dismissed by clicking on it */
this.dismissible = true;
this.id = new Date().getTime();
this.createdAt = this.id;
if (showProgress !== undefined) {
this.showProgress = showProgress;
}
if (dismissible !== undefined) {
this.dismissible = dismissible;
}
// If duration is 0, disable progress bar as it won't auto-dismiss
if (duration === 0) {
this.showProgress = false;
}
}
}
var ToastType;
(function (ToastType) {
ToastType["PRIMARY"] = "toast-primary";
ToastType["SECONDARY"] = "toast-secondary";
ToastType["SUCCESS"] = "toast-success";
ToastType["INFO"] = "toast-info";
ToastType["WARNING"] = "toast-warning";
ToastType["DANGER"] = "toast-danger";
})(ToastType || (ToastType = {}));
/**
* Service for displaying toast messages.
*/
class NgToastService {
/** Default duration for toast messages in milliseconds */
#defaultDuration;
/** Maximum number of toasts to show at once */
#maxToasts;
/**
* Constructs a new NgToastService instance.
*/
constructor() {
/**
* Signal that holds the current toast messages
*/
this.toastMessages = signal([]);
this.#defaultDuration = 3000;
this.#maxToasts = 5;
}
/**
* Displays a toast message.
* @param message The message to display.
* @param type The type of the toast message.
* @param title The optional title of the toast message.
* @param duration The duration in milliseconds for which the toast message should be displayed. Defaults to the default duration.
* @param showProgress Whether to show the progress bar. Defaults to true.
* @param dismissible Whether the toast can be manually dismissed. Defaults to true.
*/
toast(message, type, title, duration = this.#defaultDuration, showProgress = true, dismissible = true) {
// Create new toast message
const newToast = new ToastMessage(message, type, title, duration, showProgress, dismissible);
// Add to messages, limiting to max number of toasts
this.toastMessages.update(messages => {
const updatedMessages = [...messages, newToast];
// If we have more than max toasts, remove the oldest ones
return updatedMessages.length > this.#maxToasts
? updatedMessages.slice(updatedMessages.length - this.#maxToasts)
: updatedMessages;
});
// Auto-remove the toast after the duration (if duration > 0)
if (duration > 0) {
setTimeout(() => {
this.removeToast(newToast.id);
}, duration);
}
}
/**
* Displays a success toast message.
* @param message The message to display.
* @param title The optional title of the toast message.
* @param duration The duration in milliseconds for which the toast message should be displayed. Defaults to the default duration.
* @param showProgress Whether to show the progress bar. Defaults to true.
* @param dismissible Whether the toast can be manually dismissed. Defaults to true.
*/
success(message, title, duration = this.#defaultDuration, showProgress = true, dismissible = true) {
this.toast(message, ToastType.SUCCESS, title, duration, showProgress, dismissible);
}
/**
* Displays an info toast message.
* @param message The message to display.
* @param title The optional title of the toast message.
* @param duration The duration in milliseconds for which the toast message should be displayed. Defaults to the default duration.
* @param showProgress Whether to show the progress bar. Defaults to true.
* @param dismissible Whether the toast can be manually dismissed. Defaults to true.
*/
info(message, title, duration = this.#defaultDuration, showProgress = true, dismissible = true) {
this.toast(message, ToastType.INFO, title, duration, showProgress, dismissible);
}
/**
* Displays a warning toast message.
* @param message The message to display.
* @param title The optional title of the toast message.
* @param duration The duration in milliseconds for which the toast message should be displayed. Defaults to the default duration.
* @param showProgress Whether to show the progress bar. Defaults to true.
* @param dismissible Whether the toast can be manually dismissed. Defaults to true.
*/
warning(message, title, duration = this.#defaultDuration, showProgress = true, dismissible = true) {
this.toast(message, ToastType.WARNING, title, duration, showProgress, dismissible);
}
/**
* Displays a danger/error toast message.
* @param message The message to display.
* @param title The optional title of the toast message.
* @param duration The duration in milliseconds for which the toast message should be displayed. Defaults to the default duration.
* @param showProgress Whether to show the progress bar. Defaults to true.
* @param dismissible Whether the toast can be manually dismissed. Defaults to true.
*/
danger(message, title, duration = this.#defaultDuration, showProgress = true, dismissible = true) {
this.toast(message, ToastType.DANGER, title, duration, showProgress, dismissible);
}
/**
* Displays a primary toast message.
* @param message The message to display.
* @param title The optional title of the toast message.
* @param duration The duration in milliseconds for which the toast message should be displayed. Defaults to the default duration.
* @param showProgress Whether to show the progress bar. Defaults to true.
* @param dismissible Whether the toast can be manually dismissed. Defaults to true.
*/
primary(message, title, duration = this.#defaultDuration, showProgress = true, dismissible = true) {
this.toast(message, ToastType.PRIMARY, title, duration, showProgress, dismissible);
}
/**
* Displays a secondary toast message.
* @param message The message to display.
* @param title The optional title of the toast message.
* @param duration The duration in milliseconds for which the toast message should be displayed. Defaults to the default duration.
* @param showProgress Whether to show the progress bar. Defaults to true.
* @param dismissible Whether the toast can be manually dismissed. Defaults to true.
*/
secondary(message, title, duration = this.#defaultDuration, showProgress = true, dismissible = true) {
this.toast(message, ToastType.SECONDARY, title, duration, showProgress, dismissible);
}
/**
* Removes a toast message from the list
* @param messageId The ID of the message to remove
*/
removeToast(messageId) {
this.toastMessages.update(messages => messages.filter(message => message.id !== messageId));
}
/**
* Removes all toast messages
*/
clearAll() {
this.toastMessages.set([]);
}
/**
* Updates the progress bars by triggering a signal update
* This is used by the component to refresh progress bars
*/
updateProgress() {
// Force a signal update by creating a new array with the same messages
this.toastMessages.update(messages => [...messages]);
}
/**
* Sets the maximum number of toasts to display at once
* @param max The maximum number of toasts
*/
setMaxToasts(max) {
if (max > 0) {
this.#maxToasts = max;
// If we already have more than max toasts, remove the oldest ones
const currentToasts = this.toastMessages();
if (currentToasts.length > max) {
this.toastMessages.set(currentToasts.slice(currentToasts.length - max));
}
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgToastService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgToastService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgToastService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: () => [] });
/**
* Toast position values as string literals
*/
const TOAST_POSITIONS = {
TOP_LEFT: 'toaster-top-left',
TOP_CENTER: 'toaster-top-center',
TOP_RIGHT: 'toaster-top-right',
BOTTOM_LEFT: 'toaster-bottom-left',
BOTTOM_CENTER: 'toaster-bottom-center',
BOTTOM_RIGHT: 'toaster-bottom-right'
};
/**
* @deprecated Use TOAST_POSITIONS and ToastPosition instead
*/
const ToasterPosition = TOAST_POSITIONS;
/**
* Directive that renders appropriate SVG icons for different toast types
*/
class ToastIconDirective {
constructor() {
/** Input signal for the toast type */
this.type = input.required({ alias: 'toastIcon' });
/** Element reference for DOM manipulation */
this.#el = inject(ElementRef);
}
/** Element reference for DOM manipulation */
#el;
ngOnInit() {
this.setIcon();
}
/**
* Sets the appropriate SVG icon based on toast type
*/
setIcon() {
let svgContent;
switch (this.type()) {
case 'toast-success':
svgContent = `
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
<polyline points="22 4 12 14.01 9 11.01"></polyline>
</svg>`;
break;
case 'toast-danger':
svgContent = `
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<line x1="15" y1="9" x2="9" y2="15"></line>
<line x1="9" y1="9" x2="15" y2="15"></line>
</svg>`;
break;
case 'toast-info':
svgContent = `
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="16" x2="12" y2="12"></line>
<line x1="12" y1="8" x2="12.01" y2="8"></line>
</svg>`;
break;
case 'toast-warning':
svgContent = `
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
<line x1="12" y1="9" x2="12" y2="13"></line>
<line x1="12" y1="17" x2="12.01" y2="17"></line>
</svg>`;
break;
case 'toast-primary':
svgContent = `
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path>
<path d="M13.73 21a2 2 0 0 1-3.46 0"></path>
</svg>`;
break;
case 'toast-secondary':
svgContent = `
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<path d="M8 14s1.5 2 4 2 4-2 4-2"></path>
<line x1="9" y1="9" x2="9.01" y2="9"></line>
<line x1="15" y1="9" x2="15.01" y2="9"></line>
</svg>`;
break;
default:
svgContent = `
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="8" x2="12" y2="12"></line>
<line x1="12" y1="16" x2="12.01" y2="16"></line>
</svg>`;
}
this.#el.nativeElement.innerHTML = svgContent;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: ToastIconDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.0.3", type: ToastIconDirective, isStandalone: true, selector: "[toastIcon]", inputs: { type: { classPropertyName: "type", publicName: "toastIcon", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: ToastIconDirective, decorators: [{
type: Directive,
args: [{
selector: '[toastIcon]',
standalone: true
}]
}] });
class NgToastComponent {
constructor(toastService) {
this.toastService = toastService;
/**
* Input signal for the position of the toast container
*/
this.position = input(TOAST_POSITIONS.BOTTOM_RIGHT);
/**
* Input signal for the width of the toast container in pixels
*/
this.width = input(350);
/**
* Signal to track progress update intervals
*/
this.progressInterval = signal(null);
/**
* Computed signal that gets the messages from the service
*/
this.messages = computed(() => this.toastService.toastMessages());
// Create an effect to handle message positioning
effect(() => {
// This effect will run whenever the position signal changes
// We don't need to do anything here as the position is handled in the template
this.position();
});
// Create an effect to handle progress bar updates
effect(() => {
const messages = this.messages();
// Clear existing interval if no messages with progress bars
if (messages.length === 0 || !messages.some(m => m.showProgress)) {
this.clearProgressInterval();
return;
}
// Start progress interval if not already running
if (this.progressInterval() === null) {
const intervalId = window.setInterval(() => {
// Force update to trigger change detection for progress bars
this.toastService.updateProgress();
}, 100);
this.progressInterval.set(intervalId);
}
});
}
ngOnInit() {
// Initialize any required state
}
/**
* Calculates the progress width percentage for a toast message
* @param message The toast message
* @returns The progress width as a percentage (0-100)
*/
getProgressWidth(message) {
if (message.duration <= 0)
return 0;
const elapsed = Date.now() - message.createdAt;
const remaining = Math.max(0, message.duration - elapsed);
return (remaining / message.duration) * 100;
}
/**
* Gets the appropriate color for the progress bar based on toast type
* @param message The toast message
* @returns CSS color value for the progress bar
*/
getProgressColor(message) {
const colorMap = {
'toast-primary': '#4f46e5',
'toast-secondary': '#475569',
'toast-success': '#10b981',
'toast-info': '#06b6d4',
'toast-warning': '#f59e0b',
'toast-danger': '#ef4444'
};
return colorMap[message.type] || 'rgba(0, 0, 0, 0.2)';
}
/**
* Clears the progress update interval
*/
clearProgressInterval() {
const intervalId = this.progressInterval();
if (intervalId !== null) {
window.clearInterval(intervalId);
this.progressInterval.set(null);
}
}
/**
* Removes a toast message
* @param message The message to remove
*/
remove(message) {
if (message.dismissible) {
this.toastService.removeToast(message.id);
}
}
ngOnDestroy() {
// Clean up any resources
this.clearProgressInterval();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgToastComponent, deps: [{ token: NgToastService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.3", type: NgToastComponent, isStandalone: true, selector: "ng-toast", inputs: { position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div style.min-width=\"{{width()}}px\" style.max-width=\"{{width()}}px\" class=\"toaster\" [ngClass]=\"position()\">\n\n <div class=\"toast-message\" *ngFor=\"let message of messages()\" [ngClass]=\"message.type\" [@showHide]>\n <div class=\"flex-start-center gap-3\">\n <div class=\"toast-icon-wrapper\">\n <span toastIcon=\"{{message.type}}\" class=\"toast-icon\"></span>\n </div>\n <div class=\"flex-col\">\n @if (message.title && message.title !== '') {\n <span class=\"msg-title\">{{message.title}}</span>\n }\n <span class=\"msg-summary\">{{message.message}}</span>\n </div>\n </div>\n\n <button (click)=\"remove(message)\" class=\"cross-icon\" aria-label=\"Close toast\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\" aria-hidden=\"true\">\n <path d=\"M8.01186 7.00933L12.27 2.75116C12.341 2.68501 12.398 2.60524 12.4375 2.51661C12.4769 2.42798 12.4982 2.3323 12.4999 2.23529C12.5016 2.13827 12.4838 2.0419 12.4474 1.95194C12.4111 1.86197 12.357 1.78024 12.2884 1.71163C12.2198 1.64302 12.138 1.58893 12.0481 1.55259C11.9581 1.51625 11.8617 1.4984 11.7647 1.50011C11.6677 1.50182 11.572 1.52306 11.4834 1.56255C11.3948 1.60204 11.315 1.65898 11.2488 1.72997L6.99067 5.98814L2.7325 1.72997C2.59553 1.60234 2.41437 1.53286 2.22718 1.53616C2.03999 1.53946 1.8614 1.61529 1.72901 1.74767C1.59663 1.88006 1.5208 2.05865 1.5175 2.24584C1.5142 2.43303 1.58368 2.61419 1.71131 2.75116L5.96948 7.00933L1.71131 11.2675C1.576 11.403 1.5 11.5866 1.5 11.7781C1.5 11.9696 1.576 12.1532 1.71131 12.2887C1.84679 12.424 2.03043 12.5 2.2219 12.5C2.41338 12.5 2.59702 12.424 2.7325 12.2887L6.99067 8.03052L11.2488 12.2887C11.3843 12.424 11.568 12.5 11.7594 12.5C11.9509 12.5 12.1346 12.424 12.27 12.2887C12.4053 12.1532 12.4813 11.9696 12.4813 11.7781C12.4813 11.5866 12.4053 11.403 12.27 11.2675L8.01186 7.00933Z\" fill=\"currentColor\"></path>\n </svg>\n </button>\n\n <!-- Progress bar for auto-dismiss countdown -->\n @if (message.showProgress) {\n <div class=\"toast-progress\" [style]=\"{'width': getProgressWidth(message) + '%', 'background-color': getProgressColor(message)}\"></div>\n }\n </div>\n</div>\n", styles: ["@keyframes slideInRight{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}@keyframes slideInLeft{0%{transform:translate(-100%);opacity:0}to{transform:translate(0);opacity:1}}@keyframes slideInUp{0%{transform:translateY(100%);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes slideInDown{0%{transform:translateY(-100%);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes bounce{0%,20%,50%,80%,to{transform:translateY(0)}40%{transform:translateY(-10px)}60%{transform:translateY(-5px)}}.toaster{position:fixed;z-index:9999;min-width:300px;max-width:400px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.toaster .toast-message{padding:1rem 1.25rem;margin-bottom:.75rem;border-radius:8px;background:#fff;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;display:flex;justify-content:space-between;align-items:flex-start;word-break:break-word;transition:all .3s ease;border:1px solid rgba(0,0,0,.1);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);position:relative;overflow:hidden}.toaster .toast-message:hover{box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -2px #0000000d;transform:translateY(-2px)}.toaster .toast-message.toast-primary{border-left:6px solid #4338ca;background-color:#eef2ff;box-shadow:0 4px 12px #4338ca26}.toaster .toast-message.toast-primary .toast-icon{color:#4338ca}.toaster .toast-message.toast-primary .toast-title{color:#4338ca;font-weight:600}.toaster .toast-message.toast-primary .toast-content{color:#111827}.toaster .toast-message.toast-secondary{border-left:6px solid #374151;background-color:#f1f5f9;box-shadow:0 4px 12px #37415126}.toaster .toast-message.toast-secondary .toast-icon{color:#374151}.toaster .toast-message.toast-secondary .toast-title{color:#374151;font-weight:600}.toaster .toast-message.toast-secondary .toast-content{color:#111827}.toaster .toast-message.toast-success{border-left:6px solid #047857;background-color:#ecfdf5;box-shadow:0 4px 12px #04785726}.toaster .toast-message.toast-success .toast-icon{color:#047857}.toaster .toast-message.toast-success .toast-title{color:#047857;font-weight:600}.toaster .toast-message.toast-success .toast-content{color:#111827}.toaster .toast-message.toast-info{border-left:6px solid #0369a1;background-color:#ecfeff;box-shadow:0 4px 12px #0369a126}.toaster .toast-message.toast-info .toast-icon{color:#0369a1}.toaster .toast-message.toast-info .toast-title{color:#0369a1;font-weight:600}.toaster .toast-message.toast-info .toast-content{color:#111827}.toaster .toast-message.toast-warning{border-left:6px solid #b45309;background-color:#fffbeb;box-shadow:0 4px 12px #b4530926}.toaster .toast-message.toast-warning .toast-icon{color:#b45309}.toaster .toast-message.toast-warning .toast-title{color:#b45309;font-weight:600}.toaster .toast-message.toast-warning .toast-content{color:#111827}.toaster .toast-message.toast-danger{border-left:6px solid #b91c1c;background-color:#fef2f2;box-shadow:0 4px 12px #b91c1c26}.toaster .toast-message.toast-danger .toast-icon{color:#b91c1c}.toaster .toast-message.toast-danger .toast-title{color:#b91c1c;font-weight:600}.toaster .toast-message.toast-danger .toast-content{color:#111827}.toaster .toast-message .content-wrapper{flex:1;margin-right:.75rem}.toaster .toast-message .msg-title{font-size:1rem;color:#111827;font-weight:600;margin-bottom:.375rem;line-height:1.4;letter-spacing:-.01em}.toaster .toast-message .msg-summary{font-size:.9375rem;color:#111827;font-weight:400;line-height:1.5;letter-spacing:.01em}.toaster .toast-message .cross-icon{background:transparent;border:none;color:#111827;cursor:pointer;padding:8px;margin:-8px;display:flex;align-items:center;justify-content:center;border-radius:50%;transition:all .3s ease;opacity:.7}.toaster .toast-message .cross-icon:hover{opacity:1;background-color:#0000001a}.toaster.toaster-top-left{margin:1rem;top:0;left:0}.toaster.toaster-top-left .toast-message{animation:slideInLeft .3s ease-out forwards}.toaster.toaster-top-center{margin-top:1rem;top:0;left:50%;transform:translate(-50%)}.toaster.toaster-top-center .toast-message{animation:slideInDown .3s ease-out forwards}.toaster.toaster-top-right{margin:1rem;top:0;right:0}.toaster.toaster-top-right .toast-message{animation:slideInRight .3s ease-out forwards}.toaster.toaster-bottom-left{margin:1rem;bottom:0;left:0}.toaster.toaster-bottom-left .toast-message{animation:slideInLeft .3s ease-out forwards}.toaster.toaster-bottom-center{margin-bottom:1rem;bottom:0;left:50%;transform:translate(-50%)}.toaster.toaster-bottom-center .toast-message{animation:slideInUp .3s ease-out forwards}.toaster.toaster-bottom-right{margin:1rem;bottom:0;right:0}.toaster.toaster-bottom-right .toast-message{animation:slideInRight .3s ease-out forwards}@keyframes slideIn{0%{transform:translateY(-8px);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes fadeOut{to{opacity:0}}.toast-message.fade-out{animation:fadeOut .15s ease forwards}@media (max-width: 480px){.toaster{min-width:calc(100vw - 2rem);max-width:calc(100vw - 2rem);margin:.75rem}.toaster .toast-message{padding:.625rem .875rem}}.flex-start-center{display:flex;align-items:center;justify-content:flex-start}.flex-col{display:flex;flex-direction:column}.gap-3{gap:.75rem}.toast-icon{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;font-size:1.25rem;flex-shrink:0}.toast-icon.pulse{animation:pulse 2s infinite}.toast-progress{position:absolute;bottom:0;left:0;height:4px;background-color:currentColor;opacity:.3;border-radius:0 0 0 8px;transition:width linear}.toast-primary .toast-progress{background-color:#4338ca}.toast-secondary .toast-progress{background-color:#374151}.toast-success .toast-progress{background-color:#047857}.toast-info .toast-progress{background-color:#0369a1}.toast-warning .toast-progress{background-color:#b45309}.toast-danger .toast-progress{background-color:#b91c1c}@media (max-width: 576px){.toaster{min-width:calc(100% - 2rem);max-width:calc(100% - 2rem)}.toaster.toaster-top-left,.toaster.toaster-top-right,.toaster.toaster-bottom-left,.toaster.toaster-bottom-right{left:0;right:0;margin-left:1rem;margin-right:1rem;transform:none}.toaster.toaster-top-center,.toaster.toaster-bottom-center{width:calc(100% - 2rem)}.toaster .toast-message{padding:.875rem 1rem}}.p-icon{width:1rem;height:1rem}.toast-icon{border-radius:50%;color:#fff;padding:2px;display:flex;justify-content:center;align-items:center}.toast-icon.toast-success,.toast-icon.toast-primary,.toast-icon.toast-secondary{background:#34b189}.toast-icon.toast-info{background:#3b82f6}.toast-icon.toast-warning{background:#f59e0b}.toast-icon.toast-danger{background:#ff6767}.cross-icon{background:transparent;outline:none;border:none;cursor:pointer}.cross-icon.toast-success,.cross-icon.toast-primary,.cross-icon.toast-secondary{color:#34b189}.cross-icon.toast-info{color:#3b82f6}.cross-icon.toast-warning{color:#f59e0b}.cross-icon.toast-danger{color:#ff6767}.flex-col{display:flex;flex-direction:column;gap:.375rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: ToastIconDirective, selector: "[toastIcon]", inputs: ["toastIcon"] }], animations: [trigger('showHide', [
transition(':enter', [
style({
opacity: 0,
transform: 'scaleX(0.98) scaleY(0)',
position: 'relative'
}),
animate('300ms cubic-bezier(0.4, 0, 0.2, 1)', style({
opacity: 1,
transform: 'scale(1)'
}))
]),
transition(':leave', [
style({
opacity: 1,
transform: 'scale(1)'
}),
animate('250ms cubic-bezier(0.4, 0, 0.2, 1)', style({
opacity: 0,
transform: 'scaleX(0.98) scaleY(0)'
}))
])
])] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgToastComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-toast', animations: [trigger('showHide', [
transition(':enter', [
style({
opacity: 0,
transform: 'scaleX(0.98) scaleY(0)',
position: 'relative'
}),
animate('300ms cubic-bezier(0.4, 0, 0.2, 1)', style({
opacity: 1,
transform: 'scale(1)'
}))
]),
transition(':leave', [
style({
opacity: 1,
transform: 'scale(1)'
}),
animate('250ms cubic-bezier(0.4, 0, 0.2, 1)', style({
opacity: 0,
transform: 'scaleX(0.98) scaleY(0)'
}))
])
])], standalone: true, imports: [CommonModule, ToastIconDirective], template: "<div style.min-width=\"{{width()}}px\" style.max-width=\"{{width()}}px\" class=\"toaster\" [ngClass]=\"position()\">\n\n <div class=\"toast-message\" *ngFor=\"let message of messages()\" [ngClass]=\"message.type\" [@showHide]>\n <div class=\"flex-start-center gap-3\">\n <div class=\"toast-icon-wrapper\">\n <span toastIcon=\"{{message.type}}\" class=\"toast-icon\"></span>\n </div>\n <div class=\"flex-col\">\n @if (message.title && message.title !== '') {\n <span class=\"msg-title\">{{message.title}}</span>\n }\n <span class=\"msg-summary\">{{message.message}}</span>\n </div>\n </div>\n\n <button (click)=\"remove(message)\" class=\"cross-icon\" aria-label=\"Close toast\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\" aria-hidden=\"true\">\n <path d=\"M8.01186 7.00933L12.27 2.75116C12.341 2.68501 12.398 2.60524 12.4375 2.51661C12.4769 2.42798 12.4982 2.3323 12.4999 2.23529C12.5016 2.13827 12.4838 2.0419 12.4474 1.95194C12.4111 1.86197 12.357 1.78024 12.2884 1.71163C12.2198 1.64302 12.138 1.58893 12.0481 1.55259C11.9581 1.51625 11.8617 1.4984 11.7647 1.50011C11.6677 1.50182 11.572 1.52306 11.4834 1.56255C11.3948 1.60204 11.315 1.65898 11.2488 1.72997L6.99067 5.98814L2.7325 1.72997C2.59553 1.60234 2.41437 1.53286 2.22718 1.53616C2.03999 1.53946 1.8614 1.61529 1.72901 1.74767C1.59663 1.88006 1.5208 2.05865 1.5175 2.24584C1.5142 2.43303 1.58368 2.61419 1.71131 2.75116L5.96948 7.00933L1.71131 11.2675C1.576 11.403 1.5 11.5866 1.5 11.7781C1.5 11.9696 1.576 12.1532 1.71131 12.2887C1.84679 12.424 2.03043 12.5 2.2219 12.5C2.41338 12.5 2.59702 12.424 2.7325 12.2887L6.99067 8.03052L11.2488 12.2887C11.3843 12.424 11.568 12.5 11.7594 12.5C11.9509 12.5 12.1346 12.424 12.27 12.2887C12.4053 12.1532 12.4813 11.9696 12.4813 11.7781C12.4813 11.5866 12.4053 11.403 12.27 11.2675L8.01186 7.00933Z\" fill=\"currentColor\"></path>\n </svg>\n </button>\n\n <!-- Progress bar for auto-dismiss countdown -->\n @if (message.showProgress) {\n <div class=\"toast-progress\" [style]=\"{'width': getProgressWidth(message) + '%', 'background-color': getProgressColor(message)}\"></div>\n }\n </div>\n</div>\n", styles: ["@keyframes slideInRight{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}@keyframes slideInLeft{0%{transform:translate(-100%);opacity:0}to{transform:translate(0);opacity:1}}@keyframes slideInUp{0%{transform:translateY(100%);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes slideInDown{0%{transform:translateY(-100%);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes bounce{0%,20%,50%,80%,to{transform:translateY(0)}40%{transform:translateY(-10px)}60%{transform:translateY(-5px)}}.toaster{position:fixed;z-index:9999;min-width:300px;max-width:400px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.toaster .toast-message{padding:1rem 1.25rem;margin-bottom:.75rem;border-radius:8px;background:#fff;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;display:flex;justify-content:space-between;align-items:flex-start;word-break:break-word;transition:all .3s ease;border:1px solid rgba(0,0,0,.1);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);position:relative;overflow:hidden}.toaster .toast-message:hover{box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -2px #0000000d;transform:translateY(-2px)}.toaster .toast-message.toast-primary{border-left:6px solid #4338ca;background-color:#eef2ff;box-shadow:0 4px 12px #4338ca26}.toaster .toast-message.toast-primary .toast-icon{color:#4338ca}.toaster .toast-message.toast-primary .toast-title{color:#4338ca;font-weight:600}.toaster .toast-message.toast-primary .toast-content{color:#111827}.toaster .toast-message.toast-secondary{border-left:6px solid #374151;background-color:#f1f5f9;box-shadow:0 4px 12px #37415126}.toaster .toast-message.toast-secondary .toast-icon{color:#374151}.toaster .toast-message.toast-secondary .toast-title{color:#374151;font-weight:600}.toaster .toast-message.toast-secondary .toast-content{color:#111827}.toaster .toast-message.toast-success{border-left:6px solid #047857;background-color:#ecfdf5;box-shadow:0 4px 12px #04785726}.toaster .toast-message.toast-success .toast-icon{color:#047857}.toaster .toast-message.toast-success .toast-title{color:#047857;font-weight:600}.toaster .toast-message.toast-success .toast-content{color:#111827}.toaster .toast-message.toast-info{border-left:6px solid #0369a1;background-color:#ecfeff;box-shadow:0 4px 12px #0369a126}.toaster .toast-message.toast-info .toast-icon{color:#0369a1}.toaster .toast-message.toast-info .toast-title{color:#0369a1;font-weight:600}.toaster .toast-message.toast-info .toast-content{color:#111827}.toaster .toast-message.toast-warning{border-left:6px solid #b45309;background-color:#fffbeb;box-shadow:0 4px 12px #b4530926}.toaster .toast-message.toast-warning .toast-icon{color:#b45309}.toaster .toast-message.toast-warning .toast-title{color:#b45309;font-weight:600}.toaster .toast-message.toast-warning .toast-content{color:#111827}.toaster .toast-message.toast-danger{border-left:6px solid #b91c1c;background-color:#fef2f2;box-shadow:0 4px 12px #b91c1c26}.toaster .toast-message.toast-danger .toast-icon{color:#b91c1c}.toaster .toast-message.toast-danger .toast-title{color:#b91c1c;font-weight:600}.toaster .toast-message.toast-danger .toast-content{color:#111827}.toaster .toast-message .content-wrapper{flex:1;margin-right:.75rem}.toaster .toast-message .msg-title{font-size:1rem;color:#111827;font-weight:600;margin-bottom:.375rem;line-height:1.4;letter-spacing:-.01em}.toaster .toast-message .msg-summary{font-size:.9375rem;color:#111827;font-weight:400;line-height:1.5;letter-spacing:.01em}.toaster .toast-message .cross-icon{background:transparent;border:none;color:#111827;cursor:pointer;padding:8px;margin:-8px;display:flex;align-items:center;justify-content:center;border-radius:50%;transition:all .3s ease;opacity:.7}.toaster .toast-message .cross-icon:hover{opacity:1;background-color:#0000001a}.toaster.toaster-top-left{margin:1rem;top:0;left:0}.toaster.toaster-top-left .toast-message{animation:slideInLeft .3s ease-out forwards}.toaster.toaster-top-center{margin-top:1rem;top:0;left:50%;transform:translate(-50%)}.toaster.toaster-top-center .toast-message{animation:slideInDown .3s ease-out forwards}.toaster.toaster-top-right{margin:1rem;top:0;right:0}.toaster.toaster-top-right .toast-message{animation:slideInRight .3s ease-out forwards}.toaster.toaster-bottom-left{margin:1rem;bottom:0;left:0}.toaster.toaster-bottom-left .toast-message{animation:slideInLeft .3s ease-out forwards}.toaster.toaster-bottom-center{margin-bottom:1rem;bottom:0;left:50%;transform:translate(-50%)}.toaster.toaster-bottom-center .toast-message{animation:slideInUp .3s ease-out forwards}.toaster.toaster-bottom-right{margin:1rem;bottom:0;right:0}.toaster.toaster-bottom-right .toast-message{animation:slideInRight .3s ease-out forwards}@keyframes slideIn{0%{transform:translateY(-8px);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes fadeOut{to{opacity:0}}.toast-message.fade-out{animation:fadeOut .15s ease forwards}@media (max-width: 480px){.toaster{min-width:calc(100vw - 2rem);max-width:calc(100vw - 2rem);margin:.75rem}.toaster .toast-message{padding:.625rem .875rem}}.flex-start-center{display:flex;align-items:center;justify-content:flex-start}.flex-col{display:flex;flex-direction:column}.gap-3{gap:.75rem}.toast-icon{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;font-size:1.25rem;flex-shrink:0}.toast-icon.pulse{animation:pulse 2s infinite}.toast-progress{position:absolute;bottom:0;left:0;height:4px;background-color:currentColor;opacity:.3;border-radius:0 0 0 8px;transition:width linear}.toast-primary .toast-progress{background-color:#4338ca}.toast-secondary .toast-progress{background-color:#374151}.toast-success .toast-progress{background-color:#047857}.toast-info .toast-progress{background-color:#0369a1}.toast-warning .toast-progress{background-color:#b45309}.toast-danger .toast-progress{background-color:#b91c1c}@media (max-width: 576px){.toaster{min-width:calc(100% - 2rem);max-width:calc(100% - 2rem)}.toaster.toaster-top-left,.toaster.toaster-top-right,.toaster.toaster-bottom-left,.toaster.toaster-bottom-right{left:0;right:0;margin-left:1rem;margin-right:1rem;transform:none}.toaster.toaster-top-center,.toaster.toaster-bottom-center{width:calc(100% - 2rem)}.toaster .toast-message{padding:.875rem 1rem}}.p-icon{width:1rem;height:1rem}.toast-icon{border-radius:50%;color:#fff;padding:2px;display:flex;justify-content:center;align-items:center}.toast-icon.toast-success,.toast-icon.toast-primary,.toast-icon.toast-secondary{background:#34b189}.toast-icon.toast-info{background:#3b82f6}.toast-icon.toast-warning{background:#f59e0b}.toast-icon.toast-danger{background:#ff6767}.cross-icon{background:transparent;outline:none;border:none;cursor:pointer}.cross-icon.toast-success,.cross-icon.toast-primary,.cross-icon.toast-secondary{color:#34b189}.cross-icon.toast-info{color:#3b82f6}.cross-icon.toast-warning{color:#f59e0b}.cross-icon.toast-danger{color:#ff6767}.flex-col{display:flex;flex-direction:column;gap:.375rem}\n"] }]
}], ctorParameters: () => [{ type: NgToastService }] });
/**
* Provides the NgToast service for standalone applications
* @returns An array of providers for the NgToast service
*/
function provideNgToast() {
return [
NgToastService
];
}
/**
* Demo component showcasing the ng-toast library with Angular v20 and signals
*/
class NgToastDemoComponent {
constructor(toastService) {
this.toastService = toastService;
// Make positions available in template
this.TOAST_POSITIONS = TOAST_POSITIONS;
this.currentPosition = TOAST_POSITIONS.TOP_RIGHT;
// Toast options
this.duration = 3000;
this.showProgress = true;
this.dismissible = true;
this.customTitle = '';
this.customMessage = 'This is a custom toast message that showcases our revamped UI with progress bars.';
// Available toast types for custom message
this.toastTypes = Object.values(ToastType);
}
showSuccess() {
this.toastService.success('Operation completed successfully!', this.customTitle || 'Success', this.duration, this.showProgress, this.dismissible);
}
showInfo() {
this.toastService.info('Here is some important information.', this.customTitle || 'Information', this.duration, this.showProgress, this.dismissible);
}
showWarning() {
this.toastService.warning('Please proceed with caution.', this.customTitle || 'Warning', this.duration, this.showProgress, this.dismissible);
}
showDanger() {
this.toastService.danger('An error has occurred!', this.customTitle || 'Error', this.duration, this.showProgress, this.dismissible);
}
showPrimary() {
this.toastService.primary('This is a primary notification.', this.customTitle || 'Primary', this.duration, this.showProgress, this.dismissible);
}
showSecondary() {
this.toastService.secondary('This is a secondary notification.', this.customTitle || 'Secondary', this.duration, this.showProgress, this.dismissible);
}
showCustomToast(type) {
if (!this.customMessage) {
this.customMessage = 'This is a custom toast message';
}
this.toastService.toast(this.customMessage, type, this.customTitle || type, this.duration, this.showProgress, this.dismissible);
}
clearAllToasts() {
this.toastService.clearAll();
}
showMultipleToasts() {
// Show multiple toasts to demonstrate max toasts limit
const types = [ToastType.SUCCESS, ToastType.INFO, ToastType.WARNING,
ToastType.DANGER, ToastType.PRIMARY, ToastType.SECONDARY];
types.forEach((type, index) => {
setTimeout(() => {
this.toastService.toast(`This is toast #${index + 1} of ${types.length}`, type, `Multiple Toast ${index + 1}`, this.duration, this.showProgress, this.dismissible);
}, index * 300); // Stagger the toasts
});
}
setPosition(position) {
this.currentPosition = position;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgToastDemoComponent, deps: [{ token: NgToastService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.0.3", type: NgToastDemoComponent, isStandalone: true, selector: "ng-toast-demo", ngImport: i0, template: `
<div class="demo-container">
<h1>NgToast Demo</h1>
<p>A modern, lightweight toast notification library for Angular v20 with signals</p>
<div class="demo-card">
<h2>Toast Types</h2>
<div class="button-group">
<button class="success-btn" (click)="showSuccess()">Success</button>
<button class="info-btn" (click)="showInfo()">Info</button>
<button class="warning-btn" (click)="showWarning()">Warning</button>
<button class="danger-btn" (click)="showDanger()">Danger</button>
<button class="primary-btn" (click)="showPrimary()">Primary</button>
<button class="secondary-btn" (click)="showSecondary()">Secondary</button>
</div>
</div>
<div class="demo-card">
<h2>Toast Options</h2>
<div class="options-grid">
<div class="option-group">
<label>Duration (ms):</label>
<input type="number" [(ngModel)]="duration" min="0" max="10000" step="500">
<span class="hint">Set to 0 for persistent toast</span>
</div>
<div class="option-group">
<label>Show Progress Bar:</label>
<div class="toggle-switch">
<input type="checkbox" id="progress-toggle" [(ngModel)]="showProgress">
<label for="progress-toggle"></label>
</div>
</div>
<div class="option-group">
<label>Dismissible:</label>
<div class="toggle-switch">
<input type="checkbox" id="dismiss-toggle" [(ngModel)]="dismissible">
<label for="dismiss-toggle"></label>
</div>
</div>
<div class="option-group">
<label>Custom Title:</label>
<input type="text" [(ngModel)]="customTitle" placeholder="Optional title">
</div>
</div>
</div>
<div class="demo-card">
<h2>Toast Position</h2>
<div class="position-grid">
<button [class.active]="currentPosition === TOAST_POSITIONS.TOP_LEFT"
(click)="setPosition(TOAST_POSITIONS.TOP_LEFT)">Top Left</button>
<button [class.active]="currentPosition === TOAST_POSITIONS.TOP_CENTER"
(click)="setPosition(TOAST_POSITIONS.TOP_CENTER)">Top Center</button>
<button [class.active]="currentPosition === TOAST_POSITIONS.TOP_RIGHT"
(click)="setPosition(TOAST_POSITIONS.TOP_RIGHT)">Top Right</button>
<button [class.active]="currentPosition === TOAST_POSITIONS.BOTTOM_LEFT"
(click)="setPosition(TOAST_POSITIONS.BOTTOM_LEFT)">Bottom Left</button>
<button [class.active]="currentPosition === TOAST_POSITIONS.BOTTOM_CENTER"
(click)="setPosition(TOAST_POSITIONS.BOTTOM_CENTER)">Bottom Center</button>
<button [class.active]="currentPosition === TOAST_POSITIONS.BOTTOM_RIGHT"
(click)="setPosition(TOAST_POSITIONS.BOTTOM_RIGHT)">Bottom Right</button>
</div>
</div>
<div class="demo-card">
<h2>Custom Message</h2>
<div class="custom-message-container">
<textarea [(ngModel)]="customMessage" placeholder="Enter your custom message here"></textarea>
<div class="button-group">
<button *ngFor="let type of toastTypes"
[class]="type.toLowerCase() + '-btn'"
(click)="showCustomToast(type)">
{{ type }}
</button>
</div>
</div>
</div>
<div class="demo-card">
<h2>Actions</h2>
<div class="button-group">
<button class="clear-btn" (click)="clearAllToasts()">Clear All Toasts</button>
<button class="max-btn" (click)="showMultipleToasts()">Show Multiple Toasts</button>
</div>
</div>
<!-- The toast component -->
<ng-toast [position]="currentPosition" [width]="350"></ng-toast>
</div>
`, isInline: true, styles: [".demo-container{font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif;max-width:800px;margin:0 auto;padding:20px;background-color:#f9fafb;color:#111827}h1{color:#111827;margin-bottom:10px;font-weight:600;text-align:center}h2{color:#374151;font-size:1.2rem;margin-bottom:15px;font-weight:600}p{color:#6b7280;margin-bottom:20px;text-align:center}.demo-card{background-color:#fff;border-radius:8px;padding:20px;margin-bottom:20px;box-shadow:0 1px 3px #0000001a}.button-group{display:flex;gap:10px;flex-wrap:wrap}button{padding:10px 15px;border:none;border-radius:6px;cursor:pointer;font-weight:500;transition:all .2s ease;box-shadow:0 1px 2px #0000000d}button:hover{opacity:.9;transform:translateY(-2px);box-shadow:0 4px 6px #0000001a}.success-btn{background-color:#22c55e;color:#fff}.info-btn{background-color:#3b82f6;color:#fff}.warning-btn{background-color:#f59e0b;color:#fff}.danger-btn{background-color:#ef4444;color:#fff}.primary-btn{background-color:#6366f1;color:#fff}.secondary-btn{background-color:#9ca3af;color:#fff}.clear-btn{background-color:#e5e7eb;color:#374151}.max-btn{background-color:#8b5cf6;color:#fff}.position-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}.position-grid button{background-color:#f3f4f6;color:#374151;font-size:.9rem}.position-grid button.active{background-color:#6366f1;color:#fff}.options-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:15px}.option-group{display:flex;flex-direction:column;gap:5px}.option-group label{font-size:.9rem;font-weight:500;color:#4b5563}.option-group input[type=number],.option-group input[type=text]{padding:8px 12px;border:1px solid #d1d5db;border-radius:4px;font-size:.9rem}.hint{font-size:.8rem;color:#9ca3af}.toggle-switch{position:relative;display:inline-block;width:50px;height:24px}.toggle-switch input{opacity:0;width:0;height:0}.toggle-switch label{position:absolute;cursor:pointer;inset:0;background-color:#e5e7eb;transition:.4s;border-radius:24px}.toggle-switch label:before{position:absol