@euro-ombudsman/ngx-modal-dialog
Version:
Dynamic modal dialog for Angular 20
413 lines (404 loc) • 19.7 kB
JavaScript
import * as i0 from '@angular/core';
import { ViewContainerRef, Component, ViewChild, HostListener, Injectable, ComponentFactoryResolver, Inject, InjectionToken, Optional, SkipSelf, NgModule } from '@angular/core';
import { Subject, from } from 'rxjs';
import * as i1 from '@angular/common';
import { CommonModule } from '@angular/common';
/**
* Modal dialog component
*/
class ModalDialogComponent {
/**
* CTOR
* @param _element
* @param componentFactoryResolver
*/
constructor(_element, componentFactoryResolver) {
this._element = _element;
this.componentFactoryResolver = componentFactoryResolver;
/** Modal dialog style settings */
this.settings = {
overlayClass: 'modal-backdrop fade',
overlayAnimationTriggerClass: 'show',
modalClass: 'modal ngx-modal fade',
modalAnimationTriggerClass: 'show',
modalDialogClass: 'modal-dialog modal-dialog-centered',
contentClass: 'modal-content',
headerClass: 'modal-header',
headerTitleClass: 'modal-title',
closeButtonClass: 'close glyphicon glyphicon-remove',
closeButtonTitle: 'CLOSE',
bodyClass: 'modal-body',
footerClass: 'modal-footer',
alertClass: 'ngx-modal-shake',
alertDuration: 250,
notifyWithAlert: true,
buttonClass: 'btn btn-primary'
};
this.showAlert = false;
this.animateOverlayClass = '';
this.animateModalClass = '';
this.showOverlay = false;
this._inProgress = false;
}
onClick(event) {
if (event.target !== this.dialogElement.nativeElement) {
return;
}
this.close();
}
/**
* Initialize dialog with reference to instance and options
* @param reference
* @param options
*/
dialogInit(reference, options = {}) {
this.reference = reference;
// inject component
if (options.childComponent) {
let factory = this.componentFactoryResolver.resolveComponentFactory(options.childComponent);
let componentRef = this.dynamicComponentTarget.createComponent(factory);
this._childInstance = componentRef.instance;
this._closeDialog$ = new Subject();
this._closeDialog$.subscribe(() => {
this._finalizeAndDestroy();
});
options.closeDialogSubject = this._closeDialog$;
this._childInstance['dialogInit'](componentRef, options);
document.activeElement != null ?
document.activeElement.blur() :
document.body.blur();
}
// set options
this._setOptions(options);
}
ngOnInit() {
// a trick to defer css animations
setTimeout(() => {
this.animateOverlayClass = this.settings.overlayAnimationTriggerClass;
this.animateModalClass = this.settings.modalAnimationTriggerClass;
}, 0);
}
/**
* Cleanup on destroy
*/
ngOnDestroy() {
// run animations
this.animateOverlayClass = '';
this.animateModalClass = '';
// cleanup listeners
if (this._alertTimeout) {
clearTimeout(this._alertTimeout);
this._alertTimeout = null;
}
if (this._closeDialog$) {
this._closeDialog$.unsubscribe();
}
}
/**
* Run action defined on action button
* @param action
*/
doAction(action) {
// disable multi clicks
if (this._inProgress) {
return;
}
this._inProgress = true;
this._closeIfSuccessful(action);
}
/**
* Method to run on close
* if action buttons are defined, it will not close
*/
close() {
if (this._inProgress) {
return;
}
if (this.actionButtons && this.actionButtons.length) {
return;
}
this._inProgress = true;
if (this.onClose) {
this._closeIfSuccessful(this.onClose);
return;
}
this._finalizeAndDestroy();
}
/**
* Pass options from dialog setup to component
* @param {IModalDialogOptions} options?
*/
_setOptions(options) {
if (options.onClose && options.actionButtons && options.actionButtons.length) {
throw new Error(`OnClose callback and ActionButtons are not allowed to be defined on the same dialog.`);
}
// set references
this.title = (options && options.title) || '';
this.onClose = (options && options.onClose) || null;
this.actionButtons = (this._childInstance && this._childInstance['actionButtons']) ||
(options && options.actionButtons) || null;
if (options && options.settings) {
Object.assign(this.settings, options.settings);
}
}
/**
* Close if successful
* @param callback
*/
_closeIfSuccessful(callback) {
if (!callback) {
return this._finalizeAndDestroy();
}
let response = callback();
if (typeof response === 'boolean') {
if (response) {
return this._finalizeAndDestroy();
}
return this._triggerAlert();
}
if (this.isPromise(response)) {
response = from(response);
}
if (this.isObservable(response)) {
response.subscribe(() => {
this._finalizeAndDestroy();
}, () => {
this._triggerAlert();
});
}
else {
this._inProgress = false;
}
}
_finalizeAndDestroy() {
this._inProgress = false;
this.reference.destroy();
}
_triggerAlert() {
if (this.settings.notifyWithAlert) {
this.showAlert = true;
this._alertTimeout = window.setTimeout(() => {
this.showAlert = false;
this._inProgress = false;
clearTimeout(this._alertTimeout);
this._alertTimeout = null;
}, this.settings.alertDuration);
}
}
isPromise(value) {
return value && typeof value.subscribe !== 'function' && typeof value.then === 'function';
}
isObservable(value) {
return value && typeof value.subscribe === 'function';
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: ModalDialogComponent, deps: [{ token: i0.ElementRef }, { token: i0.ComponentFactoryResolver }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.4", type: ModalDialogComponent, isStandalone: false, selector: "modal-dialog", host: { listeners: { "click": "onClick($event)" } }, viewQueries: [{ propertyName: "dynamicComponentTarget", first: true, predicate: ["modalDialogBody"], descendants: true, read: ViewContainerRef, static: true }, { propertyName: "dialogElement", first: true, predicate: ["dialog"], descendants: true }], ngImport: i0, template: `
<div *ngIf="settings.overlayClass && showOverlay" [ngClass]="[settings.overlayClass, animateOverlayClass]"></div>
<div [ngClass]="[settings.modalClass, animateModalClass]" #dialog>
<div [ngClass]="settings.modalDialogClass">
<div [ngClass]="[ showAlert ? settings.alertClass : '', settings.contentClass]">
<div [ngClass]="settings.headerClass">
<h4 [ngClass]="settings.headerTitleClass">{{title}}</h4>
<button (click)="close()" *ngIf="!actionButtons || !actionButtons.length" type="button"
[title]="settings.closeButtonTitle"
[ngClass]="settings.closeButtonClass">
</button>
</div>
<div [ngClass]="settings.bodyClass">
<i #modalDialogBody></i>
</div>
<div [ngClass]="settings.footerClass" *ngIf="actionButtons && actionButtons.length">
<button *ngFor="let button of actionButtons" (click)="doAction(button.onAction)"
[ngClass]="button.buttonClass || settings.buttonClass">{{button.text}}
</button>
</div>
</div>
</div>
</div>
`, isInline: true, styles: ["@-moz-keyframes shake{0%,to{transform:translateZ(0)}10%,30%,50%,70%,90%{transform:translate3d(-2rem,0,0)}20%,40%,60%,80%{transform:translate3d(2rem,0,0)}}@-webkit-keyframes shake{0%,to{transform:translateZ(0)}10%,30%,50%,70%,90%{transform:translate3d(-2rem,0,0)}20%,40%,60%,80%{transform:translate3d(2rem,0,0)}}@keyframes shake{0%,to{transform:translateZ(0)}10%,30%,50%,70%,90%{transform:translate3d(-2rem,0,0)}20%,40%,60%,80%{transform:translate3d(2rem,0,0)}}.ngx-modal{display:flex}.ngx-modal-shake{backface-visibility:hidden;-webkit-animation-duration:.5s;-moz-animation-duration:.5s;animation-duration:.5s;-webkit-animation-fill-mode:both;-moz-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-iteration-count:infinite;-moz-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-name:shake;-moz-animation-name:shake;animation-name:shake}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: ModalDialogComponent, decorators: [{
type: Component,
args: [{ selector: 'modal-dialog', template: `
<div *ngIf="settings.overlayClass && showOverlay" [ngClass]="[settings.overlayClass, animateOverlayClass]"></div>
<div [ngClass]="[settings.modalClass, animateModalClass]" #dialog>
<div [ngClass]="settings.modalDialogClass">
<div [ngClass]="[ showAlert ? settings.alertClass : '', settings.contentClass]">
<div [ngClass]="settings.headerClass">
<h4 [ngClass]="settings.headerTitleClass">{{title}}</h4>
<button (click)="close()" *ngIf="!actionButtons || !actionButtons.length" type="button"
[title]="settings.closeButtonTitle"
[ngClass]="settings.closeButtonClass">
</button>
</div>
<div [ngClass]="settings.bodyClass">
<i #modalDialogBody></i>
</div>
<div [ngClass]="settings.footerClass" *ngIf="actionButtons && actionButtons.length">
<button *ngFor="let button of actionButtons" (click)="doAction(button.onAction)"
[ngClass]="button.buttonClass || settings.buttonClass">{{button.text}}
</button>
</div>
</div>
</div>
</div>
`, standalone: false, styles: ["@-moz-keyframes shake{0%,to{transform:translateZ(0)}10%,30%,50%,70%,90%{transform:translate3d(-2rem,0,0)}20%,40%,60%,80%{transform:translate3d(2rem,0,0)}}@-webkit-keyframes shake{0%,to{transform:translateZ(0)}10%,30%,50%,70%,90%{transform:translate3d(-2rem,0,0)}20%,40%,60%,80%{transform:translate3d(2rem,0,0)}}@keyframes shake{0%,to{transform:translateZ(0)}10%,30%,50%,70%,90%{transform:translate3d(-2rem,0,0)}20%,40%,60%,80%{transform:translate3d(2rem,0,0)}}.ngx-modal{display:flex}.ngx-modal-shake{backface-visibility:hidden;-webkit-animation-duration:.5s;-moz-animation-duration:.5s;animation-duration:.5s;-webkit-animation-fill-mode:both;-moz-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-iteration-count:infinite;-moz-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-name:shake;-moz-animation-name:shake;animation-name:shake}\n"] }]
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.ComponentFactoryResolver }], propDecorators: { dynamicComponentTarget: [{
type: ViewChild,
args: ['modalDialogBody', { read: ViewContainerRef, static: true }]
}], dialogElement: [{
type: ViewChild,
args: ['dialog']
}], onClick: [{
type: HostListener,
args: ['click', ['$event']]
}] } });
class ModalDialogInstanceService {
constructor() {
/**
* Used to make sure there is exactly one instance of Modal Dialog
*/
this.componentRefs = [];
}
/**
* Closes existing modal dialog
*/
closeAnyExistingModalDialog() {
while (this.componentRefs.length) {
this.componentRefs[this.componentRefs.length - 1].destroy();
}
}
/**
* Save component ref for future comparison
* @param componentRef
*/
saveExistingModalDialog(componentRef) {
// remove overlay from top element
this.setOverlayForTopDialog(false);
// add new component
this.componentRefs = [...this.componentRefs, componentRef];
componentRef.instance.showOverlay = true;
componentRef.onDestroy(() => {
this.componentRefs.pop();
this.setOverlayForTopDialog(true);
});
}
setOverlayForTopDialog(value) {
if (this.componentRefs.length) {
this.componentRefs[this.componentRefs.length - 1].instance.showOverlay = value;
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: ModalDialogInstanceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: ModalDialogInstanceService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: ModalDialogInstanceService, decorators: [{
type: Injectable
}] });
class ModalDialogService {
/**
* CTOR
* @param componentFactoryResolver
* @param modalDialogInstanceService
*/
constructor(componentFactoryResolver, modalDialogInstanceService) {
this.componentFactoryResolver = componentFactoryResolver;
this.modalDialogInstanceService = modalDialogInstanceService;
}
/**
* Open dialog in given target element with given options
* @param {ViewContainerRef} target
* @param {IModalDialogOptions} options?
*/
openDialog(target, options = {}) {
if (!options.placeOnTop) {
this.modalDialogInstanceService.closeAnyExistingModalDialog();
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalDialogComponent);
const componentRef = target.createComponent(factory);
componentRef.instance.dialogInit(componentRef, options);
this.modalDialogInstanceService.saveExistingModalDialog(componentRef);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: ModalDialogService, deps: [{ token: ComponentFactoryResolver }, { token: ModalDialogInstanceService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: ModalDialogService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: ModalDialogService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i0.ComponentFactoryResolver, decorators: [{
type: Inject,
args: [ComponentFactoryResolver]
}] }, { type: ModalDialogInstanceService, decorators: [{
type: Inject,
args: [ModalDialogInstanceService]
}] }] });
class SimpleModalComponent {
/**
* Initialize dialog with simple HTML content
* @param {ComponentRef<IModalDialog>} reference
* @param {Partial<IModalDialogOptions>} options
*/
dialogInit(reference, options) {
if (!options.data) {
throw new Error(`Data information for simple modal dialog is missing`);
}
this.text = options.data.text;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: SimpleModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.4", type: SimpleModalComponent, isStandalone: false, selector: "simple-modal-dialog", host: { properties: { "innerHTML": "text" } }, ngImport: i0, template: ``, isInline: true, styles: [":host{display:block}\n"] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: SimpleModalComponent, decorators: [{
type: Component,
args: [{ selector: 'simple-modal-dialog', template: ``, host: {
'[innerHTML]': 'text'
}, standalone: false, styles: [":host{display:block}\n"] }]
}] });
// components and directives
/**
* Guard to make sure we have single initialization of forRoot
* @type {InjectionToken<ModalDialogModule>}
*/
const MODAL_DIALOG_FORROOT_GUARD = new InjectionToken('MODAL_DIALOG_FORROOT_GUARD');
class ModalDialogModule {
static forRoot() {
return {
ngModule: ModalDialogModule,
providers: [
{
provide: MODAL_DIALOG_FORROOT_GUARD,
useFactory: provideForRootGuard,
deps: [ModalDialogModule, new Optional(), new SkipSelf()]
},
ModalDialogInstanceService
]
};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: ModalDialogModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.1.4", ngImport: i0, type: ModalDialogModule, declarations: [ModalDialogComponent, SimpleModalComponent], imports: [CommonModule], exports: [ModalDialogComponent, SimpleModalComponent] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: ModalDialogModule, providers: [ModalDialogService, ModalDialogInstanceService], imports: [CommonModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: ModalDialogModule, decorators: [{
type: NgModule,
args: [{
imports: [CommonModule],
declarations: [ModalDialogComponent, SimpleModalComponent],
exports: [ModalDialogComponent, SimpleModalComponent],
providers: [ModalDialogService, ModalDialogInstanceService]
}]
}] });
/**
* @param dialogModule
* @returns {string}
*/
function provideForRootGuard(dialogModule) {
if (dialogModule) {
throw new Error(`ModalDialogModule.forRoot() called twice.`);
}
return 'guarded';
}
/*
* Public API Surface of ngx-modal-dialog
*/
/**
* Generated bundle index. Do not edit.
*/
export { MODAL_DIALOG_FORROOT_GUARD, ModalDialogComponent, ModalDialogInstanceService, ModalDialogModule, ModalDialogService, SimpleModalComponent, provideForRootGuard };
//# sourceMappingURL=euro-ombudsman-ngx-modal-dialog.mjs.map