├── .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 | 
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 |
2 |
46 |
47 |
48 |
67 |
87 |
--------------------------------------------------------------------------------
/docs/.vuepress/components/common/tree.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/docs/.vuepress/components/demo-filter.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
107 |
118 |
--------------------------------------------------------------------------------
/docs/.vuepress/components/demo-find.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
107 |
118 |
--------------------------------------------------------------------------------
/docs/.vuepress/components/demo-foreach.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
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 | }
--------------------------------------------------------------------------------