Skip to content

Commit b78b99f

Browse files
authored
feat(module:transfer): support multiple row selection with Shift key (#9092)
1 parent adb91e4 commit b78b99f

2 files changed

Lines changed: 64 additions & 31 deletions

File tree

components/transfer/transfer.component.ts

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
Component,
1111
ElementRef,
1212
EventEmitter,
13+
HostListener,
1314
Input,
1415
OnChanges,
1516
OnDestroy,
@@ -220,9 +221,31 @@ export class NzTransferComponent implements OnInit, OnChanges, OnDestroy {
220221

221222
// left
222223
leftDataSource: TransferItem[] = [];
224+
lastLeftCheckedIndex?: number;
223225

224226
// right
225227
rightDataSource: TransferItem[] = [];
228+
lastRightCheckedIndex?: number;
229+
230+
isShiftPressed = false;
231+
232+
@HostListener('window:keydown.shift')
233+
onTriggerShiftDown(): void {
234+
this.isShiftPressed = true;
235+
}
236+
237+
@HostListener('window:keyup.shift')
238+
onTriggerShiftUp(): void {
239+
this.isShiftPressed = false;
240+
}
241+
242+
@HostListener('mousedown', ['$event'])
243+
onTriggerMouseDown(event: MouseEvent): void {
244+
const isInsideTransfer = (event.target as HTMLElement).closest('.ant-transfer-list');
245+
if (event.shiftKey && isInsideTransfer) {
246+
event.preventDefault();
247+
}
248+
}
226249

227250
private splitDataSource(): void {
228251
this.leftDataSource = [];
@@ -249,6 +272,23 @@ export class NzTransferComponent implements OnInit, OnChanges, OnDestroy {
249272
handleRightSelect = (item: TransferItem): void => this.handleSelect('right', !!item.checked, item);
250273

251274
handleSelect(direction: TransferDirection, checked: boolean, item?: TransferItem): void {
275+
if (item) {
276+
const datasource = direction === 'left' ? this.leftDataSource : this.rightDataSource;
277+
const currentIndex = datasource.findIndex(i => i.key === item.key);
278+
const lastCheckedIndex = this[direction === 'left' ? 'lastLeftCheckedIndex' : 'lastRightCheckedIndex'] ?? -1;
279+
if (this.isShiftPressed && lastCheckedIndex > -1) {
280+
const start = Math.min(lastCheckedIndex, currentIndex);
281+
const end = Math.max(lastCheckedIndex, currentIndex);
282+
for (let i = start; i <= end; i++) {
283+
const item = datasource[i];
284+
if (!item.disabled) {
285+
item.checked = checked;
286+
}
287+
}
288+
this.markForCheckAllList();
289+
}
290+
this[direction === 'left' ? 'lastLeftCheckedIndex' : 'lastRightCheckedIndex'] = currentIndex;
291+
}
252292
const list = this.getCheckedData(direction);
253293
const count = list.filter(i => !i.disabled).length;
254294
this.updateOperationStatus(direction, count);
@@ -301,11 +341,7 @@ export class NzTransferComponent implements OnInit, OnChanges, OnDestroy {
301341
}
302342
targetDatasource.splice(0, 0, ...list);
303343
this.updateOperationStatus(oppositeDirection);
304-
this.nzChange.emit({
305-
from: oppositeDirection,
306-
to: direction,
307-
list
308-
});
344+
this.nzChange.emit({ from: oppositeDirection, to: direction, list });
309345
this.markForCheckAllList();
310346
}
311347

components/transfer/transfer.spec.ts

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,7 @@ describe('transfer', () => {
3939
let pageObject: TransferPageObject<AbstractTestTransferComponent>;
4040

4141
beforeEach(() => {
42-
TestBed.configureTestingModule({
43-
providers: [provideNzIconsTesting(), provideNoopAnimations()]
44-
});
42+
TestBed.configureTestingModule({ providers: [provideNzIconsTesting(), provideNoopAnimations()] });
4543
fixture = TestBed.createComponent(TestTransferComponent);
4644
debugElement = fixture.debugElement;
4745
instance = debugElement.componentInstance;
@@ -210,6 +208,22 @@ describe('transfer', () => {
210208
expect(instance.comp.rightDataSource.filter(w => w.checked).length).toBe(0);
211209
});
212210

211+
it('should be checkboxes are toggle select via shift key', () => {
212+
expect(instance.comp.rightDataSource.filter(w => w.checked).length).toBe(0);
213+
pageObject.checkItem('right', 0);
214+
expect(instance.comp.rightDataSource.filter(w => w.checked).length).toBe(1);
215+
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Shift' }));
216+
expect(instance.comp.isShiftPressed).toBeTrue();
217+
fixture.detectChanges();
218+
const multiSelectEndIndex = 9;
219+
pageObject.checkItem('right', multiSelectEndIndex);
220+
expect(instance.comp.rightDataSource.filter(w => w.checked).length).toBe(
221+
COUNT - LEFTCOUNT - DISABLED - multiSelectEndIndex + 1
222+
);
223+
window.dispatchEvent(new KeyboardEvent('keyup', { key: 'Shift' }));
224+
expect(instance.comp.isShiftPressed).toBeFalse();
225+
});
226+
213227
describe('#notFoundContent', () => {
214228
it('should be the left and right list have data', () => {
215229
instance.nzDataSource = [{ title: `content0`, direction: 'right' }, { title: `content1` }];
@@ -680,22 +694,12 @@ class TestTransferComponent implements OnInit, AbstractTestTransferComponent {
680694
})
681695
class TestTransferCustomRenderComponent implements OnInit, AbstractTestTransferComponent {
682696
@ViewChild('comp', { static: false }) comp!: NzTransferComponent;
683-
nzDataSource: Array<{
684-
key: string;
685-
title: string;
686-
description: string;
687-
direction: TransferDirection;
688-
icon: string;
689-
}> = [];
697+
nzDataSource: Array<{ key: string; title: string; description: string; direction: TransferDirection; icon: string }> =
698+
[];
690699

691700
ngOnInit(): void {
692-
const ret: Array<{
693-
key: string;
694-
title: string;
695-
description: string;
696-
direction: TransferDirection;
697-
icon: string;
698-
}> = [];
701+
const ret: Array<{ key: string; title: string; description: string; direction: TransferDirection; icon: string }> =
702+
[];
699703
for (let i = 0; i < COUNT; i++) {
700704
ret.push({
701705
key: i.toString(),
@@ -710,21 +714,14 @@ class TestTransferCustomRenderComponent implements OnInit, AbstractTestTransferC
710714
}
711715

712716
// https://github.com/NG-ZORRO/ng-zorro-antd/issues/996
713-
@Component({
714-
imports: [NzTransferModule],
715-
template: `<nz-transfer [nzDataSource]="list"></nz-transfer>`
716-
})
717+
@Component({ imports: [NzTransferModule], template: `<nz-transfer [nzDataSource]="list"></nz-transfer>` })
717718
class Test996Component implements OnInit {
718719
@ViewChild(NzTransferComponent, { static: true }) comp!: NzTransferComponent;
719720
list: NzSafeAny[] = [];
720721

721722
ngOnInit(): void {
722723
for (let i = 0; i < 2; i++) {
723-
this.list.push({
724-
key: i.toString(),
725-
title: `content${i + 1}`,
726-
disabled: i % 3 < 1
727-
});
724+
this.list.push({ key: i.toString(), title: `content${i + 1}`, disabled: i % 3 < 1 });
728725
}
729726

730727
[0, 1].forEach(idx => (this.list[idx].direction = 'right'));

0 commit comments

Comments
 (0)