Skip to content

Commit

Permalink
feat(module:select): select max tag count (#8371)
Browse files Browse the repository at this point in the history
* feat(module:select): select max tag count

* feat(module:select): select max tag count

* feat(module:select): select max tag count
  • Loading branch information
ParsaArvanehPA authored Mar 11, 2024
1 parent a0b08be commit 18b898e
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 2 deletions.
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

0 comments on commit 18b898e

Please sign in to comment.