diff --git a/components/message/doc/index.en-US.md b/components/message/doc/index.en-US.md index 2a6da581a96..c9283f53cc1 100644 --- a/components/message/doc/index.en-US.md +++ b/components/message/doc/index.en-US.md @@ -64,6 +64,7 @@ Methods for destruction are also provided: | nzMaxStack | The maximum number of messages that can be displayed at the same time | `number` | `8` | | nzPauseOnHover | Do not remove automatically when mouse is over while setting to `true` | `boolean` | `true` | | nzAnimate | Whether to turn on animation | `boolean` | `true` | +| nzTop | Distance from top | `number|string` | `24` | ### NzMessageDataFilled diff --git a/components/message/doc/index.zh-CN.md b/components/message/doc/index.zh-CN.md index 5818f1e2a80..d37ea73081b 100644 --- a/components/message/doc/index.zh-CN.md +++ b/components/message/doc/index.zh-CN.md @@ -65,6 +65,7 @@ title: Message | nzMaxStack | 同一时间可展示的最大提示数量 | `number` | `8` | | nzPauseOnHover | 鼠标移上时禁止自动移除 | `boolean` | `true` | | nzAnimate | 开关动画效果 | `boolean` | `true` | +| nzTop | 消息距离顶部的位置 | `number|string` | `24` | ### NzMessageDataFilled diff --git a/components/message/nz-message-config.ts b/components/message/nz-message-config.ts index 0c2736132e6..daa5fe3b42c 100644 --- a/components/message/nz-message-config.ts +++ b/components/message/nz-message-config.ts @@ -1,14 +1,13 @@ import { InjectionToken } from '@angular/core'; export interface NzMessageConfig { - // For all messages as default config (can override when dynamically created) - nzDuration?: number; - nzPauseOnHover?: boolean; nzAnimate?: boolean; - // For message container only + nzDuration?: number; nzMaxStack?: number; - /* tslint:disable-next-line:no-any */ - [index: string]: any; + nzPauseOnHover?: boolean; + nzTop?: number | string; + + [index: string]: any; // tslint:disable-line:no-any } export const NZ_MESSAGE_DEFAULT_CONFIG = new InjectionToken('NZ_MESSAGE_DEFAULT_CONFIG'); @@ -18,9 +17,10 @@ export const NZ_MESSAGE_CONFIG = new InjectionToken('NZ_MESSAGE export const NZ_MESSAGE_DEFAULT_CONFIG_PROVIDER = { provide : NZ_MESSAGE_DEFAULT_CONFIG, useValue: { - nzDuration : 3000, nzAnimate : true, + nzDuration : 3000, + nzMaxStack : 7, nzPauseOnHover: true, - nzMaxStack : 7 + nzTop : 24 } }; diff --git a/components/message/nz-message-container.component.html b/components/message/nz-message-container.component.html index 079edd6be74..fb8ac584b88 100644 --- a/components/message/nz-message-container.component.html +++ b/components/message/nz-message-container.component.html @@ -1,3 +1,3 @@ -
+
\ No newline at end of file diff --git a/components/message/nz-message-container.component.ts b/components/message/nz-message-container.component.ts index 60f0fab5767..06ded53a711 100644 --- a/components/message/nz-message-container.component.ts +++ b/components/message/nz-message-container.component.ts @@ -1,8 +1,24 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Optional, ViewEncapsulation } from '@angular/core'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Inject, + Optional, + ViewEncapsulation +} from '@angular/core'; import { Subject } from 'rxjs'; -import { NzMessageConfig, NZ_MESSAGE_CONFIG, NZ_MESSAGE_DEFAULT_CONFIG } from './nz-message-config'; -import { NzMessageDataFilled, NzMessageDataOptions } from './nz-message.definitions'; +import { toCssPixel } from '../core/util'; + +import { + NzMessageConfig, + NZ_MESSAGE_CONFIG, + NZ_MESSAGE_DEFAULT_CONFIG +} from './nz-message-config'; +import { + NzMessageDataFilled, + NzMessageDataOptions +} from './nz-message.definitions'; @Component({ changeDetection : ChangeDetectionStrategy.OnPush, @@ -14,6 +30,7 @@ import { NzMessageDataFilled, NzMessageDataOptions } from './nz-message.definiti export class NzMessageContainerComponent { messages: NzMessageDataFilled[] = []; config: Required; + top: string | null; constructor( protected cdr: ChangeDetectorRef, @@ -25,6 +42,8 @@ export class NzMessageContainerComponent { setConfig(config: NzMessageConfig): void { this.config = { ...this.config, ...config }; + this.top = toCssPixel(this.config.nzTop); + this.cdr.markForCheck(); } /** diff --git a/components/message/nz-message.spec.ts b/components/message/nz-message.spec.ts index 43fe597cdaf..5a56f30922f 100644 --- a/components/message/nz-message.spec.ts +++ b/components/message/nz-message.spec.ts @@ -14,13 +14,13 @@ describe('NzMessage', () => { let messageService: NzMessageService; let overlayContainer: OverlayContainer; let overlayContainerElement: HTMLElement; - let demoAppFixture: ComponentFixture; + let demoAppFixture: ComponentFixture; beforeEach(fakeAsync(() => { TestBed.configureTestingModule({ imports: [ NzMessageModule, NoopAnimationsModule ], - declarations: [ DemoAppComponent ], - providers: [ { provide: NZ_MESSAGE_CONFIG, useValue: { nzMaxStack: 2 } } ] // Override default config + declarations: [ NzTestMessageBasicComponent ], + providers: [ { provide: NZ_MESSAGE_CONFIG, useValue: { nzMaxStack: 2, nzTop: 24 } } ] }); TestBed.compileComponents(); @@ -37,7 +37,7 @@ describe('NzMessage', () => { }); beforeEach(() => { - demoAppFixture = TestBed.createComponent(DemoAppComponent); + demoAppFixture = TestBed.createComponent(NzTestMessageBasicComponent); }); it('should open a message box with success', (() => { @@ -155,21 +155,28 @@ describe('NzMessage', () => { it('should emit event when message close', fakeAsync(() => { let onCloseFlag = false; - const msg = messageService.create('loading', 'CLOSE'); msg.onClose!.subscribe(() => { onCloseFlag = true; }); - demoAppFixture.detectChanges(); tick(50000); - expect(onCloseFlag).toBeTruthy(); })); + + it('should container top to configured', fakeAsync(() => { + messageService.create('top', 'CHANGE'); + demoAppFixture.detectChanges(); + + const messageContainerElement = overlayContainerElement.querySelector('.ant-message') as HTMLElement; + expect(messageContainerElement.style.top).toBe('24px'); + + tick(50000); + })); }); @Component({ selector: 'nz-demo-app-component', template: `` }) -export class DemoAppComponent {} +export class NzTestMessageBasicComponent {} diff --git a/components/notification/nz-notification-config.ts b/components/notification/nz-notification-config.ts index da1d71b717a..e80477ffee3 100644 --- a/components/notification/nz-notification-config.ts +++ b/components/notification/nz-notification-config.ts @@ -3,8 +3,8 @@ import { InjectionToken } from '@angular/core'; import { NzMessageConfig } from '../message/nz-message-config'; export interface NzNotificationConfig extends NzMessageConfig { - nzTop?: string; - nzBottom?: string; + nzTop?: string | number; + nzBottom?: string | number; nzPlacement?: 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight' | string; } diff --git a/components/notification/nz-notification-container.component.html b/components/notification/nz-notification-container.component.html index 61e1bd4da20..9bfa46477ee 100644 --- a/components/notification/nz-notification-container.component.html +++ b/components/notification/nz-notification-container.component.html @@ -1,8 +1,12 @@
- + [style.top]="(config.nzPlacement === 'topLeft' || config.nzPlacement === 'topRight') ? top : null" + [style.bottom]="(config.nzPlacement === 'bottomLeft' || config.nzPlacement === 'bottomRight') ? bottom : null" + [style.right]="(config.nzPlacement === 'bottomRight' || config.nzPlacement === 'topRight') ? '0px' : null" + [style.left]="(config.nzPlacement === 'topLeft' || config.nzPlacement === 'bottomLeft') ? '0px' : null"> + +
\ No newline at end of file diff --git a/components/notification/nz-notification-container.component.ts b/components/notification/nz-notification-container.component.ts index 7aec66b8905..cbe7f7e12e1 100644 --- a/components/notification/nz-notification-container.component.ts +++ b/components/notification/nz-notification-container.component.ts @@ -8,9 +8,18 @@ import { } from '@angular/core'; import { Subject } from 'rxjs'; +import { toCssPixel } from '../core/util'; import { NzMessageContainerComponent } from '../message/nz-message-container.component'; -import { NzNotificationConfig, NZ_NOTIFICATION_CONFIG, NZ_NOTIFICATION_DEFAULT_CONFIG } from './nz-notification-config'; -import { NzNotificationDataFilled, NzNotificationDataOptions } from './nz-notification.definitions'; + +import { + NzNotificationConfig, + NZ_NOTIFICATION_CONFIG, + NZ_NOTIFICATION_DEFAULT_CONFIG +} from './nz-notification-config'; +import { + NzNotificationDataFilled, + NzNotificationDataOptions +} from './nz-notification.definitions'; @Component({ changeDetection : ChangeDetectionStrategy.OnPush, @@ -20,6 +29,14 @@ import { NzNotificationDataFilled, NzNotificationDataOptions } from './nz-notifi templateUrl : './nz-notification-container.component.html' }) export class NzNotificationContainerComponent extends NzMessageContainerComponent { + config: Required; + bottom: string | null; + + /** + * @override + */ + messages: Array> = []; + constructor( cdr: ChangeDetectorRef, @Optional() @Inject(NZ_NOTIFICATION_DEFAULT_CONFIG) defaultConfig: NzNotificationConfig, @@ -29,14 +46,26 @@ export class NzNotificationContainerComponent extends NzMessageContainerComponen } /** - * A list of notifications displayed on the screen. * @override */ - messages: Array> = []; + setConfig(config: NzNotificationConfig): void { + const newConfig = this.config = { ...this.config, ...config }; + const placement = this.config.nzPlacement; + + this.top = placement === 'topLeft' || placement === 'topRight' + ? toCssPixel(newConfig.nzTop) + : null; + this.bottom = placement === 'bottomLeft' || placement === 'bottomRight' + ? toCssPixel(newConfig.nzBottom) + : null; + + this.cdr.markForCheck(); + } /** * Create a new notification. - * If there's a notification whose `nzKey` is same with `nzKey` in `NzNotificationDataFilled`, replace its content instead of create a new one. + * If there's a notification whose `nzKey` is same with `nzKey` in `NzNotificationDataFilled`, + * replace its content instead of create a new one. * @override * @param notification */ @@ -59,7 +88,10 @@ export class NzNotificationContainerComponent extends NzMessageContainerComponen this.cdr.detectChanges(); } - private replaceNotification(old: NzNotificationDataFilled, _new: NzNotificationDataFilled): void { + private replaceNotification( + old: NzNotificationDataFilled, + _new: NzNotificationDataFilled + ): void { old.title = _new.title; old.content = _new.content; old.template = _new.template; diff --git a/components/notification/nz-notification.spec.ts b/components/notification/nz-notification.spec.ts index ea98d74451e..f6de37e43ea 100644 --- a/components/notification/nz-notification.spec.ts +++ b/components/notification/nz-notification.spec.ts @@ -9,11 +9,21 @@ import { NZ_NOTIFICATION_CONFIG } from './nz-notification-config'; import { NzNotificationModule } from './nz-notification.module'; import { NzNotificationService } from './nz-notification.service'; +@Component({ + selector: 'nz-demo-app-component', + template: ` + {{ 'test template content' }}{{ data }} + ` +}) +export class DemoAppComponent { + @ViewChild(TemplateRef) demoTemplateRef: TemplateRef<{}>; +} + describe('NzNotification', () => { - let messageService: NzNotificationService; + let notificationService: NzNotificationService; let overlayContainer: OverlayContainer; let overlayContainerElement: HTMLElement; - let demoAppFixture: ComponentFixture; + let fixture: ComponentFixture; beforeEach(fakeAsync(() => { TestBed.configureTestingModule({ @@ -25,8 +35,8 @@ describe('NzNotification', () => { TestBed.compileComponents(); })); - beforeEach(inject([ NzNotificationService, OverlayContainer ], (m: NzNotificationService, oc: OverlayContainer) => { - messageService = m; + beforeEach(inject([ NzNotificationService, OverlayContainer ], (n: NzNotificationService, oc: OverlayContainer) => { + notificationService = n; overlayContainer = oc; overlayContainerElement = oc.getContainerElement(); })); @@ -36,52 +46,52 @@ describe('NzNotification', () => { }); beforeEach(() => { - demoAppFixture = TestBed.createComponent(DemoAppComponent); + fixture = TestBed.createComponent(DemoAppComponent); }); it('should open a message box with success', (() => { - messageService.success('test-title', 'SUCCESS'); - demoAppFixture.detectChanges(); + notificationService.success('test-title', 'SUCCESS'); + fixture.detectChanges(); expect(overlayContainerElement.textContent).toContain('SUCCESS'); expect(overlayContainerElement.querySelector('.ant-notification-notice-icon-success')).not.toBeNull(); })); it('should open a message box with error', (() => { - messageService.error('test-title', 'ERROR'); - demoAppFixture.detectChanges(); + notificationService.error('test-title', 'ERROR'); + fixture.detectChanges(); expect(overlayContainerElement.textContent).toContain('ERROR'); expect(overlayContainerElement.querySelector('.ant-notification-notice-icon-error')).not.toBeNull(); })); it('should open a message box with warning', (() => { - messageService.warning('test-title', 'WARNING'); - demoAppFixture.detectChanges(); + notificationService.warning('test-title', 'WARNING'); + fixture.detectChanges(); expect(overlayContainerElement.textContent).toContain('WARNING'); expect(overlayContainerElement.querySelector('.ant-notification-notice-icon-warning')).not.toBeNull(); })); it('should open a message box with info', (() => { - messageService.info('test-title', 'INFO'); - demoAppFixture.detectChanges(); + notificationService.info('test-title', 'INFO'); + fixture.detectChanges(); expect(overlayContainerElement.textContent).toContain('INFO'); expect(overlayContainerElement.querySelector('.ant-notification-notice-icon-info')).not.toBeNull(); })); it('should open a message box with blank', (() => { - messageService.blank('test-title', 'BLANK'); - demoAppFixture.detectChanges(); + notificationService.blank('test-title', 'BLANK'); + fixture.detectChanges(); expect(overlayContainerElement.textContent).toContain('BLANK'); expect(overlayContainerElement.querySelector('.ant-notification-notice-icon')).toBeNull(); })); it('should auto closed by 1s', fakeAsync(() => { - messageService.create('', '', 'EXISTS', { nzDuration: 1000 }); - demoAppFixture.detectChanges(); + notificationService.create('', '', 'EXISTS', { nzDuration: 1000 }); + fixture.detectChanges(); expect(overlayContainerElement.textContent).toContain('EXISTS'); @@ -90,8 +100,8 @@ describe('NzNotification', () => { })); it('should not destroy when hovered', fakeAsync(() => { - messageService.create('', '', 'EXISTS', { nzDuration: 3000 }); - demoAppFixture.detectChanges(); + notificationService.create('', '', 'EXISTS', { nzDuration: 3000 }); + fixture.detectChanges(); const messageElement = overlayContainerElement.querySelector('.ant-notification-notice')!; dispatchMouseEvent(messageElement, 'mouseenter'); @@ -104,72 +114,72 @@ describe('NzNotification', () => { })); it('should not destroyed automatically but manually', fakeAsync(() => { - const filledMessage = messageService.success('title', 'SUCCESS', { nzDuration: 0 }); - demoAppFixture.detectChanges(); + const filledMessage = notificationService.success('title', 'SUCCESS', { nzDuration: 0 }); + fixture.detectChanges(); tick(50000); expect(overlayContainerElement.textContent).toContain('SUCCESS'); - messageService.remove(filledMessage.messageId); - demoAppFixture.detectChanges(); + notificationService.remove(filledMessage.messageId); + fixture.detectChanges(); expect(overlayContainerElement.textContent).not.toContain('SUCCESS'); })); it('should keep the balance of messages length and then remove all', fakeAsync(() => { [ 1, 2, 3 ].forEach(id => { const content = `SUCCESS-${id}`; - messageService.success('', content); - demoAppFixture.detectChanges(); + notificationService.success('', content); + fixture.detectChanges(); tick(); - demoAppFixture.detectChanges(); + fixture.detectChanges(); expect(overlayContainerElement.textContent).toContain(content); if (id === 3) { expect(overlayContainerElement.textContent).not.toContain('SUCCESS-1'); - expect((messageService as any)._container.messages.length).toBe(2); // tslint:disable-line:no-any + expect((notificationService as any)._container.messages.length).toBe(2); // tslint:disable-line:no-any } }); - messageService.remove(); - demoAppFixture.detectChanges(); + notificationService.remove(); + fixture.detectChanges(); expect(overlayContainerElement.textContent).not.toContain('SUCCESS-3'); - expect((messageService as any)._container.messages.length).toBe(0); // tslint:disable-line:no-any + expect((notificationService as any)._container.messages.length).toBe(0); // tslint:disable-line:no-any })); it('should destroy without animation', fakeAsync(() => { - messageService.error('', 'EXISTS', { nzDuration: 1000, nzAnimate: false }); - demoAppFixture.detectChanges(); + notificationService.error('', 'EXISTS', { nzDuration: 1000, nzAnimate: false }); + fixture.detectChanges(); tick(1000 + 10); expect(overlayContainerElement.textContent).not.toContain('EXISTS'); })); it('should reset default config dynamically', fakeAsync(() => { - messageService.config({ nzDuration: 0 }); - messageService.create('', 'loading', 'EXISTS'); - demoAppFixture.detectChanges(); + notificationService.config({ nzDuration: 0 }); + notificationService.create('', 'loading', 'EXISTS'); + fixture.detectChanges(); tick(50000); expect(overlayContainerElement.textContent).toContain('EXISTS'); })); it('should show with placement of topLeft', () => { - messageService.config({ nzPlacement: 'topLeft' }); - messageService.create('', '', 'EXISTS'); - demoAppFixture.detectChanges(); + notificationService.config({ nzPlacement: 'topLeft' }); + notificationService.create('', '', 'EXISTS'); + fixture.detectChanges(); expect(overlayContainerElement.textContent).toContain('EXISTS'); expect(overlayContainerElement.querySelector('.ant-notification-topLeft')).not.toBeNull(); }); // Should support nzData as context. it('should open a message box with template ref', () => { - messageService.template(demoAppFixture.componentInstance.demoTemplateRef, { nzData: 'data' }); - demoAppFixture.detectChanges(); + notificationService.template(fixture.componentInstance.demoTemplateRef, { nzData: 'data' }); + fixture.detectChanges(); expect(overlayContainerElement.textContent).toContain('test template contentdata'); }); it('should update an existing notification when keys are matched', () => { - messageService.create('', '', 'EXISTS', { nzKey: 'exists' }); + notificationService.create('', '', 'EXISTS', { nzKey: 'exists' }); expect(overlayContainerElement.textContent).toContain('EXISTS'); - messageService.create('success', 'Title', 'SHOULD NOT CHANGE', { nzKey: 'exists' }); + notificationService.create('success', 'Title', 'SHOULD NOT CHANGE', { nzKey: 'exists' }); expect(overlayContainerElement.textContent).not.toContain('EXISTS'); expect(overlayContainerElement.textContent).toContain('Title'); expect(overlayContainerElement.textContent).toContain('SHOULD NOT CHANGE'); @@ -179,28 +189,46 @@ describe('NzNotification', () => { it('should receive `true` when it is closed by user', fakeAsync(() => { let onCloseFlag = false; - messageService.create('', '', 'close').onClose!.subscribe(user => { + notificationService.create('', '', 'close').onClose!.subscribe(user => { if (user) { onCloseFlag = true; } }); - demoAppFixture.detectChanges(); + fixture.detectChanges(); tick(1000); const closeEl = overlayContainerElement.querySelector('.ant-notification-notice-close')!; dispatchMouseEvent(closeEl, 'click'); tick(1000); expect(onCloseFlag).toBeTruthy(); - tick(50000); + + waitForNotificationsToClose(); + })); + + it('should support configurable nzTop & nzBottom', fakeAsync(() => { + notificationService.config({ nzTop: 48 }); + notificationService.create('', '', 'TEST TOP', { nzDuration: 3000 }); + waitForNotificationToggling(fixture); + const notificationContainer = overlayContainerElement.querySelector('.ant-notification') as HTMLElement; + expect(notificationContainer.style.top).toBe('48px'); + expect(notificationContainer.style.bottom).toBeFalsy(); + + notificationService.config({ nzPlacement: 'bottomLeft', nzBottom: '48px' }); + notificationService.create('', '', 'TEST BOTTOM'); + waitForNotificationToggling(fixture); + expect(notificationContainer.style.top).toBeFalsy(); + expect(notificationContainer.style.bottom).toBe('48px'); + + waitForNotificationsToClose(); })); }); -@Component({ - selector: 'nz-demo-app-component', - template: ` - {{ 'test template content' }}{{ data }} - ` -}) -export class DemoAppComponent { - @ViewChild(TemplateRef) demoTemplateRef: TemplateRef<{}>; +function waitForNotificationToggling(fixture: ComponentFixture): void { + fixture.detectChanges(); + tick(2000); + fixture.detectChanges(); +} + +function waitForNotificationsToClose(): void { + tick(10000); } diff --git a/tslint.json b/tslint.json index 28b5debce3d..318dca8821a 100644 --- a/tslint.json +++ b/tslint.json @@ -137,7 +137,7 @@ "no-empty": false, "no-empty-interface": true, "no-eval": true, - "no-floating-promises": true, + "no-floating-promises": false, "no-for-in-array": true, "no-import-side-effect": true, "no-inferrable-types": [