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
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.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