diff --git a/components/modal/doc/index.en-US.md b/components/modal/doc/index.en-US.md index 29ffa05edb1..9559e64c908 100644 --- a/components/modal/doc/index.en-US.md +++ b/components/modal/doc/index.en-US.md @@ -45,6 +45,7 @@ The dialog is currently divided into 2 modes, `normal mode` and `confirm box mod | nzCancelLoading | Whether to apply loading visual effect for Cancel button or not | boolean | false | | nzFooter | Footer content, set as footer=null when you don't need default buttons. 1. Only valid in normal mode.
2. You can customize the buttons to the maximum extent by passing a `ModalButtonOptions` configuration (see the case or the instructions below).
| string
TemplateRef
ModalButtonOptions | OK and Cancel buttons | | nzGetContainer | The mount node for Modal | HTMLElement / () => HTMLElement| A default container | +| nzKeyboard | Whether support press esc to close | boolean | true | | nzMask | Whether show mask or not. | boolean | true | | nzMaskClosable | Whether to close the modal dialog when the mask (area outside the modal) is clicked | boolean | true | | nzMaskStyle | Style for modal's mask element. | object | - | diff --git a/components/modal/doc/index.zh-CN.md b/components/modal/doc/index.zh-CN.md index 906f06217ab..0ea67eafc37 100644 --- a/components/modal/doc/index.zh-CN.md +++ b/components/modal/doc/index.zh-CN.md @@ -46,6 +46,7 @@ title: Modal | nzCancelLoading | 取消按钮 loading | boolean | false | | nzFooter | 底部内容。1. 仅在普通模式下有效。
2. 可通过传入 ModalButtonOptions 来最大程度自定义按钮(详见案例或下方说明)。
3. 当不需要底部时,可以设为 null
| string
TemplateRef
ModalButtonOptions | 默认的确定取消按钮 | | nzGetContainer | 指定 Modal 挂载的 HTML 节点 | HTMLElement
() => HTMLElement| 默认容器 | +| nzKeyboard | 是否支持键盘esc关闭 | boolean | true | | nzMask | 是否展示遮罩 | boolean | true | | nzMaskClosable | 点击蒙层是否允许关闭 | boolean | true | | nzMaskStyle | 遮罩样式 | object | 无 | diff --git a/components/modal/nz-modal.component.ts b/components/modal/nz-modal.component.ts index 18e82921150..e4fe9011a27 100644 --- a/components/modal/nz-modal.component.ts +++ b/components/modal/nz-modal.component.ts @@ -23,7 +23,7 @@ import { ViewContainerRef } from '@angular/core'; -import { Observable, Subject } from 'rxjs'; +import { fromEvent, Observable, Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { NzMeasureScrollbarService } from '../core/services/nz-measure-scrollbar.service'; @@ -31,6 +31,7 @@ import { NzMeasureScrollbarService } from '../core/services/nz-measure-scrollbar import { InputBoolean } from '../core/util/convert'; import { NzI18nService } from '../i18n/nz-i18n.service'; +import { ESCAPE } from '@angular/cdk/keycodes'; import ModalUtil from './modal-util'; import { NzModalConfig, NZ_MODAL_CONFIG, NZ_MODAL_DEFAULT_CONFIG } from './nz-modal-config'; import { NzModalControlService } from './nz-modal-control.service'; @@ -108,6 +109,8 @@ export class NzModalComponent extends NzModalRef impleme @ViewChild('modalContainer') modalContainer: ElementRef; @ViewChild('bodyContainer', { read: ViewContainerRef }) bodyContainer: ViewContainerRef; + @Input() @InputBoolean() nzKeyboard: boolean = true; + get hidden(): boolean { return !this.nzVisible && !this.animationState; } // Indicate whether this dialog should hidden @@ -140,6 +143,8 @@ export class NzModalComponent extends NzModalRef impleme ngOnInit(): void { this.i18n.localeChange.pipe(takeUntil(this.unsubscribe$)).subscribe(() => this.locale = this.i18n.getLocaleData('Modal')); + fromEvent(this.document.body, 'keydown').pipe(takeUntil(this.unsubscribe$)).subscribe(e => this.keydownListener(e)); + if (this.isComponent(this.nzContent)) { this.createDynamicComponent(this.nzContent as Type); // Create component along without View } @@ -195,6 +200,12 @@ export class NzModalComponent extends NzModalRef impleme }); } + keydownListener(event: KeyboardEvent): void { + if (event.keyCode === ESCAPE && this.nzKeyboard) { + this.onClickOkCancel('cancel'); + } + } + open(): void { this.changeVisibleFromInside(true); } diff --git a/components/modal/nz-modal.spec.ts b/components/modal/nz-modal.spec.ts index 05fabd0a1c8..eccc0c9adc9 100644 --- a/components/modal/nz-modal.spec.ts +++ b/components/modal/nz-modal.spec.ts @@ -11,6 +11,8 @@ import { NzButtonComponent } from '../button/nz-button.component'; import { NzButtonModule } from '../button/nz-button.module'; import { NzMeasureScrollbarService } from '../core/services/nz-measure-scrollbar.service'; +import { ESCAPE } from '@angular/cdk/keycodes'; +import { createKeyboardEvent, dispatchKeyboardEvent } from '../core/testing'; import en_US from '../i18n/languages/en_US'; import { NzI18nService } from '../i18n/nz-i18n.service'; import { NzIconModule } from '../icon/nz-icon.module'; @@ -22,6 +24,8 @@ 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)', () => { let instance; let fixture: ComponentFixture<{}>; @@ -112,9 +116,10 @@ describe('modal testing (legacy)', () => { }); // /confirm-promise describe('NormalModal: created by service with most APIs', () => { - const tempModalId = generateUniqueId(); // Temp unique id to mark the confirm modal that created by service + let tempModalId; // Temp unique id to mark the confirm modal that created by service let modalAgent: NzModalRef; let modalElement: HTMLElement; + let modalInstance; beforeEach(async(() => { TestBed.configureTestingModule({ @@ -129,13 +134,13 @@ describe('modal testing (legacy)', () => { 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 modalInstance = modalAgent.getInstance(); spyOn(console, 'log'); - // [Hack] Codes that can't be covered by normal operations // tslint:disable-next-line:no-any expect((modalInstance as any).changeVisibleFromInside(true) instanceof Promise).toBe(true); @@ -159,8 +164,13 @@ describe('modal testing (legacy)', () => { // click ok button getButtonOk(modalElement).click(); + flush(); expect(console.log).toHaveBeenCalledWith('click ok'); expectModalDestroyed(tempModalId, false); // shouldn't destroy when ok button returns false + })); // /basic props + + it('should be closed when clicking cancel button', fakeAsync(() => { + spyOn(console, 'log'); // change and click mask modalInstance.nzMask = true; // should show mask @@ -178,9 +188,22 @@ describe('modal testing (legacy)', () => { (modalElement.querySelector('.ant-modal-wrap') as HTMLElement).click(); expect(console.log).not.toHaveBeenCalledWith('click cancel'); flush(); - // TODO: repair this, why my modifying this case would influence another case? - // expectModalDestroyed(tempModalId, true); // should be destroyed - })); // /basic props + fixture.detectChanges(); + expectModalDestroyed(tempModalId, true); // should be destroyed + })); + + 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', () => { @@ -658,6 +681,7 @@ class TestBasicServiceComponent { nzTitle: 'TEST BOLD TITLE', nzContent: '

test html content

', nzClosable: false, + nzKeyboard: false, nzMask: false, nzMaskClosable: false, nzMaskStyle: { opacity: 0.4 }, @@ -785,7 +809,6 @@ function expectModalDestroyed(classId: string, destroyed: boolean): void { } } -let counter = 0; function generateUniqueId(): string { return `testing-uniqueid-${counter++}`; } diff --git a/components/modal/nz-modal.type.ts b/components/modal/nz-modal.type.ts index 42e73ed63c6..c971f03872f 100644 --- a/components/modal/nz-modal.type.ts +++ b/components/modal/nz-modal.type.ts @@ -21,6 +21,7 @@ export interface ModalOptions { // tslint:disable-line:no-any nzContent?: string | TemplateRef<{}> | Type; nzComponentParams?: Partial; nzClosable?: boolean; + nzKeyboard?: boolean; nzMask?: boolean; nzMaskClosable?: boolean; nzMaskStyle?: object;