import { h } from '@ledge/jsx';
import { NgController, makeInjectableCtrl } from './controller';
import { NgLogger } from './logger';
import { NgService } from './service';
import { NgHttp } from './http';
import { NgAppConfig } from './options';
const MODAL_SHOW_DELAY = 23;
const MODAL_HIDE_DELAY = 232;
export class NgModal extends NgService {
protected readonly $backdrop =
;
protected readonly $title = as HTMLHeadingElement;
protected readonly $header = ;
protected readonly $main = ;
protected readonly $cancelBtn =
as HTMLButtonElement;
protected readonly $submitBtn = as HTMLInputElement;
protected readonly $footer =
;
protected readonly $form =
as HTMLFormElement;
protected readonly $dialog = {this.$form}
;
protected readonly $container =
{this.$dialog}
;
constructor(
protected readonly $log: NgLogger,
protected readonly $http: NgHttp,
protected readonly $config: NgAppConfig,
protected readonly $injector: angular.auto.IInjectorService | (() => angular.auto.IInjectorService),
) {
super();
this.$backdrop.style.setProperty('opacity', '0');
this.$backdrop.style.setProperty('transition', `opacity ${MODAL_HIDE_DELAY}ms`);
this.$container.style.setProperty('display', 'unset');
this.$container.style.setProperty('transition', `opacity ${MODAL_HIDE_DELAY}ms`);
}
public open(options: NgModalOptions = {}) {
const defaultCancelBtnText = 'Cancel';
const defaultOkBtnText = 'Ok';
const {
item,
title = 'Set the title property to replace me :)',
template = 'Set the template property to replace me :)
',
cancelBtnText = defaultCancelBtnText,
okBtnText = defaultOkBtnText,
controller = NgController,
show = true,
onClose = () => {
return true;
},
} = options;
if (cancelBtnText === false) {
this.$cancelBtn.hidden = true;
} else {
this.$cancelBtn.innerText = cancelBtnText === true ? defaultCancelBtnText : cancelBtnText;
}
if (okBtnText === false) {
this.$submitBtn.hidden = true;
} else {
this.$submitBtn.value = okBtnText === true ? defaultOkBtnText : okBtnText;
}
this.$footer.hidden = this.$cancelBtn.hidden && this.$submitBtn.hidden;
this.$title.innerHTML = typeof title === 'function' ? title() : title;
this.$main.innerHTML =
typeof template === 'function' ? template() : template;
const $injector = typeof this.$injector === 'function' ? this.$injector() : this.$injector;
const $rootScope = $injector.get('$rootScope');
const $compile = $injector.get('$compile');
const $scope = $rootScope.$new(true) as Parameters[1];
const $element = $compile(this.$container)($scope);
const $ctrl = makeInjectableCtrl(controller, {
log: this.$log,
http: this.$http,
config: () => this.$config,
});
$scope.$ctrl = new $ctrl($element, $scope, $injector) as NgController;
Object.defineProperty($scope.$ctrl, 'item', { value: item });
$scope.$applyAsync();
const deferred = $injector.get('$q').defer();
const escapeKeyListener = (e: KeyboardEvent) => {
if (e.key === 'Escape' || e.key === 'Esc') {
close();
}
};
const dismiss = (shouldReject?: Event | boolean) => {
removeEventListeners();
this.hideModal(escapeKeyListener, $scope);
if (shouldReject !== false) {
deferred.reject(new Error('NgModal dismissed'));
}
$rootScope.$apply();
};
if (show) {
this.showModal(escapeKeyListener);
}
const removeEventListeners = () => {
this.$cancelBtn.removeEventListener('click', dismiss);
this.$submitBtn.removeEventListener('click', close);
this.$backdrop.removeEventListener('click', close);
};
const close = () => {
if (onClose()) {
deferred.resolve(item);
dismiss(false);
}
};
this.$cancelBtn.addEventListener('click', dismiss);
this.$submitBtn.addEventListener('click', close);
this.$backdrop.addEventListener('click', close);
return {
close,
dismiss,
show: () => this.showModal(escapeKeyListener),
hide: () => this.hideModal(escapeKeyListener, $scope),
element: this.$container,
result: deferred.promise,
};
}
protected showModal(escapeKeyListener: (e: KeyboardEvent) => void) {
if (this.$container.hasAttribute('aria-hidden') === false) {
return;
}
return new Promise(resolve => {
this.$container.removeAttribute('aria-hidden');
window.addEventListener('keydown', escapeKeyListener);
document.body.appendChild(this.$backdrop);
document.body.appendChild(this.$container);
document.body.classList.add('modal-open');
setTimeout(() => {
this.$backdrop.style.setProperty('opacity', '0.46');
this.$container.style.setProperty('opacity', '1');
resolve();
}, MODAL_SHOW_DELAY);
});
}
protected hideModal(
escapeKeyListener: (e: KeyboardEvent) => void,
scope: angular.IScope & { $ctrl: NgController; },
) {
if (this.$container.hasAttribute('aria-hidden')) {
return;
}
return new Promise(resolve => {
this.$backdrop.style.setProperty('opacity', '0');
this.$container.style.setProperty('opacity', '0');
this.$container.setAttribute('aria-hidden', 'true');
scope.$destroy();
window.removeEventListener('keydown', escapeKeyListener);
setTimeout(() => {
document.body.classList.remove('modal-open');
}, MODAL_SHOW_DELAY * 3);
setTimeout(() => {
document.body.removeChild(this.$backdrop);
document.body.removeChild(this.$container);
resolve();
}, MODAL_HIDE_DELAY);
});
}
}
export interface NgModalOptions {
item?: Y;
/**
* String representing the modal's title.
*/
title?: string | (() => string);
/**
* Inline template representing the modal's content.
*/
template?: string | (() => string);
/**
* Submit button text. Set to `false` to hide. Defaults to `'Ok'`.
*/
okBtnText?: string | boolean;
/**
* Cancel button text. Set to `false` to hide. Defaults to `'Cancel'`.
*/
cancelBtnText?: string | boolean;
/**
* Whether to immediately show the modal. Defaults to `true`.
*/
show?: boolean;
/**
* A controller for a modal instance.
*/
controller?: T;
onClose?(): boolean;
}