diff --git a/components/affix/nz-affix.component.html b/components/affix/nz-affix.component.html
index fccd5c8c4db..d09600028e0 100644
--- a/components/affix/nz-affix.component.html
+++ b/components/affix/nz-affix.component.html
@@ -1,3 +1,3 @@
-
\ No newline at end of file
+
diff --git a/components/cascader/demo/basic.ts b/components/cascader/demo/basic.ts
index 0a1d26456f8..020a11aa4d0 100644
--- a/components/cascader/demo/basic.ts
+++ b/components/cascader/demo/basic.ts
@@ -83,7 +83,7 @@ const otherOptions = [
@Component({
selector: 'nz-demo-cascader-basic',
template: `
-
+
Change Options
diff --git a/components/cascader/doc/index.en-US.md b/components/cascader/doc/index.en-US.md
index d95ac66cacd..575548236cf 100755
--- a/components/cascader/doc/index.en-US.md
+++ b/components/cascader/doc/index.en-US.md
@@ -33,7 +33,7 @@ import { NzCascaderModule } from 'ng-zorro-antd/cascader';
| `[ngModel]` | selected value | `any[]` | - |
| `[nzAllowClear]` | whether allow clear | `boolean` | `true` |
| `[nzAutoFocus]` | whether auto focus the input box | `boolean` | `false` |
-| `[nzChangeOn]` | change value on each selection if this function return `true` | `function(option: any, index: number) => boolean` | - |
+| `[nzChangeOn]` | change value on each selection if this function return `true` | `(option: any, index: number) => boolean` | - |
| `[nzChangeOnSelect]` | change value on each selection if set to true, see above demo for details | `boolean` | `false` |
| `[nzColumnClassName]` | additional className of column in the popup overlay | `string` | - |
| `[nzDisabled]` | whether disabled select | `boolean` | `false` |
@@ -54,8 +54,7 @@ import { NzCascaderModule } from 'ng-zorro-antd/cascader';
| `(ngModelChange)` | Emit on values change | `EventEmitter` | - |
| `(nzClear)` | Emit on clear values | `EventEmitter` | - |
| `(nzVisibleChange)` | Emit on popup menu visible or hide | `EventEmitter` | - |
-| `(nzSelect)` | Emit on select | `EventEmitter<{option: any, index: number}>` | - |
-| `(nzSelectionChange)` | Emit on selection change | `EventEmitter` | - |
+| `(nzSelectionChange)` | Emit on values change | `EventEmitter` | - |
When `nzShowSearch` is an object it should implements `NzShowSearchOptions`:
diff --git a/components/cascader/doc/index.zh-CN.md b/components/cascader/doc/index.zh-CN.md
index d6af9e4aaa2..fed99ae2a53 100755
--- a/components/cascader/doc/index.zh-CN.md
+++ b/components/cascader/doc/index.zh-CN.md
@@ -34,7 +34,7 @@ import { NzCascaderModule } from 'ng-zorro-antd/cascader';
| `[ngModel]` | 指定选中项 | `any[]` | - |
| `[nzAllowClear]` | 是否支持清除 | `boolean` | `true` |
| `[nzAutoFocus]` | 是否自动聚焦,当存在输入框时 | `boolean` | `false` |
-| `[nzChangeOn]` | 点击父级菜单选项时,可通过该函数判断是否允许值的变化 | `function(option: any, index: number) => boolean` | - |
+| `[nzChangeOn]` | 点击父级菜单选项时,可通过该函数判断是否允许值的变化 | `(option: any, index: number) => boolean` | - |
| `[nzChangeOnSelect]` | 当此项为 true 时,点选每级菜单选项值都会发生变化,具体见上面的演示 | `boolean` | `false` |
| `[nzColumnClassName]` | 自定义浮层列类名 | `string` | - |
| `[nzDisabled]` | 禁用 | `boolean` | `false` |
@@ -53,10 +53,8 @@ import { NzCascaderModule } from 'ng-zorro-antd/cascader';
| `[nzSize]` | 输入框大小,可选 `large` `default` `small` | `'large' \| 'small' \| 'default'` | `'default'` |
| `[nzValueProperty]` | 选项的实际值的属性名 | `string` | `'value'` |
| `(ngModelChange)` | 值发生变化时触发 | `EventEmitter` | - |
-| `(nzClear)` | 清空值时触发 | `EventEmitter` | - |
| `(nzVisibleChange)` | 菜单浮层的显示/隐藏 | `EventEmitter` | - |
-| `(nzSelect)` | 选中菜单选项时触发 | `EventEmitter<{option: any, index: number}>` | - |
-| `(nzSelectionChange)` | 选中菜单选项时触发 | `EventEmitter` |- |
+| `(nzSelectionChange)` | 值发生变化时触发 | `EventEmitter` |- |
`nzShowSearch` 为对象时需遵守 `NzShowSearchOptions` 接口:
diff --git a/components/cascader/nz-cascader.component.html b/components/cascader/nz-cascader.component.html
index 4dce63f7fe0..6e42c613c31 100644
--- a/components/cascader/nz-cascader.component.html
+++ b/components/cascader/nz-cascader.component.html
@@ -11,7 +11,7 @@
[class.ant-cascader-input-lg]="nzSize === 'large'"
[class.ant-cascader-input-sm]="nzSize === 'small'"
[attr.autoComplete]="'off'"
- [attr.placeholder]="showPlaceholder ? nzPlaceHolder : null"
+ [attr.placeholder]="showPlaceholder ? (nzPlaceHolder || locale.placeholder ) : null"
[attr.autofocus]="nzAutoFocus ? 'autofocus' : null"
[readonly]="!nzShowSearch"
[disabled]="nzDisabled"
diff --git a/components/cascader/nz-cascader.component.ts b/components/cascader/nz-cascader.component.ts
index 6e6bf674cf4..98fd279d075 100644
--- a/components/cascader/nz-cascader.component.ts
+++ b/components/cascader/nz-cascader.component.ts
@@ -31,17 +31,19 @@ import {
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Subject } from 'rxjs';
-import { takeUntil } from 'rxjs/operators';
+import { startWith, takeUntil } from 'rxjs/operators';
import {
slideMotion,
toArray,
+ warnDeprecation,
DEFAULT_DROPDOWN_POSITIONS,
InputBoolean,
NgClassType,
NzNoAnimationDirective
} from 'ng-zorro-antd/core';
+import { NzCascaderI18nInterface, NzI18nService } from 'ng-zorro-antd/i18n';
import {
CascaderOption,
CascaderSearchOption,
@@ -59,7 +61,7 @@ const defaultDisplayRender = (labels: string[]) => labels.join(' / ');
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
- selector: 'nz-cascader,[nz-cascader]',
+ selector: 'nz-cascader, [nz-cascader]',
exportAs: 'nzCascader',
preserveWhitespaces: false,
templateUrl: './nz-cascader.component.html',
@@ -114,7 +116,7 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit,
@Input() nzNotFoundContent: string | TemplateRef;
@Input() nzSize: NzCascaderSize = 'default';
@Input() nzShowSearch: boolean | NzShowSearchOptions;
- @Input() nzPlaceHolder = 'Please select'; // TODO: i18n?
+ @Input() nzPlaceHolder: string;
@Input() nzMenuClassName: string;
@Input() nzMenuStyle: { [key: string]: string };
@Input() nzMouseEnterDelay: number = 150; // ms
@@ -132,11 +134,16 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit,
this.cascaderService.withOptions(options);
}
+ @Output() readonly nzVisibleChange = new EventEmitter();
+
@Output() readonly nzSelectionChange = new EventEmitter();
+
+ /**
+ * @deprecated 9.0.0. This api is a duplication of `ngModelChange`.
+ */
@Output() readonly nzSelect = new EventEmitter<{ option: CascaderOption; index: number } | null>();
+
@Output() readonly nzClear = new EventEmitter();
- @Output() readonly nzVisibleChange = new EventEmitter(); // Not exposed, only for test
- @Output() readonly nzChange = new EventEmitter(); // Not exposed, only for test
el: HTMLElement;
dropDownPosition = 'bottom';
@@ -150,6 +157,8 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit,
dropdownWidthStyle: string;
isFocused = false;
+ locale: NzCascaderI18nInterface;
+
private $destroy = new Subject();
private inputString = '';
private isOpening = false;
@@ -199,6 +208,7 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit,
constructor(
public cascaderService: NzCascaderService,
+ private i18nService: NzI18nService,
private cdr: ChangeDetectorRef,
elementRef: ElementRef,
renderer: Renderer2,
@@ -229,6 +239,7 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit,
if (!data) {
this.onChange([]);
this.nzSelect.emit(null);
+ this.nzSelectionChange.emit([]);
} else {
const { option, index } = data;
const shouldClose = option.isLeaf;
@@ -246,6 +257,19 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit,
this.inputString = '';
this.dropdownWidthStyle = '';
});
+
+ this.i18nService.localeChange
+ .pipe(
+ startWith(),
+ takeUntil(this.$destroy)
+ )
+ .subscribe(() => {
+ this.setLocale();
+ });
+
+ if (this.nzSelect.observers.length > 0) {
+ warnDeprecation(`nzSelect is deprecated and will be removed in 9.0.0. Please use 'nzSelectionChange' instead.`);
+ }
}
ngOnDestroy(): void {
@@ -409,38 +433,44 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit,
@HostListener('mouseenter')
onTriggerMouseEnter(): void {
- if (this.nzDisabled) {
+ if (this.nzDisabled || !this.isActionTrigger('hover')) {
return;
}
- if (this.isActionTrigger('hover')) {
- this.delaySetMenuVisible(true, this.nzMouseEnterDelay, true);
- }
+
+ this.delaySetMenuVisible(true, this.nzMouseEnterDelay, true);
}
@HostListener('mouseleave', ['$event'])
onTriggerMouseLeave(event: MouseEvent): void {
- if (this.nzDisabled) {
+ if (this.nzDisabled || !this.menuVisible || this.isOpening || !this.isActionTrigger('hover')) {
+ event.preventDefault();
return;
}
- if (!this.menuVisible || this.isOpening) {
- event.preventDefault();
+ 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;
}
- 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);
+ }
+
+ onOptionMouseEnter(option: CascaderOption, columnIndex: number, event: Event): void {
+ event.preventDefault();
+ if (this.nzExpandTrigger === 'hover') {
+ if (!option.isLeaf) {
+ this.delaySetOptionActivated(option, columnIndex, false);
+ } else {
+ this.cascaderService.setOptionDeactivatedSinceColumn(columnIndex);
}
- this.delaySetMenuVisible(false, this.nzMouseLeaveDelay);
}
}
- private isActionTrigger(action: 'click' | 'hover'): boolean {
- return typeof this.nzTriggerAction === 'string'
- ? this.nzTriggerAction === action
- : this.nzTriggerAction.indexOf(action) !== -1;
+ onOptionMouseLeave(option: CascaderOption, _columnIndex: number, event: Event): void {
+ event.preventDefault();
+ if (this.nzExpandTrigger === 'hover' && !option.isLeaf) {
+ this.clearDelaySelectTimer();
+ }
}
onOptionClick(option: CascaderOption, columnIndex: number, event: Event): void {
@@ -456,6 +486,12 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit,
: this.cascaderService.setOptionActivated(option, columnIndex, true);
}
+ private isActionTrigger(action: 'click' | 'hover'): boolean {
+ return typeof this.nzTriggerAction === 'string'
+ ? this.nzTriggerAction === action
+ : this.nzTriggerAction.indexOf(action) !== -1;
+ }
+
private onEnter(): void {
const columnIndex = Math.max(this.cascaderService.activatedOptions.length - 1, 0);
const option = this.cascaderService.activatedOptions[columnIndex];
@@ -511,20 +547,6 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit,
}
}
- onOptionMouseEnter(option: CascaderOption, columnIndex: number, event: Event): void {
- event.preventDefault();
- if (this.nzExpandTrigger === 'hover' && !option.isLeaf) {
- this.delaySelectOption(option, columnIndex, true);
- }
- }
-
- onOptionMouseLeave(option: CascaderOption, columnIndex: number, event: Event): void {
- event.preventDefault();
- if (this.nzExpandTrigger === 'hover' && !option.isLeaf) {
- this.delaySelectOption(option, columnIndex, false);
- }
- }
-
private clearDelaySelectTimer(): void {
if (this.delaySelectTimer) {
clearTimeout(this.delaySelectTimer);
@@ -532,14 +554,12 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit,
}
}
- private delaySelectOption(option: CascaderOption, index: number, doSelect: boolean): void {
+ private delaySetOptionActivated(option: CascaderOption, columnIndex: number, performSelect: boolean): void {
this.clearDelaySelectTimer();
- if (doSelect) {
- this.delaySelectTimer = setTimeout(() => {
- this.cascaderService.setOptionActivated(option, index);
- this.delaySelectTimer = null;
- }, 150);
- }
+ this.delaySelectTimer = setTimeout(() => {
+ this.cascaderService.setOptionActivated(option, columnIndex, performSelect);
+ this.delaySelectTimer = null;
+ }, 150);
}
private toggleSearchingMode(toSearching: boolean): void {
@@ -609,4 +629,9 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit,
this.labelRenderText = defaultDisplayRender.call(this, labels, selectedOptions);
}
}
+
+ private setLocale(): void {
+ this.locale = this.i18nService.getLocaleData('global');
+ this.cdr.markForCheck();
+ }
}
diff --git a/components/cascader/nz-cascader.service.ts b/components/cascader/nz-cascader.service.ts
index fcbf0a838e8..7e1d17545fd 100644
--- a/components/cascader/nz-cascader.service.ts
+++ b/components/cascader/nz-cascader.service.ts
@@ -162,13 +162,13 @@ export class NzCascaderService implements OnDestroy {
* Try to set a option as activated.
* @param option Cascader option
* @param columnIndex Of which column this option is in
- * @param select Select
+ * @param performSelect Select
* @param loadingChildren Try to load children asynchronously.
*/
setOptionActivated(
option: CascaderOption,
columnIndex: number,
- select: boolean = false,
+ performSelect: boolean = false,
loadingChildren: boolean = true
): void {
if (option.disabled) {
@@ -193,15 +193,35 @@ export class NzCascaderService implements OnDestroy {
}
// Actually perform selection to make an options not only activated but also selected.
- if (select) {
+ if (performSelect) {
this.setOptionSelected(option, columnIndex);
}
this.$redraw.next();
}
+ setOptionSelected(option: CascaderOption, index: number): void {
+ const changeOn = this.cascaderComponent.nzChangeOn;
+ const shouldPerformSelection = (o: CascaderOption, i: number): boolean => {
+ return typeof changeOn === 'function' ? changeOn(o, i) : false;
+ };
+
+ if (option.isLeaf || this.cascaderComponent.nzChangeOnSelect || shouldPerformSelection(option, index)) {
+ this.selectedOptions = [...this.activatedOptions];
+ this.prepareEmitValue();
+ this.$redraw.next();
+ this.$optionSelected.next({ option, index });
+ }
+ }
+
+ setOptionDeactivatedSinceColumn(column: number): void {
+ this.dropBehindActivatedOptions(column - 1);
+ this.dropBehindColumns(column);
+ this.$redraw.next();
+ }
+
/**
- * Set a searching option as activated, finishing up things.
+ * Set a searching option as selected, finishing up things.
* @param option
*/
setSearchOptionSelected(option: CascaderSearchOption): void {
@@ -305,20 +325,6 @@ export class NzCascaderService implements OnDestroy {
}
}
- setOptionSelected(option: CascaderOption, index: number): void {
- const changeOn = this.cascaderComponent.nzChangeOn;
- const shouldPerformSelection = (o: CascaderOption, i: number): boolean => {
- return typeof changeOn === 'function' ? changeOn(o, i) : false;
- };
-
- if (option.isLeaf || this.cascaderComponent.nzChangeOnSelect || shouldPerformSelection(option, index)) {
- this.selectedOptions = [...this.activatedOptions];
- this.prepareEmitValue();
- this.$redraw.next();
- this.$optionSelected.next({ option, index });
- }
- }
-
/**
* Clear selected options.
*/
diff --git a/components/cascader/nz-cascader.spec.ts b/components/cascader/nz-cascader.spec.ts
index b0d855e6510..67e8f61d579 100644
--- a/components/cascader/nz-cascader.spec.ts
+++ b/components/cascader/nz-cascader.spec.ts
@@ -56,6 +56,7 @@ describe('cascader', () => {
overlayContainerElement = oc.getContainerElement();
})();
}));
+
afterEach(inject([OverlayContainer], (currentOverlayContainer: OverlayContainer) => {
currentOverlayContainer.ngOnDestroy();
overlayContainer.ngOnDestroy();
@@ -71,12 +72,14 @@ describe('cascader', () => {
fixture.detectChanges();
expect(cascader.nativeElement.className).toContain('ant-cascader ant-cascader-picker');
});
+
it('should have input', () => {
fixture.detectChanges();
const input: HTMLElement = cascader.nativeElement.querySelector('.ant-cascader-input');
expect(input).toBeDefined();
expect(input.getAttribute('placeholder')).toBe('please select');
});
+
it('should input change event stopPropagation', () => {
fixture.detectChanges();
const input: HTMLElement = cascader.nativeElement.querySelector('.ant-cascader-input');
@@ -86,12 +89,14 @@ describe('cascader', () => {
fixture.detectChanges();
expect(fakeInputChangeEvent.stopPropagation).toHaveBeenCalled();
});
+
it('should have EMPTY label', () => {
fixture.detectChanges();
const label: HTMLElement = cascader.nativeElement.querySelector('.ant-cascader-picker-label');
expect(label).toBeDefined();
expect(label.innerText).toBe('');
});
+
it('should placeholder work', () => {
const placeholder = 'placeholder test';
testComponent.nzPlaceHolder = placeholder;
@@ -99,13 +104,7 @@ describe('cascader', () => {
const input: HTMLElement = cascader.nativeElement.querySelector('.ant-cascader-input');
expect(input.getAttribute('placeholder')).toBe(placeholder);
});
- // 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();
@@ -115,6 +114,7 @@ describe('cascader', () => {
fixture.detectChanges();
expect(input.classList).toContain('ant-input-lg');
});
+
it('should value and label property work', fakeAsync(() => {
testComponent.nzOptions = ID_NAME_LIST;
testComponent.nzValueProperty = 'id';
@@ -131,6 +131,7 @@ describe('cascader', () => {
);
expect(testComponent.cascader.getSubmitValue().join(',')).toBe('1,2,3');
}));
+
it('should no value and label property work', fakeAsync(() => {
testComponent.nzValueProperty = null;
testComponent.nzLabelProperty = null;
@@ -146,6 +147,7 @@ describe('cascader', () => {
);
expect(testComponent.cascader.getSubmitValue().join(',')).toBe('zhejiang,hangzhou,xihu');
}));
+
it('should showArrow work', () => {
testComponent.nzShowArrow = true;
fixture.detectChanges();
@@ -155,6 +157,7 @@ describe('cascader', () => {
fixture.detectChanges();
expect(cascader.nativeElement.querySelector('.ant-cascader-picker-arrow')).toBeNull();
});
+
it('should allowClear work', () => {
fixture.detectChanges();
testComponent.values = ['zhejiang', 'hangzhou', 'xihu'];
@@ -164,6 +167,7 @@ describe('cascader', () => {
fixture.detectChanges();
expect(cascader.nativeElement.querySelector('.ant-cascader-picker-clear')).toBeNull();
});
+
it('should open work', () => {
fixture.detectChanges();
expect(cascader.nativeElement.classList).not.toContain('ant-cascader-picker-open');
@@ -173,6 +177,7 @@ describe('cascader', () => {
expect(testComponent.onVisibleChange).toHaveBeenCalledTimes(1);
expect(testComponent.cascader.nzOptions).toBe(options1);
});
+
it('should click toggle open', fakeAsync(() => {
fixture.detectChanges();
expect(testComponent.nzDisabled).toBe(false);
@@ -193,6 +198,7 @@ describe('cascader', () => {
expect(testComponent.cascader.menuVisible).toBe(false);
expect(testComponent.onVisibleChange).toHaveBeenCalledTimes(2);
}));
+
it('should mouse hover toggle open', fakeAsync(() => {
fixture.detectChanges();
testComponent.nzTriggerAction = 'hover';
@@ -276,6 +282,7 @@ describe('cascader', () => {
expect(testComponent.cascader.menuVisible).toBe(false);
expect(testComponent.onVisibleChange).toHaveBeenCalledTimes(2);
}));
+
it('should mouse hover toggle open immediately', fakeAsync(() => {
fixture.detectChanges();
testComponent.nzTriggerAction = ['hover'];
@@ -296,6 +303,7 @@ describe('cascader', () => {
expect(testComponent.cascader.menuVisible).toBe(false);
expect(testComponent.onVisibleChange).toHaveBeenCalledTimes(2);
}));
+
it('should clear timer on option mouseenter and mouseleave', fakeAsync(() => {
const mouseenter = createMouseEvent('mouseenter');
const mouseleave = createMouseEvent('mouseleave');
@@ -331,6 +339,7 @@ describe('cascader', () => {
fixture.detectChanges();
expect(optionEl.classList).toContain('ant-cascader-menu-item-active');
}));
+
it('should disabled work', fakeAsync(() => {
fixture.detectChanges();
expect(cascader.nativeElement.classList).not.toContain('ant-cascader-picker-disabled');
@@ -351,6 +360,7 @@ describe('cascader', () => {
expect(testComponent.cascader.menuVisible).toBe(false);
expect(testComponent.onVisibleChange).toHaveBeenCalledTimes(0);
}));
+
it('should disabled state work', fakeAsync(() => {
fixture.detectChanges();
expect(cascader.nativeElement.classList).not.toContain('ant-cascader-picker-disabled');
@@ -365,6 +375,7 @@ describe('cascader', () => {
expect(testComponent.cascader.menuVisible).toBe(false);
expect(testComponent.onVisibleChange).toHaveBeenCalledTimes(0);
}));
+
it('should disabled mouse hover open', fakeAsync(() => {
testComponent.nzTriggerAction = 'hover';
testComponent.nzDisabled = true;
@@ -393,6 +404,7 @@ describe('cascader', () => {
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();
@@ -404,6 +416,7 @@ describe('cascader', () => {
expect(testComponent.cascader.menuVisible).toBe(false);
expect(testComponent.onVisibleChange).toHaveBeenCalledTimes(0);
}));
+
it('should clear value work', fakeAsync(() => {
fixture.detectChanges();
testComponent.nzAllowClear = true;
@@ -416,6 +429,7 @@ describe('cascader', () => {
fixture.detectChanges();
expect(testComponent.values!.length).toBe(0);
}));
+
it('should clear value work 2', fakeAsync(() => {
fixture.detectChanges();
testComponent.values = ['zhejiang', 'hangzhou', 'xihu'];
@@ -427,6 +441,7 @@ describe('cascader', () => {
fixture.detectChanges();
expect(testComponent.values!.length).toBe(0);
}));
+
it('should autofocus work', () => {
testComponent.nzShowInput = true;
testComponent.nzAutoFocus = true;
@@ -436,6 +451,7 @@ describe('cascader', () => {
fixture.detectChanges();
expect(cascader.nativeElement.querySelector('input').getAttribute('autofocus')).toBe(null);
});
+
it('should input focus and blur work', fakeAsync(() => {
const fakeInputFocusEvent = createFakeEvent('focus', false, true);
const fakeInputBlurEvent = createFakeEvent('blur', false, true);
@@ -457,6 +473,7 @@ describe('cascader', () => {
fixture.detectChanges();
expect(cascader.nativeElement.classList).toContain('ant-cascader-focused');
}));
+
it('should focus and blur function work', () => {
testComponent.nzShowInput = true;
cascader.nativeElement.click();
@@ -469,6 +486,7 @@ describe('cascader', () => {
fixture.detectChanges();
expect(cascader.nativeElement.querySelector('input') === document.activeElement).toBe(false);
});
+
it('should focus and blur function work 2', () => {
testComponent.nzShowInput = false;
cascader.nativeElement.click();
@@ -481,6 +499,7 @@ describe('cascader', () => {
fixture.detectChanges();
expect(cascader.nativeElement === document.activeElement).toBe(false);
});
+
it('should menu class work', fakeAsync(() => {
fixture.detectChanges();
cascader.nativeElement.click();
@@ -491,6 +510,7 @@ describe('cascader', () => {
expect(overlayContainerElement.querySelector('.ant-cascader-menus')!.classList).toContain('menu-classA');
expect(overlayContainerElement.querySelector('.ant-cascader-menu')!.classList).toContain('column-classA');
}));
+
it('should menu style work', fakeAsync(() => {
fixture.detectChanges();
cascader.nativeElement.click();
@@ -501,6 +521,7 @@ describe('cascader', () => {
const targetElement = overlayContainerElement.querySelector('.menu-classA') as HTMLElement;
expect(targetElement.style.height).toBe('120px');
}));
+
it('should show input false work', fakeAsync(() => {
testComponent.nzShowInput = false;
fixture.detectChanges();
@@ -515,6 +536,7 @@ describe('cascader', () => {
expect(cascader.nativeElement.querySelector('.ant-cascader-picker-clear')).toBeNull();
expect(cascader.nativeElement.querySelector('.ant-cascader-picker-label')).toBeNull();
}));
+
it('should input value work', fakeAsync(() => {
fixture.detectChanges();
expect(cascader.nativeElement.classList).not.toContain('ant-cascader-picker-with-value');
@@ -522,6 +544,7 @@ describe('cascader', () => {
fixture.detectChanges();
expect(cascader.nativeElement.classList).toContain('ant-cascader-picker-with-value');
}));
+
it('should create label work', fakeAsync(() => {
fixture.detectChanges();
expect(cascader.nativeElement.querySelector('.ant-cascader-picker-label').innerText).toBe('');
@@ -533,6 +556,7 @@ describe('cascader', () => {
'Zhejiang / Hangzhou / West Lake'
);
}));
+
it('should label template work', fakeAsync(() => {
fixture.detectChanges();
expect(cascader.nativeElement.querySelector('.ant-cascader-picker-label').innerText).toBe('');
@@ -555,6 +579,7 @@ describe('cascader', () => {
'Zhejiang | Hangzhou | West Lake'
);
}));
+
it('should write value work', fakeAsync(() => {
const control = testComponent.cascader;
testComponent.nzOptions = options1;
@@ -620,6 +645,7 @@ describe('cascader', () => {
expect(values4[2]).toBe('xihu');
expect(control.labelRenderText).toBe('ZJ / HZ / XH');
}));
+
it('should write value work on setting `nzOptions` asyn', fakeAsync(() => {
const control = testComponent.cascader;
testComponent.nzOptions = null;
@@ -648,6 +674,7 @@ describe('cascader', () => {
expect(control.getSubmitValue()[0]).toBe('zhejiang');
expect(control.labelRenderText).toBe('Zhejiang');
}));
+
it('should write value work on setting `nzOptions` asyn (match)', fakeAsync(() => {
const control = testComponent.cascader;
testComponent.nzOptions = null;
@@ -665,6 +692,7 @@ describe('cascader', () => {
expect(values![2]).toBe('xihu');
expect(control.labelRenderText).toBe('Zhejiang / Hangzhou / West Lake');
}));
+
it('should write value work on setting `nzOptions` asyn (not match)', fakeAsync(() => {
const control = testComponent.cascader;
testComponent.nzOptions = null;
@@ -682,6 +710,7 @@ describe('cascader', () => {
expect(values![2]).toBe('xihu2');
expect(control.labelRenderText).toBe('zhejiang2 / hangzhou2 / xihu2');
}));
+
it('should click option to expand', () => {
fixture.detectChanges();
expect(overlayContainerElement.querySelectorAll('.ant-cascader-menu').length).toBe(0); // 0列:未显示菜单
@@ -698,6 +727,7 @@ describe('cascader', () => {
fixture.detectChanges();
expect(overlayContainerElement.querySelectorAll('.ant-cascader-menu').length).toBe(3); // 3列
});
+
it('should click option to change column count', () => {
fixture.detectChanges();
expect(overlayContainerElement.querySelectorAll('.ant-cascader-menu').length).toBe(0); // 0列:未显示菜单
@@ -724,6 +754,7 @@ describe('cascader', () => {
fixture.detectChanges();
expect(overlayContainerElement.querySelectorAll('.ant-cascader-menu').length).toBe(2); // 2列
});
+
it('should click option to change column count 2', fakeAsync(() => {
testComponent.values = ['zhejiang', 'hangzhou', 'xihu'];
fixture.detectChanges();
@@ -771,6 +802,7 @@ describe('cascader', () => {
expect(testComponent.cascader.menuVisible).toBe(false);
expect(testComponent.values!.join(',')).toBe('zhejiang,ningbo');
}));
+
it('should click option to change column count 3', () => {
testComponent.nzOptions = options3;
fixture.detectChanges();
@@ -799,6 +831,7 @@ describe('cascader', () => {
) as HTMLElement;
expect(itemEl21.innerText.trim()).toBe('Nanjing');
});
+
it('should click disabled option false to expand', fakeAsync(() => {
testComponent.nzOptions = options2;
fixture.detectChanges();
@@ -822,6 +855,7 @@ describe('cascader', () => {
expect(optionEl1.classList).toContain('ant-cascader-menu-item-active');
expect(optionEl2.classList).not.toContain('ant-cascader-menu-item-active');
}));
+
it('should click leaf option to close menu', fakeAsync(() => {
fixture.detectChanges();
testComponent.cascader.setMenuVisible(true);
@@ -848,6 +882,7 @@ describe('cascader', () => {
expect(testComponent.cascader.menuVisible).toBe(false);
expect(overlayContainerElement.querySelectorAll('.ant-cascader-menu').length).toBe(0);
}));
+
it('should open menu when press DOWN_ARROW', fakeAsync(() => {
fixture.detectChanges();
expect(testComponent.cascader.menuVisible).toBe(false);
@@ -857,6 +892,7 @@ describe('cascader', () => {
fixture.detectChanges();
expect(testComponent.cascader.menuVisible).toBe(true);
}));
+
it('should open menu when press UP_ARROW', fakeAsync(() => {
fixture.detectChanges();
expect(testComponent.cascader.menuVisible).toBe(false);
@@ -866,6 +902,7 @@ describe('cascader', () => {
fixture.detectChanges();
expect(testComponent.cascader.menuVisible).toBe(true);
}));
+
it('should close menu when press ESC', fakeAsync(() => {
fixture.detectChanges();
testComponent.cascader.setMenuVisible(true);
@@ -877,6 +914,7 @@ describe('cascader', () => {
fixture.detectChanges();
expect(testComponent.cascader.menuVisible).toBe(false);
}));
+
it('should navigate up when press UP_ARROW', fakeAsync(() => {
fixture.detectChanges();
testComponent.cascader.setMenuVisible(true);
@@ -897,6 +935,7 @@ describe('cascader', () => {
expect(itemEl2.classList).toContain('ant-cascader-menu-item-active');
expect(itemEl1.classList).not.toContain('ant-cascader-menu-item-active');
}));
+
it('should navigate down when press DOWN_ARROW', fakeAsync(() => {
fixture.detectChanges();
testComponent.cascader.setMenuVisible(true);
@@ -909,6 +948,7 @@ describe('cascader', () => {
fixture.detectChanges();
expect(itemEl1.classList).toContain('ant-cascader-menu-item-active');
}));
+
it('should navigate right when press RIGHT_ARROW', fakeAsync(() => {
fixture.detectChanges();
testComponent.cascader.setMenuVisible(true);
@@ -940,6 +980,7 @@ describe('cascader', () => {
) as HTMLElement; // The first option in the third column
expect(itemEl3.classList).toContain('ant-cascader-menu-item-active');
}));
+
it('should navigate left when press LEFT_ARROW', fakeAsync(() => {
fixture.detectChanges();
testComponent.values = ['zhejiang', 'hangzhou', 'xihu'];
@@ -977,6 +1018,7 @@ describe('cascader', () => {
expect(itemEl2.classList).not.toContain('ant-cascader-menu-item-active');
expect(itemEl3.classList).not.toContain('ant-cascader-menu-item-active');
}));
+
it('should select option when press ENTER', fakeAsync(() => {
fixture.detectChanges();
expect(testComponent.values).toBeNull();
@@ -1012,6 +1054,7 @@ describe('cascader', () => {
fixture.detectChanges();
expect(testComponent.cascader.menuVisible).toBe(false);
}));
+
it('should key nav disabled option correct', fakeAsync(() => {
testComponent.nzOptions = options2;
fixture.detectChanges();
@@ -1074,6 +1117,7 @@ describe('cascader', () => {
expect(optionEl13.classList).not.toContain('ant-cascader-menu-item-active');
expect(optionEl14.classList).not.toContain('ant-cascader-menu-item-active');
}));
+
it('should ignore keyboardEvent on some key', fakeAsync(() => {
const A = 65;
const Z = 90;
@@ -1093,6 +1137,7 @@ describe('cascader', () => {
expect(testComponent.cascader.menuVisible).toBe(false);
});
}));
+
it('should expand option on hover', fakeAsync(() => {
testComponent.nzExpandTrigger = 'hover';
fixture.detectChanges();
@@ -1156,7 +1201,7 @@ describe('cascader', () => {
expect(overlayContainerElement.querySelectorAll('.ant-cascader-menu').length).toBe(3); // 3列
expect(itemEl1.classList).toContain('ant-cascader-menu-item-active');
expect(itemEl2.classList).toContain('ant-cascader-menu-item-active');
- expect(itemEl3.classList).not.toContain('ant-cascader-menu-item-active'); // not select because it is leaf
+ expect(itemEl3.classList).not.toContain('ant-cascader-menu-item-active');
expect(testComponent.values).toBeNull(); // not select yet
itemEl3.click();
@@ -1173,6 +1218,7 @@ describe('cascader', () => {
expect(overlayContainerElement.querySelectorAll('.ant-cascader-menu').length).toBe(0); // 0列
expect(testComponent.cascader.menuVisible).toBe(false);
}));
+
it('should not expand disabled option on hover', fakeAsync(() => {
testComponent.nzExpandTrigger = 'hover';
testComponent.nzOptions = options2;
@@ -1204,6 +1250,31 @@ describe('cascader', () => {
expect(itemEl2.classList).not.toContain('ant-cascader-menu-item-active');
expect(overlayContainerElement.querySelectorAll('.ant-cascader-menu').length).toBe(1); // 1列
}));
+
+ // fix #3914
+ it('should drop selected items and columns if a leaf node is hovered', fakeAsync(() => {
+ testComponent.nzExpandTrigger = 'hover';
+ fixture.detectChanges();
+
+ testComponent.values = ['zhejiang', 'hangzhou', 'xihu'];
+ testComponent.cascader.setMenuVisible(true); // Open cascader dropdown.
+
+ fixture.detectChanges();
+ tick(500);
+ fixture.detectChanges();
+ expect(overlayContainerElement.querySelectorAll('.ant-cascader-menu').length).toBe(3);
+
+ const c2i2 = overlayContainerElement.querySelector(
+ '.ant-cascader-menu:nth-child(2) .ant-cascader-menu-item:nth-child(2)'
+ ) as HTMLElement;
+ dispatchMouseEvent(c2i2, 'mouseenter');
+
+ fixture.detectChanges();
+ tick(500);
+ fixture.detectChanges();
+ expect(overlayContainerElement.querySelectorAll('.ant-cascader-menu').length).toBe(2);
+ }));
+
it('should change on select work', fakeAsync(() => {
testComponent.nzChangeOnSelect = true;
fixture.detectChanges();
@@ -1268,6 +1339,7 @@ describe('cascader', () => {
expect(overlayContainerElement.querySelectorAll('.ant-cascader-menu').length).toBe(0); // 0列
expect(testComponent.cascader.menuVisible).toBe(false);
}));
+
it('should not change on hover work', fakeAsync(() => {
testComponent.nzChangeOnSelect = true;
testComponent.nzExpandTrigger = 'hover';
@@ -1339,6 +1411,7 @@ describe('cascader', () => {
expect(overlayContainerElement.querySelectorAll('.ant-cascader-menu').length).toBe(0); // 0列
expect(testComponent.cascader.menuVisible).toBe(false);
}));
+
it('should change on function work', fakeAsync(() => {
testComponent.nzChangeOn = testComponent.fakeChangeOn;
fixture.detectChanges();
@@ -1371,6 +1444,7 @@ describe('cascader', () => {
expect(testComponent.values!.length).toBe(1);
expect(testComponent.values![0]).toBe('zhejiang');
}));
+
it('should position change correct', () => {
const fakeTopEvent = {
connectionPair: {
@@ -1399,6 +1473,7 @@ describe('cascader', () => {
fixture.detectChanges();
expect(testComponent.cascader.dropDownPosition).toBe('bottom');
});
+
it('should support search', fakeAsync(() => {
fixture.detectChanges();
testComponent.nzShowSearch = true;
@@ -1475,6 +1550,7 @@ describe('cascader', () => {
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 {
@@ -1499,6 +1575,7 @@ describe('cascader', () => {
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 {
@@ -1525,6 +1602,7 @@ describe('cascader', () => {
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();
@@ -1544,6 +1622,7 @@ describe('cascader', () => {
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();
@@ -1554,6 +1633,7 @@ describe('cascader', () => {
expect(testComponent.cascader.cascaderService.columns[0][1].disabled).toBe(undefined);
expect(testComponent.cascader.cascaderService.columns[0][2].disabled).toBe(true);
});
+
it('should support arrow in search mode', done => {
testComponent.nzOptions = options2;
fixture.detectChanges();
@@ -1580,6 +1660,7 @@ describe('cascader', () => {
done();
});
});
+
it('should not preventDefault left/right arrow in search mode', () => {
fixture.detectChanges();
testComponent.nzShowSearch = true;
@@ -1596,6 +1677,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 ', fakeAsync(() => {
fixture.detectChanges();
testComponent.nzShowSearch = true;
@@ -1615,6 +1697,7 @@ describe('cascader', () => {
expect(itemEl1.innerText.trim()).toBe('暂无数据');
flush();
}));
+
it('should re-prepare search results when nzOptions change', () => {
fixture.detectChanges();
testComponent.nzShowSearch = true;
@@ -1641,6 +1724,7 @@ describe('cascader', () => {
let fixture: ComponentFixture;
let cascader: DebugElement;
let testComponent: NzDemoCascaderLoadDataComponent;
+
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [FormsModule, ReactiveFormsModule, NoopAnimationsModule, NzCascaderModule],
@@ -1653,6 +1737,7 @@ describe('cascader', () => {
overlayContainerElement = oc.getContainerElement();
})();
}));
+
afterEach(inject([OverlayContainer], (currentOverlayContainer: OverlayContainer) => {
currentOverlayContainer.ngOnDestroy();
overlayContainer.ngOnDestroy();
@@ -2028,6 +2113,7 @@ const options5: any[] = []; // tslint:disable-line:no-any
[nzChangeOnSelect]="nzChangeOnSelect"
(ngModelChange)="onValueChanges($event)"
(nzVisibleChange)="onVisibleChange($event)"
+ (nzSelect)="onSelect($event)"
>
@@ -2083,6 +2169,8 @@ export class NzDemoCascaderDefaultComponent {
clearSelection(): void {
this.cascader.clearSelection();
}
+
+ onSelect(_d: { option: CascaderOption; index: number }): void {}
}
@Component({
diff --git a/components/i18n/nz-i18n.interface.ts b/components/i18n/nz-i18n.interface.ts
index 4897df14baa..38be071f17d 100644
--- a/components/i18n/nz-i18n.interface.ts
+++ b/components/i18n/nz-i18n.interface.ts
@@ -21,18 +21,13 @@ export interface NzPaginationI18nInterface {
next_3: string;
}
-export interface NzDatePickerI18nInterface {
- lang: NzDatePickerLangI18nInterface;
- timePickerLocale: NzTimePickerI18nInterface;
-}
-
-export interface NzDatePickerLangI18nInterface extends NzCalendarI18nInterface {
+export interface NzGlobalI18nInterface {
placeholder: string;
- rangePlaceholder: string[];
}
-export interface NzTimePickerI18nInterface {
- placeholder: string;
+export interface NzDatePickerI18nInterface {
+ lang: NzDatePickerLangI18nInterface;
+ timePickerLocale: NzTimePickerI18nInterface;
}
export interface NzCalendarI18nInterface {
@@ -64,12 +59,24 @@ export interface NzCalendarI18nInterface {
nextCentury: string;
}
+export interface NzDatePickerLangI18nInterface extends NzCalendarI18nInterface {
+ placeholder: string;
+ rangePlaceholder: string[];
+}
+
+export interface NzTimePickerI18nInterface {
+ placeholder: string;
+}
+
+export type NzCascaderI18nInterface = NzGlobalI18nInterface;
+
export interface NzI18nInterface {
locale: string;
Pagination: NzPaginationI18nInterface;
DatePicker: NzDatePickerI18nInterface;
TimePicker: NzTimePickerI18nInterface;
Calendar: NzCalendarI18nInterface;
+ global: NzGlobalI18nInterface;
Table: {
filterTitle: string;
filterConfirm: string;