UNPKG

primeng

Version:

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

1,016 lines (1,008 loc) 125 kB
export * from 'primeng/types/tree'; import { NgTemplateOutlet } from '@angular/common'; import * as i0 from '@angular/core'; import { InjectionToken, Injectable, inject, input, booleanAttribute, signal, computed, ViewEncapsulation, ChangeDetectionStrategy, Component, model, numberAttribute, output, contentChild, viewChild, DestroyRef, effect, untracked, NgModule } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { getOuterWidth, getOuterHeight, find, findSingle, focus, removeAccents, resolveFieldData } from '@primeuix/utils'; import { TreeDragDropService, TranslationKeys } from 'primeng/api'; import * as i2 from 'primeng/autofocus'; import { AutoFocusModule } from 'primeng/autofocus'; import { BaseComponent, PARENT_INSTANCE } from 'primeng/basecomponent'; import * as i1$1 from 'primeng/bind'; import { Bind } from 'primeng/bind'; import { IconField } from 'primeng/iconfield'; import { Search } from '@primeicons/angular/search'; import { Spinner } from '@primeicons/angular/spinner'; import { InputIcon } from 'primeng/inputicon'; import { InputText } from 'primeng/inputtext'; import { Scroller } from 'primeng/scroller'; import * as i1 from '@angular/forms'; import { FormsModule } from '@angular/forms'; import { Checkbox } from 'primeng/checkbox'; import { ChevronDown } from '@primeicons/angular/chevron-down'; import { ChevronRight } from '@primeicons/angular/chevron-right'; import { Ripple } from 'primeng/ripple'; import { style } from '@primeuix/styles/tree'; import { BaseStyle } from 'primeng/base'; const TREE_INSTANCE = new InjectionToken('TREE_INSTANCE'); const TREENODE_INSTANCE = new InjectionToken('TREENODE_INSTANCE'); const classes = { root: ({ instance }) => [ 'p-tree p-component', { 'p-tree-selectable': instance.selectionMode() != null, 'p-tree-loading': instance.loading(), 'p-tree-flex-scrollable': instance.scrollHeight() === 'flex', 'p-tree-node-dragover': instance.dragHover } ], mask: 'p-tree-mask p-overlay-mask', loadingIcon: 'p-tree-loading-icon', pcFilterInput: 'p-tree-filter-input', wrapper: 'p-tree-root', rootChildren: 'p-tree-root-children', node: ({ instance }) => ({ 'p-tree-node': true, 'p-tree-node-leaf': instance.isLeaf() }), nodeContent: ({ instance }) => ({ 'p-tree-node-content': true, 'p-tree-node-selectable': instance.selectable, 'p-tree-node-dragover': instance.isNodeDropActive(), 'p-tree-node-selected': instance.tree.selectionMode() === 'checkbox' && instance.tree.highlightOnSelect() ? instance.checked : instance.selected, 'p-tree-node-contextmenu-selected': instance.isContextMenuSelected() }), nodeToggleButton: 'p-tree-node-toggle-button', nodeToggleIcon: 'p-tree-node-toggle-icon', nodeCheckbox: 'p-tree-node-checkbox', nodeIcon: 'p-tree-node-icon', nodeLabel: 'p-tree-node-label', nodeChildren: 'p-tree-node-children', emptyMessage: 'p-tree-empty-message', dropPoint: 'p-tree-node-drop-point' }; class TreeStyle extends BaseStyle { name = 'tree'; style = style; classes = classes; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: TreeStyle, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: TreeStyle }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: TreeStyle, decorators: [{ type: Injectable }] }); /** * * Tree is used to display hierarchical data. * * [Live Demo](https://www.primeng.org/tree/) * * @module treestyle * */ var TreeClasses; (function (TreeClasses) { /** * Class name of the root element */ TreeClasses["root"] = "p-tree"; /** * Class name of the mask element */ TreeClasses["mask"] = "p-tree-mask"; /** * Class name of the loading icon element */ TreeClasses["loadingIcon"] = "p-tree-loading-icon"; /** * Class name of the filter input element */ TreeClasses["pcFilterInput"] = "p-tree-filter-input"; /** * Class name of the wrapper element */ TreeClasses["wrapper"] = "p-tree-root"; /** * Class name of the root children element */ TreeClasses["rootChildren"] = "p-tree-root-children"; /** * Class name of the node element */ TreeClasses["node"] = "p-tree-node"; /** * Class name of the node content element */ TreeClasses["nodeContent"] = "p-tree-node-content"; /** * Class name of the node toggle button element */ TreeClasses["nodeToggleButton"] = "p-tree-node-toggle-button"; /** * Class name of the node toggle icon element */ TreeClasses["nodeToggleIcon"] = "p-tree-node-toggle-icon"; /** * Class name of the node checkbox element */ TreeClasses["nodeCheckbox"] = "p-tree-node-checkbox"; /** * Class name of the node icon element */ TreeClasses["nodeIcon"] = "p-tree-node-icon"; /** * Class name of the node label element */ TreeClasses["nodeLabel"] = "p-tree-node-label"; /** * Class name of the node children element */ TreeClasses["nodeChildren"] = "p-tree-node-children"; /** * Class name of the empty message element */ TreeClasses["emptyMessage"] = "p-tree-empty-message"; /** * Class name of the drop point element */ TreeClasses["dropPoint"] = "p-tree-node-droppoint"; })(TreeClasses || (TreeClasses = {})); class UITreeNode extends BaseComponent { $pcTreeNode = inject(TREENODE_INSTANCE, { optional: true, skipSelf: true }) ?? undefined; static ICON_CLASS = 'p-tree-node-icon '; rowNode = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "rowNode" }] : /* istanbul ignore next */ [])); node = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "node" }] : /* istanbul ignore next */ [])); parentNode = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "parentNode" }] : /* istanbul ignore next */ [])); root = input(false, { ...(ngDevMode ? { debugName: "root" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); index = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "index" }] : /* istanbul ignore next */ [])); firstChild = input(false, { ...(ngDevMode ? { debugName: "firstChild" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); lastChild = input(false, { ...(ngDevMode ? { debugName: "lastChild" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); level = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "level" }] : /* istanbul ignore next */ [])); indentation = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "indentation" }] : /* istanbul ignore next */ [])); itemSize = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "itemSize" }] : /* istanbul ignore next */ [])); loadingMode = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "loadingMode" }] : /* istanbul ignore next */ [])); tree = inject(TREE_INSTANCE); timeout; isPrevDropPointHovered = signal(false, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "isPrevDropPointHovered" }] : /* istanbul ignore next */ [])); isNextDropPointHovered = signal(false, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "isNextDropPointHovered" }] : /* istanbul ignore next */ [])); isNodeDropHovered = signal(false, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "isNodeDropHovered" }] : /* istanbul ignore next */ [])); isPrevDropPointActive = computed(() => this.isPrevDropPointHovered() && this.isDroppable(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "isPrevDropPointActive" }] : /* istanbul ignore next */ [])); isNextDropPointActive = computed(() => this.isNextDropPointHovered() && this.isDroppable(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "isNextDropPointActive" }] : /* istanbul ignore next */ [])); isNodeDropActive = computed(() => this.isNodeDropHovered() && this.isNodeDroppable(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "isNodeDropActive" }] : /* istanbul ignore next */ [])); dropPosition = computed(() => (this.isPrevDropPointActive() ? -1 : this.isNextDropPointActive() ? 1 : 0), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "dropPosition" }] : /* istanbul ignore next */ [])); get ariaSetSize() { return this.node()?.children?.length ?? 0; } get ariaExpanded() { return this.node()?.expanded; } ariaPosInSet = computed(() => (this.index() ?? 0) + 1, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "ariaPosInSet" }] : /* istanbul ignore next */ [])); ariaLevel = computed(() => (this.level() ?? 0) + 1, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "ariaLevel" }] : /* istanbul ignore next */ [])); nodeTabindex = computed(() => (this.index() === 0 ? 0 : -1), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "nodeTabindex" }] : /* istanbul ignore next */ [])); nodeContentPaddingLeft = computed(() => this.level() * this.indentation() + 'rem', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "nodeContentPaddingLeft" }] : /* istanbul ignore next */ [])); get hasNodeIcon() { return !!(this.node()?.icon || this.node()?.expandedIcon || this.node()?.collapsedIcon); } get showChildren() { return !this.tree.virtualScroll() && !!this.node()?.children && !!this.node()?.expanded; } get showLoadingSpinner() { return this.loadingMode() === 'icon' && !!this.node()?.loading; } checkboxVariant = computed(() => this.tree?.config.inputVariant() || 'outlined', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "checkboxVariant" }] : /* istanbul ignore next */ [])); get togglerIconContext() { return { $implicit: this.node()?.expanded, loading: this.node()?.loading }; } get checkboxIconContext() { return { $implicit: this.isSelected(), partialSelected: this.node()?.partialSelected, class: this.cx('nodeCheckbox') }; } _componentStyle = inject(TreeStyle); /** * Computed signal that reactively tracks selection state. */ _selected = computed(() => { // Reading selection() makes this computed reactive to selection changes this.tree.selection(); return this.tree.isSelected(this.node()); }, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "_selected" }] : /* istanbul ignore next */ [])); /** * Computed signal that reactively tracks context menu selection state. */ _contextMenuSelected = computed(() => { const selection = this.tree.contextMenuSelection(); const node = this.node(); if (!selection || !node) { return false; } return selection === node || (selection.key && selection.key === node.key); }, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "_contextMenuSelected" }] : /* istanbul ignore next */ [])); get selected() { return this.tree.selectionMode() === 'single' || this.tree.selectionMode() === 'multiple' ? this._selected() : undefined; } get checked() { return this.tree.selectionMode() === 'checkbox' ? this._selected() : undefined; } get nodeClass() { return this.tree._componentStyle.classes.node({ instance: this }); } get selectable() { return this.node()?.selectable === false ? false : this.tree?.selectionMode() != null; } get subNodes() { return this.node()?.parent ? this.node().parent.children : this.tree.value(); } /** The node this row represents in the original value; when a filter is active the rendered node is a copy. */ get originalNode() { const node = this.node(); return node && this.tree.getOriginalNode(node); } /** Sibling list of this node within the original value; when a filter is active `subNodes` holds copies. */ get originalSubNodes() { const parent = this.node()?.parent; return parent ? this.tree.getOriginalNode(parent).children : this.tree.value(); } getPTOptions(key) { return this.ptm(key, { context: { node: this.node(), index: this.index(), expanded: this.node()?.expanded, selected: this.selected, checked: this.checked, partialChecked: this.node()?.partialSelected, leaf: this.isLeaf() } }); } onInit() { this.node().parent = this.parentNode(); const nativeElement = this.tree.el.nativeElement; const pDialogWrapper = nativeElement.closest('p-dialog'); if (this.parentNode() && !pDialogWrapper) { this.setAllNodesTabIndexes(); this.tree.syncNodeOption(this.node(), this.tree.value(), 'parent', this.tree.getNodeWithKey(this.parentNode().key, this.tree.value())); } } getIcon() { let icon; const node = this.node(); if (node.icon) icon = node.icon; else icon = node.expanded && node.children && node.children?.length ? node.expandedIcon : node.collapsedIcon; return UITreeNode.ICON_CLASS + ' ' + icon + ' p-tree-node-icon'; } isLeaf() { return this.tree.isNodeLeaf(this.node()); } isSelected() { return this._selected(); } isContextMenuSelected() { return this._contextMenuSelected(); } isSameNode(event) { return event.currentTarget && (event.currentTarget.isSameNode(event.target) || event.currentTarget.isSameNode(event.target.closest('[role="treeitem"]'))); } isDraggable() { return this.tree.draggableNodes(); } isDroppable() { return this.tree.droppableNodes() && this.tree.allowDrop(this.tree.dragNode, this.node(), this.tree.dragNodeScope); } isNodeDroppable() { return this.node()?.droppable !== false && this.isDroppable(); } isNodeDraggable() { return this.node()?.draggable !== false && this.isDraggable(); } toggle(event) { if (this.node().expanded) this.collapse(event); else this.expand(event); event.stopPropagation(); } expand(event) { this.node().expanded = true; this.tree.setNodeCollapsed(this.node(), false); if (this.tree.virtualScroll()) { this.tree.updateSerializedValue(); this.focusVirtualNode(); } this.tree.onNodeExpand.emit({ originalEvent: event, node: this.node() }); } collapse(event) { this.node().expanded = false; this.tree.setNodeCollapsed(this.node(), true); if (this.tree.virtualScroll()) { this.tree.updateSerializedValue(); } this.tree.onNodeCollapse.emit({ originalEvent: event, node: this.node() }); this.focusVirtualNode(); } onNodeClick(event) { this.tree.onNodeClick(event, this.node()); } onNodeKeydown(event) { if (event.key === 'Enter') { this.tree.onNodeClick(event, this.node()); } } onNodeTouchEnd() { this.tree.onNodeTouchEnd(); } onNodeRightClick(event) { this.tree.onNodeRightClick(event, this.node()); } onNodeDblClick(event) { this.tree.onNodeDblClick(event, this.node()); } /** * The drag state and drop position are taken as arguments (captured at drop time) since * `accept` may be invoked asynchronously, after `dragend` has cleared the drag state * and the drop point hover signals were reset. */ insertNodeOnDrop(dragNode, dragNodeSubNodes, position) { // The drag state holds original nodes; resolve the drop side to originals as well so the // move is applied to the original value even when a filtered copy is rendered. const dropNode = this.originalNode; if (!dropNode || !dragNode || !dragNodeSubNodes) { return; } const subNodes = this.originalSubNodes || []; // With virtual scrolling the `index` input holds the position in the flattened list, // so resolve both positions against the actual sibling arrays instead. const dragNodeIndex = dragNodeSubNodes.indexOf(dragNode); const index = subNodes.indexOf(dropNode); const dropIndex = dragNodeSubNodes === subNodes ? (dragNodeIndex > index ? index : index - 1) : index; if (dragNodeIndex !== -1) { dragNodeSubNodes.splice(dragNodeIndex, 1); } if (position < 0) { // insert before a Node subNodes.splice(dropIndex, 0, dragNode); } else if (position > 0) { // insert after a Node subNodes.splice(dropIndex + 1, 0, dragNode); } else { // insert as child of a Node dropNode.children = dropNode.children || []; dropNode.children.push(dragNode); } this.tree.dragDropService.stopDrag({ node: dragNode, subNodes, index: dragNodeIndex }); } onNodeDrop(event) { event.preventDefault(); event.stopPropagation(); if (this.isDroppable()) { const { dragNode, dragNodeSubNodes } = this.tree; const position = this.dropPosition(); const isValidDrop = position !== 0 || (position === 0 && this.isNodeDroppable()); const dropPoint = position === 0 ? 'node' : 'between'; if (isValidDrop) { if (this.tree.validateDrop()) { this.tree.onNodeDrop.emit({ originalEvent: event, dragNode: dragNode, dropNode: this.originalNode, index: this.index(), dropPoint: dropPoint, accept: () => { this.insertNodeOnDrop(dragNode, dragNodeSubNodes, position); } }); } else { this.insertNodeOnDrop(dragNode, dragNodeSubNodes, position); this.tree.onNodeDrop.emit({ originalEvent: event, dragNode: dragNode, dropNode: this.originalNode, index: this.index(), dropPoint: dropPoint }); } } } this.isPrevDropPointHovered.set(false); this.isNextDropPointHovered.set(false); this.isNodeDropHovered.set(false); } onNodeDragStart(event) { if (this.isNodeDraggable()) { event.dataTransfer.effectAllowed = 'all'; event.dataTransfer?.setData('text', 'data'); const target = event.currentTarget; const dragEl = target.cloneNode(true); const toggler = dragEl.querySelector('[data-pc-section="nodetogglebutton"]'); const checkbox = dragEl.querySelector('[data-pc-name="pcnodecheckbox"]'); target.setAttribute('data-p-dragging', 'true'); dragEl.style.width = getOuterWidth(target) + 'px'; dragEl.style.height = getOuterHeight(target) + 'px'; dragEl.setAttribute('data-pc-section', 'drag-image'); toggler.style.visibility = 'hidden'; checkbox?.remove(); document.body.appendChild(dragEl); event.dataTransfer?.setDragImage(dragEl, 0, 0); setTimeout(() => document.body.removeChild(dragEl), 0); this.tree.dragDropService.startDrag({ tree: this, node: this.originalNode, subNodes: this.originalSubNodes, index: this.index(), scope: this.tree.draggableScope() }); } else { event.preventDefault(); } } onNodeDragOver(event) { if (this.isDroppable()) { event.dataTransfer.dropEffect = 'copy'; const nodeElement = event.currentTarget; const rect = nodeElement.getBoundingClientRect(); const y = event.clientY - parseInt(rect.top); this.isPrevDropPointHovered.set(false); this.isNextDropPointHovered.set(false); this.isNodeDropHovered.set(false); if (y < rect.height * 0.25) { this.isPrevDropPointHovered.set(true); } else if (y > rect.height * 0.75) { this.isNextDropPointHovered.set(true); } else if (this.isNodeDroppable()) { this.isNodeDropHovered.set(true); } } else { event.dataTransfer.dropEffect = 'none'; } if (this.tree.droppableNodes()) { event.preventDefault(); event.stopPropagation(); } } onNodeDragLeave() { this.isPrevDropPointHovered.set(false); this.isNextDropPointHovered.set(false); this.isNodeDropHovered.set(false); } onNodeDragEnd(event) { event.currentTarget?.removeAttribute('data-p-dragging'); this.tree.dragDropService.stopDrag({ node: this.originalNode, subNodes: this.originalSubNodes, index: this.index() }); } onKeyDown(event) { if (!this.isSameNode(event) || (this.tree.contextMenu() && this.tree.contextMenu().containerViewChild?.nativeElement.style.display === 'block')) { return; } switch (event.code) { //down arrow case 'ArrowDown': this.onArrowDown(event); break; //up arrow case 'ArrowUp': this.onArrowUp(event); break; //right arrow case 'ArrowRight': this.onArrowRight(event); break; //left arrow case 'ArrowLeft': this.onArrowLeft(event); break; //enter case 'Enter': case 'Space': case 'NumpadEnter': this.onEnter(event); break; //tab case 'Tab': this.setAllNodesTabIndexes(); break; default: //no op break; } } onArrowUp(event) { const nodeElement = event.target.getAttribute('data-pc-section') === 'nodetogglebutton' ? event.target.closest('[role="treeitem"]') : event.target.parentElement; if (nodeElement?.previousElementSibling) { this.focusRowChange(nodeElement, nodeElement.previousElementSibling, this.findLastVisibleDescendant(nodeElement.previousElementSibling)); } else { let parentNodeElement = this.getParentNodeElement(nodeElement); if (parentNodeElement) { this.focusRowChange(nodeElement, parentNodeElement); } } event.preventDefault(); } onArrowDown(event) { const nodeElement = event.target.getAttribute('data-pc-section') === 'nodetogglebutton' ? event.target.closest('[role="treeitem"]') : event.target; const listElement = nodeElement?.children[1]; if (listElement && listElement.children.length > 0) { this.focusRowChange(nodeElement, listElement.children[0]); } else { if (nodeElement?.parentElement?.nextElementSibling) { this.focusRowChange(nodeElement, nodeElement.parentElement.nextElementSibling); } else { let nextSiblingAncestor = this.findNextSiblingOfAncestor(nodeElement?.parentElement); if (nextSiblingAncestor) { this.focusRowChange(nodeElement, nextSiblingAncestor); } } } event.preventDefault(); } onArrowRight(event) { if (!this.node()?.expanded && !this.tree.isNodeLeaf(this.node())) { this.expand(event); event.currentTarget.tabIndex = -1; setTimeout(() => { this.onArrowDown(event); }, 1); } event.preventDefault(); } onArrowLeft(event) { const nodeElement = event.target.getAttribute('data-pc-section') === 'nodetogglebutton' ? event.target.closest('[role="treeitem"]') : event.target; if (this.level() === 0 && !this.node()?.expanded) { return false; } if (this.node()?.expanded) { this.collapse(event); return; } let parentNodeElement = this.getParentNodeElement(nodeElement?.parentElement); if (parentNodeElement) { this.focusRowChange(event.currentTarget, parentNodeElement); } event.preventDefault(); } onEnter(event) { this.tree.onNodeClick(event, this.node()); this.setTabIndexForSelectionMode(event, this.tree.nodeTouched); event.preventDefault(); } setAllNodesTabIndexes() { const nodes = find(this.tree.el.nativeElement, '[data-pc-section="node"]'); const hasSelectedNode = [...nodes].some((node) => node.getAttribute('aria-selected') === 'true' || node.getAttribute('aria-checked') === 'true'); [...nodes].forEach((node) => { node.tabIndex = -1; }); if (hasSelectedNode) { const selectedNodes = [...nodes].filter((node) => node.getAttribute('aria-selected') === 'true' || node.getAttribute('aria-checked') === 'true'); selectedNodes[0].tabIndex = 0; return; } if (nodes.length) { [...nodes][0].tabIndex = 0; } } setTabIndexForSelectionMode(event, nodeTouched) { if (this.tree.selectionMode() !== null) { const elements = [...find(this.tree.el.nativeElement, '[role="treeitem"]')]; event.currentTarget.tabIndex = nodeTouched === false ? -1 : 0; if (elements.every((element) => element.tabIndex === -1)) { elements[0].tabIndex = 0; } } } findNextSiblingOfAncestor(nodeElement) { let parentNodeElement = this.getParentNodeElement(nodeElement); if (parentNodeElement) { if (parentNodeElement.nextElementSibling) return parentNodeElement.nextElementSibling; else return this.findNextSiblingOfAncestor(parentNodeElement); } else { return null; } } findLastVisibleDescendant(nodeElement) { const listElement = Array.from(nodeElement.children).find((el) => el.getAttribute('data-pc-section') === 'node'); const childrenListElement = listElement?.children[1]; if (childrenListElement && childrenListElement.children.length > 0) { const lastChildElement = childrenListElement.children[childrenListElement.children.length - 1]; return this.findLastVisibleDescendant(lastChildElement); } else { return nodeElement; } } getParentNodeElement(nodeElement) { const parentNodeElement = nodeElement.parentElement?.parentElement?.parentElement; return parentNodeElement?.tagName === 'P-TREENODE' ? parentNodeElement : null; } focusNode(element) { element.children[0].focus(); } focusRowChange(firstFocusableRow, currentFocusedRow, lastVisibleDescendant) { firstFocusableRow.tabIndex = '-1'; currentFocusedRow.children[0].tabIndex = '0'; this.focusNode(lastVisibleDescendant || currentFocusedRow); } focusVirtualNode() { this.timeout = setTimeout(() => { let node = findSingle(this.tree?.contentViewChild()?.nativeElement, `[data-id="${this.node()?.key ?? this.node()?.data}"]`); focus(node); }, 1); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UITreeNode, deps: null, target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: UITreeNode, isStandalone: true, selector: "p-treenode", inputs: { rowNode: { classPropertyName: "rowNode", publicName: "rowNode", isSignal: true, isRequired: false, transformFunction: null }, node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: false, transformFunction: null }, parentNode: { classPropertyName: "parentNode", publicName: "parentNode", isSignal: true, isRequired: false, transformFunction: null }, root: { classPropertyName: "root", publicName: "root", isSignal: true, isRequired: false, transformFunction: null }, index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: false, transformFunction: null }, firstChild: { classPropertyName: "firstChild", publicName: "firstChild", isSignal: true, isRequired: false, transformFunction: null }, lastChild: { classPropertyName: "lastChild", publicName: "lastChild", isSignal: true, isRequired: false, transformFunction: null }, level: { classPropertyName: "level", publicName: "level", isSignal: true, isRequired: false, transformFunction: null }, indentation: { classPropertyName: "indentation", publicName: "indentation", isSignal: true, isRequired: false, transformFunction: null }, itemSize: { classPropertyName: "itemSize", publicName: "itemSize", isSignal: true, isRequired: false, transformFunction: null }, loadingMode: { classPropertyName: "loadingMode", publicName: "loadingMode", isSignal: true, isRequired: false, transformFunction: null } }, providers: [TreeStyle, { provide: TREENODE_INSTANCE, useExisting: UITreeNode }, { provide: PARENT_INSTANCE, useExisting: UITreeNode }], usesInheritance: true, ngImport: i0, template: ` @if (node()) { <li [class]="cn(cx('node'), node()!.styleClass)" [style.height.px]="itemSize()" [style]="node()!.style" [attr.aria-label]="node()!.label" [attr.aria-checked]="checked" [attr.aria-setsize]="ariaSetSize" [attr.aria-selected]="selected" [attr.aria-expanded]="ariaExpanded" [attr.aria-posinset]="ariaPosInSet()" [attr.aria-level]="ariaLevel()" [attr.tabindex]="nodeTabindex()" [attr.data-id]="node()!.key" role="treeitem" (keydown)="onKeyDown($event)" [pBind]="getPTOptions('node')" > @if (isPrevDropPointActive()) { <div [class]="cx('dropPoint')" [attr.aria-hidden]="true" [pBind]="getPTOptions('dropPoint')"></div> } <div [class]="cx('nodeContent')" [style.paddingLeft]="nodeContentPaddingLeft()" (click)="onNodeClick($event)" (contextmenu)="onNodeRightClick($event)" (dblclick)="onNodeDblClick($event)" (touchend)="onNodeTouchEnd()" (drop)="onNodeDrop($event)" (dragstart)="onNodeDragStart($event)" (dragover)="onNodeDragOver($event)" (dragleave)="onNodeDragLeave()" (dragend)="onNodeDragEnd($event)" [draggable]="tree.draggableNodes()" [pBind]="getPTOptions('nodeContent')" > <button type="button" [class]="cx('nodeToggleButton')" (click)="toggle($event)" pRipple tabindex="-1" [pBind]="getPTOptions('nodeToggleButton')"> @if (!tree.togglerIconTemplate()) { @if (!node()!.loading) { @if (!node()!.expanded) { <svg data-p-icon="chevron-right" [class]="cx('nodeToggleIcon')" [pBind]="getPTOptions('nodeToggleIcon')" /> } @if (node()!.expanded) { <svg data-p-icon="chevron-down" [class]="cx('nodeToggleIcon')" [pBind]="getPTOptions('nodeToggleIcon')" /> } } @if (showLoadingSpinner) { <svg data-p-icon="spinner" [class]="cx('nodeToggleIcon')" [spin]="true" [pBind]="getPTOptions('nodeToggleIcon')" /> } } @if (tree.togglerIconTemplate()) { <span [class]="cx('nodeToggleIcon')" [pBind]="getPTOptions('nodeToggleIcon')"> <ng-template *ngTemplateOutlet="tree.togglerIconTemplate()!; context: togglerIconContext"></ng-template> </span> } </button> @if (tree.selectionMode() == 'checkbox') { <p-checkbox [ngModel]="isSelected()" [class]="cx('nodeCheckbox')" [binary]="true" [indeterminate]="node()!.partialSelected" [disabled]="node()!.selectable === false" [variant]="checkboxVariant()" [attr.data-p-partialchecked]="node()!.partialSelected" [tabindex]="-1" (click)="$event.preventDefault()" [pt]="getPTOptions('pcNodeCheckbox')" [unstyled]="unstyled()" > @if (tree.checkboxIconTemplate()) { <ng-template #icon> <ng-template *ngTemplateOutlet="tree.checkboxIconTemplate()!; context: checkboxIconContext"></ng-template> </ng-template> } </p-checkbox> } @if (hasNodeIcon) { <span [class]="getIcon()" [pBind]="getPTOptions('nodeIcon')"></span> } <span [class]="cx('nodeLabel')" [pBind]="getPTOptions('nodeLabel')"> @if (!tree.getTemplateForNode(node()!)) { <span>{{ node()!.label }}</span> } @if (tree.getTemplateForNode(node()!)) { <span> <ng-container *ngTemplateOutlet="tree.getTemplateForNode(node()!)!; context: { $implicit: node() }"></ng-container> </span> } </span> </div> @if (isNextDropPointActive()) { <div [class]="cx('dropPoint', { next: true })" [attr.aria-hidden]="true" [pBind]="getPTOptions('dropPoint')"></div> } @if (showChildren) { <ul [class]="cx('nodeChildren')" role="group" [pBind]="ptm('nodeChildren')"> @for (childNode of node()!.children; track tree.trackBy()($index, childNode); let firstChild = $first; let lastChild = $last; let idx = $index) { <p-treenode [node]="childNode" [parentNode]="node()" [firstChild]="firstChild" [lastChild]="lastChild" [index]="idx" [itemSize]="itemSize()" [level]="level()! + 1" [loadingMode]="loadingMode()" [pt]="pt()" [unstyled]="unstyled()" /> } </ul> } </li> } `, isInline: true, dependencies: [{ kind: "component", type: UITreeNode, selector: "p-treenode", inputs: ["rowNode", "node", "parentNode", "root", "index", "firstChild", "lastChild", "level", "indentation", "itemSize", "loadingMode"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: Ripple, selector: "[pRipple]" }, { kind: "component", type: Checkbox, selector: "p-checkbox, p-check-box", inputs: ["value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ChevronRight, selector: "svg[data-p-icon=\"chevron-right\"]" }, { kind: "component", type: ChevronDown, selector: "svg[data-p-icon=\"chevron-down\"]" }, { kind: "component", type: Spinner, selector: "svg[data-p-icon=\"spinner\"]" }, { kind: "directive", type: Bind, selector: "[pBind]", inputs: ["pBind"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UITreeNode, decorators: [{ type: Component, args: [{ selector: 'p-treenode', standalone: true, imports: [NgTemplateOutlet, Ripple, Checkbox, FormsModule, ChevronRight, ChevronDown, Spinner, Bind], changeDetection: ChangeDetectionStrategy.OnPush, template: ` @if (node()) { <li [class]="cn(cx('node'), node()!.styleClass)" [style.height.px]="itemSize()" [style]="node()!.style" [attr.aria-label]="node()!.label" [attr.aria-checked]="checked" [attr.aria-setsize]="ariaSetSize" [attr.aria-selected]="selected" [attr.aria-expanded]="ariaExpanded" [attr.aria-posinset]="ariaPosInSet()" [attr.aria-level]="ariaLevel()" [attr.tabindex]="nodeTabindex()" [attr.data-id]="node()!.key" role="treeitem" (keydown)="onKeyDown($event)" [pBind]="getPTOptions('node')" > @if (isPrevDropPointActive()) { <div [class]="cx('dropPoint')" [attr.aria-hidden]="true" [pBind]="getPTOptions('dropPoint')"></div> } <div [class]="cx('nodeContent')" [style.paddingLeft]="nodeContentPaddingLeft()" (click)="onNodeClick($event)" (contextmenu)="onNodeRightClick($event)" (dblclick)="onNodeDblClick($event)" (touchend)="onNodeTouchEnd()" (drop)="onNodeDrop($event)" (dragstart)="onNodeDragStart($event)" (dragover)="onNodeDragOver($event)" (dragleave)="onNodeDragLeave()" (dragend)="onNodeDragEnd($event)" [draggable]="tree.draggableNodes()" [pBind]="getPTOptions('nodeContent')" > <button type="button" [class]="cx('nodeToggleButton')" (click)="toggle($event)" pRipple tabindex="-1" [pBind]="getPTOptions('nodeToggleButton')"> @if (!tree.togglerIconTemplate()) { @if (!node()!.loading) { @if (!node()!.expanded) { <svg data-p-icon="chevron-right" [class]="cx('nodeToggleIcon')" [pBind]="getPTOptions('nodeToggleIcon')" /> } @if (node()!.expanded) { <svg data-p-icon="chevron-down" [class]="cx('nodeToggleIcon')" [pBind]="getPTOptions('nodeToggleIcon')" /> } } @if (showLoadingSpinner) { <svg data-p-icon="spinner" [class]="cx('nodeToggleIcon')" [spin]="true" [pBind]="getPTOptions('nodeToggleIcon')" /> } } @if (tree.togglerIconTemplate()) { <span [class]="cx('nodeToggleIcon')" [pBind]="getPTOptions('nodeToggleIcon')"> <ng-template *ngTemplateOutlet="tree.togglerIconTemplate()!; context: togglerIconContext"></ng-template> </span> } </button> @if (tree.selectionMode() == 'checkbox') { <p-checkbox [ngModel]="isSelected()" [class]="cx('nodeCheckbox')" [binary]="true" [indeterminate]="node()!.partialSelected" [disabled]="node()!.selectable === false" [variant]="checkboxVariant()" [attr.data-p-partialchecked]="node()!.partialSelected" [tabindex]="-1" (click)="$event.preventDefault()" [pt]="getPTOptions('pcNodeCheckbox')" [unstyled]="unstyled()" > @if (tree.checkboxIconTemplate()) { <ng-template #icon> <ng-template *ngTemplateOutlet="tree.checkboxIconTemplate()!; context: checkboxIconContext"></ng-template> </ng-template> } </p-checkbox> } @if (hasNodeIcon) { <span [class]="getIcon()" [pBind]="getPTOptions('nodeIcon')"></span> } <span [class]="cx('nodeLabel')" [pBind]="getPTOptions('nodeLabel')"> @if (!tree.getTemplateForNode(node()!)) { <span>{{ node()!.label }}</span> } @if (tree.getTemplateForNode(node()!)) { <span> <ng-container *ngTemplateOutlet="tree.getTemplateForNode(node()!)!; context: { $implicit: node() }"></ng-container> </span> } </span> </div> @if (isNextDropPointActive()) { <div [class]="cx('dropPoint', { next: true })" [attr.aria-hidden]="true" [pBind]="getPTOptions('dropPoint')"></div> } @if (showChildren) { <ul [class]="cx('nodeChildren')" role="group" [pBind]="ptm('nodeChildren')"> @for (childNode of node()!.children; track tree.trackBy()($index, childNode); let firstChild = $first; let lastChild = $last; let idx = $index) { <p-treenode [node]="childNode" [parentNode]="node()" [firstChild]="firstChild" [lastChild]="lastChild" [index]="idx" [itemSize]="itemSize()" [level]="level()! + 1" [loadingMode]="loadingMode()" [pt]="pt()" [unstyled]="unstyled()" /> } </ul> } </li> } `, encapsulation: ViewEncapsulation.None, providers: [TreeStyle, { provide: TREENODE_INSTANCE, useExisting: UITreeNode }, { provide: PARENT_INSTANCE, useExisting: UITreeNode }] }] }], propDecorators: { rowNode: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowNode", required: false }] }], node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: false }] }], parentNode: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentNode", required: false }] }], root: [{ type: i0.Input, args: [{ isSignal: true, alias: "root", required: false }] }], index: [{ type: i0.Input, args: [{ isSignal: true, alias: "index", required: false }] }], firstChild: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstChild", required: false }] }], lastChild: [{ type: i0.Input, args: [{ isSignal: true, alias: "lastChild", required: false }] }], level: [{ type: i0.Input, args: [{ isSignal: true, alias: "level", required: false }] }], indentation: [{ type: i0.Input, args: [{ isSignal: true, alias: "indentation", required: false }] }], itemSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemSize", required: false }] }], loadingMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingMode", required: false }] }] } }); /** * Tree is used to display hierarchical data. * @group Components */ class Tree extends BaseComponent { componentName = 'Tree'; _destroyed = false; bindDirectiveInstance = inject(Bind, { self: true }); $pcTree = inject(TREE_INSTANCE, { optional: true, skipSelf: true }) ?? undefined; onDestroy() { this._destroyed = true; } onAfterViewChecked() { this.bindDirectiveInstance.setAttrs(this.ptms(['host', 'root'])); } /** * An array of treenodes. * @group Props */ value = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "value" }] : /* istanbul ignore next */ [])); /** * Defines the selection mode. * @group Props */ selectionMode = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "selectionMode" }] : /* istanbul ignore next */ [])); /** * Loading mode display. * @group Props */ loadingMode = input('mask', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "loadingMode" }] : /* istanbul ignore next */ [])); /** * A single treenode instance or an array to refer to the selections. * @deprecated since v22, use `selectionKeys` instead for key-based selection state. * @group Props */ selection = model(null, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ [])); /** * A map of node keys (by `TreeNode.key`) whose value is `true` for collapsed nodes. Two-way bindable. * When provided it controls expansion (a node is expanded unless its key maps to `true`); leave it * unset to keep driving expansion from each node's `expanded` flag (default, non-breaking). * @group Props */ collapsedKeys = model(null, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "collapsedKeys" }] : /* istanbul ignore next */ [])); /** * A map of node keys (by `TreeNode.key`) describing the selection state. Two-way bindable. * For single/multiple selection a key maps to `true`; for checkbox selection it maps to * `{ checked, partialChecked }`. When provided it is kept in sync with `selection`; leave it unset * to keep using the node-reference based `selection` model (default, non-breaking). * @group Props */ selectionKeys = model(null, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "selectionKeys" }] : /* istanbul ignore next */ [])); /** * Context menu instance. * @group Props */ contextMenu = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "contextMenu" }] : /* istanbul ignore next */ [])); /** * Selected node with a context menu. * @group Props */ contextMenuSelection = model(null, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "contextMenuSelection" }] : /* istanbul ignore next */ [])); /** * Scope of the draggable nodes to match a droppableScope. * @group Props */ draggableScope = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "draggableScope" }] : /* istanbul ignore next */ [])); /** * Scope of the droppable nodes to match a draggableScope. * @group Props */ droppableScope = input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "droppableScope" }] : /* istanbul ignore next */ [])); /** * Whether the nodes are draggable. * @group Props */ draggableNodes = input(false, { ...(ngDevMode ? { debugName: "draggableNodes" } : /* istanbul ignore next */ {}), transform: booleanAttribute }); /** * Whether the