diff --git a/.nvmrc b/.nvmrc index a033c976ef8..5c088ddb94a 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -12.13.1 \ No newline at end of file +12.14.1 diff --git a/components/alert/alert.component.ts b/components/alert/alert.component.ts new file mode 100644 index 00000000000..1a6230a29f1 --- /dev/null +++ b/components/alert/alert.component.ts @@ -0,0 +1,146 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Input, + OnChanges, + OnDestroy, + Output, + SimpleChanges, + TemplateRef, + ViewEncapsulation +} from '@angular/core'; +import { InputBoolean, NzConfigService, slideAlertMotion, WithConfig } from 'ng-zorro-antd/core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +const NZ_CONFIG_COMPONENT_NAME = 'alert'; + +@Component({ + selector: 'nz-alert', + exportAs: 'nzAlert', + animations: [slideAlertMotion], + template: ` +
+ + + + + {{ nzMessage }} + + + {{ nzDescription }} + + +
+ `, + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, + preserveWhitespaces: false +}) +export class NzAlertComponent implements OnChanges, OnDestroy { + @Input() nzCloseText: string | TemplateRef | null = null; + @Input() nzIconType: string | null = null; + @Input() nzMessage: string | TemplateRef | null = null; + @Input() nzDescription: string | TemplateRef | null = null; + @Input() nzType: 'success' | 'info' | 'warning' | 'error' = 'info'; + @Input() @WithConfig(NZ_CONFIG_COMPONENT_NAME, false) @InputBoolean() nzCloseable: boolean; + @Input() @WithConfig(NZ_CONFIG_COMPONENT_NAME, false) @InputBoolean() nzShowIcon: boolean; + @Input() @InputBoolean() nzBanner = false; + @Output() readonly nzOnClose = new EventEmitter(); + closed = false; + iconTheme: 'outline' | 'fill' = 'fill'; + inferredIconType: string = 'info-circle'; + private isTypeSet = false; + private isShowIconSet = false; + private destroy$ = new Subject(); + + constructor(public nzConfigService: NzConfigService, private cdr: ChangeDetectorRef) { + this.nzConfigService + .getConfigChangeEventForComponent(NZ_CONFIG_COMPONENT_NAME) + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + this.cdr.markForCheck(); + }); + } + + closeAlert(): void { + this.closed = true; + } + + onFadeAnimationDone(): void { + if (this.closed) { + this.nzOnClose.emit(true); + } + } + + ngOnChanges(changes: SimpleChanges): void { + const { nzShowIcon, nzDescription, nzType, nzBanner } = changes; + if (nzShowIcon) { + this.isShowIconSet = true; + } + if (nzType) { + this.isTypeSet = true; + switch (this.nzType) { + case 'error': + this.inferredIconType = 'close-circle'; + break; + case 'success': + this.inferredIconType = 'check-circle'; + break; + case 'info': + this.inferredIconType = 'info-circle'; + break; + case 'warning': + this.inferredIconType = 'exclamation-circle'; + break; + } + } + if (nzDescription) { + this.iconTheme = this.nzDescription ? 'outline' : 'fill'; + } + if (nzBanner) { + if (!this.isTypeSet) { + this.nzType = 'warning'; + } + if (!this.isShowIconSet) { + this.nzShowIcon = true; + } + } + } + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/components/alert/nz-alert.module.ts b/components/alert/alert.module.ts similarity index 75% rename from components/alert/nz-alert.module.ts rename to components/alert/alert.module.ts index d3030b86638..4438f30cf3c 100644 --- a/components/alert/nz-alert.module.ts +++ b/components/alert/alert.module.ts @@ -8,14 +8,14 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule } from 'ng-zorro-antd/core'; +import { NzOutletModule } from 'ng-zorro-antd/core'; import { NzIconModule } from 'ng-zorro-antd/icon'; -import { NzAlertComponent } from './nz-alert.component'; +import { NzAlertComponent } from './alert.component'; @NgModule({ declarations: [NzAlertComponent], exports: [NzAlertComponent], - imports: [CommonModule, NzIconModule, NzAddOnModule] + imports: [CommonModule, NzIconModule, NzOutletModule] }) export class NzAlertModule {} diff --git a/components/alert/nz-alert.spec.ts b/components/alert/alert.spec.ts similarity index 98% rename from components/alert/nz-alert.spec.ts rename to components/alert/alert.spec.ts index 13b4ef488ea..c6d648ab7d7 100644 --- a/components/alert/nz-alert.spec.ts +++ b/components/alert/alert.spec.ts @@ -2,11 +2,9 @@ import { Component, DebugElement, TemplateRef, ViewChild } from '@angular/core'; import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; - import { NzIconTestModule } from 'ng-zorro-antd/icon/testing'; - -import { NzAlertComponent } from './nz-alert.component'; -import { NzAlertModule } from './nz-alert.module'; +import { NzAlertComponent } from './alert.component'; +import { NzAlertModule } from './alert.module'; describe('alert', () => { beforeEach(fakeAsync(() => { diff --git a/components/alert/doc/index.en-US.md b/components/alert/doc/index.en-US.md index 27f6b3d364f..c58accb87a7 100644 --- a/components/alert/doc/index.en-US.md +++ b/components/alert/doc/index.en-US.md @@ -27,6 +27,6 @@ import { NzAlertModule } from 'ng-zorro-antd/alert'; | `[nzDescription]` | Additional content of Alert | `string \| TemplateRef` | - | | `[nzMessage]` | Content of Alert | `string \| TemplateRef` | - | | `[nzShowIcon]` | Whether to show icon, in `nzBanner` mode default is `true` | `boolean` | `false` | ✅ | -| `[nzIconType]` | Icon type, effective when `nzShowIcon` is `true` | `string \| string[] \| Set \| { [klass: string]: any; }` | - | +| `[nzIconType]` | Icon type, effective when `nzShowIcon` is `true` | `string` | - | | `[nzType]` | Type of Alert styles, in `nzBanner` mode default is `'warning'` | `'success' \| 'info' \| 'warning' \| 'error'` | `'info'` | | `(nzOnClose)` | Callback when Alert is closed | `EventEmitter` | - | diff --git a/components/alert/doc/index.zh-CN.md b/components/alert/doc/index.zh-CN.md index 92cd4526f42..1f5753b99c0 100644 --- a/components/alert/doc/index.zh-CN.md +++ b/components/alert/doc/index.zh-CN.md @@ -28,6 +28,6 @@ import { NzAlertModule } from 'ng-zorro-antd/alert'; | `[nzDescription]` | 警告提示的辅助性文字介绍 | `string \| TemplateRef` | - | | `[nzMessage]` | 警告提示内容 | `string \| TemplateRef` | - | | `[nzShowIcon]` | 是否显示辅助图标,`nzBanner` 模式下默认值为 `true` | `boolean` | `false` | ✅ | -| `[nzIconType]` | 自定义图标类型,`nzShowIcon` 为 `true` 时有效 | `string \| string[] \| Set \| { [klass: string]: any; }` | - | +| `[nzIconType]` | 自定义图标类型,`nzShowIcon` 为 `true` 时有效 | `string` | - | | `[nzType]` | 指定警告提示的样式,`nzBanner` 模式下默认值为 `'warning'` | `'success' \| 'info' \| 'warning' \| 'error'` | `'info'` | | `(nzOnClose)` | 关闭时触发的回调函数 | `EventEmitter` | - | diff --git a/components/alert/nz-alert.component.html b/components/alert/nz-alert.component.html deleted file mode 100644 index 7901b639e17..00000000000 --- a/components/alert/nz-alert.component.html +++ /dev/null @@ -1,39 +0,0 @@ -
- - - - - - - - {{ nzMessage }} - - - {{ nzDescription }} - - - - - - - {{ nzCloseText }} - - -
diff --git a/components/alert/nz-alert.component.ts b/components/alert/nz-alert.component.ts deleted file mode 100644 index 2b2e57aa10c..00000000000 --- a/components/alert/nz-alert.component.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { - ChangeDetectionStrategy, - Component, - EventEmitter, - Input, - OnChanges, - Output, - SimpleChanges, - TemplateRef, - ViewEncapsulation -} from '@angular/core'; -import { InputBoolean, NgClassType, NzConfigService, slideAlertMotion, WithConfig } from 'ng-zorro-antd/core'; - -const NZ_CONFIG_COMPONENT_NAME = 'alert'; - -@Component({ - selector: 'nz-alert', - exportAs: 'nzAlert', - animations: [slideAlertMotion], - templateUrl: './nz-alert.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - encapsulation: ViewEncapsulation.None, - preserveWhitespaces: false, - styles: [ - ` - nz-alert { - display: block; - } - ` - ] -}) -export class NzAlertComponent implements OnChanges { - @Input() nzCloseText: string | TemplateRef; - @Input() nzIconType: NgClassType; - @Input() nzMessage: string | TemplateRef; - @Input() nzDescription: string | TemplateRef; - @Input() nzType: 'success' | 'info' | 'warning' | 'error' = 'info'; - @Input() @WithConfig(NZ_CONFIG_COMPONENT_NAME, false) @InputBoolean() nzCloseable: boolean; - @Input() @WithConfig(NZ_CONFIG_COMPONENT_NAME, false) @InputBoolean() nzShowIcon: boolean; - @Input() @InputBoolean() nzBanner = false; - @Output() readonly nzOnClose = new EventEmitter(); - - get iconType(): NgClassType { - return this.nzIconType || this.inferredIconType; - } - - destroy = false; - iconTheme = 'fill'; - isIconTypeObject = false; - - private isTypeSet = false; - private isShowIconSet = false; - private inferredIconType: string = 'info-circle'; - - constructor(public nzConfigService: NzConfigService) {} - - closeAlert(): void { - this.destroy = true; - } - - onFadeAnimationDone(): void { - if (this.destroy) { - this.nzOnClose.emit(true); - } - } - - updateIconClassMap(): void { - switch (this.nzType) { - case 'error': - this.inferredIconType = 'close-circle'; - break; - case 'success': - this.inferredIconType = 'check-circle'; - break; - case 'info': - this.inferredIconType = 'info-circle'; - break; - case 'warning': - this.inferredIconType = 'exclamation-circle'; - break; - } - this.iconTheme = this.nzDescription ? 'outline' : 'fill'; - } - - ngOnChanges(changes: SimpleChanges): void { - const { nzShowIcon, nzDescription, nzType, nzBanner, nzIconType } = changes; - - if (nzShowIcon) { - this.isShowIconSet = true; - } - - if (nzDescription || nzType) { - this.updateIconClassMap(); - } - - if (nzType) { - this.isTypeSet = true; - } - - if (nzBanner) { - if (!this.isTypeSet) { - this.nzType = 'warning'; - } - if (!this.isShowIconSet) { - this.nzShowIcon = true; - } - } - - if (nzIconType) { - this.isIconTypeObject = typeof nzIconType.currentValue === 'object'; - } - } -} diff --git a/components/alert/public-api.ts b/components/alert/public-api.ts index 7b610705bf8..3a4869a26ed 100644 --- a/components/alert/public-api.ts +++ b/components/alert/public-api.ts @@ -6,5 +6,5 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -export * from './nz-alert.component'; -export * from './nz-alert.module'; +export * from './alert.component'; +export * from './alert.module'; diff --git a/components/alert/style/entry.less b/components/alert/style/entry.less index 06547c43acd..96cebe33bff 100644 --- a/components/alert/style/entry.less +++ b/components/alert/style/entry.less @@ -1 +1,2 @@ @import './index.less'; +@import './patch.less'; diff --git a/components/alert/style/patch.less b/components/alert/style/patch.less new file mode 100644 index 00000000000..1bd7be6dc7a --- /dev/null +++ b/components/alert/style/patch.less @@ -0,0 +1,3 @@ +nz-alert { + display: block; +} diff --git a/components/auto-complete/nz-autocomplete-trigger.directive.ts b/components/auto-complete/nz-autocomplete-trigger.directive.ts index c5268f15e8e..8f24f2758b0 100644 --- a/components/auto-complete/nz-autocomplete-trigger.directive.ts +++ b/components/auto-complete/nz-autocomplete-trigger.directive.ts @@ -205,7 +205,6 @@ export class NzAutocompleteTriggerDirective implements ControlValueAccessor, OnD } handleBlur(): void { - this.closePanel(); this._onTouched(); } diff --git a/components/auto-complete/nz-autocomplete.module.ts b/components/auto-complete/nz-autocomplete.module.ts index 04552e08026..1ce4fc5fda5 100644 --- a/components/auto-complete/nz-autocomplete.module.ts +++ b/components/auto-complete/nz-autocomplete.module.ts @@ -11,7 +11,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { NzAddOnModule, NzNoAnimationModule } from 'ng-zorro-antd/core'; +import { NzNoAnimationModule, NzOutletModule } from 'ng-zorro-antd/core'; import { NzAutocompleteOptgroupComponent } from './nz-autocomplete-optgroup.component'; import { NzAutocompleteOptionComponent } from './nz-autocomplete-option.component'; @@ -21,6 +21,6 @@ import { NzAutocompleteComponent } from './nz-autocomplete.component'; @NgModule({ declarations: [NzAutocompleteComponent, NzAutocompleteOptionComponent, NzAutocompleteTriggerDirective, NzAutocompleteOptgroupComponent], exports: [NzAutocompleteComponent, NzAutocompleteOptionComponent, NzAutocompleteTriggerDirective, NzAutocompleteOptgroupComponent], - imports: [CommonModule, OverlayModule, FormsModule, NzAddOnModule, NzNoAnimationModule] + imports: [CommonModule, OverlayModule, FormsModule, NzOutletModule, NzNoAnimationModule] }) export class NzAutocompleteModule {} diff --git a/components/auto-complete/nz-autocomplete.spec.ts b/components/auto-complete/nz-autocomplete.spec.ts index de176300a86..4b55fd52e5c 100644 --- a/components/auto-complete/nz-autocomplete.spec.ts +++ b/components/auto-complete/nz-autocomplete.spec.ts @@ -144,18 +144,6 @@ describe('auto-complete', () => { expect(fixture.componentInstance.trigger.panelOpen).toBe(false); })); - it('should close the panel when the trigger blur', fakeAsync(() => { - dispatchFakeEvent(input, 'focusin'); - fixture.detectChanges(); - flush(); - - expect(fixture.componentInstance.trigger.panelOpen).toBe(true); - - dispatchFakeEvent(input, 'blur'); - - expect(fixture.componentInstance.trigger.panelOpen).toBe(false); - })); - it('should not close the panel when the user clicks this input', fakeAsync(() => { dispatchFakeEvent(input, 'focusin'); fixture.detectChanges(); diff --git a/components/badge/nz-badge.module.ts b/components/badge/nz-badge.module.ts index 10ce1e27202..e2740253c8d 100644 --- a/components/badge/nz-badge.module.ts +++ b/components/badge/nz-badge.module.ts @@ -10,13 +10,13 @@ import { ObserversModule } from '@angular/cdk/observers'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule } from 'ng-zorro-antd/core'; +import { NzOutletModule } from 'ng-zorro-antd/core'; import { NzBadgeComponent } from './nz-badge.component'; @NgModule({ declarations: [NzBadgeComponent], exports: [NzBadgeComponent], - imports: [CommonModule, ObserversModule, NzAddOnModule] + imports: [CommonModule, ObserversModule, NzOutletModule] }) export class NzBadgeModule {} diff --git a/components/badge/style/index.less b/components/badge/style/index.less index 698aa3d9065..36deed8298b 100644 --- a/components/badge/style/index.less +++ b/components/badge/style/index.less @@ -190,7 +190,7 @@ display: inline-block; height: @badge-height; transition: all 0.3s @ease-in-out; - > p { + > p.@{number-prefix-cls}-only-unit { height: @badge-height; margin: 0; } diff --git a/components/breadcrumb/nz-breadcrumb.module.ts b/components/breadcrumb/nz-breadcrumb.module.ts index 84ce5eec63e..b0fc68c201e 100644 --- a/components/breadcrumb/nz-breadcrumb.module.ts +++ b/components/breadcrumb/nz-breadcrumb.module.ts @@ -10,7 +10,7 @@ import { OverlayModule } from '@angular/cdk/overlay'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule, NzOverlayModule } from 'ng-zorro-antd/core'; +import { NzOutletModule, NzOverlayModule } from 'ng-zorro-antd/core'; import { NzDropDownModule } from 'ng-zorro-antd/dropdown'; import { NzIconModule } from 'ng-zorro-antd/icon'; @@ -18,7 +18,7 @@ import { NzBreadCrumbItemComponent } from './nz-breadcrumb-item.component'; import { NzBreadCrumbComponent } from './nz-breadcrumb.component'; @NgModule({ - imports: [CommonModule, NzAddOnModule, OverlayModule, NzOverlayModule, NzDropDownModule, NzIconModule], + imports: [CommonModule, NzOutletModule, OverlayModule, NzOverlayModule, NzDropDownModule, NzIconModule], declarations: [NzBreadCrumbComponent, NzBreadCrumbItemComponent], exports: [NzBreadCrumbComponent, NzBreadCrumbItemComponent] }) diff --git a/components/breadcrumb/style/index.less b/components/breadcrumb/style/index.less index 2edaf549c18..20e2cd76071 100644 --- a/components/breadcrumb/style/index.less +++ b/components/breadcrumb/style/index.less @@ -9,6 +9,14 @@ color: @breadcrumb-base-color; font-size: @breadcrumb-font-size; + &-rtl { + direction: rtl; + + > span { + float: right; + } + } + .@{iconfont-css-prefix} { font-size: @breadcrumb-icon-font-size; } @@ -40,12 +48,22 @@ &-link { > .@{iconfont-css-prefix} + span { margin-left: 4px; + + .@{breadcrumb-prefix-cls}-rtl & { + margin-right: 4px; + margin-left: 0; + } } } &-overlay-link { > .@{iconfont-css-prefix} { margin-left: 4px; + + .@{breadcrumb-prefix-cls}-rtl & { + margin-right: 4px; + margin-left: 0; + } } } } diff --git a/components/button/button-group.component.ts b/components/button/button-group.component.ts index 7bd61a9d3d0..eb3e627afc8 100644 --- a/components/button/button-group.component.ts +++ b/components/button/button-group.component.ts @@ -6,8 +6,7 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit, ViewEncapsulation } from '@angular/core'; -import { IndexableObject } from 'ng-zorro-antd/core'; +import { ChangeDetectionStrategy, Component, Input, ViewEncapsulation } from '@angular/core'; export type NzButtonGroupSize = 'large' | 'default' | 'small'; @@ -16,27 +15,16 @@ export type NzButtonGroupSize = 'large' | 'default' | 'small'; exportAs: 'nzButtonGroup', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, - host: { '[class]': 'hostClassMap' }, + host: { + '[class.ant-btn-group]': `true`, + '[class.ant-btn-group-lg]': `nzSize === 'large'`, + '[class.ant-btn-group-sm]': `nzSize === 'small'` + }, preserveWhitespaces: false, template: ` ` }) -export class NzButtonGroupComponent implements OnInit, OnChanges { +export class NzButtonGroupComponent { @Input() nzSize: NzButtonGroupSize = 'default'; - hostClassMap: IndexableObject = {}; - updateHostClassMap(): void { - this.hostClassMap = { - ['ant-btn-group']: true, - ['ant-btn-group-lg']: this.nzSize === 'large', - ['ant-btn-group-sm']: this.nzSize === 'small' - }; - } - - ngOnInit(): void { - this.updateHostClassMap(); - } - ngOnChanges(): void { - this.updateHostClassMap(); - } } diff --git a/components/button/button.component.ts b/components/button/button.component.ts index cf3d1777f72..a8621ada4ab 100644 --- a/components/button/button.component.ts +++ b/components/button/button.component.ts @@ -17,13 +17,12 @@ import { Input, OnChanges, OnDestroy, - OnInit, Renderer2, SimpleChanges, ViewEncapsulation } from '@angular/core'; -import { IndexableObject, InputBoolean, NzConfigService, WithConfig } from 'ng-zorro-antd/core'; +import { InputBoolean, NzConfigService, WithConfig } from 'ng-zorro-antd/core'; import { NzIconDirective } from 'ng-zorro-antd/icon'; import { Subject } from 'rxjs'; import { filter, startWith, takeUntil } from 'rxjs/operators'; @@ -40,13 +39,28 @@ const NZ_CONFIG_COMPONENT_NAME = 'button'; preserveWhitespaces: false, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, - host: { '[class]': 'hostClassMap' }, template: ` - ` + `, + host: { + '[class.ant-btn]': `true`, + '[class.ant-btn-primary]': `nzType === 'primary'`, + '[class.ant-btn-dashed]': `nzType === 'dashed'`, + '[class.ant-btn-link]': `nzType === 'link'`, + '[class.ant-btn-danger]': `nzType === 'danger'`, + '[class.ant-btn-circle]': `nzShape === 'circle'`, + '[class.ant-btn-round]': `nzShape === 'round'`, + '[class.ant-btn-lg]': `nzSize === 'large'`, + '[class.ant-btn-sm]': `nzSize === 'small'`, + '[class.ant-btn-dangerous]': `nzDanger`, + '[class.ant-btn-loading]': `nzLoading`, + '[class.ant-btn-background-ghost]': `nzGhost`, + '[class.ant-btn-block]': `nzBlock`, + '[class.ant-input-search-button]': `nzSearch` + } }) -export class NzButtonComponent implements OnInit, OnDestroy, OnChanges, AfterViewInit, AfterContentInit { +export class NzButtonComponent implements OnDestroy, OnChanges, AfterViewInit, AfterContentInit { @ContentChild(NzIconDirective, { read: ElementRef }) nzIconDirectiveElement: ElementRef; @Input() @InputBoolean() nzBlock: boolean = false; @Input() @InputBoolean() nzGhost: boolean = false; @@ -58,27 +72,6 @@ export class NzButtonComponent implements OnInit, OnDestroy, OnChanges, AfterVie @Input() @WithConfig(NZ_CONFIG_COMPONENT_NAME, 'default') nzSize: NzButtonSize; private destroy$ = new Subject(); private loading$ = new Subject(); - hostClassMap: IndexableObject = {}; - - updateHostClassMap(): void { - this.hostClassMap = { - ['ant-btn']: true, - ['ant-btn-primary']: this.nzType === 'primary', - ['ant-btn-dashed']: this.nzType === 'dashed', - ['ant-btn-link']: this.nzType === 'link', - ['ant-btn-danger']: this.nzType === 'danger', - ['ant-btn-circle']: this.nzShape === 'circle', - ['ant-btn-round']: this.nzShape === 'round', - ['ant-btn-lg']: this.nzSize === 'large', - ['ant-btn-sm']: this.nzSize === 'small', - ['ant-btn-dangerous']: this.nzDanger, - ['ant-btn-loading']: this.nzLoading, - ['ant-btn-background-ghost']: this.nzGhost, - ['ant-btn-block']: this.nzBlock, - ['ant-input-search-button']: this.nzSearch - }; - this.cdr.markForCheck(); - } insertSpan(nodes: NodeList, renderer: Renderer2): void { nodes.forEach(node => { @@ -101,16 +94,11 @@ export class NzButtonComponent implements OnInit, OnDestroy, OnChanges, AfterVie .getConfigChangeEventForComponent(NZ_CONFIG_COMPONENT_NAME) .pipe(takeUntil(this.destroy$)) .subscribe(() => { - this.updateHostClassMap(); + this.cdr.markForCheck(); }); } - ngOnInit(): void { - this.updateHostClassMap(); - } - ngOnChanges(changes: SimpleChanges): void { - this.updateHostClassMap(); const { nzLoading } = changes; if (nzLoading) { this.loading$.next(this.nzLoading); diff --git a/components/button/button.module.ts b/components/button/button.module.ts index 370d0f4cd67..ad7ba37243f 100644 --- a/components/button/button.module.ts +++ b/components/button/button.module.ts @@ -9,7 +9,8 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzWaveModule, ɵNzTransitionPatchModule as NzTransitionPatchModule } from 'ng-zorro-antd/core'; +import { NzWaveModule } from 'ng-zorro-antd/core'; +import { ɵNzTransitionPatchModule as NzTransitionPatchModule } from 'ng-zorro-antd/core/transition-patch'; import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzButtonGroupComponent } from './button-group.component'; import { NzButtonComponent } from './button.component'; diff --git a/components/button/style/index.less b/components/button/style/index.less index d68f06986f9..f0d345307a3 100644 --- a/components/button/style/index.less +++ b/components/button/style/index.less @@ -15,12 +15,11 @@ // Fixing https://github.com/ant-design/ant-design/issues/12978 // Fixing https://github.com/ant-design/ant-design/issues/20058 // Fixing https://github.com/ant-design/ant-design/issues/19972 - // Fixing https://github.com/ant-design/ant-design/issues/12978 // Fixing https://github.com/ant-design/ant-design/issues/18107 // Fixing https://github.com/ant-design/ant-design/issues/13214 // It is a render problem of chrome, which is only happened in the codesandbox demo // 0.001px solution works and I don't why - line-height: @line-height-base - 0.001; + line-height: @line-height-base; .btn; .btn-default; diff --git a/components/button/style/mixin.less b/components/button/style/mixin.less index b316f0f79ab..a009493cee7 100644 --- a/components/button/style/mixin.less +++ b/components/button/style/mixin.less @@ -1,8 +1,11 @@ // mixins for button // ------------------------ -.button-size(@height; @padding; @font-size; @border-radius) { +.button-size(@height; @padding-horizontal; @font-size; @border-radius) { + @padding-vertical: round((@height - @font-size * @line-height-base) / 2 * 10) / 10 - + @border-width-base; + height: @height; - padding: @padding; + padding: @padding-vertical @padding-horizontal; font-size: @font-size; border-radius: @border-radius; } @@ -181,7 +184,7 @@ // size &-lg > .@{btnClassName}, &-lg > span > .@{btnClassName} { - .button-size(@btn-height-lg; @btn-padding-lg; @btn-font-size-lg; 0); + .button-size(@btn-height-lg; @btn-padding-horizontal-lg; @btn-font-size-lg; 0); line-height: @btn-height-lg - 2px; } &-lg > .@{btnClassName}.@{btnClassName}-icon-only { @@ -191,7 +194,7 @@ } &-sm > .@{btnClassName}, &-sm > span > .@{btnClassName} { - .button-size(@btn-height-sm; @btn-padding-sm; @font-size-base; 0); + .button-size(@btn-height-sm; @btn-padding-horizontal-sm; @font-size-base; 0); line-height: @btn-height-sm - 2px; > .@{iconfont-css-prefix} { font-size: @font-size-base; @@ -218,7 +221,9 @@ transition: all 0.3s @ease-in-out; user-select: none; touch-action: manipulation; - .button-size(@btn-height-base; @btn-padding-base; @font-size-base; @btn-border-radius-base); + .button-size( + @btn-height-base; @btn-padding-horizontal-base; @font-size-base; @btn-border-radius-base + ); > .@{iconfont-css-prefix} { line-height: 1; } @@ -242,10 +247,14 @@ } } &-lg { - .button-size(@btn-height-lg; @btn-padding-lg; @btn-font-size-lg; @btn-border-radius-base); + .button-size( + @btn-height-lg; @btn-padding-horizontal-lg; @btn-font-size-lg; @btn-border-radius-base + ); } &-sm { - .button-size(@btn-height-sm; @btn-padding-sm; @btn-font-size-sm; @btn-border-radius-sm); + .button-size( + @btn-height-sm; @btn-padding-horizontal-sm; @btn-font-size-sm; @btn-border-radius-sm + ); } } // primary button style @@ -347,15 +356,15 @@ } // round button .btn-round(@btnClassName: btn) { - .button-size(@btn-circle-size; 0 @btn-circle-size / 2; @font-size-base; @btn-circle-size); + .button-size(@btn-circle-size; @btn-circle-size / 2; @font-size-base; @btn-circle-size); &.@{btnClassName}-lg { .button-size( - @btn-circle-size-lg; 0 @btn-circle-size-lg / 2; @btn-font-size-lg; @btn-circle-size-lg + @btn-circle-size-lg; @btn-circle-size-lg / 2; @btn-font-size-lg; @btn-circle-size-lg ); } &.@{btnClassName}-sm { .button-size( - @btn-circle-size-sm; 0 @btn-circle-size-sm / 2; @font-size-base; @btn-circle-size-sm + @btn-circle-size-sm; @btn-circle-size-sm / 2; @font-size-base; @btn-circle-size-sm ); } } diff --git a/components/card/nz-card-grid.directive.ts b/components/card/card-grid.directive.ts similarity index 63% rename from components/card/nz-card-grid.directive.ts rename to components/card/card-grid.directive.ts index d8a3c93cd75..75c09dea176 100644 --- a/components/card/nz-card-grid.directive.ts +++ b/components/card/card-grid.directive.ts @@ -6,19 +6,17 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -import { Directive, ElementRef, Input, Renderer2 } from '@angular/core'; +import { Directive, Input } from '@angular/core'; import { InputBoolean } from 'ng-zorro-antd/core'; @Directive({ selector: '[nz-card-grid]', exportAs: 'nzCardGrid', host: { + '[class.ant-card-grid]': 'true', '[class.ant-card-hoverable]': 'nzHoverable' } }) export class NzCardGridDirective { - @Input() @InputBoolean() nzHoverable: boolean = true; - constructor(elementRef: ElementRef, renderer: Renderer2) { - renderer.addClass(elementRef.nativeElement, 'ant-card-grid'); - } + @Input() @InputBoolean() nzHoverable = true; } diff --git a/components/card/card-loading.component.ts b/components/card/card-loading.component.ts new file mode 100644 index 00000000000..2d33bf48fac --- /dev/null +++ b/components/card/card-loading.component.ts @@ -0,0 +1,40 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core'; + +@Component({ + selector: 'nz-card-loading', + exportAs: 'nzCardLoading', + template: ` +
+
+
+
+
+
+
+ `, + preserveWhitespaces: false, + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, + host: { + '[class.ant-card-loading-content]': 'true' + } +}) +export class NzCardLoadingComponent { + listOfLoading: string[][] = [ + ['ant-col-22'], + ['ant-col-8', 'ant-col-15'], + ['ant-col-6', 'ant-col-18'], + ['ant-col-13', 'ant-col-9'], + ['ant-col-4', 'ant-col-3', 'ant-col-16'], + ['ant-col-8', 'ant-col-6', 'ant-col-8'] + ]; + constructor() {} +} diff --git a/components/card/card-meta.component.ts b/components/card/card-meta.component.ts new file mode 100755 index 00000000000..3e74da29ac1 --- /dev/null +++ b/components/card/card-meta.component.ts @@ -0,0 +1,38 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { ChangeDetectionStrategy, Component, Input, TemplateRef, ViewEncapsulation } from '@angular/core'; + +@Component({ + selector: 'nz-card-meta', + exportAs: 'nzCardMeta', + preserveWhitespaces: false, + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, + template: ` +
+ +
+
+
+ {{ nzTitle }} +
+
+ {{ nzDescription }} +
+
+ `, + host: { + '[class.ant-card-meta]': 'true' + } +}) +export class NzCardMetaComponent { + @Input() nzTitle: string | TemplateRef | null = null; + @Input() nzDescription: string | TemplateRef | null = null; + @Input() nzAvatar: TemplateRef | null = null; +} diff --git a/components/card/nz-card-tab.component.ts b/components/card/card-tab.component.ts similarity index 87% rename from components/card/nz-card-tab.component.ts rename to components/card/card-tab.component.ts index 2cf76b88b90..ad417d620f8 100644 --- a/components/card/nz-card-tab.component.ts +++ b/components/card/card-tab.component.ts @@ -13,7 +13,11 @@ import { ChangeDetectionStrategy, Component, TemplateRef, ViewChild, ViewEncapsu exportAs: 'nzCardTab', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, - templateUrl: './nz-card-tab.component.html' + template: ` + + + + ` }) export class NzCardTabComponent { @ViewChild(TemplateRef, { static: true }) template: TemplateRef; diff --git a/components/card/card.component.ts b/components/card/card.component.ts new file mode 100755 index 00000000000..d42a85f58f4 --- /dev/null +++ b/components/card/card.component.ts @@ -0,0 +1,104 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ContentChild, + ContentChildren, + Input, + OnDestroy, + QueryList, + TemplateRef, + ViewEncapsulation +} from '@angular/core'; +import { InputBoolean, NzConfigService, NzSizeDSType, WithConfig } from 'ng-zorro-antd/core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { NzCardGridDirective } from './card-grid.directive'; +import { NzCardTabComponent } from './card-tab.component'; + +const NZ_CONFIG_COMPONENT_NAME = 'card'; + +@Component({ + selector: 'nz-card', + exportAs: 'nzCard', + preserveWhitespaces: false, + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, + template: ` +
+
+
+ {{ nzTitle }} +
+
+ {{ nzExtra }} +
+
+ + + +
+
+ +
+
+ + + + + + +
+
    +
  • + +
  • +
+ `, + host: { + '[class.ant-card]': 'true', + '[class.ant-card-loading]': 'nzLoading', + '[class.ant-card-bordered]': 'nzBordered', + '[class.ant-card-hoverable]': 'nzHoverable', + '[class.ant-card-small]': 'nzSize === "small"', + '[class.ant-card-contain-grid]': 'listOfNzCardGridDirective && listOfNzCardGridDirective.length', + '[class.ant-card-type-inner]': 'nzType === "inner"', + '[class.ant-card-contain-tabs]': '!!listOfNzCardTabComponent' + } +}) +export class NzCardComponent implements OnDestroy { + @Input() @WithConfig(NZ_CONFIG_COMPONENT_NAME, true) @InputBoolean() nzBordered: boolean; + @Input() @InputBoolean() nzLoading = false; + @Input() @WithConfig(NZ_CONFIG_COMPONENT_NAME, false) @InputBoolean() nzHoverable: boolean; + @Input() nzBodyStyle: { [key: string]: string }; + @Input() nzCover: TemplateRef; + @Input() nzActions: Array> = []; + @Input() nzType: string | 'inner' | null = null; + @Input() @WithConfig(NZ_CONFIG_COMPONENT_NAME, 'default') nzSize: NzSizeDSType; + @Input() nzTitle: string | TemplateRef; + @Input() nzExtra: string | TemplateRef; + @ContentChild(NzCardTabComponent, { static: false }) listOfNzCardTabComponent: NzCardTabComponent; + @ContentChildren(NzCardGridDirective) listOfNzCardGridDirective: QueryList; + private destroy$ = new Subject(); + + constructor(public nzConfigService: NzConfigService, private cdr: ChangeDetectorRef) { + this.nzConfigService + .getConfigChangeEventForComponent(NZ_CONFIG_COMPONENT_NAME) + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + this.cdr.markForCheck(); + }); + } + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/components/card/nz-card.module.ts b/components/card/card.module.ts similarity index 59% rename from components/card/nz-card.module.ts rename to components/card/card.module.ts index 6ce4f7b4960..55a0d498f91 100644 --- a/components/card/nz-card.module.ts +++ b/components/card/card.module.ts @@ -8,16 +8,16 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule } from 'ng-zorro-antd/core'; +import { NzOutletModule } from 'ng-zorro-antd/core'; -import { NzCardGridDirective } from './nz-card-grid.directive'; -import { NzCardLoadingComponent } from './nz-card-loading.component'; -import { NzCardMetaComponent } from './nz-card-meta.component'; -import { NzCardTabComponent } from './nz-card-tab.component'; -import { NzCardComponent } from './nz-card.component'; +import { NzCardGridDirective } from './card-grid.directive'; +import { NzCardLoadingComponent } from './card-loading.component'; +import { NzCardMetaComponent } from './card-meta.component'; +import { NzCardTabComponent } from './card-tab.component'; +import { NzCardComponent } from './card.component'; @NgModule({ - imports: [CommonModule, NzAddOnModule], + imports: [CommonModule, NzOutletModule], declarations: [NzCardComponent, NzCardGridDirective, NzCardMetaComponent, NzCardLoadingComponent, NzCardTabComponent], exports: [NzCardComponent, NzCardGridDirective, NzCardMetaComponent, NzCardLoadingComponent, NzCardTabComponent] }) diff --git a/components/card/nz-card.spec.ts b/components/card/card.spec.ts similarity index 98% rename from components/card/nz-card.spec.ts rename to components/card/card.spec.ts index 7acf6bba894..8d11916a6fe 100644 --- a/components/card/nz-card.spec.ts +++ b/components/card/card.spec.ts @@ -2,6 +2,9 @@ import { Component, NO_ERRORS_SCHEMA } from '@angular/core'; import { fakeAsync, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; +import { NzCardComponent } from './card.component'; +import { NzCardModule } from './card.module'; + import { NzDemoCardBasicComponent } from './demo/basic'; import { NzDemoCardBorderLessComponent } from './demo/border-less'; import { NzDemoCardFlexibleContentComponent } from './demo/flexible-content'; @@ -13,9 +16,6 @@ import { NzDemoCardMetaComponent } from './demo/meta'; import { NzDemoCardSimpleComponent } from './demo/simple'; import { NzDemoCardTabsComponent } from './demo/tabs'; -import { NzCardComponent } from './nz-card.component'; -import { NzCardModule } from './nz-card.module'; - describe('card', () => { beforeEach(fakeAsync(() => { TestBed.configureTestingModule({ diff --git a/components/card/nz-card-loading.component.html b/components/card/nz-card-loading.component.html deleted file mode 100644 index 320ff848c75..00000000000 --- a/components/card/nz-card-loading.component.html +++ /dev/null @@ -1,53 +0,0 @@ -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/components/card/nz-card-loading.component.ts b/components/card/nz-card-loading.component.ts deleted file mode 100644 index bd5e7e2135a..00000000000 --- a/components/card/nz-card-loading.component.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { ChangeDetectionStrategy, Component, ElementRef, Renderer2, ViewEncapsulation } from '@angular/core'; - -@Component({ - selector: 'nz-card-loading', - exportAs: 'nzCardLoading', - templateUrl: './nz-card-loading.component.html', - preserveWhitespaces: false, - changeDetection: ChangeDetectionStrategy.OnPush, - encapsulation: ViewEncapsulation.None, - styles: [ - ` - nz-card-loading { - display: block; - } - ` - ] -}) -export class NzCardLoadingComponent { - constructor(elementRef: ElementRef, renderer: Renderer2) { - renderer.addClass(elementRef.nativeElement, 'ant-card-loading-content'); - } -} diff --git a/components/card/nz-card-meta.component.html b/components/card/nz-card-meta.component.html deleted file mode 100755 index b2e1d07edd7..00000000000 --- a/components/card/nz-card-meta.component.html +++ /dev/null @@ -1,11 +0,0 @@ -
- -
-
-
- {{ nzTitle }} -
-
- {{ nzDescription }} -
-
diff --git a/components/card/nz-card-meta.component.ts b/components/card/nz-card-meta.component.ts deleted file mode 100755 index 1f6dfe9b8b7..00000000000 --- a/components/card/nz-card-meta.component.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { ChangeDetectionStrategy, Component, ElementRef, Input, Renderer2, TemplateRef, ViewEncapsulation } from '@angular/core'; - -@Component({ - selector: 'nz-card-meta', - exportAs: 'nzCardMeta', - preserveWhitespaces: false, - changeDetection: ChangeDetectionStrategy.OnPush, - encapsulation: ViewEncapsulation.None, - templateUrl: './nz-card-meta.component.html', - styles: [ - ` - nz-card-meta { - display: block; - } - ` - ] -}) -export class NzCardMetaComponent { - @Input() nzTitle: string | TemplateRef; - @Input() nzDescription: string | TemplateRef; - @Input() nzAvatar: TemplateRef; - - constructor(elementRef: ElementRef, renderer: Renderer2) { - renderer.addClass(elementRef.nativeElement, 'ant-card-meta'); - } -} diff --git a/components/card/nz-card-tab.component.html b/components/card/nz-card-tab.component.html deleted file mode 100644 index a4bd3d8f63e..00000000000 --- a/components/card/nz-card-tab.component.html +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/components/card/nz-card.component.html b/components/card/nz-card.component.html deleted file mode 100755 index 2fc87dd1655..00000000000 --- a/components/card/nz-card.component.html +++ /dev/null @@ -1,27 +0,0 @@ -
-
-
- {{ nzTitle }} -
-
- {{ nzExtra }} -
-
- - - -
-
- -
-
- - - - -
-
    -
  • - -
  • -
diff --git a/components/card/nz-card.component.ts b/components/card/nz-card.component.ts deleted file mode 100755 index f1d98aa4d9f..00000000000 --- a/components/card/nz-card.component.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { - ChangeDetectionStrategy, - Component, - ContentChild, - ContentChildren, - ElementRef, - Input, - QueryList, - Renderer2, - TemplateRef, - ViewEncapsulation -} from '@angular/core'; -import { InputBoolean, NzConfigService, NzSizeDSType, WithConfig } from 'ng-zorro-antd/core'; -import { NzCardGridDirective } from './nz-card-grid.directive'; -import { NzCardTabComponent } from './nz-card-tab.component'; - -const NZ_CONFIG_COMPONENT_NAME = 'card'; - -@Component({ - selector: 'nz-card', - exportAs: 'nzCard', - preserveWhitespaces: false, - changeDetection: ChangeDetectionStrategy.OnPush, - encapsulation: ViewEncapsulation.None, - templateUrl: './nz-card.component.html', - styles: [ - ` - nz-card { - display: block; - } - ` - ], - host: { - '[class.ant-card-loading]': 'nzLoading', - '[class.ant-card-bordered]': 'nzBordered', - '[class.ant-card-hoverable]': 'nzHoverable', - '[class.ant-card-small]': 'nzSize === "small"', - '[class.ant-card-contain-grid]': 'grids && grids.length', - '[class.ant-card-type-inner]': 'nzType === "inner"', - '[class.ant-card-contain-tabs]': '!!tab' - } -}) -export class NzCardComponent { - @Input() @WithConfig(NZ_CONFIG_COMPONENT_NAME, true) @InputBoolean() nzBordered: boolean; - @Input() @InputBoolean() nzLoading = false; - @Input() @WithConfig(NZ_CONFIG_COMPONENT_NAME, false) @InputBoolean() nzHoverable: boolean; - @Input() nzBodyStyle: { [key: string]: string }; - @Input() nzCover: TemplateRef; - @Input() nzActions: Array> = []; - @Input() nzType: string; - @Input() @WithConfig(NZ_CONFIG_COMPONENT_NAME, 'default') nzSize: NzSizeDSType; - @Input() nzTitle: string | TemplateRef; - @Input() nzExtra: string | TemplateRef; - @ContentChild(NzCardTabComponent, { static: false }) tab: NzCardTabComponent; - @ContentChildren(NzCardGridDirective) grids: QueryList; - - constructor(public nzConfigService: NzConfigService, renderer: Renderer2, elementRef: ElementRef) { - renderer.addClass(elementRef.nativeElement, 'ant-card'); - } -} diff --git a/components/card/public-api.ts b/components/card/public-api.ts index 87c2569872e..f9da2af34c4 100644 --- a/components/card/public-api.ts +++ b/components/card/public-api.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -export * from './nz-card-grid.directive'; -export * from './nz-card.component'; -export * from './nz-card.module'; -export * from './nz-card-loading.component'; -export * from './nz-card-meta.component'; -export * from './nz-card-tab.component'; +export * from './card-grid.directive'; +export * from './card.component'; +export * from './card.module'; +export * from './card-loading.component'; +export * from './card-meta.component'; +export * from './card-tab.component'; diff --git a/components/card/style/entry.less b/components/card/style/entry.less index 8929c6bd8f0..dec11131b7f 100644 --- a/components/card/style/entry.less +++ b/components/card/style/entry.less @@ -1,4 +1,5 @@ @import './index.less'; // style dependencies @import '../../tabs/style/index.less'; -@import '../../grid/style/index.less'; \ No newline at end of file +@import '../../grid/style/index.less'; +@import './patch.less'; diff --git a/components/card/style/index.less b/components/card/style/index.less index 53683775700..444ba7ba23c 100644 --- a/components/card/style/index.less +++ b/components/card/style/index.less @@ -166,7 +166,7 @@ position: relative; display: block; min-width: 32px; - font-size: 14px; + font-size: @font-size-base; line-height: 22px; cursor: pointer; diff --git a/components/card/style/index.tsx b/components/card/style/index.tsx deleted file mode 100644 index b3e369978a6..00000000000 --- a/components/card/style/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import '../../style/index.less'; -import './index.less'; - -// style dependencies -import '../../tabs/style'; diff --git a/components/card/style/patch.less b/components/card/style/patch.less new file mode 100644 index 00000000000..7c57b31052c --- /dev/null +++ b/components/card/style/patch.less @@ -0,0 +1,9 @@ +nz-card { + display: block; +} +nz-card-meta { + display: block; +} +nz-card-loading { + display: block; +} diff --git a/components/cascader/nz-cascader.module.ts b/components/cascader/nz-cascader.module.ts index 1d0ecc9dffa..a6ea360b5a9 100644 --- a/components/cascader/nz-cascader.module.ts +++ b/components/cascader/nz-cascader.module.ts @@ -11,7 +11,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { NzAddOnModule, NzHighlightModule, NzNoAnimationModule, NzOverlayModule } from 'ng-zorro-antd/core'; +import { NzHighlightModule, NzNoAnimationModule, NzOutletModule, NzOverlayModule } from 'ng-zorro-antd/core'; import { NzEmptyModule } from 'ng-zorro-antd/empty'; import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzInputModule } from 'ng-zorro-antd/input'; @@ -24,7 +24,7 @@ import { NzCascaderComponent } from './nz-cascader.component'; CommonModule, FormsModule, OverlayModule, - NzAddOnModule, + NzOutletModule, NzEmptyModule, NzHighlightModule, NzIconModule, diff --git a/components/cascader/style/index.less b/components/cascader/style/index.less index b99938e9f22..5d54d9ebe76 100644 --- a/components/cascader/style/index.less +++ b/components/cascader/style/index.less @@ -59,6 +59,11 @@ .active; } + &-borderless .@{cascader-prefix-cls}-input { + border-color: transparent !important; + box-shadow: none !important; + } + &-show-search&-focused { color: @disabled-color; } diff --git a/components/checkbox/nz-checkbox-group.component.ts b/components/checkbox/checkbox-group.component.ts similarity index 63% rename from components/checkbox/nz-checkbox-group.component.ts rename to components/checkbox/checkbox-group.component.ts index 36b06777eb3..660f23cae19 100644 --- a/components/checkbox/nz-checkbox-group.component.ts +++ b/components/checkbox/checkbox-group.component.ts @@ -7,20 +7,10 @@ */ import { FocusMonitor } from '@angular/cdk/a11y'; -import { - ChangeDetectorRef, - Component, - ElementRef, - forwardRef, - Input, - OnDestroy, - OnInit, - Renderer2, - ViewEncapsulation -} from '@angular/core'; +import { ChangeDetectorRef, Component, ElementRef, forwardRef, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; - import { InputBoolean } from 'ng-zorro-antd/core'; +import { OnChangeType, OnTouchedType } from 'ng-zorro-antd/core/types'; export interface NzCheckBoxOptionInterface { label: string; @@ -34,34 +24,40 @@ export interface NzCheckBoxOptionInterface { exportAs: 'nzCheckboxGroup', preserveWhitespaces: false, encapsulation: ViewEncapsulation.None, - templateUrl: './nz-checkbox-group.component.html', + template: ` + + `, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NzCheckboxGroupComponent), multi: true } - ] + ], + host: { + '[class.ant-checkbox-group]': 'true' + } }) export class NzCheckboxGroupComponent implements ControlValueAccessor, OnInit, OnDestroy { - // tslint:disable-next-line:no-any - onChange: (value: any) => void = () => null; - // tslint:disable-next-line:no-any - onTouched: () => any = () => null; + onChange: OnChangeType = () => {}; + onTouched: OnTouchedType = () => {}; options: NzCheckBoxOptionInterface[] = []; @Input() @InputBoolean() nzDisabled = false; - onOptionChange(): void { - this.onChange(this.options); - } - - trackByOption(_index: number, option: NzCheckBoxOptionInterface): string { + trackByOption(_: number, option: NzCheckBoxOptionInterface): string { return option.value; } - constructor(private elementRef: ElementRef, private focusMonitor: FocusMonitor, private cdr: ChangeDetectorRef, renderer: Renderer2) { - renderer.addClass(elementRef.nativeElement, 'ant-checkbox-group'); - } + constructor(private elementRef: ElementRef, private focusMonitor: FocusMonitor, private cdr: ChangeDetectorRef) {} ngOnInit(): void { this.focusMonitor.monitor(this.elementRef, true).subscribe(focusOrigin => { @@ -80,16 +76,16 @@ export class NzCheckboxGroupComponent implements ControlValueAccessor, OnInit, O this.cdr.markForCheck(); } - registerOnChange(fn: (_: NzCheckBoxOptionInterface[]) => {}): void { + registerOnChange(fn: OnChangeType): void { this.onChange = fn; } - registerOnTouched(fn: () => {}): void { + registerOnTouched(fn: OnTouchedType): void { this.onTouched = fn; } - setDisabledState(isDisabled: boolean): void { - this.nzDisabled = isDisabled; + setDisabledState(disabled: boolean): void { + this.nzDisabled = disabled; this.cdr.markForCheck(); } } diff --git a/components/checkbox/nz-checkbox-wrapper.component.ts b/components/checkbox/checkbox-wrapper.component.ts similarity index 73% rename from components/checkbox/nz-checkbox-wrapper.component.ts rename to components/checkbox/checkbox-wrapper.component.ts index 3f69a586617..f4d5f40b0ac 100644 --- a/components/checkbox/nz-checkbox-wrapper.component.ts +++ b/components/checkbox/checkbox-wrapper.component.ts @@ -7,8 +7,8 @@ */ import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Output, Renderer2, ViewEncapsulation } from '@angular/core'; - -import { NzCheckboxComponent } from './nz-checkbox.component'; +import { NzSafeAny } from 'ng-zorro-antd/core/types'; +import { NzCheckboxComponent } from './checkbox.component'; @Component({ selector: 'nz-checkbox-wrapper', @@ -16,10 +16,12 @@ import { NzCheckboxComponent } from './nz-checkbox.component'; preserveWhitespaces: false, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, - templateUrl: './nz-checkbox-wrapper.component.html' + template: ` + + ` }) export class NzCheckboxWrapperComponent { - @Output() readonly nzOnChange = new EventEmitter(); + @Output() readonly nzOnChange = new EventEmitter(); private checkboxList: NzCheckboxComponent[] = []; addCheckbox(value: NzCheckboxComponent): void { @@ -30,13 +32,9 @@ export class NzCheckboxWrapperComponent { this.checkboxList.splice(this.checkboxList.indexOf(value), 1); } - outputValue(): string[] { - const checkedList = this.checkboxList.filter(item => item.nzChecked); - return checkedList.map(item => item.nzValue); - } - onChange(): void { - this.nzOnChange.emit(this.outputValue()); + const listOfCheckedValue = this.checkboxList.filter(item => item.nzChecked).map(item => item.nzValue); + this.nzOnChange.emit(listOfCheckedValue); } constructor(renderer: Renderer2, elementRef: ElementRef) { diff --git a/components/checkbox/nz-checkbox.component.ts b/components/checkbox/checkbox.component.ts similarity index 65% rename from components/checkbox/nz-checkbox.component.ts rename to components/checkbox/checkbox.component.ts index b0cd3cc6a99..7984ad14231 100644 --- a/components/checkbox/nz-checkbox.component.ts +++ b/components/checkbox/checkbox.component.ts @@ -16,21 +16,17 @@ import { EventEmitter, forwardRef, Input, - OnChanges, OnDestroy, OnInit, Optional, Output, - Renderer2, - SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; - -import { InputBoolean, isEmpty } from 'ng-zorro-antd/core'; - -import { NzCheckboxWrapperComponent } from './nz-checkbox-wrapper.component'; +import { InputBoolean } from 'ng-zorro-antd/core'; +import { NzSafeAny, OnChangeType, OnTouchedType } from 'ng-zorro-antd/core/types'; +import { NzCheckboxWrapperComponent } from './checkbox-wrapper.component'; @Component({ selector: '[nz-checkbox]', @@ -38,7 +34,28 @@ import { NzCheckboxWrapperComponent } from './nz-checkbox-wrapper.component'; preserveWhitespaces: false, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, - templateUrl: './nz-checkbox.component.html', + template: ` + + + + + + `, providers: [ { provide: NG_VALUE_ACCESSOR, @@ -47,18 +64,17 @@ import { NzCheckboxWrapperComponent } from './nz-checkbox-wrapper.component'; } ], host: { + '[class.ant-checkbox-wrapper]': 'true', + '[class.ant-checkbox-wrapper-checked]': 'nzChecked', '(click)': 'hostClick($event)' } }) -export class NzCheckboxComponent implements OnInit, ControlValueAccessor, OnChanges, AfterViewInit, OnDestroy { - // tslint:disable-next-line:no-any - onChange: (value: any) => void = () => null; - // tslint:disable-next-line:no-any - onTouched: () => any = () => null; +export class NzCheckboxComponent implements OnInit, ControlValueAccessor, OnDestroy, AfterViewInit { + onChange: OnChangeType = () => {}; + onTouched: OnTouchedType = () => {}; @ViewChild('inputElement', { static: true }) private inputElement: ElementRef; - @ViewChild('contentElement', { static: false }) private contentElement: ElementRef; @Output() readonly nzCheckedChange = new EventEmitter(); - @Input() nzValue: string; + @Input() nzValue: NzSafeAny | null = null; @Input() @InputBoolean() nzAutoFocus = false; @Input() @InputBoolean() nzDisabled = false; @Input() @InputBoolean() nzIndeterminate = false; @@ -81,29 +97,21 @@ export class NzCheckboxComponent implements OnInit, ControlValueAccessor, OnChan } } - updateAutoFocus(): void { - if (this.inputElement && this.nzAutoFocus) { - this.renderer.setAttribute(this.inputElement.nativeElement, 'autofocus', 'autofocus'); - } else { - this.renderer.removeAttribute(this.inputElement.nativeElement, 'autofocus'); - } - } - writeValue(value: boolean): void { this.nzChecked = value; this.cdr.markForCheck(); } - registerOnChange(fn: (_: boolean) => {}): void { + registerOnChange(fn: OnChangeType): void { this.onChange = fn; } - registerOnTouched(fn: () => {}): void { + registerOnTouched(fn: OnTouchedType): void { this.onTouched = fn; } - setDisabledState(isDisabled: boolean): void { - this.nzDisabled = isDisabled; + setDisabledState(disabled: boolean): void { + this.nzDisabled = disabled; this.cdr.markForCheck(); } @@ -115,23 +123,12 @@ export class NzCheckboxComponent implements OnInit, ControlValueAccessor, OnChan this.inputElement.nativeElement.blur(); } - checkContent(): void { - if (isEmpty(this.contentElement.nativeElement)) { - this.renderer.setStyle(this.contentElement.nativeElement, 'display', 'none'); - } else { - this.renderer.removeStyle(this.contentElement.nativeElement, 'display'); - } - } - constructor( private elementRef: ElementRef, - private renderer: Renderer2, @Optional() private nzCheckboxWrapperComponent: NzCheckboxWrapperComponent, private cdr: ChangeDetectorRef, private focusMonitor: FocusMonitor - ) { - renderer.addClass(elementRef.nativeElement, 'ant-checkbox-wrapper'); - } + ) {} ngOnInit(): void { this.focusMonitor.monitor(this.elementRef, true).subscribe(focusOrigin => { @@ -143,16 +140,10 @@ export class NzCheckboxComponent implements OnInit, ControlValueAccessor, OnChan this.nzCheckboxWrapperComponent.addCheckbox(this); } } - - ngOnChanges(changes: SimpleChanges): void { - if (changes.nzAutoFocus) { - this.updateAutoFocus(); - } - } - ngAfterViewInit(): void { - this.updateAutoFocus(); - this.checkContent(); + if (this.nzAutoFocus) { + this.focus(); + } } ngOnDestroy(): void { diff --git a/components/checkbox/nz-checkbox.module.ts b/components/checkbox/checkbox.module.ts similarity index 64% rename from components/checkbox/nz-checkbox.module.ts rename to components/checkbox/checkbox.module.ts index d6754d8d651..ef3b3ee20a3 100644 --- a/components/checkbox/nz-checkbox.module.ts +++ b/components/checkbox/checkbox.module.ts @@ -6,17 +6,16 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -import { ObserversModule } from '@angular/cdk/observers'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { NzCheckboxGroupComponent } from './nz-checkbox-group.component'; -import { NzCheckboxWrapperComponent } from './nz-checkbox-wrapper.component'; -import { NzCheckboxComponent } from './nz-checkbox.component'; +import { NzCheckboxGroupComponent } from './checkbox-group.component'; +import { NzCheckboxWrapperComponent } from './checkbox-wrapper.component'; +import { NzCheckboxComponent } from './checkbox.component'; @NgModule({ - imports: [CommonModule, FormsModule, ObserversModule], + imports: [CommonModule, FormsModule], declarations: [NzCheckboxComponent, NzCheckboxGroupComponent, NzCheckboxWrapperComponent], exports: [NzCheckboxComponent, NzCheckboxGroupComponent, NzCheckboxWrapperComponent] }) diff --git a/components/checkbox/nz-checkbox.spec.ts b/components/checkbox/checkbox.spec.ts similarity index 98% rename from components/checkbox/nz-checkbox.spec.ts rename to components/checkbox/checkbox.spec.ts index 58feef510fd..80368f7b409 100644 --- a/components/checkbox/nz-checkbox.spec.ts +++ b/components/checkbox/checkbox.spec.ts @@ -3,10 +3,10 @@ import { ComponentFixture, fakeAsync, flush, TestBed } from '@angular/core/testi import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; -import { NzCheckboxGroupComponent } from './nz-checkbox-group.component'; -import { NzCheckboxWrapperComponent } from './nz-checkbox-wrapper.component'; -import { NzCheckboxComponent } from './nz-checkbox.component'; -import { NzCheckboxModule } from './nz-checkbox.module'; +import { NzCheckboxGroupComponent } from './checkbox-group.component'; +import { NzCheckboxWrapperComponent } from './checkbox-wrapper.component'; +import { NzCheckboxComponent } from './checkbox.component'; +import { NzCheckboxModule } from './checkbox.module'; describe('checkbox', () => { beforeEach(fakeAsync(() => { diff --git a/components/checkbox/doc/index.en-US.md b/components/checkbox/doc/index.en-US.md index 39cb3239ed7..97f5677339b 100755 --- a/components/checkbox/doc/index.en-US.md +++ b/components/checkbox/doc/index.en-US.md @@ -25,7 +25,7 @@ import { NzCheckboxModule } from 'ng-zorro-antd/checkbox'; | `[nzDisabled]` | Disable checkbox | `boolean` | `false` | | `[ngModel]` | Specifies whether the checkbox is selected, double binding | `boolean` | `false` | | `[nzIndeterminate]` | set the status of indeterminate,only affect the style | `boolean` | `false` | -| `[nzValue]` | use for the callback of `nz-checkbox-wrapper` | `string` | - | +| `[nzValue]` | use for the callback of `nz-checkbox-wrapper` | `any` | - | | `(ngModelChange)` | The callback function that is triggered when the state changes. | `EventEmitter` | - | ### nz-checkbox-group @@ -40,7 +40,7 @@ import { NzCheckboxModule } from 'ng-zorro-antd/checkbox'; | Property | Description | Type | Default | | -------- | ----------- | ---- | ------- | -| `(nzOnChange)` | The callback function that is triggered when the state changes. | `EventEmitter` | - | +| `(nzOnChange)` | The callback function that is triggered when the state changes. | `EventEmitter` | - | ## Methods diff --git a/components/checkbox/doc/index.zh-CN.md b/components/checkbox/doc/index.zh-CN.md index b06ca9a6497..42e9bbbab8b 100755 --- a/components/checkbox/doc/index.zh-CN.md +++ b/components/checkbox/doc/index.zh-CN.md @@ -26,7 +26,7 @@ import { NzCheckboxModule } from 'ng-zorro-antd/checkbox'; | `[nzDisabled]` | 设定 disable 状态 | `boolean` | `false` | | `[ngModel]` | 指定当前是否选中,可双向绑定 | `boolean` | `false` | | `[nzIndeterminate]` | 设置 indeterminate 状态,只负责样式控制 | `boolean` | `false` | -| `[nzValue]` | 仅与 `nz-checkbox-wrapper` 的选中回调配合使用 | `string` | - | +| `[nzValue]` | 仅与 `nz-checkbox-wrapper` 的选中回调配合使用 | `any` | - | | `(ngModelChange)` | 选中变化时回调 | `EventEmitter` | - | ### nz-checkbox-group @@ -42,7 +42,7 @@ import { NzCheckboxModule } from 'ng-zorro-antd/checkbox'; | 参数 | 说明 | 类型 | 默认值 | | --- | --- | --- | --- | -| `(nzOnChange)` | 选中数据变化时的回调 | `EventEmitter` | - | +| `(nzOnChange)` | 选中数据变化时的回调 | `EventEmitter` | - | ## 方法 diff --git a/components/checkbox/nz-checkbox-group.component.html b/components/checkbox/nz-checkbox-group.component.html deleted file mode 100644 index b8a371c919c..00000000000 --- a/components/checkbox/nz-checkbox-group.component.html +++ /dev/null @@ -1,10 +0,0 @@ - diff --git a/components/checkbox/nz-checkbox-wrapper.component.html b/components/checkbox/nz-checkbox-wrapper.component.html deleted file mode 100644 index 6dbc7430638..00000000000 --- a/components/checkbox/nz-checkbox-wrapper.component.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/components/checkbox/nz-checkbox.component.html b/components/checkbox/nz-checkbox.component.html deleted file mode 100644 index 531ff4ec3db..00000000000 --- a/components/checkbox/nz-checkbox.component.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/components/checkbox/public-api.ts b/components/checkbox/public-api.ts index 40fe19351d1..99ae9ea02fc 100644 --- a/components/checkbox/public-api.ts +++ b/components/checkbox/public-api.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -export * from './nz-checkbox.component'; -export * from './nz-checkbox.module'; -export * from './nz-checkbox-group.component'; -export * from './nz-checkbox-wrapper.component'; +export * from './checkbox.component'; +export * from './checkbox.module'; +export * from './checkbox-group.component'; +export * from './checkbox-wrapper.component'; diff --git a/components/checkbox/style/entry.less b/components/checkbox/style/entry.less index 06547c43acd..96cebe33bff 100644 --- a/components/checkbox/style/entry.less +++ b/components/checkbox/style/entry.less @@ -1 +1,2 @@ @import './index.less'; +@import './patch.less'; diff --git a/components/checkbox/style/patch.less b/components/checkbox/style/patch.less new file mode 100644 index 00000000000..b81e7efc76a --- /dev/null +++ b/components/checkbox/style/patch.less @@ -0,0 +1,5 @@ +.ant-checkbox + span { + &:empty { + display: none; + } +} diff --git a/components/collapse/nz-collapse-panel.component.ts b/components/collapse/collapse-panel.component.ts similarity index 56% rename from components/collapse/nz-collapse-panel.component.ts rename to components/collapse/collapse-panel.component.ts index dc4473b3c4f..3e5c212f0f5 100644 --- a/components/collapse/nz-collapse-panel.component.ts +++ b/components/collapse/collapse-panel.component.ts @@ -10,39 +10,51 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - ElementRef, EventEmitter, Host, Input, OnDestroy, OnInit, Output, - Renderer2, TemplateRef, ViewEncapsulation } from '@angular/core'; import { collapseMotion, InputBoolean, NzConfigService, WithConfig } from 'ng-zorro-antd/core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; -import { NzCollapseComponent } from './nz-collapse.component'; +import { NzCollapseComponent } from './collapse.component'; const NZ_CONFIG_COMPONENT_NAME = 'collapsePanel'; @Component({ selector: 'nz-collapse-panel', exportAs: 'nzCollapsePanel', - templateUrl: './nz-collapse-panel.component.html', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, animations: [collapseMotion], - styles: [ - ` - nz-collapse-panel { - display: block; - } - ` - ], + template: ` + +
+
+ +
+
+ `, + host: { + '[class.ant-collapse-item]': 'true', '[class.ant-collapse-no-arrow]': '!nzShowArrow', '[class.ant-collapse-item-active]': 'nzActive', '[class.ant-collapse-item-disabled]': 'nzDisabled' @@ -56,7 +68,7 @@ export class NzCollapsePanelComponent implements OnInit, OnDestroy { @Input() nzHeader: string | TemplateRef; @Input() nzExpandedIcon: string | TemplateRef; @Output() readonly nzActiveChange = new EventEmitter(); - + private destroy$ = new Subject(); clickHeader(): void { if (!this.nzDisabled) { this.nzCollapseComponent.click(this); @@ -70,11 +82,14 @@ export class NzCollapsePanelComponent implements OnInit, OnDestroy { constructor( public nzConfigService: NzConfigService, private cdr: ChangeDetectorRef, - @Host() private nzCollapseComponent: NzCollapseComponent, - elementRef: ElementRef, - renderer: Renderer2 + @Host() private nzCollapseComponent: NzCollapseComponent ) { - renderer.addClass(elementRef.nativeElement, 'ant-collapse-item'); + this.nzConfigService + .getConfigChangeEventForComponent(NZ_CONFIG_COMPONENT_NAME) + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + this.cdr.markForCheck(); + }); } ngOnInit(): void { @@ -82,6 +97,8 @@ export class NzCollapsePanelComponent implements OnInit, OnDestroy { } ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); this.nzCollapseComponent.removePanel(this); } } diff --git a/components/collapse/nz-collapse.component.ts b/components/collapse/collapse.component.ts similarity index 58% rename from components/collapse/nz-collapse.component.ts rename to components/collapse/collapse.component.ts index cbbee93fd1d..055ad16dd43 100644 --- a/components/collapse/nz-collapse.component.ts +++ b/components/collapse/collapse.component.ts @@ -6,34 +6,45 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -import { ChangeDetectionStrategy, Component, Input, ViewEncapsulation } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, ViewEncapsulation } from '@angular/core'; import { InputBoolean, NzConfigService, WithConfig } from 'ng-zorro-antd/core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; -import { NzCollapsePanelComponent } from './nz-collapse-panel.component'; +import { NzCollapsePanelComponent } from './collapse-panel.component'; const NZ_CONFIG_COMPONENT_NAME = 'collapse'; @Component({ selector: 'nz-collapse', exportAs: 'nzCollapse', - templateUrl: './nz-collapse.component.html', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, - styles: [ - ` - nz-collapse { - display: block; - } - ` - ] + template: ` + + `, + host: { + '[class.ant-collapse]': 'true', + '[class.ant-collapse-icon-position-left]': `nzExpandIconPosition === 'left'`, + '[class.ant-collapse-icon-position-right]': `nzExpandIconPosition === 'right'`, + '[class.ant-collapse-borderless]': '!nzBordered' + } }) -export class NzCollapseComponent { - private listOfNzCollapsePanelComponent: NzCollapsePanelComponent[] = []; +export class NzCollapseComponent implements OnDestroy { @Input() @WithConfig(NZ_CONFIG_COMPONENT_NAME, false) @InputBoolean() nzAccordion: boolean; @Input() @WithConfig(NZ_CONFIG_COMPONENT_NAME, true) @InputBoolean() nzBordered: boolean; - - constructor(public nzConfigService: NzConfigService) {} + @Input() nzExpandIconPosition: 'left' | 'right' = 'left'; + private listOfNzCollapsePanelComponent: NzCollapsePanelComponent[] = []; + private destroy$ = new Subject(); + constructor(public nzConfigService: NzConfigService, private cdr: ChangeDetectorRef) { + this.nzConfigService + .getConfigChangeEventForComponent(NZ_CONFIG_COMPONENT_NAME) + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + this.cdr.markForCheck(); + }); + } addPanel(value: NzCollapsePanelComponent): void { this.listOfNzCollapsePanelComponent.push(value); @@ -58,4 +69,8 @@ export class NzCollapseComponent { collapse.nzActive = !collapse.nzActive; collapse.nzActiveChange.emit(collapse.nzActive); } + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } } diff --git a/components/collapse/nz-collapse.module.ts b/components/collapse/collapse.module.ts similarity index 69% rename from components/collapse/nz-collapse.module.ts rename to components/collapse/collapse.module.ts index 97bf028ce74..dc24fffae14 100644 --- a/components/collapse/nz-collapse.module.ts +++ b/components/collapse/collapse.module.ts @@ -8,15 +8,15 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule } from 'ng-zorro-antd/core'; +import { NzOutletModule } from 'ng-zorro-antd/core'; import { NzIconModule } from 'ng-zorro-antd/icon'; -import { NzCollapsePanelComponent } from './nz-collapse-panel.component'; -import { NzCollapseComponent } from './nz-collapse.component'; +import { NzCollapsePanelComponent } from './collapse-panel.component'; +import { NzCollapseComponent } from './collapse.component'; @NgModule({ declarations: [NzCollapsePanelComponent, NzCollapseComponent], exports: [NzCollapsePanelComponent, NzCollapseComponent], - imports: [CommonModule, NzIconModule, NzAddOnModule] + imports: [CommonModule, NzIconModule, NzOutletModule] }) export class NzCollapseModule {} diff --git a/components/collapse/nz-collapse.spec.ts b/components/collapse/collapse.spec.ts similarity index 95% rename from components/collapse/nz-collapse.spec.ts rename to components/collapse/collapse.spec.ts index 0fa72119534..8c86b72b7e2 100644 --- a/components/collapse/nz-collapse.spec.ts +++ b/components/collapse/collapse.spec.ts @@ -3,9 +3,9 @@ import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { NzCollapsePanelComponent } from './nz-collapse-panel.component'; -import { NzCollapseComponent } from './nz-collapse.component'; -import { NzCollapseModule } from './nz-collapse.module'; +import { NzCollapsePanelComponent } from './collapse-panel.component'; +import { NzCollapseComponent } from './collapse.component'; +import { NzCollapseModule } from './collapse.module'; describe('collapse', () => { beforeEach(fakeAsync(() => { @@ -30,15 +30,15 @@ describe('collapse', () => { }); it('should className correct', () => { fixture.detectChanges(); - expect(collapse.nativeElement.firstElementChild!.classList).toContain('ant-collapse'); + expect(collapse.nativeElement!.classList).toContain('ant-collapse'); expect(panels.every(panel => panel.nativeElement.classList.contains('ant-collapse-item'))).toBe(true); }); it('should border work', () => { fixture.detectChanges(); - expect(collapse.nativeElement.firstElementChild!.classList).not.toContain('ant-collapse-borderless'); + expect(collapse.nativeElement!.classList).not.toContain('ant-collapse-borderless'); testComponent.bordered = false; fixture.detectChanges(); - expect(collapse.nativeElement.firstElementChild!.classList).toContain('ant-collapse-borderless'); + expect(collapse.nativeElement!.classList).toContain('ant-collapse-borderless'); }); it('should showArrow work', () => { fixture.detectChanges(); diff --git a/components/collapse/doc/index.en-US.md b/components/collapse/doc/index.en-US.md index 0483e6514f1..594c1c73faf 100755 --- a/components/collapse/doc/index.en-US.md +++ b/components/collapse/doc/index.en-US.md @@ -24,6 +24,7 @@ import { NzCollapseModule } from 'ng-zorro-antd/collapse'; | -------- | ----------- | ---- | ------- | ------------- | | `[nzAccordion]` | Accordion mode | `boolean` | `false`| ✅ | | `[nzBordered]` | Set border style | `boolean` | `true` | ✅ | +| `[nzExpandIconPosition]` | Set expand icon position | `'left' \| 'right'` | `left` | - | ### nz-collapse-panel diff --git a/components/collapse/doc/index.zh-CN.md b/components/collapse/doc/index.zh-CN.md index defd041f4fd..8aa3ccd7d8b 100755 --- a/components/collapse/doc/index.zh-CN.md +++ b/components/collapse/doc/index.zh-CN.md @@ -25,6 +25,7 @@ import { NzCollapseModule } from 'ng-zorro-antd/collapse'; | --- | --- | --- | --- | --- | | `[nzAccordion]` | 是否每次只打开一个tab | `boolean` | `false` | ✅ | | `[nzBordered]` | 是否有边框 | `boolean` | `true` | ✅ | +| `[nzExpandIconPosition]` | 设置图标位置 | `'left' \| 'right'` | `left` | - | ### nz-collapse-panel diff --git a/components/collapse/nz-collapse-panel.component.html b/components/collapse/nz-collapse-panel.component.html deleted file mode 100644 index 9b17d8e33a5..00000000000 --- a/components/collapse/nz-collapse-panel.component.html +++ /dev/null @@ -1,25 +0,0 @@ - -
-
- -
-
diff --git a/components/collapse/nz-collapse.component.html b/components/collapse/nz-collapse.component.html deleted file mode 100644 index a0a0d99ea94..00000000000 --- a/components/collapse/nz-collapse.component.html +++ /dev/null @@ -1,3 +0,0 @@ -
- -
diff --git a/components/collapse/public-api.ts b/components/collapse/public-api.ts index 518faea2c5b..705887f3425 100644 --- a/components/collapse/public-api.ts +++ b/components/collapse/public-api.ts @@ -6,6 +6,6 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -export * from './nz-collapse-panel.component'; -export * from './nz-collapse.component'; -export * from './nz-collapse.module'; +export * from './collapse-panel.component'; +export * from './collapse.component'; +export * from './collapse.module'; diff --git a/components/collapse/style/entry.less b/components/collapse/style/entry.less index 06547c43acd..96cebe33bff 100644 --- a/components/collapse/style/entry.less +++ b/components/collapse/style/entry.less @@ -1 +1,2 @@ @import './index.less'; +@import './patch.less'; diff --git a/components/collapse/style/index.less b/components/collapse/style/index.less index d96f6138259..4354572e50b 100644 --- a/components/collapse/style/index.less +++ b/components/collapse/style/index.less @@ -33,6 +33,7 @@ line-height: 22px; cursor: pointer; transition: all 0.3s; + .clearfix; .@{collapse-prefix-cls}-rtl & { padding: @collapse-header-padding; diff --git a/components/collapse/style/patch.less b/components/collapse/style/patch.less new file mode 100644 index 00000000000..1798a5aecac --- /dev/null +++ b/components/collapse/style/patch.less @@ -0,0 +1,6 @@ +nz-collapse { + display: block; +} +nz-collapse-panel { + display: block; +} diff --git a/components/comment/nz-comment.module.ts b/components/comment/nz-comment.module.ts index 529c6f2544f..f0a89f29934 100644 --- a/components/comment/nz-comment.module.ts +++ b/components/comment/nz-comment.module.ts @@ -9,7 +9,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule } from 'ng-zorro-antd/core'; +import { NzOutletModule } from 'ng-zorro-antd/core'; import { NzCommentActionComponent, @@ -22,7 +22,7 @@ import { NzCommentComponent } from './nz-comment.component'; const NZ_COMMENT_CELLS = [NzCommentAvatarDirective, NzCommentContentDirective, NzCommentActionComponent, NzCommentActionHostDirective]; @NgModule({ - imports: [CommonModule, NzAddOnModule], + imports: [CommonModule, NzOutletModule], exports: [NzCommentComponent, ...NZ_COMMENT_CELLS], declarations: [NzCommentComponent, ...NZ_COMMENT_CELLS] }) diff --git a/components/core/addon/classlist_add.ts b/components/core/addon/classlist_add.ts deleted file mode 100644 index 2e8b5c71736..00000000000 --- a/components/core/addon/classlist_add.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { Directive, ElementRef, Input, Renderer2 } from '@angular/core'; - -@Directive({ - selector: '[nzClassListAdd]', - exportAs: 'nzClassListAdd' -}) -export class NzClassListAddDirective { - classList: string[] = []; - - @Input() - set nzClassListAdd(list: string[]) { - this.classList.forEach(name => { - this.renderer.removeClass(this.elementRef.nativeElement, name); - }); - list.forEach(name => { - this.renderer.addClass(this.elementRef.nativeElement, name); - }); - this.classList = list; - } - - constructor(private elementRef: ElementRef, private renderer: Renderer2) {} -} diff --git a/components/core/addon/string_template_outlet.ts b/components/core/addon/string_template_outlet.ts deleted file mode 100644 index 8f4bf45a039..00000000000 --- a/components/core/addon/string_template_outlet.ts +++ /dev/null @@ -1,120 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { Directive, EmbeddedViewRef, Input, OnChanges, SimpleChange, SimpleChanges, TemplateRef, ViewContainerRef } from '@angular/core'; - -@Directive({ - selector: '[nzStringTemplateOutlet]', - exportAs: 'nzStringTemplateOutlet' -}) -export class NzStringTemplateOutletDirective implements OnChanges { - private isTemplate: boolean; - // tslint:disable-next-line:no-any - private inputTemplate: TemplateRef | null = null; - private inputViewRef: EmbeddedViewRef | null = null; - private defaultViewRef: EmbeddedViewRef | null = null; - - // tslint:disable-next-line:no-any - @Input() nzStringTemplateOutletContext: any | null = null; - - @Input() - // tslint:disable-next-line:no-any - set nzStringTemplateOutlet(value: string | TemplateRef) { - if (value instanceof TemplateRef) { - this.isTemplate = true; - this.inputTemplate = value; - } else { - this.isTemplate = false; - } - } - - recreateView(): void { - if (!this.isTemplate) { - /** use default template when input is string **/ - if (!this.defaultViewRef) { - if (this.defaultTemplate) { - this.defaultViewRef = this.viewContainer.createEmbeddedView(this.defaultTemplate, this.nzStringTemplateOutletContext); - } - } - } else { - /** use input template when input is templateRef **/ - if (!this.inputViewRef) { - if (this.inputTemplate) { - this.inputViewRef = this.viewContainer.createEmbeddedView(this.inputTemplate, this.nzStringTemplateOutletContext); - } - } - } - } - - // tslint:disable-next-line:no-any - private getType(value: string | TemplateRef): 'template' | 'string' { - if (value instanceof TemplateRef) { - return 'template'; - } else { - return 'string'; - } - } - - private shouldRecreateView(changes: SimpleChanges): boolean { - const { nzStringTemplateOutletContext, nzStringTemplateOutlet } = changes; - let shouldOutletRecreate = false; - if (nzStringTemplateOutlet) { - if (nzStringTemplateOutlet.firstChange) { - shouldOutletRecreate = true; - } else { - const previousOutletType = this.getType(nzStringTemplateOutlet.previousValue); - const currentOutletType = this.getType(nzStringTemplateOutlet.currentValue); - shouldOutletRecreate = !(previousOutletType === 'string' && currentOutletType === 'string'); - } - } - const shouldContextRecreate = nzStringTemplateOutletContext && this.hasContextShapeChanged(nzStringTemplateOutletContext); - return shouldContextRecreate || shouldOutletRecreate; - } - - private hasContextShapeChanged(ctxChange: SimpleChange): boolean { - const prevCtxKeys = Object.keys(ctxChange.previousValue || {}); - const currCtxKeys = Object.keys(ctxChange.currentValue || {}); - - if (prevCtxKeys.length === currCtxKeys.length) { - for (const propName of currCtxKeys) { - if (prevCtxKeys.indexOf(propName) === -1) { - return true; - } - } - return false; - } else { - return true; - } - } - - // tslint:disable-next-line:no-any - private updateExistingContext(ctx: any): void { - for (const propName of Object.keys(ctx)) { - // tslint:disable-next-line:no-any - (this.inputViewRef!.context as any)[propName] = this.nzStringTemplateOutletContext[propName]; - } - } - - constructor(private viewContainer: ViewContainerRef, private defaultTemplate: TemplateRef) {} - - ngOnChanges(changes: SimpleChanges): void { - const recreateView = this.shouldRecreateView(changes); - if (recreateView) { - if (this.viewContainer) { - this.viewContainer.clear(); - this.defaultViewRef = null; - this.inputViewRef = null; - } - this.recreateView(); - } else { - if (this.inputViewRef && this.nzStringTemplateOutletContext) { - this.updateExistingContext(this.nzStringTemplateOutletContext); - } - } - } -} diff --git a/components/core/dropdown/nz-menu-base.service.ts b/components/core/dropdown/nz-menu-base.service.ts deleted file mode 100644 index 0d6310c2432..00000000000 --- a/components/core/dropdown/nz-menu-base.service.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { Injectable } from '@angular/core'; -import { BehaviorSubject, Subject } from 'rxjs'; - -import { NzDirectionVHIType } from '../types'; - -@Injectable() -export class NzMenuBaseService { - isInDropDown: boolean; - menuItemClick$ = new Subject(); // tslint:disable-line no-any - theme$ = new Subject(); - mode$ = new BehaviorSubject('vertical'); - inlineIndent$ = new BehaviorSubject(24); - theme: 'light' | 'dark' = 'light'; - mode: NzDirectionVHIType = 'vertical'; - inlineIndent = 24; - menuOpen$ = new BehaviorSubject(false); - - // tslint:disable-next-line no-any - onMenuItemClick(menu: any): void { - this.menuItemClick$.next(menu); - } - - setMode(mode: NzDirectionVHIType): void { - this.mode = mode; - this.mode$.next(mode); - } - - setTheme(theme: 'light' | 'dark'): void { - this.theme = theme; - this.theme$.next(theme); - } - - setInlineIndent(indent: number): void { - this.inlineIndent = indent; - this.inlineIndent$.next(indent); - } -} diff --git a/components/core/addon/index.ts b/components/core/outlet/index.ts similarity index 100% rename from components/core/addon/index.ts rename to components/core/outlet/index.ts diff --git a/components/core/addon/addon.module.ts b/components/core/outlet/outlet.module.ts similarity index 63% rename from components/core/addon/addon.module.ts rename to components/core/outlet/outlet.module.ts index 435d5743c59..43c332a5797 100644 --- a/components/core/addon/addon.module.ts +++ b/components/core/outlet/outlet.module.ts @@ -8,12 +8,11 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzClassListAddDirective } from './classlist_add'; -import { NzStringTemplateOutletDirective } from './string_template_outlet'; +import { NzStringTemplateOutletDirective } from './string_template_outlet.directive'; @NgModule({ imports: [CommonModule], - exports: [NzStringTemplateOutletDirective, NzClassListAddDirective], - declarations: [NzStringTemplateOutletDirective, NzClassListAddDirective] + exports: [NzStringTemplateOutletDirective], + declarations: [NzStringTemplateOutletDirective] }) -export class NzAddOnModule {} +export class NzOutletModule {} diff --git a/components/core/addon/package.json b/components/core/outlet/package.json similarity index 100% rename from components/core/addon/package.json rename to components/core/outlet/package.json diff --git a/components/core/addon/public-api.ts b/components/core/outlet/public-api.ts similarity index 71% rename from components/core/addon/public-api.ts rename to components/core/outlet/public-api.ts index ec8cbfaf0da..efacca0fad7 100644 --- a/components/core/addon/public-api.ts +++ b/components/core/outlet/public-api.ts @@ -6,6 +6,5 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -export { NzAddOnModule } from './addon.module'; -export { NzClassListAddDirective } from './classlist_add'; -export { NzStringTemplateOutletDirective } from './string_template_outlet'; +export { NzOutletModule } from './outlet.module'; +export { NzStringTemplateOutletDirective } from './string_template_outlet.directive'; diff --git a/components/core/outlet/string_template_outlet.directive.ts b/components/core/outlet/string_template_outlet.directive.ts new file mode 100644 index 00000000000..865d9eab288 --- /dev/null +++ b/components/core/outlet/string_template_outlet.directive.ts @@ -0,0 +1,79 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { Directive, EmbeddedViewRef, Input, OnChanges, SimpleChange, SimpleChanges, TemplateRef, ViewContainerRef } from '@angular/core'; +import { NzSafeAny } from 'ng-zorro-antd/core/types'; + +@Directive({ + selector: '[nzStringTemplateOutlet]', + exportAs: 'nzStringTemplateOutlet' +}) +export class NzStringTemplateOutletDirective implements OnChanges { + private embeddedViewRef: EmbeddedViewRef | null = null; + @Input() nzStringTemplateOutletContext: NzSafeAny | null = null; + @Input() nzStringTemplateOutlet: string | TemplateRef | null = null; + + private recreateView(): void { + this.viewContainer.clear(); + const isTemplateRef = this.nzStringTemplateOutlet instanceof TemplateRef; + const templateRef = (isTemplateRef ? this.nzStringTemplateOutlet : this.templateRef) as NzSafeAny; + this.embeddedViewRef = this.viewContainer.createEmbeddedView(templateRef, this.nzStringTemplateOutletContext); + } + + private updateContext(): void { + const newCtx = this.nzStringTemplateOutletContext; + const oldCtx = this.embeddedViewRef!.context as NzSafeAny; + if (newCtx) { + for (const propName of Object.keys(newCtx)) { + oldCtx[propName] = newCtx[propName]; + } + } + } + + constructor(private viewContainer: ViewContainerRef, private templateRef: TemplateRef) {} + + ngOnChanges(changes: SimpleChanges): void { + const shouldRecreateView = (ctxChanges: SimpleChanges): boolean => { + const { nzStringTemplateOutletContext, nzStringTemplateOutlet } = ctxChanges; + let shouldOutletRecreate = false; + if (nzStringTemplateOutlet) { + if (nzStringTemplateOutlet.firstChange) { + shouldOutletRecreate = true; + } else { + const isPreviousOutletTemplate = nzStringTemplateOutlet.previousValue instanceof TemplateRef; + const isCurrentOutletTemplate = nzStringTemplateOutlet.currentValue instanceof TemplateRef; + shouldOutletRecreate = isPreviousOutletTemplate || isCurrentOutletTemplate; + } + } + const hasContextShapeChanged = (ctxChange: SimpleChange): boolean => { + const prevCtxKeys = Object.keys(ctxChange.previousValue || {}); + const currCtxKeys = Object.keys(ctxChange.currentValue || {}); + if (prevCtxKeys.length === currCtxKeys.length) { + for (const propName of currCtxKeys) { + if (prevCtxKeys.indexOf(propName) === -1) { + return true; + } + } + return false; + } else { + return true; + } + }; + const shouldContextRecreate = nzStringTemplateOutletContext && hasContextShapeChanged(nzStringTemplateOutletContext); + return shouldContextRecreate || shouldOutletRecreate; + }; + const recreateView = shouldRecreateView(changes); + if (recreateView) { + /** recreate view when context shape or outlet change **/ + this.recreateView(); + } else { + /** update context **/ + this.updateContext(); + } + } +} diff --git a/components/core/outlet/string_template_outlet.spec.ts b/components/core/outlet/string_template_outlet.spec.ts new file mode 100644 index 00000000000..a59e04bd011 --- /dev/null +++ b/components/core/outlet/string_template_outlet.spec.ts @@ -0,0 +1,111 @@ +import { Component, TemplateRef, ViewChild } from '@angular/core'; +import { NzSafeAny, ɵcreateComponentBed as createComponentBed } from 'ng-zorro-antd/core'; +import { NzOutletModule } from './outlet.module'; +import { NzStringTemplateOutletDirective } from './string_template_outlet.directive'; + +describe('string template outlet', () => { + describe('null', () => { + it('should no error when null', () => { + const testBed = createComponentBed(StringTemplateOutletTestComponent, { imports: [NzOutletModule] }); + expect(testBed.nativeElement.innerText).toBe('TargetText'); + }); + }); + describe('outlet change', () => { + it('should work when switch between null and string', () => { + const testBed = createComponentBed(StringTemplateOutletTestComponent, { imports: [NzOutletModule] }); + testBed.component.stringTemplateOutlet = 'String Testing'; + testBed.fixture.detectChanges(); + expect(testBed.nativeElement.innerText).toBe('TargetText String Testing'); + testBed.component.stringTemplateOutlet = null; + testBed.fixture.detectChanges(); + expect(testBed.nativeElement.innerText).toBe('TargetText'); + }); + it('should work when switch between null and template', () => { + const testBed = createComponentBed(StringTemplateOutletTestComponent, { imports: [NzOutletModule] }); + testBed.component.stringTemplateOutlet = testBed.component.stringTpl; + testBed.fixture.detectChanges(); + expect(testBed.nativeElement.innerText).toBe('TargetText The data is'); + testBed.component.stringTemplateOutlet = null; + testBed.fixture.detectChanges(); + expect(testBed.nativeElement.innerText).toBe('TargetText'); + }); + it('should work when switch between string', () => { + const testBed = createComponentBed(StringTemplateOutletTestComponent, { imports: [NzOutletModule] }); + testBed.component.stringTemplateOutlet = 'String Testing'; + testBed.fixture.detectChanges(); + expect(testBed.nativeElement.innerText).toBe('TargetText String Testing'); + testBed.component.stringTemplateOutlet = 'String String'; + testBed.fixture.detectChanges(); + expect(testBed.nativeElement.innerText).toBe('TargetText String String'); + }); + it('should work when switch between string and template', () => { + const testBed = createComponentBed(StringTemplateOutletTestComponent, { imports: [NzOutletModule] }); + testBed.component.stringTemplateOutlet = 'String Testing'; + testBed.fixture.detectChanges(); + expect(testBed.nativeElement.innerText).toBe('TargetText String Testing'); + testBed.component.stringTemplateOutlet = testBed.component.stringTpl; + testBed.fixture.detectChanges(); + expect(testBed.nativeElement.innerText).toBe('TargetText The data is'); + testBed.component.stringTemplateOutlet = 'String Testing'; + testBed.fixture.detectChanges(); + expect(testBed.nativeElement.innerText).toBe('TargetText String Testing'); + }); + it('should work when switch between template', () => { + const testBed = createComponentBed(StringTemplateOutletTestComponent, { imports: [NzOutletModule] }); + testBed.component.stringTemplateOutlet = testBed.component.stringTpl; + testBed.fixture.detectChanges(); + expect(testBed.nativeElement.innerText).toBe('TargetText The data is'); + testBed.component.stringTemplateOutlet = testBed.component.emptyTpl; + testBed.fixture.detectChanges(); + expect(testBed.nativeElement.innerText).toBe('TargetText Empty Template'); + }); + }); + describe('context shape change', () => { + it('should work when context shape change', () => { + const testBed = createComponentBed(StringTemplateOutletTestComponent, { imports: [NzOutletModule] }); + testBed.component.stringTemplateOutlet = testBed.component.dataTimeTpl; + const spyOnUpdateContext = spyOn(testBed.component.nzStringTemplateOutletDirective as NzSafeAny, 'updateContext').and.callThrough(); + const spyOnRecreateView = spyOn(testBed.component.nzStringTemplateOutletDirective as NzSafeAny, 'recreateView').and.callThrough(); + testBed.fixture.detectChanges(); + expect(testBed.nativeElement.innerText).toBe('TargetText The data is , The time is'); + testBed.component.context = { $implicit: 'data', time: 'time' }; + testBed.fixture.detectChanges(); + expect(spyOnUpdateContext).toHaveBeenCalledTimes(0); + expect(spyOnRecreateView).toHaveBeenCalledTimes(2); + expect(testBed.nativeElement.innerText).toBe('TargetText The data is data, The time is time'); + }); + }); + describe('context data change', () => { + it('should work when context implicit change', () => { + const testBed = createComponentBed(StringTemplateOutletTestComponent, { imports: [NzOutletModule] }); + testBed.component.stringTemplateOutlet = testBed.component.stringTpl; + const spyOnUpdateContext = spyOn(testBed.component.nzStringTemplateOutletDirective as NzSafeAny, 'updateContext').and.callThrough(); + const spyOnRecreateView = spyOn(testBed.component.nzStringTemplateOutletDirective as NzSafeAny, 'recreateView').and.callThrough(); + testBed.fixture.detectChanges(); + expect(testBed.nativeElement.innerText).toBe('TargetText The data is'); + testBed.component.context = { $implicit: 'data' }; + testBed.fixture.detectChanges(); + expect(spyOnUpdateContext).toHaveBeenCalledTimes(1); + expect(spyOnRecreateView).toHaveBeenCalledTimes(1); + expect(testBed.nativeElement.innerText).toBe('TargetText The data is data'); + }); + }); +}); + +@Component({ + template: ` + TargetText + {{ stringTemplateOutlet }} + The data is {{ data }} + Empty Template + The data is {{ data }}, The time is {{ time }} + ` +}) +export class StringTemplateOutletTestComponent { + @ViewChild('stringTpl') stringTpl: TemplateRef; + @ViewChild('emptyTpl') emptyTpl: TemplateRef; + @ViewChild('dataTimeTpl') dataTimeTpl: TemplateRef; + @ViewChild(NzStringTemplateOutletDirective) nzStringTemplateOutletDirective: NzStringTemplateOutletDirective; + stringTemplateOutlet: TemplateRef | string | null = null; + context: NzSafeAny = { $implicit: '' }; +} diff --git a/components/core/overlay/overlay-position.ts b/components/core/overlay/overlay-position.ts index 577d23a7d21..e537a25c7bc 100644 --- a/components/core/overlay/overlay-position.ts +++ b/components/core/overlay/overlay-position.ts @@ -26,8 +26,6 @@ export const POSITION_MAP: { [key: string]: ConnectionPositionPair } = { }; export const DEFAULT_TOOLTIP_POSITIONS = [POSITION_MAP.top, POSITION_MAP.right, POSITION_MAP.bottom, POSITION_MAP.left]; -export const DEFAULT_DROPDOWN_POSITIONS = [POSITION_MAP.bottomLeft, POSITION_MAP.bottomRight, POSITION_MAP.topRight, POSITION_MAP.topLeft]; -export const DEFAULT_SUBMENU_POSITIONS = [POSITION_MAP.rightTop, POSITION_MAP.leftTop]; export const DEFAULT_CASCADER_POSITIONS = [POSITION_MAP.bottomLeft, POSITION_MAP.topLeft]; export const DEFAULT_MENTION_TOP_POSITIONS = [ @@ -41,10 +39,13 @@ export const DEFAULT_MENTION_BOTTOM_POSITIONS = [ ]; export function getPlacementName(position: ConnectedOverlayPositionChange): string | undefined { - const keyList = ['originX', 'originY', 'overlayX', 'overlayY']; for (const placement in POSITION_MAP) { - // @ts-ignore - if (keyList.every(key => position.connectionPair[key] === POSITION_MAP[placement][key])) { + if ( + position.connectionPair.originX === POSITION_MAP[placement].originX && + position.connectionPair.originY === POSITION_MAP[placement].originY && + position.connectionPair.overlayX === POSITION_MAP[placement].overlayX && + position.connectionPair.overlayY === POSITION_MAP[placement].overlayY + ) { return placement; } } diff --git a/components/core/pipe/nz-pipe.module.ts b/components/core/pipe/nz-pipe.module.ts index e9b8a2534ba..176f100325e 100644 --- a/components/core/pipe/nz-pipe.module.ts +++ b/components/core/pipe/nz-pipe.module.ts @@ -10,7 +10,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { NzToCssUnitPipe } from './nz-css-unit.pipe'; -import { NzTimeRangePipe } from './nz-time-range.pipe'; +import { NzTimeRangePipe } from './time-range.pipe'; @NgModule({ imports: [CommonModule], diff --git a/components/core/pipe/public-api.ts b/components/core/pipe/public-api.ts index c9698f1056e..d4ff86c01cb 100644 --- a/components/core/pipe/public-api.ts +++ b/components/core/pipe/public-api.ts @@ -7,5 +7,5 @@ */ export * from './nz-pipe.module'; -export * from './nz-time-range.pipe'; +export * from './time-range.pipe'; export * from './nz-css-unit.pipe'; diff --git a/components/core/pipe/nz-time-range.pipe.ts b/components/core/pipe/time-range.pipe.ts similarity index 99% rename from components/core/pipe/nz-time-range.pipe.ts rename to components/core/pipe/time-range.pipe.ts index 70935161e90..0f8f6dacd3d 100644 --- a/components/core/pipe/nz-time-range.pipe.ts +++ b/components/core/pipe/time-range.pipe.ts @@ -7,6 +7,7 @@ */ import { Pipe, PipeTransform } from '@angular/core'; + import { timeUnits } from '../time'; import { padStart } from '../util'; diff --git a/components/core/pipe/nz-time-range.spec.ts b/components/core/pipe/time-range.spec.ts similarity index 92% rename from components/core/pipe/nz-time-range.spec.ts rename to components/core/pipe/time-range.spec.ts index a4ddeb1c991..84efa6f1c7f 100644 --- a/components/core/pipe/nz-time-range.spec.ts +++ b/components/core/pipe/time-range.spec.ts @@ -3,7 +3,6 @@ import { Component } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { NzPipesModule } from 'ng-zorro-antd/core/pipe'; -import { NzStatisticModule } from '../../statistic/nz-statistic.module'; @Component({ template: ` @@ -22,7 +21,7 @@ describe('nz time range pipeline', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [CommonModule, NzStatisticModule, NzPipesModule], + imports: [CommonModule, NzPipesModule], declarations: [NzTestTimeRangeComponent] }).compileComponents(); }); diff --git a/components/core/public-api.ts b/components/core/public-api.ts index 9a48f024c2b..c6f2e488966 100644 --- a/components/core/public-api.ts +++ b/components/core/public-api.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -export * from './addon/public-api'; +export * from './outlet/public-api'; export * from './animation/public-api'; export * from './transition-patch/public-api'; export * from './no-animation/public-api'; @@ -20,7 +20,6 @@ export * from './tree/public-api'; export * from './types/public-api'; export * from './util/public-api'; export * from './wave/public-api'; -export * from './dropdown/public-api'; export * from './logger/public-api'; export * from './responsive/public-api'; export * from './trans-button/public-api'; diff --git a/components/core/testing/componet-bed.ts b/components/core/testing/componet-bed.ts index 857df2bc1d5..4cbb48d0060 100644 --- a/components/core/testing/componet-bed.ts +++ b/components/core/testing/componet-bed.ts @@ -1,10 +1,12 @@ import { CommonModule } from '@angular/common'; import { DebugElement, NO_ERRORS_SCHEMA, Provider, Type } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, TestBedStatic } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -// tslint:disable-next-line:no-any -type ComponentDeps = Array>; +import { NzSafeAny } from 'ng-zorro-antd/core/types'; + +type ComponentDeps = Array>; export interface ComponentBed { + bed: TestBedStatic; fixture: ComponentFixture; nativeElement: HTMLElement; debugElement: DebugElement; @@ -29,10 +31,11 @@ export function createComponentBed( schemas: [NO_ERRORS_SCHEMA], providers: providers || [] }; - TestBed.configureTestingModule(config); + const bed = TestBed.configureTestingModule(config); const fixture = TestBed.createComponent(component); fixture.detectChanges(); return { + bed, fixture, nativeElement: fixture.nativeElement, debugElement: fixture.debugElement, diff --git a/components/core/testing/event-objects.ts b/components/core/testing/event-objects.ts index 5373294cb12..8b1c02e5608 100644 --- a/components/core/testing/event-objects.ts +++ b/components/core/testing/event-objects.ts @@ -54,7 +54,15 @@ export function createTouchEvent(type: string, pageX: number = 0, pageY: number } /** Dispatches a keydown event from an element. */ -export function createKeyboardEvent(type: string, keyCode: number, target?: Element, key?: string): KeyboardEvent { +export function createKeyboardEvent( + type: string, + keyCode: number, + target?: Element, + key?: string, + ctrlKey?: boolean, + metaKey?: boolean, + shiftKey?: boolean +): KeyboardEvent { // tslint:disable-next-line:no-any const event = document.createEvent('KeyboardEvent') as any; const originalPreventDefault = event.preventDefault; @@ -71,7 +79,10 @@ export function createKeyboardEvent(type: string, keyCode: number, target?: Elem Object.defineProperties(event, { keyCode: { get: () => keyCode }, key: { get: () => key }, - target: { get: () => target } + target: { get: () => target }, + ctrlKey: { get: () => ctrlKey }, + metaKey: { get: () => metaKey }, + shiftKey: { get: () => shiftKey } }); // IE won't set `defaultPrevented` on synthetic events so we need to do it manually. diff --git a/components/core/transition-patch/transition-patch.directive.ts b/components/core/transition-patch/transition-patch.directive.ts index 4b33a2599c8..be1a7d94799 100644 --- a/components/core/transition-patch/transition-patch.directive.ts +++ b/components/core/transition-patch/transition-patch.directive.ts @@ -6,8 +6,8 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -import { Platform } from '@angular/cdk/platform'; -import { AfterViewInit, Directive, ElementRef, Renderer2 } from '@angular/core'; +import { AfterViewInit, Directive, ElementRef, Input, OnChanges, Renderer2 } from '@angular/core'; +import { NzSafeAny } from 'ng-zorro-antd/core/types'; /** * hack the bug @@ -15,48 +15,29 @@ import { AfterViewInit, Directive, ElementRef, Renderer2 } from '@angular/core'; * https://github.com/angular/angular/issues/34718 */ @Directive({ - selector: '[nz-button],nz-button-group,[nz-icon]' + selector: '[nz-button],nz-button-group,[nz-icon],[nz-menu-item],[nz-submenu]' }) -export class NzTransitionPatchDirective implements AfterViewInit { - readonly nativeElement: HTMLElement | null = null; - private readonly hiddenAttribute: string | null = null; - getHiddenAttribute(element: HTMLElement): string | null { - if (this.platform.isBrowser) { - return element.getAttribute('hidden'); - } else { - return null; +export class NzTransitionPatchDirective implements AfterViewInit, OnChanges { + @Input() hidden: NzSafeAny = null; + setHiddenAttribute(): void { + if (this.hidden === true) { + this.renderer.setAttribute(this.elementRef.nativeElement, 'hidden', ''); + } else if (this.hidden === false || this.hidden === null) { + this.renderer.removeAttribute(this.elementRef.nativeElement, 'hidden'); + } else if (typeof this.hidden === 'string') { + this.renderer.setAttribute(this.elementRef.nativeElement, 'hidden', this.hidden); } } - getHiddenAttributeCount(attribute: string | null): number { - if (attribute === null) { - return 0; - } else if (typeof +attribute === 'number' && !isNaN(+attribute)) { - return +attribute; - } else { - return 1; - } - } - setHiddenAttribute(element: HTMLElement): void { - const increasedHiddenAttribute = this.getHiddenAttributeCount(this.getHiddenAttribute(element)) + 1; - this.renderer.setAttribute(element, 'hidden', `${increasedHiddenAttribute}`); - } - restoreHiddenAttribute(element: HTMLElement, originHiddenAttribute: string | null): void { - const decreasedHiddenAttribute = this.getHiddenAttributeCount(this.getHiddenAttribute(element)) - 1; - if (decreasedHiddenAttribute === 0) { - this.renderer.removeAttribute(element, 'hidden'); - } else if (decreasedHiddenAttribute === 1) { - this.renderer.setAttribute(element, 'hidden', originHiddenAttribute || ''); - } else { - this.renderer.setAttribute(element, 'hidden', `${decreasedHiddenAttribute}`); - } + + constructor(private elementRef: ElementRef, private renderer: Renderer2) { + this.renderer.setAttribute(this.elementRef.nativeElement, 'hidden', ''); } - constructor(elementRef: ElementRef, private renderer: Renderer2, private platform: Platform) { - this.nativeElement = elementRef.nativeElement; - this.hiddenAttribute = this.getHiddenAttribute(this.nativeElement!); - this.setHiddenAttribute(this.nativeElement!); + ngOnChanges(): void { + this.setHiddenAttribute(); } + ngAfterViewInit(): void { - this.restoreHiddenAttribute(this.nativeElement!, this.hiddenAttribute); + this.setHiddenAttribute(); } } diff --git a/components/core/transition-patch/transition-patch.spec.ts b/components/core/transition-patch/transition-patch.spec.ts index 3da4bdfa53c..4f4a8621750 100644 --- a/components/core/transition-patch/transition-patch.spec.ts +++ b/components/core/transition-patch/transition-patch.spec.ts @@ -10,23 +10,32 @@ import { Component } from '@angular/core'; import { By } from '@angular/platform-browser'; import { ɵcreateComponentBed as createComponentBed } from 'ng-zorro-antd/core'; import { NzTransitionPatchDirective } from './transition-patch.directive'; +import { NzTransitionPatchModule } from './transition-patch.module'; describe('transition-patch', () => { it('should visible after afterViewInit', () => { - const testBed = createComponentBed(TestTransitionPatchComponent, { declarations: [NzTransitionPatchDirective] }); + const testBed = createComponentBed(TestTransitionPatchComponent, { imports: [NzTransitionPatchModule] }); const buttonElement = testBed.debugElement.query(By.directive(NzTransitionPatchDirective)).nativeElement; expect(buttonElement.getAttribute('hidden')).toBeFalsy(); }); it('should hidden after afterViewInit', () => { - const testBed = createComponentBed(TestTransitionPatchHiddenComponent, { declarations: [NzTransitionPatchDirective] }); + const testBed = createComponentBed(TestTransitionPatchHiddenComponent, { imports: [NzTransitionPatchModule] }); const buttonElement = testBed.debugElement.query(By.directive(NzTransitionPatchDirective)).nativeElement; expect(buttonElement.getAttribute('hidden')).toBeFalsy(); }); it('should restore after afterViewInit', () => { - const testBed = createComponentBed(TestTransitionPatchRestoreComponent, { declarations: [NzTransitionPatchDirective] }); + const testBed = createComponentBed(TestTransitionPatchRestoreComponent, { imports: [NzTransitionPatchModule] }); const buttonElement = testBed.debugElement.query(By.directive(NzTransitionPatchDirective)).nativeElement; expect(buttonElement.getAttribute('hidden')).toBe('abc'); }); + it('should work if hidden binding', () => { + const testBed = createComponentBed(TestTransitionPatchHiddenBindingComponent, { imports: [NzTransitionPatchModule] }); + const buttonElement = testBed.debugElement.query(By.directive(NzTransitionPatchDirective)).nativeElement; + expect(buttonElement.getAttribute('hidden')).toBeFalsy(); + testBed.component.hidden = true; + testBed.fixture.detectChanges(); + expect(buttonElement.getAttribute('hidden')).toBe(''); + }); }); @Component({ @@ -49,3 +58,12 @@ export class TestTransitionPatchHiddenComponent {} ` }) export class TestTransitionPatchRestoreComponent {} + +@Component({ + template: ` + + ` +}) +export class TestTransitionPatchHiddenBindingComponent { + hidden = false; +} diff --git a/components/core/dropdown/index.ts b/components/core/types/any.ts similarity index 78% rename from components/core/dropdown/index.ts rename to components/core/types/any.ts index f17e95188c8..fbce61d3cac 100644 --- a/components/core/dropdown/index.ts +++ b/components/core/types/any.ts @@ -6,4 +6,5 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -export * from './public-api'; +// tslint:disable-next-line:no-any +export type NzSafeAny = any; diff --git a/components/core/types/control-value-accessor.ts b/components/core/types/control-value-accessor.ts new file mode 100644 index 00000000000..29f419b6d78 --- /dev/null +++ b/components/core/types/control-value-accessor.ts @@ -0,0 +1,12 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { NzSafeAny } from './any'; + +export type OnTouchedType = () => NzSafeAny; +export type OnChangeType = (value: NzSafeAny) => void; diff --git a/components/core/types/direction.ts b/components/core/types/direction.ts index f4d426086c7..4b9188a49b3 100644 --- a/components/core/types/direction.ts +++ b/components/core/types/direction.ts @@ -6,6 +6,5 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -export type NzDirectionVHIType = 'vertical' | 'horizontal' | 'inline'; export type NzDirectionVHType = 'vertical' | 'horizontal'; export type NzFourDirectionType = 'top' | 'bottom' | 'left' | 'right'; diff --git a/components/core/types/package.json b/components/core/types/package.json new file mode 100644 index 00000000000..54c27fb98f1 --- /dev/null +++ b/components/core/types/package.json @@ -0,0 +1,7 @@ +{ + "ngPackage": { + "lib": { + "entryFile": "./public-api.ts" + } + } +} diff --git a/components/core/types/public-api.ts b/components/core/types/public-api.ts index fb235a9e863..436c9e37b98 100644 --- a/components/core/types/public-api.ts +++ b/components/core/types/public-api.ts @@ -15,3 +15,5 @@ export * from './size'; export * from './template'; export * from './shape'; export * from './compare-with'; +export * from './any'; +export * from './control-value-accessor'; diff --git a/components/date-picker/style/index.less b/components/date-picker/style/index.less index 2537b7d06e8..fee73bfafb6 100644 --- a/components/date-picker/style/index.less +++ b/components/date-picker/style/index.less @@ -51,6 +51,12 @@ border-color: @select-border-color; } + &&-borderless { + background-color: transparent !important; + border-color: transparent !important; + box-shadow: none !important; + } + // ======================== Input ========================= &-input { position: relative; diff --git a/components/descriptions/nz-descriptions.module.ts b/components/descriptions/nz-descriptions.module.ts index 78ad7b909ae..d7b09fd2efc 100644 --- a/components/descriptions/nz-descriptions.module.ts +++ b/components/descriptions/nz-descriptions.module.ts @@ -8,13 +8,13 @@ import { PlatformModule } from '@angular/cdk/platform'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule } from 'ng-zorro-antd/core'; +import { NzOutletModule } from 'ng-zorro-antd/core'; import { NzDescriptionsItemComponent } from './nz-descriptions-item.component'; import { NzDescriptionsComponent } from './nz-descriptions.component'; @NgModule({ - imports: [CommonModule, NzAddOnModule, PlatformModule], + imports: [CommonModule, NzOutletModule, PlatformModule], declarations: [NzDescriptionsComponent, NzDescriptionsItemComponent], exports: [NzDescriptionsComponent, NzDescriptionsItemComponent] }) diff --git a/components/divider/demo/orientation.md b/components/divider/demo/orientation.md index 245254cad5b..0a030ac535f 100644 --- a/components/divider/demo/orientation.md +++ b/components/divider/demo/orientation.md @@ -1,5 +1,5 @@ --- -order: 2 +order: 1 title: zh-CN: 标题位置 en-US: Orientation of title @@ -11,4 +11,4 @@ title: ## en-US -Set orientation of divider to left or right. \ No newline at end of file +Set orientation of divider to left or right. diff --git a/components/divider/demo/vertical.md b/components/divider/demo/vertical.md index 2e4b076d488..96dc00d5b3e 100644 --- a/components/divider/demo/vertical.md +++ b/components/divider/demo/vertical.md @@ -1,5 +1,5 @@ --- -order: 1 +order: 2 title: zh-CN: 垂直分割线 en-US: Vertical @@ -11,4 +11,4 @@ title: ## en-US -Use `nzType="vertical"` make it vertical. \ No newline at end of file +Use `nzType="vertical"` make it vertical. diff --git a/components/divider/divider.component.ts b/components/divider/divider.component.ts new file mode 100644 index 00000000000..65dd7255717 --- /dev/null +++ b/components/divider/divider.component.ts @@ -0,0 +1,39 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { ChangeDetectionStrategy, Component, Input, TemplateRef, ViewEncapsulation } from '@angular/core'; + +import { InputBoolean } from 'ng-zorro-antd/core'; + +@Component({ + selector: 'nz-divider', + exportAs: 'nzDivider', + preserveWhitespaces: false, + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` + + {{ nzText }} + + `, + host: { + '[class.ant-divider]': 'true', + '[class.ant-divider-horizontal]': `nzType === 'horizontal'`, + '[class.ant-divider-vertical]': `nzType === 'vertical'`, + '[class.ant-divider-with-text-left]': `nzText && nzOrientation === 'left'`, + '[class.ant-divider-with-text-right]': `nzText && nzOrientation === 'right'`, + '[class.ant-divider-with-text-center]': `nzText && nzOrientation === 'center'`, + '[class.ant-divider-dashed]': `nzDashed` + } +}) +export class NzDividerComponent { + @Input() nzText: string | TemplateRef; + @Input() nzType: 'horizontal' | 'vertical' = 'horizontal'; + @Input() nzOrientation: 'left' | 'right' | 'center' = 'center'; + @Input() @InputBoolean() nzDashed = false; +} diff --git a/components/divider/nz-divider.module.ts b/components/divider/divider.module.ts similarity index 74% rename from components/divider/nz-divider.module.ts rename to components/divider/divider.module.ts index 671b36ec630..8aae3513560 100644 --- a/components/divider/nz-divider.module.ts +++ b/components/divider/divider.module.ts @@ -8,11 +8,11 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule } from 'ng-zorro-antd/core'; -import { NzDividerComponent } from './nz-divider.component'; +import { NzOutletModule } from 'ng-zorro-antd/core'; +import { NzDividerComponent } from './divider.component'; @NgModule({ - imports: [CommonModule, NzAddOnModule], + imports: [CommonModule, NzOutletModule], declarations: [NzDividerComponent], exports: [NzDividerComponent] }) diff --git a/components/divider/divider.spec.ts b/components/divider/divider.spec.ts index c9cd4f36585..7eca8601f35 100644 --- a/components/divider/divider.spec.ts +++ b/components/divider/divider.spec.ts @@ -4,8 +4,8 @@ import { By } from '@angular/platform-browser'; import { NzIconTestModule } from 'ng-zorro-antd/icon/testing'; -import { NzDividerComponent } from './nz-divider.component'; -import { NzDividerModule } from './nz-divider.module'; +import { NzDividerComponent } from './divider.component'; +import { NzDividerModule } from './divider.module'; describe('divider', () => { let fixture: ComponentFixture; diff --git a/components/divider/nz-divider.component.html b/components/divider/nz-divider.component.html deleted file mode 100644 index a0b86fc3447..00000000000 --- a/components/divider/nz-divider.component.html +++ /dev/null @@ -1,3 +0,0 @@ - - {{ nzText }} - diff --git a/components/divider/nz-divider.component.ts b/components/divider/nz-divider.component.ts deleted file mode 100644 index a4c44e4df67..00000000000 --- a/components/divider/nz-divider.component.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges, OnInit, TemplateRef, ViewEncapsulation } from '@angular/core'; - -import { InputBoolean, NzUpdateHostClassService } from 'ng-zorro-antd/core'; - -@Component({ - selector: 'nz-divider', - exportAs: 'nzDivider', - templateUrl: './nz-divider.component.html', - preserveWhitespaces: false, - providers: [NzUpdateHostClassService], - encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class NzDividerComponent implements OnChanges, OnInit { - @Input() nzText: string | TemplateRef; - @Input() nzType: 'horizontal' | 'vertical' = 'horizontal'; - @Input() nzOrientation: 'left' | 'right' | 'center' = 'center'; - @Input() @InputBoolean() nzDashed = false; - - private setClass(): void { - this.nzUpdateHostClassService.updateHostClass(this.elementRef.nativeElement, { - ['ant-divider']: true, - [`ant-divider-${this.nzType}`]: true, - [`ant-divider-with-text-${this.nzOrientation}`]: this.nzText, - [`ant-divider-dashed`]: this.nzDashed - }); - } - - constructor(private elementRef: ElementRef, private nzUpdateHostClassService: NzUpdateHostClassService) {} - - ngOnChanges(): void { - this.setClass(); - } - - ngOnInit(): void { - this.setClass(); - } -} diff --git a/components/divider/public-api.ts b/components/divider/public-api.ts index 64ef1f899e4..19f309096cc 100644 --- a/components/divider/public-api.ts +++ b/components/divider/public-api.ts @@ -6,5 +6,5 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -export * from './nz-divider.component'; -export * from './nz-divider.module'; +export * from './divider.component'; +export * from './divider.module'; diff --git a/components/drawer/nz-drawer.module.ts b/components/drawer/nz-drawer.module.ts index 5c7cc63919e..5c0798a082c 100644 --- a/components/drawer/nz-drawer.module.ts +++ b/components/drawer/nz-drawer.module.ts @@ -11,14 +11,14 @@ import { PortalModule } from '@angular/cdk/portal'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule, NzNoAnimationModule } from 'ng-zorro-antd/core'; +import { NzNoAnimationModule, NzOutletModule } from 'ng-zorro-antd/core'; import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzDrawerComponent } from './nz-drawer.component'; import { NzDrawerServiceModule } from './nz-drawer.service.module'; @NgModule({ - imports: [CommonModule, OverlayModule, PortalModule, NzIconModule, NzAddOnModule, NzNoAnimationModule, NzDrawerServiceModule], + imports: [CommonModule, OverlayModule, PortalModule, NzIconModule, NzOutletModule, NzNoAnimationModule, NzDrawerServiceModule], exports: [NzDrawerComponent], declarations: [NzDrawerComponent], entryComponents: [NzDrawerComponent] diff --git a/components/dropdown/nz-context-menu.service.module.ts b/components/dropdown/context-menu.service.module.ts similarity index 100% rename from components/dropdown/nz-context-menu.service.module.ts rename to components/dropdown/context-menu.service.module.ts diff --git a/components/dropdown/nz-context-menu.service.spec.ts b/components/dropdown/context-menu.service.spec.ts similarity index 94% rename from components/dropdown/nz-context-menu.service.spec.ts rename to components/dropdown/context-menu.service.spec.ts index de9fe160c93..9228319b450 100644 --- a/components/dropdown/nz-context-menu.service.spec.ts +++ b/components/dropdown/context-menu.service.spec.ts @@ -2,17 +2,16 @@ import { OverlayContainer, ScrollDispatcher } from '@angular/cdk/overlay'; import { Component, Provider, Type, ViewChild } from '@angular/core'; import { ComponentFixture, fakeAsync, inject, TestBed, tick } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { createMouseEvent } from 'ng-zorro-antd/core'; +import { NzMenuModule } from 'ng-zorro-antd/menu'; import { Subject } from 'rxjs'; -import { createMouseEvent } from '../core/testing'; -import { NzMenuModule } from '../menu/nz-menu.module'; -import { NzContextMenuService } from './nz-context-menu.service'; -import { NzDropdownMenuComponent } from './nz-dropdown-menu.component'; -import { NzDropDownModule } from './nz-dropdown.module'; +import { NzContextMenuService } from './context-menu.service'; +import { NzDropdownMenuComponent } from './dropdown-menu.component'; +import { NzDropDownModule } from './dropdown.module'; describe('context-menu', () => { let overlayContainer: OverlayContainer; let overlayContainerElement: HTMLElement; - function createComponent( component: Type, providers: Provider[] = [], @@ -34,7 +33,6 @@ describe('context-menu', () => { return TestBed.createComponent(component); } - afterEach(inject([OverlayContainer], (currentOverlayContainer: OverlayContainer) => { currentOverlayContainer.ngOnDestroy(); overlayContainer.ngOnDestroy(); diff --git a/components/dropdown/context-menu.service.ts b/components/dropdown/context-menu.service.ts new file mode 100644 index 00000000000..85c4d7161c1 --- /dev/null +++ b/components/dropdown/context-menu.service.ts @@ -0,0 +1,74 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { ConnectionPositionPair, Overlay, OverlayRef } from '@angular/cdk/overlay'; +import { TemplatePortal } from '@angular/cdk/portal'; +import { Injectable } from '@angular/core'; +import { fromEvent, merge, Subscription } from 'rxjs'; +import { filter, take } from 'rxjs/operators'; +import { NzContextMenuServiceModule } from './context-menu.service.module'; +import { NzDropdownMenuComponent } from './dropdown-menu.component'; + +const listOfPositions = [ + new ConnectionPositionPair({ originX: 'start', originY: 'top' }, { overlayX: 'start', overlayY: 'top' }), + new ConnectionPositionPair({ originX: 'start', originY: 'top' }, { overlayX: 'start', overlayY: 'bottom' }), + new ConnectionPositionPair({ originX: 'start', originY: 'top' }, { overlayX: 'end', overlayY: 'bottom' }), + new ConnectionPositionPair({ originX: 'start', originY: 'top' }, { overlayX: 'end', overlayY: 'top' }) +]; + +@Injectable({ + providedIn: NzContextMenuServiceModule +}) +export class NzContextMenuService { + private overlayRef: OverlayRef | null = null; + private closeSubscription = Subscription.EMPTY; + + constructor(private overlay: Overlay) {} + + create($event: MouseEvent | { x: number; y: number }, nzDropdownMenuComponent: NzDropdownMenuComponent): void { + this.close(true); + const { x, y } = $event; + if ($event instanceof MouseEvent) { + $event.preventDefault(); + } + const positionStrategy = this.overlay + .position() + .flexibleConnectedTo({ x, y }) + .withPositions(listOfPositions); + this.overlayRef = this.overlay.create({ + positionStrategy, + scrollStrategy: this.overlay.scrollStrategies.close() + }); + positionStrategy.positionChanges.subscribe(change => { + nzDropdownMenuComponent.setValue('dropDownPosition', change.connectionPair.overlayY === 'bottom' ? 'top' : 'bottom'); + }); + this.closeSubscription = merge( + nzDropdownMenuComponent.descendantMenuItemClick$, + fromEvent(document, 'click').pipe( + filter(event => !!this.overlayRef && !this.overlayRef.overlayElement.contains(event.target as HTMLElement)), + /** handle firefox contextmenu event **/ + filter(event => event.button !== 2), + take(1) + ) + ).subscribe(() => { + this.close(); + }); + this.overlayRef.attach(new TemplatePortal(nzDropdownMenuComponent.templateRef, nzDropdownMenuComponent.viewContainerRef)); + } + + close(clear: boolean = false): void { + if (this.overlayRef) { + this.overlayRef.detach(); + if (clear) { + this.overlayRef.dispose(); + } + this.overlayRef = null; + this.closeSubscription.unsubscribe(); + } + } +} diff --git a/components/dropdown/demo/context-menu.ts b/components/dropdown/demo/context-menu.ts index 5b0514ee81c..dc98c8b5ebd 100644 --- a/components/dropdown/demo/context-menu.ts +++ b/components/dropdown/demo/context-menu.ts @@ -5,7 +5,7 @@ import { NzContextMenuService, NzDropdownMenuComponent } from 'ng-zorro-antd/dro selector: 'nz-demo-dropdown-context-menu', template: `
- Context Menu + Right Click on here
    @@ -30,14 +30,11 @@ import { NzContextMenuService, NzDropdownMenuComponent } from 'ng-zorro-antd/dro styles: [ ` .context-area { - background: rgb(190, 200, 200); - padding: 32px; + background: #f7f7f7; + color: #777; text-align: center; - } - - .context-intro { - color: #fff; - font-size: 14px; + height: 200px; + line-height: 200px; } ` ] diff --git a/components/dropdown/demo/placement.ts b/components/dropdown/demo/placement.ts index b3d952953c8..f12ac9d587c 100644 --- a/components/dropdown/demo/placement.ts +++ b/components/dropdown/demo/placement.ts @@ -1,5 +1,4 @@ import { Component } from '@angular/core'; -import { NzPlacementType } from 'ng-zorro-antd/dropdown'; @Component({ selector: 'nz-demo-dropdown-placement', @@ -27,5 +26,5 @@ import { NzPlacementType } from 'ng-zorro-antd/dropdown'; ] }) export class NzDemoDropdownPlacementComponent { - listOfPosition: NzPlacementType[] = ['bottomLeft', 'bottomCenter', 'bottomRight', 'topLeft', 'topCenter', 'topRight']; + listOfPosition: string[] = ['bottomLeft', 'bottomCenter', 'bottomRight', 'topLeft', 'topCenter', 'topRight']; } diff --git a/components/dropdown/doc/index.en-US.md b/components/dropdown/doc/index.en-US.md index 35dc505515d..905e21ff3fb 100755 --- a/components/dropdown/doc/index.en-US.md +++ b/components/dropdown/doc/index.en-US.md @@ -58,5 +58,5 @@ Create dropdown with contextmenu, the detail can be found in the example above | Property | Description | Arguments | Return Value | | --- | --- | --- | --- | -| create | create dropdown | `($event:MouseEvent, menu:NzDropdownMenuComponent)` | - | -| close | close dropdown | - | - | \ No newline at end of file +| create | create dropdown | `($event:MouseEvent | {x:number, y:number}, menu:NzDropdownMenuComponent)` | - | +| close | close dropdown | - | - | diff --git a/components/dropdown/doc/index.zh-CN.md b/components/dropdown/doc/index.zh-CN.md index c5a6b120900..5ddc50953eb 100755 --- a/components/dropdown/doc/index.zh-CN.md +++ b/components/dropdown/doc/index.zh-CN.md @@ -59,5 +59,5 @@ import { NzDropDownModule } from 'ng-zorro-antd/dropdown'; | 方法/属性 | 说明 | 参数 | 返回 | | --- | --- | --- | --- | -| create | 创建右键菜单 | `($event:MouseEvent, menu:NzDropdownMenuComponent)` | - | -| close | 关闭右键菜单 | - | - | \ No newline at end of file +| create | 创建右键菜单 | `($event:MouseEvent | {x:number, y:number}, menu:NzDropdownMenuComponent)` | - | +| close | 关闭右键菜单 | - | - | diff --git a/components/dropdown/dropdown-a.directive.ts b/components/dropdown/dropdown-a.directive.ts new file mode 100644 index 00000000000..dbedc0b9885 --- /dev/null +++ b/components/dropdown/dropdown-a.directive.ts @@ -0,0 +1,17 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { Directive } from '@angular/core'; + +@Directive({ + selector: 'a[nz-dropdown]', + host: { + '[class.ant-dropdown-link]': 'true' + } +}) +export class NzDropDownADirective {} diff --git a/components/dropdown/nz-dropdown-button.directive.ts b/components/dropdown/dropdown-button.directive.ts similarity index 100% rename from components/dropdown/nz-dropdown-button.directive.ts rename to components/dropdown/dropdown-button.directive.ts diff --git a/components/dropdown/nz-dropdown-menu.component.ts b/components/dropdown/dropdown-menu.component.ts similarity index 53% rename from components/dropdown/nz-dropdown-menu.component.ts rename to components/dropdown/dropdown-menu.component.ts index e27429da099..4b5ce0aebcb 100644 --- a/components/dropdown/nz-dropdown-menu.component.ts +++ b/components/dropdown/dropdown-menu.component.ts @@ -13,72 +13,66 @@ import { Component, ElementRef, Host, - Injector, Optional, Renderer2, - Self, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core'; -import { NzDropdownHigherOrderServiceToken, NzMenuBaseService, NzNoAnimationDirective, slideMotion } from 'ng-zorro-antd/core'; - -import { Subject } from 'rxjs'; -import { NzMenuDropdownService } from './nz-menu-dropdown.service'; +import { NzNoAnimationDirective, slideMotion } from 'ng-zorro-antd/core'; +import { IndexableObject, NzSafeAny } from 'ng-zorro-antd/core/types'; +import { MenuService, NzIsMenuInsideDropDownToken } from 'ng-zorro-antd/menu'; +import { BehaviorSubject } from 'rxjs'; export type NzPlacementType = 'bottomLeft' | 'bottomCenter' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight'; -export function dropdownMenuServiceFactory(injector: Injector): NzMenuBaseService { - return injector.get(NzMenuDropdownService); -} - @Component({ selector: `nz-dropdown-menu`, - templateUrl: './nz-dropdown-menu.component.html', exportAs: `nzDropdownMenu`, animations: [slideMotion], providers: [ - NzMenuDropdownService, + MenuService, + /** menu is inside dropdown-menu component **/ { - provide: NzDropdownHigherOrderServiceToken, - useFactory: dropdownMenuServiceFactory, - deps: [[new Self(), Injector]] + provide: NzIsMenuInsideDropDownToken, + useValue: true } ], - styles: [ - ` - :root .ant-dropdown.nz-dropdown { - top: 0; - left: 0; - position: relative; - width: 100%; - margin-top: 4px; - margin-bottom: 4px; - } - ` - ], + template: ` + +
    +
    + +
    +
    +
    + `, preserveWhitespaces: false, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush }) export class NzDropdownMenuComponent implements AfterContentInit { - open = false; - triggerWidth = 0; dropDownPosition: 'top' | 'center' | 'bottom' = 'bottom'; - visible$ = new Subject(); - nzTrigger: 'click' | 'hover' = 'hover'; - nzPlacement: NzPlacementType = 'bottomLeft'; - nzOverlayClassName = ''; - nzOverlayStyle: { [key: string]: string } = {}; - nzTableFilter = false; - // tslint:disable-next-line:no-any - @ViewChild(TemplateRef, { static: true }) templateRef: TemplateRef; + mouseState$ = new BehaviorSubject(false); + isChildSubMenuOpen$ = this.nzMenuService.isChildSubMenuOpen$; + descendantMenuItemClick$ = this.nzMenuService.descendantMenuItemClick$; + nzOverlayClassName: string | null = null; + nzOverlayStyle: IndexableObject = {}; + isInsideTh = false; + @ViewChild(TemplateRef, { static: true }) templateRef: TemplateRef; - setVisibleStateWhen(visible: boolean, trigger: 'click' | 'hover' | 'all' = 'all'): void { - if (this.nzTrigger === trigger || trigger === 'all') { - this.visible$.next(visible); - } + setMouseState(visible: boolean): void { + this.mouseState$.next(visible); } setValue(key: T, value: this[T]): void { @@ -91,7 +85,7 @@ export class NzDropdownMenuComponent implements AfterContentInit { private elementRef: ElementRef, private renderer: Renderer2, public viewContainerRef: ViewContainerRef, - public nzMenuDropdownService: NzMenuDropdownService, + public nzMenuService: MenuService, @Host() @Optional() public noAnimation?: NzNoAnimationDirective ) {} diff --git a/components/dropdown/nz-dropdown.directive.spec.ts b/components/dropdown/dropdown.directive.spec.ts similarity index 76% rename from components/dropdown/nz-dropdown.directive.spec.ts rename to components/dropdown/dropdown.directive.spec.ts index 754f9b01830..50ac0b875b1 100644 --- a/components/dropdown/nz-dropdown.directive.spec.ts +++ b/components/dropdown/dropdown.directive.spec.ts @@ -4,15 +4,14 @@ import { Component, Provider, Type } from '@angular/core'; import { ComponentFixture, fakeAsync, inject, TestBed, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { dispatchFakeEvent, dispatchKeyboardEvent } from '../core/testing'; -import { NzMenuModule } from '../menu/nz-menu.module'; -import { NzDropDownDirective } from './nz-dropdown.directive'; -import { NzDropDownModule } from './nz-dropdown.module'; +import { dispatchFakeEvent, dispatchKeyboardEvent } from 'ng-zorro-antd/core'; +import { NzMenuModule } from 'ng-zorro-antd/menu'; +import { NzDropDownDirective } from './dropdown.directive'; +import { NzDropDownModule } from './dropdown.module'; describe('dropdown', () => { let overlayContainer: OverlayContainer; let overlayContainerElement: HTMLElement; - function createComponent( component: Type, providers: Provider[] = [], @@ -82,18 +81,6 @@ describe('dropdown', () => { expect(overlayContainerElement.textContent).toBe(''); }).not.toThrowError(); })); - it('should placement work', fakeAsync(() => { - const fixture = createComponent(NzTestDropdownComponent, [], []); - fixture.detectChanges(); - expect(() => { - const dropdownElement = fixture.debugElement.query(By.directive(NzDropDownDirective)).nativeElement; - dispatchFakeEvent(dropdownElement, 'mouseenter'); - fixture.detectChanges(); - tick(1000); - fixture.detectChanges(); - expect(overlayContainerElement.querySelector('.ant-dropdown')!.classList).toContain('ant-dropdown-placement-bottomLeft'); - }).not.toThrowError(); - })); describe('when nzBackdrop=false', () => { let fixture: ComponentFixture; @@ -183,35 +170,32 @@ describe('dropdown', () => { const fixture = createComponent(NzTestDropdownVisibleComponent, [], []); fixture.detectChanges(); expect(fixture.componentInstance.triggerVisible).toHaveBeenCalledTimes(0); - expect(() => { - const dropdownElement = fixture.debugElement.query(By.directive(NzDropDownDirective)).nativeElement; - dispatchFakeEvent(dropdownElement, 'mouseenter'); - fixture.detectChanges(); - tick(1000); - fixture.detectChanges(); - expect(fixture.componentInstance.triggerVisible).toHaveBeenCalledTimes(1); - expect(fixture.componentInstance.triggerVisible).toHaveBeenCalledWith(true); - expect(overlayContainerElement.textContent).toContain('Clicking me will not close the menu.'); - dispatchFakeEvent(overlayContainerElement.querySelector('.first-menu')!, 'click'); - fixture.detectChanges(); - tick(1000); - fixture.detectChanges(); - expect(fixture.componentInstance.triggerVisible).toHaveBeenCalledTimes(1); - expect(fixture.componentInstance.triggerVisible).toHaveBeenCalledWith(true); - expect(overlayContainerElement.textContent).toContain('Clicking me will not close the menu.'); - dispatchFakeEvent(overlayContainerElement.querySelector('.second-menu')!, 'click'); - fixture.detectChanges(); - tick(1000); - fixture.detectChanges(); - expect(fixture.componentInstance.triggerVisible).toHaveBeenCalledTimes(1); - expect(overlayContainerElement.textContent).toContain('Clicking me will not close the menu.'); - dispatchFakeEvent(overlayContainerElement.querySelector('.close-menu')!, 'click'); - fixture.detectChanges(); - tick(1000); - fixture.detectChanges(); - expect(fixture.componentInstance.triggerVisible).toHaveBeenCalledTimes(1); - expect(overlayContainerElement.textContent).toBe(''); - }).not.toThrowError(); + const dropdownElement = fixture.debugElement.query(By.directive(NzDropDownDirective)).nativeElement; + dispatchFakeEvent(dropdownElement, 'mouseenter'); + fixture.detectChanges(); + tick(1000); + fixture.detectChanges(); + expect(fixture.componentInstance.triggerVisible).toHaveBeenCalledTimes(1); + expect(fixture.componentInstance.triggerVisible).toHaveBeenCalledWith(true); + expect(overlayContainerElement.textContent).toContain('Clicking me will not close the menu.'); + dispatchFakeEvent(overlayContainerElement.querySelector('.first-menu')!, 'click'); + fixture.detectChanges(); + tick(1000); + fixture.detectChanges(); + expect(fixture.componentInstance.triggerVisible).toHaveBeenCalledTimes(1); + expect(fixture.componentInstance.triggerVisible).toHaveBeenCalledWith(true); + expect(overlayContainerElement.textContent).toContain('Clicking me will not close the menu.'); + dispatchFakeEvent(overlayContainerElement.querySelector('.second-menu')!, 'click'); + fixture.detectChanges(); + tick(1000); + fixture.detectChanges(); + expect(fixture.componentInstance.triggerVisible).toHaveBeenCalledTimes(1); + expect(overlayContainerElement.textContent).toContain('Clicking me will not close the menu.'); + dispatchFakeEvent(overlayContainerElement.querySelector('.close-menu')!, 'click'); + fixture.detectChanges(); + tick(2000); + fixture.detectChanges(); + expect(fixture.componentInstance.triggerVisible).toHaveBeenCalledTimes(1); })); }); diff --git a/components/dropdown/dropdown.directive.ts b/components/dropdown/dropdown.directive.ts new file mode 100644 index 00000000000..5f28406cfd6 --- /dev/null +++ b/components/dropdown/dropdown.directive.ts @@ -0,0 +1,202 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { ESCAPE, hasModifierKey } from '@angular/cdk/keycodes'; +import { Overlay, OverlayRef } from '@angular/cdk/overlay'; +import { TemplatePortal } from '@angular/cdk/portal'; +import { + AfterViewInit, + Directive, + ElementRef, + EventEmitter, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + SimpleChanges, + ViewContainerRef +} from '@angular/core'; +import { InputBoolean, POSITION_MAP } from 'ng-zorro-antd/core'; +import { IndexableObject } from 'ng-zorro-antd/core/types'; +import { BehaviorSubject, combineLatest, EMPTY, fromEvent, merge, Subject } from 'rxjs'; +import { auditTime, distinctUntilChanged, filter, map, mapTo, switchMap, takeUntil } from 'rxjs/operators'; +import { NzDropdownMenuComponent, NzPlacementType } from './dropdown-menu.component'; + +const listOfPositions = [POSITION_MAP.bottomLeft, POSITION_MAP.bottomRight, POSITION_MAP.topRight, POSITION_MAP.topLeft]; + +@Directive({ + selector: '[nz-dropdown]', + exportAs: 'nzDropdown', + host: { + '[attr.disabled]': `nzDisabled ? '' : null`, + '[class.ant-dropdown-trigger]': 'true' + } +}) +export class NzDropDownDirective implements AfterViewInit, OnDestroy, OnChanges, OnInit { + private portal: TemplatePortal; + private overlayRef: OverlayRef | null = null; + private destroy$ = new Subject(); + private positionStrategy = this.overlay + .position() + .flexibleConnectedTo(this.elementRef.nativeElement) + .withLockedPosition(); + private inputVisible$ = new BehaviorSubject(false); + private nzTrigger$ = new BehaviorSubject<'click' | 'hover'>('hover'); + private overlayClose$ = new Subject(); + @Input() nzDropdownMenu: NzDropdownMenuComponent | null = null; + @Input() nzTrigger: 'click' | 'hover' = 'hover'; + @Input() nzMatchWidthElement: ElementRef | null = null; + @Input() @InputBoolean() nzBackdrop = true; + @Input() @InputBoolean() nzClickHide = true; + @Input() @InputBoolean() nzDisabled = false; + @Input() @InputBoolean() nzVisible = false; + @Input() @InputBoolean() nzTableFilter = false; + @Input() nzOverlayClassName: string | null = null; + @Input() nzOverlayStyle: IndexableObject = {}; + @Input() nzPlacement: NzPlacementType = 'bottomLeft'; + @Output() readonly nzVisibleChange: EventEmitter = new EventEmitter(); + + setDropdownMenuValue(key: T, value: NzDropdownMenuComponent[T]): void { + if (this.nzDropdownMenu) { + this.nzDropdownMenu.setValue(key, value); + } + } + + constructor(public elementRef: ElementRef, private overlay: Overlay, private viewContainerRef: ViewContainerRef) {} + + ngOnInit(): void { + this.positionStrategy.positionChanges.pipe(takeUntil(this.destroy$)).subscribe(change => { + this.setDropdownMenuValue('dropDownPosition', change.connectionPair.originY); + }); + } + + ngAfterViewInit(): void { + if (this.nzDropdownMenu) { + const nativeElement: HTMLElement = this.elementRef.nativeElement; + /** host mouse state **/ + const hostMouseState$ = merge( + fromEvent(nativeElement, 'mouseenter').pipe(mapTo(true)), + fromEvent(nativeElement, 'mouseleave').pipe(mapTo(false)) + ); + /** menu mouse state **/ + const menuMouseState$ = this.nzDropdownMenu.mouseState$; + /** merged mouse state **/ + const mergedMouseState$ = merge(menuMouseState$, hostMouseState$); + /** host click state **/ + const hostClickState$ = fromEvent(nativeElement, 'click').pipe(mapTo(true)); + /** visible state switch by nzTrigger **/ + const visibleStateByTrigger$ = this.nzTrigger$.pipe( + switchMap(trigger => { + if (trigger === 'hover') { + return mergedMouseState$; + } else if (trigger === 'click') { + return hostClickState$; + } else { + return EMPTY; + } + }) + ); + const descendantMenuItemClick$ = this.nzDropdownMenu.descendantMenuItemClick$.pipe( + filter(() => this.nzClickHide), + mapTo(false) + ); + const domTriggerVisible$ = merge(visibleStateByTrigger$, descendantMenuItemClick$, this.overlayClose$).pipe( + filter(() => !this.nzDisabled) + ); + const visible$ = merge(this.inputVisible$, domTriggerVisible$); + combineLatest([visible$, this.nzDropdownMenu.isChildSubMenuOpen$]) + .pipe( + map(([visible, sub]) => visible || sub), + auditTime(150), + distinctUntilChanged(), + takeUntil(this.destroy$) + ) + .subscribe((visible: boolean) => { + const element = this.nzMatchWidthElement ? this.nzMatchWidthElement.nativeElement : nativeElement; + const triggerWidth = element.getBoundingClientRect().width; + if (this.nzVisible !== visible) { + this.nzVisibleChange.emit(visible); + } + this.nzVisible = visible; + if (visible) { + /** set up overlayRef **/ + if (!this.overlayRef) { + /** new overlay **/ + this.overlayRef = this.overlay.create({ + positionStrategy: this.positionStrategy, + minWidth: triggerWidth, + disposeOnNavigation: true, + hasBackdrop: this.nzTrigger === 'click', + backdropClass: this.nzBackdrop ? undefined : 'nz-overlay-transparent-backdrop', + scrollStrategy: this.overlay.scrollStrategies.reposition() + }); + merge( + this.overlayRef.backdropClick(), + this.overlayRef.detachments(), + this.overlayRef.keydownEvents().pipe(filter(e => e.keyCode === ESCAPE && !hasModifierKey(e))) + ) + .pipe(mapTo(false), takeUntil(this.destroy$)) + .subscribe(this.overlayClose$); + } else { + /** update overlay config **/ + const overlayConfig = this.overlayRef.getConfig(); + overlayConfig.minWidth = triggerWidth; + overlayConfig.hasBackdrop = this.nzTrigger === 'click'; + } + /** open dropdown with animation **/ + this.positionStrategy.withPositions([POSITION_MAP[this.nzPlacement], ...listOfPositions]); + /** reset portal if needed **/ + if (!this.portal || this.portal.templateRef !== this.nzDropdownMenu!.templateRef) { + this.portal = new TemplatePortal(this.nzDropdownMenu!.templateRef, this.viewContainerRef); + } + this.overlayRef.attach(this.portal); + } else { + /** detach overlayRef if needed **/ + if (this.overlayRef) { + this.overlayRef.detach(); + } + } + }); + } + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + if (this.overlayRef) { + this.overlayRef.dispose(); + this.overlayRef = null; + } + } + + ngOnChanges(changes: SimpleChanges): void { + const { nzVisible, nzPlacement, nzDisabled, nzOverlayClassName, nzOverlayStyle, nzTableFilter, nzTrigger } = changes; + if (nzTrigger) { + this.nzTrigger$.next(this.nzTrigger); + } + if (nzVisible) { + this.inputVisible$.next(this.nzVisible); + } + if (nzDisabled && this.nzDisabled) { + this.inputVisible$.next(false); + } + if (nzTableFilter) { + this.setDropdownMenuValue('isInsideTh', this.nzTableFilter); + } + if (nzOverlayClassName) { + this.setDropdownMenuValue('nzOverlayClassName', this.nzOverlayClassName); + } + if (nzOverlayStyle) { + this.setDropdownMenuValue('nzOverlayStyle', this.nzOverlayStyle); + } + if (nzPlacement) { + this.setDropdownMenuValue('dropDownPosition', this.nzPlacement.indexOf('top') !== -1 ? 'top' : 'bottom'); + } + } +} diff --git a/components/dropdown/nz-dropdown.module.ts b/components/dropdown/dropdown.module.ts similarity index 62% rename from components/dropdown/nz-dropdown.module.ts rename to components/dropdown/dropdown.module.ts index 6315cff45e4..5f2647485e7 100644 --- a/components/dropdown/nz-dropdown.module.ts +++ b/components/dropdown/dropdown.module.ts @@ -8,21 +8,19 @@ import { OverlayModule } from '@angular/cdk/overlay'; -import { PlatformModule } from '@angular/cdk/platform'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NzButtonModule } from 'ng-zorro-antd/button'; -import { NzAddOnModule, NzNoAnimationModule, NzOverlayModule } from 'ng-zorro-antd/core'; +import { NzNoAnimationModule, NzOutletModule, NzOverlayModule } from 'ng-zorro-antd/core'; import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzMenuModule } from 'ng-zorro-antd/menu'; -import { NzContextMenuServiceModule } from './nz-context-menu.service.module'; -import { NzDropDownADirective } from './nz-dropdown-a.directive'; -import { NzDropdownButtonDirective } from './nz-dropdown-button.directive'; -import { NzDropdownMenuComponent } from './nz-dropdown-menu.component'; -import { NzDropDownDirective } from './nz-dropdown.directive'; -import { NzDropdownServiceModule } from './nz-dropdown.service.module'; +import { NzContextMenuServiceModule } from './context-menu.service.module'; +import { NzDropDownADirective } from './dropdown-a.directive'; +import { NzDropdownButtonDirective } from './dropdown-button.directive'; +import { NzDropdownMenuComponent } from './dropdown-menu.component'; +import { NzDropDownDirective } from './dropdown.directive'; @NgModule({ imports: [ @@ -32,12 +30,10 @@ import { NzDropdownServiceModule } from './nz-dropdown.service.module'; NzButtonModule, NzMenuModule, NzIconModule, - PlatformModule, NzNoAnimationModule, NzOverlayModule, - NzDropdownServiceModule, NzContextMenuServiceModule, - NzAddOnModule + NzOutletModule ], declarations: [NzDropDownDirective, NzDropDownADirective, NzDropdownMenuComponent, NzDropdownButtonDirective], exports: [NzMenuModule, NzDropDownDirective, NzDropDownADirective, NzDropdownMenuComponent, NzDropdownButtonDirective] diff --git a/components/dropdown/nz-context-menu.service.ts b/components/dropdown/nz-context-menu.service.ts deleted file mode 100644 index ce02ac3facd..00000000000 --- a/components/dropdown/nz-context-menu.service.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 - */ - -/** keep track https://github.com/angular/material2/issues/5007 **/ -import { ConnectionPositionPair, FlexibleConnectedPositionStrategy, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; -import { TemplatePortal } from '@angular/cdk/portal'; -import { Injectable } from '@angular/core'; -import { fromEvent, Subscription } from 'rxjs'; -import { filter, take } from 'rxjs/operators'; -import { NzContextMenuServiceModule } from './nz-context-menu.service.module'; -import { NzDropdownMenuComponent } from './nz-dropdown-menu.component'; - -@Injectable({ - providedIn: NzContextMenuServiceModule -}) -export class NzContextMenuService { - private overlayRef: OverlayRef; - private nzDropdownMenuComponent: NzDropdownMenuComponent; - private clickOutsideSubscription = Subscription.EMPTY; - private clickMenuSubscription = Subscription.EMPTY; - private positionSubscription = Subscription.EMPTY; - - constructor(private overlay: Overlay) {} - - create($event: MouseEvent, nzDropdownMenuComponent: NzDropdownMenuComponent): void { - $event.preventDefault(); - const overlayRef = this.createOverlay($event); - if (overlayRef.hasAttached()) { - this.close(); - } - this.attachTemplatePortal(overlayRef, nzDropdownMenuComponent); - this.handleClickOutside(); - } - - close(): void { - if (this.overlayRef) { - this.overlayRef.detach(); - this.setOpenState(false); - this.clickOutsideSubscription.unsubscribe(); - this.clickMenuSubscription.unsubscribe(); - this.positionSubscription.unsubscribe(); - } - } - - private handleClickOutside(): void { - this.clickOutsideSubscription.unsubscribe(); - this.clickOutsideSubscription = fromEvent(document, 'click') - .pipe( - filter(event => !!this.overlayRef && !this.overlayRef.overlayElement.contains(event.target as HTMLElement)), - // handle firefox contextmenu event - filter(event => event.button !== 2), - take(1) - ) - .subscribe(() => { - this.close(); - }); - } - - private attachTemplatePortal(overlayRef: OverlayRef, nzDropdownMenuComponent: NzDropdownMenuComponent): void { - this.nzDropdownMenuComponent = nzDropdownMenuComponent; - nzDropdownMenuComponent.setValue('nzTrigger', 'click'); - this.clickMenuSubscription.unsubscribe(); - this.clickMenuSubscription = nzDropdownMenuComponent.nzMenuDropdownService.menuItemClick$.subscribe(() => { - this.close(); - }); - overlayRef.attach(new TemplatePortal(nzDropdownMenuComponent.templateRef, nzDropdownMenuComponent.viewContainerRef)); - this.setOpenState(true); - } - - private setOpenState(state: boolean): void { - this.nzDropdownMenuComponent.setValue('open', state); - } - - private getOverlayConfig($event: MouseEvent): OverlayConfig { - return new OverlayConfig({ - panelClass: 'nz-dropdown-panel', - positionStrategy: this.generatePositionStrategy($event), - scrollStrategy: this.overlay.scrollStrategies.close() - }); - } - - private generatePositionStrategy($event: MouseEvent): FlexibleConnectedPositionStrategy { - return this.overlay - .position() - .flexibleConnectedTo({ x: $event.x, y: $event.y }) - .withPositions([ - new ConnectionPositionPair({ originX: 'start', originY: 'top' }, { overlayX: 'start', overlayY: 'top' }), - new ConnectionPositionPair({ originX: 'start', originY: 'top' }, { overlayX: 'start', overlayY: 'bottom' }), - new ConnectionPositionPair({ originX: 'start', originY: 'top' }, { overlayX: 'end', overlayY: 'bottom' }), - new ConnectionPositionPair({ originX: 'start', originY: 'top' }, { overlayX: 'end', overlayY: 'top' }) - ]); - } - - private subscribeToPositions(position: FlexibleConnectedPositionStrategy): void { - this.positionSubscription.unsubscribe(); - this.positionSubscription = position.positionChanges.subscribe(change => { - // TODO: positionChanges won't trigger if not dispose - this.nzDropdownMenuComponent.setValue('dropDownPosition', change.connectionPair.overlayY === 'bottom' ? 'top' : 'bottom'); - }); - } - - private createOverlay($event: MouseEvent): OverlayRef { - const config = this.getOverlayConfig($event); - if (!this.overlayRef) { - this.overlayRef = this.overlay.create(config); - } else { - this.updatePosition(this.overlayRef, $event); - } - this.subscribeToPositions(config.positionStrategy as FlexibleConnectedPositionStrategy); - return this.overlayRef; - } - - private updatePosition(overlayRef: OverlayRef, $event: MouseEvent): void { - overlayRef.updatePositionStrategy(this.generatePositionStrategy($event)); - } -} diff --git a/components/dropdown/nz-dropdown-a.directive.ts b/components/dropdown/nz-dropdown-a.directive.ts deleted file mode 100644 index cdc7d6c53ec..00000000000 --- a/components/dropdown/nz-dropdown-a.directive.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { Directive, ElementRef, Renderer2 } from '@angular/core'; - -@Directive({ - selector: 'a[nz-dropdown]', - exportAs: 'nzDropdown' -}) -export class NzDropDownADirective { - constructor(private elementRef: ElementRef, private renderer: Renderer2) { - this.renderer.addClass(this.elementRef.nativeElement, 'ant-dropdown-link'); - } -} diff --git a/components/dropdown/nz-dropdown-menu.component.html b/components/dropdown/nz-dropdown-menu.component.html deleted file mode 100644 index 9591b4ed9f8..00000000000 --- a/components/dropdown/nz-dropdown-menu.component.html +++ /dev/null @@ -1,17 +0,0 @@ - -
    -
    - -
    -
    -
    diff --git a/components/dropdown/nz-dropdown.component.html b/components/dropdown/nz-dropdown.component.html deleted file mode 100644 index 044a1c5d697..00000000000 --- a/components/dropdown/nz-dropdown.component.html +++ /dev/null @@ -1,30 +0,0 @@ - - -
    -
    - - -
    -
    -
    diff --git a/components/dropdown/nz-dropdown.directive.ts b/components/dropdown/nz-dropdown.directive.ts deleted file mode 100644 index 8e883466c46..00000000000 --- a/components/dropdown/nz-dropdown.directive.ts +++ /dev/null @@ -1,287 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { ESCAPE, hasModifierKey } from '@angular/cdk/keycodes'; -import { - ConnectedPosition, - ConnectionPositionPair, - FlexibleConnectedPositionStrategy, - Overlay, - OverlayConfig, - OverlayRef -} from '@angular/cdk/overlay'; -import { Platform } from '@angular/cdk/platform'; -import { TemplatePortal } from '@angular/cdk/portal'; -import { - AfterViewInit, - Directive, - ElementRef, - EventEmitter, - Input, - OnChanges, - OnDestroy, - Output, - Renderer2, - SimpleChanges, - ViewContainerRef -} from '@angular/core'; -import { DEFAULT_DROPDOWN_POSITIONS, InputBoolean, POSITION_MAP } from 'ng-zorro-antd/core'; -import { combineLatest, EMPTY, fromEvent, merge, Observable, Subject, Subscription } from 'rxjs'; -import { debounceTime, distinctUntilChanged, filter, map, mapTo, takeUntil, tap } from 'rxjs/operators'; -import { NzDropdownMenuComponent, NzPlacementType } from './nz-dropdown-menu.component'; - -@Directive({ - selector: '[nz-dropdown]', - exportAs: 'nzDropdown' -}) -export class NzDropDownDirective implements AfterViewInit, OnDestroy, OnChanges { - private portal: TemplatePortal; - private overlayRef: OverlayRef | null = null; - private destroy$ = new Subject(); - private triggerWidth = 0; - private el: HTMLElement = this.elementRef.nativeElement; - private dropdownOpen = false; - private positionStrategy: FlexibleConnectedPositionStrategy; - private positions: ConnectionPositionPair[] = [...DEFAULT_DROPDOWN_POSITIONS]; - private positionSubscription = Subscription.EMPTY; - private overlaySubscription = Subscription.EMPTY; - readonly hover$: Observable = merge( - fromEvent(this.el, 'mouseenter').pipe(mapTo(true)), - fromEvent(this.el, 'mouseleave').pipe(mapTo(false)) - ); - readonly $click: Observable = fromEvent(this.el, 'click').pipe( - tap(e => e.stopPropagation()), - mapTo(true) - ); - @Input() nzDropdownMenu: NzDropdownMenuComponent; - @Input() nzTrigger: 'click' | 'hover' = 'hover'; - @Input() nzMatchWidthElement: ElementRef; - @Input() @InputBoolean() nzBackdrop = true; - @Input() @InputBoolean() nzClickHide = true; - @Input() @InputBoolean() nzDisabled = false; - @Input() @InputBoolean() nzVisible = false; - @Input() @InputBoolean() nzTableFilter = false; - @Input() nzOverlayClassName = ''; - @Input() nzOverlayStyle: { [key: string]: string } = {}; - @Input() nzPlacement: NzPlacementType = 'bottomLeft'; - @Output() readonly nzVisibleChange: EventEmitter = new EventEmitter(); - - setDisabled(disabled: boolean): void { - if (disabled) { - this.renderer.setAttribute(this.el, 'disabled', ''); - if (this.nzVisible) { - this.nzVisible = false; - this.nzVisibleChange.emit(this.nzVisible); - this.updateOverlayByVisible(); - } - } else { - this.renderer.removeAttribute(this.el, 'disabled'); - } - } - - private getOverlayConfig(): OverlayConfig { - return new OverlayConfig({ - positionStrategy: this.overlay - .position() - .flexibleConnectedTo(this.el) - .withLockedPosition(), - minWidth: this.triggerWidth, - hasBackdrop: this.nzTrigger === 'click', - backdropClass: this.nzBackdrop ? undefined : 'nz-overlay-transparent-backdrop', - scrollStrategy: this.overlay.scrollStrategies.reposition() - }); - } - - private createOverlay(): OverlayRef { - if (!this.overlayRef) { - const config = this.getOverlayConfig(); - this.overlayRef = this.overlay.create(config); - this.subscribeOverlayEvent(this.overlayRef); - this.subscribeToPositions(config.positionStrategy as FlexibleConnectedPositionStrategy); - return this.overlayRef; - } else { - const overlayConfig = this.overlayRef.getConfig(); - this.updateOverlayConfig(overlayConfig); - return this.overlayRef; - } - } - - updateOverlayConfig(overlayConfig: OverlayConfig): OverlayConfig { - overlayConfig.minWidth = this.triggerWidth; - overlayConfig.hasBackdrop = this.nzTrigger === 'click'; - return overlayConfig; - } - - dispose(): void { - if (this.overlayRef) { - this.overlayRef.dispose(); - this.overlayRef = null; - this.positionSubscription.unsubscribe(); - this.overlaySubscription.unsubscribe(); - } - } - - private subscribeToPositions(position: FlexibleConnectedPositionStrategy): void { - this.positionSubscription.unsubscribe(); - this.positionSubscription = position.positionChanges.pipe(takeUntil(this.destroy$)).subscribe(change => { - this.nzDropdownMenu.setValue('dropDownPosition', change.connectionPair.originY); - }); - } - - private subscribeOverlayEvent(overlayRef: OverlayRef): void { - this.overlaySubscription.unsubscribe(); - this.overlaySubscription = merge( - overlayRef.backdropClick(), - overlayRef.detachments(), - overlayRef.keydownEvents().pipe(filter(e => e.keyCode === ESCAPE && !hasModifierKey(e))) - ) - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - this.nzDropdownMenu.setVisibleStateWhen(false); - }); - } - - private getPortal(): TemplatePortal { - if (!this.portal || this.portal.templateRef !== this.nzDropdownMenu.templateRef) { - this.portal = new TemplatePortal(this.nzDropdownMenu.templateRef, this.viewContainerRef); - } - return this.portal; - } - - private openMenu(): void { - if (!this.dropdownOpen) { - const overlayRef = this.createOverlay(); - const overlayConfig = overlayRef.getConfig(); - this.nzDropdownMenu.setValue('open', true); - this.setPosition(overlayConfig.positionStrategy as FlexibleConnectedPositionStrategy); - overlayRef.attach(this.getPortal()); - this.dropdownOpen = true; - } - } - - private closeMenu(): void { - if (this.overlayRef) { - this.overlayRef.detach(); - this.dropdownOpen = false; - this.nzDropdownMenu.setValue('open', false); - } - } - - private setPosition(positionStrategy: FlexibleConnectedPositionStrategy): void { - this.positionStrategy = positionStrategy; - positionStrategy.withPositions([...this.positions]); - } - - private updatePositionStrategy(positions: ConnectedPosition[]): void { - if (this.positionStrategy) { - this.positionStrategy.withPositions(positions); - } - } - - private setTriggerWidth(): void { - if (this.platform.isBrowser) { - const element = this.nzMatchWidthElement ? this.nzMatchWidthElement.nativeElement : this.el; - this.triggerWidth = element.getBoundingClientRect().width; - } - } - - initActionSubscribe(): void { - const hostVisible$ = this.nzTrigger === 'hover' ? this.hover$ : this.$click; - const dropdownMenuVisible$ = this.nzDropdownMenu.visible$; - const menuClickVisible$ = this.nzClickHide ? this.nzDropdownMenu.nzMenuDropdownService.menuItemClick$.pipe(mapTo(false)) : EMPTY; - const supVisible$ = merge(dropdownMenuVisible$, hostVisible$, menuClickVisible$); - const subVisible$ = this.nzDropdownMenu.nzMenuDropdownService.menuOpen$; - combineLatest([supVisible$, subVisible$]) - .pipe( - map(([supVisible, subVisible]) => supVisible || subVisible), - debounceTime(50), - distinctUntilChanged(), - takeUntil(this.destroy$) - ) - .subscribe(visible => { - if (!this.nzDisabled && this.nzVisible !== visible) { - this.nzVisible = visible; - this.updateOverlayByVisible(); - this.nzVisibleChange.emit(this.nzVisible); - this.setTriggerWidth(); - this.nzDropdownMenu.setValue('triggerWidth', this.triggerWidth); - } - }); - } - - updateOverlayByVisible(): void { - if (this.nzVisible) { - this.openMenu(); - } else { - this.closeMenu(); - } - } - - updateDisabledState(): void { - this.setDisabled(this.nzDisabled); - } - - regeneratePosition(placement: NzPlacementType, positions: ConnectionPositionPair[]): ConnectionPositionPair[] { - return [POSITION_MAP[placement], ...positions]; - } - - constructor( - public elementRef: ElementRef, - private renderer: Renderer2, - private overlay: Overlay, - private platform: Platform, - private viewContainerRef: ViewContainerRef - ) { - renderer.addClass(elementRef.nativeElement, 'ant-dropdown-trigger'); - } - - ngAfterViewInit(): void { - if (this.nzDropdownMenu) { - this.setTriggerWidth(); - this.initActionSubscribe(); - this.updateDisabledState(); - } - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - this.dispose(); - } - - ngOnChanges(changes: SimpleChanges): void { - const { nzVisible, nzTrigger, nzPlacement, nzDisabled, nzOverlayClassName, nzOverlayStyle, nzTableFilter } = changes; - if (this.nzDropdownMenu) { - if (nzVisible) { - this.updateOverlayByVisible(); - this.nzDropdownMenu.visible$.next(this.nzVisible); - } - if (nzTrigger) { - this.nzDropdownMenu.setValue('nzTrigger', this.nzTrigger); - } - if (nzTableFilter) { - this.nzDropdownMenu.setValue('nzTableFilter', this.nzTableFilter); - } - if (nzOverlayClassName) { - this.nzDropdownMenu.setValue('nzOverlayClassName', this.nzOverlayClassName); - } - if (nzOverlayStyle) { - this.nzDropdownMenu.setValue('nzOverlayStyle', this.nzOverlayStyle); - } - if (nzPlacement) { - this.nzDropdownMenu.setValue('nzPlacement', this.nzPlacement); - this.nzDropdownMenu.setValue('dropDownPosition', this.nzDropdownMenu.nzPlacement.indexOf('top') !== -1 ? 'top' : 'bottom'); - this.positions = this.regeneratePosition(this.nzPlacement, this.positions); - this.updatePositionStrategy(this.positions); - } - } - if (nzDisabled) { - this.updateDisabledState(); - } - } -} diff --git a/components/dropdown/nz-menu-dropdown.service.ts b/components/dropdown/nz-menu-dropdown.service.ts deleted file mode 100644 index 7346ed5cf24..00000000000 --- a/components/dropdown/nz-menu-dropdown.service.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { Injectable } from '@angular/core'; - -import { NzMenuBaseService } from 'ng-zorro-antd/core'; - -@Injectable() -export class NzMenuDropdownService extends NzMenuBaseService { - isInDropDown = true; -} diff --git a/components/dropdown/public-api.ts b/components/dropdown/public-api.ts index 9d0d89c4e41..1b57d563ccd 100644 --- a/components/dropdown/public-api.ts +++ b/components/dropdown/public-api.ts @@ -6,12 +6,10 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -export * from './nz-dropdown.directive'; -export * from './nz-dropdown.service.module'; -export * from './nz-dropdown.module'; -export * from './nz-menu-dropdown.service'; -export * from './nz-dropdown-a.directive'; -export * from './nz-dropdown-button.directive'; -export * from './nz-dropdown-menu.component'; -export * from './nz-context-menu.service'; -export * from './nz-context-menu.service.module'; +export * from './dropdown.directive'; +export * from './dropdown.module'; +export * from './dropdown-a.directive'; +export * from './dropdown-button.directive'; +export * from './dropdown-menu.component'; +export * from './context-menu.service'; +export * from './context-menu.service.module'; diff --git a/components/dropdown/style/patch.less b/components/dropdown/style/patch.less index a8d92282009..5fcecdab575 100644 --- a/components/dropdown/style/patch.less +++ b/components/dropdown/style/patch.less @@ -1,7 +1,16 @@ .ant-dropdown-menu { & > ul { - list-style: none; + list-style: inherit; margin: 0; padding: 0; } } + +.ant-dropdown { + top: 0; + left: 0; + position: relative; + width: 100%; + margin-top: 6px; + margin-bottom: 6px; +} diff --git a/components/empty/config.ts b/components/empty/config.ts new file mode 100644 index 00000000000..f5b9a66c6dd --- /dev/null +++ b/components/empty/config.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 + */ + +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { InjectionToken, TemplateRef, Type } from '@angular/core'; + +export type NzEmptySize = 'normal' | 'small' | ''; + +// tslint:disable-next-line:no-any +export type NzEmptyCustomContent = Type | TemplateRef | string; + +export const NZ_EMPTY_COMPONENT_NAME = new InjectionToken('nz-empty-component-name'); diff --git a/components/empty/demo/config.md b/components/empty/demo/config.md index 15f2aa5aaee..011047e9f54 100644 --- a/components/empty/demo/config.md +++ b/components/empty/demo/config.md @@ -1,5 +1,5 @@ --- -order: 2 +order: 3 title: zh-CN: 全局化配置 en-US: Default Config diff --git a/components/empty/demo/customize.md b/components/empty/demo/customize.md index 1c62515a3e8..fb582b8b11e 100644 --- a/components/empty/demo/customize.md +++ b/components/empty/demo/customize.md @@ -1,5 +1,5 @@ --- -order: 1 +order: 2 title: zh-CN: 自定义 en-US: Customize diff --git a/components/empty/demo/description.md b/components/empty/demo/description.md new file mode 100644 index 00000000000..fec0f8fa4f1 --- /dev/null +++ b/components/empty/demo/description.md @@ -0,0 +1,14 @@ +--- +order: 4 +title: + zh-CN: 无描述 + en-US: No description +--- + +## zh-CN + +无描述展示。 + +## en-US + +Simplest Usage with no description. diff --git a/components/empty/demo/description.ts b/components/empty/demo/description.ts new file mode 100644 index 00000000000..e97c3eb7a05 --- /dev/null +++ b/components/empty/demo/description.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'nz-demo-empty-description', + template: ` + + ` +}) +export class NzDemoEmptyDescriptionComponent {} diff --git a/components/empty/demo/simple.md b/components/empty/demo/simple.md new file mode 100644 index 00000000000..7da3a1a9a73 --- /dev/null +++ b/components/empty/demo/simple.md @@ -0,0 +1,14 @@ +--- +order: 1 +title: + zh-CN: 选择图片 + en-US: Chose image +--- + +## zh-CN + +可以通过设置 `nzNotFoundImage` 为 `simple` 选择另一种风格的图片。 + +## en-US + +You can choose another style of `image` by setting `simple` to `nzNotFoundImage`. diff --git a/components/empty/demo/simple.ts b/components/empty/demo/simple.ts new file mode 100644 index 00000000000..c34df2a2de4 --- /dev/null +++ b/components/empty/demo/simple.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'nz-demo-empty-simple', + template: ` + + ` +}) +export class NzDemoEmptySimpleComponent {} diff --git a/components/empty/doc/index.en-US.md b/components/empty/doc/index.en-US.md index 808373cb67d..85b34abeec5 100644 --- a/components/empty/doc/index.en-US.md +++ b/components/empty/doc/index.en-US.md @@ -21,9 +21,9 @@ import { NzEmptyModule } from 'ng-zorro-antd/empty'; | Property | Description | Type | Default | | -------- | ----------- | ---- | ------- | -| `[nzNotFoundImage]` | Customize image. Will tread as image url when string provided | `string` \| `TemplateRef` | - | -| `[nzNotFoundContent]` | Custom description | `string` \| `TemplateRef` | - | -| `[nzNotFoundFooter]` | Custom Footer | `string` \| `TemplateRef` | - | +| `[nzNotFoundImage]` | Customize image. Will tread as image url when string provided | `string \| TemplateRef` | - | +| `[nzNotFoundContent]` | Custom description | `string \| TemplateRef \| null` | - | +| `[nzNotFoundFooter]` | Custom Footer | `string \| TemplateRef` | - | ### `NZ_CONFIG` @@ -37,10 +37,19 @@ The `nzEmpty` interface has properties as follows: | Token | Description | Parameters | | ----- | --- | ---- | -| `NZ_DEFAULT_EMPTY_CONTENT` | To provide a user default empty component | `Component` \| `string` | | `NZ_EMPTY_COMPONENT_NAME` | Would be injected to `NZ_DEFAULT_EMPTY_CONTENT`, telling that component its parent component's name | `string` | ### Global Customizable Empty Content You may notice or used some inputs like `nzNotFoundContent` in some components. Now they would use `Empty` component. So you can provide `nzDefaultEmptyContent` to customize them. +```ts +{ + provide: NZ_CONFIG, + useValue: { + empty: { + nzDefaultEmptyContent + } + } +} +``` \ No newline at end of file diff --git a/components/empty/doc/index.zh-CN.md b/components/empty/doc/index.zh-CN.md index 3283c2e27d3..4222925f975 100644 --- a/components/empty/doc/index.zh-CN.md +++ b/components/empty/doc/index.zh-CN.md @@ -22,9 +22,9 @@ import { NzEmptyModule } from 'ng-zorro-antd/empty'; | 参数 | 说明 | 类型 | 默认值 | | -------- | ----------- | ---- | ------- | -| `[nzNotFoundImage]` | 设置显示图片,为 `string` 时表示自定义图片地址 | `string` \| `TemplateRef` | - | -| `[nzNotFoundContent]` | 自定义描述内容 | `string` \| `TemplateRef` | - | -| `[nzNotFoundFooter]` | 设置自定义 footer | `string` \| `TemplateRef` | - | +| `[nzNotFoundImage]` | 设置显示图片,为 `string` 时表示自定义图片地址 | `string \| TemplateRef` | - | +| `[nzNotFoundContent]` | 自定义描述内容 | `string \| TemplateRef \| null` | - | +| `[nzNotFoundFooter]` | 设置自定义 footer | `string \| TemplateRef` | - | ### `NZ_CONFIG` @@ -42,5 +42,5 @@ import { NzEmptyModule } from 'ng-zorro-antd/empty'; ### 全局自定义空组件 -你或许知道或者用过一些类似 `nzNotFoundContent` 的属性来自定义组件数据为空时的内容,现在它们都会使用 `Empty` 组件。你可以通过在 `NZ_CONFIG` 中提供 `{ nzEmpty: { nzDefaultEmptyContent } }` 来定义一个自定义的全局空组件。 +你或许知道或者用过一些类似 `nzNotFoundContent` 的属性来自定义组件数据为空时的内容,现在它们都会使用 `Empty` 组件。你可以通过在 `NZ_CONFIG` 中提供 `{ empty: { nzDefaultEmptyContent: something } }` 来定义一个自定义的全局空组件。 diff --git a/components/empty/nz-embed-empty.component.ts b/components/empty/embed-empty.component.ts similarity index 65% rename from components/empty/nz-embed-empty.component.ts rename to components/empty/embed-empty.component.ts index 9ca58dd8e7d..854061b88dd 100644 --- a/components/empty/nz-embed-empty.component.ts +++ b/components/empty/embed-empty.component.ts @@ -22,12 +22,11 @@ import { ViewContainerRef, ViewEncapsulation } from '@angular/core'; -import { DomSanitizer } from '@angular/platform-browser'; import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { startWith, takeUntil } from 'rxjs/operators'; -import { NZ_EMPTY_COMPONENT_NAME, NzEmptyCustomContent, NzEmptySize, simpleEmptyImage } from './nz-empty-config'; -import { NzEmptyService } from './nz-empty.service'; +import { NzConfigService } from 'ng-zorro-antd/core'; +import { NZ_EMPTY_COMPONENT_NAME, NzEmptyCustomContent, NzEmptySize } from './config'; function getEmptySize(componentName: string): NzEmptySize { switch (componentName) { @@ -51,7 +50,19 @@ type NzEmptyContentType = 'component' | 'template' | 'string'; encapsulation: ViewEncapsulation.None, selector: 'nz-embed-empty', exportAs: 'nzEmbedEmpty', - templateUrl: './nz-embed-empty.component.html' + template: ` + + + + + + + + + {{ content }} + + + ` }) export class NzEmbedEmptyComponent implements OnChanges, OnInit, OnDestroy { @Input() nzComponentName: string; @@ -60,14 +71,12 @@ export class NzEmbedEmptyComponent implements OnChanges, OnInit, OnDestroy { content?: NzEmptyCustomContent; contentType: NzEmptyContentType = 'string'; contentPortal?: Portal; // tslint:disable-line:no-any - defaultSvg = this.sanitizer.bypassSecurityTrustResourceUrl(simpleEmptyImage); size: NzEmptySize = ''; - private $destroy = new Subject(); + private destroy$ = new Subject(); constructor( - public emptyService: NzEmptyService, - private sanitizer: DomSanitizer, + private configService: NzConfigService, private viewContainerRef: ViewContainerRef, private cdr: ChangeDetectorRef, private injector: Injector @@ -85,15 +94,12 @@ export class NzEmbedEmptyComponent implements OnChanges, OnInit, OnDestroy { } ngOnInit(): void { - this.emptyService.userDefaultContent$.pipe(takeUntil(this.$destroy)).subscribe(content => { - this.content = this.specificContent || content; - this.renderEmpty(); - }); + this.subscribeDefaultEmptyContentChange(); } ngOnDestroy(): void { - this.$destroy.next(); - this.$destroy.complete(); + this.destroy$.next(); + this.destroy$.complete(); } private renderEmpty(): void { @@ -115,6 +121,21 @@ export class NzEmbedEmptyComponent implements OnChanges, OnInit, OnDestroy { this.contentPortal = undefined; } - this.cdr.markForCheck(); + this.cdr.detectChanges(); + } + + private subscribeDefaultEmptyContentChange(): void { + this.configService + .getConfigChangeEventForComponent('empty') + .pipe(startWith(true), takeUntil(this.destroy$)) + .subscribe(() => { + this.content = this.specificContent || this.getUserDefaultEmptyContent(); + this.renderEmpty(); + }); + } + + // tslint:disable-next-line:no-any + private getUserDefaultEmptyContent(): Type | TemplateRef | string | undefined { + return (this.configService.getConfigForComponent('empty') || {}).nzDefaultEmptyContent; } } diff --git a/components/empty/empty.component.ts b/components/empty/empty.component.ts new file mode 100644 index 00000000000..08e1d4610c2 --- /dev/null +++ b/components/empty/empty.component.ts @@ -0,0 +1,98 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, + TemplateRef, + ViewEncapsulation +} from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { NzI18nService } from 'ng-zorro-antd/i18n'; + +const NzEmptyDefaultImages = ['default', 'simple'] as const; +type NzEmptyNotFoundImageType = typeof NzEmptyDefaultImages[number] | null | string | TemplateRef; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, + selector: 'nz-empty', + exportAs: 'nzEmpty', + styles: ['nz-empty { display: block; }'], + template: ` +
    + + + + + + + +
    +

    + + {{ isContentString ? nzNotFoundContent : locale['description'] }} + +

    + + `, + host: { + class: 'ant-empty' + } +}) +export class NzEmptyComponent implements OnChanges, OnInit, OnDestroy { + @Input() nzNotFoundImage: NzEmptyNotFoundImageType = 'default'; + @Input() nzNotFoundContent: string | TemplateRef | null; + @Input() nzNotFoundFooter: string | TemplateRef; + + isContentString = false; + isImageBuildIn = true; + locale: { [key: string]: string } = {}; + + private readonly destroy$ = new Subject(); + + constructor(private i18n: NzI18nService, private cdr: ChangeDetectorRef) {} + + ngOnChanges(changes: SimpleChanges): void { + const { nzNotFoundContent, nzNotFoundImage } = changes; + + if (nzNotFoundContent) { + const content = nzNotFoundContent.currentValue; + this.isContentString = typeof content === 'string'; + } + + if (nzNotFoundImage) { + const image = nzNotFoundImage.currentValue || 'default'; + this.isImageBuildIn = NzEmptyDefaultImages.findIndex(i => i === image) > -1; + } + } + + ngOnInit(): void { + this.i18n.localeChange.pipe(takeUntil(this.destroy$)).subscribe(() => { + this.locale = this.i18n.getLocaleData('Empty'); + this.cdr.markForCheck(); + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/components/empty/nz-empty.module.ts b/components/empty/empty.module.ts similarity index 53% rename from components/empty/nz-empty.module.ts rename to components/empty/empty.module.ts index 2ead8e88b92..95e5d870388 100644 --- a/components/empty/nz-empty.module.ts +++ b/components/empty/empty.module.ts @@ -9,16 +9,18 @@ import { PortalModule } from '@angular/cdk/portal'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule } from 'ng-zorro-antd/core'; +import { NzOutletModule } from 'ng-zorro-antd/core'; import { NzI18nModule } from 'ng-zorro-antd/i18n'; -import { NzEmbedEmptyComponent } from './nz-embed-empty.component'; -import { NzEmptyComponent } from './nz-empty.component'; +import { NzEmbedEmptyComponent } from './embed-empty.component'; +import { NzEmptyComponent } from './empty.component'; +import { NzEmptyDefaultComponent } from './partial/default'; +import { NzEmptySimpleComponent } from './partial/simple'; @NgModule({ - imports: [CommonModule, PortalModule, NzAddOnModule, NzI18nModule], - declarations: [NzEmptyComponent, NzEmbedEmptyComponent], + imports: [CommonModule, PortalModule, NzOutletModule, NzI18nModule], + declarations: [NzEmptyComponent, NzEmbedEmptyComponent, NzEmptyDefaultComponent, NzEmptySimpleComponent], exports: [NzEmptyComponent, NzEmbedEmptyComponent] }) export class NzEmptyModule {} diff --git a/components/empty/nz-empty.spec.ts b/components/empty/empty.spec.ts similarity index 83% rename from components/empty/nz-empty.spec.ts rename to components/empty/empty.spec.ts index b71952d2a9a..b8850c1bfaf 100644 --- a/components/empty/nz-empty.spec.ts +++ b/components/empty/empty.spec.ts @@ -1,36 +1,32 @@ import { CommonModule } from '@angular/common'; import { Component, DebugElement, Inject, NgModule, TemplateRef, ViewChild } from '@angular/core'; -import { ComponentFixture, fakeAsync, TestBed, TestBedStatic, tick } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { NZ_CONFIG } from 'ng-zorro-antd'; +import { NZ_CONFIG, NzConfigService } from '../core'; +import { ComponentBed, createComponentBed } from '../core/testing/componet-bed'; import { NzI18nService } from '../i18n'; import en_US from '../i18n/languages/en_US'; import { NzListModule } from '../list'; +import { NZ_EMPTY_COMPONENT_NAME } from './config'; +import { NzEmbedEmptyComponent } from './embed-empty.component'; +import { NzEmptyComponent } from './empty.component'; +import { NzEmptyModule } from './empty.module'; -import { NzEmbedEmptyComponent } from './nz-embed-empty.component'; -import { NZ_EMPTY_COMPONENT_NAME } from './nz-empty-config'; -import { NzEmptyComponent } from './nz-empty.component'; -import { NzEmptyModule } from './nz-empty.module'; -import { NzEmptyService } from './nz-empty.service'; - -describe('NzEmpty', () => { +describe('nz-empty', () => { describe('basic', () => { - let testBed: TestBedStatic; + let testBed: ComponentBed; let fixture: ComponentFixture; let testComponent: NzEmptyTestBasicComponent; let emptyComponent: DebugElement; beforeEach(() => { - testBed = TestBed.configureTestingModule({ - imports: [CommonModule, NzEmptyModule], - declarations: [NzEmptyTestBasicComponent] + testBed = createComponentBed(NzEmptyTestBasicComponent, { + imports: [NzEmptyModule] }); - TestBed.compileComponents(); - - fixture = TestBed.createComponent(NzEmptyTestBasicComponent); - testComponent = fixture.debugElement.componentInstance; + fixture = testBed.fixture; + testComponent = testBed.component; emptyComponent = fixture.debugElement.query(By.directive(NzEmptyComponent)); fixture.detectChanges(); @@ -40,11 +36,12 @@ describe('NzEmpty', () => { expect(emptyComponent.nativeElement.classList.contains('ant-empty')).toBe(true); const imageEl = emptyComponent.nativeElement.firstChild; + + console.log(imageEl.outerHTML); + expect(imageEl.tagName).toBe('DIV'); expect(imageEl.classList.contains('ant-empty-image')).toBe(true); - expect(imageEl.firstElementChild.tagName).toBe('IMG'); - expect(imageEl.firstElementChild.getAttribute('alt')).toBe('empty'); - expect(imageEl.firstElementChild.src).toContain('data:image'); + expect(imageEl.firstElementChild.tagName).toBe('NZ-EMPTY-DEFAULT'); const contentEl = emptyComponent.nativeElement.lastElementChild; expect(contentEl.tagName).toBe('P'); @@ -112,11 +109,11 @@ describe('NzEmpty', () => { expect(contentEl.innerText).toBe(''); }); - it('#i18n', () => { + it('i18n', () => { const contentEl = emptyComponent.nativeElement.lastElementChild; expect(contentEl.innerText.trim()).toBe('暂无数据'); - testBed.get(NzI18nService).setLocale(en_US); + testBed.bed.get(NzI18nService).setLocale(en_US); fixture.detectChanges(); expect(contentEl.innerText.trim()).toBe('No Data'); }); @@ -161,9 +158,7 @@ describe('NzEmpty', () => { const imageEl = emptyComponent.nativeElement.firstChild; expect(imageEl.tagName).toBe('DIV'); expect(imageEl.classList.contains('ant-empty-image')).toBe(true); - expect(imageEl.firstElementChild.tagName).toBe('IMG'); - expect(imageEl.firstElementChild.getAttribute('alt')).toBe('empty'); - expect(imageEl.firstElementChild.src).toContain('data:image/svg+xml'); + expect(imageEl.firstElementChild.tagName).toBe('NZ-EMPTY-SIMPLE'); // Prop. testComponent.noResult = 'list'; @@ -180,16 +175,6 @@ describe('NzEmpty', () => { expect(embedComponent.nativeElement.innerText).toBe(''); })); - it('should raise error when set a invalid default value', () => { - expect(() => { - // tslint:disable-next-line:no-any - testComponent.emptyService.nzConfigService.set('empty', { nzDefaultEmptyContent: false as any }); - fixture.detectChanges(); - tick(); - fixture.detectChanges(); - }).toThrowError(); - }); - it('should support string, template and component', fakeAsync(() => { const refresh = () => { fixture.detectChanges(); @@ -201,7 +186,7 @@ describe('NzEmpty', () => { }; // String. - testComponent.emptyService.nzConfigService.set('empty', { nzDefaultEmptyContent: 'empty' }); + testComponent.configService.set('empty', { nzDefaultEmptyContent: 'empty' }); refresh(); expect(embedComponent).toBeTruthy(); expect(emptyComponent).toBeFalsy(); @@ -238,9 +223,7 @@ describe('NzEmpty', () => { const imageEl = emptyComponent.nativeElement.firstChild; expect(imageEl.tagName).toBe('DIV'); expect(imageEl.classList.contains('ant-empty-image')).toBe(true); - expect(imageEl.firstElementChild.tagName).toBe('IMG'); - expect(imageEl.firstElementChild.getAttribute('alt')).toBe('empty'); - expect(imageEl.firstElementChild.src).toContain('data:image/svg+xml'); + expect(imageEl.firstElementChild.tagName).toBe('NZ-EMPTY-SIMPLE'); })); }); @@ -260,7 +243,7 @@ describe('NzEmpty', () => { it('should support injection', fakeAsync(() => { const refresh = () => { fixture.detectChanges(); - tick(); + tick(100); fixture.detectChanges(); embedComponent = fixture.debugElement.query(By.directive(NzEmbedEmptyComponent)); @@ -313,14 +296,14 @@ export class NzEmptyTestServiceComponent { noResult?: string | null; - constructor(public emptyService: NzEmptyService) {} + constructor(public configService: NzConfigService) {} reset(): void { - this.emptyService.nzConfigService.set('empty', { nzDefaultEmptyContent: undefined }); + this.configService.set('empty', { nzDefaultEmptyContent: undefined }); } changeToTemplate(): void { - this.emptyService.nzConfigService.set('empty', { nzDefaultEmptyContent: this.template }); + this.configService.set('empty', { nzDefaultEmptyContent: this.template }); } } diff --git a/components/empty/nz-embed-empty.component.html b/components/empty/nz-embed-empty.component.html deleted file mode 100644 index 3170d2acd69..00000000000 --- a/components/empty/nz-embed-empty.component.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - {{ content }} - - diff --git a/components/empty/nz-empty-config.ts b/components/empty/nz-empty-config.ts deleted file mode 100644 index 989d96333d0..00000000000 --- a/components/empty/nz-empty-config.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { InjectionToken, TemplateRef, Type } from '@angular/core'; - -export type NzEmptySize = 'normal' | 'small' | ''; - -// tslint:disable-next-line:no-any -export type NzEmptyCustomContent = Type | TemplateRef | string; - -// tslint:disable-next-line:no-any -export const NZ_DEFAULT_EMPTY_CONTENT = new InjectionToken>('nz-empty-content'); - -export const NZ_EMPTY_COMPONENT_NAME = new InjectionToken('nz-empty-component-name'); - -export const emptyImage = - 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTg0IiBoZWlnaHQ9IjE1MiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI0IDMxLjY3KSI+PGVsbGlwc2UgZmlsbC1vcGFjaXR5PSIuOCIgZmlsbD0iI0Y1RjVGNyIgY3g9IjY3Ljc5NyIgY3k9IjEwNi44OSIgcng9IjY3Ljc5NyIgcnk9IjEyLjY2OCIvPjxwYXRoIGQ9Ik0xMjIuMDM0IDY5LjY3NEw5OC4xMDkgNDAuMjI5Yy0xLjE0OC0xLjM4Ni0yLjgyNi0yLjIyNS00LjU5My0yLjIyNWgtNTEuNDRjLTEuNzY2IDAtMy40NDQuODM5LTQuNTkyIDIuMjI1TDEzLjU2IDY5LjY3NHYxNS4zODNoMTA4LjQ3NVY2OS42NzR6IiBmaWxsPSIjQUVCOEMyIi8+PHBhdGggZD0iTTEwMS41MzcgODYuMjE0TDgwLjYzIDYxLjEwMmMtMS4wMDEtMS4yMDctMi41MDctMS44NjctNC4wNDgtMS44NjdIMzEuNzI0Yy0xLjU0IDAtMy4wNDcuNjYtNC4wNDggMS44NjdMNi43NjkgODYuMjE0djEzLjc5Mmg5NC43NjhWODYuMjE0eiIgZmlsbD0idXJsKCNsaW5lYXJHcmFkaWVudC0xKSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTMuNTYpIi8+PHBhdGggZD0iTTMzLjgzIDBoNjcuOTMzYTQgNCAwIDAgMSA0IDR2OTMuMzQ0YTQgNCAwIDAgMS00IDRIMzMuODNhNCA0IDAgMCAxLTQtNFY0YTQgNCAwIDAgMSA0LTR6IiBmaWxsPSIjRjVGNUY3Ii8+PHBhdGggZD0iTTQyLjY3OCA5Ljk1M2g1MC4yMzdhMiAyIDAgMCAxIDIgMlYzNi45MWEyIDIgMCAwIDEtMiAySDQyLjY3OGEyIDIgMCAwIDEtMi0yVjExLjk1M2EyIDIgMCAwIDEgMi0yek00Mi45NCA0OS43NjdoNDkuNzEzYTIuMjYyIDIuMjYyIDAgMSAxIDAgNC41MjRINDIuOTRhMi4yNjIgMi4yNjIgMCAwIDEgMC00LjUyNHpNNDIuOTQgNjEuNTNoNDkuNzEzYTIuMjYyIDIuMjYyIDAgMSAxIDAgNC41MjVINDIuOTRhMi4yNjIgMi4yNjIgMCAwIDEgMC00LjUyNXpNMTIxLjgxMyAxMDUuMDMyYy0uNzc1IDMuMDcxLTMuNDk3IDUuMzYtNi43MzUgNS4zNkgyMC41MTVjLTMuMjM4IDAtNS45Ni0yLjI5LTYuNzM0LTUuMzZhNy4zMDkgNy4zMDkgMCAwIDEtLjIyMi0xLjc5VjY5LjY3NWgyNi4zMThjMi45MDcgMCA1LjI1IDIuNDQ4IDUuMjUgNS40MnYuMDRjMCAyLjk3MSAyLjM3IDUuMzcgNS4yNzcgNS4zN2gzNC43ODVjMi45MDcgMCA1LjI3Ny0yLjQyMSA1LjI3Ny01LjM5M1Y3NS4xYzAtMi45NzIgMi4zNDMtNS40MjYgNS4yNS01LjQyNmgyNi4zMTh2MzMuNTY5YzAgLjYxNy0uMDc3IDEuMjE2LS4yMjEgMS43ODl6IiBmaWxsPSIjRENFMEU2Ii8+PC9nPjxwYXRoIGQ9Ik0xNDkuMTIxIDMzLjI5MmwtNi44MyAyLjY1YTEgMSAwIDAgMS0xLjMxNy0xLjIzbDEuOTM3LTYuMjA3Yy0yLjU4OS0yLjk0NC00LjEwOS02LjUzNC00LjEwOS0xMC40MDhDMTM4LjgwMiA4LjEwMiAxNDguOTIgMCAxNjEuNDAyIDAgMTczLjg4MSAwIDE4NCA4LjEwMiAxODQgMTguMDk3YzAgOS45OTUtMTAuMTE4IDE4LjA5Ny0yMi41OTkgMTguMDk3LTQuNTI4IDAtOC43NDQtMS4wNjYtMTIuMjgtMi45MDJ6IiBmaWxsPSIjRENFMEU2Ii8+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTQ5LjY1IDE1LjM4MykiIGZpbGw9IiNGRkYiPjxlbGxpcHNlIGN4PSIyMC42NTQiIGN5PSIzLjE2NyIgcng9IjIuODQ5IiByeT0iMi44MTUiLz48cGF0aCBkPSJNNS42OTggNS42M0gwTDIuODk4LjcwNHpNOS4yNTkuNzA0aDQuOTg1VjUuNjNIOS4yNTl6Ii8+PC9nPjwvZz48L3N2Zz4='; - -export const simpleEmptyImage = - 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iNjRweCIgaGVpZ2h0PSI0MXB4IiB2aWV3Qm94PSIwIDAgNjQgNDEiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDUyLjUgKDY3NDY5KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDxnIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPgogICAgICAgIDxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC00NzIuMDAwMDAwLCAtMTMzNS4wMDAwMDApIj4KICAgICAgICAgICAgPGcgIHRyYW5zZm9ybT0idHJhbnNsYXRlKDY0LjAwMDAwMCwgMTExNC4wMDAwMDApIj4KICAgICAgICAgICAgICAgIDxnICB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0MC4wMDAwMDAsIDc4LjAwMDAwMCkiPgogICAgICAgICAgICAgICAgICAgIDxnICB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzNjguMDAwMDAwLCAxNDQuMDAwMDAwKSI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxnID4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxlbGxpcHNlICBmaWxsPSIjRjVGNUY1IiBjeD0iMzIiIGN5PSIzMyIgcng9IjMyIiByeT0iNyI+PC9lbGxpcHNlPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPGcgICB0cmFuc2Zvcm09InRyYW5zbGF0ZSg5LjAwMDAwMCwgMC4wMDAwMDApIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0iI0Q5RDlEOSI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTQ2LDEyLjc2MDU2MDQgTDM1Ljg1NDMwNDcsMS4yNTczOTYzMyBDMzUuMzY3NDQxNCwwLjQ3MzgyNjYwNSAzNC42NTU4Nzg5LDAgMzMuOTA2NzYxNywwIEwxMi4wOTMyMzgzLDAgQzExLjM0NDEyMTEsMCAxMC42MzI1NTg2LDAuNDczOTUwMjU1IDEwLjE0NTY5NTMsMS4yNTczOTYzMyBMMi42MTQ3OTcyN2UtMTIsMTIuNzYwNTYwNCBMMCwyMiBMNDYsMjIgTDQ2LDEyLjc2MDU2MDQgWiIgID48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTMyLjYxMzI4MTMsMTUuOTMxNSBDMzIuNjEzMjgxMywxNC4zMjU4NTExIDMzLjYwNjk1MzEsMTMuMDAwMjM0IDM0LjgzOTY5OTIsMTMgTDQ2LDEzIEw0NiwzMS4xMzcxMjc3IEM0NiwzMy4yNTg5NTc0IDQ0LjY3OTM4NjcsMzUgNDMuMDUwNDI5NywzNSBMMi45NDk1NzAzMSwzNSBDMS4zMjA1MjM0NCwzNSAwLDMzLjI1ODg0MDQgMCwzMS4xMzcxMjc3IEwwLDEzIEwxMS4xNjAzMDA4LDEzIEMxMi4zOTMwNDY5LDEzIDEzLjM4NjcxODgsMTQuMzIyODA4NSAxMy4zODY3MTg4LDE1LjkyODQ1NzQgTDEzLjM4NjcxODgsMTUuOTQ5NjM4MyBDMTMuMzg2NzE4OCwxNy41NTUyODcyIDE0LjM5MTcxMDksMTguODUxMTgwOSAxNS42MjQ0NTcsMTguODUxMTgwOSBMMzAuMzc1NTQzLDE4Ljg1MTE4MDkgQzMxLjYwODI4OTEsMTguODUxMTgwOSAzMi42MTMyODEzLDE3LjU0MzM1MTEgMzIuNjEzMjgxMywxNS45Mzc3MDIxIEwzMi42MTMyODEzLDE1LjkzMTUgWiIgIGZpbGw9IiNGQUZBRkEiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgPC9nPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+'; diff --git a/components/empty/nz-empty.component.html b/components/empty/nz-empty.component.html deleted file mode 100644 index 47559c50508..00000000000 --- a/components/empty/nz-empty.component.html +++ /dev/null @@ -1,18 +0,0 @@ -
    - - - -
    -

    - - {{ shouldRenderContent ? nzNotFoundContent : locale['description'] }} - -

    - diff --git a/components/empty/nz-empty.component.ts b/components/empty/nz-empty.component.ts deleted file mode 100644 index 3882da7721f..00000000000 --- a/components/empty/nz-empty.component.ts +++ /dev/null @@ -1,82 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - Input, - OnChanges, - OnDestroy, - OnInit, - SimpleChanges, - TemplateRef, - ViewEncapsulation -} from '@angular/core'; -import { DomSanitizer } from '@angular/platform-browser'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; - -import { NzI18nService } from 'ng-zorro-antd/i18n'; - -import { emptyImage } from './nz-empty-config'; - -@Component({ - changeDetection: ChangeDetectionStrategy.OnPush, - encapsulation: ViewEncapsulation.None, - selector: 'nz-empty', - exportAs: 'nzEmpty', - templateUrl: './nz-empty.component.html', - styles: ['nz-empty { display: block; }'], - host: { - class: 'ant-empty' - } -}) -export class NzEmptyComponent implements OnChanges, OnInit, OnDestroy { - @Input() nzNotFoundImage: string | TemplateRef; - @Input() nzNotFoundContent: string | TemplateRef; - @Input() nzNotFoundFooter: string | TemplateRef; - - // NOTE: It would be very hack to use `ContentChild`, because Angular could - // tell if user actually pass something to . - // See: https://github.com/angular/angular/issues/12530. - // I can use a directive but this would expose the name `footer`. - // @ContentChild(TemplateRef, {static: false}) nzNotFoundFooter: TemplateRef; - - defaultSvg = this.sanitizer.bypassSecurityTrustResourceUrl(emptyImage); - isContentString = false; - locale: { [key: string]: string } = {}; - - get shouldRenderContent(): boolean { - const content = this.nzNotFoundContent; - return !!(content || typeof content === 'string'); - } - - private destroy$ = new Subject(); - - constructor(private sanitizer: DomSanitizer, private i18n: NzI18nService, private cdr: ChangeDetectorRef) {} - - ngOnChanges(changes: SimpleChanges): void { - const { nzNotFoundContent } = changes; - if (nzNotFoundContent) { - this.isContentString = typeof nzNotFoundContent.currentValue === 'string'; - } - } - - ngOnInit(): void { - this.i18n.localeChange.pipe(takeUntil(this.destroy$)).subscribe(() => { - this.locale = this.i18n.getLocaleData('Empty'); - this.cdr.markForCheck(); - }); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } -} diff --git a/components/empty/nz-empty.service.ts b/components/empty/nz-empty.service.ts deleted file mode 100644 index 07232b41f5d..00000000000 --- a/components/empty/nz-empty.service.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { Injectable, TemplateRef, Type } from '@angular/core'; -import { BehaviorSubject } from 'rxjs'; -import { startWith } from 'rxjs/operators'; - -import { NzConfigService, PREFIX } from 'ng-zorro-antd/core'; - -import { NzEmptyCustomContent } from './nz-empty-config'; - -@Injectable({ - providedIn: 'root' -}) -export class NzEmptyService { - userDefaultContent$ = new BehaviorSubject(undefined); - - constructor(public nzConfigService: NzConfigService) { - this.subscribeDefaultEmptyContentChange(); - } - - private subscribeDefaultEmptyContentChange(): void { - this.nzConfigService.getConfigChangeEventForComponent('empty').pipe(startWith(true)).subscribe(() => { - this.userDefaultContent$.next(this.getUserDefaultEmptyContent()); - }); - } - - // tslint:disable-next-line:no-any - private getUserDefaultEmptyContent(): Type | TemplateRef | string | undefined { - const content = (this.nzConfigService.getConfigForComponent('empty') || {}).nzDefaultEmptyContent; - - if ( - typeof content === 'string' || - content === undefined || - content === null || - content instanceof TemplateRef || - content instanceof Type - ) { - return content; - } else { - throw new Error( - `${PREFIX} default empty content expects a 'null', 'undefined', 'string', 'TemplateRef' or 'Component' but get ${content}.` - ); - } - } -} diff --git a/components/empty/partial/default.ts b/components/empty/partial/default.ts new file mode 100644 index 00000000000..9b3a5e4f4ed --- /dev/null +++ b/components/empty/partial/default.ts @@ -0,0 +1,48 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, + selector: 'nz-empty-default', + exportAs: 'nzEmptyDefault', + template: ` + + + + + + + + + + + + + + + + + ` +}) +export class NzEmptyDefaultComponent {} diff --git a/components/empty/partial/simple.ts b/components/empty/partial/simple.ts new file mode 100644 index 00000000000..288b9ad16c1 --- /dev/null +++ b/components/empty/partial/simple.ts @@ -0,0 +1,31 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, + selector: 'nz-empty-simple', + exportAs: 'nzEmptySimple', + template: ` + + + + + + + + + + ` +}) +export class NzEmptySimpleComponent {} diff --git a/components/empty/public-api.ts b/components/empty/public-api.ts index 180ca97c025..16609642e80 100644 --- a/components/empty/public-api.ts +++ b/components/empty/public-api.ts @@ -6,8 +6,7 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -export * from './nz-embed-empty.component'; -export * from './nz-empty.component'; -export * from './nz-empty.module'; -export * from './nz-empty.service'; -export * from './nz-empty-config'; +export * from './embed-empty.component'; +export * from './empty.component'; +export * from './empty.module'; +export * from './config'; diff --git a/components/form/nz-form.module.ts b/components/form/nz-form.module.ts index 5a3b944d3cf..15cb4124423 100644 --- a/components/form/nz-form.module.ts +++ b/components/form/nz-form.module.ts @@ -11,7 +11,7 @@ import { PlatformModule } from '@angular/cdk/platform'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule } from 'ng-zorro-antd/core'; +import { NzOutletModule } from 'ng-zorro-antd/core'; import { NzGridModule } from 'ng-zorro-antd/grid'; import { NzIconModule } from 'ng-zorro-antd/icon'; @@ -46,6 +46,6 @@ import { NzFormDirective } from './nz-form.directive'; NzFormSplitComponent, NzGridModule ], - imports: [CommonModule, NzGridModule, NzIconModule, LayoutModule, PlatformModule, NzAddOnModule] + imports: [CommonModule, NzGridModule, NzIconModule, LayoutModule, PlatformModule, NzOutletModule] }) export class NzFormModule {} diff --git a/components/form/style/index.less b/components/form/style/index.less index 0a182f0d00d..b8f0279e106 100644 --- a/components/form/style/index.less +++ b/components/form/style/index.less @@ -62,9 +62,10 @@ height: 100%; max-height: @input-height-base; color: @label-color; + font-size: @form-item-label-font-size; > .@{iconfont-css-prefix} { - font-size: @font-size-base; + font-size: @form-item-label-font-size; vertical-align: top; } @@ -72,7 +73,7 @@ display: inline-block; margin-right: 4px; color: @label-required-color; - font-size: @font-size-base; + font-size: @form-item-label-font-size; font-family: SimSun, sans-serif; line-height: 1; content: '*'; diff --git a/components/form/style/mixin.less b/components/form/style/mixin.less index ef6de5c815d..1f3b3b299eb 100644 --- a/components/form/style/mixin.less +++ b/components/form/style/mixin.less @@ -6,14 +6,16 @@ color: @text-color; } // 输入框的不同校验状态 - .@{ant-prefix}-input { + .@{ant-prefix}-input, + .@{ant-prefix}-input-affix-wrapper { &, &:hover { background-color: @background-color; border-color: @border-color; } - &:focus { + &:focus, + &-focused { .active(@border-color); } @@ -22,27 +24,14 @@ } } - .@{ant-prefix}-calendar-picker-open .@{ant-prefix}-calendar-picker-input { - .active(@border-color); - } - - // Input prefix .@{ant-prefix}-input-affix-wrapper { - .@{ant-prefix}-input { - &, - &:hover { - background-color: @background-color; - border-color: @border-color; - } - - &:focus { - .active(@border-color); - } + input:focus { + box-shadow: none !important; } + } - &:hover .@{ant-prefix}-input:not(.@{ant-prefix}-input-disabled) { - border-color: @border-color; - } + .@{ant-prefix}-calendar-picker-open .@{ant-prefix}-calendar-picker-input { + .active(@border-color); } .@{ant-prefix}-input-prefix { diff --git a/components/form/style/vertical.less b/components/form/style/vertical.less index feb9d99eebc..f724a86604b 100644 --- a/components/form/style/vertical.less +++ b/components/form/style/vertical.less @@ -20,6 +20,12 @@ } } +.make-vertical-layout() { + .@{form-prefix-cls}-item .@{form-prefix-cls}-item-label { + .make-vertical-layout-label(); + } +} + .@{form-prefix-cls}-vertical { .@{form-item-prefix-cls} { flex-direction: column; @@ -34,6 +40,7 @@ } @media (max-width: @screen-xs-max) { + .make-vertical-layout(); .@{ant-prefix}-col-xs-24.@{form-item-prefix-cls}-label { .make-vertical-layout-label(); } diff --git a/components/grid/row.directive.ts b/components/grid/row.directive.ts index d45e7067ab9..3976d5c45fa 100644 --- a/components/grid/row.directive.ts +++ b/components/grid/row.directive.ts @@ -19,7 +19,17 @@ export type NzAlign = 'top' | 'middle' | 'bottom'; @Directive({ selector: '[nz-row],nz-row,nz-form-item', exportAs: 'nzRow', - host: { '[class]': 'hostClassMap' } + host: { + '[class.ant-row]': `true`, + '[class.ant-row-top]': `nzAlign === 'top'`, + '[class.ant-row-middle]': `nzAlign === 'middle'`, + '[class.ant-row-bottom]': `nzAlign === 'bottom'`, + '[class.ant-row-start]': `nzJustify === 'start'`, + '[class.ant-row-end]': `nzJustify === 'end'`, + '[class.ant-row-center]': `nzJustify === 'center'`, + '[class.ant-row-space-around]': `nzJustify === 'space-around'`, + '[class.ant-row-space-between]': `nzJustify === 'space-between'` + } }) export class NzRowDirective implements OnInit, OnChanges, AfterViewInit, OnDestroy { /** @@ -31,7 +41,6 @@ export class NzRowDirective implements OnInit, OnChanges, AfterViewInit, OnDestr @Input() nzGutter: number | IndexableObject | [number, number] | [IndexableObject, IndexableObject] | null = null; actualGutter$ = new ReplaySubject<[number, number]>(1); destroy$ = new Subject(); - hostClassMap: IndexableObject = {}; getGutter(breakPoint: NzBreakpointKey): [number, number] { const results: [number, number] = [0, 0]; @@ -70,21 +79,6 @@ export class NzRowDirective implements OnInit, OnChanges, AfterViewInit, OnDestr renderGutter('margin-bottom', verticalGutter); } } - - setHostClassMap(): void { - this.hostClassMap = { - ['ant-row']: true, - ['ant-row-top']: this.nzAlign === 'top', - ['ant-row-middle']: this.nzAlign === 'middle', - ['ant-row-bottom']: this.nzAlign === 'bottom', - ['ant-row-start']: this.nzJustify === 'start', - ['ant-row-end']: this.nzJustify === 'end', - ['ant-row-center']: this.nzJustify === 'center', - ['ant-row-space-around']: this.nzJustify === 'space-around', - ['ant-row-space-between']: this.nzJustify === 'space-between' - }; - } - constructor( public elementRef: ElementRef, public renderer: Renderer2, @@ -95,14 +89,10 @@ export class NzRowDirective implements OnInit, OnChanges, AfterViewInit, OnDestr ) {} ngOnInit(): void { - this.setHostClassMap(); this.setGutterStyle(); } ngOnChanges(changes: SimpleChanges): void { - if (changes.nzType || changes.nzAlign || changes.nzJustify) { - this.setHostClassMap(); - } if (changes.nzGutter) { this.setGutterStyle(); } diff --git a/components/input-number/demo/precision.md b/components/input-number/demo/precision.md new file mode 100644 index 00000000000..155122335ac --- /dev/null +++ b/components/input-number/demo/precision.md @@ -0,0 +1,14 @@ +--- +order: 5 +title: + zh-CN: 精度 + en-US: Precision +--- + +## zh-CN + +指定 value 的精度 + +## en-US + +Set precision of the value diff --git a/components/input-number/demo/precision.ts b/components/input-number/demo/precision.ts new file mode 100644 index 00000000000..6a2e1e7e992 --- /dev/null +++ b/components/input-number/demo/precision.ts @@ -0,0 +1,31 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'nz-demo-input-number-precision', + template: ` + + + + `, + styles: [ + ` + nz-input-number { + margin-right: 8px; + } + ` + ] +}) +export class NzDemoInputNumberPrecisionComponent { + toFixedValue = 2; + cutValue = 2; + customFnValue = 2; + precision = 2; + customPrecisionFn(value: number, precision: number): number { + return +Number(value).toFixed(precision + 1); + } +} diff --git a/components/input-number/doc/index.en-US.md b/components/input-number/doc/index.en-US.md index 81e4a890c23..3e262e24dc2 100755 --- a/components/input-number/doc/index.en-US.md +++ b/components/input-number/doc/index.en-US.md @@ -6,8 +6,6 @@ title: InputNumber Enter a number within certain range with the mouse or keyboard. -> Note:InputNumber will validate the input value only when `(blur)` and `(keydown.enter)` happened other than when user input character to avoid error `ngModelChange` output (-0.02001 or -1.0e28) - ## When To Use When a numeric value needs to be provided. @@ -20,8 +18,6 @@ import { NzInputNumberModule } from 'ng-zorro-antd/input-number'; ### nz-input-number -The value entered in `nz-input-number` will not be verified at the time of input, but will be fed back to `[ngModel]` and `(ngModelChange)` at a specific timing (Enter key, up and down keys, blur, etc.), otherwise input data such as `-0.12` or `1e10`, the `ngModel` will always be `undefined`. - | property | description | type | default | | -------- | ----------- | ---- | ------- | | `[ngModel]` | current value, double binding | `number \| string` \| `string` | - | @@ -30,8 +26,9 @@ The value entered in `nz-input-number` will not be verified at the time of input | `[nzMax]` | max value | `number` | `Infinity` | | `[nzMin]` | min value | `number` | `-Infinity` | | `[nzFormatter]` | Specifies the format of the value presented | `(value: number \| string) => string \| number` | - | -| `[nzParser]` | Specifies the value extracted from nzFormatter | `(value: string) => string \| number` | - | +| `[nzParser]` | Specifies the value extracted from nzFormatter | `(value: string) => string \| number` | `(value: string) => value.trim().replace(/。/g, '.').replace(/[^\w\.-]+/g, '')` | | `[nzPrecision]` | precision of input value | `number` | - | +| `[nzPrecisionMode]` | The method for calculating the precision of input value | `'cut' \| 'toFixed' \| ((value: number \| string, precision?: number) => number)` | `'toFixed'` | | `[nzSize]` | width of input box | `'large' \| 'small' \| 'default'` | `'default'` | | `[nzStep]` | The number to which the current value is increased or decreased. It can be an integer or decimal. | `number \| string` | `1` | | `[nzPlaceHolder]` | Placeholder of select | `string` | - | diff --git a/components/input-number/doc/index.zh-CN.md b/components/input-number/doc/index.zh-CN.md index 3a26446b9e6..228df7535df 100755 --- a/components/input-number/doc/index.zh-CN.md +++ b/components/input-number/doc/index.zh-CN.md @@ -7,8 +7,6 @@ title: InputNumber 通过鼠标或键盘,输入范围内的数值。 -> 注意:InputNumber 会在 `(blur)` 和 `(keydown.enter)` 时触发校验,而不是在用户输入每个字符时立刻进行校验,以此来避免反复输出错误校验结果的情况(例如输入 -0.02001 或者 -1.0e28) - ## 何时使用 当需要获取标准数值时。 @@ -21,8 +19,6 @@ import { NzInputNumberModule } from 'ng-zorro-antd/input-number'; ### nz-input-number -在 `nz-input-number` 中输入的数值不会在输入时进行校验,而是在特定时机(回车键,上下键,失去焦点等)时才会校验后反馈到 `[ngModel]` 和 `(ngModelChange)` 中,否则输入 `-0.12` 或者 `1e10` 这种类型数据时,双向绑定的数据会永远是 `undefined` - | 成员 | 说明 | 类型 | 默认值 | | --- | --- | --- | --- | | `[ngModel]` | 当前值,可双向绑定 | `number \| string` \| `string` | - | @@ -31,8 +27,9 @@ import { NzInputNumberModule } from 'ng-zorro-antd/input-number'; | `[nzMax]` | 最大值 | `number` | `Infinity` | | `[nzMin]` | 最小值 | `number` | `-Infinity` | | `[nzFormatter]` | 指定输入框展示值的格式 | `(value: number \| string) => string \| number` | - | -| `[nzParser]` | 指定从 nzFormatter 里转换回数字的方式,和 nzFormatter 搭配使用 | `(value: string) => string \| number` | - | +| `[nzParser]` | 指定从 nzFormatter 里转换回数字的方式,和 nzFormatter 搭配使用 | `(value: string) => string \| number` | `(value: string) => value.trim().replace(/。/g, '.').replace(/[^\w\.-]+/g, '')` | | `[nzPrecision]` | 数值精度 | `number` | - | +| `[nzPrecisionMode]` | 数值精度的取值方式 | `'cut' \| 'toFixed' \| ((value: number \| string, precision?: number) => number)` | `'toFixed'` | | `[nzSize]` | 输入框大小 | `'large' \| 'small' \| 'default'` | `'default'` | | `[nzStep]` | 每次改变步数,可以为小数 | `number \| string` | `1` | | `[nzPlaceHolder]` | 选择框默认文字 | `string` | - | diff --git a/components/input-number/nz-input-number.component.ts b/components/input-number/input-number.component.ts similarity index 66% rename from components/input-number/nz-input-number.component.ts rename to components/input-number/input-number.component.ts index 443f36f26e1..5e592487107 100644 --- a/components/input-number/nz-input-number.component.ts +++ b/components/input-number/input-number.component.ts @@ -21,7 +21,6 @@ import { OnDestroy, OnInit, Output, - Renderer2, SimpleChanges, ViewChild, ViewEncapsulation @@ -29,11 +28,53 @@ import { import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { InputBoolean, isNotNil, NzSizeLDSType } from 'ng-zorro-antd/core'; +import { OnChangeType, OnTouchedType } from 'ng-zorro-antd/core/types'; @Component({ selector: 'nz-input-number', exportAs: 'nzInputNumber', - templateUrl: './nz-input-number.component.html', + template: ` +
    + + + + + + +
    +
    + +
    + `, providers: [ { provide: NG_VALUE_ACCESSOR, @@ -44,6 +85,7 @@ import { InputBoolean, isNotNil, NzSizeLDSType } from 'ng-zorro-antd/core'; changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: { + '[class.ant-input-number]': 'true', '[class.ant-input-number-focused]': 'isFocused', '[class.ant-input-number-lg]': `nzSize === 'large'`, '[class.ant-input-number-sm]': `nzSize === 'small'`, @@ -52,47 +94,39 @@ import { InputBoolean, isNotNil, NzSizeLDSType } from 'ng-zorro-antd/core'; }) export class NzInputNumberComponent implements ControlValueAccessor, AfterViewInit, OnChanges, OnInit, OnDestroy { private autoStepTimer: number; - public actualValue: string | number; - private value: string | number; + private parsedValue: string | number; + private value: number; displayValue: string | number; isFocused = false; disabledUp = false; disabledDown = false; - onChange: (value: number) => void = () => null; - onTouched: () => void = () => null; + onChange: OnChangeType = () => {}; + onTouched: OnTouchedType = () => {}; @Output() readonly nzBlur = new EventEmitter(); @Output() readonly nzFocus = new EventEmitter(); @ViewChild('inputElement', { static: true }) inputElement: ElementRef; @Input() nzSize: NzSizeLDSType = 'default'; @Input() nzMin: number = -Infinity; @Input() nzMax: number = Infinity; - @Input() nzParser = (value: any) => value; // tslint:disable-line:no-any + @Input() nzParser = (value: string) => + value + .trim() + .replace(/。/g, '.') + .replace(/[^\w\.-]+/g, ''); @Input() nzPrecision: number; + @Input() nzPrecisionMode: 'cut' | 'toFixed' | ((value: number | string, precision?: number) => number) = 'toFixed'; @Input() nzPlaceHolder = ''; @Input() nzStep = 1; - @Input() nzId: string; + @Input() nzId: string | null = null; @Input() @InputBoolean() nzDisabled = false; @Input() @InputBoolean() nzAutoFocus = false; @Input() nzFormatter: (value: number) => string | number = value => value; - [property: string]: any; // tslint:disable-line:no-any - - updateAutoFocus(): void { - if (this.nzAutoFocus) { - this.renderer.setAttribute(this.inputElement.nativeElement, 'autofocus', 'autofocus'); - } else { - this.renderer.removeAttribute(this.inputElement.nativeElement, 'autofocus'); - } - } - onModelChange(value: string): void { - this.actualValue = this.nzParser( - value - .trim() - .replace(/。/g, '.') - .replace(/[^\w\.-]+/g, '') - ); - this.inputElement.nativeElement.value = `${this.actualValue}`; + this.parsedValue = this.nzParser(value); + this.inputElement.nativeElement.value = `${this.parsedValue}`; + const validValue = this.getCurrentValidValue(this.parsedValue); + this.setValue(validValue); } getCurrentValidValue(value: string | number): number { @@ -100,7 +134,7 @@ export class NzInputNumberComponent implements ControlValueAccessor, AfterViewIn if (val === '') { val = ''; } else if (!this.isNotCompleteNumber(val)) { - val = this.getValidValue(val) as string; + val = `${this.getValidValue(val)}`; } else { val = this.value; } @@ -131,26 +165,20 @@ export class NzInputNumberComponent implements ControlValueAccessor, AfterViewIn if (this.isNotCompleteNumber(num)) { return num as number; } - if (isNotNil(this.nzPrecision)) { + const numStr = String(num); + if (numStr.indexOf('.') >= 0 && isNotNil(this.nzPrecision)) { + if (typeof this.nzPrecisionMode === 'function') { + return this.nzPrecisionMode(num, this.nzPrecision); + } else if (this.nzPrecisionMode === 'cut') { + const numSplit = numStr.split('.'); + numSplit[1] = numSplit[1].slice(0, this.nzPrecision); + return Number(numSplit.join('.')); + } return Number(Number(num).toFixed(this.nzPrecision)); } return Number(num); } - setValidateValue(): void { - const value = this.getCurrentValidValue(this.actualValue); - this.setValue(value, `${this.value}` !== `${value}`); - } - - onBlur(): void { - this.isFocused = false; - this.setValidateValue(); - } - - onFocus(): void { - this.isFocused = true; - } - getRatio(e: KeyboardEvent): number { let ratio = 1; if (e.metaKey || e.ctrlKey) { @@ -234,13 +262,13 @@ export class NzInputNumberComponent implements ControlValueAccessor, AfterViewIn return this.toNumber(result); } - step(type: string, e: MouseEvent | KeyboardEvent, ratio: number = 1): void { + step(type: T, e: MouseEvent | KeyboardEvent, ratio: number = 1): void { this.stop(); e.preventDefault(); if (this.nzDisabled) { return; } - const value = this.getCurrentValidValue(this.actualValue) || 0; + const value = this.getCurrentValidValue(this.parsedValue) || 0; let val = 0; if (type === 'up') { val = this.upStep(value, ratio); @@ -253,14 +281,15 @@ export class NzInputNumberComponent implements ControlValueAccessor, AfterViewIn } else if (val < this.nzMin) { val = this.nzMin; } - this.setValue(val, true); + this.setValue(val); + this.updateDisplayValue(val); this.isFocused = true; if (outOfRange) { return; } this.autoStepTimer = setTimeout(() => { - this[type](e, ratio, true); - }, 600); + (this[type] as (e: MouseEvent | KeyboardEvent, ratio: number) => void)(e, ratio); + }, 300); } stop(): void { @@ -269,15 +298,12 @@ export class NzInputNumberComponent implements ControlValueAccessor, AfterViewIn } } - setValue(value: number, emit: boolean): void { - if (emit && `${this.value}` !== `${value}`) { + setValue(value: number): void { + if (`${this.value}` !== `${value}`) { this.onChange(value); } this.value = value; - this.actualValue = value; - const displayValue = isNotNil(this.nzFormatter(this.value)) ? this.nzFormatter(this.value) : ''; - this.displayValue = displayValue; - this.inputElement.nativeElement.value = `${displayValue}`; + this.parsedValue = value; this.disabledUp = this.disabledDown = false; if (value || value === 0) { const val = Number(value); @@ -290,39 +316,43 @@ export class NzInputNumberComponent implements ControlValueAccessor, AfterViewIn } } + updateDisplayValue(value: number): void { + const displayValue = isNotNil(this.nzFormatter(value)) ? this.nzFormatter(value) : ''; + this.displayValue = displayValue; + this.inputElement.nativeElement.value = `${displayValue}`; + } + onKeyDown(e: KeyboardEvent): void { - if (e.code === 'ArrowUp' || e.keyCode === UP_ARROW) { + if (e.keyCode === UP_ARROW) { const ratio = this.getRatio(e); this.up(e, ratio); this.stop(); - } else if (e.code === 'ArrowDown' || e.keyCode === DOWN_ARROW) { + } else if (e.keyCode === DOWN_ARROW) { const ratio = this.getRatio(e); this.down(e, ratio); this.stop(); } else if (e.keyCode === ENTER) { - this.setValidateValue(); + this.updateDisplayValue(this.value); } } - onKeyUp(): void { - this.stop(); - } - writeValue(value: number): void { - this.setValue(value, false); + this.value = value; + this.setValue(value); + this.updateDisplayValue(value); this.cdr.markForCheck(); } - registerOnChange(fn: (_: number) => void): void { + registerOnChange(fn: OnChangeType): void { this.onChange = fn; } - registerOnTouched(fn: () => void): void { + registerOnTouched(fn: OnTouchedType): void { this.onTouched = fn; } - setDisabledState(isDisabled: boolean): void { - this.nzDisabled = isDisabled; + setDisabledState(disabled: boolean): void { + this.nzDisabled = disabled; this.cdr.markForCheck(); } @@ -334,35 +364,27 @@ export class NzInputNumberComponent implements ControlValueAccessor, AfterViewIn this.inputElement.nativeElement.blur(); } - constructor( - private elementRef: ElementRef, - private renderer: Renderer2, - private cdr: ChangeDetectorRef, - private focusMonitor: FocusMonitor - ) { - renderer.addClass(elementRef.nativeElement, 'ant-input-number'); - } + constructor(private elementRef: ElementRef, private cdr: ChangeDetectorRef, private focusMonitor: FocusMonitor) {} ngOnInit(): void { this.focusMonitor.monitor(this.elementRef, true).subscribe(focusOrigin => { if (!focusOrigin) { - this.onBlur(); + this.isFocused = false; + this.updateDisplayValue(this.value); this.nzBlur.emit(); Promise.resolve().then(() => this.onTouched()); } else { - this.onFocus(); + this.isFocused = true; this.nzFocus.emit(); } }); } ngOnChanges(changes: SimpleChanges): void { - if (changes.nzAutoFocus) { - this.updateAutoFocus(); - } - if (changes.nzFormatter) { - const value = this.getCurrentValidValue(this.actualValue); - this.setValue(value, true); + if (changes.nzFormatter && !changes.nzFormatter.isFirstChange()) { + const validValue = this.getCurrentValidValue(this.parsedValue); + this.setValue(validValue); + this.updateDisplayValue(validValue); } } diff --git a/components/input-number/nz-input-number.module.ts b/components/input-number/input-number.module.ts similarity index 89% rename from components/input-number/nz-input-number.module.ts rename to components/input-number/input-number.module.ts index e4b9275dd3c..5ea22b329b9 100644 --- a/components/input-number/nz-input-number.module.ts +++ b/components/input-number/input-number.module.ts @@ -12,7 +12,7 @@ import { FormsModule } from '@angular/forms'; import { NzIconModule } from 'ng-zorro-antd/icon'; -import { NzInputNumberComponent } from './nz-input-number.component'; +import { NzInputNumberComponent } from './input-number.component'; @NgModule({ imports: [CommonModule, FormsModule, NzIconModule], diff --git a/components/input-number/nz-input-number.spec.ts b/components/input-number/input-number.spec.ts similarity index 84% rename from components/input-number/nz-input-number.spec.ts rename to components/input-number/input-number.spec.ts index 6c3dd5b57c8..dc2bb921dd6 100644 --- a/components/input-number/nz-input-number.spec.ts +++ b/components/input-number/input-number.spec.ts @@ -1,12 +1,13 @@ +import { DOWN_ARROW, UP_ARROW } from '@angular/cdk/keycodes'; import { Component, DebugElement, ViewChild } from '@angular/core'; import { ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angular/core/testing'; import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; -import { dispatchEvent, dispatchFakeEvent } from 'ng-zorro-antd/core'; +import { createKeyboardEvent, dispatchEvent, dispatchFakeEvent } from 'ng-zorro-antd/core'; -import { NzInputNumberComponent } from './nz-input-number.component'; -import { NzInputNumberModule } from './nz-input-number.module'; +import { NzInputNumberComponent } from './input-number.component'; +import { NzInputNumberModule } from './input-number.module'; describe('input number', () => { beforeEach(fakeAsync(() => { @@ -23,13 +24,28 @@ describe('input number', () => { let inputElement: HTMLInputElement; let upHandler: HTMLElement; let downHandler: HTMLElement; - + let upArrowEvent: KeyboardEvent; + let downArrowEvent: KeyboardEvent; + let upArrowCtrlEvent: KeyboardEvent; + let downArrowCtrlEvent: KeyboardEvent; + let upArrowMetaEvent: KeyboardEvent; + let downArrowMetaEvent: KeyboardEvent; + let upArrowShiftEvent: KeyboardEvent; + let downArrowShiftEvent: KeyboardEvent; beforeEach(() => { fixture = TestBed.createComponent(NzTestInputNumberBasicComponent); fixture.detectChanges(); testComponent = fixture.debugElement.componentInstance; inputNumber = fixture.debugElement.query(By.directive(NzInputNumberComponent)); inputElement = inputNumber.nativeElement.querySelector('input'); + upArrowEvent = createKeyboardEvent('keydown', UP_ARROW, inputElement, 'ArrowUp'); + downArrowEvent = createKeyboardEvent('keydown', DOWN_ARROW, inputElement, 'ArrowDown'); + upArrowCtrlEvent = createKeyboardEvent('keydown', UP_ARROW, inputElement, 'ArrowUp', true); + downArrowCtrlEvent = createKeyboardEvent('keydown', DOWN_ARROW, inputElement, 'ArrowDown', true); + upArrowMetaEvent = createKeyboardEvent('keydown', UP_ARROW, inputElement, 'ArrowUp', false, true); + downArrowMetaEvent = createKeyboardEvent('keydown', DOWN_ARROW, inputElement, 'ArrowDown', false, true); + upArrowShiftEvent = createKeyboardEvent('keydown', UP_ARROW, inputElement, 'ArrowUp', false, false, true); + downArrowShiftEvent = createKeyboardEvent('keydown', DOWN_ARROW, inputElement, 'ArrowDown', false, false, true); upHandler = inputNumber.nativeElement.querySelector('.ant-input-number-handler-up'); downHandler = inputNumber.nativeElement.querySelector('.ant-input-number-handler-down'); }); @@ -93,15 +109,11 @@ describe('input number', () => { it('should empty value work', () => { testComponent.nzInputNumberComponent.onModelChange(''); fixture.detectChanges(); - testComponent.nzInputNumberComponent.onBlur(); - fixture.detectChanges(); expect(testComponent.value).toBe(''); }); it('should NaN value work', () => { testComponent.nzInputNumberComponent.onModelChange('NaN'); fixture.detectChanges(); - testComponent.nzInputNumberComponent.onBlur(); - fixture.detectChanges(); expect(testComponent.value).toBe(undefined); }); it('should up and down work', () => { @@ -125,10 +137,8 @@ describe('input number', () => { it('should not complete number work', () => { testComponent.nzInputNumberComponent.onModelChange('1.'); fixture.detectChanges(); - testComponent.nzInputNumberComponent.onBlur(); - fixture.detectChanges(); expect(testComponent.value).toBe(undefined); - expect(inputElement.value).toBe(''); + expect(inputElement.value).toBe('1.'); }); it('should not complete number work with up arrow', () => { testComponent.nzInputNumberComponent.onModelChange('1.'); @@ -208,25 +218,14 @@ describe('input number', () => { it('should user input work', () => { testComponent.nzInputNumberComponent.onModelChange('123'); fixture.detectChanges(); - expect(testComponent.value).toBe(undefined); - expect(testComponent.modelChange).toHaveBeenCalledTimes(0); - testComponent.nzInputNumberComponent.onBlur(); - fixture.detectChanges(); expect(testComponent.value).toBe(1); expect(testComponent.modelChange).toHaveBeenCalledTimes(1); testComponent.nzInputNumberComponent.onModelChange('0'); fixture.detectChanges(); - expect(testComponent.value).toBe(1); - expect(testComponent.modelChange).toHaveBeenCalledTimes(1); - testComponent.nzInputNumberComponent.onBlur(); - fixture.detectChanges(); expect(testComponent.value).toBe(0); expect(testComponent.modelChange).toHaveBeenCalledTimes(2); - testComponent.nzInputNumberComponent.onModelChange('-4'); - fixture.detectChanges(); expect(testComponent.value).toBe(0); - expect(testComponent.modelChange).toHaveBeenCalledTimes(2); - testComponent.nzInputNumberComponent.onBlur(); + testComponent.nzInputNumberComponent.onModelChange('-4'); fixture.detectChanges(); expect(testComponent.value).toBe(-1); expect(testComponent.modelChange).toHaveBeenCalledTimes(3); @@ -234,10 +233,8 @@ describe('input number', () => { it('should auto precision work', () => { testComponent.precision = undefined; testComponent.max = 10; - testComponent.nzInputNumberComponent.onModelChange('0.999999'); - fixture.detectChanges(); - testComponent.nzInputNumberComponent.onBlur(); fixture.detectChanges(); + testComponent.nzInputNumberComponent.onModelChange('0.999999'); expect(testComponent.value).toBe(0.999999); dispatchFakeEvent(upHandler, 'mousedown'); fixture.detectChanges(); @@ -247,8 +244,6 @@ describe('input number', () => { expect(testComponent.value).toBe(0.999999); testComponent.nzInputNumberComponent.onModelChange('1e-10'); fixture.detectChanges(); - testComponent.nzInputNumberComponent.onBlur(); - fixture.detectChanges(); expect(testComponent.value).toBe(1e-10); dispatchFakeEvent(upHandler, 'mousedown'); fixture.detectChanges(); @@ -260,17 +255,43 @@ describe('input number', () => { it('should nzPrecision work', () => { testComponent.nzInputNumberComponent.onModelChange('0.99'); fixture.detectChanges(); - testComponent.nzInputNumberComponent.onBlur(); - fixture.detectChanges(); expect(testComponent.value).toBe(0.99); testComponent.nzInputNumberComponent.onModelChange('0.993'); fixture.detectChanges(); - testComponent.nzInputNumberComponent.onBlur(); + expect(testComponent.value).toBe(0.99); + testComponent.nzInputNumberComponent.onModelChange('0.999'); + fixture.detectChanges(); + expect(testComponent.value).toBe(1); + }); + it('should nzPrecisionMode work', () => { + testComponent.nzInputNumberComponent.onModelChange('0.999'); + fixture.detectChanges(); + expect(testComponent.value).toBe(1); + + testComponent.precisionMode = 'toFixed'; + testComponent.nzInputNumberComponent.onModelChange('0.991'); fixture.detectChanges(); expect(testComponent.value).toBe(0.99); testComponent.nzInputNumberComponent.onModelChange('0.999'); fixture.detectChanges(); - testComponent.nzInputNumberComponent.onBlur(); + expect(testComponent.value).toBe(1); + testComponent.nzInputNumberComponent.onModelChange('1.0099'); + fixture.detectChanges(); + expect(testComponent.value).toBe(1); + + testComponent.precisionMode = 'cut'; + testComponent.nzInputNumberComponent.onModelChange('0.991'); + fixture.detectChanges(); + expect(testComponent.value).toBe(0.99); + testComponent.nzInputNumberComponent.onModelChange('0.998'); + fixture.detectChanges(); + expect(testComponent.value).toBe(0.99); + + testComponent.precisionMode = value => +Number(value).toFixed(2); + testComponent.nzInputNumberComponent.onModelChange('0.991'); + fixture.detectChanges(); + expect(testComponent.value).toBe(0.99); + testComponent.nzInputNumberComponent.onModelChange('0.998'); fixture.detectChanges(); expect(testComponent.value).toBe(1); }); @@ -291,12 +312,6 @@ describe('input number', () => { expect(testComponent.value).toBe(2); }); it('should key up and down work', () => { - const upArrowEvent = new KeyboardEvent('keydown', { - code: 'ArrowUp' - }); - const downArrowEvent = new KeyboardEvent('keydown', { - code: 'ArrowDown' - }); fixture.detectChanges(); expect(testComponent.value).toBe(undefined); dispatchEvent(inputElement, upArrowEvent); @@ -313,68 +328,44 @@ describe('input number', () => { expect(testComponent.value).toBe(-1); }); it('should key up and down work with ctrl key', () => { - const upArrowEvent = new KeyboardEvent('keydown', { - code: 'ArrowUp', - ctrlKey: true - }); - const downArrowEvent = new KeyboardEvent('keydown', { - code: 'ArrowDown', - ctrlKey: true - }); fixture.detectChanges(); expect(testComponent.value).toBe(undefined); - dispatchEvent(inputElement, upArrowEvent); + dispatchEvent(inputElement, upArrowCtrlEvent); fixture.detectChanges(); expect(testComponent.value).toBe(0.1); - dispatchEvent(inputElement, downArrowEvent); + dispatchEvent(inputElement, downArrowCtrlEvent); fixture.detectChanges(); expect(testComponent.value).toBe(0); - dispatchEvent(inputElement, downArrowEvent); + dispatchEvent(inputElement, downArrowCtrlEvent); fixture.detectChanges(); expect(testComponent.value).toBe(-0.1); }); it('should key up and down work with meta key', () => { - const upArrowEvent = new KeyboardEvent('keydown', { - code: 'ArrowUp', - metaKey: true - }); - const downArrowEvent = new KeyboardEvent('keydown', { - code: 'ArrowDown', - metaKey: true - }); fixture.detectChanges(); expect(testComponent.value).toBe(undefined); - dispatchEvent(inputElement, upArrowEvent); + dispatchEvent(inputElement, upArrowMetaEvent); fixture.detectChanges(); expect(testComponent.value).toBe(0.1); - dispatchEvent(inputElement, downArrowEvent); + dispatchEvent(inputElement, downArrowMetaEvent); fixture.detectChanges(); expect(testComponent.value).toBe(0); - dispatchEvent(inputElement, downArrowEvent); + dispatchEvent(inputElement, downArrowMetaEvent); fixture.detectChanges(); expect(testComponent.value).toBe(-0.1); }); it('should key up and down work with shift key', () => { testComponent.max = 100; testComponent.min = -100; - const upArrowEvent = new KeyboardEvent('keydown', { - code: 'ArrowUp', - shiftKey: true - }); - const downArrowEvent = new KeyboardEvent('keydown', { - code: 'ArrowDown', - shiftKey: true - }); fixture.detectChanges(); expect(testComponent.value).toBe(undefined); - dispatchEvent(inputElement, upArrowEvent); + dispatchEvent(inputElement, upArrowShiftEvent); dispatchFakeEvent(inputElement, 'keyup'); fixture.detectChanges(); expect(testComponent.value).toBe(10); - dispatchEvent(inputElement, downArrowEvent); + dispatchEvent(inputElement, downArrowShiftEvent); fixture.detectChanges(); expect(testComponent.value).toBe(0); - dispatchEvent(inputElement, downArrowEvent); + dispatchEvent(inputElement, downArrowShiftEvent); fixture.detectChanges(); expect(testComponent.value).toBe(-10); }); @@ -382,10 +373,10 @@ describe('input number', () => { const newFormatter = (v: number) => `${v} %`; const initValue = 1; const component = testComponent.nzInputNumberComponent; + fixture.detectChanges(); component.onModelChange(`${initValue}`); fixture.detectChanges(); - component.nzFormatter = newFormatter; - component.setValue(component.getCurrentValidValue(component.actualValue), true); + testComponent.formatter = newFormatter; fixture.detectChanges(); expect(inputElement.value).toBe(newFormatter(initValue)); }); @@ -462,6 +453,7 @@ describe('input number', () => { [nzFormatter]="formatter" [nzParser]="parser" [nzPrecision]="precision" + [nzPrecisionMode]="precisionMode" > ` @@ -477,6 +469,7 @@ export class NzTestInputNumberBasicComponent { placeholder = 'placeholder'; step = 1; precision?: number = 2; + precisionMode: 'cut' | 'toFixed' | ((value: number | string, precision?: number) => number); formatter = (value: number) => (value !== null ? `${value}` : ''); parser = (value: number) => value; modelChange = jasmine.createSpy('change callback'); diff --git a/components/input-number/nz-input-number.component.html b/components/input-number/nz-input-number.component.html deleted file mode 100644 index 573fc29158f..00000000000 --- a/components/input-number/nz-input-number.component.html +++ /dev/null @@ -1,39 +0,0 @@ -
    - - - - - - -
    -
    - -
    diff --git a/components/input-number/public-api.ts b/components/input-number/public-api.ts index 8b1e9f0e31a..00c4bf9c337 100644 --- a/components/input-number/public-api.ts +++ b/components/input-number/public-api.ts @@ -6,5 +6,5 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -export * from './nz-input-number.component'; -export * from './nz-input-number.module'; +export * from './input-number.component'; +export * from './input-number.module'; diff --git a/components/input-number/style/index.tsx b/components/input-number/style/index.tsx deleted file mode 100755 index 3a3ab0de59a..00000000000 --- a/components/input-number/style/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -import '../../style/index.less'; -import './index.less'; diff --git a/components/input/nz-autosize.directive.ts b/components/input/autosize.directive.ts similarity index 80% rename from components/input/nz-autosize.directive.ts rename to components/input/autosize.directive.ts index e52e46d98f0..d7bdc7bd451 100644 --- a/components/input/nz-autosize.directive.ts +++ b/components/input/autosize.directive.ts @@ -8,20 +8,15 @@ import { Platform } from '@angular/cdk/platform'; import { AfterViewInit, Directive, DoCheck, ElementRef, Input, NgZone, OnDestroy } from '@angular/core'; +import { NzDomEventService } from 'ng-zorro-antd/core'; import { Subject } from 'rxjs'; import { finalize, takeUntil } from 'rxjs/operators'; -import { NzDomEventService } from 'ng-zorro-antd/core'; - export interface AutoSizeType { minRows?: number; maxRows?: number; } -export function isAutoSizeType(value: string | boolean | AutoSizeType): value is AutoSizeType { - return typeof value !== 'string' && typeof value !== 'boolean' && (!!value.maxRows || !!value.minRows); -} - @Directive({ selector: 'textarea[nzAutosize]', exportAs: 'nzAutosize', @@ -40,26 +35,27 @@ export class NzAutosizeDirective implements AfterViewInit, OnDestroy, DoCheck { private previousMinRows: number | undefined; private minRows: number | undefined; private maxRows: number | undefined; + private maxHeight: number | null = null; + private minHeight: number | null = null; private destroy$ = new Subject(); private inputGap = 10; @Input() set nzAutosize(value: string | boolean | AutoSizeType) { + const isAutoSizeType = (data: string | boolean | AutoSizeType): data is AutoSizeType => { + return typeof data !== 'string' && typeof data !== 'boolean' && (!!data.maxRows || !!data.minRows); + }; if (typeof value === 'string') { this.autosize = true; } else if (isAutoSizeType(value)) { this.autosize = value; this.minRows = value.minRows; this.maxRows = value.maxRows; - this.setMaxHeight(); - this.setMinHeight(); + this.maxHeight = this.setMaxHeight(); + this.minHeight = this.setMinHeight(); } } - get nzAutosize(): string | boolean | AutoSizeType { - return this.autosize; - } - resizeToFitContent(force: boolean = false): void { this.cacheTextareaLineHeight(); @@ -83,13 +79,18 @@ export class NzAutosizeDirective implements AfterViewInit, OnDestroy, DoCheck { // Long placeholders that are wider than the textarea width may lead to a bigger scrollHeight // value. To ensure that the scrollHeight is not bigger than the content, the placeholders // need to be removed temporarily. - textarea.classList.add('cdk-textarea-autosize-measuring'); + textarea.classList.add('nz-textarea-autosize-measuring'); textarea.placeholder = ''; - const height = Math.round((textarea.scrollHeight - this.inputGap) / this.cachedLineHeight) * this.cachedLineHeight + this.inputGap; - + let height = Math.round((textarea.scrollHeight - this.inputGap) / this.cachedLineHeight) * this.cachedLineHeight + this.inputGap; + if (this.maxHeight !== null && height > this.maxHeight) { + height = this.maxHeight!; + } + if (this.minHeight !== null && height < this.minHeight) { + height = this.minHeight!; + } // Use the scrollHeight to know how large the textarea *would* be if fit its entire value. textarea.style.height = `${height}px`; - textarea.classList.remove('cdk-textarea-autosize-measuring'); + textarea.classList.remove('nz-textarea-autosize-measuring'); textarea.placeholder = placeholderText; // On Firefox resizing the textarea will prevent it from scrolling to the caret position. @@ -144,28 +145,29 @@ export class NzAutosizeDirective implements AfterViewInit, OnDestroy, DoCheck { textareaClone.style.overflow = 'hidden'; this.el.parentNode!.appendChild(textareaClone); - this.cachedLineHeight = textareaClone.clientHeight - this.inputGap - 1; + this.cachedLineHeight = textareaClone.clientHeight - this.inputGap; this.el.parentNode!.removeChild(textareaClone); // Min and max heights have to be re-calculated if the cached line height changes - this.setMinHeight(); - this.setMaxHeight(); + this.maxHeight = this.setMaxHeight(); + this.minHeight = this.setMinHeight(); } - setMinHeight(): void { - const minHeight = this.minRows && this.cachedLineHeight ? `${this.minRows * this.cachedLineHeight + this.inputGap}px` : null; + setMinHeight(): number | null { + const minHeight = this.minRows && this.cachedLineHeight ? this.minRows * this.cachedLineHeight + this.inputGap : null; - if (minHeight) { - this.el.style.minHeight = minHeight; + if (minHeight !== null) { + this.el.style.minHeight = `${minHeight}px`; } + return minHeight; } - setMaxHeight(): void { - const maxHeight = this.maxRows && this.cachedLineHeight ? `${this.maxRows * this.cachedLineHeight + this.inputGap}px` : null; - - if (maxHeight) { - this.el.style.maxHeight = maxHeight; + setMaxHeight(): number | null { + const maxHeight = this.maxRows && this.cachedLineHeight ? this.maxRows * this.cachedLineHeight + this.inputGap : null; + if (maxHeight !== null) { + this.el.style.maxHeight = `${maxHeight}px`; } + return maxHeight; } noopInputHandler(): void { @@ -180,7 +182,7 @@ export class NzAutosizeDirective implements AfterViewInit, OnDestroy, DoCheck { ) {} ngAfterViewInit(): void { - if (this.nzAutosize && this.platform.isBrowser) { + if (this.autosize && this.platform.isBrowser) { this.resizeToFitContent(); this.nzDomEventService .registerResizeListener() @@ -198,7 +200,7 @@ export class NzAutosizeDirective implements AfterViewInit, OnDestroy, DoCheck { } ngDoCheck(): void { - if (this.nzAutosize && this.platform.isBrowser) { + if (this.autosize && this.platform.isBrowser) { this.resizeToFitContent(); } } diff --git a/components/input/nz-autosize.spec.ts b/components/input/autosize.spec.ts similarity index 98% rename from components/input/nz-autosize.spec.ts rename to components/input/autosize.spec.ts index 4ab82e27a3e..f836af047a7 100644 --- a/components/input/nz-autosize.spec.ts +++ b/components/input/autosize.spec.ts @@ -3,8 +3,8 @@ import { async, ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angul import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; import { dispatchFakeEvent, MockNgZone } from 'ng-zorro-antd/core'; -import { NzAutosizeDirective } from './nz-autosize.directive'; -import { NzInputModule } from './nz-input.module'; +import { NzAutosizeDirective } from './autosize.directive'; +import { NzInputModule } from './input.module'; describe('autoresize', () => { let zone: MockNgZone; diff --git a/components/input/demo/addon.ts b/components/input/demo/addon.ts index a591b756907..ba0ac3fd2f3 100644 --- a/components/input/demo/addon.ts +++ b/components/input/demo/addon.ts @@ -3,36 +3,43 @@ import { Component } from '@angular/core'; @Component({ selector: 'nz-demo-input-addon', template: ` -
    +
    -
    +
    - - + + - - - - + + + +
    -
    - +
    +
    - ` + `, + styles: [ + ` + div { + margin-bottom: 16px; + } + ` + ] }) export class NzDemoInputAddonComponent { inputValue: string = 'my site'; diff --git a/components/input/demo/allow-clear.ts b/components/input/demo/allow-clear.ts index 57f7ece7491..9655b2a20d9 100644 --- a/components/input/demo/allow-clear.ts +++ b/components/input/demo/allow-clear.ts @@ -3,22 +3,30 @@ import { Component } from '@angular/core'; @Component({ selector: 'nz-demo-input-allow-clear', template: ` - + - +
    +
    + + + + ` }) export class NzDemoInputAllowClearComponent { inputValue: string | null; + textValue: string | null; } diff --git a/components/input/demo/autosize-textarea.ts b/components/input/demo/autosize-textarea.ts index 4da84654f2f..4a17ac2939b 100644 --- a/components/input/demo/autosize-textarea.ts +++ b/components/input/demo/autosize-textarea.ts @@ -4,17 +4,21 @@ import { Component } from '@angular/core'; selector: 'nz-demo-input-autosize-textarea', template: `
    - -
    + +
    - ` + `, + styles: [ + ` + textarea + textarea { + margin-top: 24px; + } + ` + ] }) -export class NzDemoInputAutosizeTextareaComponent { - value: string; -} +export class NzDemoInputAutosizeTextareaComponent {} diff --git a/components/input/demo/group.ts b/components/input/demo/group.ts index faefe6404da..e14ae84ef30 100644 --- a/components/input/demo/group.ts +++ b/components/input/demo/group.ts @@ -4,11 +4,13 @@ import { Component } from '@angular/core'; selector: 'nz-demo-input-group', template: ` -
    - -
    -
    - +
    +
    + +
    +
    + +

    diff --git a/components/input/demo/presuffix.ts b/components/input/demo/presuffix.ts index f1cac453a21..d51e0ee03d9 100644 --- a/components/input/demo/presuffix.ts +++ b/components/input/demo/presuffix.ts @@ -7,7 +7,7 @@ import { Component } from '@angular/core'; - +

    diff --git a/components/input/demo/tooltip.ts b/components/input/demo/tooltip.ts index 8281c5893da..ad49da59e6e 100644 --- a/components/input/demo/tooltip.ts +++ b/components/input/demo/tooltip.ts @@ -10,10 +10,10 @@ import { Component, ElementRef, ViewChild, ViewEncapsulation } from '@angular/co nz-input nz-tooltip nzTooltipTrigger="focus" - nzPlacement="topLeft" + nzTooltipPlacement="topLeft" nzOverlayClassName="numeric-input" [ngModel]="value" - [nzTitle]="title" + [nzTooltipTitle]="title" placeholder="Input a number" (ngModelChange)="onChange($event)" (blur)="onBlur()" diff --git a/components/input/input-group-slot.component.ts b/components/input/input-group-slot.component.ts new file mode 100644 index 00000000000..8297c8e5e1a --- /dev/null +++ b/components/input/input-group-slot.component.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { ChangeDetectionStrategy, Component, Input, TemplateRef, ViewEncapsulation } from '@angular/core'; + +@Component({ + selector: '[nz-input-group-slot]', + preserveWhitespaces: false, + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` + + {{ template }} + `, + host: { + '[class.ant-input-group-addon]': `type === 'addon'`, + '[class.ant-input-prefix]': `type === 'prefix'`, + '[class.ant-input-suffix]': `type === 'suffix'` + } +}) +export class NzInputGroupSlotComponent { + @Input() icon: string | null = null; + @Input() type: 'addon' | 'prefix' | 'suffix' | null = null; + @Input() template: string | TemplateRef | null = null; +} diff --git a/components/input/input-group.component.ts b/components/input/input-group.component.ts new file mode 100644 index 00000000000..a452484857a --- /dev/null +++ b/components/input/input-group.component.ts @@ -0,0 +1,138 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { + AfterContentInit, + ChangeDetectionStrategy, + Component, + ContentChildren, + Input, + OnChanges, + QueryList, + SimpleChanges, + TemplateRef, + ViewEncapsulation +} from '@angular/core'; +import { InputBoolean, NgClassType, NzSizeLDSType } from 'ng-zorro-antd/core'; +import { NzInputDirective } from './input.directive'; + +@Component({ + selector: 'nz-input-group', + exportAs: 'nzInputGroup', + preserveWhitespaces: false, + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` + + + + + + + + + + + + + + + + + + + + + + `, + host: { + '[class.ant-input-group-compact]': `nzCompact`, + '[class.ant-input-search-enter-button]': `nzSearch`, + '[class.ant-input-search]': `nzSearch`, + '[class.ant-input-search-sm]': `nzSearch && isSmall`, + '[class.ant-input-search-large]': `nzSearch && isLarge`, + '[class.ant-input-group-wrapper]': `isAddOn`, + '[class.ant-input-group-wrapper-lg]': `isAddOn && isLarge`, + '[class.ant-input-group-wrapper-sm]': `isAddOn && isSmall`, + '[class.ant-input-affix-wrapper]': `isAffix && !isAddOn`, + '[class.ant-input-affix-wrapper-lg]': `isAffix && !isAddOn && isLarge`, + '[class.ant-input-affix-wrapper-sm]': `isAffix && !isAddOn && isSmall`, + '[class.ant-input-group]': `!isAffix && !isAddOn`, + '[class.ant-input-group-lg]': `!isAffix && !isAddOn && isLarge`, + '[class.ant-input-group-sm]': `!isAffix && !isAddOn && isSmall` + } +}) +export class NzInputGroupComponent implements AfterContentInit, OnChanges { + @ContentChildren(NzInputDirective) listOfNzInputDirective: QueryList; + @Input() nzAddOnBeforeIcon: NgClassType; + @Input() nzAddOnAfterIcon: NgClassType; + @Input() nzPrefixIcon: NgClassType; + @Input() nzSuffixIcon: NgClassType; + @Input() nzAddOnBefore: string | TemplateRef; + @Input() nzAddOnAfter: string | TemplateRef; + @Input() nzPrefix: string | TemplateRef; + @Input() nzSuffix: string | TemplateRef; + @Input() nzSize: NzSizeLDSType; + @Input() @InputBoolean() nzSearch = false; + @Input() @InputBoolean() nzCompact = false; + isLarge = false; + isSmall = false; + isAffix = false; + isAddOn = false; + + updateChildrenInputSize(): void { + if (this.listOfNzInputDirective) { + this.listOfNzInputDirective.forEach(item => (item.nzSize = this.nzSize)); + } + } + + ngAfterContentInit(): void { + this.updateChildrenInputSize(); + } + ngOnChanges(changes: SimpleChanges): void { + const { + nzSize, + nzSuffix, + nzPrefix, + nzPrefixIcon, + nzSuffixIcon, + nzAddOnAfter, + nzAddOnBefore, + nzAddOnAfterIcon, + nzAddOnBeforeIcon + } = changes; + if (nzSize) { + this.updateChildrenInputSize(); + this.isLarge = this.nzSize === 'large'; + this.isSmall = this.nzSize === 'small'; + } + if (nzSuffix || nzPrefix || nzPrefixIcon || nzSuffixIcon) { + this.isAffix = !!(this.nzSuffix || this.nzPrefix || this.nzPrefixIcon || this.nzSuffixIcon); + } + if (nzAddOnAfter || nzAddOnBefore || nzAddOnAfterIcon || nzAddOnBeforeIcon) { + this.isAddOn = !!(this.nzAddOnAfter || this.nzAddOnBefore || this.nzAddOnAfterIcon || this.nzAddOnBeforeIcon); + } + } +} diff --git a/components/input/nz-input-group.spec.ts b/components/input/input-group.spec.ts similarity index 98% rename from components/input/nz-input-group.spec.ts rename to components/input/input-group.spec.ts index dac47193fd4..e02ae986178 100644 --- a/components/input/nz-input-group.spec.ts +++ b/components/input/input-group.spec.ts @@ -3,8 +3,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; import { NzIconTestModule } from 'ng-zorro-antd/icon/testing'; -import { NzInputGroupComponent } from './nz-input-group.component'; -import { NzInputModule } from './nz-input.module'; +import { NzInputGroupComponent } from './input-group.component'; +import { NzInputModule } from './input.module'; describe('input-group', () => { beforeEach(async(() => { @@ -183,7 +183,7 @@ describe('input-group', () => { expect(inputGroupElement.classList).toContain('ant-input-search'); testComponent.size = 'large'; fixture.detectChanges(); - expect(inputGroupElement.classList).toContain('ant-input-search-lg'); + expect(inputGroupElement.classList).toContain('ant-input-search-large'); testComponent.size = 'small'; fixture.detectChanges(); expect(inputGroupElement.classList).toContain('ant-input-search-sm'); diff --git a/components/input/nz-input.directive.ts b/components/input/input.directive.ts similarity index 94% rename from components/input/nz-input.directive.ts rename to components/input/input.directive.ts index c75dcf49d03..453af2c7a69 100644 --- a/components/input/nz-input.directive.ts +++ b/components/input/input.directive.ts @@ -10,7 +10,7 @@ import { Directive, ElementRef, Input, Renderer2 } from '@angular/core'; import { InputBoolean, NzSizeLDSType } from 'ng-zorro-antd/core'; @Directive({ - selector: '[nz-input]', + selector: 'input[nz-input],textarea[nz-input]', exportAs: 'nzInput', host: { '[class.ant-input-disabled]': 'disabled', diff --git a/components/input/nz-input.module.ts b/components/input/input.module.ts similarity index 59% rename from components/input/nz-input.module.ts rename to components/input/input.module.ts index 98c5fe7d719..05807139f39 100644 --- a/components/input/nz-input.module.ts +++ b/components/input/input.module.ts @@ -6,20 +6,20 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ +import { PlatformModule } from '@angular/cdk/platform'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule } from 'ng-zorro-antd/core'; +import { NzOutletModule } from 'ng-zorro-antd/core'; import { NzIconModule } from 'ng-zorro-antd/icon'; - -import { PlatformModule } from '@angular/cdk/platform'; -import { NzAutosizeDirective } from './nz-autosize.directive'; -import { NzInputGroupComponent } from './nz-input-group.component'; -import { NzInputDirective } from './nz-input.directive'; +import { NzAutosizeDirective } from './autosize.directive'; +import { NzInputGroupSlotComponent } from './input-group-slot.component'; +import { NzInputGroupComponent } from './input-group.component'; +import { NzInputDirective } from './input.directive'; @NgModule({ - declarations: [NzInputDirective, NzInputGroupComponent, NzAutosizeDirective], + declarations: [NzInputDirective, NzInputGroupComponent, NzAutosizeDirective, NzInputGroupSlotComponent], exports: [NzInputDirective, NzInputGroupComponent, NzAutosizeDirective], - imports: [CommonModule, NzIconModule, PlatformModule, NzAddOnModule] + imports: [CommonModule, NzIconModule, PlatformModule, NzOutletModule] }) export class NzInputModule {} diff --git a/components/input/nz-input.spec.ts b/components/input/input.spec.ts similarity index 97% rename from components/input/nz-input.spec.ts rename to components/input/input.spec.ts index 966743578f9..7e93628d6b2 100644 --- a/components/input/nz-input.spec.ts +++ b/components/input/input.spec.ts @@ -3,8 +3,8 @@ import { async, ComponentFixture, fakeAsync, flush, TestBed } from '@angular/cor import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; import { NzIconTestModule } from 'ng-zorro-antd/icon/testing'; -import { NzInputDirective } from './nz-input.directive'; -import { NzInputModule } from './nz-input.module'; +import { NzInputDirective } from './input.directive'; +import { NzInputModule } from './input.module'; describe('input', () => { beforeEach(async(() => { diff --git a/components/input/nz-input-group.component.html b/components/input/nz-input-group.component.html deleted file mode 100644 index 3582718043f..00000000000 --- a/components/input/nz-input-group.component.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - {{ nzAddOnBefore }} - - - - - - - - - - {{ nzAddOnAfter }} - - - - - - - - - - {{ nzPrefix }} - - - - - {{ nzSuffix }} - - - - - - - - diff --git a/components/input/nz-input-group.component.ts b/components/input/nz-input-group.component.ts deleted file mode 100644 index 3a12a399e1e..00000000000 --- a/components/input/nz-input-group.component.ts +++ /dev/null @@ -1,134 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { - AfterContentInit, - ChangeDetectionStrategy, - Component, - ContentChildren, - Input, - QueryList, - TemplateRef, - ViewEncapsulation -} from '@angular/core'; -import { InputBoolean, NgClassType, NzSizeLDSType } from 'ng-zorro-antd/core'; -import { NzInputDirective } from './nz-input.directive'; - -@Component({ - selector: 'nz-input-group', - exportAs: 'nzInputGroup', - preserveWhitespaces: false, - encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush, - templateUrl: './nz-input-group.component.html', - host: { - '[class.ant-input-group-compact]': 'nzCompact', - '[class.ant-input-search-enter-button]': 'nzSearch', - '[class.ant-input-search]': 'nzSearch', - '[class.ant-input-search-sm]': 'isSmallSearch', - '[class.ant-input-affix-wrapper]': 'isAffixWrapper', - '[class.ant-input-group-wrapper]': 'isAddOn', - '[class.ant-input-group]': 'isGroup', - '[class.ant-input-group-lg]': 'isLargeGroup', - '[class.ant-input-group-wrapper-lg]': 'isLargeGroupWrapper', - '[class.ant-input-affix-wrapper-lg]': 'isLargeAffix', - '[class.ant-input-search-lg]': 'isLargeSearch', - '[class.ant-input-group-sm]': 'isSmallGroup', - '[class.ant-input-affix-wrapper-sm]': 'isSmallAffix', - '[class.ant-input-group-wrapper-sm]': 'isSmallGroupWrapper' - } -}) -export class NzInputGroupComponent implements AfterContentInit { - @ContentChildren(NzInputDirective) listOfNzInputDirective: QueryList; - private _size: NzSizeLDSType = 'default'; - @Input() nzAddOnBeforeIcon: NgClassType; - @Input() nzAddOnAfterIcon: NgClassType; - @Input() nzPrefixIcon: NgClassType; - @Input() nzSuffixIcon: NgClassType; - @Input() nzAddOnBefore: string | TemplateRef; - @Input() nzAddOnAfter: string | TemplateRef; - @Input() nzPrefix: string | TemplateRef; - @Input() nzSuffix: string | TemplateRef; - @Input() @InputBoolean() nzSearch = false; - @Input() @InputBoolean() nzCompact = false; - - @Input() set nzSize(value: NzSizeLDSType) { - this._size = value; - this.updateChildrenInputSize(); - } - - get nzSize(): NzSizeLDSType { - return this._size; - } - - get isLarge(): boolean { - return this.nzSize === 'large'; - } - - get isSmall(): boolean { - return this.nzSize === 'small'; - } - - get isAffix(): boolean { - return !!(this.nzSuffix || this.nzPrefix || this.nzPrefixIcon || this.nzSuffixIcon); - } - - get isAddOn(): boolean { - return !!(this.nzAddOnAfter || this.nzAddOnBefore || this.nzAddOnAfterIcon || this.nzAddOnBeforeIcon); - } - - get isAffixWrapper(): boolean { - return this.isAffix && !this.isAddOn; - } - - get isGroup(): boolean { - return !this.isAffix && !this.isAddOn; - } - - get isLargeGroup(): boolean { - return this.isGroup && this.isLarge; - } - - get isLargeGroupWrapper(): boolean { - return this.isAddOn && this.isLarge; - } - - get isLargeAffix(): boolean { - return this.isAffixWrapper && this.isLarge; - } - - get isLargeSearch(): boolean { - return this.nzSearch && this.isLarge; - } - - get isSmallGroup(): boolean { - return this.isGroup && this.isSmall; - } - - get isSmallAffix(): boolean { - return this.isAffixWrapper && this.isSmall; - } - - get isSmallGroupWrapper(): boolean { - return this.isAddOn && this.isSmall; - } - - get isSmallSearch(): boolean { - return this.nzSearch && this.isSmall; - } - - updateChildrenInputSize(): void { - if (this.listOfNzInputDirective) { - this.listOfNzInputDirective.forEach(item => (item.nzSize = this.nzSize)); - } - } - - ngAfterContentInit(): void { - this.updateChildrenInputSize(); - } -} diff --git a/components/input/public-api.ts b/components/input/public-api.ts index 3f22e7c07d7..92c6d7d19cd 100644 --- a/components/input/public-api.ts +++ b/components/input/public-api.ts @@ -6,8 +6,9 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -export * from './nz-input-group.component'; -export * from './nz-input.module'; -export * from './nz-input-group.component'; -export * from './nz-input.directive'; -export * from './nz-autosize.directive'; +export * from './input-group.component'; +export * from './input.module'; +export * from './input-group.component'; +export * from './input-group-slot.component'; +export * from './input.directive'; +export * from './autosize.directive'; diff --git a/components/input/style/affix.less b/components/input/style/affix.less new file mode 100644 index 00000000000..0c0578bd205 --- /dev/null +++ b/components/input/style/affix.less @@ -0,0 +1,50 @@ +@import './index'; +@import './mixin'; + +@input-affix-margin: 4px; + +.@{ant-prefix}-input { + &-affix-wrapper { + .input(); + display: inline-flex; + + > .@{ant-prefix}-input { + padding: 0; + border: none; + outline: none; + + &:focus { + box-shadow: none; + } + } + + .@{ant-prefix}-input-clear-icon { + margin: 0 @input-affix-margin; + vertical-align: -1px; + } + } + + &-prefix, + &-suffix { + flex: none; + } + + &-prefix { + margin-right: @input-affix-margin; + } + + &-suffix { + margin-left: @input-affix-margin; + } + + // ======================== RTL ======================== + &-affix-wrapper-rtl { + .@{ant-prefix}-input-prefix { + margin: 0 0 0 @input-affix-margin; + } + + .@{ant-prefix}-input-suffix { + margin: 0 @input-affix-margin 0 0; + } + } +} diff --git a/components/input/style/entry.less b/components/input/style/entry.less index 8d6c97e9eb9..96cebe33bff 100644 --- a/components/input/style/entry.less +++ b/components/input/style/entry.less @@ -1,3 +1,2 @@ @import './index.less'; -// style dependencies -@import '../../button/style/index.less'; +@import './patch.less'; diff --git a/components/input/style/index.less b/components/input/style/index.less index cac078d00dd..8665dedc698 100644 --- a/components/input/style/index.less +++ b/components/input/style/index.less @@ -1,6 +1,7 @@ @import '../../style/themes/index'; @import '../../style/mixins/index'; @import './mixin'; +@import './affix'; // Input styles .@{ant-prefix}-input { @@ -26,17 +27,6 @@ } } -// Input with affix: prefix or suffix -.@{ant-prefix}-input-affix-wrapper { - .reset-component; - .input-affix-wrapper(~'@{ant-prefix}-input'); - - // https://github.com/ant-design/ant-design/issues/6144 - .@{ant-prefix}-input { - min-height: 100%; // use min-height, assume that no smaller height to override - } -} - .@{ant-prefix}-input-password-icon { color: @text-color-secondary; cursor: pointer; diff --git a/components/input/style/mixin.less b/components/input/style/mixin.less index a9e995beeaf..f968b6d0095 100644 --- a/components/input/style/mixin.less +++ b/components/input/style/mixin.less @@ -3,18 +3,15 @@ @input-rtl-cls: ~'@{ant-prefix}-input-rtl'; -@input-affix-width: 19px; @input-affix-with-clear-btn-width: 38px; // size mixins for input .input-lg() { - height: @input-height-lg; padding: @input-padding-vertical-lg @input-padding-horizontal-lg; font-size: @font-size-lg; } .input-sm() { - height: @input-height-sm; padding: @input-padding-vertical-sm @input-padding-horizontal-sm; } @@ -64,7 +61,6 @@ position: relative; display: inline-block; width: 100%; - height: @input-height-base; padding: @input-padding-vertical-base @input-padding-horizontal-base; color: @input-color; font-size: @font-size-base; @@ -80,7 +76,8 @@ .hover(); } - &:focus { + &:focus, + &-focused { .active(); } @@ -309,9 +306,7 @@ } .@{inputClass}-affix-wrapper { - display: table-cell; - float: left; - width: 100%; + border-radius: 0; } &&-compact { @@ -423,101 +418,6 @@ } } -.input-affix-wrapper(@inputClass) { - position: relative; - display: inline-block; - width: 100%; - text-align: start; - - &:hover .@{inputClass}:not(.@{inputClass}-disabled) { - .hover(); - } - - .@{inputClass} { - position: relative; - text-align: inherit; - } - - // Should not break align of icon & text - // https://github.com/ant-design/ant-design/issues/18087 - // https://github.com/ant-design/ant-design/issues/17414 - // https://github.com/ant-design/ant-design/pull/17684 - // https://codesandbox.io/embed/pensive-paper-di2wk - // https://codesandbox.io/embed/nifty-benz-gb7ml - .@{inputClass}-prefix, - .@{inputClass}-suffix { - position: absolute; - top: 50%; - z-index: 2; - display: flex; - align-items: center; - color: @input-icon-color; - line-height: 0; - transform: translateY(-50%); - - :not(.anticon) { - line-height: @line-height-base; - } - } - - .@{inputClass}-disabled ~ .@{inputClass}-suffix { - .anticon { - color: @disabled-color; - cursor: not-allowed; - } - } - - .@{inputClass}-prefix { - left: @input-padding-horizontal-base + 1px; - - .@{inputClass}-affix-wrapper-rtl& { - right: @input-padding-horizontal-base + 1px; - left: auto; - } - } - - .@{inputClass}-suffix { - right: @input-padding-horizontal-base + 1px; - - .@{inputClass}-affix-wrapper-rtl& { - right: auto; - left: @input-padding-horizontal-base + 1px; - } - } - - .@{inputClass}:not(:first-child) { - padding-left: @input-padding-horizontal-base + @input-affix-width; - } - - .@{inputClass}:not(:last-child) { - padding-right: @input-padding-horizontal-base + @input-affix-width; - } - - // Needed to change priority of classes in rtl direction (last-child comes before first-child) - .@{inputClass}-affix-wrapper-rtl& .@{inputClass}:not(:last-child) { - padding-right: @input-padding-horizontal-base; - padding-left: @input-padding-horizontal-base + @input-affix-width; - } - - .@{inputClass}-affix-wrapper-rtl& .@{inputClass}:not(:first-child) { - padding-right: @input-padding-horizontal-base + @input-affix-width; - padding-left: @input-padding-horizontal-base; - } - - &.@{inputClass}-affix-wrapper-input-with-clear-btn .@{inputClass}:not(:last-child) { - padding-right: @input-padding-horizontal-base + @input-affix-with-clear-btn-width; - - .@{inputClass}-affix-wrapper-rtl& { - padding-right: @input-padding-horizontal-base; - padding-left: @input-padding-horizontal-base + @input-affix-with-clear-btn-width; - } - } - - &.@{inputClass}-affix-wrapper-textarea-with-clear-btn .@{inputClass} { - padding-right: 22px; - } -} - .clear-icon() { color: @disabled-color; font-size: @font-size-sm; diff --git a/components/input/style/patch.less b/components/input/style/patch.less new file mode 100644 index 00000000000..a0d7ee21662 --- /dev/null +++ b/components/input/style/patch.less @@ -0,0 +1,8 @@ +textarea.nz-textarea-autosize-measuring { + height: auto !important; + overflow: hidden !important; + // Having 2px top and bottom padding seems to fix a bug where Chrome gets an incorrect + // measurement. We just have to account for it later and subtract it off the final result. + padding: 2px 0 !important; + box-sizing: content-box !important; +} diff --git a/components/layout/demo/custom-trigger.ts b/components/layout/demo/custom-trigger.ts index 24259bc9de5..c0feef9d481 100644 --- a/components/layout/demo/custom-trigger.ts +++ b/components/layout/demo/custom-trigger.ts @@ -6,7 +6,7 @@ import { Component } from '@angular/core'; -
      +
        • Tom
        • diff --git a/components/layout/demo/responsive.ts b/components/layout/demo/responsive.ts index 2de8f705893..0c7064d7087 100644 --- a/components/layout/demo/responsive.ts +++ b/components/layout/demo/responsive.ts @@ -4,9 +4,9 @@ import { Component } from '@angular/core'; selector: 'nz-demo-layout-responsive', template: ` - + -
            +
            • nav 1 @@ -69,6 +69,4 @@ import { Component } from '@angular/core'; ` ] }) -export class NzDemoLayoutResponsiveComponent { - isCollapsed = false; -} +export class NzDemoLayoutResponsiveComponent {} diff --git a/components/layout/demo/side.ts b/components/layout/demo/side.ts index b788b0ef9bf..464fc5a7fb1 100644 --- a/components/layout/demo/side.ts +++ b/components/layout/demo/side.ts @@ -4,9 +4,9 @@ import { Component } from '@angular/core'; selector: 'nz-demo-layout-side', template: ` - + -
                +
                  • Tom
                  • @@ -74,6 +74,4 @@ import { Component } from '@angular/core'; ` ] }) -export class NzDemoLayoutSideComponent { - isCollapsed = false; -} +export class NzDemoLayoutSideComponent {} diff --git a/components/layout/demo/top-side.ts b/components/layout/demo/top-side.ts index c0c760391f9..9cdc0a95d51 100644 --- a/components/layout/demo/top-side.ts +++ b/components/layout/demo/top-side.ts @@ -6,7 +6,7 @@ import { Component } from '@angular/core'; -
                      +
                      • nav 1
                      • nav 2
                      • nav 3
                      • diff --git a/components/layout/sider.component.ts b/components/layout/sider.component.ts index 055faf9ea46..7b1f2464f23 100644 --- a/components/layout/sider.component.ts +++ b/components/layout/sider.component.ts @@ -9,23 +9,28 @@ import { MediaMatcher } from '@angular/cdk/layout'; import { Platform } from '@angular/cdk/platform'; import { + AfterContentInit, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, + ContentChild, EventEmitter, Input, + NgZone, OnChanges, OnDestroy, OnInit, Output, + SimpleChanges, TemplateRef, ViewEncapsulation } from '@angular/core'; -import { IndexableObject, InputBoolean, NzBreakpointKey, NzDomEventService, siderResponsiveMap, toCssPixel } from 'ng-zorro-antd/core'; -import { Subject } from 'rxjs'; -import { finalize, takeUntil } from 'rxjs/operators'; +import { InputBoolean, NzBreakpointKey, NzDomEventService, siderResponsiveMap, toCssPixel } from 'ng-zorro-antd/core'; +import { NzMenuDirective } from 'ng-zorro-antd/menu'; +import { merge, of, Subject } from 'rxjs'; +import { delay, finalize, takeUntil } from 'rxjs/operators'; @Component({ selector: 'nz-sider', @@ -47,7 +52,7 @@ import { finalize, takeUntil } from 'rxjs/operators'; [nzReverseArrow]="nzReverseArrow" [nzTrigger]="nzTrigger" [nzZeroTrigger]="nzZeroTrigger" - [siderWidth]="hostStyleMap.width" + [siderWidth]="widthSetting" (click)="setCollapsed(!nzCollapsed)" >
    `, @@ -57,11 +62,15 @@ import { finalize, takeUntil } from 'rxjs/operators'; '[class.ant-layout-sider-light]': `nzTheme === 'light'`, '[class.ant-layout-sider-dark]': `nzTheme === 'dark'`, '[class.ant-layout-sider-collapsed]': `nzCollapsed`, - '[style]': 'hostStyleMap' + '[style.flex]': 'flexSetting', + '[style.maxWidth]': 'widthSetting', + '[style.minWidth]': 'widthSetting', + '[style.width]': 'widthSetting' } }) -export class NzSiderComponent implements OnInit, AfterViewInit, OnDestroy, OnChanges { +export class NzSiderComponent implements OnInit, AfterViewInit, OnDestroy, OnChanges, AfterContentInit { private destroy$ = new Subject(); + @ContentChild(NzMenuDirective) nzMenuDirective: NzMenuDirective | null = null; @Output() readonly nzCollapsedChange = new EventEmitter(); @Input() nzWidth: string | number = 200; @Input() nzTheme: 'light' | 'dark' = 'dark'; @@ -73,22 +82,12 @@ export class NzSiderComponent implements OnInit, AfterViewInit, OnDestroy, OnCha @Input() @InputBoolean() nzCollapsible = false; @Input() @InputBoolean() nzCollapsed = false; matchBreakPoint = false; - hostStyleMap: IndexableObject = {}; + flexSetting: string | null = null; + widthSetting: string | null = null; updateStyleMap(): void { - let widthSetting: string; - if (this.nzCollapsed) { - widthSetting = `${this.nzCollapsedWidth}px`; - } else { - widthSetting = toCssPixel(this.nzWidth); - } - const flexSetting = `0 0 ${widthSetting}`; - this.hostStyleMap = { - flex: flexSetting, - maxWidth: widthSetting, - minWidth: widthSetting, - width: widthSetting - }; + this.widthSetting = this.nzCollapsed ? `${this.nzCollapsedWidth}px` : toCssPixel(this.nzWidth); + this.flexSetting = `0 0 ${this.widthSetting}`; this.cdr.markForCheck(); } @@ -96,20 +95,31 @@ export class NzSiderComponent implements OnInit, AfterViewInit, OnDestroy, OnCha if (this.nzBreakpoint) { this.matchBreakPoint = this.mediaMatcher.matchMedia(siderResponsiveMap[this.nzBreakpoint]).matches; this.setCollapsed(this.matchBreakPoint); + this.cdr.markForCheck(); + } + } + + updateMenuInlineCollapsed(): void { + if (this.nzMenuDirective && this.nzMenuDirective.nzMode === 'inline' && this.nzCollapsedWidth !== 0) { + this.nzMenuDirective.setInlineCollapsed(this.nzCollapsed); } - this.cdr.markForCheck(); } setCollapsed(collapsed: boolean): void { - this.nzCollapsed = collapsed; - this.nzCollapsedChange.emit(collapsed); - this.cdr.markForCheck(); + if (collapsed !== this.nzCollapsed) { + this.nzCollapsed = collapsed; + this.nzCollapsedChange.emit(collapsed); + this.updateMenuInlineCollapsed(); + this.updateStyleMap(); + this.cdr.markForCheck(); + } } constructor( private mediaMatcher: MediaMatcher, private platform: Platform, private cdr: ChangeDetectorRef, + private ngZone: NgZone, private nzDomEventService: NzDomEventService ) {} @@ -117,20 +127,32 @@ export class NzSiderComponent implements OnInit, AfterViewInit, OnDestroy, OnCha this.updateStyleMap(); } - ngOnChanges(): void { - this.updateStyleMap(); + ngOnChanges(changes: SimpleChanges): void { + const { nzCollapsed, nzCollapsedWidth, nzWidth } = changes; + if (nzCollapsed || nzCollapsedWidth || nzWidth) { + this.updateStyleMap(); + } + if (nzCollapsed) { + this.updateMenuInlineCollapsed(); + } + } + + ngAfterContentInit(): void { + this.updateMenuInlineCollapsed(); } ngAfterViewInit(): void { if (this.platform.isBrowser) { - Promise.resolve().then(() => this.updateBreakpointMatch()); - this.nzDomEventService - .registerResizeListener() - .pipe( - takeUntil(this.destroy$), - finalize(() => this.nzDomEventService.unregisterResizeListener()) - ) - .subscribe(() => this.updateBreakpointMatch()); + merge( + this.nzDomEventService.registerResizeListener().pipe(finalize(() => this.nzDomEventService.unregisterResizeListener())), + of(true).pipe(delay(0)) + ) + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + this.ngZone.run(() => { + this.updateBreakpointMatch(); + }); + }); } } diff --git a/components/layout/style/index.less b/components/layout/style/index.less index b7a9082dde6..f186662ae8f 100644 --- a/components/layout/style/index.less +++ b/components/layout/style/index.less @@ -32,6 +32,7 @@ &-header { height: @layout-header-height; padding: @layout-header-padding; + color: @layout-header-color; line-height: @layout-header-height; background: @layout-header-background; } diff --git a/components/list/nz-list.module.ts b/components/list/nz-list.module.ts index 8e6edecd3de..0189aee244b 100644 --- a/components/list/nz-list.module.ts +++ b/components/list/nz-list.module.ts @@ -10,7 +10,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { NzAvatarModule } from 'ng-zorro-antd/avatar'; -import { NzAddOnModule } from 'ng-zorro-antd/core'; +import { NzOutletModule } from 'ng-zorro-antd/core'; import { NzEmptyModule } from 'ng-zorro-antd/empty'; import { NzGridModule } from 'ng-zorro-antd/grid'; import { NzSpinModule } from 'ng-zorro-antd/spin'; @@ -20,7 +20,7 @@ import { NzListItemComponent } from './nz-list-item.component'; import { NzListComponent } from './nz-list.component'; @NgModule({ - imports: [CommonModule, NzSpinModule, NzGridModule, NzAvatarModule, NzAddOnModule, NzEmptyModule], + imports: [CommonModule, NzSpinModule, NzGridModule, NzAvatarModule, NzOutletModule, NzEmptyModule], declarations: [NzListComponent, NzListItemComponent, NzListItemMetaComponent], exports: [NzListComponent, NzListItemComponent, NzListItemMetaComponent] }) diff --git a/components/list/style/index.less b/components/list/style/index.less index 6da354048ec..07ebc1c8515 100644 --- a/components/list/style/index.less +++ b/components/list/style/index.less @@ -66,6 +66,7 @@ &-item { display: flex; align-items: center; + justify-content: space-between; padding: @list-item-padding; &-content { diff --git a/components/menu/demo/inline-collapsed.ts b/components/menu/demo/inline-collapsed.ts index e002c478f0d..a8ecf8f911f 100644 --- a/components/menu/demo/inline-collapsed.ts +++ b/components/menu/demo/inline-collapsed.ts @@ -8,7 +8,7 @@ import { Component } from '@angular/core';
      -
    • +
    • Navigation One
    • diff --git a/components/menu/demo/vertical.ts b/components/menu/demo/vertical.ts index b7bca47b379..41db05bdc49 100644 --- a/components/menu/demo/vertical.ts +++ b/components/menu/demo/vertical.ts @@ -3,7 +3,7 @@ import { Component } from '@angular/core'; @Component({ selector: 'nz-demo-menu-vertical', template: ` -
        +
          • @@ -40,7 +40,14 @@ import { Component } from '@angular/core';
        - ` + `, + styles: [ + ` + [nz-menu] { + width: 240px; + } + ` + ] }) export class NzDemoMenuVerticalComponent { change(value: boolean): void { diff --git a/components/menu/nz-menu-divider.directive.ts b/components/menu/menu-divider.directive.ts similarity index 100% rename from components/menu/nz-menu-divider.directive.ts rename to components/menu/menu-divider.directive.ts diff --git a/components/menu/menu-group.component.ts b/components/menu/menu-group.component.ts new file mode 100644 index 00000000000..e3e081e9444 --- /dev/null +++ b/components/menu/menu-group.component.ts @@ -0,0 +1,73 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { + AfterViewInit, + ChangeDetectionStrategy, + Component, + ElementRef, + Inject, + Input, + Optional, + Renderer2, + SkipSelf, + TemplateRef, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import { NzIsMenuInsideDropDownToken } from './menu.token'; + +@Component({ + selector: '[nz-menu-group]', + exportAs: 'nzMenuGroup', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + /** check if menu inside dropdown-menu component **/ + { + provide: NzIsMenuInsideDropDownToken, + useFactory: (isMenuInsideDropDownToken: boolean) => { + return isMenuInsideDropDownToken ? isMenuInsideDropDownToken : false; + }, + deps: [[new SkipSelf(), new Optional(), NzIsMenuInsideDropDownToken]] + } + ], + encapsulation: ViewEncapsulation.None, + template: ` +
        + {{ nzTitle }} + +
        + + `, + preserveWhitespaces: false +}) +export class NzMenuGroupComponent implements AfterViewInit { + @Input() nzTitle: string | TemplateRef; + @ViewChild('titleElement') titleElement: ElementRef; + + constructor( + public elementRef: ElementRef, + private renderer: Renderer2, + @Inject(NzIsMenuInsideDropDownToken) public isMenuInsideDropDown: boolean + ) { + const className = this.isMenuInsideDropDown ? 'ant-dropdown-menu-item-group' : 'ant-menu-item-group'; + this.renderer.addClass(elementRef.nativeElement, className); + } + ngAfterViewInit(): void { + const ulElement = this.renderer.nextSibling(this.titleElement.nativeElement); + if (ulElement) { + /** add classname to ul **/ + const className = this.isMenuInsideDropDown ? 'ant-dropdown-menu-item-group-list' : 'ant-menu-item-group-list'; + this.renderer.addClass(ulElement, className); + } + } +} diff --git a/components/menu/nz-menu-item.directive.ts b/components/menu/menu-item.directive.ts similarity index 64% rename from components/menu/nz-menu-item.directive.ts rename to components/menu/menu-item.directive.ts index 3cf7c75875d..cd7c3936e86 100644 --- a/components/menu/nz-menu-item.directive.ts +++ b/components/menu/menu-item.directive.ts @@ -10,38 +10,45 @@ import { AfterContentInit, ContentChildren, Directive, - ElementRef, + Inject, Input, OnChanges, OnDestroy, OnInit, Optional, QueryList, - Renderer2, SimpleChanges } from '@angular/core'; import { NavigationEnd, Router, RouterLink, RouterLinkWithHref } from '@angular/router'; -import { InputBoolean, isNotNil, NzMenuBaseService, NzUpdateHostClassService } from 'ng-zorro-antd/core'; -import { EMPTY, merge, Subject } from 'rxjs'; +import { InputBoolean } from 'ng-zorro-antd/core'; +import { combineLatest, Subject } from 'rxjs'; import { filter, takeUntil } from 'rxjs/operators'; -import { NzSubmenuService } from './nz-submenu.service'; +import { MenuService } from './menu.service'; +import { NzIsMenuInsideDropDownToken } from './menu.token'; +import { NzSubmenuService } from './submenu.service'; @Directive({ selector: '[nz-menu-item]', exportAs: 'nzMenuItem', - providers: [NzUpdateHostClassService], host: { + '[class.ant-dropdown-menu-item]': `isMenuInsideDropDown`, + '[class.ant-dropdown-menu-item-selected]': `isMenuInsideDropDown && nzSelected`, + '[class.ant-dropdown-menu-item-disabled]': `isMenuInsideDropDown && nzDisabled`, + '[class.ant-menu-item]': `!isMenuInsideDropDown`, + '[class.ant-menu-item-selected]': `!isMenuInsideDropDown && nzSelected`, + '[class.ant-menu-item-disabled]': `!isMenuInsideDropDown && nzDisabled`, + '[style.paddingLeft.px]': 'nzPaddingLeft || inlinePaddingLeft', '(click)': 'clickMenuItem($event)' } }) export class NzMenuItemDirective implements OnInit, OnChanges, OnDestroy, AfterContentInit { - private el: HTMLElement = this.elementRef.nativeElement; private destroy$ = new Subject(); - private originalPadding: number | null = null; + level = this.nzSubmenuService ? this.nzSubmenuService.level + 1 : 1; selected$ = new Subject(); + inlinePaddingLeft: number | null = null; + @Input() nzPaddingLeft: number; @Input() @InputBoolean() nzDisabled = false; @Input() @InputBoolean() nzSelected = false; - @Input() nzPaddingLeft: number; @Input() @InputBoolean() nzMatchRouterExact = false; @Input() @InputBoolean() nzMatchRouter = false; @ContentChildren(RouterLink, { descendants: true }) listOfRouterLink: QueryList; @@ -52,27 +59,21 @@ export class NzMenuItemDirective implements OnInit, OnChanges, OnDestroy, AfterC if (this.nzDisabled) { e.preventDefault(); e.stopPropagation(); - return; - } - this.nzMenuService.onMenuItemClick(this); - if (this.nzSubmenuService) { - this.nzSubmenuService.onMenuItemClick(); + } else { + this.nzMenuService.onDescendantMenuItemClick(this); + if (this.nzSubmenuService) { + /** menu item inside the submenu **/ + this.nzSubmenuService.onChildMenuItemClick(this); + } else { + /** menu item inside the root menu **/ + this.nzMenuService.onChildMenuItemClick(this); + } } } - setClassMap(): void { - const prefixName = this.nzMenuService.isInDropDown ? 'ant-dropdown-menu-item' : 'ant-menu-item'; - this.nzUpdateHostClassService.updateHostClass(this.el, { - [`${prefixName}`]: true, - [`${prefixName}-selected`]: this.nzSelected, - [`${prefixName}-disabled`]: this.nzDisabled - }); - } - setSelectedState(value: boolean): void { this.nzSelected = value; this.selected$.next(value); - this.setClassMap(); } private updateRouterActive(): void { @@ -103,11 +104,9 @@ export class NzMenuItemDirective implements OnInit, OnChanges, OnDestroy, AfterC } constructor( - private nzUpdateHostClassService: NzUpdateHostClassService, - private nzMenuService: NzMenuBaseService, + private nzMenuService: MenuService, @Optional() private nzSubmenuService: NzSubmenuService, - private renderer: Renderer2, - private elementRef: ElementRef, + @Inject(NzIsMenuInsideDropDownToken) public isMenuInsideDropDown: boolean, @Optional() private routerLink?: RouterLink, @Optional() private routerLinkWithHref?: RouterLinkWithHref, @Optional() private router?: Router @@ -124,31 +123,11 @@ export class NzMenuItemDirective implements OnInit, OnChanges, OnDestroy, AfterC ngOnInit(): void { /** store origin padding in padding */ - const paddingLeft = this.el.style.paddingLeft; - if (paddingLeft) { - this.originalPadding = parseInt(paddingLeft, 10); - } - merge(this.nzMenuService.mode$, this.nzMenuService.inlineIndent$, this.nzSubmenuService ? this.nzSubmenuService.level$ : EMPTY) + combineLatest([this.nzMenuService.mode$, this.nzMenuService.inlineIndent$]) .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - let padding: number | null = null; - if (this.nzMenuService.mode === 'inline') { - if (isNotNil(this.nzPaddingLeft)) { - padding = this.nzPaddingLeft; - } else { - const level = this.nzSubmenuService ? this.nzSubmenuService.level + 1 : 1; - padding = level * this.nzMenuService.inlineIndent; - } - } else { - padding = this.originalPadding; - } - if (padding) { - this.renderer.setStyle(this.el, 'padding-left', `${padding}px`); - } else { - this.renderer.removeStyle(this.el, 'padding-left'); - } + .subscribe(([mode, inlineIndent]) => { + this.inlinePaddingLeft = mode === 'inline' ? this.level * inlineIndent : null; }); - this.setClassMap(); } ngAfterContentInit(): void { @@ -161,9 +140,6 @@ export class NzMenuItemDirective implements OnInit, OnChanges, OnDestroy, AfterC if (changes.nzSelected) { this.setSelectedState(this.nzSelected); } - if (changes.nzDisabled) { - this.setClassMap(); - } } ngOnDestroy(): void { diff --git a/components/menu/menu.directive.ts b/components/menu/menu.directive.ts new file mode 100644 index 00000000000..4da7de0bbce --- /dev/null +++ b/components/menu/menu.directive.ts @@ -0,0 +1,163 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { + AfterContentInit, + ChangeDetectorRef, + ContentChildren, + Directive, + EventEmitter, + Inject, + Input, + OnChanges, + OnDestroy, + OnInit, + Optional, + Output, + QueryList, + SimpleChanges, + SkipSelf +} from '@angular/core'; +import { InputBoolean } from 'ng-zorro-antd/core'; +import { BehaviorSubject, combineLatest, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { NzMenuItemDirective } from './menu-item.directive'; +import { MenuService } from './menu.service'; +import { NzIsMenuInsideDropDownToken, NzMenuServiceLocalToken } from './menu.token'; +import { NzMenuModeType, NzMenuThemeType } from './menu.types'; +import { NzSubMenuComponent } from './submenu.component'; + +@Directive({ + selector: '[nz-menu]', + exportAs: 'nzMenu', + providers: [ + { + provide: NzMenuServiceLocalToken, + useClass: MenuService + }, + /** use the top level service **/ + { + provide: MenuService, + useFactory: (serviceInsideDropDown: MenuService, serviceOutsideDropDown: MenuService) => { + return serviceInsideDropDown ? serviceInsideDropDown : serviceOutsideDropDown; + }, + deps: [[new SkipSelf(), new Optional(), MenuService], NzMenuServiceLocalToken] + }, + /** check if menu inside dropdown-menu component **/ + { + provide: NzIsMenuInsideDropDownToken, + useFactory: (isMenuInsideDropDownToken: boolean) => { + return isMenuInsideDropDownToken ? isMenuInsideDropDownToken : false; + }, + deps: [[new SkipSelf(), new Optional(), NzIsMenuInsideDropDownToken]] + } + ], + host: { + '[class.ant-dropdown-menu]': `isMenuInsideDropDown`, + '[class.ant-dropdown-menu-root]': `isMenuInsideDropDown`, + '[class.ant-dropdown-menu-light]': `isMenuInsideDropDown && nzTheme === 'light'`, + '[class.ant-dropdown-menu-dark]': `isMenuInsideDropDown && nzTheme === 'dark'`, + '[class.ant-dropdown-menu-vertical]': `isMenuInsideDropDown && actualMode === 'vertical'`, + '[class.ant-dropdown-menu-horizontal]': `isMenuInsideDropDown && actualMode === 'horizontal'`, + '[class.ant-dropdown-menu-inline]': `isMenuInsideDropDown && actualMode === 'inline'`, + '[class.ant-dropdown-menu-inline-collapsed]': `isMenuInsideDropDown && nzInlineCollapsed`, + '[class.ant-menu]': `!isMenuInsideDropDown`, + '[class.ant-menu-root]': `!isMenuInsideDropDown`, + '[class.ant-menu-light]': `!isMenuInsideDropDown && nzTheme === 'light'`, + '[class.ant-menu-dark]': `!isMenuInsideDropDown && nzTheme === 'dark'`, + '[class.ant-menu-vertical]': `!isMenuInsideDropDown && actualMode === 'vertical'`, + '[class.ant-menu-horizontal]': `!isMenuInsideDropDown && actualMode === 'horizontal'`, + '[class.ant-menu-inline]': `!isMenuInsideDropDown && actualMode === 'inline'`, + '[class.ant-menu-inline-collapsed]': `!isMenuInsideDropDown && nzInlineCollapsed` + } +}) +export class NzMenuDirective implements AfterContentInit, OnInit, OnChanges, OnDestroy { + @ContentChildren(NzMenuItemDirective, { descendants: true }) listOfNzMenuItemDirective: QueryList; + @ContentChildren(NzSubMenuComponent, { descendants: true }) listOfNzSubMenuComponent: QueryList; + @Input() nzInlineIndent = 24; + @Input() nzTheme: NzMenuThemeType = 'light'; + @Input() nzMode: NzMenuModeType = 'vertical'; + @Input() @InputBoolean() nzInlineCollapsed = false; + @Input() @InputBoolean() nzSelectable = !this.isMenuInsideDropDown; + @Output() readonly nzClick = new EventEmitter(); + actualMode: NzMenuModeType = 'vertical'; + private inlineCollapsed$ = new BehaviorSubject(this.nzInlineCollapsed); + private mode$ = new BehaviorSubject(this.nzMode); + private destroy$ = new Subject(); + private listOfOpenedNzSubMenuComponent: NzSubMenuComponent[] = []; + + setInlineCollapsed(inlineCollapsed: boolean): void { + this.nzInlineCollapsed = inlineCollapsed; + this.inlineCollapsed$.next(inlineCollapsed); + } + + updateInlineCollapse(): void { + if (this.listOfNzMenuItemDirective) { + if (this.nzInlineCollapsed) { + this.listOfOpenedNzSubMenuComponent = this.listOfNzSubMenuComponent.filter(submenu => submenu.nzOpen); + this.listOfNzSubMenuComponent.forEach(submenu => submenu.setOpenStateWithoutDebounce(false)); + } else { + this.listOfOpenedNzSubMenuComponent.forEach(submenu => submenu.setOpenStateWithoutDebounce(true)); + this.listOfOpenedNzSubMenuComponent = []; + } + } + } + + constructor( + private nzMenuService: MenuService, + @Inject(NzIsMenuInsideDropDownToken) public isMenuInsideDropDown: boolean, + private cdr: ChangeDetectorRef + ) {} + + ngOnInit(): void { + combineLatest([this.inlineCollapsed$, this.mode$]) + .pipe(takeUntil(this.destroy$)) + .subscribe(([inlineCollapsed, mode]) => { + this.actualMode = inlineCollapsed ? 'vertical' : mode; + this.nzMenuService.setMode(this.actualMode); + this.cdr.markForCheck(); + }); + this.nzMenuService.descendantMenuItemClick$.pipe(takeUntil(this.destroy$)).subscribe(menu => { + this.nzClick.emit(menu); + if (this.nzSelectable && !menu.nzMatchRouter) { + this.listOfNzMenuItemDirective.forEach(item => item.setSelectedState(item === menu)); + } + }); + } + + ngAfterContentInit(): void { + this.inlineCollapsed$.pipe(takeUntil(this.destroy$)).subscribe(() => { + this.updateInlineCollapse(); + this.cdr.markForCheck(); + }); + } + + ngOnChanges(changes: SimpleChanges): void { + const { nzInlineCollapsed, nzInlineIndent, nzTheme, nzMode } = changes; + if (nzInlineCollapsed) { + this.inlineCollapsed$.next(this.nzInlineCollapsed); + } + if (nzInlineIndent) { + this.nzMenuService.setInlineIndent(this.nzInlineIndent); + } + if (nzTheme) { + this.nzMenuService.setTheme(this.nzTheme); + } + if (nzMode) { + this.mode$.next(this.nzMode); + if (!changes.nzMode.isFirstChange() && this.listOfNzSubMenuComponent) { + this.listOfNzSubMenuComponent.forEach(submenu => submenu.setOpenStateWithoutDebounce(false)); + } + } + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/components/menu/menu.module.ts b/components/menu/menu.module.ts new file mode 100644 index 00000000000..50fcc64be82 --- /dev/null +++ b/components/menu/menu.module.ts @@ -0,0 +1,37 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { OverlayModule } from '@angular/cdk/overlay'; +import { PlatformModule } from '@angular/cdk/platform'; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { NzNoAnimationModule, NzOutletModule } from 'ng-zorro-antd/core'; +import { NzIconModule } from 'ng-zorro-antd/icon'; +import { NzMenuDividerDirective } from './menu-divider.directive'; +import { NzMenuGroupComponent } from './menu-group.component'; +import { NzMenuItemDirective } from './menu-item.directive'; +import { NzMenuDirective } from './menu.directive'; +import { NzSubmenuInlineChildComponent } from './submenu-inline-child.component'; +import { NzSubmenuNoneInlineChildComponent } from './submenu-non-inline-child.component'; +import { NzSubMenuTitleComponent } from './submenu-title.component'; +import { NzSubMenuComponent } from './submenu.component'; + +@NgModule({ + imports: [CommonModule, PlatformModule, OverlayModule, NzIconModule, NzNoAnimationModule, NzOutletModule], + declarations: [ + NzMenuDirective, + NzMenuItemDirective, + NzSubMenuComponent, + NzMenuDividerDirective, + NzMenuGroupComponent, + NzSubMenuTitleComponent, + NzSubmenuInlineChildComponent, + NzSubmenuNoneInlineChildComponent + ], + exports: [NzMenuDirective, NzMenuItemDirective, NzSubMenuComponent, NzMenuDividerDirective, NzMenuGroupComponent] +}) +export class NzMenuModule {} diff --git a/components/menu/menu.service.ts b/components/menu/menu.service.ts new file mode 100644 index 00000000000..ad8b8536172 --- /dev/null +++ b/components/menu/menu.service.ts @@ -0,0 +1,44 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { Injectable } from '@angular/core'; +import { NzSafeAny } from 'ng-zorro-antd/core/types'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { NzMenuModeType, NzMenuThemeType } from './menu.types'; + +@Injectable() +export class MenuService { + /** all descendant menu click **/ + descendantMenuItemClick$ = new Subject(); + /** child menu item click **/ + childMenuItemClick$ = new Subject(); + theme$ = new BehaviorSubject('light'); + mode$ = new BehaviorSubject('vertical'); + inlineIndent$ = new BehaviorSubject(24); + isChildSubMenuOpen$ = new BehaviorSubject(false); + + onDescendantMenuItemClick(menu: NzSafeAny): void { + this.descendantMenuItemClick$.next(menu); + } + + onChildMenuItemClick(menu: NzSafeAny): void { + this.childMenuItemClick$.next(menu); + } + + setMode(mode: NzMenuModeType): void { + this.mode$.next(mode); + } + + setTheme(theme: NzMenuThemeType): void { + this.theme$.next(theme); + } + + setInlineIndent(indent: number): void { + this.inlineIndent$.next(indent); + } +} diff --git a/components/menu/nz-menu.spec.ts b/components/menu/menu.spec.ts similarity index 75% rename from components/menu/nz-menu.spec.ts rename to components/menu/menu.spec.ts index a76988b3d7b..4852a926f0e 100644 --- a/components/menu/nz-menu.spec.ts +++ b/components/menu/menu.spec.ts @@ -1,49 +1,33 @@ -import { Directionality } from '@angular/cdk/bidi'; import { ConnectedOverlayPositionChange, OverlayContainer } from '@angular/cdk/overlay'; -import { ScrollDispatcher } from '@angular/cdk/scrolling'; -import { Component, DebugElement, ElementRef, NO_ERRORS_SCHEMA, QueryList, ViewChild, ViewChildren } from '@angular/core'; +import { Component, DebugElement, ElementRef, QueryList, ViewChild, ViewChildren } from '@angular/core'; import { async, ComponentFixture, fakeAsync, inject, TestBed, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { Subject } from 'rxjs'; - import { dispatchFakeEvent } from 'ng-zorro-antd/core'; import { NzIconTestModule } from 'ng-zorro-antd/icon/testing'; - -import { NzDemoMenuInlineCollapsedComponent } from './demo/inline-collapsed'; -import { NzDemoMenuSiderCurrentComponent } from './demo/sider-current'; -import { NzDemoMenuSwitchModeComponent } from './demo/switch-mode'; -import { NzDemoMenuThemeComponent } from './demo/theme'; -import { NzMenuItemDirective } from './nz-menu-item.directive'; -import { NzMenuDirective } from './nz-menu.directive'; -import { NzMenuModule } from './nz-menu.module'; -import { NzSubMenuComponent } from './nz-submenu.component'; +import { NzMenuItemDirective } from './menu-item.directive'; +import { NzMenuDirective } from './menu.directive'; +import { NzMenuModule } from './menu.module'; +import { NzSubMenuComponent } from './submenu.component'; describe('menu', () => { let overlayContainer: OverlayContainer; let overlayContainerElement: HTMLElement; - const scrolledSubject = new Subject(); beforeEach(async(() => { - const dir = 'ltr'; TestBed.configureTestingModule({ - imports: [NzMenuModule, NoopAnimationsModule, NoopAnimationsModule, NzIconTestModule], + imports: [NzMenuModule, NoopAnimationsModule, NzIconTestModule], declarations: [ NzTestBasicMenuHorizontalComponent, NzTestBasicMenuInlineComponent, - NzDemoMenuInlineCollapsedComponent, - NzDemoMenuSiderCurrentComponent, - NzDemoMenuThemeComponent, - NzDemoMenuSwitchModeComponent, + NzTestMenuInlineCollapsedComponent, + NzTestMenuSiderCurrentComponent, + NzTestMenuThemeComponent, + NzTestMenuSwitchModeComponent, NzTestMenuHorizontalComponent, NzTestMenuInlineComponent, NzDemoMenuNgForComponent, NzTestNgIfMenuComponent, NzTestSubMenuSelectedComponent - ], - schemas: [NO_ERRORS_SCHEMA], - providers: [ - { provide: Directionality, useFactory: () => ({ value: dir }) }, - { provide: ScrollDispatcher, useFactory: () => ({ scrolled: () => scrolledSubject }) } ] }); @@ -134,12 +118,12 @@ describe('menu', () => { })); }); describe('inline-collapsed', () => { - let fixture: ComponentFixture; - let testComponent: NzDemoMenuInlineCollapsedComponent; + let fixture: ComponentFixture; + let testComponent: NzTestMenuInlineCollapsedComponent; let submenus: DebugElement[]; let menu: DebugElement; beforeEach(() => { - fixture = TestBed.createComponent(NzDemoMenuInlineCollapsedComponent); + fixture = TestBed.createComponent(NzTestMenuInlineCollapsedComponent); testComponent = fixture.debugElement.componentInstance; submenus = fixture.debugElement.queryAll(By.directive(NzSubMenuComponent)); menu = fixture.debugElement.query(By.directive(NzMenuDirective)); @@ -176,10 +160,10 @@ describe('menu', () => { }); }); describe('slider-current', () => { - let fixture: ComponentFixture; + let fixture: ComponentFixture; let submenus: DebugElement[]; beforeEach(() => { - fixture = TestBed.createComponent(NzDemoMenuSiderCurrentComponent); + fixture = TestBed.createComponent(NzTestMenuSiderCurrentComponent); submenus = fixture.debugElement.queryAll(By.directive(NzSubMenuComponent)); }); it('should collapsed self work', fakeAsync(() => { @@ -215,11 +199,11 @@ describe('menu', () => { })); }); describe('theme', () => { - let fixture: ComponentFixture; - let testComponent: NzDemoMenuThemeComponent; + let fixture: ComponentFixture; + let testComponent: NzTestMenuThemeComponent; let menu: DebugElement; beforeEach(() => { - fixture = TestBed.createComponent(NzDemoMenuThemeComponent); + fixture = TestBed.createComponent(NzTestMenuThemeComponent); testComponent = fixture.debugElement.componentInstance; menu = fixture.debugElement.query(By.directive(NzMenuDirective)); }); @@ -228,16 +212,16 @@ describe('menu', () => { expect(menu.nativeElement.className).toBe('ant-menu ant-menu-root ant-menu-dark ant-menu-inline'); testComponent.theme = false; fixture.detectChanges(); - expect(menu.nativeElement.className).toBe('ant-menu ant-menu-root ant-menu-light ant-menu-inline'); + expect(menu.nativeElement.className).toBe('ant-menu ant-menu-root ant-menu-inline ant-menu-light'); }); }); describe('swich-mode', () => { - let fixture: ComponentFixture; - let testComponent: NzDemoMenuSwitchModeComponent; + let fixture: ComponentFixture; + let testComponent: NzTestMenuSwitchModeComponent; let submenus: DebugElement[]; let menu: DebugElement; beforeEach(() => { - fixture = TestBed.createComponent(NzDemoMenuSwitchModeComponent); + fixture = TestBed.createComponent(NzTestMenuSwitchModeComponent); testComponent = fixture.debugElement.componentInstance; submenus = fixture.debugElement.queryAll(By.directive(NzSubMenuComponent)); menu = fixture.debugElement.query(By.directive(NzMenuDirective)); @@ -276,7 +260,8 @@ describe('menu', () => { const mouseenterCallback = jasmine.createSpy('mouseenter callback'); const subs = testComponent.subs.toArray(); const title = submenu.nativeElement.querySelector('.ant-menu-submenu-title'); - subs[0].nzSubmenuService.mouseEnterLeave$.subscribe(mouseenterCallback); + // tslint:disable-next-line:no-any + (subs[0].nzSubmenuService as any).isMouseEnterTitleOrOverlay$.subscribe(mouseenterCallback); dispatchFakeEvent(title, 'mouseenter'); fixture.detectChanges(); expect(mouseenterCallback).toHaveBeenCalledWith(true); @@ -287,7 +272,8 @@ describe('menu', () => { const mouseleaveCallback = jasmine.createSpy('mouseleave callback'); const subs = testComponent.subs.toArray(); const title = submenu.nativeElement.querySelector('.ant-menu-submenu-title'); - subs[0].nzSubmenuService.mouseEnterLeave$.subscribe(mouseleaveCallback); + // tslint:disable-next-line:no-any + (subs[0].nzSubmenuService as any).isMouseEnterTitleOrOverlay$.subscribe(mouseleaveCallback); dispatchFakeEvent(title, 'mouseleave'); fixture.detectChanges(); expect(mouseleaveCallback).toHaveBeenCalledWith(false); @@ -298,9 +284,10 @@ describe('menu', () => { fixture.detectChanges(); const nestedCallback = jasmine.createSpy('nested callback'); const subs = testComponent.subs.toArray(); - subs[0].nzSubmenuService.subMenuOpen$.subscribe(nestedCallback); + // tslint:disable-next-line:no-any + (subs[0].nzSubmenuService as any).isChildSubMenuOpen$.subscribe(nestedCallback); subs[1].nzOpen = true; - subs[1].nzSubmenuService.open$.next(false); + subs[1].nzSubmenuService.isCurrentSubMenuOpen$.next(false); fixture.detectChanges(); expect(nestedCallback).toHaveBeenCalledWith(false); expect(nestedCallback).toHaveBeenCalledTimes(1); @@ -311,22 +298,23 @@ describe('menu', () => { fixture.detectChanges(); const nestedCallback = jasmine.createSpy('nested callback'); const subs = testComponent.subs.toArray(); - subs[0].nzSubmenuService.subMenuOpen$.subscribe(nestedCallback); + // tslint:disable-next-line:no-any + (subs[0].nzSubmenuService as any).isChildSubMenuOpen$.subscribe(nestedCallback); subs[1].nzOpen = true; - subs[1].nzSubmenuService.open$.next(false); + subs[1].nzSubmenuService.isCurrentSubMenuOpen$.next(false); fixture.detectChanges(); expect(nestedCallback).toHaveBeenCalledTimes(1); }); it('should click menu and other submenu menu not active', fakeAsync(() => { testComponent.open = true; fixture.detectChanges(); + const subs = testComponent.subs.toArray(); dispatchFakeEvent(testComponent.menuitem1.nativeElement, 'mouseenter'); fixture.detectChanges(); testComponent.menuitem1.nativeElement.click(); - expect(submenu.nativeElement.classList).toContain('ant-menu-submenu-active'); fixture.detectChanges(); tick(500); - expect(submenu.nativeElement.classList).not.toContain('ant-menu-submenu-active'); + expect(subs[1].isActive).toBe(false); })); it('should click submenu menu item close', () => { testComponent.open = true; @@ -335,7 +323,8 @@ describe('menu', () => { const subs = testComponent.subs.toArray(); subs[1].nzOpen = true; fixture.detectChanges(); - subs[1].nzSubmenuService.mouseEnterLeave$.subscribe(nestedCallback); + // tslint:disable-next-line:no-any + (subs[1].nzSubmenuService as any).isChildSubMenuOpen$.subscribe(nestedCallback); testComponent.menuitem.nativeElement.click(); fixture.detectChanges(); expect(nestedCallback).toHaveBeenCalledWith(false); @@ -346,7 +335,8 @@ describe('menu', () => { fixture.detectChanges(); const nestedCallback = jasmine.createSpy('nested callback'); const subs = testComponent.subs.toArray(); - subs[1].nzSubmenuService.mouseEnterLeave$.subscribe(nestedCallback); + // tslint:disable-next-line:no-any + (subs[1].nzSubmenuService as any).isMouseEnterTitleOrOverlay$.subscribe(nestedCallback); subs[1].nzOpen = true; testComponent.disableditem.nativeElement.click(); fixture.detectChanges(); @@ -390,25 +380,27 @@ describe('menu', () => { fixture.detectChanges(); subs[1].onPositionChange(fakeLeftTopEvent); fixture.detectChanges(); - expect(subs[1].placement).toBe('leftTop'); + expect(subs[1].position).toBe('left'); subs[1].onPositionChange(fakeRightTopEvent); fixture.detectChanges(); - expect(subs[1].placement).toBe('rightTop'); + expect(subs[1].position).toBe('right'); }); it('should `nzMenuClassName` work', fakeAsync(() => { fixture.detectChanges(); testComponent.open = true; fixture.detectChanges(); - expect((overlayContainerElement.querySelector('ul.submenu') as HTMLUListElement).classList).toContain('ant-menu-sub'); + expect((overlayContainerElement.querySelector('.submenu') as HTMLUListElement).classList).toContain('ant-menu-sub'); })); it('should nested submenu `nzMenuClassName` work', () => { testComponent.open = true; fixture.detectChanges(); const subs = testComponent.subs.toArray(); + subs[0].nzOpen = true; subs[1].nzOpen = true; - subs[1].nzSubmenuService.open$.next(true); + // tslint:disable-next-line:no-any + (subs[1] as any).cdr.markForCheck(); fixture.detectChanges(); - expect((overlayContainerElement.querySelector('ul.nested-submenu') as HTMLUListElement).classList).toContain('ant-menu-sub'); + expect((overlayContainerElement.querySelector('.nested-submenu') as HTMLUListElement).classList).toContain('ant-menu-sub'); }); }); describe('inline submenu', () => { @@ -698,7 +690,7 @@ export class NzTestNgIfMenuComponent { @Component({ template: `
          -
        • +
        • Navigation One
        • @@ -712,3 +704,211 @@ export class NzTestNgIfMenuComponent { ` }) export class NzTestSubMenuSelectedComponent {} + +@Component({ + template: ` +
          + +
            +
          • + + Navigation One +
          • +
          • +
              +
            • Option 5
            • +
            • Option 6
            • +
            • +
                +
              • Option 7
              • +
              • Option 8
              • +
              +
            • +
            +
          • +
          • +
              +
            • Option 9
            • +
            • Option 10
            • +
            • Option 11
            • +
            +
          • +
          +
          + `, + styles: [ + ` + .wrapper { + width: 240px; + } + + button { + margin-bottom: 12px; + } + ` + ] +}) +export class NzTestMenuInlineCollapsedComponent { + isCollapsed = false; + + toggleCollapsed(): void { + this.isCollapsed = !this.isCollapsed; + } +} + +@Component({ + template: ` +
            +
          • +
              +
            • +
                +
              • Option 1
              • +
              • Option 2
              • +
              +
            • +
            • +
                +
              • Option 3
              • +
              • Option 4
              • +
              +
            • +
            +
          • +
          • +
              +
            • Option 5
            • +
            • Option 6
            • +
            • +
                +
              • Option 7
              • +
              • Option 8
              • +
              +
            • +
            +
          • +
          • +
              +
            • Option 9
            • +
            • Option 10
            • +
            • Option 11
            • +
            +
          • +
          + ` +}) +export class NzTestMenuSiderCurrentComponent { + openMap: { [name: string]: boolean } = { + sub1: true, + sub2: false, + sub3: false + }; + + openHandler(value: string): void { + for (const key in this.openMap) { + if (key !== value) { + this.openMap[key] = false; + } + } + } +} + +@Component({ + template: ` +
            +
          • +
              +
            • +
                +
              • Option 1
              • +
              • Option 2
              • +
              +
            • +
            • +
                +
              • Option 3
              • +
              • Option 4
              • +
              +
            • +
            +
          • +
          • +
              +
            • Option 5
            • +
            • Option 6
            • +
            • +
                +
              • Option 7
              • +
              • Option 8
              • +
              +
            • +
            +
          • +
          • +
              +
            • Option 9
            • +
            • Option 10
            • +
            • Option 11
            • +
            +
          • +
          + `, + styles: [ + ` + [nz-menu] { + width: 240px; + } + ` + ] +}) +export class NzTestMenuSwitchModeComponent { + mode = false; + dark = false; +} + +@Component({ + template: ` +
            +
          • +
              +
            • +
                +
              • Option 1
              • +
              • Option 2
              • +
              +
            • +
            • +
                +
              • Option 3
              • +
              • Option 4
              • +
              +
            • +
            +
          • +
          • +
              +
            • Option 5
            • +
            • Option 6
            • +
            • +
                +
              • Option 7
              • +
              • Option 8
              • +
              +
            • +
            +
          • +
          • +
              +
            • Option 9
            • +
            • Option 10
            • +
            • Option 11
            • +
            +
          • +
          + ` +}) +export class NzTestMenuThemeComponent { + theme = true; +} diff --git a/components/core/dropdown/nz-dropdown-service.resolver.ts b/components/menu/menu.token.ts similarity index 54% rename from components/core/dropdown/nz-dropdown-service.resolver.ts rename to components/menu/menu.token.ts index 613c15032bc..76b9ba337aa 100644 --- a/components/core/dropdown/nz-dropdown-service.resolver.ts +++ b/components/menu/menu.token.ts @@ -7,7 +7,7 @@ */ import { InjectionToken } from '@angular/core'; +import { MenuService } from './menu.service'; -import { NzMenuBaseService } from './nz-menu-base.service'; - -export const NzDropdownHigherOrderServiceToken = new InjectionToken('NzMenuHigherOrder'); +export const NzIsMenuInsideDropDownToken = new InjectionToken('NzIsInDropDownMenuToken'); +export const NzMenuServiceLocalToken = new InjectionToken('NzMenuServiceLocalToken'); diff --git a/components/dropdown/nz-dropdown.service.module.ts b/components/menu/menu.types.ts similarity index 67% rename from components/dropdown/nz-dropdown.service.module.ts rename to components/menu/menu.types.ts index e9325fa4f85..2496d51ccd1 100644 --- a/components/dropdown/nz-dropdown.service.module.ts +++ b/components/menu/menu.types.ts @@ -6,7 +6,5 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -import { NgModule } from '@angular/core'; - -@NgModule() -export class NzDropdownServiceModule {} +export type NzMenuModeType = 'vertical' | 'horizontal' | 'inline'; +export type NzMenuThemeType = 'light' | 'dark'; diff --git a/components/menu/nz-menu-group.component.html b/components/menu/nz-menu-group.component.html deleted file mode 100644 index 27174676dad..00000000000 --- a/components/menu/nz-menu-group.component.html +++ /dev/null @@ -1,7 +0,0 @@ -
          - {{ nzTitle }} - -
          -
            - -
          diff --git a/components/menu/nz-menu-group.component.ts b/components/menu/nz-menu-group.component.ts deleted file mode 100644 index d857c7ed1b0..00000000000 --- a/components/menu/nz-menu-group.component.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { ChangeDetectionStrategy, Component, ElementRef, Input, Renderer2, TemplateRef, ViewEncapsulation } from '@angular/core'; - -@Component({ - selector: '[nz-menu-group]', - exportAs: 'nzMenuGroup', - changeDetection: ChangeDetectionStrategy.OnPush, - encapsulation: ViewEncapsulation.None, - templateUrl: './nz-menu-group.component.html', - preserveWhitespaces: false -}) -export class NzMenuGroupComponent { - @Input() nzTitle: string | TemplateRef; - - constructor(public elementRef: ElementRef, private renderer: Renderer2) { - this.renderer.addClass(elementRef.nativeElement, 'ant-menu-item-group'); - } -} diff --git a/components/menu/nz-menu.directive.ts b/components/menu/nz-menu.directive.ts deleted file mode 100644 index 260ffe0b1ce..00000000000 --- a/components/menu/nz-menu.directive.ts +++ /dev/null @@ -1,144 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { - AfterContentInit, - ContentChildren, - Directive, - ElementRef, - EventEmitter, - Input, - OnChanges, - OnDestroy, - OnInit, - Optional, - Output, - QueryList, - SimpleChanges, - SkipSelf -} from '@angular/core'; - -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; - -import { - InputBoolean, - NzDirectionVHIType, - NzDropdownHigherOrderServiceToken, - NzMenuBaseService, - NzUpdateHostClassService -} from 'ng-zorro-antd/core'; - -import { NzMenuItemDirective } from './nz-menu-item.directive'; -import { NzMenuServiceFactory } from './nz-menu.resolver'; -import { NzMenuService } from './nz-menu.service'; -import { NzSubMenuComponent } from './nz-submenu.component'; - -@Directive({ - selector: '[nz-menu]', - exportAs: 'nzMenu', - providers: [ - NzUpdateHostClassService, - NzMenuService, - { - provide: NzMenuBaseService, - useFactory: NzMenuServiceFactory, - deps: [[new SkipSelf(), new Optional(), NzDropdownHigherOrderServiceToken], NzMenuService] - } - ] -}) -export class NzMenuDirective implements AfterContentInit, OnInit, OnChanges, OnDestroy { - private destroy$ = new Subject(); - private cacheMode: NzDirectionVHIType; - private listOfOpenedNzSubMenuComponent: NzSubMenuComponent[] = []; - @ContentChildren(NzMenuItemDirective, { descendants: true }) listOfNzMenuItemDirective: QueryList; - @ContentChildren(NzSubMenuComponent, { descendants: true }) listOfNzSubMenuComponent: QueryList; - @Input() nzInlineIndent = 24; - @Input() nzTheme: 'light' | 'dark' = 'light'; - @Input() nzMode: NzDirectionVHIType = 'vertical'; - @Input() @InputBoolean() nzInDropDown = false; - @Input() @InputBoolean() nzInlineCollapsed = false; - @Input() @InputBoolean() nzSelectable = !this.nzMenuService.isInDropDown; - @Output() readonly nzClick = new EventEmitter(); - - updateInlineCollapse(): void { - if (this.listOfNzMenuItemDirective) { - if (this.nzInlineCollapsed) { - this.listOfOpenedNzSubMenuComponent = this.listOfNzSubMenuComponent.filter(submenu => submenu.nzOpen); - this.listOfNzSubMenuComponent.forEach(submenu => submenu.setOpenState(false)); - this.nzMode = 'vertical'; - } else { - this.listOfOpenedNzSubMenuComponent.forEach(submenu => submenu.setOpenState(true)); - this.listOfOpenedNzSubMenuComponent = []; - this.nzMode = this.cacheMode; - } - this.nzMenuService.setMode(this.nzMode); - } - } - - setClassMap(): void { - const prefixName = this.nzMenuService.isInDropDown ? 'ant-dropdown-menu' : 'ant-menu'; - this.nzUpdateHostClassService.updateHostClass(this.elementRef.nativeElement, { - [`${prefixName}`]: true, - [`${prefixName}-root`]: true, - [`${prefixName}-${this.nzTheme}`]: true, - [`${prefixName}-${this.nzMode}`]: true, - [`${prefixName}-inline-collapsed`]: this.nzInlineCollapsed - }); - } - - constructor( - public elementRef: ElementRef, - private nzMenuService: NzMenuBaseService, - private nzUpdateHostClassService: NzUpdateHostClassService - ) {} - - ngOnInit(): void { - this.setClassMap(); - this.nzMenuService.menuItemClick$.pipe(takeUntil(this.destroy$)).subscribe(menu => { - this.nzClick.emit(menu); - if (this.nzSelectable) { - this.listOfNzMenuItemDirective.forEach(item => item.setSelectedState(item === menu)); - } - }); - } - - ngAfterContentInit(): void { - this.cacheMode = this.nzMode; - this.updateInlineCollapse(); - } - - ngOnChanges(changes: SimpleChanges): void { - if (changes.nzInlineCollapsed) { - this.updateInlineCollapse(); - } - if (changes.nzInlineIndent) { - this.nzMenuService.setInlineIndent(this.nzInlineIndent); - } - if (changes.nzInDropDown) { - this.nzMenuService.isInDropDown = this.nzInDropDown; - } - if (changes.nzTheme) { - this.nzMenuService.setTheme(this.nzTheme); - } - if (changes.nzMode) { - this.nzMenuService.setMode(this.nzMode); - if (!changes.nzMode.isFirstChange() && this.listOfNzSubMenuComponent) { - this.listOfNzSubMenuComponent.forEach(submenu => submenu.setOpenState(false)); - } - } - if (changes.nzTheme || changes.nzMode || changes.nzInlineCollapsed) { - this.setClassMap(); - } - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } -} diff --git a/components/menu/nz-menu.module.ts b/components/menu/nz-menu.module.ts deleted file mode 100644 index 4d8ac37ce46..00000000000 --- a/components/menu/nz-menu.module.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { OverlayModule } from '@angular/cdk/overlay'; -import { PlatformModule } from '@angular/cdk/platform'; -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { FormsModule } from '@angular/forms'; -import { NzButtonModule } from 'ng-zorro-antd/button'; -import { NzAddOnModule, NzNoAnimationModule } from 'ng-zorro-antd/core'; -import { NzIconModule } from 'ng-zorro-antd/icon'; - -import { NzMenuDividerDirective } from './nz-menu-divider.directive'; -import { NzMenuGroupComponent } from './nz-menu-group.component'; -import { NzMenuItemDirective } from './nz-menu-item.directive'; -import { NzMenuDirective } from './nz-menu.directive'; -import { NzSubMenuComponent } from './nz-submenu.component'; - -@NgModule({ - imports: [CommonModule, FormsModule, PlatformModule, NzButtonModule, OverlayModule, NzIconModule, NzNoAnimationModule, NzAddOnModule], - declarations: [NzMenuDirective, NzMenuItemDirective, NzSubMenuComponent, NzMenuDividerDirective, NzMenuGroupComponent], - exports: [NzMenuDirective, NzMenuItemDirective, NzSubMenuComponent, NzMenuDividerDirective, NzMenuGroupComponent] -}) -export class NzMenuModule {} diff --git a/components/menu/nz-menu.resolver.ts b/components/menu/nz-menu.resolver.ts deleted file mode 100644 index 106caf1bf66..00000000000 --- a/components/menu/nz-menu.resolver.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { NzMenuBaseService } from 'ng-zorro-antd/core'; - -import { NzMenuService } from './nz-menu.service'; - -export function NzMenuServiceFactory(higherOrderService: NzMenuBaseService, menuService: NzMenuService): NzMenuBaseService { - return higherOrderService ? higherOrderService : menuService; -} diff --git a/components/menu/nz-menu.service.ts b/components/menu/nz-menu.service.ts deleted file mode 100644 index af896cf7e04..00000000000 --- a/components/menu/nz-menu.service.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { Injectable } from '@angular/core'; - -import { NzMenuBaseService } from 'ng-zorro-antd/core'; - -@Injectable() -export class NzMenuService extends NzMenuBaseService { - isInDropDown = false; -} diff --git a/components/menu/nz-submenu.component.html b/components/menu/nz-submenu.component.html deleted file mode 100644 index 513bf10addd..00000000000 --- a/components/menu/nz-submenu.component.html +++ /dev/null @@ -1,84 +0,0 @@ -
          - - {{ nzTitle }} - - - - - - - -
          -
            - -
          - -
          -
            - -
          -
          -
          - - - - diff --git a/components/menu/nz-submenu.component.ts b/components/menu/nz-submenu.component.ts deleted file mode 100644 index 8e3642c5a10..00000000000 --- a/components/menu/nz-submenu.component.ts +++ /dev/null @@ -1,223 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { CdkConnectedOverlay, CdkOverlayOrigin, ConnectedOverlayPositionChange } from '@angular/cdk/overlay'; -import { Platform } from '@angular/cdk/platform'; -import { - AfterContentInit, - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ContentChildren, - ElementRef, - EventEmitter, - Host, - Input, - OnChanges, - OnDestroy, - OnInit, - Optional, - Output, - QueryList, - SimpleChanges, - TemplateRef, - ViewChild, - ViewEncapsulation -} from '@angular/core'; - -import { combineLatest, merge, Subject } from 'rxjs'; -import { flatMap, map, startWith, takeUntil } from 'rxjs/operators'; - -import { - collapseMotion, - DEFAULT_SUBMENU_POSITIONS, - getPlacementName, - InputBoolean, - NzMenuBaseService, - NzNoAnimationDirective, - NzUpdateHostClassService, - POSITION_MAP, - slideMotion, - zoomBigMotion -} from 'ng-zorro-antd/core'; - -import { NzMenuItemDirective } from './nz-menu-item.directive'; -import { NzSubmenuService } from './nz-submenu.service'; - -@Component({ - selector: '[nz-submenu]', - exportAs: 'nzSubmenu', - providers: [NzSubmenuService, NzUpdateHostClassService], - animations: [collapseMotion, zoomBigMotion, slideMotion], - encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush, - preserveWhitespaces: false, - templateUrl: './nz-submenu.component.html', - styles: [ - ` - :root .ant-menu-submenu.ant-menu-submenu-placement-bottomLeft { - top: 6px; - position: relative; - } - - :root .ant-menu-submenu.ant-menu-submenu-placement-rightTop { - left: 4px; - position: relative; - } - - :root .ant-menu-submenu.ant-menu-submenu-placement-leftTop { - right: 4px; - position: relative; - } - ` - ] -}) -export class NzSubMenuComponent implements OnInit, OnDestroy, AfterContentInit, OnChanges { - @Input() nzMenuClassName: string; - @Input() nzPaddingLeft: number; - @Input() nzTitle: string | TemplateRef; - @Input() nzIcon: string; - @Input() @InputBoolean() nzOpen = false; - @Input() @InputBoolean() nzDisabled = false; - @Output() readonly nzOpenChange: EventEmitter = new EventEmitter(); - - @ViewChild(CdkConnectedOverlay, { static: true }) cdkConnectedOverlay: CdkConnectedOverlay; - @ViewChild(CdkOverlayOrigin, { static: true, read: ElementRef }) cdkOverlayOrigin: ElementRef; - @ContentChildren(NzSubMenuComponent, { descendants: true }) - listOfNzSubMenuComponent: QueryList; - @ContentChildren(NzMenuItemDirective, { descendants: true }) - listOfNzMenuItemDirective: QueryList; - - placement = 'rightTop'; - triggerWidth: number; - expandState = 'collapsed'; - overlayPositions = [...DEFAULT_SUBMENU_POSITIONS]; - - private destroy$ = new Subject(); - private isChildMenuSelected = false; - private isMouseHover = false; - - setOpenState(open: boolean): void { - this.nzSubmenuService.setOpenState(open); - } - - clickSubMenuTitle(): void { - if (this.nzSubmenuService.mode === 'inline' && !this.nzMenuService.isInDropDown && !this.nzDisabled) { - this.setOpenState(!this.nzOpen); - } - } - - setMouseEnterState(value: boolean): void { - this.isMouseHover = value; - this.setClassMap(); - this.nzSubmenuService.setMouseEnterState(value); - } - - setTriggerWidth(): void { - if (this.nzSubmenuService.mode === 'horizontal' && this.platform.isBrowser) { - this.triggerWidth = this.cdkOverlayOrigin.nativeElement.getBoundingClientRect().width; - } - } - - onPositionChange(position: ConnectedOverlayPositionChange): void { - this.placement = getPlacementName(position)!; - this.cdr.markForCheck(); - } - - setClassMap(): void { - const prefixName = this.nzMenuService.isInDropDown ? 'ant-dropdown-menu-submenu' : 'ant-menu-submenu'; - this.nzUpdateHostClassService.updateHostClass(this.elementRef.nativeElement, { - [`${prefixName}`]: true, - [`${prefixName}-disabled`]: this.nzDisabled, - [`${prefixName}-open`]: this.nzOpen, - [`${prefixName}-selected`]: this.isChildMenuSelected, - [`${prefixName}-${this.nzSubmenuService.mode}`]: true, - [`${prefixName}-active`]: this.isMouseHover && !this.nzDisabled - }); - } - - constructor( - private elementRef: ElementRef, - public nzMenuService: NzMenuBaseService, - private cdr: ChangeDetectorRef, - public nzSubmenuService: NzSubmenuService, - private nzUpdateHostClassService: NzUpdateHostClassService, - private platform: Platform, - @Host() @Optional() public noAnimation?: NzNoAnimationDirective - ) {} - - ngOnInit(): void { - combineLatest([this.nzSubmenuService.mode$, this.nzSubmenuService.open$]) - .pipe(takeUntil(this.destroy$)) - .subscribe(([mode, open]) => { - if (open && mode === 'inline') { - this.expandState = 'expanded'; - } else if (open && mode === 'horizontal') { - this.expandState = 'bottom'; - } else if (open && mode === 'vertical') { - this.expandState = 'active'; - } else { - this.isMouseHover = false; - this.expandState = 'collapsed'; - } - this.overlayPositions = mode === 'horizontal' ? [POSITION_MAP.bottomLeft] : [POSITION_MAP.rightTop, POSITION_MAP.leftTop]; - if (open !== this.nzOpen) { - this.setTriggerWidth(); - this.nzOpen = open; - this.nzOpenChange.emit(this.nzOpen); - } - this.setClassMap(); - }); - this.nzSubmenuService.menuOpen$.pipe(takeUntil(this.destroy$)).subscribe((data: boolean) => { - this.nzMenuService.menuOpen$.next(data); - }); - merge( - this.nzMenuService.mode$, - this.nzMenuService.inlineIndent$, - this.nzSubmenuService.level$, - this.nzSubmenuService.open$, - this.nzSubmenuService.mode$ - ) - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - this.cdr.markForCheck(); - }); - } - - ngAfterContentInit(): void { - this.setTriggerWidth(); - this.listOfNzMenuItemDirective.changes - .pipe( - startWith(true), - flatMap(() => merge(this.listOfNzMenuItemDirective.changes, ...this.listOfNzMenuItemDirective.map(menu => menu.selected$))), - startWith(true), - map(() => this.listOfNzMenuItemDirective.some(e => e.nzSelected)), - takeUntil(this.destroy$) - ) - .subscribe(selected => { - this.isChildMenuSelected = selected; - this.setClassMap(); - }); - } - - ngOnChanges(changes: SimpleChanges): void { - if (changes.nzOpen) { - this.nzSubmenuService.setOpenState(this.nzOpen); - this.setTriggerWidth(); - } - if (changes.nzDisabled) { - this.nzSubmenuService.disabled = this.nzDisabled; - this.setClassMap(); - } - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } -} diff --git a/components/menu/nz-submenu.service.ts b/components/menu/nz-submenu.service.ts deleted file mode 100644 index 9bea0123c0c..00000000000 --- a/components/menu/nz-submenu.service.ts +++ /dev/null @@ -1,74 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { Injectable, Optional, SkipSelf } from '@angular/core'; -import { BehaviorSubject, combineLatest, Subject } from 'rxjs'; -import { auditTime, distinctUntilChanged, map, tap } from 'rxjs/operators'; - -import { NzDirectionVHIType } from 'ng-zorro-antd/core'; - -import { NzMenuService } from './nz-menu.service'; - -@Injectable() -export class NzSubmenuService { - disabled = false; - mode: NzDirectionVHIType = 'vertical'; - mode$ = this.nzMenuService.mode$.pipe( - map(mode => { - if (mode === 'inline') { - return 'inline'; - } else if (mode === 'vertical' || this.nzHostSubmenuService) { - return 'vertical'; - } else { - return 'horizontal'; - } - }), - tap(mode => (this.mode = mode as NzDirectionVHIType)) - ); - level = 1; - level$ = new BehaviorSubject(1); - subMenuOpen$ = new BehaviorSubject(false); - open$ = new BehaviorSubject(false); - mouseEnterLeave$ = new Subject(); - menuOpen$ = combineLatest(this.subMenuOpen$, this.mouseEnterLeave$).pipe( - map(value => value[0] || value[1]), - auditTime(150), - distinctUntilChanged(), - tap(data => { - this.setOpenState(data); - if (this.nzHostSubmenuService) { - this.nzHostSubmenuService.subMenuOpen$.next(data); - } - }) - ); - - setOpenState(value: boolean): void { - this.open$.next(value); - } - - onMenuItemClick(): void { - this.setMouseEnterState(false); - } - - setLevel(value: number): void { - this.level$.next(value); - this.level = value; - } - - setMouseEnterState(value: boolean): void { - if ((this.mode === 'horizontal' || this.mode === 'vertical' || this.nzMenuService.isInDropDown) && !this.disabled) { - this.mouseEnterLeave$.next(value); - } - } - - constructor(@SkipSelf() @Optional() private nzHostSubmenuService: NzSubmenuService, public nzMenuService: NzMenuService) { - if (this.nzHostSubmenuService) { - this.setLevel(this.nzHostSubmenuService.level + 1); - } - } -} diff --git a/components/menu/public-api.ts b/components/menu/public-api.ts index 755bc1a96fe..9d6d915da5e 100644 --- a/components/menu/public-api.ts +++ b/components/menu/public-api.ts @@ -6,12 +6,16 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -export * from './nz-menu.directive'; -export * from './nz-menu-group.component'; -export * from './nz-menu-divider.directive'; -export * from './nz-menu-item.directive'; -export * from './nz-submenu.component'; -export * from './nz-menu.module'; -export * from './nz-menu.service'; -export * from './nz-submenu.service'; -export * from './nz-menu.resolver'; +export * from './menu.directive'; +export * from './menu-group.component'; +export * from './menu-divider.directive'; +export * from './menu-item.directive'; +export * from './submenu.component'; +export * from './submenu-title.component'; +export * from './submenu-inline-child.component'; +export * from './submenu-non-inline-child.component'; +export * from './menu.module'; +export * from './submenu.service'; +export * from './menu.types'; +export * from './menu.service'; +export * from './menu.token'; diff --git a/components/menu/style/entry.less b/components/menu/style/entry.less index e6293155214..11e3b21ae73 100644 --- a/components/menu/style/entry.less +++ b/components/menu/style/entry.less @@ -2,3 +2,4 @@ // style dependencies // deps-lint-skip: layout @import '../../tooltip/style/index.less'; +@import './patch.less'; diff --git a/components/menu/style/patch.less b/components/menu/style/patch.less new file mode 100644 index 00000000000..a295d9d46b8 --- /dev/null +++ b/components/menu/style/patch.less @@ -0,0 +1,14 @@ +.ant-menu-submenu.ant-menu-submenu-placement-bottom { + top: 6px; + position: relative; +} + +.ant-menu-submenu.ant-menu-submenu-placement-right { + left: 4px; + position: relative; +} + +.ant-menu-submenu.ant-menu-submenu-placement-left { + right: 4px; + position: relative; +} diff --git a/components/menu/submenu-inline-child.component.ts b/components/menu/submenu-inline-child.component.ts new file mode 100644 index 00000000000..413ea013be7 --- /dev/null +++ b/components/menu/submenu-inline-child.component.ts @@ -0,0 +1,53 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit, SimpleChanges, TemplateRef, ViewEncapsulation } from '@angular/core'; +import { collapseMotion } from 'ng-zorro-antd/core'; +import { NzSafeAny } from 'ng-zorro-antd/core/types'; +import { NzMenuModeType } from './menu.types'; + +@Component({ + selector: '[nz-submenu-inline-child]', + animations: [collapseMotion], + exportAs: 'nzSubmenuInlineChild', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` + + `, + host: { + '[class.ant-menu]': 'true', + '[class.ant-menu-inline]': 'true', + '[class.ant-menu-sub]': 'true', + '[class]': 'menuClass', + '[@collapseMotion]': 'expandState' + } +}) +export class NzSubmenuInlineChildComponent implements OnInit, OnChanges { + @Input() templateOutlet: TemplateRef | null = null; + @Input() menuClass: string | null = null; + @Input() mode: NzMenuModeType = 'vertical'; + @Input() nzOpen = false; + expandState = 'collapsed'; + calcMotionState(): void { + if (this.nzOpen) { + this.expandState = 'expanded'; + } else { + this.expandState = 'collapsed'; + } + } + ngOnInit(): void { + this.calcMotionState(); + } + ngOnChanges(changes: SimpleChanges): void { + const { mode, nzOpen } = changes; + if (mode || nzOpen) { + this.calcMotionState(); + } + } +} diff --git a/components/menu/submenu-non-inline-child.component.ts b/components/menu/submenu-non-inline-child.component.ts new file mode 100644 index 00000000000..5ffbe2f4778 --- /dev/null +++ b/components/menu/submenu-non-inline-child.component.ts @@ -0,0 +1,94 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges, + TemplateRef, + ViewEncapsulation +} from '@angular/core'; +import { slideMotion, zoomBigMotion } from 'ng-zorro-antd/core'; +import { NzSafeAny } from 'ng-zorro-antd/core/types'; +import { NzMenuModeType, NzMenuThemeType } from './menu.types'; + +@Component({ + selector: '[nz-submenu-none-inline-child]', + exportAs: 'nzSubmenuNoneInlineChild', + encapsulation: ViewEncapsulation.None, + animations: [zoomBigMotion, slideMotion], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
          + +
          + `, + host: { + '[class.ant-menu-submenu]': 'true', + '[class.ant-menu-submenu-popup]': 'true', + '[class.ant-menu-light]': "theme === 'light'", + '[class.ant-menu-dark]': "theme === 'dark'", + '[class.ant-menu-submenu-placement-bottom]': "mode === 'horizontal'", + '[class.ant-menu-submenu-placement-right]': "mode === 'vertical' && position === 'right'", + '[class.ant-menu-submenu-placement-left]': "mode === 'vertical' && position === 'left'", + '[@slideMotion]': 'expandState', + '[@zoomBigMotion]': 'expandState', + '(mouseenter)': 'setMouseState(true)', + '(mouseleave)': 'setMouseState(false)' + } +}) +export class NzSubmenuNoneInlineChildComponent implements OnInit, OnChanges { + @Input() menuClass: string | null = null; + @Input() theme: NzMenuThemeType = 'light'; + @Input() templateOutlet: TemplateRef | null = null; + @Input() isMenuInsideDropDown = false; + @Input() mode: NzMenuModeType = 'vertical'; + @Input() position = 'right'; + @Input() nzDisabled = false; + @Input() nzOpen = false; + @Output() readonly subMenuMouseState = new EventEmitter(); + setMouseState(state: boolean): void { + if (!this.nzDisabled) { + this.subMenuMouseState.next(state); + } + } + expandState = 'collapsed'; + calcMotionState(): void { + if (this.nzOpen) { + if (this.mode === 'horizontal') { + this.expandState = 'bottom'; + } else if (this.mode === 'vertical') { + this.expandState = 'active'; + } + } else { + this.expandState = 'collapsed'; + } + } + ngOnInit(): void { + this.calcMotionState(); + } + ngOnChanges(changes: SimpleChanges): void { + const { mode, nzOpen } = changes; + if (mode || nzOpen) { + this.calcMotionState(); + } + } +} diff --git a/components/menu/submenu-title.component.ts b/components/menu/submenu-title.component.ts new file mode 100644 index 00000000000..631d1fcda06 --- /dev/null +++ b/components/menu/submenu-title.component.ts @@ -0,0 +1,58 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, TemplateRef, ViewEncapsulation } from '@angular/core'; +import { NzMenuModeType } from './menu.types'; + +@Component({ + selector: '[nz-submenu-title]', + exportAs: 'nzSubmenuTitle', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` + + + {{ nzTitle }} + + + + + + + + + `, + host: { + '[class.ant-dropdown-menu-submenu-title]': 'isMenuInsideDropDown', + '[class.ant-menu-submenu-title]': '!isMenuInsideDropDown', + '[style.paddingLeft.px]': 'paddingLeft', + '(click)': 'clickTitle()', + '(mouseenter)': 'setMouseState(true)', + '(mouseleave)': 'setMouseState(false)' + } +}) +export class NzSubMenuTitleComponent { + @Input() nzIcon: string | null = null; + @Input() nzTitle: string | TemplateRef | null = null; + @Input() isMenuInsideDropDown = false; + @Input() nzDisabled = false; + @Input() paddingLeft: number | null = null; + @Input() mode: NzMenuModeType = 'vertical'; + @Output() readonly toggleSubMenu = new EventEmitter(); + @Output() readonly subMenuMouseState = new EventEmitter(); + setMouseState(state: boolean): void { + if (!this.nzDisabled) { + this.subMenuMouseState.next(state); + } + } + clickTitle(): void { + if (this.mode === 'inline' && !this.nzDisabled) { + this.toggleSubMenu.emit(); + } + } +} diff --git a/components/menu/submenu.component.ts b/components/menu/submenu.component.ts new file mode 100644 index 00000000000..16c6da7589c --- /dev/null +++ b/components/menu/submenu.component.ts @@ -0,0 +1,259 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { CdkOverlayOrigin, ConnectedOverlayPositionChange } from '@angular/cdk/overlay'; +import { Platform } from '@angular/cdk/platform'; +import { + AfterContentInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ContentChildren, + ElementRef, + EventEmitter, + Host, + Inject, + Input, + OnChanges, + OnDestroy, + OnInit, + Optional, + Output, + QueryList, + SimpleChanges, + TemplateRef, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import { getPlacementName, InputBoolean, NzNoAnimationDirective, POSITION_MAP } from 'ng-zorro-antd/core'; +import { combineLatest, merge, Subject } from 'rxjs'; +import { map, startWith, switchMap, takeUntil } from 'rxjs/operators'; +import { NzMenuItemDirective } from './menu-item.directive'; +import { MenuService } from './menu.service'; +import { NzIsMenuInsideDropDownToken } from './menu.token'; +import { NzMenuModeType, NzMenuThemeType } from './menu.types'; +import { NzSubmenuService } from './submenu.service'; + +const listOfVerticalPositions = [POSITION_MAP.rightTop, POSITION_MAP.rightBottom, POSITION_MAP.leftTop, POSITION_MAP.leftBottom]; +const listOfHorizontalPositions = [POSITION_MAP.bottomLeft]; + +@Component({ + selector: '[nz-submenu]', + exportAs: 'nzSubmenu', + providers: [NzSubmenuService], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + preserveWhitespaces: false, + template: ` +
          + +
          +
          + + +
          +
          +
          + + + + + `, + host: { + '[class.ant-dropdown-menu-submenu]': `isMenuInsideDropDown`, + '[class.ant-dropdown-menu-submenu-disabled]': `isMenuInsideDropDown && nzDisabled`, + '[class.ant-dropdown-menu-submenu-open]': `isMenuInsideDropDown && nzOpen`, + '[class.ant-dropdown-menu-submenu-selected]': `isMenuInsideDropDown && isSelected`, + '[class.ant-dropdown-menu-submenu-vertical]': `isMenuInsideDropDown && mode === 'vertical'`, + '[class.ant-dropdown-menu-submenu-horizontal]': `isMenuInsideDropDown && mode === 'horizontal'`, + '[class.ant-dropdown-menu-submenu-inline]': `isMenuInsideDropDown && mode === 'inline'`, + '[class.ant-dropdown-menu-submenu-active]': `isMenuInsideDropDown && isActive`, + '[class.ant-menu-submenu]': `!isMenuInsideDropDown`, + '[class.ant-menu-submenu-disabled]': `!isMenuInsideDropDown && nzDisabled`, + '[class.ant-menu-submenu-open]': `!isMenuInsideDropDown && nzOpen`, + '[class.ant-menu-submenu-selected]': `!isMenuInsideDropDown && isSelected`, + '[class.ant-menu-submenu-vertical]': `!isMenuInsideDropDown && mode === 'vertical'`, + '[class.ant-menu-submenu-horizontal]': `!isMenuInsideDropDown && mode === 'horizontal'`, + '[class.ant-menu-submenu-inline]': `!isMenuInsideDropDown && mode === 'inline'`, + '[class.ant-menu-submenu-active]': `!isMenuInsideDropDown && isActive` + } +}) +export class NzSubMenuComponent implements OnInit, OnDestroy, AfterContentInit, OnChanges { + @Input() nzMenuClassName: string | null = null; + @Input() nzPaddingLeft: number | null = null; + @Input() nzTitle: string | TemplateRef | null = null; + @Input() nzIcon: string | null = null; + @Input() @InputBoolean() nzOpen = false; + @Input() @InputBoolean() nzDisabled = false; + @Output() readonly nzOpenChange: EventEmitter = new EventEmitter(); + @ViewChild(CdkOverlayOrigin, { static: true, read: ElementRef }) cdkOverlayOrigin: ElementRef | null = null; + @ContentChildren(NzSubMenuComponent, { descendants: true }) + listOfNzSubMenuComponent: QueryList | null = null; + @ContentChildren(NzMenuItemDirective, { descendants: true }) + listOfNzMenuItemDirective: QueryList | null = null; + private level = this.nzSubmenuService.level; + private destroy$ = new Subject(); + position = 'right'; + triggerWidth: number | null = null; + theme: NzMenuThemeType = 'light'; + mode: NzMenuModeType = 'vertical'; + inlinePaddingLeft: number | null = null; + overlayPositions = listOfVerticalPositions; + isSelected = false; + isActive = false; + + /** set the submenu host open status directly **/ + setOpenStateWithoutDebounce(open: boolean): void { + this.nzSubmenuService.setOpenStateWithoutDebounce(open); + } + + toggleSubMenu(): void { + this.setOpenStateWithoutDebounce(!this.nzOpen); + } + + setMouseEnterState(value: boolean): void { + this.isActive = value; + if (this.mode !== 'inline') { + this.nzSubmenuService.setMouseEnterTitleOrOverlayState(value); + } + } + + setTriggerWidth(): void { + if (this.mode === 'horizontal' && this.platform.isBrowser && this.cdkOverlayOrigin) { + /** TODO: fast dom **/ + this.triggerWidth = this.cdkOverlayOrigin!.nativeElement.getBoundingClientRect().width; + } + } + + onPositionChange(position: ConnectedOverlayPositionChange): void { + const placement = getPlacementName(position); + if (placement === 'rightTop' || placement === 'rightBottom') { + this.position = 'right'; + } else if (placement === 'leftTop' || placement === 'leftBottom') { + this.position = 'left'; + } + this.cdr.markForCheck(); + } + + constructor( + public nzMenuService: MenuService, + private cdr: ChangeDetectorRef, + public nzSubmenuService: NzSubmenuService, + private platform: Platform, + @Inject(NzIsMenuInsideDropDownToken) public isMenuInsideDropDown: boolean, + @Host() @Optional() public noAnimation?: NzNoAnimationDirective + ) {} + + ngOnInit(): void { + /** submenu theme update **/ + this.nzMenuService.theme$.pipe(takeUntil(this.destroy$)).subscribe(theme => { + this.theme = theme; + this.cdr.markForCheck(); + }); + /** submenu mode update **/ + this.nzSubmenuService.mode$.pipe(takeUntil(this.destroy$)).subscribe(mode => { + this.mode = mode; + if (mode === 'horizontal') { + this.overlayPositions = listOfHorizontalPositions; + } else if (mode === 'vertical') { + this.overlayPositions = listOfVerticalPositions; + } + this.cdr.markForCheck(); + }); + /** inlineIndent update **/ + combineLatest([this.nzSubmenuService.mode$, this.nzMenuService.inlineIndent$]) + .pipe(takeUntil(this.destroy$)) + .subscribe(([mode, inlineIndent]) => { + this.inlinePaddingLeft = mode === 'inline' ? this.level * inlineIndent : null; + this.cdr.markForCheck(); + }); + /** current submenu open status **/ + this.nzSubmenuService.isCurrentSubMenuOpen$.pipe(takeUntil(this.destroy$)).subscribe(open => { + this.isActive = open; + if (open !== this.nzOpen) { + this.setTriggerWidth(); + this.nzOpen = open; + this.nzOpenChange.emit(this.nzOpen); + this.cdr.markForCheck(); + } + }); + } + + ngAfterContentInit(): void { + this.setTriggerWidth(); + const listOfNzMenuItemDirective = this.listOfNzMenuItemDirective; + const changes = listOfNzMenuItemDirective!.changes; + const mergedObservable = merge(...[changes, ...listOfNzMenuItemDirective!.map(menu => menu.selected$)]); + changes + .pipe( + startWith(listOfNzMenuItemDirective), + switchMap(() => mergedObservable), + startWith(true), + map(() => listOfNzMenuItemDirective!.some(e => e.nzSelected)), + takeUntil(this.destroy$) + ) + .subscribe(selected => { + this.isSelected = selected; + this.cdr.markForCheck(); + }); + } + + ngOnChanges(changes: SimpleChanges): void { + const { nzOpen } = changes; + if (nzOpen) { + this.nzSubmenuService.setOpenStateWithoutDebounce(this.nzOpen); + this.setTriggerWidth(); + } + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/components/menu/submenu.service.ts b/components/menu/submenu.service.ts new file mode 100644 index 00000000000..c0aa218c680 --- /dev/null +++ b/components/menu/submenu.service.ts @@ -0,0 +1,82 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { Inject, Injectable, Optional, SkipSelf } from '@angular/core'; +import { NzSafeAny } from 'ng-zorro-antd/core/types'; +import { BehaviorSubject, combineLatest, merge, Observable, Subject } from 'rxjs'; +import { auditTime, distinctUntilChanged, filter, flatMap, map, mapTo } from 'rxjs/operators'; +import { MenuService } from './menu.service'; +import { NzIsMenuInsideDropDownToken } from './menu.token'; +import { NzMenuModeType } from './menu.types'; + +@Injectable() +export class NzSubmenuService { + mode$: Observable = this.nzMenuService.mode$.pipe( + map(mode => { + if (mode === 'inline') { + return 'inline'; + /** if inside another submenu, set the mode to vertical **/ + } else if (mode === 'vertical' || this.nzHostSubmenuService) { + return 'vertical'; + } else { + return 'horizontal'; + } + }) + ); + level = 1; + isCurrentSubMenuOpen$ = new BehaviorSubject(false); + private isChildSubMenuOpen$ = new BehaviorSubject(false); + /** submenu title & overlay mouse enter status **/ + private isMouseEnterTitleOrOverlay$ = new Subject(); + private childMenuItemClick$ = new Subject(); + /** + * menu item inside submenu clicked + * @param menu + */ + onChildMenuItemClick(menu: NzSafeAny): void { + this.childMenuItemClick$.next(menu); + } + setOpenStateWithoutDebounce(value: boolean): void { + this.isCurrentSubMenuOpen$.next(value); + } + setMouseEnterTitleOrOverlayState(value: boolean): void { + this.isMouseEnterTitleOrOverlay$.next(value); + } + + constructor( + @SkipSelf() @Optional() private nzHostSubmenuService: NzSubmenuService, + public nzMenuService: MenuService, + @Inject(NzIsMenuInsideDropDownToken) public isMenuInsideDropDown: boolean + ) { + if (this.nzHostSubmenuService) { + this.level = this.nzHostSubmenuService.level + 1; + } + /** close if menu item clicked **/ + const isClosedByMenuItemClick = this.childMenuItemClick$.pipe( + flatMap(() => this.mode$), + filter(mode => mode !== 'inline' || this.isMenuInsideDropDown), + mapTo(false) + ); + const isCurrentSubmenuOpen$ = merge(this.isMouseEnterTitleOrOverlay$, isClosedByMenuItemClick); + /** combine the child submenu status with current submenu status to calculate host submenu open **/ + const isSubMenuOpenWithDebounce$ = combineLatest([this.isChildSubMenuOpen$, isCurrentSubmenuOpen$]).pipe( + map(([isChildSubMenuOpen, isCurrentSubmenuOpen]) => isChildSubMenuOpen || isCurrentSubmenuOpen), + auditTime(150), + distinctUntilChanged() + ); + isSubMenuOpenWithDebounce$.pipe(distinctUntilChanged()).subscribe(data => { + this.setOpenStateWithoutDebounce(data); + if (this.nzHostSubmenuService) { + /** set parent submenu's child submenu open status **/ + this.nzHostSubmenuService.isChildSubMenuOpen$.next(data); + } else { + this.nzMenuService.isChildSubMenuOpen$.next(data); + } + }); + } +} diff --git a/components/message/nz-message.module.ts b/components/message/nz-message.module.ts index 6433331ed00..9f5787aa91e 100644 --- a/components/message/nz-message.module.ts +++ b/components/message/nz-message.module.ts @@ -9,7 +9,7 @@ import { OverlayModule } from '@angular/cdk/overlay'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule } from 'ng-zorro-antd/core'; +import { NzOutletModule } from 'ng-zorro-antd/core'; import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzMessageContainerComponent } from './nz-message-container.component'; @@ -17,7 +17,7 @@ import { NzMessageComponent } from './nz-message.component'; import { NzMessageServiceModule } from './nz-message.service.module'; @NgModule({ - imports: [CommonModule, OverlayModule, NzIconModule, NzAddOnModule, NzMessageServiceModule], + imports: [CommonModule, OverlayModule, NzIconModule, NzOutletModule, NzMessageServiceModule], declarations: [NzMessageContainerComponent, NzMessageComponent], entryComponents: [NzMessageContainerComponent] }) diff --git a/components/message/style/index.less b/components/message/style/index.less index b899d4399d5..62e2be4c704 100644 --- a/components/message/style/index.less +++ b/components/message/style/index.less @@ -55,7 +55,6 @@ } &-notice.move-up-leave.move-up-leave-active { - overflow: hidden; animation-name: MessageMoveOut; animation-duration: 0.3s; } diff --git a/components/modal/nz-modal.module.ts b/components/modal/nz-modal.module.ts index c0285b5cc1c..a8222182902 100644 --- a/components/modal/nz-modal.module.ts +++ b/components/modal/nz-modal.module.ts @@ -11,7 +11,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { NzButtonModule } from 'ng-zorro-antd/button'; -import { NzAddOnModule, NzNoAnimationModule, NzPipesModule } from 'ng-zorro-antd/core'; +import { NzNoAnimationModule, NzOutletModule, NzPipesModule } from 'ng-zorro-antd/core'; import { NzI18nModule } from 'ng-zorro-antd/i18n'; import { NzIconModule } from 'ng-zorro-antd/icon'; @@ -24,7 +24,7 @@ import { NzModalServiceModule } from './nz-modal.service.module'; imports: [ CommonModule, OverlayModule, - NzAddOnModule, + NzOutletModule, NzI18nModule, NzButtonModule, NzIconModule, diff --git a/components/notification/nz-notification.module.ts b/components/notification/nz-notification.module.ts index 26eed19cb24..832e24ebccd 100644 --- a/components/notification/nz-notification.module.ts +++ b/components/notification/nz-notification.module.ts @@ -9,7 +9,7 @@ import { OverlayModule } from '@angular/cdk/overlay'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule } from 'ng-zorro-antd/core'; +import { NzOutletModule } from 'ng-zorro-antd/core'; import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzNotificationContainerComponent } from './nz-notification-container.component'; @@ -17,7 +17,7 @@ import { NzNotificationComponent } from './nz-notification.component'; import { NzNotificationServiceModule } from './nz-notification.service.module'; @NgModule({ - imports: [CommonModule, OverlayModule, NzIconModule, NzNotificationServiceModule, NzAddOnModule], + imports: [CommonModule, OverlayModule, NzIconModule, NzNotificationServiceModule, NzOutletModule], declarations: [NzNotificationComponent, NzNotificationContainerComponent], entryComponents: [NzNotificationContainerComponent] }) diff --git a/components/notification/style/index.less b/components/notification/style/index.less index b8933732410..06c9cc3556a 100644 --- a/components/notification/style/index.less +++ b/components/notification/style/index.less @@ -33,15 +33,24 @@ cursor: pointer; } + &-hook-holder, &-notice { position: relative; margin-bottom: @notification-margin-bottom; - padding: @notification-padding; overflow: hidden; - line-height: 1.5; background: @notification-bg; border-radius: @border-radius-base; box-shadow: @shadow-2; + } + + &-hook-holder > &-notice { + margin-bottom: 0; + box-shadow: none; + } + + &-notice { + padding: @notification-padding; + line-height: @line-height-base; &-message { display: inline-block; diff --git a/components/package.json b/components/package.json index fbbbecc87f7..8076b3e5b36 100644 --- a/components/package.json +++ b/components/package.json @@ -27,16 +27,16 @@ "url": "https://github.com/NG-ZORRO/ng-zorro-antd/issues" }, "dependencies": { - "@angular/cdk": "^9.0.0-rc.7", - "@ant-design/icons-angular": "^9.0.0-rc.5", + "@angular/cdk": "^9.0.0", + "@ant-design/icons-angular": "^9.0.0", "date-fns": "^1.30.1" }, "peerDependencies": { - "@angular/animations": "~9.0.0-rc.8", - "@angular/common": "~9.0.0-rc.8", - "@angular/core": "~9.0.0-rc.8", - "@angular/platform-browser": "~9.0.0-rc.8", - "@angular/router": "~9.0.0-rc.8", + "@angular/animations": "^9.0.0", + "@angular/common": "^9.0.0", + "@angular/core": "^9.0.0", + "@angular/platform-browser": "^9.0.0", + "@angular/router": "^9.0.0", "rxjs": "~6.5.3" } } diff --git a/components/page-header/nz-page-header.module.ts b/components/page-header/nz-page-header.module.ts index 9482a279242..1d152891420 100644 --- a/components/page-header/nz-page-header.module.ts +++ b/components/page-header/nz-page-header.module.ts @@ -9,7 +9,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule } from 'ng-zorro-antd/core'; +import { NzOutletModule } from 'ng-zorro-antd/core'; import { NzIconModule } from 'ng-zorro-antd/icon'; import { @@ -36,7 +36,7 @@ const NzPageHeaderCells = [ ]; @NgModule({ - imports: [CommonModule, NzAddOnModule, NzIconModule], + imports: [CommonModule, NzOutletModule, NzIconModule], exports: [NzPageHeaderComponent, NzPageHeaderCells], declarations: [NzPageHeaderComponent, NzPageHeaderCells] }) diff --git a/components/page-header/style/index.less b/components/page-header/style/index.less index f6cbb1791c9..70029a7376a 100644 --- a/components/page-header/style/index.less +++ b/components/page-header/style/index.less @@ -151,7 +151,7 @@ } .@{ant-prefix}-tabs-tab { - padding: 8px; + padding: @tabs-horizontal-padding-sm; font-size: 16px; } } diff --git a/components/pagination/demo/item-render.ts b/components/pagination/demo/item-render.ts index 845caaf41d5..974f9a80d67 100644 --- a/components/pagination/demo/item-render.ts +++ b/components/pagination/demo/item-render.ts @@ -5,9 +5,13 @@ import { Component } from '@angular/core'; template: ` - Previous - Next - {{ page }} + + {{ page }} + Previous + Next + << + >> + ` }) diff --git a/components/pagination/doc/index.en-US.md b/components/pagination/doc/index.en-US.md index c438a1a5206..dd3c6bf545c 100755 --- a/components/pagination/doc/index.en-US.md +++ b/components/pagination/doc/index.en-US.md @@ -35,7 +35,7 @@ import { NzPaginationModule } from 'ng-zorro-antd/pagination'; | `[nzSimple]` | whether to use simple mode | `boolean` | - | | `[nzSize]` | specify the size of `nz-pagination`, can be set to `small` | `'small'` | `'default'` | | `[nzPageSizeOptions]` | specify the sizeChanger options | `number[]` | `[10, 20, 30, 40]` | -| `[nzItemRender]` | to customize item | `TemplateRef<{ $implicit: 'page' \| 'prev' \| 'next', page: number }>` | - | +| `[nzItemRender]` | to customize item | `TemplateRef<{ $implicit: 'page' \| 'prev' \| 'next'\| 'prev_5'\| 'next_5', page: number }>` | - | | `[nzShowTotal]` | to display the total number and range | `TemplateRef<{ $implicit: number, range: [ number, number ] }>` | - | | `[nzHideOnSinglePage]` | Whether to hide pager on single page | `boolean` | `false` | | `(nzPageIndexChange)` | current page number change callback | `EventEmitter` | - | diff --git a/components/pagination/doc/index.zh-CN.md b/components/pagination/doc/index.zh-CN.md index 48e3b1c2327..47d9a84ffc0 100755 --- a/components/pagination/doc/index.zh-CN.md +++ b/components/pagination/doc/index.zh-CN.md @@ -36,7 +36,7 @@ import { NzPaginationModule } from 'ng-zorro-antd/pagination'; | `[nzSimple]` | 当添加该属性时,显示为简单分页 | `boolean` | - | | `[nzSize]` | 当为「small」时,是小尺寸分页 | `'small'` | `'default'` | | `[nzPageSizeOptions]` | 指定每页可以显示多少条 | `number[]` | `[10, 20, 30, 40]` | -| `[nzItemRender]` | 用于自定义页码的结构 | `TemplateRef<{ $implicit: 'page' \| 'prev' \| 'next', page: number }>` | - | +| `[nzItemRender]` | 用于自定义页码的结构 | `TemplateRef<{ $implicit: 'page' \| 'prev' \| 'next'\| 'prev_5'\| 'next_5', page: number }>` | - | | `[nzShowTotal]` | 用于显示数据总量和当前数据范围,具体使用方式见代码演示部分 | `TemplateRef<{ $implicit: number, range: [ number, number ] }>` | - | | `[nzHideOnSinglePage]` | 只有一页时是否隐藏分页器 | `boolean` | `false` | | `(nzPageIndexChange)` | 页码改变的回调 | `EventEmitter` | - | diff --git a/components/pagination/nz-pagination.component.html b/components/pagination/nz-pagination.component.html deleted file mode 100644 index 38b4f8a4096..00000000000 --- a/components/pagination/nz-pagination.component.html +++ /dev/null @@ -1,179 +0,0 @@ - - - - - - - - {{ page }} - - - - diff --git a/components/pagination/nz-pagination.component.ts b/components/pagination/nz-pagination.component.ts deleted file mode 100644 index 1b3c0caea61..00000000000 --- a/components/pagination/nz-pagination.component.ts +++ /dev/null @@ -1,190 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - OnChanges, - OnDestroy, - OnInit, - Output, - SimpleChanges, - TemplateRef, - ViewChild, - ViewEncapsulation -} from '@angular/core'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; - -import { InputBoolean, InputNumber, isInteger, toNumber } from 'ng-zorro-antd/core'; -import { NzI18nService } from 'ng-zorro-antd/i18n'; - -export interface PaginationItemRenderContext { - $implicit: 'page' | 'prev' | 'next'; - page: number; -} - -@Component({ - selector: 'nz-pagination', - exportAs: 'nzPagination', - preserveWhitespaces: false, - encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush, - templateUrl: './nz-pagination.component.html' -}) -export class NzPaginationComponent implements OnInit, OnDestroy, OnChanges { - // tslint:disable-next-line:no-any - locale: any = {}; - firstIndex = 1; - pages: number[] = []; - private $destroy = new Subject(); - @Output() readonly nzPageSizeChange: EventEmitter = new EventEmitter(); - @Output() readonly nzPageIndexChange: EventEmitter = new EventEmitter(); - @Input() nzShowTotal: TemplateRef<{ $implicit: number; range: [number, number] }>; - @Input() nzInTable = false; - @Input() nzSize: 'default' | 'small' = 'default'; - @Input() nzPageSizeOptions = [10, 20, 30, 40]; - - @Input() nzItemRender: TemplateRef; - @ViewChild('renderItemTemplate', { static: true }) nzItemRenderChild: TemplateRef; - get itemRender(): TemplateRef { - return this.nzItemRender || this.nzItemRenderChild; - } - - @Input() @InputBoolean() nzDisabled = false; - @Input() @InputBoolean() nzShowSizeChanger = false; - @Input() @InputBoolean() nzHideOnSinglePage = false; - @Input() @InputBoolean() nzShowQuickJumper = false; - @Input() @InputBoolean() nzSimple = false; - @Input() @InputNumber() nzTotal = 0; - @Input() @InputNumber() nzPageIndex = 1; - @Input() @InputNumber() nzPageSize = 10; - - validatePageIndex(value: number): number { - if (value > this.lastIndex) { - return this.lastIndex; - } else if (value < this.firstIndex) { - return this.firstIndex; - } else { - return value; - } - } - - updatePageIndexValue(page: number): void { - this.nzPageIndex = page; - this.nzPageIndexChange.emit(this.nzPageIndex); - this.buildIndexes(); - } - - isPageIndexValid(value: number): boolean { - return this.validatePageIndex(value) === value; - } - - jumpPage(index: number): void { - if (index !== this.nzPageIndex && !this.nzDisabled) { - const pageIndex = this.validatePageIndex(index); - if (pageIndex !== this.nzPageIndex) { - this.updatePageIndexValue(pageIndex); - } - } - } - - jumpDiff(diff: number): void { - this.jumpPage(this.nzPageIndex + diff); - } - - onPageSizeChange($event: number): void { - this.nzPageSize = $event; - this.nzPageSizeChange.emit($event); - this.buildIndexes(); - if (this.nzPageIndex > this.lastIndex) { - this.updatePageIndexValue(this.lastIndex); - } - } - - handleKeyDown(_: KeyboardEvent, input: HTMLInputElement, clearInputValue: boolean): void { - const target = input; - const page = toNumber(target.value, this.nzPageIndex); - if (isInteger(page) && this.isPageIndexValid(page) && page !== this.nzPageIndex) { - this.updatePageIndexValue(page); - } - if (clearInputValue) { - target.value = ''; - } else { - target.value = `${this.nzPageIndex}`; - } - } - - /** generate indexes list */ - buildIndexes(): void { - const pages: number[] = []; - if (this.lastIndex <= 9) { - for (let i = 2; i <= this.lastIndex - 1; i++) { - pages.push(i); - } - } else { - const current = +this.nzPageIndex; - let left = Math.max(2, current - 2); - let right = Math.min(current + 2, this.lastIndex - 1); - if (current - 1 <= 2) { - right = 5; - } - if (this.lastIndex - current <= 2) { - left = this.lastIndex - 4; - } - for (let i = left; i <= right; i++) { - pages.push(i); - } - } - this.pages = pages; - this.cdr.markForCheck(); - } - - get lastIndex(): number { - return Math.ceil(this.nzTotal / this.nzPageSize); - } - - get isLastIndex(): boolean { - return this.nzPageIndex === this.lastIndex; - } - - get isFirstIndex(): boolean { - return this.nzPageIndex === this.firstIndex; - } - - get ranges(): number[] { - return [(this.nzPageIndex - 1) * this.nzPageSize + 1, Math.min(this.nzPageIndex * this.nzPageSize, this.nzTotal)]; - } - - get showAddOption(): boolean { - return this.nzPageSizeOptions.indexOf(this.nzPageSize) === -1; - } - - constructor(private i18n: NzI18nService, private cdr: ChangeDetectorRef) {} - - ngOnInit(): void { - this.i18n.localeChange.pipe(takeUntil(this.$destroy)).subscribe(() => { - this.locale = this.i18n.getLocaleData('Pagination'); - this.cdr.markForCheck(); - }); - } - - ngOnDestroy(): void { - this.$destroy.next(); - this.$destroy.complete(); - } - - ngOnChanges(changes: SimpleChanges): void { - if (changes.nzTotal || changes.nzPageSize || changes.nzPageIndex) { - this.buildIndexes(); - } - } -} diff --git a/components/pagination/pagination-default.component.ts b/components/pagination/pagination-default.component.ts new file mode 100644 index 00000000000..0099a75c692 --- /dev/null +++ b/components/pagination/pagination-default.component.ts @@ -0,0 +1,168 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + OnChanges, + Output, + SimpleChanges, + TemplateRef, + ViewEncapsulation +} from '@angular/core'; +import { NzSafeAny } from 'ng-zorro-antd/core/types'; +import { NzPaginationItemComponent } from './pagination-item.component'; +import { PaginationItemRenderContext } from './pagination.types'; + +@Component({ + selector: 'ul[nz-pagination-default]', + preserveWhitespaces: false, + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
        • + +
        • +
        • +
          + `, + host: { + '[class.ant-pagination]': 'true', + '[class.ant-pagination-disabled]': 'disabled', + '[class.mini]': `nzSize === 'small'` + } +}) +export class NzPaginationDefaultComponent implements OnChanges { + @Input() nzSize: 'default' | 'small' = 'default'; + @Input() itemRender: TemplateRef; + @Input() showTotal: TemplateRef<{ $implicit: number; range: [number, number] }> | null = null; + @Input() disabled = false; + @Input() locale: NzSafeAny = {}; + @Input() showSizeChanger = false; + @Input() showQuickJumper = false; + @Input() total = 0; + @Input() pageIndex = 1; + @Input() pageSize = 10; + @Input() pageSizeOptions = [10, 20, 30, 40]; + @Output() readonly pageIndexChange = new EventEmitter(); + @Output() readonly pageSizeChange = new EventEmitter(); + ranges = [0, 0]; + listOfPageItem: Array> = []; + + jumpPage(index: number): void { + this.onPageIndexChange(index); + } + + jumpDiff(diff: number): void { + this.jumpPage(this.pageIndex + diff); + } + + trackByPageItem(_: number, value: Partial): string { + return `${value.type}-${value.index}`; + } + + onPageIndexChange(index: number): void { + this.pageIndexChange.next(index); + } + + onPageSizeChange(size: number): void { + this.pageSizeChange.next(size); + } + + getLastIndex(total: number, pageSize: number): number { + return Math.ceil(total / pageSize); + } + + buildIndexes(): void { + const lastIndex = this.getLastIndex(this.total, this.pageSize); + this.listOfPageItem = this.getListOfPageItem(this.pageIndex, lastIndex); + } + + getListOfPageItem(pageIndex: number, lastIndex: number): Array> { + const concatWithPrevNext = (listOfPage: Array>) => { + const prevItem = { + type: 'prev', + disabled: pageIndex === 1 + }; + const nextItem = { + type: 'next', + disabled: pageIndex === lastIndex + }; + return [prevItem, ...listOfPage, nextItem]; + }; + const generatePage = (start: number, end: number): Array> => { + const list = []; + for (let i = start; i <= end; i++) { + list.push({ + index: i, + type: 'page' + }); + } + return list; + }; + if (lastIndex <= 9) { + return concatWithPrevNext(generatePage(1, lastIndex)); + } else { + const generateRangeItem = (selected: number, last: number) => { + let listOfRange = []; + const prevFiveItem = { + type: 'prev_5' + }; + const nextFiveItem = { + type: 'next_5' + }; + const firstPageItem = generatePage(1, 1); + const lastPageItem = generatePage(lastIndex, lastIndex); + if (selected < 4) { + listOfRange = [...generatePage(2, 5), nextFiveItem]; + } else if (selected < last - 3) { + listOfRange = [prevFiveItem, ...generatePage(selected - 2, selected + 2), nextFiveItem]; + } else { + listOfRange = [prevFiveItem, ...generatePage(last - 4, last - 1)]; + } + return [...firstPageItem, ...listOfRange, ...lastPageItem]; + }; + return concatWithPrevNext(generateRangeItem(pageIndex, lastIndex)); + } + } + + ngOnChanges(changes: SimpleChanges): void { + const { pageIndex, pageSize, total } = changes; + if (pageIndex || pageSize || total) { + this.ranges = [(this.pageIndex - 1) * this.pageSize + 1, Math.min(this.pageIndex * this.pageSize, this.total)]; + this.buildIndexes(); + } + } +} diff --git a/components/pagination/pagination-item.component.ts b/components/pagination/pagination-item.component.ts new file mode 100644 index 00000000000..16cf80d0079 --- /dev/null +++ b/components/pagination/pagination-item.component.ts @@ -0,0 +1,104 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + OnChanges, + Output, + SimpleChanges, + TemplateRef, + ViewEncapsulation +} from '@angular/core'; +import { NzSafeAny } from 'ng-zorro-antd/core/types'; +import { PaginationItemRenderContext, PaginationItemType } from './pagination.types'; + +@Component({ + selector: 'li[nz-pagination-item]', + preserveWhitespaces: false, + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` + + + {{ page }} + + + + +
          + + + + + ••• +
          +
          +
          +
          +
          + + `, + host: { + '[class.ant-pagination-prev]': `type === 'prev'`, + '[class.ant-pagination-next]': `type === 'next'`, + '[class.ant-pagination-item]': `type === 'page'`, + '[class.ant-pagination-jump-prev]': `type === 'prev_5'`, + '[class.ant-pagination-jump-prev-custom-icon]': `type === 'prev_5'`, + '[class.ant-pagination-jump-next]': `type === 'next_5'`, + '[class.ant-pagination-jump-next-custom-icon]': `type === 'next_5'`, + '[class.ant-pagination-disabled]': 'disabled', + '[class.ant-pagination-item-active]]': 'active', + '[attr.title]': 'title', + '(click)': 'clickItem()' + } +}) +export class NzPaginationItemComponent implements OnChanges { + @Input() active = false; + @Input() locale: NzSafeAny = {}; + @Input() index: number | null = null; + @Input() disabled = false; + @Input() type: PaginationItemType | string | null = null; + @Input() itemRender: TemplateRef | null = null; + @Output() readonly diffIndex = new EventEmitter(); + @Output() readonly gotoIndex = new EventEmitter(); + title: string | null = null; + clickItem(): void { + if (!this.disabled) { + if (this.type === 'page') { + this.gotoIndex.emit(this.index!); + } else { + this.diffIndex.emit( + ({ + next: 1, + prev: -1, + prev_5: -5, + next_5: 5 + } as NzSafeAny)[this.type!] + ); + } + } + } + ngOnChanges(changes: SimpleChanges): void { + const { locale, index, type } = changes; + if (locale || index || type) { + this.title = ({ + page: `${this.index}`, + next: this.locale.next_page, + prev: this.locale.prev_page, + prev_5: this.locale.prev_5, + next_5: this.locale.next_5 + } as NzSafeAny)[this.type!]; + } + } +} diff --git a/components/pagination/pagination-options.component.ts b/components/pagination/pagination-options.component.ts new file mode 100644 index 00000000000..7006556f400 --- /dev/null +++ b/components/pagination/pagination-options.component.ts @@ -0,0 +1,94 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + OnChanges, + Output, + SimpleChanges, + ViewEncapsulation +} from '@angular/core'; +import { toNumber } from 'ng-zorro-antd/core'; +import { NzSafeAny } from 'ng-zorro-antd/core/types'; + +@Component({ + selector: 'div[nz-pagination-options]', + preserveWhitespaces: false, + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` + + + +
          + {{ locale.jump_to }} + + {{ locale.page }} +
          + `, + host: { + '[class.ant-pagination-options]': 'true' + } +}) +export class NzPaginationOptionsComponent implements OnChanges { + @Input() nzSize: 'default' | 'small' = 'default'; + @Input() disabled = false; + @Input() showSizeChanger = false; + @Input() showQuickJumper = false; + @Input() locale: NzSafeAny = {}; + @Input() total = 0; + @Input() pageIndex = 1; + @Input() pageSize = 10; + @Input() pageSizeOptions = []; + @Output() readonly pageIndexChange = new EventEmitter(); + @Output() readonly pageSizeChange = new EventEmitter(); + listOfPageSizeOption: Array<{ value: number; label: string }> = []; + + onPageSizeChange(size: number): void { + if (this.pageSize !== size) { + this.pageSizeChange.next(size); + } + } + + jumpToPageViaInput($event: Event): void { + const target = $event.target as HTMLInputElement; + const index = toNumber(target.value, this.pageIndex); + this.pageIndexChange.next(index); + target.value = ''; + } + + trackByOption(_: number, option: { value: number; label: string }): number { + return option.value; + } + + ngOnChanges(changes: SimpleChanges): void { + const { pageSize, pageSizeOptions, locale } = changes; + if (pageSize || pageSizeOptions || locale) { + this.listOfPageSizeOption = [...new Set([...this.pageSizeOptions, this.pageSize])].map(item => { + return { + value: item, + label: `${item} ${this.locale.items_per_page}` + }; + }); + } + } +} diff --git a/components/pagination/pagination-simple.component.ts b/components/pagination/pagination-simple.component.ts new file mode 100644 index 00000000000..14856bbc176 --- /dev/null +++ b/components/pagination/pagination-simple.component.ts @@ -0,0 +1,100 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + OnChanges, + Output, + SimpleChanges, + TemplateRef, + ViewEncapsulation +} from '@angular/core'; +import { toNumber } from 'ng-zorro-antd/core'; +import { NzSafeAny } from 'ng-zorro-antd/core/types'; +import { PaginationItemRenderContext } from './pagination.types'; + +@Component({ + selector: 'ul[nz-pagination-simple]', + preserveWhitespaces: false, + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
        • +
        • + + / + {{ lastIndex }} +
        • +
        • + `, + host: { + '[class.ant-pagination]': 'true', + '[class.ant-pagination-simple]': 'true', + '[class.ant-pagination-disabled]': 'disabled' + } +}) +export class NzPaginationSimpleComponent implements OnChanges { + @Input() itemRender: TemplateRef | null = null; + @Input() disabled = false; + @Input() locale: NzSafeAny = {}; + @Input() total = 0; + @Input() pageIndex = 1; + @Input() pageSize = 10; + @Output() readonly pageIndexChange = new EventEmitter(); + lastIndex = 0; + isFirstIndex = false; + isLastIndex = false; + + jumpToPageViaInput($event: Event): void { + const target = $event.target as HTMLInputElement; + const index = toNumber(target.value, this.pageIndex); + this.onPageIndexChange(index); + target.value = `${this.pageIndex}`; + } + + prePage(): void { + this.onPageIndexChange(this.pageIndex - 1); + } + nextPage(): void { + this.onPageIndexChange(this.pageIndex + 1); + } + + onPageIndexChange(index: number): void { + this.pageIndexChange.next(index); + } + + updateBindingValue(): void { + this.lastIndex = Math.ceil(this.total / this.pageSize); + this.isFirstIndex = this.pageIndex === 1; + this.isLastIndex = this.pageIndex === this.lastIndex; + } + + ngOnChanges(changes: SimpleChanges): void { + const { pageIndex, total, pageSize } = changes; + if (pageIndex || total || pageSize) { + this.updateBindingValue(); + } + } +} diff --git a/components/pagination/pagination.component.ts b/components/pagination/pagination.component.ts new file mode 100644 index 00000000000..433b8c6f3f9 --- /dev/null +++ b/components/pagination/pagination.component.ts @@ -0,0 +1,144 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + SimpleChanges, + TemplateRef, + ViewEncapsulation +} from '@angular/core'; + +import { InputBoolean, InputNumber } from 'ng-zorro-antd/core'; +import { NzSafeAny } from 'ng-zorro-antd/core/types'; +import { NzI18nService } from 'ng-zorro-antd/i18n'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { PaginationItemRenderContext } from './pagination.types'; + +@Component({ + selector: 'nz-pagination', + exportAs: 'nzPagination', + preserveWhitespaces: false, + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` + +
            +
              +
              + ` +}) +export class NzPaginationComponent implements OnInit, OnDestroy, OnChanges { + @Output() readonly nzPageSizeChange: EventEmitter = new EventEmitter(); + @Output() readonly nzPageIndexChange: EventEmitter = new EventEmitter(); + @Input() nzShowTotal: TemplateRef<{ $implicit: number; range: [number, number] }> | null = null; + @Input() nzInTable = false; + @Input() nzSize: 'default' | 'small' = 'default'; + @Input() nzPageSizeOptions = [10, 20, 30, 40]; + @Input() nzItemRender: TemplateRef; + @Input() @InputBoolean() nzDisabled = false; + @Input() @InputBoolean() nzShowSizeChanger = false; + @Input() @InputBoolean() nzHideOnSinglePage = false; + @Input() @InputBoolean() nzShowQuickJumper = false; + @Input() @InputBoolean() nzSimple = false; + @Input() @InputNumber() nzTotal = 0; + @Input() @InputNumber() nzPageIndex = 1; + @Input() @InputNumber() nzPageSize = 10; + showPagination = true; + locale: NzSafeAny = {}; + private $destroy = new Subject(); + + validatePageIndex(value: number, lastIndex: number): number { + if (value > lastIndex) { + return lastIndex; + } else if (value < 1) { + return 1; + } else { + return value; + } + } + + onPageIndexChange(index: number): void { + const lastIndex = this.getLastIndex(this.nzTotal, this.nzPageSize); + const validIndex = this.validatePageIndex(index, lastIndex); + if (validIndex !== this.nzPageIndex && !this.nzDisabled) { + this.nzPageIndex = validIndex; + this.nzPageIndexChange.emit(this.nzPageIndex); + } + } + + onPageSizeChange(size: number): void { + this.nzPageSize = size; + this.nzPageSizeChange.emit(size); + const lastIndex = this.getLastIndex(this.nzTotal, this.nzPageSize); + if (this.nzPageIndex > lastIndex) { + this.onPageIndexChange(lastIndex); + } + } + + getLastIndex(total: number, pageSize: number): number { + return Math.ceil(total / pageSize); + } + + constructor(private i18n: NzI18nService, private cdr: ChangeDetectorRef) {} + + ngOnInit(): void { + this.i18n.localeChange.pipe(takeUntil(this.$destroy)).subscribe(() => { + this.locale = this.i18n.getLocaleData('Pagination'); + this.cdr.markForCheck(); + }); + } + + ngOnDestroy(): void { + this.$destroy.next(); + this.$destroy.complete(); + } + + ngOnChanges(changes: SimpleChanges): void { + const { nzHideOnSinglePage, nzTotal, nzPageSize } = changes; + if (nzHideOnSinglePage || nzTotal || nzPageSize) { + this.showPagination = (this.nzHideOnSinglePage && this.nzTotal > this.nzPageSize) || (this.nzTotal > 0 && !this.nzHideOnSinglePage); + } + } +} diff --git a/components/pagination/nz-pagination.module.ts b/components/pagination/pagination.module.ts similarity index 55% rename from components/pagination/nz-pagination.module.ts rename to components/pagination/pagination.module.ts index 876d5cc7490..383ff9011af 100644 --- a/components/pagination/nz-pagination.module.ts +++ b/components/pagination/pagination.module.ts @@ -13,11 +13,20 @@ import { FormsModule } from '@angular/forms'; import { NzI18nModule } from 'ng-zorro-antd/i18n'; import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzSelectModule } from 'ng-zorro-antd/select'; - -import { NzPaginationComponent } from './nz-pagination.component'; +import { NzPaginationDefaultComponent } from './pagination-default.component'; +import { NzPaginationItemComponent } from './pagination-item.component'; +import { NzPaginationOptionsComponent } from './pagination-options.component'; +import { NzPaginationSimpleComponent } from './pagination-simple.component'; +import { NzPaginationComponent } from './pagination.component'; @NgModule({ - declarations: [NzPaginationComponent], + declarations: [ + NzPaginationComponent, + NzPaginationSimpleComponent, + NzPaginationOptionsComponent, + NzPaginationItemComponent, + NzPaginationDefaultComponent + ], exports: [NzPaginationComponent], imports: [CommonModule, FormsModule, NzSelectModule, NzI18nModule, NzIconModule] }) diff --git a/components/pagination/nz-pagination.spec.ts b/components/pagination/pagination.spec.ts similarity index 93% rename from components/pagination/nz-pagination.spec.ts rename to components/pagination/pagination.spec.ts index 01fde5df100..f8b069f703f 100644 --- a/components/pagination/nz-pagination.spec.ts +++ b/components/pagination/pagination.spec.ts @@ -1,12 +1,14 @@ +import { ENTER } from '@angular/cdk/keycodes'; import { Component, DebugElement, Injector, ViewChild } from '@angular/core'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import en_US from '../i18n/languages/en_US'; -import { NzI18nService } from '../i18n/nz-i18n.service'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { NzPaginationComponent } from './nz-pagination.component'; -import { NzPaginationModule } from './nz-pagination.module'; +import { createKeyboardEvent, dispatchKeyboardEvent } from 'ng-zorro-antd/core'; +import en_US from '../i18n/languages/en_US'; +import { NzI18nService } from '../i18n/nz-i18n.service'; +import { NzPaginationComponent } from './pagination.component'; +import { NzPaginationModule } from './pagination.module'; describe('pagination', () => { let injector: Injector; @@ -170,30 +172,29 @@ describe('pagination', () => { input.value = 5; fixture.detectChanges(); expect(testComponent.pageIndexChange).toHaveBeenCalledTimes(0); - const event = new KeyboardEvent('keydown', { - code: 'ENTER' - }); - testComponent.nzPaginationComponent.handleKeyDown(event, input, true); + const event = createKeyboardEvent('keydown', ENTER, input, 'enter'); + input.dispatchEvent(event); + dispatchKeyboardEvent(input, 'keydown', ENTER, input); fixture.detectChanges(); expect(testComponent.pageIndexChange).toHaveBeenCalledTimes(1); expect(testComponent.pageIndex).toBe(5); expect(input.value).toBe(''); - testComponent.nzPaginationComponent.handleKeyDown(event, input, true); + dispatchKeyboardEvent(input, 'keydown', ENTER, input); fixture.detectChanges(); expect(testComponent.pageIndexChange).toHaveBeenCalledTimes(1); expect(testComponent.pageIndex).toBe(5); input.value = 'abc'; - testComponent.nzPaginationComponent.handleKeyDown(event, input, true); + dispatchKeyboardEvent(input, 'keydown', ENTER, input); fixture.detectChanges(); expect(testComponent.pageIndexChange).toHaveBeenCalledTimes(1); expect(testComponent.pageIndex).toBe(5); input.value = -1; - testComponent.nzPaginationComponent.handleKeyDown(event, input, true); + dispatchKeyboardEvent(input, 'keydown', ENTER, input); fixture.detectChanges(); expect(testComponent.pageIndexChange).toHaveBeenCalledTimes(1); expect(testComponent.pageIndex).toBe(5); input.value = 10; - testComponent.nzPaginationComponent.handleKeyDown(event, input, true); + dispatchKeyboardEvent(input, 'keydown', ENTER, input); fixture.detectChanges(); expect(testComponent.pageIndexChange).toHaveBeenCalledTimes(1); expect(testComponent.pageIndex).toBe(5); @@ -202,7 +203,6 @@ describe('pagination', () => { fixture.detectChanges(); testComponent.disabled = true; fixture.detectChanges(); - console.log(paginationElement.classList); expect(paginationElement.classList.contains('ant-pagination-disabled')).toBe(true); }); }); @@ -222,24 +222,22 @@ describe('pagination', () => { const input = pagination.nativeElement.querySelector('input'); input.value = 5; expect(testComponent.pageIndexChange).toHaveBeenCalledTimes(0); - const event = new KeyboardEvent('keydown', { - code: 'ENTER' - }); - testComponent.nzPaginationComponent.handleKeyDown(event, input, false); + const event = createKeyboardEvent('keydown', ENTER, input, 'enter'); + input.dispatchEvent(event); + dispatchKeyboardEvent(input, 'keydown', ENTER, input); fixture.detectChanges(); expect(testComponent.pageIndexChange).toHaveBeenCalledTimes(1); expect(input.value).toBe('5'); expect(testComponent.pageIndex).toBe(5); - testComponent.nzPaginationComponent.handleKeyDown(event, input, false); + dispatchKeyboardEvent(input, 'keydown', ENTER, input); fixture.detectChanges(); expect(testComponent.pageIndexChange).toHaveBeenCalledTimes(1); expect(input.value).toBe('5'); input.value = 100; expect(testComponent.pageIndex).toBe(5); - testComponent.nzPaginationComponent.handleKeyDown(event, input, false); + dispatchKeyboardEvent(input, 'keydown', ENTER, input); fixture.detectChanges(); expect(testComponent.pageIndexChange).toHaveBeenCalledTimes(1); - expect(input.value).toBe('5'); expect(testComponent.pageIndex).toBe(5); }); }); @@ -348,7 +346,7 @@ export class NzTestPaginationComponent { template: ` - Previous + Previous Next {{ page * 2 }} diff --git a/components/pagination/pagination.types.ts b/components/pagination/pagination.types.ts new file mode 100644 index 00000000000..f4296a13d00 --- /dev/null +++ b/components/pagination/pagination.types.ts @@ -0,0 +1,14 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 + */ + +export interface PaginationItemRenderContext { + $implicit: PaginationItemType; + page: number; +} + +export type PaginationItemType = 'page' | 'prev' | 'next' | 'prev_5' | 'next_5'; diff --git a/components/pagination/public-api.ts b/components/pagination/public-api.ts index a4a7e3acf38..c0110ab6b98 100644 --- a/components/pagination/public-api.ts +++ b/components/pagination/public-api.ts @@ -6,5 +6,10 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -export * from './nz-pagination.component'; -export * from './nz-pagination.module'; +export * from './pagination.component'; +export * from './pagination.module'; +export * from './pagination.types'; +export * from './pagination-simple.component'; +export * from './pagination-options.component'; +export * from './pagination-item.component'; +export * from './pagination-default.component'; diff --git a/components/popconfirm/nz-popconfirm.module.ts b/components/popconfirm/nz-popconfirm.module.ts index d598e8f1226..540ffb380ca 100644 --- a/components/popconfirm/nz-popconfirm.module.ts +++ b/components/popconfirm/nz-popconfirm.module.ts @@ -11,7 +11,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { NzButtonModule } from 'ng-zorro-antd/button'; -import { NzAddOnModule, NzNoAnimationModule, NzOverlayModule } from 'ng-zorro-antd/core'; +import { NzNoAnimationModule, NzOutletModule, NzOverlayModule } from 'ng-zorro-antd/core'; import { NzI18nModule } from 'ng-zorro-antd/i18n'; import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzToolTipModule } from 'ng-zorro-antd/tooltip'; @@ -28,7 +28,7 @@ import { NzPopconfirmDirective } from './nz-popconfirm.directive'; OverlayModule, NzI18nModule, NzIconModule, - NzAddOnModule, + NzOutletModule, NzOverlayModule, NzNoAnimationModule, NzToolTipModule diff --git a/components/popover/nz-popover.module.ts b/components/popover/nz-popover.module.ts index cb215943c43..a081f3e0194 100644 --- a/components/popover/nz-popover.module.ts +++ b/components/popover/nz-popover.module.ts @@ -10,7 +10,7 @@ import { OverlayModule } from '@angular/cdk/overlay'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule, NzNoAnimationModule, NzOverlayModule } from 'ng-zorro-antd/core'; +import { NzNoAnimationModule, NzOutletModule, NzOverlayModule } from 'ng-zorro-antd/core'; import { NzToolTipModule } from 'ng-zorro-antd/tooltip'; import { NzPopoverComponent } from './nz-popover.component'; @@ -20,6 +20,6 @@ import { NzPopoverDirective } from './nz-popover.directive'; entryComponents: [NzPopoverComponent], exports: [NzPopoverDirective, NzPopoverComponent], declarations: [NzPopoverDirective, NzPopoverComponent], - imports: [CommonModule, OverlayModule, NzAddOnModule, NzOverlayModule, NzNoAnimationModule, NzToolTipModule] + imports: [CommonModule, OverlayModule, NzOutletModule, NzOverlayModule, NzNoAnimationModule, NzToolTipModule] }) export class NzPopoverModule {} diff --git a/components/progress/nz-progress.module.ts b/components/progress/nz-progress.module.ts index 3cb3ca310f4..0aa17a3247b 100644 --- a/components/progress/nz-progress.module.ts +++ b/components/progress/nz-progress.module.ts @@ -8,7 +8,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule } from 'ng-zorro-antd/core'; +import { NzOutletModule } from 'ng-zorro-antd/core'; import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzProgressComponent } from './nz-progress.component'; @@ -16,6 +16,6 @@ import { NzProgressComponent } from './nz-progress.component'; @NgModule({ exports: [NzProgressComponent], declarations: [NzProgressComponent], - imports: [CommonModule, NzIconModule, NzAddOnModule] + imports: [CommonModule, NzIconModule, NzOutletModule] }) export class NzProgressModule {} diff --git a/components/radio/demo/disable.ts b/components/radio/demo/disable.ts index 6da83b97af6..55543984703 100644 --- a/components/radio/demo/disable.ts +++ b/components/radio/demo/disable.ts @@ -7,9 +7,9 @@ import { Component } from '@angular/core';
              -
              - -
              +
              +
              +
        ` }) diff --git a/components/radio/demo/radiobutton.ts b/components/radio/demo/radiobutton.ts index ef89afc8a86..d04c5511331 100644 --- a/components/radio/demo/radiobutton.ts +++ b/components/radio/demo/radiobutton.ts @@ -3,30 +3,28 @@ import { Component } from '@angular/core'; @Component({ selector: 'nz-demo-radio-radiobutton', template: ` -
        - - - - - - -
        -
        - - - - - - -
        -
        - - - - - - -
        + + + + + + +
        +
        + + + + + + +
        +
        + + + + + + ` }) export class NzDemoRadioRadiobuttonComponent { diff --git a/components/radio/demo/radiogroup-more.ts b/components/radio/demo/radiogroup-more.ts index cba0b56c035..a54f3bd1aee 100644 --- a/components/radio/demo/radiogroup-more.ts +++ b/components/radio/demo/radiogroup-more.ts @@ -9,7 +9,7 @@ import { Component } from '@angular/core'; `, @@ -18,6 +18,10 @@ import { Component } from '@angular/core'; [nz-radio] { display: block; } + input { + width: 100px; + margin-left: 10px; + } ` ] }) diff --git a/components/radio/demo/radiogroup-options.ts b/components/radio/demo/radiogroup-options.ts index 46004bbb1ea..bf66bc99028 100644 --- a/components/radio/demo/radiogroup-options.ts +++ b/components/radio/demo/radiogroup-options.ts @@ -3,17 +3,15 @@ import { Component } from '@angular/core'; @Component({ selector: 'nz-demo-radio-radiogroup-options', template: ` -
        - - - - - - - - - -
        + + + + + + + + + ` }) export class NzDemoRadioRadiogroupOptionsComponent { diff --git a/components/radio/demo/size.ts b/components/radio/demo/size.ts index d33adc6f66b..70e97aeee07 100644 --- a/components/radio/demo/size.ts +++ b/components/radio/demo/size.ts @@ -3,30 +3,28 @@ import { Component } from '@angular/core'; @Component({ selector: 'nz-demo-radio-size', template: ` -
        - - - - - - -
        -
        - - - - - - -
        -
        - - - - - - -
        + + + + + + +
        +
        + + + + + + +
        +
        + + + + + + ` }) export class NzDemoRadioSizeComponent { diff --git a/components/radio/demo/solid.ts b/components/radio/demo/solid.ts index c20e94ce2fd..5bf6a359ac4 100644 --- a/components/radio/demo/solid.ts +++ b/components/radio/demo/solid.ts @@ -3,14 +3,12 @@ import { Component } from '@angular/core'; @Component({ selector: 'nz-demo-radio-solid', template: ` -
        - - - - - - -
        + + + + + + ` }) export class NzDemoRadioSolidComponent { diff --git a/components/radio/doc/index.en-US.md b/components/radio/doc/index.en-US.md index 5672b3f92dd..62431adcbd3 100644 --- a/components/radio/doc/index.en-US.md +++ b/components/radio/doc/index.en-US.md @@ -24,7 +24,7 @@ import { NzRadioModule } from 'ng-zorro-antd/radio'; | `[nzAutoFocus]` | get focus when component mounted | `boolean` | `false` | | `[nzDisabled]` | Disable radio | `boolean` | `false` | | `[ngModel]` | Specifies whether the radio is selected, double binding | `boolean` | `false` | -| `[nzValue]` | use with `nz-radio-group` | `string` | - | +| `[nzValue]` | use with `nz-radio-group` | `any` | - | | `(ngModelChange)` | The callback function that is triggered when the state changes. | `EventEmitter` | - | ### nz-radio-group @@ -33,7 +33,7 @@ radio group,wrap a group of `nz-radio`。 | Property | Description | Type | Default | | -------- | ----------- | ---- | -------- | ------- | -| `[ngModel]` | current selected `nz-radio` value, double binding | `string` | - | +| `[ngModel]` | current selected `nz-radio` value, double binding | `any` | - | | `[nzName]` | The `name` property of all `input[type="radio"]` children | `string` | - | | `[nzDisabled]` | Disable all radio buttons | `boolean` | `false` | | `[nzSize]` | Size, only on radio style | `'large' \| 'small' \| 'default'` | `'default'` | diff --git a/components/radio/doc/index.zh-CN.md b/components/radio/doc/index.zh-CN.md index ce8a0464667..f00436742e6 100644 --- a/components/radio/doc/index.zh-CN.md +++ b/components/radio/doc/index.zh-CN.md @@ -25,7 +25,7 @@ import { NzRadioModule } from 'ng-zorro-antd/radio'; | `[nzAutoFocus]` | 自动获取焦点 | `boolean` | `false` | | `[nzDisabled]` | 设定 disable 状态 | `boolean` | `false` | | `[ngModel]` | 指定当前是否选中,可双向绑定 | `boolean` | `false` | -| `[nzValue]` | 设置 value,与 `nz-radio-group` 配合使用 | `string` | - | +| `[nzValue]` | 设置 value,与 `nz-radio-group` 配合使用 | `any` | - | | `(ngModelChange)` | 选中变化时回调 | `EventEmitter` | - | @@ -35,7 +35,7 @@ import { NzRadioModule } from 'ng-zorro-antd/radio'; | 参数 | 说明 | 类型 | 默认值 | | --- | --- | --- | --- | -| `[ngModel]` | 指定选中的 `nz-radio` 的 value 值 | `string` | - | +| `[ngModel]` | 指定选中的 `nz-radio` 的 value 值 | `any` | - | | `[nzName]` | `nz-radio-group` 下所有 `input[type="radio"]` 的 `name` 属性 | `string` | - | | `[nzDisabled]` | 设定所有 `nz-radio` disable 状态 | `boolean` | `false` | | `[nzSize]` | 大小,只对按钮样式生效 | `'large' \| 'small' \| 'default'` | `'default'` | diff --git a/components/radio/nz-radio-button.component.html b/components/radio/nz-radio-button.component.html deleted file mode 100644 index 8499dc2269a..00000000000 --- a/components/radio/nz-radio-button.component.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - diff --git a/components/radio/nz-radio-button.component.ts b/components/radio/nz-radio-button.component.ts deleted file mode 100644 index b76ba16ea7f..00000000000 --- a/components/radio/nz-radio-button.component.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, forwardRef, Renderer2, ViewEncapsulation } from '@angular/core'; - -import { FocusMonitor } from '@angular/cdk/a11y'; -import { NG_VALUE_ACCESSOR } from '@angular/forms'; -import { NzRadioComponent } from './nz-radio.component'; - -@Component({ - selector: '[nz-radio-button]', - exportAs: 'nzRadioButton', - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => NzRadioComponent), - multi: true - }, - { - provide: NzRadioComponent, - useExisting: forwardRef(() => NzRadioButtonComponent) - } - ], - encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush, - preserveWhitespaces: false, - templateUrl: './nz-radio-button.component.html', - host: { - '[class.ant-radio-button-wrapper-checked]': 'checked', - '[class.ant-radio-button-wrapper-disabled]': 'nzDisabled' - } -}) -export class NzRadioButtonComponent extends NzRadioComponent { - /* tslint:disable-next-line:no-any */ - constructor(elementRef: ElementRef, renderer: Renderer2, cdr: ChangeDetectorRef, focusMonitor: FocusMonitor) { - super(elementRef, renderer, cdr, focusMonitor); - renderer.removeClass(elementRef.nativeElement, 'ant-radio-wrapper'); - renderer.addClass(elementRef.nativeElement, 'ant-radio-button-wrapper'); - } -} diff --git a/components/radio/nz-radio-group.component.html b/components/radio/nz-radio-group.component.html deleted file mode 100644 index 6dbc7430638..00000000000 --- a/components/radio/nz-radio-group.component.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/components/radio/nz-radio-group.component.ts b/components/radio/nz-radio-group.component.ts deleted file mode 100644 index 816d97e1041..00000000000 --- a/components/radio/nz-radio-group.component.ts +++ /dev/null @@ -1,150 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { - AfterContentInit, - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ContentChildren, - ElementRef, - forwardRef, - Input, - OnChanges, - OnDestroy, - QueryList, - Renderer2, - SimpleChanges, - ViewEncapsulation -} from '@angular/core'; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { merge, Subject, Subscription } from 'rxjs'; -import { startWith, takeUntil } from 'rxjs/operators'; - -import { InputBoolean, isNotNil, NzSizeLDSType } from 'ng-zorro-antd/core'; - -import { NzRadioComponent } from './nz-radio.component'; - -export type NzRadioButtonStyle = 'outline' | 'solid'; - -@Component({ - selector: 'nz-radio-group', - exportAs: 'nzRadioGroup', - preserveWhitespaces: false, - templateUrl: './nz-radio-group.component.html', - encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => NzRadioGroupComponent), - multi: true - } - ], - host: { - '[class.ant-radio-group-large]': `nzSize === 'large'`, - '[class.ant-radio-group-small]': `nzSize === 'small'`, - '[class.ant-radio-group-solid]': `nzButtonStyle === 'solid'` - } -}) -export class NzRadioGroupComponent implements AfterContentInit, ControlValueAccessor, OnDestroy, OnChanges { - /* tslint:disable-next-line:no-any */ - private value: any; - private destroy$ = new Subject(); - private selectSubscription: Subscription; - private touchedSubscription: Subscription; - onChange: (_: string) => void = () => null; - onTouched: () => void = () => null; - @ContentChildren( - forwardRef(() => NzRadioComponent), - { descendants: true } - ) - radios: QueryList; - @Input() @InputBoolean() nzDisabled: boolean; - @Input() nzButtonStyle: NzRadioButtonStyle = 'outline'; - @Input() nzSize: NzSizeLDSType = 'default'; - @Input() nzName: string; - - updateChildrenStatus(): void { - if (this.radios) { - Promise.resolve().then(() => { - this.radios.forEach(radio => { - radio.checked = radio.nzValue === this.value; - if (isNotNil(this.nzDisabled)) { - radio.nzDisabled = this.nzDisabled; - } - if (this.nzName) { - radio.name = this.nzName; - } - radio.markForCheck(); - }); - }); - } - } - - constructor(private cdr: ChangeDetectorRef, renderer: Renderer2, elementRef: ElementRef) { - renderer.addClass(elementRef.nativeElement, 'ant-radio-group'); - } - - ngAfterContentInit(): void { - this.radios.changes.pipe(startWith(null), takeUntil(this.destroy$)).subscribe(() => { - this.updateChildrenStatus(); - if (this.selectSubscription) { - this.selectSubscription.unsubscribe(); - } - this.selectSubscription = merge(...this.radios.map(radio => radio.select$)) - .pipe(takeUntil(this.destroy$)) - .subscribe(radio => { - if (this.value !== radio.nzValue) { - this.value = radio.nzValue; - this.updateChildrenStatus(); - this.onChange(this.value); - } - }); - if (this.touchedSubscription) { - this.touchedSubscription.unsubscribe(); - } - this.touchedSubscription = merge(...this.radios.map(radio => radio.touched$)) - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - Promise.resolve().then(() => this.onTouched()); - }); - }); - } - - ngOnChanges(changes: SimpleChanges): void { - if (changes.nzDisabled || changes.nzName) { - this.updateChildrenStatus(); - } - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - /* tslint:disable-next-line:no-any */ - writeValue(value: any): void { - this.value = value; - this.updateChildrenStatus(); - this.cdr.markForCheck(); - } - - registerOnChange(fn: (_: string) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } - - setDisabledState(isDisabled: boolean): void { - this.nzDisabled = isDisabled; - this.cdr.markForCheck(); - } -} diff --git a/components/radio/nz-radio.component.html b/components/radio/nz-radio.component.html deleted file mode 100644 index e7543343281..00000000000 --- a/components/radio/nz-radio.component.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/components/radio/nz-radio.component.ts b/components/radio/nz-radio.component.ts deleted file mode 100644 index cfe2e9cdf34..00000000000 --- a/components/radio/nz-radio.component.ts +++ /dev/null @@ -1,148 +0,0 @@ -/** - * @license - * Copyright Alibaba.com All Rights Reserved. - * - * 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 { FocusMonitor } from '@angular/cdk/a11y'; -import { - AfterViewInit, - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - forwardRef, - HostListener, - Input, - OnChanges, - OnDestroy, - Renderer2, - SimpleChanges, - ViewChild, - ViewEncapsulation -} from '@angular/core'; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { Subject } from 'rxjs'; - -import { InputBoolean } from 'ng-zorro-antd/core'; - -@Component({ - selector: '[nz-radio]', - exportAs: 'nzRadio', - preserveWhitespaces: false, - templateUrl: './nz-radio.component.html', - encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => NzRadioComponent), - multi: true - } - ], - host: { - '[class.ant-radio-wrapper-checked]': 'checked', - '[class.ant-radio-wrapper-disabled]': 'nzDisabled' - } -}) -export class NzRadioComponent implements ControlValueAccessor, AfterViewInit, OnChanges, OnDestroy { - select$ = new Subject(); - touched$ = new Subject(); - checked = false; - name: string; - isNgModel = false; - onChange: (_: boolean) => void = () => null; - onTouched: () => void = () => null; - @ViewChild('inputElement', { static: false }) inputElement: ElementRef; - /* tslint:disable-next-line:no-any */ - @Input() nzValue: any; - @Input() @InputBoolean() nzDisabled = false; - @Input() @InputBoolean() nzAutoFocus = false; - - updateAutoFocus(): void { - if (this.inputElement) { - if (this.nzAutoFocus) { - this.renderer.setAttribute(this.inputElement.nativeElement, 'autofocus', 'autofocus'); - } else { - this.renderer.removeAttribute(this.inputElement.nativeElement, 'autofocus'); - } - } - } - - @HostListener('click', ['$event']) - onClick(event: MouseEvent): void { - // Prevent label click triggered twice. - event.stopPropagation(); - event.preventDefault(); - if (!this.nzDisabled && !this.checked) { - this.select$.next(this); - if (this.isNgModel) { - this.checked = true; - this.onChange(true); - } - } - } - - focus(): void { - this.focusMonitor.focusVia(this.inputElement, 'keyboard'); - } - - blur(): void { - this.inputElement.nativeElement.blur(); - } - - markForCheck(): void { - this.cdr.markForCheck(); - } - - /* tslint:disable-next-line:no-any */ - constructor( - private elementRef: ElementRef, - private renderer: Renderer2, - private cdr: ChangeDetectorRef, - private focusMonitor: FocusMonitor - ) { - this.renderer.addClass(elementRef.nativeElement, 'ant-radio-wrapper'); - } - - setDisabledState(isDisabled: boolean): void { - this.nzDisabled = isDisabled; - this.cdr.markForCheck(); - } - - writeValue(value: boolean): void { - this.checked = value; - this.cdr.markForCheck(); - } - - registerOnChange(fn: (_: boolean) => {}): void { - this.isNgModel = true; - this.onChange = fn; - } - - registerOnTouched(fn: () => {}): void { - this.onTouched = fn; - } - - ngAfterViewInit(): void { - this.focusMonitor.monitor(this.elementRef, true).subscribe(focusOrigin => { - if (!focusOrigin) { - Promise.resolve().then(() => this.onTouched()); - this.touched$.next(); - } - }); - this.updateAutoFocus(); - } - - ngOnChanges(changes: SimpleChanges): void { - if (changes.nzAutoFocus) { - this.updateAutoFocus(); - } - } - - ngOnDestroy(): void { - this.focusMonitor.stopMonitoring(this.elementRef); - } -} diff --git a/components/radio/public-api.ts b/components/radio/public-api.ts index 02f330ab358..5db84f74d2a 100644 --- a/components/radio/public-api.ts +++ b/components/radio/public-api.ts @@ -6,7 +6,8 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -export * from './nz-radio-button.component'; -export * from './nz-radio-group.component'; -export * from './nz-radio.component'; -export * from './nz-radio.module'; +export * from './radio-button.directive'; +export * from './radio-group.component'; +export * from './radio.component'; +export * from './radio.service'; +export * from './radio.module'; diff --git a/components/core/dropdown/public-api.ts b/components/radio/radio-button.directive.ts similarity index 64% rename from components/core/dropdown/public-api.ts rename to components/radio/radio-button.directive.ts index 47dd339dc60..f2ecebea403 100644 --- a/components/core/dropdown/public-api.ts +++ b/components/radio/radio-button.directive.ts @@ -6,5 +6,9 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -export * from './nz-menu-base.service'; -export * from './nz-dropdown-service.resolver'; +import { Directive } from '@angular/core'; + +@Directive({ + selector: '[nz-radio-button]' +}) +export class NzRadioButtonDirective {} diff --git a/components/radio/radio-group.component.ts b/components/radio/radio-group.component.ts new file mode 100644 index 00000000000..71ddb7e75ef --- /dev/null +++ b/components/radio/radio-group.component.ts @@ -0,0 +1,111 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + forwardRef, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, + ViewEncapsulation +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { InputBoolean, OnChangeType, OnTouchedType } from 'ng-zorro-antd/core'; +import { NzSafeAny, NzSizeLDSType } from 'ng-zorro-antd/core/types'; +import { Subject } from 'rxjs'; +import { NzRadioService } from './radio.service'; + +export type NzRadioButtonStyle = 'outline' | 'solid'; + +@Component({ + selector: 'nz-radio-group', + exportAs: 'nzRadioGroup', + preserveWhitespaces: false, + template: ` + + `, + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + NzRadioService, + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => NzRadioGroupComponent), + multi: true + } + ], + host: { + '[class.ant-radio-group]': `true`, + '[class.ant-radio-group-large]': `nzSize === 'large'`, + '[class.ant-radio-group-small]': `nzSize === 'small'`, + '[class.ant-radio-group-solid]': `nzButtonStyle === 'solid'` + } +}) +export class NzRadioGroupComponent implements OnInit, ControlValueAccessor, OnDestroy, OnChanges { + private value: NzSafeAny | null = null; + private destroy$ = new Subject(); + onChange: OnChangeType = () => {}; + onTouched: OnTouchedType = () => {}; + @Input() @InputBoolean() nzDisabled = false; + @Input() nzButtonStyle: NzRadioButtonStyle = 'outline'; + @Input() nzSize: NzSizeLDSType = 'default'; + @Input() nzName: string | null = null; + + constructor(private cdr: ChangeDetectorRef, private nzRadioService: NzRadioService) {} + + ngOnInit(): void { + this.nzRadioService.selected$.subscribe(value => { + if (this.value !== value) { + this.value = value; + this.onChange(this.value); + } + }); + this.nzRadioService.touched$.subscribe(() => { + Promise.resolve().then(() => this.onTouched()); + }); + } + + ngOnChanges(changes: SimpleChanges): void { + const { nzDisabled, nzName } = changes; + if (nzDisabled) { + this.nzRadioService.setDisabled(this.nzDisabled); + } + if (nzName) { + this.nzRadioService.setName(this.nzName!); + } + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + writeValue(value: NzSafeAny): void { + this.value = value; + this.nzRadioService.select(value); + this.cdr.markForCheck(); + } + + registerOnChange(fn: OnChangeType): void { + this.onChange = fn; + } + + registerOnTouched(fn: OnTouchedType): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.nzDisabled = isDisabled; + this.nzRadioService.setDisabled(isDisabled); + this.cdr.markForCheck(); + } +} diff --git a/components/radio/radio.component.ts b/components/radio/radio.component.ts new file mode 100644 index 00000000000..ca6302ca42d --- /dev/null +++ b/components/radio/radio.component.ts @@ -0,0 +1,179 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { FocusMonitor } from '@angular/cdk/a11y'; +import { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + forwardRef, + Input, + OnDestroy, + OnInit, + Optional, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; + +import { InputBoolean } from 'ng-zorro-antd/core'; +import { NzSafeAny, OnChangeType, OnTouchedType } from 'ng-zorro-antd/core/types'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { NzRadioButtonDirective } from './radio-button.directive'; +import { NzRadioService } from './radio.service'; + +@Component({ + selector: '[nz-radio],[nz-radio-button]', + exportAs: 'nzRadio', + preserveWhitespaces: false, + template: ` + + + + + + `, + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => NzRadioComponent), + multi: true + } + ], + host: { + '[class.ant-radio-wrapper]': '!isRadioButton', + '[class.ant-radio-button-wrapper]': 'isRadioButton', + '[class.ant-radio-wrapper-checked]': 'isChecked && !isRadioButton', + '[class.ant-radio-button-wrapper-checked]': 'isChecked && isRadioButton', + '[class.ant-radio-wrapper-disabled]': 'nzDisabled && !isRadioButton', + '[class.ant-radio-button-wrapper-disabled]': 'nzDisabled && isRadioButton', + '(click)': 'onHostClick($event)' + } +}) +export class NzRadioComponent implements ControlValueAccessor, AfterViewInit, OnDestroy, OnInit { + private isNgModel = false; + private destroy$ = new Subject(); + isChecked = false; + name: string | null = null; + isRadioButton = !!this.nzRadioButtonDirective; + onChange: OnChangeType = () => {}; + onTouched: OnTouchedType = () => {}; + @ViewChild('inputElement', { static: false }) inputElement: ElementRef; + @Input() nzValue: NzSafeAny | null = null; + @Input() @InputBoolean() nzDisabled = false; + @Input() @InputBoolean() nzAutoFocus = false; + + onHostClick(event: MouseEvent): void { + /** prevent label click triggered twice. **/ + event.stopPropagation(); + event.preventDefault(); + this.focus(); + if (!this.nzDisabled && !this.isChecked) { + if (this.nzRadioService) { + this.nzRadioService.select(this.nzValue); + } + if (this.isNgModel) { + this.isChecked = true; + this.onChange(true); + } + } + } + + focus(): void { + this.focusMonitor.focusVia(this.inputElement, 'keyboard'); + } + + blur(): void { + this.inputElement.nativeElement.blur(); + } + + constructor( + private elementRef: ElementRef, + private cdr: ChangeDetectorRef, + private focusMonitor: FocusMonitor, + @Optional() private nzRadioService: NzRadioService, + @Optional() private nzRadioButtonDirective: NzRadioButtonDirective + ) {} + + setDisabledState(disabled: boolean): void { + this.nzDisabled = disabled; + this.cdr.markForCheck(); + } + + writeValue(value: boolean): void { + this.isChecked = value; + this.cdr.markForCheck(); + } + + registerOnChange(fn: OnChangeType): void { + this.isNgModel = true; + this.onChange = fn; + } + + registerOnTouched(fn: OnTouchedType): void { + this.onTouched = fn; + } + + ngOnInit(): void { + if (this.nzRadioService) { + this.nzRadioService.name$.pipe(takeUntil(this.destroy$)).subscribe(name => { + this.name = name; + this.cdr.markForCheck(); + }); + this.nzRadioService.disabled$.pipe(takeUntil(this.destroy$)).subscribe(disabled => { + this.nzDisabled = disabled; + this.cdr.markForCheck(); + }); + this.nzRadioService.selected$.pipe(takeUntil(this.destroy$)).subscribe(value => { + this.isChecked = this.nzValue === value; + this.cdr.markForCheck(); + }); + } + this.focusMonitor.monitor(this.elementRef, true).subscribe(focusOrigin => { + if (!focusOrigin) { + Promise.resolve().then(() => this.onTouched()); + if (this.nzRadioService) { + this.nzRadioService.touch(); + } + } + }); + } + + ngAfterViewInit(): void { + if (this.nzAutoFocus) { + this.focus(); + } + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + this.focusMonitor.stopMonitoring(this.elementRef); + } +} diff --git a/components/radio/nz-radio.module.ts b/components/radio/radio.module.ts similarity index 60% rename from components/radio/nz-radio.module.ts rename to components/radio/radio.module.ts index 0eae375da12..edcc090a52d 100644 --- a/components/radio/nz-radio.module.ts +++ b/components/radio/radio.module.ts @@ -10,13 +10,13 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { NzRadioButtonComponent } from './nz-radio-button.component'; -import { NzRadioGroupComponent } from './nz-radio-group.component'; -import { NzRadioComponent } from './nz-radio.component'; +import { NzRadioButtonDirective } from './radio-button.directive'; +import { NzRadioGroupComponent } from './radio-group.component'; +import { NzRadioComponent } from './radio.component'; @NgModule({ imports: [CommonModule, FormsModule], - exports: [NzRadioComponent, NzRadioButtonComponent, NzRadioGroupComponent], - declarations: [NzRadioComponent, NzRadioButtonComponent, NzRadioGroupComponent] + exports: [NzRadioComponent, NzRadioButtonDirective, NzRadioGroupComponent], + declarations: [NzRadioComponent, NzRadioButtonDirective, NzRadioGroupComponent] }) export class NzRadioModule {} diff --git a/components/radio/radio.service.ts b/components/radio/radio.service.ts new file mode 100644 index 00000000000..f9746a54e46 --- /dev/null +++ b/components/radio/radio.service.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright Alibaba.com All Rights Reserved. + * + * 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 { Injectable } from '@angular/core'; +import { NzSafeAny } from 'ng-zorro-antd/core/types'; +import { ReplaySubject, Subject } from 'rxjs'; + +@Injectable() +export class NzRadioService { + selected$ = new ReplaySubject(1); + touched$ = new Subject(); + disabled$ = new ReplaySubject(1); + name$ = new ReplaySubject(1); + touch(): void { + this.touched$.next(); + } + select(value: NzSafeAny): void { + this.selected$.next(value); + } + setDisabled(value: boolean): void { + this.disabled$.next(value); + } + setName(value: string): void { + this.name$.next(value); + } +} diff --git a/components/radio/nz-radio.spec.ts b/components/radio/radio.spec.ts similarity index 97% rename from components/radio/nz-radio.spec.ts rename to components/radio/radio.spec.ts index 0211df5e036..08053724c19 100644 --- a/components/radio/nz-radio.spec.ts +++ b/components/radio/radio.spec.ts @@ -3,10 +3,9 @@ import { ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angular/core import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; -import { NzRadioButtonComponent } from './nz-radio-button.component'; -import { NzRadioGroupComponent } from './nz-radio-group.component'; -import { NzRadioComponent } from './nz-radio.component'; -import { NzRadioModule } from './nz-radio.module'; +import { NzRadioGroupComponent } from './radio-group.component'; +import { NzRadioComponent } from './radio.component'; +import { NzRadioModule } from './radio.module'; describe('radio', () => { beforeEach(fakeAsync(() => { @@ -100,7 +99,7 @@ describe('radio', () => { beforeEach(() => { fixture = TestBed.createComponent(NzTestRadioButtonComponent); fixture.detectChanges(); - radio = fixture.debugElement.query(By.directive(NzRadioButtonComponent)); + radio = fixture.debugElement.query(By.directive(NzRadioComponent)); }); it('should className correct', () => { fixture.detectChanges(); @@ -120,7 +119,7 @@ describe('radio', () => { fixture = TestBed.createComponent(NzTestRadioGroupComponent); fixture.detectChanges(); testComponent = fixture.debugElement.componentInstance; - radios = fixture.debugElement.queryAll(By.directive(NzRadioButtonComponent)); + radios = fixture.debugElement.queryAll(By.directive(NzRadioComponent)); radioGroup = fixture.debugElement.query(By.directive(NzRadioGroupComponent)); }); it('should className correct', () => { @@ -177,7 +176,7 @@ describe('radio', () => { fixture = TestBed.createComponent(NzTestRadioGroupDisabledComponent); fixture.detectChanges(); testComponent = fixture.debugElement.componentInstance; - radios = fixture.debugElement.queryAll(By.directive(NzRadioButtonComponent)); + radios = fixture.debugElement.queryAll(By.directive(NzRadioComponent)); }); it('should group disable work', fakeAsync(() => { testComponent.disabled = true; @@ -269,7 +268,7 @@ describe('radio', () => { flush(); fixture.detectChanges(); testComponent = fixture.debugElement.componentInstance; - radios = fixture.debugElement.queryAll(By.directive(NzRadioButtonComponent)); + radios = fixture.debugElement.queryAll(By.directive(NzRadioComponent)); })); it('should be in pristine, untouched, and valid states initially', fakeAsync(() => { flush(); diff --git a/components/radio/style/entry.less b/components/radio/style/entry.less index 06547c43acd..96cebe33bff 100644 --- a/components/radio/style/entry.less +++ b/components/radio/style/entry.less @@ -1 +1,2 @@ @import './index.less'; +@import './patch.less'; diff --git a/components/radio/style/index.less b/components/radio/style/index.less index b966eeb026d..152cea85087 100644 --- a/components/radio/style/index.less +++ b/components/radio/style/index.less @@ -16,6 +16,14 @@ &-rtl { direction: rtl; } + + .@{ant-prefix}-badge-count { + z-index: 1; + } + + > .@{ant-prefix}-badge:not(:first-child) > .@{radio-prefix-cls}-button-wrapper { + border-left: none; + } } // 一般状态 @@ -209,6 +217,7 @@ span.@{radio-prefix-cls} + * { content: ''; } } + &:first-child { border-left: @border-width-base @border-style-base @border-color-base; border-radius: @border-radius-base 0 0 @border-radius-base; diff --git a/components/radio/style/patch.less b/components/radio/style/patch.less new file mode 100644 index 00000000000..230ff736bb6 --- /dev/null +++ b/components/radio/style/patch.less @@ -0,0 +1,5 @@ +.ant-radio + span { + &:empty { + display: none; + } +} diff --git a/components/result/nz-result.module.ts b/components/result/nz-result.module.ts index 5631ca84f3f..4ac3754b77a 100644 --- a/components/result/nz-result.module.ts +++ b/components/result/nz-result.module.ts @@ -9,13 +9,9 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule } from 'ng-zorro-antd/core'; +import { NzOutletModule } from 'ng-zorro-antd/core'; import { NzIconModule } from 'ng-zorro-antd/icon'; -import { NzResultNotFoundComponent } from './partial/not-found'; -import { NzResultServerErrorComponent } from './partial/server-error.component'; -import { NzResultUnauthorizedComponent } from './partial/unauthorized'; - import { NzResultContentDirective, NzResultExtraDirective, @@ -25,6 +21,10 @@ import { } from './nz-result-cells'; import { NzResultComponent } from './nz-result.component'; +import { NzResultNotFoundComponent } from './partial/not-found'; +import { NzResultServerErrorComponent } from './partial/server-error.component'; +import { NzResultUnauthorizedComponent } from './partial/unauthorized'; + const partial = [NzResultNotFoundComponent, NzResultServerErrorComponent, NzResultUnauthorizedComponent]; const cellDirectives = [ @@ -36,7 +36,7 @@ const cellDirectives = [ ]; @NgModule({ - imports: [CommonModule, NzAddOnModule, NzIconModule], + imports: [CommonModule, NzOutletModule, NzIconModule], declarations: [NzResultComponent, ...cellDirectives, ...partial], exports: [NzResultComponent, ...cellDirectives] }) diff --git a/components/select/nz-select.component.html b/components/select/nz-select.component.html index 51648c767ec..964431a6e7c 100644 --- a/components/select/nz-select.component.html +++ b/components/select/nz-select.component.html @@ -37,12 +37,11 @@ [cdkConnectedOverlayOpen]="open" >
        + - ` + `, + styles: [ + ` + i { + font-size: 24px; + } + ` + ] }) export class NzDemoSpinCustomIndicatorComponent {} diff --git a/components/spin/demo/delay-and-debounce.ts b/components/spin/demo/delay-and-debounce.ts index fb016b929cc..e29ebc632a1 100644 --- a/components/spin/demo/delay-and-debounce.ts +++ b/components/spin/demo/delay-and-debounce.ts @@ -7,7 +7,8 @@ import { Component } from '@angular/core'; -
        +
        +
        Loading state:
        diff --git a/components/spin/demo/nested.ts b/components/spin/demo/nested.ts index f9f90a5ad1b..365a46e4a06 100644 --- a/components/spin/demo/nested.ts +++ b/components/spin/demo/nested.ts @@ -7,7 +7,8 @@ import { Component } from '@angular/core'; -
        +
        +
        Loading state:
        diff --git a/components/spin/nz-spin.component.html b/components/spin/nz-spin.component.html deleted file mode 100644 index 91431bc5199..00000000000 --- a/components/spin/nz-spin.component.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - -
        -
        - -
        {{ nzTip }}
        -
        -
        -
        - -
        diff --git a/components/spin/public-api.ts b/components/spin/public-api.ts index 32e456e07b3..20a5f3233d6 100644 --- a/components/spin/public-api.ts +++ b/components/spin/public-api.ts @@ -6,5 +6,5 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -export * from './nz-spin.component'; -export * from './nz-spin.module'; +export * from './spin.component'; +export * from './spin.module'; diff --git a/components/spin/nz-spin.component.ts b/components/spin/spin.component.ts similarity index 51% rename from components/spin/nz-spin.component.ts rename to components/spin/spin.component.ts index b010e4d9610..f3305e8a63d 100644 --- a/components/spin/nz-spin.component.ts +++ b/components/spin/spin.component.ts @@ -7,7 +7,6 @@ */ import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, @@ -18,10 +17,10 @@ import { TemplateRef, ViewEncapsulation } from '@angular/core'; -import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs'; -import { debounceTime, takeUntil } from 'rxjs/operators'; import { InputBoolean, InputNumber, NzConfigService, NzSizeLDSType, WithConfig } from 'ng-zorro-antd/core'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { debounceTime, flatMap, takeUntil } from 'rxjs/operators'; const NZ_CONFIG_COMPONENT_NAME = 'spin'; @@ -30,52 +29,65 @@ const NZ_CONFIG_COMPONENT_NAME = 'spin'; exportAs: 'nzSpin', preserveWhitespaces: false, encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush, - templateUrl: './nz-spin.component.html', + template: ` + + + + + + + + +
        +
        + +
        {{ nzTip }}
        +
        +
        +
        + +
        + `, host: { '[class.ant-spin-nested-loading]': '!nzSimple' - }, - styles: [ - ` - nz-spin { - display: block; - } - ` - ] + } }) export class NzSpinComponent implements OnChanges, OnDestroy, OnInit { @Input() @WithConfig(NZ_CONFIG_COMPONENT_NAME) nzIndicator: TemplateRef; @Input() nzSize: NzSizeLDSType = 'default'; - @Input() nzTip: string; + @Input() nzTip: string | null = null; @Input() @InputNumber() nzDelay = 0; @Input() @InputBoolean() nzSimple = false; @Input() @InputBoolean() nzSpinning = true; - loading = true; private destroy$ = new Subject(); private spinning$ = new BehaviorSubject(this.nzSpinning); - private loading$: Observable = this.spinning$.pipe(debounceTime(this.nzDelay)); - private loading_: Subscription | null; - - subscribeLoading(): void { - this.unsubscribeLoading(); - this.loading_ = this.loading$.subscribe(data => { - this.loading = data; - this.cdr.markForCheck(); - }); - } - - unsubscribeLoading(): void { - if (this.loading_) { - this.loading_.unsubscribe(); - this.loading_ = null; - } - } + private delay$ = new BehaviorSubject(this.nzDelay); + isLoading = true; constructor(public nzConfigService: NzConfigService, private cdr: ChangeDetectorRef) {} ngOnInit(): void { - this.subscribeLoading(); - + const loading$ = this.spinning$.pipe( + flatMap(() => this.delay$), + flatMap(delay => { + if (delay === 0) { + return this.spinning$; + } else { + return this.spinning$.pipe(debounceTime(delay)); + } + }), + takeUntil(this.destroy$) + ); + loading$.subscribe(loading => { + this.isLoading = loading; + this.cdr.markForCheck(); + }); this.nzConfigService .getConfigChangeEventForComponent(NZ_CONFIG_COMPONENT_NAME) .pipe(takeUntil(this.destroy$)) @@ -83,21 +95,17 @@ export class NzSpinComponent implements OnChanges, OnDestroy, OnInit { } ngOnChanges(changes: SimpleChanges): void { - if (changes.nzSpinning) { - if (changes.nzSpinning.isFirstChange()) { - this.loading = this.nzSpinning; - } + const { nzSpinning, nzDelay } = changes; + if (nzSpinning) { this.spinning$.next(this.nzSpinning); } - if (changes.nzDelay) { - this.loading$ = this.spinning$.pipe(debounceTime(this.nzDelay)); - this.subscribeLoading(); + if (nzDelay) { + this.delay$.next(this.nzDelay); } } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); - this.unsubscribeLoading(); } } diff --git a/components/spin/nz-spin.module.ts b/components/spin/spin.module.ts similarity index 90% rename from components/spin/nz-spin.module.ts rename to components/spin/spin.module.ts index 25e72235431..ab4ba30507c 100644 --- a/components/spin/nz-spin.module.ts +++ b/components/spin/spin.module.ts @@ -10,7 +10,7 @@ import { ObserversModule } from '@angular/cdk/observers'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzSpinComponent } from './nz-spin.component'; +import { NzSpinComponent } from './spin.component'; @NgModule({ exports: [NzSpinComponent], diff --git a/components/spin/nz-spin.spec.ts b/components/spin/spin.spec.ts similarity index 97% rename from components/spin/nz-spin.spec.ts rename to components/spin/spin.spec.ts index ac306bbfabd..6c74d2d9ebd 100644 --- a/components/spin/nz-spin.spec.ts +++ b/components/spin/spin.spec.ts @@ -1,12 +1,10 @@ import { Component, DebugElement, TemplateRef, ViewChild } from '@angular/core'; import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; - -import { NzIconTestModule } from 'ng-zorro-antd/icon/testing'; - import { NzConfigService } from 'ng-zorro-antd/core'; -import { NzSpinComponent } from './nz-spin.component'; -import { NzSpinModule } from './nz-spin.module'; +import { NzIconTestModule } from 'ng-zorro-antd/icon/testing'; +import { NzSpinComponent } from './spin.component'; +import { NzSpinModule } from './spin.module'; describe('spin', () => { beforeEach(fakeAsync(() => { diff --git a/components/spin/style/entry.less b/components/spin/style/entry.less index 06547c43acd..96cebe33bff 100644 --- a/components/spin/style/entry.less +++ b/components/spin/style/entry.less @@ -1 +1,2 @@ @import './index.less'; +@import './patch.less'; diff --git a/components/spin/style/patch.less b/components/spin/style/patch.less new file mode 100644 index 00000000000..dc84833ebcd --- /dev/null +++ b/components/spin/style/patch.less @@ -0,0 +1,3 @@ +nz-spin { + display: block; +} diff --git a/components/statistic/nz-countdown.component.ts b/components/statistic/countdown.component.ts similarity index 83% rename from components/statistic/nz-countdown.component.ts rename to components/statistic/countdown.component.ts index 14a437b86e4..a31b6c703cf 100644 --- a/components/statistic/nz-countdown.component.ts +++ b/components/statistic/countdown.component.ts @@ -23,18 +23,30 @@ import { } from '@angular/core'; import { interval, Subscription } from 'rxjs'; -import { REFRESH_INTERVAL } from './nz-statistic-definitions'; -import { NzStatisticComponent } from './nz-statistic.component'; +import { NzStatisticComponent } from './statistic.component'; + +const REFRESH_INTERVAL = 1000 / 30; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, selector: 'nz-countdown', exportAs: 'nzCountdown', - templateUrl: './nz-countdown.component.html' + template: ` + + + + {{ diff | nzTimeRange: nzFormat }} + ` }) export class NzCountdownComponent extends NzStatisticComponent implements OnInit, OnChanges, OnDestroy { - /** @override */ @Input() nzFormat: string = 'HH:mm:ss'; @Output() readonly nzCountdownFinish = new EventEmitter(); @@ -47,7 +59,6 @@ export class NzCountdownComponent extends NzStatisticComponent implements OnInit super(); } - /** @override */ ngOnChanges(changes: SimpleChanges): void { if (changes.nzValue) { this.target = Number(changes.nzValue.currentValue); diff --git a/components/statistic/nz-countdown.spec.ts b/components/statistic/countdown.spec.ts similarity index 83% rename from components/statistic/nz-countdown.spec.ts rename to components/statistic/countdown.spec.ts index 788446d1c52..aa4b3d11174 100644 --- a/components/statistic/nz-countdown.spec.ts +++ b/components/statistic/countdown.spec.ts @@ -1,65 +1,25 @@ -import { CommonModule } from '@angular/common'; import { Component, DebugElement, TemplateRef, ViewChild } from '@angular/core'; -import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { NzCountdownComponent } from './nz-countdown.component'; -import { NzStatisticModule } from './nz-statistic.module'; -@Component({ - template: ` - - - - {{ diff }} - - ` -}) -export class NzTestCountdownComponent { - @ViewChild(NzCountdownComponent, { static: true }) countdown: NzCountdownComponent; - @ViewChild('tpl', { static: true }) tpl: TemplateRef; +import { ɵComponentBed as ComponentBed, ɵcreateComponentBed as createComponentBed } from 'ng-zorro-antd/core'; - format: string; - value: number; - template: TemplateRef; - finished = 0; - - resetTimerWithFormat(format: string): void { - this.format = format; - this.value = new Date().getTime() + 1000 * 60 * 60 * 24 * 2 + 1000 * 30; - } - - resetWithTemplate(): void { - this.template = this.tpl; - this.value = new Date().getTime() + 1000 * 60 * 60 * 24 * 2 + 1000 * 30; - } - - onFinish(): void { - this.finished += 1; - } -} +import { NzCountdownComponent } from './countdown.component'; +import { NzStatisticModule } from './statistic.module'; describe('nz-countdown', () => { + let testBed: ComponentBed; let fixture: ComponentFixture; let testComponent: NzTestCountdownComponent; let countdownEl: DebugElement; - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [CommonModule, NzStatisticModule], - declarations: [NzTestCountdownComponent] - }).compileComponents(); - }); - describe('basic', () => { beforeEach(() => { - fixture = TestBed.createComponent(NzTestCountdownComponent); - testComponent = fixture.debugElement.componentInstance; + testBed = createComponentBed(NzTestCountdownComponent, { + imports: [NzStatisticModule] + }); + fixture = testBed.fixture; + testComponent = testBed.component; countdownEl = fixture.debugElement.query(By.directive(NzCountdownComponent)); }); @@ -105,3 +65,42 @@ describe('nz-countdown', () => { })); }); }); + +@Component({ + template: ` + + + + {{ diff }} + + ` +}) +export class NzTestCountdownComponent { + @ViewChild(NzCountdownComponent, { static: true }) countdown: NzCountdownComponent; + @ViewChild('tpl', { static: true }) tpl: TemplateRef; + + format: string; + value: number; + template: TemplateRef; + finished = 0; + + resetTimerWithFormat(format: string): void { + this.format = format; + this.value = new Date().getTime() + 1000 * 60 * 60 * 24 * 2 + 1000 * 30; + } + + resetWithTemplate(): void { + this.template = this.tpl; + this.value = new Date().getTime() + 1000 * 60 * 60 * 24 * 2 + 1000 * 30; + } + + onFinish(): void { + this.finished += 1; + } +} diff --git a/components/statistic/nz-countdown.component.html b/components/statistic/nz-countdown.component.html deleted file mode 100644 index e752e6819bb..00000000000 --- a/components/statistic/nz-countdown.component.html +++ /dev/null @@ -1,11 +0,0 @@ - - - -{{ diff | nzTimeRange: nzFormat }} diff --git a/components/statistic/nz-statistic-number.component.html b/components/statistic/nz-statistic-number.component.html deleted file mode 100644 index d79445e33e3..00000000000 --- a/components/statistic/nz-statistic-number.component.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - {{ displayInt }} - {{ - displayDecimal - }} - diff --git a/components/statistic/nz-statistic.component.html b/components/statistic/nz-statistic.component.html deleted file mode 100644 index 99302436325..00000000000 --- a/components/statistic/nz-statistic.component.html +++ /dev/null @@ -1,13 +0,0 @@ -
        - {{ nzTitle }} -
        -
        - - {{ nzPrefix }} - - - - - {{ nzSuffix }} - -
        diff --git a/components/statistic/public-api.ts b/components/statistic/public-api.ts index eee4106067c..15fd1a3bdc2 100644 --- a/components/statistic/public-api.ts +++ b/components/statistic/public-api.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -export * from './nz-countdown.component'; -export * from './nz-statistic.component'; -export * from './nz-statistic.module'; -export * from './nz-statistic-number.component'; +export * from './countdown.component'; +export * from './statistic.component'; +export * from './statistic.module'; +export * from './statistic-number.component'; diff --git a/components/statistic/nz-statistic-number.component.ts b/components/statistic/statistic-number.component.ts similarity index 71% rename from components/statistic/nz-statistic-number.component.ts rename to components/statistic/statistic-number.component.ts index 81f54a00c34..13fd825f3da 100644 --- a/components/statistic/nz-statistic-number.component.ts +++ b/components/statistic/statistic-number.component.ts @@ -8,7 +8,7 @@ import { getLocaleNumberSymbol, NumberSymbol } from '@angular/common'; import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnChanges, TemplateRef, ViewEncapsulation } from '@angular/core'; -import { NzStatisticValueType } from './nz-statistic-definitions'; +import { NzStatisticValueType } from './typings'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -16,11 +16,16 @@ import { NzStatisticValueType } from './nz-statistic-definitions'; preserveWhitespaces: false, selector: 'nz-statistic-number', exportAs: 'nzStatisticNumber', - templateUrl: './nz-statistic-number.component.html', - host: { - class: 'ant-statistic-content-value' - }, - styles: ['nz-number { display: inline }'] + template: ` + + + + + {{ displayInt }} + {{ displayDecimal }} + + + ` }) export class NzStatisticNumberComponent implements OnChanges { @Input() nzValue: NzStatisticValueType; diff --git a/components/statistic/nz-statistic-number.spec.ts b/components/statistic/statistic-number.spec.ts similarity index 72% rename from components/statistic/nz-statistic-number.spec.ts rename to components/statistic/statistic-number.spec.ts index a2e06b3ed9f..4605e024721 100644 --- a/components/statistic/nz-statistic-number.spec.ts +++ b/components/statistic/statistic-number.spec.ts @@ -1,32 +1,31 @@ -import { CommonModule } from '@angular/common'; import { Component, DebugElement, TemplateRef, ViewChild } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { NzStatisticNumberComponent } from './nz-statistic-number.component'; -import { NzStatisticModule } from './nz-statistic.module'; + +import { ɵComponentBed as ComponentBed, ɵcreateComponentBed as createComponentBed } from 'ng-zorro-antd/core'; + +import { NzStatisticNumberComponent } from './statistic-number.component'; +import { NzStatisticModule } from './statistic.module'; describe('nz-number', () => { + let testBed: ComponentBed; let fixture: ComponentFixture; let testComponent: NzTestNumberComponent; let numberEl: DebugElement; - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [CommonModule, NzStatisticModule], - declarations: [NzTestNumberComponent] - }).compileComponents(); - }); - describe('basic', () => { beforeEach(() => { - fixture = TestBed.createComponent(NzTestNumberComponent); - testComponent = fixture.debugElement.componentInstance; + testBed = createComponentBed(NzTestNumberComponent, { + imports: [NzStatisticModule] + }); + fixture = testBed.fixture; + testComponent = testBed.component; numberEl = fixture.debugElement.query(By.directive(NzStatisticNumberComponent)); }); it('should have correct class', () => { fixture.detectChanges(); - expect(numberEl.nativeElement.classList.contains('ant-statistic-content-value')).toBeTruthy(); + expect(numberEl.nativeElement.firstElementChild!.classList.contains('ant-statistic-content-value')).toBeTruthy(); }); it('should render number', () => { diff --git a/components/statistic/nz-statistic.component.ts b/components/statistic/statistic.component.ts similarity index 51% rename from components/statistic/nz-statistic.component.ts rename to components/statistic/statistic.component.ts index c9348139c51..35db6340a6d 100644 --- a/components/statistic/nz-statistic.component.ts +++ b/components/statistic/statistic.component.ts @@ -7,18 +7,29 @@ */ import { ChangeDetectionStrategy, Component, Input, TemplateRef, ViewEncapsulation } from '@angular/core'; -import { NzStatisticValueType } from './nz-statistic-definitions'; +import { NzStatisticValueType } from './typings'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, selector: 'nz-statistic', exportAs: 'nzStatistic', - templateUrl: './nz-statistic.component.html', - host: { - class: 'ant-statistic' - }, - styles: ['nz-statistic { display: block; }'] + template: ` +
        +
        + {{ nzTitle }} +
        +
        + + {{ nzPrefix }} + + + + {{ nzSuffix }} + +
        +
        + ` }) export class NzStatisticComponent { @Input() nzPrefix: string | TemplateRef; diff --git a/components/statistic/nz-statistic.module.ts b/components/statistic/statistic.module.ts similarity index 63% rename from components/statistic/nz-statistic.module.ts rename to components/statistic/statistic.module.ts index 7045fa14474..eeedd68b68d 100644 --- a/components/statistic/nz-statistic.module.ts +++ b/components/statistic/statistic.module.ts @@ -8,14 +8,14 @@ import { PlatformModule } from '@angular/cdk/platform'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule, NzPipesModule } from 'ng-zorro-antd/core'; +import { NzOutletModule, NzPipesModule } from 'ng-zorro-antd/core'; -import { NzCountdownComponent } from './nz-countdown.component'; -import { NzStatisticNumberComponent } from './nz-statistic-number.component'; -import { NzStatisticComponent } from './nz-statistic.component'; +import { NzCountdownComponent } from './countdown.component'; +import { NzStatisticNumberComponent } from './statistic-number.component'; +import { NzStatisticComponent } from './statistic.component'; @NgModule({ - imports: [CommonModule, PlatformModule, NzAddOnModule, NzPipesModule], + imports: [CommonModule, PlatformModule, NzOutletModule, NzPipesModule], declarations: [NzStatisticComponent, NzCountdownComponent, NzStatisticNumberComponent], exports: [NzStatisticComponent, NzCountdownComponent, NzStatisticNumberComponent] }) diff --git a/components/statistic/nz-statistic.spec.ts b/components/statistic/statistic.spec.ts similarity index 71% rename from components/statistic/nz-statistic.spec.ts rename to components/statistic/statistic.spec.ts index 8b080b7f8cd..02b73517231 100644 --- a/components/statistic/nz-statistic.spec.ts +++ b/components/statistic/statistic.spec.ts @@ -1,26 +1,25 @@ -import { CommonModule } from '@angular/common'; import { Component, DebugElement } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { NzStatisticComponent } from './nz-statistic.component'; -import { NzStatisticModule } from './nz-statistic.module'; + +import { ɵComponentBed as ComponentBed, ɵcreateComponentBed as createComponentBed } from 'ng-zorro-antd/core'; + +import { NzStatisticComponent } from './statistic.component'; +import { NzStatisticModule } from './statistic.module'; describe('nz-statistic', () => { + let testBed: ComponentBed; let fixture: ComponentFixture; let testComponent: NzTestStatisticComponent; let statisticEl: DebugElement; - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [CommonModule, NzStatisticModule], - declarations: [NzTestStatisticComponent] - }).compileComponents(); - }); - describe('basic', () => { beforeEach(() => { - fixture = TestBed.createComponent(NzTestStatisticComponent); - testComponent = fixture.debugElement.componentInstance; + testBed = createComponentBed(NzTestStatisticComponent, { + imports: [NzStatisticModule] + }); + fixture = testBed.fixture; + testComponent = testBed.component; statisticEl = fixture.debugElement.query(By.directive(NzStatisticComponent)); }); diff --git a/components/statistic/nz-statistic-definitions.ts b/components/statistic/typings.ts similarity index 86% rename from components/statistic/nz-statistic-definitions.ts rename to components/statistic/typings.ts index 9d7ea5f4467..cbd56cffc24 100644 --- a/components/statistic/nz-statistic-definitions.ts +++ b/components/statistic/typings.ts @@ -7,5 +7,3 @@ */ export type NzStatisticValueType = number | string; - -export const REFRESH_INTERVAL = 1000 / 30; diff --git a/components/steps/nz-steps.module.ts b/components/steps/nz-steps.module.ts index 9ba8c075f4a..f29e305d7e5 100644 --- a/components/steps/nz-steps.module.ts +++ b/components/steps/nz-steps.module.ts @@ -9,14 +9,14 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule } from 'ng-zorro-antd/core'; +import { NzOutletModule } from 'ng-zorro-antd/core'; import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzStepComponent } from './nz-step.component'; import { NzStepsComponent } from './nz-steps.component'; @NgModule({ - imports: [CommonModule, NzIconModule, NzAddOnModule], + imports: [CommonModule, NzIconModule, NzOutletModule], exports: [NzStepsComponent, NzStepComponent], declarations: [NzStepsComponent, NzStepComponent] }) diff --git a/components/steps/style/label-placement.less b/components/steps/style/label-placement.less index 9d816e586e8..f8cbb6fd62a 100644 --- a/components/steps/style/label-placement.less +++ b/components/steps/style/label-placement.less @@ -31,7 +31,7 @@ &.@{steps-prefix-cls}-small:not(.@{steps-prefix-cls}-dot) { .@{steps-prefix-cls}-item { &-icon { - margin-left: 40px; + margin-left: 47px; } } } diff --git a/components/style/mixins/clearfix.less b/components/style/mixins/clearfix.less index 7999a3e92fa..d61f6c7b662 100644 --- a/components/style/mixins/clearfix.less +++ b/components/style/mixins/clearfix.less @@ -1,13 +1,9 @@ // mixins for clearfix // ------------------------ .clearfix() { - zoom: 1; - &::before, - &::after { - display: table; - content: ''; - } &::after { + display: block; clear: both; + content: ''; } } diff --git a/components/style/mixins/compatibility.less b/components/style/mixins/compatibility.less index 33812652db5..c4789504442 100644 --- a/components/style/mixins/compatibility.less +++ b/components/style/mixins/compatibility.less @@ -3,6 +3,10 @@ // Placeholder text .placeholder(@color: @input-placeholder-color) { // Firefox + &::-moz-placeholder { + opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526 + } + &::placeholder { color: @color; } diff --git a/components/style/patch.less b/components/style/patch.less index ab729331500..d884e22c745 100644 --- a/components/style/patch.less +++ b/components/style/patch.less @@ -65,15 +65,6 @@ } } -textarea.cdk-textarea-autosize-measuring { - height: auto !important; - overflow: hidden !important; - // Having 2px top and bottom padding seems to fix a bug where Chrome gets an incorrect - // measurement. We just have to account for it later and subtract it off the final result. - padding: 2px 0 !important; - box-sizing: content-box !important; -} - .nz-animate-disabled { // drawer &.ant-drawer { diff --git a/components/style/themes/default.less b/components/style/themes/default.less index f058bc2dc43..84c258f5376 100644 --- a/components/style/themes/default.less +++ b/components/style/themes/default.less @@ -71,6 +71,7 @@ // https://github.com/ant-design/ant-design/issues/20210 @line-height-base: 1.5715; @border-radius-base: 2px; +@border-radius-sm: @border-radius-base; // vertical paddings @padding-lg: 24px; // containers @@ -177,11 +178,11 @@ @btn-default-ghost-bg: transparent; @btn-default-ghost-border: @component-background; -@btn-padding-base: 0 @padding-md - 1px; @btn-font-size-lg: @font-size-lg; @btn-font-size-sm: @font-size-base; -@btn-padding-lg: @btn-padding-base; -@btn-padding-sm: 0 @padding-xs - 1px; +@btn-padding-horizontal-base: @padding-md - 1px; +@btn-padding-horizontal-lg: @btn-padding-horizontal-base; +@btn-padding-horizontal-sm: @padding-xs - 1px; @btn-height-base: 32px; @btn-height-lg: 40px; @@ -271,10 +272,11 @@ // Layout @layout-body-background: #f0f2f5; @layout-header-background: #001529; -@layout-footer-background: @layout-body-background; @layout-header-height: 64px; @layout-header-padding: 0 50px; +@layout-header-color: @text-color; @layout-footer-padding: 24px 50px; +@layout-footer-background: @layout-body-background; @layout-sider-background: @layout-header-background; @layout-trigger-height: 48px; @layout-trigger-background: #002140; @@ -326,6 +328,7 @@ @form-item-trailing-colon: true; @form-vertical-label-padding: 0 0 8px; @form-vertical-label-margin: 0; +@form-item-label-font-size: @font-size-base; @form-item-label-colon-margin-right: 8px; @form-item-label-colon-margin-left: 2px; @form-error-input-bg: @input-bg; @@ -339,9 +342,13 @@ @input-padding-horizontal-base: @input-padding-horizontal; @input-padding-horizontal-sm: @control-padding-horizontal-sm - 1px; @input-padding-horizontal-lg: @input-padding-horizontal; -@input-padding-vertical-base: 4px; -@input-padding-vertical-sm: 1px; -@input-padding-vertical-lg: 6px; +@input-padding-vertical-base: round( + (@input-height-base - @font-size-base * @line-height-base) / 2 * 10 + ) / 10 - @border-width-base; +@input-padding-vertical-sm: round((@input-height-sm - @font-size-base * @line-height-base) / 2 * 10) / + 10 - @border-width-base; +@input-padding-vertical-lg: round((@input-height-lg - @font-size-lg * @line-height-base) / 2 * 10) / + 10 - @border-width-base; @input-placeholder-color: hsv(0, 0, 75%); @input-color: @text-color; @input-icon-color: @input-color; @@ -597,9 +604,9 @@ @tabs-bar-margin: 0 0 16px 0; @tabs-horizontal-margin: 0 32px 0 0; @tabs-horizontal-margin-rtl: 0 0 0 32px; -@tabs-horizontal-padding: 12px 16px; -@tabs-horizontal-padding-lg: 16px; -@tabs-horizontal-padding-sm: 8px 16px; +@tabs-horizontal-padding: 12px 0; +@tabs-horizontal-padding-lg: 16px 0; +@tabs-horizontal-padding-sm: 8px 0; @tabs-vertical-padding: 8px 24px; @tabs-vertical-margin: 0 0 16px 0; @tabs-scrolling-size: 32px; @@ -779,6 +786,7 @@ @timeline-dot-border-width: 2px; @timeline-dot-color: @primary-color; @timeline-dot-bg: @component-background; +@timeline-item-padding-bottom: 20px; // Typography // --- diff --git a/components/switch/demo/basic.md b/components/switch/demo/basic.md index 5821c90f3da..6ff7fa5a5fd 100755 --- a/components/switch/demo/basic.md +++ b/components/switch/demo/basic.md @@ -13,9 +13,3 @@ title: The most basic usage. - - diff --git a/components/switch/demo/disabled.ts b/components/switch/demo/disabled.ts index 2df457408c7..0c997c50976 100755 --- a/components/switch/demo/disabled.ts +++ b/components/switch/demo/disabled.ts @@ -5,15 +5,9 @@ import { Component } from '@angular/core'; template: `
        +
        - `, - styles: [ - ` - nz-switch { - margin-bottom: 8px; - } - ` - ] + ` }) export class NzDemoSwitchDisabledComponent { switchValue = false; diff --git a/components/switch/demo/loading.ts b/components/switch/demo/loading.ts index e08c197f42c..304fc86d6d8 100755 --- a/components/switch/demo/loading.ts +++ b/components/switch/demo/loading.ts @@ -5,14 +5,8 @@ import { Component } from '@angular/core'; template: `
        +
        - `, - styles: [ - ` - nz-switch { - margin-bottom: 8px; - } - ` - ] + ` }) export class NzDemoSwitchLoadingComponent {} diff --git a/components/switch/demo/size.ts b/components/switch/demo/size.ts index db5c73a7013..f08a06bc899 100755 --- a/components/switch/demo/size.ts +++ b/components/switch/demo/size.ts @@ -5,14 +5,8 @@ import { Component } from '@angular/core'; template: `
        +
        - `, - styles: [ - ` - nz-switch { - margin-bottom: 8px; - } - ` - ] + ` }) export class NzDemoSwitchSizeComponent {} diff --git a/components/switch/demo/text.ts b/components/switch/demo/text.ts index 31f43f4b277..f9cdab45251 100755 --- a/components/switch/demo/text.ts +++ b/components/switch/demo/text.ts @@ -5,18 +5,13 @@ import { Component } from '@angular/core'; template: `
        +

        +
        - `, - styles: [ - ` - nz-switch { - margin-bottom: 8px; - } - ` - ] + ` }) export class NzDemoSwitchTextComponent {} diff --git a/components/switch/nz-switch.component.html b/components/switch/nz-switch.component.html deleted file mode 100644 index 0ed72fa08d5..00000000000 --- a/components/switch/nz-switch.component.html +++ /dev/null @@ -1,29 +0,0 @@ - diff --git a/components/switch/public-api.ts b/components/switch/public-api.ts index 80134ef9a6e..9cd8f6d59e4 100644 --- a/components/switch/public-api.ts +++ b/components/switch/public-api.ts @@ -6,5 +6,5 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -export * from './nz-switch.component'; -export * from './nz-switch.module'; +export * from './switch.component'; +export * from './switch.module'; diff --git a/components/switch/style/entry.less b/components/switch/style/entry.less index 06547c43acd..96cebe33bff 100644 --- a/components/switch/style/entry.less +++ b/components/switch/style/entry.less @@ -1 +1,2 @@ @import './index.less'; +@import './patch.less'; diff --git a/components/switch/style/patch.less b/components/switch/style/patch.less new file mode 100644 index 00000000000..7ebef3d5801 --- /dev/null +++ b/components/switch/style/patch.less @@ -0,0 +1,3 @@ +nz-switch { + display: inline-block; +} diff --git a/components/switch/nz-switch.component.ts b/components/switch/switch.component.ts similarity index 59% rename from components/switch/nz-switch.component.ts rename to components/switch/switch.component.ts index 745c80eb467..7a0a7a4f3d5 100644 --- a/components/switch/nz-switch.component.ts +++ b/components/switch/switch.component.ts @@ -23,7 +23,8 @@ import { } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { InputBoolean, NzConfigService, NzSizeDSType, WithConfig } from 'ng-zorro-antd/core'; +import { InputBoolean, NzConfigService, WithConfig } from 'ng-zorro-antd/core'; +import { NzSizeDSType, OnChangeType, OnTouchedType } from 'ng-zorro-antd/core/types'; const NZ_CONFIG_COMPONENT_NAME = 'switch'; @@ -31,7 +32,6 @@ const NZ_CONFIG_COMPONENT_NAME = 'switch'; selector: 'nz-switch', exportAs: 'nzSwitch', preserveWhitespaces: false, - templateUrl: './nz-switch.component.html', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [ @@ -41,40 +41,59 @@ const NZ_CONFIG_COMPONENT_NAME = 'switch'; multi: true } ], + template: ` + + `, host: { - '(click)': 'hostClick($event)' - }, - styles: [ - ` - nz-switch { - display: inline-block; - } - ` - ] + '(click)': 'onHostClick($event)' + } }) export class NzSwitchComponent implements ControlValueAccessor, AfterViewInit, OnDestroy { - checked = false; - onChange: (value: boolean) => void = () => null; - onTouched: () => void = () => null; + isChecked = false; + onChange: OnChangeType = () => {}; + onTouched: OnTouchedType = () => {}; @ViewChild('switchElement', { static: true }) private switchElement: ElementRef; @Input() @InputBoolean() nzLoading = false; @Input() @InputBoolean() nzDisabled = false; @Input() @InputBoolean() nzControl = false; - @Input() nzCheckedChildren: string | TemplateRef; - @Input() nzUnCheckedChildren: string | TemplateRef; + @Input() nzCheckedChildren: string | TemplateRef | null = null; + @Input() nzUnCheckedChildren: string | TemplateRef | null = null; @Input() @WithConfig(NZ_CONFIG_COMPONENT_NAME, 'default') nzSize: NzSizeDSType; - hostClick(e: MouseEvent): void { + onHostClick(e: MouseEvent): void { e.preventDefault(); if (!this.nzDisabled && !this.nzLoading && !this.nzControl) { - this.updateValue(!this.checked); + this.updateValue(!this.isChecked); } } updateValue(value: boolean): void { - if (this.checked !== value) { - this.checked = value; - this.onChange(this.checked); + if (this.isChecked !== value) { + this.isChecked = value; + this.onChange(this.isChecked); } } @@ -87,7 +106,7 @@ export class NzSwitchComponent implements ControlValueAccessor, AfterViewInit, O this.updateValue(true); e.preventDefault(); } else if (e.keyCode === SPACE || e.keyCode === ENTER) { - this.updateValue(!this.checked); + this.updateValue(!this.isChecked); e.preventDefault(); } } @@ -106,11 +125,7 @@ export class NzSwitchComponent implements ControlValueAccessor, AfterViewInit, O ngAfterViewInit(): void { this.focusMonitor.monitor(this.switchElement.nativeElement, true).subscribe(focusOrigin => { if (!focusOrigin) { - // When a focused element becomes disabled, the browser *immediately* fires a blur event. - // Angular does not expect events to be raised during change detection, so any state change - // (such as a form control's 'ng-touched') will cause a changed-after-checked error. - // See https://github.com/angular/angular/issues/17793. To work around this, we defer - // telling the form control it has been touched until the next tick. + /** https://github.com/angular/angular/issues/17793 **/ Promise.resolve().then(() => this.onTouched()); } }); @@ -121,20 +136,20 @@ export class NzSwitchComponent implements ControlValueAccessor, AfterViewInit, O } writeValue(value: boolean): void { - this.checked = value; + this.isChecked = value; this.cdr.markForCheck(); } - registerOnChange(fn: (_: boolean) => void): void { + registerOnChange(fn: OnChangeType): void { this.onChange = fn; } - registerOnTouched(fn: () => void): void { + registerOnTouched(fn: OnTouchedType): void { this.onTouched = fn; } - setDisabledState(isDisabled: boolean): void { - this.nzDisabled = isDisabled; + setDisabledState(disabled: boolean): void { + this.nzDisabled = disabled; this.cdr.markForCheck(); } } diff --git a/components/switch/nz-switch.module.ts b/components/switch/switch.module.ts similarity index 71% rename from components/switch/nz-switch.module.ts rename to components/switch/switch.module.ts index 4d7b7face75..c51bcfc435e 100644 --- a/components/switch/nz-switch.module.ts +++ b/components/switch/switch.module.ts @@ -9,14 +9,14 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule, NzWaveModule } from 'ng-zorro-antd/core'; +import { NzOutletModule, NzWaveModule } from 'ng-zorro-antd/core'; import { NzIconModule } from 'ng-zorro-antd/icon'; -import { NzSwitchComponent } from './nz-switch.component'; +import { NzSwitchComponent } from './switch.component'; @NgModule({ exports: [NzSwitchComponent], declarations: [NzSwitchComponent], - imports: [CommonModule, NzWaveModule, NzIconModule, NzAddOnModule] + imports: [CommonModule, NzWaveModule, NzIconModule, NzOutletModule] }) export class NzSwitchModule {} diff --git a/components/switch/nz-switch.spec.ts b/components/switch/switch.spec.ts similarity index 97% rename from components/switch/nz-switch.spec.ts rename to components/switch/switch.spec.ts index 85b67a4f557..359e5291025 100644 --- a/components/switch/nz-switch.spec.ts +++ b/components/switch/switch.spec.ts @@ -3,12 +3,10 @@ import { Component, DebugElement, TemplateRef, ViewChild } from '@angular/core'; import { ComponentFixture, fakeAsync, flush, TestBed } from '@angular/core/testing'; import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; - import { dispatchKeyboardEvent } from 'ng-zorro-antd/core'; import { NzIconTestModule } from 'ng-zorro-antd/icon/testing'; - -import { NzSwitchComponent } from './nz-switch.component'; -import { NzSwitchModule } from './nz-switch.module'; +import { NzSwitchComponent } from './switch.component'; +import { NzSwitchModule } from './switch.module'; describe('switch', () => { beforeEach(fakeAsync(() => { @@ -177,16 +175,12 @@ describe('switch', () => { fixture.detectChanges(); flush(); fixture.detectChanges(); - expect(switchElement.nativeElement.firstElementChild.firstElementChild.firstElementChild.firstElementChild!.classList).toContain( - 'anticon-close' - ); + expect(switchElement.nativeElement.firstElementChild.firstElementChild.firstElementChild!.classList).toContain('anticon-close'); switchElement.nativeElement.click(); fixture.detectChanges(); flush(); fixture.detectChanges(); - expect(switchElement.nativeElement.firstElementChild.firstElementChild.firstElementChild.firstElementChild!.classList).toContain( - 'anticon-check' - ); + expect(switchElement.nativeElement.firstElementChild.firstElementChild.firstElementChild!.classList).toContain('anticon-check'); })); }); describe('switch form', () => { diff --git a/components/table/nz-table.module.ts b/components/table/nz-table.module.ts index a29792c5f0a..56951cf1d3d 100644 --- a/components/table/nz-table.module.ts +++ b/components/table/nz-table.module.ts @@ -11,7 +11,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NzCheckboxModule } from 'ng-zorro-antd/checkbox'; -import { NzAddOnModule } from 'ng-zorro-antd/core'; +import { NzOutletModule } from 'ng-zorro-antd/core'; import { NzDropDownModule } from 'ng-zorro-antd/dropdown'; import { NzEmptyModule } from 'ng-zorro-antd/empty'; import { NzI18nModule } from 'ng-zorro-antd/i18n'; @@ -43,7 +43,7 @@ import { NzVirtualScrollDirective } from './nz-virtual-scroll.directive'; imports: [ NzMenuModule, FormsModule, - NzAddOnModule, + NzOutletModule, NzRadioModule, NzCheckboxModule, NzDropDownModule, diff --git a/components/table/nz-th.component.html b/components/table/nz-th.component.html index 5e322765659..dfb1ee07a51 100644 --- a/components/table/nz-th.component.html +++ b/components/table/nz-th.component.html @@ -73,6 +73,7 @@ [nzDropdownMenu]="filterMenu" [class.ant-table-filter-selected]="hasFilterValue" [class.ant-table-filter-open]="filterVisible" + [nzVisible]="filterVisible" (nzVisibleChange)="dropDownVisibleChange($event)" > diff --git a/components/table/nz-th.component.ts b/components/table/nz-th.component.ts index 3798693f4de..64a0cb6c0a4 100644 --- a/components/table/nz-th.component.ts +++ b/components/table/nz-th.component.ts @@ -17,15 +17,13 @@ import { OnInit, Output, SimpleChanges, - ViewChild, ViewEncapsulation } from '@angular/core'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; import { InputBoolean, isNotNil } from 'ng-zorro-antd/core'; -import { NzDropdownMenuComponent } from 'ng-zorro-antd/dropdown'; import { NzI18nInterface, NzI18nService } from 'ng-zorro-antd/i18n'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; /* tslint:disable-next-line:no-any */ export type NzThFilterType = Array<{ text: string; value: any; byDefault?: boolean }>; @@ -69,7 +67,6 @@ export class NzThComponent implements OnChanges, OnInit, OnDestroy { nzWidthChange$ = new Subject(); private destroy$ = new Subject(); private hasDefaultFilter = false; - @ViewChild(NzDropdownMenuComponent, { static: false }) nzDropdownMenuComponent: NzDropdownMenuComponent; /* tslint:disable-next-line:no-any */ @Input() nzSelections: Array<{ text: string; onSelect(...args: any[]): any }> = []; @Input() nzChecked = false; @@ -158,8 +155,8 @@ export class NzThComponent implements OnChanges, OnInit, OnDestroy { } hideDropDown(): void { - this.nzDropdownMenuComponent.setVisibleStateWhen(false); this.filterVisible = false; + this.search(); } dropDownVisibleChange(value: boolean): void { diff --git a/components/tabs/nz-tabs.module.ts b/components/tabs/nz-tabs.module.ts index c1c579863da..b2f09f611ef 100644 --- a/components/tabs/nz-tabs.module.ts +++ b/components/tabs/nz-tabs.module.ts @@ -9,7 +9,7 @@ import { ObserversModule } from '@angular/cdk/observers'; import { PlatformModule } from '@angular/cdk/platform'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule } from 'ng-zorro-antd/core'; +import { NzOutletModule } from 'ng-zorro-antd/core'; import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzTabBodyComponent } from './nz-tab-body.component'; @@ -42,6 +42,6 @@ import { NzTabSetComponent } from './nz-tabset.component'; NzTabBodyComponent, NzTabLinkDirective ], - imports: [CommonModule, ObserversModule, NzIconModule, NzAddOnModule, PlatformModule] + imports: [CommonModule, ObserversModule, NzIconModule, NzOutletModule, PlatformModule] }) export class NzTabsModule {} diff --git a/components/time-picker/nz-time-picker-panel.component.ts b/components/time-picker/nz-time-picker-panel.component.ts index 1bd6a044756..107f80b9a8c 100644 --- a/components/time-picker/nz-time-picker-panel.component.ts +++ b/components/time-picker/nz-time-picker-panel.component.ts @@ -99,7 +99,7 @@ export class NzTimePickerPanelComponent implements ControlValueAccessor, OnInit, @Input() set nzDisabledHours(value: () => number[]) { this._disabledHours = value; - if (this._disabledHours) { + if (!!this._disabledHours) { this.buildHours(); } } @@ -284,7 +284,7 @@ export class NzTimePickerPanelComponent implements ControlValueAccessor, OnInit, this.time.setHours(hour.index, hour.disabled); this.scrollToSelected(this.hourListElement.nativeElement, hour.index, 120, 'hour'); - if (this._disabledMinutes) { + if (!!this._disabledMinutes) { this.buildMinutes(); } if (this._disabledSeconds || this._disabledMinutes) { @@ -295,7 +295,7 @@ export class NzTimePickerPanelComponent implements ControlValueAccessor, OnInit, selectMinute(minute: { index: number; disabled: boolean }): void { this.time.setMinutes(minute.index, minute.disabled); this.scrollToSelected(this.minuteListElement.nativeElement, minute.index, 120, 'minute'); - if (this._disabledSeconds) { + if (!!this._disabledSeconds) { this.buildSeconds(); } } @@ -307,13 +307,13 @@ export class NzTimePickerPanelComponent implements ControlValueAccessor, OnInit, select12Hours(value: { index: number; value: string }): void { this.time.selected12Hours = value.value; - if (this._disabledHours) { + if (!!this._disabledHours) { this.buildHours(); } - if (this._disabledMinutes) { + if (!!this._disabledMinutes) { this.buildMinutes(); } - if (this._disabledSeconds) { + if (!!this._disabledSeconds) { this.buildSeconds(); } this.scrollToSelected(this.use12HoursListElement.nativeElement, value.index, 120, '12-hour'); diff --git a/components/timeline/nz-timeline.module.ts b/components/timeline/nz-timeline.module.ts index bbc7bb3a8bc..1a956385b2c 100644 --- a/components/timeline/nz-timeline.module.ts +++ b/components/timeline/nz-timeline.module.ts @@ -8,7 +8,7 @@ import { PlatformModule } from '@angular/cdk/platform'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule } from 'ng-zorro-antd/core'; +import { NzOutletModule } from 'ng-zorro-antd/core'; import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzTimelineItemComponent } from './nz-timeline-item.component'; @@ -17,6 +17,6 @@ import { NzTimelineComponent } from './nz-timeline.component'; @NgModule({ declarations: [NzTimelineItemComponent, NzTimelineComponent], exports: [NzTimelineItemComponent, NzTimelineComponent], - imports: [CommonModule, PlatformModule, NzIconModule, NzAddOnModule] + imports: [CommonModule, PlatformModule, NzIconModule, NzOutletModule] }) export class NzTimelineModule {} diff --git a/components/timeline/style/index.less b/components/timeline/style/index.less index bcf74e90fed..808095403ac 100644 --- a/components/timeline/style/index.less +++ b/components/timeline/style/index.less @@ -17,7 +17,7 @@ &-item { position: relative; margin: 0; - padding: 0 0 20px; + padding-bottom: @timeline-item-padding-bottom; font-size: @font-size-base; list-style: none; @@ -168,8 +168,7 @@ text-align: right; .@{timeline-prefix-cls}-rtl& { - width: 100%; - margin-right: 12px; + text-align: left; } } } @@ -195,6 +194,7 @@ .@{timeline-prefix-cls}-rtl& { width: 100%; margin-right: 18px; + text-align: right; } } } diff --git a/components/tooltip/nz-tooltip.module.ts b/components/tooltip/nz-tooltip.module.ts index 616a2900725..45daee70c3c 100644 --- a/components/tooltip/nz-tooltip.module.ts +++ b/components/tooltip/nz-tooltip.module.ts @@ -10,8 +10,7 @@ import { OverlayModule } from '@angular/cdk/overlay'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule, NzNoAnimationModule, NzOverlayModule } from 'ng-zorro-antd/core'; - +import { NzNoAnimationModule, NzOutletModule, NzOverlayModule } from 'ng-zorro-antd/core'; // NOTE: the `t` is not uppercase in directives. Change this would however introduce breaking change. import { NzToolTipComponent } from './nz-tooltip.component'; import { NzTooltipDirective } from './nz-tooltip.directive'; @@ -19,7 +18,7 @@ import { NzTooltipDirective } from './nz-tooltip.directive'; @NgModule({ declarations: [NzToolTipComponent, NzTooltipDirective], exports: [NzToolTipComponent, NzTooltipDirective], - imports: [CommonModule, OverlayModule, NzAddOnModule, NzOverlayModule, NzNoAnimationModule], + imports: [CommonModule, OverlayModule, NzOutletModule, NzOverlayModule, NzNoAnimationModule], entryComponents: [NzToolTipComponent] }) export class NzToolTipModule {} diff --git a/components/transfer/style/index.less b/components/transfer/style/index.less index cb2d3b05fa8..958c8d7793d 100644 --- a/components/transfer/style/index.less +++ b/components/transfer/style/index.less @@ -39,7 +39,8 @@ } &-search { - padding: 0 24px 0 @control-padding-horizontal-sm; + padding-right: 24px; + padding-left: @control-padding-horizontal-sm; &-action { position: absolute; top: 12px; diff --git a/components/tree-select/doc/index.en-US.md b/components/tree-select/doc/index.en-US.md index 7cc8ff54002..bbff0e74c54 100755 --- a/components/tree-select/doc/index.en-US.md +++ b/components/tree-select/doc/index.en-US.md @@ -29,6 +29,7 @@ import { NzTreeSelectModule } from 'ng-zorro-antd/tree-select'; | `[nzNotFoundContent]` | Specify content to show when no result matches. | `string` | - | | `[nzDropdownMatchSelectWidth]` | Determine whether the dropdown menu and the select input are the same width | `boolean` | `true` | ✅ | | `[nzDropdownStyle]` | To set the style of the dropdown menu | `object` | - | +| `[nzDropdownClassName]` | classname of dropdown menu | `string` | - | | `[nzMultiple]` | Support multiple or not, will be `true` when enable `nzCheckable`. | `boolean` | `false` | | `[nzHideUnMatched]` | Hide unmatched nodes while searching | `boolean` | `false` | ✅ | | `[nzSize]` | To set the size of the select input | `'large' \| 'small' \| 'default'` | `'default'` | ✅ | diff --git a/components/tree-select/doc/index.zh-CN.md b/components/tree-select/doc/index.zh-CN.md index 97ce9e50365..3f582c72539 100755 --- a/components/tree-select/doc/index.zh-CN.md +++ b/components/tree-select/doc/index.zh-CN.md @@ -29,6 +29,7 @@ import { NzTreeSelectModule } from 'ng-zorro-antd/tree-select'; | `[nzNotFoundContent]` | 当下拉列表为空时显示的内容 | `string` | - | | `[nzDropdownMatchSelectWidth]` | 下拉菜单和选择器同宽 | `boolean` | `true` | ✅ | | `[nzDropdownStyle]` | 下拉菜单的样式 | `{ [key: string]: string; }` | - | +| `[nzDropdownClassName]` | 下拉菜单的 className 属性 | `string` | - | | `[nzMultiple]` | 支持多选(当设置 nzCheckable 时自动变为true) | `boolean` | `false` | | `[nzHideUnMatched]` | 搜索隐藏未匹配的节点 | `boolean` | `false` | ✅ | | `[nzSize]` | 选择框大小 | `'large' \| 'small' \| 'default'` | `'default'` | ✅ | diff --git a/components/tree-select/nz-tree-select.component.html b/components/tree-select/nz-tree-select.component.html index 8829453908e..68bfbf94b4a 100644 --- a/components/tree-select/nz-tree-select.component.html +++ b/components/tree-select/nz-tree-select.component.html @@ -25,7 +25,7 @@ (positionChange)="onPositionChange($event)" >
        { const targetElement = overlayContainerElement.querySelector('.ant-select-dropdown') as HTMLElement; expect(targetElement.style.height).toBe('120px'); })); + it('should dropdown classname work', fakeAsync(() => { + treeSelect.nativeElement.click(); + fixture.detectChanges(); + expect(treeSelectComponent.nzOpen).toBe(true); + flush(); + const targetElement = overlayContainerElement.querySelector('.ant-select-dropdown') as HTMLElement; + expect(targetElement.classList).toContain('class1'); + expect(targetElement.classList).toContain('class2'); + })); it('should click option close dropdown', fakeAsync(() => { treeSelect.nativeElement.click(); fixture.detectChanges(); @@ -548,6 +557,7 @@ describe('tree-select component', () => { [nzMultiple]="multiple" [nzMaxTagCount]="maxTagCount" [nzDropdownStyle]="{ height: '120px' }" + nzDropdownClassName="class1 class2" > ` diff --git a/components/tree/nz-tree.module.ts b/components/tree/nz-tree.module.ts index af93818e6ce..bc5f12ab92d 100644 --- a/components/tree/nz-tree.module.ts +++ b/components/tree/nz-tree.module.ts @@ -9,14 +9,14 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { NzAddOnModule, NzHighlightModule, NzNoAnimationModule } from 'ng-zorro-antd/core'; +import { NzHighlightModule, NzNoAnimationModule, NzOutletModule } from 'ng-zorro-antd/core'; import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzTreeNodeComponent } from './nz-tree-node.component'; import { NzTreeComponent } from './nz-tree.component'; @NgModule({ - imports: [CommonModule, NzAddOnModule, NzIconModule, NzNoAnimationModule, NzHighlightModule], + imports: [CommonModule, NzOutletModule, NzIconModule, NzNoAnimationModule, NzHighlightModule], declarations: [NzTreeComponent, NzTreeNodeComponent], exports: [NzTreeComponent, NzTreeNodeComponent] }) diff --git a/package.json b/package.json index ef4f694b725..f7038fce112 100644 --- a/package.json +++ b/package.json @@ -26,33 +26,33 @@ "integration": "npm run build:lib && bash ./integration-test.sh" }, "dependencies": { - "@angular/cdk": "^9.0.0-rc.7", - "@ant-design/icons-angular": "^9.0.0-rc.5", + "@angular/cdk": "^9.0.0", + "@ant-design/icons-angular": "^9.0.0", "date-fns": "^1.30.1" }, "devDependencies": { "tslib": "^1.10.0", - "@angular-devkit/build-angular": "~0.900.0-rc.8", - "@angular-devkit/build-ng-packagr": "~0.900.0-rc.8", - "@angular-devkit/core": "~9.0.0-rc.4", - "@angular-devkit/schematics": "~9.0.0-rc.8", - "@angular/animations": "~9.0.0-rc.8", - "@angular/cli": "~9.0.0-rc.8", - "@angular/common": "~9.0.0-rc.8", - "@angular/compiler": "~9.0.0-rc.8", - "@angular/compiler-cli": "~9.0.0-rc.8", - "@angular/core": "~9.0.0-rc.8", - "@angular/forms": "~9.0.0-rc.8", - "@angular/language-service": "~9.0.0-rc.8", - "@angular/platform-browser": "~9.0.0-rc.8", - "@angular/platform-browser-dynamic": "~9.0.0-rc.8", - "@angular/platform-server": "~9.0.0-rc.8", - "@angular/router": "~9.0.0-rc.8", - "@angular/service-worker": "~9.0.0-rc.8", + "@angular-devkit/build-angular": "~0.900.0", + "@angular-devkit/build-ng-packagr": "~0.900.0", + "@angular-devkit/core": "^9.0.0", + "@angular-devkit/schematics": "^9.0.0", + "@angular/animations": "^9.0.0", + "@angular/cli": "^9.0.0", + "@angular/common": "^9.0.0", + "@angular/compiler": "^9.0.0", + "@angular/compiler-cli": "^9.0.0", + "@angular/core": "^9.0.0", + "@angular/forms": "^9.0.0", + "@angular/language-service": "^9.0.0", + "@angular/platform-browser": "^9.0.0", + "@angular/platform-browser-dynamic": "^9.0.0", + "@angular/platform-server": "^9.0.0", + "@angular/router": "^9.0.0", + "@angular/service-worker": "^9.0.0", "@commitlint/cli": "^8.1.0", "@commitlint/config-angular": "^8.1.0", "@nguniversal/module-map-ngfactory-loader": "~9.0.0-next.6", - "@schematics/angular": "~9.0.0-rc.8", + "@schematics/angular": "^9.0.1", "@stackblitz/sdk": "^1.3.0", "@types/fs-extra": "^8.0.0", "@types/gulp": "^4.0.6", @@ -62,7 +62,6 @@ "@types/node": "^12.11.1", "@types/parse5": "^5.0.2", "antd-theme-generator": "^1.1.7", - "bundlesize": "^0.18.0", "chalk": "^2.4.2", "classlist.js": "^1.1.20150312", "clean-css": "~4.2.1", @@ -90,7 +89,7 @@ "marked": "^0.7.0", "minimist": "^1.2.0", "monaco-editor": "^0.17.1", - "ng-packagr": "9.0.0-rc.4", + "ng-packagr": "9.0.0", "ngx-color": "^4.0.0", "node-prismjs": "^0.1.2", "parse5": "^5.1.1", @@ -107,7 +106,7 @@ "sitemap": "^4.1.1", "ts-node": "~8.3.0", "tslint": "~5.18.0", - "typescript": "~3.6.4", + "typescript": "~3.7.4", "yaml-front-matter": "^4.0.0", "zone.js": "~0.10.2" }, diff --git a/scripts/site/_site/doc/app/app.component.ts b/scripts/site/_site/doc/app/app.component.ts index 1fb7578914a..7978082393e 100644 --- a/scripts/site/_site/doc/app/app.component.ts +++ b/scripts/site/_site/doc/app/app.component.ts @@ -1,16 +1,6 @@ import { Platform } from '@angular/cdk/platform'; import { DOCUMENT } from '@angular/common'; -import { - AfterViewInit, - Component, - ElementRef, - HostListener, - Inject, - NgZone, - OnInit, - Renderer2, - ViewChild -} from '@angular/core'; +import { AfterViewInit, Component, ElementRef, HostListener, Inject, NgZone, OnInit, Renderer2, ViewChild } from '@angular/core'; import { Meta, Title } from '@angular/platform-browser'; import { NavigationEnd, Router } from '@angular/router'; import { en_US, NzI18nService, zh_CN } from 'ng-zorro-antd/i18n'; @@ -33,7 +23,7 @@ interface DocPageMeta { } @Component({ - selector : 'app-root', + selector: 'app-root', templateUrl: './app.component.html' }) export class AppComponent implements OnInit, AfterViewInit { @@ -58,7 +48,7 @@ export class AppComponent implements OnInit, AfterViewInit { } language = 'zh'; - oldVersionList = [ '0.5.x', '0.6.x', '0.7.x', '1.8.x', '7.5.x' ]; + oldVersionList = ['0.5.x', '0.6.x', '0.7.x', '1.8.x', '7.5.x']; currentVersion = VERSION.full; @ViewChild('searchInput', { static: false }) searchInput: ElementRef; @@ -82,8 +72,7 @@ export class AppComponent implements OnInit, AfterViewInit { private renderer: Renderer2, // tslint:disable-next-line:no-any @Inject(DOCUMENT) private document: any - ) { - } + ) {} navigateToPage(url: string): void { if (url) { @@ -114,7 +103,7 @@ export class AppComponent implements OnInit, AfterViewInit { ngOnInit(): void { this.routerList.components.forEach(group => { - this.componentList = this.componentList.concat([ ...group.children ]); + this.componentList = this.componentList.concat([...group.children]); }); this.router.events.subscribe(event => { @@ -135,14 +124,14 @@ export class AppComponent implements OnInit, AfterViewInit { } this.isExperimental = this.router.url.search('experimental') !== -1; this.language = this.router.url - .split('/')[this.router.url.split('/').length - 1] - .split('#')[0] + .split('/') + [this.router.url.split('/').length - 1].split('#')[0] .split('?')[0]; this.appService.language$.next(this.language); this.nzI18nService.setLocale(this.language === 'en' ? en_US : zh_CN); this.updateDocMetaAndLocale(); if (this.docsearch) { - this.docsearch!.algoliaOptions = { hitsPerPage: 5, facetFilters: [ `tags:${this.language}` ] }; + this.docsearch!.algoliaOptions = { hitsPerPage: 5, facetFilters: [`tags:${this.language}`] }; } if (environment.production && this.platform.isBrowser) { @@ -175,7 +164,8 @@ export class AppComponent implements OnInit, AfterViewInit { updateDocMetaAndLocale(): void { if (this.platform.isBrowser) { const isEn = this.language === 'en'; - const enDescription = 'An enterprise-class UI design language and Angular-based implementation with a set of high-quality Angular components, one of best Angular UI library for enterprises'; + const enDescription = + 'An enterprise-class UI design language and Angular-based implementation with a set of high-quality Angular components, one of best Angular UI library for enterprises'; const zhDescription = 'Ant Design 的 Angular 实现,开发和服务于企业级后台产品,开箱即用的高质量 Angular 组件。'; const descriptionContent = isEn ? enDescription : zhDescription; this.meta.updateTag({ @@ -202,11 +192,11 @@ export class AppComponent implements OnInit, AfterViewInit { initDocsearch() { this.loadScript('https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js').then(() => { this.docsearch = docsearch({ - appId : 'BH4D9OD16A', - apiKey : '9f7d9d6527ff52ec484e90bb1f256971', - indexName : 'ng_zorro', - inputSelector : '#search-box input', - algoliaOptions: { hitsPerPage: 5, facetFilters: [ `tags:${this.language}` ] }, + appId: 'BH4D9OD16A', + apiKey: '9f7d9d6527ff52ec484e90bb1f256971', + indexName: 'ng_zorro', + inputSelector: '#search-box input', + algoliaOptions: { hitsPerPage: 5, facetFilters: [`tags:${this.language}`] }, transformData(hits: any) { // tslint:disable-line:no-any hits.forEach((hit: any) => { @@ -216,12 +206,12 @@ export class AppComponent implements OnInit, AfterViewInit { }); return hits; }, - debug : false + debug: false }); }); } - @HostListener('document:keyup.s', [ '$event' ]) + @HostListener('document:keyup.s', ['$event']) onKeyUp(event: KeyboardEvent) { if (this.useDocsearch && this.searchInput && this.searchInput.nativeElement && event.target === document.body) { this.searchInput.nativeElement.focus(); @@ -233,13 +223,13 @@ export class AppComponent implements OnInit, AfterViewInit { initColor() { if (!this.platform.isBrowser) { - return + return; } const node = document.createElement('link'); node.rel = 'stylesheet/less'; node.type = 'text/css'; node.href = '/assets/color.less'; - document.getElementsByTagName('head')[ 0 ].appendChild(node); + document.getElementsByTagName('head')[0].appendChild(node); } lessLoaded = false; @@ -316,7 +306,7 @@ export class AppComponent implements OnInit, AfterViewInit { const hasLanguage = pathname.match(/(en|zh)(\/?)$/); if (language === 'zh-cn' && !hasLanguage) { this.nzI18nService.setLocale(zh_CN); - this.router.navigate([ 'docs', 'introduce', 'zh' ]); + this.router.navigate(['docs', 'introduce', 'zh']); } } }