diff --git a/packages/theme/src/services/modal/demo/drag.md b/packages/theme/src/services/modal/demo/drag.md new file mode 100644 index 000000000..3be705d5e --- /dev/null +++ b/packages/theme/src/services/modal/demo/drag.md @@ -0,0 +1,37 @@ +--- +title: + zh-CN: 拖动 + en-US: Drag +order: 1 +--- + +## zh-CN + +支持拖动对话框。 + +## en-US + +Support for dragging dialogs. + +```ts +import { Component } from '@angular/core'; +import { ModalHelper } from '@delon/theme'; +import { DemoModalComponent } from '@shared'; +import { NzMessageService } from 'ng-zorro-antd/message'; + +@Component({ + selector: 'app-demo', + template: ` + + `, +}) +export class DemoComponent { + constructor(private modalHelper: ModalHelper, private msg: NzMessageService) {} + + open(): void { + this.modalHelper.create(DemoModalComponent, { record: { a: 1, b: '2', c: new Date() } }, { drag: true }).subscribe(res => { + this.msg.info(res); + }); + } +} +``` diff --git a/packages/theme/src/services/modal/index.en-US.md b/packages/theme/src/services/modal/index.en-US.md index b27e6f7ae..23be6d41d 100644 --- a/packages/theme/src/services/modal/index.en-US.md +++ b/packages/theme/src/services/modal/index.en-US.md @@ -53,4 +53,5 @@ Your body content | `size` | Specify modal size | `sm,md,lg,xl,number` | `lg` | | `exact` | Exact match return value, default is `true`, If the return value is not null (`null` or `undefined`) is considered successful, otherwise it is considered error. | `boolean` | `true` | | `includeTabs` | Whether to wrap the tab page | `boolean` | `false` | +| `drag` | Drag | `boolean, ModalHelperDragOptions` | - | | `modalOptions` | nz-modal raw parameters [ModalOptions](https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/components/modal/modal-types.ts) | `ModalOptions` | - | diff --git a/packages/theme/src/services/modal/index.zh-CN.md b/packages/theme/src/services/modal/index.zh-CN.md index adb4ce7f4..ef5389dd8 100644 --- a/packages/theme/src/services/modal/index.zh-CN.md +++ b/packages/theme/src/services/modal/index.zh-CN.md @@ -53,4 +53,5 @@ Your body content | `size` | 指定对话框大小 | `sm,md,lg,xl,number` | `lg` | | `exact` | 是否精准(默认:`true`),若返回值非空值(`null`或`undefined`)视为成功,否则视为错误 | `boolean` | `true` | | `includeTabs` | 是否包裹标签页 | `boolean` | `false` | +| `drag` | 支持拖动 | `boolean, ModalHelperDragOptions` | - | | `modalOptions` | 对话框 [ModalOptions](https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/components/modal/modal-types.ts) 参数 | `ModalOptions` | - | diff --git a/packages/theme/src/services/modal/modal.helper.ts b/packages/theme/src/services/modal/modal.helper.ts index d4a11bcfb..58c5f5cb3 100644 --- a/packages/theme/src/services/modal/modal.helper.ts +++ b/packages/theme/src/services/modal/modal.helper.ts @@ -1,5 +1,7 @@ -import { Injectable, TemplateRef, Type } from '@angular/core'; -import { Observable, Observer } from 'rxjs'; +import { DragDrop, DragRef } from '@angular/cdk/drag-drop'; +import { DOCUMENT } from '@angular/common'; +import { Inject, Injectable, TemplateRef, Type } from '@angular/core'; +import { Observable, Observer, filter, take } from 'rxjs'; import { deepMerge } from '@delon/util/other'; import type { NzSafeAny } from 'ng-zorro-antd/core/types'; @@ -14,6 +16,17 @@ export interface ModalHelperOptions { exact?: boolean; /** 是否包裹标签页,修复模态包含标签间距问题 */ includeTabs?: boolean; + /** + * 是否支持拖动,默认是通过标题来触发 + */ + drag?: ModalHelperDragOptions | boolean; +} + +export interface ModalHelperDragOptions { + /** + * 指定拖地区域的类名,若指定为 `null` 时表示整个对话框,默认:`.modal-header, .ant-modal-title` + */ + handleCls?: string | null; } /** @@ -21,7 +34,27 @@ export interface ModalHelperOptions { */ @Injectable({ providedIn: 'root' }) export class ModalHelper { - constructor(private srv: NzModalService) {} + private document: Document; + private dragClsPrefix = 'MODAL-DRAG'; + + constructor(private srv: NzModalService, private drag: DragDrop, @Inject(DOCUMENT) doc: NzSafeAny) { + this.document = doc; + } + + private createDragRef(options: ModalHelperDragOptions, wrapCls: string): DragRef { + const wrapEl = this.document.querySelector(wrapCls) as HTMLDivElement; + const modalEl = wrapEl.firstChild as HTMLDivElement; + const handelEl = options.handleCls ? wrapEl.querySelector(options.handleCls) : null; + if (handelEl) { + handelEl.classList.add(`${this.dragClsPrefix}-HANDLE`); + } + + return this.drag + .createDrag(handelEl ?? modalEl) + .withHandles([handelEl ?? modalEl]) + .withBoundaryElement(wrapEl) + .withRootElement(modalEl); + } /** * 构建一个对话框 @@ -53,7 +86,7 @@ export class ModalHelper { options ); return new Observable((observer: Observer) => { - const { size, includeTabs, modalOptions } = options as ModalHelperOptions; + const { size, includeTabs, modalOptions, drag } = options as ModalHelperOptions; let cls = ''; let width = ''; if (size) { @@ -70,6 +103,16 @@ export class ModalHelper { cls += ` ${modalOptions.nzWrapClassName}`; delete modalOptions.nzWrapClassName; } + let dragOptions: ModalHelperDragOptions | null; + let dragWrapCls = `${this.dragClsPrefix}-${+new Date()}`; + let dragRef: DragRef | null; + if (drag != null && drag !== false) { + dragOptions = { + handleCls: `.modal-header, .ant-modal-title`, + ...(typeof drag === 'object' ? drag : {}) + }; + cls += ` ${this.dragClsPrefix} ${dragWrapCls}`; + } const defaultOptions: ModalOptions = { nzWrapClassName: cls, nzContent: comp, @@ -78,7 +121,15 @@ export class ModalHelper { nzComponentParams: params }; const subject = this.srv.create({ ...defaultOptions, ...modalOptions }); - const afterClose$ = subject.afterClose.subscribe((res: NzSafeAny) => { + subject.afterOpen + .pipe( + take(1), + filter(() => dragOptions != null) + ) + .subscribe(() => { + dragRef = this.createDragRef(dragOptions!!, `.${dragWrapCls}`); + }); + subject.afterClose.pipe(take(1)).subscribe((res: NzSafeAny) => { if (options!.exact === true) { if (res != null) { observer.next(res); @@ -87,7 +138,7 @@ export class ModalHelper { observer.next(res); } observer.complete(); - afterClose$.unsubscribe(); + dragRef?.dispose(); }); }); } diff --git a/packages/theme/src/services/modal/modal.spec.ts b/packages/theme/src/services/modal/modal.spec.ts index 3440d0bbb..9575030ec 100644 --- a/packages/theme/src/services/modal/modal.spec.ts +++ b/packages/theme/src/services/modal/modal.spec.ts @@ -70,6 +70,31 @@ describe('theme: ModalHelper', () => { fixture.detectChanges(); })); }); + describe('#drag', () => { + it('should be working', fakeAsync(() => { + modal + .create(TestModalComponent, { ret: 'true' }, { drag: true, modalOptions: { nzTitle: 'test' } }) + .subscribe(); + fixture.detectChanges(); + tick(1000); + fixture.detectChanges(); + expect(document.querySelectorAll('.MODAL-DRAG').length).toBe(1); + })); + it('#handleCls', fakeAsync(() => { + modal + .create( + TestModalComponent, + { ret: 'true' }, + { drag: { handleCls: '.handle' }, modalOptions: { nzTitle: 'test' } } + ) + .subscribe(); + fixture.detectChanges(); + tick(1000); + fixture.detectChanges(); + const handle = document.querySelector('.MODAL-DRAG-HANDLE'); + expect(handle?.classList).toContain('handle'); + })); + }); }); describe('#createStatic', () => { @@ -113,7 +138,7 @@ describe('theme: ModalHelper', () => { }); @Component({ - template: `
modal{{ id }}
` + template: `
modal{{ id }}
` }) class TestModalComponent { id = ''; diff --git a/packages/theme/system/antd/_modal.less b/packages/theme/system/antd/_modal.less index e9314a45a..65037b972 100644 --- a/packages/theme/system/antd/_modal.less +++ b/packages/theme/system/antd/_modal.less @@ -123,3 +123,7 @@ } } } + +.MODAL-DRAG-HANDLE { + cursor: move; +} diff --git a/scripts/site/route-paths.txt b/scripts/site/route-paths.txt index 98ee91ca6..3cfb56b30 100644 --- a/scripts/site/route-paths.txt +++ b/scripts/site/route-paths.txt @@ -334,4 +334,4 @@ /util/pipes-format/en /util/pipes-format/zh /util/token/en -/util/token/zh +/util/token/zh \ No newline at end of file