Skip to content

Commit 18b898e

Browse files
feat(module:select): select max tag count (#8371)
* feat(module:select): select max tag count * feat(module:select): select max tag count * feat(module:select): select max tag count
1 parent a0b08be commit 18b898e

File tree

6 files changed

+108
-2
lines changed

6 files changed

+108
-2
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
order: 26
3+
title:
4+
zh-CN: 最大选中数量
5+
en-US: Max Count
6+
---
7+
8+
## zh-CN
9+
10+
你可以通过设置 `nzMaxMultipleCount` 约束最多可选中的数量,当超出限制时会变成禁止选中状态。
11+
12+
## en-US
13+
14+
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.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Component, OnInit } from '@angular/core';
2+
3+
@Component({
4+
selector: 'nz-demo-select-max-count',
5+
template: `
6+
<nz-select
7+
[nzMaxMultipleCount]="3"
8+
nzMode="multiple"
9+
nzPlaceHolder="Please select"
10+
nzAllowClear
11+
[nzShowArrow]="true"
12+
[(ngModel)]="listOfSelectedValue"
13+
>
14+
<nz-option *ngFor="let item of listOfOption" [nzLabel]="item" [nzValue]="item"></nz-option>
15+
</nz-select>
16+
`,
17+
styles: [
18+
`
19+
nz-select {
20+
width: 100%;
21+
}
22+
`
23+
]
24+
})
25+
export class NzDemoSelectMaxCountComponent implements OnInit {
26+
listOfOption: string[] = [];
27+
listOfSelectedValue = ['a10', 'c12'];
28+
29+
ngOnInit(): void {
30+
const children: string[] = [];
31+
for (let i = 10; i < 36; i++) {
32+
children.push(`${i.toString(36)}${i}`);
33+
}
34+
this.listOfOption = children;
35+
}
36+
}

components/select/option-container.component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ import { NzSelectItemInterface, NzSelectModeType } from './select.types';
6666
[customContent]="item.nzCustomContent"
6767
[template]="item.template"
6868
[grouped]="!!item.groupLabel"
69-
[disabled]="item.nzDisabled"
69+
[disabled]="item.nzDisabled || (isMaxLimitReached && !listOfSelectedValue.includes(item['nzValue']))"
7070
[showState]="mode === 'tags' || mode === 'multiple'"
7171
[title]="item.nzTitle"
7272
[label]="item.nzLabel"
@@ -108,6 +108,7 @@ export class NzOptionContainerComponent implements OnChanges, AfterViewInit {
108108
@Input() matchWidth = true;
109109
@Input() itemSize = 32;
110110
@Input() maxItemLength = 8;
111+
@Input() isMaxLimitReached = false;
111112
@Input() listOfContainerItem: NzSelectItemInterface[] = [];
112113
@Output() readonly itemClick = new EventEmitter<NzSafeAny>();
113114
@Output() readonly scrollToBottom = new EventEmitter<void>();

components/select/select-arrow.component.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import { NzIconModule } from 'ng-zorro-antd/icon';
1515
encapsulation: ViewEncapsulation.None,
1616
changeDetection: ChangeDetectionStrategy.OnPush,
1717
template: `
18+
<ng-container *ngIf="isMaxTagCountSet">
19+
<span>{{ listOfValue.length }} / {{ nzMaxMultipleCount }}</span>
20+
</ng-container>
1821
<span nz-icon nzType="loading" *ngIf="loading; else defaultArrow"></span>
1922
<ng-template #defaultArrow>
2023
<ng-container *ngIf="showArrow && !suffixIcon; else suffixTemplate">
@@ -37,11 +40,14 @@ import { NzIconModule } from 'ng-zorro-antd/icon';
3740
standalone: true
3841
})
3942
export class NzSelectArrowComponent {
43+
@Input() listOfValue: NzSafeAny[] = [];
4044
@Input() loading = false;
4145
@Input() search = false;
4246
@Input() showArrow = false;
47+
@Input() isMaxTagCountSet = false;
4348
@Input() suffixIcon: TemplateRef<NzSafeAny> | string | null = null;
4449
@Input() feedbackIcon: TemplateRef<NzSafeAny> | string | null = null;
50+
@Input() nzMaxMultipleCount: number = Infinity;
4551

4652
constructor() {}
4753
}

components/select/select.component.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,12 +126,15 @@ export type NzSelectSizeType = 'large' | 'default' | 'small';
126126
(keydown)="onKeyDown($event)"
127127
></nz-select-top-control>
128128
<nz-select-arrow
129-
*ngIf="nzShowArrow || (hasFeedback && !!status)"
129+
*ngIf="nzShowArrow || (hasFeedback && !!status) || isMaxTagCountSet"
130130
[showArrow]="nzShowArrow"
131131
[loading]="nzLoading"
132132
[search]="nzOpen && nzShowSearch"
133133
[suffixIcon]="nzSuffixIcon"
134134
[feedbackIcon]="feedbackIconTpl"
135+
[nzMaxMultipleCount]="nzMaxMultipleCount"
136+
[listOfValue]="listOfValue"
137+
[isMaxTagCountSet]="isMaxTagCountSet"
135138
>
136139
<ng-template #feedbackIconTpl>
137140
<nz-form-item-feedback-icon *ngIf="hasFeedback && !!status" [status]="status"></nz-form-item-feedback-icon>
@@ -178,6 +181,7 @@ export type NzSelectSizeType = 'large' | 'default' | 'small';
178181
[dropdownRender]="nzDropdownRender"
179182
[compareWith]="compareWith"
180183
[mode]="nzMode"
184+
[isMaxLimitReached]="isMaxLimitReached"
181185
(keydown)="onKeyDown($event)"
182186
(itemClick)="onItemClick($event)"
183187
(scrollToBottom)="nzScrollToBottom.emit()"
@@ -275,6 +279,10 @@ export class NzSelectComponent implements ControlValueAccessor, OnInit, AfterCon
275279
return this._nzShowArrow === undefined ? this.nzMode === 'default' : this._nzShowArrow;
276280
}
277281

282+
get isMaxTagCountSet(): boolean {
283+
return this.nzMaxMultipleCount !== Infinity;
284+
}
285+
278286
@Output() readonly nzOnSearch = new EventEmitter<string>();
279287
@Output() readonly nzScrollToBottom = new EventEmitter<void>();
280288
@Output() readonly nzOpenChange = new EventEmitter<boolean>();
@@ -309,6 +317,7 @@ export class NzSelectComponent implements ControlValueAccessor, OnInit, AfterCon
309317
focused = false;
310318
dir: Direction = 'ltr';
311319
positions: ConnectionPositionPair[] = [];
320+
isMaxLimitReached = false;
312321

313322
// status
314323
prefixCls: string = 'ant-select';
@@ -422,6 +431,9 @@ export class NzSelectComponent implements ControlValueAccessor, OnInit, AfterCon
422431
this.value = model;
423432
this.onChange(this.value);
424433
}
434+
435+
this.isMaxLimitReached =
436+
this.nzMaxMultipleCount !== Infinity && this.listOfValue.length === this.nzMaxMultipleCount;
425437
}
426438

427439
onTokenSeparate(listOfLabel: string[]): void {

components/select/select.spec.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1270,7 +1270,15 @@ describe('select', () => {
12701270
flushRefresh();
12711271
expect(component.value.length).toBe(1);
12721272
expect(component.value[0]).toBe('test_01');
1273+
expect(listOfContainerItem[1]).toHaveClass('ant-select-item-option-disabled');
12731274
}));
1275+
it('should show nzShowArrow component when having nzMaxMultipleCount', () => {
1276+
component.nzMaxMultipleCount = 0;
1277+
expect(selectElement.querySelector('nz-select-arrow')).toBeFalsy();
1278+
component.nzMaxMultipleCount = 1;
1279+
fixture.detectChanges();
1280+
expect(selectElement.querySelector('nz-select-arrow')).toBeTruthy();
1281+
});
12741282
it('should nzAutoClearSearchValue work', fakeAsync(() => {
12751283
const flushRefresh = (): void => {
12761284
fixture.detectChanges();
@@ -1425,6 +1433,33 @@ describe('select', () => {
14251433
// expect(requestAnimationFrameSpy).toHaveBeenCalledTimes(2);
14261434
}));
14271435

1436+
it('should isMaxTagCountSet work correct', () => {
1437+
component.nzMaxMultipleCount = Infinity;
1438+
fixture.detectChanges();
1439+
let isMaxTagCountSet;
1440+
isMaxTagCountSet = selectComponent['isMaxTagCountSet'];
1441+
expect(isMaxTagCountSet).toBeFalsy();
1442+
1443+
component.nzMaxMultipleCount = 1;
1444+
fixture.detectChanges();
1445+
isMaxTagCountSet = selectComponent['isMaxTagCountSet'];
1446+
expect(isMaxTagCountSet).toBeTruthy();
1447+
});
1448+
1449+
it('should isMaxLimitReached be set correctly', () => {
1450+
selectComponent.nzMaxMultipleCount = 2;
1451+
selectComponent.listOfValue = ['a', 'b'];
1452+
fixture.detectChanges();
1453+
selectComponent.updateListOfValue(['a', 'b']);
1454+
expect(selectComponent.isMaxLimitReached).toBeTruthy();
1455+
1456+
selectComponent.nzMaxMultipleCount = 20;
1457+
selectComponent.listOfValue = ['a', 'b'];
1458+
fixture.detectChanges();
1459+
selectComponent.updateListOfValue(['a', 'b']);
1460+
expect(selectComponent.isMaxLimitReached).toBeFalsy();
1461+
});
1462+
14281463
it('should not run change detection when `nz-select-top-control` is clicked and should focus the `nz-select-search`', () => {
14291464
const appRef = TestBed.inject(ApplicationRef);
14301465
spyOn(appRef, 'tick');
@@ -1625,6 +1660,7 @@ describe('select', () => {
16251660
[(nzOpen)]="nzOpen"
16261661
[nzPlacement]="nzPlacement"
16271662
[nzSelectOnTab]="nzSelectOnTab"
1663+
[nzMaxMultipleCount]="nzMaxMultipleCount"
16281664
(ngModelChange)="valueChange($event)"
16291665
(nzOnSearch)="searchValueChange($event)"
16301666
(nzOpenChange)="openChange($event)"
@@ -1671,6 +1707,7 @@ export class TestSelectTemplateDefaultComponent {
16711707
nzSuffixIcon: TemplateRef<NzSafeAny> | null = null;
16721708
nzClearIcon: TemplateRef<NzSafeAny> | null = null;
16731709
nzShowArrow = true;
1710+
nzMaxMultipleCount: number = Infinity;
16741711
nzFilterOption: NzFilterOptionType = (searchValue: string, item: NzSelectItemInterface): boolean => {
16751712
if (item && item.nzLabel) {
16761713
return item.nzLabel.toString().toLowerCase().indexOf(searchValue.toLowerCase()) > -1;

0 commit comments

Comments
 (0)