├── .gitignore ├── .mocharc.json ├── README.MD ├── docs ├── .vuepress │ ├── components │ │ ├── common │ │ │ ├── layout.vue │ │ │ └── tree.vue │ │ ├── demo-filter.vue │ │ ├── demo-find.vue │ │ └── demo-foreach.vue │ ├── config.js │ └── public │ │ └── tree-lodash.js ├── README.md ├── functions │ ├── filter.md │ ├── find.md │ ├── foreach.md │ ├── fromArray.md │ ├── map.md │ ├── some.md │ └── toArray.md └── guide │ ├── README.md │ └── features.md ├── package.json ├── rollup.esm.config.js ├── rollup.umd.config.js ├── src ├── filter.ts ├── find.ts ├── foreach.ts ├── fromArray.ts ├── helpers │ └── common.ts ├── index.ts ├── map.ts ├── some.ts ├── toArray.ts └── types.ts ├── test ├── filter.test.ts ├── find.test.ts ├── foreach.test.ts ├── fromArray.test.ts ├── map.test.ts ├── some.test.ts ├── toArray.test.ts └── tsconfig.test.json └── ts.config.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist/** 3 | 4 | yarn.lock 5 | 6 | .nyc_output 7 | 8 | docs/.vuepress/dist -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensions": ["ts", "d.ts"], 3 | "spec": ["test/**/**.test.*"], 4 | "node-option": [ 5 | "experimental-specifier-resolution=node", 6 | "loader=ts-node/esm" 7 | ] 8 | } -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # tree-lodash 2 | 3 | > Easily control the tree structure as you would with `lodash.js` 4 | 5 | > 像使用 `lodash.js` 一样方便地操控树结构 6 | 7 | 充分的单元测试: 8 | ![](https://pic.zhangshichun.top/pic/20230718-01.jpg) 9 | 10 | ## Install & Usage(安装&使用) 11 | 12 | 👉[安装&使用文档(Install Document)](./docs/guide/README.md) 13 | 14 | ## Features (特性) 15 | 16 | 👉[特性(Features) & 配置(Configs)](./docs/guide/features.md) 17 | 18 | ## Functions (方法列表) 19 | 20 | - `foreach`: 👉[foreach 文档](./docs/functions/foreach.md) 21 | - `map`: 👉[map 文档](./docs/functions/map.md) 22 | - `filter`: 👉[filter 文档](./docs/functions/filter.md) 23 | - `find`: 👉[find 文档](./docs/functions/find.md) 24 | - `some`: 👉[some 文档](./docs/functions/some.md) 25 | - `toArray`: 👉[toArray 文档](./docs/functions/toArray.md) 26 | - `fromArray`: 👉[fromArray 文档](./docs/functions/fromArray.md) 27 | 28 | ## 感谢 (Thanks) 29 | 30 | Thanks to [joaonuno/tree-model-js](https://github.com/joaonuno/tree-model-js), It's a Nice lib, and help me at work; 31 | -------------------------------------------------------------------------------- /docs/.vuepress/components/common/layout.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 67 | 87 | -------------------------------------------------------------------------------- /docs/.vuepress/components/common/tree.vue: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /docs/.vuepress/components/demo-filter.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 107 | 118 | -------------------------------------------------------------------------------- /docs/.vuepress/components/demo-find.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 107 | 118 | -------------------------------------------------------------------------------- /docs/.vuepress/components/demo-foreach.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 96 | 107 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'tree-lodash(树大师)', 3 | description: '像使用 `lodash.js` 一样方便地操控树结构', 4 | base: '/tree-lodash/', 5 | head: [ 6 | ['script', { src: '/tree-lodash.js' }], 7 | ['script', { src: 'https://gw.alipayobjects.com/os/lib/antv/g6/4.3.11/dist/g6.min.js' }], 8 | ], 9 | themeConfig: { 10 | sidebar: [ 11 | { 12 | title: '简介(Intro)', // 必要的 13 | path: '/', // 可选的, 标题的跳转链接,应为绝对路径且必须存在 14 | collapsable: false, // 可选的, 默认值是 true, 15 | sidebarDepth: 1, // 可选的, 默认值是 1 16 | }, 17 | { 18 | title: '起步(Get Start)', // 必要的 19 | path: '/guide/', // 可选的, 标题的跳转链接,应为绝对路径且必须存在 20 | collapsable: false, // 可选的, 默认值是 true, 21 | sidebarDepth: 1, // 可选的, 默认值是 1 22 | children: [ 23 | '/guide/', 24 | '/guide/features.html' 25 | ] 26 | }, 27 | { 28 | title: '方法列表', 29 | children: [ 30 | '/functions/foreach.html', 31 | '/functions/map.html', 32 | '/functions/filter.html', 33 | '/functions/find.html', 34 | '/functions/some.html', 35 | '/functions/toArray.html', 36 | '/functions/fromArray.html', 37 | ], 38 | collapsable: false, // 可选的, 默认值是 true, 39 | sidebarDepth: 1, // 可选的, 默认值是 1 40 | } 41 | ], 42 | nav: [ 43 | { text: 'Github(求求点个star吧)', link: 'https://github.com/zhangshichun/tree-lodash' }, 44 | ] 45 | } 46 | } -------------------------------------------------------------------------------- /docs/.vuepress/public/tree-lodash.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : 3 | typeof define === 'function' && define.amd ? define(['exports'], factory) : 4 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.treeLodash = {})); 5 | })(this, (function (exports) { 'use strict'; 6 | 7 | function getFinalChildrenKey(tree, meta, options) { 8 | if (typeof options.getChildrenKey === "function") { 9 | const dynamicChildrenKey = options.getChildrenKey(tree, meta); 10 | if (dynamicChildrenKey != null) { 11 | return dynamicChildrenKey; 12 | } 13 | } 14 | return options.childrenKey; 15 | } 16 | 17 | var __defProp$3 = Object.defineProperty; 18 | var __defProps$3 = Object.defineProperties; 19 | var __getOwnPropDescs$3 = Object.getOwnPropertyDescriptors; 20 | var __getOwnPropSymbols$3 = Object.getOwnPropertySymbols; 21 | var __hasOwnProp$3 = Object.prototype.hasOwnProperty; 22 | var __propIsEnum$3 = Object.prototype.propertyIsEnumerable; 23 | var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; 24 | var __spreadValues$3 = (a, b) => { 25 | for (var prop in b || (b = {})) 26 | if (__hasOwnProp$3.call(b, prop)) 27 | __defNormalProp$3(a, prop, b[prop]); 28 | if (__getOwnPropSymbols$3) 29 | for (var prop of __getOwnPropSymbols$3(b)) { 30 | if (__propIsEnum$3.call(b, prop)) 31 | __defNormalProp$3(a, prop, b[prop]); 32 | } 33 | return a; 34 | }; 35 | var __spreadProps$3 = (a, b) => __defProps$3(a, __getOwnPropDescs$3(b)); 36 | const preImpl$3 = (treeItem, callback, options) => { 37 | callback(treeItem, options); 38 | const finalChildrenKey = getFinalChildrenKey(treeItem, options, options); 39 | const children = treeItem[finalChildrenKey]; 40 | if (children && Array.isArray(children)) { 41 | const nextLevelOptions = __spreadProps$3(__spreadValues$3({}, options), { 42 | parents: [...options.parents, treeItem], 43 | depth: options.depth + 1 44 | }); 45 | children.forEach((childItem) => { 46 | preImpl$3(childItem, callback, nextLevelOptions); 47 | }); 48 | } 49 | }; 50 | const postImpl$3 = (treeItem, callback, options) => { 51 | const finalChildrenKey = getFinalChildrenKey(treeItem, options, options); 52 | const children = treeItem[finalChildrenKey]; 53 | if (children && Array.isArray(children)) { 54 | const nextLevelOptions = __spreadProps$3(__spreadValues$3({}, options), { 55 | parents: [...options.parents, treeItem], 56 | depth: options.depth + 1 57 | }); 58 | children.forEach((childItem) => { 59 | postImpl$3(childItem, callback, nextLevelOptions); 60 | }); 61 | } 62 | callback(treeItem, options); 63 | }; 64 | const breadthImpl$3 = (treeItem, callback, options) => { 65 | const queue = [ 66 | { 67 | tree: treeItem, 68 | options 69 | } 70 | ]; 71 | const runQueue = () => { 72 | if (queue.length === 0) { 73 | return; 74 | } 75 | const queueItem = queue.shift(); 76 | const treeItem2 = queueItem.tree; 77 | const finalChildrenKey = getFinalChildrenKey(treeItem2, queueItem.options, queueItem.options); 78 | if (treeItem2[finalChildrenKey] && Array.isArray(treeItem2[finalChildrenKey])) { 79 | const nextLevelOptions = __spreadProps$3(__spreadValues$3({}, queueItem.options), { 80 | parents: [...queueItem.options.parents, treeItem2], 81 | depth: queueItem.options.depth + 1 82 | }); 83 | const subQueueItems = treeItem2[finalChildrenKey].map((subTree) => ({ 84 | tree: subTree, 85 | options: nextLevelOptions 86 | })); 87 | queue.push(...subQueueItems); 88 | } 89 | callback(treeItem2, queueItem.options); 90 | runQueue(); 91 | }; 92 | runQueue(); 93 | }; 94 | const strategies$3 = { 95 | "pre": preImpl$3, 96 | "post": postImpl$3, 97 | "breadth": breadthImpl$3 98 | }; 99 | function foreach(tree, callback, options) { 100 | var _a, _b; 101 | const childrenKey = (_a = options == null ? void 0 : options.childrenKey) != null ? _a : "children"; 102 | const strategy = (_b = options == null ? void 0 : options.strategy) != null ? _b : "pre"; 103 | const getChildrenKey = options == null ? void 0 : options.getChildrenKey; 104 | const isForest = Array.isArray(tree); 105 | const method = strategies$3[strategy]; 106 | const innerOptions = { 107 | childrenKey, 108 | depth: 0, 109 | parents: [], 110 | getChildrenKey 111 | }; 112 | isForest ? tree.forEach((tree2) => { 113 | method(tree2, callback, innerOptions); 114 | }) : method(tree, callback, innerOptions); 115 | } 116 | 117 | var __defProp$2 = Object.defineProperty; 118 | var __defProps$2 = Object.defineProperties; 119 | var __getOwnPropDescs$2 = Object.getOwnPropertyDescriptors; 120 | var __getOwnPropSymbols$2 = Object.getOwnPropertySymbols; 121 | var __hasOwnProp$2 = Object.prototype.hasOwnProperty; 122 | var __propIsEnum$2 = Object.prototype.propertyIsEnumerable; 123 | var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; 124 | var __spreadValues$2 = (a, b) => { 125 | for (var prop in b || (b = {})) 126 | if (__hasOwnProp$2.call(b, prop)) 127 | __defNormalProp$2(a, prop, b[prop]); 128 | if (__getOwnPropSymbols$2) 129 | for (var prop of __getOwnPropSymbols$2(b)) { 130 | if (__propIsEnum$2.call(b, prop)) 131 | __defNormalProp$2(a, prop, b[prop]); 132 | } 133 | return a; 134 | }; 135 | var __spreadProps$2 = (a, b) => __defProps$2(a, __getOwnPropDescs$2(b)); 136 | const preImpl$2 = (treeItem, callback, options) => { 137 | const finalChildrenKey = getFinalChildrenKey(treeItem, options, options); 138 | const res = callback(treeItem, options); 139 | const children = treeItem[finalChildrenKey]; 140 | let newChildren; 141 | if (children && Array.isArray(children)) { 142 | const nextLevelOptions = __spreadProps$2(__spreadValues$2({}, options), { 143 | parents: [...options.parents, treeItem], 144 | depth: options.depth + 1 145 | }); 146 | newChildren = children.map((childItem) => { 147 | return preImpl$2(childItem, callback, nextLevelOptions); 148 | }); 149 | } 150 | return __spreadProps$2(__spreadValues$2({}, res), { 151 | [finalChildrenKey]: newChildren 152 | }); 153 | }; 154 | const postImpl$2 = (treeItem, callback, options) => { 155 | const finalChildrenKey = getFinalChildrenKey(treeItem, options, options); 156 | const children = treeItem[finalChildrenKey]; 157 | let newChildren; 158 | if (children && Array.isArray(children)) { 159 | const nextLevelOptions = __spreadProps$2(__spreadValues$2({}, options), { 160 | parents: [...options.parents, treeItem], 161 | depth: options.depth + 1 162 | }); 163 | newChildren = children.map((childItem) => { 164 | return postImpl$2(childItem, callback, nextLevelOptions); 165 | }); 166 | } 167 | const res = callback(treeItem, options); 168 | return __spreadProps$2(__spreadValues$2({}, res), { 169 | [finalChildrenKey]: newChildren 170 | }); 171 | }; 172 | const breadthImpl$2 = (treeItem, callback, options) => { 173 | const queue = [ 174 | { 175 | tree: treeItem, 176 | options 177 | } 178 | ]; 179 | let result; 180 | const cache = /* @__PURE__ */ new WeakMap(); 181 | const childrenKeyCache = /* @__PURE__ */ new WeakMap(); 182 | const runQueue = () => { 183 | if (queue.length === 0) { 184 | return result; 185 | } 186 | const queueItem = queue.shift(); 187 | const treeItem2 = queueItem.tree; 188 | const finalChildrenKey = getFinalChildrenKey(treeItem2, queueItem.options, queueItem.options); 189 | if (treeItem2[finalChildrenKey] && Array.isArray(treeItem2[finalChildrenKey])) { 190 | const nextLevelOptions = __spreadProps$2(__spreadValues$2({}, queueItem.options), { 191 | parents: [...queueItem.options.parents, treeItem2], 192 | depth: queueItem.options.depth + 1 193 | }); 194 | const subQueueItems = treeItem2[finalChildrenKey].map((subTree) => ({ 195 | tree: subTree, 196 | options: nextLevelOptions 197 | })); 198 | queue.push(...subQueueItems); 199 | } 200 | const res = callback(treeItem2, queueItem.options); 201 | cache.set(queueItem.tree, res); 202 | childrenKeyCache.set(queueItem.tree, finalChildrenKey); 203 | const parent = queueItem.options.parents.length > 0 ? queueItem.options.parents[queueItem.options.parents.length - 1] : void 0; 204 | if (parent) { 205 | const newParent = cache.get(parent); 206 | const parentChildrenKey = childrenKeyCache.get(parent); 207 | if (newParent[parentChildrenKey]) { 208 | newParent[parentChildrenKey].push(res); 209 | } else { 210 | newParent[parentChildrenKey] = [res]; 211 | } 212 | } 213 | if (queueItem.options.depth === 0) { 214 | result = res; 215 | } 216 | return runQueue(); 217 | }; 218 | return runQueue(); 219 | }; 220 | const strategies$2 = { 221 | "pre": preImpl$2, 222 | "post": postImpl$2, 223 | "breadth": breadthImpl$2 224 | }; 225 | function map(tree, callback, options = {}) { 226 | var _a, _b; 227 | const childrenKey = (_a = options.childrenKey) != null ? _a : "children"; 228 | const strategy = (_b = options.strategy) != null ? _b : "pre"; 229 | const getChildrenKey = options.getChildrenKey; 230 | const isForest = Array.isArray(tree); 231 | const method = strategies$2[strategy]; 232 | const innerOptions = { 233 | childrenKey, 234 | depth: 0, 235 | parents: [], 236 | getChildrenKey 237 | }; 238 | return isForest ? tree.map((tree2) => { 239 | return method(tree2, callback, innerOptions); 240 | }) : method(tree, callback, innerOptions); 241 | } 242 | 243 | var __defProp$1 = Object.defineProperty; 244 | var __defProps$1 = Object.defineProperties; 245 | var __getOwnPropDescs$1 = Object.getOwnPropertyDescriptors; 246 | var __getOwnPropSymbols$1 = Object.getOwnPropertySymbols; 247 | var __hasOwnProp$1 = Object.prototype.hasOwnProperty; 248 | var __propIsEnum$1 = Object.prototype.propertyIsEnumerable; 249 | var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; 250 | var __spreadValues$1 = (a, b) => { 251 | for (var prop in b || (b = {})) 252 | if (__hasOwnProp$1.call(b, prop)) 253 | __defNormalProp$1(a, prop, b[prop]); 254 | if (__getOwnPropSymbols$1) 255 | for (var prop of __getOwnPropSymbols$1(b)) { 256 | if (__propIsEnum$1.call(b, prop)) 257 | __defNormalProp$1(a, prop, b[prop]); 258 | } 259 | return a; 260 | }; 261 | var __spreadProps$1 = (a, b) => __defProps$1(a, __getOwnPropDescs$1(b)); 262 | const preImpl$1 = (treeItem, callback, options) => { 263 | const res = callback(treeItem, options); 264 | if (!res) { 265 | return void 0; 266 | } 267 | const finalChildrenKey = getFinalChildrenKey(treeItem, options, options); 268 | const children = treeItem[finalChildrenKey]; 269 | let newChildren; 270 | if (children && Array.isArray(children)) { 271 | newChildren = children.map((childItem) => { 272 | return preImpl$1(childItem, callback, __spreadProps$1(__spreadValues$1({}, options), { 273 | parents: [...options.parents, treeItem], 274 | depth: options.depth + 1 275 | })); 276 | }).filter((t) => !!t); 277 | } 278 | return __spreadProps$1(__spreadValues$1({}, treeItem), { 279 | [finalChildrenKey]: newChildren 280 | }); 281 | }; 282 | const postImpl$1 = (treeItem, callback, options) => { 283 | const finalChildrenKey = getFinalChildrenKey(treeItem, options, options); 284 | const children = treeItem[finalChildrenKey]; 285 | let newChildren; 286 | if (children && Array.isArray(children)) { 287 | newChildren = children.map((childItem) => { 288 | return postImpl$1(childItem, callback, __spreadProps$1(__spreadValues$1({}, options), { 289 | parents: [...options.parents, treeItem], 290 | depth: options.depth + 1 291 | })); 292 | }).filter((t) => !!t); 293 | } 294 | const res = callback(treeItem, options); 295 | if (!res) { 296 | return void 0; 297 | } 298 | return __spreadProps$1(__spreadValues$1({}, treeItem), { 299 | [finalChildrenKey]: newChildren 300 | }); 301 | }; 302 | function genNewNoChildrenNode(node, childrenKey) { 303 | const newNode = __spreadValues$1({}, node); 304 | delete newNode[childrenKey]; 305 | return newNode; 306 | } 307 | const breadthImpl$1 = (treeItem, callback, options) => { 308 | const queue = [ 309 | { 310 | tree: treeItem, 311 | options 312 | } 313 | ]; 314 | let result; 315 | const resultCache = /* @__PURE__ */ new WeakMap(); 316 | const newNodeCache = /* @__PURE__ */ new WeakMap(); 317 | const childrenKeyCache = /* @__PURE__ */ new WeakMap(); 318 | const runQueue = () => { 319 | if (queue.length === 0) { 320 | return result; 321 | } 322 | const queueItem = queue.shift(); 323 | const treeItem2 = queueItem.tree; 324 | const finalChildrenKey = getFinalChildrenKey(treeItem2, queueItem.options, queueItem.options); 325 | if (treeItem2[finalChildrenKey] && Array.isArray(treeItem2[finalChildrenKey])) { 326 | const subQueueItems = treeItem2[finalChildrenKey].map((subTree) => ({ 327 | tree: subTree, 328 | options: __spreadProps$1(__spreadValues$1({}, queueItem.options), { 329 | parents: [...queueItem.options.parents, treeItem2], 330 | depth: queueItem.options.depth + 1 331 | }) 332 | })); 333 | queue.push(...subQueueItems); 334 | } 335 | const parent = queueItem.options.parents.length > 0 ? queueItem.options.parents[queueItem.options.parents.length - 1] : void 0; 336 | const isTopNode = queueItem.options.depth === 0; 337 | const parentResult = parent && resultCache.get(parent); 338 | if (!isTopNode && !parentResult) { 339 | return runQueue(); 340 | } 341 | const callbackResult = callback(treeItem2, queueItem.options); 342 | if (isTopNode && !callbackResult) { 343 | return void 0; 344 | } 345 | let newNode = genNewNoChildrenNode(treeItem2, finalChildrenKey); 346 | if (isTopNode) { 347 | result = newNode; 348 | } 349 | resultCache.set(queueItem.tree, callbackResult); 350 | newNodeCache.set(queueItem.tree, newNode); 351 | childrenKeyCache.set(queueItem.tree, finalChildrenKey); 352 | if (callbackResult && parent) { 353 | const parentNewNode = newNodeCache.get(parent); 354 | const parentChildrenKey = childrenKeyCache.get(parent); 355 | if (!parentNewNode[parentChildrenKey]) { 356 | parentNewNode[parentChildrenKey] = []; 357 | } 358 | parentNewNode[parentChildrenKey].push(newNode); 359 | } 360 | return runQueue(); 361 | }; 362 | return runQueue(); 363 | }; 364 | const strategies$1 = { 365 | "pre": preImpl$1, 366 | "post": postImpl$1, 367 | "breadth": breadthImpl$1 368 | }; 369 | function filter(tree, callback, options = {}) { 370 | var _a, _b; 371 | const childrenKey = (_a = options.childrenKey) != null ? _a : "children"; 372 | const strategy = (_b = options.strategy) != null ? _b : "pre"; 373 | const getChildrenKey = options.getChildrenKey; 374 | const isForest = Array.isArray(tree); 375 | const method = strategies$1[strategy]; 376 | const innerOptions = { 377 | childrenKey, 378 | depth: 0, 379 | parents: [], 380 | getChildrenKey 381 | }; 382 | return isForest ? tree.map((tree2) => { 383 | return method(tree2, callback, innerOptions); 384 | }).filter((t) => !!t) : method(tree, callback, innerOptions); 385 | } 386 | 387 | function toArray(tree, options) { 388 | const results = []; 389 | foreach(tree, (t) => { 390 | results.push(t); 391 | }, options); 392 | return results; 393 | } 394 | 395 | var __defProp = Object.defineProperty; 396 | var __defProps = Object.defineProperties; 397 | var __getOwnPropDescs = Object.getOwnPropertyDescriptors; 398 | var __getOwnPropSymbols = Object.getOwnPropertySymbols; 399 | var __hasOwnProp = Object.prototype.hasOwnProperty; 400 | var __propIsEnum = Object.prototype.propertyIsEnumerable; 401 | var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; 402 | var __spreadValues = (a, b) => { 403 | for (var prop in b || (b = {})) 404 | if (__hasOwnProp.call(b, prop)) 405 | __defNormalProp(a, prop, b[prop]); 406 | if (__getOwnPropSymbols) 407 | for (var prop of __getOwnPropSymbols(b)) { 408 | if (__propIsEnum.call(b, prop)) 409 | __defNormalProp(a, prop, b[prop]); 410 | } 411 | return a; 412 | }; 413 | var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); 414 | const preImpl = (treeItem, callback, options) => { 415 | const callbackResult = callback(treeItem, options); 416 | if (callbackResult) { 417 | return treeItem; 418 | } 419 | const finalChildrenKey = getFinalChildrenKey(treeItem, options, options); 420 | const children = treeItem[finalChildrenKey]; 421 | if (!children || !Array.isArray(children)) { 422 | return void 0; 423 | } 424 | for (let i = 0, count = children.length; i < count; i++) { 425 | const childItem = children[i]; 426 | const findOne = preImpl(childItem, callback, __spreadProps(__spreadValues({}, options), { 427 | parents: [...options.parents, treeItem], 428 | depth: options.depth + 1 429 | })); 430 | if (findOne) { 431 | return findOne; 432 | } 433 | } 434 | return void 0; 435 | }; 436 | const postImpl = (treeItem, callback, options) => { 437 | const finalChildrenKey = getFinalChildrenKey(treeItem, options, options); 438 | const children = treeItem[finalChildrenKey]; 439 | if (children && Array.isArray(children)) { 440 | for (let i = 0, count = children.length; i < count; i++) { 441 | const childItem = children[i]; 442 | const findOne = postImpl(childItem, callback, __spreadProps(__spreadValues({}, options), { 443 | parents: [...options.parents, treeItem], 444 | depth: options.depth + 1 445 | })); 446 | if (findOne) { 447 | return findOne; 448 | } 449 | } 450 | } 451 | const callbackResult = callback(treeItem, options); 452 | if (callbackResult) { 453 | return treeItem; 454 | } 455 | return void 0; 456 | }; 457 | const breadthImpl = (treeItem, callback, options) => { 458 | const queue = [ 459 | { 460 | tree: treeItem, 461 | options 462 | } 463 | ]; 464 | const runQueue = () => { 465 | if (queue.length === 0) { 466 | return void 0; 467 | } 468 | const queueItem = queue.shift(); 469 | const treeItem2 = queueItem.tree; 470 | const finalChildrenKey = getFinalChildrenKey(treeItem2, queueItem.options, queueItem.options); 471 | if (treeItem2[finalChildrenKey] && Array.isArray(treeItem2[finalChildrenKey])) { 472 | const subQueueItems = treeItem2[finalChildrenKey].map((subTree) => ({ 473 | tree: subTree, 474 | options: __spreadProps(__spreadValues({}, queueItem.options), { 475 | parents: [...queueItem.options.parents, treeItem2], 476 | depth: queueItem.options.depth + 1 477 | }) 478 | })); 479 | queue.push(...subQueueItems); 480 | } 481 | const callbackResult = callback(treeItem2, queueItem.options); 482 | if (callbackResult) { 483 | return treeItem2; 484 | } 485 | return runQueue(); 486 | }; 487 | return runQueue(); 488 | }; 489 | const strategies = { 490 | "pre": preImpl, 491 | "post": postImpl, 492 | "breadth": breadthImpl 493 | }; 494 | function find(tree, callback, options) { 495 | var _a, _b; 496 | const childrenKey = (_a = options == null ? void 0 : options.childrenKey) != null ? _a : "children"; 497 | const strategy = (_b = options == null ? void 0 : options.strategy) != null ? _b : "pre"; 498 | const getChildrenKey = options == null ? void 0 : options.getChildrenKey; 499 | const method = strategies[strategy]; 500 | const innerOptions = { 501 | childrenKey, 502 | depth: 0, 503 | parents: [], 504 | getChildrenKey 505 | }; 506 | if (Array.isArray(tree)) { 507 | for (let i = 0, count = tree.length; i < count; i++) { 508 | const treeItem = tree[i]; 509 | const result = method(treeItem, callback, innerOptions); 510 | if (result) { 511 | return result; 512 | } 513 | } 514 | return void 0; 515 | } 516 | return method(tree, callback, innerOptions); 517 | } 518 | 519 | function some(tree, callback, options) { 520 | const matchedItem = find(tree, callback, options); 521 | return matchedItem != void 0; 522 | } 523 | 524 | function fromArray(array, options) { 525 | const result = []; 526 | const { 527 | parentKey = "pid", 528 | itemKey = "id", 529 | childrenKey = "children" 530 | } = options || {}; 531 | const map = /* @__PURE__ */ new Map(); 532 | array.map((item) => { 533 | const itemKeyValue = item[itemKey]; 534 | if (!map.get(itemKeyValue)) { 535 | map.set(itemKeyValue, item); 536 | } 537 | }); 538 | array.map((item) => { 539 | const parentKeyValue = item[parentKey]; 540 | const parentItem = map.get(parentKeyValue); 541 | if (!parentItem || !parentKeyValue) { 542 | result.push(item); 543 | return; 544 | } 545 | const siblings = parentItem[childrenKey]; 546 | if (siblings === null || siblings === void 0) { 547 | parentItem[childrenKey] = [item]; 548 | } else if (Array.isArray(siblings)) { 549 | siblings.push(item); 550 | } else { 551 | const msg = `the key "${childrenKey}" is not an array, please check your data`; 552 | throw new Error(msg); 553 | } 554 | }); 555 | return result; 556 | } 557 | 558 | var index = { 559 | foreach, 560 | map, 561 | filter, 562 | toArray, 563 | find, 564 | some, 565 | fromArray 566 | }; 567 | 568 | exports["default"] = index; 569 | exports.filter = filter; 570 | exports.find = find; 571 | exports.foreach = foreach; 572 | exports.fromArray = fromArray; 573 | exports.map = map; 574 | exports.some = some; 575 | exports.toArray = toArray; 576 | 577 | Object.defineProperty(exports, '__esModule', { value: true }); 578 | 579 | })); 580 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: '简介' 3 | --- 4 | 5 | # tree-lodash 6 | 7 | > Easily control the tree structure as you would with `lodash.js` 8 | 9 | > 像使用 `lodash.js` 一样方便地操控树结构 10 | 11 | ## `tree-lodash` 是什么? 12 | 13 | 一个简简单单,纯纯粹粹的函数库,并且,它所提供的函数都是 **“纯函数”**,并不会对原数据结构直接产生修改。(当然,用户自行写了修改逻辑除外) 14 | 15 | ## 好用,就像呼吸一样自然 16 | 17 | ```js 18 | import { filter } from 'tree-lodash' 19 | const tree = { 20 | key: 1, 21 | children: [ 22 | { 23 | key: 2, 24 | children: [ 25 | { 26 | key: 3 27 | } 28 | ] 29 | } 30 | ] 31 | } 32 | filter(tree, (t) => t.key < 2) 33 | /** 34 | * { 35 | * key: 1, 36 | * children: [] 37 | * } 38 | */ 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/functions/filter.md: -------------------------------------------------------------------------------- 1 | # filter 2 | 3 | ```js 4 | filter(tree, predicate, [options]) 5 | ``` 6 | 7 | 遍历把 "树" 或者 "森林",并把返回非真值的节点剔除。(不会影响原结构,返回的树是新生成的) 8 | 9 | **添加版本**:v0.0.2 10 | 11 | 12 | 13 | 参数: 14 | 15 | 1. `tree`: 典型树结构,或者由多个树结构构成的数组; 16 | 2. `predicate`: 每次迭代调用的函数,返回非真值时,该节点会从树上剔除。 17 | 3. `[options]`: 配置项,支持 `strategy` 和 `childrenKey` 18 | 19 | 示例: 20 | 21 | ```js 22 | const data = { 23 | key: 1, 24 | children: [ 25 | { 26 | key: 11, 27 | children: [ 28 | { 29 | key: 111 30 | }, 31 | { 32 | key: 112 33 | } 34 | ] 35 | }, 36 | { 37 | key: 12, 38 | children: [ 39 | { 40 | key: 122, 41 | children: [ 42 | { 43 | key: 1221 44 | }, 45 | { 46 | key: 1222 47 | } 48 | ] 49 | } 50 | ] 51 | } 52 | ] 53 | } 54 | filter(data, (t) => t.key < 100) 55 | /** 56 | { 57 | key: 1, 58 | children: [ 59 | { 60 | key: 11, 61 | children: [] 62 | }, 63 | { 64 | key: 12, 65 | children: [] 66 | } 67 | ] 68 | } 69 | */ 70 | ``` 71 | -------------------------------------------------------------------------------- /docs/functions/find.md: -------------------------------------------------------------------------------- 1 | # find 2 | 3 | ```js 4 | find(tree, predicate, [options]) 5 | ``` 6 | 7 | 遍历把 "树" 或者 "森林",找到第一个返回非空值的节点。 8 | 9 | **添加版本**:v0.1.0 10 | 11 | 12 | 13 | 参数: 14 | 15 | 1. `tree`: 典型树结构,或者由多个树结构构成的数组; 16 | 2. `predicate`: 每次迭代调用的函数,返回非真值时,该节点会从树上剔除。 17 | 3. `[options]`: 配置项,支持 `strategy` 和 `childrenKey` 18 | 19 | 示例: 20 | 21 | ```js 22 | const data = { 23 | key: 1, 24 | children: [ 25 | { 26 | key: 11, 27 | children: [ 28 | { 29 | key: 111 30 | }, 31 | { 32 | key: 112 33 | } 34 | ] 35 | }, 36 | { 37 | key: 12, 38 | children: [ 39 | { 40 | key: 122, 41 | children: [ 42 | { 43 | key: 1221 44 | }, 45 | { 46 | key: 1222 47 | } 48 | ] 49 | } 50 | ] 51 | } 52 | ] 53 | } 54 | find(data, (t) => t.key < 100 && t.key > 10) 55 | /** 56 | * 会保留其本来的结构 57 | { 58 | key: 11, 59 | children: [ 60 | { 61 | key: 111 62 | }, 63 | { 64 | key: 112 65 | } 66 | ] 67 | } 68 | */ 69 | ``` 70 | -------------------------------------------------------------------------------- /docs/functions/foreach.md: -------------------------------------------------------------------------------- 1 | # foreach 2 | 3 | ```js 4 | foreach(tree, predicate, [options]) 5 | ``` 6 | 7 | 遍历把 "树" 或者 "森林",对每个节点执行回调。 8 | 9 | **添加版本**:v0.0.2 10 | 11 | 12 | 13 | 参数: 14 | 15 | 1. `tree`: 典型树结构,或者由多个树结构构成的数组; 16 | 2. `predicate`: 每次迭代调用的函数。 17 | 3. `[options]`: 配置项,支持 `strategy` 和 `childrenKey` 18 | 19 | 示例: 20 | 21 | ```js 22 | const data = { 23 | key: 1, 24 | children: [ 25 | { 26 | key: 11, 27 | children: [ 28 | { 29 | key: 111 30 | }, 31 | { 32 | key: 112 33 | } 34 | ] 35 | }, 36 | { 37 | key: 12, 38 | children: [ 39 | { 40 | key: 122, 41 | children: [ 42 | { 43 | key: 1221 44 | }, 45 | { 46 | key: 1222 47 | } 48 | ] 49 | } 50 | ] 51 | } 52 | ] 53 | } 54 | foreach(data, (t) => console.log(t.key)) 55 | // 1 56 | // 11 57 | // 111 58 | // 112 59 | // 12 60 | // 122 61 | // 1221 62 | // 1222 63 | ``` 64 | -------------------------------------------------------------------------------- /docs/functions/fromArray.md: -------------------------------------------------------------------------------- 1 | # fromArray 2 | 3 | ```js 4 | fromArray(array, [options]) 5 | ``` 6 | 7 | 把一维数组,数组会包含所有节点。 8 | 9 | **添加版本**:v0.1.0 10 | 11 | 参数: 12 | 13 | 1. `array`: 一维数组,默认结构为 `{id: string, pid: string, ...}[]` 14 | 2. `[options]`: 配置项; 15 | - `options.itemKey`: 指定节点 `key` 字段名,默认值:`'id'`; 16 | - `options.parentKey`: 指定节点 `key` 字段名,默认值:`'pid'`; 17 | - `options.childrenKey`: 指定节点 `key` 字段名,默认值:`'children'`; 18 | 19 | 示例: 20 | 21 | ```js 22 | const tree = [ 23 | { 24 | id: "1", 25 | name: "1", 26 | }, 27 | { 28 | id: "2", 29 | name: "2", 30 | pid: "1", 31 | }, 32 | { 33 | id: "3", 34 | name: "3", 35 | pid: "1", 36 | }, 37 | { 38 | id: "4", 39 | name: "4", 40 | pid: "2", 41 | }, 42 | { 43 | id: "5", 44 | name: "5", 45 | }, 46 | ]; 47 | fromArray(tree) 48 | // => 49 | // [ 50 | // { 51 | // id: '1', 52 | // name: '1', 53 | // children: [ 54 | // { 55 | // id: "2", 56 | // name: "2", 57 | // pid: "1", 58 | // children: [ 59 | // { 60 | // id: "4", 61 | // name: "4", 62 | // pid: "2", 63 | // } 64 | // ] 65 | // }, 66 | // { 67 | // id: "3", 68 | // name: "3", 69 | // pid: "1", 70 | // }, 71 | // ] 72 | // }, 73 | // { 74 | // id: "5", 75 | // name: "5", 76 | // }, 77 | // ] 78 | ``` 79 | -------------------------------------------------------------------------------- /docs/functions/map.md: -------------------------------------------------------------------------------- 1 | # map 2 | 3 | ```js 4 | map(tree, predicate, [options]) 5 | ``` 6 | 7 | 遍历把 "树" 或者 "森林",根据返回的对象,组成新的树。(不会影响原结构,返回的树是新生成的) 8 | 9 | **添加版本**:v0.0.2 10 | 11 | 12 | 13 | 参数: 14 | 15 | 1. `tree`: 典型树结构,或者由多个树结构构成的数组; 16 | 2. `predicate`: 每次迭代调用的函数,需要返回一个对象,返回的对象上无需包括子节点。 17 | 3. `[options]`: 配置项,支持 `strategy` 和 `childrenKey` 18 | 19 | 示例: 20 | 21 | ```js 22 | const data = { 23 | key: 1, 24 | children: [ 25 | { 26 | key: 11, 27 | children: [ 28 | { 29 | key: 111 30 | }, 31 | { 32 | key: 112 33 | } 34 | ] 35 | }, 36 | { 37 | key: 12, 38 | children: [ 39 | { 40 | key: 122, 41 | children: [ 42 | { 43 | key: 1221 44 | }, 45 | { 46 | key: 1222 47 | } 48 | ] 49 | } 50 | ] 51 | } 52 | ] 53 | } 54 | const res = map(data, (t) => { name: `No.${t.key}` }) 55 | /** 56 | { 57 | key: 'No.1', 58 | children: [ 59 | { 60 | key: 'No.11', 61 | children: [ 62 | { 63 | key: 'No.111' 64 | }, 65 | { 66 | key: 'No.112' 67 | } 68 | ] 69 | }, 70 | { 71 | key: 'No.12', 72 | children: [ 73 | { 74 | key: 'No.122', 75 | children: [ 76 | { 77 | key: 'No.1221' 78 | }, 79 | { 80 | key: 'No.1222' 81 | } 82 | ] 83 | } 84 | ] 85 | } 86 | ] 87 | } 88 | */ 89 | ``` 90 | -------------------------------------------------------------------------------- /docs/functions/some.md: -------------------------------------------------------------------------------- 1 | # some 2 | 3 | ```js 4 | some(tree, predicate, [options]) 5 | ``` 6 | 7 | 遍历把 "树" 或者 "森林",判断符合某种条件的节点在树上是否存在。 8 | 9 | **添加版本**:v0.3.0 10 | 11 | 12 | 13 | 参数: 14 | 15 | 1. `tree`: 典型树结构,或者由多个树结构构成的数组; 16 | 2. `predicate`: 每次迭代调用的函数,返回非真值时,该节点会从树上剔除。 17 | 3. `[options]`: 配置项,支持 `strategy` 和 `childrenKey` 18 | 19 | 示例: 20 | 21 | ```js 22 | const data = { 23 | key: 1, 24 | children: [ 25 | { 26 | key: 11, 27 | children: [ 28 | { 29 | key: 111 30 | }, 31 | { 32 | key: 112 33 | } 34 | ] 35 | }, 36 | { 37 | key: 12, 38 | children: [ 39 | { 40 | key: 122, 41 | children: [ 42 | { 43 | key: 1221 44 | }, 45 | { 46 | key: 1222 47 | } 48 | ] 49 | } 50 | ] 51 | } 52 | ] 53 | } 54 | some(data, (t) => t.key < 100 && t.key > 10) 55 | /** 56 | * true (说明存在符合的节点) 57 | */ 58 | ``` 59 | -------------------------------------------------------------------------------- /docs/functions/toArray.md: -------------------------------------------------------------------------------- 1 | # toArray 2 | 3 | ```js 4 | toArray(tree, [options]) 5 | ``` 6 | 7 | 把 "树" 或者 "森林",转换为一维数组,数组会包含所有节点。 8 | 9 | **添加版本**:v0.0.2 10 | 11 | 参数: 12 | 13 | 1. `tree`: 典型树结构,或者由多个树结构构成的数组; 14 | 2. `[options]`: 配置项,支持 `strategy` 和 `childrenKey` 15 | 16 | 示例: 17 | 18 | ```js 19 | const tree = { 20 | key: '1', 21 | children: [ 22 | { 23 | key: '2', 24 | children: [ 25 | { 26 | key: '3' 27 | } 28 | ] 29 | } 30 | ] 31 | } 32 | toArray(tree).map(t => t.key) 33 | // ['1', '2', '3'] 34 | ``` 35 | -------------------------------------------------------------------------------- /docs/guide/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: '指南' 3 | --- 4 | 5 | # 指南 6 | 7 | ## 使用 `npm`安装 (Install by `npm`) 8 | 9 | ```shell 10 | yarn add tree-lodash 11 | 12 | # or 13 | 14 | npm i tree-lodash 15 | ``` 16 | 17 | ```js 18 | import { filter } from 'tree-lodash' 19 | const newTree = filter(tree, (item) => { 20 | return item.key < 100 21 | }) 22 | ``` 23 | 24 | ## 使用 `cdn` 方式引入(Use it by `cdn`) 25 | 26 | 引入:(注意:`jsdelivr` 非常不稳定,尤其针对国内网络,请绝对不要在生产环境使用它;推荐把该 `js` 下载到本地静态目录) 27 | 28 | ```html 29 | 30 | 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/guide/features.md: -------------------------------------------------------------------------------- 1 | # Features & Configs (配置项) 2 | 3 | > 所有方法的配置项均至少支持以下三个核心配置 4 | 5 | ## 一、`options.strategy`:搜索策略 6 | 7 | 所有本库提供的方法都支持以下三种策略(`strategy`): 8 | 9 | - `pre`: 深度优先,正序搜索; 10 | - `post`:深度优先,反序搜索; 11 | - `breadth`:广度优先 12 | 13 | 只需要在 `options` 入参中给出相关配置即可,默认策略为 `pre`; 14 | 15 | ```js 16 | { strategy: 'post' } 17 | ``` 18 | 19 | ## 二、`options.childrenKey` 支持树结构子节点 `key` 的命名 20 | 21 | 支持传入 `options.childrenKey` 参数,你不仅可以用 `children` 表示子节点; 22 | 23 | 也可以用 `subItems`、`babies` 等所有你能想到的词语表示子节点: 24 | 25 | ```js 26 | { childrenKey: 'babies' } 27 | ``` 28 | 29 | ## 三、`options.getChildrenKey` 支持一棵树上多种 `childrenKey` 30 | 31 | 下面这种结构的树也是可以被解析的了: 32 | 33 | ```js 34 | const treeMultiChildrenKey: Tree = { 35 | key: '1', 36 | children: [ 37 | { 38 | key: '2', 39 | subItems: [ 40 | { 41 | key: '3' 42 | } 43 | ] 44 | }, 45 | { 46 | key: '4', 47 | subItems: [ 48 | { 49 | key: '5' 50 | } 51 | ] 52 | } 53 | ] 54 | } 55 | ``` 56 | 57 | 但你需要在 `options.getChildrenKey` 返回响应的 `childrenKey`: 58 | 59 | ```js 60 | { 61 | getChildrenKey: (tree, meta) => { 62 | if (meta.depth === 1) { 63 | return 'subItems' 64 | } 65 | } 66 | } 67 | ``` 68 | 69 | (返回为 `undefined` 时,依然会使用 `options.childrenKey` 作为默认的 `key`) 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tree-lodash", 3 | "version": "0.4.0", 4 | "description": "Easily control the tree structure as you would with lodash.js (像使用 lodash.js 一样方便地操控树结构)", 5 | "keywords": [ 6 | "tree", 7 | "lodash", 8 | "functional", 9 | "browser", 10 | "node", 11 | "umd", 12 | "esm" 13 | ], 14 | "main": "dist/umd/index.js", 15 | "module": "dist/esm/index.js", 16 | "typings": "dist/esm/index.d.ts", 17 | "license": "MIT", 18 | "homepage": "https://zhangshichun.github.io/tree-lodash/", 19 | "repository": "https://github.com/zhangshichun/tree-lodash", 20 | "bugs": "https://github.com/zhangshichun/tree-lodash/issues", 21 | "author": "https://github.com/zhangshichun", 22 | "scripts": { 23 | "build": "run-p \"build:*\"", 24 | "build:umd": "cross-env NODE_ENV=production rollup -c rollup.umd.config.js", 25 | "build:esm": "cross-env NODE_ENV=production rollup -c rollup.esm.config.js", 26 | "postbuild": "tsc --emitDeclarationOnly --declaration --project ts.config.json --outDir dist/esm", 27 | "test": "cross-env TS_NODE_PROJECT='test/tsconfig.test.json' mocha", 28 | "nyc": "cross-env TS_NODE_PROJECT='test/tsconfig.test.json' nyc --reporter=text mocha", 29 | "docs:dev": "vuepress dev docs", 30 | "docs:build": "vuepress build docs", 31 | "predeploy": "yarn docs:build", 32 | "deploy": "gh-pages -d docs/.vuepress/dist -t true" 33 | }, 34 | "files": [ 35 | "dist" 36 | ], 37 | "devDependencies": { 38 | "@babel/core": "^7.17.5", 39 | "@rollup/plugin-babel": "^5.3.1", 40 | "@rollup/plugin-beep": "^0.2.0", 41 | "@rollup/plugin-commonjs": "^21.0.2", 42 | "@rollup/plugin-json": "^4.1.0", 43 | "@rollup/plugin-node-resolve": "^13.1.3", 44 | "@types/chai": "^4.3.5", 45 | "@types/mocha": "^10.0.1", 46 | "@types/node": "^20.3.3", 47 | "@types/sinon": "^10.0.15", 48 | "chai": "^4.3.7", 49 | "coveralls": "^3.1.1", 50 | "cross-env": "^7.0.3", 51 | "esbuild": "^0.14.43", 52 | "gh-pages": "^5.0.0", 53 | "mocha": "^10.2.0", 54 | "npm-run-all": "^4.1.5", 55 | "nyc": "^15.1.0", 56 | "rollup": "^2.68.0", 57 | "rollup-plugin-esbuild": "^4.9.1", 58 | "rollup-plugin-livereload": "^2.0.5", 59 | "rollup-plugin-serve": "^1.1.0", 60 | "sinon": "^15.2.0", 61 | "ts-node": "^10.9.1", 62 | "tsconfig-paths": "^4.2.0", 63 | "tslib": "^2.3.1", 64 | "typescript": "^4.6.2", 65 | "vuepress": "^1.9.9" 66 | }, 67 | "dependencies": {}, 68 | "publishConfig": { 69 | "registry": "https://registry.npmjs.org/" 70 | }, 71 | "mocha": { 72 | "require": [ 73 | "ts-node/register", 74 | "tsconfig-paths/register" 75 | ], 76 | "ui": "bdd" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /rollup.esm.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import nodeResolve from '@rollup/plugin-node-resolve' 3 | import json from '@rollup/plugin-json'; 4 | import esbuild from 'rollup-plugin-esbuild' 5 | 6 | export default { 7 | input: path.resolve(__dirname, './src/index.ts'), 8 | output: [ 9 | { 10 | dir: path.resolve(__dirname, 'dist/esm'), 11 | format: 'esm', 12 | } 13 | ], 14 | preserveModules: true, 15 | plugins: [ 16 | esbuild({ 17 | target: 'es2018' 18 | }), 19 | nodeResolve(), 20 | json(), 21 | ], 22 | external: [] 23 | }; -------------------------------------------------------------------------------- /rollup.umd.config.js: -------------------------------------------------------------------------------- 1 | import babel from '@rollup/plugin-babel'; 2 | import path from 'path'; 3 | import nodeResolve from '@rollup/plugin-node-resolve' 4 | import serve from 'rollup-plugin-serve' 5 | import livereload from 'rollup-plugin-livereload'; 6 | import commonjs from '@rollup/plugin-commonjs'; 7 | import json from '@rollup/plugin-json'; 8 | import esbuild from 'rollup-plugin-esbuild' 9 | 10 | const isProduction = process.env.NODE_ENV === 'production' 11 | const pluginsWithEnv = isProduction ? [] : [serve({ 12 | open: true, 13 | openPage: '/base/', 14 | port: 10001, 15 | contentBase: ['dist', 'examples'] 16 | }), livereload('dist/umd')] 17 | 18 | export default { 19 | input: path.resolve(__dirname, './src/index.ts'), 20 | output: [ 21 | { 22 | file: path.resolve(__dirname, 'dist/umd/index.js'), 23 | format: 'umd', 24 | name: 'treeLodash' 25 | } 26 | ], 27 | plugins: [ 28 | esbuild({ 29 | target: 'es2015' 30 | }), 31 | babel({ 32 | presets: ['@babel/preset-env'], 33 | exclude: 'node_modules/**', 34 | babelHelpers: 'bundled' 35 | }), 36 | nodeResolve(), 37 | json(), 38 | commonjs(), 39 | ...pluginsWithEnv 40 | ], 41 | }; -------------------------------------------------------------------------------- /src/filter.ts: -------------------------------------------------------------------------------- 1 | import { getFinalChildrenKey } from "./helpers/common"; 2 | import type { ChildrenKey, Tree, BaseOptions, BaseCallbackMeta, BaseInnerOptions } from "./types"; 3 | 4 | export type FilterOptions = BaseOptions 5 | 6 | export type FilterCallbackMeta = BaseCallbackMeta 7 | 8 | export type FilterCallback = (treeItem: Tree, meta: FilterCallbackMeta) => any 9 | 10 | type FilterInnerOption = BaseInnerOptions 11 | 12 | type FilterImpl = (treeItem: Tree, callback: FilterCallback, options: FilterInnerOption) => Tree | undefined 13 | 14 | // 前置遍历 15 | const preImpl: FilterImpl = (treeItem, callback, options) => { 16 | const res = callback(treeItem, options) 17 | if (!res) { 18 | return undefined 19 | } 20 | const finalChildrenKey = getFinalChildrenKey(treeItem, options, options) 21 | const children = treeItem[finalChildrenKey] 22 | let newChildren 23 | if (children && Array.isArray(children)) { 24 | newChildren = children.map((childItem) => { 25 | return preImpl(childItem, callback, { 26 | ...options, 27 | parents: [...options.parents, treeItem], 28 | depth: options.depth + 1 29 | }) 30 | }).filter(t => !!t) 31 | } 32 | return { 33 | ...treeItem, 34 | [finalChildrenKey]: newChildren 35 | } 36 | } 37 | 38 | // 子节点优先遍历 39 | const postImpl: FilterImpl = (treeItem, callback, options) => { 40 | const finalChildrenKey = getFinalChildrenKey(treeItem, options, options) 41 | const children = treeItem[finalChildrenKey] 42 | let newChildren 43 | if (children && Array.isArray(children)) { 44 | newChildren = children.map((childItem) => { 45 | return postImpl(childItem, callback, { 46 | ...options, 47 | parents: [...options.parents, treeItem], 48 | depth: options.depth + 1 49 | }) 50 | }).filter( t => !!t) 51 | } 52 | const res = callback(treeItem, options) 53 | if (!res) { 54 | return undefined 55 | } 56 | return { 57 | ...treeItem, 58 | [finalChildrenKey]: newChildren, 59 | } 60 | } 61 | 62 | type QueueItem = { 63 | tree: Tree, 64 | options: FilterInnerOption 65 | } 66 | 67 | function genNewNoChildrenNode (node: Tree, childrenKey: T) { 68 | const newNode = { 69 | ...node, 70 | } 71 | delete newNode[childrenKey] 72 | return newNode 73 | } 74 | // 广度优先遍历 75 | const breadthImpl: FilterImpl = (treeItem, callback, options) => { 76 | const queue: QueueItem[] = [ 77 | { 78 | tree: treeItem, 79 | options 80 | } 81 | ] 82 | let result: Tree 83 | const resultCache = new WeakMap() 84 | const newNodeCache = new WeakMap>() 85 | const childrenKeyCache = new WeakMap() 86 | const runQueue = (): Tree | undefined => { 87 | if (queue.length === 0) { 88 | return result 89 | } 90 | 91 | const queueItem = queue.shift() as QueueItem 92 | const treeItem = queueItem.tree 93 | const finalChildrenKey = getFinalChildrenKey(treeItem, queueItem.options, queueItem.options) 94 | if (treeItem[finalChildrenKey] && Array.isArray(treeItem[finalChildrenKey])) { 95 | const subQueueItems = treeItem[finalChildrenKey].map((subTree: Tree) => ( 96 | { 97 | tree: subTree, 98 | options: { 99 | ...queueItem.options, 100 | parents: [...queueItem.options.parents, treeItem], 101 | depth: queueItem.options.depth + 1 102 | } 103 | } 104 | )) 105 | queue.push(...subQueueItems) 106 | } 107 | 108 | const parent: Tree| undefined = queueItem.options.parents.length > 0 ? queueItem.options.parents[queueItem.options.parents.length - 1] : undefined 109 | const isTopNode = queueItem.options.depth === 0 110 | const parentResult = parent && resultCache.get(parent) 111 | 112 | // Not the top Node && parentResult is not true 113 | if (!isTopNode && !parentResult) { 114 | return runQueue() 115 | } 116 | const callbackResult = callback(treeItem, queueItem.options) 117 | // topNode callback not truthy, return null 118 | if (isTopNode && !callbackResult) { 119 | return undefined 120 | } 121 | let newNode = genNewNoChildrenNode(treeItem, finalChildrenKey) 122 | // topNode callback true, set the topNode 123 | if (isTopNode) { 124 | result = newNode 125 | } 126 | resultCache.set(queueItem.tree, callbackResult) 127 | newNodeCache.set(queueItem.tree, newNode) 128 | childrenKeyCache.set(queueItem.tree, finalChildrenKey) 129 | // Not top node, have a valid parent, and callback truthy 130 | if (callbackResult && parent) { 131 | const parentNewNode: any = newNodeCache.get(parent) 132 | const parentChildrenKey = childrenKeyCache.get(parent) 133 | if (!parentNewNode[parentChildrenKey]) { 134 | parentNewNode[parentChildrenKey] = [] 135 | } 136 | parentNewNode[parentChildrenKey].push(newNode) 137 | } 138 | return runQueue() 139 | } 140 | return runQueue() 141 | } 142 | 143 | const strategies = { 144 | 'pre': preImpl, 145 | 'post': postImpl, 146 | 'breadth': breadthImpl 147 | } 148 | 149 | 150 | function filter(tree: Tree, callback: FilterCallback, options?: FilterOptions): Tree; 151 | function filter(tree: Tree[], callback: FilterCallback, options?: FilterOptions): Tree[]; 152 | 153 | function filter(tree: Tree | Tree[], callback: FilterCallback, options: FilterOptions = {}): Tree | Tree[] | undefined { 154 | const childrenKey = options.childrenKey ?? 'children' 155 | const strategy = options.strategy ?? 'pre' 156 | const getChildrenKey = options.getChildrenKey 157 | const isForest = Array.isArray(tree) 158 | const method = strategies[strategy] 159 | const innerOptions = { 160 | childrenKey, 161 | depth: 0, 162 | parents: [] as Tree[], 163 | getChildrenKey 164 | } 165 | return isForest ? tree.map(tree => { 166 | return method(tree, callback, innerOptions) 167 | }).filter(t => !!t) as Tree[] : method(tree, callback, innerOptions) 168 | } 169 | 170 | export default filter -------------------------------------------------------------------------------- /src/find.ts: -------------------------------------------------------------------------------- 1 | import { getFinalChildrenKey } from "./helpers/common"; 2 | import { ChildrenKey, Tree, BaseOptions, BaseCallbackMeta, BaseInnerOptions } from "./types"; 3 | 4 | export type FindOptions = BaseOptions 5 | export type FindCallbackMeta = BaseCallbackMeta 6 | export type FindCallback = (treeItem: Tree, meta: FindCallbackMeta) => boolean | undefined 7 | type FindInnerOption = BaseInnerOptions 8 | type FindImpl = (treeItem: Tree, callback: FindCallback, options: FindInnerOption) => Tree|undefined 9 | 10 | // 前置深度优先遍历 11 | const preImpl: FindImpl = (treeItem, callback, options) => { 12 | const callbackResult = callback(treeItem, options) 13 | if (callbackResult) { 14 | return treeItem 15 | } 16 | const finalChildrenKey = getFinalChildrenKey(treeItem, options, options) 17 | const children = treeItem[finalChildrenKey] 18 | if (!children || !Array.isArray(children)) { 19 | return undefined 20 | } 21 | for(let i = 0, count = children.length; i < count; i ++) { 22 | const childItem = children[i] 23 | const findOne = preImpl(childItem, callback, { 24 | ...options, 25 | parents: [...options.parents, treeItem], 26 | depth: options.depth + 1 27 | }) 28 | if (findOne) { 29 | return findOne 30 | } 31 | } 32 | return undefined 33 | } 34 | 35 | // 后置深度优先遍历 36 | const postImpl: FindImpl = (treeItem, callback, options) => { 37 | const finalChildrenKey = getFinalChildrenKey(treeItem, options, options) 38 | const children = treeItem[finalChildrenKey] 39 | if (children && Array.isArray(children)) { 40 | for(let i = 0, count = children.length; i < count; i ++) { 41 | const childItem = children[i] 42 | const findOne = postImpl(childItem, callback, { 43 | ...options, 44 | parents: [...options.parents, treeItem], 45 | depth: options.depth + 1 46 | }) 47 | if (findOne) { 48 | return findOne 49 | } 50 | } 51 | } 52 | const callbackResult = callback(treeItem, options) 53 | if (callbackResult) { 54 | return treeItem 55 | } 56 | return undefined 57 | } 58 | 59 | type QueueItem = { 60 | tree: Tree, 61 | options: FindInnerOption 62 | } 63 | 64 | // 广度优先遍历 65 | const breadthImpl: FindImpl = (treeItem, callback, options) => { 66 | const queue: QueueItem[] = [ 67 | { 68 | tree: treeItem, 69 | options 70 | } 71 | ] 72 | const runQueue = (): Tree | undefined => { 73 | if (queue.length === 0) { 74 | return undefined 75 | } 76 | const queueItem = queue.shift() as QueueItem 77 | const treeItem = queueItem.tree 78 | const finalChildrenKey = getFinalChildrenKey(treeItem, queueItem.options, queueItem.options) 79 | if (treeItem[finalChildrenKey] && Array.isArray(treeItem[finalChildrenKey])) { 80 | const subQueueItems = treeItem[finalChildrenKey].map((subTree: Tree) => ( 81 | { 82 | tree: subTree, 83 | options: { 84 | ...queueItem.options, 85 | parents: [...queueItem.options.parents, treeItem], 86 | depth: queueItem.options.depth + 1 87 | } 88 | } 89 | )) 90 | queue.push(...subQueueItems) 91 | } 92 | const callbackResult = callback(treeItem, queueItem.options) 93 | if (callbackResult) { 94 | return treeItem; 95 | } 96 | return runQueue() 97 | } 98 | return runQueue() 99 | } 100 | 101 | const strategies = { 102 | 'pre': preImpl, 103 | 'post': postImpl, 104 | 'breadth': breadthImpl 105 | } 106 | 107 | function find(tree: Tree | Tree[], callback: FindCallback, options?: FindOptions): Tree | undefined { 108 | const childrenKey = options?.childrenKey ?? 'children' 109 | const strategy = options?.strategy ?? 'pre' 110 | const getChildrenKey = options?.getChildrenKey 111 | const method = strategies[strategy] 112 | const innerOptions = { 113 | childrenKey, 114 | depth: 0, 115 | parents: [] as Tree[], 116 | getChildrenKey 117 | } 118 | if (Array.isArray(tree)) { 119 | for(let i = 0, count = tree.length; i < count; i++) { 120 | const treeItem = tree[i] 121 | const result = method(treeItem, callback, innerOptions) 122 | if (result) { 123 | return result 124 | } 125 | } 126 | return undefined 127 | } 128 | return method(tree, callback, innerOptions) 129 | } 130 | 131 | export default find; -------------------------------------------------------------------------------- /src/foreach.ts: -------------------------------------------------------------------------------- 1 | import { getFinalChildrenKey } from "./helpers/common"; 2 | import type { ChildrenKey, Tree, BaseOptions, BaseCallbackMeta, BaseInnerOptions } from "./types"; 3 | 4 | export type ForeachOptions = BaseOptions 5 | 6 | export type ForeachCallbackMeta = BaseCallbackMeta 7 | 8 | export type ForeachCallback = (treeItem: Tree, meta: ForeachCallbackMeta) => void 9 | 10 | 11 | type ForeachInnerOption = BaseInnerOptions 12 | 13 | 14 | type ForeachImpl = (treeItem: Tree, callback: ForeachCallback, options: ForeachInnerOption) => void 15 | 16 | 17 | // 前置遍历 18 | const preImpl: ForeachImpl = (treeItem, callback, options) => { 19 | callback(treeItem, options) 20 | const finalChildrenKey = getFinalChildrenKey(treeItem, options, options) 21 | const children = treeItem[finalChildrenKey] 22 | if (children && Array.isArray(children)) { 23 | const nextLevelOptions = { 24 | ...options, 25 | parents: [...options.parents, treeItem], 26 | depth: options.depth + 1 27 | } 28 | children.forEach((childItem) => { 29 | preImpl(childItem, callback, nextLevelOptions) 30 | }) 31 | } 32 | } 33 | 34 | // 后置遍历 35 | const postImpl: ForeachImpl = (treeItem, callback, options) => { 36 | const finalChildrenKey = getFinalChildrenKey(treeItem, options, options) 37 | const children = treeItem[finalChildrenKey] 38 | if (children && Array.isArray(children)) { 39 | const nextLevelOptions = { 40 | ...options, 41 | parents: [...options.parents, treeItem], 42 | depth: options.depth + 1 43 | } 44 | children.forEach((childItem) => { 45 | postImpl(childItem, callback, nextLevelOptions) 46 | }) 47 | } 48 | callback(treeItem, options) 49 | } 50 | 51 | 52 | type QueueItem = { 53 | tree: Tree, 54 | options: ForeachInnerOption 55 | } 56 | 57 | // 广度优先遍历 58 | const breadthImpl: ForeachImpl = (treeItem, callback, options) => { 59 | const queue: QueueItem[] = [ 60 | { 61 | tree: treeItem, 62 | options 63 | } 64 | ] 65 | const runQueue = () => { 66 | if (queue.length === 0) { 67 | return 68 | } 69 | const queueItem = queue.shift() as QueueItem 70 | const treeItem = queueItem.tree 71 | 72 | const finalChildrenKey = getFinalChildrenKey(treeItem, queueItem.options, queueItem.options) 73 | if (treeItem[finalChildrenKey] && Array.isArray(treeItem[finalChildrenKey])) { 74 | const nextLevelOptions = { 75 | ...queueItem.options, 76 | parents: [...queueItem.options.parents, treeItem], 77 | depth: queueItem.options.depth + 1 78 | } 79 | const subQueueItems = treeItem[finalChildrenKey].map((subTree: Tree) => ( 80 | { 81 | tree: subTree, 82 | options: nextLevelOptions 83 | } 84 | )) 85 | queue.push(...subQueueItems) 86 | } 87 | callback(treeItem, queueItem.options) 88 | runQueue() 89 | } 90 | runQueue() 91 | } 92 | 93 | const strategies = { 94 | 'pre': preImpl, 95 | 'post': postImpl, 96 | 'breadth': breadthImpl 97 | } 98 | 99 | function foreach(tree: Tree | Tree[], callback: ForeachCallback, options?: ForeachOptions): void { 100 | const childrenKey = options?.childrenKey ?? 'children' 101 | const strategy = options?.strategy ?? 'pre' 102 | const getChildrenKey = options?.getChildrenKey 103 | const isForest = Array.isArray(tree) 104 | const method = strategies[strategy] 105 | const innerOptions = { 106 | childrenKey, 107 | depth: 0, 108 | parents: [] as Tree[], 109 | getChildrenKey 110 | } 111 | isForest ? tree.forEach(tree => { 112 | method(tree, callback, innerOptions) 113 | }) : method(tree, callback, innerOptions) 114 | } 115 | 116 | export default foreach; -------------------------------------------------------------------------------- /src/fromArray.ts: -------------------------------------------------------------------------------- 1 | import type { ChildrenKey } from "./types"; 2 | 3 | type TypeOfKey = string; 4 | export type KeyValue = string | number | undefined | null; 5 | 6 | export type ArrayItemWithParentKey< 7 | P extends TypeOfKey, 8 | K extends TypeOfKey 9 | > = Record & { 10 | [key in P]?: KeyValue; 11 | } & { 12 | [key in K]?: KeyValue; 13 | }; 14 | 15 | export type TreeWithParentKey< 16 | K extends TypeOfKey, 17 | P extends TypeOfKey, 18 | T extends ChildrenKey 19 | > = Record & { 20 | [key in P]?: KeyValue; 21 | } & { 22 | [key in K]: KeyValue; 23 | } & { 24 | [key in T]?: TreeWithParentKey[]; 25 | }; 26 | 27 | export type FromArrayOptions = { 28 | parentKey?: TypeOfKey; 29 | itemKey?: TypeOfKey; 30 | childrenKey?: ChildrenKey; 31 | }; 32 | 33 | export type FromArray< 34 | K extends TypeOfKey, 35 | P extends TypeOfKey, 36 | T extends ChildrenKey 37 | > = ( 38 | array: ArrayItemWithParentKey[], 39 | options: FromArrayOptions 40 | ) => TreeWithParentKey[]; 41 | 42 | function fromArray< 43 | K extends TypeOfKey, 44 | P extends TypeOfKey, 45 | T extends ChildrenKey 46 | >( 47 | array: ArrayItemWithParentKey[], 48 | options?: FromArrayOptions 49 | ): TreeWithParentKey[] { 50 | const result: TreeWithParentKey[] = []; 51 | const { 52 | parentKey = "pid", 53 | itemKey = "id", 54 | childrenKey = "children", 55 | } = options || {}; 56 | 57 | const map: Map> = new Map(); 58 | array.map((item) => { 59 | const itemKeyValue = item[itemKey]; 60 | if (!map.get(itemKeyValue)) { 61 | map.set(itemKeyValue, item); 62 | } 63 | }); 64 | array.map((item) => { 65 | const parentKeyValue = item[parentKey]; 66 | const parentItem = map.get(parentKeyValue); 67 | if (!parentItem || !parentKeyValue) { 68 | result.push(item); 69 | return; 70 | } 71 | const siblings = parentItem[childrenKey]; 72 | if (siblings === null || siblings === undefined) { 73 | // disable eslint tslint 74 | (parentItem as any)[childrenKey] = [item]; 75 | } else if (Array.isArray(siblings)) { 76 | siblings.push(item); 77 | } else { 78 | const msg = `the key "${childrenKey}" is not an array, please check your data`; 79 | throw new Error(msg); 80 | } 81 | }); 82 | 83 | return result; 84 | } 85 | 86 | export default fromArray; 87 | -------------------------------------------------------------------------------- /src/helpers/common.ts: -------------------------------------------------------------------------------- 1 | import type { ChildrenKey, Tree, BaseCallbackMeta, BaseInnerOptions } from '../types' 2 | 3 | export function getFinalChildrenKey (tree: Tree, meta: BaseCallbackMeta, options: BaseInnerOptions): ChildrenKey { 4 | if (typeof options.getChildrenKey === 'function') { 5 | const dynamicChildrenKey = options.getChildrenKey(tree, meta) 6 | if (dynamicChildrenKey != null) { 7 | return dynamicChildrenKey 8 | } 9 | } 10 | return options.childrenKey 11 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import foreach from "./foreach"; 2 | import map from "./map"; 3 | import filter from "./filter"; 4 | import toArray from "./toArray"; 5 | import find from "./find"; 6 | import some from "./some"; 7 | import fromArray from "./fromArray"; 8 | 9 | export default { 10 | foreach, 11 | map, 12 | filter, 13 | toArray, 14 | find, 15 | some, 16 | fromArray, 17 | }; 18 | 19 | export { foreach, map, filter, toArray, find, some, fromArray }; 20 | -------------------------------------------------------------------------------- /src/map.ts: -------------------------------------------------------------------------------- 1 | import { getFinalChildrenKey } from "./helpers/common"; 2 | import type { ChildrenKey, Tree, BaseOptions, BaseCallbackMeta, BaseInnerOptions } from "./types"; 3 | 4 | export type MapOptions = BaseOptions 5 | 6 | export type MapCallbackMeta = BaseCallbackMeta 7 | 8 | export type MapCallback = (treeItem: Tree, meta: MapCallbackMeta) => any 9 | 10 | type MapInnerOption = BaseInnerOptions 11 | 12 | type MapImpl = (treeItem: Tree, callback: MapCallback, options: MapInnerOption) => Tree 13 | 14 | // 前置遍历 15 | const preImpl: MapImpl = (treeItem, callback, options) => { 16 | const finalChildrenKey = getFinalChildrenKey(treeItem, options, options) 17 | const res = callback(treeItem, options) 18 | const children = treeItem[finalChildrenKey] 19 | let newChildren 20 | if (children && Array.isArray(children)) { 21 | const nextLevelOptions = { 22 | ...options, 23 | parents: [...options.parents, treeItem], 24 | depth: options.depth + 1 25 | } 26 | newChildren = children.map((childItem) => { 27 | return preImpl(childItem, callback, nextLevelOptions) 28 | }) 29 | } 30 | return { 31 | ...res, 32 | [finalChildrenKey]: newChildren, 33 | } 34 | } 35 | 36 | // 子节点优先遍历 37 | const postImpl: MapImpl = (treeItem, callback, options) => { 38 | const finalChildrenKey = getFinalChildrenKey(treeItem, options, options) 39 | const children = treeItem[finalChildrenKey] 40 | let newChildren 41 | if (children && Array.isArray(children)) { 42 | const nextLevelOptions = { 43 | ...options, 44 | parents: [...options.parents, treeItem], 45 | depth: options.depth + 1 46 | } 47 | newChildren = children.map((childItem) => { 48 | return postImpl(childItem, callback, nextLevelOptions) 49 | }) 50 | } 51 | const res = callback(treeItem, options) 52 | return { 53 | ...res, 54 | [finalChildrenKey]: newChildren, 55 | } 56 | } 57 | 58 | type QueueItem = { 59 | tree: Tree, 60 | options: MapInnerOption, 61 | } 62 | 63 | // 广度优先遍历 64 | const breadthImpl: MapImpl = (treeItem, callback, options) => { 65 | const queue: QueueItem[] = [ 66 | { 67 | tree: treeItem, 68 | options 69 | } 70 | ] 71 | let result: Tree 72 | const cache = new WeakMap() 73 | const childrenKeyCache = new WeakMap() 74 | const runQueue = () : Tree => { 75 | if (queue.length === 0) { 76 | return result 77 | } 78 | const queueItem = queue.shift() as QueueItem 79 | const treeItem = queueItem.tree 80 | const finalChildrenKey = getFinalChildrenKey(treeItem, queueItem.options, queueItem.options) 81 | if (treeItem[finalChildrenKey] && Array.isArray(treeItem[finalChildrenKey])) { 82 | const nextLevelOptions = { 83 | ...queueItem.options, 84 | parents: [...queueItem.options.parents, treeItem], 85 | depth: queueItem.options.depth + 1 86 | } 87 | const subQueueItems = treeItem[finalChildrenKey].map((subTree: Tree) => ( 88 | { 89 | tree: subTree, 90 | options: nextLevelOptions 91 | } 92 | )) 93 | queue.push(...subQueueItems) 94 | } 95 | const res = callback(treeItem, queueItem.options) 96 | cache.set(queueItem.tree, res) 97 | childrenKeyCache.set(queueItem.tree, finalChildrenKey) 98 | // breadth 模式的子节点一定晚于父节点执行,所以可以在cache中找到父节点的生成物 99 | const parent = queueItem.options.parents.length > 0 ? queueItem.options.parents[queueItem.options.parents.length - 1] : undefined 100 | if (parent) { 101 | const newParent = cache.get(parent) 102 | const parentChildrenKey = childrenKeyCache.get(parent) 103 | if (newParent[parentChildrenKey]) { 104 | newParent[parentChildrenKey].push(res) 105 | } else { 106 | newParent[parentChildrenKey] = [res] 107 | } 108 | } 109 | // 这棵树的顶点 110 | if (queueItem.options.depth === 0) { 111 | result = res 112 | } 113 | return runQueue() 114 | } 115 | return runQueue() 116 | } 117 | 118 | const strategies = { 119 | 'pre': preImpl, 120 | 'post': postImpl, 121 | 'breadth': breadthImpl 122 | } 123 | 124 | export type MapForTree = (tree: Tree , callback: MapCallback, options?: MapOptions) => Tree 125 | export type MapForForest = (tree: Tree[], callback: MapCallback, options?: MapOptions) => Tree[] 126 | 127 | function map(tree: Tree , callback: MapCallback, options?: MapOptions) : Tree; 128 | function map(tree: Tree[], callback: MapCallback, options?: MapOptions) : Tree[]; 129 | 130 | function map(tree: Tree| Tree[], callback: MapCallback, options: MapOptions = {}): Tree | Tree[]{ 131 | const childrenKey = options.childrenKey ?? 'children' 132 | const strategy = options.strategy ?? 'pre' 133 | const getChildrenKey = options.getChildrenKey 134 | const isForest = Array.isArray(tree) 135 | const method = strategies[strategy] 136 | const innerOptions = { 137 | childrenKey, 138 | depth: 0, 139 | parents: [] as Tree[], 140 | getChildrenKey 141 | } 142 | return isForest ? tree.map(tree => { 143 | return method(tree, callback, innerOptions) 144 | }) : method(tree, callback, innerOptions) 145 | } 146 | 147 | export default map; -------------------------------------------------------------------------------- /src/some.ts: -------------------------------------------------------------------------------- 1 | import { ChildrenKey, Tree } from "./types"; 2 | import find from "./find"; 3 | import { FindOptions, FindCallback } from "./find"; 4 | 5 | type MatchOptions = FindOptions 6 | 7 | function some(tree: Tree | Tree[], callback: FindCallback,options?: MatchOptions): boolean { 8 | const matchedItem = find(tree, callback, options) 9 | return matchedItem != undefined 10 | } 11 | 12 | export default some; -------------------------------------------------------------------------------- /src/toArray.ts: -------------------------------------------------------------------------------- 1 | import { ChildrenKey, Tree } from "./types"; 2 | import type { ForeachOptions } from "./foreach"; 3 | import foreach from "./foreach"; 4 | 5 | type ToArrayOptions = ForeachOptions 6 | 7 | function toArray(tree: Tree | Tree[], options?: ToArrayOptions): Tree[] { 8 | const results: Tree[] = [] 9 | foreach(tree, (t: Tree) => { 10 | results.push(t) 11 | }, options) 12 | return results; 13 | } 14 | 15 | export default toArray; -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export type TreeKey = string | number; 2 | 3 | export type ChildrenKey = TreeKey; 4 | 5 | export type Tree = { 6 | [key in T]?: Tree[]; 7 | } & { 8 | [key in TreeKey]: any; 9 | }; 10 | 11 | export type Strategy = "pre" | "post" | "breadth"; 12 | 13 | export type BaseOptions = { 14 | childrenKey?: ChildrenKey; 15 | strategy?: Strategy; 16 | getChildrenKey?: ( 17 | tree: Tree, 18 | meta: BaseCallbackMeta 19 | ) => ChildrenKey | undefined; 20 | }; 21 | 22 | export type BaseInnerOptions = { 23 | childrenKey: ChildrenKey; 24 | parents: Tree[]; 25 | depth: number; 26 | getChildrenKey?: ( 27 | tree: Tree, 28 | meta: BaseCallbackMeta 29 | ) => ChildrenKey | undefined; 30 | }; 31 | 32 | export type BaseCallbackMeta = { 33 | depth: number; 34 | parents?: Tree[]; 35 | }; 36 | -------------------------------------------------------------------------------- /test/filter.test.ts: -------------------------------------------------------------------------------- 1 | /* global describe, it, beforeEach */ 2 | 3 | import 'mocha' 4 | import { expect } from 'chai' 5 | import { filter } from '../src/index' 6 | import type { Tree } from '../src/types' 7 | 8 | describe('[filter]', function () { 9 | const tree: Tree<'children'> = { 10 | key: 1, 11 | children: [ 12 | { 13 | key: 11, 14 | children: [ 15 | { 16 | key: 111 17 | } 18 | ] 19 | }, 20 | { 21 | key: 12, 22 | children: [ 23 | { 24 | key: 112, 25 | children: [ 26 | { 27 | key: 1111 28 | } 29 | ] 30 | } 31 | ] 32 | } 33 | ] 34 | } 35 | 36 | const treeSubItems: Tree<'subItems'> = { 37 | key: 1, 38 | subItems: [ 39 | { 40 | key: 11, 41 | subItems: [ 42 | { 43 | key: 111 44 | } 45 | ] 46 | }, 47 | { 48 | key: 12, 49 | subItems: [ 50 | { 51 | key: 112, 52 | subItems: [ 53 | { 54 | key: 1111 55 | } 56 | ] 57 | } 58 | ] 59 | } 60 | ] 61 | } 62 | 63 | const treeMultiChildrenKey: Tree = { 64 | key: 1, 65 | children: [ 66 | { 67 | key: 11, 68 | subItems: [ 69 | { 70 | key: 111 71 | } 72 | ] 73 | }, 74 | { 75 | key: 12, 76 | subItems: [ 77 | { 78 | key: 112, 79 | subItems: [ 80 | { 81 | key: 1111 82 | } 83 | ] 84 | } 85 | ] 86 | } 87 | ] 88 | } 89 | 90 | it('by default strategy (pre)', function () { 91 | const res: any[] = [] 92 | const newTree: Tree<'children'> | undefined = filter(tree, ((t) => { 93 | res.push(t.key) 94 | return t.key <= 100 95 | })) 96 | expect(newTree?.key).to.be.equal(1); 97 | expect(newTree?.children?.[0]?.key).to.be.equal(11); 98 | expect(newTree?.children?.[0]?.children?.[0]?.key).to.be.equal(undefined); 99 | expect(res.length).to.be.equal(5); 100 | expect(res[0]).to.be.equal(1); 101 | expect(res[1]).to.be.equal(11); 102 | expect(res[2]).to.be.equal(111); 103 | expect(res[3]).to.be.equal(12); 104 | }) 105 | it('by post strategy ', function () { 106 | const res: any[] = [] 107 | const newTree: Tree<'children'> | undefined = filter(tree, ((t) => { 108 | res.push(t.key) 109 | return t.key <= 100 110 | }), { strategy: 'post' }) 111 | expect(newTree?.key).to.be.equal(1); 112 | expect(newTree?.children?.[0]?.key).to.be.equal(11); 113 | expect(newTree?.children?.[0]?.children?.[0]?.key).to.be.equal(undefined); 114 | expect(res.length).to.be.equal(6); 115 | expect(res[0]).to.be.equal(111); 116 | expect(res[1]).to.be.equal(11); 117 | expect(res[2]).to.be.equal(1111); 118 | expect(res[3]).to.be.equal(112); 119 | }) 120 | it('by breadth strategy ', function () { 121 | const res: any[] = [] 122 | const newTree: Tree<'children'> | undefined = filter(tree, ((t) => { 123 | res.push(t.key) 124 | return t.key <= 100 125 | }), { strategy: 'breadth' }) 126 | expect(newTree?.key).to.be.equal(1); 127 | expect(newTree?.children?.[0]?.key).to.be.equal(11); 128 | expect(newTree?.children?.[0]?.children?.[0]?.key).to.be.equal(undefined); 129 | expect(res.length).to.be.equal(5); 130 | expect(res[0]).to.be.equal(1); 131 | expect(res[1]).to.be.equal(11); 132 | expect(res[2]).to.be.equal(12); 133 | expect(res[3]).to.be.equal(111); 134 | }) 135 | it('by tree childrenKey is "subItems"', function () { 136 | const res: any[] = [] 137 | const newTree: Tree<'subItems'> | undefined = filter(treeSubItems, ((t) => { 138 | res.push(t.key) 139 | return t.key <= 100 140 | }), { childrenKey: 'subItems' }) 141 | expect(newTree?.key).to.be.equal(1); 142 | expect(newTree?.subItems?.[0]?.key).to.be.equal(11); 143 | expect(newTree?.subItems?.[0]?.subItems?.[0]?.key).to.be.equal(undefined); 144 | expect(res.length).to.be.equal(5); 145 | expect(res[0]).to.be.equal(1); 146 | expect(res[1]).to.be.equal(11); 147 | expect(res[2]).to.be.equal(111); 148 | expect(res[3]).to.be.equal(12); 149 | }) 150 | it('by tree childrenKey is wrong', function () { 151 | const res: any[] = [] 152 | const newTree: Tree | undefined = filter(tree, ((t) => { 153 | res.push(t.key) 154 | return t.key === 1 || t.key === 11 155 | }), { childrenKey: 'babies' }) 156 | expect(newTree?.key).to.be.equal(1); 157 | expect(newTree?.children?.[0]?.key).to.be.equal(11); 158 | expect(newTree?.children?.[0]?.children?.[0]?.key).to.be.equal(111); 159 | expect(res.length).to.be.equal(1); 160 | expect(res[0]).to.be.equal(1); 161 | }) 162 | it('for forest by default strategy (pre)', function () { 163 | const res: any[] = [] 164 | const newForest: Tree<'children'>[] | [] = filter([tree], ((t) => { 165 | res.push(t.key) 166 | return t.key <= 100 167 | })) 168 | expect(newForest[0]?.key).to.be.equal(1); 169 | expect(newForest[0]?.children?.[0]?.key).to.be.equal(11); 170 | expect(newForest[0]?.children?.[0]?.children?.[0]?.key).to.be.equal(undefined); 171 | expect(res.length).to.be.equal(5); 172 | expect(res[0]).to.be.equal(1); 173 | expect(res[1]).to.be.equal(11); 174 | expect(res[2]).to.be.equal(111); 175 | expect(res[3]).to.be.equal(12); 176 | }) 177 | it('for forest by post strategy', function () { 178 | const res: any[] = [] 179 | const newForest: Tree<'children'>[] | [] = filter([tree], ((t) => { 180 | res.push(t.key) 181 | return t.key <= 100 182 | }), { strategy: 'post' }) 183 | expect(newForest[0]?.key).to.be.equal(1); 184 | expect(newForest[0]?.children?.[0]?.key).to.be.equal(11); 185 | expect(newForest[0]?.children?.[0]?.children?.[0]?.key).to.be.equal(undefined); 186 | expect(res.length).to.be.equal(6); 187 | expect(res[0]).to.be.equal(111); 188 | expect(res[1]).to.be.equal(11); 189 | expect(res[2]).to.be.equal(1111); 190 | expect(res[3]).to.be.equal(112); 191 | }) 192 | it('for forest by breadth strategy', function () { 193 | const res: any[] = [] 194 | const newForest: Tree<'children'>[] | [] = filter([tree], ((t) => { 195 | res.push(t.key) 196 | return t.key <= 100 197 | }), { strategy: 'breadth' }) 198 | expect(newForest[0]?.key).to.be.equal(1); 199 | expect(newForest[0]?.children?.[0]?.key).to.be.equal(11); 200 | expect(newForest[0]?.children?.[0]?.children?.[0]?.key).to.be.equal(undefined); 201 | expect(res.length).to.be.equal(5); 202 | expect(res[0]).to.be.equal(1); 203 | expect(res[1]).to.be.equal(11); 204 | expect(res[2]).to.be.equal(12); 205 | expect(res[3]).to.be.equal(111); 206 | }) 207 | 208 | it('for forest no-matched item by default strategy (pre)', function () { 209 | const res: any[] = [] 210 | const newForest: Tree<'children'>[] | [] = filter([tree], ((t) => { 211 | res.push(t.key) 212 | return t.key <= 0 213 | })) 214 | expect(newForest.length).to.be.equal(0); 215 | expect(res.length).to.be.equal(1); 216 | }) 217 | 218 | it('for forest no-matched item by post strategy', function () { 219 | const res: any[] = [] 220 | const newForest: Tree<'children'>[] | [] = filter([tree], ((t) => { 221 | res.push(t.key) 222 | return t.key <= 0 223 | }), { strategy: 'post' }) 224 | expect(newForest.length).to.be.equal(0); 225 | expect(res.length).to.be.equal(6); 226 | }) 227 | 228 | it('for forest no-matched item by breadth strategy', function () { 229 | const res: any[] = [] 230 | const newForest: Tree<'children'>[] | [] = filter([tree], ((t) => { 231 | res.push(t.key) 232 | return t.key <= 0 233 | }), { strategy: 'breadth' }) 234 | expect(newForest.length).to.be.equal(0); 235 | expect(res.length).to.be.equal(1); 236 | }) 237 | 238 | it('by default strategy (pre) and getChildrenKey', function () { 239 | const res: any[] = [] 240 | const newTree: Tree<'children'> | undefined = filter(treeMultiChildrenKey, ((t) => { 241 | res.push(t.key) 242 | return t.key <= 100 243 | }), 244 | { 245 | getChildrenKey(tree) { 246 | return tree.key < 10 ? 'children' : 'subItems' 247 | } 248 | } 249 | ) 250 | expect(newTree?.key).to.be.equal(1); 251 | expect(newTree?.children?.[0]?.key).to.be.equal(11); 252 | expect(newTree?.children?.[0]?.children?.[0]?.key).to.be.equal(undefined); 253 | expect(res.length).to.be.equal(5); 254 | expect(res[0]).to.be.equal(1); 255 | expect(res[1]).to.be.equal(11); 256 | expect(res[2]).to.be.equal(111); 257 | expect(res[3]).to.be.equal(12); 258 | }) 259 | it('by post strategy and getChildrenKey', function () { 260 | const res: any[] = [] 261 | const newTree: Tree<'children'> | undefined = filter(treeMultiChildrenKey, ((t) => { 262 | res.push(t.key) 263 | return t.key <= 100 264 | }), { 265 | strategy: 'post', 266 | getChildrenKey(tree) { 267 | return tree.key < 10 ? 'children' : 'subItems' 268 | } 269 | }) 270 | expect(newTree?.key).to.be.equal(1); 271 | expect(newTree?.children?.[0]?.key).to.be.equal(11); 272 | expect(newTree?.children?.[0]?.children?.[0]?.key).to.be.equal(undefined); 273 | expect(res.length).to.be.equal(6); 274 | expect(res[0]).to.be.equal(111); 275 | expect(res[1]).to.be.equal(11); 276 | expect(res[2]).to.be.equal(1111); 277 | expect(res[3]).to.be.equal(112); 278 | }) 279 | it('by breadth strategy and getChildrenKey', function () { 280 | const res: any[] = [] 281 | const newTree: Tree<'children'> | undefined = filter(treeMultiChildrenKey, ((t) => { 282 | res.push(t.key) 283 | return t.key <= 100 284 | }), 285 | { 286 | strategy: 'breadth', 287 | getChildrenKey(tree) { 288 | return tree.key < 10 ? 'children' : 'subItems' 289 | } 290 | }) 291 | expect(newTree?.key).to.be.equal(1); 292 | expect(newTree?.children?.[0]?.key).to.be.equal(11); 293 | expect(newTree?.children?.[0]?.children?.[0]?.key).to.be.equal(undefined); 294 | expect(res.length).to.be.equal(5); 295 | expect(res[0]).to.be.equal(1); 296 | expect(res[1]).to.be.equal(11); 297 | expect(res[2]).to.be.equal(12); 298 | expect(res[3]).to.be.equal(111); 299 | }) 300 | }) -------------------------------------------------------------------------------- /test/find.test.ts: -------------------------------------------------------------------------------- 1 | /* global describe, it, beforeEach */ 2 | 3 | import 'mocha' 4 | import { expect } from 'chai' 5 | import { find } from '../src/index' 6 | import type { Tree } from '../src/types' 7 | 8 | describe('[find]', function () { 9 | const tree: Tree = { 10 | key: 1, 11 | children: [ 12 | { 13 | key: 11, 14 | children: [ 15 | { 16 | key: 111 17 | } 18 | ] 19 | }, 20 | { 21 | key: 12, 22 | children: [ 23 | { 24 | key: 112, 25 | children: [ 26 | { 27 | key: 1111 28 | } 29 | ] 30 | } 31 | ] 32 | } 33 | ] 34 | } 35 | 36 | const treeSubItems: Tree<'subItems'> = { 37 | key: 1, 38 | subItems: [ 39 | { 40 | key: 11, 41 | subItems: [ 42 | { 43 | key: 111 44 | } 45 | ] 46 | }, 47 | { 48 | key: 12, 49 | subItems: [ 50 | { 51 | key: 112, 52 | subItems: [ 53 | { 54 | key: 1111 55 | } 56 | ] 57 | } 58 | ] 59 | } 60 | ] 61 | } 62 | 63 | const treeMultiChildrenKey: Tree = { 64 | key: 1, 65 | children: [ 66 | { 67 | key: 11, 68 | subItems: [ 69 | { 70 | key: 111 71 | } 72 | ] 73 | }, 74 | { 75 | key: 12, 76 | subItems: [ 77 | { 78 | key: 112, 79 | subItems: [ 80 | { 81 | key: 1111 82 | } 83 | ] 84 | } 85 | ] 86 | } 87 | ] 88 | } 89 | 90 | it('by default strategy (pre)', function () { 91 | const res: any[] = [] 92 | const newTree: Tree<'children'> | undefined = find(tree, ((t) => { 93 | res.push(t.key) 94 | return t.key === 1111 95 | })) 96 | expect(newTree?.key).to.be.equal(1111); 97 | expect(res.length).to.be.equal(6); 98 | expect(res[0]).to.be.equal(1); 99 | expect(res[1]).to.be.equal(11); 100 | }) 101 | it('by post strategy ', function () { 102 | const res: any[] = [] 103 | const newTree: Tree<'children'> | undefined = find(tree, ((t) => { 104 | res.push(t.key) 105 | return t.key === 1111 106 | }), { strategy: 'post' }) 107 | expect(newTree?.key).to.be.equal(1111); 108 | expect(res.length).to.be.equal(3); 109 | expect(res[0]).to.be.equal(111); 110 | expect(res[1]).to.be.equal(11); 111 | }) 112 | it('by breadth strategy ', function () { 113 | const res: any[] = [] 114 | const newTree: Tree<'children'> | undefined = find(tree, ((t) => { 115 | res.push(t.key) 116 | return t.key === 12 117 | }), { strategy: 'breadth' }) 118 | expect(newTree?.key).to.be.equal(12); 119 | expect(newTree?.children?.[0]?.key).to.be.equal(112); 120 | expect(res.length).to.be.equal(3); 121 | expect(res[0]).to.be.equal(1); 122 | expect(res[1]).to.be.equal(11); 123 | expect(res[2]).to.be.equal(12); 124 | }) 125 | it('by tree childrenKey is "subItems"', function() { 126 | const res: any[] = [] 127 | const newTree: Tree<'subItems'> | undefined = find(treeSubItems, ((t) => { 128 | res.push(t.key) 129 | return t.key <= 100 && t.key > 1 130 | }), { childrenKey: 'subItems' }) 131 | expect(newTree?.key).to.be.equal(11); 132 | expect(newTree?.subItems?.[0]?.key).to.be.equal(111); 133 | expect(res.length).to.be.equal(2); 134 | expect(res[0]).to.be.equal(1); 135 | expect(res[1]).to.be.equal(11); 136 | }) 137 | it('for forest by default strategy (pre)', function () { 138 | const res: any[] = [] 139 | const newTree: Tree<'children'> | undefined = find([tree], ((t) => { 140 | res.push(t.key) 141 | return t.key <= 100 && t.key > 1 142 | })) 143 | expect(newTree?.key).to.be.equal(11); 144 | expect(newTree?.children?.[0]?.key).to.be.equal(111); 145 | expect(res.length).to.be.equal(2); 146 | expect(res[0]).to.be.equal(1); 147 | expect(res[1]).to.be.equal(11); 148 | }) 149 | it('for forest by post strategy', function () { 150 | const res: any[] = [] 151 | const newTree: Tree<'children'> | undefined = find(tree, ((t) => { 152 | res.push(t.key) 153 | return t.key <= 100 && t.key > 1 154 | }), { strategy: 'post' }) 155 | expect(newTree?.key).to.be.equal(11); 156 | expect(newTree?.children?.[0]?.key).to.be.equal(111); 157 | expect(res.length).to.be.equal(2); 158 | expect(res[0]).to.be.equal(111); 159 | expect(res[1]).to.be.equal(11); 160 | }) 161 | it('for forest by breadth strategy', function () { 162 | const res: any[] = [] 163 | const matchedItem: Tree<'children'> | undefined = find([tree], ((t) => { 164 | res.push(t.key) 165 | return t.key <= 100 && t.key > 1 166 | }), { strategy: 'breadth' }) 167 | expect(matchedItem?.key).to.be.equal(11); 168 | expect(matchedItem?.children?.[0]?.key).to.be.equal(111); 169 | expect(res.length).to.be.equal(2); 170 | expect(res[0]).to.be.equal(1); 171 | expect(res[1]).to.be.equal(11); 172 | }) 173 | 174 | it('for forest no-matched item by default strategy (pre)', function () { 175 | const res: any[] = [] 176 | const matchedItem: Tree<'children'> | undefined = find([tree], ((t) => { 177 | res.push(t.key) 178 | return t.key <= 0 179 | })) 180 | expect(matchedItem).to.be.equal(undefined); 181 | expect(res.length).to.be.equal(6); 182 | }) 183 | 184 | it('for forest no-matched item by post strategy', function () { 185 | const res: any[] = [] 186 | const matchedItem: Tree<'children'> | undefined = find([tree], ((t) => { 187 | res.push(t.key) 188 | return t.key <= 0 189 | }), { strategy: 'post' }) 190 | expect(matchedItem).to.be.equal(undefined); 191 | expect(res.length).to.be.equal(6); 192 | }) 193 | 194 | it('for forest no-matched item by breadth strategy', function () { 195 | const res: any[] = [] 196 | const matchedItem: Tree<'children'> | undefined = find([tree], ((t) => { 197 | res.push(t.key) 198 | return t.key <= 0 199 | }), { strategy: 'breadth' }) 200 | expect(matchedItem).to.be.equal(undefined); 201 | expect(res.length).to.be.equal(6); 202 | }) 203 | it('by default strategy (pre) and getChildrenKey', function () { 204 | const res: any[] = [] 205 | const newTree: Tree | undefined = find(treeMultiChildrenKey, ((t) => { 206 | res.push(t.key) 207 | return t.key <= 100 && t.key > 1 208 | }), 209 | { 210 | strategy: 'pre', 211 | getChildrenKey(tree) { 212 | return tree.key < 10 ? 'children' : 'subItems' 213 | } 214 | }) 215 | expect(newTree?.key).to.be.equal(11); 216 | expect(newTree?.subItems?.[0]?.key).to.be.equal(111); 217 | expect(res.length).to.be.equal(2); 218 | expect(res[0]).to.be.equal(1); 219 | expect(res[1]).to.be.equal(11); 220 | }) 221 | it('by post strategy and getChildrenKey', function () { 222 | const res: any[] = [] 223 | const newTree: Tree | undefined = find(treeMultiChildrenKey, ((t) => { 224 | res.push(t.key) 225 | return t.key <= 100 && t.key > 1 226 | }), 227 | { 228 | strategy: 'post', 229 | getChildrenKey(tree) { 230 | return tree.key < 10 ? 'children' : 'subItems' 231 | } 232 | }) 233 | expect(newTree?.key).to.be.equal(11); 234 | expect(newTree?.subItems?.[0]?.key).to.be.equal(111); 235 | expect(res.length).to.be.equal(2); 236 | expect(res[0]).to.be.equal(111); 237 | expect(res[1]).to.be.equal(11); 238 | }) 239 | it('by breadth strategy and getChildrenKey', function () { 240 | const res: any[] = [] 241 | const newTree: Tree<'children'> | undefined = find(treeMultiChildrenKey, ((t) => { 242 | res.push(t.key) 243 | return t.key === 12 244 | }), 245 | { 246 | strategy: 'breadth', 247 | getChildrenKey(tree) { 248 | return tree.key < 10 ? 'children' : 'subItems' 249 | } 250 | }) 251 | expect(newTree?.key).to.be.equal(12); 252 | expect(newTree?.subItems?.[0]?.key).to.be.equal(112); 253 | expect(res.length).to.be.equal(3); 254 | expect(res[0]).to.be.equal(1); 255 | expect(res[1]).to.be.equal(11); 256 | expect(res[2]).to.be.equal(12); 257 | }) 258 | }) -------------------------------------------------------------------------------- /test/foreach.test.ts: -------------------------------------------------------------------------------- 1 | /* global describe, it, beforeEach */ 2 | 3 | import 'mocha' 4 | import { expect } from 'chai' 5 | import { foreach } from '../src/index' 6 | import type { Tree } from '../src/types' 7 | 8 | describe('[foreach]', function () { 9 | const tree: Tree = { 10 | key: '1', 11 | children: [ 12 | { 13 | key: '2', 14 | children: [ 15 | { 16 | key: '3' 17 | } 18 | ] 19 | }, 20 | { 21 | key: '4', 22 | children: [ 23 | { 24 | key: '5' 25 | } 26 | ] 27 | } 28 | ] 29 | } 30 | 31 | const treeSubItems: Tree<'subItems'> = { 32 | key: '1', 33 | subItems: [ 34 | { 35 | key: '2', 36 | subItems: [ 37 | { 38 | key: '3' 39 | } 40 | ] 41 | }, 42 | { 43 | key: '4', 44 | subItems: [ 45 | { 46 | key: '5' 47 | } 48 | ] 49 | } 50 | ] 51 | } 52 | 53 | const treeMultiChildrenKey: Tree = { 54 | key: '1', 55 | children: [ 56 | { 57 | key: '2', 58 | subItems: [ 59 | { 60 | key: '3' 61 | } 62 | ] 63 | }, 64 | { 65 | key: '4', 66 | subItems: [ 67 | { 68 | key: '5' 69 | } 70 | ] 71 | } 72 | ] 73 | } 74 | 75 | it('by default strategy (pre)', function () { 76 | const res: string[] = [] 77 | foreach(tree, ((t) => { 78 | res.push(t.key) 79 | })) 80 | expect(res.length).to.be.equal(5); 81 | expect(res[0]).to.be.equal('1'); 82 | expect(res[1]).to.be.equal('2'); 83 | expect(res[2]).to.be.equal('3'); 84 | expect(res[3]).to.be.equal('4'); 85 | expect(res[4]).to.be.equal('5'); 86 | }) 87 | it('by pre strategy', function () { 88 | const res: string[] = [] 89 | foreach(tree, ((t) => { 90 | res.push(t.key) 91 | }), { strategy: 'pre' }) 92 | expect(res.length).to.be.equal(5); 93 | expect(res[0]).to.be.equal('1'); 94 | expect(res[1]).to.be.equal('2'); 95 | expect(res[2]).to.be.equal('3'); 96 | expect(res[3]).to.be.equal('4'); 97 | expect(res[4]).to.be.equal('5'); 98 | }) 99 | it('by post strategy', function () { 100 | const res: string[] = [] 101 | foreach(tree, ((t) => { 102 | res.push(t.key) 103 | }), { strategy: 'post' }) 104 | expect(res.length).to.be.equal(5); 105 | expect(res[0]).to.be.equal('3'); 106 | expect(res[1]).to.be.equal('2'); 107 | expect(res[2]).to.be.equal('5'); 108 | expect(res[3]).to.be.equal('4'); 109 | expect(res[4]).to.be.equal('1'); 110 | }) 111 | it('by breadth strategy', function () { 112 | const res: string[] = [] 113 | foreach(tree, ((t) => { 114 | res.push(t.key) 115 | }), { strategy: 'breadth' }) 116 | expect(res.length).to.be.equal(5); 117 | expect(res[0]).to.be.equal('1'); 118 | expect(res[1]).to.be.equal('2'); 119 | expect(res[2]).to.be.equal('4'); 120 | expect(res[3]).to.be.equal('3'); 121 | expect(res[4]).to.be.equal('5'); 122 | }) 123 | it('by tree childrenKey is "subItems"', function() { 124 | const res: string[] = [] 125 | foreach(treeSubItems, ((t) => { 126 | res.push(t.key) 127 | }), { childrenKey: 'subItems' }) 128 | expect(res.length).to.be.equal(5); 129 | expect(res[0]).to.be.equal('1'); 130 | expect(res[1]).to.be.equal('2'); 131 | expect(res[2]).to.be.equal('3'); 132 | expect(res[3]).to.be.equal('4'); 133 | expect(res[4]).to.be.equal('5'); 134 | }) 135 | it('for forest by default strategy (pre)', function () { 136 | const res: string[] = [] 137 | foreach([tree], ((t) => { 138 | res.push(t.key) 139 | })) 140 | expect(res.length).to.be.equal(5); 141 | expect(res[0]).to.be.equal('1'); 142 | expect(res[1]).to.be.equal('2'); 143 | expect(res[2]).to.be.equal('3'); 144 | expect(res[3]).to.be.equal('4'); 145 | expect(res[4]).to.be.equal('5'); 146 | }) 147 | it('by pre strategy and getChildrenKey', function () { 148 | const res: string[] = [] 149 | foreach(treeMultiChildrenKey, 150 | (t) => { 151 | res.push(t.key) 152 | }, 153 | { 154 | getChildrenKey: (tree, meta) => { 155 | if (meta.depth === 1) { 156 | return 'subItems' 157 | } 158 | } 159 | }) 160 | expect(res.length).to.be.equal(5); 161 | expect(res[0]).to.be.equal('1'); 162 | expect(res[1]).to.be.equal('2'); 163 | expect(res[2]).to.be.equal('3'); 164 | expect(res[3]).to.be.equal('4'); 165 | expect(res[4]).to.be.equal('5'); 166 | }) 167 | it('by post strategy and getChildrenKey', function () { 168 | const res: string[] = [] 169 | foreach(treeMultiChildrenKey, 170 | (t) => { 171 | res.push(t.key) 172 | }, 173 | { 174 | getChildrenKey: (tree, meta) => { 175 | if (meta.depth === 1) { 176 | return 'subItems' 177 | } 178 | }, 179 | strategy: 'post' 180 | }) 181 | expect(res.length).to.be.equal(5); 182 | expect(res[0]).to.be.equal('3'); 183 | expect(res[1]).to.be.equal('2'); 184 | expect(res[2]).to.be.equal('5'); 185 | expect(res[3]).to.be.equal('4'); 186 | expect(res[4]).to.be.equal('1'); 187 | }) 188 | it('by breadth strategy and getChildrenKey', function () { 189 | const res: string[] = [] 190 | foreach(treeMultiChildrenKey, 191 | (t) => { 192 | res.push(t.key) 193 | }, 194 | { 195 | getChildrenKey: (tree, meta) => { 196 | if (meta.depth === 1) { 197 | return 'subItems' 198 | } 199 | }, 200 | strategy: 'breadth' 201 | }) 202 | expect(res.length).to.be.equal(5); 203 | expect(res[0]).to.be.equal('1'); 204 | expect(res[1]).to.be.equal('2'); 205 | expect(res[2]).to.be.equal('4'); 206 | expect(res[3]).to.be.equal('3'); 207 | expect(res[4]).to.be.equal('5'); 208 | }) 209 | }) -------------------------------------------------------------------------------- /test/fromArray.test.ts: -------------------------------------------------------------------------------- 1 | /* global describe, it, beforeEach */ 2 | 3 | import "mocha"; 4 | import { expect } from "chai"; 5 | import { fromArray } from "../src/index"; 6 | 7 | describe("[fromArray]", function () { 8 | const commonArray = [ 9 | { 10 | id: "1", 11 | name: "1", 12 | }, 13 | { 14 | id: "2", 15 | name: "2", 16 | pid: "1", 17 | }, 18 | { 19 | id: "3", 20 | name: "3", 21 | pid: "1", 22 | }, 23 | { 24 | id: "4", 25 | name: "4", 26 | pid: "2", 27 | }, 28 | { 29 | id: "5", 30 | name: "5", 31 | }, 32 | ]; 33 | 34 | it("default option", function () { 35 | const res = fromArray(commonArray); 36 | expect(res.length).to.be.equal(1); 37 | expect(res[0].id).to.be.equal("1"); 38 | expect(res[0].children[0].id).to.be.equal("2"); 39 | expect(res[0].children[1].id).to.be.equal("3"); 40 | expect(res[0].children[0].children[0].id).to.be.equal("4"); 41 | }); 42 | it("custom keys", function () { 43 | const customArray = commonArray.map((t) => { 44 | return { 45 | name: t.name, 46 | k: t.id, 47 | pk: t.pid, 48 | }; 49 | }); 50 | const res = fromArray(customArray, { 51 | itemKey: "k", 52 | parentKey: "pk", 53 | }); 54 | expect(res.length).to.be.equal(1); 55 | expect(res[0].k).to.be.equal("1"); 56 | expect(res[0].children[0].k).to.be.equal("2"); 57 | expect(res[0].children[1].k).to.be.equal("3"); 58 | expect(res[0].children[0].children[0].k).to.be.equal("4"); 59 | }); 60 | it("custom childrenKey", function () { 61 | const res = fromArray(commonArray, { childrenKey: "subItems" }); 62 | expect(res.length).to.be.equal(1); 63 | expect(res[0].id).to.be.equal("1"); 64 | expect(res[0].subItems[0].id).to.be.equal("2"); 65 | expect(res[0].subItems[1].id).to.be.equal("3"); 66 | expect(res[0].subItems[0].subItems[0].id).to.be.equal("4"); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/map.test.ts: -------------------------------------------------------------------------------- 1 | /* global describe, it, beforeEach */ 2 | 3 | import 'mocha' 4 | import { expect } from 'chai' 5 | import { map } from '../src/index' 6 | import type { Tree } from '../src/types' 7 | 8 | describe('[map]', function () { 9 | const tree: Tree<'children'> = { 10 | key: '1', 11 | children: [ 12 | { 13 | key: '2', 14 | children: [ 15 | { 16 | key: '3' 17 | } 18 | ] 19 | }, 20 | { 21 | key: '4', 22 | children: [ 23 | { 24 | key: '5' 25 | } 26 | ] 27 | } 28 | ] 29 | } 30 | 31 | const treeSubItems: Tree<'subItems'> = { 32 | key: '1', 33 | subItems: [ 34 | { 35 | key: '2', 36 | subItems: [ 37 | { 38 | key: '3' 39 | } 40 | ] 41 | }, 42 | { 43 | key: '4', 44 | subItems: [ 45 | { 46 | key: '5' 47 | } 48 | ] 49 | } 50 | ] 51 | } 52 | 53 | const treeMultiChildrenKey: Tree = { 54 | key: '1', 55 | children: [ 56 | { 57 | key: '2', 58 | subItems: [ 59 | { 60 | key: '3' 61 | } 62 | ] 63 | }, 64 | { 65 | key: '4', 66 | subItems: [ 67 | { 68 | key: '5' 69 | } 70 | ] 71 | } 72 | ] 73 | } 74 | 75 | it('by default strategy (pre)', function () { 76 | const res: any[] = [] 77 | const newTree: Tree<'children'> = map(tree, ((t) => { 78 | res.push(t.key) 79 | return { 80 | key: t.key + '1' 81 | } 82 | })) 83 | expect(newTree.key).to.be.equal('11'); 84 | expect(newTree.children?.[0]?.key).to.be.equal('21'); 85 | expect(newTree.children?.[0]?.children?.[0]?.key).to.be.equal('31'); 86 | expect(res.length).to.be.equal(5); 87 | expect(res[0]).to.be.equal('1'); 88 | expect(res[1]).to.be.equal('2'); 89 | expect(res[2]).to.be.equal('3'); 90 | expect(res[3]).to.be.equal('4'); 91 | expect(res[4]).to.be.equal('5'); 92 | }) 93 | it('by post strategy ', function () { 94 | const res: any[] = [] 95 | const newTree: Tree<'children'> = map(tree, ((t) => { 96 | res.push(t.key) 97 | return { 98 | key: t.key + '1' 99 | } 100 | }), { strategy: 'post' }) 101 | expect(newTree.key).to.be.equal('11'); 102 | expect(newTree.children?.[0]?.key).to.be.equal('21'); 103 | expect(newTree.children?.[0]?.children?.[0]?.key).to.be.equal('31'); 104 | expect(res.length).to.be.equal(5); 105 | expect(res[0]).to.be.equal('3'); 106 | expect(res[1]).to.be.equal('2'); 107 | expect(res[2]).to.be.equal('5'); 108 | expect(res[3]).to.be.equal('4'); 109 | expect(res[4]).to.be.equal('1'); 110 | }) 111 | it('by breadth strategy ', function () { 112 | const res: any[] = [] 113 | const newTree: Tree<'children'> = map(tree, ((t) => { 114 | res.push(t.key) 115 | return { 116 | key: t.key + '1' 117 | } 118 | }), { strategy: 'breadth' }) 119 | expect(newTree.key).to.be.equal('11'); 120 | expect(newTree.children?.[0]?.key).to.be.equal('21'); 121 | expect(newTree.children?.[0]?.children?.[0]?.key).to.be.equal('31'); 122 | expect(res.length).to.be.equal(5); 123 | expect(res[0]).to.be.equal('1'); 124 | expect(res[1]).to.be.equal('2'); 125 | expect(res[2]).to.be.equal('4'); 126 | expect(res[3]).to.be.equal('3'); 127 | expect(res[4]).to.be.equal('5'); 128 | }) 129 | it('by tree childrenKey is "subItems"', function() { 130 | const res: string[] = [] 131 | const newTree: Tree<'subItems'> = map(treeSubItems, ((t) => { 132 | res.push(t.key) 133 | return { 134 | key: t.key + '1' 135 | } 136 | }), { childrenKey: 'subItems' }) 137 | expect(newTree.key).to.be.equal('11'); 138 | expect(newTree.subItems?.[0]?.key).to.be.equal('21'); 139 | expect(newTree.subItems?.[0]?.subItems?.[0]?.key).to.be.equal('31'); 140 | expect(res.length).to.be.equal(5); 141 | expect(res[0]).to.be.equal('1'); 142 | expect(res[1]).to.be.equal('2'); 143 | expect(res[2]).to.be.equal('3'); 144 | expect(res[3]).to.be.equal('4'); 145 | expect(res[4]).to.be.equal('5'); 146 | }) 147 | it('for forest by default strategy (pre)', function () { 148 | const res: string[] = [] 149 | const forest = map([tree], ((t) => { 150 | res.push(t.key) 151 | return { 152 | key: t.key + '1' 153 | } 154 | })) 155 | expect(forest?.[0].key).to.be.equal('11'); 156 | expect(forest?.[0].children?.[0]?.key).to.be.equal('21'); 157 | expect(forest?.[0].children?.[0]?.children?.[0]?.key).to.be.equal('31'); 158 | expect(res.length).to.be.equal(5); 159 | expect(res[0]).to.be.equal('1'); 160 | expect(res[1]).to.be.equal('2'); 161 | expect(res[2]).to.be.equal('3'); 162 | expect(res[3]).to.be.equal('4'); 163 | expect(res[4]).to.be.equal('5'); 164 | }) 165 | it('for forest by post strategy', function () { 166 | const res: string[] = [] 167 | const forest = map([tree], ((t) => { 168 | res.push(t.key) 169 | return { 170 | key: t.key + '1' 171 | } 172 | }), { strategy: 'post' }) 173 | expect(forest?.[0].key).to.be.equal('11'); 174 | expect(forest?.[0].children?.[0]?.key).to.be.equal('21'); 175 | expect(forest?.[0].children?.[0]?.children?.[0]?.key).to.be.equal('31'); 176 | expect(res.length).to.be.equal(5); 177 | expect(res[0]).to.be.equal('3'); 178 | expect(res[1]).to.be.equal('2'); 179 | expect(res[2]).to.be.equal('5'); 180 | expect(res[3]).to.be.equal('4'); 181 | expect(res[4]).to.be.equal('1'); 182 | }) 183 | it('for forest by breadth strategy', function () { 184 | const res: string[] = [] 185 | const forest = map([tree], ((t) => { 186 | res.push(t.key) 187 | return { 188 | key: t.key + '1' 189 | } 190 | }), { strategy: 'breadth' }) 191 | expect(forest?.[0].key).to.be.equal('11'); 192 | expect(forest?.[0].children?.[0]?.key).to.be.equal('21'); 193 | expect(forest?.[0].children?.[0]?.children?.[0]?.key).to.be.equal('31'); 194 | expect(res.length).to.be.equal(5); 195 | expect(res[0]).to.be.equal('1'); 196 | expect(res[1]).to.be.equal('2'); 197 | expect(res[2]).to.be.equal('4'); 198 | expect(res[3]).to.be.equal('3'); 199 | expect(res[4]).to.be.equal('5'); 200 | }) 201 | it('by default strategy (pre) and getChildrenKey', function () { 202 | const res: any[] = [] 203 | const newTree: Tree = map(treeMultiChildrenKey, ((t) => { 204 | res.push(t.key) 205 | return { 206 | key: t.key + '1' 207 | } 208 | }), 209 | { 210 | getChildrenKey: (tree, meta) => { 211 | if (meta.depth === 1) { 212 | return 'subItems' 213 | } 214 | }, 215 | }) 216 | expect(newTree.key).to.be.equal('11'); 217 | expect(newTree.children?.[0]?.key).to.be.equal('21'); 218 | expect(newTree.children?.[0]?.subItems?.[0]?.key).to.be.equal('31'); 219 | expect(res.length).to.be.equal(5); 220 | expect(res[0]).to.be.equal('1'); 221 | expect(res[1]).to.be.equal('2'); 222 | expect(res[2]).to.be.equal('3'); 223 | expect(res[3]).to.be.equal('4'); 224 | expect(res[4]).to.be.equal('5'); 225 | }) 226 | it('by post strategy and getChildrenKey', function () { 227 | const res: any[] = [] 228 | const newTree: Tree = map(treeMultiChildrenKey, ((t) => { 229 | res.push(t.key) 230 | return { 231 | key: t.key + '1' 232 | } 233 | }), 234 | { 235 | getChildrenKey: (tree, meta) => { 236 | if (meta.depth === 1) { 237 | return 'subItems' 238 | } 239 | }, 240 | strategy: 'post' 241 | }) 242 | expect(newTree.key).to.be.equal('11'); 243 | expect(newTree.children?.[0]?.key).to.be.equal('21'); 244 | expect(newTree.children?.[0]?.subItems?.[0]?.key).to.be.equal('31'); 245 | expect(res.length).to.be.equal(5); 246 | expect(res[0]).to.be.equal('3'); 247 | expect(res[1]).to.be.equal('2'); 248 | expect(res[2]).to.be.equal('5'); 249 | expect(res[3]).to.be.equal('4'); 250 | expect(res[4]).to.be.equal('1'); 251 | }) 252 | it('by breadth strategy and getChildrenKey', function () { 253 | const res: any[] = [] 254 | const newTree: Tree = map(treeMultiChildrenKey, ((t) => { 255 | res.push(t.key) 256 | return { 257 | key: t.key + '1' 258 | } 259 | }), 260 | { 261 | getChildrenKey: (tree, meta) => { 262 | if (meta.depth === 1) { 263 | return 'subItems' 264 | } 265 | }, 266 | strategy: 'breadth' 267 | }) 268 | expect(newTree.key).to.be.equal('11'); 269 | expect(newTree.children?.[0]?.key).to.be.equal('21'); 270 | expect(newTree.children?.[0]?.subItems?.[0]?.key).to.be.equal('31'); 271 | expect(res.length).to.be.equal(5); 272 | expect(res[0]).to.be.equal('1'); 273 | expect(res[1]).to.be.equal('2'); 274 | expect(res[2]).to.be.equal('4'); 275 | expect(res[3]).to.be.equal('3'); 276 | expect(res[4]).to.be.equal('5'); 277 | }) 278 | }) 279 | -------------------------------------------------------------------------------- /test/some.test.ts: -------------------------------------------------------------------------------- 1 | /* global describe, it, beforeEach */ 2 | 3 | import 'mocha' 4 | import { expect } from 'chai' 5 | import { some } from '../src/index' 6 | import type { Tree } from '../src/types' 7 | 8 | describe('[some]', function () { 9 | const tree: Tree = { 10 | key: '1', 11 | children: [ 12 | { 13 | key: '2', 14 | children: [ 15 | { 16 | key: '3' 17 | } 18 | ] 19 | }, 20 | { 21 | key: '4', 22 | children: [ 23 | { 24 | key: '5' 25 | } 26 | ] 27 | } 28 | ] 29 | } 30 | 31 | it('by default strategy (pre)', function () { 32 | const node4Exist = some(tree, (t) => t.key === '4') 33 | const node6Exist = some(tree, (t) => t.key === '6') 34 | expect(node4Exist).to.be.equal(true); 35 | expect(node6Exist).to.be.equal(false) 36 | }) 37 | }) -------------------------------------------------------------------------------- /test/toArray.test.ts: -------------------------------------------------------------------------------- 1 | /* global describe, it, beforeEach */ 2 | 3 | import 'mocha' 4 | import { expect } from 'chai' 5 | import { toArray } from '../src/index' 6 | import type { Tree } from '../src/types' 7 | 8 | describe('[toArray]', function () { 9 | const tree: Tree = { 10 | key: '1', 11 | children: [ 12 | { 13 | key: '2', 14 | children: [ 15 | { 16 | key: '3' 17 | } 18 | ] 19 | }, 20 | { 21 | key: '4', 22 | children: [ 23 | { 24 | key: '5' 25 | } 26 | ] 27 | } 28 | ] 29 | } 30 | 31 | const treeSubItems: Tree<'subItems'> = { 32 | key: '1', 33 | subItems: [ 34 | { 35 | key: '2', 36 | subItems: [ 37 | { 38 | key: '3' 39 | } 40 | ] 41 | }, 42 | { 43 | key: '4', 44 | subItems: [ 45 | { 46 | key: '5' 47 | } 48 | ] 49 | } 50 | ] 51 | } 52 | 53 | it('by default strategy (pre)', function () { 54 | const res = toArray(tree) 55 | expect(res.length).to.be.equal(5); 56 | expect(res[0]?.key).to.be.equal('1'); 57 | expect(res[1]?.key).to.be.equal('2'); 58 | expect(res[2]?.key).to.be.equal('3'); 59 | expect(res[3]?.key).to.be.equal('4'); 60 | expect(res[4]?.key).to.be.equal('5'); 61 | }) 62 | it('by post strategy', function () { 63 | const res = toArray(tree, { strategy: 'post' }) 64 | expect(res.length).to.be.equal(5); 65 | expect(res[0]?.key).to.be.equal('3'); 66 | expect(res[1]?.key).to.be.equal('2'); 67 | expect(res[2]?.key).to.be.equal('5'); 68 | expect(res[3]?.key).to.be.equal('4'); 69 | expect(res[4]?.key).to.be.equal('1'); 70 | }) 71 | it('by breadth strategy', function () { 72 | const res = toArray(tree, { strategy: 'breadth' }) 73 | expect(res.length).to.be.equal(5); 74 | expect(res[0]?.key).to.be.equal('1'); 75 | expect(res[1]?.key).to.be.equal('2'); 76 | expect(res[2]?.key).to.be.equal('4'); 77 | expect(res[3]?.key).to.be.equal('3'); 78 | expect(res[4]?.key).to.be.equal('5'); 79 | }) 80 | it('by tree childrenKey is "subItems"', function() { 81 | const res = toArray(treeSubItems, { childrenKey: 'subItems' }) 82 | expect(res.length).to.be.equal(5); 83 | expect(res[0]?.key).to.be.equal('1'); 84 | expect(res[1]?.key).to.be.equal('2'); 85 | expect(res[2]?.key).to.be.equal('3'); 86 | expect(res[3]?.key).to.be.equal('4'); 87 | expect(res[4]?.key).to.be.equal('5'); 88 | }) 89 | it('for forest by default strategy (pre)', function () { 90 | const res = toArray([tree]) 91 | expect(res.length).to.be.equal(5); 92 | expect(res[0]?.key).to.be.equal('1'); 93 | expect(res[1]?.key).to.be.equal('2'); 94 | expect(res[2]?.key).to.be.equal('3'); 95 | expect(res[3]?.key).to.be.equal('4'); 96 | expect(res[4]?.key).to.be.equal('5'); 97 | }) 98 | }) -------------------------------------------------------------------------------- /test/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../ts.config.json", 3 | "include": ["**/*.test.ts"], 4 | "ts-node": { 5 | "transpileOnly": true 6 | }, 7 | "compilerOptions": { 8 | "esModuleInterop": true, 9 | "module": "commonjs", 10 | "target": "es6" 11 | } 12 | } -------------------------------------------------------------------------------- /ts.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "noImplicitAny": true, 5 | "removeComments": true, 6 | "preserveConstEnums": true, 7 | "sourceMap": true 8 | }, 9 | "include": [ 10 | "src/**/*" 11 | ], 12 | "exclude": [ 13 | "node_modules", 14 | "dist" 15 | ] 16 | } --------------------------------------------------------------------------------