UNPKG

primeng

Version:

PrimeNG is an open source UI library for Angular featuring a rich set of 80+ components, a theme designer, various theme alternatives such as Material, Bootstrap, Tailwind, premium templates and professional support. In addition, it integrates with PrimeB

1,317 lines (1,312 loc) 110 kB
import * as i2 from '@angular/cdk/drag-drop'; import { transferArrayItem, moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop'; import * as i1 from '@angular/common'; import { isPlatformBrowser, CommonModule } from '@angular/common'; import * as i0 from '@angular/core'; import { Injectable, EventEmitter, inject, booleanAttribute, numberAttribute, ContentChildren, ContentChild, ViewChild, Output, Input, ViewEncapsulation, ChangeDetectionStrategy, Component, NgModule } from '@angular/core'; import * as i3 from '@angular/forms'; import { FormsModule } from '@angular/forms'; import { uuid, find, scrollInView, findIndexInList, findSingle, setAttribute, isEmpty } from '@primeuix/utils'; import { FilterService, SharedModule, PrimeTemplate } from 'primeng/api'; import { BaseComponent } from 'primeng/basecomponent'; import { ButtonDirective } from 'primeng/button'; import { AngleDoubleDownIcon, AngleDoubleLeftIcon, AngleDoubleRightIcon, AngleDoubleUpIcon, AngleDownIcon, AngleLeftIcon, AngleRightIcon, AngleUpIcon } from 'primeng/icons'; import { Listbox } from 'primeng/listbox'; import { Ripple } from 'primeng/ripple'; import { BaseStyle } from 'primeng/base'; const theme = ({ dt }) => ` .p-picklist { display: flex; gap: ${dt('picklist.gap')}; } .p-picklist-controls { display: flex; flex-direction: column; justify-content: center; gap: ${dt('picklist.controls.gap')}; } .p-picklist-list-container { flex: 1 1 50%; } .p-picklist .p-listbox { height: 100%; } `; const classes = { root: 'p-picklist p-component', sourceControls: 'p-picklist-controls p-picklist-source-controls', sourceListContainer: 'p-picklist-list-container p-picklist-source-list-container', transferControls: 'p-picklist-controls p-picklist-transfer-controls', targetListContainer: 'p-picklist-list-container p-picklist-target-list-container', targetControls: 'p-picklist-controls p-picklist-target-controls' }; class PickListStyle extends BaseStyle { name = 'picklist'; theme = theme; classes = classes; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: PickListStyle, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: PickListStyle }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: PickListStyle, decorators: [{ type: Injectable }] }); /** * * PickList is used to reorder items between different lists. * * [Live Demo](https://www.primeng.org/picklist) * * @module pickliststyle * */ var PickListClasses; (function (PickListClasses) { /** * Class name of the root element */ PickListClasses["root"] = "p-picklist"; /** * Class name of the source controls element */ PickListClasses["sourceControls"] = "p-picklist-source-controls"; /** * Class name of the source list container element */ PickListClasses["sourceListContainer"] = "p-picklist-source-list-container"; /** * Class name of the transfer controls element */ PickListClasses["transferControls"] = "p-picklist-transfer-controls"; /** * Class name of the target list container element */ PickListClasses["targetListContainer"] = "p-picklist-target-list-container"; /** * Class name of the target controls element */ PickListClasses["targetControls"] = "p-picklist-target-controls"; })(PickListClasses || (PickListClasses = {})); /** * PickList is used to reorder items between different lists. * @group Components */ class PickList extends BaseComponent { /** * An array of objects for the source list. * @group Props */ source; /** * An array of objects for the target list. * @group Props */ target; /** * Text for the source list caption * @group Props */ sourceHeader; /** * Index of the element in tabbing order. * @group Props */ tabindex = 0; /** * Defines a string that labels the move to right button for accessibility. * @group Props */ rightButtonAriaLabel; /** * Defines a string that labels the move to left button for accessibility. * @group Props */ leftButtonAriaLabel; /** * Defines a string that labels the move to all right button for accessibility. * @group Props */ allRightButtonAriaLabel; /** * Defines a string that labels the move to all left button for accessibility. * @group Props */ allLeftButtonAriaLabel; /** * Defines a string that labels the move to up button for accessibility. * @group Props */ upButtonAriaLabel; /** * Defines a string that labels the move to down button for accessibility. * @group Props */ downButtonAriaLabel; /** * Defines a string that labels the move to top button for accessibility. * @group Props */ topButtonAriaLabel; /** * Defines a string that labels the move to bottom button for accessibility. * @group Props */ bottomButtonAriaLabel; /** * Text for the target list caption * @group Props */ targetHeader; /** * When enabled orderlist adjusts its controls based on screen size. * @group Props */ responsive; /** * When specified displays an input field to filter the items on keyup and decides which field to search (Accepts multiple fields with a comma). * @group Props */ filterBy; /** * Locale to use in filtering. The default locale is the host environment's current locale. * @group Props */ filterLocale; /** * Function to optimize the dom operations by delegating to ngForTrackBy, default algorithm checks for object identity. Use sourceTrackBy or targetTrackBy in case different algorithms are needed per list. * @group Props */ trackBy = (index, item) => item; /** * Function to optimize the dom operations by delegating to ngForTrackBy in source list, default algorithm checks for object identity. * @group Props */ sourceTrackBy; /** * Function to optimize the dom operations by delegating to ngForTrackBy in target list, default algorithm checks for object identity. * @group Props */ targetTrackBy; /** * Whether to show filter input for source list when filterBy is enabled. * @group Props */ showSourceFilter = true; /** * Whether to show filter input for target list when filterBy is enabled. * @group Props */ showTargetFilter = true; /** * Defines how multiple items can be selected, when true metaKey needs to be pressed to select or unselect an item and when set to false selection of each item can be toggled individually. On touch enabled devices, metaKeySelection is turned off automatically. * @group Props */ metaKeySelection = false; /** * Whether to enable dragdrop based reordering. * @group Props */ dragdrop = false; /** * Inline style of the component. * @group Props */ style; /** * Style class of the component. * @group Props */ styleClass; /** * Inline style of the source list element. * @group Props */ sourceStyle; /** * Inline style of the target list element. * @group Props */ targetStyle; /** * Whether to show buttons of source list. * @group Props */ showSourceControls = true; /** * Whether to show buttons of target list. * @group Props */ showTargetControls = true; /** * Placeholder text on source filter input. * @group Props */ sourceFilterPlaceholder; /** * Placeholder text on target filter input. * @group Props */ targetFilterPlaceholder; /** * When present, it specifies that the component should be disabled. * @group Props */ disabled = false; /** * Name of the disabled field of a target option or function to determine disabled state. * @group Props */ sourceOptionDisabled; /** * Name of the disabled field of a target option or function to determine disabled state. * @group Props */ targetOptionDisabled; /** * Defines a string that labels the filter input of source list. * @group Props */ ariaSourceFilterLabel; /** * Defines a string that labels the filter input of target list. * @group Props */ ariaTargetFilterLabel; /** * Defines how the items are filtered. * @group Props */ filterMatchMode = 'contains'; /** * Whether to displays rows with alternating colors. * @group Props */ stripedRows; /** * Keeps selection on the transfer list. * @group Props */ keepSelection = false; /** * Height of the viewport, a scrollbar is defined if height of list exceeds this value. * @group Props */ scrollHeight = '14rem'; /** * Whether to focus on the first visible or selected element. * @group Props */ autoOptionFocus = true; /** * Used to pass all properties of the ButtonProps to the Button component. * @group Props */ buttonProps = { severity: 'secondary' }; /** * Used to pass all properties of the ButtonProps to the move up button inside the component. * @group Props */ moveUpButtonProps; /** * Used to pass all properties of the ButtonProps to the move top button inside the component. * @group Props */ moveTopButtonProps; /** * Used to pass all properties of the ButtonProps to the move down button inside the component. * @group Props */ moveDownButtonProps; /** * Used to pass all properties of the ButtonProps to the move bottom button inside the component. * @group Props */ moveBottomButtonProps; /** * Used to pass all properties of the ButtonProps to the move to target button inside the component. * @group Props */ moveToTargetProps; /** * Used to pass all properties of the ButtonProps to the move all to target button inside the component. * @group Props */ moveAllToTargetProps; /** * Used to pass all properties of the ButtonProps to the move to source button inside the component. * @group Props */ moveToSourceProps; /** * Used to pass all properties of the ButtonProps to the move all to source button inside the component. * @group Props */ moveAllToSourceProps; /** * Indicates the width of the screen at which the component should change its behavior. * @group Props */ get breakpoint() { return this._breakpoint; } set breakpoint(value) { if (value !== this._breakpoint) { this._breakpoint = value; if (isPlatformBrowser(this.platformId)) { this.destroyMedia(); this.initMedia(); } } } /** * Callback to invoke when items are moved from target to source. * @param {PickListMoveToSourceEvent} event - Custom move to source event. * @group Emits */ onMoveToSource = new EventEmitter(); /** * Callback to invoke when all items are moved from target to source. * @param {PickListMoveAllToSourceEvent} event - Custom move all to source event. * @group Emits */ onMoveAllToSource = new EventEmitter(); /** * Callback to invoke when all items are moved from source to target. * @param {PickListMoveAllToTargetEvent} event - Custom move all to target event. * @group Emits */ onMoveAllToTarget = new EventEmitter(); /** * Callback to invoke when items are moved from source to target. * @param {PickListMoveToTargetEvent} event - Custom move to target event. * @group Emits */ onMoveToTarget = new EventEmitter(); /** * Callback to invoke when items are reordered within source list. * @param {PickListSourceReorderEvent} event - Custom source reorder event. * @group Emits */ onSourceReorder = new EventEmitter(); /** * Callback to invoke when items are reordered within target list. * @param {PickListTargetReorderEvent} event - Custom target reorder event. * @group Emits */ onTargetReorder = new EventEmitter(); /** * Callback to invoke when items are selected within source list. * @param {PickListSourceSelectEvent} event - Custom source select event. * @group Emits */ onSourceSelect = new EventEmitter(); /** * Callback to invoke when items are selected within target list. * @param {PickListTargetSelectEvent} event - Custom target select event. * @group Emits */ onTargetSelect = new EventEmitter(); /** * Callback to invoke when the source list is filtered * @param {PickListSourceFilterEvent} event - Custom source filter event. * @group Emits */ onSourceFilter = new EventEmitter(); /** * Callback to invoke when the target list is filtered * @param {PickListTargetFilterEvent} event - Custom target filter event. * @group Emits */ onTargetFilter = new EventEmitter(); /** * Callback to invoke when the list is focused * @param {Event} event - Browser event. * @group Emits */ onFocus = new EventEmitter(); /** * Callback to invoke when the list is blurred * @param {Event} event - Browser event. * @group Emits */ onBlur = new EventEmitter(); listViewSourceChild; listViewTargetChild; sourceFilterViewChild; targetFilterViewChild; getButtonProps(direction) { switch (direction) { case 'moveup': return { ...this.buttonProps, ...this.moveUpButtonProps }; case 'movetop': return { ...this.buttonProps, ...this.moveTopButtonProps }; case 'movedown': return { ...this.buttonProps, ...this.moveDownButtonProps }; case 'movebottom': return { ...this.buttonProps, ...this.moveBottomButtonProps }; case 'movetotarget': return { ...this.buttonProps, ...this.moveToTargetProps }; case 'movealltotarget': return { ...this.buttonProps, ...this.moveAllToTargetProps }; case 'movetosource': return { ...this.buttonProps, ...this.moveToSourceProps }; case 'movealltosource': return { ...this.buttonProps, ...this.moveAllToSourceProps }; default: return this.buttonProps; } } get moveUpAriaLabel() { return this.upButtonAriaLabel ? this.upButtonAriaLabel : this.config.translation.aria ? this.config.translation.aria.moveUp : undefined; } get moveTopAriaLabel() { return this.topButtonAriaLabel ? this.topButtonAriaLabel : this.config.translation.aria ? this.config.translation.aria.moveTop : undefined; } get moveDownAriaLabel() { return this.downButtonAriaLabel ? this.downButtonAriaLabel : this.config.translation.aria ? this.config.translation.aria.moveDown : undefined; } get moveBottomAriaLabel() { return this.bottomButtonAriaLabel ? this.bottomButtonAriaLabel : this.config.translation.aria ? this.config.translation.aria.moveDown : undefined; } get moveToTargetAriaLabel() { return this.rightButtonAriaLabel ? this.rightButtonAriaLabel : this.config.translation.aria ? this.config.translation.aria.moveToTarget : undefined; } get moveAllToTargetAriaLabel() { return this.allRightButtonAriaLabel ? this.allRightButtonAriaLabel : this.config.translation.aria ? this.config.translation.aria.moveAllToTarget : undefined; } get moveToSourceAriaLabel() { return this.leftButtonAriaLabel ? this.leftButtonAriaLabel : this.config.translation.aria ? this.config.translation.aria.moveToSource : undefined; } get moveAllToSourceAriaLabel() { return this.allLeftButtonAriaLabel ? this.allLeftButtonAriaLabel : this.config.translation.aria ? this.config.translation.aria.moveAllToSource : undefined; } get idSource() { return this.id + '_source'; } get idTarget() { return this.id + '_target'; } get focusedOptionId() { return this.focusedOptionIndex !== -1 ? this.focusedOptionIndex : null; } _breakpoint = '960px'; visibleOptionsSource; visibleOptionsTarget; selectedItemsSource = []; selectedItemsTarget = []; reorderedListElement; movedUp; movedDown; itemTouched; styleElement; id = uuid('pn_id_'); filterValueSource; filterValueTarget; fromListType; sourceFilterOptions; targetFilterOptions; SOURCE_LIST = -1; TARGET_LIST = 1; window; media; viewChanged; focusedOptionIndex = -1; focusedOption; focused = { sourceList: false, targetList: false }; _componentStyle = inject(PickListStyle); mediaChangeListener; filterService = inject(FilterService); ngOnInit() { super.ngOnInit(); if (this.responsive) { this.createStyle(); this.initMedia(); } if (this.filterBy) { this.sourceFilterOptions = { filter: (value) => this.filterSource(value), reset: () => this.resetSourceFilter() }; this.targetFilterOptions = { filter: (value) => this.filterTarget(value), reset: () => this.resetTargetFilter() }; } } /** * Custom item template. * @group Templates */ itemTemplate; /** * Custom source header template. * @group Templates */ sourceHeaderTemplate; /** * Custom target header template. * @group Templates */ targetHeaderTemplate; /** * Custom source filter template. * @group Templates */ sourceFilterTemplate; /** * Custom target filter template. * @group Templates */ targetFilterTemplate; /** * Custom empty message when source is empty template. * @group Templates */ emptyMessageSourceTemplate; /** * Custom empty filter message when source is empty template. * @group Templates */ emptyFilterMessageSourceTemplate; /** * Custom empty message when target is empty template. * @group Templates */ emptyMessageTargetTemplate; /** * Custom empty filter message when target is empty template. * @group Templates */ emptyFilterMessageTargetTemplate; /** * Custom move up icon template. * @group Templates */ moveUpIconTemplate; /** * Custom move top icon template. * @group Templates */ moveTopIconTemplate; /** * Custom move down icon template. * @group Templates */ moveDownIconTemplate; /** * Custom move bottom icon template. * @group Templates */ moveBottomIconTemplate; /** * Custom move to target icon template. * @group Templates */ moveToTargetIconTemplate; /** * Custom move all to target icon template. * @group Templates */ moveAllToTargetIconTemplate; /** * Custom move to source icon template. * @group Templates */ moveToSourceIconTemplate; /** * Custom move all to source icon template. * @group Templates */ moveAllToSourceIconTemplate; /** * Custom target filter icon template. * @group Templates */ targetFilterIconTemplate; /** * Custom source filter icon template. * @group Templates */ sourceFilterIconTemplate; templates; _itemTemplate; _sourceHeaderTemplate; _targetHeaderTemplate; _sourceFilterTemplate; _targetFilterTemplate; _emptyMessageSourceTemplate; _emptyFilterMessageSourceTemplate; _emptyMessageTargetTemplate; _emptyFilterMessageTargetTemplate; _moveUpIconTemplate; _moveTopIconTemplate; _moveDownIconTemplate; _moveBottomIconTemplate; _moveToTargetIconTemplate; _moveAllToTargetIconTemplate; _moveToSourceIconTemplate; _moveAllToSourceIconTemplate; _targetFilterIconTemplate; _sourceFilterIconTemplate; ngAfterContentInit() { this.templates.forEach((item) => { switch (item.getType()) { case 'item': this._itemTemplate = item.template; break; case 'option': this._itemTemplate = item.template; break; case 'sourceHeader': this._sourceHeaderTemplate = item.template; break; case 'targetHeader': this._targetHeaderTemplate = item.template; break; case 'sourceFilter': this._sourceFilterTemplate = item.template; break; case 'targetFilter': this._targetFilterTemplate = item.template; break; case 'emptymessagesource': this._emptyMessageSourceTemplate = item.template; break; case 'emptyfiltermessagesource': this._emptyFilterMessageSourceTemplate = item.template; break; case 'emptymessagetarget': this._emptyMessageTargetTemplate = item.template; break; case 'emptyfiltermessagetarget': this._emptyFilterMessageTargetTemplate = item.template; break; case 'moveupicon': this._moveUpIconTemplate = item.template; break; case 'movetopicon': this._moveTopIconTemplate = item.template; break; case 'movedownicon': this._moveDownIconTemplate = item.template; break; case 'movebottomicon': this._moveBottomIconTemplate = item.template; break; case 'movetotargeticon': this._moveToTargetIconTemplate = item.template; break; case 'movealltotargeticon': this._moveAllToTargetIconTemplate = item.template; break; case 'movetosourceicon': this._moveToSourceIconTemplate = item.template; break; case 'movealltosourceicon': this._moveAllToSourceIconTemplate = item.template; break; case 'targetfiltericon': this._targetFilterIconTemplate = item.template; break; case 'sourcefiltericon': this._sourceFilterIconTemplate = item.template; break; default: this._itemTemplate = item.template; break; } }); } ngAfterViewChecked() { if (this.movedUp || this.movedDown) { let listItems = find(this.reorderedListElement?.el.nativeElement, 'li.p-listbox-option-selected'); let listItem; if (listItems.length > 0) { if (this.movedUp) listItem = listItems[0]; else listItem = listItems[listItems.length - 1]; scrollInView(this.reorderedListElement?.el.nativeElement, listItem); } this.movedUp = false; this.movedDown = false; this.reorderedListElement = null; } } onItemClick(event, item, selectedItems, listType, callback, itemId) { if (this.disabled) { return; } let index = this.findIndexInList(item, selectedItems); if (itemId) this.focusedOptionIndex = itemId; let selected = index != -1; let metaSelection = this.itemTouched ? false : this.metaKeySelection; if (metaSelection) { let metaKey = event.metaKey || event.ctrlKey || event.shiftKey; if (selected && metaKey) { selectedItems = selectedItems.filter((_, i) => i !== index); } else { if (!metaKey) { selectedItems = []; } selectedItems.push(item); } } else { if (selected) { selectedItems = selectedItems.filter((_, i) => i !== index); // Creating a new array without the selected item } else { selectedItems.push(item); } } this.setSelectionList(listType, selectedItems); callback.emit({ originalEvent: event, items: selectedItems }); this.itemTouched = false; } onOptionMouseDown(index, listType) { this.focused[listType === this.SOURCE_LIST ? 'sourceList' : 'targetList'] = true; this.focusedOptionIndex = index; } onSourceItemDblClick() { if (this.disabled) { return; } this.moveRight(); this.viewChildMarkForCheck(); } onTargetItemDblClick() { if (this.disabled) { return; } this.moveLeft(); this.viewChildMarkForCheck(); } onFilter(event, listType) { let query = event.target.value; if (listType === this.SOURCE_LIST) this.filterSource(query); else if (listType === this.TARGET_LIST) this.filterTarget(query); } filterSource(value = '') { this.filterValueSource = value.trim().toLocaleLowerCase(this.filterLocale); this.filter(this.source, this.SOURCE_LIST); } filterTarget(value = '') { this.filterValueTarget = value.trim().toLocaleLowerCase(this.filterLocale); this.filter(this.target, this.TARGET_LIST); } filter(data, listType) { let searchFields = this.filterBy.split(','); if (listType === this.SOURCE_LIST) { this.visibleOptionsSource = this.filterService.filter(data, searchFields, this.filterValueSource, this.filterMatchMode, this.filterLocale); this.onSourceFilter.emit({ query: this.filterValueSource, value: this.visibleOptionsSource }); } else if (listType === this.TARGET_LIST) { this.visibleOptionsTarget = this.filterService.filter(data, searchFields, this.filterValueTarget, this.filterMatchMode, this.filterLocale); this.onTargetFilter.emit({ query: this.filterValueTarget, value: this.visibleOptionsTarget }); } } isItemVisible(item, listType) { if (listType == this.SOURCE_LIST) return this.isVisibleInList(this.visibleOptionsSource, item, this.filterValueSource); else return this.isVisibleInList(this.visibleOptionsTarget, item, this.filterValueTarget); } isEmpty(listType) { if (listType == this.SOURCE_LIST) return this.filterValueSource ? !this.visibleOptionsSource || this.visibleOptionsSource.length === 0 : !this.source || this.source.length === 0; else return this.filterValueTarget ? !this.visibleOptionsTarget || this.visibleOptionsTarget.length === 0 : !this.target || this.target.length === 0; } isVisibleInList(data, item, filterValue) { if (filterValue && filterValue.trim().length) { for (let i = 0; i < data.length; i++) { if (item == data[i]) { return true; } } } else { return true; } } onItemTouchEnd() { if (this.disabled) { return; } this.itemTouched = true; } sortByIndexInList(items, list) { return items.sort((item1, item2) => findIndexInList(item1, list) - findIndexInList(item2, list)); } viewChildMarkForCheck() { this.listViewSourceChild.cd.markForCheck(); this.listViewTargetChild.cd.markForCheck(); } moveUp(listElement, list, selectedItems, callback, listType) { if (selectedItems && selectedItems.length) { selectedItems = this.sortByIndexInList(selectedItems, list); for (let i = 0; i < selectedItems.length; i++) { let selectedItem = selectedItems[i]; let selectedItemIndex = findIndexInList(selectedItem, list); if (selectedItemIndex != 0) { let movedItem = list[selectedItemIndex]; let temp = list[selectedItemIndex - 1]; list[selectedItemIndex - 1] = movedItem; list[selectedItemIndex] = temp; } else { break; } } if (this.dragdrop && ((this.filterValueSource && listType === this.SOURCE_LIST) || (this.filterValueTarget && listType === this.TARGET_LIST))) this.filter(list, listType); this.movedUp = true; this.reorderedListElement = listElement; callback.emit({ items: selectedItems }); this.viewChildMarkForCheck(); } } moveTop(listElement, list, selectedItems, callback, listType) { if (selectedItems && selectedItems.length) { selectedItems = this.sortByIndexInList(selectedItems, list); for (let i = 0; i < selectedItems.length; i++) { let selectedItem = selectedItems[i]; let selectedItemIndex = findIndexInList(selectedItem, list); if (selectedItemIndex != 0) { let movedItem = list.splice(selectedItemIndex, 1)[0]; list.unshift(movedItem); } else { break; } } if (this.dragdrop && ((this.filterValueSource && listType === this.SOURCE_LIST) || (this.filterValueTarget && listType === this.TARGET_LIST))) this.filter(list, listType); listElement.scrollTop = 0; callback.emit({ items: selectedItems }); this.viewChildMarkForCheck(); } } moveDown(listElement, list, selectedItems, callback, listType) { if (selectedItems && selectedItems.length) { selectedItems = this.sortByIndexInList(selectedItems, list); for (let i = selectedItems.length - 1; i >= 0; i--) { let selectedItem = selectedItems[i]; let selectedItemIndex = findIndexInList(selectedItem, list); if (selectedItemIndex != list.length - 1) { let movedItem = list[selectedItemIndex]; let temp = list[selectedItemIndex + 1]; list[selectedItemIndex + 1] = movedItem; list[selectedItemIndex] = temp; } else { break; } } if (this.dragdrop && ((this.filterValueSource && listType === this.SOURCE_LIST) || (this.filterValueTarget && listType === this.TARGET_LIST))) this.filter(list, listType); this.movedDown = true; this.reorderedListElement = listElement; callback.emit({ items: selectedItems }); this.viewChildMarkForCheck(); } } moveBottom(listElement, list, selectedItems, callback, listType) { if (selectedItems && selectedItems.length) { selectedItems = this.sortByIndexInList(selectedItems, list); for (let i = selectedItems.length - 1; i >= 0; i--) { let selectedItem = selectedItems[i]; let selectedItemIndex = findIndexInList(selectedItem, list); if (selectedItemIndex != list.length - 1) { let movedItem = list.splice(selectedItemIndex, 1)[0]; list.push(movedItem); } else { break; } } if (this.dragdrop && ((this.filterValueSource && listType === this.SOURCE_LIST) || (this.filterValueTarget && listType === this.TARGET_LIST))) this.filter(list, listType); listElement.scrollTop = listElement.scrollHeight; callback.emit({ items: selectedItems }); this.viewChildMarkForCheck(); } } moveRight() { if (this.selectedItemsSource && this.selectedItemsSource.length) { let itemsToMove = [...this.selectedItemsSource]; for (let i = 0; i < itemsToMove.length; i++) { let selectedItem = itemsToMove[i]; if (findIndexInList(selectedItem, this.target) == -1) { this.target?.push(this.source?.splice(findIndexInList(selectedItem, this.source), 1)[0]); if (this.visibleOptionsSource?.includes(selectedItem)) { this.visibleOptionsSource.splice(findIndexInList(selectedItem, this.visibleOptionsSource), 1); } } } this.onMoveToTarget.emit({ items: itemsToMove }); if (this.keepSelection) { this.selectedItemsTarget = [...this.selectedItemsTarget, ...itemsToMove]; } itemsToMove = []; this.selectedItemsSource = []; if (this.filterValueTarget) { this.filter(this.target, this.TARGET_LIST); } this.viewChildMarkForCheck(); } } moveAllRight() { if (this.source) { let movedItems = []; for (let i = 0; i < this.source.length; i++) { if (this.isItemVisible(this.source[i], this.SOURCE_LIST)) { let removedItem = this.source.splice(i, 1)[0]; this.target?.push(removedItem); movedItems.push(removedItem); i--; } } this.onMoveAllToTarget.emit({ items: movedItems }); if (this.keepSelection) { this.selectedItemsTarget = [...this.selectedItemsTarget, ...this.selectedItemsSource]; } this.selectedItemsSource = []; if (this.filterValueTarget) { this.filter(this.target, this.TARGET_LIST); } this.visibleOptionsSource = []; this.viewChildMarkForCheck(); } } moveLeft() { if (this.selectedItemsTarget && this.selectedItemsTarget.length) { let itemsToMove = [...this.selectedItemsTarget]; for (let i = 0; i < itemsToMove.length; i++) { let selectedItem = itemsToMove[i]; if (findIndexInList(selectedItem, this.source) == -1) { this.source?.push(this.target?.splice(findIndexInList(selectedItem, this.target), 1)[0]); if (this.visibleOptionsTarget?.includes(selectedItem)) { this.visibleOptionsTarget.splice(findIndexInList(selectedItem, this.visibleOptionsTarget), 1)[0]; } } } this.onMoveToSource.emit({ items: itemsToMove }); if (this.keepSelection) { this.selectedItemsSource = [...this.selectedItemsSource, itemsToMove]; } itemsToMove = []; this.selectedItemsTarget = []; if (this.filterValueSource) { this.filter(this.source, this.SOURCE_LIST); } this.viewChildMarkForCheck(); } } moveAllLeft() { if (this.target) { let movedItems = []; for (let i = 0; i < this.target.length; i++) { if (this.isItemVisible(this.target[i], this.TARGET_LIST)) { let removedItem = this.target.splice(i, 1)[0]; this.source?.push(removedItem); movedItems.push(removedItem); i--; } } this.onMoveAllToSource.emit({ items: movedItems }); if (this.keepSelection) { this.selectedItemsSource = [...this.selectedItemsSource, ...this.selectedItemsTarget]; } this.selectedItemsTarget = []; if (this.filterValueSource) { this.filter(this.source, this.SOURCE_LIST); } this.visibleOptionsTarget = []; this.viewChildMarkForCheck(); } } isSelected(item, selectedItems) { return this.findIndexInList(item, selectedItems) != -1; } findIndexInList(item, selectedItems) { return findIndexInList(item, selectedItems); } onDrop(event, listType) { let isTransfer = event.previousContainer !== event.container; let dropIndexes = this.getDropIndexes(event.previousIndex, event.currentIndex, listType, isTransfer, event.item.data); if (listType === this.SOURCE_LIST) { if (isTransfer) { transferArrayItem(event.previousContainer.data, event.container.data, dropIndexes.previousIndex, dropIndexes.currentIndex); let selectedItemIndex = findIndexInList(event.item.data, this.selectedItemsTarget); if (selectedItemIndex != -1) { this.selectedItemsTarget.splice(selectedItemIndex, 1); if (this.keepSelection) { this.selectedItemsTarget.push(event.item.data); } } if (this.visibleOptionsTarget) this.visibleOptionsTarget.splice(event.previousIndex, 1); this.onMoveToSource.emit({ items: [event.item.data] }); } else { moveItemInArray(event.container.data, dropIndexes.previousIndex, dropIndexes.currentIndex); this.onSourceReorder.emit({ items: [event.item.data] }); } if (this.filterValueSource) { this.filter(this.source, this.SOURCE_LIST); } } else { if (isTransfer) { transferArrayItem(event.previousContainer.data, event.container.data, dropIndexes.previousIndex, dropIndexes.currentIndex); let selectedItemIndex = findIndexInList(event.item.data, this.selectedItemsSource); if (selectedItemIndex != -1) { this.selectedItemsSource.splice(selectedItemIndex, 1); if (this.keepSelection) { this.selectedItemsTarget.push(event.item.data); } } if (this.visibleOptionsSource) this.visibleOptionsSource.splice(event.previousIndex, 1); this.onMoveToTarget.emit({ items: [event.item.data] }); } else { moveItemInArray(event.container.data, dropIndexes.previousIndex, dropIndexes.currentIndex); this.onTargetReorder.emit({ items: [event.item.data] }); } if (this.filterValueTarget) { this.filter(this.target, this.TARGET_LIST); } } } onListFocus(event, listType) { this.onFocus.emit(event); } onListBlur(event, listType) { this.onBlur.emit(event); } getListElement(listType) { return listType === this.SOURCE_LIST ? this.listViewSourceChild?.el.nativeElement : this.listViewTargetChild?.el.nativeElement; } getListItems(listType) { let listElemet = this.getListElement(listType); return find(listElemet, 'li.p-picklist-item'); } getLatestSelectedVisibleOptionIndex(visibleList, selectedItems) { const latestSelectedItem = [...selectedItems].reverse().find((item) => visibleList.includes(item)); return latestSelectedItem !== undefined ? visibleList.indexOf(latestSelectedItem) : -1; } getVisibleList(listType) { if (listType === this.SOURCE_LIST) { return this.visibleOptionsSource && this.visibleOptionsSource.length > 0 ? this.visibleOptionsSource : this.source && this.source.length > 0 ? this.source : null; } return this.visibleOptionsTarget && this.visibleOptionsTarget.length > 0 ? this.visibleOptionsTarget : this.target && this.target.length > 0 ? this.target : null; } setSelectionList(listType, selectedItems) { if (listType === this.SOURCE_LIST) { this.selectedItemsSource = selectedItems; } else { this.selectedItemsTarget = selectedItems; } } findNextOptionIndex(index, listType) { const items = this.getListItems(listType); const matchedOptionIndex = [...items].findIndex((link) => link.id === index); return matchedOptionIndex > -1 ? matchedOptionIndex + 1 : 0; } findPrevOptionIndex(index, listType) { const items = this.getListItems(listType); const matchedOptionIndex = [...items].findIndex((link) => link.id === index); return matchedOptionIndex > -1 ? matchedOptionIndex - 1 : 0; } onItemKeyDown(event, selectedItems, callback, listType) { switch (event.code) { case 'ArrowDown': this.onArrowDownKey(event, selectedItems, callback, listType); break; case 'ArrowUp': this.onArrowUpKey(event, selectedItems, callback, listType); break; case 'Home': this.onHomeKey(event, selectedItems, callback, listType); break; case 'End': this.onEndKey(event, selectedItems, callback, listType); break; case 'Enter': this.onEnterKey(event, selectedItems, callback, listType); break; case 'Space': this.onSpaceKey(event, selectedItems, callback, listType); break; case 'KeyA': if (event.ctrlKey) { this.setSelectionList(listType, this.getVisibleList(listType)); callback.emit({ items: selectedItems }); event.preventDefault(); } default: break; } } getFocusedOption(index, listType) { if (index === -1) return null; if (listType === this.SOURCE_LIST) { return this.visibleOptionsSource && this.visibleOptionsSource.length ? this.visibleOptionsSource[index] : this.source && this.source.length ? this.source[index] : null; } return this.visibleOptionsTarget && this.visibleOptionsTarget.length ? this.visibleOptionsTarget[index] : this.target && this.target.length ? this.target[index] : null; } changeFocusedOptionIndex(index, listType) { const items = this.getListItems(listType); if (items?.length > 0) { let order = index >= items.length ? items.length - 1 : index < 0 ? 0 : index; this.focusedOptionIndex = items[order].getAttribute('id'); this.focusedOption = this.getFocusedOption(order, listType); this.scrollInView(items[order].getAttribute('id'), listType); } } scrollInView(id, listType) { const element = findSingle(this.getListElement(listType), `li[id="${id}"]`); if (element) { element.scrollIntoView && element.scrollIntoView({ block: 'nearest', inline: 'start' }); } } onArrowDownKey(event, selectedItems, callback, listType) { const optionIndex = this.findNextOptionIndex(this.focusedOptionIndex, listType); this.changeFocusedOptionIndex(optionIndex, listType); if (event.shiftKey) { this.onEnterKey(event, selectedItems, callback, listType); } event.preventDefault(); } onArrowUpKey(event, selectedItems, callback, listType) { const optionIndex = this.findPrevOptionIndex(this.focusedOptionIndex, listType); this.changeFocusedOptionIndex(optionIndex, listType); if (event.shiftKey) { this.onEnterKey(event, selectedItems, callback, listType); } event.preventDefault(); } onEnterKey(event, selectedItems, callback, listType) { this.onItemClick(event, this.focusedOption, selectedItems, listType, callback); event.preventDefault(); } onSpaceKey(event, selectedItems, callback, listType) { if (event.target.tagName === 'INPUT') return; event.preventDefault(); if (event.shiftKey && selectedItems && selectedItems.length > 0) { let visibleList = this.getVisibleList(listType); let lastSelectedIndex = this.getLatestSelectedVisibleOptionIndex(visibleList, selectedItems); if (lastSelectedIndex !== -1) { let focusedIndex = findIndexInList(this.focusedOption, visibleList); selectedItems = [...visibleList.slice(Math.min(lastSelectedIndex, focusedIndex), Math.max(lastSelectedIndex, focusedIndex) + 1)]; this.setSelectionList(listType, selectedItems); callback.emit({ items: selectedItems }); return; } } this.onEnterKey(event, selectedItems, callback, listType); } onHomeKey(event, selectedItems, callback, listType) { if (event.ctrlKey && event.shiftKey) { let visibleList = this.getVisibleList(listType); let focusedIndex = findIndexInList(this.focusedOption, visibleList); selectedItems = [...visibleList.slice(0, focusedIndex + 1)]; this.setSelectionList(listType, selectedItems); callback.emit({ items: selectedItems }); } else { this.changeFocusedOptionIndex(0, listType); } event.preventDefault(); } onEndKey(event, selectedItems, callback, listType) { let visibleList = this.getVisibleList(listType); let lastIndex = visibleList && visibleList.length > 0 ? visibleList.length - 1 : null; if (lastIndex === null) return; if (event.ctrlKey && event.shiftKey) { let focusedIndex = findIndexInList(this.focusedOption, visibleList); selectedItems = [...visibleList.slice(focusedIndex, lastIndex)]; this.setSelectionList(listType, selectedItems); callback.emit({ items: selectedItems }); } else { this.changeFocusedOptionIndex(lastIndex, listType); } event.preventDefault(); } getDropIndexes(fromIndex, toIndex, droppedList, isTransfer, data) { let previousIndex, currentIndex; if (droppedList === this.SOURCE_LIST) { previousIndex = isTransfer ? (this.filterValueTarget ? findIndexInList(data, this.target) : fromIndex) : this.filterValueSource ? findIndexInList(data, this.source) : fromIndex; currentIndex = this.filterValueSource ? this.findFilteredCurrentIndex(this.visibleOptionsSource, toIndex, this.source) : toIndex; } else { previousIndex = isTransfer ? (this.filterValueSource ? findIndexInList(data, this.source) : fromIndex) : this.filterValueTarget ? findIndexInList(data, this.target) : fromIndex; currentIndex = this.filterValueTarget ? this.findFilteredCurrentIndex(this.visibleOptionsTarget, toIndex, this.target) : toIndex; } return { previousIndex, currentIndex }; } findFilteredCurrentIndex(visibleOptions, index, options) { if (visibleOptions.length === index) { let toIndex = findIndexInList(visibleOptions[index - 1], options); return toIndex + 1; } else { return findIndexInList(visibleOptions[index], options); } } resetSourceFilter() { this.visibleOptionsSource = null; this.filterValueSource = null; this.sourceFilterViewChild && (this.sourceFilterViewChild.nativeElement.value = ''); } resetTargetFilter() { this.visibleOptionsTarget = null; this.filterValueTarget = null; this.targetFilterViewChild && (this.targetFilterViewChild.nativeElement.value = ''); } resetFilter() { this.resetSourceFilter(); this.resetTargetFilter(); } initMedia() { if (isPlatformBrowser(this.platformId)) { this.media = this.document.defaultView.matchMedia(`(max-width: ${this.breakpoint})`); this.viewChanged = this.media.matches; this.bindMediaChangeListener(); } } destroyMedia() { this.unbindMediaChangeListener(); } bindMediaChangeListener() { if (this.media && !this.mediaChangeListener) { this.mediaChangeListener = this.renderer.listen(this.media, 'change', (event) => { this.viewChanged = event.matches; this.cd.markForCheck(); }); } } unbindMediaChangeListener() { if