UNPKG

@zag-js/collection

Version:

Utilities to manage a collection of items.

426 lines (424 loc) 13.5 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/tree-visit.ts var tree_visit_exports = {}; __export(tree_visit_exports, { access: () => access, accessPath: () => accessPath, ancestorIndexPaths: () => ancestorIndexPaths, compareIndexPaths: () => compareIndexPaths, filter: () => filter, find: () => find, findAll: () => findAll, findAllIndexPaths: () => findAllIndexPaths, findIndexPath: () => findIndexPath, flat: () => flat, flatMap: () => flatMap, flatten: () => flatten, insert: () => insert, map: () => map, move: () => move, reduce: () => reduce, remove: () => remove, replace: () => replace, sortIndexPaths: () => sortIndexPaths, visit: () => visit }); module.exports = __toCommonJS(tree_visit_exports); function access(node, indexPath, options) { for (let i = 0; i < indexPath.length; i++) node = options.getChildren(node, indexPath.slice(i + 1))[indexPath[i]]; return node; } function accessPath(node, indexPath, options) { const result = [node]; for (let i = 0; i < indexPath.length; i++) { node = options.getChildren(node, indexPath.slice(i + 1))[indexPath[i]]; result.push(node); } return result; } function ancestorIndexPaths(indexPaths) { const sortedPaths = sortIndexPaths(indexPaths); const result = []; const seen = /* @__PURE__ */ new Set(); for (const indexPath of sortedPaths) { const key = indexPath.join(); if (!seen.has(key)) { seen.add(key); result.push(indexPath); } } return result; } function compareIndexPaths(a, b) { for (let i = 0; i < Math.min(a.length, b.length); i++) { if (a[i] < b[i]) return -1; if (a[i] > b[i]) return 1; } return a.length - b.length; } function sortIndexPaths(indexPaths) { return indexPaths.sort(compareIndexPaths); } function find(node, options) { let found; visit(node, { ...options, onEnter: (child, indexPath) => { if (options.predicate(child, indexPath)) { found = child; return "stop"; } } }); return found; } function findAll(node, options) { const found = []; visit(node, { onEnter: (child, indexPath) => { if (options.predicate(child, indexPath)) found.push(child); }, getChildren: options.getChildren }); return found; } function findIndexPath(node, options) { let found; visit(node, { onEnter: (child, indexPath) => { if (options.predicate(child, indexPath)) { found = [...indexPath]; return "stop"; } }, getChildren: options.getChildren }); return found; } function findAllIndexPaths(node, options) { let found = []; visit(node, { onEnter: (child, indexPath) => { if (options.predicate(child, indexPath)) found.push([...indexPath]); }, getChildren: options.getChildren }); return found; } function reduce(node, options) { let result = options.initialResult; visit(node, { ...options, onEnter: (child, indexPath) => { result = options.nextResult(result, child, indexPath); } }); return result; } function flat(node, options) { return reduce(node, { ...options, initialResult: [], nextResult: (result, child) => { result.push(child); return result; } }); } function flatMap(node, options) { return reduce(node, { ...options, initialResult: [], nextResult: (result, child, indexPath) => { result.push(...options.transform(child, indexPath)); return result; } }); } function filter(node, options) { const { predicate, create, getChildren } = options; const filterRecursive = (node2, indexPath) => { const children = getChildren(node2, indexPath); const filteredChildren = []; children.forEach((child, index) => { const childIndexPath = [...indexPath, index]; const filteredChild = filterRecursive(child, childIndexPath); if (filteredChild) filteredChildren.push(filteredChild); }); const isRoot = indexPath.length === 0; const nodeMatches = predicate(node2, indexPath); const hasFilteredChildren = filteredChildren.length > 0; if (isRoot || nodeMatches || hasFilteredChildren) { return create(node2, filteredChildren, indexPath); } return null; }; return filterRecursive(node, []) || create(node, [], []); } function flatten(rootNode, options) { const nodes = []; let idx = 0; const idxMap = /* @__PURE__ */ new Map(); const parentMap = /* @__PURE__ */ new Map(); visit(rootNode, { getChildren: options.getChildren, onEnter: (node, indexPath) => { if (!idxMap.has(node)) { idxMap.set(node, idx++); } const children = options.getChildren(node, indexPath); children.forEach((child) => { if (!parentMap.has(child)) { parentMap.set(child, node); } if (!idxMap.has(child)) { idxMap.set(child, idx++); } }); const _children = children.length > 0 ? children.map((child) => idxMap.get(child)) : void 0; const parent = parentMap.get(node); const _parent = parent ? idxMap.get(parent) : void 0; const _index = idxMap.get(node); nodes.push({ ...node, _children, _parent, _index }); } }); return nodes; } function insertOperation(index, nodes) { return { type: "insert", index, nodes }; } function removeOperation(indexes) { return { type: "remove", indexes }; } function replaceOperation() { return { type: "replace" }; } function splitIndexPath(indexPath) { return [indexPath.slice(0, -1), indexPath[indexPath.length - 1]]; } function getInsertionOperations(indexPath, nodes, operations = /* @__PURE__ */ new Map()) { const [parentIndexPath, index] = splitIndexPath(indexPath); for (let i = parentIndexPath.length - 1; i >= 0; i--) { const parentKey = parentIndexPath.slice(0, i).join(); switch (operations.get(parentKey)?.type) { case "remove": continue; } operations.set(parentKey, replaceOperation()); } const operation = operations.get(parentIndexPath.join()); switch (operation?.type) { case "remove": operations.set(parentIndexPath.join(), { type: "removeThenInsert", removeIndexes: operation.indexes, insertIndex: index, insertNodes: nodes }); break; default: operations.set(parentIndexPath.join(), insertOperation(index, nodes)); } return operations; } function getRemovalOperations(indexPaths) { const operations = /* @__PURE__ */ new Map(); const indexesToRemove = /* @__PURE__ */ new Map(); for (const indexPath of indexPaths) { const parentKey = indexPath.slice(0, -1).join(); const value = indexesToRemove.get(parentKey) ?? []; value.push(indexPath[indexPath.length - 1]); indexesToRemove.set( parentKey, value.sort((a, b) => a - b) ); } for (const indexPath of indexPaths) { for (let i = indexPath.length - 2; i >= 0; i--) { const parentKey = indexPath.slice(0, i).join(); if (!operations.has(parentKey)) { operations.set(parentKey, replaceOperation()); } } } for (const [parentKey, indexes] of indexesToRemove) { operations.set(parentKey, removeOperation(indexes)); } return operations; } function getReplaceOperations(indexPath, node) { const operations = /* @__PURE__ */ new Map(); const [parentIndexPath, index] = splitIndexPath(indexPath); for (let i = parentIndexPath.length - 1; i >= 0; i--) { const parentKey = parentIndexPath.slice(0, i).join(); operations.set(parentKey, replaceOperation()); } operations.set(parentIndexPath.join(), { type: "removeThenInsert", removeIndexes: [index], insertIndex: index, insertNodes: [node] }); return operations; } function mutate(node, operations, options) { return map(node, { ...options, getChildren: (node2, indexPath) => { const key = indexPath.join(); const operation = operations.get(key); switch (operation?.type) { case "replace": case "remove": case "removeThenInsert": case "insert": return options.getChildren(node2, indexPath); default: return []; } }, transform: (node2, children, indexPath) => { const key = indexPath.join(); const operation = operations.get(key); switch (operation?.type) { case "remove": return options.create( node2, children.filter((_, index) => !operation.indexes.includes(index)), indexPath ); case "removeThenInsert": const updatedChildren = children.filter((_, index) => !operation.removeIndexes.includes(index)); const adjustedIndex = operation.removeIndexes.reduce( (index, removedIndex) => removedIndex < index ? index - 1 : index, operation.insertIndex ); return options.create(node2, splice(updatedChildren, adjustedIndex, 0, ...operation.insertNodes), indexPath); case "insert": return options.create(node2, splice(children, operation.index, 0, ...operation.nodes), indexPath); case "replace": return options.create(node2, children, indexPath); default: return node2; } } }); } function splice(array, start, deleteCount, ...items) { return [...array.slice(0, start), ...items, ...array.slice(start + deleteCount)]; } function map(node, options) { const childrenMap = {}; visit(node, { ...options, onLeave: (child, indexPath) => { const keyIndexPath = [0, ...indexPath]; const key = keyIndexPath.join(); const transformed = options.transform(child, childrenMap[key] ?? [], indexPath); const parentKey = keyIndexPath.slice(0, -1).join(); const parentChildren = childrenMap[parentKey] ?? []; parentChildren.push(transformed); childrenMap[parentKey] = parentChildren; } }); return childrenMap[""][0]; } function insert(node, options) { const { nodes, at } = options; if (at.length === 0) throw new Error(`Can't insert nodes at the root`); const state = getInsertionOperations(at, nodes); return mutate(node, state, options); } function replace(node, options) { if (options.at.length === 0) return options.node; const operations = getReplaceOperations(options.at, options.node); return mutate(node, operations, options); } function remove(node, options) { if (options.indexPaths.length === 0) return node; for (const indexPath of options.indexPaths) { if (indexPath.length === 0) throw new Error(`Can't remove the root node`); } const operations = getRemovalOperations(options.indexPaths); return mutate(node, operations, options); } function move(node, options) { if (options.indexPaths.length === 0) return node; for (const indexPath of options.indexPaths) { if (indexPath.length === 0) throw new Error(`Can't move the root node`); } if (options.to.length === 0) throw new Error(`Can't move nodes to the root`); const _ancestorIndexPaths = ancestorIndexPaths(options.indexPaths); const nodesToInsert = _ancestorIndexPaths.map((indexPath) => access(node, indexPath, options)); const operations = getInsertionOperations(options.to, nodesToInsert, getRemovalOperations(_ancestorIndexPaths)); return mutate(node, operations, options); } function visit(node, options) { const { onEnter, onLeave, getChildren } = options; let indexPath = []; let stack = [{ node }]; const getIndexPath = options.reuseIndexPath ? () => indexPath : () => indexPath.slice(); while (stack.length > 0) { let wrapper = stack[stack.length - 1]; if (wrapper.state === void 0) { const enterResult = onEnter?.(wrapper.node, getIndexPath()); if (enterResult === "stop") return; wrapper.state = enterResult === "skip" ? -1 : 0; } const children = wrapper.children || getChildren(wrapper.node, getIndexPath()); wrapper.children || (wrapper.children = children); if (wrapper.state !== -1) { if (wrapper.state < children.length) { let currentIndex = wrapper.state; indexPath.push(currentIndex); stack.push({ node: children[currentIndex] }); wrapper.state = currentIndex + 1; continue; } const leaveResult = onLeave?.(wrapper.node, getIndexPath()); if (leaveResult === "stop") return; } indexPath.pop(); stack.pop(); } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { access, accessPath, ancestorIndexPaths, compareIndexPaths, filter, find, findAll, findAllIndexPaths, findIndexPath, flat, flatMap, flatten, insert, map, move, reduce, remove, replace, sortIndexPaths, visit });