UNPKG

@progress/kendo-angular-listbox

Version:
1,165 lines (1,141 loc) 62.1 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import * as i0 from '@angular/core'; import { EventEmitter, Injectable, Directive, Input, HostBinding, HostListener, forwardRef, isDevMode, Component, ContentChild, ViewChild, ViewChildren, Output, NgModule } from '@angular/core'; import { validatePackage } from '@progress/kendo-licensing'; import { Subscription } from 'rxjs'; import { getter } from '@progress/kendo-common'; import { caretAltUpIcon, caretAltDownIcon, caretAltRightIcon, caretAltLeftIcon, caretDoubleAltRightIcon, caretDoubleAltLeftIcon, xIcon } from '@progress/kendo-svg-icons'; import { ButtonComponent } from '@progress/kendo-angular-buttons'; import { Keys, TemplateContextDirective, isChanged, ResizeBatchService } from '@progress/kendo-angular-common'; import { take } from 'rxjs/operators'; import * as i1 from '@progress/kendo-angular-l10n'; import { ComponentMessages, LocalizationService, L10N_PREFIX } from '@progress/kendo-angular-l10n'; import { NgIf, NgFor } from '@angular/common'; import { IconsService } from '@progress/kendo-angular-icons'; import { PopupService } from '@progress/kendo-angular-popup'; /** * @hidden */ const packageMetadata = { name: '@progress/kendo-angular-listbox', productName: 'Kendo UI for Angular', productCode: 'KENDOUIANGULAR', productCodes: ['KENDOUIANGULAR'], publishDate: 1749540406, version: '19.1.1', 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' }; /** * @hidden */ class ListBoxSelectionService { onSelect = new EventEmitter(); selectedIndex = null; select(index) { this.onSelect.next({ index: index, prevIndex: this.selectedIndex }); this.selectedIndex = index; } isSelected(index) { return index === this.selectedIndex; } clearSelection() { this.selectedIndex = null; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ListBoxSelectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ListBoxSelectionService }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ListBoxSelectionService, decorators: [{ type: Injectable }] }); /** * Renders the ListBox item content. To define the item template, nest an `<ng-template>` tag * with the `kendoListBoxItemTemplate` directive inside the `<kendo-listbox>` tag. The template context is * set to the current data item. * * @example * ```ts * _@Component({ * selector: 'my-app', * template: ` * <kendo-listbox [data]="listBoxItems"> * <ng-template kendoListBoxItemTemplate let-dataItem> * <span>{{ dataItem }} item</span> * </ng-template> * </kendo-listbox> * ` * }) * ``` */ class ItemTemplateDirective { templateRef; constructor(templateRef) { this.templateRef = templateRef; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ItemTemplateDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: ItemTemplateDirective, isStandalone: true, selector: "[kendoListBoxItemTemplate]", ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ItemTemplateDirective, decorators: [{ type: Directive, args: [{ selector: '[kendoListBoxItemTemplate]', standalone: true }] }], ctorParameters: function () { return [{ type: i0.TemplateRef }]; } }); /** * @hidden */ const DEFAULT_TOOLBAR_POSITION = 'right'; /** * @hidden */ const allTools = [ { name: 'moveUp', label: 'Move Up', icon: 'caret-alt-up', svgIcon: caretAltUpIcon }, { name: 'moveDown', label: 'Move Down', icon: 'caret-alt-down', svgIcon: caretAltDownIcon }, { name: 'transferTo', label: 'Transfer To', icon: 'caret-alt-right', svgIcon: caretAltRightIcon }, { name: 'transferFrom', label: 'Transfer From', icon: 'caret-alt-left', svgIcon: caretAltLeftIcon }, { name: 'transferAllTo', label: 'Transfer All To', icon: 'caret-double-alt-right', svgIcon: caretDoubleAltRightIcon }, { name: 'transferAllFrom', label: 'Transfer All From', icon: 'caret-double-alt-left', svgIcon: caretDoubleAltLeftIcon }, { name: 'remove', label: 'Remove', icon: 'x', svgIcon: xIcon } ]; /** * @hidden */ const sizeClassMap = { small: 'sm', medium: 'md', large: 'lg' }; /** * @hidden */ const actionsClasses = { left: 'k-listbox-actions-left', right: 'k-listbox-actions-right', top: 'k-listbox-actions-top', bottom: 'k-listbox-actions-bottom' }; /** * @hidden */ const isPresent = (value) => value !== null && value !== undefined; /** * @hidden */ const isObject = (value) => isPresent(value) && typeof value === 'object'; /** * @hidden */ const fieldAccessor = (dataItem, field) => { if (!isPresent(dataItem)) { return null; } if (!isPresent(field) || !isObject(dataItem)) { return dataItem; } // creates a field accessor supporting nested fields processing const valueFrom = getter(field); return valueFrom(dataItem); }; /** * @hidden */ const defaultItemDisabled = () => false; /** * @hidden */ const getTools = (names) => { return names.map(tool => allTools.find(meta => meta.name === tool)); }; /* eslint-disable @typescript-eslint/no-inferrable-types */ /* eslint-disable @typescript-eslint/no-explicit-any */ /** * @hidden */ class KeyboardNavigationService { renderer; zone; selectedListboxItemIndex = 0; focusedListboxItemIndex = 0; focusedToolIndex = 0; onDeleteEvent = new EventEmitter(); onMoveSelectedItem = new EventEmitter(); onTransferAllEvent = new EventEmitter(); onShiftSelectedItem = new EventEmitter(); onSelectionChange = new EventEmitter(); constructor(renderer, zone) { this.renderer = renderer; this.zone = zone; } onKeyDown(event, toolsRef, toolbar, childListbox, parentListbox, listboxItems) { const target = event.target; const keyCode = event.keyCode; const ctrlOrMetaKey = event.ctrlKey || event.metaKey; const parentListboxToolbar = parentListbox?.selectedTools; const tool = toolsRef.find(elem => elem.element === target); const activeToolbar = toolbar.length > 0 ? toolbar : parentListboxToolbar; if (toolsRef.length > 0 || parentListbox?.tools.toArray().length > 0) { const focusNextTool = (keyCode === Keys.ArrowDown || keyCode === Keys.ArrowRight); const focusPrevTool = (keyCode === Keys.ArrowUp || keyCode === Keys.ArrowLeft); if ((focusNextTool || focusPrevTool) && tool) { const dir = focusPrevTool ? 'up' : 'down'; this.handleToolbarArrows(toolsRef, dir); } else if (keyCode === Keys.F10) { event.preventDefault(); this.onF10Key(toolsRef); } else if (keyCode === Keys.Delete && activeToolbar.some(tool => tool.name === 'remove')) { this.onDeleteEvent.emit(this.selectedListboxItemIndex); } } const isTargetListboxItem = listboxItems.find(elem => elem.nativeElement === target); if (isTargetListboxItem) { let isTransferToolVisible; if (activeToolbar) { isTransferToolVisible = activeToolbar.some(tool => tool.name.startsWith('transfer')); } if ((keyCode === Keys.ArrowRight || keyCode === Keys.ArrowLeft) && ctrlOrMetaKey && isTransferToolVisible) { this.onArrowLeftOrRight(keyCode, parentListbox, childListbox, event, listboxItems); } else if ((keyCode === Keys.ArrowUp || keyCode === Keys.ArrowDown)) { this.onArrowUpOrDown(keyCode, ctrlOrMetaKey, event, activeToolbar, listboxItems); } else if ((event.metaKey && keyCode === Keys.Enter) || (event.ctrlKey && keyCode === Keys.Space)) { this.onSelectChange(event, listboxItems); } else if (keyCode === Keys.Space) { if (this.selectedListboxItemIndex !== this.focusedListboxItemIndex) { this.onSpaceKey(event, listboxItems); } } } } changeTabindex(previousItem, currentItem, shouldBlur = true) { if (previousItem) { this.renderer.setAttribute(previousItem, 'tabindex', '-1'); if (shouldBlur) { previousItem.blur(); } } if (currentItem) { this.renderer.setAttribute(currentItem, 'tabindex', '0'); currentItem.focus(); } } handleToolbarArrows(toolsRef, dir) { const topReached = dir === 'up' && this.focusedToolIndex <= 0; const bottomReached = dir === 'down' && this.focusedToolIndex >= toolsRef.length - 1; if (topReached || bottomReached) { return; } const offset = dir === 'up' ? -1 : 1; this.focusedToolIndex += offset; const prevItem = toolsRef[this.focusedToolIndex + (offset * -1)].element; const currentItem = toolsRef[this.focusedToolIndex].element; this.changeTabindex(prevItem, currentItem); } onSpaceKey(event, listboxItems) { event.stopImmediatePropagation(); event.preventDefault(); const previousItem = listboxItems[this.selectedListboxItemIndex]?.nativeElement; const currentItem = listboxItems[this.focusedListboxItemIndex]?.nativeElement; this.changeTabindex(previousItem, currentItem); this.onSelectionChange.emit({ index: this.focusedListboxItemIndex, prevIndex: this.selectedListboxItemIndex }); this.selectedListboxItemIndex = this.focusedListboxItemIndex; } onArrowUpOrDown(keyCode, ctrlOrMetaKey, event, activeToolbar, listboxItems) { event.preventDefault(); const dir = keyCode === Keys.ArrowUp ? 'moveUp' : 'moveDown'; if (ctrlOrMetaKey) { let isMoveToolVisible; if (activeToolbar) { isMoveToolVisible = activeToolbar.some(tool => tool.name.startsWith('move')); } if (event.shiftKey && isMoveToolVisible) { this.onMoveSelectedItem.emit(dir); return; } this.changeFocusedItem(dir, listboxItems); return; } dir === 'moveUp' ? this.onArrowUp(listboxItems) : this.onArrowDown(listboxItems); this.onSelectionChange.emit({ index: this.selectedListboxItemIndex, prevIndex: this.focusedListboxItemIndex }); this.focusedListboxItemIndex = this.selectedListboxItemIndex; } onArrowLeftOrRight(keyCode, parentListbox, childListbox, event, listboxItems) { event.preventDefault(); if (event.shiftKey) { this.transferAllItems(keyCode, childListbox, parentListbox); return; } if (this.selectedListboxItemIndex >= 0) { this.transferItem(keyCode, childListbox, parentListbox, listboxItems); } } onSelectChange(event, listboxItems) { event.stopImmediatePropagation(); event.preventDefault(); const areIndexesEqual = this.selectedListboxItemIndex === this.focusedListboxItemIndex; const canDeselect = (this.selectedListboxItemIndex || this.selectedListboxItemIndex === 0) && areIndexesEqual; let previousItem; let currentItem; let prevIndex; if (canDeselect) { previousItem = listboxItems[this.selectedListboxItemIndex]?.nativeElement; this.selectedListboxItemIndex = null; } else { previousItem = listboxItems[this.selectedListboxItemIndex]?.nativeElement; currentItem = listboxItems[this.focusedListboxItemIndex]?.nativeElement; prevIndex = this.selectedListboxItemIndex; this.selectedListboxItemIndex = this.focusedListboxItemIndex; } this.changeTabindex(previousItem, currentItem, !!currentItem); this.onSelectionChange.emit({ index: this.selectedListboxItemIndex, prevIndex }); } onF10Key(tools) { if (this.focusedToolIndex && this.focusedToolIndex > -1) { if (this.focusedToolIndex >= tools.length) { tools[tools.length - 1].element.focus(); } else { tools[this.focusedToolIndex].element.focus(); } } else { tools[0]?.element.focus(); } } transferAllItems(keyCode, childListbox, parentListbox) { const isArrowRight = keyCode === Keys.ArrowRight; const actionToPerform = isArrowRight ? 'transferAllTo' : 'transferAllFrom'; this.onTransferAllEvent.emit(actionToPerform); const adjustTabindex = (items) => { items.forEach(item => { if (item.nativeElement.getAttribute('tabindex') === '0') { this.changeTabindex(item.nativeElement, null); } }); }; this.zone.onStable.pipe(take(1)).subscribe(() => { const childListboxNav = childListbox?.keyboardNavigationService || parentListbox?.childListbox.keyboardNavigationService; let currentItem; if (isArrowRight) { if (childListbox) { const childListBoxItems = childListbox.listboxItems.toArray(); const childListboxItemsLength = childListBoxItems.length - 1; currentItem = childListBoxItems[childListboxItemsLength].nativeElement; childListboxNav.focusedListboxItemIndex = childListboxItemsLength; childListboxNav.selectedListboxItemIndex = childListboxItemsLength; this.focusedListboxItemIndex = 0; this.selectedListboxItemIndex = 0; adjustTabindex(childListBoxItems); } } else { if (parentListbox) { const parentListboxNav = parentListbox.keyboardNavigationService; const parentListBoxItems = parentListbox.listboxItems.toArray(); const parentListboxItemsLength = parentListBoxItems.length - 1; currentItem = parentListBoxItems[parentListboxItemsLength].nativeElement; parentListboxNav.focusedListboxItemIndex = parentListboxItemsLength; parentListboxNav.selectedListboxItemIndex = parentListboxItemsLength; childListboxNav.focusedListboxItemIndex = 0; childListboxNav.selectedListboxItemIndex = 0; adjustTabindex(parentListBoxItems); } } this.changeTabindex(null, currentItem); }); } transferItem(keyCode, childListbox, parentListbox, listboxItems) { const isArrowRight = keyCode === Keys.ArrowRight; const actionToPerform = isArrowRight ? 'transferTo' : 'transferFrom'; this.onShiftSelectedItem.emit(actionToPerform); const adjustTabindex = (items, firstItem, currentItem) => { items.forEach(item => { if (item.nativeElement.getAttribute('tabindex') === '0') { this.changeTabindex(item.nativeElement, firstItem); } }); this.changeTabindex(null, currentItem); }; this.zone.onStable.pipe(take(1)).subscribe(() => { if (isArrowRight) { if (childListbox) { const childListBoxItems = childListbox.listboxItems.toArray(); const childListboxNav = childListbox.keyboardNavigationService; const childListboxItemsLength = childListbox.listboxItems.length - 1; const parentListboxFirstItem = listboxItems[0].nativeElement; const currentItem = childListBoxItems[childListboxItemsLength].nativeElement; childListboxNav.focusedListboxItemIndex = childListboxItemsLength; childListboxNav.selectedListboxItemIndex = childListboxItemsLength; this.focusedListboxItemIndex = 0; this.selectedListboxItemIndex = 0; adjustTabindex(childListBoxItems, parentListboxFirstItem, currentItem); } } else { if (parentListbox) { const parentListBoxItems = parentListbox.listboxItems.toArray(); const childListboxNav = parentListbox.childListbox.keyboardNavigationService; const parentListboxNav = parentListbox.keyboardNavigationService; const parentListboxItemsLength = parentListbox.listboxItems.length - 1; const childListboxFirstItem = listboxItems[0].nativeElement; const currentItem = parentListBoxItems[parentListboxItemsLength].nativeElement; parentListboxNav.focusedListboxItemIndex = parentListboxItemsLength; parentListboxNav.selectedListboxItemIndex = parentListboxItemsLength; childListboxNav.focusedListboxItemIndex = 0; childListboxNav.selectedListboxItemIndex = 0; adjustTabindex(parentListBoxItems, childListboxFirstItem, currentItem); } } }); } changeFocusedItem(dir, listboxItems) { listboxItems[this.focusedListboxItemIndex].nativeElement.blur(); if (this.focusedListboxItemIndex > 0 && dir === 'moveUp') { this.focusedListboxItemIndex -= 1; } else if (this.focusedListboxItemIndex < listboxItems.length - 1 && dir === 'moveDown') { this.focusedListboxItemIndex += 1; } listboxItems[this.focusedListboxItemIndex].nativeElement.focus(); } onArrowDown(listboxItems) { if (this.selectedListboxItemIndex < listboxItems.length - 1) { const offset = this.selectedListboxItemIndex ? this.selectedListboxItemIndex : this.focusedListboxItemIndex; this.selectedListboxItemIndex = offset + 1; const previousItem = listboxItems[this.selectedListboxItemIndex - 1]?.nativeElement; const currentItem = listboxItems[this.selectedListboxItemIndex]?.nativeElement; this.changeTabindex(previousItem, currentItem); } } onArrowUp(listboxItems) { if (this.selectedListboxItemIndex > 0 || this.focusedListboxItemIndex > 0) { const offset = this.selectedListboxItemIndex ? this.selectedListboxItemIndex : this.focusedListboxItemIndex; this.selectedListboxItemIndex = offset - 1; const previousItem = listboxItems[this.selectedListboxItemIndex + 1]?.nativeElement; const currentItem = listboxItems[this.selectedListboxItemIndex]?.nativeElement; this.changeTabindex(previousItem, currentItem); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: KeyboardNavigationService, deps: [{ token: i0.Renderer2 }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: KeyboardNavigationService }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: KeyboardNavigationService, decorators: [{ type: Injectable }], ctorParameters: function () { return [{ type: i0.Renderer2 }, { type: i0.NgZone }]; } }); /** * @hidden */ class ItemSelectableDirective { selectionService; index; constructor(selectionService) { this.selectionService = selectionService; } get selectedClassName() { return this.selectionService.isSelected(this.index); } onClick(event) { event.stopPropagation(); this.selectionService.select(this.index); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ItemSelectableDirective, deps: [{ token: ListBoxSelectionService }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: ItemSelectableDirective, isStandalone: true, selector: "[kendoListBoxItemSelectable]", inputs: { index: "index" }, host: { listeners: { "mousedown": "onClick($event)" }, properties: { "class.k-selected": "this.selectedClassName" } }, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ItemSelectableDirective, decorators: [{ type: Directive, args: [{ selector: '[kendoListBoxItemSelectable]', standalone: true }] }], ctorParameters: function () { return [{ type: ListBoxSelectionService }]; }, propDecorators: { index: [{ type: Input }], selectedClassName: [{ type: HostBinding, args: ['class.k-selected'] }], onClick: [{ type: HostListener, args: ['mousedown', ['$event']] }] } }); /** * @hidden */ class Messages extends ComponentMessages { /** * The text of the `Move Up` button title. */ moveUp; /** * The text of the `Move Down` button title. */ moveDown; /** * The text of the `Remove` button tittle. */ remove; /** * The text of the `Transfer To` button title. */ transferTo; /** * The text of the `Transfer From` button title. */ transferFrom; /** * The text of the `Transfer All To` button title. */ transferAllTo; /** * The text of the `Transfer All From` button title. */ transferAllFrom; /** * The text displayed when there are no items. */ noDataText; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: Messages, deps: null, target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: Messages, inputs: { moveUp: "moveUp", moveDown: "moveDown", remove: "remove", transferTo: "transferTo", transferFrom: "transferFrom", transferAllTo: "transferAllTo", transferAllFrom: "transferAllFrom", noDataText: "noDataText" }, usesInheritance: true, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: Messages, decorators: [{ type: Directive }], propDecorators: { moveUp: [{ type: Input }], moveDown: [{ type: Input }], remove: [{ type: Input }], transferTo: [{ type: Input }], transferFrom: [{ type: Input }], transferAllTo: [{ type: Input }], transferAllFrom: [{ type: Input }], noDataText: [{ type: Input }] } }); /** * @hidden */ class LocalizedMessagesDirective extends Messages { service; constructor(service) { super(); this.service = service; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: LocalizedMessagesDirective, deps: [{ token: i1.LocalizationService }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: LocalizedMessagesDirective, isStandalone: true, selector: "[kendoListBoxLocalizedMessages]", providers: [ { provide: Messages, useExisting: forwardRef(() => LocalizedMessagesDirective) } ], usesInheritance: true, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: LocalizedMessagesDirective, decorators: [{ type: Directive, args: [{ providers: [ { provide: Messages, useExisting: forwardRef(() => LocalizedMessagesDirective) } ], selector: '[kendoListBoxLocalizedMessages]', standalone: true }] }], ctorParameters: function () { return [{ type: i1.LocalizationService }]; } }); /* eslint-disable @typescript-eslint/no-inferrable-types */ /* eslint-disable @typescript-eslint/no-explicit-any */ const DEFAULT_SIZE = 'medium'; let idx = 0; /** * Represents the [Kendo UI ListBox component for Angular]({% slug overview_listbox %}). */ class ListBoxComponent { keyboardNavigationService; selectionService; hostElement; renderer; zone; localization; changeDetector; /** * @hidden */ listboxClassName = true; /** * @hidden */ direction; /** * @hidden */ itemTemplate; /** * @hidden */ listboxElement; /** * @hidden */ listboxItems; /** * @hidden */ toolbarElement; /** * @hidden */ tools; /** * The fields of the data item that provide the text content of the nodes. */ textField; /** * The data which will be displayed by the ListBox. */ data = []; /** * Sets the size of the component. * * The possible values are: * - `'small'` * - `'medium'` (default) * - `'large'` */ set size(size) { const newSize = size ? size : DEFAULT_SIZE; this.renderer.removeClass(this.hostElement.nativeElement, `k-listbox-${sizeClassMap[this.size]}`); this.setSizingClass(newSize); this._size = size; } get size() { return this._size; } /** * Sets whether a toolbar should be displayed with the ListBox, as well as what tools and position should be used. */ set toolbar(config) { let position = DEFAULT_TOOLBAR_POSITION; if (typeof config === 'boolean') { this.selectedTools = config ? allTools : []; } else { this.selectedTools = config.tools ? getTools(config.tools) : allTools; if (config.position) { position = config.position; } } this.setToolbarClass(position); } /** * The value of the aria-label attribute of the Listbox element. */ listboxLabel = 'Listbox'; /** * The value of the aria-label attribute of the Listbox toolbar element. */ listboxToolbarLabel = 'Toolbar'; /** * A function which determines if a specific item is disabled. */ itemDisabled = defaultItemDisabled; /** * Fires when the user selects a different ListBox item. Also fires when a node is moved, since that also changes its index. */ selectionChange = new EventEmitter(); /** * Fires when the user clicks a ListBox item. */ actionClick = new EventEmitter(); /** * @hidden */ getChildListbox = new EventEmitter(); /** * @hidden */ get listClasses() { return `k-list k-list-${sizeClassMap[this.size]}`; } /** * @hidden */ messageFor(key) { return this.localization.get(key); } /** * @hidden */ selectedTools = allTools; /** * @hidden */ listboxId; /** * @hidden */ toolbarId; /** * @hidden */ childListbox; /** * @hidden */ parentListbox; /** * @hidden */ caretAltLeftIcon = caretAltLeftIcon; /** * @hidden */ caretAltRightIcon = caretAltRightIcon; localizationSubscription; _size = DEFAULT_SIZE; subs = new Subscription(); shouldFireFocusIn = true; constructor(keyboardNavigationService, selectionService, hostElement, renderer, zone, localization, changeDetector) { this.keyboardNavigationService = keyboardNavigationService; this.selectionService = selectionService; this.hostElement = hostElement; this.renderer = renderer; this.zone = zone; this.localization = localization; this.changeDetector = changeDetector; validatePackage(packageMetadata); this.setToolbarClass(DEFAULT_TOOLBAR_POSITION); this.setSizingClass(this.size); this.direction = localization.rtl ? 'rtl' : 'ltr'; } ngOnInit() { // This event emitter gives us the connectedWith value from the DataBinding directive this.getChildListbox.emit(); if (this.childListbox) { // This allows us to know to which parent Listbox the child Listbox is connected to this.childListbox.parentListbox = this; } if (this.selectedIndex) { this.keyboardNavigationService.focusedToolIndex = this.selectedIndex; } this.localizationSubscription = this.localization.changes.subscribe(({ rtl }) => { this.direction = rtl ? 'rtl' : 'ltr'; }); this.subs.add(this.localizationSubscription); } ngAfterViewInit() { const toolsRef = this.tools.toArray(); const hostEl = this.hostElement.nativeElement; const navService = this.keyboardNavigationService; this.setIds(); this.initSubscriptions(navService, hostEl, toolsRef); } ngOnDestroy() { this.subs.unsubscribe(); } /** * @hidden */ performAction(actionName) { const isActionTransferFrom = actionName === 'transferFrom' || actionName === 'transferAllFrom'; const isListboxChild = this.parentListbox && !this.childListbox; const isListboxParentAndChild = !!(this.parentListbox && this.childListbox); const isListboxParent = !!(this.childListbox || (!this.childListbox && !this.parentListbox)); if (isListboxChild || (isListboxParentAndChild && isActionTransferFrom)) { this.parentListbox.actionClick.next(actionName); } else if (isListboxParent || (isListboxParentAndChild && !isActionTransferFrom)) { this.actionClick.next(actionName); } const toolsRef = this.tools.toArray() || this.parentListbox.tools.toArray(); const focusedToolIndex = toolsRef.findIndex(elem => elem.nativeElement === document.activeElement); if ((this.selectedTools.length > 0 || this.parentListbox.selectedTools.length > 0) && focusedToolIndex > -1) { const navService = this.keyboardNavigationService || this.parentListbox.keyboardNavigationService; const selectedTools = this.selectedTools || this.parentListbox.selectedTools; const prevTool = toolsRef[navService.focusedToolIndex]?.element; navService.focusedToolIndex = selectedTools.findIndex(tool => tool.name === actionName); const currentTool = toolsRef[navService.focusedToolIndex]?.element; navService.changeTabindex(prevTool, currentTool); } } /** * Programmatically selects a ListBox node. */ selectItem(index) { this.selectionService.selectedIndex = index; } /** * Programmatically clears the ListBox selection. */ clearSelection() { this.selectionService.clearSelection(); } /** * The index of the currently selected item in the ListBox. */ get selectedIndex() { return this.selectionService.selectedIndex; } /** * @hidden */ get getListboxId() { const id = ++idx; const listboxId = `k-listbox-${id}`; return listboxId; } /** * @hidden */ getText(dataItem) { if (typeof dataItem !== 'string' && !this.textField && isDevMode()) { throw new Error('Missing textField input. When passing an array of objects as data, please set the textField input of the ListBox accordingly.'); } return fieldAccessor(dataItem, this.textField); } /** * @hidden */ toolIcon(icon) { return this.direction === 'ltr' ? icon : icon === 'caret-alt-left' ? 'caret-alt-right' : icon === 'caret-alt-right' ? 'caret-alt-left' : icon; } /** * @hidden */ toolSVGIcon(icon) { return this.direction === 'ltr' ? icon : icon === this.caretAltLeftIcon ? this.caretAltRightIcon : icon === this.caretAltRightIcon ? this.caretAltLeftIcon : icon; } onClickEvent(prevIndex, index) { this.shouldFireFocusIn = false; this.selectionChange.next({ index, prevIndex: this.keyboardNavigationService.selectedListboxItemIndex }); this.keyboardNavigationService.selectedListboxItemIndex = index; this.keyboardNavigationService.focusedListboxItemIndex = index; this.zone.onStable.pipe(take(1)).subscribe(() => { const listboxItems = this.listboxItems.toArray(); const previousItem = prevIndex ? listboxItems[prevIndex].nativeElement : listboxItems[0].nativeElement; const currentItem = listboxItems[index].nativeElement; this.keyboardNavigationService.changeTabindex(previousItem, currentItem); }); this.zone.onStable.pipe(take(1)).subscribe(() => { this.shouldFireFocusIn = true; }); } initSubscriptions(navService, hostEl, toolsRef) { this.subs.add(navService.onShiftSelectedItem.subscribe((actionToPerform) => this.performAction(actionToPerform))); this.subs.add(navService.onTransferAllEvent.subscribe((actionToPerform) => this.performAction(actionToPerform))); this.subs.add(this.selectionService.onSelect.subscribe((e) => this.onClickEvent(e.prevIndex, e.index))); this.subs.add(navService.onDeleteEvent.subscribe((index) => this.onDeleteEvent(index, navService))); this.subs.add(navService.onMoveSelectedItem.subscribe((dir) => this.performAction(dir))); if (this.listboxElement) { this.subs.add(this.renderer.listen(this.listboxElement.nativeElement, 'focusin', (event) => this.onFocusIn(event))); } this.subs.add(this.renderer.listen(hostEl, 'keydown', (event) => navService.onKeyDown(event, toolsRef, this.selectedTools, this.childListbox, this.parentListbox, this.listboxItems.toArray()))); this.subs.add(navService.onSelectionChange.subscribe((indexes) => { const { prevIndex, index } = indexes; this.selectionService.selectedIndex = index; this.selectionChange.next({ index, prevIndex }); this.changeDetector.markForCheck(); })); } onFocusIn(event) { const navService = this.keyboardNavigationService; if (navService.focusedListboxItemIndex === navService.selectedListboxItemIndex && this.shouldFireFocusIn) { const items = this.listboxItems.toArray(); const index = items.findIndex(elem => elem.nativeElement === event.target); if (index === -1) { return; } this.selectionService.selectedIndex = index; this.selectionChange.next({ index, prevIndex: null }); const previousItem = items[navService.selectedListboxItemIndex]?.nativeElement; const currentItem = items[index]?.nativeElement; this.renderer.setAttribute(previousItem, 'tabindex', '-1'); this.renderer.setAttribute(currentItem, 'tabindex', '0'); } } setIds() { if (!this.listboxElement) { return; } const listbox = this.listboxElement.nativeElement; this.listboxId = this.getListboxId; this.renderer.setAttribute(listbox, 'id', this.listboxId); if (this.selectedTools.length > 0 || this.parentListbox?.selectedTools.length > 0) { const toolbar = this.toolbarElement?.nativeElement; const parentToolbar = this.parentListbox?.toolbarElement?.nativeElement; if (this.parentListbox && this.childListbox) { this.zone.onStable.pipe(take(1)).subscribe(() => { this.toolbarId = `${this.parentListbox.listboxId} ${this.listboxId} ${this.childListbox.listboxId}`; this.renderer.setAttribute(toolbar, 'aria-controls', this.toolbarId); }); } else if (this.childListbox && !this.parentListbox) { this.zone.onStable.pipe(take(1)).subscribe(() => { this.toolbarId = this.toolbarId = `${this.listboxId} ${this.childListbox.listboxId}`; this.renderer.setAttribute(toolbar, 'aria-controls', this.toolbarId); }); } else if (this.parentListbox && this.selectedTools.length > 0) { this.toolbarId = `${this.parentListbox.listboxId} ${this.listboxId}`; this.parentListbox.toolbarId = this.toolbarId = `${this.parentListbox.listboxId} ${this.listboxId}`; this.renderer.setAttribute(toolbar, 'aria-controls', this.toolbarId); parentToolbar && this.renderer.setAttribute(parentToolbar, 'aria-controls', this.parentListbox.toolbarId); } else if (!this.parentListbox && !this.childListbox) { this.toolbarId = this.listboxId; this.renderer.setAttribute(toolbar, 'aria-controls', this.toolbarId); } } } onDeleteEvent(index, navService) { this.selectionService.selectedIndex = index; this.performAction('remove'); const listboxItems = this.listboxItems.toArray(); const setIndex = index + 1 === listboxItems.length ? { index: index - 1, tabindex: index - 1 } : { index, tabindex: index + 1 }; navService.changeTabindex(null, listboxItems[setIndex['tabindex']]?.nativeElement); this.selectionChange.next({ index: setIndex['index'], prevIndex: null }); navService.selectedListboxItemIndex = setIndex['index']; navService.focusedListboxItemIndex = setIndex['index']; navService.focusedListboxItem = setIndex['index']; this.selectionService.selectedIndex = setIndex['index']; } setToolbarClass(pos) { Object.keys(actionsClasses).forEach((className) => { if (pos === className) { this.renderer.addClass(this.hostElement.nativeElement, actionsClasses[className]); } else { this.renderer.removeClass(this.hostElement.nativeElement, actionsClasses[className]); } }); } setSizingClass(size) { this.renderer.addClass(this.hostElement.nativeElement, `k-listbox-${sizeClassMap[size]}`); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", 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 }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", 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: [ ListBoxSelectionService, KeyboardNavigationService, LocalizationService, { provide: L10N_PREFIX, useValue: 'kendo.listbox' }, ], 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: "listboxItems", predicate: ["listboxItems"], descendants: true }, { propertyName: "tools", predicate: ["tools"], descendants: true }], ngImport: i0, template: ` <ng-container kendoListBoxLocalizedMessages i18n-moveUp="kendo.listbox.moveUp|The title of the Move Up button" moveUp="Move Up" i18n-moveDown="kendo.listbox.moveDown|The title of the Move Down button" moveDown="Move Down" i18n-transferTo="kendo.listbox.transferTo|The title of the Transfer To button" transferTo="Transfer To" i18n-transferAllTo="kendo.listbox.transferAllTo|The title of the Transfer All To button" transferAllTo="Transfer All To" i18n-transferFrom="kendo.listbox.transferFrom|The title of the Transfer From button" transferFrom="Transfer From" i18n-transferAllFrom="kendo.listbox.transferAllFrom|The title of the Transfer All From button" transferAllFrom="Transfer All From" i18n-remove="kendo.listbox.remove|The title of the Remove button" remove="Remove" i18n-noDataText="kendo.listbox.noDataText|The text displayed when there are no items" noDataText="No data found." > </ng-container> <div #toolbar class="k-listbox-actions" *ngIf="selectedTools.length > 0" role="toolbar" [attr.aria-label]="listboxToolbarLabel" > <button #tools *ngFor="let tool of selectedTools; let i = index" kendoButton [attr.tabindex]="i === 0 ? '0' : '-1'" [size]="this.size" [icon]="toolIcon(tool.icon)" [svgIcon]="toolSVGIcon(tool.svgIcon)" [attr.title]="messageFor(tool.name)" (click)="performAction(tool.name)" role="button" type="button" ></button> </div> <div class="k-list-scroller k-selectable"> <div class="{{ listClasses }}"> <div *ngIf="data.length > 0" class="k-list-content" > <ul #listbox class="k-list-ul" role="listbox" [attr.aria-label]="listboxLabel" [attr.aria-multiselectable]="false" > <li #listboxItems *ngFor="let item of data; let i = index" kendoListBoxItemSelectable class="k-list-item" [attr.tabindex]="i === 0 ? '0' : '-1'" role="option" [attr.aria-selected]="selectedIndex === i" [index]="i" [class.k-disabled]="itemDisabled(item)" > <ng-template *ngIf="itemTemplate; else defaultItemTemplate" [templateContext]="{ templateRef: itemTemplate.templateRef, $implicit: item }" > </ng-template> <ng-template #defaultItemTemplate> <span class="k-list-item-text">{{ getText(item) }}</span> </ng-template> </li> </ul> </div> <span *ngIf="data.length === 0" class="k-nodata" >{{ messageFor('noDataText') }}</span> </div> </div> `, isInline: true, dependencies: [{ kind: "directive", type: LocalizedMessagesDirective, selector: "[kendoListBoxLocalizedMessages]" }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "directive", type: ItemSelectableDirective, selector: "[kendoListBoxItemSelectable]", inputs: ["index"] }, { kind: "directive", type: TemplateContextDirective, selector: "[templateContext]", inputs: ["templateContext"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ListBoxComponent, decorators: [{ type: Component, args: [{ selector: 'kendo-listbox', providers: [ ListBoxSelectionService, KeyboardNavigationService, LocalizationService, { provide: L10N_PREFIX, useValue: 'kendo.listbox' }, ], template: ` <ng-container kendoListBoxLocalizedMessages i18n-moveUp="kendo.listbox.moveUp|The title of the Move Up button" moveUp="Move Up" i18n-moveDown="kendo.listbox.moveDown|The title of the Move Down button" moveDown="Move Down" i18n-transferTo="kendo.listbox.transferTo|The title of the Transfer To button" transferTo="Transfer To" i18n-transferAllTo="kendo.listbox.transferAllTo|The title of the Transfer All To button" transferAllTo="Transfer All To" i18n-transferFrom="kendo.listbox.transferFrom|The title of the Transfer From button" transferFrom="Transfer From" i18n-transferAllFrom="kendo.listbox.transferAllFrom|The title of the Transfer All From button" transferAllFrom="Transfer All From" i18n-remove="kendo.listbox.remove|The title of the Remove button" remove="Remove" i18n-noDataText="kendo.listbox.noDataText|The text displayed when there are no items" noDataText="No data found." > </ng-container> <div #toolbar class="k-listbox-actions" *ngIf="selectedTools.length > 0" role="toolbar" [attr.aria-label]="listboxToolbarLabel" > <button #tools *ngFor="let tool of selectedTools; let i = index" kendoButton [attr.tabindex]="i === 0 ? '0' : '-1'" [size]="this.size" [icon]="toolIcon(tool.icon)" [svgIcon]="toolSVGIcon(tool.svgIcon)" [attr.title]="messageFor(tool.name)" (click)="performAction(tool.name)" role="button" type="button" ></button> </div> <div class="k-list-scroller k-selectable"> <div class="{{ listClasses }}"> <div *ngIf="data.length > 0" class="k-list-content" > <ul #listbox class="k-list-ul" role="listbox" [attr.aria-label]="listboxLabel" [attr.aria-multiselectable]="false" > <li #listboxItems *ngFor="let item of data; let i = index" kendoListBoxItemSelectable class="k-list-item" [attr.tabindex]="i === 0 ? '0' : '-1'" role="option" [attr.aria-s