diff --git a/components/modal/demo/async.ts b/components/modal/demo/async.ts index 86fbef3977f..197abf1f67b 100644 --- a/components/modal/demo/async.ts +++ b/components/modal/demo/async.ts @@ -6,7 +6,7 @@ import { Component } from '@angular/core'; -

Modal Content

-
+ ` }) export class NzDemoModalAsyncComponent { diff --git a/components/modal/demo/basic.ts b/components/modal/demo/basic.ts index ce7a14070ac..eed584370b7 100644 --- a/components/modal/demo/basic.ts +++ b/components/modal/demo/basic.ts @@ -4,12 +4,12 @@ import { Component } from '@angular/core'; selector: 'nz-demo-modal-basic', template: ` - +

Content one

Content two

Content three

Content three

-
+ ` }) export class NzDemoModalBasicComponent { diff --git a/components/modal/demo/confirm-promise.ts b/components/modal/demo/confirm-promise.ts index 25c5e0669f4..88401c322f4 100644 --- a/components/modal/demo/confirm-promise.ts +++ b/components/modal/demo/confirm-promise.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { NzModal, NzModalRef2 } from 'ng-zorro-antd/modal'; +import { NzModalRef, NzModalService } from 'ng-zorro-antd/modal'; @Component({ selector: 'nz-demo-modal-confirm-promise', @@ -8,9 +8,9 @@ import { NzModal, NzModalRef2 } from 'ng-zorro-antd/modal'; ` }) export class NzDemoModalConfirmPromiseComponent { - confirmModal: NzModalRef2; // For testing by now + confirmModal: NzModalRef; // For testing by now - constructor(private modal: NzModal) {} + constructor(private modal: NzModalService) {} showConfirm(): void { this.confirmModal = this.modal.confirm({ diff --git a/components/modal/demo/confirm.ts b/components/modal/demo/confirm.ts index 159dcb16c49..a70ed64faff 100644 --- a/components/modal/demo/confirm.ts +++ b/components/modal/demo/confirm.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { NzModal } from 'ng-zorro-antd/modal'; +import { NzModalService } from 'ng-zorro-antd/modal'; @Component({ selector: 'nz-demo-modal-confirm', @@ -16,7 +16,7 @@ import { NzModal } from 'ng-zorro-antd/modal'; ] }) export class NzDemoModalConfirmComponent { - constructor(private modal: NzModal) {} + constructor(private modal: NzModalService) {} showConfirm(): void { this.modal.confirm({ diff --git a/components/modal/demo/footer.ts b/components/modal/demo/footer.ts index c2515b522f9..24a6a2c7b78 100644 --- a/components/modal/demo/footer.ts +++ b/components/modal/demo/footer.ts @@ -6,7 +6,7 @@ import { Component } from '@angular/core'; - Custom Callback - + ` }) export class NzDemoModalFooterComponent { diff --git a/components/modal/demo/footer2.ts b/components/modal/demo/footer2.ts index 983516a26b9..1f6c82a184b 100644 --- a/components/modal/demo/footer2.ts +++ b/components/modal/demo/footer2.ts @@ -1,7 +1,7 @@ /* entryComponents: NzModalCustomFooterComponent */ import { Component } from '@angular/core'; -import { NzModal, NzModalRef2 } from 'ng-zorro-antd/modal'; +import { NzModalRef, NzModalService } from 'ng-zorro-antd/modal'; @Component({ selector: 'nz-demo-modal-footer2', @@ -14,7 +14,7 @@ import { NzModal, NzModalRef2 } from 'ng-zorro-antd/modal'; - +

Modal Content

Modal Content

@@ -27,7 +27,7 @@ import { NzModal, NzModalRef2 } from 'ng-zorro-antd/modal';
-
+ `, styles: [] }) @@ -35,7 +35,7 @@ export class NzDemoModalFooter2Component { isVisible = false; isConfirmLoading = false; - constructor(private modalService: NzModal) {} + constructor(private modalService: NzModalService) {} showModal1(): void { this.isVisible = true; @@ -78,7 +78,7 @@ export class NzDemoModalFooter2Component { ` }) export class NzModalCustomFooterComponent { - constructor(private modal: NzModalRef2) {} + constructor(private modal: NzModalRef) {} destroyModal(): void { this.modal.destroy(); diff --git a/components/modal/demo/info.ts b/components/modal/demo/info.ts index 84937f51848..5a5d19013b8 100644 --- a/components/modal/demo/info.ts +++ b/components/modal/demo/info.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { NzModal } from 'ng-zorro-antd/modal'; +import { NzModalService } from 'ng-zorro-antd/modal'; @Component({ selector: 'nz-demo-modal-info', @@ -18,7 +18,7 @@ import { NzModal } from 'ng-zorro-antd/modal'; ] }) export class NzDemoModalInfoComponent { - constructor(private modal: NzModal) {} + constructor(private modal: NzModalService) {} info(): void { this.modal.info({ diff --git a/components/modal/demo/locale.ts b/components/modal/demo/locale.ts index aeb6a31d154..1b1282b02f0 100644 --- a/components/modal/demo/locale.ts +++ b/components/modal/demo/locale.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { NzModal } from 'ng-zorro-antd/modal'; +import { NzModalService } from 'ng-zorro-antd/modal'; @Component({ selector: 'nz-demo-modal-locale', @@ -26,7 +26,7 @@ import { NzModal } from 'ng-zorro-antd/modal'; export class NzDemoModalLocaleComponent { isVisible = false; - constructor(private modalService: NzModal) {} + constructor(private modalService: NzModalService) {} showModal(): void { this.isVisible = true; diff --git a/components/modal/demo/position.ts b/components/modal/demo/position.ts index 43a4824b0b4..01db401760c 100644 --- a/components/modal/demo/position.ts +++ b/components/modal/demo/position.ts @@ -4,7 +4,7 @@ import { Component } from '@angular/core'; selector: 'nz-demo-modal-position', template: ` - some contents...

some contents...

some contents...

-
+

- some contents...

some contents...

some contents...

-
+ `, styles: [ ` diff --git a/components/modal/demo/service.ts b/components/modal/demo/service.ts index 8a789c25a92..c219d3e3b65 100644 --- a/components/modal/demo/service.ts +++ b/components/modal/demo/service.ts @@ -1,7 +1,7 @@ -/* entryComponents: NzModalCustomComponent,NzModalCustomComponent */ +/* entryComponents: NzModalCustomComponent */ import { Component, Input, TemplateRef } from '@angular/core'; -import { NzModal, NzModalRef2 } from 'ng-zorro-antd/modal'; +import { NzModalRef, NzModalService } from 'ng-zorro-antd/modal'; @Component({ selector: 'nz-demo-modal-service', @@ -35,9 +35,6 @@ import { NzModal, NzModalRef2 } from 'ng-zorro-antd/modal'; Use Component -

@@ -56,12 +53,12 @@ import { NzModal, NzModalRef2 } from 'ng-zorro-antd/modal'; ] }) export class NzDemoModalServiceComponent { - tplModal: NzModalRef2; + tplModal: NzModalRef; tplModalButtonLoading = false; htmlModalVisible = false; disabled = false; - constructor(private modal: NzModal) {} + constructor(private modal: NzModalService) {} createModal(): void { this.modal.create({ @@ -91,29 +88,6 @@ export class NzDemoModalServiceComponent { }, 1000); } - createComponentModal2(): void { - const modalRef = this.modal.create({ - nzContent: NzModalCustom2Component, - nzTitle: 'Title', - nzMaskStyle: { - color: 'red' - }, - nzStyle: { - color: 'red' - }, - nzClassName: 'my-class-name', - nzWrapClassName: 'my-wrap-class-name', - nzZIndex: 1100, - nzComponentParams: { - title: 'title', - subtitle: 'subtitle' - }, - nzOnOk: () => new Promise(resolve => setTimeout(resolve, 1000)) - }); - modalRef.afterOpen.subscribe(console.log); - modalRef.afterClose.subscribe(console.log); - } - createComponentModal(): void { const modal = this.modal.create({ nzTitle: 'Modal Title', @@ -133,27 +107,25 @@ export class NzDemoModalServiceComponent { } ] }); - + const instance = modal.getContentComponent(); modal.afterOpen.subscribe(() => console.log('[afterOpen] emitted!')); - // Return a result when closed modal.afterClose.subscribe(result => console.log('[afterClose] The result is:', result)); // delay until modal instance created setTimeout(() => { - const instance = modal.getContentComponent(); instance.subtitle = 'sub title is changed'; }, 2000); } createCustomButtonModal(): void { - const modal: NzModalRef2 = this.modal.create({ + const modal: NzModalRef = this.modal.create({ nzTitle: 'custom button demo', nzContent: 'pass array of button config to nzFooter to create multiple buttons', nzFooter: [ { label: 'Close', - shape: 'default', + shape: 'round', onClick: () => modal.destroy() }, { @@ -222,32 +194,7 @@ export class NzModalCustomComponent { @Input() title: string; @Input() subtitle: string; - constructor(private modal: NzModalRef2) {} - - destroyModal(): void { - this.modal.destroy({ data: 'this the result data' }); - } -} - -@Component({ - selector: 'nz-modal-custom-component', - template: ` -

{{ title }}

-

{{ subtitle }}

- Content -
- Content -
- Content... - ` -}) -export class NzModalCustom2Component { - @Input() title: string; - @Input() subtitle: string; - - constructor(private modal: NzModalRef2) { - console.log(modal); - } + constructor(private modal: NzModalRef) {} destroyModal(): void { this.modal.destroy({ data: 'this the result data' }); diff --git a/components/modal/modal-close.component.ts b/components/modal/modal-close.component.ts index 9c1172b90bf..8b495e5aa0e 100644 --- a/components/modal/modal-close.component.ts +++ b/components/modal/modal-close.component.ts @@ -8,7 +8,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { ModalConfig } from './nz-modal.type'; +import { ModalOptions } from './modal-types'; @Component({ selector: 'button[nz-modal-close]', @@ -27,5 +27,5 @@ import { ModalConfig } from './nz-modal.type'; changeDetection: ChangeDetectionStrategy.OnPush }) export class NzModalCloseComponent { - constructor(public config: ModalConfig) {} + constructor(public config: ModalOptions) {} } diff --git a/components/modal/nz-modal-config.ts b/components/modal/modal-config.ts similarity index 100% rename from components/modal/nz-modal-config.ts rename to components/modal/modal-config.ts diff --git a/components/modal/modal-confirm-container.component.ts b/components/modal/modal-confirm-container.component.ts index 3ea0b0c5508..d04216f511a 100644 --- a/components/modal/modal-confirm-container.component.ts +++ b/components/modal/modal-confirm-container.component.ts @@ -24,6 +24,7 @@ import { Renderer2, ViewChild } from '@angular/core'; +import { ANIMATION_MODULE_TYPE } from '@angular/platform-browser/animations'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -31,8 +32,8 @@ import { takeUntil } from 'rxjs/operators'; import { NzI18nService } from 'ng-zorro-antd/i18n'; import { nzModalAnimations } from './modal-animations'; -import { NzModalContainerComponent } from './modal-container.component'; -import { ModalConfig } from './nz-modal.type'; +import { BaseModalContainer } from './modal-container'; +import { ModalOptions } from './modal-types'; @Component({ selector: 'nz-modal-confirm-container', @@ -44,7 +45,7 @@ import { ModalConfig } from './nz-modal.type'; class="ant-modal" [class]="config.nzClassName" [ngStyle]="config.nzStyle" - [style.width]="config.nzWidth | nzToCssUnit" + [style.width]="config?.nzWidth | nzToCssUnit" >
@@ -107,7 +108,7 @@ import { ModalConfig } from './nz-modal.type'; '(mouseup)': 'onMouseup($event)' } }) -export class NzModalConfirmContainerComponent extends NzModalContainerComponent implements OnDestroy { +export class NzModalConfirmContainerComponent extends BaseModalContainer implements OnDestroy { @ViewChild(CdkPortalOutlet, { static: true }) portalOutlet: CdkPortalOutlet; @ViewChild('modalElement', { static: true }) modalElementRef: ElementRef; @Output() readonly cancelTriggered = new EventEmitter(); @@ -123,11 +124,12 @@ export class NzModalConfirmContainerComponent extends NzModalContainerComponent render: Renderer2, zone: NgZone, overlayRef: OverlayRef, - public config: ModalConfig, + public config: ModalOptions, // tslint:disable-next-line:no-any - @Optional() @Inject(DOCUMENT) document: any + @Optional() @Inject(DOCUMENT) document: any, + @Optional() @Inject(ANIMATION_MODULE_TYPE) animationType: string ) { - super(elementRef, focusTrapFactory, cdr, render, zone, overlayRef, config, document); + super(elementRef, focusTrapFactory, cdr, render, zone, overlayRef, config, document, animationType); this.i18n.localeChange.pipe(takeUntil(this.destroy$)).subscribe(() => { this.locale = this.i18n.getLocaleData('Modal'); }); diff --git a/components/modal/modal-container.component.ts b/components/modal/modal-container.component.ts index 257849da838..cc5ac5982e3 100644 --- a/components/modal/modal-container.component.ts +++ b/components/modal/modal-container.component.ts @@ -6,49 +6,26 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -import { AnimationEvent } from '@angular/animations'; -import { FocusTrap, FocusTrapFactory } from '@angular/cdk/a11y'; +import { FocusTrapFactory } from '@angular/cdk/a11y'; import { OverlayRef } from '@angular/cdk/overlay'; -import { BasePortalOutlet, CdkPortalOutlet, ComponentPortal, TemplatePortal } from '@angular/cdk/portal'; +import { CdkPortalOutlet } from '@angular/cdk/portal'; import { DOCUMENT } from '@angular/common'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - ComponentRef, ElementRef, - EmbeddedViewRef, - EventEmitter, Inject, NgZone, Optional, Renderer2, ViewChild } from '@angular/core'; - -import { getElementOffset } from 'ng-zorro-antd/core'; +import { ANIMATION_MODULE_TYPE } from '@angular/platform-browser/animations'; import { nzModalAnimations } from './modal-animations'; -import { NzModalRef2 } from './nz-modal-ref'; -import { ModalConfig } from './nz-modal.type'; - -export function throwNzModalContentAlreadyAttachedError(): never { - throw Error('Attempting to attach modal content after content is already attached'); -} - -const ZOOM_CLASS_NAME_MAP = { - enter: 'zoom-enter', - enterActive: 'zoom-enter-active', - leave: 'zoom-leave', - leaveActive: 'zoom-leave-active' -}; - -const FADE_CLASS_NAME_MAP = { - enter: 'fade-enter', - enterActive: 'fade-enter-active', - leave: 'fade-leave', - leaveActive: 'fade-leave-active' -}; +import { BaseModalContainer } from './modal-container'; +import { ModalOptions } from './modal-types'; @Component({ selector: 'nz-modal-container', @@ -60,7 +37,7 @@ const FADE_CLASS_NAME_MAP = { class="ant-modal" [class]="config.nzClassName" [ngStyle]="config.nzStyle" - [style.width]="config.nzWidth | nzToCssUnit" + [style.width]="config?.nzWidth | nzToCssUnit" >
@@ -69,7 +46,13 @@ const FADE_CLASS_NAME_MAP = {
-
+
`, @@ -91,261 +74,21 @@ const FADE_CLASS_NAME_MAP = { } }) // tslint:disable-next-line:no-any -export class NzModalContainerComponent extends BasePortalOutlet { +export class NzModalContainerComponent extends BaseModalContainer { @ViewChild(CdkPortalOutlet, { static: true }) portalOutlet: CdkPortalOutlet; @ViewChild('modalElement', { static: true }) modalElementRef: ElementRef; - - animationStateChanged = new EventEmitter(); - containerClick = new EventEmitter(); - cancelTriggered = new EventEmitter(); - okTriggered = new EventEmitter(); - - state: 'void' | 'enter' | 'exit' = 'enter'; - document: Document; - // tslint:disable-next-line:no-any - modalRef: NzModalRef2; - isStringContent: boolean = false; - private elementFocusedBeforeModalWasOpened: HTMLElement | null = null; - private focusTrap: FocusTrap; - private latestMousedownTarget: HTMLElement | null = null; - private oldMaskStyle: { [key: string]: string } | null = null; - constructor( - protected elementRef: ElementRef, - protected focusTrapFactory: FocusTrapFactory, - protected cdr: ChangeDetectorRef, - protected render: Renderer2, - protected zone: NgZone, - protected overlayRef: OverlayRef, - public config: ModalConfig, + elementRef: ElementRef, + focusTrapFactory: FocusTrapFactory, + cdr: ChangeDetectorRef, + render: Renderer2, + zone: NgZone, + overlayRef: OverlayRef, + public config: ModalOptions, // tslint:disable-next-line:no-any - @Optional() @Inject(DOCUMENT) document: any + @Optional() @Inject(DOCUMENT) document: any, + @Optional() @Inject(ANIMATION_MODULE_TYPE) animationType: string ) { - super(); - this.document = document; - this.isStringContent = typeof config.nzContent === 'string'; - this.setContainer(); - console.log(config); - } - - onMousedown(e: MouseEvent): void { - this.latestMousedownTarget = (e.target as HTMLElement) || null; - } - - onMouseup(e: MouseEvent): void { - if (e.target === this.latestMousedownTarget && e.target === this.elementRef.nativeElement) { - this.containerClick.emit(); - } - this.latestMousedownTarget = null; - } - - onCloseClick(): void { - this.cancelTriggered.emit(); - } - - onOkClick(): void { - this.okTriggered.emit(); - } - - attachComponentPortal(portal: ComponentPortal): ComponentRef { - if (this.portalOutlet.hasAttached()) { - throwNzModalContentAlreadyAttachedError(); - } - this.savePreviouslyFocusedElement(); - this.setModalTransformOrigin(); - return this.portalOutlet.attachComponentPortal(portal); - } - - attachTemplatePortal(portal: TemplatePortal): EmbeddedViewRef { - if (this.portalOutlet.hasAttached()) { - throwNzModalContentAlreadyAttachedError(); - } - this.savePreviouslyFocusedElement(); - return this.portalOutlet.attachTemplatePortal(portal); - } - - getNativeElement(): HTMLElement { - return this.elementRef.nativeElement; - } - - private setModalTransformOrigin(): void { - const modalElement = this.modalElementRef.nativeElement; - if (this.elementFocusedBeforeModalWasOpened as HTMLElement) { - const previouslyDOMRect = this.elementFocusedBeforeModalWasOpened!.getBoundingClientRect(); - const lastPosition = getElementOffset(this.elementFocusedBeforeModalWasOpened!); - const x = lastPosition.left + previouslyDOMRect.width / 2; - const y = lastPosition.top + previouslyDOMRect.height / 2; - const transformOrigin = `${x - modalElement.offsetLeft}px ${y - modalElement.offsetTop}px 0px`; - this.render.setStyle(modalElement, 'transform-origin', transformOrigin); - } - } - - private savePreviouslyFocusedElement(): void { - if (this.document) { - this.elementFocusedBeforeModalWasOpened = this.document.activeElement as HTMLElement; - if (this.elementRef.nativeElement.focus) { - Promise.resolve().then(() => this.elementRef.nativeElement.focus()); - } - } - } - - private trapFocus(): void { - const element = this.elementRef.nativeElement; - - if (!this.focusTrap) { - this.focusTrap = this.focusTrapFactory.create(element); - } - - if (this.config.nzAutofocus) { - this.focusTrap.focusInitialElementWhenReady().then(); - } else { - const activeElement = this.document.activeElement; - if (activeElement !== element && !element.contains(activeElement)) { - element.focus(); - } - } - } - - private restoreFocus(): void { - const toFocus = this.elementFocusedBeforeModalWasOpened as HTMLElement; - - // We need the extra check, because IE can set the `activeElement` to null in some cases. - if (toFocus && typeof toFocus.focus === 'function') { - const activeElement = this.document.activeElement as Element; - const element = this.elementRef.nativeElement; - - if (!activeElement || activeElement === this.document.body || activeElement === element || element.contains(activeElement)) { - toFocus.focus(); - } - } - - if (this.focusTrap) { - this.focusTrap.destroy(); - } - } - - private setEnterAnimationClass(): void { - if (this.config.nzNoAnimation) { - return; - } - this.zone.runOutsideAngular(() => { - // Make sure to set the `TransformOrigin` style before set the modelElement's class names - this.setModalTransformOrigin(); - const modalElement = this.modalElementRef.nativeElement; - const backdropElement = this.overlayRef.backdropElement; - this.render.addClass(modalElement, ZOOM_CLASS_NAME_MAP.enter); - this.render.addClass(modalElement, ZOOM_CLASS_NAME_MAP.enterActive); - this.render.addClass(backdropElement, FADE_CLASS_NAME_MAP.enter); - this.render.addClass(backdropElement, FADE_CLASS_NAME_MAP.enterActive); - }); - } - - private setExitAnimationClass(): void { - if (this.config.nzNoAnimation) { - return; - } - this.zone.runOutsideAngular(() => { - const modalElement = this.modalElementRef.nativeElement; - const backdropElement = this.overlayRef.backdropElement; - this.render.addClass(modalElement, ZOOM_CLASS_NAME_MAP.leave); - this.render.addClass(modalElement, ZOOM_CLASS_NAME_MAP.leaveActive); - this.render.addClass(backdropElement, FADE_CLASS_NAME_MAP.leave); - this.render.addClass(backdropElement, FADE_CLASS_NAME_MAP.leaveActive); - }); - } - - private cleanAnimationClass(): void { - if (this.config.nzNoAnimation) { - return; - } - this.zone.runOutsideAngular(() => { - const backdropElement = this.overlayRef.backdropElement; - const modalElement = this.modalElementRef.nativeElement; - this.render.removeClass(modalElement, ZOOM_CLASS_NAME_MAP.enter); - this.render.removeClass(modalElement, ZOOM_CLASS_NAME_MAP.enterActive); - this.render.removeClass(modalElement, ZOOM_CLASS_NAME_MAP.leave); - this.render.removeClass(modalElement, ZOOM_CLASS_NAME_MAP.leaveActive); - this.render.removeClass(backdropElement, FADE_CLASS_NAME_MAP.enter); - this.render.removeClass(backdropElement, FADE_CLASS_NAME_MAP.enterActive); - }); - } - - private bindBackdropStyle(): void { - this.zone.runOutsideAngular(() => { - if (this.oldMaskStyle) { - const backdropElement = this.overlayRef.backdropElement; - const styles = this.oldMaskStyle as { [key: string]: string }; - Object.keys(styles).forEach(key => { - this.render.removeStyle(backdropElement, key); - }); - this.oldMaskStyle = null; - } - - if (typeof this.config.nzMaskStyle === 'object' && Object.keys(this.config.nzMaskStyle).length) { - const backdropElement = this.overlayRef.backdropElement; - const styles: { [key: string]: string } = { ...this.config.nzMaskStyle }; - Object.keys(styles).forEach(key => { - this.render.setStyle(backdropElement, key, styles[key]); - }); - this.oldMaskStyle = styles; - } - }); - } - - /** - * Set the container element. - * @deprecated Not supported. - * @breaking-change 10.0.0 - */ - private setContainer(): void { - const container = this.getContainer(); - if (container) { - this.render.appendChild(container, this.elementRef.nativeElement); - } - } - - /** - * Reset the container element. - * @deprecated Not supported. - * @breaking-change 10.0.0 - */ - private resetContainer(): void { - const container = this.getContainer(); - if (container) { - this.render.appendChild(this.overlayRef.overlayElement, this.elementRef.nativeElement); - } - } - - private getContainer(): HTMLElement | null { - const { nzGetContainer } = this.config; - const container = typeof nzGetContainer === 'function' ? nzGetContainer() : nzGetContainer; - return container instanceof HTMLElement ? container : null; - } - - onAnimationDone(event: AnimationEvent): void { - if (event.toState === 'enter') { - this.setContainer(); - this.trapFocus(); - } else if (event.toState === 'exit') { - this.restoreFocus(); - } - this.cleanAnimationClass(); - this.animationStateChanged.emit(event); - } - - onAnimationStart(event: AnimationEvent): void { - if (event.toState === 'enter') { - this.setEnterAnimationClass(); - this.bindBackdropStyle(); - } else if (event.toState === 'exit') { - this.resetContainer(); - this.setExitAnimationClass(); - } - this.animationStateChanged.emit(event); - } - - startExitAnimation(): void { - this.state = 'exit'; - this.cdr.markForCheck(); + super(elementRef, focusTrapFactory, cdr, render, zone, overlayRef, config, document, animationType); } } diff --git a/components/modal/modal-container.ts b/components/modal/modal-container.ts new file mode 100644 index 00000000000..7076f42a5b9 --- /dev/null +++ b/components/modal/modal-container.ts @@ -0,0 +1,298 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE + */ + +import { AnimationEvent } from '@angular/animations'; +import { FocusTrap, FocusTrapFactory } from '@angular/cdk/a11y'; +import { OverlayRef } from '@angular/cdk/overlay'; +import { BasePortalOutlet, CdkPortalOutlet, ComponentPortal, TemplatePortal } from '@angular/cdk/portal'; +import { ChangeDetectorRef, ComponentRef, ElementRef, EmbeddedViewRef, EventEmitter, NgZone, Renderer2 } from '@angular/core'; + +import { getElementOffset } from 'ng-zorro-antd/core'; + +import { NzModalRef } from './modal-ref'; +import { ModalOptions } from './modal-types'; + +export function throwNzModalContentAlreadyAttachedError(): never { + throw Error('Attempting to attach modal content after content is already attached'); +} + +const ZOOM_CLASS_NAME_MAP = { + enter: 'zoom-enter', + enterActive: 'zoom-enter-active', + leave: 'zoom-leave', + leaveActive: 'zoom-leave-active' +}; + +const FADE_CLASS_NAME_MAP = { + enter: 'fade-enter', + enterActive: 'fade-enter-active', + leave: 'fade-leave', + leaveActive: 'fade-leave-active' +}; + +export class BaseModalContainer extends BasePortalOutlet { + portalOutlet: CdkPortalOutlet; + modalElementRef: ElementRef; + + animationStateChanged = new EventEmitter(); + containerClick = new EventEmitter(); + cancelTriggered = new EventEmitter(); + okTriggered = new EventEmitter(); + + state: 'void' | 'enter' | 'exit' = 'enter'; + document: Document; + modalRef: NzModalRef; + isStringContent: boolean = false; + private elementFocusedBeforeModalWasOpened: HTMLElement | null = null; + private focusTrap: FocusTrap; + private latestMousedownTarget: HTMLElement | null = null; + private oldMaskStyle: { [key: string]: string } | null = null; + + constructor( + protected elementRef: ElementRef, + protected focusTrapFactory: FocusTrapFactory, + protected cdr: ChangeDetectorRef, + protected render: Renderer2, + protected zone: NgZone, + protected overlayRef: OverlayRef, + public config: ModalOptions, + // tslint:disable-next-line:no-any + document?: any, + protected animationType?: string + ) { + super(); + this.document = document; + this.isStringContent = typeof config.nzContent === 'string'; + this.setContainer(); + } + + onMousedown(e: MouseEvent): void { + this.latestMousedownTarget = (e.target as HTMLElement) || null; + } + + onMouseup(e: MouseEvent): void { + if (e.target === this.latestMousedownTarget && e.target === this.elementRef.nativeElement) { + this.containerClick.emit(); + } + this.latestMousedownTarget = null; + } + + onCloseClick(): void { + this.cancelTriggered.emit(); + } + + onOkClick(): void { + this.okTriggered.emit(); + } + + attachComponentPortal(portal: ComponentPortal): ComponentRef { + if (this.portalOutlet.hasAttached()) { + throwNzModalContentAlreadyAttachedError(); + } + this.savePreviouslyFocusedElement(); + this.setModalTransformOrigin(); + return this.portalOutlet.attachComponentPortal(portal); + } + + attachTemplatePortal(portal: TemplatePortal): EmbeddedViewRef { + if (this.portalOutlet.hasAttached()) { + throwNzModalContentAlreadyAttachedError(); + } + this.savePreviouslyFocusedElement(); + return this.portalOutlet.attachTemplatePortal(portal); + } + + getNativeElement(): HTMLElement { + return this.elementRef.nativeElement; + } + + private animationDisabled(): boolean { + return this.config.nzNoAnimation || this.animationType === 'NoopAnimations'; + } + + private setModalTransformOrigin(): void { + const modalElement = this.modalElementRef.nativeElement; + if (this.elementFocusedBeforeModalWasOpened as HTMLElement) { + const previouslyDOMRect = this.elementFocusedBeforeModalWasOpened!.getBoundingClientRect(); + const lastPosition = getElementOffset(this.elementFocusedBeforeModalWasOpened!); + const x = lastPosition.left + previouslyDOMRect.width / 2; + const y = lastPosition.top + previouslyDOMRect.height / 2; + const transformOrigin = `${x - modalElement.offsetLeft}px ${y - modalElement.offsetTop}px 0px`; + this.render.setStyle(modalElement, 'transform-origin', transformOrigin); + } + } + + private savePreviouslyFocusedElement(): void { + if (this.document) { + this.elementFocusedBeforeModalWasOpened = this.document.activeElement as HTMLElement; + if (this.elementRef.nativeElement.focus) { + Promise.resolve().then(() => this.elementRef.nativeElement.focus()); + } + } + } + + private trapFocus(): void { + const element = this.elementRef.nativeElement; + + if (!this.focusTrap) { + this.focusTrap = this.focusTrapFactory.create(element); + } + + if (this.config.nzAutofocus) { + this.focusTrap.focusInitialElementWhenReady().then(); + } else { + const activeElement = this.document.activeElement; + if (activeElement !== element && !element.contains(activeElement)) { + element.focus(); + } + } + } + + private restoreFocus(): void { + const toFocus = this.elementFocusedBeforeModalWasOpened as HTMLElement; + + // We need the extra check, because IE can set the `activeElement` to null in some cases. + if (toFocus && typeof toFocus.focus === 'function') { + const activeElement = this.document.activeElement as Element; + const element = this.elementRef.nativeElement; + + if (!activeElement || activeElement === this.document.body || activeElement === element || element.contains(activeElement)) { + toFocus.focus(); + } + } + + if (this.focusTrap) { + this.focusTrap.destroy(); + } + } + + private setEnterAnimationClass(): void { + if (this.animationDisabled()) { + return; + } + this.zone.runOutsideAngular(() => { + // Make sure to set the `TransformOrigin` style before set the modelElement's class names + this.setModalTransformOrigin(); + const modalElement = this.modalElementRef.nativeElement; + const backdropElement = this.overlayRef.backdropElement; + this.render.addClass(modalElement, ZOOM_CLASS_NAME_MAP.enter); + this.render.addClass(modalElement, ZOOM_CLASS_NAME_MAP.enterActive); + this.render.addClass(backdropElement, FADE_CLASS_NAME_MAP.enter); + this.render.addClass(backdropElement, FADE_CLASS_NAME_MAP.enterActive); + }); + } + + private setExitAnimationClass(): void { + if (this.animationDisabled()) { + return; + } + this.zone.runOutsideAngular(() => { + const modalElement = this.modalElementRef.nativeElement; + const backdropElement = this.overlayRef.backdropElement; + this.render.addClass(modalElement, ZOOM_CLASS_NAME_MAP.leave); + this.render.addClass(modalElement, ZOOM_CLASS_NAME_MAP.leaveActive); + this.render.addClass(backdropElement, FADE_CLASS_NAME_MAP.leave); + this.render.addClass(backdropElement, FADE_CLASS_NAME_MAP.leaveActive); + }); + } + + private cleanAnimationClass(): void { + if (this.animationDisabled()) { + return; + } + this.zone.runOutsideAngular(() => { + const backdropElement = this.overlayRef.backdropElement; + const modalElement = this.modalElementRef.nativeElement; + this.render.removeClass(modalElement, ZOOM_CLASS_NAME_MAP.enter); + this.render.removeClass(modalElement, ZOOM_CLASS_NAME_MAP.enterActive); + this.render.removeClass(modalElement, ZOOM_CLASS_NAME_MAP.leave); + this.render.removeClass(modalElement, ZOOM_CLASS_NAME_MAP.leaveActive); + this.render.removeClass(backdropElement, FADE_CLASS_NAME_MAP.enter); + this.render.removeClass(backdropElement, FADE_CLASS_NAME_MAP.enterActive); + }); + } + + private bindBackdropStyle(): void { + this.zone.runOutsideAngular(() => { + if (this.oldMaskStyle) { + const backdropElement = this.overlayRef.backdropElement; + const styles = this.oldMaskStyle as { [key: string]: string }; + Object.keys(styles).forEach(key => { + this.render.removeStyle(backdropElement, key); + }); + this.oldMaskStyle = null; + } + + if (typeof this.config.nzMaskStyle === 'object' && Object.keys(this.config.nzMaskStyle).length) { + const backdropElement = this.overlayRef.backdropElement; + const styles: { [key: string]: string } = { ...this.config.nzMaskStyle }; + Object.keys(styles).forEach(key => { + this.render.setStyle(backdropElement, key, styles[key]); + }); + this.oldMaskStyle = styles; + } + }); + } + + /** + * Set the container element. + * @deprecated Not supported. + * @breaking-change 10.0.0 + */ + private setContainer(): void { + const container = this.getContainer(); + if (container) { + this.render.appendChild(container, this.elementRef.nativeElement); + } + } + + /** + * Reset the container element. + * @deprecated Not supported. + * @breaking-change 10.0.0 + */ + private resetContainer(): void { + const container = this.getContainer(); + if (container) { + this.render.appendChild(this.overlayRef.overlayElement, this.elementRef.nativeElement); + } + } + + private getContainer(): HTMLElement | null { + const { nzGetContainer } = this.config; + const container = typeof nzGetContainer === 'function' ? nzGetContainer() : nzGetContainer; + return container instanceof HTMLElement ? container : null; + } + + onAnimationDone(event: AnimationEvent): void { + if (event.toState === 'enter') { + this.setContainer(); + this.trapFocus(); + } else if (event.toState === 'exit') { + this.restoreFocus(); + } + this.cleanAnimationClass(); + this.animationStateChanged.emit(event); + } + + onAnimationStart(event: AnimationEvent): void { + if (event.toState === 'enter') { + this.setEnterAnimationClass(); + this.bindBackdropStyle(); + } else if (event.toState === 'exit') { + this.resetContainer(); + this.setExitAnimationClass(); + } + this.animationStateChanged.emit(event); + } + + startExitAnimation(): void { + this.state = 'exit'; + this.cdr.markForCheck(); + } +} diff --git a/components/modal/modal-footer.component.ts b/components/modal/modal-footer.component.ts index 60f099baf8c..8e1288ce9a8 100644 --- a/components/modal/modal-footer.component.ts +++ b/components/modal/modal-footer.component.ts @@ -6,15 +6,15 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -import { ChangeDetectionStrategy, Component, EventEmitter, OnDestroy, Output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core'; +import { NzModalRef } from 'ng-zorro-antd'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { isPromise } from 'ng-zorro-antd/core'; import { NzI18nService } from 'ng-zorro-antd/i18n'; -import { NzModalContainerComponent } from './modal-container.component'; -import { ModalButtonOptions, ModalConfig } from './nz-modal.type'; +import { ModalButtonOptions, ModalOptions } from './modal-types'; @Component({ selector: 'div[nz-modal-footer]', @@ -78,10 +78,10 @@ export class NzModalFooterComponent implements OnDestroy { locale: { okText?: string; cancelText?: string } = {}; @Output() readonly cancelTriggered = new EventEmitter(); @Output() readonly okTriggered = new EventEmitter(); - + @Input() modalRef: NzModalRef; private destroy$ = new Subject(); - constructor(private i18n: NzI18nService, private modalContainer: NzModalContainerComponent, public config: ModalConfig) { + constructor(private i18n: NzI18nService, public config: ModalOptions) { if (Array.isArray(config.nzFooter)) { this.buttonsFooter = true; this.buttons = (config.nzFooter as ModalButtonOptions[]).map(mergeDefaultOption); @@ -107,7 +107,7 @@ export class NzModalFooterComponent implements OnDestroy { */ getButtonCallableProp(options: ModalButtonOptions, prop: keyof ModalButtonOptions): boolean { const value = options[prop]; - const componentInstance = this.modalContainer.modalRef.getContentComponent(); + const componentInstance = this.modalRef.getContentComponent(); return typeof value === 'function' ? value.apply(options, componentInstance && [componentInstance]) : value; } diff --git a/components/modal/nz-modal-footer.directive.spec.ts b/components/modal/modal-footer.directive.spec.ts similarity index 95% rename from components/modal/nz-modal-footer.directive.spec.ts rename to components/modal/modal-footer.directive.spec.ts index dbbb9be4b27..73ceabb939b 100644 --- a/components/modal/nz-modal-footer.directive.spec.ts +++ b/components/modal/modal-footer.directive.spec.ts @@ -4,8 +4,8 @@ import { async, ComponentFixture, fakeAsync, inject, TestBed, tick } from '@angu import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { NzModalModule } from './nz-modal.module'; -import { NzModalService } from './nz-modal.service'; +import { NzModalModule } from './modal.module'; +import { NzModalService } from './modal.service'; describe('modal footer directive', () => { let overlayContainer: OverlayContainer; diff --git a/components/modal/nz-modal-footer.directive.ts b/components/modal/modal-footer.directive.ts similarity index 78% rename from components/modal/nz-modal-footer.directive.ts rename to components/modal/modal-footer.directive.ts index 3215efd06da..ae4dc5727d7 100644 --- a/components/modal/nz-modal-footer.directive.ts +++ b/components/modal/modal-footer.directive.ts @@ -7,14 +7,14 @@ */ import { Directive, Optional, TemplateRef } from '@angular/core'; -import { NzModalRef2 } from './nz-modal-ref'; +import { NzModalRef } from './modal-ref'; @Directive({ selector: '[nzModalFooter]', exportAs: 'nzModalFooter' }) export class NzModalFooterDirective { - constructor(@Optional() private nzModalRef: NzModalRef2, public templateRef: TemplateRef<{}>) { + constructor(@Optional() private nzModalRef: NzModalRef, public templateRef: TemplateRef<{}>) { if (this.nzModalRef) { this.nzModalRef.updateConfig({ nzFooter: this.templateRef diff --git a/components/modal/nz-modal-ref.ts b/components/modal/modal-ref.ts similarity index 91% rename from components/modal/nz-modal-ref.ts rename to components/modal/modal-ref.ts index 8b969451c2c..05ac56f7336 100644 --- a/components/modal/nz-modal-ref.ts +++ b/components/modal/modal-ref.ts @@ -13,8 +13,8 @@ import { filter, take } from 'rxjs/operators'; import { isPromise } from 'ng-zorro-antd/core'; -import { ModalConfig } from 'ng-zorro-antd/modal/nz-modal.type'; -import { NzModalContainerComponent } from './modal-container.component'; +import { ModalOptions } from 'ng-zorro-antd/modal/modal-types'; +import { BaseModalContainer } from './modal-container'; import { NzModalLegacyAPI } from './modal-legacy-api'; export const enum NzModalState { @@ -29,7 +29,7 @@ export const enum NzTriggerAction { } // tslint:disable-next-line:no-any -export class NzModalRef2 implements NzModalLegacyAPI { +export class NzModalRef implements NzModalLegacyAPI { componentInstance: T | null; result?: R; state: NzModalState = NzModalState.OPEN; @@ -38,7 +38,7 @@ export class NzModalRef2 implements NzModalLegacyAPI { private closeTimeout: number; - constructor(private overlayRef: OverlayRef, private config: ModalConfig, public containerInstance: NzModalContainerComponent) { + constructor(private overlayRef: OverlayRef, private config: ModalOptions, public containerInstance: BaseModalContainer) { containerInstance.animationStateChanged .pipe( filter(event => event.phaseName === 'done' && event.toState === 'enter'), @@ -150,14 +150,6 @@ export class NzModalRef2 implements NzModalLegacyAPI { this.state = NzModalState.CLOSING; } - updateSize(width: string = '', height: string = ''): this { - this.getPositionStrategy() - .width(width) - .height(height); - this.overlayRef.updatePosition(); - return this; - } - updatePosition(): this { const strategy = this.getPositionStrategy(); strategy.centerHorizontally(); @@ -166,7 +158,7 @@ export class NzModalRef2 implements NzModalLegacyAPI { return this; } - updateConfig(config: ModalConfig): void { + updateConfig(config: ModalOptions): void { Object.assign(this.config, config); } diff --git a/components/modal/modal-title.component.ts b/components/modal/modal-title.component.ts index 9aecdba6d89..f242396ba28 100644 --- a/components/modal/modal-title.component.ts +++ b/components/modal/modal-title.component.ts @@ -8,7 +8,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { ModalConfig } from './nz-modal.type'; +import { ModalOptions } from './modal-types'; @Component({ selector: 'div[nz-modal-title]', @@ -26,5 +26,5 @@ import { ModalConfig } from './nz-modal.type'; changeDetection: ChangeDetectionStrategy.OnPush }) export class NzModalTitleComponent { - constructor(public config: ModalConfig) {} + constructor(public config: ModalOptions) {} } diff --git a/components/modal/nz-modal.type.ts b/components/modal/modal-types.ts similarity index 62% rename from components/modal/nz-modal.type.ts rename to components/modal/modal-types.ts index 2f5c1b8aa61..22f199a1146 100644 --- a/components/modal/nz-modal.type.ts +++ b/components/modal/modal-types.ts @@ -12,7 +12,7 @@ import { NzButtonShape, NzButtonSize, NzButtonType } from 'ng-zorro-antd/button' export type OnClickCallback = (instance: T) => (false | void | {}) | Promise; -export type ModalType = 'default' | 'confirm'; // Different modal styles we have supported +export type ModalTypes = 'default' | 'confirm'; // Different modal styles we have supported export type ConfirmType = 'confirm' | 'info' | 'success' | 'error' | 'warning'; // Subtypes of Confirm Modal @@ -23,7 +23,7 @@ export interface StyleObjectLike { const noopFun = () => void 0; // tslint:disable-next-line:no-any -export class ModalConfig implements ModalOptions { +export class ModalOptions { nzClosable?: boolean = true; nzOkLoading?: boolean = false; nzOkDisabled?: boolean = false; @@ -38,7 +38,7 @@ export class ModalConfig implements ModalOptions { nzWidth?: number | string = 520; nzCloseIcon?: string | TemplateRef = 'close'; nzOkType?: NzButtonType = 'primary'; - nzModalType?: ModalType = 'default'; + nzModalType?: ModalTypes = 'default'; nzOnCancel?: EventEmitter | OnClickCallback = noopFun; nzOnOk?: EventEmitter | OnClickCallback = noopFun; nzComponentParams?: Partial; @@ -68,52 +68,6 @@ export class ModalConfig implements ModalOptions { nzIconType?: string = 'question-circle'; } -// Public options for using by service -// tslint:disable-next-line:no-any -export interface ModalOptions { - nzModalType?: ModalType; - nzVisible?: boolean; - nzZIndex?: number; - nzWidth?: number | string; - nzWrapClassName?: string; - nzClassName?: string; - nzStyle?: object; - nzIconType?: string; // Confirm Modal ONLY - nzTitle?: string | TemplateRef<{}>; - nzCloseIcon?: string | TemplateRef; - nzContent?: string | TemplateRef<{}> | Type; - nzComponentParams?: Partial; - nzClosable?: boolean; - nzKeyboard?: boolean; - nzMask?: boolean; - nzMaskClosable?: boolean; - nzMaskStyle?: StyleObjectLike; - nzBodyStyle?: StyleObjectLike; - nzFooter?: string | TemplateRef<{}> | Array> | null; // Default Modal ONLY - nzGetContainer?: HTMLElement | OverlayRef | (() => HTMLElement | OverlayRef); // STATIC - nzAfterOpen?: EventEmitter; - nzAfterClose?: EventEmitter; - - // --- Predefined OK & Cancel buttons - nzOkText?: string | null; - nzOkType?: NzButtonType; - nzOkLoading?: boolean; - nzOkDisabled?: boolean; - nzCancelDisabled?: boolean; - nzOnOk?: EventEmitter | OnClickCallback; // Mixed using ng's Input/Output (Should care of "this" when using OnClickCallback) - nzCancelText?: string | null; - nzCancelLoading?: boolean; - nzNoAnimation?: boolean; - nzOnCancel?: EventEmitter | OnClickCallback; // Mixed using ng's Input/Output (Should care of "this" when using OnClickCallback) -} - -// tslint:disable-next-line:no-any -export interface ModalOptionsForService extends ModalOptions { - // Limitations for using by service - nzOnOk?: OnClickCallback; - nzOnCancel?: OnClickCallback; -} - // tslint:disable-next-line:no-any export interface ModalButtonOptions { label: string; @@ -129,7 +83,6 @@ export interface ModalButtonOptions { disabled?: boolean | ((this: ModalButtonOptions, contentComponentInstance?: T) => boolean); // tslint:disable-next-line:no-any onClick?(this: ModalButtonOptions, contentComponentInstance?: T): any | Promise; - // tslint:disable-next-line:no-any [key: string]: any; } diff --git a/components/modal/modal.component.ts b/components/modal/modal.component.ts index efcd7447a14..5ea08d3266c 100644 --- a/components/modal/modal.component.ts +++ b/components/modal/modal.component.ts @@ -26,25 +26,25 @@ import { NzButtonType } from 'ng-zorro-antd/button'; import { InputBoolean, NzConfigService, WithConfig } from 'ng-zorro-antd/core'; import { Observable } from 'rxjs'; +import { NzModalFooterDirective } from './modal-footer.directive'; import { NzModalLegacyAPI } from './modal-legacy-api'; -import { NzModal } from './nz-modal'; -import { NzModalFooterDirective } from './nz-modal-footer.directive'; -import { NzModalRef2 } from './nz-modal-ref'; -import { ModalButtonOptions, ModalConfig, ModalType, OnClickCallback, StyleObjectLike } from './nz-modal.type'; +import { NzModalRef } from './modal-ref'; +import { ModalButtonOptions, ModalOptions, ModalTypes, OnClickCallback, StyleObjectLike } from './modal-types'; +import { NzModalService } from './modal.service'; import { getConfigFromComponent } from './utils'; const NZ_CONFIG_COMPONENT_NAME = 'modal'; @Component({ - selector: 'nz-modal2', - exportAs: 'nzModal2', + selector: 'nz-modal', + exportAs: 'nzModal', template: ` `, changeDetection: ChangeDetectionStrategy.OnPush }) // tslint:disable-next-line:no-any -export class NzModal2Component implements OnChanges, NzModalLegacyAPI { +export class NzModalComponent implements OnChanges, NzModalLegacyAPI { @Input() @WithConfig(NZ_CONFIG_COMPONENT_NAME, true) @InputBoolean() nzMask: boolean; @Input() @WithConfig(NZ_CONFIG_COMPONENT_NAME, true) @InputBoolean() nzMaskClosable: boolean; @Input() @InputBoolean() nzVisible: boolean = false; @@ -72,7 +72,7 @@ export class NzModal2Component implements OnChanges, NzModalLe @Input() nzCancelText: string | null; @Input() nzOkType: NzButtonType = 'primary'; @Input() nzIconType: string = 'question-circle'; // Confirm Modal ONLY - @Input() nzModalType: ModalType = 'default'; + @Input() nzModalType: ModalTypes = 'default'; @Input() @Output() @@ -92,7 +92,7 @@ export class NzModal2Component implements OnChanges, NzModalLe this.setFooterWithTemplate(value.templateRef); } } - private modalRef: NzModalRef2 | null = null; + private modalRef: NzModalRef | null = null; get afterOpen(): Observable { // Observable alias for nzAfterOpen @@ -104,7 +104,7 @@ export class NzModal2Component implements OnChanges, NzModalLe return this.nzAfterClose.asObservable(); } - constructor(public nzConfigService: NzConfigService, private cdr: ChangeDetectorRef, private modal: NzModal) {} + constructor(public nzConfigService: NzConfigService, private cdr: ChangeDetectorRef, private modal: NzModalService) {} open(): void { if (!this.modalRef) { @@ -153,7 +153,7 @@ export class NzModal2Component implements OnChanges, NzModalLe this.cdr.markForCheck(); } - private getConfig(): ModalConfig { + private getConfig(): ModalOptions { const componentConfig = getConfigFromComponent(this); if (!this.nzContent) { componentConfig.nzContent = this.contentTemplateRef; diff --git a/components/modal/nz-modal.module.ts b/components/modal/modal.module.ts similarity index 69% rename from components/modal/nz-modal.module.ts rename to components/modal/modal.module.ts index 6636d0e20d7..ab22a969420 100644 --- a/components/modal/nz-modal.module.ts +++ b/components/modal/modal.module.ts @@ -20,12 +20,10 @@ import { NzModalCloseComponent } from './modal-close.component'; import { NzModalConfirmContainerComponent } from './modal-confirm-container.component'; import { NzModalContainerComponent } from './modal-container.component'; import { NzModalFooterComponent } from './modal-footer.component'; +import { NzModalFooterDirective } from './modal-footer.directive'; import { NzModalTitleComponent } from './modal-title.component'; -import { NzModal2Component } from './modal.component'; -import { NzModalControlServiceModule } from './nz-modal-control.service.module'; -import { NzModalFooterDirective } from './nz-modal-footer.directive'; -import { NzModalComponent } from './nz-modal.component'; -import { NzModalServiceModule } from './nz-modal.service.module'; +import { NzModalComponent } from './modal.component'; +import { NzModalService } from './modal.service'; @NgModule({ imports: [ @@ -37,11 +35,10 @@ import { NzModalServiceModule } from './nz-modal.service.module'; NzButtonModule, NzIconModule, NzPipesModule, - NzNoAnimationModule, - NzModalServiceModule, - NzModalControlServiceModule + NzNoAnimationModule ], - exports: [NzModalComponent, NzModal2Component, NzModalFooterDirective, NzModalContainerComponent, NzModalConfirmContainerComponent], + exports: [NzModalComponent, NzModalFooterDirective], + providers: [NzModalService], declarations: [ NzModalComponent, NzModalFooterDirective, @@ -50,8 +47,7 @@ import { NzModalServiceModule } from './nz-modal.service.module'; NzModalTitleComponent, NzModalContainerComponent, NzModalConfirmContainerComponent, - NzModal2Component - ], - entryComponents: [NzModalComponent, NzModalContainerComponent] + NzModalComponent + ] }) export class NzModalModule {} diff --git a/components/modal/nz-modal.ts b/components/modal/modal.service.ts similarity index 70% rename from components/modal/nz-modal.ts rename to components/modal/modal.service.ts index f3f42fd25f0..67b8ad69b96 100644 --- a/components/modal/nz-modal.ts +++ b/components/modal/modal.service.ts @@ -9,28 +9,27 @@ import { ComponentType, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; import { ComponentPortal, PortalInjector, TemplatePortal } from '@angular/cdk/portal'; import { Injectable, Injector, OnDestroy, Optional, SkipSelf, TemplateRef } from '@angular/core'; -import { IndexableObject, warn } from 'ng-zorro-antd/core'; -import { NzModalConfirmContainerComponent } from 'ng-zorro-antd/modal/modal-confirm-container.component'; -import { applyConfigDefaults, setContentInstanceParams } from 'ng-zorro-antd/modal/utils'; import { defer, Observable, Subject } from 'rxjs'; import { startWith } from 'rxjs/operators'; +import { IndexableObject, warn } from 'ng-zorro-antd/core'; + +import { NzModalConfirmContainerComponent } from './modal-confirm-container.component'; +import { BaseModalContainer } from './modal-container'; import { NzModalContainerComponent } from './modal-container.component'; -import { NzModalRef2 } from './nz-modal-ref'; -import { NzModalServiceModule } from './nz-modal.service.module'; -import { ConfirmType, ModalConfig } from './nz-modal.type'; +import { NzModalRef } from './modal-ref'; +import { ConfirmType, ModalOptions } from './modal-types'; +import { applyConfigDefaults, setContentInstanceParams } from './utils'; const MODAL_MASK_CLASS_NAME = 'ant-modal-mask'; type ContentType = ComponentType | TemplateRef | string; -@Injectable({ - providedIn: NzModalServiceModule -}) -export class NzModal implements OnDestroy { - private openModalsAtThisLevel: NzModalRef2[] = []; +@Injectable() +export class NzModalService implements OnDestroy { + private openModalsAtThisLevel: NzModalRef[] = []; private readonly afterAllClosedAtThisLevel = new Subject(); - get openModals(): NzModalRef2[] { + get openModals(): NzModalRef[] { return this.parentModal ? this.parentModal.openModals : this.openModalsAtThisLevel; } @@ -43,31 +42,18 @@ export class NzModal implements OnDestroy { this.openModals.length ? this._afterAllClosed : this._afterAllClosed.pipe(startWith(undefined)) ) as Observable; - constructor(private overlay: Overlay, private injector: Injector, @Optional() @SkipSelf() private parentModal: NzModal) {} + constructor(private overlay: Overlay, private injector: Injector, @Optional() @SkipSelf() private parentModal: NzModalService) {} // tslint:disable-next-line:no-any - create(config: ModalConfig): NzModalRef2 { + create(config: ModalOptions): NzModalRef { return this.open(config.nzContent as ComponentType, config); } - open(componentOrTemplateRef: ContentType, config?: ModalConfig): NzModalRef2 { - const configMerged = applyConfigDefaults(config || {}, new ModalConfig()); - const overlayRef = this.createOverlay(configMerged); - const modalContainer = this.attachModalContainer(overlayRef, configMerged); - const modalRef = this.attachModalContent(componentOrTemplateRef, modalContainer, overlayRef, configMerged); - modalContainer.modalRef = modalRef; - - this.openModals.push(modalRef); - modalRef.afterClose.subscribe(() => this.removeOpenModal(modalRef)); - - return modalRef; - } - closeAll(): void { this.closeModals(this.openModals); } - confirm(options: ModalConfig = {}, confirmType: ConfirmType = 'confirm'): NzModalRef2 { + confirm(options: ModalOptions = {}, confirmType: ConfirmType = 'confirm'): NzModalRef { if ('nzFooter' in options) { warn(`The Confirm-Modal doesn't support "nzFooter", this property will be ignored.`); } @@ -83,30 +69,43 @@ export class NzModal implements OnDestroy { return this.create(options); } - info(options: ModalConfig = {}): NzModalRef2 { + info(options: ModalOptions = {}): NzModalRef { return this.confirmFactory(options, 'info'); } - success(options: ModalConfig = {}): NzModalRef2 { + success(options: ModalOptions = {}): NzModalRef { return this.confirmFactory(options, 'success'); } - error(options: ModalConfig = {}): NzModalRef2 { + error(options: ModalOptions = {}): NzModalRef { return this.confirmFactory(options, 'error'); } - warning(options: ModalConfig = {}): NzModalRef2 { + warning(options: ModalOptions = {}): NzModalRef { return this.confirmFactory(options, 'warning'); } - private removeOpenModal(modalRef: NzModalRef2): void { + private open(componentOrTemplateRef: ContentType, config?: ModalOptions): NzModalRef { + const configMerged = applyConfigDefaults(config || {}, new ModalOptions()); + const overlayRef = this.createOverlay(configMerged); + const modalContainer = this.attachModalContainer(overlayRef, configMerged); + const modalRef = this.attachModalContent(componentOrTemplateRef, modalContainer, overlayRef, configMerged); + modalContainer.modalRef = modalRef; + + this.openModals.push(modalRef); + modalRef.afterClose.subscribe(() => this.removeOpenModal(modalRef)); + + return modalRef; + } + + private removeOpenModal(modalRef: NzModalRef): void { const index = this.openModals.indexOf(modalRef); if (index > -1) { this.openModals.splice(index); } } - private closeModals(dialogs: NzModalRef2[]): void { + private closeModals(dialogs: NzModalRef[]): void { let i = dialogs.length; while (i--) { dialogs[i].close(); @@ -116,7 +115,7 @@ export class NzModal implements OnDestroy { } } - private createOverlay(config: ModalConfig): OverlayRef { + private createOverlay(config: ModalOptions): OverlayRef { const overlayConfig = new OverlayConfig({ hasBackdrop: true, scrollStrategy: this.overlay.scrollStrategies.block(), @@ -130,37 +129,37 @@ export class NzModal implements OnDestroy { return this.overlay.create(overlayConfig); } - private attachModalContainer(overlayRef: OverlayRef, config: ModalConfig): NzModalContainerComponent { + private attachModalContainer(overlayRef: OverlayRef, config: ModalOptions): BaseModalContainer { const injector = new PortalInjector( this.injector, // tslint:disable-next-line:no-any new WeakMap([ [OverlayRef, overlayRef], - [ModalConfig, config] + [ModalOptions, config] ]) ); - const containerPortal = new ComponentPortal( + console.log(config.nzModalType); + const ComponentClass = config.nzModalType === 'confirm' ? // If the mode is `confirm`, use `NzModalConfirmContainerComponent` NzModalConfirmContainerComponent : // If the mode is not `confirm`, use `NzModalContainerComponent` - NzModalContainerComponent, - undefined, - injector - ); - const containerRef = overlayRef.attach(containerPortal); + NzModalContainerComponent; + + const containerPortal = new ComponentPortal(ComponentClass, undefined, injector); + const containerRef = overlayRef.attach(containerPortal); return containerRef.instance; } private attachModalContent( componentOrTemplateRef: ContentType, - modalContainer: NzModalContainerComponent, + modalContainer: BaseModalContainer, overlayRef: OverlayRef, - config: ModalConfig - ): NzModalRef2 { - const modalRef = new NzModalRef2(overlayRef, config, modalContainer); + config: ModalOptions + ): NzModalRef { + const modalRef = new NzModalRef(overlayRef, config, modalContainer); if (componentOrTemplateRef instanceof TemplateRef) { modalContainer.attachTemplatePortal( @@ -168,26 +167,22 @@ export class NzModal implements OnDestroy { new TemplatePortal(componentOrTemplateRef, null!, { $implicit: modalRef } as any) ); } else if (typeof componentOrTemplateRef !== 'string') { - const injector = this.createInjector(modalRef, modalContainer); + const injector = this.createInjector(modalRef); const contentRef = modalContainer.attachComponentPortal(new ComponentPortal(componentOrTemplateRef, undefined, injector)); setContentInstanceParams(contentRef.instance, config.nzComponentParams); modalRef.componentInstance = contentRef.instance; } - modalRef.updateSize('520px').updatePosition(); return modalRef; } - private createInjector(modalRef: NzModalRef2, modalContainer: NzModalContainerComponent): PortalInjector { + private createInjector(modalRef: NzModalRef): PortalInjector { // tslint:disable-next-line:no-any - const injectionTokens = new WeakMap([ - [NzModalContainerComponent, modalContainer], - [NzModalRef2, modalRef] - ]); + const injectionTokens = new WeakMap([[NzModalRef, modalRef]]); return new PortalInjector(this.injector, injectionTokens); } - private confirmFactory(options: ModalConfig = {}, confirmType: ConfirmType): NzModalRef2 { + private confirmFactory(options: ModalOptions = {}, confirmType: ConfirmType): NzModalRef { const iconMap: IndexableObject = { info: 'info-circle', success: 'check-circle', diff --git a/components/modal/modal.spec.ts b/components/modal/modal.spec.ts new file mode 100644 index 00000000000..7fc4dcb2701 --- /dev/null +++ b/components/modal/modal.spec.ts @@ -0,0 +1 @@ +describe('NzModal', () => {}); diff --git a/components/modal/nz-modal-control.service.module.ts b/components/modal/nz-modal-control.service.module.ts deleted file mode 100644 index 407ff91cc52..00000000000 --- a/components/modal/nz-modal-control.service.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE - */ - -import { NgModule } from '@angular/core'; - -@NgModule() -export class NzModalControlServiceModule {} diff --git a/components/modal/nz-modal-control.service.ts b/components/modal/nz-modal-control.service.ts deleted file mode 100644 index 2a99e364910..00000000000 --- a/components/modal/nz-modal-control.service.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE - */ - -import { Injectable, Optional, SkipSelf } from '@angular/core'; -import { Subject, Subscription } from 'rxjs'; - -import { NzModalControlServiceModule } from './nz-modal-control.service.module'; -import { NzModalRef } from './nz-modal-ref.class'; - -interface RegisteredMeta { - modalRef: NzModalRef; - afterOpenSubscription: Subscription; - afterCloseSubscription: Subscription; -} - -@Injectable({ - providedIn: NzModalControlServiceModule -}) -export class NzModalControlService { - // Track singleton afterAllClose through over the injection tree - get afterAllClose(): Subject { - return this.parentService ? this.parentService.afterAllClose : this.rootAfterAllClose!; - } - - // Track singleton openModals array through over the injection tree - get openModals(): NzModalRef[] { - return this.parentService ? this.parentService.openModals : this.rootOpenModals!; - } - - private rootOpenModals: NzModalRef[] | null = this.parentService ? null : []; - private rootAfterAllClose: Subject | null = this.parentService ? null : new Subject(); - private rootRegisteredMetaMap: Map | null = this.parentService ? null : new Map(); - - private get registeredMetaMap(): Map { - // Registered modal for later usage - return this.parentService ? this.parentService.registeredMetaMap : this.rootRegisteredMetaMap!; - } - - constructor(@Optional() @SkipSelf() private parentService: NzModalControlService) {} - - // Register a modal to listen its open/close - registerModal(modalRef: NzModalRef): void { - if (!this.hasRegistered(modalRef)) { - const afterOpenSubscription = modalRef.afterOpen.subscribe(() => this.openModals.push(modalRef)); - const afterCloseSubscription = modalRef.afterClose.subscribe(() => this.removeOpenModal(modalRef)); - - this.registeredMetaMap.set(modalRef, { modalRef, afterOpenSubscription, afterCloseSubscription }); - } - } - - // deregister modals - deregisterModal(modalRef: NzModalRef): void { - const registeredMeta = this.registeredMetaMap.get(modalRef); - if (registeredMeta) { - // Remove this modal if it is still in the opened modal list (NOTE: it may trigger "afterAllClose") - this.removeOpenModal(registeredMeta.modalRef); - registeredMeta.afterOpenSubscription.unsubscribe(); - registeredMeta.afterCloseSubscription.unsubscribe(); - this.registeredMetaMap.delete(modalRef); - } - } - - hasRegistered(modalRef: NzModalRef): boolean { - return this.registeredMetaMap.has(modalRef); - } - - // Close all registered opened modals - closeAll(): void { - let i = this.openModals.length; - - while (i--) { - this.openModals[i].close(); - } - } - - private removeOpenModal(modalRef: NzModalRef): void { - const index = this.openModals.indexOf(modalRef); - - if (index > -1) { - this.openModals.splice(index, 1); - - if (!this.openModals.length) { - this.afterAllClose.next(); - } - } - } -} diff --git a/components/modal/nz-modal-ref.class.ts b/components/modal/nz-modal-ref.class.ts deleted file mode 100644 index 99b9f65c29d..00000000000 --- a/components/modal/nz-modal-ref.class.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE - */ - -import { Observable } from 'rxjs'; - -import { NzModalComponent } from './nz-modal.component'; - -/** - * API class that public to users to handle the modal instance. - * NzModalRef is aim to avoid accessing to the modal instance directly by users. - */ -// tslint:disable-next-line:no-any -export abstract class NzModalRef { - abstract afterOpen: Observable; - abstract afterClose: Observable; - - abstract open(): void; - abstract close(result?: R): void; - abstract destroy(result?: R): void; - - /** - * Trigger the nzOnOk/nzOnCancel by manual - */ - abstract triggerOk(): void; - abstract triggerCancel(): void; - - // /** - // * Return the ComponentRef of nzContent when specify nzContent as a Component - // * Note: this method may return undefined if the Component has not ready yet. (it only available after Modal's ngOnInit) - // */ - // abstract getContentComponentRef(): ComponentRef<{}>; - - /** - * Return the component instance of nzContent when specify nzContent as a Component - * Note: this method may return undefined if the Component has not ready yet. (it only available after Modal's ngOnInit) - */ - abstract getContentComponent(): T; - - /** - * Get the dom element of this Modal - */ - abstract getElement(): HTMLElement; - - /** - * Get the instance of the Modal itself - */ - abstract getInstance(): NzModalComponent; -} diff --git a/components/modal/nz-modal.component.html b/components/modal/nz-modal.component.html deleted file mode 100644 index 08d419da643..00000000000 --- a/components/modal/nz-modal.component.html +++ /dev/null @@ -1,198 +0,0 @@ - - - -
-
- -
- - - -
-
- - -
-
-
-
-
- - - -
- -
-
-
- -
- - - - -
-
-
- - - - - - - -
- - - -
- -
-
-
-
-
- - -
-
- -
-
- diff --git a/components/modal/nz-modal.component.ts b/components/modal/nz-modal.component.ts deleted file mode 100644 index 79bfa19713a..00000000000 --- a/components/modal/nz-modal.component.ts +++ /dev/null @@ -1,575 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE - */ - -import { FocusTrap, FocusTrapFactory } from '@angular/cdk/a11y'; - -import { ESCAPE } from '@angular/cdk/keycodes'; -import { BlockScrollStrategy, Overlay, OverlayKeyboardDispatcher, OverlayRef } from '@angular/cdk/overlay'; -import { DOCUMENT } from '@angular/common'; -import { - AfterViewInit, - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ComponentFactoryResolver, - ComponentRef, - ContentChild, - ElementRef, - EventEmitter, - Inject, - Injector, - Input, - OnChanges, - OnDestroy, - OnInit, - Optional, - Output, - SimpleChanges, - TemplateRef, - Type, - ViewChild, - ViewContainerRef -} from '@angular/core'; - -import { fromEvent, Observable, Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; - -import { getElementOffset, InputBoolean, isPromise, NzConfigService, warnDeprecation, WithConfig } from 'ng-zorro-antd/core'; -import { NzI18nService } from 'ng-zorro-antd/i18n'; - -import { NZ_MODAL_CONFIG, NzModalConfig } from './nz-modal-config'; -import { NzModalControlService } from './nz-modal-control.service'; -import { NzModalFooterDirective } from './nz-modal-footer.directive'; -import { NzModalRef } from './nz-modal-ref.class'; -import { ModalButtonOptions, ModalOptions, ModalType, OnClickCallback } from './nz-modal.type'; - -export const MODAL_ANIMATE_DURATION = 200; // Duration when perform animations (ms) -export const WRAP_CLASS_NAME = 'ant-modal-wrap'; - -type AnimationState = 'enter' | 'leave' | null; - -const NZ_CONFIG_COMPONENT_NAME = 'modal'; - -@Component({ - selector: 'nz-modal', - exportAs: 'nzModal', - templateUrl: './nz-modal.component.html', - // Using OnPush for modal caused footer can not to detect changes. we can fix it when 8.x. - changeDetection: ChangeDetectionStrategy.Default -}) - -// tslint:disable-next-line:no-any -export class NzModalComponent extends NzModalRef - implements OnInit, OnChanges, AfterViewInit, OnDestroy, ModalOptions { - @Input() @InputBoolean() nzVisible: boolean = false; - @Input() @InputBoolean() nzClosable: boolean = true; - @Input() @InputBoolean() nzOkLoading: boolean = false; - @Input() @InputBoolean() nzOkDisabled: boolean = false; - @Input() @InputBoolean() nzCancelDisabled: boolean = false; - @Input() @InputBoolean() nzCancelLoading: boolean = false; - @Input() @InputBoolean() nzKeyboard: boolean = true; - @Input() @InputBoolean() nzNoAnimation = false; - - // TODO(hsuanxyz): add default value once old API is deprecated. - @Input() @WithConfig(NZ_CONFIG_COMPONENT_NAME) @InputBoolean() nzMask: boolean; - @Input() @WithConfig(NZ_CONFIG_COMPONENT_NAME) @InputBoolean() nzMaskClosable: boolean; - - @Input() nzContent: string | TemplateRef<{}> | Type; // [STATIC] If not specified, will use - @Input() nzComponentParams: T; // [STATIC] ONLY avaliable when nzContent is a component - @Input() nzFooter: string | TemplateRef<{}> | Array> | null; // [STATIC] Default Modal ONLY - @Input() nzGetContainer: HTMLElement | OverlayRef | (() => HTMLElement | OverlayRef) = () => this.overlay.create(); // [STATIC] - @Input() nzZIndex: number = 1000; - @Input() nzWidth: number | string = 520; - @Input() nzWrapClassName: string; - @Input() nzClassName: string; - @Input() nzStyle: object; - @Input() nzTitle: string | TemplateRef<{}>; - @Input() nzCloseIcon: string | TemplateRef = 'close'; - @Input() nzMaskStyle: object; - @Input() nzBodyStyle: object; - @Input() nzOkText: string | null; - @Input() nzCancelText: string | null; - @Input() nzOkType: string = 'primary'; - @Input() nzIconType: string = 'question-circle'; // Confirm Modal ONLY - @Input() nzModalType: ModalType = 'default'; - - @Input() @Output() readonly nzOnOk: EventEmitter | OnClickCallback = new EventEmitter(); - @Input() @Output() readonly nzOnCancel: EventEmitter | OnClickCallback = new EventEmitter(); - - @Output() readonly nzAfterOpen = new EventEmitter(); // Trigger when modal open(visible) after animations - @Output() readonly nzAfterClose = new EventEmitter(); // Trigger when modal leave-animation over - @Output() readonly nzVisibleChange = new EventEmitter(); - - @ViewChild('modalContainer', { static: true }) modalContainer: ElementRef; - @ViewChild('bodyContainer', { static: false, read: ViewContainerRef }) bodyContainer: ViewContainerRef; - @ViewChild('autoFocusButtonOk', { static: false, read: ElementRef }) autoFocusButtonOk: ElementRef; // Only aim to focus the ok button that needs to be auto focused - - @ContentChild(NzModalFooterDirective, { static: false }) - set modalFooter(value: NzModalFooterDirective) { - if (value && value.templateRef) { - this.setFooterWithTemplate(value.templateRef); - } - } - - get afterOpen(): Observable { - // Observable alias for nzAfterOpen - return this.nzAfterOpen.asObservable(); - } - - get afterClose(): Observable { - // Observable alias for nzAfterClose - return this.nzAfterClose.asObservable(); - } - - get cancelText(): string { - return this.nzCancelText || this.locale.cancelText!; - } - - get okText(): string { - return this.nzOkText || this.locale.okText!; - } - - get hidden(): boolean { - return !this.nzVisible && !this.animationState; - } // Indicate whether this dialog should hidden - - /** - * @description - * The calculated highest weight of mask value - * - * Weight of different mask input: - * component default value < global configuration < component input value - */ - get mask(): boolean { - if (this.nzMask != null) { - return this.nzMask; - } else if (this.nzModalGlobalConfig && this.nzModalGlobalConfig.nzMask != null) { - return this.nzModalGlobalConfig.nzMask; - } else { - return true; - } - } - - /** - * @description - * The calculated highest weight of maskClosable value - * - * Weight of different maskClosable input: - * component default value < global configuration < component input value - */ - get maskClosable(): boolean { - if (this.nzMaskClosable != null) { - return this.nzMaskClosable; - } else if (this.nzModalGlobalConfig && this.nzModalGlobalConfig.nzMaskClosable != null) { - return this.nzModalGlobalConfig.nzMaskClosable; - } else { - return true; - } - } - - locale: { okText?: string; cancelText?: string } = {}; - maskAnimationClassMap: object | null; - modalAnimationClassMap: object | null; - transformOrigin = '0px 0px 0px'; // The origin point that animation based on - - private contentComponentRef: ComponentRef; // Handle the reference when using nzContent as Component - private animationState: AnimationState; // Current animation state - private container: HTMLElement | OverlayRef; - private unsubscribe$ = new Subject(); - private previouslyFocusedElement: HTMLElement; - private focusTrap: FocusTrap; - private scrollStrategy: BlockScrollStrategy; - private overlayRef: OverlayRef; - private dialogMouseDown = false; - private timeoutId: number; - - [key: string]: any; // tslint:disable-line:no-any - - constructor( - public nzConfigService: NzConfigService, - private overlay: Overlay, - private overlayKeyboardDispatcher: OverlayKeyboardDispatcher, - private i18n: NzI18nService, - private cfr: ComponentFactoryResolver, - private elementRef: ElementRef, - private viewContainer: ViewContainerRef, - private modalControl: NzModalControlService, - private focusTrapFactory: FocusTrapFactory, - private cdr: ChangeDetectorRef, - @Optional() @Inject(NZ_MODAL_CONFIG) private nzModalGlobalConfig: NzModalConfig, - @Inject(DOCUMENT) private document: any // tslint:disable-line:no-any - ) { - super(); - this.scrollStrategy = this.overlay.scrollStrategies.block(); - - if (this.nzModalGlobalConfig) { - warnDeprecation('`NZ_MODAL_CONFIG` has been deprecated and will be removed in 9.0.0. Please use global config instead.'); - } - } - - ngOnInit(): void { - this.i18n.localeChange.pipe(takeUntil(this.unsubscribe$)).subscribe(() => { - this.locale = this.i18n.getLocaleData('Modal'); - }); - - if (this.isComponent(this.nzContent)) { - this.createDynamicComponent(this.nzContent as Type); // Create component along without View - } - - if (this.isModalButtons(this.nzFooter)) { - // Setup default button options - this.nzFooter = this.formatModalButtons(this.nzFooter as Array>); - } - - // Place the modal dom to elsewhere - this.container = typeof this.nzGetContainer === 'function' ? this.nzGetContainer() : this.nzGetContainer; - if (this.container instanceof HTMLElement) { - this.container.appendChild(this.elementRef.nativeElement); - fromEvent(this.document.body, 'keydown') - .pipe(takeUntil(this.unsubscribe$)) - .subscribe(e => this.keydownListener(e)); - } else if (this.container instanceof OverlayRef) { - // NOTE: only attach the dom to overlay, the view container is not changed actually - this.setOverlayRef(this.container); - this.container.overlayElement.appendChild(this.elementRef.nativeElement); - } - - if (this.overlayRef) { - this.overlayRef - .keydownEvents() - .pipe(takeUntil(this.unsubscribe$)) - .subscribe(e => this.keydownListener(e)); - } - - // Register modal when afterOpen/afterClose is stable - this.modalControl.registerModal(this); - } - - // [NOTE] NOT available when using by service! - // Because ngOnChanges never be called when using by service, - // here we can't support "nzContent"(Component) etc. as inputs that initialized dynamically. - // BUT: User also can change "nzContent" dynamically to trigger UI changes (provided you don't use Component that needs initializations) - ngOnChanges(changes: SimpleChanges): void { - if (changes.nzVisible) { - this.handleVisibleStateChange(this.nzVisible, !changes.nzVisible.firstChange); // Do not trigger animation while initializing - } - } - - ngAfterViewInit(): void { - // If using Component, it is the time to attach View while bodyContainer is ready - if (this.contentComponentRef) { - this.bodyContainer.insert(this.contentComponentRef.hostView); - } - - if (this.autoFocusButtonOk) { - (this.autoFocusButtonOk.nativeElement as HTMLButtonElement).focus(); - } - } - - ngOnDestroy(): void { - // Close self before destructing - this.changeVisibleFromInside(false).then(() => { - this.modalControl.deregisterModal(this); - - if (this.container instanceof OverlayRef) { - this.container.dispose(); - } - - this.unsubscribe$.next(); - this.unsubscribe$.complete(); - }); - clearTimeout(this.timeoutId); - } - - setFooterWithTemplate(templateRef: TemplateRef<{}>): void { - this.nzFooter = templateRef; - this.cdr.markForCheck(); - } - - setOverlayRef(overlayRef: OverlayRef): void { - this.overlayRef = overlayRef; - } - - keydownListener(event: KeyboardEvent): void { - if (event.keyCode === ESCAPE && this.nzKeyboard) { - this.onClickOkCancel('cancel'); - } - } - - open(): void { - this.changeVisibleFromInside(true); - } - - close(result?: R): void { - this.changeVisibleFromInside(false, result); - } - - destroy(result?: R): void { - // Destroy equals Close - this.close(result); - } - - triggerOk(): void { - this.onClickOkCancel('ok'); - } - - triggerCancel(): void { - this.onClickOkCancel('cancel'); - } - - getInstance(): NzModalComponent { - return this; - } - - getContentComponentRef(): ComponentRef { - return this.contentComponentRef; - } - - getContentComponent(): T { - return this.contentComponentRef && this.contentComponentRef.instance; - } - - getElement(): HTMLElement { - return this.elementRef && this.elementRef.nativeElement; - } - - onMaskDialogDown(): void { - this.dialogMouseDown = true; - } - - onDialogUp(): void { - if (this.dialogMouseDown) { - this.timeoutId = setTimeout(() => { - this.dialogMouseDown = false; - }, 0); - } - } - - onClickMask($event: MouseEvent): void { - if ( - this.mask && - this.maskClosable && - ($event.target as HTMLElement).classList.contains(WRAP_CLASS_NAME) && - this.nzVisible && - !this.dialogMouseDown - ) { - this.onClickOkCancel('cancel'); - } - } - - isModalType(type: ModalType): boolean { - return this.nzModalType === type; - } - - public onClickCloseBtn(): void { - if (this.nzVisible) { - this.onClickOkCancel('cancel'); - } - } - - public onClickOkCancel(type: 'ok' | 'cancel'): void { - const trigger = { ok: this.nzOnOk, cancel: this.nzOnCancel }[type]; - const loadingKey = { ok: 'nzOkLoading', cancel: 'nzCancelLoading' }[type]; - if (trigger instanceof EventEmitter) { - trigger.emit(this.getContentComponent()); - } else if (typeof trigger === 'function') { - const result = trigger(this.getContentComponent()); - const caseClose = (doClose: boolean | void | {}) => doClose !== false && this.close(doClose as R); // Users can return "false" to prevent closing by default - if (isPromise(result)) { - this[loadingKey] = true; - const handleThen = (doClose: boolean | void | {}) => { - this[loadingKey] = false; - caseClose(doClose); - }; - result.then(handleThen).catch(handleThen); - } else { - caseClose(result); - } - } - } - - public isNonEmptyString(value: {}): boolean { - return typeof value === 'string' && value !== ''; - } - - public isTemplateRef(value: {}): boolean { - return value instanceof TemplateRef; - } - - public isComponent(value: {}): boolean { - return value instanceof Type; - } - - public isModalButtons(value: string | TemplateRef<{}> | Array> | null): boolean { - return Array.isArray(value) && value.length > 0; - } - - // Do rest things when visible state changed - private handleVisibleStateChange(visible: boolean, animation: boolean = true, closeResult?: R): Promise { - if (visible) { - // Hide scrollbar at the first time when shown up - this.scrollStrategy.enable(); - this.savePreviouslyFocusedElement(); - this.trapFocus(); - if (this.container instanceof OverlayRef) { - this.overlayKeyboardDispatcher.add(this.overlayRef); - } - } else { - if (this.container instanceof OverlayRef) { - this.overlayKeyboardDispatcher.remove(this.overlayRef); - } - } - - return Promise.resolve(animation ? this.animateTo(visible) : undefined).then(() => { - // Emit open/close event after animations over - if (visible) { - this.nzAfterOpen.emit(); - } else { - this.nzAfterClose.emit(closeResult); - this.restoreFocus(); - this.scrollStrategy.disable(); - // Mark the for check so it can react if the view container is using OnPush change detection. - this.cdr.markForCheck(); - } - }); - } - - // Lookup a button's property, if the prop is a function, call & then return the result, otherwise, return itself. - public getButtonCallableProp(options: ModalButtonOptions, prop: string): {} { - const value = options[prop]; - const args: T[] = []; - if (this.contentComponentRef) { - args.push(this.contentComponentRef.instance); - } - return typeof value === 'function' ? value.apply(options, args) : value; - } - - // On nzFooter's modal button click - public onButtonClick(button: ModalButtonOptions): void { - const result = this.getButtonCallableProp(button, 'onClick'); // Call onClick directly - if (isPromise(result)) { - button.loading = true; - result.then(() => (button.loading = false)).catch(() => (button.loading = false)); - } - } - - // Change nzVisible from inside - private changeVisibleFromInside(visible: boolean, closeResult?: R): Promise { - if (this.nzVisible !== visible) { - // Change nzVisible value immediately - this.nzVisible = visible; - this.nzVisibleChange.emit(visible); - return this.handleVisibleStateChange(visible, true, closeResult); - } - return Promise.resolve(); - } - - private changeAnimationState(state: AnimationState): void { - this.animationState = state; - if (state) { - this.maskAnimationClassMap = { - [`fade-${state}`]: true, - [`fade-${state}-active`]: true - }; - this.modalAnimationClassMap = { - [`zoom-${state}`]: true, - [`zoom-${state}-active`]: true - }; - } else { - this.maskAnimationClassMap = this.modalAnimationClassMap = null; - } - } - - private animateTo(isVisible: boolean): Promise { - if (isVisible) { - // Figure out the lastest click position when shows up - setTimeout(() => this.updateTransformOrigin()); // [NOTE] Using timeout due to the document.click event is fired later than visible change, so if not postponed to next event-loop, we can't get the lastest click position - } - - this.changeAnimationState(isVisible ? 'enter' : 'leave'); - return new Promise(resolve => - setTimeout( - () => { - // Return when animation is over - this.changeAnimationState(null); - resolve(); - }, - this.nzNoAnimation ? 0 : MODAL_ANIMATE_DURATION - ) - ); - } - - private formatModalButtons(buttons: Array>): Array> { - return buttons.map(button => { - return { - ...{ - type: 'default', - size: 'default', - autoLoading: true, - show: true, - loading: false, - disabled: false - }, - ...button - }; - }); - } - - /** - * Create a component dynamically but not attach to any View (this action will be executed when bodyContainer is ready) - * @param component Component class - */ - private createDynamicComponent(component: Type): void { - const factory = this.cfr.resolveComponentFactory(component); - const childInjector = Injector.create({ - providers: [{ provide: NzModalRef, useValue: this }], - parent: this.viewContainer.parentInjector - }); - this.contentComponentRef = factory.create(childInjector); - if (this.nzComponentParams) { - Object.assign(this.contentComponentRef.instance, this.nzComponentParams); - } - // Do the first change detection immediately (or we do detection at ngAfterViewInit, multi-changes error will be thrown) - this.contentComponentRef.changeDetectorRef.detectChanges(); - } - - // Update transform-origin to the last click position on document - private updateTransformOrigin(): void { - const modalElement = this.modalContainer.nativeElement as HTMLElement; - if (this.previouslyFocusedElement) { - const previouslyDOMRect = this.previouslyFocusedElement.getBoundingClientRect(); - const lastPosition = getElementOffset(this.previouslyFocusedElement); - const x = lastPosition.left + previouslyDOMRect.width / 2; - const y = lastPosition.top + previouslyDOMRect.height / 2; - this.transformOrigin = `${x - modalElement.offsetLeft}px ${y - modalElement.offsetTop}px 0px`; - } - } - - private savePreviouslyFocusedElement(): void { - if (this.document) { - this.previouslyFocusedElement = this.document.activeElement as HTMLElement; - } - } - - private trapFocus(): void { - if (!this.focusTrap) { - this.focusTrap = this.focusTrapFactory.create(this.elementRef.nativeElement); - } - this.focusTrap.focusInitialElementWhenReady(); - } - - private restoreFocus(): void { - // We need the extra check, because IE can set the `activeElement` to null in some cases. - if (this.previouslyFocusedElement && typeof this.previouslyFocusedElement.focus === 'function') { - this.previouslyFocusedElement.focus(); - } - if (this.focusTrap) { - this.focusTrap.destroy(); - } - } -} diff --git a/components/modal/nz-modal.service.module.ts b/components/modal/nz-modal.service.module.ts deleted file mode 100644 index 1fae59bbb8c..00000000000 --- a/components/modal/nz-modal.service.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE - */ - -import { NgModule } from '@angular/core'; - -@NgModule() -export class NzModalServiceModule {} diff --git a/components/modal/nz-modal.service.ts b/components/modal/nz-modal.service.ts deleted file mode 100644 index 92014e3c688..00000000000 --- a/components/modal/nz-modal.service.ts +++ /dev/null @@ -1,148 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE - */ - -import { Overlay, OverlayRef } from '@angular/cdk/overlay'; -import { ComponentPortal } from '@angular/cdk/portal'; -import { ComponentRef, Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; - -import { IndexableObject, warn } from 'ng-zorro-antd/core'; - -import { NzModalControlService } from './nz-modal-control.service'; -import { NzModalRef } from './nz-modal-ref.class'; -import { NzModalComponent } from './nz-modal.component'; -import { NzModalServiceModule } from './nz-modal.service.module'; -import { ConfirmType, ModalOptions, ModalOptionsForService } from './nz-modal.type'; - -// A builder used for managing service creating modals -export class ModalBuilderForService { - private modalRef: ComponentRef | null; // Modal ComponentRef, "null" means it has been destroyed - private overlayRef: OverlayRef; - - constructor(private overlay: Overlay, options: ModalOptionsForService = {}) { - this.createModal(); - - if (!('nzGetContainer' in options)) { - // As we use CDK to create modal in service by force, there is no need to use nzGetContainer - options.nzGetContainer = undefined; // Override nzGetContainer's default value to prevent creating another overlay - } - - this.changeProps(options); - this.modalRef!.instance.setOverlayRef(this.overlayRef); - this.modalRef!.instance.open(); - this.modalRef!.instance.nzAfterClose.subscribe(() => this.destroyModal()); // [NOTE] By default, close equals destroy when using as Service - } - - getInstance(): NzModalComponent | null { - return this.modalRef && this.modalRef.instance; - } - - destroyModal(): void { - if (this.modalRef) { - this.overlayRef.dispose(); - this.modalRef = null; - } - } - - private changeProps(options: ModalOptions): void { - if (this.modalRef) { - Object.assign(this.modalRef.instance, options); // DANGER: here not limit user's inputs at runtime - } - } - - // Create component to ApplicationRef - private createModal(): void { - this.overlayRef = this.overlay.create(); - this.modalRef = this.overlayRef.attach(new ComponentPortal(NzModalComponent)); - } -} - -@Injectable({ - providedIn: NzModalServiceModule -}) -export class NzModalService { - // Track of the current close modals (we assume invisible is close this time) - get openModals(): NzModalRef[] { - return this.modalControl.openModals; - } - - get afterAllClose(): Observable { - return this.modalControl.afterAllClose.asObservable(); - } - - constructor(private overlay: Overlay, private modalControl: NzModalControlService) {} - - // Closes all of the currently-open dialogs - closeAll(): void { - this.modalControl.closeAll(); - } - - create(options: ModalOptionsForService = {}): NzModalRef { - if (typeof options.nzOnCancel !== 'function') { - options.nzOnCancel = () => {}; // Leave a empty function to close this modal by default - } - - // NOTE: use NzModalComponent as the NzModalRef by now, we may need archive the real NzModalRef object in the future - const modalRef = new ModalBuilderForService(this.overlay, options).getInstance()!; - - return modalRef; - } - - confirm(options: ModalOptionsForService = {}, confirmType: ConfirmType = 'confirm'): NzModalRef { - if ('nzFooter' in options) { - warn(`The Confirm-Modal doesn't support "nzFooter", this property will be ignored.`); - } - if (!('nzWidth' in options)) { - options.nzWidth = 416; - } - if (!('nzMaskClosable' in options)) { - options.nzMaskClosable = false; - } - if (typeof options.nzOnOk !== 'function') { - // NOTE: only support function currently by calling confirm() - options.nzOnOk = () => {}; // Leave a empty function to close this modal by default - } - - options.nzModalType = 'confirm'; - options.nzClassName = `ant-modal-confirm ant-modal-confirm-${confirmType} ${options.nzClassName || ''}`; - return this.create(options); - } - - info(options: ModalOptionsForService = {}): NzModalRef { - return this.simpleConfirm(options, 'info'); - } - - success(options: ModalOptionsForService = {}): NzModalRef { - return this.simpleConfirm(options, 'success'); - } - - error(options: ModalOptionsForService = {}): NzModalRef { - return this.simpleConfirm(options, 'error'); - } - - warning(options: ModalOptionsForService = {}): NzModalRef { - return this.simpleConfirm(options, 'warning'); - } - - private simpleConfirm(options: ModalOptionsForService = {}, confirmType: ConfirmType): NzModalRef { - const iconMap: IndexableObject = { - info: 'info-circle', - success: 'check-circle', - error: 'close-circle', - warning: 'exclamation-circle' - }; - if (!('nzIconType' in options)) { - options.nzIconType = iconMap[confirmType]; - } - if (!('nzCancelText' in options)) { - // Remove the Cancel button if the user not specify a Cancel button - options.nzCancelText = null; - } - return this.confirm(options, confirmType); - } -} diff --git a/components/modal/nz-modal.spec.ts b/components/modal/nz-modal.spec.ts deleted file mode 100644 index 99342e54954..00000000000 --- a/components/modal/nz-modal.spec.ts +++ /dev/null @@ -1,1041 +0,0 @@ -/* TODO: Sort out and rewrite for more standardized */ - -import { ESCAPE } from '@angular/cdk/keycodes'; -import { OverlayContainer } from '@angular/cdk/overlay'; -import { Component, ElementRef, EventEmitter, Input } from '@angular/core'; -import { async, ComponentFixture, fakeAsync, flush, inject, TestBed, tick } from '@angular/core/testing'; -import { By } from '@angular/platform-browser'; -import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing'; -import { NoopAnimationsModule } from '@angular/platform-browser/animations'; - -import { NzButtonComponent, NzButtonModule } from 'ng-zorro-antd/button'; -import { dispatchFakeEvent, dispatchKeyboardEvent } from 'ng-zorro-antd/core'; -import { NzToCssUnitPipe } from 'ng-zorro-antd/core/pipe/nz-css-unit.pipe'; -import { NzI18nService } from 'ng-zorro-antd/i18n'; -import { NzIconTestModule } from 'ng-zorro-antd/icon/testing'; -import en_US from '../i18n/languages/en_US'; - -import { NZ_MODAL_CONFIG } from './nz-modal-config'; -import { NzModalControlService } from './nz-modal-control.service'; -import { NzModalRef } from './nz-modal-ref.class'; -import { NzModalComponent } from './nz-modal.component'; -import { NzModalModule } from './nz-modal.module'; -import { NzModalService } from './nz-modal.service'; - -let counter = 0; -describe('modal testing (legacy)', () => { - describe('demo-async', () => { - let fixture: ComponentFixture; - let modalElement: HTMLElement; - let buttonShow: HTMLButtonElement; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [NoopAnimationsModule, NzButtonModule, NzModalModule], - declarations: [NzDemoModalAsyncComponent] - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(NzDemoModalAsyncComponent); - modalElement = fixture.debugElement.query(By.directive(NzModalComponent)).nativeElement; - buttonShow = fixture.debugElement.query(By.directive(NzButtonComponent)).nativeElement; - }); - - it('should show and hide after 3000ms with loading', fakeAsync(() => { - buttonShow.click(); - fixture.detectChanges(); - flush(); - expectModalHidden(modalElement, false); - - const buttonOk = getButtonOk(modalElement); - buttonOk.click(); // Click Ok button - fixture.detectChanges(); - expect(isButtonLoading(buttonOk)).not.toBeFalsy(); - - tick(3000 + 10); - fixture.detectChanges(); - flush(); - fixture.detectChanges(); // In order to trigger ModalInstance's UI updating after finished hiding - expectModalHidden(modalElement, true); - })); - }); // /async - - describe('demo-confirm-promise', () => { - const tempModalId = generateUniqueId(); // Temp unique id to mark the confirm modal that created by service - let fixture: ComponentFixture; - let instance: NzDemoModalConfirmPromiseComponent; - let modalAgent: NzModalRef; - let buttonShow: HTMLButtonElement; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [NoopAnimationsModule, NzButtonModule, NzModalModule], - declarations: [NzDemoModalConfirmPromiseComponent] - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(NzDemoModalConfirmPromiseComponent); - instance = fixture.debugElement.componentInstance; - buttonShow = fixture.debugElement.query(By.directive(NzButtonComponent)).nativeElement; - - buttonShow.click(); - fixture.detectChanges(); - modalAgent = instance.confirmModal; - modalAgent.getElement().classList.add(tempModalId); - }); - - it('should open and click Ok to destroy after 1000ms', fakeAsync(() => { - expectModalDestroyed(tempModalId, false); - - getButtonOk(modalAgent.getElement()).click(); // Click Ok button - fixture.detectChanges(); - tick(1000 + 10); - fixture.detectChanges(); - flush(); - fixture.detectChanges(); - expectModalDestroyed(tempModalId, true); - })); - - it('should open and destroy immediately when click Cancel', fakeAsync(() => { - expectModalDestroyed(tempModalId, false); - - getButtonClose(modalAgent.getElement()).click(); // Click Close button - fixture.detectChanges(); - flush(); - fixture.detectChanges(); - expectModalDestroyed(tempModalId, true); - })); - }); // /confirm-promise - - describe('NormalModal: created by service with most APIs', () => { - let tempModalId: string; // Temp unique id to mark the confirm modal that created by service - let fixture: ComponentFixture; - let instance: TestBasicServiceComponent; - let modalAgent: NzModalRef; - let modalElement: HTMLElement; - let modalInstance: NzModalComponent; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [NoopAnimationsModule, NzModalModule, NzIconTestModule], - declarations: [TestBasicServiceComponent] - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(TestBasicServiceComponent); - instance = fixture.debugElement.componentInstance; - modalAgent = instance.basicModal; - modalElement = modalAgent.getElement(); - tempModalId = generateUniqueId(); - modalElement.classList.add(tempModalId); // Mark with id - modalInstance = modalAgent.getInstance(); - }); - - it('should correctly render all basic props', fakeAsync(() => { - const spy = spyOn(console, 'log'); - - // [Hack] Codes that can't be covered by normal operations - // @ts-ignore - expect(modalInstance.changeVisibleFromInside(true) instanceof Promise).toBe(true); - - expect((modalElement.querySelector('.ant-modal-wrap') as HTMLElement).style.zIndex).toBe('1888'); - expect((modalElement.querySelector('.ant-modal-wrap') as HTMLElement).classList.contains('test-wrap-class-name')).toBe(true); - expect((modalElement.querySelector('.ant-modal') as HTMLElement).style.width).toBe('250px'); - expect((modalElement.querySelector('.ant-modal') as HTMLElement).classList.contains('test-class-name')).toBe(true); - expect((modalElement.querySelector('.ant-modal') as HTMLElement).style.top).toBe('20pt'); - expect((modalElement.querySelector('.ant-modal-title') as HTMLElement).innerHTML.indexOf('TEST BOLD TITLE')).toBeGreaterThan( - -1 - ); - // expect((modalElement.querySelector('.ant-modal-footer') as HTMLElement).innerHTML.indexOf('
custom html footer: OK
')).toBeGreaterThan(-1); - expect((modalElement.querySelector('.ant-modal-body') as HTMLElement).innerHTML.indexOf('

test html content

')).toBeGreaterThan( - -1 - ); - expect((modalElement.querySelector('.ant-modal-body') as HTMLElement).style.background).toBe('gray'); - expect(getButtonOk(modalElement).innerHTML.indexOf('custom ok')).toBeGreaterThan(-1); - expect(getButtonOk(modalElement).classList.contains('ant-btn-primary')).toBe(true); - expect(isButtonLoading(getButtonOk(modalElement))).toBeFalsy(); - expect(getButtonCancel(modalElement).innerHTML.indexOf('custom cancel')).toBeGreaterThan(-1); - expect(isButtonLoading(getButtonCancel(modalElement))).not.toBeFalsy(); - expect(modalElement.querySelector('.ant-modal-close')).toBeFalsy(); - expect(modalElement.querySelector('.ant-modal-mask')).toBeFalsy(); - expect(getButtonOk(modalElement).disabled).toBeFalsy(); - expect(getButtonCancel(modalElement).disabled).toBeFalsy(); - - // click ok button - getButtonOk(modalElement).click(); - flush(); - expect(console.log).toHaveBeenCalledWith('click ok'); - expectModalDestroyed(tempModalId, false); // shouldn't destroy when ok button returns false - spy.calls.reset(); - })); // /basic props - - it('should be closed when clicking cancel button', fakeAsync(() => { - const spy = spyOn(console, 'log'); - // change and click mask - modalInstance.nzMask = true; - // should show mask - // TODO: repair this - // expect((modalElement.querySelector('div.ant-modal-mask') as HTMLElement).style.opacity).toBe('0.4'); - // should not trigger nzOnCancel if click mask - (modalElement.querySelector('.ant-modal-wrap') as HTMLElement).click(); - expect(console.log).not.toHaveBeenCalledWith('click cancel'); - // change nzMaskClosable to true then click, should be called and destroyed - modalInstance.nzMaskClosable = true; - (modalElement.querySelector('.ant-modal-wrap') as HTMLElement).click(); - expect(console.log).toHaveBeenCalledWith('click cancel'); - // second click on mask should not trigger nzOnCancel - (console.log as jasmine.Spy).calls.reset(); - (modalElement.querySelector('.ant-modal-wrap') as HTMLElement).click(); - expect(console.log).not.toHaveBeenCalledWith('click cancel'); - flush(); - fixture.detectChanges(); - expectModalDestroyed(tempModalId, true); // should be destroyed - spy.calls.reset(); - })); - - it('should be closed when clicking ESC', fakeAsync(() => { - // click 'ESC' key - dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); - fixture.detectChanges(); - expectModalDestroyed(tempModalId, false); - - modalInstance.nzKeyboard = true; - dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); - flush(); - fixture.detectChanges(); - expectModalDestroyed(tempModalId, true); - })); - }); - - describe('NormalModal: created by service with vary nzContent and nzFooter', () => { - const tempModalId = generateUniqueId(); // Temp unique id to mark the confirm modal that created by service - let fixture: ComponentFixture; - let instance: TestVaryServiceComponent; - let modalAgent: NzModalRef; - let modalElement: HTMLElement; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [NoopAnimationsModule, NzModalModule], - declarations: [TestVaryServiceComponent, TestVaryServiceCustomComponent] - }); - TestBed.overrideModule(BrowserDynamicTestingModule, { - set: { entryComponents: [TestVaryServiceCustomComponent] } - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(TestVaryServiceComponent); - instance = fixture.debugElement.componentInstance; - modalAgent = instance.createWithVary(); - modalElement = modalAgent.getElement(); - modalElement.classList.add(tempModalId); // Mark with id - }); - - it('should change title from in/outside and trigger button', fakeAsync(() => { - fixture.detectChanges(); // Initial change detecting - - const contentComponent = modalAgent.getContentComponent(); - const contentComponentRef = (modalAgent as any).getContentComponentRef(); // tslint:disable-line:no-any - expect(contentComponent).toBe(contentComponentRef.instance); - const contentElement = contentComponent.elementRef.nativeElement as HTMLElement; - // change title from outside - const firstButton = modalElement.querySelector('.ant-modal-footer button:first-child') as HTMLButtonElement; - firstButton.click(); - fixture.detectChanges(); - expect(contentComponent.title).toBe('internal title changed'); - expect(isButtonLoading(firstButton)).toBe(false); // stopped immediately - - // button loading for Promise - const lastButton = modalElement.querySelector('.ant-modal-footer button:last-child') as HTMLButtonElement; - lastButton.click(); - fixture.detectChanges(); - expect(isButtonLoading(lastButton)).toBe(false); // stopped immediately - - // destroy from inside - contentElement.querySelector('button')!.click(); - fixture.detectChanges(); - tick(1000); - fixture.detectChanges(); - expectModalDestroyed(tempModalId, true); - })); // /vary with component - }); - - describe('ConfirmModal: should apply options correctly', () => { - let fixture: ComponentFixture; - let instance: TestConfirmModalComponent; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [NoopAnimationsModule, NzModalModule], - declarations: [TestConfirmModalComponent, TestConfirmCustomComponent] - }).compileComponents(); - - TestBed.overrideModule(BrowserDynamicTestingModule, { - set: { entryComponents: [TestConfirmCustomComponent] } - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(TestConfirmModalComponent); - instance = fixture.debugElement.componentInstance; - }); - - it('should click mask is closable', fakeAsync(() => { - const modalRef = instance.createMaskClosableConfirm(); - const modalElement = modalRef.getElement(); - fixture.detectChanges(); - expect(modalRef.getInstance().nzMaskClosable).toBe(true); - const maskElement = modalElement.querySelector('.ant-modal-wrap') as HTMLDivElement; - maskElement!.click(); - flush(); - fixture.detectChanges(); - expect(instance.maskClosedSpy).toHaveBeenCalled(); - })); - - it('boundary detection for options', fakeAsync(() => { - const spy = spyOn(console, 'warn'); - - const tempModalId = generateUniqueId(); - const modalAgent = instance.createConfirm() as NzModalRef; - const modalElement = modalAgent.getElement(); - modalElement.classList.add(tempModalId); - fixture.detectChanges(); - expect(console.warn).toHaveBeenCalled(); - // nzOnOk: close modal when clicked - getButtonOk(modalElement).click(); - fixture.detectChanges(); - flush(); - fixture.detectChanges(); - expectModalDestroyed(tempModalId, true); - spy.calls.reset(); - })); - - it('should render other confirm modals', fakeAsync(() => { - const ids: string[] = instance.createOtherModals(); - fixture.detectChanges(); - flush(); - fixture.detectChanges(); - ids.forEach(id => expectModalDestroyed(id, false)); - })); - - it('should disable buttons', fakeAsync(() => { - const modalRef = instance.createDisabledModal(); - fixture.detectChanges(); - flush(); - fixture.detectChanges(); - const buttons = modalRef.getElement().querySelectorAll('.ant-modal-confirm-btns button') as NodeListOf; - buttons.forEach(button => expect(button.disabled).toBe(true)); - })); - - it('should render content with component', fakeAsync(() => { - const modalRef = instance.createCustomContentWithComponent(); - const modalElement = modalRef.getElement(); - fixture.detectChanges(); - expect(modalElement.querySelector('.custom-component-in-confirm')).toBeTruthy(); - getButtonOk(modalElement).click(); - fixture.detectChanges(); - flush(); - fixture.detectChanges(); - })); - }); - - describe('css-unit.pipe', () => { - let fixture: ComponentFixture; - let testElement: HTMLDivElement; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [NoopAnimationsModule], - declarations: [NzToCssUnitPipe, TestCssUnitPipeComponent] - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(TestCssUnitPipeComponent); - testElement = fixture.debugElement.query(By.css('div')).nativeElement; - fixture.detectChanges(); - }); - - it('should "width" & "height" to be 100px', () => { - // fixture.detectChanges(); - expect(testElement.style.width).toBe('100px'); - expect(testElement.style.height).toBe('100px'); - }); - - it('should "top" to be 100pt', () => { - // fixture.detectChanges(); - expect(testElement.style.top).toBe('100pt'); - }); - }); - - it('#i18n', () => { - let fixture: ComponentFixture; - - const injector = TestBed.configureTestingModule({ - imports: [NoopAnimationsModule, NzButtonModule, NzModalModule], - declarations: [NzDemoModalAsyncComponent] - }); - fixture = TestBed.createComponent(NzDemoModalAsyncComponent); - const comp = fixture.componentInstance as NzDemoModalAsyncComponent; - comp.showModal(); - fixture.detectChanges(); - injector.get(NzI18nService).setLocale(en_US); - fixture.detectChanges(); - const cancelText = (fixture.debugElement.query(By.css('nz-modal .ant-btn')).nativeElement as HTMLElement).textContent!.trim(); - expect(cancelText).toBe(en_US.Modal.cancelText); - const okText = (fixture.debugElement.query(By.css('nz-modal .ant-btn-primary')).nativeElement as HTMLElement).textContent!.trim(); - expect(okText).toBe(en_US.Modal.okText); - }); -}); - -describe('global config', () => { - let basicFixture: ComponentFixture; - let inputFixture: ComponentFixture; - let nativeElement: HTMLElement; - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [NoopAnimationsModule, NzModalModule], - providers: [ - { - provide: NZ_MODAL_CONFIG, - useValue: { - nzMask: false, - nzMaskClosable: false - } - } - ], - declarations: [NzDemoModalBasicComponent, NzDemoModalWithInputComponent] - }).compileComponents(); - basicFixture = TestBed.createComponent(NzDemoModalBasicComponent); - inputFixture = TestBed.createComponent(NzDemoModalWithInputComponent); - }); - - it('nzMask should be global config value', fakeAsync(() => { - const debugElement = basicFixture.debugElement.query(By.css('.ant-modal-mask')); - basicFixture.detectChanges(); - expect(debugElement).toBeNull(); - })); - - it('nzMask should be input value', fakeAsync(() => { - inputFixture.componentInstance.nzMask = true; - inputFixture.detectChanges(); - nativeElement = inputFixture.debugElement.query(By.css('.ant-modal-mask')).nativeElement; - inputFixture.detectChanges(); - expect(nativeElement).not.toBeNull(); - })); - - it('nzMaskClosable should be global config value', fakeAsync(() => { - inputFixture.componentInstance.nzMask = true; - inputFixture.detectChanges(); - nativeElement = inputFixture.debugElement.query(By.css('.ant-modal-wrap')).nativeElement; - inputFixture.detectChanges(); - nativeElement!.click(); - inputFixture.detectChanges(); - expectModalHidden(inputFixture.debugElement.query(By.css('nz-modal')).nativeElement, true); - })); -}); - -describe('NzModal', () => { - let modalService: NzModalService; - let overlayContainer: OverlayContainer; - let overlayContainerElement: HTMLElement; - - beforeEach(fakeAsync(() => { - TestBed.configureTestingModule({ - imports: [NoopAnimationsModule, NzModalModule, NzIconTestModule], - declarations: [NzDemoModalBasicComponent, NzDemoModalMaskComponent, ModalByServiceComponent] - }); - - TestBed.compileComponents(); - })); - - beforeEach(inject([NzModalService, OverlayContainer], (ms: NzModalService, oc: OverlayContainer) => { - modalService = ms; - overlayContainer = oc; - overlayContainerElement = oc.getContainerElement(); - })); - - afterEach(() => { - overlayContainer.ngOnDestroy(); - }); - - describe('basic usage', () => { - let fixture: ComponentFixture; - beforeEach(() => { - fixture = TestBed.createComponent(NzDemoModalBasicComponent); - }); - - it('should destroy normally when the component context is over', fakeAsync(() => { - fixture.detectChanges(); - tick(1000); - fixture.detectChanges(); - expect(overlayContainerElement.textContent).toContain('BASIC_MODAL_TITLE'); - fixture.componentInstance.modalAvailable = false; - fixture.detectChanges(); - tick(1000); - fixture.detectChanges(); - expect(overlayContainerElement.textContent).not.toContain('BASIC_MODAL_TITLE'); - })); - - it('should custom close icon work', fakeAsync(() => { - fixture.componentInstance.modalAvailable = true; - fixture.componentInstance.icon = 'close-square'; - fixture.detectChanges(); - tick(1000); - fixture.detectChanges(); - const closeIcon = overlayContainerElement.querySelector('.ant-modal-close-icon') as HTMLElement; - expect(closeIcon).toBeTruthy(); - expect(closeIcon.classList).toContain('anticon-close-square'); - })); - }); - - describe('created by service', () => { - let fixture: ComponentFixture; - - beforeEach(() => { - fixture = TestBed.createComponent(ModalByServiceComponent); - }); - afterEach(fakeAsync(() => { - // wait all openModals tobe closed to clean up the ModalManager as it is globally static - document.documentElement!.classList.remove('cdk-global-scrollblock'); - modalService.closeAll(); - fixture.detectChanges(); - tick(1000); - })); - - it('should trigger both afterOpen/nzAfterOpen and have the correct openModals length', fakeAsync(() => { - const spy = jasmine.createSpy('afterOpen spy'); - const nzAfterOpen = new EventEmitter(); - const modalRef = modalService.create({ nzAfterOpen }); - - modalRef.afterOpen.subscribe(spy); - nzAfterOpen.subscribe(spy); - - fixture.detectChanges(); - expect(spy).not.toHaveBeenCalled(); - - tick(600); - expect(spy).toHaveBeenCalledTimes(2); - expect(modalService.openModals.indexOf(modalRef)).toBeGreaterThan(-1); - expect(modalService.openModals.length).toBe(1); - })); - - it('should trigger both afterClose/nzAfterClose and have the correct openModals length', fakeAsync(() => { - const spy = jasmine.createSpy('afterClose spy'); - const nzAfterClose = new EventEmitter(); - const modalRef = modalService.create({ nzAfterClose }); - - modalRef.afterClose.subscribe(spy); - nzAfterClose.subscribe(spy); - - fixture.detectChanges(); - tick(600); - modalRef.close(); - fixture.detectChanges(); - expect(spy).not.toHaveBeenCalled(); - - tick(600); - expect(spy).toHaveBeenCalledTimes(2); - expect(modalService.openModals.indexOf(modalRef)).toBe(-1); - expect(modalService.openModals.length).toBe(0); - })); - - it('should return/receive with/without result data', fakeAsync(() => { - const spy = jasmine.createSpy('afterClose without result spy'); - const modalRef = modalService.success(); - - modalRef.afterClose.subscribe(spy); - fixture.detectChanges(); - tick(600); - modalRef.destroy(); - expect(spy).not.toHaveBeenCalled(); - tick(600); - expect(spy).toHaveBeenCalledWith(undefined); - })); - - it('should return/receive with result data', fakeAsync(() => { - const result = { data: 'Fake Error' }; - const spy = jasmine.createSpy('afterClose with result spy'); - const modalRef = modalService.error(); - - fixture.detectChanges(); - tick(600); - modalRef.destroy(result); - modalRef.afterClose.subscribe(spy); - expect(spy).not.toHaveBeenCalled(); - tick(600); - expect(spy).toHaveBeenCalledWith(result); - })); - - it('should close all opened modals (include non-service modals)', fakeAsync(() => { - const spy = jasmine.createSpy('afterAllClose spy'); - const modalMethods = ['create', 'info', 'success', 'error', 'confirm']; - const uniqueId = (name: string) => `__${name}_ID_SUFFIX__`; - const queryOverlayElement = (name: string) => overlayContainerElement.querySelector(`.${uniqueId(name)}`) as HTMLElement; - - modalService.afterAllClose.subscribe(spy); - - fixture.componentInstance.nonServiceModalVisible = true; // Show non-service modal - // @ts-ignore - modalMethods.forEach(method => modalService[method]({ nzWrapClassName: uniqueId(method) })); // Service modals - - fixture.detectChanges(); - tick(600); - modalMethods.concat('NON_SERVICE').forEach(method => expect(queryOverlayElement(method).style.display).not.toBe('none')); // Cover non-service modal for later checking - expect(modalService.openModals.length).toBe(6); - - modalService.closeAll(); - fixture.detectChanges(); - expect(spy).not.toHaveBeenCalled(); - tick(600); - expect(spy).toHaveBeenCalled(); - expect(modalService.openModals.length).toBe(0); - })); - - it('should only a confirm button when the type is "info"|"success"|"error"|"warning"', fakeAsync(() => { - const modalMethods = ['info', 'success', 'error', 'warning']; - const uniqueId = (name: string) => `__${name}_ID_SUFFIX__`; - const queryOverlayElement = (name: string) => - overlayContainerElement.querySelectorAll(`.${uniqueId(name)} .ant-modal-confirm-btns > button`) as NodeListOf; - - fixture.componentInstance.nonServiceModalVisible = false; // Show non-service modal - // @ts-ignore - modalMethods.forEach(method => modalService[method]({ nzWrapClassName: uniqueId(method) })); // Service modals - - fixture.detectChanges(); - tick(600); - modalMethods.forEach(method => { - const buttons = queryOverlayElement(method); - expect(buttons.length).toBe(1); - expect(buttons[0]!.classList).toContain('ant-btn-primary'); - }); // Cover non-service modal for later checking - expect(modalService.openModals.length).toBe(4); - - modalService.closeAll(); - fixture.detectChanges(); - tick(600); - expect(modalService.openModals.length).toBe(0); - })); - - it('should modal not be registered twice', fakeAsync(() => { - const modalRef = modalService.create(); - - fixture.detectChanges(); - (modalService as any).modalControl.registerModal(modalRef); // tslint:disable-line:no-any - tick(600); - expect(modalService.openModals.length).toBe(1); - })); - - it('should degregister a modal', fakeAsync(() => { - const modalRef = modalService.create(); - const modalControl = (modalService as any).modalControl as NzModalControlService; // tslint:disable-line:no-any - - fixture.detectChanges(); - tick(600); - expect(modalService.openModals.length).toBe(1); - - modalControl.deregisterModal(modalRef); - expect(modalService.openModals.length).toBe(0); - - // Should nothing happened - modalControl.deregisterModal(modalRef); - expect(modalService.openModals.length).toBe(0); - })); - - it('should trigger nzOnOk/nzOnCancel', () => { - const spyOk = jasmine.createSpy('ok spy'); - const spyCancel = jasmine.createSpy('cancel spy'); - const modalRef: NzModalRef = modalService.create({ - nzOnOk: spyOk, - nzOnCancel: spyCancel - }); - - fixture.detectChanges(); - - modalRef.triggerOk(); - expect(spyOk).toHaveBeenCalled(); - - modalRef.triggerCancel(); - expect(spyCancel).toHaveBeenCalled(); - }); - - it('should block body scroll', fakeAsync(() => { - const forceScrollElement = document.createElement('div'); - document.body.appendChild(forceScrollElement); - forceScrollElement.style.width = '100px'; - forceScrollElement.style.height = '3000px'; - forceScrollElement.style.background = 'rebeccapurple'; - - const modalRef = modalService.create(); - tick(600); - fixture.detectChanges(); - - expect(document.documentElement!.classList).toContain('cdk-global-scrollblock'); - - modalRef.close(); - tick(600); - fixture.detectChanges(); - - expect(document.documentElement!.classList).not.toContain('cdk-global-scrollblock'); - document.body.removeChild(forceScrollElement); - })); - }); - - describe('close with mask', () => { - let fixture: ComponentFixture; - beforeEach(() => { - fixture = TestBed.createComponent(NzDemoModalMaskComponent); - }); - - it('should close when mask click', fakeAsync(() => { - fixture.componentInstance.isVisible = true; - fixture.detectChanges(); - tick(1000); - fixture.detectChanges(); - const nativeElement = fixture.debugElement.query(By.css('.ant-modal-wrap')).nativeElement; - fixture.detectChanges(); - nativeElement!.click(); - fixture.detectChanges(); - tick(1000); - fixture.detectChanges(); - expectModalHidden(fixture.debugElement.query(By.css('nz-modal')).nativeElement, true); - })); - - it('should not close if mouse down in dialog', fakeAsync(() => { - fixture.componentInstance.isVisible = true; - fixture.detectChanges(); - tick(1000); - fixture.detectChanges(); - const bodyNativeElement = fixture.debugElement.query(By.css('.ant-modal-body')).nativeElement; - dispatchFakeEvent(bodyNativeElement, 'mousedown'); - fixture.detectChanges(); - const warpNativeElement = fixture.debugElement.query(By.css('.ant-modal-wrap')).nativeElement; - dispatchFakeEvent(warpNativeElement, 'mouseup'); - dispatchFakeEvent(warpNativeElement, 'click'); - fixture.detectChanges(); - tick(1000); - fixture.detectChanges(); - expectModalHidden(fixture.debugElement.query(By.css('nz-modal')).nativeElement, false); - })); - }); -}); - -// ------------------------------------------- -// | Testing Components -// ------------------------------------------- - -@Component({ - template: ` - -

content

-
- ` -}) -class NzDemoModalBasicComponent { - modalAvailable = true; - icon = 'close'; -} - -@Component({ - template: ` - -

content

-
- ` -}) -class NzDemoModalMaskComponent { - isVisible = false; - handleCancel(): void { - this.isVisible = false; - } -} - -@Component({ - template: ` - -

content

-
- ` -}) -class NzDemoModalWithInputComponent { - modalAvailable = true; - nzMask = true; -} - -@Component({ - template: ` - - -

content

-
- ` -}) -class NzDemoModalAsyncComponent { - isVisible = false; - isOkLoading = false; - - showModal(): void { - this.isVisible = true; - } - - handleOk(): void { - this.isOkLoading = true; - setTimeout(() => { - this.isVisible = false; - this.isOkLoading = false; - }, 3000); - } - - handleCancel(): void { - this.isVisible = false; - } -} - -@Component({ - template: ` - - ` -}) -class NzDemoModalConfirmPromiseComponent { - confirmModal: NzModalRef; // For testing by now - - constructor(private modal: NzModalService) {} - - showConfirm(): void { - this.confirmModal = this.modal.confirm({ - nzTitle: 'Do you Want to delete these items?', - nzContent: 'When clicked the OK button, this dialog will be closed after 1 second', - nzOnOk: () => - new Promise((resolve, reject) => { - setTimeout(Math.random() > 0.5 ? resolve : reject, 1000); - }).catch(() => console.log('Oops errors!')) - }); - } -} - -@Component({ - template: `` -}) -class TestBasicServiceComponent { - basicModal: NzModalRef; - - constructor(private modalService: NzModalService) { - this.modalService.create(); // [Testing Required] Only for coverage temporarily - - // Testing for creating modal immediately - this.basicModal = this.modalService.create({ - nzGetContainer: () => document.body, - nzZIndex: 1888, - nzWidth: 250, - nzWrapClassName: 'test-wrap-class-name', - nzClassName: 'test-class-name', - nzStyle: { left: '10px', top: '20pt', border: '2px solid red' }, - nzTitle: 'TEST BOLD TITLE', - nzContent: '

test html content

', - nzClosable: false, - nzKeyboard: false, - nzMask: false, - nzMaskClosable: false, - nzMaskStyle: { opacity: 0.4 }, - nzBodyStyle: { background: 'gray' }, - // nzFooter: '
custom html footer: OK
', - nzOkText: 'custom ok', - nzOkType: 'primary', - nzOkLoading: false, - nzOkDisabled: false, - nzCancelDisabled: false, - nzOnOk: () => { - console.log('click ok'); - return false; - }, - nzCancelText: 'custom cancel', - nzCancelLoading: true, - nzOnCancel: () => console.log('click cancel') - }); - } -} - -@Component({ - template: `` -}) -class TestVaryServiceComponent { - constructor(private modalService: NzModalService) {} - - createWithVary(): NzModalRef { - const modal = this.modalService.create({ - nzContent: TestVaryServiceCustomComponent, - nzComponentParams: { title: 'internal title', subtitle: 'subtitle' }, - nzFooter: [ - { - label: 'change title from outside', - onClick: componentInstance => { - componentInstance!.title = 'internal title changed'; - return Promise.resolve(); - } - }, - { - label: 'show loading', - onClick: () => Promise.reject(null) - } - ] - }); - - return modal; - } -} - -@Component({ - template: ` -

{{ title }}

-

{{ subtitle }}

- - ` -}) -export class TestVaryServiceCustomComponent { - @Input() title: string; - @Input() subtitle: string; - - constructor(private modal: NzModalRef, public elementRef: ElementRef) {} - - destroyModal(): void { - this.modal.destroy(); - } -} - -@Component({ - template: `` -}) -export class TestConfirmModalComponent { - maskClosedSpy = jasmine.createSpy(); - constructor(public modalService: NzModalService) {} - - createConfirm(): NzModalRef { - this.modalService.confirm(); // [Testing Required] Only for coverage temporarily - this.modalService.confirm({ nzWidth: 100 }); // [Testing Required] Only for coverage temporarily - - // Boundary detection for options: nzFooter, nzOnOk - return this.modalService.confirm({ - nzFooter: 'should warning', - nzOkText: 'close' - }); - } - - createMaskClosableConfirm(): NzModalRef { - return this.modalService.confirm({ - nzMaskClosable: true, - nzOnCancel: () => { - this.maskClosedSpy(); - } - }); - } - - createOtherModals(): string[] { - return ['info', 'success', 'error', 'warning'].map(type => { - const modalId = generateUniqueId(); - // @ts-ignore - this.modalService[type]({ nzClassName: modalId }); - // @ts-ignore - this.modalService[type](); // [Testing Required] Only for coverage temporarily - return modalId; - }); - } - - createCustomContentWithComponent(): NzModalRef { - return this.modalService.confirm({ - nzContent: TestConfirmCustomComponent - }); - } - - createDisabledModal(): NzModalRef { - return this.modalService.confirm({ - nzCancelDisabled: true, - nzOkDisabled: true - }); - } -} - -@Component({ - template: ` - - Content - - ` -}) -export class TestConfirmCustomComponent { - constructor() {} -} - -@Component({ - template: ` -
- ` -}) -class TestCssUnitPipeComponent {} - -@Component({ - template: ` - - `, - providers: [NzModalControlService] // Testing for service with parent service -}) -export class ModalByServiceComponent { - nonServiceModalVisible = false; -} - -// ------------------------------------------- -// | Local tool functions -// ------------------------------------------- - -function expectModalHidden(modalElement: HTMLElement, hidden: boolean): void { - const display = (modalElement.querySelector('.ant-modal-wrap') as HTMLElement).style.visibility; - if (hidden) { - expect(display).toBe('hidden'); - } else { - expect(display).not.toBe('hidden'); - } - expect(modalElement.querySelector('.ant-modal-mask')!.classList.contains('ant-modal-mask-hidden')).toBe(hidden); -} - -function expectModalDestroyed(classId: string, destroyed: boolean): void { - const element = document.querySelector(`.${classId}`); - if (destroyed) { - expect(element).toBeFalsy(); - } else { - expect(element).not.toBeFalsy(); - } -} - -function generateUniqueId(): string { - return `testing-uniqueid-${counter++}`; -} - -function getButtonOk(modalElement: HTMLElement): HTMLButtonElement { - return isConfirmModal(modalElement) - ? (modalElement.querySelector('.ant-modal-confirm-btns button:last-child') as HTMLButtonElement) - : (modalElement.querySelector('.ant-modal-footer button:last-child') as HTMLButtonElement); -} - -function getButtonCancel(modalElement: HTMLElement): HTMLButtonElement { - return isConfirmModal(modalElement) - ? (modalElement.querySelector('.ant-modal-confirm-btns button:first-child') as HTMLButtonElement) - : (modalElement.querySelector('.ant-modal-footer button:first-child') as HTMLButtonElement); -} - -function getButtonClose(modalElement: HTMLElement): HTMLButtonElement { - // For normal modal only - return modalElement.querySelector('.ant-modal-close') as HTMLButtonElement; -} - -function isConfirmModal(modalElement: HTMLElement): boolean { - return !!modalElement.querySelector('.ant-modal-confirm'); -} - -function isButtonLoading(buttonElement: HTMLButtonElement): boolean { - return !!buttonElement.querySelector('i.anticon-loading'); -} diff --git a/components/modal/public-api.ts b/components/modal/public-api.ts index b6411baee96..0f08654cab0 100644 --- a/components/modal/public-api.ts +++ b/components/modal/public-api.ts @@ -6,15 +6,10 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -export { NzModalComponent } from './nz-modal.component'; -export { NzModalFooterDirective } from './nz-modal-footer.directive'; -export { NzModalRef } from './nz-modal-ref.class'; -export { NzModalModule } from './nz-modal.module'; -export { NzModalService } from './nz-modal.service'; -export { NzModalServiceModule } from './nz-modal.service.module'; -export { NZ_MODAL_CONFIG, NzModalConfig } from './nz-modal-config'; -export { NzModalControlService } from './nz-modal-control.service'; -export { NzModal } from './nz-modal'; -export { NzModalRef2 } from './nz-modal-ref'; -export { NzModalControlServiceModule } from './nz-modal-control.service.module'; -export * from './nz-modal.type'; +export * from './modal-types'; +export { NzModalService } from './modal.service'; +export { NzModalRef } from './modal-ref'; +export { NZ_MODAL_CONFIG, NzModalConfig } from './modal-config'; +export { NzModalComponent } from './modal.component'; +export { NzModalFooterDirective } from './modal-footer.directive'; +export { NzModalModule } from './modal.module'; diff --git a/components/modal/utils.ts b/components/modal/utils.ts index 613273b1e63..9f1ca63758c 100644 --- a/components/modal/utils.ts +++ b/components/modal/utils.ts @@ -6,10 +6,10 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -import { NzModal2Component } from './modal.component'; -import { ModalConfig } from './nz-modal.type'; +import { ModalOptions } from './modal-types'; +import { NzModalComponent } from './modal.component'; -export function applyConfigDefaults(config: ModalConfig, defaultOptions: ModalConfig): ModalConfig { +export function applyConfigDefaults(config: ModalOptions, defaultOptions: ModalOptions): ModalOptions { return { ...defaultOptions, ...config }; } @@ -26,7 +26,7 @@ export function setContentInstanceParams( Object.assign(instance, params); } -export function getConfigFromComponent(component: NzModal2Component): ModalConfig { +export function getConfigFromComponent(component: NzModalComponent): ModalOptions { const { nzMask, nzMaskClosable,