@serene-dev/toast-notifications
Version:
This is an Angular Toast Notification library.
315 lines (306 loc) • 23.3 kB
JavaScript
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