UNPKG

@serene-dev/toast-notifications

Version:

This is an Angular Toast Notification library.

315 lines (306 loc) 23.3 kB
import * as i0 from '@angular/core'; import { signal, computed, Injectable, inject, Pipe, ChangeDetectorRef, input, output, effect, Component, ChangeDetectionStrategy } from '@angular/core'; import { Subject, of } from 'rxjs'; import * as i1 from '@angular/common'; import { CommonModule } from '@angular/common'; /** * Enum for the different notification tags */ var EToastTag; (function (EToastTag) { /** Danger */ EToastTag["danger"] = "DANGER"; /** Success */ EToastTag["success"] = "SUCCESS"; /** Info */ EToastTag["info"] = "INFO"; /** Warning */ EToastTag["warning"] = "WARNING"; })(EToastTag || (EToastTag = {})); /**Maps tag enum values to tag numerical values */ const ToastTagMap = { [EToastTag.danger]: 0, [EToastTag.success]: 1, [EToastTag.info]: 2, [EToastTag.warning]: 3, }; /**Maps the numerical tags to the enums */ const ToastTagReverseMap = { 0: EToastTag.danger, 1: EToastTag.success, 2: EToastTag.info, 3: EToastTag.warning, }; /**Service to manage all instances of the notification component internally. */ class ToastNotificationsInternalService { constructor() { this._toastNotifications = signal({}); this.toastNotifications = computed(() => Object.values(this._toastNotifications())?.reverse() || []); this._config = signal({ showExpansionButton: true, xPosition: 'left', defaultDuration: 5000, }); this.config = computed(() => this._config()); this.generateUUID = () => { let d = new Date().getTime(); let d2 = (typeof performance !== 'undefined' && performance.now && performance.now() * 1000) || 0; //Time in microseconds since page-load or 0 if unsupported return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { let r = Math.random() * 16; //random number between 0 and 16 if (d > 0) { //Use timestamp until depleted r = (d + r) % 16 | 0; d = Math.floor(d / 16); } else { //Use microseconds since page-load if supported r = (d2 + r) % 16 | 0; d2 = Math.floor(d2 / 16); } return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16); }); }; /** * Adds a new notification item * @param item * @returns */ this.addToastNotification = (item) => { const id = this.generateUUID(); const { defaultDuration } = this._config(), duration = item.manuallyDismiss ? null : item.duration || defaultDuration, closed = new Subject(), formattedItem = this.formatToast({ ...item, duration, datetime: Date.now(), id, closed, expandable: false, }); this._toastNotifications.update((map) => ({ ...map, [id]: formattedItem, })); this.handleDuration(formattedItem); return { id, update: (update) => this.updateToastNotification(id, update), close: () => this.closeToastNotification(id, false), loader: { play: () => this.updateToastNotification(id, { loading: true }), stop: () => this.updateToastNotification(id, { loading: false }), }, closed: closed, }; }; this.formatToast = (item) => { item._tag = typeof item.tag == 'string' ? item.tag : ToastTagReverseMap[item.tag] || EToastTag.info; item._iconClass = item.iconClass || `fa fa-${item._tag == EToastTag.success ? 'check-circle' : item._tag == EToastTag.info ? 'info-circle' : 'warning'}`; item.expandable = !!item.body; return item; }; this.handleDuration = (item) => { if (item?.duration != null) setTimeout(() => this.closeToastNotification(item.id, false), item.duration || 0); }; /**Update the details of a notification */ this.updateToastNotification = (id, item) => { this._toastNotifications.update((map) => { if (!map[id]) return map; const f = this.formatToast({ ...map[id], ...item }); this.handleDuration({ id, duration: f.duration }); return { ...map, [id]: f }; }); }; /**Toggle the expansion state of a notification */ this.toggleToastNotification = (id, expand) => this._toastNotifications.update((map) => { if (!map[id]) return map; return { ...map, [id]: { ...map[id], expanded: expand == null ? !map[id].expanded : expand }, }; }); /**Close a notification */ this.closeToastNotification = (id, manualDismissal) => { return this._toastNotifications.update((map) => { if (map[id]) { map[id].closed.next({ manuallyClosed: manualDismissal }); map[id].closed.complete(); delete map[id]; } return { ...map }; }); }; } updateConfig(val) { this._config.update((config) => ({ ...config, ...val })); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ToastNotificationsInternalService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ToastNotificationsInternalService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ToastNotificationsInternalService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }] }); /**Service to manage all instances of the notification component. */ class ToastNotificationsService { constructor() { this.internalService = inject(ToastNotificationsInternalService); /**Global configuration */ this.config = computed(() => this.internalService.config()); /**Shortcut functions for managing the notifications */ this.toastShortcut = () => ({ add: this.internalService.addToastNotification, close: this.internalService.closeToastNotification, // toggle: this.internalService.toggleToastNotification, update: this.internalService.updateToastNotification, }); } /** * Update the global configuration. * @param val */ updateConfig(val) { this.internalService.updateConfig(val); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ToastNotificationsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ToastNotificationsService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ToastNotificationsService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }] }); /** * Value formatter pipe. */ class ToastNotificationsLabelPipe { /** * Transforms the string texts passed into it using the preferred formatter function. The formatter function can be set in the Toast Notification Service * @param value * @returns */ transform(value) { return this.service.labelPipe ? this.service.labelPipe(value) : of(value); } constructor(service) { this.service = service; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ToastNotificationsLabelPipe, deps: [{ token: ToastNotificationsService }], target: i0.ɵɵFactoryTarget.Pipe }); } static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.1.1", ngImport: i0, type: ToastNotificationsLabelPipe, isStandalone: true, name: "tnLabelPipe" }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ToastNotificationsLabelPipe, decorators: [{ type: Pipe, args: [{ name: 'tnLabelPipe', standalone: true, }] }], ctorParameters: () => [{ type: ToastNotificationsService }] }); /**Toast notification component. * * Reference this in your app.html file to display the notifications in the root of your UI */ class ToastNotificationsComponent { constructor() { this.service = inject(ToastNotificationsInternalService); this.cdr = inject(ChangeDetectorRef); /**Specify if to show the expansion button for all notifications. It overrides the global settings. */ this._showExpansionButton = input(undefined, { alias: 'showExpansionButton', }); /**Horizontal position of the notification panel. It overrides the global settings. */ this._xPosition = input(undefined, { alias: 'xPosition', }); /**Expansion handler in the event that the expand button is clicked. */ this._expandHandler = input(undefined, { alias: 'expandHandler', }); /**Output emitter to notify if the expansion button is clicked. */ this.expanded = output(); this.showExpansionButton = computed(() => { const genConfig = this.service.config(); return this._showExpansionButton() != null ? this._showExpansionButton() : genConfig?.showExpansionButton; }); this.xPosition = computed(() => { const genConfig = this.service.config(); return this._xPosition() != null ? this._xPosition() : genConfig?.xPosition; }); this.expandHandler = computed(() => { const genConfig = this.service.config(); return this._expandHandler() != null ? this._expandHandler() : genConfig?.expandHandler; }); this.style = computed(() => { const xPosition = this.xPosition(); if (xPosition == 'right') return { right: 0 }; else if (xPosition == 'left') return { left: 0 }; else if (xPosition == 'center') return { left: '50%', right: '50%', transform: 'translate(-50%, 0%)' }; return {}; }); this.notifications = computed(() => this.service.toastNotifications()); this.notificationsEffect = effect(() => { this.notifications(); this.cdr.markForCheck(); setTimeout(() => this.cdr.markForCheck(), 500); setTimeout(() => this.cdr.markForCheck(), 1000); }); } expand(item) { (item.expansionHandler ?? this.expandHandler())?.(item); this.expanded.emit(item); } /** * Adds a new notification item * @param item * @returns */ add(item) { return this.service.addToastNotification(item); } /** * Close a notification item * @param id ID of the notification * @param manualDismissal Indicate if it is a manual dismissal * @returns */ close(id, manualDismissal) { return this.service.closeToastNotification(id, manualDismissal); } /** * Update the details of a notification item * @param id ID of the notification * @param item Notification item * @returns */ update(id, item) { return this.service.updateToastNotification(id, item); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ToastNotificationsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.1", type: ToastNotificationsComponent, isStandalone: true, selector: "toast-notifications", inputs: { _showExpansionButton: { classPropertyName: "_showExpansionButton", publicName: "showExpansionButton", isSignal: true, isRequired: false, transformFunction: null }, _xPosition: { classPropertyName: "_xPosition", publicName: "xPosition", isSignal: true, isRequired: false, transformFunction: null }, _expandHandler: { classPropertyName: "_expandHandler", publicName: "expandHandler", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expanded: "expanded" }, ngImport: i0, template: "<div class=\"toast-list\" [style]=\"style()\">\n @for (item of notifications(); track item.id) {\n <div class=\"toast-item toast-tag-{{ item._tag | lowercase }} {{ item.toastClass }} \">\n <div class=\"toast-item-content\" [style.width]=\"'calc(100% - ' + side.offsetWidth + 'px )'\">\n <div\n class=\"toast-item-header\"\n [ngClass]=\"{ compact: !item.expanded }\"\n (click)=\"service.toggleToastNotification(item.id)\"\n >\n <i class=\"toast-icon {{ item._iconClass }}\"></i>\n <span [innerHTML]=\"item.header | tnLabelPipe | async\"></span>\n </div>\n <!-- <hr [hidden]=\"!item.expanded\" class=\"toast-item-divider\" /> -->\n <div\n [hidden]=\"!item.expanded || !item.expandable\"\n class=\"toast-item-body\"\n [innerHTML]=\"item.body | tnLabelPipe | async\"\n ></div>\n </div>\n <div class=\"d-flex align-items-center\" #side>\n @if (item.loading) {\n <div class=\"toast-item-loader\">\n <i class=\"fa fa-spinner fa-spin\"></i>\n </div>\n }\n <div class=\"toast-item-action\">\n @if ((showExpansionButton() && item.expandable) || item.expansionHandler) {\n <button class=\"cbtn\" (click)=\"expand(item)\">\n <i class=\"fa fa-expand\"></i>\n </button>\n }\n <button class=\"cbtn\" (click)=\"service.closeToastNotification(item.id, true)\">\n <i class=\"fa fa-close\"></i>\n </button>\n </div>\n </div>\n </div>\n }\n</div>\n", styles: ["::ng-deep .toast-list{display:block;position:fixed;z-index:1000;bottom:0;padding:0 10px;width:min(100%,400px);--toast-color: #000;--toast-color: #fff;--toast-blur: blur(1.5px);--toast-danger-color: 255, 0, 0;--toast-success-color: 0, 209, 0;--toast-info-color: 45, 143, 255;--toast-warning-color: 255, 255, 0;--toast-icon-color: var(--toast-color, #fff);--toast-loader-color: var(--toast-color);--mdc-circular-progress-active-indicator-color: var(--toast-loader-color);--toast-background-color: #dbdbdb80;max-height:90vh;overflow-y:auto}::ng-deep .toast-list .toast-item{--toast-item-radius: 10px;--item-margin: 5px;display:flex;align-items:center;box-shadow:0 0 10px #00000042;color:var(--toast-color);background-color:var(--toast-background-color);backdrop-filter:var(--toast-blur);-webkit-backdrop-filter:var(--toast-blur);border-radius:var(--toast-item-radius);margin-top:calc(var(--item-margin) * 2);margin-bottom:var(--item-margin);background-image:linear-gradient(225deg,rgba(0,0,0,.1019607843),transparent)}::ng-deep .toast-list .toast-item .toast-item-content{padding:var(--item-margin) calc(var(--item-margin) * 2)}::ng-deep .toast-list .toast-item .toast-item-content .toast-item-header{cursor:pointer;font-size:14px;display:flex;align-items:center}::ng-deep .toast-list .toast-item .toast-item-content .toast-item-header.compact{text-overflow:ellipsis;white-space:nowrap;overflow:hidden;word-wrap:break-word}::ng-deep .toast-list .toast-item .toast-item-content .toast-item-body{font-size:13px;word-wrap:break-word;opacity:.8;white-space:break-spaces}::ng-deep .toast-list .toast-item .toast-item-content .toast-item-divider{opacity:.3;border:1px solid var(--toast-color);margin-top:1px;margin-bottom:0;width:30%}::ng-deep .toast-list .toast-item .toast-item-action{color:var(--toast-color);padding-right:2px;cursor:pointer;display:flex;align-items:center}::ng-deep .toast-list .toast-item .toast-item-action .cbtn{color:var(--toast-color);border:none;background-color:transparent;outline:none;padding:0 7px}::ng-deep .toast-list .toast-item .toast-icon{color:var(--toast-icon-color);margin-right:5px}::ng-deep .toast-list .toast-tag-danger{--toast-icon-color: rgb(var(--toast-danger-color));--toast-icon-color: var(--toast-color);--toast-background-color: rgb(var(--toast-danger-color), .8)}::ng-deep .toast-list .toast-tag-success{--toast-icon-color: rgb(var(--toast-success-color));--toast-icon-color: var(--toast-color);--toast-background-color: rgb(var(--toast-success-color), .8)}::ng-deep .toast-list .toast-tag-info{--toast-icon-color: rgb(var(--toast-info-color));--toast-icon-color: var(--toast-color);--toast-background-color: rgb(var(--toast-info-color), .8)}::ng-deep .toast-list .toast-tag-warning{--toast-icon-color: rgb(var(--toast-warning-color));--toast-color: #000;--toast-icon-color: var(--toast-color);--toast-background-color: rgb(var(--toast-warning-color), .8)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1.LowerCasePipe, name: "lowercase" }, { kind: "pipe", type: ToastNotificationsLabelPipe, name: "tnLabelPipe" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ToastNotificationsComponent, decorators: [{ type: Component, args: [{ selector: 'toast-notifications', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, ToastNotificationsLabelPipe], template: "<div class=\"toast-list\" [style]=\"style()\">\n @for (item of notifications(); track item.id) {\n <div class=\"toast-item toast-tag-{{ item._tag | lowercase }} {{ item.toastClass }} \">\n <div class=\"toast-item-content\" [style.width]=\"'calc(100% - ' + side.offsetWidth + 'px )'\">\n <div\n class=\"toast-item-header\"\n [ngClass]=\"{ compact: !item.expanded }\"\n (click)=\"service.toggleToastNotification(item.id)\"\n >\n <i class=\"toast-icon {{ item._iconClass }}\"></i>\n <span [innerHTML]=\"item.header | tnLabelPipe | async\"></span>\n </div>\n <!-- <hr [hidden]=\"!item.expanded\" class=\"toast-item-divider\" /> -->\n <div\n [hidden]=\"!item.expanded || !item.expandable\"\n class=\"toast-item-body\"\n [innerHTML]=\"item.body | tnLabelPipe | async\"\n ></div>\n </div>\n <div class=\"d-flex align-items-center\" #side>\n @if (item.loading) {\n <div class=\"toast-item-loader\">\n <i class=\"fa fa-spinner fa-spin\"></i>\n </div>\n }\n <div class=\"toast-item-action\">\n @if ((showExpansionButton() && item.expandable) || item.expansionHandler) {\n <button class=\"cbtn\" (click)=\"expand(item)\">\n <i class=\"fa fa-expand\"></i>\n </button>\n }\n <button class=\"cbtn\" (click)=\"service.closeToastNotification(item.id, true)\">\n <i class=\"fa fa-close\"></i>\n </button>\n </div>\n </div>\n </div>\n }\n</div>\n", styles: ["::ng-deep .toast-list{display:block;position:fixed;z-index:1000;bottom:0;padding:0 10px;width:min(100%,400px);--toast-color: #000;--toast-color: #fff;--toast-blur: blur(1.5px);--toast-danger-color: 255, 0, 0;--toast-success-color: 0, 209, 0;--toast-info-color: 45, 143, 255;--toast-warning-color: 255, 255, 0;--toast-icon-color: var(--toast-color, #fff);--toast-loader-color: var(--toast-color);--mdc-circular-progress-active-indicator-color: var(--toast-loader-color);--toast-background-color: #dbdbdb80;max-height:90vh;overflow-y:auto}::ng-deep .toast-list .toast-item{--toast-item-radius: 10px;--item-margin: 5px;display:flex;align-items:center;box-shadow:0 0 10px #00000042;color:var(--toast-color);background-color:var(--toast-background-color);backdrop-filter:var(--toast-blur);-webkit-backdrop-filter:var(--toast-blur);border-radius:var(--toast-item-radius);margin-top:calc(var(--item-margin) * 2);margin-bottom:var(--item-margin);background-image:linear-gradient(225deg,rgba(0,0,0,.1019607843),transparent)}::ng-deep .toast-list .toast-item .toast-item-content{padding:var(--item-margin) calc(var(--item-margin) * 2)}::ng-deep .toast-list .toast-item .toast-item-content .toast-item-header{cursor:pointer;font-size:14px;display:flex;align-items:center}::ng-deep .toast-list .toast-item .toast-item-content .toast-item-header.compact{text-overflow:ellipsis;white-space:nowrap;overflow:hidden;word-wrap:break-word}::ng-deep .toast-list .toast-item .toast-item-content .toast-item-body{font-size:13px;word-wrap:break-word;opacity:.8;white-space:break-spaces}::ng-deep .toast-list .toast-item .toast-item-content .toast-item-divider{opacity:.3;border:1px solid var(--toast-color);margin-top:1px;margin-bottom:0;width:30%}::ng-deep .toast-list .toast-item .toast-item-action{color:var(--toast-color);padding-right:2px;cursor:pointer;display:flex;align-items:center}::ng-deep .toast-list .toast-item .toast-item-action .cbtn{color:var(--toast-color);border:none;background-color:transparent;outline:none;padding:0 7px}::ng-deep .toast-list .toast-item .toast-icon{color:var(--toast-icon-color);margin-right:5px}::ng-deep .toast-list .toast-tag-danger{--toast-icon-color: rgb(var(--toast-danger-color));--toast-icon-color: var(--toast-color);--toast-background-color: rgb(var(--toast-danger-color), .8)}::ng-deep .toast-list .toast-tag-success{--toast-icon-color: rgb(var(--toast-success-color));--toast-icon-color: var(--toast-color);--toast-background-color: rgb(var(--toast-success-color), .8)}::ng-deep .toast-list .toast-tag-info{--toast-icon-color: rgb(var(--toast-info-color));--toast-icon-color: var(--toast-color);--toast-background-color: rgb(var(--toast-info-color), .8)}::ng-deep .toast-list .toast-tag-warning{--toast-icon-color: rgb(var(--toast-warning-color));--toast-color: #000;--toast-icon-color: var(--toast-color);--toast-background-color: rgb(var(--toast-warning-color), .8)}\n"] }] }] }); /* * Public API Surface of toast-notifications */ /** * Generated bundle index. Do not edit. */ export { EToastTag, ToastNotificationsComponent, ToastNotificationsLabelPipe, ToastNotificationsService, ToastTagMap, ToastTagReverseMap }; //# sourceMappingURL=serene-dev-toast-notifications.mjs.map