@progress/kendo-angular-listbox
Version:
Kendo UI for Angular ListBox
1,165 lines (1,141 loc) • 62.1 kB
JavaScript
/**-----------------------------------------------------------------------------------------
* 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