From 2bfbc890361da3a0927833449bf507545a2a7c80 Mon Sep 17 00:00:00 2001 From: vthinkxie Date: Mon, 17 Jun 2019 15:40:12 +0800 Subject: [PATCH] refactor(module:dropdown): refactor dropdown --- components/affix/nz-affix.component.ts | 14 +- components/anchor/nz-anchor.component.ts | 4 +- components/badge/nz-badge.spec.ts | 4 +- components/badge/style/index.less | 191 ------------- components/button/demo/multiple.ts | 6 +- .../dropdown/nz-dropdown-service.resolver.ts | 2 +- .../core/dropdown/nz-menu-base.service.ts | 3 +- components/core/overlay/overlay-position.ts | 4 +- .../services/update-host-class.service.ts | 2 +- components/core/types/ng-class.ts | 2 +- .../date-picker/date-picker.component.spec.ts | 6 +- .../month-picker.component.spec.ts | 6 +- .../range-picker.component.spec.ts | 6 +- .../date-picker/year-picker.component.spec.ts | 6 +- components/dropdown/demo/basic.ts | 24 +- components/dropdown/demo/context-menu.ts | 81 +++--- components/dropdown/demo/dropdown-button.ts | 94 ++++--- components/dropdown/demo/event.ts | 12 +- components/dropdown/demo/item.ts | 20 +- components/dropdown/demo/overlay-visible.ts | 9 +- components/dropdown/demo/placement.ts | 98 +------ components/dropdown/demo/sub-menu.ts | 9 +- components/dropdown/demo/trigger.ts | 12 +- components/dropdown/doc/index.en-US.md | 38 ++- components/dropdown/doc/index.zh-CN.md | 37 ++- .../dropdown/nz-context-menu.service.spec.ts | 139 ++++++++++ .../dropdown/nz-context-menu.service.ts | 127 +++++++++ .../dropdown/nz-dropdown-button.component.ts | 15 +- .../dropdown/nz-dropdown-context.component.ts | 7 +- .../dropdown/nz-dropdown-menu.component.html | 13 + .../dropdown/nz-dropdown-menu.component.ts | 106 +++++++ components/dropdown/nz-dropdown.component.ts | 17 +- .../dropdown/nz-dropdown.directive.spec.ts | 198 +++++++++++++ components/dropdown/nz-dropdown.directive.ts | 261 +++++++++++++++++- components/dropdown/nz-dropdown.module.ts | 18 +- components/dropdown/nz-dropdown.service.ts | 10 +- components/dropdown/nz-dropdown.spec.ts | 2 +- components/dropdown/public-api.ts | 2 + components/menu/nz-submenu.component.ts | 6 +- .../slider/nz-slider-handle.component.ts | 4 +- components/table/demo/custom-filter-panel.ts | 38 +-- components/table/demo/nested-table.ts | 6 +- components/table/nz-th.component.html | 100 ++++--- components/table/nz-th.component.ts | 6 +- components/tabs/nz-tabs.spec.ts | 4 +- components/tree/demo/directory.ts | 28 +- 46 files changed, 1214 insertions(+), 583 deletions(-) create mode 100644 components/dropdown/nz-context-menu.service.spec.ts create mode 100644 components/dropdown/nz-context-menu.service.ts create mode 100644 components/dropdown/nz-dropdown-menu.component.html create mode 100644 components/dropdown/nz-dropdown-menu.component.ts create mode 100644 components/dropdown/nz-dropdown.directive.spec.ts diff --git a/components/affix/nz-affix.component.ts b/components/affix/nz-affix.component.ts index fb77dfc03db..72884c9ee01 100644 --- a/components/affix/nz-affix.component.ts +++ b/components/affix/nz-affix.component.ts @@ -25,8 +25,8 @@ import { shallowEqual, throttleByAnimationFrameDecorator, toNumber, - NzScrollService, - NGStyleInterface + NgStyleInterface, + NzScrollService } from 'ng-zorro-antd/core'; @Component({ @@ -84,8 +84,8 @@ export class NzAffixComponent implements OnInit, OnDestroy { private readonly placeholderNode: HTMLElement; - private affixStyle: NGStyleInterface | undefined; - private placeholderStyle: NGStyleInterface | undefined; + private affixStyle: NgStyleInterface | undefined; + private placeholderStyle: NgStyleInterface | undefined; private _target: Element | Window | null = null; private _offsetTop: number | null; private _offsetBottom: number | null; @@ -167,7 +167,7 @@ export class NzAffixComponent implements OnInit, OnDestroy { : ({ top: 0, left: 0, bottom: 0 } as ClientRect); } - private genStyle(affixStyle?: NGStyleInterface): string { + private genStyle(affixStyle?: NgStyleInterface): string { if (!affixStyle) { return ''; } @@ -179,7 +179,7 @@ export class NzAffixComponent implements OnInit, OnDestroy { .join(';'); } - private setAffixStyle(e: Event, affixStyle?: NGStyleInterface): void { + private setAffixStyle(e: Event, affixStyle?: NgStyleInterface): void { const originalAffixStyle = this.affixStyle; const isWindow = this._target === window; if (e.type === 'scroll' && originalAffixStyle && affixStyle && isWindow) { @@ -205,7 +205,7 @@ export class NzAffixComponent implements OnInit, OnDestroy { } } - private setPlaceholderStyle(placeholderStyle?: NGStyleInterface): void { + private setPlaceholderStyle(placeholderStyle?: NgStyleInterface): void { const originalPlaceholderStyle = this.placeholderStyle; if (shallowEqual(placeholderStyle, originalPlaceholderStyle)) { return; diff --git a/components/anchor/nz-anchor.component.ts b/components/anchor/nz-anchor.component.ts index 059a438a24d..775a846b2fa 100644 --- a/components/anchor/nz-anchor.component.ts +++ b/components/anchor/nz-anchor.component.ts @@ -25,7 +25,7 @@ import { import { fromEvent, Subscription } from 'rxjs'; import { distinctUntilChanged, throttleTime } from 'rxjs/operators'; -import { toNumber, InputBoolean, InputNumber, NzScrollService, NGStyleInterface } from 'ng-zorro-antd/core'; +import { toNumber, InputBoolean, InputNumber, NgStyleInterface, NzScrollService } from 'ng-zorro-antd/core'; import { NzAnchorLinkComponent } from './nz-anchor-link.component'; @@ -75,7 +75,7 @@ export class NzAnchorComponent implements OnDestroy, AfterViewInit { @Output() readonly nzScroll = new EventEmitter(); visible = false; - wrapperStyle: NGStyleInterface = { 'max-height': '100vh' }; + wrapperStyle: NgStyleInterface = { 'max-height': '100vh' }; private links: NzAnchorLinkComponent[] = []; private animating = false; diff --git a/components/badge/nz-badge.spec.ts b/components/badge/nz-badge.spec.ts index f4cd86b4179..12fde9c6cc0 100644 --- a/components/badge/nz-badge.spec.ts +++ b/components/badge/nz-badge.spec.ts @@ -3,7 +3,7 @@ import { fakeAsync, tick, ComponentFixture, TestBed } from '@angular/core/testin import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { NGStyleInterface } from 'ng-zorro-antd/core'; +import { NgStyleInterface } from 'ng-zorro-antd/core'; import { NzBadgeComponent } from './nz-badge.component'; import { NzBadgeModule } from './nz-badge.module'; @@ -142,6 +142,6 @@ export class NzTestBadgeBasicComponent { overflow = 20; showZero = false; status: string; - style: NGStyleInterface; + style: NgStyleInterface; text: string; } diff --git a/components/badge/style/index.less b/components/badge/style/index.less index d638e0a15b1..e69de29bb2d 100644 --- a/components/badge/style/index.less +++ b/components/badge/style/index.less @@ -1,191 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; - -@badge-prefix-cls: ~'@{ant-prefix}-badge'; -@number-prefix-cls: ~'@{ant-prefix}-scroll-number'; - -.@{badge-prefix-cls} { - .reset-component; - - position: relative; - display: inline-block; - color: unset; - line-height: 1; - - &-count { - z-index: @zindex-badge; - min-width: @badge-height; - height: @badge-height; - padding: 0 6px; - color: @badge-text-color; - font-weight: @badge-font-weight; - font-size: @badge-font-size; - line-height: @badge-height; - white-space: nowrap; - text-align: center; - background: @highlight-color; - border-radius: @badge-height / 2; - box-shadow: 0 0 0 1px @shadow-color-inverse; - a, - a:hover { - color: @badge-text-color; - } - } - - &-multiple-words { - padding: 0 8px; - } - - &-dot { - z-index: @zindex-badge; - width: @badge-dot-size; - height: @badge-dot-size; - background: @highlight-color; - border-radius: 100%; - box-shadow: 0 0 0 1px @shadow-color-inverse; - } - - &-count, - &-dot, - .@{number-prefix-cls}-custom-component { - position: absolute; - top: 0; - right: 0; - transform: translate(50%, -50%); - transform-origin: 100% 0%; - } - - &-status { - line-height: inherit; - vertical-align: baseline; - - &-dot { - position: relative; - top: -1px; - display: inline-block; - width: @badge-status-size; - height: @badge-status-size; - vertical-align: middle; - border-radius: 50%; - } - &-success { - background-color: @success-color; - } - &-processing { - position: relative; - background-color: @processing-color; - &::after { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border: 1px solid @processing-color; - border-radius: 50%; - animation: antStatusProcessing 1.2s infinite ease-in-out; - content: ''; - } - } - &-default { - background-color: @normal-color; - } - &-error { - background-color: @error-color; - } - &-warning { - background-color: @warning-color; - } - - // mixin to iterate over colors and create CSS class for each one - .make-color-classes(@i: length(@preset-colors)) when (@i > 0) { - .make-color-classes(@i - 1); - @color: extract(@preset-colors, @i); - @darkColor: '@{color}-6'; - &-@{color} { - background: @@darkColor; - } - } - .make-color-classes(); - - &-text { - margin-left: 8px; - color: @text-color; - font-size: @font-size-base; - } - } - - &-zoom-appear, - &-zoom-enter { - animation: antZoomBadgeIn 0.3s @ease-out-back; - animation-fill-mode: both; - } - - &-zoom-leave { - animation: antZoomBadgeOut 0.3s @ease-in-back; - animation-fill-mode: both; - } - - &-not-a-wrapper { - &:not(.@{badge-prefix-cls}-status) { - vertical-align: middle; - } - - .@{ant-prefix}-scroll-number { - position: relative; - top: auto; - display: block; - } - - .@{badge-prefix-cls}-count { - transform: none; - } - } -} - -@keyframes antStatusProcessing { - 0% { - transform: scale(0.8); - opacity: 0.5; - } - 100% { - transform: scale(2.4); - opacity: 0; - } -} - -.@{number-prefix-cls} { - overflow: hidden; - &-only { - display: inline-block; - height: @badge-height; - transition: all 0.3s @ease-in-out; - > p { - height: @badge-height; - margin: 0; - } - } - - &-symbol { - vertical-align: top; - } -} - -@keyframes antZoomBadgeIn { - 0% { - transform: scale(0) translate(50%, -50%); - opacity: 0; - } - 100% { - transform: scale(1) translate(50%, -50%); - } -} - -@keyframes antZoomBadgeOut { - 0% { - transform: scale(1) translate(50%, -50%); - } - 100% { - transform: scale(0) translate(50%, -50%); - opacity: 0; - } -} diff --git a/components/button/demo/multiple.ts b/components/button/demo/multiple.ts index 75f6eb73a56..7752a24c1d8 100644 --- a/components/button/demo/multiple.ts +++ b/components/button/demo/multiple.ts @@ -5,8 +5,8 @@ import { Component } from '@angular/core'; template: ` - - + +
  • 1st item @@ -18,7 +18,7 @@ import { Component } from '@angular/core'; 3rd item
-
+ `, styles: [ ` diff --git a/components/core/dropdown/nz-dropdown-service.resolver.ts b/components/core/dropdown/nz-dropdown-service.resolver.ts index c48579edd7e..613c15032bc 100644 --- a/components/core/dropdown/nz-dropdown-service.resolver.ts +++ b/components/core/dropdown/nz-dropdown-service.resolver.ts @@ -10,4 +10,4 @@ import { InjectionToken } from '@angular/core'; import { NzMenuBaseService } from './nz-menu-base.service'; -export const NzDropdownHigherOrderServiceToken = new InjectionToken('NzTreeHigherOrder'); +export const NzDropdownHigherOrderServiceToken = new InjectionToken('NzMenuHigherOrder'); diff --git a/components/core/dropdown/nz-menu-base.service.ts b/components/core/dropdown/nz-menu-base.service.ts index 333f84dc23b..0d6310c2432 100644 --- a/components/core/dropdown/nz-menu-base.service.ts +++ b/components/core/dropdown/nz-menu-base.service.ts @@ -7,7 +7,7 @@ */ import { Injectable } from '@angular/core'; -import { merge, BehaviorSubject, Subject } from 'rxjs'; +import { BehaviorSubject, Subject } from 'rxjs'; import { NzDirectionVHIType } from '../types'; @@ -18,7 +18,6 @@ export class NzMenuBaseService { theme$ = new Subject(); mode$ = new BehaviorSubject('vertical'); inlineIndent$ = new BehaviorSubject(24); - check$ = merge(this.theme$, this.mode$, this.inlineIndent$); theme: 'light' | 'dark' = 'light'; mode: NzDirectionVHIType = 'vertical'; inlineIndent = 24; diff --git a/components/core/overlay/overlay-position.ts b/components/core/overlay/overlay-position.ts index 5cfee18b4af..f46887270ae 100644 --- a/components/core/overlay/overlay-position.ts +++ b/components/core/overlay/overlay-position.ts @@ -74,8 +74,8 @@ export const DEFAULT_TOOLTIP_POSITIONS = [POSITION_MAP.top, POSITION_MAP.right, export const DEFAULT_DROPDOWN_POSITIONS = [ POSITION_MAP.bottomLeft, POSITION_MAP.bottomRight, - POSITION_MAP.topLeft, - POSITION_MAP.topRight + POSITION_MAP.topRight, + POSITION_MAP.topLeft ]; export const DEFAULT_SUBMENU_POSITIONS = [POSITION_MAP.rightTop, POSITION_MAP.leftTop]; export const DEFAULT_CASCADER_POSITIONS = [ diff --git a/components/core/services/update-host-class.service.ts b/components/core/services/update-host-class.service.ts index 0fdeb5a2b08..404424090b5 100644 --- a/components/core/services/update-host-class.service.ts +++ b/components/core/services/update-host-class.service.ts @@ -13,7 +13,7 @@ import { NgClassInterface } from '../types/ng-class'; @Injectable() export class NzUpdateHostClassService { private classMap = {}; - private renderer: Renderer2; + readonly renderer: Renderer2; updateHostClass(el: HTMLElement, classMap: object): void { this.removeClass(el, this.classMap, this.renderer); diff --git a/components/core/types/ng-class.ts b/components/core/types/ng-class.ts index c31599bb8ea..0cc1d6cf922 100644 --- a/components/core/types/ng-class.ts +++ b/components/core/types/ng-class.ts @@ -13,6 +13,6 @@ export interface NgClassInterface { [klass: string]: any; // tslint:disable-line:no-any } -export interface NGStyleInterface { +export interface NgStyleInterface { [klass: string]: any; // tslint:disable-line:no-any } diff --git a/components/date-picker/date-picker.component.spec.ts b/components/date-picker/date-picker.component.spec.ts index 4fda2d05f92..41fa3865309 100644 --- a/components/date-picker/date-picker.component.spec.ts +++ b/components/date-picker/date-picker.component.spec.ts @@ -9,7 +9,7 @@ import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import isSameDay from 'date-fns/is_same_day'; -import { dispatchKeyboardEvent, dispatchMouseEvent, NGStyleInterface } from 'ng-zorro-antd/core'; +import { dispatchKeyboardEvent, dispatchMouseEvent, NgStyleInterface } from 'ng-zorro-antd/core'; import en_US from '../i18n/languages/en_US'; import { NzI18nModule, NzI18nService } from 'ng-zorro-antd/i18n'; @@ -846,10 +846,10 @@ class NzTestDatePickerComponent { nzDisabledDate: (d: Date) => boolean; nzLocale: any; // tslint:disable-line:no-any nzPlaceHolder: string; - nzPopupStyle: NGStyleInterface; + nzPopupStyle: NgStyleInterface; nzDropdownClassName: string; nzSize: string; - nzStyle: NGStyleInterface; + nzStyle: NgStyleInterface; nzOnChange(): void {} nzOnCalendarChange(): void {} diff --git a/components/date-picker/month-picker.component.spec.ts b/components/date-picker/month-picker.component.spec.ts index 21e4f8cdac5..568d19b9c5a 100644 --- a/components/date-picker/month-picker.component.spec.ts +++ b/components/date-picker/month-picker.component.spec.ts @@ -8,7 +8,7 @@ import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import isBefore from 'date-fns/is_before'; -import { dispatchMouseEvent, NGStyleInterface } from 'ng-zorro-antd/core'; +import { dispatchMouseEvent, NgStyleInterface } from 'ng-zorro-antd/core'; import { NzInputModule } from 'ng-zorro-antd/input'; import { NzDatePickerModule } from './date-picker.module'; @@ -472,10 +472,10 @@ class NzTestMonthPickerComponent { nzDisabledDate: (d: Date) => boolean; nzLocale: any; // tslint:disable-line:no-any nzPlaceHolder: string; - nzPopupStyle: NGStyleInterface; + nzPopupStyle: NgStyleInterface; nzDropdownClassName: string; nzSize: string; - nzStyle: NGStyleInterface; + nzStyle: NgStyleInterface; nzOnOpenChange(): void {} diff --git a/components/date-picker/range-picker.component.spec.ts b/components/date-picker/range-picker.component.spec.ts index ff0736cb17a..0d1ab13d461 100644 --- a/components/date-picker/range-picker.component.spec.ts +++ b/components/date-picker/range-picker.component.spec.ts @@ -9,7 +9,7 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import differenceInDays from 'date-fns/difference_in_days'; import isSameDay from 'date-fns/is_same_day'; -import { dispatchMouseEvent, NGStyleInterface } from 'ng-zorro-antd/core'; +import { dispatchMouseEvent, NgStyleInterface } from 'ng-zorro-antd/core'; import { NzDatePickerModule } from './date-picker.module'; import { CandyDate } from './lib/candy-date/candy-date'; @@ -785,10 +785,10 @@ class NzTestRangePickerComponent { nzDisabledDate: (d: Date) => boolean; nzLocale: any; // tslint:disable-line:no-any nzPlaceHolder: string[]; - nzPopupStyle: NGStyleInterface; + nzPopupStyle: NgStyleInterface; nzDropdownClassName: string; nzSize: string; - nzStyle: NGStyleInterface; + nzStyle: NgStyleInterface; nzOnOpenChange(): void {} modelValue: Array; modelValueChange(): void {} diff --git a/components/date-picker/year-picker.component.spec.ts b/components/date-picker/year-picker.component.spec.ts index 358d6f8b836..eb932cca4a8 100644 --- a/components/date-picker/year-picker.component.spec.ts +++ b/components/date-picker/year-picker.component.spec.ts @@ -5,7 +5,7 @@ import { FormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { dispatchMouseEvent, NGStyleInterface } from 'ng-zorro-antd/core'; +import { dispatchMouseEvent, NgStyleInterface } from 'ng-zorro-antd/core'; import { NzInputModule } from 'ng-zorro-antd/input'; import { NzDatePickerModule } from './date-picker.module'; @@ -431,10 +431,10 @@ class NzTestYearPickerComponent { nzDisabledDate: (d: Date) => boolean; nzLocale: any; // tslint:disable-line:no-any nzPlaceHolder: string; - nzPopupStyle: NGStyleInterface; + nzPopupStyle: NgStyleInterface; nzDropdownClassName: string; nzSize: string; - nzStyle: NGStyleInterface; + nzStyle: NgStyleInterface; nzOnOpenChange(): void {} diff --git a/components/dropdown/demo/basic.ts b/components/dropdown/demo/basic.ts index 322529e95ec..a4cfc378b8d 100644 --- a/components/dropdown/demo/basic.ts +++ b/components/dropdown/demo/basic.ts @@ -3,21 +3,17 @@ import { Component } from '@angular/core'; @Component({ selector: 'nz-demo-dropdown-basic', template: ` - - Hover me + + Hover me + + + - - `, - styles: [] + + ` }) export class NzDemoDropdownBasicComponent {} diff --git a/components/dropdown/demo/context-menu.ts b/components/dropdown/demo/context-menu.ts index 969dc90b78d..3abb3729471 100644 --- a/components/dropdown/demo/context-menu.ts +++ b/components/dropdown/demo/context-menu.ts @@ -1,50 +1,57 @@ -import { Component, TemplateRef } from '@angular/core'; -import { NzDropdownContextComponent, NzDropdownService, NzMenuItemDirective } from 'ng-zorro-antd'; +import { Component } from '@angular/core'; +import { NzContextMenuService, NzDropdownMenuComponent } from 'ng-zorro-antd'; @Component({ selector: 'nz-demo-dropdown-context-menu', template: ` -
- -
    -
  • 1st menu item
  • -
  • 2nd menu item
  • -
  • disabled menu item
  • -
  • - sub menu -
      -
    • 3rd menu item
    • -
    • 4th menu item
    • -
    -
  • -
  • - disabled sub menu -
      -
    • 3rd menu item
    • -
    • 4th menu item
    • -
    -
  • -
-
- Context Menu +
+ Context Menu
+ +
    +
  • 1st menu item
  • +
  • 2nd menu item
  • +
  • disabled menu item
  • +
  • + sub menu +
      +
    • 3rd menu item
    • +
    • 4th menu item
    • +
    +
  • +
  • + disabled sub menu +
      +
    • 3rd menu item
    • +
    • 4th menu item
    • +
    +
  • +
+
`, - styles: [] + styles: [ + ` + .context-area { + background: rgb(190, 200, 200); + padding: 32px; + text-align: center; + } + + .context-intro { + color: #fff; + font-size: 14px; + } + ` + ] }) export class NzDemoDropdownContextMenuComponent { - private dropdown: NzDropdownContextComponent; - - contextMenu($event: MouseEvent, template: TemplateRef): void { - this.dropdown = this.nzDropdownService.create($event, template); + contextMenu($event: MouseEvent, menu: NzDropdownMenuComponent): void { + this.nzContextMenuService.create($event, menu); } - close(e: NzMenuItemDirective): void { - console.log(e); - this.dropdown.close(); + closeMenu(): void { + this.nzContextMenuService.close(); } - constructor(private nzDropdownService: NzDropdownService) {} + constructor(private nzContextMenuService: NzContextMenuService) {} } diff --git a/components/dropdown/demo/dropdown-button.ts b/components/dropdown/demo/dropdown-button.ts index 62c810023a9..ff409714382 100644 --- a/components/dropdown/demo/dropdown-button.ts +++ b/components/dropdown/demo/dropdown-button.ts @@ -4,53 +4,61 @@ import { Component } from '@angular/core'; selector: 'nz-demo-dropdown-dropdown-button', template: `
- - DropDown -
    -
  • 1st menu item
  • -
  • 2nd menu item
  • -
  • - sub menu -
      -
    • 3rd menu item
    • -
    • 4th menu item
    • -
    -
  • -
-
- - DropDown - - - - - - + + + + + + + + + + + + +
+ +
    +
  • menu1 1st menu item
  • +
  • menu1 2nd menu item
  • +
  • menu1 3rd menu item
  • +
+
+ +
    +
  • menu2 1st menu item
  • +
  • menu2 2nd menu item
  • +
  • menu2 3rd menu item
  • +
+
+ +
    +
  • menu3 1st menu item
  • +
  • menu3 2nd menu item
  • +
  • menu3 3rd menu item
  • +
+
+ +
    +
  • menu4 1st menu item
  • +
  • menu4 2nd menu item
  • +
  • menu4 3rd menu item
  • +
+
`, styles: [ ` - nz-dropdown-button { + nz-button-group { margin-right: 8px; } ` diff --git a/components/dropdown/demo/event.ts b/components/dropdown/demo/event.ts index 45e3d616091..2ed7e9c58f5 100644 --- a/components/dropdown/demo/event.ts +++ b/components/dropdown/demo/event.ts @@ -3,16 +3,18 @@ import { Component } from '@angular/core'; @Component({ selector: 'nz-demo-dropdown-event', template: ` - - Hover me, Click menu item + + Hover me, Click menu item + + +
  • 1st menu item
  • 2nd menu item
  • 3rd menu item
-
- `, - styles: [] + + ` }) export class NzDemoDropdownEventComponent { log(data: string): void { diff --git a/components/dropdown/demo/item.ts b/components/dropdown/demo/item.ts index 8708cd1d23a..8c0307be2e7 100644 --- a/components/dropdown/demo/item.ts +++ b/components/dropdown/demo/item.ts @@ -3,20 +3,18 @@ import { Component } from '@angular/core'; @Component({ selector: 'nz-demo-dropdown-item', template: ` - - Hover me + + Hover me + + + - - `, - styles: [] + + ` }) export class NzDemoDropdownItemComponent {} diff --git a/components/dropdown/demo/overlay-visible.ts b/components/dropdown/demo/overlay-visible.ts index 5090c0d9cf4..a9e3211273a 100644 --- a/components/dropdown/demo/overlay-visible.ts +++ b/components/dropdown/demo/overlay-visible.ts @@ -3,14 +3,17 @@ import { Component } from '@angular/core'; @Component({ selector: 'nz-demo-dropdown-overlay-visible', template: ` - - Hover me + + Hover me + + +
  • Clicking me will not close the menu.
  • Clicking me will not close the menu also.
  • Clicking me will close the menu
-
+ `, styles: [] }) diff --git a/components/dropdown/demo/placement.ts b/components/dropdown/demo/placement.ts index 1fb59fc7254..2f91b433eb2 100644 --- a/components/dropdown/demo/placement.ts +++ b/components/dropdown/demo/placement.ts @@ -4,90 +4,16 @@ import { Component } from '@angular/core'; selector: 'nz-demo-dropdown-placement', template: `
- - - - - - - - - - - - - - - - - - - - - - - - + + + +
    +
  • 1st menu item length
  • +
  • 2nd menu item length
  • +
  • 3rd menu item length
  • +
+
+
`, styles: [ @@ -99,4 +25,6 @@ import { Component } from '@angular/core'; ` ] }) -export class NzDemoDropdownPlacementComponent {} +export class NzDemoDropdownPlacementComponent { + listOfPosition = ['bottomLeft', 'bottomCenter', 'bottomRight', 'topLeft', 'topCenter', 'topRight']; +} diff --git a/components/dropdown/demo/sub-menu.ts b/components/dropdown/demo/sub-menu.ts index 718692ab210..ba742b8c230 100644 --- a/components/dropdown/demo/sub-menu.ts +++ b/components/dropdown/demo/sub-menu.ts @@ -3,8 +3,11 @@ import { Component } from '@angular/core'; @Component({ selector: 'nz-demo-dropdown-sub-menu', template: ` - - Cascading menu + + Cascading menu + + +
  • 1st menu item
  • 2nd menu item
  • @@ -23,7 +26,7 @@ import { Component } from '@angular/core';
-
+ `, styles: [] }) diff --git a/components/dropdown/demo/trigger.ts b/components/dropdown/demo/trigger.ts index b9af1fc796c..7a6b891df69 100644 --- a/components/dropdown/demo/trigger.ts +++ b/components/dropdown/demo/trigger.ts @@ -3,8 +3,11 @@ import { Component } from '@angular/core'; @Component({ selector: 'nz-demo-dropdown-trigger', template: ` - - Click me + + Click me + + +
  • 1st menu item
  • 2nd menu item
  • @@ -25,8 +28,7 @@ import { Component } from '@angular/core';
-
- `, - styles: [] + + ` }) export class NzDemoDropdownTriggerComponent {} diff --git a/components/dropdown/doc/index.en-US.md b/components/dropdown/doc/index.en-US.md index 302ead3e3c0..307e99a4d5f 100755 --- a/components/dropdown/doc/index.en-US.md +++ b/components/dropdown/doc/index.en-US.md @@ -20,12 +20,11 @@ You can get more detail [here](/docs/getting-started/en#import-a-component-indiv import { NzDropDownModule } from 'ng-zorro-antd'; ``` -### nz-dropdown - -> You should add `[nz-dropdown]` to the element that trigger dropdown +### [nz-dropdown] | Property | Description | Type | Default | | -------- | ----------- | ---- | ------- | +| `[nzDropdownMenu]` | Dropdown menu | `NzDropdownMenuComponent` | - | | `[nzDisabled]` | whether the dropdown menu is disabled | `boolean` | - | | `[nzPlacement]` | placement of pop menu | `'bottomLeft'|'bottomCenter'|'bottomRight'|'topLeft'|'topCenter'|'topRight'` | `'bottomLeft'` | | `[nzTrigger]` | the trigger mode which executes the drop-down action | `'click'|'hover'` | `'hover'` | @@ -39,31 +38,28 @@ You should use [nz-menu](/components/menu/en) in `nz-dropdown`. The menu items a > nz-menu of nz-dropdown is unselectable by default, you can make it selectable via `
    `. -### [nz-dropdown] - -mark the element that trigger dropdown +### nz-dropdown-menu -### nz-dropdown-button +Wrap Dropdown Menu and pass to `[nz-dropdown]` 和 `NzContextMenuService`, you can export it via Template Syntax `nzDropdownMenu` -| Property | Description | Type | Default | -| -------- | ----------- | ---- | ------- | -| `[nzDisabled]` | whether the dropdown menu is disabled | `boolean` | - | -| `[nzPlacement]` | placement of pop menu | `'bottomLeft'|'bottomCenter'|'bottomRight'|'topLeft'|'topCenter'|'topRight'` | `'bottomLeft'` | -| `[nzSize]` | size of the button, the same as [nz-buutton](/components/button/en) | `'large'|'small'|'default'` | `'default'` | -| `[nzType]` | type of the button, the same as [nz-button](/components/button/en) | `'primary'|'ghost'|'dashed'|'danger'|'default'` | `'default'` | -| `[nzTrigger]` | the trigger mode which executes the drop-down action | `'click'|'hover'` | `'hover'` | -| `[nzClickHide]` | whether hide menu when click | `boolean` | `true` | -| `[nzVisible]` | whether the dropdown menu is visible | `boolean` | - | -| `[nzIcon]` | icon of right side | `string|TemplateRef` | `'ellipsis'` | -| `(nzVisibleChange)` | a callback function takes an argument: `nzVisible`, is executed when the visible state is changed | `EventEmitter` | - | -| `(nzClick)` | a callback function which will be executed when you click the button on the left | `EventEmitter` | - | +> Note:Every `[nz-dropdown]` should pass independent `nz-dropdown-menu`. +```html +Hover me + +
      +
    • 1st menu item
    • +
    • 2nd menu item
    • +
    • 3rd menu item
    • +
    +
    +``` -### NzDropdownService +### NzContextMenuService Create dropdown with contextmenu, the detail can be found in the example above | Property | Description | Arguments | Return Value | | --- | --- | --- | --- | -| create | create dropdown | `($event:MouseEvent, template:TemplateRef)` | `NzDropdownContextComponent` | +| create | create dropdown | `($event:MouseEvent, menu:NzDropdownMenuComponent)` | - | | close | close dropdown | - | - | \ No newline at end of file diff --git a/components/dropdown/doc/index.zh-CN.md b/components/dropdown/doc/index.zh-CN.md index f215b271936..d0abaca35ab 100755 --- a/components/dropdown/doc/index.zh-CN.md +++ b/components/dropdown/doc/index.zh-CN.md @@ -21,12 +21,11 @@ title: Dropdown import { NzDropDownModule } from 'ng-zorro-antd'; ``` -### nz-dropdown - -> 需要在触发下拉菜单的元素上加入 `[nz-dropdown]` 标记用于定位元素位置 +### [nz-dropdown] | 参数 | 说明 | 类型 | 默认值 | | --- | --- | --- | --- | +| `[nzDropdownMenu]` | Dropdown 下拉菜单组件 | `NzDropdownMenuComponent` | - | | `[nzDisabled]` | 菜单是否禁用 | `boolean` | - | | `[nzPlacement]` | 菜单弹出位置 | `'bottomLeft'|'bottomCenter'|'bottomRight'|'topLeft'|'topCenter'|'topRight'` | `'bottomLeft'` | | `[nzTrigger]` | 触发下拉的行为 | `'click'|'hover'` | `'hover'` | @@ -40,30 +39,28 @@ import { NzDropDownModule } from 'ng-zorro-antd'; > nz-dropdown 下的 nz-menu 默认不可选中。如果需要菜单可选中,可以指定 `
      `. -### [nz-dropdown] +### nz-dropdown-menu -用于标定下拉菜单定位元素 +用于包裹菜单项,可以通过 `nzDropdownMenu` 模板变量导出后传入 `[nz-dropdown]` 和 `NzContextMenuService`。 -### nz-dropdown-button +> 注意:每个 `nz-dropdown-menu` 只能作为一个 `[nz-dropdown]` 的输入项 -| 参数 | 说明 | 类型 | 默认值 | -| --- | --- | --- | --- | -| `[nzDisabled]` | 菜单是否禁用 | `boolean` | - | -| `[nzPlacement]` | 菜单弹出位置 | `'bottomLeft'|'bottomCenter'|'bottomRight'|'topLeft'|'topCenter'|'topRight'` | `'bottomLeft'` | -| `[nzSize]` | 按钮大小,和 [nz-button](/components/button/zh) 一致 | `'large'|'small'|'default'` | `'default'` | -| `[nzType]` | 按钮类型,和 [nz-button](/components/button/zh) 一致 | `'primary'|'ghost'|'dashed'|'danger'|'default'` | `'default'` | -| `[nzTrigger]` | 触发下拉的行为 | `'click'|'hover'` | `'hover'` | -| `[nzClickHide]` | 点击后是否隐藏菜单 | `boolean` | `true` | -| `[nzVisible]` | 菜单是否显示 | `boolean` | - | -| `[nzIcon]` | 右侧的 icon | `string|TemplateRef` | `'ellipsis'` | -| `(nzVisibleChange)` | 菜单显示状态改变时调用,参数为 nzVisible | `EventEmitter` | - | -| `(nzClick)` | 点击左侧按钮的回调 | `EventEmitter` | - | +```html +Hover me + +
        +
      • 1st menu item
      • +
      • 2nd menu item
      • +
      • 3rd menu item
      • +
      +
      +``` -### NzDropdownService +### NzContextMenuService 用于右键弹出下拉菜单,具体参见示例 | 参数 | 说明 | 参数 | 返回 | | --- | --- | --- | --- | -| create | 创建右键菜单 | `($event:MouseEvent, template:TemplateRef)` | `NzDropdownContextComponent` | +| create | 创建右键菜单 | `($event:MouseEvent, menu:NzDropdownMenuComponent)` | - | | close | 关闭右键菜单 | - | - | \ No newline at end of file diff --git a/components/dropdown/nz-context-menu.service.spec.ts b/components/dropdown/nz-context-menu.service.spec.ts new file mode 100644 index 00000000000..8a81d7de410 --- /dev/null +++ b/components/dropdown/nz-context-menu.service.spec.ts @@ -0,0 +1,139 @@ +import { OverlayContainer, ScrollDispatcher } from '@angular/cdk/overlay'; +import { Component, Provider, Type, ViewChild } from '@angular/core'; +import { fakeAsync, inject, tick, ComponentFixture, TestBed } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +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'; + +describe('context-menu', () => { + let overlayContainer: OverlayContainer; + let overlayContainerElement: HTMLElement; + + function createComponent( + component: Type, + providers: Provider[] = [], + // tslint:disable-next-line:no-any + declarations: any[] = [] + ): ComponentFixture { + TestBed.configureTestingModule({ + imports: [NzDropDownModule, NzMenuModule, NoopAnimationsModule], + declarations: [component, ...declarations], + providers + }) + .compileComponents() + .then(); + + inject([OverlayContainer], (oc: OverlayContainer) => { + overlayContainer = oc; + overlayContainerElement = oc.getContainerElement(); + })(); + + return TestBed.createComponent(component); + } + + afterEach(inject([OverlayContainer], (currentOverlayContainer: OverlayContainer) => { + currentOverlayContainer.ngOnDestroy(); + overlayContainer.ngOnDestroy(); + })); + it('should create dropdown', fakeAsync(() => { + const fixture = createComponent(NzTestDropdownContextMenuComponent, [], []); + fixture.detectChanges(); + expect(overlayContainerElement.textContent).toBe(''); + fixture.detectChanges(); + expect(() => { + const fakeEvent = createMouseEvent('contextmenu', 300, 300); + const component = fixture.componentInstance; + component.nzContextMenuService.create(fakeEvent, component.nzDropdownMenuComponent); + fixture.detectChanges(); + tick(1000); + fixture.detectChanges(); + expect(overlayContainerElement.textContent).toContain('1st menu item'); + }).not.toThrowError(); + })); + it('should only one dropdown exist', fakeAsync(() => { + const fixture = createComponent(NzTestDropdownContextMenuComponent, [], []); + fixture.detectChanges(); + expect(overlayContainerElement.textContent).toBe(''); + fixture.detectChanges(); + expect(() => { + let fakeEvent = createMouseEvent('contextmenu', 0, 0); + const component = fixture.componentInstance; + component.nzContextMenuService.create(fakeEvent, component.nzDropdownMenuComponent); + fixture.detectChanges(); + tick(1000); + fixture.detectChanges(); + expect(overlayContainerElement.textContent).toContain('1st menu item'); + fakeEvent = createMouseEvent('contextmenu', window.innerWidth, window.innerHeight); + component.nzContextMenuService.create(fakeEvent, component.nzDropdownMenuComponent); + fixture.detectChanges(); + tick(1000); + fixture.detectChanges(); + expect(overlayContainerElement.textContent).toContain('1st menu item'); + }).not.toThrowError(); + })); + it('should dropdown close when scroll', fakeAsync(() => { + const scrolledSubject = new Subject(); + const fixture = createComponent( + NzTestDropdownContextMenuComponent, + [{ provide: ScrollDispatcher, useFactory: () => ({ scrolled: () => scrolledSubject }) }], + [] + ); + fixture.detectChanges(); + expect(overlayContainerElement.textContent).toBe(''); + expect(() => { + const fakeEvent = createMouseEvent('contextmenu', 0, 0); + const component = fixture.componentInstance; + component.nzContextMenuService.create(fakeEvent, component.nzDropdownMenuComponent); + fixture.detectChanges(); + tick(1000); + fixture.detectChanges(); + expect(overlayContainerElement.textContent).toContain('1st menu item'); + scrolledSubject.next(); + fixture.detectChanges(); + tick(1000); + fixture.detectChanges(); + expect(overlayContainerElement.textContent).toBe(''); + }).not.toThrowError(); + })); + it('should backdrop work with click', fakeAsync(() => { + const fixture = createComponent(NzTestDropdownContextMenuComponent, [], []); + fixture.detectChanges(); + expect(overlayContainerElement.textContent).toBe(''); + fixture.detectChanges(); + expect(() => { + const fakeEvent = createMouseEvent('contextmenu', 300, 300); + const component = fixture.componentInstance; + component.nzContextMenuService.create(fakeEvent, component.nzDropdownMenuComponent); + fixture.detectChanges(); + tick(1000); + fixture.detectChanges(); + expect(overlayContainerElement.textContent).toContain('1st menu item'); + document.body.click(); + fixture.detectChanges(); + tick(1000); + fixture.detectChanges(); + expect(overlayContainerElement.textContent).toBe(''); + }).not.toThrowError(); + })); +}); + +@Component({ + template: ` + +
        +
      • 1st menu item
      • +
      • 2nd menu item
      • +
      • 3rd menu item
      • +
      +
      + ` +}) +export class NzTestDropdownContextMenuComponent { + @ViewChild(NzDropdownMenuComponent, { static: true }) nzDropdownMenuComponent: NzDropdownMenuComponent; + + constructor(public nzContextMenuService: NzContextMenuService) {} +} diff --git a/components/dropdown/nz-context-menu.service.ts b/components/dropdown/nz-context-menu.service.ts new file mode 100644 index 00000000000..ec701aa5936 --- /dev/null +++ b/components/dropdown/nz-context-menu.service.ts @@ -0,0 +1,127 @@ +/** + * @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 { NzDropdownMenuComponent } from './nz-dropdown-menu.component'; + +@Injectable() +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 { + 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-button.component.ts b/components/dropdown/nz-dropdown-button.component.ts index e3946bd16ab..289230c5a0a 100644 --- a/components/dropdown/nz-dropdown-button.component.ts +++ b/components/dropdown/nz-dropdown-button.component.ts @@ -25,7 +25,12 @@ import { ViewEncapsulation } from '@angular/core'; -import { slideMotion, NzDropdownHigherOrderServiceToken, NzNoAnimationDirective } from 'ng-zorro-antd/core'; +import { + slideMotion, + warnDeprecation, + NzDropdownHigherOrderServiceToken, + NzNoAnimationDirective +} from 'ng-zorro-antd/core'; import { menuServiceFactory, NzDropDownComponent } from './nz-dropdown.component'; import { NzDropDownDirective } from './nz-dropdown.directive'; @@ -54,7 +59,7 @@ import { NzMenuDropdownService } from './nz-menu-dropdown.service'; display: inline-block; } - .ant-dropdown { + :root .ant-dropdown { top: 100%; left: 0; position: relative; @@ -65,6 +70,9 @@ import { NzMenuDropdownService } from './nz-menu-dropdown.service'; ` ] }) +/** + * @deprecated Use `NzDropdownDirective` instead, will remove in 9.0.0. + */ export class NzDropDownButtonComponent extends NzDropDownComponent implements OnDestroy, AfterContentInit, OnChanges { @Input() nzSize = 'default'; @Input() nzType = 'default'; @@ -78,6 +86,9 @@ export class NzDropDownButtonComponent extends NzDropDownComponent implements On @Host() @Optional() public noAnimation?: NzNoAnimationDirective ) { super(cdr, nzMenuDropdownService, noAnimation); + warnDeprecation( + `'nz-dropdown-button' Component is going to be removed in 9.0.0. Please use 'nz-dropdown-menu' instead. Read https://ng.ant.design/components/dropdown/en` + ); } /** rewrite afterViewInit hook */ diff --git a/components/dropdown/nz-dropdown-context.component.ts b/components/dropdown/nz-dropdown-context.component.ts index 4569ffded25..5e121708084 100644 --- a/components/dropdown/nz-dropdown-context.component.ts +++ b/components/dropdown/nz-dropdown-context.component.ts @@ -15,11 +15,9 @@ import { TemplateRef, ViewEncapsulation } from '@angular/core'; +import { slideMotion } from 'ng-zorro-antd/core'; import { Observable, Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; - -import { slideMotion } from 'ng-zorro-antd/core'; - import { NzDropdownService } from './nz-dropdown.service'; import { NzMenuDropdownService } from './nz-menu-dropdown.service'; @@ -49,6 +47,9 @@ import { NzMenuDropdownService } from './nz-menu-dropdown.service'; ` ] }) +/** + * @deprecated Use `NzDropdownMenuComponent` instead, will remove in 9.0.0. + */ export class NzDropdownContextComponent implements OnDestroy { open = true; templateRef: TemplateRef; diff --git a/components/dropdown/nz-dropdown-menu.component.html b/components/dropdown/nz-dropdown-menu.component.html new file mode 100644 index 00000000000..6a36e98c682 --- /dev/null +++ b/components/dropdown/nz-dropdown-menu.component.html @@ -0,0 +1,13 @@ + +
      +
      + +
      +
      +
      \ No newline at end of file diff --git a/components/dropdown/nz-dropdown-menu.component.ts b/components/dropdown/nz-dropdown-menu.component.ts new file mode 100644 index 00000000000..e85641fcf5b --- /dev/null +++ b/components/dropdown/nz-dropdown-menu.component.ts @@ -0,0 +1,106 @@ +/** + * @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, + ElementRef, + Host, + Injector, + Optional, + Renderer2, + Self, + TemplateRef, + ViewChild, + ViewContainerRef, + ViewEncapsulation +} from '@angular/core'; +import { + slideMotion, + NzDropdownHigherOrderServiceToken, + NzMenuBaseService, + NzNoAnimationDirective +} from 'ng-zorro-antd/core'; + +import { Subject } from 'rxjs'; +import { NzMenuDropdownService } from './nz-menu-dropdown.service'; + +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, + { + provide: NzDropdownHigherOrderServiceToken, + useFactory: dropdownMenuServiceFactory, + deps: [[new Self(), Injector]] + } + ], + styles: [ + ` + :root .ant-dropdown.nz-dropdown { + top: 0; + left: 0; + position: relative; + width: 100%; + margin-top: 4px; + margin-bottom: 4px; + } + ` + ], + 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; + + setVisibleStateWhen(visible: boolean, trigger: 'click' | 'hover' | 'all' = 'all'): void { + if (this.nzTrigger === trigger || trigger === 'all') { + this.visible$.next(visible); + } + } + + setValue(key: T, value: this[T]): void { + this[key] = value; + this.cdr.markForCheck(); + } + + constructor( + private cdr: ChangeDetectorRef, + private elementRef: ElementRef, + private renderer: Renderer2, + public viewContainerRef: ViewContainerRef, + public nzMenuDropdownService: NzMenuDropdownService, + @Host() @Optional() public noAnimation?: NzNoAnimationDirective + ) {} + + ngAfterContentInit(): void { + this.renderer.removeChild(this.renderer.parentNode(this.elementRef.nativeElement), this.elementRef.nativeElement); + } +} diff --git a/components/dropdown/nz-dropdown.component.ts b/components/dropdown/nz-dropdown.component.ts index e02d77f311c..54b0edd391e 100644 --- a/components/dropdown/nz-dropdown.component.ts +++ b/components/dropdown/nz-dropdown.component.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -import { CdkConnectedOverlay, ConnectedOverlayPositionChange, ConnectionPositionPair } from '@angular/cdk/overlay'; +import { ConnectedOverlayPositionChange, ConnectionPositionPair } from '@angular/cdk/overlay'; import { AfterContentInit, ChangeDetectionStrategy, @@ -23,7 +23,6 @@ import { Output, Self, SimpleChanges, - ViewChild, ViewEncapsulation } from '@angular/core'; @@ -32,6 +31,7 @@ import { debounceTime, distinctUntilChanged, map, mapTo, takeUntil } from 'rxjs/ import { slideMotion, + warnDeprecation, DEFAULT_DROPDOWN_POSITIONS, InputBoolean, NzDropdownHigherOrderServiceToken, @@ -39,9 +39,9 @@ import { NzNoAnimationDirective, POSITION_MAP } from 'ng-zorro-antd/core'; -import { NzMenuDirective } from 'ng-zorro-antd/menu'; import { NzDropDownDirective } from './nz-dropdown.directive'; import { NzMenuDropdownService } from './nz-menu-dropdown.service'; + export type NzPlacement = 'bottomLeft' | 'bottomCenter' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight'; export function menuServiceFactory(injector: Injector): NzMenuBaseService { @@ -77,6 +77,9 @@ export function menuServiceFactory(injector: Injector): NzMenuBaseService { ` ] }) +/** + * @deprecated Use `NzDropdownDirective` instead, will remove in 9.0.0. + */ export class NzDropDownComponent implements OnDestroy, AfterContentInit, OnChanges { triggerWidth = 0; dropDownPosition: 'top' | 'center' | 'bottom' = 'bottom'; @@ -84,8 +87,6 @@ export class NzDropDownComponent implements OnDestroy, AfterContentInit, OnChang visible$ = new Subject(); private destroy$ = new Subject(); @ContentChild(NzDropDownDirective, { static: false }) nzDropDownDirective: NzDropDownDirective; - @ContentChild(NzMenuDirective, { static: false }) nzMenuDirective: NzMenuDirective; - @ViewChild(CdkConnectedOverlay, { static: false }) cdkConnectedOverlay: CdkConnectedOverlay; @Input() nzTrigger: 'click' | 'hover' = 'hover'; @Input() nzOverlayClassName = ''; @Input() nzOverlayStyle: { [key: string]: string } = {}; @@ -136,7 +137,11 @@ export class NzDropDownComponent implements OnDestroy, AfterContentInit, OnChang protected cdr: ChangeDetectorRef, private nzMenuDropdownService: NzMenuDropdownService, @Host() @Optional() public noAnimation?: NzNoAnimationDirective - ) {} + ) { + warnDeprecation( + `'nz-dropdown' Component is going to be removed in 9.0.0. Please use 'nz-dropdown-menu' instead. Read https://ng.ant.design/components/dropdown/en` + ); + } ngOnDestroy(): void { this.destroy$.next(); diff --git a/components/dropdown/nz-dropdown.directive.spec.ts b/components/dropdown/nz-dropdown.directive.spec.ts new file mode 100644 index 00000000000..0afb7ca3924 --- /dev/null +++ b/components/dropdown/nz-dropdown.directive.spec.ts @@ -0,0 +1,198 @@ +import { OverlayContainer } from '@angular/cdk/overlay'; +import { Component, Provider, Type } from '@angular/core'; +import { fakeAsync, inject, tick, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { dispatchFakeEvent } from '../core/testing'; +import { NzMenuModule } from '../menu/nz-menu.module'; +import { NzDropDownDirective } from './nz-dropdown.directive'; +import { NzDropDownModule } from './nz-dropdown.module'; + +describe('dropdown', () => { + let overlayContainer: OverlayContainer; + let overlayContainerElement: HTMLElement; + + function createComponent( + component: Type, + providers: Provider[] = [], + // tslint:disable-next-line:no-any + declarations: any[] = [] + ): ComponentFixture { + TestBed.configureTestingModule({ + imports: [NzDropDownModule, NzMenuModule, NoopAnimationsModule], + declarations: [component, ...declarations], + providers + }) + .compileComponents() + .then(); + + inject([OverlayContainer], (oc: OverlayContainer) => { + overlayContainer = oc; + overlayContainerElement = oc.getContainerElement(); + })(); + + return TestBed.createComponent(component); + } + + afterEach(inject([OverlayContainer], (currentOverlayContainer: OverlayContainer) => { + currentOverlayContainer.ngOnDestroy(); + overlayContainer.ngOnDestroy(); + })); + + it('should hover correct', fakeAsync(() => { + const fixture = createComponent(NzTestDropdownComponent, [], []); + fixture.componentInstance.trigger = 'hover'; + fixture.detectChanges(); + expect(overlayContainerElement.textContent).toBe(''); + expect(() => { + const dropdownElement = fixture.debugElement.query(By.directive(NzDropDownDirective)).nativeElement; + dispatchFakeEvent(dropdownElement, 'mouseenter'); + fixture.detectChanges(); + tick(1000); + fixture.detectChanges(); + expect(overlayContainerElement.textContent).toContain('1st menu item'); + }).not.toThrowError(); + })); + it('should click correct', fakeAsync(() => { + const fixture = createComponent(NzTestDropdownComponent, [], []); + fixture.componentInstance.trigger = 'click'; + fixture.detectChanges(); + expect(overlayContainerElement.textContent).toBe(''); + expect(() => { + const dropdownElement = fixture.debugElement.query(By.directive(NzDropDownDirective)).nativeElement; + dispatchFakeEvent(dropdownElement, 'click'); + fixture.detectChanges(); + tick(1000); + fixture.detectChanges(); + expect(overlayContainerElement.textContent).toContain('1st menu item'); + }).not.toThrowError(); + })); + it('should disabled work', fakeAsync(() => { + const fixture = createComponent(NzTestDropdownComponent, [], []); + fixture.componentInstance.disabled = true; + fixture.detectChanges(); + expect(overlayContainerElement.textContent).toBe(''); + expect(() => { + const dropdownElement = fixture.debugElement.query(By.directive(NzDropDownDirective)).nativeElement; + dispatchFakeEvent(dropdownElement, 'mouseenter'); + fixture.detectChanges(); + tick(1000); + fixture.detectChanges(); + 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(); + })); + it('should nzOverlayClassName and nzOverlayStyle 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('custom-class'); + expect(overlayContainerElement.querySelector('.ant-dropdown')!.style.color).toBe('rgb(0, 0, 0)'); + }).not.toThrowError(); + })); + it('should nzVisible & nzClickHide work', fakeAsync(() => { + 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(); + })); +}); + +@Component({ + template: ` + Trigger + + +
        +
      • 1st menu item
      • +
      • 2nd menu item
      • +
      • 3rd menu item
      • +
      +
      + ` +}) +export class NzTestDropdownComponent { + trigger = 'hover'; + placement = 'bottomLeft'; + disabled = false; + className = 'custom-class'; + overlayStyle = { color: '#000' }; +} + +@Component({ + template: ` + Hover me + +
        +
      • Clicking me will not close the menu.
      • +
      • Clicking me will not close the menu also.
      • +
      • Clicking me will close the menu
      • +
      +
      + ` +}) +export class NzTestDropdownVisibleComponent { + visible = false; + triggerVisible = jasmine.createSpy('visibleChange'); +} diff --git a/components/dropdown/nz-dropdown.directive.ts b/components/dropdown/nz-dropdown.directive.ts index ffcda0b8c72..3a2d3aec8b2 100644 --- a/components/dropdown/nz-dropdown.directive.ts +++ b/components/dropdown/nz-dropdown.directive.ts @@ -6,24 +6,67 @@ * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ -import { Directive, ElementRef, Renderer2 } from '@angular/core'; -import { fromEvent, merge, Observable } from 'rxjs'; -import { mapTo, tap } from 'rxjs/operators'; +import { + ConnectedPosition, + ConnectionPositionPair, + FlexibleConnectedPositionStrategy, + Overlay, + OverlayConfig, + OverlayRef +} from '@angular/cdk/overlay'; +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, fromEvent, merge, EMPTY, Observable, Subject, Subscription } from 'rxjs'; +import { debounceTime, distinctUntilChanged, map, mapTo, takeUntil, tap } from 'rxjs/operators'; +import { NzDropdownMenuComponent, NzPlacementType } from './nz-dropdown-menu.component'; @Directive({ selector: '[nz-dropdown]', exportAs: 'nzDropdown' }) -export class NzDropDownDirective { - el: HTMLElement = this.elementRef.nativeElement; - hover$: Observable = merge( +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)) ); - $click: Observable = fromEvent(this.el, 'click').pipe( + 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() 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) { @@ -33,7 +76,209 @@ export class NzDropDownDirective { } } - constructor(public elementRef: ElementRef, private renderer: Renderer2) { + private getOverlayConfig(): OverlayConfig { + return new OverlayConfig({ + positionStrategy: this.overlay + .position() + .flexibleConnectedTo(this.el) + .withLockedPosition(), + minWidth: this.triggerWidth, + hasBackdrop: this.nzTrigger === 'click', + 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()) + .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.setPosition(overlayConfig.positionStrategy as FlexibleConnectedPositionStrategy); + overlayRef.attach(this.getPortal()); + this.dropdownOpen = true; + this.nzDropdownMenu.setValue('open', 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 { + 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 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-dropdown.module.ts b/components/dropdown/nz-dropdown.module.ts index e7a07ddb7e9..2d80434d3b9 100644 --- a/components/dropdown/nz-dropdown.module.ts +++ b/components/dropdown/nz-dropdown.module.ts @@ -16,9 +16,11 @@ import { NzAddOnModule, NzNoAnimationModule, NzOverlayModule } from 'ng-zorro-an import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzMenuModule } from 'ng-zorro-antd/menu'; +import { NzContextMenuService } from './nz-context-menu.service'; import { NzDropDownADirective } from './nz-dropdown-a.directive'; import { NzDropDownButtonComponent } from './nz-dropdown-button.component'; import { NzDropdownContextComponent } from './nz-dropdown-context.component'; +import { NzDropdownMenuComponent } from './nz-dropdown-menu.component'; import { NzDropDownComponent } from './nz-dropdown.component'; import { NzDropDownDirective } from './nz-dropdown.directive'; import { NzDropdownService } from './nz-dropdown.service'; @@ -35,15 +37,23 @@ import { NzDropdownService } from './nz-dropdown.service'; NzOverlayModule, NzAddOnModule ], - entryComponents: [NzDropdownContextComponent], + entryComponents: [NzDropdownContextComponent, NzDropdownMenuComponent], declarations: [ NzDropDownComponent, NzDropDownButtonComponent, NzDropDownDirective, NzDropDownADirective, - NzDropdownContextComponent + NzDropdownContextComponent, + NzDropdownMenuComponent ], - exports: [NzMenuModule, NzDropDownComponent, NzDropDownButtonComponent, NzDropDownDirective, NzDropDownADirective], - providers: [NzDropdownService] + exports: [ + NzMenuModule, + NzDropDownComponent, + NzDropDownButtonComponent, + NzDropDownDirective, + NzDropDownADirective, + NzDropdownMenuComponent + ], + providers: [NzDropdownService, NzContextMenuService] }) export class NzDropDownModule {} diff --git a/components/dropdown/nz-dropdown.service.ts b/components/dropdown/nz-dropdown.service.ts index 69cd97add86..8494f3cbbf0 100644 --- a/components/dropdown/nz-dropdown.service.ts +++ b/components/dropdown/nz-dropdown.service.ts @@ -16,15 +16,23 @@ import { } from '@angular/cdk/overlay'; import { ComponentPortal } from '@angular/cdk/portal'; import { Injectable, TemplateRef } from '@angular/core'; +import { warnDeprecation } from 'ng-zorro-antd/core'; import { fromEvent } from 'rxjs'; import { filter, take } from 'rxjs/operators'; import { NzDropdownContextComponent } from './nz-dropdown-context.component'; @Injectable() +/** + * @deprecated Use `NzContextMenuService` instead. + */ export class NzDropdownService { private overlayRef: OverlayRef | null; - constructor(private overlay: Overlay) {} + constructor(private overlay: Overlay) { + warnDeprecation( + `'NzDropdownService' is going to be removed in 9.0.0. Please use 'NzContextMenuService' instead. Read https://ng.ant.design/components/dropdown/en` + ); + } create($event: MouseEvent, templateRef: TemplateRef): NzDropdownContextComponent { $event.preventDefault(); diff --git a/components/dropdown/nz-dropdown.spec.ts b/components/dropdown/nz-dropdown.spec.ts index 11c6f66834d..dea976d17c4 100644 --- a/components/dropdown/nz-dropdown.spec.ts +++ b/components/dropdown/nz-dropdown.spec.ts @@ -18,7 +18,7 @@ import { NzDropDownDirective } from './nz-dropdown.directive'; import { NzDropDownModule } from './nz-dropdown.module'; import { NzDropdownService } from './nz-dropdown.service'; -describe('dropdown', () => { +describe('dropdown-deprecated', () => { let overlayContainer: OverlayContainer; let overlayContainerElement: HTMLElement; const scrolledSubject = new Subject(); diff --git a/components/dropdown/public-api.ts b/components/dropdown/public-api.ts index 779cd7e4c93..4dee764a9b8 100644 --- a/components/dropdown/public-api.ts +++ b/components/dropdown/public-api.ts @@ -14,3 +14,5 @@ export * from './nz-dropdown-button.component'; export * from './nz-dropdown.module'; export * from './nz-menu-dropdown.service'; export * from './nz-dropdown-a.directive'; +export * from './nz-dropdown-menu.component'; +export * from './nz-context-menu.service'; diff --git a/components/menu/nz-submenu.component.ts b/components/menu/nz-submenu.component.ts index 3814b6e3a6f..b04b86a8aa9 100644 --- a/components/menu/nz-submenu.component.ts +++ b/components/menu/nz-submenu.component.ts @@ -149,11 +149,9 @@ export class NzSubMenuComponent implements OnInit, OnDestroy, AfterContentInit, ) {} ngOnInit(): void { - combineLatest(this.nzSubmenuService.mode$, this.nzSubmenuService.open$) + combineLatest([this.nzSubmenuService.mode$, this.nzSubmenuService.open$]) .pipe(takeUntil(this.destroy$)) - .subscribe(data => { - const mode = data[0]; - const open = data[1]; + .subscribe(([mode, open]) => { if (open && mode === 'inline') { this.expandState = 'expanded'; } else if (open && mode === 'horizontal') { diff --git a/components/slider/nz-slider-handle.component.ts b/components/slider/nz-slider-handle.component.ts index a009d01bcb3..983ba46e712 100644 --- a/components/slider/nz-slider-handle.component.ts +++ b/components/slider/nz-slider-handle.component.ts @@ -19,7 +19,7 @@ import { } from '@angular/core'; import { Subscription } from 'rxjs'; -import { InputBoolean, NGStyleInterface } from 'ng-zorro-antd/core'; +import { InputBoolean, NgStyleInterface } from 'ng-zorro-antd/core'; import { NzToolTipComponent } from 'ng-zorro-antd/tooltip'; import { SliderShowTooltip } from './nz-slider-definitions'; @@ -48,7 +48,7 @@ export class NzSliderHandleComponent implements OnChanges, OnDestroy { @Input() @InputBoolean() nzActive = false; tooltipTitle: string; - style: NGStyleInterface = {}; + style: NgStyleInterface = {}; private hovers_ = new Subscription(); diff --git a/components/table/demo/custom-filter-panel.ts b/components/table/demo/custom-filter-panel.ts index a222ac117ec..f21b769aa30 100644 --- a/components/table/demo/custom-filter-panel.ts +++ b/components/table/demo/custom-filter-panel.ts @@ -8,22 +8,19 @@ import { Component } from '@angular/core'; Name - - - - + Age Address @@ -37,6 +34,15 @@ import { Component } from '@angular/core'; + + + `, styles: [ ` diff --git a/components/table/demo/nested-table.ts b/components/table/demo/nested-table.ts index de667b5d124..b3e96aca33c 100644 --- a/components/table/demo/nested-table.ts +++ b/components/table/demo/nested-table.ts @@ -53,8 +53,8 @@ import { Component, OnInit } from '@angular/core'; {{ data.upgradeNum }} - - Pause + Pause +
      • Action 1 @@ -63,7 +63,7 @@ import { Component, OnInit } from '@angular/core'; Action 2
      -
      + Stop diff --git a/components/table/nz-th.component.html b/components/table/nz-th.component.html index a5f4ad8230c..36ebe7ec9d8 100644 --- a/components/table/nz-th.component.html +++ b/components/table/nz-th.component.html @@ -1,10 +1,10 @@ @@ -17,54 +17,70 @@ - -
      - -
      +
      + +
      +
        -
      • {{selection.text}}
      • +
      • {{selection.text}}
      -
      +
+
+ type="caret-up" + class="ant-table-column-sorter-up" + [class.on]="nzSort == 'ascend'" + [class.off]="nzSort != 'ascend'"> + type="caret-down" + class="ant-table-column-sorter-down" + [class.on]="nzSort == 'descend'" + [class.off]="nzSort != 'descend'">
- - -
    - -
  • - {{filter.text}} -
  • -
    - -
  • - -
  • -
    -
- -
+ + + +
    + +
  • + + {{filter.text}} +
  • +
    + +
  • + +
  • +
    +
+ +
+
diff --git a/components/table/nz-th.component.ts b/components/table/nz-th.component.ts index 8720b3c41e6..7dd6db1f4b3 100644 --- a/components/table/nz-th.component.ts +++ b/components/table/nz-th.component.ts @@ -24,7 +24,7 @@ import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { isNotNil, InputBoolean } from 'ng-zorro-antd/core'; -import { NzDropDownComponent } from 'ng-zorro-antd/dropdown'; +import { NzDropdownMenuComponent } from 'ng-zorro-antd/dropdown'; import { NzI18nInterface, NzI18nService } from 'ng-zorro-antd/i18n'; /* tslint:disable-next-line:no-any */ @@ -69,7 +69,7 @@ export class NzThComponent implements OnChanges, OnInit, OnDestroy { nzWidthChange$ = new Subject(); private destroy$ = new Subject(); private hasDefaultFilter = false; - @ViewChild(NzDropDownComponent, { static: false }) nzDropDownComponent: NzDropDownComponent; + @ViewChild(NzDropdownMenuComponent, { static: false }) nzDropdownMenuComponent: NzDropdownMenuComponent; /* tslint:disable-next-line:no-any */ @Input() nzSelections: Array<{ text: string; onSelect(...args: any[]): any }> = []; @Input() nzChecked = false; @@ -155,7 +155,7 @@ export class NzThComponent implements OnChanges, OnInit, OnDestroy { } hideDropDown(): void { - this.nzDropDownComponent.setVisibleStateWhen(false); + this.nzDropdownMenuComponent.setVisibleStateWhen(false); this.filterVisible = false; } diff --git a/components/tabs/nz-tabs.spec.ts b/components/tabs/nz-tabs.spec.ts index e0d4c50449c..4f3be9ac0da 100644 --- a/components/tabs/nz-tabs.spec.ts +++ b/components/tabs/nz-tabs.spec.ts @@ -1,7 +1,7 @@ import { Component, DebugElement, TemplateRef, ViewChild, ViewEncapsulation } from '@angular/core'; import { fakeAsync, tick, ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { NGStyleInterface } from 'ng-zorro-antd/core'; +import { NgStyleInterface } from 'ng-zorro-antd/core'; import { NzTabsModule } from './nz-tabs.module'; import { NzAnimatedInterface, NzTabSetComponent } from './nz-tabset.component'; @@ -545,7 +545,7 @@ export class NzTestTabsBasicComponent { animated: NzAnimatedInterface | boolean = true; size = 'default'; tabBarExtraContent: TemplateRef; - tabBarStyle: NGStyleInterface; + tabBarStyle: NgStyleInterface; tabPosition = 'top'; type = 'line'; tabBarGutter: number; diff --git a/components/tree/demo/directory.ts b/components/tree/demo/directory.ts index c62cc1ceaf6..9c25dfd85ad 100644 --- a/components/tree/demo/directory.ts +++ b/components/tree/demo/directory.ts @@ -1,24 +1,18 @@ -import { Component, TemplateRef } from '@angular/core'; -import { NzDropdownContextComponent, NzDropdownService, NzFormatEmitEvent, NzTreeNode } from 'ng-zorro-antd'; +import { Component } from '@angular/core'; +import { NzContextMenuService, NzDropdownMenuComponent, NzFormatEmitEvent, NzTreeNode } from 'ng-zorro-antd'; @Component({ selector: 'nz-demo-tree-directory', template: ` - -
    -
  • Action 1
  • -
  • Action 2
  • -
-
- + {{ node.title }} created by {{ node?.origin?.author | lowercase }} - + {{ node.title }} modified by {{ node?.origin?.author | lowercase }} @@ -26,6 +20,12 @@ import { NzDropdownContextComponent, NzDropdownService, NzFormatEmitEvent, NzTre
+ +
    +
  • Action 1
  • +
  • Action 2
  • +
+
`, styles: [ ` @@ -71,7 +71,6 @@ import { NzDropdownContextComponent, NzDropdownService, NzFormatEmitEvent, NzTre ] }) export class NzDemoTreeDirectoryComponent { - dropdown: NzDropdownContextComponent; // actived node activedNode: NzTreeNode; nodes = [ @@ -112,14 +111,13 @@ export class NzDemoTreeDirectoryComponent { this.activedNode = data.node!; } - contextMenu($event: MouseEvent, template: TemplateRef): void { - this.dropdown = this.nzDropdownService.create($event, template); + contextMenu($event: MouseEvent, menu: NzDropdownMenuComponent): void { + this.nzContextMenuService.create($event, menu); } selectDropdown(): void { - this.dropdown.close(); // do something } - constructor(private nzDropdownService: NzDropdownService) {} + constructor(private nzContextMenuService: NzContextMenuService) {} }