Skip to content

Commit

Permalink
feat(module:modal): support clicking ESC to close modal (NG-ZORRO#2483)
Browse files Browse the repository at this point in the history
  • Loading branch information
wenqi73 authored and vthinkxie committed Nov 30, 2018
1 parent 4c22c4f commit 4ee644c
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 8 deletions.
1 change: 1 addition & 0 deletions components/modal/doc/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. <i>1. Only valid in normal mode.<br>2. You can customize the buttons to the maximum extent by passing a `ModalButtonOptions` configuration (see the case or the instructions below).</i> | string<br>TemplateRef<br>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 | - |
Expand Down
1 change: 1 addition & 0 deletions components/modal/doc/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ title: Modal
| nzCancelLoading | 取消按钮 loading | boolean | false |
| nzFooter | 底部内容。<i>1. 仅在普通模式下有效。<br>2. 可通过传入 ModalButtonOptions 来最大程度自定义按钮(详见案例或下方说明)。<br>3. 当不需要底部时,可以设为 null</i> | string<br>TemplateRef<br>ModalButtonOptions | 默认的确定取消按钮 |
| nzGetContainer | 指定 Modal 挂载的 HTML 节点 | HTMLElement<br>() => HTMLElement| 默认容器 |
| nzKeyboard | 是否支持键盘esc关闭 | boolean | true |
| nzMask | 是否展示遮罩 | boolean | true |
| nzMaskClosable | 点击蒙层是否允许关闭 | boolean | true |
| nzMaskStyle | 遮罩样式 | object ||
Expand Down
13 changes: 12 additions & 1 deletion components/modal/nz-modal.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ 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';

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';
Expand Down Expand Up @@ -108,6 +109,8 @@ export class NzModalComponent<T = any, R = any> extends NzModalRef<T, R> 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
Expand Down Expand Up @@ -140,6 +143,8 @@ export class NzModalComponent<T = any, R = any> extends NzModalRef<T, R> impleme
ngOnInit(): void {
this.i18n.localeChange.pipe(takeUntil(this.unsubscribe$)).subscribe(() => this.locale = this.i18n.getLocaleData('Modal'));

fromEvent<KeyboardEvent>(this.document.body, 'keydown').pipe(takeUntil(this.unsubscribe$)).subscribe(e => this.keydownListener(e));

if (this.isComponent(this.nzContent)) {
this.createDynamicComponent(this.nzContent as Type<T>); // Create component along without View
}
Expand Down Expand Up @@ -195,6 +200,12 @@ export class NzModalComponent<T = any, R = any> extends NzModalRef<T, R> impleme
});
}

keydownListener(event: KeyboardEvent): void {
if (event.keyCode === ESCAPE && this.nzKeyboard) {
this.onClickOkCancel('cancel');
}
}

open(): void {
this.changeVisibleFromInside(true);
}
Expand Down
37 changes: 30 additions & 7 deletions components/modal/nz-modal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<{}>;
Expand Down Expand Up @@ -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({
Expand All @@ -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);
Expand All @@ -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
Expand All @@ -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', () => {
Expand Down Expand Up @@ -658,6 +681,7 @@ class TestBasicServiceComponent {
nzTitle: '<b>TEST BOLD TITLE</b>',
nzContent: '<p>test html content</p>',
nzClosable: false,
nzKeyboard: false,
nzMask: false,
nzMaskClosable: false,
nzMaskStyle: { opacity: 0.4 },
Expand Down Expand Up @@ -785,7 +809,6 @@ function expectModalDestroyed(classId: string, destroyed: boolean): void {
}
}

let counter = 0;
function generateUniqueId(): string {
return `testing-uniqueid-${counter++}`;
}
Expand Down
1 change: 1 addition & 0 deletions components/modal/nz-modal.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface ModalOptions<T = any, R = any> { // tslint:disable-line:no-any
nzContent?: string | TemplateRef<{}> | Type<T>;
nzComponentParams?: Partial<T>;
nzClosable?: boolean;
nzKeyboard?: boolean;
nzMask?: boolean;
nzMaskClosable?: boolean;
nzMaskStyle?: object;
Expand Down

0 comments on commit 4ee644c

Please sign in to comment.