@progress/kendo-angular-listbox 11.4.0-develop.6 → 11.4.0-develop.8

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.
@@ -3,16 +3,17 @@
3
3
  * Licensed under commercial license. See LICENSE.md in the project root for more information
4
4
  *-------------------------------------------------------------------------------------------*/
5
5
  import * as i0 from '@angular/core';
6
- import { EventEmitter, Injectable, Directive, Input, HostBinding, HostListener, isDevMode, Component, ContentChild, Output, NgModule } from '@angular/core';
6
+ import { EventEmitter, Injectable, Directive, Input, HostBinding, HostListener, isDevMode, Component, ContentChild, ViewChild, ViewChildren, Output, NgModule } from '@angular/core';
7
7
  import { validatePackage } from '@progress/kendo-licensing';
8
8
  import { Subscription } from 'rxjs';
9
9
  import { getter } from '@progress/kendo-common';
10
10
  import { caretAltUpIcon, caretAltDownIcon, caretAltLeftIcon, caretAltRightIcon, caretDoubleAltRightIcon, caretDoubleAltLeftIcon, xIcon } from '@progress/kendo-svg-icons';
11
- import * as i2 from '@progress/kendo-angular-buttons';
11
+ import { Keys, isChanged } from '@progress/kendo-angular-common';
12
+ import { take } from 'rxjs/operators';
13
+ import * as i3 from '@progress/kendo-angular-buttons';
12
14
  import { ButtonsModule } from '@progress/kendo-angular-buttons';
13
- import * as i3 from '@angular/common';
15
+ import * as i4 from '@angular/common';
14
16
  import { CommonModule } from '@angular/common';
15
- import { isChanged } from '@progress/kendo-angular-common';
16
17
 
17
18
  /**
18
19
  * @hidden
@@ -21,8 +22,8 @@ const packageMetadata = {
21
22
  name: '@progress/kendo-angular-listbox',
22
23
  productName: 'Kendo UI for Angular',
23
24
  productCodes: ['KENDOUIANGULAR', 'KENDOUICOMPLETE'],
24
- publishDate: 1678518161,
25
- version: '11.4.0-develop.6',
25
+ publishDate: 1678794094,
26
+ version: '11.4.0-develop.8',
26
27
  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'
27
28
  };
28
29
 
@@ -34,9 +35,9 @@ class ListBoxSelectionService {
34
35
  this.onSelect = new EventEmitter();
35
36
  this.selectedIndex = null;
36
37
  }
37
- select(index) {
38
+ select(index, dir) {
39
+ this.onSelect.next({ index: index, prevIndex: this.selectedIndex, dir });
38
40
  this.selectedIndex = index;
39
- this.onSelect.next({ index: this.selectedIndex });
40
41
  }
41
42
  isSelected(index) {
42
43
  return index === this.selectedIndex;
@@ -106,13 +107,13 @@ const allTools = [
106
107
  },
107
108
  {
108
109
  name: 'transferTo',
109
- label: 'Transfer From',
110
+ label: 'Transfer To',
110
111
  icon: 'caret-alt-left',
111
112
  svgIcon: caretAltLeftIcon
112
113
  },
113
114
  {
114
115
  name: 'transferFrom',
115
- label: 'Transfer To',
116
+ label: 'Transfer From',
116
117
  icon: 'caret-alt-right',
117
118
  svgIcon: caretAltRightIcon
118
119
  },
@@ -186,6 +187,260 @@ const getTools = (names) => {
186
187
  return names.map(tool => allTools.find(meta => meta.name === tool));
187
188
  };
188
189
 
190
+ /* eslint-disable @typescript-eslint/no-inferrable-types */
191
+ /**
192
+ * @hidden
193
+ */
194
+ class KeyboardNavigationService {
195
+ constructor(renderer, zone) {
196
+ this.renderer = renderer;
197
+ this.zone = zone;
198
+ this.selectedListboxItemIndex = 0;
199
+ this.focusedListboxItemIndex = 0;
200
+ this.focusedToolIndex = 0;
201
+ this.onDeleteEvent = new EventEmitter();
202
+ this.onSelectionChange = new EventEmitter();
203
+ this.onMoveSelectedItem = new EventEmitter();
204
+ this.onTransferAllEvent = new EventEmitter();
205
+ this.onShiftSelectedItem = new EventEmitter();
206
+ }
207
+ onKeyDown(event, toolsRef, toolbar, childListbox, parentListbox) {
208
+ const target = event.target;
209
+ const keyCode = event.keyCode;
210
+ const ctrlOrMetaKey = event.ctrlKey || event.metaKey;
211
+ const parentListboxToolbar = parentListbox?.selectedTools;
212
+ const tool = toolsRef.find(elem => elem.element === target);
213
+ const activeToolbar = toolbar.length > 0 ? toolbar : parentListboxToolbar;
214
+ if (toolsRef.length > 0 || parentListbox?.tools.toArray().length > 0) {
215
+ const focusNextTool = (keyCode === Keys.ArrowDown || keyCode === Keys.ArrowRight);
216
+ const focusPrevTool = (keyCode === Keys.ArrowUp || keyCode === Keys.ArrowLeft);
217
+ if ((focusNextTool || focusPrevTool) && tool) {
218
+ const dir = focusPrevTool ? 'up' : 'down';
219
+ this.handleToolbarArrows(toolsRef, dir);
220
+ }
221
+ else if (keyCode === Keys.F10) {
222
+ event.preventDefault();
223
+ this.onF10Key(toolsRef);
224
+ }
225
+ else if (keyCode === Keys.Delete && activeToolbar.some(tool => tool.name === 'remove')) {
226
+ this.onDeleteEvent.emit(this.selectedListboxItemIndex);
227
+ }
228
+ }
229
+ const isTargetListboxItem = this.listboxItems.find(elem => elem === target);
230
+ if (isTargetListboxItem) {
231
+ let isTransferToolVisible;
232
+ if (activeToolbar) {
233
+ isTransferToolVisible = activeToolbar.some(tool => tool.name.startsWith('transfer'));
234
+ }
235
+ if ((keyCode === Keys.ArrowRight || keyCode === Keys.ArrowLeft) && ctrlOrMetaKey && isTransferToolVisible) {
236
+ this.onArrowLeftOrRight(keyCode, parentListbox, childListbox, event);
237
+ }
238
+ else if ((keyCode === Keys.ArrowUp || keyCode === Keys.ArrowDown)) {
239
+ this.onArrowUpOrDown(keyCode, ctrlOrMetaKey, event, activeToolbar);
240
+ }
241
+ else if ((event.metaKey && keyCode === Keys.Enter) || (event.ctrlKey && keyCode === Keys.Space)) {
242
+ event.stopImmediatePropagation();
243
+ event.preventDefault();
244
+ this.onSelectChange();
245
+ }
246
+ else if (keyCode === Keys.Space) {
247
+ if (this.selectedListboxItemIndex !== this.focusedListboxItemIndex) {
248
+ const items = this.listboxItems;
249
+ this.changeTabindex(items[this.selectedListboxItemIndex], items[this.focusedListboxItemIndex]);
250
+ this.selectedListboxItemIndex = this.focusedListboxItemIndex;
251
+ this.onSelectionChange.emit(this.selectedListboxItemIndex);
252
+ }
253
+ }
254
+ }
255
+ }
256
+ changeTabindex(previousItem, currentItem) {
257
+ if (previousItem) {
258
+ this.renderer.setAttribute(previousItem, 'tabindex', '-1');
259
+ previousItem.blur();
260
+ }
261
+ ;
262
+ if (currentItem) {
263
+ this.renderer.setAttribute(currentItem, 'tabindex', '0');
264
+ currentItem.focus();
265
+ }
266
+ }
267
+ onFocusIn() {
268
+ this.onSelectionChange.emit(this.selectedListboxItemIndex);
269
+ }
270
+ handleToolbarArrows(toolsRef, dir) {
271
+ const topReached = dir === 'up' && this.focusedToolIndex <= 0;
272
+ const bottomReached = dir === 'down' && this.focusedToolIndex >= toolsRef.length - 1;
273
+ if (topReached || bottomReached) {
274
+ return;
275
+ }
276
+ const offset = dir === 'up' ? -1 : 1;
277
+ this.focusedToolIndex += offset;
278
+ const prevItem = toolsRef[this.focusedToolIndex + (offset * -1)].element;
279
+ const currentItem = toolsRef[this.focusedToolIndex].element;
280
+ this.changeTabindex(prevItem, currentItem);
281
+ }
282
+ onArrowUpOrDown(keyCode, ctrlOrMetaKey, event, activeToolbar) {
283
+ event.preventDefault();
284
+ const dir = keyCode === Keys.ArrowUp ? 'moveUp' : 'moveDown';
285
+ if (ctrlOrMetaKey) {
286
+ let isMoveToolVisible;
287
+ if (activeToolbar) {
288
+ isMoveToolVisible = activeToolbar.some(tool => tool.name.startsWith('move'));
289
+ }
290
+ if (event.shiftKey && isMoveToolVisible) {
291
+ this.onMoveSelectedItem.emit(dir);
292
+ return;
293
+ }
294
+ this.changeFocusedItem(dir);
295
+ return;
296
+ }
297
+ dir === 'moveUp' ? this.onArrowUp() : this.onArrowDown();
298
+ this.onSelectionChange.emit(this.selectedListboxItemIndex);
299
+ this.focusedListboxItemIndex = this.selectedListboxItemIndex;
300
+ }
301
+ onArrowLeftOrRight(keyCode, parentListbox, childListbox, event) {
302
+ event.preventDefault();
303
+ if (event.shiftKey) {
304
+ this.transferAllItems(keyCode, childListbox, parentListbox);
305
+ return;
306
+ }
307
+ if (this.selectedListboxItemIndex >= 0) {
308
+ this.transferItem(keyCode, childListbox, parentListbox);
309
+ }
310
+ }
311
+ onSelectChange() {
312
+ const canDeslect = (this.selectedListboxItemIndex || this.selectedListboxItemIndex === 0) && this.selectedListboxItemIndex === this.focusedListboxItemIndex;
313
+ if (canDeslect) {
314
+ this.selectedListboxItemIndex = null;
315
+ }
316
+ else {
317
+ const items = this.listboxItems;
318
+ this.changeTabindex(items[this.selectedListboxItemIndex], items[this.focusedListboxItemIndex]);
319
+ this.selectedListboxItemIndex = this.focusedListboxItemIndex;
320
+ }
321
+ this.onSelectionChange.emit(this.selectedListboxItemIndex);
322
+ }
323
+ onF10Key(tools) {
324
+ if (this.focusedToolIndex && this.focusedToolIndex > -1) {
325
+ if (this.focusedToolIndex >= tools.length) {
326
+ tools[tools.length - 1].element.focus();
327
+ }
328
+ else {
329
+ tools[this.focusedToolIndex].element.focus();
330
+ }
331
+ }
332
+ else {
333
+ tools[0].element.focus();
334
+ }
335
+ }
336
+ transferAllItems(keyCode, childListbox, parentListbox) {
337
+ const isArrowRight = keyCode === Keys.ArrowRight;
338
+ const actionToPerform = isArrowRight ? 'transferAllTo' : 'transferAllFrom';
339
+ this.onTransferAllEvent.emit(actionToPerform);
340
+ this.zone.onStable.pipe(take(1)).subscribe(() => {
341
+ const childListboxNav = childListbox?.keyboardNavigationService || parentListbox?.childListbox.keyboardNavigationService;
342
+ let currentItem;
343
+ if (isArrowRight) {
344
+ if (childListbox) {
345
+ const childListboxItemsLength = childListboxNav.listboxItems.length - 1;
346
+ currentItem = childListboxNav.listboxItems[childListboxItemsLength];
347
+ childListboxNav.focusedListboxItemIndex = childListboxItemsLength;
348
+ childListboxNav.selectedListboxItemIndex = childListboxItemsLength;
349
+ this.focusedListboxItemIndex = 0;
350
+ this.selectedListboxItemIndex = 0;
351
+ }
352
+ }
353
+ else {
354
+ if (parentListbox) {
355
+ const parentListboxNav = parentListbox.keyboardNavigationService;
356
+ const parentListboxItemsLength = parentListboxNav.listboxItems.length - 1;
357
+ currentItem = parentListboxNav.listboxItems[parentListboxItemsLength];
358
+ parentListboxNav.focusedListboxItemIndex = parentListboxItemsLength;
359
+ parentListboxNav.selectedListboxItemIndex = parentListboxItemsLength;
360
+ childListboxNav.focusedListboxItemIndex = 0;
361
+ childListboxNav.selectedListboxItemIndex = 0;
362
+ }
363
+ }
364
+ this.changeTabindex(null, currentItem);
365
+ });
366
+ }
367
+ transferItem(keyCode, childListbox, parentListbox) {
368
+ const isArrowRight = keyCode === Keys.ArrowRight;
369
+ const actionToPerform = isArrowRight ? 'transferFrom' : 'transferTo';
370
+ this.onShiftSelectedItem.emit(actionToPerform);
371
+ const adjustTabindex = (items, firstItem, currentItem) => {
372
+ items.listboxItems.forEach(item => {
373
+ if (item.getAttribute('tabindex') === '0') {
374
+ this.changeTabindex(item, firstItem);
375
+ }
376
+ });
377
+ this.changeTabindex(null, currentItem);
378
+ };
379
+ this.zone.onStable.pipe(take(1)).subscribe(() => {
380
+ if (isArrowRight) {
381
+ if (childListbox) {
382
+ const childListboxNav = childListbox.keyboardNavigationService;
383
+ const childListboxItemsLength = childListboxNav.listboxItems.length - 1;
384
+ const currentItem = childListboxNav.listboxItems[childListboxItemsLength];
385
+ const parentListboxFirstItem = this.listboxItems[0];
386
+ childListboxNav.focusedListboxItemIndex = childListboxItemsLength;
387
+ childListboxNav.selectedListboxItemIndex = childListboxItemsLength;
388
+ this.focusedListboxItemIndex = 0;
389
+ this.selectedListboxItemIndex = 0;
390
+ adjustTabindex(childListboxNav, parentListboxFirstItem, currentItem);
391
+ }
392
+ }
393
+ else {
394
+ if (parentListbox) {
395
+ const childListboxNav = parentListbox.childListbox.keyboardNavigationService;
396
+ const parentListboxNav = parentListbox.keyboardNavigationService;
397
+ const parentListboxItemsLength = parentListboxNav.listboxItems.length - 1;
398
+ const currentItem = parentListboxNav.listboxItems[parentListboxItemsLength];
399
+ const childListboxFirstItem = childListboxNav.listboxItems[0];
400
+ parentListboxNav.focusedListboxItemIndex = parentListboxItemsLength;
401
+ parentListboxNav.selectedListboxItemIndex = parentListboxItemsLength;
402
+ childListboxNav.focusedListboxItemIndex = 0;
403
+ childListboxNav.selectedListboxItemIndex = 0;
404
+ adjustTabindex(parentListboxNav, childListboxFirstItem, currentItem);
405
+ }
406
+ }
407
+ });
408
+ }
409
+ changeFocusedItem(dir) {
410
+ this.listboxItems[this.focusedListboxItemIndex].blur();
411
+ if (this.focusedListboxItemIndex > 0 && dir === 'moveUp') {
412
+ this.focusedListboxItemIndex -= 1;
413
+ }
414
+ else if (this.focusedListboxItemIndex < this.listboxItems.length - 1 && dir === 'moveDown') {
415
+ this.focusedListboxItemIndex += 1;
416
+ }
417
+ this.listboxItems[this.focusedListboxItemIndex].focus();
418
+ }
419
+ onArrowDown() {
420
+ if (this.selectedListboxItemIndex < this.listboxItems.length - 1) {
421
+ const offset = this.selectedListboxItemIndex ? this.selectedListboxItemIndex : this.focusedListboxItemIndex;
422
+ this.selectedListboxItemIndex = offset + 1;
423
+ const previousItem = this.listboxItems[this.selectedListboxItemIndex - 1];
424
+ const currentItem = this.listboxItems[this.selectedListboxItemIndex];
425
+ this.changeTabindex(previousItem, currentItem);
426
+ }
427
+ }
428
+ onArrowUp() {
429
+ if (this.selectedListboxItemIndex > 0 || this.focusedListboxItemIndex > 0) {
430
+ const offset = this.selectedListboxItemIndex ? this.selectedListboxItemIndex : this.focusedListboxItemIndex;
431
+ this.selectedListboxItemIndex = offset - 1;
432
+ const previousItem = this.listboxItems[this.selectedListboxItemIndex + 1];
433
+ const currentItem = this.listboxItems[this.selectedListboxItemIndex];
434
+ this.changeTabindex(previousItem, currentItem);
435
+ }
436
+ }
437
+ }
438
+ KeyboardNavigationService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: KeyboardNavigationService, deps: [{ token: i0.Renderer2 }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
439
+ KeyboardNavigationService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: KeyboardNavigationService });
440
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: KeyboardNavigationService, decorators: [{
441
+ type: Injectable
442
+ }], ctorParameters: function () { return [{ type: i0.Renderer2 }, { type: i0.NgZone }]; } });
443
+
189
444
  /**
190
445
  * @hidden
191
446
  */
@@ -218,15 +473,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
218
473
  args: ['click', ['$event']]
219
474
  }] } });
220
475
 
476
+ /* eslint-disable @typescript-eslint/no-inferrable-types */
221
477
  const DEFAULT_SIZE = 'medium';
478
+ let idx = 0;
222
479
  /**
223
480
  * Represents the [Kendo UI ListBox component for Angular]({% slug overview_listbox %}).
224
481
  */
225
482
  class ListBoxComponent {
226
- constructor(selectionService, renderer, hostElement) {
483
+ constructor(keyboardNavigationService, selectionService, hostElement, renderer, zone) {
484
+ this.keyboardNavigationService = keyboardNavigationService;
227
485
  this.selectionService = selectionService;
228
- this.renderer = renderer;
229
486
  this.hostElement = hostElement;
487
+ this.renderer = renderer;
488
+ this.zone = zone;
230
489
  /**
231
490
  * @hidden
232
491
  */
@@ -235,6 +494,14 @@ class ListBoxComponent {
235
494
  * The data which will be displayed by the ListBox.
236
495
  */
237
496
  this.data = [];
497
+ /**
498
+ * The value of the aria-label attribute of the Listbox element.
499
+ */
500
+ this.listboxLabel = 'Listbox';
501
+ /**
502
+ * The value of the aria-label attribute of the Listbox toolbar element.
503
+ */
504
+ this.listboxToolbarLabel = 'Toolbar';
238
505
  /**
239
506
  * A function which determines if a specific item is disabled.
240
507
  */
@@ -247,18 +514,19 @@ class ListBoxComponent {
247
514
  * Fires when the user clicks a ListBox item.
248
515
  */
249
516
  this.actionClick = new EventEmitter();
517
+ /**
518
+ * @hidden
519
+ */
520
+ this.getChildListbox = new EventEmitter();
250
521
  /**
251
522
  * @hidden
252
523
  */
253
524
  this.selectedTools = allTools;
254
525
  this._size = DEFAULT_SIZE;
255
- this.sub = new Subscription();
526
+ this.subs = new Subscription();
256
527
  validatePackage(packageMetadata);
257
528
  this.setToolbarClass(DEFAULT_TOOLBAR_POSITION);
258
529
  this.setSizingClass(this.size);
259
- this.sub.add(this.selectionService.onSelect.subscribe((e) => {
260
- this.selectionChange.next(e);
261
- }));
262
530
  }
263
531
  /**
264
532
  * Sets the size of the component.
@@ -299,17 +567,52 @@ class ListBoxComponent {
299
567
  get listClasses() {
300
568
  return `k-list k-list-${sizeClassMap[this.size]}`;
301
569
  }
302
- /**
303
- * @hidden
304
- */
570
+ ngOnInit() {
571
+ // This event emitter gives us the connectedWith value from the DataBinding directive
572
+ this.getChildListbox.emit();
573
+ if (this.childListbox) {
574
+ // This allows us to know to which parent Listbox the child Listbox is connected to
575
+ this.childListbox.parentListbox = this;
576
+ }
577
+ if (this.selectedIndex) {
578
+ this.keyboardNavigationService.focusedToolIndex = this.selectedIndex;
579
+ }
580
+ }
581
+ ngAfterViewInit() {
582
+ const toolsRef = this.tools.toArray();
583
+ const hostEl = this.hostElement.nativeElement;
584
+ const navService = this.keyboardNavigationService;
585
+ navService.listboxItems = Array.from(this.listboxElement.nativeElement.querySelectorAll('li.k-list-item'));
586
+ this.setIds();
587
+ this.initSubscriptions(navService, hostEl, toolsRef);
588
+ }
305
589
  ngOnDestroy() {
306
- this.sub.unsubscribe();
590
+ this.subs.unsubscribe();
307
591
  }
308
592
  /**
309
593
  * @hidden
310
594
  */
311
595
  performAction(actionName) {
312
- this.actionClick.next(actionName);
596
+ const isActionTransferTo = actionName === 'transferTo' || actionName === 'transferAllFrom';
597
+ const isListboxChild = this.parentListbox && !this.childListbox;
598
+ const isListboxParentAndChild = !!(this.parentListbox && this.childListbox);
599
+ const isListboxParent = !!(this.childListbox || (!this.childListbox && !this.parentListbox));
600
+ if (isListboxChild || (isListboxParentAndChild && isActionTransferTo)) {
601
+ this.parentListbox.actionClick.next(actionName);
602
+ }
603
+ else if (isListboxParent || (isListboxParentAndChild && !isActionTransferTo)) {
604
+ this.actionClick.next(actionName);
605
+ }
606
+ const toolsRef = this.tools.toArray() || this.parentListbox.tools.toArray();
607
+ const focusedToolIndex = toolsRef.findIndex(elem => elem.nativeElement === document.activeElement);
608
+ if ((this.selectedTools.length > 0 || this.parentListbox.selectedTools.length > 0) && focusedToolIndex > -1) {
609
+ const navService = this.keyboardNavigationService || this.parentListbox.keyboardNavigationService;
610
+ const selectedTools = this.selectedTools || this.parentListbox.selectedTools;
611
+ const prevTool = toolsRef[navService.focusedToolIndex]?.element;
612
+ navService.focusedToolIndex = selectedTools.findIndex(tool => tool.name === actionName);
613
+ const currentTool = toolsRef[navService.focusedToolIndex]?.element;
614
+ navService.changeTabindex(prevTool, currentTool);
615
+ }
313
616
  }
314
617
  /**
315
618
  * Programmatically selects a ListBox node.
@@ -329,6 +632,15 @@ class ListBoxComponent {
329
632
  get selectedIndex() {
330
633
  return this.selectionService.selectedIndex;
331
634
  }
635
+ /**
636
+ * @hidden
637
+ */
638
+ get getListboxId() {
639
+ const id = ++idx;
640
+ const listboxId = `k-listbox-${id}`;
641
+ return listboxId;
642
+ }
643
+ ;
332
644
  /**
333
645
  * @hidden
334
646
  */
@@ -338,6 +650,100 @@ class ListBoxComponent {
338
650
  }
339
651
  return fieldAccessor(dataItem, this.textField);
340
652
  }
653
+ /**
654
+ * @hidden
655
+ */
656
+ updateListboxItems() {
657
+ this.zone.onStable.pipe(take(1)).subscribe(() => {
658
+ this.keyboardNavigationService.listboxItems = Array.from(this.listboxElement.nativeElement.querySelectorAll('li.k-list-item'));
659
+ });
660
+ }
661
+ /**
662
+ * @hidden
663
+ */
664
+ swapItems(firstItemIndex, secondItemIndex) {
665
+ const listboxItems = this.keyboardNavigationService.listboxItems;
666
+ [listboxItems[firstItemIndex], listboxItems[secondItemIndex]] = [listboxItems[secondItemIndex], listboxItems[firstItemIndex]];
667
+ }
668
+ onClickEvent(listboxItems, prevIndex, currentIndex, dir) {
669
+ this.selectionChange.next(currentIndex);
670
+ this.keyboardNavigationService.selectedListboxItemIndex = currentIndex;
671
+ this.keyboardNavigationService.focusedListboxItemIndex = currentIndex;
672
+ this.zone.onStable.pipe(take(1)).subscribe(() => {
673
+ if (dir)
674
+ this.swapItems(prevIndex, currentIndex);
675
+ const previousItem = prevIndex ? listboxItems[prevIndex] : listboxItems[0];
676
+ const currentItem = listboxItems[currentIndex];
677
+ this.keyboardNavigationService.changeTabindex(previousItem, currentItem);
678
+ });
679
+ }
680
+ initSubscriptions(navService, hostEl, toolsRef) {
681
+ this.subs.add(navService.onDeleteEvent.subscribe((index) => this.onDeleteEvent(index, navService)));
682
+ this.subs.add(this.renderer.listen(hostEl, 'keydown', event => navService.onKeyDown(event, toolsRef, this.selectedTools, this.childListbox, this.parentListbox)));
683
+ this.subs.add(this.renderer.listen(this.listboxElement.nativeElement, 'focusin', () => navService.onFocusIn()));
684
+ this.subs.add(navService.onShiftSelectedItem.subscribe((actionToPerform) => this.onShiftItems(actionToPerform)));
685
+ this.subs.add(navService.onTransferAllEvent.subscribe((actionToPerform) => this.onShiftItems(actionToPerform)));
686
+ this.subs.add(navService.onMoveSelectedItem.subscribe((dir) => this.performAction(dir)));
687
+ this.subs.add(this.selectionService.onSelect.subscribe((e) => this.onClickEvent(navService.listboxItems, e.prevIndex, e.index, e.dir)));
688
+ this.subs.add(navService.onSelectionChange.subscribe((index) => {
689
+ this.selectionService.selectedIndex = index;
690
+ this.selectionChange.next({ index, prevIndex: null });
691
+ }));
692
+ }
693
+ onShiftItems(actionToPerform) {
694
+ this.performAction(actionToPerform);
695
+ if (actionToPerform === 'transferFrom' || actionToPerform === 'transferAllTo') {
696
+ this.updateListboxItems();
697
+ this.childListbox?.updateListboxItems();
698
+ }
699
+ else {
700
+ this.updateListboxItems();
701
+ this.parentListbox?.updateListboxItems();
702
+ }
703
+ }
704
+ setIds() {
705
+ const listbox = this.listboxElement.nativeElement;
706
+ this.listboxId = this.getListboxId;
707
+ this.renderer.setAttribute(listbox, 'id', this.listboxId);
708
+ if (this.selectedTools.length > 0 || this.parentListbox?.selectedTools.length > 0) {
709
+ const toolbar = this.toolbarElement?.nativeElement;
710
+ const parentToolbar = this.parentListbox?.toolbarElement.nativeElement;
711
+ if (this.parentListbox && this.childListbox) {
712
+ this.zone.onStable.pipe(take(1)).subscribe(() => {
713
+ this.toolbarId = `${this.parentListbox.listboxId} ${this.listboxId} ${this.childListbox.listboxId}`;
714
+ this.renderer.setAttribute(toolbar, 'aria-controls', this.toolbarId);
715
+ });
716
+ }
717
+ else if (this.childListbox && !this.parentListbox) {
718
+ this.zone.onStable.pipe(take(1)).subscribe(() => {
719
+ this.toolbarId = this.toolbarId = `${this.listboxId} ${this.childListbox.listboxId}`;
720
+ this.renderer.setAttribute(toolbar, 'aria-controls', this.toolbarId);
721
+ });
722
+ }
723
+ else if (this.parentListbox && this.selectedTools.length > 0) {
724
+ this.toolbarId = `${this.parentListbox.listboxId} ${this.listboxId}`;
725
+ this.parentListbox.toolbarId = this.toolbarId = `${this.parentListbox.listboxId} ${this.listboxId}`;
726
+ this.renderer.setAttribute(toolbar, 'aria-controls', this.toolbarId);
727
+ this.renderer.setAttribute(parentToolbar, 'aria-controls', this.parentListbox.toolbarId);
728
+ }
729
+ else if (!this.parentListbox && !this.childListbox) {
730
+ this.toolbarId = this.listboxId;
731
+ this.renderer.setAttribute(toolbar, 'aria-controls', this.toolbarId);
732
+ }
733
+ }
734
+ }
735
+ onDeleteEvent(index, navService) {
736
+ this.selectionService.selectedIndex = index;
737
+ this.performAction('remove');
738
+ this.updateListboxItems();
739
+ const setIndex = index + 1 === navService.listboxItems.length ?
740
+ { index: index - 1, tabindex: index - 1 } : { index, tabindex: index + 1 };
741
+ navService.changeTabindex(null, navService.listboxItems[setIndex['tabindex']]);
742
+ this.selectionChange.next({ index: setIndex['index'], prevIndex: null });
743
+ navService.selectedListboxItemIndex = setIndex['index'];
744
+ navService.focusedListboxItem = setIndex['index'];
745
+ this.selectionService.selectedIndex = setIndex['index'];
746
+ }
341
747
  setToolbarClass(pos) {
342
748
  Object.keys(actionsClasses).forEach((className) => {
343
749
  if (pos === className) {
@@ -352,95 +758,140 @@ class ListBoxComponent {
352
758
  this.renderer.addClass(this.hostElement.nativeElement, `k-listbox-${sizeClassMap[size]}`);
353
759
  }
354
760
  }
355
- ListBoxComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: ListBoxComponent, deps: [{ token: ListBoxSelectionService }, { token: i0.Renderer2 }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
356
- ListBoxComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: ListBoxComponent, selector: "kendo-listbox", inputs: { textField: "textField", data: "data", size: "size", toolbar: "toolbar", itemDisabled: "itemDisabled" }, outputs: { selectionChange: "selectionChange", actionClick: "actionClick" }, host: { properties: { "class.k-listbox": "this.listboxClassName" } }, providers: [ListBoxSelectionService], queries: [{ propertyName: "itemTemplate", first: true, predicate: ItemTemplateDirective, descendants: true }], ngImport: i0, template: `
357
- <div class="k-listbox-actions" *ngIf="selectedTools.length > 0">
358
- <button
359
- *ngFor="let tool of selectedTools"
360
- kendoButton
361
- [size]="this.size"
362
- [icon]="tool.icon"
363
- [svgIcon]="tool.svgIcon"
364
- (click)="performAction(tool.name)"
365
- role="button"
366
- ></button>
367
- </div>
368
- <div class="k-list-scroller k-selectable">
369
- <div class="{{ listClasses }}">
370
- <div class="k-list-content">
371
- <ul class="k-list-ul">
372
- <li
373
- class="k-list-item"
374
- *ngFor="let item of data; let i = index;"
375
- kendoListBoxItemSelectable
376
- [index]="i"
377
- [class.k-disabled]="itemDisabled(item)"
378
- >
379
- <ng-template *ngIf="itemTemplate; else defaultItemTemplate"
380
- [templateContext]="{
381
- templateRef: itemTemplate.templateRef,
382
- $implicit: item
383
- }">
384
- </ng-template>
385
- <ng-template #defaultItemTemplate>
386
- <span class="k-list-item-text">{{ getText(item) }}</span>
387
- </ng-template>
388
- </li>
389
- </ul>
390
- </div>
761
+ ListBoxComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: ListBoxComponent, deps: [{ token: KeyboardNavigationService }, { token: ListBoxSelectionService }, { token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
762
+ ListBoxComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: ListBoxComponent, 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" } }, providers: [ListBoxSelectionService, KeyboardNavigationService], queries: [{ propertyName: "itemTemplate", first: true, predicate: ItemTemplateDirective, descendants: true }], viewQueries: [{ propertyName: "listboxElement", first: true, predicate: ["listbox"], descendants: true }, { propertyName: "toolbarElement", first: true, predicate: ["toolbar"], descendants: true }, { propertyName: "tools", predicate: ["tools"], descendants: true }], ngImport: i0, template: `
763
+ <div
764
+ #toolbar
765
+ class="k-listbox-actions"
766
+ *ngIf="selectedTools.length > 0"
767
+ role="toolbar"
768
+ [attr.aria-label]="listboxToolbarLabel"
769
+ >
770
+ <button
771
+ #tools
772
+ *ngFor="let tool of selectedTools; let i = index;"
773
+ kendoButton
774
+ [attr.tabindex]="i === 0 ? '0' : '-1'"
775
+ [size]="this.size"
776
+ [icon]="tool.icon"
777
+ [svgIcon]="tool.svgIcon"
778
+ [attr.title]="tool.label"
779
+ (click)="performAction(tool.name)"
780
+ role="button"
781
+ ></button>
782
+ </div>
783
+ <div class="k-list-scroller k-selectable">
784
+ <div class="{{ listClasses }}">
785
+ <div class="k-list-content">
786
+ <ul
787
+ #listbox
788
+ class="k-list-ul"
789
+ role="listbox"
790
+ [attr.aria-label]="listboxLabel"
791
+ [attr.aria-multiselectable]="false"
792
+ >
793
+ <li
794
+ *ngFor="let item of data; let i = index;"
795
+ kendoListBoxItemSelectable
796
+ class="k-list-item"
797
+ [attr.tabindex]="i === 0 ? '0' : '-1'"
798
+ role="option"
799
+ [attr.aria-selected]="selectedIndex === i"
800
+ [index]="i"
801
+ [class.k-disabled]="itemDisabled(item)"
802
+ >
803
+ <ng-template *ngIf="itemTemplate; else defaultItemTemplate"
804
+ [templateContext]="{
805
+ templateRef: itemTemplate.templateRef,
806
+ $implicit: item
807
+ }">
808
+ </ng-template>
809
+ <ng-template #defaultItemTemplate>
810
+ <span class="k-list-item-text">{{ getText(item) }}</span>
811
+ </ng-template>
812
+ </li>
813
+ </ul>
391
814
  </div>
392
815
  </div>
393
- `, isInline: true, components: [{ type: i2.Button, selector: "button[kendoButton], span[kendoButton], kendo-button", inputs: ["toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "role", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }], directives: [{ type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: ItemSelectableDirective, selector: "[kendoListBoxItemSelectable]", inputs: ["index"] }, { type: i2.TemplateContextDirective, selector: "[templateContext]", inputs: ["templateContext"] }] });
816
+ </div>
817
+ `, isInline: true, components: [{ type: i3.Button, selector: "button[kendoButton], span[kendoButton], kendo-button", inputs: ["toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "role", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }], directives: [{ type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: ItemSelectableDirective, selector: "[kendoListBoxItemSelectable]", inputs: ["index"] }, { type: i3.TemplateContextDirective, selector: "[templateContext]", inputs: ["templateContext"] }] });
394
818
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: ListBoxComponent, decorators: [{
395
819
  type: Component,
396
820
  args: [{
397
821
  selector: 'kendo-listbox',
398
- providers: [ListBoxSelectionService],
822
+ providers: [ListBoxSelectionService, KeyboardNavigationService],
399
823
  template: `
400
- <div class="k-listbox-actions" *ngIf="selectedTools.length > 0">
401
- <button
402
- *ngFor="let tool of selectedTools"
403
- kendoButton
404
- [size]="this.size"
405
- [icon]="tool.icon"
406
- [svgIcon]="tool.svgIcon"
407
- (click)="performAction(tool.name)"
408
- role="button"
409
- ></button>
410
- </div>
411
- <div class="k-list-scroller k-selectable">
412
- <div class="{{ listClasses }}">
413
- <div class="k-list-content">
414
- <ul class="k-list-ul">
415
- <li
416
- class="k-list-item"
417
- *ngFor="let item of data; let i = index;"
418
- kendoListBoxItemSelectable
419
- [index]="i"
420
- [class.k-disabled]="itemDisabled(item)"
421
- >
422
- <ng-template *ngIf="itemTemplate; else defaultItemTemplate"
423
- [templateContext]="{
424
- templateRef: itemTemplate.templateRef,
425
- $implicit: item
426
- }">
427
- </ng-template>
428
- <ng-template #defaultItemTemplate>
429
- <span class="k-list-item-text">{{ getText(item) }}</span>
430
- </ng-template>
431
- </li>
432
- </ul>
433
- </div>
824
+ <div
825
+ #toolbar
826
+ class="k-listbox-actions"
827
+ *ngIf="selectedTools.length > 0"
828
+ role="toolbar"
829
+ [attr.aria-label]="listboxToolbarLabel"
830
+ >
831
+ <button
832
+ #tools
833
+ *ngFor="let tool of selectedTools; let i = index;"
834
+ kendoButton
835
+ [attr.tabindex]="i === 0 ? '0' : '-1'"
836
+ [size]="this.size"
837
+ [icon]="tool.icon"
838
+ [svgIcon]="tool.svgIcon"
839
+ [attr.title]="tool.label"
840
+ (click)="performAction(tool.name)"
841
+ role="button"
842
+ ></button>
843
+ </div>
844
+ <div class="k-list-scroller k-selectable">
845
+ <div class="{{ listClasses }}">
846
+ <div class="k-list-content">
847
+ <ul
848
+ #listbox
849
+ class="k-list-ul"
850
+ role="listbox"
851
+ [attr.aria-label]="listboxLabel"
852
+ [attr.aria-multiselectable]="false"
853
+ >
854
+ <li
855
+ *ngFor="let item of data; let i = index;"
856
+ kendoListBoxItemSelectable
857
+ class="k-list-item"
858
+ [attr.tabindex]="i === 0 ? '0' : '-1'"
859
+ role="option"
860
+ [attr.aria-selected]="selectedIndex === i"
861
+ [index]="i"
862
+ [class.k-disabled]="itemDisabled(item)"
863
+ >
864
+ <ng-template *ngIf="itemTemplate; else defaultItemTemplate"
865
+ [templateContext]="{
866
+ templateRef: itemTemplate.templateRef,
867
+ $implicit: item
868
+ }">
869
+ </ng-template>
870
+ <ng-template #defaultItemTemplate>
871
+ <span class="k-list-item-text">{{ getText(item) }}</span>
872
+ </ng-template>
873
+ </li>
874
+ </ul>
434
875
  </div>
435
876
  </div>
877
+ </div>
436
878
  `
437
879
  }]
438
- }], ctorParameters: function () { return [{ type: ListBoxSelectionService }, { type: i0.Renderer2 }, { type: i0.ElementRef }]; }, propDecorators: { listboxClassName: [{
880
+ }], ctorParameters: function () { return [{ type: KeyboardNavigationService }, { type: ListBoxSelectionService }, { type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i0.NgZone }]; }, propDecorators: { listboxClassName: [{
439
881
  type: HostBinding,
440
882
  args: ['class.k-listbox']
441
883
  }], itemTemplate: [{
442
884
  type: ContentChild,
443
- args: [ItemTemplateDirective, { static: false }]
885
+ args: [ItemTemplateDirective]
886
+ }], listboxElement: [{
887
+ type: ViewChild,
888
+ args: ['listbox']
889
+ }], toolbarElement: [{
890
+ type: ViewChild,
891
+ args: ['toolbar']
892
+ }], tools: [{
893
+ type: ViewChildren,
894
+ args: ['tools']
444
895
  }], textField: [{
445
896
  type: Input
446
897
  }], data: [{
@@ -449,12 +900,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
449
900
  type: Input
450
901
  }], toolbar: [{
451
902
  type: Input
903
+ }], listboxLabel: [{
904
+ type: Input
905
+ }], listboxToolbarLabel: [{
906
+ type: Input
452
907
  }], itemDisabled: [{
453
908
  type: Input
454
909
  }], selectionChange: [{
455
910
  type: Output
456
911
  }], actionClick: [{
457
912
  type: Output
913
+ }], getChildListbox: [{
914
+ type: Output
458
915
  }] } });
459
916
 
460
917
  /**
@@ -465,7 +922,11 @@ class DataBindingDirective {
465
922
  this.listbox = listbox;
466
923
  this.actionClickSub = new Subscription();
467
924
  this.selectedBoxSub = new Subscription();
925
+ this.connectedWithSub = new Subscription();
468
926
  this.selectedBox = this.listbox;
927
+ this.connectedWithSub.add(this.listbox.getChildListbox.subscribe(() => {
928
+ this.listbox.childListbox = this.connectedWith;
929
+ }));
469
930
  this.actionClickSub.add(this.listbox.actionClick.subscribe((actionName) => {
470
931
  switch (actionName) {
471
932
  case 'moveUp': {
@@ -547,7 +1008,7 @@ class DataBindingDirective {
547
1008
  const newIndex = dir === 'up' ? index - 1 : index + 1;
548
1009
  // ES6 Destructuring swap
549
1010
  [this.selectedBox.data[newIndex], this.selectedBox.data[index]] = [this.selectedBox.data[index], this.selectedBox.data[newIndex]];
550
- this.selectedBox.selectionService.select(newIndex);
1011
+ this.selectedBox.selectionService.select(newIndex, dir);
551
1012
  }
552
1013
  removeItem() {
553
1014
  const index = this.selectedBox.selectedIndex;