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 lines • 133 kB
Source Map (JSON)
{"version":3,"file":"primeng-tree.mjs","sources":["../../src/tree/style/treestyle.ts","../../src/tree/tree.ts","../../src/tree/primeng-tree.ts"],"sourcesContent":["import { Injectable } from '@angular/core';\nimport { BaseStyle } from 'primeng/base';\n\nconst theme = ({ dt }) => `\n.p-tree {\n background: ${dt('tree.background')};\n color: ${dt('tree.color')};\n padding: ${dt('tree.padding')};\n}\n\n.p-tree-root-children,\n.p-tree-node-children {\n display: flex;\n list-style-type: none;\n flex-direction: column;\n margin: 0;\n gap: ${dt('tree.gap')};\n}\n\n.p-tree-root-children {\n padding: 0;\n padding-block-start: ${dt('tree.gap')};\n}\n\n.p-tree-node-children {\n padding-block-start: ${dt('tree.gap')};\n padding-inline-start: ${dt('tree.indent')};\n}\n\n.p-tree-node {\n padding: 0;\n outline: 0 none;\n}\n\n.p-tree-node-content {\n border-radius: ${dt('tree.node.border.radius')};\n padding: ${dt('tree.node.padding')};\n display: flex;\n align-items: center;\n outline-color: transparent;\n color: ${dt('tree.node.color')};\n gap: ${dt('tree.node.gap')};\n transition: background ${dt('tree.transition.duration')}, color ${dt('tree.transition.duration')}, outline-color ${dt('tree.transition.duration')}, box-shadow ${dt('tree.transition.duration')};\n}\n\n.p-tree-node:focus-visible > .p-tree-node-content {\n box-shadow: ${dt('tree.node.focus.ring.shadow')};\n outline: ${dt('tree.node.focus.ring.width')} ${dt('tree.node.focus.ring.style')} ${dt('tree.node.focus.ring.color')};\n outline-offset: ${dt('tree.node.focus.ring.offset')};\n}\n\n.p-tree-node-content.p-tree-node-selectable:not(.p-tree-node-selected):hover {\n background: ${dt('tree.node.hover.background')};\n color: ${dt('tree.node.hover.color')};\n}\n\n.p-tree-node-content.p-tree-node-selectable:not(.p-tree-node-selected):hover .p-tree-node-icon {\n color: ${dt('tree.node.icon.hover.color')};\n}\n\n.p-tree-node-content.p-tree-node-selected {\n background: ${dt('tree.node.selected.background')};\n color: ${dt('tree.node.selected.color')};\n}\n\n.p-tree-node-content.p-tree-node-selected .p-tree-node-toggle-button {\n color: inherit;\n}\n\n.p-tree-node-toggle-button {\n cursor: pointer;\n user-select: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n position: relative;\n flex-shrink: 0;\n width: ${dt('tree.node.toggle.button.size')};\n height: ${dt('tree.node.toggle.button.size')};\n color: ${dt('tree.node.toggle.button.color')};\n border: 0 none;\n background: transparent;\n border-radius: ${dt('tree.node.toggle.button.border.radius')};\n transition: background ${dt('tree.transition.duration')}, color ${dt('tree.transition.duration')}, border-color ${dt('tree.transition.duration')}, outline-color ${dt('tree.transition.duration')}, box-shadow ${dt('tree.transition.duration')};\n outline-color: transparent;\n padding: 0;\n}\n\n.p-tree-node-toggle-button:enabled:hover {\n background: ${dt('tree.node.toggle.button.hover.background')};\n color: ${dt('tree.node.toggle.button.hover.color')};\n}\n\n.p-tree-node-content.p-tree-node-selected .p-tree-node-toggle-button:hover {\n background: ${dt('tree.node.toggle.button.selected.hover.background')};\n color: ${dt('tree.node.toggle.button.selected.hover.color')};\n}\n\n.p-tree-root {\n overflow: auto;\n}\n\n.p-tree-node-selectable {\n cursor: pointer;\n user-select: none;\n}\n\n.p-tree-node-leaf > .p-tree-node-content .p-tree-node-toggle-button {\n visibility: hidden;\n}\n\n.p-tree-node-icon {\n color: ${dt('tree.node.icon.color')};\n transition: color ${dt('tree.transition.duration')};\n}\n\n.p-tree-node-content.p-tree-node-selected .p-tree-node-icon {\n color: ${dt('tree.node.icon.selected.color')};\n}\n\n.p-tree-filter-input {\n width: 100%;\n}\n\n.p-tree-loading {\n position: relative;\n height: 100%;\n}\n\n.p-tree-loading-icon {\n font-size: ${dt('tree.loading.icon.size')};\n width: ${dt('tree.loading.icon.size')};\n height: ${dt('tree.loading.icon.size')};\n}\n\n.p-tree .p-tree-mask {\n position: absolute;\n z-index: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.p-tree-flex-scrollable {\n display: flex;\n flex: 1;\n height: 100%;\n flex-direction: column;\n}\n\n.p-tree-flex-scrollable .p-tree-root {\n flex: 1;\n}\n\n/* For PrimeNG */\n.p-tree .p-tree-node-droppoint {\n height: 4px;\n list-style-type: none;\n}\n\n.p-tree .p-tree-node-droppoint-active {\n border: 0 none;\n background-color: ${dt('primary.color')};\n}\n\n.p-tree-node-content.p-tree-node-dragover {\n background: ${dt('tree.node.hover.background')};\n color: ${dt('tree.node.hover.color')};\n}\n\n.p-tree-node-content.p-tree-node-dragover .p-tree-node-icon {\n color: ${dt('tree.node.icon.hover.color')};\n}\n\n.p-tree-horizontal {\n width: auto;\n padding-inline-start: 0;\n padding-inline-end: 0;\n overflow: auto;\n}\n\n.p-tree.p-tree-horizontal table,\n.p-tree.p-tree-horizontal tr,\n.p-tree.p-tree-horizontal td {\n border-collapse: collapse;\n margin: 0;\n padding: 0;\n vertical-align: middle;\n}\n\n.p-tree-horizontal .p-tree-node-content {\n font-weight: normal;\n padding: 0.4em 1em 0.4em 0.2em;\n display: flex;\n align-items: center;\n}\n\n.p-tree-horizontal .p-tree-node-parent .p-tree-node-content {\n font-weight: normal;\n white-space: nowrap;\n}\n\n.p-tree.p-tree-horizontal .p-tree-node.p-tree-node-leaf,\n.p-tree.p-tree-horizontal .p-tree-node.p-tree-node-collapsed {\n padding-inline-end: 0;\n}\n\n.p-tree.p-tree-horizontal .p-tree-node-children {\n padding: 0;\n margin: 0;\n}\n\n.p-tree.p-tree-horizontal .p-tree-node-connector {\n width: 1px;\n}\n\n.p-tree.p-tree-horizontal .p-tree-node-connector-table {\n height: 100%;\n width: 1px;\n}\n\n.p-tree.p-tree-horizontal table {\n height: 0;\n}\n`;\n\nconst classes = {\n root: ({ instance }) => ({\n 'p-tree p-component': true,\n 'p-tree-selectable': instance.selectionMode != null,\n 'p-tree-loading': instance.loading,\n 'p-tree-flex-scrollable': instance.scrollHeight === 'flex',\n 'p-tree-node-dragover': instance.dragHover\n }),\n mask: 'p-tree-mask p-overlay-mask',\n loadingIcon: 'p-tree-loading-icon',\n pcFilterInput: 'p-tree-filter-input',\n wrapper: 'p-tree-root', //TODO: discuss\n rootChildren: 'p-tree-root-children',\n node: ({ instance }) => ({ 'p-tree-node': true, 'p-tree-node-leaf': instance.isLeaf() }),\n nodeContent: ({ instance }) => ({\n 'p-tree-node-content': true,\n [instance.styleClass]: !!instance.styleClass,\n 'p-tree-node-selectable': instance.selectable,\n 'p-tree-node-dragover': instance.draghoverNode,\n 'p-tree-node-selected': instance.selectionMode === 'checkbox' && instance.tree.highlightOnSelect ? instance.checked : instance.selected\n }),\n nodeToggleButton: 'p-tree-node-toggle-button',\n nodeToggleIcon: 'p-tree-node-toggle-icon',\n nodeCheckbox: 'p-tree-node-checkbox',\n nodeIcon: 'p-tree-node-icon',\n nodeLabel: 'p-tree-node-label',\n nodeChildren: 'p-tree-node-children'\n};\n\n@Injectable()\nexport class TreeStyle extends BaseStyle {\n name = 'tree';\n\n theme = theme;\n\n classes = classes;\n}\n\n/**\n *\n * Tree is used to display hierarchical data.\n *\n * [Live Demo](https://www.primeng.org/tree/)\n *\n * @module treestyle\n *\n */\nexport enum TreeClasses {\n /**\n * Class name of the root element\n */\n root = 'p-tree',\n /**\n * Class name of the mask element\n */\n mask = 'p-tree-mask',\n /**\n * Class name of the loading icon element\n */\n loadingIcon = 'p-tree-loading-icon',\n /**\n * Class name of the filter input element\n */\n pcFilterInput = 'p-tree-filter-input',\n /**\n * Class name of the wrapper element\n */\n wrapper = 'p-tree-root',\n /**\n * Class name of the root children element\n */\n rootChildren = 'p-tree-root-children',\n /**\n * Class name of the node element\n */\n node = 'p-tree-node',\n /**\n * Class name of the node content element\n */\n nodeContent = 'p-tree-node-content',\n /**\n * Class name of the node toggle button element\n */\n nodeToggleButton = 'p-tree-node-toggle-button',\n /**\n * Class name of the node toggle icon element\n */\n nodeToggleIcon = 'p-tree-node-toggle-icon',\n /**\n * Class name of the node checkbox element\n */\n nodeCheckbox = 'p-tree-node-checkbox',\n /**\n * Class name of the node icon element\n */\n nodeIcon = 'p-tree-node-icon',\n /**\n * Class name of the node label element\n */\n nodeLabel = 'p-tree-node-label',\n /**\n * Class name of the node children element\n */\n nodeChildren = 'p-tree-node-children'\n}\n\nexport interface TreeStyle extends BaseStyle {}\n","import { CommonModule } from '@angular/common';\nimport {\n AfterContentInit,\n booleanAttribute,\n ChangeDetectionStrategy,\n Component,\n ContentChild,\n ContentChildren,\n ElementRef,\n EventEmitter,\n forwardRef,\n inject,\n Input,\n NgModule,\n numberAttribute,\n OnChanges,\n OnDestroy,\n OnInit,\n Optional,\n Output,\n QueryList,\n SimpleChanges,\n TemplateRef,\n ViewChild,\n ViewEncapsulation\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { find, findSingle, focus, hasClass, removeAccents, resolveFieldData } from '@primeuix/utils';\nimport { BlockableUI, PrimeTemplate, ScrollerOptions, SharedModule, TranslationKeys, TreeDragDropService, TreeNode } from 'primeng/api';\nimport { BaseComponent } from 'primeng/basecomponent';\nimport { Checkbox } from 'primeng/checkbox';\nimport { IconField } from 'primeng/iconfield';\nimport { ChevronDownIcon, ChevronRightIcon, SearchIcon, SpinnerIcon } from 'primeng/icons';\nimport { InputIcon } from 'primeng/inputicon';\nimport { InputText } from 'primeng/inputtext';\nimport { Ripple } from 'primeng/ripple';\nimport { Scroller } from 'primeng/scroller';\nimport { Nullable } from 'primeng/ts-helpers';\nimport { Subscription } from 'rxjs';\nimport { TreeStyle } from './style/treestyle';\nimport {\n TreeFilterEvent,\n TreeLazyLoadEvent,\n TreeNodeCollapseEvent,\n TreeNodeContextMenuSelectEvent,\n TreeNodeDoubleClickEvent,\n TreeNodeDropEvent,\n TreeNodeExpandEvent,\n TreeNodeSelectEvent,\n TreeNodeUnSelectEvent,\n TreeScrollEvent,\n TreeScrollIndexChangeEvent\n} from './tree.interface';\nimport { AutoFocusModule } from 'primeng/autofocus';\n\n@Component({\n selector: 'p-treeNode',\n standalone: true,\n imports: [CommonModule, Ripple, Checkbox, FormsModule, ChevronRightIcon, ChevronDownIcon, SpinnerIcon, SharedModule],\n template: `\n @if (node) {\n <li\n *ngIf=\"tree.droppableNodes\"\n class=\"p-tree-node-droppoint\"\n [attr.aria-hidden]=\"true\"\n [ngClass]=\"{ 'p-tree-node-droppoint-active': draghoverPrev }\"\n (drop)=\"onDropPoint($event, -1)\"\n (dragover)=\"onDropPointDragOver($event)\"\n (dragenter)=\"onDropPointDragEnter($event, -1)\"\n (dragleave)=\"onDropPointDragLeave($event)\"\n ></li>\n <li\n [ngClass]=\"nodeClass\"\n [class]=\"node.styleClass\"\n [ngStyle]=\"{ height: itemSize + 'px' }\"\n [style]=\"node.style\"\n [attr.aria-label]=\"node.label\"\n [attr.aria-checked]=\"checked\"\n [attr.aria-setsize]=\"node.children ? node.children.length : 0\"\n [attr.aria-selected]=\"selected\"\n [attr.aria-expanded]=\"node.expanded\"\n [attr.aria-posinset]=\"index + 1\"\n [attr.aria-level]=\"level + 1\"\n [attr.tabindex]=\"index === 0 ? 0 : -1\"\n [attr.data-id]=\"node.key\"\n role=\"treeitem\"\n (keydown)=\"onKeyDown($event)\"\n >\n <div\n [ngClass]=\"nodeContentClass\"\n [style.paddingLeft]=\"level * indentation + 'rem'\"\n (click)=\"onNodeClick($event)\"\n (contextmenu)=\"onNodeRightClick($event)\"\n (dblclick)=\"onNodeDblClick($event)\"\n (touchend)=\"onNodeTouchEnd()\"\n (drop)=\"onDropNode($event)\"\n (dragover)=\"onDropNodeDragOver($event)\"\n (dragenter)=\"onDropNodeDragEnter($event)\"\n (dragleave)=\"onDropNodeDragLeave($event)\"\n [draggable]=\"tree.draggableNodes\"\n (dragstart)=\"onDragStart($event)\"\n (dragend)=\"onDragStop($event)\"\n >\n <button type=\"button\" [attr.data-pc-section]=\"'toggler'\" class=\"p-tree-node-toggle-button\" (click)=\"toggle($event)\" pRipple tabindex=\"-1\">\n <ng-container *ngIf=\"!tree.togglerIconTemplate && !tree._togglerIconTemplate\">\n <ng-container *ngIf=\"!node.loading\">\n <ChevronRightIcon *ngIf=\"!node.expanded\" [styleClass]=\"'p-tree-node-toggle-icon'\" />\n <ChevronDownIcon *ngIf=\"node.expanded\" [styleClass]=\"'p-tree-node-toggle-icon'\" />\n </ng-container>\n <ng-container *ngIf=\"loadingMode === 'icon' && node.loading\">\n <SpinnerIcon [styleClass]=\"'pi-spin p-tree-node-toggle-icon'\" />\n </ng-container>\n </ng-container>\n <span *ngIf=\"tree.togglerIconTemplate || tree._togglerIconTemplate\" class=\"p-tree-node-toggle-icon\">\n <ng-template *ngTemplateOutlet=\"tree.togglerIconTemplate || tree._togglerIconTemplate; context: { $implicit: node.expanded, loading: node.loading }\"></ng-template>\n </span>\n </button>\n\n <p-checkbox\n [ngModel]=\"isSelected()\"\n styleClass=\"p-tree-node-checkbox\"\n [binary]=\"true\"\n [indeterminate]=\"node.partialSelected\"\n *ngIf=\"tree.selectionMode == 'checkbox'\"\n [disabled]=\"node.selectable === false\"\n [variant]=\"tree?.config.inputStyle() === 'filled' || tree?.config.inputVariant() === 'filled' ? 'filled' : 'outlined'\"\n [attr.data-p-partialchecked]=\"node.partialSelected\"\n [tabindex]=\"-1\"\n (click)=\"$event.preventDefault()\"\n >\n <ng-container *ngIf=\"tree.checkboxIconTemplate || tree._checkboxIconTemplate\">\n <ng-template #icon>\n <ng-template\n *ngTemplateOutlet=\"\n tree.checkboxIconTemplate || tree._checkboxIconTemplate;\n context: {\n $implicit: isSelected(),\n partialSelected: node.partialSelected,\n class: 'p-tree-node-checkbox'\n }\n \"\n ></ng-template>\n </ng-template>\n </ng-container>\n </p-checkbox>\n\n <span [class]=\"getIcon()\" *ngIf=\"node.icon || node.expandedIcon || node.collapsedIcon\"></span>\n <span class=\"p-tree-node-label\">\n <span *ngIf=\"!tree.getTemplateForNode(node)\">{{ node.label }}</span>\n <span *ngIf=\"tree.getTemplateForNode(node)\">\n <ng-container *ngTemplateOutlet=\"tree.getTemplateForNode(node); context: { $implicit: node }\"></ng-container>\n </span>\n </span>\n </div>\n <ul class=\"p-tree-node-children\" style=\"display: none;\" *ngIf=\"!tree.virtualScroll && node.children && node.expanded\" [style.display]=\"node.expanded ? 'flex' : 'none'\" role=\"group\">\n <p-treeNode\n *ngFor=\"let childNode of node.children; let firstChild = first; let lastChild = last; let index = index; trackBy: tree.trackBy.bind(this)\"\n [node]=\"childNode\"\n [parentNode]=\"node\"\n [firstChild]=\"firstChild\"\n [lastChild]=\"lastChild\"\n [index]=\"index\"\n [itemSize]=\"itemSize\"\n [level]=\"level + 1\"\n [loadingMode]=\"loadingMode\"\n ></p-treeNode>\n </ul>\n </li>\n\n <li\n *ngIf=\"tree.droppableNodes && lastChild\"\n class=\"p-tree-node-droppoint\"\n [ngClass]=\"{ 'p-tree-node-droppoint-active': draghoverNext }\"\n (drop)=\"onDropPoint($event, 1)\"\n [attr.aria-hidden]=\"true\"\n (dragover)=\"onDropPointDragOver($event)\"\n (dragenter)=\"onDropPointDragEnter($event, 1)\"\n (dragleave)=\"onDropPointDragLeave($event)\"\n ></li>\n }\n `,\n encapsulation: ViewEncapsulation.None\n})\nexport class UITreeNode extends BaseComponent implements OnInit {\n static ICON_CLASS: string = 'p-tree-node-icon ';\n\n @Input() rowNode: any;\n\n @Input() node: TreeNode<any> | undefined;\n\n @Input() parentNode: TreeNode<any> | undefined;\n\n @Input({ transform: booleanAttribute }) root: boolean | undefined;\n\n @Input({ transform: numberAttribute }) index: number | undefined;\n\n @Input({ transform: booleanAttribute }) firstChild: boolean | undefined;\n\n @Input({ transform: booleanAttribute }) lastChild: boolean | undefined;\n\n @Input({ transform: numberAttribute }) level: number | undefined;\n\n @Input({ transform: numberAttribute }) indentation: number | undefined;\n\n @Input({ transform: numberAttribute }) itemSize: number | undefined;\n\n @Input() loadingMode: string;\n\n tree: Tree = inject(forwardRef(() => Tree));\n\n timeout: any;\n\n draghoverPrev: boolean | undefined;\n\n draghoverNext: boolean | undefined;\n\n draghoverNode: boolean | undefined;\n\n get selected() {\n return this.tree.selectionMode === 'single' || this.tree.selectionMode === 'multiple' ? this.isSelected() : undefined;\n }\n\n get checked() {\n return this.tree.selectionMode === 'checkbox' ? this.isSelected() : undefined;\n }\n\n get nodeClass() {\n return this.tree._componentStyle.classes.node({ instance: this });\n }\n\n get nodeContentClass() {\n return this.tree._componentStyle.classes.nodeContent({ instance: this });\n }\n\n get selectable() {\n return this.node.selectable === false ? false : this.tree.selectionMode != null;\n }\n\n ngOnInit() {\n super.ngOnInit();\n (<TreeNode>this.node).parent = this.parentNode;\n const nativeElement = this.tree.el.nativeElement;\n const pDialogWrapper = nativeElement.closest('p-dialog');\n if (this.parentNode && !pDialogWrapper) {\n this.setAllNodesTabIndexes();\n this.tree.syncNodeOption(<TreeNode>this.node, <TreeNode<any>[]>this.tree.value, 'parent', this.tree.getNodeWithKey(<string>this.parentNode.key, <TreeNode<any>[]>this.tree.value));\n }\n }\n\n getIcon() {\n let icon: string | undefined;\n\n if ((<TreeNode>this.node).icon) icon = (<TreeNode>this.node).icon as string;\n else icon = (<TreeNode>this.node).expanded && (<TreeNode>this.node).children && (<TreeNode>this.node).children?.length ? (<TreeNode>this.node).expandedIcon : (<TreeNode>this.node).collapsedIcon;\n\n return UITreeNode.ICON_CLASS + ' ' + icon + ' p-tree-node-icon';\n }\n\n isLeaf() {\n return this.tree.isNodeLeaf(<TreeNode>this.node);\n }\n\n toggle(event: Event) {\n if ((<TreeNode>this.node).expanded) this.collapse(event);\n else this.expand(event);\n\n event.stopPropagation();\n }\n\n expand(event: Event) {\n (<TreeNode>this.node).expanded = true;\n if (this.tree.virtualScroll) {\n this.tree.updateSerializedValue();\n this.focusVirtualNode();\n }\n this.tree.onNodeExpand.emit({ originalEvent: event, node: <TreeNode>this.node });\n }\n\n collapse(event: Event) {\n (<TreeNode>this.node).expanded = false;\n if (this.tree.virtualScroll) {\n this.tree.updateSerializedValue();\n this.focusVirtualNode();\n }\n this.tree.onNodeCollapse.emit({ originalEvent: event, node: <TreeNode>this.node });\n }\n\n onNodeClick(event: MouseEvent) {\n this.tree.onNodeClick(event, <TreeNode>this.node);\n }\n\n onNodeKeydown(event: KeyboardEvent) {\n if (event.key === 'Enter') {\n this.tree.onNodeClick(event, <TreeNode>this.node);\n }\n }\n\n onNodeTouchEnd() {\n this.tree.onNodeTouchEnd();\n }\n\n onNodeRightClick(event: MouseEvent) {\n this.tree.onNodeRightClick(event, <TreeNode>this.node);\n }\n\n onNodeDblClick(event: MouseEvent) {\n this.tree.onNodeDblClick(event, <TreeNode>this.node);\n }\n\n isSelected() {\n return this.tree.isSelected(<TreeNode>this.node);\n }\n\n isSameNode(event) {\n return event.currentTarget && (event.currentTarget.isSameNode(event.target) || event.currentTarget.isSameNode(event.target.closest('[role=\"treeitem\"]')));\n }\n\n onDropPoint(event: DragEvent, position: number) {\n event.preventDefault();\n let dragNode = this.tree.dragNode;\n let dragNodeIndex = this.tree.dragNodeIndex;\n let dragNodeScope = this.tree.dragNodeScope;\n let isValidDropPointIndex = this.tree.dragNodeTree === this.tree ? position === 1 || dragNodeIndex !== <number>this.index - 1 : true;\n\n if (this.tree.allowDrop(<TreeNode>dragNode, <TreeNode>this.node, dragNodeScope) && isValidDropPointIndex) {\n let dropParams = { ...this.createDropPointEventMetadata(<number>position) };\n\n if (this.tree.validateDrop) {\n this.tree.onNodeDrop.emit({\n originalEvent: event,\n dragNode: dragNode,\n dropNode: this.node,\n index: this.index,\n accept: () => {\n this.processPointDrop(dropParams);\n }\n });\n } else {\n this.processPointDrop(dropParams);\n this.tree.onNodeDrop.emit({\n originalEvent: event,\n dragNode: dragNode,\n dropNode: this.node,\n index: this.index\n });\n }\n }\n\n this.draghoverPrev = false;\n this.draghoverNext = false;\n }\n\n processPointDrop(event: any) {\n let newNodeList = event.dropNode.parent ? event.dropNode.parent.children : this.tree.value;\n event.dragNodeSubNodes.splice(event.dragNodeIndex, 1);\n let dropIndex = this.index;\n\n if (event.position < 0) {\n dropIndex = event.dragNodeSubNodes === newNodeList ? (event.dragNodeIndex > event.index ? event.index : event.index - 1) : event.index;\n newNodeList.splice(dropIndex, 0, event.dragNode);\n } else {\n dropIndex = newNodeList.length;\n newNodeList.push(event.dragNode);\n }\n\n this.tree.dragDropService.stopDrag({\n node: event.dragNode,\n subNodes: event.dropNode.parent ? event.dropNode.parent.children : this.tree.value,\n index: event.dragNodeIndex\n });\n }\n\n createDropPointEventMetadata(position: number) {\n return {\n dragNode: this.tree.dragNode,\n dragNodeIndex: this.tree.dragNodeIndex,\n dragNodeSubNodes: this.tree.dragNodeSubNodes,\n dropNode: this.node,\n index: this.index,\n position: position\n };\n }\n\n onDropPointDragOver(event: any) {\n event.dataTransfer.dropEffect = 'move';\n event.preventDefault();\n }\n\n onDropPointDragEnter(event: Event, position: number) {\n if (this.tree.allowDrop(<TreeNode>this.tree.dragNode, <TreeNode>this.node, this.tree.dragNodeScope)) {\n if (position < 0) this.draghoverPrev = true;\n else this.draghoverNext = true;\n }\n }\n\n onDropPointDragLeave(event: Event) {\n this.draghoverPrev = false;\n this.draghoverNext = false;\n }\n\n onDragStart(event: any) {\n if (this.tree.draggableNodes && (<TreeNode>this.node).draggable !== false) {\n event.dataTransfer.setData('text', 'data');\n\n this.tree.dragDropService.startDrag({\n tree: this,\n node: this.node,\n subNodes: this.node?.parent ? this.node.parent.children : this.tree.value,\n index: this.index,\n scope: this.tree.draggableScope\n });\n } else {\n event.preventDefault();\n }\n }\n\n onDragStop(event: any) {\n this.tree.dragDropService.stopDrag({\n node: this.node,\n subNodes: this.node?.parent ? this.node.parent.children : this.tree.value,\n index: this.index\n });\n }\n\n onDropNodeDragOver(event: any) {\n event.dataTransfer.dropEffect = 'move';\n if (this.tree.droppableNodes) {\n event.preventDefault();\n event.stopPropagation();\n }\n }\n\n onDropNode(event: any) {\n if (this.tree.droppableNodes && this.node?.droppable !== false) {\n let dragNode = this.tree.dragNode;\n\n if (this.tree.allowDrop(<TreeNode>dragNode, <TreeNode>this.node, this.tree.dragNodeScope)) {\n let dropParams = { ...this.createDropNodeEventMetadata() };\n\n if (this.tree.validateDrop) {\n this.tree.onNodeDrop.emit({\n originalEvent: event,\n dragNode: dragNode,\n dropNode: this.node,\n index: this.index,\n accept: () => {\n this.processNodeDrop(dropParams);\n }\n });\n } else {\n this.processNodeDrop(dropParams);\n this.tree.onNodeDrop.emit({\n originalEvent: event,\n dragNode: dragNode,\n dropNode: this.node,\n index: this.index\n });\n }\n }\n }\n\n event.preventDefault();\n event.stopPropagation();\n this.draghoverNode = false;\n }\n\n createDropNodeEventMetadata() {\n return {\n dragNode: this.tree.dragNode,\n dragNodeIndex: this.tree.dragNodeIndex,\n dragNodeSubNodes: this.tree.dragNodeSubNodes,\n dropNode: this.node\n };\n }\n\n processNodeDrop(event: any) {\n let dragNodeIndex = event.dragNodeIndex;\n event.dragNodeSubNodes.splice(dragNodeIndex, 1);\n\n if (event.dropNode.children) event.dropNode.children.push(event.dragNode);\n else event.dropNode.children = [event.dragNode];\n\n this.tree.dragDropService.stopDrag({\n node: event.dragNode,\n subNodes: event.dropNode.parent ? event.dropNode.parent.children : this.tree.value,\n index: dragNodeIndex\n });\n }\n\n onDropNodeDragEnter(event: any) {\n if (this.tree.droppableNodes && this.node?.droppable !== false && this.tree.allowDrop(<TreeNode>this.tree.dragNode, <TreeNode>this.node, this.tree.dragNodeScope)) {\n this.draghoverNode = true;\n }\n }\n\n onDropNodeDragLeave(event: any) {\n if (this.tree.droppableNodes) {\n let rect = event.currentTarget.getBoundingClientRect();\n if (event.x > rect.left + rect.width || event.x < rect.left || event.y >= Math.floor(rect.top + rect.height) || event.y < rect.top) {\n this.draghoverNode = false;\n }\n }\n }\n\n onKeyDown(event: KeyboardEvent) {\n if (!this.isSameNode(event) || (this.tree.contextMenu && this.tree.contextMenu.containerViewChild?.nativeElement.style.display === 'block')) {\n return;\n }\n\n switch (event.code) {\n //down arrow\n case 'ArrowDown':\n this.onArrowDown(event);\n break;\n\n //up arrow\n case 'ArrowUp':\n this.onArrowUp(event);\n break;\n\n //right arrow\n case 'ArrowRight':\n this.onArrowRight(event);\n break;\n\n //left arrow\n case 'ArrowLeft':\n this.onArrowLeft(event);\n break;\n\n //enter\n case 'Enter':\n case 'Space':\n case 'NumpadEnter':\n this.onEnter(event);\n break;\n //tab\n case 'Tab':\n this.setAllNodesTabIndexes();\n break;\n\n default:\n //no op\n break;\n }\n }\n\n onArrowUp(event: KeyboardEvent) {\n const nodeElement = (<HTMLDivElement>event.target).getAttribute('data-pc-section') === 'toggler' ? (<HTMLDivElement>event.target).closest('[role=\"treeitem\"]') : (<HTMLDivElement>event.target).parentElement;\n\n if (nodeElement.previousElementSibling) {\n this.focusRowChange(nodeElement, nodeElement.previousElementSibling, this.findLastVisibleDescendant(nodeElement.previousElementSibling));\n } else {\n let parentNodeElement = this.getParentNodeElement(nodeElement);\n\n if (parentNodeElement) {\n this.focusRowChange(nodeElement, parentNodeElement);\n }\n }\n\n event.preventDefault();\n }\n\n onArrowDown(event: KeyboardEvent) {\n const nodeElement = (<HTMLDivElement>event.target).getAttribute('data-pc-section') === 'toggler' ? (<HTMLDivElement>event.target).closest('[role=\"treeitem\"]') : <HTMLDivElement>event.target;\n const listElement = nodeElement.children[1];\n\n if (listElement && listElement.children.length > 0) {\n this.focusRowChange(nodeElement, listElement.children[0]);\n } else {\n if (nodeElement.parentElement.nextElementSibling) {\n this.focusRowChange(nodeElement, nodeElement.parentElement.nextElementSibling);\n } else {\n let nextSiblingAncestor = this.findNextSiblingOfAncestor(nodeElement.parentElement);\n\n if (nextSiblingAncestor) {\n this.focusRowChange(nodeElement, nextSiblingAncestor);\n }\n }\n }\n event.preventDefault();\n }\n\n onArrowRight(event: KeyboardEvent) {\n if (!this.node?.expanded && !this.tree.isNodeLeaf(<TreeNode>this.node)) {\n this.expand(event);\n (<HTMLDivElement>event.currentTarget).tabIndex = -1;\n\n setTimeout(() => {\n this.onArrowDown(event);\n }, 1);\n }\n event.preventDefault();\n }\n\n onArrowLeft(event: KeyboardEvent) {\n const nodeElement = (<HTMLDivElement>event.target).getAttribute('data-pc-section') === 'toggler' ? (<HTMLDivElement>event.target).closest('[role=\"treeitem\"]') : <HTMLDivElement>event.target;\n\n if (this.level === 0 && !this.node?.expanded) {\n return false;\n }\n\n if (this.node?.expanded) {\n this.collapse(event);\n return;\n }\n\n let parentNodeElement = this.getParentNodeElement(nodeElement.parentElement);\n\n if (parentNodeElement) {\n this.focusRowChange(event.currentTarget, parentNodeElement);\n }\n\n event.preventDefault();\n }\n\n onEnter(event: KeyboardEvent) {\n this.tree.onNodeClick(event, <TreeNode>this.node);\n this.setTabIndexForSelectionMode(event, this.tree.nodeTouched);\n event.preventDefault();\n }\n\n setAllNodesTabIndexes() {\n const nodes = <any>find(this.tree.el.nativeElement, '.p-tree-node');\n\n const hasSelectedNode = [...nodes].some((node) => node.getAttribute('aria-selected') === 'true' || node.getAttribute('aria-checked') === 'true');\n\n [...nodes].forEach((node) => {\n node.tabIndex = -1;\n });\n\n if (hasSelectedNode) {\n const selectedNodes = [...nodes].filter((node) => node.getAttribute('aria-selected') === 'true' || node.getAttribute('aria-checked') === 'true');\n\n selectedNodes[0].tabIndex = 0;\n\n return;\n }\n\n if (nodes.length) {\n ([...nodes][0] as any).tabIndex = 0;\n }\n }\n\n setTabIndexForSelectionMode(event, nodeTouched) {\n if (this.tree.selectionMode !== null) {\n const elements = [...find(this.tree.el.nativeElement, '[role=\"treeitem\"]')];\n\n event.currentTarget.tabIndex = nodeTouched === false ? -1 : 0;\n\n if (elements.every((element: any) => element.tabIndex === -1)) {\n (elements[0] as any).tabIndex = 0;\n }\n }\n }\n\n findNextSiblingOfAncestor(nodeElement: any): any {\n let parentNodeElement = this.getParentNodeElement(nodeElement);\n\n if (parentNodeElement) {\n if (parentNodeElement.nextElementSibling) return parentNodeElement.nextElementSibling;\n else return this.findNextSiblingOfAncestor(parentNodeElement);\n } else {\n return null;\n }\n }\n\n findLastVisibleDescendant(nodeElement: any): any {\n const listElement = <HTMLElement>Array.from(nodeElement.children).find((el: any) => hasClass(el, 'p-tree-node'));\n const childrenListElement = listElement?.children[1];\n if (childrenListElement && childrenListElement.children.length > 0) {\n const lastChildElement = childrenListElement.children[childrenListElement.children.length - 1];\n\n return this.findLastVisibleDescendant(lastChildElement);\n } else {\n return nodeElement;\n }\n }\n\n getParentNodeElement(nodeElement: HTMLElement | Element) {\n const parentNodeElement = nodeElement.parentElement?.parentElement?.parentElement;\n\n return parentNodeElement?.tagName === 'P-TREENODE' ? parentNodeElement : null;\n }\n\n focusNode(element: any) {\n if (this.tree.droppableNodes) (element.children[1] as HTMLElement).focus();\n else (element.children[0] as HTMLElement).focus();\n }\n\n focusRowChange(firstFocusableRow, currentFocusedRow, lastVisibleDescendant?) {\n firstFocusableRow.tabIndex = '-1';\n currentFocusedRow.children[0].tabIndex = '0';\n\n this.focusNode(lastVisibleDescendant || currentFocusedRow);\n }\n\n focusVirtualNode() {\n this.timeout = setTimeout(() => {\n let node = <any>findSingle(document.body, `[data-id=\"${<TreeNode>this.node?.key ?? <TreeNode>this.node?.data}\"]`);\n focus(node);\n }, 1);\n }\n}\n/**\n * Tree is used to display hierarchical data.\n * @group Components\n */\n@Component({\n selector: 'p-tree',\n standalone: true,\n imports: [CommonModule, Scroller, SharedModule, SearchIcon, SpinnerIcon, InputText, FormsModule, IconField, InputIcon, UITreeNode, AutoFocusModule],\n template: `\n <div [ngClass]=\"containerClass\" [ngStyle]=\"style\" [class]=\"styleClass\" (drop)=\"onDrop($event)\" (dragover)=\"onDragOver($event)\" (dragenter)=\"onDragEnter()\" (dragleave)=\"onDragLeave($event)\">\n <div class=\"p-tree-mask p-overlay-mask\" *ngIf=\"loading && loadingMode === 'mask'\">\n <i *ngIf=\"loadingIcon\" [class]=\"'p-tree-loading-icon pi-spin ' + loadingIcon\"></i>\n <ng-container *ngIf=\"!loadingIcon\">\n <SpinnerIcon *ngIf=\"!loadingIconTemplate && !_loadingIconTemplate\" [spin]=\"true\" [styleClass]=\"'p-tree-loading-icon'\" />\n <span *ngIf=\"loadingIconTemplate || _loadingIconTemplate\" class=\"p-tree-loading-icon\">\n <ng-template *ngTemplateOutlet=\"loadingIconTemplate || _loadingIconTemplate\"></ng-template>\n </span>\n </ng-container>\n </div>\n <ng-container *ngTemplateOutlet=\"headerTemplate || _headerTemplate\"></ng-container>\n @if (filterTemplate || _filterTemplate) {\n <ng-container *ngTemplateOutlet=\"filterTemplate || _filterTemplate; context: { $implicit: filterOptions }\"></ng-container>\n } @else {\n <p-iconField *ngIf=\"filter\">\n <input\n #filter\n [pAutoFocus]=\"filterInputAutoFocus\"\n pInputText\n type=\"search\"\n autocomplete=\"off\"\n class=\"p-tree-filter-input\"\n [attr.placeholder]=\"filterPlaceholder\"\n (keydown.enter)=\"$event.preventDefault()\"\n (input)=\"_filter($event.target.value)\"\n />\n <p-inputIcon>\n <SearchIcon *ngIf=\"!filterIconTemplate && !_filterIconTemplate\" class=\"p-tree-filter-icon\" />\n <span *ngIf=\"filterIconTemplate || _filterIconTemplate\">\n <ng-template *ngTemplateOutlet=\"filterIconTemplate || _filterIconTemplate\"></ng-template>\n </span>\n </p-inputIcon>\n </p-iconField>\n }\n\n <ng-container *ngIf=\"getRootNode()?.length\">\n <p-scroller\n #scroller\n *ngIf=\"virtualScroll\"\n [items]=\"serializedValue\"\n [tabindex]=\"-1\"\n styleClass=\"p-tree-root\"\n [style]=\"{ height: scrollHeight !== 'flex' ? scrollHeight : undefined }\"\n [scrollHeight]=\"scrollHeight !== 'flex' ? undefined : '100%'\"\n [itemSize]=\"virtualScrollItemSize || _virtualNodeHeight\"\n [lazy]=\"lazy\"\n (onScroll)=\"onScroll.emit($event)\"\n (onScrollIndexChange)=\"onScrollIndexChange.emit($event)\"\n (onLazyLoad)=\"onLazyLoad.emit($event)\"\n [options]=\"virtualScrollOptions\"\n >\n <ng-template #content let-items let-scrollerOptions=\"options\">\n <ul *ngIf=\"items\" class=\"p-tree-root-children\" [ngClass]=\"scrollerOptions.contentStyleClass\" [style]=\"scrollerOptions.contentStyle\" role=\"tree\" [attr.aria-label]=\"ariaLabel\" [attr.aria-labelledby]=\"ariaLabelledBy\">\n <p-treeNode\n #treeNode\n *ngFor=\"let rowNode of items; let firstChild = first; let lastChild = last; let index = index; trackBy: trackBy\"\n [level]=\"rowNode.level\"\n [rowNode]=\"rowNode\"\n [node]=\"rowNode.node\"\n [parentNode]=\"rowNode.parent\"\n [firstChild]=\"firstChild\"\n [lastChild]=\"lastChild\"\n [index]=\"getIndex(scrollerOptions, index)\"\n [itemSize]=\"scrollerOptions.itemSize\"\n [indentation]=\"indentation\"\n [loadingMode]=\"loadingMode\"\n ></p-treeNode>\n </ul>\n </ng-template>\n <ng-container *ngIf=\"loaderTemplate || _loaderTemplate\">\n <ng-template #loader let-scrollerOptions=\"options\">\n <ng-container *ngTemplateOutlet=\"loaderTemplate || _loaderTemplate; context: { options: scrollerOptions }\"></ng-container>\n </ng-template>\n </ng-container>\n </p-scroller>\n <ng-container *ngIf=\"!virtualScroll\">\n <div #wrapper class=\"p-tree-root\" [style.max-height]=\"scrollHeight\">\n <ul class=\"p-tree-root-children\" *ngIf=\"getRootNode()\" role=\"tree\" [attr.aria-label]=\"ariaLabel\" [attr.aria-labelledby]=\"ariaLabelledBy\">\n <p-treeNode\n *ngFor=\"let node of getRootNode(); let firstChild = first; let lastChild = last; let index = index; trackBy: trackBy.bind(this)\"\n [node]=\"node\"\n [firstChild]=\"firstChild\"\n [lastChild]=\"lastChild\"\n [index]=\"index\"\n [level]=\"0\"\n [loadingMode]=\"loadingMode\"\n ></p-treeNode>\n </ul>\n </div>\n </ng-container>\n </ng-container>\n\n <div class=\"p-tree-empty-message\" *ngIf=\"!loading && (getRootNode() == null || getRootNode().length === 0)\">\n <ng-container *ngIf=\"!emptyMessageTemplate && !_emptyMessageTemplate; else emptyFilter\">\n {{ emptyMessageLabel }}\n </ng-container>\n <ng-template #emptyFilter *ngTemplateOutlet=\"emptyMessageTemplate || _emptyMessageTemplate\"></ng-template>\n </div>\n <ng-container *ngTemplateOutlet=\"footerTemplate || _footerTemplate\"></ng-container>\n </div>\n `,\n changeDetection: ChangeDetectionStrategy.Default,\n encapsulation: ViewEncapsulation.None,\n providers: [TreeStyle]\n})\nexport class Tree extends BaseComponent implements OnInit, AfterContentInit, OnChanges, OnDestroy, BlockableUI {\n /**\n * An array of treenodes.\n * @group Props\n */\n @Input() value: TreeNode<any> | TreeNode<any>[] | any[] | any;\n /**\n * Defines the selection mode.\n * @group Props\n */\n @Input() selectionMode: 'single' | 'multiple' | 'checkbox' | null | undefined;\n /**\n * Loading mode display.\n * @group Props\n */\n @Input() loadingMode: 'mask' | 'icon' = 'mask';\n /**\n * A single treenode instance or an array to refer to the selections.\n * @group Props\n */\n @Input() selection: any;\n /**\n * Inline style of the component.\n * @group Props\n */\n @Input() style: { [klass: string]: any } | null | undefined;\n /**\n * Style class of the component.\n * @group Props\n */\n @Input() styleClass: string | undefined;\n /**\n * Context menu instance.\n * @group Props\n */\n @Input() contextMenu: any;\n /**\n * Scope of the draggable nodes to match a droppableScope.\n * @group Props\n */\n @Input() draggableScope: any;\n /**\n * Scope of the droppable nodes to match a draggableScope.\n * @group Props\n */\n @Input() droppableScope: any;\n /**\n * Whether the nodes are draggable.\n * @group Props\n */\n @Input({ transform: booleanAttribute }) draggableNodes: boolean | undefined;\n /**\n * Whether the nodes are droppable.\n * @group Props\n */\n @Input({ transform: booleanAttribute }) droppableNodes: boolean | undefined;\n /**\n * 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.\n * @group Props\n */\n @Input({ transform: booleanAttribute }) metaKeySelection: boolean = false;\n /**\n * Whether checkbox selections propagate to ancestor nodes.\n * @group Props\n */\n @Input({ transform: booleanAttribute }) propagateSelectionUp: boolean = true;\n /**\n * Whether checkbox selections propagate to descendant nodes.\n * @group Props\n */\n @Input({ transform: booleanAttribute }) propagateSelectionDown: boolean = true;\n /**\n * Displays a loader to indicate data load is in progress.\n * @group Props\n */\n @Input({ transform: booleanAttribute }) loading: boolean | undefined;\n /**\n * The icon to show while indicating data load is in progress.\n * @group Props\n */\n @Input() loadingIcon: string | undefined;\n /**\n * Text to display when there is no data.\n * @group Props\n */\n @Input() emptyMessage: string = '';\n /**\n * Used to define a string that labels the tree.\n * @group Props\n */\n @Input() ariaLabel: string | undefined;\n /**\n * Defines a string that labels the toggler icon for accessibility.\n * @group Props\n */\n @Input() togglerAriaLabel: string | undefined;\n /**\n * Establishes relationships between the component and label(s) where its value should be one or more element IDs.\n * @group Props\n */\n @Input() ariaLabelledBy: string | undefined;\n /**\n * When enabled, drop can be accepted or rejected based on condition defined at onNodeDrop.\n * @group Props\n */\n @Input({ transform: booleanAttribute }) validateDrop: boolean | undefined;\n /**\n * When specified, displays an input field to filter the items.\n * @group Props\n */\n @Input({ transform: booleanAttribute }) filter: boolean | undefined;\n /**\n * Determines whether the filter input should be automatically focused when the component is rendered.\n * @group Props\n */\n @Input({ transform: booleanAttribute }) filterInputAutoFocus: boolean = false;\n /**\n * When filtering is enabled, filterBy decides which field or fields (comma separated) to search against.\n * @group Props\n */\n @Input() filterBy: string = 'label';\n /**\n * Mode for filtering valid values are \"lenient\" and \"strict\". Default is lenient.\n * @group Props\n */\n @Input() filterMode: string = 'lenient';\n /**\n * Mode for filtering valid values are \"lenient\" and \"strict\". Default is lenient.\n * @group Props\n */\n @Input() filterOptions: any;\n /**\n * Placeholder text to show when filter input is empty.\n * @group Props\n */\n @Input() filterPlaceholder: string | undefined;\n /**\n * Values after the tree nodes are filtered.\n * @group Props\n */\n @Input() filteredNodes: TreeNode<any>[] | undefined | null;\n /**\n * Locale to use in filtering. The default locale is the host environment's current locale.\n * @group Props\n */\n @Input() filterLocale: string | undefined;\n /**\n * Height of the scrollable viewport.\n * @group Props\n */\n @Input() scrollHeight: string | undefined;\n /**\n * Defines if data is loaded and interacted with in lazy manner.\n * @group Props\n */\n @Input({ transform: booleanAttribute }) lazy: boolean = false;\n /**\n * Whether the data should be loaded on demand during scroll.\n * @group Props\n */\n @Input({ transform: booleanAttribute }) virtualScroll: boolean | undefined;\n /**\n * Height of an item in the list for VirtualScrolling.\n * @group Props\n */\n @Input({ transform: numberAttribute }) virtualScrollItemSize: number | undefined;\n /**\n * Whether to use the scroller feature. The properties of scroller component can be used like an object in it.\n * @group Props\n */\n @Input() virtualScrollOptions: ScrollerOptions | undefined;\n /**\n * Indentation factor for spacing of the nested node when virtual scrolling is enabled.\n * @group Props\n */\n @Input({ transform: numberAttribute }) indentation: number = 1.5;\n /**\n * Custom templates of the component.\n * @group Props\n */\n @Input() _templateMap: any;\n /**\n * Function to optimize the node list rendering, default algorithm checks for object identity.\n * @group Props\n */\n @Input()