Skip to content

Commit 3714840

Browse files
authored
fix(module:select): limit number of pasted item to nzMaxMultipleCount (#9080)
1 parent 7937671 commit 3714840

File tree

3 files changed

+63
-20
lines changed

3 files changed

+63
-20
lines changed

components/select/select-top-control.component.ts

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,17 @@ import {
1212
Input,
1313
NgZone,
1414
OnChanges,
15-
OnDestroy,
1615
OnInit,
1716
Output,
1817
SimpleChanges,
1918
TemplateRef,
2019
ViewChild,
2120
ViewEncapsulation,
2221
inject,
23-
numberAttribute
22+
numberAttribute,
23+
DestroyRef
2424
} from '@angular/core';
25-
import { Subject } from 'rxjs';
26-
import { takeUntil } from 'rxjs/operators';
25+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
2726

2827
import { NzNoAnimationDirective } from 'ng-zorro-antd/core/no-animation';
2928
import { NzSafeAny } from 'ng-zorro-antd/core/types';
@@ -99,7 +98,7 @@ import { NzSelectItemInterface, NzSelectModeType, NzSelectTopControlItemType } f
9998
host: { class: 'ant-select-selector' },
10099
imports: [NzSelectSearchComponent, NzSelectItemComponent, NzSelectPlaceholderComponent]
101100
})
102-
export class NzSelectTopControlComponent implements OnChanges, OnInit, OnDestroy {
101+
export class NzSelectTopControlComponent implements OnChanges, OnInit {
103102
@Input() nzId: string | null = null;
104103
@Input() showSearch = false;
105104
@Input() placeHolder: string | TemplateRef<NzSafeAny> | null = null;
@@ -123,8 +122,6 @@ export class NzSelectTopControlComponent implements OnChanges, OnInit, OnDestroy
123122
isComposing = false;
124123
inputValue: string | null = null;
125124

126-
private destroy$ = new Subject<void>();
127-
128125
updateTemplateVariable(): void {
129126
const isSelectedValueEmpty = this.listOfTopItem.length === 0;
130127
this.isShowPlaceholder = isSelectedValueEmpty && !this.isComposing && !this.inputValue;
@@ -196,13 +193,11 @@ export class NzSelectTopControlComponent implements OnChanges, OnInit, OnDestroy
196193
}
197194
}
198195

196+
private destroyRef = inject(DestroyRef);
197+
private elementRef = inject(ElementRef<HTMLElement>);
198+
private ngZone = inject(NgZone);
199199
noAnimation = inject(NzNoAnimationDirective, { host: true, optional: true });
200200

201-
constructor(
202-
private elementRef: ElementRef<HTMLElement>,
203-
private ngZone: NgZone
204-
) {}
205-
206201
ngOnChanges(changes: SimpleChanges): void {
207202
const { listOfTopItem, maxTagCount, customTemplate, maxTagPlaceholder } = changes;
208203
if (listOfTopItem) {
@@ -234,7 +229,7 @@ export class NzSelectTopControlComponent implements OnChanges, OnInit, OnDestroy
234229

235230
ngOnInit(): void {
236231
fromEventOutsideAngular<MouseEvent>(this.elementRef.nativeElement, 'click')
237-
.pipe(takeUntil(this.destroy$))
232+
.pipe(takeUntilDestroyed(this.destroyRef))
238233
.subscribe(event => {
239234
// `HTMLElement.focus()` is a native DOM API which doesn't require Angular to run change detection.
240235
if (event.target !== this.nzSelectSearchComponent.inputElement.nativeElement) {
@@ -243,7 +238,7 @@ export class NzSelectTopControlComponent implements OnChanges, OnInit, OnDestroy
243238
});
244239

245240
fromEventOutsideAngular<KeyboardEvent>(this.elementRef.nativeElement, 'keydown')
246-
.pipe(takeUntil(this.destroy$))
241+
.pipe(takeUntilDestroyed(this.destroyRef))
247242
.subscribe(event => {
248243
if (event.target instanceof HTMLInputElement) {
249244
const inputValue = event.target.value;
@@ -256,8 +251,4 @@ export class NzSelectTopControlComponent implements OnChanges, OnInit, OnDestroy
256251
}
257252
});
258253
}
259-
260-
ngOnDestroy(): void {
261-
this.destroy$.next();
262-
}
263254
}

components/select/select.component.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -453,13 +453,21 @@ export class NzSelectComponent implements ControlValueAccessor, OnInit, AfterCon
453453
.filter(item => listOfLabel.findIndex(label => label === item.nzLabel) !== -1)
454454
.map(item => item.nzValue)
455455
.filter(item => this.listOfValue.findIndex(v => this.compareWith(v, item)) === -1);
456+
/**
457+
* Limit the number of selected item to nzMaxMultipleCount
458+
*/
459+
const limitWithinMaxCount = <T>(value: T[]): T[] =>
460+
this.isMaxMultipleCountSet ? value.slice(0, this.nzMaxMultipleCount) : value;
461+
456462
if (this.nzMode === 'multiple') {
457-
this.updateListOfValue([...this.listOfValue, ...listOfMatchedValue]);
463+
const updateValue = limitWithinMaxCount([...this.listOfValue, ...listOfMatchedValue]);
464+
this.updateListOfValue(updateValue);
458465
} else if (this.nzMode === 'tags') {
459466
const listOfUnMatchedLabel = listOfLabel.filter(
460467
label => this.listOfTagAndTemplateItem.findIndex(item => item.nzLabel === label) === -1
461468
);
462-
this.updateListOfValue([...this.listOfValue, ...listOfMatchedValue, ...listOfUnMatchedLabel]);
469+
const updateValue = limitWithinMaxCount([...this.listOfValue, ...listOfMatchedValue, ...listOfUnMatchedLabel]);
470+
this.updateListOfValue(updateValue);
463471
}
464472
this.clearInput();
465473
}

components/select/select.spec.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,6 +1310,27 @@ describe('select', () => {
13101310
expect(component.value[0]).toBe('test_01');
13111311
}));
13121312

1313+
it('should nzTokenSeparators + nzMaxMultipleCount work', fakeAsync(() => {
1314+
component.nzMaxMultipleCount = 1;
1315+
component.listOfOption = [
1316+
{ value: 'test_01', label: 'label_01' },
1317+
{ value: 'test_02', label: 'label_02' }
1318+
];
1319+
component.value = [];
1320+
component.nzTokenSeparators = [','];
1321+
fixture.detectChanges();
1322+
flush();
1323+
fixture.detectChanges();
1324+
const inputElement = selectElement.querySelector('input')!;
1325+
inputElement.value = 'label_01,label_02';
1326+
dispatchFakeEvent(inputElement, 'input');
1327+
fixture.detectChanges();
1328+
flush();
1329+
fixture.detectChanges();
1330+
expect(component.value.length).toBe(1);
1331+
expect(component.value[0]).toBe('test_01');
1332+
}));
1333+
13131334
it('should nzMaxMultipleCount work', fakeAsync(() => {
13141335
const flushRefresh = (): void => {
13151336
fixture.detectChanges();
@@ -1497,6 +1518,27 @@ describe('select', () => {
14971518
expect(component.value[1]).toBe('test_02');
14981519
}));
14991520

1521+
it('should nzTokenSeparators + nzMaxMultipleCount work', fakeAsync(() => {
1522+
component.nzMaxMultipleCount = 1;
1523+
component.listOfOption = [
1524+
{ value: 'test_01', label: 'label_01' },
1525+
{ value: 'test_02', label: 'label_02' }
1526+
];
1527+
component.value = [];
1528+
component.nzTokenSeparators = [','];
1529+
fixture.detectChanges();
1530+
flush();
1531+
fixture.detectChanges();
1532+
const inputElement = selectElement.querySelector('input')!;
1533+
inputElement.value = 'label_01,label_02';
1534+
dispatchFakeEvent(inputElement, 'input');
1535+
fixture.detectChanges();
1536+
flush();
1537+
fixture.detectChanges();
1538+
expect(component.value.length).toBe(1);
1539+
expect(component.value[0]).toBe('test_01');
1540+
}));
1541+
15001542
it('should nzMaxTagCount works', fakeAsync(() => {
15011543
component.listOfOption = [
15021544
{ value: 'test_01', label: 'label_01' },
@@ -2021,6 +2063,7 @@ export class TestSelectReactiveMultipleComponent {
20212063
[nzOptions]="listOfOption"
20222064
[nzSize]="nzSize"
20232065
[nzMaxTagCount]="nzMaxTagCount"
2066+
[nzMaxMultipleCount]="nzMaxMultipleCount"
20242067
[nzTokenSeparators]="nzTokenSeparators"
20252068
[nzMaxTagPlaceholder]="nzMaxTagPlaceholder ?? null"
20262069
(ngModelChange)="valueChange($event)"
@@ -2037,6 +2080,7 @@ export class TestSelectReactiveTagsComponent {
20372080
valueChange = jasmine.createSpy('valueChange');
20382081
nzTokenSeparators: string[] = [];
20392082
nzMaxTagPlaceholder?: TemplateRef<NzSafeAny>;
2083+
nzMaxMultipleCount?: number;
20402084
}
20412085

20422086
@Component({

0 commit comments

Comments
 (0)