Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(module:modal): support draggable modal #4978

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions components/modal/demo/draggable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
order: 13
title:
zh-CN: 可拖拽
en-US: Draggable
---

## zh-CN

创建可拖拽的模态框,在 `afterOpen` 后可以通过 `ModalRef.getDragRef` 方法获取 [CDK/DragRef](https://material.angular.io/cdk/drag-drop/api#DragRef) 实例。

## en-US

Create a draggable modal, you can get the [CDK/DragRef](https://material.angular.io/cdk/drag-drop/api#DragRef) instance use the` ModalRef.getDragRef` method when After `afterOpen`.
138 changes: 138 additions & 0 deletions components/modal/demo/draggable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/* declarations: NzModalDraggableComponent */

import { Component } from '@angular/core';
import { NzModalService } from 'ng-zorro-antd';

@Component({
selector: 'nz-demo-modal-draggable',
template: `
<button nz-button [nzType]="'primary'" (click)="showModal()">Show Modal</button>
&nbsp;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
&nbsp;

should not including &nbsp in demo

<button nz-button nzType="primary" (click)="createComponentModal()">Use Component</button>
<br />
<br />
<button nz-button nzType="primary" (click)="createConfirm()">Confirm</button>
&nbsp;
<button nz-button nzType="primary" (click)="createMultiComponentModal()">Multi Modal</button>
<nz-modal
[(nzVisible)]="isVisible"
nzDraggable
nzWrapClassName="draggable-modal"
nzTitle="Draggable Modal"
(nzOnCancel)="handleCancel()"
(nzOnOk)="handleOk()"
>
<p>Content one</p>
<p>Content two</p>
<p>Content three</p>
</nz-modal>
`,
styles: [
`
::ng-deep .multi-modal {
pointer-events: none;
}

::ng-deep .draggable-modal .ant-modal-header {
cursor: move;
}
`
]
})
export class NzDemoModalDraggableComponent {
isVisible = false;

constructor(private modal: NzModalService) {}

showModal(): void {
this.isVisible = true;
}

handleOk(): void {
this.isVisible = false;
}

handleCancel(): void {
this.isVisible = false;
}

createComponentModal(): void {
const modalRef = this.modal.create({
nzTitle: 'Draggable Modal',
nzWrapClassName: 'draggable-modal',
nzDraggable: true,
nzContent: NzModalDraggableComponent
});

modalRef.afterOpen.subscribe(() => {
const dragRef = modalRef.getDragRef();
if (dragRef) {
dragRef.withBoundaryElement(modalRef.getBackdropElement());
dragRef.moved.subscribe(console.log);
}
});
}

createConfirm(): void {
this.modal.confirm({
nzTitle: 'Are you sure delete this task?',
nzContent: 'Some descriptions',
nzDraggable: true
});
}

createMultiComponentModal(): void {
const modalRef1 = this.modal.create({
nzTitle: 'Draggable Modal1',
nzDraggable: true,
nzMask: false,
nzMaskClosable: false,
nzContent: NzModalDraggableComponent,
nzWrapClassName: 'multi-modal draggable-modal',
nzMaskStyle: { pointerEvents: 'none' }
});

const modalRef2 = this.modal.create({
nzTitle: 'Draggable Modal2',
nzDraggable: true,
nzMask: false,
nzMaskClosable: false,
nzContent: NzModalDraggableComponent,
nzStyle: { top: `70px`, left: `100px` },
nzWrapClassName: 'multi-modal draggable-modal',
nzMaskStyle: { pointerEvents: 'none' }
});

modalRef1.afterOpen.subscribe(() => {
const dragRef = modalRef1.getDragRef();
if (dragRef) {
dragRef.moved.subscribe(() => {
modalRef1.updateConfig({ nzZIndex: 1001 });
modalRef2.updateConfig({ nzZIndex: 1000 });
});
}
});

modalRef2.afterOpen.subscribe(() => {
const dragRef = modalRef2.getDragRef();
if (dragRef) {
dragRef.moved.subscribe(() => {
modalRef2.updateConfig({ nzZIndex: 1001 });
modalRef1.updateConfig({ nzZIndex: 1000 });
});
}
});
}
}

@Component({
selector: 'nz-modal-draggable-component',
template: `
<p>Content one</p>
<p>Content two</p>
<p>Content three</p>
`
})
export class NzModalDraggableComponent {
constructor() {}
}
2 changes: 2 additions & 0 deletions components/modal/doc/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ The dialog created by the service method `NzModalService.xxx()` will return a `N
| getContentComponent() | Gets the Component instance in the contents of the dialog for `nzContent`. <i> Note: When the dialog is not initialized (`ngOnInit` is not executed), this function will return `undefined`</i> |
| triggerOk() | Manually trigger nzOnOk |
| triggerCancel() | Manually trigger nzOnCancel |
| updateConfig(option: ModalOptions) | Update option |
| getDragRef(): DragRef \| null | Get [CDK/DragRef](https://material.angular.io/cdk/drag-drop/api#DragRef) instance when the modal is draggable |

### ModalButtonOptions (used to customize the bottom button)

Expand Down
2 changes: 2 additions & 0 deletions components/modal/doc/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ constructor(modal: NzModalService) {
| getContentComponent() | 获取对话框内容中`nzContent`的Component实例instance。<i>注:当对话框还未初始化完毕(`ngOnInit`未执行)时,此函数将返回`undefined`</i> |
| triggerOk() | 手动触发nzOnOk |
| triggerCancel() | 手动触发nzOnCancel |
| updateConfig(option: ModalOptions) | 更新参数 |
| getDragRef(): DragRef \| null | 当模态框是可拖拽时获取 [CDK/DragRef](https://material.angular.io/cdk/drag-drop/api#DragRef) 实例 |

### ModalButtonOptions(用于自定义底部按钮)

Expand Down
8 changes: 5 additions & 3 deletions components/modal/modal-confirm-container.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/

import { FocusTrapFactory } from '@angular/cdk/a11y';
import { ConfigurableFocusTrapFactory } from '@angular/cdk/a11y';
import { DragDrop } from '@angular/cdk/drag-drop';
import { OverlayRef } from '@angular/cdk/overlay';
import { CdkPortalOutlet, ComponentPortal } from '@angular/cdk/portal';
import { DOCUMENT } from '@angular/common';
Expand Down Expand Up @@ -120,16 +121,17 @@ export class NzModalConfirmContainerComponent extends BaseModalContainer impleme
constructor(
private i18n: NzI18nService,
elementRef: ElementRef,
focusTrapFactory: FocusTrapFactory,
focusTrapFactory: ConfigurableFocusTrapFactory,
cdr: ChangeDetectorRef,
render: Renderer2,
zone: NgZone,
overlayRef: OverlayRef,
dragDrop: DragDrop,
public config: ModalOptions,
@Optional() @Inject(DOCUMENT) document: NzSafeAny,
@Optional() @Inject(ANIMATION_MODULE_TYPE) animationType: string
) {
super(elementRef, focusTrapFactory, cdr, render, zone, overlayRef, config, document, animationType);
super(elementRef, focusTrapFactory, cdr, render, zone, overlayRef, dragDrop, config, document, animationType);
this.i18n.localeChange.pipe(takeUntil(this.destroy$)).subscribe(() => {
this.locale = this.i18n.getLocaleData('Modal');
});
Expand Down
10 changes: 7 additions & 3 deletions components/modal/modal-container.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/

import { FocusTrapFactory } from '@angular/cdk/a11y';
import { ConfigurableFocusTrapFactory } from '@angular/cdk/a11y';
import { DragDrop } from '@angular/cdk/drag-drop';
import { OverlayRef } from '@angular/cdk/overlay';
import { CdkPortalOutlet } from '@angular/cdk/portal';
import { DOCUMENT } from '@angular/common';
Expand All @@ -26,6 +27,7 @@ import { NzSafeAny } from 'ng-zorro-antd/core/types';

import { nzModalAnimations } from './modal-animations';
import { BaseModalContainer } from './modal-container';
import { NzModalTitleComponent } from './modal-title.component';
import { ModalOptions } from './modal-types';

@Component({
Expand Down Expand Up @@ -76,18 +78,20 @@ import { ModalOptions } from './modal-types';
})
export class NzModalContainerComponent extends BaseModalContainer {
@ViewChild(CdkPortalOutlet, { static: true }) portalOutlet: CdkPortalOutlet;
@ViewChild(NzModalTitleComponent, { static: false }) modalHeaderRef: NzModalTitleComponent;
@ViewChild('modalElement', { static: true }) modalElementRef: ElementRef<HTMLDivElement>;
constructor(
elementRef: ElementRef,
focusTrapFactory: FocusTrapFactory,
focusTrapFactory: ConfigurableFocusTrapFactory,
cdr: ChangeDetectorRef,
render: Renderer2,
zone: NgZone,
overlayRef: OverlayRef,
dragDrop: DragDrop,
public config: ModalOptions,
@Optional() @Inject(DOCUMENT) document: NzSafeAny,
@Optional() @Inject(ANIMATION_MODULE_TYPE) animationType: string
) {
super(elementRef, focusTrapFactory, cdr, render, zone, overlayRef, config, document, animationType);
super(elementRef, focusTrapFactory, cdr, render, zone, overlayRef, dragDrop, config, document, animationType);
}
}
27 changes: 24 additions & 3 deletions components/modal/modal-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@
*/

import { AnimationEvent } from '@angular/animations';
import { FocusTrap, FocusTrapFactory } from '@angular/cdk/a11y';
import { ConfigurableFocusTrapFactory, FocusTrap } from '@angular/cdk/a11y';
import { DragDrop } from '@angular/cdk/drag-drop';
import { DragRef } from '@angular/cdk/drag-drop/drag-ref';
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 { NzSafeAny } from 'ng-zorro-antd/core/types';
import { getElementOffset } from 'ng-zorro-antd/core/util';

import { NzModalRef } from './modal-ref';
import { NzModalTitleComponent } from './modal-title.component';
import { ModalOptions } from './modal-types';

export function throwNzModalContentAlreadyAttachedError(): never {
Expand All @@ -38,6 +41,7 @@ const FADE_CLASS_NAME_MAP = {
export class BaseModalContainer extends BasePortalOutlet {
portalOutlet: CdkPortalOutlet;
modalElementRef: ElementRef<HTMLDivElement>;
modalHeaderRef: NzModalTitleComponent;

animationStateChanged = new EventEmitter<AnimationEvent>();
containerClick = new EventEmitter<void>();
Expand All @@ -50,16 +54,18 @@ export class BaseModalContainer extends BasePortalOutlet {
isStringContent: boolean = false;
private elementFocusedBeforeModalWasOpened: HTMLElement | null = null;
private focusTrap: FocusTrap;
private dragRef: DragRef;
private latestMousedownTarget: HTMLElement | null = null;
private oldMaskStyle: { [key: string]: string } | null = null;

constructor(
protected elementRef: ElementRef,
protected focusTrapFactory: FocusTrapFactory,
protected focusTrapFactory: ConfigurableFocusTrapFactory,
public cdr: ChangeDetectorRef,
protected render: Renderer2,
protected zone: NgZone,
protected overlayRef: OverlayRef,
protected dragDrop: DragDrop,
public config: ModalOptions,
document?: NzSafeAny,
protected animationType?: string
Expand Down Expand Up @@ -94,7 +100,6 @@ export class BaseModalContainer extends BasePortalOutlet {
throwNzModalContentAlreadyAttachedError();
}
this.savePreviouslyFocusedElement();
this.setModalTransformOrigin();
return this.portalOutlet.attachComponentPortal(portal);
}

Expand All @@ -106,10 +111,26 @@ export class BaseModalContainer extends BasePortalOutlet {
return this.portalOutlet.attachTemplatePortal(portal);
}

attachStringContent(): void {
this.savePreviouslyFocusedElement();
}

getNativeElement(): HTMLElement {
return this.elementRef.nativeElement;
}

registerDrag(): DragRef {
this.dragRef = this.dragDrop.createDrag(this.modalElementRef.nativeElement.lastChild! as HTMLElement);
this.setDragHandler();
return this.dragRef;
}

private setDragHandler(): void {
if (this.modalHeaderRef) {
this.dragRef.withHandles([this.modalHeaderRef!.elementRef]);
}
}

private animationDisabled(): boolean {
return this.config.nzNoAnimation || this.animationType === 'NoopAnimations';
}
Expand Down
18 changes: 18 additions & 0 deletions components/modal/modal-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 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 { DragRef } from '@angular/cdk/drag-drop';
import { ESCAPE, hasModifierKey } from '@angular/cdk/keycodes';
import { OverlayRef } from '@angular/cdk/overlay';
import { EventEmitter } from '@angular/core';
Expand Down Expand Up @@ -35,6 +36,7 @@ export class NzModalRef<T = NzSafeAny, R = NzSafeAny> implements NzModalLegacyAP
afterClose: Subject<R> = new Subject();
afterOpen: Subject<void> = new Subject();

private dragRef: DragRef | null = null;
private closeTimeout: number;

constructor(private overlayRef: OverlayRef, private config: ModalOptions, public containerInstance: BaseModalContainer) {
Expand All @@ -44,6 +46,7 @@ export class NzModalRef<T = NzSafeAny, R = NzSafeAny> implements NzModalLegacyAP
take(1)
)
.subscribe(() => {
this.registerDragIfDraggable();
this.afterOpen.next();
this.afterOpen.complete();
if (config.nzAfterOpen instanceof EventEmitter) {
Expand All @@ -58,6 +61,8 @@ export class NzModalRef<T = NzSafeAny, R = NzSafeAny> implements NzModalLegacyAP
)
.subscribe(() => {
clearTimeout(this.closeTimeout);
this.dragRef?.dispose();
this.dragRef = null;
this.overlayRef.dispose();
});

Expand Down Expand Up @@ -151,6 +156,7 @@ export class NzModalRef<T = NzSafeAny, R = NzSafeAny> implements NzModalLegacyAP

updateConfig(config: ModalOptions): void {
Object.assign(this.config, config);
this.registerDragIfDraggable();
this.containerInstance.cdr.markForCheck();
}

Expand All @@ -166,6 +172,18 @@ export class NzModalRef<T = NzSafeAny, R = NzSafeAny> implements NzModalLegacyAP
return this.overlayRef.backdropElement;
}

getDragRef(): DragRef | null {
return this.dragRef;
}

private registerDragIfDraggable(): void {
if (this.config.nzDraggable && !this.dragRef) {
this.dragRef = this.containerInstance.registerDrag();
} else if (!this.config.nzDraggable) {
this.dragRef?.dispose();
}
}

private trigger(action: NzTriggerAction): void {
const trigger = { ok: this.config.nzOnOk, cancel: this.config.nzOnCancel }[action];
const loadingKey = { ok: 'nzOkLoading', cancel: 'nzCancelLoading' }[action] as 'nzOkLoading' | 'nzCancelLoading';
Expand Down
Loading