UNPKG

ng-toast-notify

Version:

Lightweight and flexible toast notifications for Angular

486 lines (479 loc) 37.6 kB
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)\">&times;</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)\">&times;</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