UNPKG

21.5 kBJavaScriptView Raw
1/**
2 * @license ngx-smart-modal
3 * MIT license
4 */
5
6import { ChangeDetectorRef, Component, EventEmitter, HostListener, Injectable, Input, NgModule, Output, Renderer2, ViewChild } from '@angular/core';
7import { CommonModule } from '@angular/common';
8
9/**
10 * @fileoverview added by tsickle
11 * @suppress {checkTypes} checked by tsc
12 */
13class NgxSmartModalService {
14 constructor() {
15 this.modalStack = [];
16 }
17 /**
18 * Add a new modal instance. This step is essential and allows to retrieve any modal at any time.
19 * It stores an object that contains the given modal identifier and the modal itself directly in the `modalStack`.
20 *
21 * @param {?} modalInstance The object that contains the given modal identifier and the modal itself.
22 * @param {?=} force Optional parameter that forces the overriding of modal instance if it already exists.
23 * @return {?} nothing special.
24 */
25 addModal(modalInstance, force) {
26 if (force) {
27 const /** @type {?} */ i = this.modalStack.findIndex((o) => {
28 return o.id === modalInstance.id;
29 });
30 if (i > -1) {
31 this.modalStack[i].modal = modalInstance.modal;
32 }
33 else {
34 this.modalStack.push(modalInstance);
35 }
36 return;
37 }
38 this.modalStack.push(modalInstance);
39 }
40 /**
41 * Retrieve a modal instance by its identifier.
42 *
43 * @param {?} id The modal identifier used at creation time.
44 * @return {?}
45 */
46 getModal(id) {
47 return this.modalStack.filter((o) => {
48 return o.id === id;
49 })[0].modal;
50 }
51 /**
52 * Alias of `getModal` to retrieve a modal instance by its identifier.
53 *
54 * @param {?} id The modal identifier used at creation time.
55 * @return {?}
56 */
57 get(id) {
58 return this.getModal(id);
59 }
60 /**
61 * Open a given modal
62 *
63 * @param {?} id The modal identifier used at creation time.
64 * @param {?=} force Tell the modal to open top of all other opened modals
65 * @return {?}
66 */
67 open(id, force = false) {
68 const /** @type {?} */ instance = this.modalStack.find((o) => {
69 return o.id === id;
70 });
71 if (!!instance) {
72 instance.modal.open(force);
73 }
74 else {
75 throw new Error('Modal not found');
76 }
77 }
78 /**
79 * Close a given modal
80 *
81 * @param {?} id The modal identifier used at creation time.
82 * @return {?}
83 */
84 close(id) {
85 const /** @type {?} */ instance = this.modalStack.find((o) => {
86 return o.id === id;
87 });
88 if (!!instance) {
89 instance.modal.close();
90 }
91 else {
92 throw new Error('Modal not found');
93 }
94 }
95 /**
96 * Toggles a given modal
97 * If the retrieved modal is opened it closes it, else it opens it.
98 *
99 * @param {?} id The modal identifier used at creation time.
100 * @param {?=} force Tell the modal to open top of all other opened modals
101 * @return {?}
102 */
103 toggle(id, force = false) {
104 const /** @type {?} */ instance = this.modalStack.find((o) => {
105 return o.id === id;
106 });
107 if (!!instance) {
108 instance.modal.toggle(force);
109 }
110 else {
111 throw new Error('Modal not found');
112 }
113 }
114 /**
115 * Retrieve all the created modals.
116 *
117 * @return {?} an array that contains all modal instances.
118 */
119 getModalStack() {
120 return this.modalStack;
121 }
122 /**
123 * Retrieve all the opened modals. It looks for all modal instances with their `visible` property set to `true`.
124 *
125 * @return {?} an array that contains all the opened modals.
126 */
127 getOpenedModals() {
128 const /** @type {?} */ modals = [];
129 this.modalStack.forEach((o) => {
130 if (o.modal.visible) {
131 modals.push(o);
132 }
133 });
134 return modals;
135 }
136 /**
137 * Get the higher `z-index` value between all the modal instances. It iterates over the `ModalStack` array and
138 * calculates a higher value (it takes the highest index value between all the modal instances and adds 1).
139 * Use it to make a modal appear foreground.
140 *
141 * @return {?} a higher index from all the existing modal instances.
142 */
143 getHigherIndex() {
144 const /** @type {?} */ index = [1041];
145 const /** @type {?} */ modals = this.getModalStack();
146 modals.forEach((o) => {
147 index.push(o.modal.layerPosition);
148 });
149 return Math.max(...index) + 1;
150 }
151 /**
152 * It gives the number of modal instances. It's helpful to know if the modal stack is empty or not.
153 *
154 * @return {?} the number of modal instances.
155 */
156 getModalStackCount() {
157 return this.modalStack.length;
158 }
159 /**
160 * Remove a modal instance from the modal stack.
161 *
162 * @param {?} id The modal identifier.
163 * @return {?} the removed modal instance.
164 */
165 removeModal(id) {
166 const /** @type {?} */ i = this.modalStack.findIndex((o) => {
167 return o.id === id;
168 });
169 if (i > -1) {
170 this.modalStack.splice(i, 1);
171 }
172 }
173 /**
174 * Associate data to an identified modal. If the modal isn't already associated to some data, it creates a new
175 * entry in the `modalData` array with its `id` and the given `data`. If the modal already has data, it rewrites
176 * them with the new ones. Finally if no modal found it returns an error message in the console and false value
177 * as method output.
178 *
179 * @param {?} data The data you want to associate to the modal.
180 * @param {?} id The modal identifier.
181 * @param {?=} force If true, overrides the previous stored data if there was.
182 * @return {?} true if the given modal exists and the process has been tried, either false.
183 */
184 setModalData(data, id, force) {
185 if (!!this.modalStack.find((o) => {
186 return o.id === id;
187 })) {
188 this.getModal(id).setData(data, force);
189 return true;
190 }
191 else {
192 return false;
193 }
194 }
195 /**
196 * Retrieve modal data by its identifier.
197 *
198 * @param {?} id The modal identifier used at creation time.
199 * @return {?} the associated modal data.
200 */
201 getModalData(id) {
202 return this.getModal(id).getData();
203 }
204 /**
205 * Reset the data attached to a given modal.
206 *
207 * @param {?} id The modal identifier used at creation time.
208 * @return {?} the removed data or false if modal doesn't exist.
209 */
210 resetModalData(id) {
211 if (!!this.modalStack.find((o) => {
212 return o.id === id;
213 })) {
214 const /** @type {?} */ removed = this.getModal(id).getData();
215 this.getModal(id).removeData();
216 return removed;
217 }
218 else {
219 return false;
220 }
221 }
222 /**
223 * Close the latest opened modal if it has been declared as escapable
224 * Using a debounce system because one or more modals could be listening
225 * escape key press event.
226 * @return {?}
227 */
228 closeLatestModal() {
229 const /** @type {?} */ me = this;
230 clearTimeout(this.debouncer);
231 this.debouncer = setTimeout(() => {
232 let /** @type {?} */ tmp;
233 me.getOpenedModals().forEach((m) => {
234 if (m.modal.layerPosition > (!!tmp ? tmp.modal.layerPosition : 0 && m.modal.escapable)) {
235 tmp = m;
236 }
237 });
238 return !!tmp ? tmp.modal.close() : false;
239 }, 100);
240 }
241}
242NgxSmartModalService.decorators = [
243 { type: Injectable },
244];
245/** @nocollapse */
246NgxSmartModalService.ctorParameters = () => [];
247
248/**
249 * @fileoverview added by tsickle
250 * @suppress {checkTypes} checked by tsc
251 */
252class NgxSmartModalComponent {
253 /**
254 * @param {?} _renderer
255 * @param {?} _changeDetectorRef
256 * @param {?} _ngxSmartModalService
257 */
258 constructor(_renderer, _changeDetectorRef, _ngxSmartModalService) {
259 this._renderer = _renderer;
260 this._changeDetectorRef = _changeDetectorRef;
261 this._ngxSmartModalService = _ngxSmartModalService;
262 this.closable = true;
263 this.escapable = true;
264 this.dismissable = true;
265 this.identifier = '';
266 this.customClass = 'nsm-dialog-animation-fade';
267 this.visible = false;
268 this.backdrop = true;
269 this.force = true;
270 this.hideDelay = 500;
271 this.autostart = false;
272 this.visibleChange = new EventEmitter();
273 this.onClose = new EventEmitter();
274 this.onCloseFinished = new EventEmitter();
275 this.onDismiss = new EventEmitter();
276 this.onDismissFinished = new EventEmitter();
277 this.onAnyCloseEvent = new EventEmitter();
278 this.onAnyCloseEventFinished = new EventEmitter();
279 this.onOpen = new EventEmitter();
280 this.onEscape = new EventEmitter();
281 this.onDataAdded = new EventEmitter();
282 this.onDataRemoved = new EventEmitter();
283 this.layerPosition = 1041;
284 this.overlayVisible = false;
285 this.openedClass = false;
286 this.escapeKeyboardEvent = (event) => {
287 if (event.keyCode === 27) {
288 this.onEscape.emit(this);
289 this._ngxSmartModalService.closeLatestModal();
290 }
291 };
292 }
293 /**
294 * @return {?}
295 */
296 ngOnInit() {
297 if (!!this.identifier && this.identifier.length) {
298 this.layerPosition += this._ngxSmartModalService.getModalStackCount();
299 this._ngxSmartModalService.addModal({ id: this.identifier, modal: this }, this.force);
300 if (this.autostart) {
301 this._ngxSmartModalService.open(this.identifier);
302 }
303 }
304 else {
305 throw new Error('identifier field isn’t set. Please set one before calling <ngx-smart-modal> in a template.');
306 }
307 }
308 /**
309 * @return {?}
310 */
311 ngOnDestroy() {
312 this._ngxSmartModalService.removeModal(this.identifier);
313 window.removeEventListener('keyup', this.escapeKeyboardEvent);
314 if (!this._ngxSmartModalService.getModalStack.length) {
315 this._renderer.removeClass(document.body, 'dialog-open');
316 }
317 }
318 /**
319 * @param {?=} top
320 * @return {?}
321 */
322 open(top) {
323 if (top) {
324 this.layerPosition = this._ngxSmartModalService.getHigherIndex();
325 }
326 this._renderer.addClass(document.body, 'dialog-open');
327 this.overlayVisible = true;
328 this.visible = true;
329 setTimeout(() => {
330 this.openedClass = true;
331 if (this.target) {
332 this.targetPlacement();
333 }
334 this._changeDetectorRef.markForCheck();
335 });
336 this.onOpen.emit(this);
337 if (this.escapable) {
338 window.addEventListener('keyup', this.escapeKeyboardEvent);
339 }
340 }
341 /**
342 * @return {?}
343 */
344 close() {
345 const /** @type {?} */ me = this;
346 this.openedClass = false;
347 this.onClose.emit(this);
348 this.onAnyCloseEvent.emit(this);
349 if (this._ngxSmartModalService.getOpenedModals().length < 2) {
350 this._renderer.removeClass(document.body, 'dialog-open');
351 }
352 setTimeout(() => {
353 me.visibleChange.emit(me.visible);
354 me.visible = false;
355 me.overlayVisible = false;
356 me._changeDetectorRef.markForCheck();
357 me.onCloseFinished.emit(me);
358 me.onAnyCloseEventFinished.emit(me);
359 }, this.hideDelay);
360 window.removeEventListener('keyup', this.escapeKeyboardEvent);
361 }
362 /**
363 * @param {?} e
364 * @return {?}
365 */
366 dismiss(e) {
367 const /** @type {?} */ me = this;
368 if (!this.dismissable) {
369 return;
370 }
371 if (e.target.classList.contains('overlay')) {
372 this.openedClass = false;
373 this.onDismiss.emit(this);
374 this.onAnyCloseEvent.emit(this);
375 if (this._ngxSmartModalService.getOpenedModals().length < 2) {
376 this._renderer.removeClass(document.body, 'dialog-open');
377 }
378 setTimeout(() => {
379 me.visible = false;
380 me.visibleChange.emit(me.visible);
381 me.overlayVisible = false;
382 me._changeDetectorRef.markForCheck();
383 me.onDismissFinished.emit(me);
384 me.onAnyCloseEventFinished.emit(me);
385 }, this.hideDelay);
386 window.removeEventListener('keyup', this.escapeKeyboardEvent);
387 }
388 }
389 /**
390 * @param {?=} top
391 * @return {?}
392 */
393 toggle(top) {
394 if (this.visible) {
395 this.close();
396 }
397 else {
398 this.open(top);
399 }
400 }
401 /**
402 * @param {?} className
403 * @return {?}
404 */
405 addCustomClass(className) {
406 if (!this.customClass.length) {
407 this.customClass = className;
408 }
409 else {
410 this.customClass += ' ' + className;
411 }
412 }
413 /**
414 * @param {?=} className
415 * @return {?}
416 */
417 removeCustomClass(className) {
418 if (className) {
419 this.customClass = this.customClass.replace(className, '').trim();
420 }
421 else {
422 this.customClass = '';
423 }
424 }
425 /**
426 * @return {?}
427 */
428 isVisible() {
429 return this.visible;
430 }
431 /**
432 * @return {?}
433 */
434 hasData() {
435 return this._data !== undefined;
436 }
437 /**
438 * @param {?} data
439 * @param {?=} force
440 * @return {?}
441 */
442 setData(data, force) {
443 if (!this.hasData() || (this.hasData() && force)) {
444 this._data = data;
445 this.onDataAdded.emit(this._data);
446 this._changeDetectorRef.markForCheck();
447 }
448 }
449 /**
450 * @return {?}
451 */
452 getData() {
453 return this._data;
454 }
455 /**
456 * @return {?}
457 */
458 removeData() {
459 this._data = undefined;
460 this.onDataRemoved.emit(true);
461 this._changeDetectorRef.markForCheck();
462 }
463 /**
464 * @return {?}
465 */
466 targetPlacement() {
467 if (!this.nsmDialog || !this.nsmContent || !this.nsmOverlay || !this.target) {
468 return;
469 }
470 const /** @type {?} */ targetElementRect = document.querySelector(this.target).getBoundingClientRect();
471 const /** @type {?} */ bodyRect = this.nsmOverlay.nativeElement.getBoundingClientRect();
472 const /** @type {?} */ nsmContentRect = this.nsmContent.nativeElement.getBoundingClientRect();
473 const /** @type {?} */ nsmDialogRect = this.nsmDialog.nativeElement.getBoundingClientRect();
474 const /** @type {?} */ marginLeft = parseInt(/** @type {?} */ (getComputedStyle(this.nsmContent.nativeElement).marginLeft), 10);
475 const /** @type {?} */ marginTop = parseInt(/** @type {?} */ (getComputedStyle(this.nsmContent.nativeElement).marginTop), 10);
476 let /** @type {?} */ offsetTop = targetElementRect.top - nsmDialogRect.top - ((nsmContentRect.height - targetElementRect.height) / 2);
477 let /** @type {?} */ offsetLeft = targetElementRect.left - nsmDialogRect.left - ((nsmContentRect.width - targetElementRect.width) / 2);
478 if (offsetLeft + nsmDialogRect.left + nsmContentRect.width + (marginLeft * 2) > bodyRect.width) {
479 offsetLeft = bodyRect.width - (nsmDialogRect.left + nsmContentRect.width) - (marginLeft * 2);
480 }
481 else if (offsetLeft + nsmDialogRect.left < 0) {
482 offsetLeft = -nsmDialogRect.left;
483 }
484 if (offsetTop + nsmDialogRect.top + nsmContentRect.height + marginTop > bodyRect.height) {
485 offsetTop = bodyRect.height - (nsmDialogRect.top + nsmContentRect.height) - marginTop;
486 }
487 if (offsetTop < 0) {
488 offsetTop = 0;
489 }
490 this._renderer.setStyle(this.nsmContent.nativeElement, 'top', offsetTop + 'px');
491 this._renderer.setStyle(this.nsmContent.nativeElement, 'left', offsetLeft + 'px');
492 }
493}
494NgxSmartModalComponent.decorators = [
495 { type: Component, args: [{
496 selector: 'ngx-smart-modal',
497 template: `
498 <div *ngIf="overlayVisible"
499 [style.z-index]="visible ? layerPosition-1 : -1"
500 [ngClass]="{'transparent':!backdrop, 'overlay':true, 'nsm-overlay-open':openedClass}"
501 (click)="dismiss($event)" #nsmOverlay>
502 <div [style.z-index]="visible ? layerPosition : -1"
503 [ngClass]="['nsm-dialog', customClass, openedClass ? 'nsm-dialog-open': 'nsm-dialog-close']" #nsmDialog>
504 <div class="nsm-content" #nsmContent>
505 <div class="nsm-body">
506 <ng-content></ng-content>
507 </div>
508 <button type="button" *ngIf="closable" (click)="close()" aria-label="Close" class="nsm-dialog-btn-close">
509 <img
510 src="data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTkuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeD0iMHB4IiB5PSIwcHgiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1MTIgNTEyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgd2lkdGg9IjE2cHgiIGhlaWdodD0iMTZweCI+CjxnPgoJPGc+CgkJPHBhdGggZD0iTTUwNS45NDMsNi4wNThjLTguMDc3LTguMDc3LTIxLjE3Mi04LjA3Ny0yOS4yNDksMEw2LjA1OCw0NzYuNjkzYy04LjA3Nyw4LjA3Ny04LjA3NywyMS4xNzIsMCwyOS4yNDkgICAgQzEwLjA5Niw1MDkuOTgyLDE1LjM5LDUxMiwyMC42ODMsNTEyYzUuMjkzLDAsMTAuNTg2LTIuMDE5LDE0LjYyNS02LjA1OUw1MDUuOTQzLDM1LjMwNiAgICBDNTE0LjAxOSwyNy4yMyw1MTQuMDE5LDE0LjEzNSw1MDUuOTQzLDYuMDU4eiIgZmlsbD0iIzAwMDAwMCIvPgoJPC9nPgo8L2c+CjxnPgoJPGc+CgkJPHBhdGggZD0iTTUwNS45NDIsNDc2LjY5NEwzNS4zMDYsNi4wNTljLTguMDc2LTguMDc3LTIxLjE3Mi04LjA3Ny0yOS4yNDgsMGMtOC4wNzcsOC4wNzYtOC4wNzcsMjEuMTcxLDAsMjkuMjQ4bDQ3MC42MzYsNDcwLjYzNiAgICBjNC4wMzgsNC4wMzksOS4zMzIsNi4wNTgsMTQuNjI1LDYuMDU4YzUuMjkzLDAsMTAuNTg3LTIuMDE5LDE0LjYyNC02LjA1N0M1MTQuMDE4LDQ5Ny44NjYsNTE0LjAxOCw0ODQuNzcxLDUwNS45NDIsNDc2LjY5NHoiIGZpbGw9IiMwMDAwMDAiLz4KCTwvZz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8L3N2Zz4K" />
511 </button>
512 </div>
513 </div>
514 </div>
515 `
516 },] },
517];
518/** @nocollapse */
519NgxSmartModalComponent.ctorParameters = () => [
520 { type: Renderer2, },
521 { type: ChangeDetectorRef, },
522 { type: NgxSmartModalService, },
523];
524NgxSmartModalComponent.propDecorators = {
525 "closable": [{ type: Input },],
526 "escapable": [{ type: Input },],
527 "dismissable": [{ type: Input },],
528 "identifier": [{ type: Input },],
529 "customClass": [{ type: Input },],
530 "visible": [{ type: Input },],
531 "backdrop": [{ type: Input },],
532 "force": [{ type: Input },],
533 "hideDelay": [{ type: Input },],
534 "autostart": [{ type: Input },],
535 "target": [{ type: Input },],
536 "visibleChange": [{ type: Output },],
537 "onClose": [{ type: Output },],
538 "onCloseFinished": [{ type: Output },],
539 "onDismiss": [{ type: Output },],
540 "onDismissFinished": [{ type: Output },],
541 "onAnyCloseEvent": [{ type: Output },],
542 "onAnyCloseEventFinished": [{ type: Output },],
543 "onOpen": [{ type: Output },],
544 "onEscape": [{ type: Output },],
545 "onDataAdded": [{ type: Output },],
546 "onDataRemoved": [{ type: Output },],
547 "nsmContent": [{ type: ViewChild, args: ['nsmContent',] },],
548 "nsmDialog": [{ type: ViewChild, args: ['nsmDialog',] },],
549 "nsmOverlay": [{ type: ViewChild, args: ['nsmOverlay',] },],
550 "targetPlacement": [{ type: HostListener, args: ['window:resize',] },],
551};
552
553/**
554 * @fileoverview added by tsickle
555 * @suppress {checkTypes} checked by tsc
556 */
557class NgxSmartModalModule {
558 /**
559 * Use in AppModule: new instance of NgxSmartModal.
560 * @return {?}
561 */
562 static forRoot() {
563 return {
564 ngModule: NgxSmartModalModule,
565 providers: [NgxSmartModalService]
566 };
567 }
568 /**
569 * Use in features modules with lazy loading: new instance of NgxSmartModal.
570 * @return {?}
571 */
572 static forChild() {
573 return {
574 ngModule: NgxSmartModalModule,
575 providers: [NgxSmartModalService]
576 };
577 }
578}
579NgxSmartModalModule.decorators = [
580 { type: NgModule, args: [{
581 declarations: [NgxSmartModalComponent],
582 exports: [NgxSmartModalComponent],
583 imports: [CommonModule]
584 },] },
585];
586/** @nocollapse */
587NgxSmartModalModule.ctorParameters = () => [];
588
589/**
590 * @fileoverview added by tsickle
591 * @suppress {checkTypes} checked by tsc
592 */
593// Public classes.
594
595/**
596 * @fileoverview added by tsickle
597 * @suppress {checkTypes} checked by tsc
598 */
599/**
600 * Entry point for all public APIs of the package.
601 */
602
603/**
604 * @fileoverview added by tsickle
605 * @suppress {checkTypes} checked by tsc
606 */
607/**
608 * Generated bundle index. Do not edit.
609 */
610
611export { NgxSmartModalService, NgxSmartModalComponent, NgxSmartModalModule };
612//# sourceMappingURL=ngx-smart-modal.js.map