@furystack/shades-common-components
Version:
Common UI components for FuryStack Shades
145 lines • 4.94 kB
JavaScript
import { ObservableValue } from '@furystack/utils';
import { ListService } from './list-service.js';
/**
* Service for managing tree state including expand/collapse, hierarchy navigation,
* and flattening the tree into a visible items list for rendering
*/
export class TreeService extends ListService {
treeOptions;
expandedNodes = new ObservableValue(new Set());
rootItems = new ObservableValue([]);
flattenedNodes = new ObservableValue([]);
[Symbol.dispose]() {
super[Symbol.dispose]();
this.expandedNodes[Symbol.dispose]();
this.rootItems[Symbol.dispose]();
this.flattenedNodes[Symbol.dispose]();
}
/**
* Checks whether a node is currently expanded
*/
isExpanded = (item) => this.expandedNodes.getValue().has(item);
/**
* Expands a node, making its children visible
*/
expand = (item) => {
const expanded = new Set(this.expandedNodes.getValue());
expanded.add(item);
this.expandedNodes.setValue(expanded);
this.updateFlattenedNodes();
};
/**
* Collapses a node, hiding its children
*/
collapse = (item) => {
const expanded = new Set(this.expandedNodes.getValue());
expanded.delete(item);
this.expandedNodes.setValue(expanded);
this.updateFlattenedNodes();
};
/**
* Toggles the expanded state of a node
*/
toggleExpanded = (item) => {
if (this.isExpanded(item)) {
this.collapse(item);
}
else {
const children = this.treeOptions.getChildren(item);
if (children.length > 0) {
this.expand(item);
}
}
};
/**
* Finds the parent of a given item in the tree
*/
getParent(item) {
const findParent = (nodes) => {
for (const node of nodes) {
const children = this.treeOptions.getChildren(node);
if (children.includes(item)) {
return node;
}
const found = findParent(children);
if (found)
return found;
}
return undefined;
};
return findParent(this.rootItems.getValue());
}
/**
* Flattens the tree based on which nodes are expanded, and syncs the result
* to both flattenedNodes and the inherited ListService items
*/
updateFlattenedNodes() {
const expanded = this.expandedNodes.getValue();
const result = [];
const flatten = (nodes, level) => {
for (const node of nodes) {
const children = this.treeOptions.getChildren(node);
const hasChildren = children.length > 0;
const isExpanded = expanded.has(node);
result.push({ item: node, level, hasChildren, isExpanded });
if (hasChildren && isExpanded) {
flatten(children, level + 1);
}
}
};
flatten(this.rootItems.getValue(), 0);
this.flattenedNodes.setValue(result);
this.items.setValue(result.map((n) => n.item));
}
/**
* Gets the FlattenedTreeNode for a given item
*/
getNodeInfo(item) {
return this.flattenedNodes.getValue().find((n) => n.item === item);
}
handleKeyDown(ev) {
const hasFocus = this.hasFocus.getValue();
const focusedItem = this.focusedItem.getValue();
if (hasFocus && focusedItem) {
switch (ev.key) {
case 'ArrowRight': {
const children = this.treeOptions.getChildren(focusedItem);
if (children.length > 0 && !this.isExpanded(focusedItem)) {
ev.preventDefault();
this.expand(focusedItem);
}
return;
}
case 'ArrowLeft': {
if (this.isExpanded(focusedItem)) {
ev.preventDefault();
this.collapse(focusedItem);
}
else {
const parent = this.getParent(focusedItem);
if (parent) {
ev.preventDefault();
this.focusedItem.setValue(parent);
}
}
return;
}
default: {
break;
}
}
}
super.handleKeyDown(ev);
}
handleItemDoubleClick(item) {
const children = this.treeOptions.getChildren(item);
if (children.length > 0) {
this.toggleExpanded(item);
}
}
constructor(treeOptions) {
super(treeOptions);
this.treeOptions = treeOptions;
}
}
//# sourceMappingURL=tree-service.js.map