diff --git a/components/cascader/demo/custom-field-names.md b/components/cascader/demo/custom-field-names.md
new file mode 100644
index 00000000000..6e70f37e193
--- /dev/null
+++ b/components/cascader/demo/custom-field-names.md
@@ -0,0 +1,15 @@
+---
+order: 16
+title:
+ zh-CN: 自定义字段名
+ en-US: Custom Field Names
+---
+
+## zh-CN
+
+自定义字段名。
+
+## en-US
+
+Custom field names.
+
diff --git a/components/cascader/demo/custom-field-names.ts b/components/cascader/demo/custom-field-names.ts
new file mode 100644
index 00000000000..25952fd5fe4
--- /dev/null
+++ b/components/cascader/demo/custom-field-names.ts
@@ -0,0 +1,73 @@
+// tslint:disable:no-any
+import { Component } from '@angular/core';
+
+const options = [{
+ code: 'zhejiang',
+ name: 'Zhejiang',
+ children: [{
+ code: 'hangzhou',
+ name: 'Hangzhou',
+ children: [{
+ code: 'xihu',
+ name: 'West Lake',
+ isLeaf: true
+ }]
+ }, {
+ code: 'ningbo',
+ name: 'Ningbo',
+ children: [{
+ code: 'dongqianlake',
+ name: 'Dongqian Lake',
+ isLeaf: true
+ }]
+ }]
+}, {
+ code: 'jiangsu',
+ name: 'Jiangsu',
+ children: [{
+ code: 'nanjing',
+ name: 'Nanjing',
+ children: [{
+ code: 'zhonghuamen',
+ name: 'Zhong Hua Men',
+ isLeaf: true
+ }]
+ }]
+}];
+
+@Component({
+ selector: 'nz-demo-cascader-custom-field-names',
+ template: `
+
+ `,
+ styles : [
+ `
+ .ant-cascader-picker {
+ width: 300px;
+ }
+ `
+ ]
+})
+export class NzDemoCascaderCustomFieldNamesComponent {
+ /** init data */
+ nzOptions = options;
+
+ /** ngModel value */
+ public values: any[] = null;
+
+ public onChanges(values: any): void {
+ console.log(values, this.values);
+ }
+
+ public validate(option: any, index: number): boolean {
+ const value = option.value;
+ return ['hangzhou', 'xihu', 'nanjing', 'zhonghuamen'].indexOf(value) >= 0;
+ }
+}
diff --git a/components/cascader/doc/index.en-US.md b/components/cascader/doc/index.en-US.md
index 9e365980d80..c84bfacc406 100755
--- a/components/cascader/doc/index.en-US.md
+++ b/components/cascader/doc/index.en-US.md
@@ -32,6 +32,7 @@ Cascade selection box.
| `[nzExpandTrigger]` | expand current item when click or hover, one of 'click' 'hover' | string | 'click' |
| `[nzMenuClassName]` | additional className of popup overlay | string | - |
| `[nzMenuStyle]` | additional css style of popup overlay | object | - |
+| `[nzNotFoundContent]` | Specify content to show when no result matches. | string | - |
| `[nzLabelProperty]` | the label property name of options | string | 'label' |
| `[nzLabelRender]` | render template of displaying selected options | TemplateRef<any> | - |
| `[nzLoadData]` | To load option lazily. If setting `ngModel` with an array value and `nzOptions` is not setting, lazy load will be call immediately | (option: any, index?: index) => PromiseLike<any> | - |
diff --git a/components/cascader/doc/index.zh-CN.md b/components/cascader/doc/index.zh-CN.md
index 1fb2a4699b6..e51aea67459 100755
--- a/components/cascader/doc/index.zh-CN.md
+++ b/components/cascader/doc/index.zh-CN.md
@@ -33,6 +33,7 @@ subtitle: 级联选择
| `[nzExpandTrigger]` | 次级菜单的展开方式,可选 'click' 和 'hover' | string | 'click' |
| `[nzMenuClassName]` | 自定义浮层类名 | string | - |
| `[nzMenuStyle]` | 自定义浮层样式 | object | - |
+| `[nzNotFoundContent]` | 当下拉列表为空时显示的内容 | string | - |
| `[nzLabelProperty]` | 选项的显示值的属性名 | string | 'label' |
| `[nzLabelRender]` | 选择后展示的渲染模板 | TemplateRef<any> | - |
| `[nzLoadData]` | 用于动态加载选项。如果提供了`ngModel`初始值,且未提供`nzOptions`值,则会立即触发动态加载。 | (option: any, index?: index) => PromiseLike<any> | - |
diff --git a/components/cascader/nz-cascader-li.component.html b/components/cascader/nz-cascader-li.component.html
new file mode 100644
index 00000000000..edf960bf6c9
--- /dev/null
+++ b/components/cascader/nz-cascader-li.component.html
@@ -0,0 +1,9 @@
+
+
+
+
+ {{ getOptionLabel() }}
+
+
+
+
\ No newline at end of file
diff --git a/components/cascader/nz-cascader-li.component.ts b/components/cascader/nz-cascader-li.component.ts
new file mode 100644
index 00000000000..1ee1ed82cb9
--- /dev/null
+++ b/components/cascader/nz-cascader-li.component.ts
@@ -0,0 +1,47 @@
+import { Component, Input, SecurityContext } from '@angular/core';
+import { DomSanitizer } from '@angular/platform-browser';
+import { classMapToString } from '../core/style/map';
+import { CascaderOption } from './types';
+
+const prefixCls = 'ant-cascader-menu-item';
+
+@Component({
+ selector : '[nz-cascader-option]',
+ templateUrl: './nz-cascader-li.component.html',
+ host : {
+ '[attr.title]': 'option.title || getOptionLabel()',
+ '[class]' : 'getOptionClassString()'
+ }
+})
+export class NzCascaderOptionComponent {
+ @Input() option: CascaderOption;
+ @Input() activated = false;
+ @Input() highlightText: string;
+ @Input() nzLabelProperty = 'label';
+
+ getOptionLabel(): string {
+ return this.option ? this.option[ this.nzLabelProperty ] : '';
+ }
+
+ getOptionClassString(): string {
+ return classMapToString({
+ [ `${prefixCls}` ] : true,
+ [ `${prefixCls}-active` ] : this.activated,
+ [ `${prefixCls}-expand` ] : !this.option.isLeaf,
+ [ `${prefixCls}-disabled` ]: this.option.disabled
+ });
+ }
+
+ renderHighlightString(str: string): string {
+ const safeHtml = this.sanitizer.sanitize(
+ SecurityContext.HTML, ``
+ );
+ if (!safeHtml) {
+ throw new Error(`[NG-ZORRO] Input value "${this.highlightText}" is not considered security.`);
+ }
+ return str.replace(new RegExp(this.highlightText, 'g'), safeHtml);
+ }
+
+ constructor(private sanitizer: DomSanitizer) {
+ }
+}
diff --git a/components/cascader/nz-cascader.component.html b/components/cascader/nz-cascader.component.html
index 6b48651d3bc..be400d017b4 100644
--- a/components/cascader/nz-cascader.component.html
+++ b/components/cascader/nz-cascader.component.html
@@ -4,33 +4,26 @@
#trigger>
-
-
-
+ nz-input
+ [attr.autoComplete]="'off'"
+ [attr.placeholder]="showPlaceholder ? nzPlaceHolder : null"
+ [attr.autofocus]="nzAutoFocus ? 'autofocus' : null"
+ [readonly]="!nzShowSearch"
+ [disabled]="nzDisabled"
+ [nzSize]="nzSize"
+ [ngClass]="inputCls"
+ [(ngModel)]="inputValue"
+ (blur)="handleInputBlur($event)"
+ (focus)="handleInputFocus($event)"
+ (change)="$event.stopPropagation()">
+
+
+
{{ labelRenderText }}
@@ -50,29 +43,23 @@
(positionChange)="onPositionChange($event)"
[cdkConnectedOverlayOpen]="menuVisible">
-
- -
-
-
-
-
- {{ getOptionLabel(option) }}
-
-
-
-
+ [ngClass]="menuCls" [ngStyle]="nzMenuStyle"
+ [@dropDownAnimation]="dropDownPosition"
+ (mouseleave)="onTriggerMouseLeave($event)">
+
+ -
- -
- Not Found
+
-
+ {{ nzNotFoundContent || ('Select.notFoundContent' | nzI18n) }}
diff --git a/components/cascader/nz-cascader.component.less b/components/cascader/nz-cascader.component.less
new file mode 100644
index 00000000000..930b41eb6d3
--- /dev/null
+++ b/components/cascader/nz-cascader.component.less
@@ -0,0 +1,8 @@
+.ant-cascader-menus {
+ margin-top: 4px;
+ margin-bottom: 4px;
+ top: 100%;
+ left: 0;
+ position: relative;
+ width: 100%;
+}
\ No newline at end of file
diff --git a/components/cascader/nz-cascader.component.ts b/components/cascader/nz-cascader.component.ts
index d755dccab11..0c3297688be 100644
--- a/components/cascader/nz-cascader.component.ts
+++ b/components/cascader/nz-cascader.component.ts
@@ -1,6 +1,8 @@
-// tslint:disable:no-any
+import { BACKSPACE, DOWN_ARROW, ENTER, ESCAPE, LEFT_ARROW, RIGHT_ARROW, UP_ARROW } from '@angular/cdk/keycodes';
+import { ConnectedOverlayPositionChange, ConnectionPositionPair } from '@angular/cdk/overlay';
import {
forwardRef,
+ ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
@@ -8,333 +10,83 @@ import {
HostListener,
Input,
OnDestroy,
- OnInit,
Output,
TemplateRef,
ViewChild
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
-import { BACKSPACE, DOWN_ARROW, ENTER, ESCAPE, LEFT_ARROW, RIGHT_ARROW, UP_ARROW } from '@angular/cdk/keycodes';
-import { ConnectedOverlayPositionChange, ConnectionPositionPair } from '@angular/cdk/overlay';
-import { DEFAULT_DROPDOWN_POSITIONS } from '../core/overlay/overlay-position-map';
-
import { dropDownAnimation } from '../core/animation/dropdown-animations';
-import { NzUpdateHostClassService } from '../core/services/update-host-class.service';
-import { toBoolean } from '../core/util/convert';
-
-function toArray(value: T | T[]): T[] {
- let ret: T[];
- if (value == null) {
- ret = [];
- } else if (!Array.isArray(value)) {
- ret = [ value ];
- } else {
- ret = value;
- }
- return ret;
-}
-
-function arrayEquals(array1: T[], array2: T[]): boolean {
- if (!array1 || !array2 || array1.length !== array2.length) {
- return false;
- }
-
- const len = array1.length;
- for (let i = 0; i < len; i++) {
- if (array1[ i ] !== array2[ i ]) {
- return false;
- }
- }
- return true;
-}
+import { ClassMap } from '../core/interface/interface';
+import { EXPANDED_DROPDOWN_POSITIONS } from '../core/overlay/overlay-position-map';
+import { classMapToString } from '../core/style/map';
+import { arrayEquals, toArray } from '../core/util/array';
+import { InputBoolean } from '../core/util/convert';
+import {
+ CascaderOption,
+ CascaderSearchOption,
+ NzCascaderExpandTrigger,
+ NzCascaderSize,
+ NzCascaderTriggerType,
+ NzShowSearchOptions
+} from './types';
const defaultDisplayRender = label => label.join(' / ');
-export type NzCascaderExpandTrigger = 'click' | 'hover';
-export type NzCascaderTriggerType = 'click' | 'hover';
-export type NzCascaderSize = 'small' | 'large' | 'default' ;
-
-export interface CascaderOption {
- value?: any;
- label?: string;
- title?: string;
- disabled?: boolean;
- loading?: boolean;
- isLeaf?: boolean;
- parent?: CascaderOption;
- children?: CascaderOption[];
-
- [ key: string ]: any;
-}
-
-export interface CascaderSearchOption extends CascaderOption {
- path: CascaderOption[];
-}
-
-export interface NzShowSearchOptions {
- filter?(inputValue: string, path: CascaderOption[]): boolean;
-
- sorter?(a: CascaderOption[], b: CascaderOption[], inputValue: string): number;
-}
-
@Component({
- selector : 'nz-cascader,[nz-cascader]',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ selector: 'nz-cascader,[nz-cascader]',
preserveWhitespaces: false,
- animations : [
- dropDownAnimation
- ],
- templateUrl : './nz-cascader.component.html',
- providers : [
- NzUpdateHostClassService,
+ templateUrl: './nz-cascader.component.html',
+ styleUrls: ['./nz-cascader.component.less'],
+ animations: [dropDownAnimation],
+ providers: [
{
- provide : NG_VALUE_ACCESSOR,
+ provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NzCascaderComponent),
- multi : true
+ multi: true
}
],
- host : {
- '[attr.tabIndex]': '"0"'
- },
- styles : [
- `.ant-cascader-menus {
- margin-top: 4px;
- margin-bottom: 4px;
- top: 100%;
- left: 0;
- position: relative;
- width: 100%;
- }`
- ]
-})
-export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAccessor {
- private allowClear = true;
- private autoFocus = false;
- private disabled = false;
- private enableCache = true;
- private showArrow = true;
- private showInput = true;
- private size: NzCascaderSize = 'default';
- private prefixCls = 'ant-cascader';
- private inputPrefixCls = 'ant-input';
- private menuClassName;
- private columnClassName;
- private changeOnSelect = false;
- private showSearch: boolean | NzShowSearchOptions;
- private defaultValue: any[];
-
- public dropDownPosition = 'bottom';
- public menuVisible = false;
- public isLoading = false;
- private isOpening = false;
-
- // 内部样式
- private _arrowCls: { [ name: string ]: any };
- private _clearCls: { [ name: string ]: any };
- private _inputCls: { [ name: string ]: any };
- private _labelCls: { [ name: string ]: any };
- private _loadingCls: { [ name: string ]: any };
- private _menuCls: { [ name: string ]: any };
- private _menuColumnCls: { [ name: string ]: any };
-
- public el: HTMLElement = this.elementRef.nativeElement;
-
- private isFocused = false;
-
- /** 选择选项后,渲染显示文本 */
- private labelRenderTpl: TemplateRef;
- public isLabelRenderTemplate = false;
- public labelRenderText: string;
- public labelRenderContext: any = {};
-
- // 当前值
- private value: any[];
- // 已选择的选项表示当前已确认的选项:selection will trigger value change
- private selectedOptions: CascaderOption[] = [];
- // 已激活的选项表示通过键盘方向键选择的选项,并未最终确认(除非按ENTER键):activaction will not trigger value change
- private activatedOptions: CascaderOption[] = [];
- // 表示当前菜单的数据列:all data columns
- public nzColumns: CascaderOption[][] = [];
-
- // 显示或隐藏菜单计时器
- private delayTimer: any;
- private delaySelectTimer: any;
-
- /** 搜索相关的输入值 */
- private _inputValue = '';
- get inputValue(): string {
- return this._inputValue;
- }
-
- set inputValue(inputValue: string) {
- this._inputValue = inputValue;
- const willBeInSearch = !!inputValue;
-
- // 搜索状态变动之前,如要进入则要保留之前激活选项的快照,退出搜索状态要还原该快照
- if (!this.inSearch && willBeInSearch) {
- this.oldActivatedOptions = this.activatedOptions;
- this.activatedOptions = [];
- } else if (this.inSearch && !willBeInSearch) {
- this.activatedOptions = this.oldActivatedOptions;
- }
-
- // 搜索状态变更之后
- this.inSearch = !!willBeInSearch;
- if (this.inSearch) {
- this.labelRenderText = '';
- this.prepareSearchValue();
- } else {
- if (this.showSearch) {
- this.nzColumns = this.oldColumnsHolder;
- }
- this.buildDisplayLabel();
- this.searchWidthStyle = '';
- }
- this.setClassMap();
- }
-
- // ngModel Access
- onChange: any = Function.prototype;
- onTouched: any = Function.prototype;
- positions: ConnectionPositionPair[] = [ ...DEFAULT_DROPDOWN_POSITIONS ];
-
- /** Display Render ngTemplate */
- @Input()
- set nzLabelRender(value: TemplateRef) {
- this.labelRenderTpl = value;
- this.isLabelRenderTemplate = (value instanceof TemplateRef);
- }
-
- get nzLabelRender(): TemplateRef {
- return this.labelRenderTpl;
- }
-
- /** prefixCls */
- @Input()
- set nzPrefixCls(prefixCls: string) {
- this.prefixCls = prefixCls;
- this.setClassMap();
- this.setLabelClass();
- this.setArrowClass();
- this.setLoadingClass();
- this.setClearClass();
- this.setInputClass();
- this.setMenuClass();
- this.setMenuColumnClass();
- }
-
- get nzPrefixCls(): string {
- return this.prefixCls;
- }
-
- /** Whether is disabled */
- @Input()
- set nzDisabled(value: boolean) {
- this.disabled = toBoolean(value);
- this.setClassMap();
- this.setInputClass();
- }
-
- get nzDisabled(): boolean {
- return this.disabled;
- }
-
- /** Input size, one of `large` `default` `small` */
- @Input()
- set nzSize(value: NzCascaderSize) {
- this.size = value;
- this.setClassMap();
- this.setInputClass();
- }
-
- get nzSize(): NzCascaderSize {
- return this.size;
- }
-
- /** Whether show input box. Defaults to `true`. */
- @Input()
- set nzShowInput(value: boolean) {
- this.showInput = toBoolean(value);
- }
-
- get nzShowInput(): boolean {
- return this.showInput;
- }
-
- /** Whether can search. Defaults to `false`. */
- @Input()
- set nzShowSearch(value: boolean | NzShowSearchOptions) {
- this.showSearch = value;
- }
-
- get nzShowSearch(): boolean | NzShowSearchOptions {
- return this.showSearch;
- }
-
- public searchWidthStyle: string;
- private oldColumnsHolder;
- private oldActivatedOptions;
-
- /** If cascader is in search mode. */
- public inSearch = false;
-
- /** Whether allow clear. Defaults to `true`. */
- @Input()
- set nzAllowClear(value: boolean) {
- this.allowClear = toBoolean(value);
- }
-
- get nzAllowClear(): boolean {
- return this.allowClear;
- }
-
- /** Whether auto focus. */
- @Input()
- set nzAutoFocus(value: boolean) {
- this.autoFocus = toBoolean(value);
- }
-
- get nzAutoFocus(): boolean {
- return this.autoFocus;
- }
-
- /** Whether to show arrow */
- @Input()
- set nzShowArrow(value: boolean) {
- this.showArrow = toBoolean(value);
- }
-
- get nzShowArrow(): boolean {
- return this.showArrow;
+ host: {
+ '[attr.tabIndex]': '"0"',
+ '[class]': 'hostCls'
}
+})
+export class NzCascaderComponent implements OnDestroy, ControlValueAccessor {
+ @ViewChild('input') input: ElementRef;
+ @ViewChild('menu') menu: ElementRef;
- /** Additional className of popup overlay */
- @Input()
- set nzMenuClassName(value: string) {
- this.menuClassName = value;
- this.setMenuClass();
- }
+ @Input() @InputBoolean() nzShowInput = true;
+ @Input() @InputBoolean() nzShowArrow = true;
+ @Input() @InputBoolean() nzAllowClear = true;
+ @Input() @InputBoolean() nzAutoFocus = false;
+ @Input() @InputBoolean() nzChangeOnSelect = false;
+ @Input() @InputBoolean() nzDisabled = false;
+ @Input() nzColumnClassName: string;
+ @Input() nzExpandTrigger: NzCascaderExpandTrigger = 'click';
+ @Input() nzValueProperty = 'value';
+ @Input() nzLabelRender: TemplateRef;
+ @Input() nzLabelProperty = 'label';
+ @Input() nzNotFoundContent: string;
+ @Input() nzSize: NzCascaderSize = 'default';
+ @Input() nzShowSearch: boolean | NzShowSearchOptions;
+ @Input() nzPlaceHolder = 'Please select';
+ @Input() nzMenuClassName: string;
+ @Input() nzMenuStyle: { [key: string]: string; };
+ @Input() nzMouseEnterDelay: number = 150; // ms
+ @Input() nzMouseLeaveDelay: number = 150; // ms
+ @Input() nzTriggerAction: NzCascaderTriggerType | NzCascaderTriggerType[] = ['click'] as NzCascaderTriggerType[];
+ @Input() nzChangeOn: (option: CascaderOption, level: number) => boolean;
- get nzMenuClassName(): string {
- return this.menuClassName;
- }
+ // tslint:disable:no-any
+ @Input() nzLoadData: (node: CascaderOption, index?: number) => PromiseLike;
+ // tslint:enable:no-any
- /** Additional className of popup overlay column */
@Input()
- set nzColumnClassName(value: string) {
- this.columnClassName = value;
- this.setMenuColumnClass();
- }
-
- get nzColumnClassName(): string {
- return this.columnClassName;
- }
-
- /** Options for first column, sub column will be load async */
- @Input() set nzOptions(options: CascaderOption[] | null) {
- this.oldColumnsHolder = this.nzColumns = options && options.length ? [ options ] : [];
- if (!this.inSearch) {
- if (this.defaultValue && this.nzColumns.length) {
+ set nzOptions(options: CascaderOption[] | null) {
+ this.columnsSnapshot = this.columns = options && options.length ? [options] : [];
+ if (!this.isSearching) {
+ if (this.defaultValue && this.columns.length) {
this.initOptions(0);
}
} else {
@@ -343,658 +95,494 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces
}
get nzOptions(): CascaderOption[] {
- return this.nzColumns[ 0 ];
+ return this.columns[0];
}
- /** Change value on each selection if set to true */
- @Input()
- set nzChangeOnSelect(value: boolean) {
- this.changeOnSelect = toBoolean(value);
- }
-
- get nzChangeOnSelect(): boolean {
- return this.changeOnSelect;
- }
-
- /** Hover text for the clear icon */
- @Input() nzClearText = 'Clear';
-
- /** Expand column item when click or hover, one of 'click' 'hover' */
- @Input() nzExpandTrigger: NzCascaderExpandTrigger = 'click';
-
- /** Specify content to show when no result matches. */
- @Input() nzNotFoundContent = 'Not Found';
-
- /** Input placeholder */
- @Input() nzPlaceHolder = 'Please select';
-
- /** Additional style of popup overlay */
- @Input() nzMenuStyle: { [ key: string ]: string; };
-
- /** Change value on selection only if this function returns `true` */
- @Input() nzChangeOn: (option: CascaderOption, level: number) => boolean;
-
- /** Delay time to show when mouse enter, when `nzExpandTrigger` is `hover`. */
- @Input() nzMouseEnterDelay = 150; // ms
-
- /** Delay time to hide when mouse enter, when `nzExpandTrigger` is `hover`. */
- @Input() nzMouseLeaveDelay = 150; // ms
-
- /** Triggering mode: can be Array<'click'|'hover'> */
- @Input() nzTriggerAction: NzCascaderTriggerType | NzCascaderTriggerType[] = [ 'click' ];
-
- /** Property name for getting `value` in the option */
- @Input() nzValueProperty = 'value';
-
- /** Property name for getting `label` in the option */
- @Input() nzLabelProperty = 'label';
-
- /** 异步加载数据 */
- @Input() nzLoadData: (node: CascaderOption, index?: number) => PromiseLike;
-
- /** Event: emit on popup show or hide */
- @Output() readonly nzVisibleChange = new EventEmitter();
-
- /** Event: emit on values changed */
- @Output() readonly nzChange = new EventEmitter();
-
- /** Event: emit on values and selection changed */
@Output() readonly nzSelectionChange = new EventEmitter();
-
- /**
- * Event: emit on option selected, event data:{option: any, index: number}
- */
- @Output() readonly nzSelect = new EventEmitter<{
- option: CascaderOption,
- index: number
- }>();
-
- /** Event: emit on the clear button clicked */
+ @Output() readonly nzSelect = new EventEmitter<{ option: CascaderOption, index: number }>();
@Output() readonly nzClear = new EventEmitter();
+ @Output() readonly nzVisibleChange = new EventEmitter(); // Not exposed.
+ @Output() readonly nzChange = new EventEmitter(); // Not exposed.
- @ViewChild('input') input: ElementRef;
- /** 浮层菜单 */
- @ViewChild('menu') menu: ElementRef;
-
- public onPositionChange(position: ConnectedOverlayPositionChange): void {
- const newValue = position.connectionPair.originY === 'bottom' ? 'bottom' : 'top';
- if (this.dropDownPosition !== newValue) {
- this.dropDownPosition = newValue;
- this.cdr.detectChanges();
- }
- }
-
- public focus(): void {
- if (!this.isFocused) {
- const input = this.el.querySelector(`.${this.prefixCls}-input`) as HTMLElement;
- if (input && input.focus) {
- input.focus();
- } else {
- this.el.focus();
- }
- this.isFocused = true;
- this.setClassMap();
- }
- }
-
- public blur(): void {
- if (this.isFocused) {
- const input = this.el.querySelector(`.${this.prefixCls}-input`) as HTMLElement;
- if (input && input.blur) {
- input.blur();
- } else {
- this.el.blur();
- }
- this.isFocused = false;
- this.setClassMap();
- this.setLabelClass();
- }
- }
+ private prefixCls = 'ant-cascader';
+ private inputPrefixCls = 'ant-input';
+ private isOpening = false;
+ private isFocused = false;
+ private defaultValue; // Default value written by `[ngModel]`
+ private value;
+ private selectedOptions: CascaderOption[] = [];
+ private activatedOptions: CascaderOption[] = [];
+ private columnsSnapshot: CascaderOption[][];
+ private activatedOptionsSnapshot: CascaderOption[];
+ private delayMenuTimer;
+ private delaySelectTimer;
+
+ el: HTMLElement = this.elementRef.nativeElement;
+ dropDownPosition = 'bottom';
+ menuVisible = false;
+ isLoading = false;
+ labelRenderText: string;
+ labelRenderContext = {};
+ columns: CascaderOption[][] = [];
+ onChange = Function.prototype;
+ onTouched = Function.prototype;
+ // TODO: when cascader opens columns, it would exceed the limit of the window.
+ positions: ConnectionPositionPair[] = [...EXPANDED_DROPDOWN_POSITIONS];
+ dropdownWidthStyle: string;
+ isSearching = false;
- private setClassMap(): void {
- const classMap = {
- [ `${this.prefixCls}` ] : 1,
- [ `${this.prefixCls}-picker` ] : 1,
- [ `${this.prefixCls}-lg` ] : this.nzSize === 'large',
- [ `${this.prefixCls}-sm` ] : this.nzSize === 'small',
- [ `${this.prefixCls}-picker-disabled` ] : this.disabled,
- [ `${this.prefixCls}-focused` ] : this.isFocused,
- [ `${this.prefixCls}-picker-open` ] : this.menuVisible,
- [ `${this.prefixCls}-picker-with-value` ]: this.inputValue && this.inputValue.length
- };
- this.nzUpdateHostClassService.updateHostClass(this.el, classMap);
+ get inputValue(): string {
+ return this._inputValue;
}
- /** 标签 样式 */
- public get labelCls(): any {
- return this._labelCls;
+ set inputValue(inputValue: string) {
+ this._inputValue = inputValue;
+ this.toggleSearchMode();
}
- private setLabelClass(): void {
- this._labelCls = {
- [ `${this.prefixCls}-picker-label` ]: true,
- [ `${this.prefixCls}-show-search` ] : !!this.nzShowSearch,
- [ `${this.prefixCls}-focused` ] : !!this.nzShowSearch && this.isFocused && !this._inputValue
- };
- }
+ private _inputValue = '';
- /** 箭头 样式 */
- public get arrowCls(): any {
- return this._arrowCls;
+ //#region Class getters
+
+ get hostCls(): string {
+ return classMapToString({
+ [`${this.prefixCls}`]: true,
+ [`${this.prefixCls}-picker`]: true,
+ [`${this.prefixCls}-lg`]: this.nzSize === 'large',
+ [`${this.prefixCls}-sm`]: this.nzSize === 'small',
+ [`${this.prefixCls}-picker-disabled`]: this.nzDisabled,
+ [`${this.prefixCls}-focused`]: this.isFocused,
+ [`${this.prefixCls}-picker-open`]: this.menuVisible,
+ [`${this.prefixCls}-picker-with-value`]: !!this.inputValue && !!this.inputValue.length
+ });
}
- private setArrowClass(): void {
- this._arrowCls = {
- [ `${this.prefixCls}-picker-arrow` ] : true,
- [ `${this.prefixCls}-picker-arrow-expand` ]: this.menuVisible
+ get labelCls(): ClassMap {
+ return {
+ [`${this.prefixCls}-picker-label`]: true,
+ [`${this.prefixCls}-show-search`]: !!this.nzShowSearch,
+ [`${this.prefixCls}-focused`]: !!this.nzShowSearch && this.isFocused && !this._inputValue
};
}
- /** 加载中图标 样式 */
- public get loadingCls(): any {
- return this._loadingCls;
- }
-
- private setLoadingClass(): void {
- this._loadingCls = {
- [ `${this.prefixCls}-picker-arrow` ]: true
+ get arrowCls(): ClassMap {
+ return {
+ [`${this.prefixCls}-picker-arrow`]: true,
+ [`${this.prefixCls}-picker-arrow-expand`]: this.menuVisible
};
}
- /** 清除图标 样式 */
- public get clearCls(): any {
- return this._clearCls;
- }
-
- private setClearClass(): void {
- this._clearCls = {
- [ `${this.prefixCls}-picker-clear` ]: true
+ get inputCls(): ClassMap {
+ return {
+ [`${this.prefixCls}-input`]: true,
+ [`${this.inputPrefixCls}-disabled`]: this.nzDisabled,
+ [`${this.inputPrefixCls}-lg`]: this.nzSize === 'large',
+ [`${this.inputPrefixCls}-sm`]: this.nzSize === 'small'
};
}
- /** 输入框 样式 */
- public get inputCls(): any {
- return this._inputCls;
- }
-
- private setInputClass(): void {
- this._inputCls = {
- [ `${this.prefixCls}-input` ] : 1,
- [ `${this.inputPrefixCls}-disabled` ]: this.nzDisabled,
- [ `${this.inputPrefixCls}-lg` ] : this.nzSize === 'large',
- [ `${this.inputPrefixCls}-sm` ] : this.nzSize === 'small'
+ get menuCls(): ClassMap {
+ return {
+ [`${this.prefixCls}-menus`]: true,
+ [`${this.prefixCls}-menus-hidden`]: !this.menuVisible,
+ [`${this.nzMenuClassName}`]: !!this.nzMenuClassName
};
}
- /** 浮层 样式 */
- public get menuCls(): any {
- return this._menuCls;
- }
-
- private setMenuClass(): void {
- this._menuCls = {
- [ `${this.prefixCls}-menus` ] : true,
- [ `${this.prefixCls}-menus-hidden` ]: !this.menuVisible,
- [ `${this.nzMenuClassName}` ] : this.nzMenuClassName
+ get menuColumnCls(): ClassMap {
+ return {
+ [`${this.prefixCls}-menu`]: true,
+ [`${this.nzColumnClassName}`]: !!this.nzColumnClassName
};
}
- /** 浮层列 样式 */
- public get menuColumnCls(): any {
- return this._menuColumnCls;
- }
+ //#endregion
- private setMenuColumnClass(): void {
- this._menuColumnCls = {
- [ `${this.prefixCls}-menu` ] : true,
- [ `${this.nzColumnClassName}` ]: this.nzColumnClassName
- };
- }
+ //#region Menu
- /** 获取列中Option的样式 */
- public getOptionCls(option: CascaderOption, index: number): any {
- return {
- [ `${this.prefixCls}-menu-item` ] : true,
- [ `${this.prefixCls}-menu-item-expand` ] : !option.isLeaf,
- [ `${this.prefixCls}-menu-item-active` ] : this.isActivedOption(option, index),
- [ `${this.prefixCls}-menu-item-disabled` ]: option.disabled
- };
+ private clearDelayMenuTimer(): void {
+ if (this.delayMenuTimer) {
+ clearTimeout(this.delayMenuTimer);
+ this.delayMenuTimer = null;
+ }
}
- /** prevent input change event */
- public handlerInputChange(event: Event): void {
- event.stopPropagation();
+ private loadRootOptions(): void {
+ if (!this.columns.length) {
+ const root = {};
+ this.loadChildrenAsync(root, -1);
+ }
}
- /** input element blur */
- public handleInputBlur(event: Event): void {
- /*
- if (!this.nzShowSearch) {
- return;
- }
- */
- if (this.menuVisible) {
- this.focus(); // keep input has focus when menu opened
+ delaySetMenuVisible(visible: boolean, delay: number, setOpening: boolean = false): void {
+ this.clearDelayMenuTimer();
+ if (delay) {
+ if (visible && setOpening) {
+ this.isOpening = true;
+ }
+ this.delayMenuTimer = setTimeout(() => {
+ this.setMenuVisible(visible);
+ this.cdr.detectChanges();
+ this.clearDelayMenuTimer();
+ if (visible) {
+ setTimeout(() => {
+ this.isOpening = false;
+ }, 100);
+ }
+ }, delay);
} else {
- this.blur();
+ this.setMenuVisible(visible);
}
}
- /** input element focus */
- public handleInputFocus(event: Event): void {
- /*
- if (!this.nzShowSearch) {
+ setMenuVisible(visible: boolean): void {
+ if (this.nzDisabled) {
return;
}
- */
- this.focus();
- this.setLabelClass();
- }
- private hasInput(): boolean {
- return this.inputValue.length > 0;
+ if (this.menuVisible !== visible) {
+ this.menuVisible = visible;
+ this.cdr.detectChanges();
+ if (visible) {
+ this.loadRootOptions();
+ }
+ this.nzVisibleChange.emit(visible);
+ }
}
- private hasValue(): boolean {
- return this.value && this.value.length > 0;
- }
+ //#endregion
+
+ //#region Init
- /** Whether to show input element placeholder */
- public get showPlaceholder(): boolean {
- return !(this.hasInput() || this.hasValue());
+ private isLoaded(index: number): boolean {
+ return this.columns[index] && this.columns[index].length > 0;
}
- /** Whether the clear button is visible */
- public get showClearIcon(): boolean {
- const isHasValue = this.hasValue();
- const isHasInput = this.hasInput();
- return this.nzAllowClear && !this.nzDisabled && (isHasValue || isHasInput);
+ private findOption(option: CascaderOption, index: number): CascaderOption {
+ const options: CascaderOption[] = this.columns[index];
+ if (options) {
+ const value = typeof option === 'object' ? this.getOptionValue(option) : option;
+ return options.find(o => value === this.getOptionValue(o));
+ }
+ return null;
}
- /** clear the input box and selected options */
- public clearSelection(event?: Event): void {
- if (event) {
- event.preventDefault();
- event.stopPropagation();
+ // tslint:disable:no-any
+ private activateOnInit(index: number, value: any): void {
+ let option = this.findOption(value, index);
+ if (!option) {
+ option = typeof value === 'object' ? value : {
+ [`${this.nzValueProperty || 'value'}`]: value,
+ [`${this.nzLabelProperty || 'label'}`]: value
+ };
}
+ this.setOptionActivated(option, index, false, false);
+ }
+ // tslint:enable:no-any
- this.labelRenderText = '';
- // this.isLabelRenderTemplate = false;
- // clear custom context
- this.labelRenderContext = {};
- this.selectedOptions = [];
- this.activatedOptions = [];
- this.inputValue = '';
- this.setMenuVisible(false);
+ private initOptions(index: number): void {
+ const vs = this.defaultValue;
+ const lastIndex = vs.length - 1;
- // trigger change event
- this.onValueChange();
- }
+ const load = () => {
+ this.activateOnInit(index, vs[index]);
+ if (index < lastIndex) {
+ this.initOptions(index + 1);
+ }
+ if (index === lastIndex) {
+ this.afterWriteValue();
+ }
+ };
- private buildDisplayLabel(): void {
- const selectedOptions = this.selectedOptions;
- const labels: string[] = selectedOptions.map(o => this.getOptionLabel(o));
- // 设置当前控件的显示值
- if (this.isLabelRenderTemplate) {
- this.labelRenderContext = { labels, selectedOptions };
+ if (this.isLoaded(index) || !this.nzLoadData) {
+ load();
} else {
- this.labelRenderText = defaultDisplayRender.call(this, labels, selectedOptions);
+ const node = this.activatedOptions[index - 1] || {};
+ this.loadChildrenAsync(node, index - 1, load, this.afterWriteValue);
}
}
- @HostListener('keydown', [ '$event' ])
- public onKeyDown(event: KeyboardEvent): void {
- const keyCode = event.keyCode;
- if (keyCode !== DOWN_ARROW &&
- keyCode !== UP_ARROW &&
- keyCode !== LEFT_ARROW &&
- keyCode !== RIGHT_ARROW &&
- keyCode !== ENTER &&
- keyCode !== BACKSPACE &&
- keyCode !== ESCAPE) {
- return;
- }
+ //#endregion
- if (this.inSearch && (
- keyCode === BACKSPACE ||
- keyCode === LEFT_ARROW ||
- keyCode === RIGHT_ARROW
- )) {
- return;
- }
+ //#region Mutating data
- // Press any keys above to reopen menu
- if (!this.isMenuVisible() &&
- keyCode !== BACKSPACE &&
- keyCode !== ESCAPE) {
- this.setMenuVisible(true);
- return;
- }
- // Press ESC to close menu
- if (keyCode === ESCAPE) {
- // this.setMenuVisible(false); // already call by cdk-overlay detach
+ private setOptionActivated(option: CascaderOption, columnIndex: number, select: boolean = false, loadChildren: boolean = true): void {
+ if (!option || option.disabled) {
return;
}
- if (this.isMenuVisible()) {
- event.preventDefault();
- if (keyCode === DOWN_ARROW) {
- this.moveDown();
- } else if (keyCode === UP_ARROW) {
- this.moveUp();
- } else if (keyCode === LEFT_ARROW) {
- this.moveLeft();
- } else if (keyCode === RIGHT_ARROW) {
- this.moveRight();
- } else if (keyCode === ENTER) {
- this.onEnter();
+ this.activatedOptions[columnIndex] = option;
+
+ // Set parent option and all ancestor options as active.
+ for (let i = columnIndex - 1; i >= 0; i--) {
+ if (!this.activatedOptions[i]) {
+ this.activatedOptions[i] = this.activatedOptions[i + 1].parent;
}
}
- }
- @HostListener('click', [ '$event' ])
- public onTriggerClick(event: MouseEvent): void {
- if (this.nzDisabled) {
- return;
+ // Set child options and all success options as inactive.
+ if (columnIndex < this.activatedOptions.length - 1) {
+ this.activatedOptions = this.activatedOptions.slice(0, columnIndex + 1);
}
- this.onTouched(); // set your control to 'touched'
- if (this.nzShowSearch) {
- this.focus();
+
+ // Load child options.
+ if (option.children && option.children.length && !option.isLeaf) {
+ option.children.forEach(child => child.parent = option);
+ this.setColumnData(option.children, columnIndex + 1);
+ } else if (!option.isLeaf && loadChildren) {
+ this.loadChildrenAsync(option, columnIndex);
}
- if (this.isClickTiggerAction()) {
- this.delaySetMenuVisible(!this.menuVisible, 100);
+ if (select) {
+ this.setOptionSelected(option, columnIndex);
}
+
+ this.cdr.detectChanges();
}
- @HostListener('mouseenter', [ '$event' ])
- public onTriggerMouseEnter(event: MouseEvent): void {
- if (this.nzDisabled) {
- return;
- }
- if (this.isPointerTiggerAction()) {
- this.delaySetMenuVisible(true, this.nzMouseEnterDelay, true);
+ private loadChildrenAsync(option: CascaderOption, columnIndex: number, success?: () => void, failure?: () => void): void {
+ if (this.nzLoadData) {
+ this.isLoading = columnIndex < 0;
+ option.loading = true;
+ this.nzLoadData(option, columnIndex).then(() => {
+ option.loading = this.isLoading = false;
+ if (option.children) {
+ option.children.forEach(child => child.parent = columnIndex < 0 ? undefined : option);
+ this.setColumnData(option.children, columnIndex + 1);
+ this.cdr.detectChanges();
+ }
+ if (success) {
+ success();
+ }
+ }, () => {
+ option.loading = this.isLoading = false;
+ option.isLeaf = true;
+ this.cdr.detectChanges();
+ if (failure) {
+ failure();
+ }
+ });
}
}
- @HostListener('mouseleave', [ '$event' ])
- public onTriggerMouseLeave(event: MouseEvent): void {
- if (this.nzDisabled) {
- return;
- }
- if (!this.isMenuVisible() || this.isOpening) {
- event.preventDefault();
- return;
+ private setOptionSelected(option: CascaderOption, columnIndex: number): void {
+ const shouldPerformSelection = (o: CascaderOption, i: number): boolean => {
+ return typeof this.nzChangeOn === 'function' ? this.nzChangeOn(o, i) === true : false;
+ };
+
+ this.nzSelect.emit({ option, index: columnIndex });
+
+ if (option.isLeaf || this.nzChangeOnSelect || shouldPerformSelection(option, columnIndex)) {
+ this.selectedOptions = this.activatedOptions;
+ this.buildDisplayLabel();
+ this.onValueChange();
}
- if (this.isPointerTiggerAction()) {
- const mouseTarget = event.relatedTarget as HTMLElement;
- const hostEl = this.el;
- const menuEl = this.menu && this.menu.nativeElement as HTMLElement;
- if (hostEl.contains(mouseTarget) || (menuEl && menuEl.contains(mouseTarget))
- /*|| mouseTarget.parentElement.contains(menuEl)*/) {
- // 因为浮层的backdrop出现,暂时没有办法自动消失
- return;
- }
+
+ if (option.isLeaf) {
this.delaySetMenuVisible(false, this.nzMouseLeaveDelay);
}
}
- private isClickTiggerAction(): boolean {
- if (typeof this.nzTriggerAction === 'string') {
- return this.nzTriggerAction === 'click';
+ private setColumnData(options: CascaderOption[], columnIndex: number): void {
+ if (!arrayEquals(this.columns[columnIndex], options)) {
+ this.columns[columnIndex] = options;
+ if (columnIndex < this.columns.length - 1) {
+ this.columns = this.columns.slice(0, columnIndex + 1);
+ }
}
- return this.nzTriggerAction.indexOf('click') !== -1;
}
- private isPointerTiggerAction(): boolean {
- if (typeof this.nzTriggerAction === 'string') {
- return this.nzTriggerAction === 'hover';
+ clearSelection(event?: Event): void {
+ if (event) {
+ event.preventDefault();
+ event.stopPropagation();
}
- return this.nzTriggerAction.indexOf('hover') !== -1;
- }
- public closeMenu(): void {
- this.blur();
- this.clearDelayTimer();
+ this.labelRenderText = '';
+ this.labelRenderContext = {};
+ this.selectedOptions = [];
+ this.activatedOptions = [];
+ this.inputValue = '';
this.setMenuVisible(false);
+
+ this.onValueChange();
}
- private clearDelayTimer(): void {
- if (this.delayTimer) {
- clearTimeout(this.delayTimer);
- this.delayTimer = null;
- }
+ // tslint:disable:no-any
+ getSubmitValue(): any[] {
+ const values: any[] = [];
+ this.selectedOptions.forEach(option => {
+ values.push(this.getOptionValue(option));
+ });
+ return values;
}
+ // tslint:enable:no-any
- /**
- * 显示或者隐藏菜单
- *
- * @param visible true-显示,false-隐藏
- * @param delay 延迟时间
- */
- public delaySetMenuVisible(visible: boolean, delay: number, setOpening: boolean = false): void {
- this.clearDelayTimer();
- if (delay) {
- if (visible && setOpening) {
- this.isOpening = true;
+ private onValueChange(): void {
+ const value = this.getSubmitValue();
+ if (!arrayEquals(this.value, value)) {
+ this.defaultValue = null;
+ this.value = value;
+ this.onChange(value);
+ if (value.length === 0) {
+ this.nzClear.emit();
}
- this.delayTimer = setTimeout(() => {
- this.setMenuVisible(visible);
- this.clearDelayTimer();
- if (visible) {
- setTimeout(() => {
- this.isOpening = false;
- }, 100);
- }
- }, delay);
- } else {
- this.setMenuVisible(visible);
+ this.nzSelectionChange.emit(this.selectedOptions);
+ this.nzChange.emit(value);
}
}
- public isMenuVisible(): boolean {
- return this.menuVisible;
+ afterWriteValue(): void {
+ this.selectedOptions = this.activatedOptions;
+ this.value = this.getSubmitValue();
+ this.buildDisplayLabel();
}
- public setMenuVisible(menuVisible: boolean): void {
- if (this.nzDisabled) {
- return;
- }
-
- if (this.menuVisible !== menuVisible) {
- this.menuVisible = menuVisible;
+ //#endregion
- // update class
- this.setClassMap();
- this.setArrowClass();
- this.setMenuClass();
+ //#region Mouse and keyboard event handlers, view children
- if (menuVisible) {
- this.beforeVisible();
- }
- this.nzVisibleChange.emit(menuVisible);
+ focus(): void {
+ if (!this.isFocused) {
+ (this.input ? this.input.nativeElement : this.el).focus();
+ this.isFocused = true;
}
}
- /** load init data if necessary */
- private beforeVisible(): void {
- this.loadRootOptions();
- }
-
- private loadRootOptions(): void {
- if (!this.nzColumns.length) {
- const root: any = {};
- this.loadChildren(root, -1);
+ blur(): void {
+ if (this.isFocused) {
+ (this.input ? this.input.nativeElement : this.el).blur();
+ this.isFocused = false;
}
}
- /** 获取Option的值,例如,可以指定labelProperty="name"来取Name */
- public getOptionLabel(option: CascaderOption): any {
- return option[ this.nzLabelProperty || 'label' ];
+ handleInputBlur(event: Event): void {
+ this.menuVisible ? this.focus() : this.blur();
}
- /** 获取Option的值,例如,可以指定valueProperty="id"来取ID */
- public getOptionValue(option: CascaderOption): any {
- return option[ this.nzValueProperty || 'value' ];
+ handleInputFocus(event: Event): void {
+ this.focus();
}
- private isActivedOption(option: CascaderOption, index: number): boolean {
- const activeOpt = this.activatedOptions[ index ];
- return activeOpt === option;
- }
+ @HostListener('keydown', ['$event'])
+ onKeyDown(event: KeyboardEvent): void {
+ const keyCode = event.keyCode;
- /**
- * 设置某列的激活的菜单选项
- *
- * @param option 菜单选项
- * @param index 选项所在的列组的索引
- * @param select 是否触发选择结点
- */
- private setActiveOption(option: CascaderOption, index: number, select: boolean = false, loadChildren: boolean = true): void {
- if (!option || option.disabled) {
+ if (keyCode !== DOWN_ARROW &&
+ keyCode !== UP_ARROW &&
+ keyCode !== LEFT_ARROW &&
+ keyCode !== RIGHT_ARROW &&
+ keyCode !== ENTER &&
+ keyCode !== BACKSPACE &&
+ keyCode !== ESCAPE
+ ) {
return;
}
- this.activatedOptions[ index ] = option;
-
- // 当直接选择最后一级时,前面的选项要补全。例如,选择“城市”,则自动补全“国家”、“省份”
- for (let i = index - 1; i >= 0; i--) {
- if (!this.activatedOptions[ i ]) {
- this.activatedOptions[ i ] = this.activatedOptions[ i + 1 ].parent;
- }
- }
- // 截断多余的选项,如选择“省份”,则只会有“国家”、“省份”,去掉“城市”、“区县”
- if (index < this.activatedOptions.length - 1) {
- this.activatedOptions = this.activatedOptions.slice(0, index + 1);
+ // Press any keys above to reopen menu.
+ if (!this.menuVisible && keyCode !== BACKSPACE && keyCode !== ESCAPE) {
+ return this.setMenuVisible(true);
}
- // load children
- if (option.children && option.children.length) {
- option.isLeaf = false;
- option.children.forEach(child => child.parent = option);
- this.setColumnData(option.children, index + 1);
- } else if (!option.isLeaf && loadChildren) {
- this.loadChildren(option, index);
- } else {
- // clicking leaf node will remove any children columns
- if (index < this.nzColumns.length - 1) {
- this.nzColumns = this.nzColumns.slice(0, index + 1);
- }
+ // Make these keys work as default in searching mode.
+ if (this.isSearching && (keyCode === BACKSPACE || keyCode === LEFT_ARROW || keyCode === RIGHT_ARROW)) {
+ return;
}
- // trigger select event, and display label
- if (select) {
- this.onSelectOption(option, index);
+ // Interact with the component.
+ if (this.menuVisible) {
+ event.preventDefault();
+ if (keyCode === DOWN_ARROW) {
+ this.moveUpOrDown(false);
+ } else if (keyCode === UP_ARROW) {
+ this.moveUpOrDown(true);
+ } else if (keyCode === LEFT_ARROW) {
+ this.moveLeft();
+ } else if (keyCode === RIGHT_ARROW) {
+ this.moveRight();
+ } else if (keyCode === ENTER) {
+ this.onEnter();
+ }
}
}
- private loadChildren(option: CascaderOption, index: number, success?: () => void, failure?: () => void): void {
- if (this.nzLoadData) {
- this.isLoading = index < 0;
- option.loading = true;
- this.nzLoadData(option, index).then(() => {
- option.loading = this.isLoading = false;
- if (option.children) {
- option.children.forEach(child => child.parent = index < 0 ? undefined : option);
- this.setColumnData(option.children, index + 1);
- }
- if (success) {
- success();
- }
- }, () => {
- option.loading = this.isLoading = false;
- option.isLeaf = true;
- if (failure) {
- failure();
- }
- });
+ @HostListener('click', ['$event'])
+ onTriggerClick(event: MouseEvent): void {
+ if (this.nzDisabled) {
+ return;
}
- }
-
- private onSelectOption(option: CascaderOption, index: number): void {
- // trigger `nzSelect` event
- this.nzSelect.emit({ option, index });
-
- // 生成显示
- if (option.isLeaf || this.nzChangeOnSelect || this.isChangeOn(option, index)) {
- this.selectedOptions = this.activatedOptions;
- // 设置当前控件的显示值
- this.buildDisplayLabel();
- // 触发变更事件
- this.onValueChange();
+ if (this.nzShowSearch) {
+ this.focus();
}
-
- // close menu if click on leaf
- if (option.isLeaf) {
- this.delaySetMenuVisible(false, this.nzMouseLeaveDelay);
+ if (this.isActionTrigger('click')) {
+ this.delaySetMenuVisible(!this.menuVisible, 100);
}
+ this.onTouched();
}
- /** 由用户来定义点击后是否变更 */
- private isChangeOn(option: CascaderOption, index: number): boolean {
- if (typeof this.nzChangeOn === 'function') {
- return this.nzChangeOn(option, index) === true;
+ @HostListener('mouseenter', ['$event'])
+ onTriggerMouseEnter(event: MouseEvent): void {
+ if (this.nzDisabled) {
+ return;
+ }
+ if (this.isActionTrigger('hover')) {
+ this.delaySetMenuVisible(true, this.nzMouseEnterDelay, true);
}
- return false;
}
- private setColumnData(options: CascaderOption[], index: number): void {
- if (!arrayEquals(this.nzColumns[ index ], options)) {
- this.nzColumns[ index ] = options;
- if (index < this.nzColumns.length - 1) {
- this.nzColumns = this.nzColumns.slice(0, index + 1);
+ @HostListener('mouseleave', ['$event'])
+ onTriggerMouseLeave(event: MouseEvent): void {
+ if (this.nzDisabled) {
+ return;
+ }
+ if (!this.menuVisible || this.isOpening) {
+ event.preventDefault();
+ return;
+ }
+ if (this.isActionTrigger('hover')) {
+ const mouseTarget = event.relatedTarget as HTMLElement;
+ const hostEl = this.el;
+ const menuEl = this.menu && this.menu.nativeElement as HTMLElement;
+ if (hostEl.contains(mouseTarget) || (menuEl && menuEl.contains(mouseTarget))) {
+ return;
}
+ this.delaySetMenuVisible(false, this.nzMouseLeaveDelay);
}
}
- /**
- * 鼠标点击选项
- *
- * @param option 菜单选项
- * @param index 选项所在的列组的索引
- * @param event 鼠标事件
- */
- onOptionClick(option: CascaderOption, index: number, event: Event): void {
+ private isActionTrigger(action: 'click' | 'hover'): boolean {
+ return typeof this.nzTriggerAction === 'string'
+ ? this.nzTriggerAction === action
+ : this.nzTriggerAction.indexOf(action) !== -1;
+ }
+
+ onOptionClick(option: CascaderOption, columnIndex: number, event: Event): void {
if (event) {
event.preventDefault();
}
-
- // Keep focused state for keyboard support
- this.el.focus();
-
if (option && option.disabled) {
return;
}
-
- if (this.inSearch) {
- this.setSearchActiveOption(option as CascaderSearchOption, event);
- } else {
- this.setActiveOption(option, index, true);
- }
+ this.el.focus();
+ this.isSearching
+ ? this.setSearchOptionActivated(option as CascaderSearchOption, event)
+ : this.setOptionActivated(option, columnIndex, true);
}
- /** 按下回车键时选择 */
private onEnter(): void {
const columnIndex = Math.max(this.activatedOptions.length - 1, 0);
- const activeOption = this.activatedOptions[ columnIndex ];
- if (activeOption && !activeOption.disabled) {
- if (this.inSearch) {
- this.setSearchActiveOption(activeOption as CascaderSearchOption, null);
- } else {
- this.onSelectOption(activeOption, columnIndex);
- }
+ const option = this.activatedOptions[columnIndex];
+ if (option && !option.disabled) {
+ this.isSearching
+ ? this.setSearchOptionActivated(option as CascaderSearchOption, null)
+ : this.setOptionSelected(option, columnIndex);
}
}
- /**
- * press `up` or `down` arrow to activate the sibling option.
- */
private moveUpOrDown(isUp: boolean): void {
const columnIndex = Math.max(this.activatedOptions.length - 1, 0);
- // 该组中已经被激活的选项
- const activeOption = this.activatedOptions[ columnIndex ];
- // 该组所有的选项,用于遍历获取下一个被激活的选项
- const options = this.nzColumns[ columnIndex ] || [];
+ const activeOption = this.activatedOptions[columnIndex];
+ const options = this.columns[columnIndex] || [];
const length = options.length;
let nextIndex = -1;
if (!activeOption) { // 该列还没有选中的选项
@@ -1008,26 +596,15 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces
if (nextIndex < 0 || nextIndex >= length) {
break;
}
- const nextOption = options[ nextIndex ];
+ const nextOption = options[nextIndex];
if (!nextOption || nextOption.disabled) {
continue;
}
- this.setActiveOption(nextOption, columnIndex);
+ this.setOptionActivated(nextOption, columnIndex);
break;
}
}
- private moveUp(): void {
- this.moveUpOrDown(true);
- }
-
- private moveDown(): void {
- this.moveUpOrDown(false);
- }
-
- /**
- * press `left` arrow to remove the last selected option.
- */
private moveLeft(): void {
const options = this.activatedOptions;
if (options.length) {
@@ -1035,45 +612,28 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces
}
}
- /**
- * press `right` arrow to select the next column option.
- */
private moveRight(): void {
const length = this.activatedOptions.length;
- const options = this.nzColumns[ length ];
+ const options = this.columns[length];
if (options && options.length) {
const nextOpt = options.find(o => !o.disabled);
if (nextOpt) {
- this.setActiveOption(nextOpt, length);
+ this.setOptionActivated(nextOpt, length);
}
}
}
- /**
- * 鼠标划入选项
- *
- * @param option 菜单选项
- * @param index 选项所在的列组的索引
- * @param event 鼠标事件
- */
- onOptionMouseEnter(option: CascaderOption, index: number, event: Event): void {
+ onOptionMouseEnter(option: CascaderOption, columnIndex: number, event: Event): void {
event.preventDefault();
if (this.nzExpandTrigger === 'hover' && !option.isLeaf) {
- this.delaySelect(option, index, true);
+ this.delaySelectOption(option, columnIndex, true);
}
}
- /**
- * 鼠标划出选项
- *
- * @param option 菜单选项
- * @param index 选项所在的列组的索引
- * @param event 鼠标事件
- */
- onOptionMouseLeave(option: CascaderOption, index: number, event: Event): void {
+ onOptionMouseLeave(option: CascaderOption, columnIndex: number, event: Event): void {
event.preventDefault();
if (this.nzExpandTrigger === 'hover' && !option.isLeaf) {
- this.delaySelect(option, index, false);
+ this.delaySelectOption(option, columnIndex, false);
}
}
@@ -1084,162 +644,82 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces
}
}
- private delaySelect(option: CascaderOption, index: number, doSelect: boolean): void {
+ private delaySelectOption(option: CascaderOption, index: number, doSelect: boolean): void {
this.clearDelaySelectTimer();
if (doSelect) {
this.delaySelectTimer = setTimeout(() => {
- // 鼠标滑入只展开,不进行选中操作
- this.setActiveOption(option, index);
+ this.setOptionActivated(option, index);
this.delaySelectTimer = null;
}, 150);
}
}
- public getSubmitValue(): any[] {
- const values: any[] = [];
- this.selectedOptions.forEach(option => {
- values.push(this.getOptionValue(option));
- });
- return values;
- }
-
- private onValueChange(): void {
- const value = this.getSubmitValue();
- if (!arrayEquals(this.value, value)) {
- this.defaultValue = null; // clear the init-value
- this.value = value;
- this.onChange(value); // Angular need this
- if (value.length === 0) {
- this.nzClear.emit(); // first trigger `clear` and then `change`
- }
- this.nzSelectionChange.emit(this.selectedOptions);
- this.nzChange.emit(value);
- }
- }
-
- constructor(private elementRef: ElementRef,
- private cdr: ChangeDetectorRef,
- private nzUpdateHostClassService: NzUpdateHostClassService) {
- }
+ //#endregion
- private findOption(option: any, index: number): CascaderOption {
- const options: CascaderOption[] = this.nzColumns[ index ];
- if (options) {
- const value = typeof option === 'object' ? this.getOptionValue(option) : option;
- return options.find(o => value === this.getOptionValue(o));
- }
- return null;
- }
+ //#region Search
- private isLoaded(index: number): boolean {
- return this.nzColumns[ index ] && this.nzColumns[ index ].length > 0;
- }
+ private toggleSearchMode(): void {
+ const willBeInSearch = !!this._inputValue;
- private activateOnInit(index: number, value: any): void {
- let option = this.findOption(value, index);
- if (!option) {
- option = typeof value === 'object' ? value : {
- [ `${this.nzValueProperty || 'value'}` ]: value,
- [ `${this.nzLabelProperty || 'label'}` ]: value
- };
- }
- this.setActiveOption(option, index, false, false);
- }
+ // Take a snapshot before entering search mode.
+ if (!this.isSearching && willBeInSearch) {
+ this.isSearching = true;
+ this.activatedOptionsSnapshot = this.activatedOptions;
+ this.activatedOptions = [];
+ this.labelRenderText = '';
- private initOptions(index: number): void {
- const vs = this.defaultValue;
- const load = () => {
- this.activateOnInit(index, vs[ index ]);
- if (index < vs.length - 1) {
- this.initOptions(index + 1);
- }
- if (index === vs.length - 1) {
- this.afterWriteValue();
+ if (this.input) {
+ const width = this.input.nativeElement.offsetWidth;
+ this.dropdownWidthStyle = `${width}px`;
}
- };
-
- if (this.isLoaded(index) || !this.nzLoadData) {
- load();
- } else {
- const node = this.activatedOptions[ index - 1 ] || {};
- this.loadChildren(node, index - 1, load, this.afterWriteValue);
}
- }
-
- afterWriteValue(): void {
- this.selectedOptions = this.activatedOptions;
- this.value = this.getSubmitValue();
- this.buildDisplayLabel();
- }
- /**
- * Write a new value to the element.
- *
- * @Override (From ControlValueAccessor interface)
- */
- writeValue(value: any): void {
- const vs = this.defaultValue = toArray(value);
- if (vs.length) {
- this.initOptions(0);
- } else {
- this.value = vs;
- this.activatedOptions = [];
- this.afterWriteValue();
+ // Restore the snapshot after leaving search mode.
+ if (this.isSearching && !willBeInSearch) {
+ this.isSearching = false;
+ this.activatedOptions = this.activatedOptionsSnapshot;
+ this.columns = this.columnsSnapshot;
+ this.dropdownWidthStyle = '';
+ if (this.activatedOptions) {
+ this.buildDisplayLabel();
+ }
}
- }
-
- registerOnChange(fn: (_: any) => {}): void {
- this.onChange = fn;
- }
-
- registerOnTouched(fn: () => {}): void {
- this.onTouched = fn;
- }
- setDisabledState(isDisabled: boolean): void {
- if (isDisabled) {
- this.closeMenu();
+ if (this.isSearching) {
+ this.prepareSearchValue();
}
- this.nzDisabled = isDisabled;
}
private prepareSearchValue(): void {
const results: CascaderSearchOption[] = [];
const path: CascaderOption[] = [];
+
const defaultFilter = (inputValue: string, p: CascaderOption[]): boolean => {
- let flag = false;
- p.forEach(n => {
- const labelName = this.nzLabelProperty;
- if (n[ labelName ] && n[ labelName ].indexOf(inputValue) > -1) {
- flag = true;
- }
+ return p.some(n => {
+ const label = this.getOptionLabel(n);
+ return label && label.indexOf(inputValue) !== -1;
});
- return flag;
};
const filter: (inputValue: string, p: CascaderOption[]) => boolean =
this.nzShowSearch instanceof Object && (this.nzShowSearch as NzShowSearchOptions).filter
? (this.nzShowSearch as NzShowSearchOptions).filter
: defaultFilter;
+
const sorter: (a: CascaderOption[], b: CascaderOption[], inputValue: string) => number =
this.nzShowSearch instanceof Object && (this.nzShowSearch as NzShowSearchOptions).sorter;
+
const loopParent = (node: CascaderOption, forceDisabled = false) => {
const disabled = forceDisabled || node.disabled;
path.push(node);
node.children.forEach((sNode) => {
- if (!sNode.parent) {
- sNode.parent = node;
- }
- /** 搜索的同时建立 parent 连接,因为用户直接搜索的话是没有建立连接的,会提升从叶子节点回溯的难度 */
- if (!sNode.isLeaf) {
- loopParent(sNode, disabled);
- }
- if (sNode.isLeaf || !sNode.children || !sNode.children.length) {
- loopChild(sNode, disabled);
- }
+ if (!sNode.parent) { sNode.parent = node; } // Build parent reference when doing searching
+ if (!sNode.isLeaf) { loopParent(sNode, disabled); }
+ if (sNode.isLeaf || !sNode.children || !sNode.children.length) { loopChild(sNode, disabled); }
});
path.pop();
};
+
const loopChild = (node: CascaderOption, forceDisabled = false) => {
path.push(node);
const cPath = Array.from(path);
@@ -1247,62 +727,143 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces
const disabled = forceDisabled || node.disabled;
const option: CascaderSearchOption = {
disabled,
- isLeaf : true,
- path : cPath,
- [ this.nzLabelProperty ]: cPath.map(p => p.label).join(' / ')
+ isLeaf: true,
+ path: cPath,
+ [this.nzLabelProperty]: cPath.map(p => this.getOptionLabel(p)).join(' / ')
};
results.push(option);
}
path.pop();
};
- this.oldColumnsHolder[ 0 ].forEach(node => (node.isLeaf || !node.children || !node.children.length)
+ this.columnsSnapshot[0].forEach(node => (node.isLeaf || !node.children || !node.children.length)
? loopChild(node)
: loopParent(node));
if (sorter) {
results.sort((a, b) => sorter(a.path, b.path, this._inputValue));
}
- this.nzColumns = [ results ];
- }
-
- renderSearchString(str: string): string {
- return str.replace(new RegExp(this._inputValue, 'g'),
- ``);
+ this.columns = [results];
}
- setSearchActiveOption(result: CascaderSearchOption, event: Event): void {
- this.activatedOptions = [ result ];
+ setSearchOptionActivated(result: CascaderSearchOption, event: Event): void {
+ this.activatedOptions = [result];
this.delaySetMenuVisible(false, 200);
setTimeout(() => {
- this.inputValue = ''; // Not only remove `inputValue` but also reverse `nzColumns` in the hook.
+ this.inputValue = '';
const index = result.path.length - 1;
- const destiNode = result.path[ index ];
- const mockClickParent = (node: CascaderOption, cIndex: number) => {
+ const destinationNode = result.path[index];
+ // NOTE: optimize this.
+ const mockClickParent = (node: CascaderOption, columnIndex: number) => {
if (node && node.parent) {
- mockClickParent(node.parent, cIndex - 1);
+ mockClickParent(node.parent, columnIndex - 1);
}
- this.onOptionClick(node, cIndex, event);
+ this.onOptionClick(node, columnIndex, event);
};
- mockClickParent(destiNode, index);
+ mockClickParent(destinationNode, index);
}, 300);
}
- ngOnInit(): void {
- // 设置样式
- this.setClassMap();
- this.setLabelClass();
- this.setArrowClass();
- this.setLoadingClass();
- this.setClearClass();
- this.setInputClass();
- this.setMenuClass();
- this.setMenuColumnClass();
+ //#endregion
+
+ //#region Helpers
+
+ private get hasInput(): boolean {
+ return !!this.inputValue;
+ }
+
+ private get hasValue(): boolean {
+ return !!this.value && !!this.value.length;
+ }
+
+ get showPlaceholder(): boolean {
+ return !(this.hasInput || this.hasValue);
+ }
+
+ get clearIconVisible(): boolean {
+ return this.nzAllowClear && !this.nzDisabled && (this.hasValue || this.hasInput);
+ }
+
+ get isLabelRenderTemplate(): boolean {
+ return !!this.nzLabelRender;
+ }
+
+ // tslint:disable:no-any
+ getOptionLabel(option: CascaderOption): any {
+ return option[this.nzLabelProperty || 'label'];
+ }
+
+ getOptionValue(option: CascaderOption): any {
+ return option[this.nzValueProperty || 'value'];
+ }
+ // tslint:enable:no-any
+
+ isOptionActivated(option: CascaderOption, index: number): boolean {
+ const activeOpt = this.activatedOptions[index];
+ return activeOpt === option;
+ }
+
+ private buildDisplayLabel(): void {
+ const selectedOptions = this.selectedOptions;
+ const labels: string[] = selectedOptions.map(o => this.getOptionLabel(o));
+ if (this.isLabelRenderTemplate) {
+ this.labelRenderContext = { labels, selectedOptions };
+ } else {
+ this.labelRenderText = defaultDisplayRender.call(this, labels, selectedOptions);
+ }
+ // When components inits with default value, this would make display label appear correctly.
+ this.cdr.detectChanges();
+ }
+
+ //#endregion
+
+ setDisabledState(isDisabled: boolean): void {
+ if (isDisabled) {
+ this.closeMenu();
+ }
+ this.nzDisabled = isDisabled;
+ }
+
+ closeMenu(): void {
+ this.blur();
+ this.clearDelayMenuTimer();
+ this.setMenuVisible(false);
+ }
+
+ constructor(private elementRef: ElementRef, private cdr: ChangeDetectorRef) {
}
ngOnDestroy(): void {
- this.clearDelayTimer();
+ this.clearDelayMenuTimer();
this.clearDelaySelectTimer();
}
+ registerOnChange(fn: () => {}): void {
+ this.onChange = fn;
+ }
+
+ registerOnTouched(fn: () => {}): void {
+ this.onTouched = fn;
+ }
+
+ // tslint:disable:no-any
+ writeValue(value: any): void {
+ const vs = this.defaultValue = toArray(value);
+ if (vs.length) {
+ this.initOptions(0);
+ } else {
+ this.value = vs;
+ this.activatedOptions = [];
+ this.afterWriteValue();
+ }
+ }
+ // tslint:enable:no-any
+
+ onPositionChange(position: ConnectedOverlayPositionChange): void {
+ const newValue = position.connectionPair.originY === 'bottom' ? 'bottom' : 'top';
+ if (this.dropDownPosition !== newValue) {
+ this.dropDownPosition = newValue;
+ this.cdr.detectChanges();
+ }
+ }
}
diff --git a/components/cascader/nz-cascader.module.ts b/components/cascader/nz-cascader.module.ts
index fbe49a6c409..d6035d1bcaf 100644
--- a/components/cascader/nz-cascader.module.ts
+++ b/components/cascader/nz-cascader.module.ts
@@ -2,16 +2,18 @@ import { OverlayModule } from '@angular/cdk/overlay';
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
-import { NzIconModule } from '../icon/nz-icon.module';
+import { NzI18nModule } from '../i18n/nz-i18n.module';
+import { NzIconModule } from '../icon/nz-icon.module';
import { NzInputModule } from '../input/nz-input.module';
-
+import { NzCascaderOptionComponent } from './nz-cascader-li.component';
import { NzCascaderComponent } from './nz-cascader.component';
@NgModule({
- imports : [ CommonModule, FormsModule, OverlayModule, NzInputModule, NzIconModule ],
+ imports : [ CommonModule, FormsModule, OverlayModule, NzInputModule, NzIconModule, NzI18nModule ],
declarations: [
- NzCascaderComponent
+ NzCascaderComponent,
+ NzCascaderOptionComponent
],
exports : [
NzCascaderComponent
diff --git a/components/cascader/nz-cascader.spec.ts b/components/cascader/nz-cascader.spec.ts
index e1e0aa8b301..84987f942a6 100644
--- a/components/cascader/nz-cascader.spec.ts
+++ b/components/cascader/nz-cascader.spec.ts
@@ -13,8 +13,9 @@ import {
dispatchMouseEvent
} from '../core/testing';
-import { CascaderOption, NzCascaderComponent, NzShowSearchOptions } from './nz-cascader.component';
+import { NzCascaderComponent } from './nz-cascader.component';
import { NzCascaderModule } from './nz-cascader.module';
+import { CascaderOption, NzShowSearchOptions } from './types';
describe('cascader', () => {
let fixture;
@@ -79,12 +80,13 @@ describe('cascader', () => {
const input: HTMLElement = cascader.nativeElement.querySelector('.ant-cascader-input');
expect(input.getAttribute('placeholder')).toBe(placeholder);
});
- it('should prefixCls work', () => {
- testComponent.nzPrefixCls = 'new-cascader';
- fixture.detectChanges();
- expect(testComponent.cascader.nzPrefixCls).toBe('new-cascader');
- expect(cascader.nativeElement.className).toContain('new-cascader new-cascader-picker');
- });
+ // This API is redundant and should be removed.
+ // it('should prefixCls work', () => {
+ // testComponent.nzPrefixCls = 'new-cascader';
+ // fixture.detectChanges();
+ // expect(testComponent.cascader.nzPrefixCls).toBe('new-cascader');
+ // expect(cascader.nativeElement.className).toContain('new-cascader new-cascader-picker');
+ // });
it('should size work', () => {
testComponent.nzSize = 'small';
fixture.detectChanges();
@@ -158,7 +160,7 @@ describe('cascader', () => {
fixture.detectChanges();
tick(200);
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(true);
+ expect(testComponent.cascader.menuVisible).toBe(true);
expect(testComponent.onVisibleChange).toHaveBeenCalledTimes(1);
cascader.nativeElement.click();
@@ -167,7 +169,7 @@ describe('cascader', () => {
fixture.detectChanges();
flush();
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
expect(testComponent.onVisibleChange).toHaveBeenCalledTimes(2);
}));
it('should mouse hover toggle open', fakeAsync(() => {
@@ -175,12 +177,12 @@ describe('cascader', () => {
testComponent.nzTriggerAction = 'hover';
fixture.detectChanges();
expect(testComponent.nzDisabled).toBe(false);
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
expect(testComponent.onVisibleChange).toHaveBeenCalledTimes(0);
dispatchMouseEvent(cascader.nativeElement, 'mouseenter');
tick(300);
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(true);
+ expect(testComponent.cascader.menuVisible).toBe(true);
expect(testComponent.onVisibleChange).toHaveBeenCalledTimes(1);
const mouseleave = createMouseEvent('mouseleave');
@@ -202,7 +204,7 @@ describe('cascader', () => {
dispatchEvent(cascader.nativeElement, mouseleave);
tick(300);
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(true);
+ expect(testComponent.cascader.menuVisible).toBe(true);
mouseleave.initMouseEvent('mouseleave',
false, /* canBubble */
@@ -222,7 +224,7 @@ describe('cascader', () => {
dispatchEvent(cascader.nativeElement, mouseleave);
tick(300);
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(true);
+ expect(testComponent.cascader.menuVisible).toBe(true);
mouseleave.initMouseEvent('mouseleave',
false, /* canBubble */
@@ -244,7 +246,7 @@ describe('cascader', () => {
fixture.detectChanges();
flush();
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
expect(testComponent.onVisibleChange).toHaveBeenCalledTimes(2);
}));
it('should mouse hover toggle open immediately', fakeAsync(() => {
@@ -253,18 +255,18 @@ describe('cascader', () => {
testComponent.nzMouseEnterDelay = 0;
testComponent.nzMouseLeaveDelay = 0;
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
dispatchMouseEvent(cascader.nativeElement, 'mouseenter');
fixture.detectChanges();
flush();
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(true);
+ expect(testComponent.cascader.menuVisible).toBe(true);
expect(testComponent.onVisibleChange).toHaveBeenCalledTimes(1);
dispatchMouseEvent(cascader.nativeElement, 'mouseleave');
fixture.detectChanges();
flush();
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
expect(testComponent.onVisibleChange).toHaveBeenCalledTimes(2);
}));
it('should clear timer on option mouseenter and mouseleave', fakeAsync(() => {
@@ -274,10 +276,10 @@ describe('cascader', () => {
testComponent.nzExpandTrigger = 'hover';
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
testComponent.cascader.setMenuVisible(true);
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(true);
+ expect(testComponent.cascader.menuVisible).toBe(true);
flush();
fixture.detectChanges();
const optionEl = overlayContainerElement.querySelector('.ant-cascader-menu:nth-child(1) .ant-cascader-menu-item:nth-child(1)') as HTMLElement; // 第1列第1个
@@ -311,13 +313,13 @@ describe('cascader', () => {
fixture.detectChanges();
tick();
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
expect(testComponent.onVisibleChange).toHaveBeenCalledTimes(0);
testComponent.cascader.setMenuVisible(true);
fixture.detectChanges();
tick();
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
expect(testComponent.onVisibleChange).toHaveBeenCalledTimes(0);
}));
it('should disabled state work', fakeAsync(() => {
@@ -331,7 +333,7 @@ describe('cascader', () => {
fixture.detectChanges();
tick();
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
expect(testComponent.onVisibleChange).toHaveBeenCalledTimes(0);
}));
it('should disabled mouse hover open', fakeAsync(() => {
@@ -339,12 +341,12 @@ describe('cascader', () => {
testComponent.nzDisabled = true;
fixture.detectChanges();
expect(testComponent.cascader.nzDisabled).toBe(true);
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
expect(testComponent.onVisibleChange).toHaveBeenCalledTimes(0);
dispatchMouseEvent(cascader.nativeElement, 'mouseenter');
tick(300);
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
expect(testComponent.onVisibleChange).toHaveBeenCalledTimes(0);
testComponent.nzDisabled = false;
@@ -352,25 +354,25 @@ describe('cascader', () => {
testComponent.cascader.setMenuVisible(true);
fixture.detectChanges();
expect(testComponent.cascader.nzDisabled).toBe(false);
- expect(testComponent.cascader.isMenuVisible()).toBe(true);
+ expect(testComponent.cascader.menuVisible).toBe(true);
expect(testComponent.onVisibleChange).toHaveBeenCalledTimes(1);
testComponent.nzDisabled = true;
fixture.detectChanges();
dispatchMouseEvent(cascader.nativeElement, 'mouseleave');
tick(300);
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(true);
+ expect(testComponent.cascader.menuVisible).toBe(true);
expect(testComponent.onVisibleChange).toHaveBeenCalledTimes(1);
}));
it('should mouse leave not work when menu not open', fakeAsync(() => {
testComponent.nzTriggerAction = [ 'hover' ];
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
dispatchMouseEvent(cascader.nativeElement, 'mouseleave');
fixture.detectChanges();
tick(300);
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
expect(testComponent.onVisibleChange).toHaveBeenCalledTimes(0);
}));
it('should clear value work', fakeAsync(() => {
@@ -456,7 +458,7 @@ describe('cascader', () => {
fixture.detectChanges();
tick(200);
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(true);
+ expect(testComponent.cascader.menuVisible).toBe(true);
expect(overlayContainerElement.querySelector('.ant-cascader-menus').classList).toContain('menu-classA');
expect(overlayContainerElement.querySelector('.ant-cascader-menu').classList).toContain('column-classA');
}));
@@ -466,7 +468,7 @@ describe('cascader', () => {
fixture.detectChanges();
tick(200);
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(true);
+ expect(testComponent.cascader.menuVisible).toBe(true);
const targetElement = overlayContainerElement.querySelector('.menu-classA') as HTMLElement;
expect(targetElement.style.height).toBe('120px');
}));
@@ -688,7 +690,7 @@ describe('cascader', () => {
fixture.detectChanges();
flush(); // wait for cdk-overlay to open
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(true);
+ expect(testComponent.cascader.menuVisible).toBe(true);
expect(overlayContainerElement.querySelectorAll('.ant-cascader-menu').length).toBe(3); // 3列
const itemEl1 = overlayContainerElement.querySelector('.ant-cascader-menu:nth-child(1) .ant-cascader-menu-item:nth-child(1)') as HTMLElement;
@@ -717,7 +719,7 @@ describe('cascader', () => {
fixture.detectChanges();
flush(); // wait for cdk-overlay close
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
expect(testComponent.values.join(',')).toBe('zhejiang,ningbo');
}));
it('should click option to change column count 3', () => {
@@ -763,53 +765,53 @@ describe('cascader', () => {
fixture.detectChanges();
testComponent.cascader.setMenuVisible(true);
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(true);
+ expect(testComponent.cascader.menuVisible).toBe(true);
(overlayContainerElement.querySelector('.ant-cascader-menu:nth-child(1) .ant-cascader-menu-item:nth-child(1)') as HTMLElement).click(); // 第1列第1个
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(true);
+ expect(testComponent.cascader.menuVisible).toBe(true);
(overlayContainerElement.querySelector('.ant-cascader-menu:nth-child(2) .ant-cascader-menu-item:nth-child(1)') as HTMLElement).click(); // 第2列第1个
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(true);
+ expect(testComponent.cascader.menuVisible).toBe(true);
(overlayContainerElement.querySelector('.ant-cascader-menu:nth-child(3) .ant-cascader-menu-item:nth-child(1)') as HTMLElement).click(); // 第3列第1个
fixture.detectChanges();
tick(200);
fixture.detectChanges();
flush(); // wait for cdk-overlay to close
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
expect(overlayContainerElement.querySelectorAll('.ant-cascader-menu').length).toBe(0);
}));
it('should open menu when press DOWN_ARROW', fakeAsync(() => {
const DOWN_ARROW = 40;
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
dispatchKeyboardEvent(cascader.nativeElement, 'keydown', DOWN_ARROW);
fixture.detectChanges();
tick(200);
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(true);
+ expect(testComponent.cascader.menuVisible).toBe(true);
}));
it('should open menu when press UP_ARROW', fakeAsync(() => {
const UP_ARROW = 38;
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
dispatchKeyboardEvent(cascader.nativeElement, 'keydown', UP_ARROW);
fixture.detectChanges();
tick(200);
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(true);
+ expect(testComponent.cascader.menuVisible).toBe(true);
}));
it('should close menu when press ESC', fakeAsync(() => {
const ESC = 27;
fixture.detectChanges();
testComponent.cascader.setMenuVisible(true);
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(true);
+ expect(testComponent.cascader.menuVisible).toBe(true);
dispatchKeyboardEvent(cascader.nativeElement, 'keydown', ESC);
fixture.detectChanges();
flush(); // wait for cdk-overlay to close
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
}));
it('should navigate up when press UP_ARROW', fakeAsync(() => {
const UP_ARROW = 38;
@@ -930,7 +932,7 @@ describe('cascader', () => {
expect(testComponent.values[ 2 ]).toBe('xihu');
flush(); // wait for cdk-overlay to close
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
}));
it('should key nav disabled option correct', fakeAsync(() => {
const DOWN_ARROW = 40;
@@ -1009,10 +1011,10 @@ describe('cascader', () => {
fixture.detectChanges();
keys.forEach(key => {
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
dispatchKeyboardEvent(cascader.nativeElement, 'keydown', key);
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
});
}));
it('should expand option on hover', fakeAsync(() => {
@@ -1087,7 +1089,7 @@ describe('cascader', () => {
flush(); // wait for cdk-overlay to close
fixture.detectChanges();
expect(overlayContainerElement.querySelectorAll('.ant-cascader-menu').length).toBe(0); // 0列
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
}));
it('should not expand disabled option on hover', fakeAsync(() => {
testComponent.nzExpandTrigger = 'hover';
@@ -1135,7 +1137,7 @@ describe('cascader', () => {
itemEl1.click();
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(true);
+ expect(testComponent.cascader.menuVisible).toBe(true);
expect(itemEl1.classList).toContain('ant-cascader-menu-item-active');
expect(overlayContainerElement.querySelectorAll('.ant-cascader-menu').length).toBe(2); // 2列
expect(testComponent.values).toBeDefined();
@@ -1148,7 +1150,7 @@ describe('cascader', () => {
itemEl2.click();
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(true);
+ expect(testComponent.cascader.menuVisible).toBe(true);
expect(itemEl1.classList).toContain('ant-cascader-menu-item-active');
expect(itemEl2.classList).toContain('ant-cascader-menu-item-active');
expect(overlayContainerElement.querySelectorAll('.ant-cascader-menu').length).toBe(3); // 3列
@@ -1174,7 +1176,7 @@ describe('cascader', () => {
flush(); // wait for cdk-overlay to close
fixture.detectChanges();
expect(overlayContainerElement.querySelectorAll('.ant-cascader-menu').length).toBe(0); // 0列
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
}));
it('should not change on hover work', fakeAsync(() => {
testComponent.nzChangeOnSelect = true;
@@ -1196,7 +1198,7 @@ describe('cascader', () => {
tick(200);
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(true);
+ expect(testComponent.cascader.menuVisible).toBe(true);
expect(itemEl1.classList).toContain('ant-cascader-menu-item-active');
expect(overlayContainerElement.querySelectorAll('.ant-cascader-menu').length).toBe(2); // 2列
expect(testComponent.values).toBeNull(); // mouseenter does not trigger selection
@@ -1209,7 +1211,7 @@ describe('cascader', () => {
tick(200);
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(true);
+ expect(testComponent.cascader.menuVisible).toBe(true);
expect(itemEl1.classList).toContain('ant-cascader-menu-item-active');
expect(itemEl2.classList).toContain('ant-cascader-menu-item-active');
expect(overlayContainerElement.querySelectorAll('.ant-cascader-menu').length).toBe(3); // 3列
@@ -1239,7 +1241,7 @@ describe('cascader', () => {
flush(); // wait for cdk-overlay to close
fixture.detectChanges();
expect(overlayContainerElement.querySelectorAll('.ant-cascader-menu').length).toBe(0); // 0列
- expect(testComponent.cascader.isMenuVisible()).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
}));
it('should change on function work', fakeAsync(() => {
testComponent.nzChangeOn = testComponent.fakeChangeOn;
@@ -1264,7 +1266,7 @@ describe('cascader', () => {
fixture.detectChanges();
tick(200);
fixture.detectChanges();
- expect(testComponent.cascader.isMenuVisible()).toBe(true);
+ expect(testComponent.cascader.menuVisible).toBe(true);
itemEl1 = overlayContainerElement.querySelector('.ant-cascader-menu:nth-child(1) .ant-cascader-menu-item:nth-child(1)') as HTMLElement; // 第1列第1个
itemEl2 = overlayContainerElement.querySelector('.ant-cascader-menu:nth-child(1) .ant-cascader-menu-item:nth-child(2)') as HTMLElement; // 第1列第2个
@@ -1300,32 +1302,30 @@ describe('cascader', () => {
fixture.detectChanges();
expect(testComponent.cascader.dropDownPosition).toBe('bottom');
});
- it('should support search', (done) => {
+ it('should support search', fakeAsync(() => {
fixture.detectChanges();
testComponent.nzShowSearch = true;
fixture.detectChanges();
- // const input: HTMLElement = cascader.nativeElement.querySelector('.ant-cascader-input');
const spy = spyOn(testComponent.cascader, 'focus');
cascader.nativeElement.click();
fixture.detectChanges();
- // expect(document.activeElement).toBe(input);
expect(spy).toHaveBeenCalled();
testComponent.cascader.inputValue = 'o';
testComponent.cascader.setMenuVisible(true);
fixture.detectChanges();
const itemEl1 = overlayContainerElement.querySelector('.ant-cascader-menu:nth-child(1) .ant-cascader-menu-item:nth-child(1)') as HTMLElement;
- expect(testComponent.cascader.inSearch).toBe(true);
+ expect(testComponent.cascader.isSearching).toBe(true);
expect(itemEl1.innerText).toBe('Zhejiang / Hangzhou / West Lake');
itemEl1.click();
- fixture.whenStable().then(() => {
- expect(testComponent.cascader.inSearch).toBe(false);
- expect(testComponent.cascader.menuVisible).toBe(false);
- expect(testComponent.cascader.inputValue).toBe('');
- expect(testComponent.values.join(',')).toBe('zhejiang,hangzhou,xihu');
- done();
- });
- });
- it('should support nzLabelProperty', (done) => {
+ fixture.detectChanges();
+ flush();
+ fixture.detectChanges();
+ expect(testComponent.cascader.isSearching).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
+ expect(testComponent.cascader.inputValue).toBe('');
+ expect(testComponent.values.join(',')).toBe('zhejiang,hangzhou,xihu');
+ }));
+ it('should support nzLabelProperty', fakeAsync(() => {
testComponent.nzShowSearch = true;
testComponent.nzLabelProperty = 'l';
fixture.detectChanges();
@@ -1335,27 +1335,21 @@ describe('cascader', () => {
testComponent.cascader.setMenuVisible(true);
fixture.detectChanges();
const itemEl1 = overlayContainerElement.querySelector('.ant-cascader-menu:nth-child(1) .ant-cascader-menu-item:nth-child(1)') as HTMLElement;
- expect(testComponent.cascader.inSearch).toBe(true);
+ expect(testComponent.cascader.isSearching).toBe(true);
expect(itemEl1.innerText).toBe('Zhejiang / Hangzhou / West Lake');
itemEl1.click();
- fixture.whenStable().then(() => {
- expect(testComponent.cascader.inSearch).toBe(false);
- expect(testComponent.cascader.menuVisible).toBe(false);
- expect(testComponent.cascader.inputValue).toBe('');
- expect(testComponent.values.join(',')).toBe('zhejiang,hangzhou,xihu');
- done();
- });
- });
- it('should support custom filter', (done) => {
+ fixture.detectChanges();
+ flush();
+ fixture.detectChanges();
+ expect(testComponent.cascader.isSearching).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
+ expect(testComponent.cascader.inputValue).toBe('');
+ expect(testComponent.values.join(',')).toBe('zhejiang,hangzhou,xihu');
+ }));
+ it('should support custom filter', fakeAsync(() => {
testComponent.nzShowSearch = {
filter(inputValue: string, path: CascaderOption[]): boolean {
- let flag = false;
- path.forEach(p => {
- if (p.label.indexOf(inputValue) > -1) {
- flag = true;
- }
- });
- return flag;
+ return path.some(p => p.label.indexOf(inputValue) !== -1);
}
} as NzShowSearchOptions;
fixture.detectChanges();
@@ -1363,18 +1357,18 @@ describe('cascader', () => {
testComponent.cascader.setMenuVisible(true);
fixture.detectChanges();
const itemEl1 = overlayContainerElement.querySelector('.ant-cascader-menu:nth-child(1) .ant-cascader-menu-item:nth-child(1)') as HTMLElement;
- expect(testComponent.cascader.inSearch).toBe(true);
+ expect(testComponent.cascader.isSearching).toBe(true);
expect(itemEl1.innerText).toBe('Zhejiang / Hangzhou / West Lake');
itemEl1.click();
- fixture.whenStable().then(() => {
- expect(testComponent.cascader.inSearch).toBe(false);
- expect(testComponent.cascader.menuVisible).toBe(false);
- expect(testComponent.cascader.inputValue).toBe('');
- expect(testComponent.values.join(',')).toBe('zhejiang,hangzhou,xihu');
- done();
- });
- });
- it('should support custom sorter', (done) => {
+ fixture.detectChanges();
+ flush();
+ fixture.detectChanges();
+ expect(testComponent.cascader.isSearching).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
+ expect(testComponent.cascader.inputValue).toBe('');
+ expect(testComponent.values.join(',')).toBe('zhejiang,hangzhou,xihu');
+ }));
+ it('should support custom sorter', fakeAsync(() => {
testComponent.nzShowSearch = {
sorter(a: CascaderOption[], b: CascaderOption[], inputValue: string): number {
const l1 = a[ 0 ].label;
@@ -1387,17 +1381,17 @@ describe('cascader', () => {
testComponent.cascader.setMenuVisible(true);
fixture.detectChanges();
const itemEl1 = overlayContainerElement.querySelector('.ant-cascader-menu:nth-child(1) .ant-cascader-menu-item:nth-child(1)') as HTMLElement;
- expect(testComponent.cascader.inSearch).toBe(true);
+ expect(testComponent.cascader.isSearching).toBe(true);
expect(itemEl1.innerText).toBe('Jiangsu / Nanjing / Zhong Hua Men');
itemEl1.click();
- fixture.whenStable().then(() => {
- expect(testComponent.cascader.inSearch).toBe(false);
- expect(testComponent.cascader.menuVisible).toBe(false);
- expect(testComponent.cascader.inputValue).toBe('');
- expect(testComponent.values.join(',')).toBe('jiangsu,nanjing,zhonghuamen');
- done();
- });
- });
+ fixture.detectChanges();
+ flush();
+ fixture.detectChanges();
+ expect(testComponent.cascader.isSearching).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
+ expect(testComponent.cascader.inputValue).toBe('');
+ expect(testComponent.values.join(',')).toBe('jiangsu,nanjing,zhonghuamen');
+ }));
it('should forbid disabled search options to be clicked', fakeAsync(() => {
testComponent.nzOptions = options4;
fixture.detectChanges();
@@ -1406,11 +1400,11 @@ describe('cascader', () => {
fixture.detectChanges();
const itemEl1 = overlayContainerElement.querySelector('.ant-cascader-menu:nth-child(1) .ant-cascader-menu-item:nth-child(1)') as HTMLElement;
expect(itemEl1.innerText).toBe('Zhejiang / Hangzhou / West Lake');
- expect(testComponent.cascader.nzColumns[ 0 ][ 0 ].disabled).toBe(true);
+ expect(testComponent.cascader.columns[ 0 ][ 0 ].disabled).toBe(true);
itemEl1.click();
tick(300);
fixture.detectChanges();
- expect(testComponent.cascader.inSearch).toBe(true);
+ expect(testComponent.cascader.isSearching).toBe(true);
expect(testComponent.cascader.menuVisible).toBe(true);
expect(testComponent.cascader.inputValue).toBe('o');
expect(testComponent.values).toBe(null);
@@ -1421,9 +1415,9 @@ describe('cascader', () => {
testComponent.cascader.inputValue = 'o';
testComponent.cascader.setMenuVisible(true);
fixture.detectChanges();
- expect(testComponent.cascader.nzColumns[ 0 ][ 0 ].disabled).toBe(true);
- expect(testComponent.cascader.nzColumns[ 0 ][ 1 ].disabled).toBe(undefined);
- expect(testComponent.cascader.nzColumns[ 0 ][ 2 ].disabled).toBe(true);
+ expect(testComponent.cascader.columns[ 0 ][ 0 ].disabled).toBe(true);
+ expect(testComponent.cascader.columns[ 0 ][ 1 ].disabled).toBe(undefined);
+ expect(testComponent.cascader.columns[ 0 ][ 2 ].disabled).toBe(true);
});
it('should support arrow in search mode', (done) => {
const DOWN_ARROW = 40;
@@ -1465,7 +1459,7 @@ describe('cascader', () => {
fixture.detectChanges();
expect(itemEl1.classList).not.toContain('ant-cascader-menu-item-active');
});
- it('should support search a root node have no children ', (done) => {
+ it('should support search a root node have no children ', fakeAsync(() => {
fixture.detectChanges();
testComponent.nzShowSearch = true;
testComponent.nzOptions = options5;
@@ -1478,17 +1472,17 @@ describe('cascader', () => {
testComponent.cascader.setMenuVisible(true);
fixture.detectChanges();
const itemEl1 = overlayContainerElement.querySelector('.ant-cascader-menu:nth-child(1) .ant-cascader-menu-item:nth-child(1)') as HTMLElement;
- expect(testComponent.cascader.inSearch).toBe(true);
+ expect(testComponent.cascader.isSearching).toBe(true);
expect(itemEl1.innerText).toBe('Root');
itemEl1.click();
- fixture.whenStable().then(() => {
- expect(testComponent.cascader.inSearch).toBe(false);
- expect(testComponent.cascader.menuVisible).toBe(false);
- expect(testComponent.cascader.inputValue).toBe('');
- expect(testComponent.values.join(',')).toBe('root');
- done();
- });
- });
+ fixture.detectChanges();
+ flush();
+ fixture.detectChanges();
+ expect(testComponent.cascader.isSearching).toBe(false);
+ expect(testComponent.cascader.menuVisible).toBe(false);
+ expect(testComponent.cascader.inputValue).toBe('');
+ expect(testComponent.values.join(',')).toBe('root');
+ }));
it('should re-prepare search results when nzOptions change', () => {
fixture.detectChanges();
testComponent.nzShowSearch = true;
@@ -1497,11 +1491,11 @@ describe('cascader', () => {
testComponent.cascader.setMenuVisible(true);
fixture.detectChanges();
let itemEl1 = overlayContainerElement.querySelector('.ant-cascader-menu:nth-child(1) .ant-cascader-menu-item:nth-child(1)') as HTMLElement;
- expect(testComponent.cascader.inSearch).toBe(true);
+ expect(testComponent.cascader.isSearching).toBe(true);
expect(itemEl1.innerText).toBe('Zhejiang / Hangzhou / West Lake');
testComponent.nzOptions = options2;
fixture.detectChanges();
- expect(testComponent.cascader.inSearch).toBe(true);
+ expect(testComponent.cascader.isSearching).toBe(true);
itemEl1 = overlayContainerElement.querySelector('.ant-cascader-menu:nth-child(1) .ant-cascader-menu-item:nth-child(1)') as HTMLElement;
expect(itemEl1.innerText).toBe('Option1 / Option11');
});
@@ -1607,7 +1601,7 @@ describe('cascader', () => {
tick(3000);
fixture.detectChanges();
expect(testComponent.addCallTimes).toHaveBeenCalledTimes(3);
- expect(testComponent.cascader.nzColumns.length).toBe(3);
+ expect(testComponent.cascader.columns.length).toBe(3);
expect(testComponent.values.join(',')).toBe('zhejiang,hangzhou,xihu');
}));
@@ -1854,7 +1848,6 @@ const options5 = [ {
[nzLabelProperty]="nzLabelProperty"
[nzValueProperty]="nzValueProperty"
[nzPlaceHolder]="nzPlaceHolder"
- [nzPrefixCls]="nzPrefixCls"
[nzShowArrow]="nzShowArrow"
[nzShowInput]="nzShowInput"
[nzShowSearch]="nzShowSearch"
@@ -1899,7 +1892,6 @@ export class NzDemoCascaderDefaultComponent {
nzLabelProperty = 'label';
nzValueProperty = 'value';
nzPlaceHolder = 'please select';
- nzPrefixCls = 'ant-cascader';
nzShowArrow = true;
nzShowInput = true;
nzShowSearch: boolean | NzShowSearchOptions = false;
diff --git a/components/cascader/public-api.ts b/components/cascader/public-api.ts
index 3c6add083c3..234fabbcae6 100644
--- a/components/cascader/public-api.ts
+++ b/components/cascader/public-api.ts
@@ -1,2 +1,3 @@
export * from './nz-cascader.module';
export * from './nz-cascader.component';
+export * from './types';
diff --git a/components/cascader/types.ts b/components/cascader/types.ts
new file mode 100644
index 00000000000..4431b586d67
--- /dev/null
+++ b/components/cascader/types.ts
@@ -0,0 +1,27 @@
+export type NzCascaderExpandTrigger = 'click' | 'hover';
+export type NzCascaderTriggerType = 'click' | 'hover';
+export type NzCascaderSize = 'small' | 'large' | 'default' ;
+
+// tslint:disable:no-any
+export interface CascaderOption {
+ value?: any;
+ label?: string;
+ title?: string;
+ disabled?: boolean;
+ loading?: boolean;
+ isLeaf?: boolean;
+ parent?: CascaderOption;
+ children?: CascaderOption[];
+
+ [ key: string ]: any;
+}
+// tslint:enable:no-any
+
+export interface CascaderSearchOption extends CascaderOption {
+ path: CascaderOption[];
+}
+
+export interface NzShowSearchOptions {
+ filter?(inputValue: string, path: CascaderOption[]): boolean;
+ sorter?(a: CascaderOption[], b: CascaderOption[], inputValue: string): number;
+}
diff --git a/components/core/interface/interface.ts b/components/core/interface/interface.ts
new file mode 100644
index 00000000000..cec0b74e495
--- /dev/null
+++ b/components/core/interface/interface.ts
@@ -0,0 +1,3 @@
+export interface ClassMap {
+ [ key: string ]: boolean;
+}
diff --git a/components/core/overlay/overlay-position-map.ts b/components/core/overlay/overlay-position-map.ts
index ac2797b8aa2..12364eb33e0 100644
--- a/components/core/overlay/overlay-position-map.ts
+++ b/components/core/overlay/overlay-position-map.ts
@@ -90,6 +90,7 @@ export const POSITION_MAP: { [key: string]: ConnectionPositionPair } = {
// TODO: The whole logic does not make sense here, _objectValues just returns a copy of original array
export const DEFAULT_4_POSITIONS = _objectValues([ POSITION_MAP.top, POSITION_MAP.right, POSITION_MAP.bottom, POSITION_MAP.left]);
export const DEFAULT_DROPDOWN_POSITIONS = _objectValues([ POSITION_MAP.bottomLeft, POSITION_MAP.topLeft ]);
+export const EXPANDED_DROPDOWN_POSITIONS = _objectValues([ POSITION_MAP.bottomLeft, POSITION_MAP.bottomRight, POSITION_MAP.topLeft, POSITION_MAP.topRight ]);
// export const DEFAULT_DATEPICKER_POSITIONS = [
// {
diff --git a/components/core/style/map.ts b/components/core/style/map.ts
new file mode 100644
index 00000000000..a344f42a3d5
--- /dev/null
+++ b/components/core/style/map.ts
@@ -0,0 +1,3 @@
+export function classMapToString(map: { [ key: string ]: boolean }): string {
+ return Object.keys(map).filter(item => !!map[ item ]).join(' ');
+}
diff --git a/components/core/util/array.ts b/components/core/util/array.ts
new file mode 100644
index 00000000000..a05e9ffff17
--- /dev/null
+++ b/components/core/util/array.ts
@@ -0,0 +1,25 @@
+export function toArray(value: T | T[]): T[] {
+ let ret: T[];
+ if (value == null) {
+ ret = [];
+ } else if (!Array.isArray(value)) {
+ ret = [ value ];
+ } else {
+ ret = value;
+ }
+ return ret;
+}
+
+export function arrayEquals(array1: T[], array2: T[]): boolean {
+ if (!array1 || !array2 || array1.length !== array2.length) {
+ return false;
+ }
+
+ const len = array1.length;
+ for (let i = 0; i < len; i++) {
+ if (array1[ i ] !== array2[ i ]) {
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/components/core/util/logger/logger.module.ts b/components/core/util/logger/logger.module.ts
index 1bf233207ce..0680598acdc 100644
--- a/components/core/util/logger/logger.module.ts
+++ b/components/core/util/logger/logger.module.ts
@@ -5,7 +5,7 @@ import { LOGGER_SERVICE_PROVIDER, NZ_LOGGER_STATE } from './logger.service';
@NgModule({
providers: [
{ provide: NZ_LOGGER_STATE, useValue: false },
- LOGGER_SERVICE_PROVIDER,
- ],
+ LOGGER_SERVICE_PROVIDER
+ ]
})
export class LoggerModule { }
diff --git a/components/icon/nz-icon.service.ts b/components/icon/nz-icon.service.ts
index 92be31bca93..2c0a9910eaf 100644
--- a/components/icon/nz-icon.service.ts
+++ b/components/icon/nz-icon.service.ts
@@ -1,6 +1,6 @@
import { DOCUMENT } from '@angular/common';
import { HttpBackend } from '@angular/common/http';
-import { isDevMode, Inject, Injectable, InjectionToken, Optional, RendererFactory2 } from '@angular/core';
+import { Inject, Injectable, InjectionToken, Optional, RendererFactory2 } from '@angular/core';
import { IconDefinition, IconService } from '@ant-design/icons-angular';
import {
CalendarOutline,
@@ -27,7 +27,6 @@ import {
UploadOutline,
UpOutline
} from '@ant-design/icons-angular/icons';
-import { LoggerService } from '../core/util/logger';
export interface NzIconfontOption {
scriptUrl: string;
@@ -76,15 +75,15 @@ export class NzIconService extends IconService {
warnAPI(type: 'old' | 'cross' | 'vertical'): void {
if (type === 'old' && !this.warnedAboutAPI) {
- this.loggerService.warn(` would be deprecated soon. Please use API.`);
+ console.warn(`[NG-ZORRO] would be deprecated soon. Please use API.`);
this.warnedAboutAPI = true;
}
if (type === 'cross' && !this.warnedAboutCross) {
- this.loggerService.warn(`'cross' icon is replaced by 'close' icon.`);
+ console.warn(`[NG-ZORRO]: 'cross' icon is replaced by 'close' icon.`);
this.warnedAboutCross = true;
}
if (type === 'vertical' && !this.warnedAboutVertical) {
- this.loggerService.warn(`'verticle' is misspelled, would be corrected in the next major version.`);
+ console.warn(`[NG-ZORRO]: 'verticle' is misspelled, would be corrected in the next major version.`);
this.warnedAboutVertical = true;
}
}
@@ -123,8 +122,7 @@ export class NzIconService extends IconService {
@Optional() protected handler: HttpBackend,
@Optional() @Inject(DOCUMENT) protected document: any,
@Optional() @Inject(NZ_ICONS) private icons: IconDefinition[],
- @Optional() @Inject(NZ_ICON_DEFAULT_TWOTONE_COLOR) private defaultColor: string,
- private loggerService: LoggerService
+ @Optional() @Inject(NZ_ICON_DEFAULT_TWOTONE_COLOR) private defaultColor: string
) {
super(rendererFactory, handler, document);
@@ -135,7 +133,7 @@ export class NzIconService extends IconService {
if (this.defaultColor.startsWith('#')) {
primaryColor = this.defaultColor;
} else {
- this.loggerService.warn('twotone color must be a hex color!');
+ console.warn('[NG-ZORRO]: twotone color must be a hex color!');
}
}
this.twoToneColor = { primaryColor };