diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 00000000000..12513a1b076
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,12 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "chrome",
+ "request": "launch",
+ "name": "Chrome",
+ "url": "http://localhost:9876",
+ "webRoot": "${workspaceRoot}"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/components/cascader/demo/basic.ts b/components/cascader/demo/basic.ts
index 43bd60c3cbb..5cd1a260370 100644
--- a/components/cascader/demo/basic.ts
+++ b/components/cascader/demo/basic.ts
@@ -65,6 +65,7 @@ const otherOptions = [{
[(ngModel)]="values"
(ngModelChange)="onChanges($event)">
+
Change Options
@@ -92,7 +93,7 @@ export class NzDemoCascaderBasicComponent implements OnInit {
ngOnInit(): void {
// let's set nzOptions in a asynchronous way
setTimeout(() => {
- this.nzOptions = options;
+ this.nzOptions = options;
}, 100);
}
diff --git a/components/cascader/demo/search.md b/components/cascader/demo/search.md
new file mode 100644
index 00000000000..9b633396755
--- /dev/null
+++ b/components/cascader/demo/search.md
@@ -0,0 +1,14 @@
+---
+order: 11
+title:
+ zh-CN: 搜索
+ en-US: Search
+---
+
+## zh-CN
+
+可以直接搜索选项并选择。
+
+## en-US
+
+Search and select an option directly.
diff --git a/components/cascader/demo/search.ts b/components/cascader/demo/search.ts
new file mode 100644
index 00000000000..e9367131bce
--- /dev/null
+++ b/components/cascader/demo/search.ts
@@ -0,0 +1,113 @@
+// tslint:disable:no-any
+import { Component, OnInit } from '@angular/core';
+
+const options = [ {
+ value: 'zhejiang',
+ label: 'Zhejiang',
+ children: [ {
+ value: 'hangzhou',
+ label: 'Hangzhou',
+ children: [ {
+ value: 'xihu',
+ label: 'West Lake',
+ isLeaf: true
+ } ]
+ }, {
+ value: 'ningbo',
+ label: 'Ningbo',
+ isLeaf: true,
+ disabled: true
+ } ]
+}, {
+ value: 'jiangsu',
+ label: 'Jiangsu',
+ children: [ {
+ value: 'nanjing',
+ label: 'Nanjing',
+ children: [ {
+ value: 'zhonghuamen',
+ label: 'Zhong Hua Men',
+ isLeaf: true
+ } ]
+ } ]
+} ];
+
+const otherOptions = [ {
+ value: 'fujian',
+ label: 'Fujian',
+ children: [ {
+ value: 'xiamen',
+ label: 'Xiamen',
+ children: [ {
+ value: 'Kulangsu',
+ label: 'Kulangsu',
+ isLeaf: true
+ } ]
+ } ]
+}, {
+ value: 'guangxi',
+ label: 'Guangxi',
+ children: [ {
+ value: 'guilin',
+ label: 'Guilin',
+ children: [ {
+ value: 'Lijiang',
+ label: 'Li Jiang River',
+ isLeaf: true
+ } ]
+ } ]
+} ];
+
+@Component({
+ selector: 'nz-demo-cascader-search',
+ template: `
+
+
+
+
+ Change Options
+
+ `,
+ styles: [
+ `
+ .ant-cascader-picker {
+ width: 300px;
+ }
+ .change-options {
+ display: inline-block;
+ font-size: 12px;
+ margin-top: 8px;
+ }
+ `
+ ]
+})
+export class NzDemoCascaderSearchComponent implements OnInit {
+ /** init data */
+ public nzOptions = null;
+
+ /** ngModel value */
+ public values: any[] = null;
+
+ ngOnInit(): void {
+ // let's set nzOptions in a asynchronous way
+ setTimeout(() => {
+ this.nzOptions = options;
+ }, 100);
+ }
+
+ public changeNzOptions(): void {
+ if (this.nzOptions === options) {
+ this.nzOptions = otherOptions;
+ } else {
+ this.nzOptions = options;
+ }
+ }
+
+ public onChanges(values: any): void {
+ console.log(values, this.values);
+ }
+}
diff --git a/components/cascader/doc/index.en-US.md b/components/cascader/doc/index.en-US.md
index e5555693a07..91d8f7d28dc 100755
--- a/components/cascader/doc/index.en-US.md
+++ b/components/cascader/doc/index.en-US.md
@@ -39,6 +39,7 @@ Cascade selection box.
| `[nzPlaceHolder]` | input placeholder | string | 'Please select' |
| `[nzShowArrow]` | Whether show arrow | boolean | true |
| `[nzShowInput]` | Whether show input | boolean | true |
+| `[nzShowSearch]` | Whether support search | `boolean` `NzShowSearchOptions` | `false` |
| `[nzSize]` | input size, one of `large` `default` `small` | string | `default` |
| `[nzValueProperty]` | the value property name of options | string | 'value' |
| `(ngModelChange)` | Emit on values change | `EventEmitter` | - |
@@ -47,6 +48,13 @@ Cascade selection box.
| `(nzSelect)` | Emit on select | `EventEmitter<{option: any, index: number}>` | - |
| `(nzSelectionChange)` | Emit on selection change | `EventEmitter` | - |
+When `nzShowSearch` is an object it should implements `NzShowSearchOptions`:
+
+| Params | Explanation | Type | Default |
+| --- | --- | --- | --- |
+| `filter` | Optional. Be aware that all non-leaf CascaderOptions would be filtered | `(inputValue: string, path: CascaderOption[]): boolean` | - |
+| `sorter` | Optional | `(a: CascaderOption[], b: CascaderOption[], inputValue: string): number` | - |
+
#### Methods
| Name | Description |
diff --git a/components/cascader/doc/index.zh-CN.md b/components/cascader/doc/index.zh-CN.md
index 075703f566d..f6d40a2e925 100755
--- a/components/cascader/doc/index.zh-CN.md
+++ b/components/cascader/doc/index.zh-CN.md
@@ -40,6 +40,7 @@ subtitle: 级联选择
| `[nzPlaceHolder]` | 输入框占位文本 | string | '请选择' |
| `[nzShowArrow]` | 是否显示箭头 | boolean | true |
| `[nzShowInput]` | 显示输入框 | boolean | true |
+| `[nzShowSearch]` | 是否支持搜索,默认情况下对 `label` 进行全匹配搜索 | `boolean` `NzShowSearchOptions` | `false` |
| `[nzSize]` | 输入框大小,可选 `large` `default` `small` | string | `default` |
| `[nzValueProperty]` | 选项的实际值的属性名 | string | 'value' |
| `(ngModelChange)` | 值发生变化时触发 | `EventEmitter` | - |
@@ -48,6 +49,13 @@ subtitle: 级联选择
| `(nzSelect)` | 选中菜单选项时触发 | `EventEmitter<{option: any, index: number}>` | - |
| `(nzSelectionChange)` | 选中菜单选项时触发 | `EventEmitter` |- |
+`nzShowSearch` 为对象时需遵守 `NzShowSearchOptions` 接口:
+
+| 参数 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| `filter` | 可选,选择是否保留选项的过滤函数,每级菜单的选项都会被匹配 | `(inputValue: string, path: CascaderOption[]): boolean` | - |
+| `sorter` | 可选,按照到每个最终选项的路径进行排序,默认按照原始数据的顺序 | `(a: CascaderOption[], b: CascaderOption[], inputValue: string): number` | - |
+
#### 方法
| 名称 | 描述 |
diff --git a/components/cascader/nz-cascader.component.html b/components/cascader/nz-cascader.component.html
index fc9bd1c7266..330d6ac6592 100644
--- a/components/cascader/nz-cascader.component.html
+++ b/components/cascader/nz-cascader.component.html
@@ -49,15 +49,24 @@
[ngClass]="menuCls" [ngStyle]="nzMenuStyle"
[@dropDownAnimation]="dropDownPosition"
(mouseleave)="onTriggerMouseLeave($event)">
-
+
-
- {{ getOptionLabel(option) }}
+
+
+
+
+ {{ getOptionLabel(option) }}
+
+
+ -
+ Not Found
-
\ No newline at end of file
+
diff --git a/components/cascader/nz-cascader.component.ts b/components/cascader/nz-cascader.component.ts
index 3b160fb96e5..e6521e3c640 100644
--- a/components/cascader/nz-cascader.component.ts
+++ b/components/cascader/nz-cascader.component.ts
@@ -68,6 +68,15 @@ export interface 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]',
preserveWhitespaces: false,
@@ -110,7 +119,7 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces
private menuClassName;
private columnClassName;
private changeOnSelect = false;
- // private showSearch = false;
+ private showSearch: boolean | NzShowSearchOptions;
private defaultValue: any[];
public dropDownPosition = 'bottom';
@@ -157,6 +166,22 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces
set inputValue(inputValue: string) {
this._inputValue = inputValue;
+
+ if (!this.inSearch) {
+ this.oldActivatedOptions = this.activatedOptions;
+ this.activatedOptions = [];
+ } else {
+ this.activatedOptions = this.oldActivatedOptions;
+ }
+
+ this.inSearch = !!inputValue;
+ if (this.inSearch) {
+ this.searchWidthStyle = `${this.input.nativeElement.offsetWidth}px`;
+ this.prepareSearchValue();
+ } else {
+ this.nzColumns = this.oldColumnsHolder;
+ this.searchWidthStyle = '';
+ }
this.setClassMap();
}
@@ -229,16 +254,20 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces
}
/** Whether can search. Defaults to `false`. */
-
- /* // not support yet
@Input()
- set nzShowSearch(value: boolean) {
- this.showSearch = toBoolean(value);
+ set nzShowSearch(value: boolean | NzShowSearchOptions) {
+ this.showSearch = value;
}
- get nzShowSearch(): boolean {
+ 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()
@@ -294,7 +323,7 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces
/** Options for first column, sub column will be load async */
@Input() set nzOptions(options: CascaderOption[] | null) {
- this.nzColumns = options && options.length ? [ options ] : [];
+ this.oldColumnsHolder = this.nzColumns = options && options.length ? [ options ] : [];
if (this.defaultValue && this.nzColumns.length) {
this.initOptions(0);
}
@@ -370,6 +399,7 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces
/** Event: emit on the clear button clicked */
@Output() nzClear = new EventEmitter();
+ @ViewChild('input') input: ElementRef;
/** 浮层菜单 */
@ViewChild('menu') menu: ElementRef;
@@ -404,6 +434,7 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces
}
this.isFocused = false;
this.setClassMap();
+ this.setLabelClass();
}
}
@@ -428,7 +459,9 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces
private setLabelClass(): void {
this._labelCls = {
- [ `${this.prefixCls}-picker-label` ]: true
+ [ `${this.prefixCls}-picker-label` ]: true,
+ [ `${this.prefixCls}-show-search`]: !!this.nzShowSearch,
+ [ `${this.prefixCls}-focused`]: !!this.nzShowSearch && this.isFocused && !this._inputValue
};
}
@@ -543,6 +576,7 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces
}
*/
this.focus();
+ this.setLabelClass();
}
private hasInput(): boolean {
@@ -609,6 +643,14 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces
return;
}
+ if (this.inSearch && (
+ keyCode === BACKSPACE ||
+ keyCode === LEFT_ARROW ||
+ keyCode === RIGHT_ARROW
+ )) {
+ return;
+ }
+
// Press any keys above to reopen menu
if (!this.isMenuVisible() &&
keyCode !== BACKSPACE &&
@@ -644,6 +686,7 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces
return;
}
this.onTouched(); // set your control to 'touched'
+ if (this.nzShowSearch) { this.focus(); }
if (this.isClickTiggerAction()) {
this.delaySetMenuVisible(!this.menuVisible, 100);
@@ -697,6 +740,7 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces
}
public closeMenu(): void {
+ this.blur();
this.clearDelayTimer();
this.setMenuVisible(false);
}
@@ -897,7 +941,7 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces
* @param event 鼠标事件
*/
onOptionClick(option: CascaderOption, index: number, event: Event): void {
- event.preventDefault();
+ if (event) { event.preventDefault(); }
// Keep focused state for keyboard support
this.el.focus();
@@ -905,7 +949,12 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces
if (option && option.disabled) {
return;
}
- this.setActiveOption(option, index, true);
+
+ if (this.inSearch) {
+ this.setSearchActiveOption(option as CascaderSearchOption, event);
+ } else {
+ this.setActiveOption(option, index, true);
+ }
}
/** 按下回车键时选择 */
@@ -913,7 +962,11 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces
const columnIndex = Math.max(this.activatedOptions.length - 1, 0);
const activeOption = this.activatedOptions[ columnIndex ];
if (activeOption && !activeOption.disabled) {
- this.onSelectOption(activeOption, columnIndex);
+ if (this.inSearch) {
+ this.setSearchActiveOption(activeOption as CascaderSearchOption, null);
+ } else {
+ this.onSelectOption(activeOption, columnIndex);
+ }
}
}
@@ -1134,6 +1187,73 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces
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 => {
+ if (n.label.indexOf(inputValue) > -1) { flag = true; }
+ });
+ 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) { loopChild(sNode, disabled); }
+ });
+ path.pop();
+ };
+ const loopChild = (node: CascaderOption, forceDisabled = false) => {
+ path.push(node);
+ const cPath = Array.from(path);
+ if (filter(this._inputValue, cPath)) {
+ const disabled = forceDisabled || node.disabled;
+ results.push({
+ disabled,
+ isLeaf: true,
+ path: cPath,
+ label: cPath.map(p => p.label).join(' / ')
+ } as CascaderSearchOption);
+ }
+ path.pop();
+ };
+
+ this.oldColumnsHolder[0].forEach(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'),
+ ``);
+ }
+
+ setSearchActiveOption(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.
+ const index = result.path.length - 1;
+ const destiNode = result.path[index];
+ const mockClickParent = (node: CascaderOption, cIndex: number) => {
+ if (node && node.parent) { mockClickParent(node.parent, cIndex - 1); }
+ this.onOptionClick(node, cIndex, event);
+ };
+ mockClickParent(destiNode, index);
+ }, 300);
+ }
+
ngOnInit(): void {
// 设置样式
this.setClassMap();
diff --git a/components/cascader/nz-cascader.spec.ts b/components/cascader/nz-cascader.spec.ts
index 54c1002f5ae..228da7ffe8c 100644
--- a/components/cascader/nz-cascader.spec.ts
+++ b/components/cascader/nz-cascader.spec.ts
@@ -13,8 +13,7 @@ import {
dispatchMouseEvent
} from '../core/testing';
-// import { NzDemoCascaderBasicComponent } from './demo/basic';
-import { NzCascaderComponent } from './nz-cascader.component';
+import { CascaderOption, NzCascaderComponent, NzShowSearchOptions } from './nz-cascader.component';
import { NzCascaderModule } from './nz-cascader.module';
describe('cascader', () => {
@@ -1309,6 +1308,140 @@ describe('cascader', () => {
fixture.detectChanges();
expect(testComponent.cascader.dropDownPosition).toBe('bottom');
});
+ it('should support search', (done) => {
+ fixture.detectChanges();
+ testComponent.nzShowSearch = true;
+ 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(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) => {
+ testComponent.nzShowSearch = {
+ filter(inputValue: string, path: CascaderOption[]): boolean {
+ let flag = false;
+ path.forEach(p => {
+ if (p.label.indexOf(inputValue) > -1) { flag = true; }
+ });
+ return flag;
+ }
+ } as NzShowSearchOptions;
+ fixture.detectChanges();
+ 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(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) => {
+ testComponent.nzShowSearch = {
+ sorter(a: CascaderOption[], b: CascaderOption[], inputValue: string): number {
+ return 1; // all reversed, just to be sure it works
+ }
+ } as NzShowSearchOptions;
+ fixture.detectChanges();
+ 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(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();
+ });
+ });
+ it('should forbid disabled search options to be clicked', fakeAsync(() => {
+ testComponent.nzOptions = options4;
+ fixture.detectChanges();
+ 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(itemEl1.innerText).toBe('Zhejiang / Hangzhou / West Lake');
+ expect(testComponent.cascader.nzColumns[0][0].disabled).toBe(true);
+ itemEl1.click();
+ tick(300);
+ fixture.detectChanges();
+ expect(testComponent.cascader.inSearch).toBe(true);
+ expect(testComponent.cascader.menuVisible).toBe(true);
+ expect(testComponent.cascader.inputValue).toBe('o');
+ expect(testComponent.values).toBe(null);
+ }));
+ it('should pass disabled property to children when searching', () => {
+ testComponent.nzOptions = options4;
+ fixture.detectChanges();
+ 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);
+ });
+ it('should support arrow in search mode', (done) => {
+ const DOWN_ARROW = 40;
+ const ENTER = 13;
+ testComponent.nzOptions = options2;
+ fixture.detectChanges();
+ testComponent.cascader.inputValue = 'o';
+ testComponent.cascader.setMenuVisible(true);
+ fixture.detectChanges();
+ const itemEl2 = overlayContainerElement.querySelector('.ant-cascader-menu:nth-child(1) .ant-cascader-menu-item:nth-child(2)') as HTMLElement;
+ const itemEl4 = overlayContainerElement.querySelector('.ant-cascader-menu:nth-child(1) .ant-cascader-menu-item:nth-child(4)') as HTMLElement;
+ dispatchKeyboardEvent(cascader.nativeElement, 'keydown', DOWN_ARROW);
+ fixture.detectChanges();
+ expect(itemEl2.classList).toContain('ant-cascader-menu-item-active');
+ dispatchKeyboardEvent(cascader.nativeElement, 'keydown', DOWN_ARROW);
+ fixture.detectChanges();
+ expect(itemEl2.classList).not.toContain('ant-cascader-menu-item-active');
+ expect(itemEl4.classList).toContain('ant-cascader-menu-item-active');
+ dispatchKeyboardEvent(cascader.nativeElement, 'keydown', ENTER);
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ expect(testComponent.values.join(',')).toBe('option1,option14');
+ done();
+ });
+ });
+ // How can I test BACKSPACE?
+ it('should not preventDefault left/right arrow in search mode', () => {
+ const LEFT_ARROW = 37;
+ const RIGHT_ARROW = 39;
+ fixture.detectChanges();
+ testComponent.nzShowSearch = true;
+ testComponent.cascader.inputValue = 'o';
+ testComponent.cascader.setMenuVisible(true);
+ fixture.detectChanges();
+ dispatchKeyboardEvent(cascader.nativeElement, 'keydown', LEFT_ARROW);
+ const itemEl1 = overlayContainerElement.querySelector('.ant-cascader-menu:nth-child(1) .ant-cascader-menu-item:nth-child(1)') as HTMLElement;
+ fixture.detectChanges();
+ expect(itemEl1.classList).not.toContain('ant-cascader-menu-item-active');
+ dispatchKeyboardEvent(cascader.nativeElement, 'keydown', RIGHT_ARROW);
+ fixture.detectChanges();
+ expect(itemEl1.classList).not.toContain('ant-cascader-menu-item-active');
+ });
});
describe('load data lazily', () => {
@@ -1533,6 +1666,37 @@ const options3 = [ {
} ]
} ];
+const options4 = [ {
+ value: 'zhejiang',
+ label: 'Zhejiang',
+ children: [ {
+ value: 'hangzhou',
+ label: 'Hangzhou',
+ disabled: true,
+ children: [ {
+ value: 'xihu',
+ label: 'West Lake',
+ isLeaf: true
+ } ]
+ }, {
+ value: 'ningbo',
+ label: 'Ningbo',
+ isLeaf: true
+ } ]
+}, {
+ value: 'jiangsu',
+ label: 'Jiangsu',
+ disabled: true,
+ children: [ {
+ value: 'nanjing',
+ label: 'Nanjing',
+ children: [ {
+ value: 'zhonghuamen',
+ label: 'Zhong Hua Men',
+ isLeaf: true
+ } ]
+ } ]
+} ];
@Component({
selector: 'nz-demo-cascader-default',
template: `
@@ -1553,6 +1717,7 @@ const options3 = [ {
[nzPrefixCls]="nzPrefixCls"
[nzShowArrow]="nzShowArrow"
[nzShowInput]="nzShowInput"
+ [nzShowSearch]="nzShowSearch"
[nzSize]="nzSize"
[nzTriggerAction]="nzTriggerAction"
[nzMouseEnterDelay]="nzMouseEnterDelay"
@@ -1597,6 +1762,7 @@ export class NzDemoCascaderDefaultComponent {
nzPrefixCls = 'ant-cascader';
nzShowArrow = true;
nzShowInput = true;
+ nzShowSearch: boolean | NzShowSearchOptions = false;
nzSize = 'default';
nzLabelRender = null;
nzChangeOn = null;
diff --git a/components/cascader/style/index.less b/components/cascader/style/index.less
index 73c0b32b627..65b72b46517 100644
--- a/components/cascader/style/index.less
+++ b/components/cascader/style/index.less
@@ -21,6 +21,10 @@
position: relative;
}
+ &-show-search&-focused {
+ color: @disabled-color;
+ }
+
&-picker {
.reset-component;
position: relative;
@@ -29,6 +33,7 @@
background-color: @component-background;
border-radius: @border-radius-base;
outline: 0;
+ transition: color .3s;
&-with-value &-label {
color: transparent;