ngx-modialog
Version:
Modal / Dialog for Angular
299 lines (298 loc) • 10.3 kB
JavaScript
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
import { Component, ElementRef, ViewChild, ViewContainerRef, ViewEncapsulation, Renderer2, TemplateRef } from '@angular/core';
import { PromiseCompleter, supportsKey } from '../framework/utils';
import { DialogRef } from '../models/dialog-ref';
import { BaseDynamicComponent } from '../components/index';
// TODO: use DI factory for this.
// TODO: consolidate dup code
const /** @type {?} */ isDoc = !(typeof document === 'undefined' || !document);
/**
* @record
*/
export function EmbedComponentConfig() { }
function EmbedComponentConfig_tsickle_Closure_declarations() {
/** @type {?} */
EmbedComponentConfig.prototype.component;
/** @type {?|undefined} */
EmbedComponentConfig.prototype.projectableNodes;
}
/**
* Represents the modal overlay.
*/
export class ModalOverlay extends BaseDynamicComponent {
/**
* @param {?} dialogRef
* @param {?} vcr
* @param {?} el
* @param {?} renderer
*/
constructor(dialogRef, vcr, el, renderer) {
super(el, renderer);
this.dialogRef = dialogRef;
this.vcr = vcr;
this.activateAnimationListener();
}
/**
* \@internal
* @template T
* @param {?} content
* @return {?}
*/
getProjectables(content) {
let /** @type {?} */ nodes;
if (typeof content === 'string') {
nodes = [[this.renderer.createText(`${content}`)]];
}
else if (content instanceof TemplateRef) {
nodes = [this.vcr.createEmbeddedView(content, { $implicit: this.dialogRef.context, dialogRef: this.dialogRef }).rootNodes];
}
else {
nodes = [this.embedComponent({ component: content }).rootNodes];
}
return nodes;
}
/**
* @param {?} config
* @return {?}
*/
embedComponent(config) {
const /** @type {?} */ ctx = /** @type {?} */ (config);
return this.vcr.createEmbeddedView(this.template, /** @type {?} */ ({
$implicit: ctx
}));
}
/**
* @template T
* @param {?} type
* @param {?=} projectableNodes
* @return {?}
*/
addComponent(type, projectableNodes = []) {
return super._addComponent({
component: type,
vcRef: this.innerVcr,
projectableNodes
});
}
/**
* @return {?}
*/
fullscreen() {
const /** @type {?} */ style = {
position: 'fixed',
top: 0,
left: 0,
bottom: 0,
right: 0,
'z-index': 1500
};
Object.keys(style).forEach(k => this.setStyle(k, style[k]));
}
/**
* @return {?}
*/
insideElement() {
const /** @type {?} */ style = {
position: 'absolute',
overflow: 'hidden',
width: '100%',
height: '100%',
top: 0,
left: 0,
bottom: 0,
right: 0
};
Object.keys(style).forEach(k => this.setStyle(k, style[k]));
}
/**
* Set a specific inline style for the container of the whole dialog component
* The dialog component root element is the host of this component, it contains only 1 direct
* child which is the container.
*
* Structure:
*
* ```html
* <modal-overlay>
* <div>
* <!-- BACKDROP ELEMENT -->
* <!-- DIALOG CONTAINER ELEMENT -->
* </div>
* </modal-overlay>
* ```
*
* @param {?} prop The style key
* @param {?} value The value, undefined to remove
* @return {?}
*/
setContainerStyle(prop, value) {
this.renderer.setStyle(this.container.nativeElement, prop, value);
return this;
}
/**
* Define an element that click inside it will not trigger modal close.
* Since events bubble, clicking on a dialog will bubble up to the overlay, a plugin
* must define an element that represent the dialog, the overlay will make sure no to close when
* it was clicked.
* @param {?} element
* @return {?}
*/
setClickBoundary(element) {
let /** @type {?} */ target;
const /** @type {?} */ elListener = event => target = /** @type {?} */ (event.target);
const /** @type {?} */ docListener = event => {
if (this.dialogRef.context.isBlocking || !this.dialogRef.overlay.isTopMost(this.dialogRef)) {
return;
}
let /** @type {?} */ current = event.target;
// on click, this will hit.
if (current === target)
return;
// on mouse down -> drag -> release the current might not be 'target', it might be
// a sibling or a child (i.e: not part of the tree-up direction). It might also be a release
// outside the dialog... so we compare to the boundary element
do {
if (current === element) {
return;
}
} while (current.parentNode && (current = current.parentNode));
this.dialogRef.dismiss();
};
if (isDoc) {
this.dialogRef.onDestroy.subscribe(() => {
element.removeEventListener('click', elListener, false);
element.removeEventListener('touchstart', elListener, false);
document.removeEventListener('click', docListener, false);
document.removeEventListener('touchend', docListener, false);
});
setTimeout(() => {
element.addEventListener('mousedown', elListener, false);
element.addEventListener('touchstart', docListener, false);
document.addEventListener('click', docListener, false);
document.addEventListener('touchend', docListener, false);
});
}
}
/**
* Temp workaround for animation where destruction of the top level component does not
* trigger child animations. Solution should be found either in animation module or in design
* of the modal component tree.
* @return {?}
*/
canDestroy() {
const /** @type {?} */ completer = new PromiseCompleter();
if (!Array.isArray(this.beforeDestroyHandlers)) {
completer.resolve();
}
else {
// run destroy notification but protect against halt.
let /** @type {?} */ id = setTimeout(() => {
id = null;
completer.reject();
}, 1000);
const /** @type {?} */ resolve = () => {
if (id === null)
return;
clearTimeout(id);
completer.resolve();
};
Promise.all(this.beforeDestroyHandlers.map(fn => fn()))
.then(resolve)
.catch(resolve);
}
return completer.promise;
}
/**
* A handler running before destruction of the overlay
* use to delay destruction due to animation.
* This is part of the workaround for animation, see canDestroy.
*
* NOTE: There is no guarantee that the listeners will fire, use dialog.onDestory for that.
* @param {?} fn
* @return {?}
*/
beforeDestroy(fn) {
if (!this.beforeDestroyHandlers) {
this.beforeDestroyHandlers = [];
}
this.beforeDestroyHandlers.push(fn);
}
/**
* @param {?} event
* @return {?}
*/
documentKeypress(event) {
// check that this modal is the last in the stack.
if (!this.dialogRef.overlay.isTopMost(this.dialogRef))
return;
if (supportsKey(event.keyCode, /** @type {?} */ (this.dialogRef.context.keyboard))) {
this.dialogRef.dismiss();
}
}
/**
* @return {?}
*/
ngOnDestroy() {
super.ngOnDestroy();
if (this.dialogRef.destroyed !== true) {
// if we're here the overlay is destroyed by an external event that is not user invoked.
// i.e: The user did no call dismiss or close and dialogRef.destroy() did not invoke.
// this will happen when routing or killing an element containing a blocked overlay (ngIf)
// we bail out, i.e gracefully shutting down.
this.dialogRef.bailOut();
}
}
}
ModalOverlay.decorators = [
{ type: Component, args: [{
selector: 'modal-overlay',
host: {
'(body:keydown)': 'documentKeypress($event)'
},
encapsulation: ViewEncapsulation.None,
template: `<div #container>
<ng-template #innerView></ng-template>
</div>
<ng-template #template let-ctx>
<ng-container *ngComponentOutlet="ctx.component; injector: ctx.injector; content: ctx.projectableNodes"></ng-container>
</ng-template>`
},] },
];
/** @nocollapse */
ModalOverlay.ctorParameters = () => [
{ type: DialogRef, },
{ type: ViewContainerRef, },
{ type: ElementRef, },
{ type: Renderer2, },
];
ModalOverlay.propDecorators = {
"container": [{ type: ViewChild, args: ['container', { read: ElementRef },] },],
"innerVcr": [{ type: ViewChild, args: ['innerView', { read: ViewContainerRef },] },],
"template": [{ type: ViewChild, args: ['template',] },],
};
function ModalOverlay_tsickle_Closure_declarations() {
/** @type {!Array<{type: !Function, args: (undefined|!Array<?>)}>} */
ModalOverlay.decorators;
/**
* @nocollapse
* @type {function(): !Array<(null|{type: ?, decorators: (undefined|!Array<{type: !Function, args: (undefined|!Array<?>)}>)})>}
*/
ModalOverlay.ctorParameters;
/** @type {!Object<string,!Array<{type: !Function, args: (undefined|!Array<?>)}>>} */
ModalOverlay.propDecorators;
/** @type {?} */
ModalOverlay.prototype.beforeDestroyHandlers;
/** @type {?} */
ModalOverlay.prototype.container;
/** @type {?} */
ModalOverlay.prototype.innerVcr;
/** @type {?} */
ModalOverlay.prototype.template;
/** @type {?} */
ModalOverlay.prototype.dialogRef;
/** @type {?} */
ModalOverlay.prototype.vcr;
}
//# sourceMappingURL=overlay.component.js.map