@zag-js/collection
Version:
Utilities to manage a collection of items.
458 lines (456 loc) • 16.8 kB
JavaScript
import {
__publicField
} from "./chunk-QZ7TP4HQ.mjs";
// src/tree-collection.ts
import { hasProp, isEqual, isObject } from "@zag-js/utils";
import {
access,
compareIndexPaths,
filter,
find,
findAll,
findIndexPath,
flatMap,
flatten,
insert,
move,
remove,
replace,
visit
} from "./tree-visit.mjs";
var TreeCollection = class _TreeCollection {
constructor(options) {
this.options = options;
__publicField(this, "rootNode");
__publicField(this, "isEqual", (other) => {
return isEqual(this.rootNode, other.rootNode);
});
__publicField(this, "getNodeChildren", (node) => {
return this.options.nodeToChildren?.(node) ?? fallbackMethods.nodeToChildren(node) ?? [];
});
__publicField(this, "resolveIndexPath", (valueOrIndexPath) => {
return typeof valueOrIndexPath === "string" ? this.getIndexPath(valueOrIndexPath) : valueOrIndexPath;
});
__publicField(this, "resolveNode", (valueOrIndexPath) => {
const indexPath = this.resolveIndexPath(valueOrIndexPath);
return indexPath ? this.at(indexPath) : void 0;
});
__publicField(this, "getNodeChildrenCount", (node) => {
return this.options.nodeToChildrenCount?.(node) ?? fallbackMethods.nodeToChildrenCount(node);
});
__publicField(this, "getNodeValue", (node) => {
return this.options.nodeToValue?.(node) ?? fallbackMethods.nodeToValue(node);
});
__publicField(this, "getNodeDisabled", (node) => {
return this.options.isNodeDisabled?.(node) ?? fallbackMethods.isNodeDisabled(node);
});
__publicField(this, "stringify", (value) => {
const node = this.findNode(value);
if (!node) return null;
return this.stringifyNode(node);
});
__publicField(this, "stringifyNode", (node) => {
return this.options.nodeToString?.(node) ?? fallbackMethods.nodeToString(node);
});
__publicField(this, "getFirstNode", (rootNode = this.rootNode, opts = {}) => {
let firstChild;
visit(rootNode, {
getChildren: this.getNodeChildren,
onEnter: (node, indexPath) => {
if (this.isSameNode(node, rootNode)) return;
if (opts.skip?.({ value: this.getNodeValue(node), node, indexPath })) return "skip";
if (!firstChild && indexPath.length > 0 && !this.getNodeDisabled(node)) {
firstChild = node;
return "stop";
}
}
});
return firstChild;
});
__publicField(this, "getLastNode", (rootNode = this.rootNode, opts = {}) => {
let lastChild;
visit(rootNode, {
getChildren: this.getNodeChildren,
onEnter: (node, indexPath) => {
if (this.isSameNode(node, rootNode)) return;
if (opts.skip?.({ value: this.getNodeValue(node), node, indexPath })) return "skip";
if (indexPath.length > 0 && !this.getNodeDisabled(node)) {
lastChild = node;
}
}
});
return lastChild;
});
__publicField(this, "at", (indexPath) => {
return access(this.rootNode, indexPath, {
getChildren: this.getNodeChildren
});
});
__publicField(this, "findNode", (value, rootNode = this.rootNode) => {
return find(rootNode, {
getChildren: this.getNodeChildren,
predicate: (node) => this.getNodeValue(node) === value
});
});
__publicField(this, "findNodes", (values, rootNode = this.rootNode) => {
const v = new Set(values.filter((v2) => v2 != null));
return findAll(rootNode, {
getChildren: this.getNodeChildren,
predicate: (node) => v.has(this.getNodeValue(node))
});
});
__publicField(this, "sort", (values) => {
return values.reduce((acc, value) => {
const indexPath = this.getIndexPath(value);
if (indexPath) acc.push({ value, indexPath });
return acc;
}, []).sort((a, b) => compareIndexPaths(a.indexPath, b.indexPath)).map(({ value }) => value);
});
__publicField(this, "getValue", (indexPath) => {
const node = this.at(indexPath);
return node ? this.getNodeValue(node) : void 0;
});
__publicField(this, "getValuePath", (indexPath) => {
if (!indexPath) return [];
const valuePath = [];
let currentPath = [...indexPath];
while (currentPath.length > 0) {
const node = this.at(currentPath);
if (node) valuePath.unshift(this.getNodeValue(node));
currentPath.pop();
}
return valuePath;
});
__publicField(this, "getDepth", (value) => {
const indexPath = findIndexPath(this.rootNode, {
getChildren: this.getNodeChildren,
predicate: (node) => this.getNodeValue(node) === value
});
return indexPath?.length ?? 0;
});
__publicField(this, "isSameNode", (node, other) => {
return this.getNodeValue(node) === this.getNodeValue(other);
});
__publicField(this, "isRootNode", (node) => {
return this.isSameNode(node, this.rootNode);
});
__publicField(this, "contains", (parentIndexPath, valueIndexPath) => {
if (!parentIndexPath || !valueIndexPath) return false;
return valueIndexPath.slice(0, parentIndexPath.length).every((_, i) => parentIndexPath[i] === valueIndexPath[i]);
});
__publicField(this, "getNextNode", (value, opts = {}) => {
let found = false;
let nextNode;
visit(this.rootNode, {
getChildren: this.getNodeChildren,
onEnter: (node, indexPath) => {
if (this.isRootNode(node)) return;
const nodeValue = this.getNodeValue(node);
if (opts.skip?.({ value: nodeValue, node, indexPath })) {
if (nodeValue === value) {
found = true;
}
return "skip";
}
if (found && !this.getNodeDisabled(node)) {
nextNode = node;
return "stop";
}
if (nodeValue === value) {
found = true;
}
}
});
return nextNode;
});
__publicField(this, "getPreviousNode", (value, opts = {}) => {
let previousNode;
let found = false;
visit(this.rootNode, {
getChildren: this.getNodeChildren,
onEnter: (node, indexPath) => {
if (this.isRootNode(node)) return;
const nodeValue = this.getNodeValue(node);
if (opts.skip?.({ value: nodeValue, node, indexPath })) {
return "skip";
}
if (nodeValue === value) {
found = true;
return "stop";
}
if (!this.getNodeDisabled(node)) {
previousNode = node;
}
}
});
return found ? previousNode : void 0;
});
__publicField(this, "getParentNodes", (valueOrIndexPath) => {
const indexPath = this.resolveIndexPath(valueOrIndexPath)?.slice();
if (!indexPath) return [];
const result = [];
while (indexPath.length > 0) {
indexPath.pop();
const parentNode = this.at(indexPath);
if (parentNode && !this.isRootNode(parentNode)) {
result.unshift(parentNode);
}
}
return result;
});
__publicField(this, "getDescendantNodes", (valueOrIndexPath, options) => {
const parentNode = this.resolveNode(valueOrIndexPath);
if (!parentNode) return [];
const result = [];
visit(parentNode, {
getChildren: this.getNodeChildren,
onEnter: (node, nodeIndexPath) => {
if (nodeIndexPath.length === 0) return;
if (!options?.withBranch && this.isBranchNode(node)) return;
result.push(node);
}
});
return result;
});
__publicField(this, "getDescendantValues", (valueOrIndexPath, options) => {
const children = this.getDescendantNodes(valueOrIndexPath, options);
return children.map((child) => this.getNodeValue(child));
});
__publicField(this, "getParentIndexPath", (indexPath) => {
return indexPath.slice(0, -1);
});
__publicField(this, "getParentNode", (valueOrIndexPath) => {
const indexPath = this.resolveIndexPath(valueOrIndexPath);
return indexPath ? this.at(this.getParentIndexPath(indexPath)) : void 0;
});
__publicField(this, "visit", (opts) => {
const { skip, ...rest } = opts;
visit(this.rootNode, {
...rest,
getChildren: this.getNodeChildren,
onEnter: (node, indexPath) => {
if (this.isRootNode(node)) return;
if (skip?.({ value: this.getNodeValue(node), node, indexPath })) return "skip";
return rest.onEnter?.(node, indexPath);
}
});
});
__publicField(this, "getPreviousSibling", (indexPath) => {
const parentNode = this.getParentNode(indexPath);
if (!parentNode) return;
const siblings = this.getNodeChildren(parentNode);
let idx = indexPath[indexPath.length - 1];
while (--idx >= 0) {
const sibling = siblings[idx];
if (!this.getNodeDisabled(sibling)) return sibling;
}
return;
});
__publicField(this, "getNextSibling", (indexPath) => {
const parentNode = this.getParentNode(indexPath);
if (!parentNode) return;
const siblings = this.getNodeChildren(parentNode);
let idx = indexPath[indexPath.length - 1];
while (++idx < siblings.length) {
const sibling = siblings[idx];
if (!this.getNodeDisabled(sibling)) return sibling;
}
return;
});
__publicField(this, "getSiblingNodes", (indexPath) => {
const parentNode = this.getParentNode(indexPath);
return parentNode ? this.getNodeChildren(parentNode) : [];
});
__publicField(this, "getValues", (rootNode = this.rootNode) => {
const values = flatMap(rootNode, {
getChildren: this.getNodeChildren,
transform: (node) => [this.getNodeValue(node)]
});
return values.slice(1);
});
__publicField(this, "isValidDepth", (indexPath, depth) => {
if (depth == null) return true;
if (typeof depth === "function") return depth(indexPath.length);
return indexPath.length === depth;
});
__publicField(this, "isBranchNode", (node) => {
return this.getNodeChildren(node).length > 0 || this.getNodeChildrenCount(node) != null;
});
__publicField(this, "getBranchValues", (rootNode = this.rootNode, opts = {}) => {
let values = [];
visit(rootNode, {
getChildren: this.getNodeChildren,
onEnter: (node, indexPath) => {
if (indexPath.length === 0) return;
const nodeValue = this.getNodeValue(node);
if (opts.skip?.({ value: nodeValue, node, indexPath })) return "skip";
if (this.isBranchNode(node) && this.isValidDepth(indexPath, opts.depth)) {
values.push(this.getNodeValue(node));
}
}
});
return values;
});
__publicField(this, "flatten", (rootNode = this.rootNode) => {
return flatten(rootNode, { getChildren: this.getNodeChildren });
});
__publicField(this, "_create", (node, children) => {
if (this.getNodeChildren(node).length > 0 || children.length > 0) {
return { ...node, children };
}
return { ...node };
});
__publicField(this, "_insert", (rootNode, indexPath, nodes) => {
return this.copy(
insert(rootNode, { at: indexPath, nodes, getChildren: this.getNodeChildren, create: this._create })
);
});
__publicField(this, "copy", (rootNode) => {
return new _TreeCollection({ ...this.options, rootNode });
});
__publicField(this, "_replace", (rootNode, indexPath, node) => {
return this.copy(
replace(rootNode, { at: indexPath, node, getChildren: this.getNodeChildren, create: this._create })
);
});
__publicField(this, "_move", (rootNode, indexPaths, to) => {
return this.copy(move(rootNode, { indexPaths, to, getChildren: this.getNodeChildren, create: this._create }));
});
__publicField(this, "_remove", (rootNode, indexPaths) => {
return this.copy(remove(rootNode, { indexPaths, getChildren: this.getNodeChildren, create: this._create }));
});
__publicField(this, "replace", (indexPath, node) => {
return this._replace(this.rootNode, indexPath, node);
});
__publicField(this, "remove", (indexPaths) => {
return this._remove(this.rootNode, indexPaths);
});
__publicField(this, "insertBefore", (indexPath, nodes) => {
const parentNode = this.getParentNode(indexPath);
return parentNode ? this._insert(this.rootNode, indexPath, nodes) : void 0;
});
__publicField(this, "insertAfter", (indexPath, nodes) => {
const parentNode = this.getParentNode(indexPath);
if (!parentNode) return;
const nextIndex = [...indexPath.slice(0, -1), indexPath[indexPath.length - 1] + 1];
return this._insert(this.rootNode, nextIndex, nodes);
});
__publicField(this, "move", (fromIndexPaths, toIndexPath) => {
return this._move(this.rootNode, fromIndexPaths, toIndexPath);
});
__publicField(this, "filter", (predicate) => {
const filteredRoot = filter(this.rootNode, {
predicate,
getChildren: this.getNodeChildren,
create: this._create
});
return this.copy(filteredRoot);
});
__publicField(this, "toJSON", () => {
return this.getValues(this.rootNode);
});
this.rootNode = options.rootNode;
}
getIndexPath(valueOrValuePath) {
if (Array.isArray(valueOrValuePath)) {
if (valueOrValuePath.length === 0) return [];
const indexPath = [];
let currentChildren = this.getNodeChildren(this.rootNode);
for (let i = 0; i < valueOrValuePath.length; i++) {
const currentValue = valueOrValuePath[i];
const matchingChildIndex = currentChildren.findIndex((child) => this.getNodeValue(child) === currentValue);
if (matchingChildIndex === -1) break;
indexPath.push(matchingChildIndex);
if (i < valueOrValuePath.length - 1) {
const currentNode = currentChildren[matchingChildIndex];
currentChildren = this.getNodeChildren(currentNode);
}
}
return indexPath;
} else {
return findIndexPath(this.rootNode, {
getChildren: this.getNodeChildren,
predicate: (node) => this.getNodeValue(node) === valueOrValuePath
});
}
}
};
function flattenedToTree(nodes, options = fallbackMethods) {
if (nodes.length === 0) {
throw new Error("[zag-js/tree] Cannot create tree from empty flattened array");
}
const rootFlatNode = nodes.find((node) => node._parent === void 0);
if (!rootFlatNode) {
throw new Error("[zag-js/tree] No root node found in flattened data");
}
const nodeMap = /* @__PURE__ */ new Map();
nodes.forEach((node) => {
nodeMap.set(node._index, node);
});
const buildNode = (idx) => {
const flatNode = nodeMap.get(idx);
if (!flatNode) return {};
const { _children, _parent, _index, ...cleanNode } = flatNode;
const children = [];
_children?.forEach((childIndex) => {
children.push(buildNode(childIndex));
});
return {
...cleanNode,
...children.length > 0 && { children }
};
};
const rootNode = buildNode(rootFlatNode._index);
return new TreeCollection({ ...options, rootNode });
}
function filePathToTree(paths) {
const rootNode = {
label: "",
value: "ROOT",
children: []
};
paths.forEach((path) => {
const parts = path.split("/");
let currentNode = rootNode;
parts.forEach((part, index) => {
let childNode = currentNode.children?.find((child) => child.label === part);
if (!childNode) {
childNode = {
value: parts.slice(0, index + 1).join("/"),
label: part
};
currentNode.children || (currentNode.children = []);
currentNode.children.push(childNode);
}
currentNode = childNode;
});
});
return new TreeCollection({ rootNode });
}
var fallbackMethods = {
nodeToValue(node) {
if (typeof node === "string") return node;
if (isObject(node) && hasProp(node, "value")) return node.value;
return "";
},
nodeToString(node) {
if (typeof node === "string") return node;
if (isObject(node) && hasProp(node, "label")) return node.label;
return fallbackMethods.nodeToValue(node);
},
isNodeDisabled(node) {
if (isObject(node) && hasProp(node, "disabled")) return !!node.disabled;
return false;
},
nodeToChildren(node) {
return node.children;
},
nodeToChildrenCount(node) {
if (isObject(node) && hasProp(node, "childrenCount")) return node.childrenCount;
}
};
export {
TreeCollection,
filePathToTree,
flattenedToTree
};