From d212ea5058af5404c20c7e8c32011a6576e14802 Mon Sep 17 00:00:00 2001 From: cipchk Date: Wed, 14 Aug 2024 23:05:33 +0800 Subject: [PATCH 1/3] feat(theme:modal): support build-in and focus button --- .../theme/src/services/modal/index.en-US.md | 2 + .../theme/src/services/modal/index.zh-CN.md | 2 + .../theme/src/services/modal/modal.helper.ts | 51 ++++++++++++++----- .../theme/src/services/modal/modal.spec.ts | 51 ++++++++++++++----- 4 files changed, 82 insertions(+), 24 deletions(-) diff --git a/packages/theme/src/services/modal/index.en-US.md b/packages/theme/src/services/modal/index.en-US.md index 18ce61aa2..7459168aa 100644 --- a/packages/theme/src/services/modal/index.en-US.md +++ b/packages/theme/src/services/modal/index.en-US.md @@ -8,6 +8,8 @@ type: Service Based on the `NzModalService` package, it solves some known issues: - More friendly handling callbacks +- Default Button Focus +- Responsive Width ## Usage diff --git a/packages/theme/src/services/modal/index.zh-CN.md b/packages/theme/src/services/modal/index.zh-CN.md index 51fa72ede..82d15b752 100644 --- a/packages/theme/src/services/modal/index.zh-CN.md +++ b/packages/theme/src/services/modal/index.zh-CN.md @@ -8,6 +8,8 @@ type: Service 基于 `NzModalService` 封装,它解决一些已知问题: - 更友好的处理回调 +- 按钮默认焦点 +- 响应式宽度 ## 用法 diff --git a/packages/theme/src/services/modal/modal.helper.ts b/packages/theme/src/services/modal/modal.helper.ts index c2fca011c..d15c40c55 100644 --- a/packages/theme/src/services/modal/modal.helper.ts +++ b/packages/theme/src/services/modal/modal.helper.ts @@ -1,11 +1,11 @@ import { DragDrop, DragRef } from '@angular/cdk/drag-drop'; import { DOCUMENT } from '@angular/common'; import { Injectable, TemplateRef, Type, inject } from '@angular/core'; -import { Observable, Observer, filter, take } from 'rxjs'; +import { Observable, Observer, delay, filter, take, tap } from 'rxjs'; import { deepMerge } from '@delon/util/other'; import type { NzSafeAny } from 'ng-zorro-antd/core/types'; -import { ModalOptions, NzModalService } from 'ng-zorro-antd/modal'; +import { ModalOptions, NzModalRef, NzModalService } from 'ng-zorro-antd/modal'; const CLS_DRAG = 'MODAL-DRAG'; @@ -26,6 +26,11 @@ export interface ModalHelperOptions { * 是否强制使用 `nzData` 传递参数,若为 `false` 表示参数会直接映射到组件实例中,其他值只能通过 `NZ_MODAL_DATA` 的方式来获取参数,默认:`false` */ useNzData?: boolean; + + /** + * 设置焦点按钮 + */ + focus?: 'ok' | 'cancel'; } export interface ModalHelperDragOptions { @@ -76,20 +81,21 @@ export class ModalHelper { * this.nzModalRef.destroy(); */ create( - comp: TemplateRef | Type, - params?: NzSafeAny, + comp?: TemplateRef | Type | 'confirm' | 'info' | 'success' | 'error' | 'warning', + params?: NzSafeAny | ModalHelperOptions | null, options?: ModalHelperOptions ): Observable { + const isBuildIn = typeof comp === 'string'; options = deepMerge( { size: 'lg', exact: true, includeTabs: false }, - options + isBuildIn && arguments.length === 2 ? params : options ); return new Observable((observer: Observer) => { - const { size, includeTabs, modalOptions, drag, useNzData } = options as ModalHelperOptions; + const { size, includeTabs, modalOptions, drag, useNzData, focus } = options as ModalHelperOptions; let cls: string[] = []; let width = ''; if (size) { @@ -118,25 +124,46 @@ export class ModalHelper { }; cls.push(CLS_DRAG, dragWrapCls); } - const subject = this.srv.create({ + const mth = isBuildIn ? this.srv[comp] : this.srv.create; + const subject: NzModalRef = mth.call(this.srv, { nzWrapClassName: cls.join(' '), - nzContent: comp, + nzContent: isBuildIn ? undefined : comp, nzWidth: width ? width : undefined, nzFooter: null, nzData: params, ...modalOptions - }); + } as ModalOptions); // 保留 nzComponentParams 原有风格,但依然可以通过 @Inject(NZ_MODAL_DATA) 获取 - if (useNzData !== true) { + if (subject.componentInstance != null && useNzData !== true) { Object.assign(subject.componentInstance, params); } subject.afterOpen .pipe( take(1), - filter(() => dragOptions != null) + tap(() => { + if (dragOptions != null) { + dragRef = this.createDragRef(dragOptions, `.${dragWrapCls}`); + } + }), + filter(() => focus != null), + delay(modalOptions?.nzNoAnimation ? 10 : 200) ) .subscribe(() => { - dragRef = this.createDragRef(dragOptions!!, `.${dragWrapCls}`); + const btns = subject + .getElement() + .querySelector('.ant-modal-confirm-btns, .modal-footer') + ?.querySelectorAll('.ant-btn'); + const btnSize = btns?.length ?? 0; + let el: HTMLButtonElement | null = null; + if (btnSize === 1) { + el = btns![0]; + } else if (btnSize > 1) { + el = btns![focus === 'ok' ? 1 : 0]; + } + if (el != null) { + el.focus(); + el.dataset.focused = focus; + } }); subject.afterClose.pipe(take(1)).subscribe((res: NzSafeAny) => { if (options!.exact === true) { diff --git a/packages/theme/src/services/modal/modal.spec.ts b/packages/theme/src/services/modal/modal.spec.ts index 9f0188c55..5de463f61 100644 --- a/packages/theme/src/services/modal/modal.spec.ts +++ b/packages/theme/src/services/modal/modal.spec.ts @@ -6,7 +6,7 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { NZ_MODAL_DATA, NzModalModule, NzModalRef } from 'ng-zorro-antd/modal'; import { AlainThemeModule } from '../../theme.module'; -import { ModalHelper } from './modal.helper'; +import { ModalHelper, ModalHelperOptions } from './modal.helper'; describe('theme: ModalHelper', () => { let modal: ModalHelper; @@ -25,12 +25,11 @@ describe('theme: ModalHelper', () => { }); afterEach(() => { - const a = document.querySelector('nz-modal'); - if (a) a.remove(); + document.querySelector('nz-modal')?.remove(); }); describe('#create', () => { - it('should be open', fakeAsync(() => { + xit('should be open', fakeAsync(() => { modal.create(TestModalComponent, { ret: 'true' }).subscribe(() => { expect(true).toBeTruthy(); flush(); @@ -39,7 +38,7 @@ describe('theme: ModalHelper', () => { tick(1000); fixture.detectChanges(); })); - it('should be open a tabset', fakeAsync(() => { + xit('should be open a tabset', fakeAsync(() => { modal.create(TestModalComponent, { ret: 'true' }, { includeTabs: true }).subscribe(() => { expect(true).toBeTruthy(); flush(); @@ -49,7 +48,7 @@ describe('theme: ModalHelper', () => { fixture.detectChanges(); expect(document.querySelector('.modal-include-tabs')).not.toBeNull(); })); - it('should be useNzData is true', fakeAsync(() => { + xit('should be useNzData is true', fakeAsync(() => { modal.create(TestModalComponent, { ret: 'a' }, { useNzData: true }).subscribe(() => { expect(true).toBeTruthy(); flush(); @@ -61,7 +60,7 @@ describe('theme: ModalHelper', () => { expect(document.querySelector('.nzData')?.innerText.trim()).toBe('a'); })); describe('#exact width true', () => { - it('should be not trigger subscript when return a undefined value', fakeAsync(() => { + xit('should be not trigger subscript when return a undefined value', fakeAsync(() => { modal.create(TestModalComponent, { ret: undefined }, { includeTabs: true, exact: true }).subscribe({ next: () => { expect(false).toBeTruthy(); @@ -82,7 +81,7 @@ describe('theme: ModalHelper', () => { })); }); describe('#drag', () => { - it('should be working', fakeAsync(() => { + xit('should be working', fakeAsync(() => { modal .create(TestModalComponent, { ret: 'true' }, { drag: true, modalOptions: { nzTitle: 'test' } }) .subscribe(); @@ -91,7 +90,7 @@ describe('theme: ModalHelper', () => { fixture.detectChanges(); expect(document.querySelectorAll('.MODAL-DRAG').length).toBe(1); })); - it('#handleCls', fakeAsync(() => { + xit('#handleCls', fakeAsync(() => { modal .create( TestModalComponent, @@ -106,10 +105,38 @@ describe('theme: ModalHelper', () => { expect(handle?.classList).toContain('handle'); })); }); + describe('#focus', () => { + xit('should be focus ok button', fakeAsync(() => { + modal.create('confirm', {}, { focus: 'ok', modalOptions: { nzNoAnimation: true } }).subscribe(); + fixture.detectChanges(); + tick(1000); + fixture.detectChanges(); + const btn = document.querySelector('[data-focused="ok"]'); + expect(btn != null).toBe(true); + expect(btn?.classList).toContain('ant-btn-primary'); + })); + xit('should be focus cancel button', fakeAsync(() => { + modal.create('confirm', {}, { focus: 'cancel', modalOptions: { nzNoAnimation: true } }).subscribe(); + fixture.detectChanges(); + tick(1000); + fixture.detectChanges(); + const btn = document.querySelector('[data-focused="cancel"]'); + expect(btn != null).toBe(true); + expect(btn?.classList).not.toContain('ant-btn-primary'); + })); + }); + it('should argument length is 2', fakeAsync(() => { + modal.create('info', { size: '23%' } as ModalHelperOptions).subscribe(); + fixture.detectChanges(); + tick(1000); + fixture.detectChanges(); + const width = document.querySelector('.ant-modal')?.style.width; + expect(width).toBe('23%'); + })); }); describe('#createStatic', () => { - it('should be open', fakeAsync(() => { + xit('should be open', fakeAsync(() => { const id = `${+new Date()}`; modal .createStatic(TestModalComponent, { @@ -125,7 +152,7 @@ describe('theme: ModalHelper', () => { tick(1000); fixture.detectChanges(); })); - it('should be open sm size', fakeAsync(() => { + xit('should be open sm size', fakeAsync(() => { const id = `${+new Date()}`; modal .createStatic( @@ -145,7 +172,7 @@ describe('theme: ModalHelper', () => { tick(1000); fixture.detectChanges(); })); - it('should be 80% size', fakeAsync(() => { + xit('should be 80% size', fakeAsync(() => { modal.create(TestModalComponent, { ret: 'true' }, { size: '10%' }).subscribe(); fixture.detectChanges(); tick(1000); From 1c6186b6cb64f42eae09e6eb8244414449ecb6ce Mon Sep 17 00:00:00 2001 From: cipchk Date: Fri, 16 Aug 2024 19:46:13 +0800 Subject: [PATCH 2/3] chore: fix test --- .../theme/src/services/modal/modal.spec.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/theme/src/services/modal/modal.spec.ts b/packages/theme/src/services/modal/modal.spec.ts index 5de463f61..fc5fc4de6 100644 --- a/packages/theme/src/services/modal/modal.spec.ts +++ b/packages/theme/src/services/modal/modal.spec.ts @@ -29,7 +29,7 @@ describe('theme: ModalHelper', () => { }); describe('#create', () => { - xit('should be open', fakeAsync(() => { + it('should be open', fakeAsync(() => { modal.create(TestModalComponent, { ret: 'true' }).subscribe(() => { expect(true).toBeTruthy(); flush(); @@ -38,7 +38,7 @@ describe('theme: ModalHelper', () => { tick(1000); fixture.detectChanges(); })); - xit('should be open a tabset', fakeAsync(() => { + it('should be open a tabset', fakeAsync(() => { modal.create(TestModalComponent, { ret: 'true' }, { includeTabs: true }).subscribe(() => { expect(true).toBeTruthy(); flush(); @@ -48,7 +48,7 @@ describe('theme: ModalHelper', () => { fixture.detectChanges(); expect(document.querySelector('.modal-include-tabs')).not.toBeNull(); })); - xit('should be useNzData is true', fakeAsync(() => { + it('should be useNzData is true', fakeAsync(() => { modal.create(TestModalComponent, { ret: 'a' }, { useNzData: true }).subscribe(() => { expect(true).toBeTruthy(); flush(); @@ -60,7 +60,7 @@ describe('theme: ModalHelper', () => { expect(document.querySelector('.nzData')?.innerText.trim()).toBe('a'); })); describe('#exact width true', () => { - xit('should be not trigger subscript when return a undefined value', fakeAsync(() => { + it('should be not trigger subscript when return a undefined value', fakeAsync(() => { modal.create(TestModalComponent, { ret: undefined }, { includeTabs: true, exact: true }).subscribe({ next: () => { expect(false).toBeTruthy(); @@ -81,7 +81,7 @@ describe('theme: ModalHelper', () => { })); }); describe('#drag', () => { - xit('should be working', fakeAsync(() => { + it('should be working', fakeAsync(() => { modal .create(TestModalComponent, { ret: 'true' }, { drag: true, modalOptions: { nzTitle: 'test' } }) .subscribe(); @@ -90,7 +90,7 @@ describe('theme: ModalHelper', () => { fixture.detectChanges(); expect(document.querySelectorAll('.MODAL-DRAG').length).toBe(1); })); - xit('#handleCls', fakeAsync(() => { + it('#handleCls', fakeAsync(() => { modal .create( TestModalComponent, @@ -106,7 +106,7 @@ describe('theme: ModalHelper', () => { })); }); describe('#focus', () => { - xit('should be focus ok button', fakeAsync(() => { + it('should be focus ok button', fakeAsync(() => { modal.create('confirm', {}, { focus: 'ok', modalOptions: { nzNoAnimation: true } }).subscribe(); fixture.detectChanges(); tick(1000); @@ -115,7 +115,7 @@ describe('theme: ModalHelper', () => { expect(btn != null).toBe(true); expect(btn?.classList).toContain('ant-btn-primary'); })); - xit('should be focus cancel button', fakeAsync(() => { + it('should be focus cancel button', fakeAsync(() => { modal.create('confirm', {}, { focus: 'cancel', modalOptions: { nzNoAnimation: true } }).subscribe(); fixture.detectChanges(); tick(1000); @@ -136,7 +136,7 @@ describe('theme: ModalHelper', () => { }); describe('#createStatic', () => { - xit('should be open', fakeAsync(() => { + it('should be open', fakeAsync(() => { const id = `${+new Date()}`; modal .createStatic(TestModalComponent, { @@ -152,7 +152,7 @@ describe('theme: ModalHelper', () => { tick(1000); fixture.detectChanges(); })); - xit('should be open sm size', fakeAsync(() => { + it('should be open sm size', fakeAsync(() => { const id = `${+new Date()}`; modal .createStatic( @@ -172,7 +172,7 @@ describe('theme: ModalHelper', () => { tick(1000); fixture.detectChanges(); })); - xit('should be 80% size', fakeAsync(() => { + it('should be 80% size', fakeAsync(() => { modal.create(TestModalComponent, { ret: 'true' }, { size: '10%' }).subscribe(); fixture.detectChanges(); tick(1000); From e74dd0f53ac88dd1815e83b2ff46dc25082a4ba1 Mon Sep 17 00:00:00 2001 From: cipchk Date: Sat, 24 Aug 2024 01:09:51 +0800 Subject: [PATCH 3/3] chore: fix time value --- packages/theme/src/services/modal/modal.helper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/theme/src/services/modal/modal.helper.ts b/packages/theme/src/services/modal/modal.helper.ts index d15c40c55..b8bebf8f7 100644 --- a/packages/theme/src/services/modal/modal.helper.ts +++ b/packages/theme/src/services/modal/modal.helper.ts @@ -146,7 +146,7 @@ export class ModalHelper { } }), filter(() => focus != null), - delay(modalOptions?.nzNoAnimation ? 10 : 200) + delay(modalOptions?.nzNoAnimation ? 10 : 241) ) .subscribe(() => { const btns = subject