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,018 lines (1,010 loc) • 122 kB
JavaScript
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.0.6", ngImport: i0, type: TreeStyle, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: TreeStyle });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", 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();
}
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());
}
insertNodeOnDrop() {
const { dragNode, dragNodeIndex, dragNodeSubNodes } = this.tree;
if (!this.node() || dragNodeIndex == null || !dragNode || !dragNodeSubNodes) {
return;
}
const position = this.dropPosition();
const subNodes = this.subNodes || [];
const index = this.index() || 0;
const dropIndex = dragNodeSubNodes === subNodes ? (dragNodeIndex > index ? index : index - 1) : index;
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
this.node().children = this.node().children || [];
this.node().children.push(dragNode);
}
this.tree.dragDropService.stopDrag({
node: dragNode,
subNodes,
index: dragNodeIndex
});
}
onNodeDrop(event) {
event.preventDefault();
event.stopPropagation();
if (this.isDroppable()) {
const { dragNode } = 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.node(),
index: this.index(),
dropPoint: dropPoint,
accept: () => {
this.insertNodeOnDrop();
}
});
}
else {
this.insertNodeOnDrop();
this.tree.onNodeDrop.emit({
originalEvent: event,
dragNode: dragNode,
dropNode: this.node(),
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.node(),
subNodes: this.subNodes,
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.node(),
subNodes: this.subNodes,
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.0.6", ngImport: i0, type: UITreeNode, deps: null, target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", 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($event)"
(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.0.6", 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($event)"
(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 nodes are droppable.
* @group Props
*/
droppableNodes = input(false, { ...(ngDevMode ? { debugName: "droppableNodes" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
/**
* 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 = input(false, { ...(ngDevMode ? { debugName: "metaKeySelection" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
/**
* Whether checkbox selections propagate to ancestor nodes.
* @group Props
*/
propagateSelectionUp = input(true, { ...(ngDevMode ? { debugName: "propagateSelectionUp" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
/**
* Whether checkbox selections propagate to descendant nodes.
* @group Props
*/
propagateSelectionDown = input(true, { ...(ngDevMode ? { debugName: "propagateSelectionDown" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
/**
* Displays a loader to indicate data load is in progress.
* @group Props
*/
loading = input(false,