ng-toast-notify
Version:
Lightweight and flexible toast notifications for Angular
486 lines (479 loc) • 37.6 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, HostListener, Input, Component } from '@angular/core';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { NgClass, NgStyle, AsyncPipe, NgComponentOutlet } from '@angular/common';
class ToastysoundService {
constructor() {
this.oscillatorType = 'sine';
this.frequency = 430;
this.audioCtx = new AudioContext();
}
notify() {
if (this.audioCtx.state === 'suspended') {
this.audioCtx.resume();
}
const osc = this.audioCtx.createOscillator();
const gain = this.audioCtx.createGain();
osc.type = this.oscillatorType;
osc.frequency.value = this.frequency;
gain.gain.setValueAtTime(0.3, this.audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.00009, this.audioCtx.currentTime + 0.4);
osc.connect(gain);
gain.connect(this.audioCtx.destination);
osc.start(0);
osc.stop(this.audioCtx.currentTime + 1);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ToastysoundService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ToastysoundService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ToastysoundService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: () => [] });
class Queue {
constructor(capacity = Infinity) {
this.capacity = capacity;
this.storage = [];
}
setCapacity(c) {
this.capacity = c;
}
getCapacity() { return this.capacity; }
enqueue(item) {
if (this.size() >= this.capacity) {
throw Error('Toasty queue has reached max capacity, you cannot add more items.');
}
this.storage.push(item);
}
dequeue() {
return this.storage.shift();
}
size() {
return this.storage.length;
}
getElements() {
return this.storage;
}
removeId(id) {
const index = this.storage.findIndex(item => item.id === id);
if (index !== -1) {
const [removed] = this.storage.splice(index, 1);
return removed;
}
return undefined;
}
}
var ToastType;
(function (ToastType) {
ToastType["Basic"] = "basic";
ToastType["Success"] = "success";
ToastType["Error"] = "error";
ToastType["Info"] = "info";
ToastType["Warning"] = "warning";
ToastType["Custom"] = "custom";
ToastType["Promise"] = "promise";
ToastType["Component"] = "component";
})(ToastType || (ToastType = {}));
class ToastyService {
constructor(_toastySoundService) {
this._toastySoundService = _toastySoundService;
this.TOASTY_SERVICE_CONFIG = {
capacity: 10,
duration: 5000,
grouping: false,
messages: {
now: 'just now',
seconds: '{0} seconds ago',
minutes: '{0} minutes ago'
},
threshold_offsetX: 170
};
this.queue = new Queue(this.TOASTY_SERVICE_CONFIG.capacity);
this.counter = 0;
this.newToastBehaviorSubject = new BehaviorSubject([]);
this.newToast$ = this.newToastBehaviorSubject.asObservable();
this.deleteToastBehaviorSubject = new BehaviorSubject(undefined);
this.deleteToast$ = this.deleteToastBehaviorSubject.asObservable();
this.updateToastBehaviorSubject = new BehaviorSubject(undefined);
this.updateToast$ = this.updateToastBehaviorSubject.asObservable();
this.scheduled = new Map();
}
setDefaultDuration(d) { this.TOASTY_SERVICE_CONFIG.duration = d; }
getDefaultDuration() { return this.TOASTY_SERVICE_CONFIG.duration; }
setCapacity(c) { this.TOASTY_SERVICE_CONFIG.capacity = c; this.queue.setCapacity(c); }
getCapacity() { return this.queue.getCapacity(); }
getGrouping() { return this.TOASTY_SERVICE_CONFIG.grouping; }
setGrouping(g) { this.TOASTY_SERVICE_CONFIG.grouping = g; }
showToast(title, message, toastConfig) {
const hash = this.hash(title + message + (toastConfig ? JSON.stringify(toastConfig) : ''));
if (this.TOASTY_SERVICE_CONFIG.grouping) {
// check if a toast with the same hash already exists
const existingToast = this.queue.getElements().find(t => t.hash === hash);
if (existingToast) {
existingToast.count++;
//TODO: reset expiration time if not sticky, we have to reset the setTimeout associated to the toast too
// if (!existingToast.config?.sticky && !isPromise) {
// existingToast.expires = Date.now() + existingToast.duration;
// }
// this.newToastBehaviorSubject.next([...this.queue.getElements()]); // Trigger re-render to show updated count
this.refreshToastExpiration(existingToast);
return existingToast.id;
}
}
this.counter++;
// if sticky is true duration is infinity else use the provided duration or the default one
// promise toasts duration is setted inifity at the beginning and updated when the promise is resolved or rejected
const duration = (toastConfig?.sticky || toastConfig?.type == ToastType.Promise) ? Infinity : (toastConfig?.duration ?? this.TOASTY_SERVICE_CONFIG.duration);
const toast = {
id: this.counter,
hash: hash,
title: title,
message: message,
type: toastConfig?.type ?? ToastType.Basic,
timestamp: Date.now(),
duration: duration,
expires: Date.now() + duration,
config: toastConfig,
count: 1,
get icon() {
if (toastConfig?.type == ToastType.Success)
return "✅";
if (toastConfig?.type == ToastType.Info)
return "ℹ️";
if (toastConfig?.type == ToastType.Warning)
return "⚠️";
if (toastConfig?.type == ToastType.Error)
return "❌";
return "";
}
};
if (toastConfig?.schedule) {
this.scheduleToast({ ...toast, schedule: toastConfig.schedule });
}
else {
this.queue.enqueue(toast);
this.newToastBehaviorSubject.next([...this.queue.getElements()]);
this.executeToastFinalLogic(toast);
}
return toast.id;
}
executeToastFinalLogic(toast) {
if (toast.config?.beep) {
this._toastySoundService.notify();
}
// if sticky progress bar is not animated and toast will not expire
if (!toast.config?.sticky) {
if (toast.config?.progressBar) {
setTimeout(() => {
this.updateToast(toast.id, { animatePbar: true });
}, 100);
}
if (toast.config?.type != ToastType.Promise) {
toast.timeOutRef = setTimeout(() => {
this.removeToast(toast.id);
}, toast.expires - Date.now() + 5);
}
}
}
showToastPromise(title, promise, config) {
const idtoast = this.showToast(title, promise.loading, { ...config, type: ToastType.Promise, loading: true });
const duration = config?.duration ?? this.TOASTY_SERVICE_CONFIG.duration;
promise.promise
.then((result) => {
const successMessage = typeof promise.success === 'function'
? promise.success(result)
: promise.success;
this.updateToast(idtoast, {
message: successMessage,
type: ToastType.Success,
expires: Date.now() + duration,
config: { loading: false }
});
})
.catch((err) => {
const errorMessage = typeof promise.error === 'function'
? promise.error(err)
: promise.error;
this.updateToast(idtoast, {
message: errorMessage,
type: ToastType.Error,
expires: Date.now() + duration,
config: { loading: false }
});
})
.finally(() => {
if (!config?.sticky)
setTimeout(() => this.removeToast(idtoast), duration);
});
}
showToastComponent(component, componentParams, config) {
const toastId = this.showToast('', '', { ...config, component: component, componentParams: componentParams, type: ToastType.Component });
return toastId;
}
showToastSchedule(title, message, schedule, config) {
const toastId = this.showToast(title, message, { ...config, schedule });
}
showToastConfirm(title, message, onConfirm, onCancel, config) {
// Primero creamos el toast sin acciones para obtener el ID
const toastId = this.showToast(title, message, {
...config,
sticky: true, // Confirmation toasts should not be dismissible by timeout
type: config?.type || ToastType.Info
});
// Ahora que tenemos el ID, creamos las acciones que lo referencian
const actions = [
{
label: 'OK',
callback: () => {
onConfirm();
this.closeToast(toastId);
},
class: 'confirm-btn'
},
{
label: 'Cancel',
callback: () => {
if (onCancel)
onCancel();
this.closeToast(toastId);
},
class: 'cancel-btn'
}
];
// Actualizamos el toast con las acciones
this.updateToast(toastId, { config: { ...config, actions, sticky: true, type: config?.type || ToastType.Info } });
return toastId;
}
scheduleToast(toast) {
const { startAt, repeatEvery, repeatCount } = toast.schedule;
const now = Date.now();
let delay = 0;
if (startAt && startAt > now) {
delay = startAt - now;
}
[toast.timestamp, toast.expires] = [startAt, startAt + toast.duration];
const ref = { timeoutId: undefined };
this.scheduled.set(toast.id, ref);
ref.timeoutId = setTimeout(() => {
let times = 0;
const showandrepeat = () => {
const now = Date.now();
[toast.timestamp, toast.expires] = [now, now + toast.duration];
this.queue.enqueue(toast);
this.newToastBehaviorSubject.next([...this.queue.getElements()]);
this.executeToastFinalLogic(toast);
times++;
if (repeatEvery && (times < repeatCount || repeatCount == 0)) {
ref.timeoutId = setTimeout(showandrepeat, toast.duration + repeatEvery);
}
else {
this.scheduled.delete(toast.id);
}
};
showandrepeat();
}, delay);
}
removeToast(id) {
this.newToastBehaviorSubject.next([...this.queue.getElements()]); // Trigger re-render to start exit animation
setTimeout(() => { const t = this.queue.removeId(id); this.newToastBehaviorSubject.next([...this.queue.getElements()]); }, 500); // Wait for animation to finish before removing from queue
}
closeToast(id) {
const t = this.queue.removeId(id);
this.newToastBehaviorSubject.next([...this.queue.getElements()]);
}
updateTouchStart(id, touch) {
const gesture = { date: Date.now(), touchStart: touch };
this.updateToast(id, { gesture: gesture });
}
// Loops through the toasts to find which one was touched and updates its touchEnd
updateTouchEnd(touch) {
const toasts = this.queue.getElements().forEach(t => {
if (t.gesture) {
const deltaY = Math.abs(touch.clientY - t.gesture.touchStart.clientY);
const deltaX = Math.abs(touch.clientX - t.gesture.touchStart.clientX);
const deltaTime = Date.now() - t.gesture.date;
if (deltaTime < 500 && deltaX > this.TOASTY_SERVICE_CONFIG.threshold_offsetX) {
this.closeToast(t.id);
}
else {
t.gesture = undefined; // Reset gesture if not swiped
}
}
});
}
updateTouchMove(touch) {
const toasts = this.queue.getElements();
toasts.forEach(t => {
if (t.gesture) {
const deltaX = touch.clientX - t.gesture.touchStart.clientX;
t.gesture.offsetX = deltaX; // Store the offset for potential use in UI updates
}
});
}
updateToast(id, newData) {
const toasts = this.queue.getElements();
const index = toasts.findIndex(t => t.id === id);
if (index !== -1) {
toasts[index] = { ...toasts[index], ...newData };
this.newToastBehaviorSubject.next([...toasts]);
}
}
refreshToastExpiration(param) {
let toast;
if ('id' in param) {
const toasts = this.queue.getElements();
toast = toasts.find(t => t.id === param.id);
}
else {
toast = param.toast;
}
if (toast) {
clearTimeout(toast?.timeOutRef);
toast.expires = Date.now() + toast.duration;
this.executeToastFinalLogic(toast);
this.newToastBehaviorSubject.next([...this.queue.getElements()]);
}
}
hash(str) {
let hash = 5381;
for (let i = 0; i < str.length; i++) {
hash = (hash * 33) ^ str.charCodeAt(i);
}
return (hash >>> 0).toString(16);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ToastyService, deps: [{ token: ToastysoundService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ToastyService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ToastyService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: () => [{ type: ToastysoundService }] });
var ToastyContainerPosition;
(function (ToastyContainerPosition) {
ToastyContainerPosition["TOP_LEFT"] = "top-left";
ToastyContainerPosition["TOP"] = "top";
ToastyContainerPosition["TOP_RIGHT"] = "top-right";
ToastyContainerPosition["BOTTOM_LEFT"] = "bottom-left";
ToastyContainerPosition["BOTTOM"] = "bottom";
ToastyContainerPosition["BOTTOM_RIGHT"] = "bottom-right";
})(ToastyContainerPosition || (ToastyContainerPosition = {}));
class ToastyComponent {
constructor(_toastService) {
this._toastService = _toastService;
this.position = ToastyContainerPosition.BOTTOM_RIGHT;
this.duration = this._toastService.getDefaultDuration();
this.capacity = this._toastService.getCapacity();
this.grouping = this._toastService.getGrouping();
this.ToastType = ToastType; // to use in the template
this.toasts$ = this._toastService.newToast$;
this.positionMap = {
'top-left': ['top', 'left'],
'top': ['top', 'center'],
'top-right': ['top', 'right'],
'bottom-left': ['bottom', 'left'],
'bottom': ['bottom', 'center'],
'bottom-right': ['bottom', 'right'],
};
}
ngOnInit() {
// Subscribe to the toast service to receive new toasts
// this._toastService.newToast$.subscribe(t => {
// if (t) {
// // console.log(t);
// }
// });
this._toastService.deleteToast$.subscribe(t => {
if (t) {
// we dont need to delete toast, because internally in the service the toast has been deleted and in future toasts, only live toasts will be received from the service
// console.log(t);
}
});
this._toastService.updateToast$.subscribe(t => {
if (t) {
// console.log(t);
}
});
}
ngOnChanges(changes) {
if (changes['duration']) {
this._toastService.setDefaultDuration(this.duration);
}
if (changes['capacity']) {
this._toastService.setCapacity(changes['capacity'].currentValue);
}
if (changes['grouping']) {
this._toastService.setGrouping(changes['grouping'].currentValue);
}
}
closeToast(id) {
this._toastService.closeToast(id);
}
executeAction(action) {
action.callback();
}
containerClass() {
return this.positionMap[this.position] ?? [ToastyContainerPosition.BOTTOM_RIGHT];
}
expires(t) {
return (t.expires < Date.now());
}
getDurationUntilExpire(expires) {
const duration = expires - Date.now();
return duration > 0 ? duration : 0;
}
// Swipe to close functionality
onTouchStart(event) {
const toastid = this.localizeToastIdFromTouch(event);
this._toastService.updateTouchStart(toastid, event.changedTouches[0]);
}
onTouchEnd(event) {
this._toastService.updateTouchEnd(event.changedTouches[0]);
}
onTouchMove(event) {
this._toastService.updateTouchMove(event.changedTouches[0]);
}
localizeToastIdFromTouch(event) {
let current = event.target;
let maxnesting = 4;
while (!current.classList.contains('toasty') && maxnesting > 0) {
current = current.parentElement;
maxnesting--;
}
return parseInt(current.getAttribute('data-toast-id') || '0');
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ToastyComponent, deps: [{ token: ToastyService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: ToastyComponent, isStandalone: true, selector: "toasty", inputs: { position: "position", duration: "duration", capacity: "capacity", grouping: "grouping" }, host: { listeners: { "touchstart": "onTouchStart($event)", "touchend": "onTouchEnd($event)", "touchmove": "onTouchMove($event)" } }, usesOnChanges: true, ngImport: i0, template: "<div class=\"toasty-container\" [ngClass]=\"containerClass()\">\r\n @for (item of toasts$ | async; track item.id) {\r\n <div class=\"toasty-wrapper\" [style.transform]=\"'translateX(' + (item.gesture?.offsetX || 0) + 'px)'\"\r\n [style.transition]=\"item.gesture ? 'none' : 'transform 0.3s ease'\">\r\n <div class=\"toasty\" [attr.data-toast-id]=\"item.id\" [ngClass]=\"item.type\" [class.fadeout]=\"expires(item)\"\r\n [ngStyle]=\"item.config?.customStyle\">\r\n\r\n <!-- icon -->\r\n @if (item.config?.loading) {\r\n <div class=\"toasty-loading\"></div>\r\n } @else if (item.type == ToastType.Success) {\r\n <div class=\"toasty-icon\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-6 w-6 shrink-0 stroke-current\" fill=\"none\"\r\n viewBox=\"0 0 24 24\">\r\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"\r\n d=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\" />\r\n </svg>\r\n </div>\r\n } @else if (item.type == ToastType.Error) {\r\n <div class=\"toasty-icon\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-6 w-6 shrink-0 stroke-current\" fill=\"none\"\r\n viewBox=\"0 0 24 24\">\r\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"\r\n d=\"M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z\" />\r\n </svg>\r\n </div>\r\n } @else if (item.type == ToastType.Info) {\r\n <div class=\"toasty-icon\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\"\r\n class=\"h-6 w-6 shrink-0 stroke-current\">\r\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"\r\n d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"></path>\r\n </svg>\r\n </div>\r\n } @else if (item.type == ToastType.Warning) {\r\n <div class=\"toasty-icon\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-6 w-6 shrink-0 stroke-current\" fill=\"none\"\r\n viewBox=\"0 0 24 24\">\r\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"\r\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\" />\r\n </svg>\r\n </div>\r\n } @else {\r\n <div class=\"toasty-noicon\"></div>\r\n }\r\n\r\n <div class=\"toasty-content\">\r\n @if (item.config?.type == ToastType.Component && item.config?.component) {\r\n <ng-container *ngComponentOutlet=\"item.config?.component ?? null; \r\n inputs: item.config?.componentParams\">\r\n </ng-container>\r\n } @else {\r\n @if (item.title) {\r\n <div class=\"toasty-header\">\r\n <strong class=\"toasty-title\">{{item.title}}</strong>\r\n @if (item.count > 1) {\r\n <span class=\"toasty-badge\">x{{item.count}}</span>\r\n }\r\n <!-- <small class=\"toasty-time\">{{item.timestamp}}</small> -->\r\n </div>\r\n }\r\n @if (item.config?.enableHtml) {\r\n <div class=\"toasty-body\" [innerHTML]=\"item.message\"></div>\r\n } @else {\r\n <div class=\"toasty-body\">{{item.message}}</div>\r\n }\r\n \r\n @if (item.config?.actions?.length) {\r\n <div class=\"toasty-actions\">\r\n @for (action of item.config?.actions; track $index) {\r\n <button type=\"button\" \r\n class=\"toasty-action-btn\"\r\n [ngClass]=\"action?.class\"\r\n (click)=\"executeAction(action)\">\r\n {{action?.label}}\r\n </button>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n <div class=\"toasty-close\">\r\n <button type=\"button\" class=\"toasty-close\" (click)=\"closeToast(item.id)\">×</button>\r\n </div>\r\n @if (item.config?.progressBar) {\r\n <div class=\"toasty-progress\" [class.animate]=\"item.animatePbar\"\r\n [style.transition-duration.ms]=\"item.duration\"></div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n</div>", styles: [".toasty-container{position:fixed;width:350px;padding:.5rem;right:0;bottom:0;pointer-events:none}.toasty-container.left{left:12px}.toasty-container.center{left:50%;transform:translate(-50%)}.toasty-container.right{right:12px}.toasty-container.top{top:10px;display:flex;flex-direction:column-reverse;justify-content:flex-end}.toasty-container.bottom{bottom:0}.toasty{padding:10px;border:1px solid rgba(0,0,0,.15);border-radius:8px;box-shadow:0 4px 10px #00000026;background-color:#fff;overflow:hidden;margin-bottom:1rem;font-family:Arial,Helvetica,sans-serif;animation-name:toasty_fadein;animation-duration:.3s;animation-timing-function:ease-out;animation-delay:0s;animation-iteration-count:1;animation-fill-mode:forwards;display:flex;align-items:center;pointer-events:auto;touch-action:manipulation}@keyframes toasty_fadein{0%{opacity:0;transform:translate(100%)}to{opacity:1;transform:translate(0)}}@keyframes toasty_fadeout{0%{opacity:1}to{opacity:0;pointer-events:none}}.toasty.fadeout{animation-name:toasty_fadeout;animation-duration:.5s;animation-timing-function:ease-in;animation-delay:0s;animation-iteration-count:1;animation-fill-mode:forwards;pointer-events:none}.toasty-icon{width:20px;height:20px;margin-right:.6rem;border-radius:4px}.toasty-loading{width:10px;height:10px;margin-right:.6rem;border-radius:50%;display:inline-block;border-top:3px solid;border-right:3px solid transparent;box-sizing:border-box;animation:loading-toasty-anim 1s linear infinite}@keyframes loading-toasty-anim{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.toasty-noicon{width:7px}.toasty-content{flex-direction:column;flex:1}.toasty-header{display:flex;align-items:center}.toasty-title{font-size:.95rem;font-weight:700;margin-right:auto}.toasty-time{font-size:.85rem;color:#6c757d;margin-right:1rem}.toasty-close{background:none;border:none;font-size:1.4rem;line-height:1;cursor:pointer;color:inherit;opacity:.6}.toasty-body{font-size:.9rem;margin-top:1px}.toasty-close:hover{opacity:1}.toasty.none{background-color:#fff;border-color:#ededed;color:#171717}.toasty.success{background-color:#ecfdf3;border-color:#bffcd9;color:#008a2e}.toasty.error{background-color:#fff0f0;border-color:#ffe0e1;color:#e60000}.toasty.info{background-color:#f0f8ff;border-color:#dde7fd;color:#0973dc}.toasty.warning{background-color:#fffcf0;border-color:#fbeeb1;color:#dc7609}.toasty.custom{background-color:#fff}.toasty-progress{position:absolute;bottom:0;left:0;height:4px;width:100%;background-color:currentColor;opacity:.3;transform-origin:left;transform:scaleX(1);transition-property:transform;transition-timing-function:linear}.toasty-progress.animate{transform:scaleX(0)}.toasty-badge{position:absolute;top:2px;right:8px;font-size:.65rem;padding:1px 5px;border-radius:999px;font-weight:700;background:#0000001a;color:currentColor}.toasty-actions{display:flex;gap:8px;margin-top:12px;justify-content:flex-end}.toasty-action-btn{padding:6px 12px;border:1px solid transparent;border-radius:4px;cursor:pointer;font-size:.85rem;font-weight:500;transition:all .2s ease;background:#0000000d;color:inherit}.toasty-action-btn:hover{transform:translateY(-1px)}.toasty-action-btn.confirm-btn{background:#0973dc;color:#fff;opacity:.7}.toasty-action-btn.confirm-btn:hover{opacity:1;transform:translateY(-1px)}.toasty-action-btn.cancel-btn{background:#0000001a;color:#000000b3}.toasty-action-btn.cancel-btn:hover{background:#00000040}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ToastyComponent, decorators: [{
type: Component,
args: [{ selector: 'toasty', standalone: true, imports: [NgClass, NgStyle, AsyncPipe, NgComponentOutlet], template: "<div class=\"toasty-container\" [ngClass]=\"containerClass()\">\r\n @for (item of toasts$ | async; track item.id) {\r\n <div class=\"toasty-wrapper\" [style.transform]=\"'translateX(' + (item.gesture?.offsetX || 0) + 'px)'\"\r\n [style.transition]=\"item.gesture ? 'none' : 'transform 0.3s ease'\">\r\n <div class=\"toasty\" [attr.data-toast-id]=\"item.id\" [ngClass]=\"item.type\" [class.fadeout]=\"expires(item)\"\r\n [ngStyle]=\"item.config?.customStyle\">\r\n\r\n <!-- icon -->\r\n @if (item.config?.loading) {\r\n <div class=\"toasty-loading\"></div>\r\n } @else if (item.type == ToastType.Success) {\r\n <div class=\"toasty-icon\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-6 w-6 shrink-0 stroke-current\" fill=\"none\"\r\n viewBox=\"0 0 24 24\">\r\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"\r\n d=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\" />\r\n </svg>\r\n </div>\r\n } @else if (item.type == ToastType.Error) {\r\n <div class=\"toasty-icon\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-6 w-6 shrink-0 stroke-current\" fill=\"none\"\r\n viewBox=\"0 0 24 24\">\r\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"\r\n d=\"M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z\" />\r\n </svg>\r\n </div>\r\n } @else if (item.type == ToastType.Info) {\r\n <div class=\"toasty-icon\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\"\r\n class=\"h-6 w-6 shrink-0 stroke-current\">\r\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"\r\n d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"></path>\r\n </svg>\r\n </div>\r\n } @else if (item.type == ToastType.Warning) {\r\n <div class=\"toasty-icon\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-6 w-6 shrink-0 stroke-current\" fill=\"none\"\r\n viewBox=\"0 0 24 24\">\r\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"\r\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\" />\r\n </svg>\r\n </div>\r\n } @else {\r\n <div class=\"toasty-noicon\"></div>\r\n }\r\n\r\n <div class=\"toasty-content\">\r\n @if (item.config?.type == ToastType.Component && item.config?.component) {\r\n <ng-container *ngComponentOutlet=\"item.config?.component ?? null; \r\n inputs: item.config?.componentParams\">\r\n </ng-container>\r\n } @else {\r\n @if (item.title) {\r\n <div class=\"toasty-header\">\r\n <strong class=\"toasty-title\">{{item.title}}</strong>\r\n @if (item.count > 1) {\r\n <span class=\"toasty-badge\">x{{item.count}}</span>\r\n }\r\n <!-- <small class=\"toasty-time\">{{item.timestamp}}</small> -->\r\n </div>\r\n }\r\n @if (item.config?.enableHtml) {\r\n <div class=\"toasty-body\" [innerHTML]=\"item.message\"></div>\r\n } @else {\r\n <div class=\"toasty-body\">{{item.message}}</div>\r\n }\r\n \r\n @if (item.config?.actions?.length) {\r\n <div class=\"toasty-actions\">\r\n @for (action of item.config?.actions; track $index) {\r\n <button type=\"button\" \r\n class=\"toasty-action-btn\"\r\n [ngClass]=\"action?.class\"\r\n (click)=\"executeAction(action)\">\r\n {{action?.label}}\r\n </button>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n <div class=\"toasty-close\">\r\n <button type=\"button\" class=\"toasty-close\" (click)=\"closeToast(item.id)\">×</button>\r\n </div>\r\n @if (item.config?.progressBar) {\r\n <div class=\"toasty-progress\" [class.animate]=\"item.animatePbar\"\r\n [style.transition-duration.ms]=\"item.duration\"></div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n</div>", styles: [".toasty-container{position:fixed;width:350px;padding:.5rem;right:0;bottom:0;pointer-events:none}.toasty-container.left{left:12px}.toasty-container.center{left:50%;transform:translate(-50%)}.toasty-container.right{right:12px}.toasty-container.top{top:10px;display:flex;flex-direction:column-reverse;justify-content:flex-end}.toasty-container.bottom{bottom:0}.toasty{padding:10px;border:1px solid rgba(0,0,0,.15);border-radius:8px;box-shadow:0 4px 10px #00000026;background-color:#fff;overflow:hidden;margin-bottom:1rem;font-family:Arial,Helvetica,sans-serif;animation-name:toasty_fadein;animation-duration:.3s;animation-timing-function:ease-out;animation-delay:0s;animation-iteration-count:1;animation-fill-mode:forwards;display:flex;align-items:center;pointer-events:auto;touch-action:manipulation}@keyframes toasty_fadein{0%{opacity:0;transform:translate(100%)}to{opacity:1;transform:translate(0)}}@keyframes toasty_fadeout{0%{opacity:1}to{opacity:0;pointer-events:none}}.toasty.fadeout{animation-name:toasty_fadeout;animation-duration:.5s;animation-timing-function:ease-in;animation-delay:0s;animation-iteration-count:1;animation-fill-mode:forwards;pointer-events:none}.toasty-icon{width:20px;height:20px;margin-right:.6rem;border-radius:4px}.toasty-loading{width:10px;height:10px;margin-right:.6rem;border-radius:50%;display:inline-block;border-top:3px solid;border-right:3px solid transparent;box-sizing:border-box;animation:loading-toasty-anim 1s linear infinite}@keyframes loading-toasty-anim{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.toasty-noicon{width:7px}.toasty-content{flex-direction:column;flex:1}.toasty-header{display:flex;align-items:center}.toasty-title{font-size:.95rem;font-weight:700;margin-right:auto}.toasty-time{font-size:.85rem;color:#6c757d;margin-right:1rem}.toasty-close{background:none;border:none;font-size:1.4rem;line-height:1;cursor:pointer;color:inherit;opacity:.6}.toasty-body{font-size:.9rem;margin-top:1px}.toasty-close:hover{opacity:1}.toasty.none{background-color:#fff;border-color:#ededed;color:#171717}.toasty.success{background-color:#ecfdf3;border-color:#bffcd9;color:#008a2e}.toasty.error{background-color:#fff0f0;border-color:#ffe0e1;color:#e60000}.toasty.info{background-color:#f0f8ff;border-color:#dde7fd;color:#0973dc}.toasty.warning{background-color:#fffcf0;border-color:#fbeeb1;color:#dc7609}.toasty.custom{background-color:#fff}.toasty-progress{position:absolute;bottom:0;left:0;height:4px;width:100%;background-color:currentColor;opacity:.3;transform-origin:left;transform:scaleX(1);transition-property:transform;transition-timing-function:linear}.toasty-progress.animate{transform:scaleX(0)}.toasty-badge{position:absolute;top:2px;right:8px;font-size:.65rem;padding:1px 5px;border-radius:999px;font-weight:700;background:#0000001a;color:currentColor}.toasty-actions{display:flex;gap:8px;margin-top:12px;justify-content:flex-end}.toasty-action-btn{padding:6px 12px;border:1px solid transparent;border-radius:4px;cursor:pointer;font-size:.85rem;font-weight:500;transition:all .2s ease;background:#0000000d;color:inherit}.toasty-action-btn:hover{transform:translateY(-1px)}.toasty-action-btn.confirm-btn{background:#0973dc;color:#fff;opacity:.7}.toasty-action-btn.confirm-btn:hover{opacity:1;transform:translateY(-1px)}.toasty-action-btn.cancel-btn{background:#0000001a;color:#000000b3}.toasty-action-btn.cancel-btn:hover{background:#00000040}\n"] }]
}], ctorParameters: () => [{ type: ToastyService }], propDecorators: { position: [{
type: Input
}], duration: [{
type: Input
}], capacity: [{
type: Input
}], grouping: [{
type: Input
}], onTouchStart: [{
type: HostListener,
args: ['touchstart', ['$event']]
}], onTouchEnd: [{
type: HostListener,
args: ['touchend', ['$event']]
}], onTouchMove: [{
type: HostListener,
args: ['touchmove', ['$event']]
}] } });
/*
* Public API Surface of ng-toast-notify
*/
/**
* Generated bundle index. Do not edit.
*/
export { ToastType, ToastyComponent, ToastyService };
//# sourceMappingURL=ng-toast-notify.mjs.map