@progress/kendo-angular-listbox 21.0.0-develop.2 → 21.0.0-develop.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,7 +9,7 @@ import { Subscription } from 'rxjs';
9
9
  import { getter } from '@progress/kendo-common';
10
10
  import { caretAltUpIcon, caretAltDownIcon, caretAltRightIcon, caretAltLeftIcon, caretDoubleAltRightIcon, caretDoubleAltLeftIcon, xIcon } from '@progress/kendo-svg-icons';
11
11
  import { ButtonComponent } from '@progress/kendo-angular-buttons';
12
- import { normalizeNumpadKeys, Keys, TemplateContextDirective, isChanged, ResizeBatchService } from '@progress/kendo-angular-common';
12
+ import { normalizeNumpadKeys, Keys, isPresent as isPresent$1, TemplateContextDirective, isChanged, ResizeBatchService } from '@progress/kendo-angular-common';
13
13
  import { take } from 'rxjs/operators';
14
14
  import * as i1 from '@progress/kendo-angular-l10n';
15
15
  import { ComponentMessages, LocalizationService, L10N_PREFIX } from '@progress/kendo-angular-l10n';
@@ -25,8 +25,8 @@ const packageMetadata = {
25
25
  productName: 'Kendo UI for Angular',
26
26
  productCode: 'KENDOUIANGULAR',
27
27
  productCodes: ['KENDOUIANGULAR'],
28
- publishDate: 1761753096,
29
- version: '21.0.0-develop.2',
28
+ publishDate: 1762427032,
29
+ version: '21.0.0-develop.21',
30
30
  licensingDocsUrl: 'https://www.telerik.com/kendo-angular-ui/my-license/?utm_medium=product&utm_source=kendoangular&utm_campaign=kendo-ui-angular-purchase-license-keys-warning'
31
31
  };
32
32
 
@@ -34,17 +34,138 @@ const packageMetadata = {
34
34
  * @hidden
35
35
  */
36
36
  class ListBoxSelectionService {
37
+ selectedIndices = [];
38
+ selectionMode = 'single';
39
+ lastSelectedOrUnselectedIndex = null;
40
+ rangeSelectionTargetIndex = null;
41
+ rangeSelectionAnchorIndex = null;
37
42
  onSelect = new EventEmitter();
38
- selectedIndex = null;
39
- select(index) {
40
- this.onSelect.next({ index: index, prevIndex: this.selectedIndex });
41
- this.selectedIndex = index;
43
+ select(index, ctrlKey = false, shiftKey = false) {
44
+ const previousSelection = [...this.selectedIndices];
45
+ let selectedIndices = [];
46
+ let deselectedIndices = null;
47
+ const previousTargetIndex = this.rangeSelectionTargetIndex;
48
+ if (this.selectionMode === 'single') {
49
+ if (ctrlKey) {
50
+ const isSelected = this.isSelected(index);
51
+ if (isSelected) {
52
+ this.selectedIndices = [];
53
+ deselectedIndices = [index];
54
+ }
55
+ else {
56
+ this.selectedIndices = [index];
57
+ selectedIndices = [index];
58
+ if (previousSelection.length > 0) {
59
+ deselectedIndices = previousSelection;
60
+ }
61
+ }
62
+ this.lastSelectedOrUnselectedIndex = index;
63
+ }
64
+ else {
65
+ this.selectedIndices = [index];
66
+ selectedIndices = [index];
67
+ this.lastSelectedOrUnselectedIndex = index;
68
+ this.rangeSelectionAnchorIndex = index;
69
+ if (previousSelection.length > 0 && previousSelection[0] !== index) {
70
+ deselectedIndices = previousSelection;
71
+ }
72
+ }
73
+ }
74
+ else if (this.selectionMode === 'multiple') {
75
+ if (shiftKey) {
76
+ let anchorIndex = this.rangeSelectionAnchorIndex ?? this.lastSelectedOrUnselectedIndex ?? 0;
77
+ if (index === anchorIndex) {
78
+ this.selectedIndices = [anchorIndex];
79
+ this.rangeSelectionTargetIndex = index;
80
+ selectedIndices = this.selectedIndices.filter(i => !previousSelection.includes(i));
81
+ const nowDeselected = previousSelection.filter(i => !this.selectedIndices.includes(i));
82
+ deselectedIndices = nowDeselected.length > 0 ? nowDeselected : null;
83
+ }
84
+ else {
85
+ if (previousTargetIndex !== null && previousTargetIndex !== anchorIndex) {
86
+ const previousDirection = previousTargetIndex > anchorIndex ? 'down' : 'up';
87
+ const currentDirection = index > anchorIndex ? 'down' : 'up';
88
+ if (previousDirection !== currentDirection) {
89
+ this.rangeSelectionAnchorIndex = previousTargetIndex;
90
+ anchorIndex = previousTargetIndex;
91
+ }
92
+ }
93
+ const startIndex = Math.min(anchorIndex, index);
94
+ const endIndex = Math.max(anchorIndex, index);
95
+ this.selectedIndices = [];
96
+ for (let i = startIndex; i <= endIndex; i++) {
97
+ this.selectedIndices.push(i);
98
+ }
99
+ this.rangeSelectionTargetIndex = index;
100
+ selectedIndices = this.selectedIndices.filter(i => !previousSelection.includes(i));
101
+ const nowDeselected = previousSelection.filter(i => !this.selectedIndices.includes(i));
102
+ deselectedIndices = nowDeselected.length > 0 ? nowDeselected : null;
103
+ }
104
+ }
105
+ else if (ctrlKey) {
106
+ const indexInSelection = this.selectedIndices.indexOf(index);
107
+ if (indexInSelection === -1) {
108
+ this.selectedIndices.push(index);
109
+ selectedIndices = [index];
110
+ }
111
+ else {
112
+ this.selectedIndices.splice(indexInSelection, 1);
113
+ deselectedIndices = [index];
114
+ }
115
+ this.lastSelectedOrUnselectedIndex = index;
116
+ this.rangeSelectionAnchorIndex = index;
117
+ this.rangeSelectionTargetIndex = index;
118
+ }
119
+ else {
120
+ this.selectedIndices = [index];
121
+ selectedIndices = [index];
122
+ this.lastSelectedOrUnselectedIndex = index;
123
+ this.rangeSelectionAnchorIndex = index;
124
+ this.rangeSelectionTargetIndex = index;
125
+ const nowDeselected = previousSelection.filter(i => i !== index);
126
+ deselectedIndices = nowDeselected.length > 0 ? nowDeselected : null;
127
+ }
128
+ }
129
+ this.onSelect.next({
130
+ selectedIndices: selectedIndices.length > 0 ? selectedIndices : null,
131
+ deselectedIndices
132
+ });
133
+ }
134
+ selectRange(targetIndex) {
135
+ const anchorIndex = this.lastSelectedOrUnselectedIndex ?? 0;
136
+ const startIndex = Math.min(anchorIndex, targetIndex);
137
+ const endIndex = Math.max(anchorIndex, targetIndex);
138
+ this.selectedIndices = [];
139
+ for (let i = startIndex; i <= endIndex; i++) {
140
+ this.selectedIndices.push(i);
141
+ }
142
+ }
143
+ setSelectedIndices(indices) {
144
+ this.selectedIndices = [...indices];
145
+ }
146
+ addToSelectedIndices(index) {
147
+ if (this.selectionMode === 'single') {
148
+ this.selectedIndices = [index];
149
+ }
150
+ else if (this.selectedIndices.indexOf(index) === -1) {
151
+ this.selectedIndices = [...this.selectedIndices, index];
152
+ }
153
+ }
154
+ selectAll(totalItems) {
155
+ if (this.selectionMode === 'multiple') {
156
+ this.selectedIndices = Array.from({ length: totalItems }, (_, i) => i);
157
+ }
158
+ }
159
+ areAllSelected(totalItems) {
160
+ return this.selectedIndices.length === totalItems && totalItems > 0;
42
161
  }
43
162
  isSelected(index) {
44
- return index === this.selectedIndex;
163
+ return this.selectedIndices.indexOf(index) !== -1;
45
164
  }
46
165
  clearSelection() {
47
- this.selectedIndex = null;
166
+ this.selectedIndices = [];
167
+ this.lastSelectedOrUnselectedIndex = null;
168
+ this.rangeSelectionAnchorIndex = null;
48
169
  }
49
170
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ListBoxSelectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
50
171
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ListBoxSelectionService });
@@ -208,6 +329,8 @@ class KeyboardNavigationService {
208
329
  onTransferAllEvent = new EventEmitter();
209
330
  onShiftSelectedItem = new EventEmitter();
210
331
  onSelectionChange = new EventEmitter();
332
+ onSelectAll = new EventEmitter();
333
+ onSelectToEnd = new EventEmitter();
211
334
  constructor(renderer, zone) {
212
335
  this.renderer = renderer;
213
336
  this.zone = zone;
@@ -234,6 +357,17 @@ class KeyboardNavigationService {
234
357
  this.onDeleteEvent.emit(this.selectedListboxItemIndex);
235
358
  }
236
359
  }
360
+ if (ctrlOrMetaKey && (event.key === 'a' || event.key === 'A')) {
361
+ event.preventDefault();
362
+ this.onSelectAll.emit();
363
+ return;
364
+ }
365
+ if (ctrlOrMetaKey && event.shiftKey && (keyCode === Keys.Home || keyCode === Keys.End)) {
366
+ event.preventDefault();
367
+ const direction = keyCode === Keys.Home ? 'home' : 'end';
368
+ this.onSelectToEnd.emit({ direction });
369
+ return;
370
+ }
237
371
  const isTargetListboxItem = listboxItems.find(elem => elem.nativeElement === target);
238
372
  if (isTargetListboxItem) {
239
373
  let isTransferToolVisible;
@@ -250,9 +384,7 @@ class KeyboardNavigationService {
250
384
  this.onSelectChange(event, listboxItems);
251
385
  }
252
386
  else if (keyCode === Keys.Space) {
253
- if (this.selectedListboxItemIndex !== this.focusedListboxItemIndex) {
254
- this.onSpaceKey(event, listboxItems);
255
- }
387
+ this.onSpaceKey(event, listboxItems);
256
388
  }
257
389
  }
258
390
  }
@@ -276,18 +408,30 @@ class KeyboardNavigationService {
276
408
  }
277
409
  const offset = dir === 'up' ? -1 : 1;
278
410
  this.focusedToolIndex += offset;
279
- const prevItem = toolsRef[this.focusedToolIndex + (offset * -1)].element;
280
- const currentItem = toolsRef[this.focusedToolIndex].element;
411
+ const prevItem = toolsRef[this.focusedToolIndex + (offset * -1)]?.element;
412
+ const currentItem = toolsRef[this.focusedToolIndex]?.element;
281
413
  this.changeTabindex(prevItem, currentItem);
282
414
  }
283
415
  onSpaceKey(event, listboxItems) {
284
416
  event.stopImmediatePropagation();
285
417
  event.preventDefault();
286
- const previousItem = listboxItems[this.selectedListboxItemIndex]?.nativeElement;
287
- const currentItem = listboxItems[this.focusedListboxItemIndex]?.nativeElement;
288
- this.changeTabindex(previousItem, currentItem);
289
- this.onSelectionChange.emit({ index: this.focusedListboxItemIndex, prevIndex: this.selectedListboxItemIndex });
290
- this.selectedListboxItemIndex = this.focusedListboxItemIndex;
418
+ event.stopPropagation();
419
+ const ctrlKey = event.ctrlKey || event.metaKey;
420
+ const shiftKey = event.shiftKey;
421
+ if (this.selectedListboxItemIndex !== this.focusedListboxItemIndex) {
422
+ const previousItem = listboxItems[this.selectedListboxItemIndex]?.nativeElement;
423
+ const currentItem = listboxItems[this.focusedListboxItemIndex]?.nativeElement;
424
+ this.changeTabindex(previousItem, currentItem);
425
+ }
426
+ this.onSelectionChange.emit({
427
+ index: this.focusedListboxItemIndex,
428
+ prevIndex: this.selectedListboxItemIndex,
429
+ ctrlKey,
430
+ shiftKey
431
+ });
432
+ if (!shiftKey) {
433
+ this.selectedListboxItemIndex = this.focusedListboxItemIndex;
434
+ }
291
435
  }
292
436
  onArrowUpOrDown(keyCode, ctrlOrMetaKey, event, activeToolbar, listboxItems) {
293
437
  event.preventDefault();
@@ -304,6 +448,10 @@ class KeyboardNavigationService {
304
448
  this.changeFocusedItem(dir, listboxItems);
305
449
  return;
306
450
  }
451
+ if (event.shiftKey) {
452
+ this.onShiftArrow(dir, listboxItems);
453
+ return;
454
+ }
307
455
  dir === 'moveUp' ? this.onArrowUp(listboxItems) : this.onArrowDown(listboxItems);
308
456
  this.onSelectionChange.emit({ index: this.selectedListboxItemIndex, prevIndex: this.focusedListboxItemIndex });
309
457
  this.focusedListboxItemIndex = this.selectedListboxItemIndex;
@@ -337,7 +485,8 @@ class KeyboardNavigationService {
337
485
  this.selectedListboxItemIndex = this.focusedListboxItemIndex;
338
486
  }
339
487
  this.changeTabindex(previousItem, currentItem, !!currentItem);
340
- this.onSelectionChange.emit({ index: this.selectedListboxItemIndex, prevIndex });
488
+ const ctrlKey = event.ctrlKey || event.metaKey;
489
+ this.onSelectionChange.emit({ index: this.selectedListboxItemIndex, prevIndex, ctrlKey });
341
490
  }
342
491
  onF10Key(tools) {
343
492
  if (this.focusedToolIndex && this.focusedToolIndex > -1) {
@@ -439,14 +588,36 @@ class KeyboardNavigationService {
439
588
  });
440
589
  }
441
590
  changeFocusedItem(dir, listboxItems) {
442
- listboxItems[this.focusedListboxItemIndex].nativeElement.blur();
591
+ const previousIndex = this.focusedListboxItemIndex;
592
+ const previousItem = listboxItems[previousIndex].nativeElement;
443
593
  if (this.focusedListboxItemIndex > 0 && dir === 'moveUp') {
444
594
  this.focusedListboxItemIndex -= 1;
445
595
  }
446
596
  else if (this.focusedListboxItemIndex < listboxItems.length - 1 && dir === 'moveDown') {
447
597
  this.focusedListboxItemIndex += 1;
448
598
  }
449
- listboxItems[this.focusedListboxItemIndex].nativeElement.focus();
599
+ const currentItem = listboxItems[this.focusedListboxItemIndex].nativeElement;
600
+ this.changeTabindex(previousItem, currentItem);
601
+ }
602
+ onShiftArrow(dir, listboxItems) {
603
+ const previousFocusIndex = this.focusedListboxItemIndex;
604
+ if (dir === 'moveUp' && this.focusedListboxItemIndex > 0) {
605
+ this.focusedListboxItemIndex -= 1;
606
+ }
607
+ else if (dir === 'moveDown' && this.focusedListboxItemIndex < listboxItems.length - 1) {
608
+ this.focusedListboxItemIndex += 1;
609
+ }
610
+ if (previousFocusIndex !== this.focusedListboxItemIndex) {
611
+ const previousItem = listboxItems[previousFocusIndex]?.nativeElement;
612
+ const currentItem = listboxItems[this.focusedListboxItemIndex]?.nativeElement;
613
+ this.changeTabindex(previousItem, currentItem);
614
+ this.onSelectionChange.emit({
615
+ index: this.focusedListboxItemIndex,
616
+ prevIndex: this.selectedListboxItemIndex,
617
+ shiftKey: true
618
+ });
619
+ this.selectedListboxItemIndex = this.focusedListboxItemIndex;
620
+ }
450
621
  }
451
622
  onArrowDown(listboxItems) {
452
623
  if (this.selectedListboxItemIndex < listboxItems.length - 1) {
@@ -487,7 +658,12 @@ class ItemSelectableDirective {
487
658
  }
488
659
  onClick(event) {
489
660
  event.stopPropagation();
490
- this.selectionService.select(this.index);
661
+ const ctrlKey = event.ctrlKey || event.metaKey;
662
+ const shiftKey = event.shiftKey;
663
+ if (shiftKey) {
664
+ event.preventDefault();
665
+ }
666
+ this.selectionService.select(this.index, ctrlKey, shiftKey);
491
667
  }
492
668
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ItemSelectableDirective, deps: [{ token: ListBoxSelectionService }], target: i0.ɵɵFactoryTarget.Directive });
493
669
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: ItemSelectableDirective, isStandalone: true, selector: "[kendoListBoxItemSelectable]", inputs: { index: "index" }, host: { listeners: { "mousedown": "onClick($event)" }, properties: { "class.k-selected": "this.selectedClassName" } }, ngImport: i0 });
@@ -637,7 +813,6 @@ class ListBoxComponent {
637
813
  renderer;
638
814
  zone;
639
815
  localization;
640
- changeDetector;
641
816
  /**
642
817
  * @hidden
643
818
  */
@@ -670,6 +845,18 @@ class ListBoxComponent {
670
845
  * Specifies the field of the data item that provides the text content of the nodes.
671
846
  */
672
847
  textField;
848
+ /**
849
+ * Sets the selection mode of the ListBox.
850
+ *
851
+ * @default 'single'
852
+ */
853
+ set selectable(mode) {
854
+ this._selectable = mode;
855
+ this.selectionService.selectionMode = mode;
856
+ }
857
+ get selectable() {
858
+ return this._selectable;
859
+ }
673
860
  /**
674
861
  * Specifies the data that the ListBox displays.
675
862
  *
@@ -692,6 +879,8 @@ class ListBoxComponent {
692
879
  /**
693
880
  * Configures the toolbar of the ListBox.
694
881
  * Specifies whether to display a toolbar and which tools and position to use.
882
+ *
883
+ * @default false
695
884
  */
696
885
  set toolbar(config) {
697
886
  let position = DEFAULT_TOOLBAR_POSITION;
@@ -730,7 +919,7 @@ class ListBoxComponent {
730
919
  /**
731
920
  * Fires when you click a ListBox item.
732
921
  */
733
- actionClick = new EventEmitter();
922
+ action = new EventEmitter();
734
923
  /**
735
924
  * @hidden
736
925
  */
@@ -750,7 +939,7 @@ class ListBoxComponent {
750
939
  /**
751
940
  * @hidden
752
941
  */
753
- selectedTools = allTools;
942
+ selectedTools = [];
754
943
  /**
755
944
  * @hidden
756
945
  */
@@ -778,15 +967,15 @@ class ListBoxComponent {
778
967
  localizationSubscription;
779
968
  _size = DEFAULT_SIZE;
780
969
  subs = new Subscription();
781
- shouldFireFocusIn = true;
782
- constructor(keyboardNavigationService, selectionService, hostElement, renderer, zone, localization, changeDetector) {
970
+ shouldFireFocusIn = false;
971
+ _selectable = 'single';
972
+ constructor(keyboardNavigationService, selectionService, hostElement, renderer, zone, localization) {
783
973
  this.keyboardNavigationService = keyboardNavigationService;
784
974
  this.selectionService = selectionService;
785
975
  this.hostElement = hostElement;
786
976
  this.renderer = renderer;
787
977
  this.zone = zone;
788
978
  this.localization = localization;
789
- this.changeDetector = changeDetector;
790
979
  validatePackage(packageMetadata);
791
980
  this.setToolbarClass(DEFAULT_TOOLBAR_POSITION);
792
981
  this.setSizingClass(this.size);
@@ -799,9 +988,6 @@ class ListBoxComponent {
799
988
  // This allows us to know to which parent Listbox the child Listbox is connected to
800
989
  this.childListbox.parentListbox = this;
801
990
  }
802
- if (this.selectedIndex) {
803
- this.keyboardNavigationService.focusedToolIndex = this.selectedIndex;
804
- }
805
991
  this.localizationSubscription = this.localization.changes.subscribe(({ rtl }) => {
806
992
  this.direction = rtl ? 'rtl' : 'ltr';
807
993
  });
@@ -826,10 +1012,10 @@ class ListBoxComponent {
826
1012
  const isListboxParentAndChild = !!(this.parentListbox && this.childListbox);
827
1013
  const isListboxParent = !!(this.childListbox || (!this.childListbox && !this.parentListbox));
828
1014
  if (isListboxChild || (isListboxParentAndChild && isActionTransferFrom)) {
829
- this.parentListbox.actionClick.next(actionName);
1015
+ this.parentListbox.action.next(actionName);
830
1016
  }
831
1017
  else if (isListboxParent || (isListboxParentAndChild && !isActionTransferFrom)) {
832
- this.actionClick.next(actionName);
1018
+ this.action.next(actionName);
833
1019
  }
834
1020
  const toolsRef = this.tools.toArray() || this.parentListbox.tools.toArray();
835
1021
  const focusedToolIndex = toolsRef.findIndex(elem => elem.nativeElement === document.activeElement);
@@ -842,11 +1028,22 @@ class ListBoxComponent {
842
1028
  navService.changeTabindex(prevTool, currentTool);
843
1029
  }
844
1030
  }
1031
+ /**
1032
+ * Selects multiple ListBox nodes programmatically.
1033
+ */
1034
+ select(indices) {
1035
+ const validIndices = indices.filter(index => index >= 0 && index < this.data.length);
1036
+ this.selectionService.setSelectedIndices(validIndices);
1037
+ }
845
1038
  /**
846
1039
  * Selects a ListBox node programmatically.
1040
+ *
1041
+ * @hidden
847
1042
  */
848
1043
  selectItem(index) {
849
- this.selectionService.selectedIndex = index;
1044
+ if (index >= 0 && index < this.data.length) {
1045
+ this.select([index]);
1046
+ }
850
1047
  }
851
1048
  /**
852
1049
  * Clears the ListBox selection programmatically.
@@ -855,10 +1052,10 @@ class ListBoxComponent {
855
1052
  this.selectionService.clearSelection();
856
1053
  }
857
1054
  /**
858
- * Gets the index of the currently selected item in the ListBox.
1055
+ * Gets the indexes of the currently selected items in the ListBox.
859
1056
  */
860
- get selectedIndex() {
861
- return this.selectionService.selectedIndex;
1057
+ get selectedIndices() {
1058
+ return this.selectionService.selectedIndices;
862
1059
  }
863
1060
  /**
864
1061
  * @hidden
@@ -901,36 +1098,81 @@ class ListBoxComponent {
901
1098
  this.caretAltLeftIcon :
902
1099
  icon;
903
1100
  }
904
- onClickEvent(prevIndex, index) {
905
- this.shouldFireFocusIn = false;
906
- this.selectionChange.next({ index, prevIndex: this.keyboardNavigationService.selectedListboxItemIndex });
907
- this.keyboardNavigationService.selectedListboxItemIndex = index;
908
- this.keyboardNavigationService.focusedListboxItemIndex = index;
909
- this.zone.onStable.pipe(take(1)).subscribe(() => {
910
- const listboxItems = this.listboxItems.toArray();
911
- const previousItem = prevIndex ? listboxItems[prevIndex].nativeElement : listboxItems[0].nativeElement;
912
- const currentItem = listboxItems[index].nativeElement;
913
- this.keyboardNavigationService.changeTabindex(previousItem, currentItem);
914
- });
915
- this.zone.onStable.pipe(take(1)).subscribe(() => {
916
- this.shouldFireFocusIn = true;
917
- });
918
- }
919
1101
  initSubscriptions(navService, hostEl, toolsRef) {
920
1102
  this.subs.add(navService.onShiftSelectedItem.subscribe((actionToPerform) => this.performAction(actionToPerform)));
921
1103
  this.subs.add(navService.onTransferAllEvent.subscribe((actionToPerform) => this.performAction(actionToPerform)));
922
- this.subs.add(this.selectionService.onSelect.subscribe((e) => this.onClickEvent(e.prevIndex, e.index)));
1104
+ this.subs.add(this.selectionService.onSelect.subscribe((event) => {
1105
+ this.shouldFireFocusIn = false;
1106
+ this.selectionChange.next(event);
1107
+ const newFocusIndex = isPresent$1(this.selectionService.rangeSelectionTargetIndex)
1108
+ ? this.selectionService.rangeSelectionTargetIndex
1109
+ : this.selectionService.lastSelectedOrUnselectedIndex;
1110
+ if (newFocusIndex !== null && newFocusIndex !== undefined) {
1111
+ const listboxItems = this.listboxItems.toArray();
1112
+ const previousItem = listboxItems[navService.focusedListboxItemIndex]?.nativeElement;
1113
+ const currentItem = listboxItems[newFocusIndex]?.nativeElement;
1114
+ if (previousItem !== currentItem && currentItem) {
1115
+ navService.changeTabindex(previousItem, currentItem, false);
1116
+ navService.focusedListboxItemIndex = newFocusIndex;
1117
+ navService.selectedListboxItemIndex = newFocusIndex;
1118
+ }
1119
+ }
1120
+ this.zone.runOutsideAngular(() => setTimeout(() => {
1121
+ this.shouldFireFocusIn = true;
1122
+ }));
1123
+ }));
923
1124
  this.subs.add(navService.onDeleteEvent.subscribe((index) => this.onDeleteEvent(index, navService)));
924
1125
  this.subs.add(navService.onMoveSelectedItem.subscribe((dir) => this.performAction(dir)));
1126
+ this.subs.add(navService.onSelectAll.subscribe(() => {
1127
+ if (this.selectable === 'multiple') {
1128
+ const previousSelection = [...this.selectionService.selectedIndices];
1129
+ const allSelected = this.selectionService.areAllSelected(this.data.length);
1130
+ if (allSelected) {
1131
+ this.selectionService.clearSelection();
1132
+ this.selectionChange.next({
1133
+ selectedIndices: null,
1134
+ deselectedIndices: previousSelection.length > 0 ? previousSelection : null
1135
+ });
1136
+ }
1137
+ else {
1138
+ this.selectionService.selectAll(this.data.length);
1139
+ const selectedIndices = this.selectionService.selectedIndices.filter(i => !previousSelection.includes(i));
1140
+ this.selectionChange.next({
1141
+ selectedIndices: selectedIndices.length > 0 ? selectedIndices : null,
1142
+ deselectedIndices: null
1143
+ });
1144
+ }
1145
+ }
1146
+ }));
1147
+ this.subs.add(navService.onSelectToEnd.subscribe(({ direction }) => {
1148
+ if (this.selectable === 'multiple') {
1149
+ this.shouldFireFocusIn = false;
1150
+ const previousSelection = [...this.selectionService.selectedIndices];
1151
+ const targetIndex = direction === 'home' ? 0 : this.data.length - 1;
1152
+ this.selectionService.selectRange(targetIndex);
1153
+ const selectedIndices = this.selectionService.selectedIndices.filter(i => !previousSelection.includes(i));
1154
+ const deselectedIndices = previousSelection.filter(i => !this.selectionService.selectedIndices.includes(i));
1155
+ this.selectionChange.next({
1156
+ selectedIndices: selectedIndices.length > 0 ? selectedIndices : null,
1157
+ deselectedIndices: deselectedIndices.length > 0 ? deselectedIndices : null
1158
+ });
1159
+ const listboxItems = this.listboxItems.toArray();
1160
+ const currentItem = listboxItems[navService.focusedListboxItemIndex]?.nativeElement;
1161
+ const targetItem = listboxItems[targetIndex]?.nativeElement;
1162
+ navService.changeTabindex(currentItem, targetItem);
1163
+ navService.focusedListboxItemIndex = targetIndex;
1164
+ this.zone.runOutsideAngular(() => setTimeout(() => {
1165
+ this.shouldFireFocusIn = true;
1166
+ }));
1167
+ }
1168
+ }));
925
1169
  if (this.listboxElement) {
926
1170
  this.subs.add(this.renderer.listen(this.listboxElement.nativeElement, 'focusin', (event) => this.onFocusIn(event)));
927
1171
  }
928
1172
  this.subs.add(this.renderer.listen(hostEl, 'keydown', (event) => navService.onKeyDown(event, toolsRef, this.selectedTools, this.childListbox, this.parentListbox, this.listboxItems.toArray())));
929
- this.subs.add(navService.onSelectionChange.subscribe((indexes) => {
930
- const { prevIndex, index } = indexes;
931
- this.selectionService.selectedIndex = index;
932
- this.selectionChange.next({ index, prevIndex });
933
- this.changeDetector.markForCheck();
1173
+ this.subs.add(navService.onSelectionChange.subscribe((event) => {
1174
+ const { index, ctrlKey, shiftKey } = event;
1175
+ this.selectionService.select(index, ctrlKey, shiftKey);
934
1176
  }));
935
1177
  }
936
1178
  onFocusIn(event) {
@@ -941,8 +1183,15 @@ class ListBoxComponent {
941
1183
  if (index === -1) {
942
1184
  return;
943
1185
  }
944
- this.selectionService.selectedIndex = index;
945
- this.selectionChange.next({ index, prevIndex: null });
1186
+ const previousSelection = [...this.selectionService.selectedIndices];
1187
+ this.selectionService.addToSelectedIndices(index);
1188
+ navService.focusedListboxItemIndex = index;
1189
+ navService.selectedListboxItemIndex = index;
1190
+ const deselected = previousSelection.filter(i => i !== index);
1191
+ this.selectionChange.next({
1192
+ selectedIndices: [index],
1193
+ deselectedIndices: deselected.length > 0 ? deselected : null
1194
+ });
946
1195
  const previousItem = items[navService.selectedListboxItemIndex]?.nativeElement;
947
1196
  const currentItem = items[index]?.nativeElement;
948
1197
  this.renderer.setAttribute(previousItem, 'tabindex', '-1');
@@ -984,17 +1233,20 @@ class ListBoxComponent {
984
1233
  }
985
1234
  }
986
1235
  onDeleteEvent(index, navService) {
987
- this.selectionService.selectedIndex = index;
1236
+ this.selectionService.addToSelectedIndices(index);
988
1237
  this.performAction('remove');
989
1238
  const listboxItems = this.listboxItems.toArray();
990
1239
  const setIndex = index + 1 === listboxItems.length ?
991
1240
  { index: index - 1, tabindex: index - 1 } : { index, tabindex: index + 1 };
992
1241
  navService.changeTabindex(null, listboxItems[setIndex['tabindex']]?.nativeElement);
993
- this.selectionChange.next({ index: setIndex['index'], prevIndex: null });
1242
+ this.selectionChange.next({
1243
+ selectedIndices: [setIndex['index']],
1244
+ deselectedIndices: null
1245
+ });
994
1246
  navService.selectedListboxItemIndex = setIndex['index'];
995
1247
  navService.focusedListboxItemIndex = setIndex['index'];
996
1248
  navService.focusedListboxItem = setIndex['index'];
997
- this.selectionService.selectedIndex = setIndex['index'];
1249
+ this.selectionService.selectedIndices = [setIndex['index']];
998
1250
  }
999
1251
  setToolbarClass(pos) {
1000
1252
  Object.keys(actionsClasses).forEach((className) => {
@@ -1009,8 +1261,8 @@ class ListBoxComponent {
1009
1261
  setSizingClass(size) {
1010
1262
  this.renderer.addClass(this.hostElement.nativeElement, `k-listbox-${sizeClassMap[size]}`);
1011
1263
  }
1012
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ListBoxComponent, deps: [{ token: KeyboardNavigationService }, { token: ListBoxSelectionService }, { token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i0.NgZone }, { token: i1.LocalizationService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
1013
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ListBoxComponent, isStandalone: true, selector: "kendo-listbox", inputs: { textField: "textField", data: "data", size: "size", toolbar: "toolbar", listboxLabel: "listboxLabel", listboxToolbarLabel: "listboxToolbarLabel", itemDisabled: "itemDisabled" }, outputs: { selectionChange: "selectionChange", actionClick: "actionClick", getChildListbox: "getChildListbox" }, host: { properties: { "class.k-listbox": "this.listboxClassName", "attr.dir": "this.direction" } }, providers: [
1264
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ListBoxComponent, deps: [{ token: KeyboardNavigationService }, { token: ListBoxSelectionService }, { token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i0.NgZone }, { token: i1.LocalizationService }], target: i0.ɵɵFactoryTarget.Component });
1265
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ListBoxComponent, isStandalone: true, selector: "kendo-listbox", inputs: { textField: "textField", selectable: "selectable", data: "data", size: "size", toolbar: "toolbar", listboxLabel: "listboxLabel", listboxToolbarLabel: "listboxToolbarLabel", itemDisabled: "itemDisabled" }, outputs: { selectionChange: "selectionChange", action: "action", getChildListbox: "getChildListbox" }, host: { properties: { "class.k-listbox": "this.listboxClassName", "attr.dir": "this.direction" } }, providers: [
1014
1266
  ListBoxSelectionService,
1015
1267
  KeyboardNavigationService,
1016
1268
  LocalizationService,
@@ -1077,16 +1329,16 @@ class ListBoxComponent {
1077
1329
  class="k-list-ul"
1078
1330
  role="listbox"
1079
1331
  [attr.aria-label]="listboxLabel"
1080
- [attr.aria-multiselectable]="false"
1332
+ [attr.aria-multiselectable]="selectable === 'multiple'"
1081
1333
  >
1082
1334
  <li
1083
1335
  #listboxItems
1084
1336
  *ngFor="let item of data; let i = index"
1085
1337
  kendoListBoxItemSelectable
1086
1338
  class="k-list-item"
1087
- [attr.tabindex]="i === 0 ? '0' : '-1'"
1339
+ [attr.tabindex]="i === keyboardNavigationService.focusedListboxItemIndex ? '0' : '-1'"
1088
1340
  role="option"
1089
- [attr.aria-selected]="selectedIndex === i"
1341
+ [attr.aria-selected]="selectedIndices.indexOf(i) >= 0"
1090
1342
  [index]="i"
1091
1343
  [class.k-disabled]="itemDisabled(item)"
1092
1344
  >
@@ -1184,16 +1436,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
1184
1436
  class="k-list-ul"
1185
1437
  role="listbox"
1186
1438
  [attr.aria-label]="listboxLabel"
1187
- [attr.aria-multiselectable]="false"
1439
+ [attr.aria-multiselectable]="selectable === 'multiple'"
1188
1440
  >
1189
1441
  <li
1190
1442
  #listboxItems
1191
1443
  *ngFor="let item of data; let i = index"
1192
1444
  kendoListBoxItemSelectable
1193
1445
  class="k-list-item"
1194
- [attr.tabindex]="i === 0 ? '0' : '-1'"
1446
+ [attr.tabindex]="i === keyboardNavigationService.focusedListboxItemIndex ? '0' : '-1'"
1195
1447
  role="option"
1196
- [attr.aria-selected]="selectedIndex === i"
1448
+ [attr.aria-selected]="selectedIndices.indexOf(i) >= 0"
1197
1449
  [index]="i"
1198
1450
  [class.k-disabled]="itemDisabled(item)"
1199
1451
  >
@@ -1221,7 +1473,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
1221
1473
  standalone: true,
1222
1474
  imports: [LocalizedMessagesDirective, NgIf, NgFor, ButtonComponent, ItemSelectableDirective, TemplateContextDirective]
1223
1475
  }]
1224
- }], ctorParameters: () => [{ type: KeyboardNavigationService }, { type: ListBoxSelectionService }, { type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i0.NgZone }, { type: i1.LocalizationService }, { type: i0.ChangeDetectorRef }], propDecorators: { listboxClassName: [{
1476
+ }], ctorParameters: () => [{ type: KeyboardNavigationService }, { type: ListBoxSelectionService }, { type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i0.NgZone }, { type: i1.LocalizationService }], propDecorators: { listboxClassName: [{
1225
1477
  type: HostBinding,
1226
1478
  args: ['class.k-listbox']
1227
1479
  }], direction: [{
@@ -1244,6 +1496,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
1244
1496
  args: ['tools']
1245
1497
  }], textField: [{
1246
1498
  type: Input
1499
+ }], selectable: [{
1500
+ type: Input
1247
1501
  }], data: [{
1248
1502
  type: Input
1249
1503
  }], size: [{
@@ -1258,7 +1512,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
1258
1512
  type: Input
1259
1513
  }], selectionChange: [{
1260
1514
  type: Output
1261
- }], actionClick: [{
1515
+ }], action: [{
1262
1516
  type: Output
1263
1517
  }], getChildListbox: [{
1264
1518
  type: Output
@@ -1288,22 +1542,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
1288
1542
  */
1289
1543
  class DataBindingDirective {
1290
1544
  listbox;
1545
+ zone;
1291
1546
  /**
1292
1547
  * Specifies the `ListBoxComponent` instance with which the current ListBox connects.
1293
1548
  * When you link two listboxes through this input, you can transfer items between them.
1294
1549
  */
1295
1550
  connectedWith;
1296
- actionClickSub = new Subscription();
1551
+ actionSub = new Subscription();
1297
1552
  selectedBoxSub = new Subscription();
1298
1553
  connectedWithSub = new Subscription();
1299
1554
  selectedBox;
1300
- constructor(listbox) {
1555
+ constructor(listbox, zone) {
1301
1556
  this.listbox = listbox;
1557
+ this.zone = zone;
1302
1558
  this.selectedBox = this.listbox;
1303
1559
  this.connectedWithSub.add(this.listbox.getChildListbox.subscribe(() => {
1304
1560
  this.listbox.childListbox = this.connectedWith;
1305
1561
  }));
1306
- this.actionClickSub.add(this.listbox.actionClick.subscribe((actionName) => {
1562
+ this.actionSub.add(this.listbox.action.subscribe((actionName) => {
1307
1563
  switch (actionName) {
1308
1564
  case 'moveUp': {
1309
1565
  this.moveVertically('up');
@@ -1314,11 +1570,11 @@ class DataBindingDirective {
1314
1570
  break;
1315
1571
  }
1316
1572
  case 'transferFrom': {
1317
- this.transferItem(this.connectedWith, this.listbox);
1573
+ this.transferSelectedItems(this.connectedWith, this.listbox);
1318
1574
  break;
1319
1575
  }
1320
1576
  case 'transferTo': {
1321
- this.transferItem(this.listbox, this.connectedWith);
1577
+ this.transferSelectedItems(this.listbox, this.connectedWith);
1322
1578
  break;
1323
1579
  }
1324
1580
  case 'transferAllTo': {
@@ -1330,7 +1586,7 @@ class DataBindingDirective {
1330
1586
  break;
1331
1587
  }
1332
1588
  case 'remove': {
1333
- this.removeItem();
1589
+ this.removeSelectedItems();
1334
1590
  break;
1335
1591
  }
1336
1592
  default: {
@@ -1362,9 +1618,9 @@ class DataBindingDirective {
1362
1618
  * @hidden
1363
1619
  */
1364
1620
  ngOnDestroy() {
1365
- if (this.actionClickSub) {
1366
- this.actionClickSub.unsubscribe();
1367
- this.actionClickSub = null;
1621
+ if (this.actionSub) {
1622
+ this.actionSub.unsubscribe();
1623
+ this.actionSub = null;
1368
1624
  }
1369
1625
  if (this.selectedBoxSub) {
1370
1626
  this.selectedBoxSub.unsubscribe();
@@ -1372,38 +1628,73 @@ class DataBindingDirective {
1372
1628
  }
1373
1629
  }
1374
1630
  moveVertically(dir) {
1375
- const index = this.selectedBox.selectedIndex;
1376
- if (!isPresent(index)) {
1631
+ const selectedIndices = this.selectedBox.selectedIndices;
1632
+ if (!isPresent(selectedIndices) || selectedIndices.length === 0) {
1377
1633
  return;
1378
1634
  }
1379
- const topReached = dir === 'up' && index <= 0;
1380
- const bottomReached = dir === 'down' && index >= this.selectedBox.data.length - 1;
1635
+ const sortedIndices = [...selectedIndices].sort((a, b) => a - b);
1636
+ const topIndex = sortedIndices[0];
1637
+ const bottomIndex = sortedIndices[sortedIndices.length - 1];
1638
+ const topReached = dir === 'up' && topIndex <= 0;
1639
+ const bottomReached = dir === 'down' && bottomIndex >= this.selectedBox.data.length - 1;
1381
1640
  if (topReached || bottomReached) {
1382
1641
  return;
1383
1642
  }
1384
- const newIndex = dir === 'up' ? index - 1 : index + 1;
1643
+ const data = this.selectedBox.data;
1644
+ const newSelectedIndices = [];
1645
+ if (dir === 'up') {
1646
+ for (const index of sortedIndices) {
1647
+ const newIndex = index - 1;
1648
+ [data[newIndex], data[index]] = [data[index], data[newIndex]];
1649
+ newSelectedIndices.push(newIndex);
1650
+ }
1651
+ }
1652
+ else {
1653
+ for (let i = sortedIndices.length - 1; i >= 0; i--) {
1654
+ const index = sortedIndices[i];
1655
+ const newIndex = index + 1;
1656
+ [data[newIndex], data[index]] = [data[index], data[newIndex]];
1657
+ newSelectedIndices.push(newIndex);
1658
+ }
1659
+ }
1660
+ newSelectedIndices.sort((a, b) => a - b);
1661
+ this.selectedBox.selectionService.setSelectedIndices(newSelectedIndices);
1385
1662
  const navigation = this.selectedBox.keyboardNavigationService;
1386
- navigation.focusedListboxItemIndex = navigation.selectedListboxItemIndex = newIndex;
1387
- [this.selectedBox.data[newIndex], this.selectedBox.data[index]] = [this.selectedBox.data[index], this.selectedBox.data[newIndex]];
1388
- this.selectedBox.selectionService.select(newIndex);
1663
+ const currentFocusedIndex = navigation.focusedListboxItemIndex;
1664
+ const focusedItemIndexInSelection = sortedIndices.indexOf(currentFocusedIndex);
1665
+ let newFocusIndex;
1666
+ if (focusedItemIndexInSelection !== -1) {
1667
+ newFocusIndex = newSelectedIndices[focusedItemIndexInSelection];
1668
+ }
1669
+ else {
1670
+ newFocusIndex = dir === 'up' ? topIndex - 1 : bottomIndex + 1;
1671
+ }
1672
+ this.zone.onStable.pipe(take(1)).subscribe(() => {
1673
+ const listboxItems = this.selectedBox.listboxItems.toArray();
1674
+ const previousItem = listboxItems[currentFocusedIndex]?.nativeElement;
1675
+ const currentItem = listboxItems[newFocusIndex]?.nativeElement;
1676
+ navigation.changeTabindex(previousItem, currentItem);
1677
+ navigation.focusedListboxItemIndex = newFocusIndex;
1678
+ navigation.selectedListboxItemIndex = newFocusIndex;
1679
+ });
1389
1680
  }
1390
- removeItem() {
1391
- const index = this.selectedBox.selectedIndex;
1392
- if (!isPresent(index)) {
1681
+ removeSelectedItems() {
1682
+ const itemIndices = this.selectedBox.selectedIndices;
1683
+ if (!isPresent(itemIndices) || itemIndices.length === 0) {
1393
1684
  return;
1394
1685
  }
1395
- this.selectedBox.data.splice(index, 1);
1686
+ this.selectedBox.data = this.selectedBox.data.filter((_, index) => !itemIndices.includes(index));
1396
1687
  this.selectedBox.selectionService.clearSelection();
1397
1688
  }
1398
- transferItem(source, target) {
1399
- const item = source && source.data[source.selectedIndex];
1400
- if (!item || !target || !source) {
1689
+ transferSelectedItems(source, target) {
1690
+ const selectedIndices = source?.data && source?.selectedIndices;
1691
+ if (!target || !source || !isPresent(selectedIndices) || selectedIndices.length === 0) {
1401
1692
  return;
1402
1693
  }
1403
- target.data.push(item);
1404
- source.data.splice(source.selectedIndex, 1);
1694
+ target.data.push(...selectedIndices.map(index => source.data[index]));
1695
+ source.data = source.data.filter((_, index) => !selectedIndices.includes(index));
1405
1696
  source.clearSelection();
1406
- target.selectItem(target.data.length - 1);
1697
+ target.select([target.data.length - 1]);
1407
1698
  this.selectedBox = target;
1408
1699
  }
1409
1700
  transferAll(source, target) {
@@ -1411,10 +1702,10 @@ class DataBindingDirective {
1411
1702
  return;
1412
1703
  }
1413
1704
  target.data.splice(target.data.length, 0, ...source.data.splice(0, source.data.length));
1414
- target.selectItem(target.data.length - 1);
1705
+ target.select([target.data.length - 1]);
1415
1706
  this.selectedBox = target;
1416
1707
  }
1417
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DataBindingDirective, deps: [{ token: ListBoxComponent }], target: i0.ɵɵFactoryTarget.Directive });
1708
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DataBindingDirective, deps: [{ token: ListBoxComponent }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Directive });
1418
1709
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: DataBindingDirective, isStandalone: true, selector: "[kendoListBoxDataBinding]", inputs: { connectedWith: "connectedWith" }, usesOnChanges: true, ngImport: i0 });
1419
1710
  }
1420
1711
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DataBindingDirective, decorators: [{
@@ -1423,7 +1714,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
1423
1714
  selector: '[kendoListBoxDataBinding]',
1424
1715
  standalone: true
1425
1716
  }]
1426
- }], ctorParameters: () => [{ type: ListBoxComponent }], propDecorators: { connectedWith: [{
1717
+ }], ctorParameters: () => [{ type: ListBoxComponent }, { type: i0.NgZone }], propDecorators: { connectedWith: [{
1427
1718
  type: Input
1428
1719
  }] } });
1429
1720