Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(module:select): select max tag count #8371

Merged
merged 3 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions components/select/demo/max-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
order: 26
title:
zh-CN: 最大选中数量
en-US: Max Count
---

## zh-CN

你可以通过设置 `nzMaxMultipleCount` 约束最多可选中的数量,当超出限制时会变成禁止选中状态。

## en-US

You can set the `nzMaxMultipleCount` prop to control the max number of items can be selected. When the limit is exceeded, the options will become disabled.
36 changes: 36 additions & 0 deletions components/select/demo/max-count.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Component, OnInit } from '@angular/core';

@Component({
selector: 'nz-demo-select-max-count',
template: `
<nz-select
[nzMaxMultipleCount]="3"
nzMode="multiple"
nzPlaceHolder="Please select"
nzAllowClear
[nzShowArrow]="true"
[(ngModel)]="listOfSelectedValue"
>
<nz-option *ngFor="let item of listOfOption" [nzLabel]="item" [nzValue]="item"></nz-option>
</nz-select>
`,
styles: [
`
nz-select {
width: 100%;
}
`
]
})
export class NzDemoSelectMaxCountComponent implements OnInit {
listOfOption: string[] = [];
listOfSelectedValue = ['a10', 'c12'];

ngOnInit(): void {
const children: string[] = [];
for (let i = 10; i < 36; i++) {
children.push(`${i.toString(36)}${i}`);
}
this.listOfOption = children;
}
}
3 changes: 2 additions & 1 deletion components/select/option-container.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ import { NzSelectItemInterface, NzSelectModeType } from './select.types';
[customContent]="item.nzCustomContent"
[template]="item.template"
[grouped]="!!item.groupLabel"
[disabled]="item.nzDisabled"
[disabled]="item.nzDisabled || (isMaxLimitReached && !listOfSelectedValue.includes(item['nzValue']))"
[showState]="mode === 'tags' || mode === 'multiple'"
[title]="item.nzTitle"
[label]="item.nzLabel"
Expand Down Expand Up @@ -108,6 +108,7 @@ export class NzOptionContainerComponent implements OnChanges, AfterViewInit {
@Input() matchWidth = true;
@Input() itemSize = 32;
@Input() maxItemLength = 8;
@Input() isMaxLimitReached = false;
@Input() listOfContainerItem: NzSelectItemInterface[] = [];
@Output() readonly itemClick = new EventEmitter<NzSafeAny>();
@Output() readonly scrollToBottom = new EventEmitter<void>();
Expand Down
6 changes: 6 additions & 0 deletions components/select/select-arrow.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import { NzIconModule } from 'ng-zorro-antd/icon';
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<ng-container *ngIf="isMaxTagCountSet">
<span>{{ listOfValue.length }} / {{ nzMaxMultipleCount }}</span>
</ng-container>
<span nz-icon nzType="loading" *ngIf="loading; else defaultArrow"></span>
<ng-template #defaultArrow>
<ng-container *ngIf="showArrow && !suffixIcon; else suffixTemplate">
Expand All @@ -37,11 +40,14 @@ import { NzIconModule } from 'ng-zorro-antd/icon';
standalone: true
})
export class NzSelectArrowComponent {
@Input() listOfValue: NzSafeAny[] = [];
@Input() loading = false;
@Input() search = false;
@Input() showArrow = false;
@Input() isMaxTagCountSet = false;
@Input() suffixIcon: TemplateRef<NzSafeAny> | string | null = null;
@Input() feedbackIcon: TemplateRef<NzSafeAny> | string | null = null;
@Input() nzMaxMultipleCount: number = Infinity;

constructor() {}
}
14 changes: 13 additions & 1 deletion components/select/select.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,15 @@ export type NzSelectSizeType = 'large' | 'default' | 'small';
(keydown)="onKeyDown($event)"
></nz-select-top-control>
<nz-select-arrow
*ngIf="nzShowArrow || (hasFeedback && !!status)"
*ngIf="nzShowArrow || (hasFeedback && !!status) || isMaxTagCountSet"
[showArrow]="nzShowArrow"
[loading]="nzLoading"
[search]="nzOpen && nzShowSearch"
[suffixIcon]="nzSuffixIcon"
[feedbackIcon]="feedbackIconTpl"
[nzMaxMultipleCount]="nzMaxMultipleCount"
[listOfValue]="listOfValue"
[isMaxTagCountSet]="isMaxTagCountSet"
>
<ng-template #feedbackIconTpl>
<nz-form-item-feedback-icon *ngIf="hasFeedback && !!status" [status]="status"></nz-form-item-feedback-icon>
Expand Down Expand Up @@ -178,6 +181,7 @@ export type NzSelectSizeType = 'large' | 'default' | 'small';
[dropdownRender]="nzDropdownRender"
[compareWith]="compareWith"
[mode]="nzMode"
[isMaxLimitReached]="isMaxLimitReached"
(keydown)="onKeyDown($event)"
(itemClick)="onItemClick($event)"
(scrollToBottom)="nzScrollToBottom.emit()"
Expand Down Expand Up @@ -275,6 +279,10 @@ export class NzSelectComponent implements ControlValueAccessor, OnInit, AfterCon
return this._nzShowArrow === undefined ? this.nzMode === 'default' : this._nzShowArrow;
}

get isMaxTagCountSet(): boolean {
return this.nzMaxMultipleCount !== Infinity;
}

@Output() readonly nzOnSearch = new EventEmitter<string>();
@Output() readonly nzScrollToBottom = new EventEmitter<void>();
@Output() readonly nzOpenChange = new EventEmitter<boolean>();
Expand Down Expand Up @@ -309,6 +317,7 @@ export class NzSelectComponent implements ControlValueAccessor, OnInit, AfterCon
focused = false;
dir: Direction = 'ltr';
positions: ConnectionPositionPair[] = [];
isMaxLimitReached = false;

// status
prefixCls: string = 'ant-select';
Expand Down Expand Up @@ -422,6 +431,9 @@ export class NzSelectComponent implements ControlValueAccessor, OnInit, AfterCon
this.value = model;
this.onChange(this.value);
}

this.isMaxLimitReached =
this.nzMaxMultipleCount !== Infinity && this.listOfValue.length === this.nzMaxMultipleCount;
}

onTokenSeparate(listOfLabel: string[]): void {
Expand Down
37 changes: 37 additions & 0 deletions components/select/select.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1270,7 +1270,15 @@ describe('select', () => {
flushRefresh();
expect(component.value.length).toBe(1);
expect(component.value[0]).toBe('test_01');
expect(listOfContainerItem[1]).toHaveClass('ant-select-item-option-disabled');
}));
it('should show nzShowArrow component when having nzMaxMultipleCount', () => {
component.nzMaxMultipleCount = 0;
expect(selectElement.querySelector('nz-select-arrow')).toBeFalsy();
component.nzMaxMultipleCount = 1;
fixture.detectChanges();
expect(selectElement.querySelector('nz-select-arrow')).toBeTruthy();
});
it('should nzAutoClearSearchValue work', fakeAsync(() => {
const flushRefresh = (): void => {
fixture.detectChanges();
Expand Down Expand Up @@ -1425,6 +1433,33 @@ describe('select', () => {
// expect(requestAnimationFrameSpy).toHaveBeenCalledTimes(2);
}));

it('should isMaxTagCountSet work correct', () => {
component.nzMaxMultipleCount = Infinity;
fixture.detectChanges();
let isMaxTagCountSet;
isMaxTagCountSet = selectComponent['isMaxTagCountSet'];
expect(isMaxTagCountSet).toBeFalsy();

component.nzMaxMultipleCount = 1;
fixture.detectChanges();
isMaxTagCountSet = selectComponent['isMaxTagCountSet'];
expect(isMaxTagCountSet).toBeTruthy();
});

it('should isMaxLimitReached be set correctly', () => {
selectComponent.nzMaxMultipleCount = 2;
selectComponent.listOfValue = ['a', 'b'];
fixture.detectChanges();
selectComponent.updateListOfValue(['a', 'b']);
expect(selectComponent.isMaxLimitReached).toBeTruthy();

selectComponent.nzMaxMultipleCount = 20;
selectComponent.listOfValue = ['a', 'b'];
fixture.detectChanges();
selectComponent.updateListOfValue(['a', 'b']);
expect(selectComponent.isMaxLimitReached).toBeFalsy();
});

it('should not run change detection when `nz-select-top-control` is clicked and should focus the `nz-select-search`', () => {
const appRef = TestBed.inject(ApplicationRef);
spyOn(appRef, 'tick');
Expand Down Expand Up @@ -1625,6 +1660,7 @@ describe('select', () => {
[(nzOpen)]="nzOpen"
[nzPlacement]="nzPlacement"
[nzSelectOnTab]="nzSelectOnTab"
[nzMaxMultipleCount]="nzMaxMultipleCount"
(ngModelChange)="valueChange($event)"
(nzOnSearch)="searchValueChange($event)"
(nzOpenChange)="openChange($event)"
Expand Down Expand Up @@ -1671,6 +1707,7 @@ export class TestSelectTemplateDefaultComponent {
nzSuffixIcon: TemplateRef<NzSafeAny> | null = null;
nzClearIcon: TemplateRef<NzSafeAny> | null = null;
nzShowArrow = true;
nzMaxMultipleCount: number = Infinity;
nzFilterOption: NzFilterOptionType = (searchValue: string, item: NzSelectItemInterface): boolean => {
if (item && item.nzLabel) {
return item.nzLabel.toString().toLowerCase().indexOf(searchValue.toLowerCase()) > -1;
Expand Down