UNPKG

@zag-js/collection

Version:

Utilities to manage a collection of items.

458 lines (456 loc) 16.8 kB
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 };