UNPKG

23.1 kBJavaScriptView Raw
1import { Injectable, ɵɵdefineInjectable, Component, Input, EventEmitter, ComponentFactoryResolver, ChangeDetectorRef, NgZone, ElementRef, Renderer2, ViewChild, ViewContainerRef, Output, HostListener, Pipe, NgModule } from '@angular/core';
2import { trigger, state, style, transition, animate, group } from '@angular/animations';
3import { Observable, Subject } from 'rxjs';
4import { share } from 'rxjs/operators';
5import { CommonModule } from '@angular/common';
6import { DomSanitizer } from '@angular/platform-browser';
7
8const Transitions = [
9 trigger('toastState', [
10 state('flyRight, flyLeft, slideDown, slideDown, slideUp, slideUp, fade', style({ opacity: 1, transform: 'translate(0,0)' })),
11 transition('void => flyRight', [
12 style({
13 opacity: 0,
14 transform: 'translateX(100%)',
15 height: 0
16 }),
17 animate('0.15s ease-in', style({
18 opacity: 1,
19 height: '*'
20 })),
21 animate('0.25s 15ms ease-in')
22 ]),
23 transition('flyRight => void', [
24 animate('0.25s ease-out', style({
25 opacity: 0,
26 transform: 'translateX(100%)'
27 })),
28 animate('0.15s ease-out', style({
29 height: 0
30 }))
31 ]),
32 transition('void => flyLeft', [
33 style({
34 opacity: 0,
35 transform: 'translateX(-100%)',
36 height: 0
37 }),
38 animate('0.15s ease-in', style({
39 opacity: 1,
40 height: '*'
41 })),
42 animate('0.25s 15ms ease-in')
43 ]),
44 transition('flyLeft => void', [
45 animate('0.25s 10ms ease-out', style({
46 opacity: 0,
47 transform: 'translateX(-100%)'
48 })),
49 animate('0.15s ease-out', style({
50 height: 0
51 }))
52 ]),
53 transition('void => slideDown', [
54 style({
55 opacity: 0,
56 transform: 'translateY(-500%)',
57 height: 0
58 }),
59 group([
60 animate('0.2s ease-in', style({
61 height: '*'
62 })),
63 animate('0.4s ease-in', style({
64 transform: 'translate(0,0)'
65 })),
66 animate('0.3s 0.1s ease-in', style({
67 opacity: 1
68 }))
69 ])
70 ]),
71 transition('slideDown => void', group([
72 animate('0.3s ease-out', style({
73 opacity: 0
74 })),
75 animate('0.4s ease-out', style({
76 transform: 'translateY(-500%)'
77 })),
78 animate('0.2s 0.2s ease-out', style({
79 height: 0
80 }))
81 ])),
82 transition('void => slideUp', [
83 style({
84 opacity: 0,
85 transform: 'translateY(1000%)',
86 height: 0
87 }),
88 group([
89 animate('0.2s ease-in', style({
90 height: '*'
91 })),
92 animate('0.5s ease-in', style({
93 transform: 'translate(0,0)'
94 })),
95 animate('0.3s 0.1s ease-in', style({
96 opacity: 1
97 }))
98 ])
99 ]),
100 transition('slideUp => void', group([
101 animate('0.3s ease-out', style({
102 opacity: 0
103 })),
104 animate('0.5s ease-out', style({
105 transform: 'translateY(1000%)'
106 })),
107 animate('0.3s 0.15s ease-out', style({
108 height: 0
109 }))
110 ])),
111 transition('void => fade', [
112 style({
113 opacity: 0,
114 height: 0
115 }),
116 animate('0.20s ease-in', style({
117 height: '*'
118 })),
119 animate('0.15s ease-in', style({
120 opacity: 1
121 }))
122 ]),
123 transition('fade => void', [
124 group([
125 animate('0.0s ease-out', style({
126 // reposition the background to prevent
127 // a ghost image during transition
128 'background-position': '-99999px'
129 })),
130 animate('0.15s ease-out', style({
131 opacity: 0,
132 'background-image': ''
133 })),
134 animate('0.3s 20ms ease-out', style({
135 height: 0
136 }))
137 ])
138 ])
139 ]),
140];
141
142var BodyOutputType;
143(function (BodyOutputType) {
144 BodyOutputType[BodyOutputType["Default"] = 0] = "Default";
145 BodyOutputType[BodyOutputType["TrustedHtml"] = 1] = "TrustedHtml";
146 BodyOutputType[BodyOutputType["Component"] = 2] = "Component";
147})(BodyOutputType || (BodyOutputType = {}));
148
149const DefaultTypeClasses = {
150 error: 'toast-error',
151 info: 'toast-info',
152 wait: 'toast-wait',
153 success: 'toast-success',
154 warning: 'toast-warning'
155};
156const DefaultIconClasses = {
157 error: 'icon-error',
158 info: 'icon-info',
159 wait: 'icon-wait',
160 success: 'icon-success',
161 warning: 'icon-warning'
162};
163class ToasterConfig {
164 constructor(configOverrides) {
165 configOverrides = configOverrides || {};
166 this.limit = configOverrides.limit || null;
167 this.tapToDismiss = configOverrides.tapToDismiss != null ? configOverrides.tapToDismiss : true;
168 this.showCloseButton = configOverrides.showCloseButton != null ? configOverrides.showCloseButton : false;
169 this.closeHtml = configOverrides.closeHtml || '<span>&times;</span>';
170 this.newestOnTop = configOverrides.newestOnTop != null ? configOverrides.newestOnTop : true;
171 this.timeout = configOverrides.timeout != null ? configOverrides.timeout : 5000;
172 this.typeClasses = configOverrides.typeClasses || DefaultTypeClasses;
173 this.iconClasses = configOverrides.iconClasses || DefaultIconClasses;
174 this.bodyOutputType = configOverrides.bodyOutputType || BodyOutputType.Default;
175 this.bodyTemplate = configOverrides.bodyTemplate || 'toasterBodyTmpl.html';
176 this.defaultToastType = configOverrides.defaultToastType || 'info';
177 this.positionClass = configOverrides.positionClass || 'toast-top-right';
178 this.titleClass = configOverrides.titleClass || 'toast-title';
179 this.messageClass = configOverrides.messageClass || 'toast-message';
180 this.animation = configOverrides.animation || '';
181 this.preventDuplicates = configOverrides.preventDuplicates != null ? configOverrides.preventDuplicates : false;
182 this.mouseoverTimerStop = configOverrides.mouseoverTimerStop != null ? configOverrides.mouseoverTimerStop : false;
183 this.toastContainerId = configOverrides.toastContainerId != null ? configOverrides.toastContainerId : null;
184 }
185}
186ToasterConfig.decorators = [
187 { type: Injectable }
188];
189ToasterConfig.ctorParameters = () => [
190 { type: undefined }
191];
192
193// http://stackoverflow.com/questions/26501688/a-typescript-guid-class
194class Guid {
195 static newGuid() {
196 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
197 const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
198 return v.toString(16);
199 });
200 }
201}
202class ToasterService {
203 /**
204 * Creates an instance of ToasterService.
205 */
206 constructor() {
207 this.addToast = new Observable((observer) => this._addToast = observer).pipe(share());
208 this.clearToasts = new Observable((observer) => this._clearToasts = observer).pipe(share());
209 this._removeToastSubject = new Subject();
210 this.removeToast = this._removeToastSubject.pipe(share());
211 }
212 /**
213 * Synchronously create and show a new toast instance.
214 *
215 * @param {(string | Toast)} type The type of the toast, or a Toast object.
216 * @param {string=} title The toast title.
217 * @param {string=} body The toast body.
218 * @returns {Toast}
219 * The newly created Toast instance with a randomly generated GUID Id.
220 */
221 pop(type, title, body) {
222 const toast = typeof type === 'string' ? { type: type, title: title, body: body } : type;
223 if (!toast.toastId) {
224 toast.toastId = Guid.newGuid();
225 }
226 if (!this._addToast) {
227 throw new Error('No Toaster Containers have been initialized to receive toasts.');
228 }
229 this._addToast.next(toast);
230 return toast;
231 }
232 /**
233 * Asynchronously create and show a new toast instance.
234 *
235 * @param {(string | Toast)} type The type of the toast, or a Toast object.
236 * @param {string=} title The toast title.
237 * @param {string=} body The toast body.
238 * @returns {Observable<Toast>}
239 * A hot Observable that can be subscribed to in order to receive the Toast instance
240 * with a randomly generated GUID Id.
241 */
242 popAsync(type, title, body) {
243 setTimeout(() => {
244 this.pop(type, title, body);
245 }, 0);
246 return this.addToast;
247 }
248 /**
249 * Clears a toast by toastId and/or toastContainerId.
250 *
251 * @param {string} toastId The toastId to clear.
252 * @param {number=} toastContainerId
253 * The toastContainerId of the container to remove toasts from.
254 */
255 clear(toastId, toastContainerId) {
256 const clearWrapper = {
257 toastId: toastId, toastContainerId: toastContainerId
258 };
259 this._clearToasts.next(clearWrapper);
260 }
261}
262ToasterService.ɵprov = ɵɵdefineInjectable({ factory: function ToasterService_Factory() { return new ToasterService(); }, token: ToasterService, providedIn: "root" });
263ToasterService.decorators = [
264 { type: Injectable, args: [{ providedIn: 'root' },] }
265];
266ToasterService.ctorParameters = () => [];
267
268class ToasterContainerComponent {
269 constructor(toasterService) {
270 this.toasts = [];
271 this.toasterService = toasterService;
272 }
273 ngOnInit() {
274 this.registerSubscribers();
275 if (this.isNullOrUndefined(this.toasterconfig)) {
276 this.toasterconfig = new ToasterConfig();
277 }
278 }
279 // event handlers
280 click(toast, isCloseButton) {
281 if (toast.onClickCallback) {
282 toast.onClickCallback(toast);
283 }
284 const tapToDismiss = !this.isNullOrUndefined(toast.tapToDismiss)
285 ? toast.tapToDismiss
286 : this.toasterconfig.tapToDismiss;
287 if (tapToDismiss || (toast.showCloseButton && isCloseButton)) {
288 this.removeToast(toast);
289 }
290 }
291 childClick($event) {
292 this.click($event.value.toast, $event.value.isCloseButton);
293 }
294 removeToast(toast) {
295 const index = this.toasts.indexOf(toast);
296 if (index < 0) {
297 return;
298 }
299 ;
300 const toastId = this.toastIdOrDefault(toast);
301 this.toasts.splice(index, 1);
302 if (toast.onHideCallback) {
303 toast.onHideCallback(toast);
304 }
305 this.toasterService._removeToastSubject.next({ toastId: toastId, toastContainerId: toast.toastContainerId });
306 }
307 // private functions
308 registerSubscribers() {
309 this.addToastSubscriber = this.toasterService.addToast.subscribe((toast) => {
310 this.addToast(toast);
311 });
312 this.clearToastsSubscriber = this.toasterService.clearToasts.subscribe((clearWrapper) => {
313 this.clearToasts(clearWrapper);
314 });
315 }
316 addToast(toast) {
317 if (toast.toastContainerId && this.toasterconfig.toastContainerId
318 && toast.toastContainerId !== this.toasterconfig.toastContainerId) {
319 return;
320 }
321 ;
322 if (!toast.type
323 || !this.toasterconfig.typeClasses[toast.type]
324 || !this.toasterconfig.iconClasses[toast.type]) {
325 toast.type = this.toasterconfig.defaultToastType;
326 }
327 if (this.toasterconfig.preventDuplicates && this.toasts.length > 0) {
328 if (toast.toastId && this.toasts.some(t => t.toastId === toast.toastId)) {
329 return;
330 }
331 else if (this.toasts.some(t => t.body === toast.body)) {
332 return;
333 }
334 }
335 if (this.isNullOrUndefined(toast.showCloseButton)) {
336 if (typeof this.toasterconfig.showCloseButton === 'object') {
337 toast.showCloseButton = this.toasterconfig.showCloseButton[toast.type];
338 }
339 else if (typeof this.toasterconfig.showCloseButton === 'boolean') {
340 toast.showCloseButton = this.toasterconfig.showCloseButton;
341 }
342 }
343 if (toast.showCloseButton) {
344 toast.closeHtml = toast.closeHtml || this.toasterconfig.closeHtml;
345 }
346 toast.bodyOutputType = toast.bodyOutputType || this.toasterconfig.bodyOutputType;
347 if (this.toasterconfig.newestOnTop) {
348 this.toasts.unshift(toast);
349 if (this.isLimitExceeded()) {
350 this.toasts.pop();
351 }
352 }
353 else {
354 this.toasts.push(toast);
355 if (this.isLimitExceeded()) {
356 this.toasts.shift();
357 }
358 }
359 if (toast.onShowCallback) {
360 toast.onShowCallback(toast);
361 }
362 }
363 isLimitExceeded() {
364 return this.toasterconfig.limit && this.toasts.length > this.toasterconfig.limit;
365 }
366 removeAllToasts() {
367 for (let i = this.toasts.length - 1; i >= 0; i--) {
368 this.removeToast(this.toasts[i]);
369 }
370 }
371 clearToasts(clearWrapper) {
372 const toastId = clearWrapper.toastId;
373 const toastContainerId = clearWrapper.toastContainerId;
374 if (this.isNullOrUndefined(toastContainerId) || (toastContainerId === this.toasterconfig.toastContainerId)) {
375 this.clearToastsAction(toastId);
376 }
377 }
378 clearToastsAction(toastId) {
379 if (toastId) {
380 this.removeToast(this.toasts.filter(t => t.toastId === toastId)[0]);
381 }
382 else {
383 this.removeAllToasts();
384 }
385 }
386 toastIdOrDefault(toast) {
387 return toast.toastId || '';
388 }
389 isNullOrUndefined(value) {
390 return value === null || typeof value === 'undefined';
391 }
392 ngOnDestroy() {
393 if (this.addToastSubscriber) {
394 this.addToastSubscriber.unsubscribe();
395 }
396 if (this.clearToastsSubscriber) {
397 this.clearToastsSubscriber.unsubscribe();
398 }
399 }
400}
401ToasterContainerComponent.decorators = [
402 { type: Component, args: [{
403 selector: 'toaster-container',
404 template: `
405 <div class="toast-container" [ngClass]="[toasterconfig.positionClass]">
406 <div toastComp *ngFor="let toast of toasts" class="toast" [toast]="toast"
407 [toasterconfig]="toasterconfig"
408 [@toastState]="toasterconfig.animation"
409 [titleClass]="toasterconfig.titleClass"
410 [messageClass]="toasterconfig.messageClass"
411 [ngClass]="[
412 toasterconfig.iconClasses[toast.type],
413 toasterconfig.typeClasses[toast.type]
414 ]"
415 (click)="click(toast)" (clickEvent)="childClick($event)"
416 (removeToastEvent)="removeToast($event)"
417 >
418 </div>
419 </div>
420 `,
421 animations: Transitions
422 },] }
423];
424ToasterContainerComponent.ctorParameters = () => [
425 { type: ToasterService }
426];
427ToasterContainerComponent.propDecorators = {
428 toasterconfig: [{ type: Input }]
429};
430
431class ToastComponent {
432 constructor(componentFactoryResolver, changeDetectorRef, ngZone, element, renderer2) {
433 this.componentFactoryResolver = componentFactoryResolver;
434 this.changeDetectorRef = changeDetectorRef;
435 this.ngZone = ngZone;
436 this.element = element;
437 this.renderer2 = renderer2;
438 this.progressBarWidth = -1;
439 this.bodyOutputType = BodyOutputType;
440 this.clickEvent = new EventEmitter();
441 this.removeToastEvent = new EventEmitter();
442 this.timeoutId = null;
443 this.timeout = 0;
444 this.progressBarIntervalId = null;
445 }
446 ngOnInit() {
447 if (this.toast.progressBar) {
448 this.toast.progressBarDirection = this.toast.progressBarDirection || 'decreasing';
449 }
450 let timeout = (typeof this.toast.timeout === 'number')
451 ? this.toast.timeout : this.toasterconfig.timeout;
452 if (typeof timeout === 'object') {
453 timeout = timeout[this.toast.type];
454 }
455 ;
456 this.timeout = timeout;
457 }
458 ngAfterViewInit() {
459 if (this.toast.bodyOutputType === this.bodyOutputType.Component) {
460 const component = this.componentFactoryResolver.resolveComponentFactory(this.toast.body);
461 const componentInstance = this.componentBody.createComponent(component, undefined, this.componentBody.injector);
462 componentInstance.instance.toast = this.toast;
463 this.changeDetectorRef.detectChanges();
464 }
465 if (this.toasterconfig.mouseoverTimerStop) {
466 // only apply a mouseenter event when necessary to avoid
467 // unnecessary event and change detection cycles.
468 this.removeMouseOverListener = this.renderer2.listen(this.element.nativeElement, 'mouseenter', () => this.stopTimer());
469 }
470 this.configureTimer();
471 }
472 click(event, toast) {
473 event.stopPropagation();
474 this.clickEvent.emit({ value: { toast: toast, isCloseButton: true } });
475 }
476 stopTimer() {
477 this.progressBarWidth = 0;
478 this.clearTimers();
479 }
480 restartTimer() {
481 if (this.toasterconfig.mouseoverTimerStop) {
482 if (!this.timeoutId) {
483 this.configureTimer();
484 }
485 }
486 else if (this.timeout && !this.timeoutId) {
487 this.removeToast();
488 }
489 }
490 ngOnDestroy() {
491 if (this.removeMouseOverListener) {
492 this.removeMouseOverListener();
493 }
494 this.clearTimers();
495 }
496 configureTimer() {
497 if (!this.timeout || this.timeout < 1) {
498 return;
499 }
500 if (this.toast.progressBar) {
501 this.removeToastTick = new Date().getTime() + this.timeout;
502 this.progressBarWidth = -1;
503 }
504 this.ngZone.runOutsideAngular(() => {
505 this.timeoutId = window.setTimeout(() => {
506 this.ngZone.run(() => {
507 this.changeDetectorRef.markForCheck();
508 this.removeToast();
509 });
510 }, this.timeout);
511 if (this.toast.progressBar) {
512 this.progressBarIntervalId = window.setInterval(() => {
513 this.ngZone.run(() => {
514 this.updateProgressBar();
515 });
516 }, 10);
517 }
518 });
519 }
520 updateProgressBar() {
521 if (this.progressBarWidth === 0 || this.progressBarWidth === 100) {
522 return;
523 }
524 this.progressBarWidth = ((this.removeToastTick - new Date().getTime()) / this.timeout) * 100;
525 if (this.toast.progressBarDirection === 'increasing') {
526 this.progressBarWidth = 100 - this.progressBarWidth;
527 }
528 if (this.progressBarWidth < 0) {
529 this.progressBarWidth = 0;
530 }
531 if (this.progressBarWidth > 100) {
532 this.progressBarWidth = 100;
533 }
534 }
535 clearTimers() {
536 if (this.timeoutId) {
537 window.clearTimeout(this.timeoutId);
538 }
539 if (this.progressBarIntervalId) {
540 window.clearInterval(this.progressBarIntervalId);
541 }
542 this.timeoutId = null;
543 this.progressBarIntervalId = null;
544 }
545 removeToast() {
546 this.removeToastEvent.emit(this.toast);
547 }
548}
549ToastComponent.decorators = [
550 { type: Component, args: [{
551 selector: '[toastComp]',
552 template: `
553 <div class="toast-content">
554 <div [ngClass]="titleClass">{{toast.title}}</div>
555 <div [ngClass]="messageClass" [ngSwitch]="toast.bodyOutputType">
556 <div *ngSwitchCase="bodyOutputType.Component" #componentBody></div>
557 <div *ngSwitchCase="bodyOutputType.TrustedHtml" [innerHTML]="toast.body | trustHtml"></div>
558 <div *ngSwitchCase="bodyOutputType.Default">{{toast.body}}</div>
559 </div>
560 </div>
561 <button class="toast-close-button" *ngIf="toast.showCloseButton" (click)="click($event, toast)"
562 [innerHTML]="toast.closeHtml | trustHtml">
563 </button>
564 <div *ngIf="toast.progressBar">
565 <div class="toast-progress-bar" [style.width]="progressBarWidth + '%'"></div>
566 </div>`
567 },] }
568];
569ToastComponent.ctorParameters = () => [
570 { type: ComponentFactoryResolver },
571 { type: ChangeDetectorRef },
572 { type: NgZone },
573 { type: ElementRef },
574 { type: Renderer2 }
575];
576ToastComponent.propDecorators = {
577 toasterconfig: [{ type: Input }],
578 toast: [{ type: Input }],
579 titleClass: [{ type: Input }],
580 messageClass: [{ type: Input }],
581 componentBody: [{ type: ViewChild, args: ['componentBody', { read: ViewContainerRef, static: false },] }],
582 clickEvent: [{ type: Output }],
583 removeToastEvent: [{ type: Output }],
584 restartTimer: [{ type: HostListener, args: ['mouseleave',] }]
585};
586
587class TrustHtmlPipe {
588 constructor(sanitizer) {
589 this.sanitizer = sanitizer;
590 }
591 transform(content) {
592 return this.sanitizer.bypassSecurityTrustHtml(content);
593 }
594}
595TrustHtmlPipe.decorators = [
596 { type: Pipe, args: [{
597 name: 'trustHtml',
598 pure: true
599 },] }
600];
601TrustHtmlPipe.ctorParameters = () => [
602 { type: DomSanitizer }
603];
604
605class ToasterModule {
606 static forRoot() {
607 return {
608 ngModule: ToasterModule,
609 providers: [ToasterService, ToasterContainerComponent]
610 };
611 }
612 static forChild() {
613 return {
614 ngModule: ToasterModule,
615 providers: [ToasterContainerComponent]
616 };
617 }
618}
619ToasterModule.decorators = [
620 { type: NgModule, args: [{
621 imports: [CommonModule],
622 declarations: [
623 ToastComponent,
624 ToasterContainerComponent,
625 TrustHtmlPipe
626 ],
627 exports: [
628 ToasterContainerComponent,
629 ToastComponent
630 ]
631 },] }
632];
633
634/*
635 * Public API Surface of angular2-toaster
636 */
637
638/**
639 * Generated bundle index. Do not edit.
640 */
641
642export { BodyOutputType, DefaultIconClasses, DefaultTypeClasses, ToasterConfig, ToasterContainerComponent, ToasterModule, ToasterService, Transitions as ɵa, ToastComponent as ɵb, TrustHtmlPipe as ɵc };
643//# sourceMappingURL=angular2-toaster.js.map