import { NgTemplateOutlet } from '@angular/common';
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
export interface TreeNodeData {
id?: string;
label: string;
badge?: string;
children?: TreeNodeData[];
}
@Component({
selector: 'vsp-tree',
imports: [NgTemplateOutlet],
template: `
@for (n of data; track $index) {
}
@if (hasKids(n)) {
} @else {
}
{{ n.label }}
@if (n.badge != null) {
{{
n.badge
}}
}
@if (hasKids(n) && expandedSet.has(id(n))) {
@for (c of n.children; track $index) {
}
}
`,
})
export class VspTree implements OnInit {
@Input() data: TreeNodeData[] = [];
@Input() defaultExpanded: string[] = [];
/** Controlled expanded node ids. Omit for uncontrolled. */
@Input('expanded') expandedInput?: string[];
@Output() expandedChange = new EventEmitter();
/** Controlled selected node id. Omit for uncontrolled. */
@Input('selected') selectedInput?: string | null;
@Output() select = new EventEmitter();
private internalExpanded = new Set();
private internalSelected: string | null = null;
ngOnInit(): void {
this.internalExpanded = new Set(this.defaultExpanded);
}
get expandedSet(): Set {
return this.expandedInput !== undefined ? new Set(this.expandedInput) : this.internalExpanded;
}
get selectedId(): string | null {
return this.selectedInput !== undefined ? this.selectedInput : this.internalSelected;
}
id(n: TreeNodeData): string {
return n.id ?? n.label;
}
hasKids(n: TreeNodeData): boolean {
return (n.children ?? []).length > 0;
}
rowCls(n: TreeNodeData): string {
const i = this.id(n);
return (
'ui-tree-row' +
(this.expandedSet.has(i) ? ' open' : '') +
(this.selectedId === i ? ' sel' : '')
);
}
activate(n: TreeNodeData): void {
const i = this.id(n);
if (this.hasKids(n)) {
const next = new Set(this.expandedSet);
if (next.has(i)) next.delete(i);
else next.add(i);
if (this.expandedInput === undefined) this.internalExpanded = next;
this.expandedChange.emit([...next]);
}
if (this.selectedInput === undefined) this.internalSelected = i;
this.select.emit(i);
}
}