@utopia-utils/tree
Version:
Collection of common tree utils
1 lines • 17.7 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","names":["tree: TreeNode[] | TreeNode","action: (node: TreeNode) => unknown","options?: Options","list: any[]","options?: Options","treeRes: TreeNode[]","tree: TreeNode | TreeNode[]","action: TraverseAction<TreeNode>","options?: Options","tree: TreeNode[]","parent: TreeNode | null","level: number","tree","tree: TreeNode | TreeNode[]","options?: Options<TreeNode>","tree: TreeNode[] | TreeNode","predicate: (node: TreeNode) => boolean","options?: Options<TreeNode>","_tree: TreeNode[]","tree: TreeNode[] | TreeNode","predicate: (node: TreeNode) => boolean","options?: Options<TreeNode>","res: TreeNode[]","tree: TreeNode[] | TreeNode","predicate: (node: TreeNode) => boolean","options?: Options","path: TreeNode[]"],"sources":["../src/constant.ts","../src/breadthFirstTraverse.ts","../src/buildTreeFromList.ts","../src/deepFirstTraverse.ts","../src/flattenTree.ts","../src/treeFilterNode.ts","../src/treeFindNode.ts","../src/treeFindPath.ts"],"sourcesContent":["/** 默认的树节点 parentId、id、children 的字段 */\nexport const DEFAULT_FIELD_NAMES = {\n /** 唯一标识属性名 */\n id: 'id',\n /** 子节点集合属性名 */\n children: 'children',\n /** 父节点标识属性名 */\n parentId: 'parentId',\n} as const\n","import { isFunction } from '@utopia-utils/share'\nimport { DEFAULT_FIELD_NAMES } from './constant'\nimport type { FieldNames } from './type'\n\ninterface Options {\n /** Customize node children field name */\n fieldNames?: Pick<FieldNames, 'children'>\n}\n\n/**\n * Breadth-First Traverse (BFS or 广度优先遍历)\n * @param {TreeNode[] | TreeNode} tree - The tree to traverse.\n * @param action - (node: TreeNode) => unknown ; function called for each node in the tree, return false to stop traverse\n * @param {Options} [options]\n * @linkcode https://github.com/GreatAuk/utopia-utils/blob/main/packages/tree/src/breadthFirstTraverse.ts\n * @example\n * ```\n const tree = [\n {\n name: 'a',\n Children_: [\n { name: 'b' }\n ],\n },\n {\n name: 'c',\n }\n ]\n breadthFirstTraverse(tree, node => console.log(node.name), {\n fieldNames: {\n children: 'Children_', // default is children\n }\n })\n // output 'a', 'c', 'b'\n * ```\n */\nexport function breadthFirstTraverse<TreeNode>(tree: TreeNode[] | TreeNode, action: (node: TreeNode) => unknown, options?: Options): void {\n if (!isFunction(action))\n throw new Error('traverse action should be a function')\n\n const { fieldNames } = options || {}\n const { children } = { ...DEFAULT_FIELD_NAMES, ...fieldNames }\n const queue = Array.isArray(tree) ? [...tree] : [tree]\n\n while (queue.length) {\n const node = queue.shift()\n if (!node)\n continue\n\n // if action return false, stop traverse\n if (action(node) === false)\n break\n\n // @ts-expect-error - children field is dynamic\n if (node[children])\n // @ts-expect-error - children field is dynamic\n queue.push(...node[children])\n }\n}\n","import { DEFAULT_FIELD_NAMES } from './constant'\nimport type { FieldNames } from './type'\n\ninterface Options {\n /**\n * custom list field names\n *\n * @default { id: 'id', parentId: 'parentId', children: 'children' }\n */\n listFieldNames?: FieldNames\n /**\n * custom tree field names\n *\n * @default { id: 'id', parentId: 'parentId', children: 'children' }\n */\n treeFieldNames?: FieldNames\n}\n\n/**\n * It takes a list of items and returns a tree of items\n * @param {any[]} list - the list of items to be converted to a tree\n * @param {Options} [options] - {\n * @returns A tree structure\n * @linkcode https://github.com/GreatAuk/utopia-utils/blob/main/packages/tree/src/buildTreeFromList.ts\n * @example\n * ```\n const list = [\n { uid: '1', title: 'node 1', pid: '' },\n { uid: '1-1', title: 'node 1-1', pid: '1' },\n { uid: '1-2', title: 'node 1-2', pid: '1' },\n { uid: '1-1-1', title: 'node 1-1-1', pid: '1-1' },\n ]\n\n interface TreeNode {\n key: string\n title: string\n children: TreeNode[]\n pid: string\n }\n const tree = buildTreeFromList<TreeNode>(list, {\n listFieldNames: { id: 'uid', parentId: 'pid', children: '_Children' },\n treeFieldNames: { id: 'key', parentId: 'pid', children: 'children' },\n })\n * ```\n */\nexport function buildTreeFromList<TreeNode>(list: any[], options?: Options): TreeNode[] {\n const { listFieldNames, treeFieldNames } = options || {}\n const { id: listIdField, parentId: listParentIdField } = { ...DEFAULT_FIELD_NAMES, ...listFieldNames }\n const { id: treeIdField, parentId: treeParentIdField, children: treeChildrenField } = { ...DEFAULT_FIELD_NAMES, ...treeFieldNames }\n\n const nodeMap = new Map<string, any>()\n const treeRes: TreeNode[] = []\n\n list.forEach((item) => {\n if (treeFieldNames) {\n item[treeIdField] = item[listIdField]\n item[treeParentIdField] = item[listParentIdField]\n }\n item[treeChildrenField] = []\n nodeMap.set(item[listIdField], item)\n })\n\n list.forEach((item) => {\n const parent = nodeMap.get(item[listParentIdField])\n if (parent)\n parent[treeChildrenField].push(item)\n\n else\n treeRes.push(item)\n })\n\n return treeRes\n}\n","import { isFunction } from '@utopia-utils/share'\nimport type { FieldNames } from './type'\nimport { DEFAULT_FIELD_NAMES } from './constant'\n\ninterface Options {\n /** Customize node children field name */\n fieldNames?: Pick<FieldNames, 'children'>\n /** Traverse order, support 'pre' and 'post' */\n order?: 'pre' | 'post'\n}\n\ninterface TraverseAction<TreeNode> {\n (node: TreeNode, parent: TreeNode | null, level: number): unknown\n}\n\n/**\n * The function performs a deep first traversal of a tree data structure and applies a given action to\n * each node.\n * @param {TreeNode | TreeNode[]} tree - The tree parameter is either a single TreeNode or an array of\n * TreeNodes.\n * @param action - The function to be executed on each node of the tree during traversal. It takes\n * three arguments: the current node, its parent node (or null if it's the root node), and the current\n * level of the node in the tree. If the action function returns false, the traversal will be stopped.\n * @param {Options} [options]\n * @linkcode https://github.com/GreatAuk/utopia-utils/blob/main/packages/tree/src/deepFirstTraverse.ts\n * @example\n * ```ts\n * const tree = [\n * {\n * name: 'a',\n * children: [\n * { name: 'b' }\n * ],\n * },\n * {\n * name: 'c',\n * }\n * ]\n * deepFirstTraverse(tree, node => console.log(node.name))\n * // output 'a', 'b', 'c'\n * ```\n */\nexport function deepFirstTraverse<TreeNode>(tree: TreeNode | TreeNode[], action: TraverseAction<TreeNode>, options?: Options): void {\n if (!isFunction(action))\n throw new Error('traverse action should be a function')\n\n const { fieldNames, order = 'pre' } = options || {}\n const { children } = { ...DEFAULT_FIELD_NAMES, ...fieldNames }\n\n const traverseInner = (tree: TreeNode[], parent: TreeNode | null, level: number) => {\n for (const node of tree) {\n if (order === 'pre') {\n if (action(node, parent, level) === false)\n return false\n }\n\n // @ts-expect-error - children field is dynamic\n if (node[children]) {\n // @ts-expect-error - children field is dynamic\n const res = traverseInner(node[children], node, level + 1)\n if (res === false)\n return false\n }\n\n if (order === 'post') {\n if (action(node, parent, level) === false)\n return false\n }\n }\n }\n\n traverseInner(Array.isArray(tree) ? tree : [tree], null, 0)\n}\n","import { DEFAULT_FIELD_NAMES } from './constant'\nimport type { FieldNames } from './type'\n\ninterface Options<TreeNode> {\n /**\n * customize node field name\n *\n * @default { id: 'id', parentId: 'parentId', children: 'children' }\n */\n fieldNames?: FieldNames\n /** Function called for each node in the tree */\n onEachTraverse?: (node: TreeNode) => void\n}\n\n/**\n * It takes a tree and returns a flat array of all the nodes in the tree\n * @param {TreeNode | TreeNode[]} tree - The tree to flatten.\n * @param [options]\n * @returns An array of all the nodes in the tree.\n * @linkcode https://github.com/GreatAuk/utopia-utils/blob/main/packages/tree/src/flattenTree.ts\n * @example\n * ```\n const tree = {\n id: 1,\n children: [\n {\n id: 2,\n },\n ],\n }\n const list = flattenTree(tree)\n console.log(list)\n // output\n // [\n // {\n // \"ID\": 1,\n // \"children\": [\n // {\n // \"ID\": 2,\n // },\n // ],\n // },\n // {\n // \"ID\": 2,\n // },\n // ]\n * ```\n */\nexport function flattenTree<TreeNode>(tree: TreeNode | TreeNode[], options?: Options<TreeNode>): TreeNode[] {\n const { fieldNames, onEachTraverse } = options || {}\n const { children } = { ...DEFAULT_FIELD_NAMES, ...fieldNames }\n\n const queue = Array.isArray(tree) ? [...tree] : [tree]\n\n for (let i = 0; i < queue.length; i++) {\n const node = queue[i]\n onEachTraverse?.(node)\n // @ts-expect-error - dynamic field name\n if (node[children])\n // @ts-expect-error - dynamic field name\n queue.splice(i + 1, 0, ...node[children])\n }\n\n return queue\n}\n","import { DEFAULT_FIELD_NAMES } from './constant'\nimport type { FieldNames } from './type'\n\ninterface Options<TreeNode> {\n /** Customize node field name */\n fieldNames?: Pick<FieldNames, 'children'>\n /** Function called for each node in the tree */\n onEachTraverse?: (node: TreeNode) => void\n}\n\n/**\n * It takes a tree, a predicate function, and returns a filtered tree with\n * only the nodes that pass the predicate\n * @param {TreeNode[] | TreeNode} tree - The tree to filter.\n * @param predicate - (node: TreeNode) => boolean\n * @param {Options} [options] - {\n * @returns A filtered tree\n * @linkcode https://github.com/GreatAuk/utopia-utils/blob/main/packages/tree/src/treeFilterNode.ts\n */\nexport function treeFilterNode<TreeNode>(tree: TreeNode[] | TreeNode, predicate: (node: TreeNode) => boolean, options?: Options<TreeNode>): TreeNode[] {\n const { fieldNames, onEachTraverse } = options || {}\n const { children } = { ...DEFAULT_FIELD_NAMES, ...fieldNames }\n const _tree = Array.isArray(tree) ? [...tree] : [tree]\n\n function recursionFilter(_tree: TreeNode[]) {\n // copy the array to avoid mutating the original tree\n const copyTree = _tree.map(node => ({ ...node }))\n return copyTree.filter((node) => {\n // @ts-expect-error - dynamic field name\n if (node[children])\n // @ts-expect-error - dynamic field name\n node[children] = recursionFilter(node[children])\n\n onEachTraverse?.(node)\n // @ts-expect-error - dynamic field name\n return predicate(node) || !!node[children]?.length\n })\n }\n\n return recursionFilter(_tree)\n}\n","import { isFunction } from '@utopia-utils/share'\nimport type { FieldNames } from './type'\nimport { breadthFirstTraverse } from './breadthFirstTraverse'\n\ninterface Options<TreeNode> {\n /** Customize node field name */\n fieldNames?: Pick<FieldNames, 'children'>\n /** whether to find all nodes, default is find first node */\n isFindAll?: boolean\n /** Function called for each node in the tree */\n onEachTraverse?: (node: TreeNode) => void\n}\n\n/**\n * Returns the first node or all nodes in the tree that matches the predicate (use breadth-first traverse).\n * @param {TreeNode[] | TreeNode} tree - TreeNode[] | TreeNode\n * @param predicate - (node: TreeNode) => boolean\n * @param {Options} [options]\n * @returns {TreeNode[]} - a array of TreeNode that matches the predicate\n * @linkcode https://github.com/GreatAuk/utopia-utils/blob/main/packages/tree/src/treeFindNode.ts\n * @example\n * ```\n const tree = [\n {\n name: 'a',\n children: [\n { name: 'b' },\n ],\n }\n ]\n const res = treeFindNode(tree, node => node.name === 'b') // res is [{ name: 'b' }]\n * ```\n */\nexport function treeFindNode<TreeNode>(tree: TreeNode[] | TreeNode, predicate: (node: TreeNode) => boolean, options?: Options<TreeNode>): TreeNode[] {\n const { isFindAll, fieldNames, onEachTraverse } = options || {}\n if (!isFunction(predicate))\n throw new Error('predicate should be a function')\n\n const res: TreeNode[] = []\n\n breadthFirstTraverse<TreeNode>(\n tree,\n (node) => {\n onEachTraverse?.(node)\n if (predicate(node)) {\n res.push(node)\n if (!isFindAll)\n return false\n }\n },\n {\n fieldNames,\n },\n )\n return res\n}\n","import { DEFAULT_FIELD_NAMES } from './constant'\nimport type { FieldNames } from './type'\n\ninterface Options {\n /** Customize node field name */\n fieldNames?: Pick<FieldNames, 'children'>\n}\n\n/**\n * It takes a tree and a predicate, and returns the path to the first node that satisfies the predicate\n * @param {TreeNode[] | TreeNode} tree - The tree to search.\n * @param predicate - (node: TreeNode) => boolean\n * @param {Options}\n * @returns A path to a node in a tree that matches the predicate.\n * @linkcode https://github.com/GreatAuk/utopia-utils/blob/main/packages/tree/src/treeFindPath.ts\n * @example\n * ```ts\n const tree = [\n {\n name: 'a',\n children: [\n { name: 'b' },\n ],\n },\n {\n name: 'c',\n },\n ]\n\n const path = treeFindPath(tree, node => node.name === 'b')\n console.log(path?.map(v => v.name)) // ['a', 'b']\n * ```\n */\nexport function treeFindPath<TreeNode>(tree: TreeNode[] | TreeNode, predicate: (node: TreeNode) => boolean, options?: Options): TreeNode[] | null {\n const { fieldNames } = options || {}\n const { children: childrenField } = { ...DEFAULT_FIELD_NAMES, ...fieldNames }\n\n const path: TreeNode[] = []\n const queue = Array.isArray(tree) ? [...tree] : [tree]\n const visitedSet = new Set<TreeNode>()\n\n while (queue.length) {\n const node = queue[0]\n if (visitedSet.has(node)) {\n path.pop()\n queue.shift()\n }\n else {\n visitedSet.add(node)\n // @ts-expect-error - dynamic field name\n node[childrenField] && queue.unshift(...node[childrenField])\n path.push(node)\n if (predicate(node))\n return path\n }\n }\n return null\n}\n"],"mappings":"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"}