@snack-uikit/tree 0.9.37 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/cjs/helpers/__tests__/collectIds.spec.d.ts +1 -0
- package/dist/cjs/helpers/__tests__/collectIds.spec.js +14 -0
- package/dist/cjs/helpers/__tests__/constants.d.ts +2 -0
- package/dist/cjs/helpers/__tests__/constants.js +42 -0
- package/dist/cjs/helpers/__tests__/getSearchedTreeItems.spec.d.ts +1 -0
- package/dist/cjs/helpers/__tests__/getSearchedTreeItems.spec.js +183 -0
- package/dist/cjs/helpers/__tests__/getSearchedTreeNodeById.spec.d.ts +1 -0
- package/dist/cjs/helpers/__tests__/getSearchedTreeNodeById.spec.js +123 -0
- package/dist/cjs/helpers/checkNestedNodesSelection.d.ts +5 -0
- package/dist/cjs/helpers/checkNestedNodesSelection.js +17 -0
- package/dist/cjs/helpers/collectIds.d.ts +2 -0
- package/dist/cjs/helpers/collectIds.js +13 -0
- package/dist/cjs/helpers/extractTreeNodeTitle.d.ts +2 -0
- package/dist/cjs/helpers/extractTreeNodeTitle.js +15 -0
- package/dist/cjs/helpers/findAllChildNodeIds.d.ts +2 -0
- package/dist/cjs/helpers/findAllChildNodeIds.js +21 -0
- package/dist/cjs/helpers/findAllExpandedChildNodeIds.d.ts +2 -0
- package/dist/cjs/helpers/findAllExpandedChildNodeIds.js +19 -0
- package/dist/cjs/helpers/getSearchedTreeItems.d.ts +10 -0
- package/dist/cjs/helpers/getSearchedTreeItems.js +43 -0
- package/dist/cjs/helpers/getSearchedTreeNodeById.d.ts +11 -0
- package/dist/cjs/helpers/getSearchedTreeNodeById.js +39 -0
- package/dist/cjs/helpers/index.d.ts +8 -0
- package/dist/cjs/helpers/index.js +32 -0
- package/dist/cjs/helpers/lookupTreeForSelectedNodes.d.ts +6 -0
- package/dist/cjs/{helpers.js → helpers/lookupTreeForSelectedNodes.js} +5 -43
- package/dist/cjs/helpers/sortTreeItemsByTitle.d.ts +2 -0
- package/dist/cjs/helpers/sortTreeItemsByTitle.js +13 -0
- package/dist/cjs/helpers/traverse.d.ts +2 -0
- package/dist/cjs/helpers/traverse.js +40 -0
- package/dist/cjs/types.d.ts +3 -0
- package/dist/esm/helpers/__tests__/collectIds.spec.d.ts +1 -0
- package/dist/esm/helpers/__tests__/collectIds.spec.js +20 -0
- package/dist/esm/helpers/__tests__/constants.d.ts +2 -0
- package/dist/esm/helpers/__tests__/constants.js +52 -0
- package/dist/esm/helpers/__tests__/getSearchedTreeItems.spec.d.ts +1 -0
- package/dist/esm/helpers/__tests__/getSearchedTreeItems.spec.js +165 -0
- package/dist/esm/helpers/__tests__/getSearchedTreeNodeById.spec.d.ts +1 -0
- package/dist/esm/helpers/__tests__/getSearchedTreeNodeById.spec.js +112 -0
- package/dist/esm/helpers/checkNestedNodesSelection.d.ts +5 -0
- package/dist/esm/helpers/checkNestedNodesSelection.js +11 -0
- package/dist/esm/helpers/collectIds.d.ts +2 -0
- package/dist/esm/helpers/collectIds.js +6 -0
- package/dist/esm/helpers/extractTreeNodeTitle.d.ts +2 -0
- package/dist/esm/helpers/extractTreeNodeTitle.js +1 -0
- package/dist/esm/helpers/findAllChildNodeIds.d.ts +2 -0
- package/dist/esm/helpers/findAllChildNodeIds.js +15 -0
- package/dist/esm/helpers/findAllExpandedChildNodeIds.d.ts +2 -0
- package/dist/esm/helpers/findAllExpandedChildNodeIds.js +13 -0
- package/dist/esm/helpers/getSearchedTreeItems.d.ts +10 -0
- package/dist/esm/helpers/getSearchedTreeItems.js +31 -0
- package/dist/esm/helpers/getSearchedTreeNodeById.d.ts +11 -0
- package/dist/esm/helpers/getSearchedTreeNodeById.js +23 -0
- package/dist/esm/helpers/index.d.ts +8 -0
- package/dist/esm/helpers/index.js +8 -0
- package/dist/esm/helpers/lookupTreeForSelectedNodes.d.ts +6 -0
- package/dist/esm/{helpers.js → helpers/lookupTreeForSelectedNodes.js} +2 -38
- package/dist/esm/helpers/sortTreeItemsByTitle.d.ts +2 -0
- package/dist/esm/helpers/sortTreeItemsByTitle.js +6 -0
- package/dist/esm/helpers/traverse.d.ts +2 -0
- package/dist/esm/helpers/traverse.js +19 -0
- package/dist/esm/types.d.ts +3 -0
- package/package.json +3 -2
- package/src/helperComponents/TreeNode/TreeNode.tsx +2 -1
- package/src/helpers/__tests__/collectIds.spec.ts +23 -0
- package/src/helpers/__tests__/constants.ts +54 -0
- package/src/helpers/__tests__/getSearchedTreeItems.spec.ts +191 -0
- package/src/helpers/__tests__/getSearchedTreeNodeById.spec.ts +138 -0
- package/src/helpers/checkNestedNodesSelection.ts +15 -0
- package/src/helpers/collectIds.ts +10 -0
- package/src/helpers/extractTreeNodeTitle.ts +4 -0
- package/src/helpers/findAllChildNodeIds.ts +20 -0
- package/src/helpers/findAllExpandedChildNodeIds.ts +18 -0
- package/src/helpers/getSearchedTreeItems.ts +50 -0
- package/src/helpers/getSearchedTreeNodeById.ts +46 -0
- package/src/helpers/index.ts +8 -0
- package/src/{helpers.ts → helpers/lookupTreeForSelectedNodes.ts} +3 -50
- package/src/helpers/sortTreeItemsByTitle.ts +10 -0
- package/src/helpers/traverse.ts +27 -0
- package/src/types.ts +4 -0
- package/dist/cjs/helpers.d.ts +0 -11
- package/dist/esm/helpers.d.ts +0 -11
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './checkNestedNodesSelection';
|
|
2
|
+
export * from './collectIds';
|
|
3
|
+
export * from './findAllExpandedChildNodeIds';
|
|
4
|
+
export * from './getSearchedTreeItems';
|
|
5
|
+
export * from './getSearchedTreeNodeById';
|
|
6
|
+
export * from './lookupTreeForSelectedNodes';
|
|
7
|
+
export * from './sortTreeItemsByTitle';
|
|
8
|
+
export * from './traverse';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './checkNestedNodesSelection';
|
|
2
|
+
export * from './collectIds';
|
|
3
|
+
export * from './findAllExpandedChildNodeIds';
|
|
4
|
+
export * from './getSearchedTreeItems';
|
|
5
|
+
export * from './getSearchedTreeNodeById';
|
|
6
|
+
export * from './lookupTreeForSelectedNodes';
|
|
7
|
+
export * from './sortTreeItemsByTitle';
|
|
8
|
+
export * from './traverse';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ParentNode, TreeNodeId, TreeNodeProps } from '../types';
|
|
2
|
+
export declare function lookupTreeForSelectedNodes({ node, selectedNodes, parentNode, }: {
|
|
3
|
+
node: Pick<TreeNodeProps, 'id' | 'nested' | 'disabled'>;
|
|
4
|
+
selectedNodes: TreeNodeId[];
|
|
5
|
+
parentNode?: ParentNode;
|
|
6
|
+
}): string[];
|
|
@@ -1,41 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const stack = [...nodes];
|
|
4
|
-
const ids = [];
|
|
5
|
-
let node;
|
|
6
|
-
while ((node = stack.pop())) {
|
|
7
|
-
if (!node.disabled) {
|
|
8
|
-
ids.push(node.id);
|
|
9
|
-
if ((_a = node.nested) === null || _a === void 0 ? void 0 : _a.length) {
|
|
10
|
-
stack.push(...node.nested);
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
return ids;
|
|
15
|
-
}
|
|
16
|
-
export function checkNestedNodesSelection(nodes, selectedKeys) {
|
|
17
|
-
const allIds = findAllChildNodeIds(nodes);
|
|
18
|
-
const selected = allIds.filter(id => selectedKeys.includes(id));
|
|
19
|
-
const someSelected = selected.length > 0;
|
|
20
|
-
const allSelected = someSelected && allIds.length === selected.length;
|
|
21
|
-
return {
|
|
22
|
-
someSelected: !allSelected && someSelected,
|
|
23
|
-
allSelected,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
export function findAllExpandedChildNodeIds(nodes, expandedNodes) {
|
|
27
|
-
var _a;
|
|
28
|
-
const stack = [...nodes];
|
|
29
|
-
const ids = [];
|
|
30
|
-
let node;
|
|
31
|
-
while ((node = stack.shift())) {
|
|
32
|
-
ids.push(node.id);
|
|
33
|
-
if (((_a = node.nested) === null || _a === void 0 ? void 0 : _a.length) && expandedNodes.includes(node.id)) {
|
|
34
|
-
stack.unshift(...node.nested);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return ids;
|
|
38
|
-
}
|
|
1
|
+
import { checkNestedNodesSelection } from './checkNestedNodesSelection';
|
|
2
|
+
import { findAllChildNodeIds } from './findAllChildNodeIds';
|
|
39
3
|
export function lookupTreeForSelectedNodes({ node, selectedNodes, parentNode, }) {
|
|
40
4
|
var _a;
|
|
41
5
|
const { nested } = node;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { extractTreeNodeTitle } from './extractTreeNodeTitle';
|
|
2
|
+
export const sortTreeItemsByTitle = (items) => items === null || items === void 0 ? void 0 : items.toSorted((itemA, itemB) => {
|
|
3
|
+
const valueA = extractTreeNodeTitle(itemA);
|
|
4
|
+
const valueB = extractTreeNodeTitle(itemB);
|
|
5
|
+
return valueA.localeCompare(valueB);
|
|
6
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Queue from 'queue-fifo';
|
|
2
|
+
export const traverse = (nodes, callback) => {
|
|
3
|
+
var _a;
|
|
4
|
+
const queue = new Queue();
|
|
5
|
+
for (const node of nodes) {
|
|
6
|
+
queue.enqueue({ node, depth: 0 });
|
|
7
|
+
}
|
|
8
|
+
while (!queue.isEmpty()) {
|
|
9
|
+
const nodeWithDepth = queue.dequeue();
|
|
10
|
+
if (!nodeWithDepth) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
const { node, depth } = nodeWithDepth;
|
|
14
|
+
callback(node, depth);
|
|
15
|
+
for (const child of (_a = node.nested) !== null && _a !== void 0 ? _a : []) {
|
|
16
|
+
queue.enqueue({ node: child, depth: depth + 1 });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -100,3 +100,6 @@ export type TreeMultiSelect = Omit<TreeCommonProps, 'selected'> & {
|
|
|
100
100
|
onSelect?(selectedKeys: TreeNodeId[], node: TreeNodeProps): void;
|
|
101
101
|
};
|
|
102
102
|
export type TreeBaseProps = TreeView | TreeMultiSelect | TreeSingleSelect;
|
|
103
|
+
export type ExtendedTreeNodeProps = TreeNodeProps & {
|
|
104
|
+
getTitle?(): void;
|
|
105
|
+
};
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
6
|
"title": "Tree",
|
|
7
|
-
"version": "0.
|
|
7
|
+
"version": "0.10.0",
|
|
8
8
|
"sideEffects": [
|
|
9
9
|
"*.css",
|
|
10
10
|
"*.woff",
|
|
@@ -44,8 +44,9 @@
|
|
|
44
44
|
"@snack-uikit/typography": "0.8.12",
|
|
45
45
|
"@snack-uikit/utils": "4.0.1",
|
|
46
46
|
"classnames": "2.5.1",
|
|
47
|
+
"queue-fifo": "0.2.6",
|
|
47
48
|
"react-transition-state": "2.1.1",
|
|
48
49
|
"uncontrollable": "8.0.4"
|
|
49
50
|
},
|
|
50
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "afb2d2dbb5fdeedfed3e1056b6fd2fc197f42ab0"
|
|
51
52
|
}
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
FocusEvent,
|
|
4
4
|
forwardRef,
|
|
5
5
|
KeyboardEventHandler,
|
|
6
|
+
MouseEvent,
|
|
6
7
|
MouseEventHandler,
|
|
7
8
|
useEffect,
|
|
8
9
|
useMemo,
|
|
@@ -128,7 +129,7 @@ export const TreeNode = forwardRef<HTMLDivElement, TreeNodeComponentProps>(
|
|
|
128
129
|
);
|
|
129
130
|
};
|
|
130
131
|
|
|
131
|
-
const handleAnchorClick = (e:
|
|
132
|
+
const handleAnchorClick = (e: MouseEvent<Element>) => {
|
|
132
133
|
e.stopPropagation();
|
|
133
134
|
if (e?.metaKey || e?.ctrlKey || e?.button === 1) {
|
|
134
135
|
return;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { collectIds } from '../collectIds';
|
|
4
|
+
import { tree } from './constants';
|
|
5
|
+
|
|
6
|
+
describe('collect tree ids', () => {
|
|
7
|
+
it('find all ids', () => {
|
|
8
|
+
const expected: string[] = [
|
|
9
|
+
'customer 1',
|
|
10
|
+
'customer 2',
|
|
11
|
+
'same_hierarchy customer',
|
|
12
|
+
'department 1.1',
|
|
13
|
+
'department 1.2',
|
|
14
|
+
'department 2.1',
|
|
15
|
+
'same_hierarchy department',
|
|
16
|
+
'project 1.1.1',
|
|
17
|
+
'project 1.1.2',
|
|
18
|
+
'same_hierarchy project',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
expect(collectIds(tree)).toEqual(expected);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { TreeNodeProps } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const tree: TreeNodeProps[] = [
|
|
4
|
+
{
|
|
5
|
+
id: 'customer 1',
|
|
6
|
+
title: 'customer 1',
|
|
7
|
+
nested: [
|
|
8
|
+
{
|
|
9
|
+
id: 'department 1.1',
|
|
10
|
+
title: 'department 1.1',
|
|
11
|
+
nested: [
|
|
12
|
+
{
|
|
13
|
+
id: 'project 1.1.1',
|
|
14
|
+
title: 'project 1.1.1',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: 'project 1.1.2',
|
|
18
|
+
title: 'project 1.1.2',
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'department 1.2',
|
|
24
|
+
title: 'department 1.2',
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'customer 2',
|
|
30
|
+
title: 'customer 2',
|
|
31
|
+
nested: [
|
|
32
|
+
{
|
|
33
|
+
id: 'department 2.1',
|
|
34
|
+
title: 'department 2.1',
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'same_hierarchy customer',
|
|
40
|
+
title: 'same_hierarchy customer',
|
|
41
|
+
nested: [
|
|
42
|
+
{
|
|
43
|
+
id: 'same_hierarchy department',
|
|
44
|
+
title: 'same_hierarchy department',
|
|
45
|
+
nested: [
|
|
46
|
+
{
|
|
47
|
+
id: 'same_hierarchy project',
|
|
48
|
+
title: 'same_hierarchy project',
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
];
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import type { TreeNodeProps } from '../../types';
|
|
4
|
+
import { getSearchedTreeItems } from '../getSearchedTreeItems';
|
|
5
|
+
import { tree } from './constants';
|
|
6
|
+
|
|
7
|
+
describe('search tree items', () => {
|
|
8
|
+
it('find items if 1 project is matched', () => {
|
|
9
|
+
const expected: TreeNodeProps[] = [
|
|
10
|
+
{
|
|
11
|
+
id: 'customer 1',
|
|
12
|
+
title: 'customer 1',
|
|
13
|
+
nested: [
|
|
14
|
+
{
|
|
15
|
+
id: 'department 1.1',
|
|
16
|
+
title: 'department 1.1',
|
|
17
|
+
nested: [
|
|
18
|
+
{
|
|
19
|
+
id: 'project 1.1.1',
|
|
20
|
+
title: 'project 1.1.1',
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
expect(getSearchedTreeItems({ tree, searchOptions: { query: 'project 1.1.1' } })).toEqual(expected);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('find items if 2 projects are matched', () => {
|
|
32
|
+
const expected: TreeNodeProps[] = [
|
|
33
|
+
{
|
|
34
|
+
id: 'customer 1',
|
|
35
|
+
title: 'customer 1',
|
|
36
|
+
nested: [
|
|
37
|
+
{
|
|
38
|
+
id: 'department 1.1',
|
|
39
|
+
title: 'department 1.1',
|
|
40
|
+
nested: [
|
|
41
|
+
{
|
|
42
|
+
id: 'project 1.1.1',
|
|
43
|
+
title: 'project 1.1.1',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'project 1.1.2',
|
|
47
|
+
title: 'project 1.1.2',
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
expect(getSearchedTreeItems({ tree, searchOptions: { query: 'project 1.1' } })).toEqual(expected);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('find items if 1 department is matched', () => {
|
|
59
|
+
const expected: TreeNodeProps[] = [
|
|
60
|
+
{
|
|
61
|
+
id: 'customer 1',
|
|
62
|
+
title: 'customer 1',
|
|
63
|
+
nested: [
|
|
64
|
+
{
|
|
65
|
+
id: 'department 1.1',
|
|
66
|
+
title: 'department 1.1',
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
expect(getSearchedTreeItems({ tree, searchOptions: { query: 'department 1.1' } })).toEqual(expected);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('find items if 2 departments are matched', () => {
|
|
76
|
+
const expected: TreeNodeProps[] = [
|
|
77
|
+
{
|
|
78
|
+
id: 'customer 1',
|
|
79
|
+
title: 'customer 1',
|
|
80
|
+
nested: [
|
|
81
|
+
{
|
|
82
|
+
id: 'department 1.1',
|
|
83
|
+
title: 'department 1.1',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: 'department 1.2',
|
|
87
|
+
title: 'department 1.2',
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
expect(getSearchedTreeItems({ tree, searchOptions: { query: 'department 1.' } })).toEqual(expected);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('find items if 1 customer is matched', () => {
|
|
97
|
+
const expected: TreeNodeProps[] = [
|
|
98
|
+
{
|
|
99
|
+
id: 'customer 1',
|
|
100
|
+
title: 'customer 1',
|
|
101
|
+
},
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
expect(getSearchedTreeItems({ tree, searchOptions: { query: 'customer 1' } })).toEqual(expected);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('find items if not one items are matched', () => {
|
|
108
|
+
const expected: TreeNodeProps[] = [];
|
|
109
|
+
|
|
110
|
+
expect(getSearchedTreeItems({ tree, searchOptions: { query: 'test' } })).toEqual(expected);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('find items if empty query', () => {
|
|
114
|
+
expect(getSearchedTreeItems({ tree, searchOptions: { query: '' } })).toEqual(tree);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('find items if empty searchOptions', () => {
|
|
118
|
+
expect(getSearchedTreeItems({ tree })).toEqual(tree);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('find matches in the same hierarchy', () => {
|
|
122
|
+
const expected: TreeNodeProps[] = [
|
|
123
|
+
{
|
|
124
|
+
id: 'same_hierarchy customer',
|
|
125
|
+
title: 'same_hierarchy customer',
|
|
126
|
+
nested: [
|
|
127
|
+
{
|
|
128
|
+
id: 'same_hierarchy department',
|
|
129
|
+
title: 'same_hierarchy department',
|
|
130
|
+
nested: [
|
|
131
|
+
{
|
|
132
|
+
id: 'same_hierarchy project',
|
|
133
|
+
title: 'same_hierarchy project',
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
expect(getSearchedTreeItems({ tree, searchOptions: { query: 'same_hierarchy' } })).toEqual(expected);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('find all items from customer 2', () => {
|
|
145
|
+
const expected: TreeNodeProps[] = [
|
|
146
|
+
{
|
|
147
|
+
id: 'customer 2',
|
|
148
|
+
title: 'customer 2',
|
|
149
|
+
nested: [
|
|
150
|
+
{
|
|
151
|
+
id: 'department 2.1',
|
|
152
|
+
title: 'department 2.1',
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
expect(
|
|
159
|
+
getSearchedTreeItems({ tree, searchOptions: { query: 'customer 2', includeChildrenMatchedParent: true } }),
|
|
160
|
+
).toEqual(expected);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('find all items from department 1.1', () => {
|
|
164
|
+
const expected: TreeNodeProps[] = [
|
|
165
|
+
{
|
|
166
|
+
id: 'customer 1',
|
|
167
|
+
title: 'customer 1',
|
|
168
|
+
nested: [
|
|
169
|
+
{
|
|
170
|
+
id: 'department 1.1',
|
|
171
|
+
title: 'department 1.1',
|
|
172
|
+
nested: [
|
|
173
|
+
{
|
|
174
|
+
id: 'project 1.1.1',
|
|
175
|
+
title: 'project 1.1.1',
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: 'project 1.1.2',
|
|
179
|
+
title: 'project 1.1.2',
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
},
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
expect(
|
|
188
|
+
getSearchedTreeItems({ tree, searchOptions: { query: 'department 1.1', includeChildrenMatchedParent: true } }),
|
|
189
|
+
).toEqual(expected);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import type { TreeNodeProps } from '../../types';
|
|
4
|
+
import { getSearchedTreeNodeById } from '../getSearchedTreeNodeById';
|
|
5
|
+
import { tree } from './constants';
|
|
6
|
+
|
|
7
|
+
describe('get tree items by id', () => {
|
|
8
|
+
it('should return the item with nested when it exists', () => {
|
|
9
|
+
const expected: TreeNodeProps = {
|
|
10
|
+
id: 'customer 1',
|
|
11
|
+
title: 'customer 1',
|
|
12
|
+
nested: [
|
|
13
|
+
{
|
|
14
|
+
id: 'department 1.1',
|
|
15
|
+
title: 'department 1.1',
|
|
16
|
+
nested: [
|
|
17
|
+
{
|
|
18
|
+
id: 'project 1.1.1',
|
|
19
|
+
title: 'project 1.1.1',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'project 1.1.2',
|
|
23
|
+
title: 'project 1.1.2',
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'department 1.2',
|
|
29
|
+
title: 'department 1.2',
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
expect(getSearchedTreeNodeById({ tree, searchOptions: { id: 'customer 1' } })).toEqual(expected);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should return the item without nested when it exists', () => {
|
|
38
|
+
const expected: TreeNodeProps = {
|
|
39
|
+
id: 'customer 1',
|
|
40
|
+
title: 'customer 1',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
expect(getSearchedTreeNodeById({ tree, searchOptions: { id: 'customer 1', includeNested: false } })).toEqual(
|
|
44
|
+
expected,
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should return null or undefined when the item does not exist', () => {
|
|
49
|
+
const expected = null;
|
|
50
|
+
|
|
51
|
+
expect(getSearchedTreeNodeById({ tree, searchOptions: { id: 'customer none' } })).toEqual(expected);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('get tree items by ids', () => {
|
|
56
|
+
it('should return the item when only 1 item exists with nested', () => {
|
|
57
|
+
const expected: TreeNodeProps[] = [
|
|
58
|
+
{
|
|
59
|
+
id: 'department 1.1',
|
|
60
|
+
title: 'department 1.1',
|
|
61
|
+
nested: [
|
|
62
|
+
{
|
|
63
|
+
id: 'project 1.1.1',
|
|
64
|
+
title: 'project 1.1.1',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'project 1.1.2',
|
|
68
|
+
title: 'project 1.1.2',
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
expect(
|
|
75
|
+
getSearchedTreeNodeById({
|
|
76
|
+
tree,
|
|
77
|
+
searchOptions: { id: ['department 1.1'] },
|
|
78
|
+
}),
|
|
79
|
+
).toEqual(expected);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should return the item when only 1 item exists without nested', () => {
|
|
83
|
+
const expected: TreeNodeProps[] = [
|
|
84
|
+
{
|
|
85
|
+
id: 'department 1.1',
|
|
86
|
+
title: 'department 1.1',
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
expect(
|
|
91
|
+
getSearchedTreeNodeById({
|
|
92
|
+
tree,
|
|
93
|
+
searchOptions: { id: ['department 1.1'], includeNested: false },
|
|
94
|
+
}),
|
|
95
|
+
).toEqual(expected);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should return both items when 2 items exist', () => {
|
|
99
|
+
const expected: TreeNodeProps[] = [
|
|
100
|
+
{
|
|
101
|
+
id: 'department 1.1',
|
|
102
|
+
title: 'department 1.1',
|
|
103
|
+
nested: [
|
|
104
|
+
{
|
|
105
|
+
id: 'project 1.1.1',
|
|
106
|
+
title: 'project 1.1.1',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: 'project 1.1.2',
|
|
110
|
+
title: 'project 1.1.2',
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: 'department 1.2',
|
|
116
|
+
title: 'department 1.2',
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
expect(
|
|
121
|
+
getSearchedTreeNodeById({
|
|
122
|
+
tree,
|
|
123
|
+
searchOptions: { id: ['department 1.1', 'department 1.2'] },
|
|
124
|
+
}),
|
|
125
|
+
).toEqual(expected);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should return an empty array or null when no items exist', () => {
|
|
129
|
+
const expected: TreeNodeProps[] = [];
|
|
130
|
+
|
|
131
|
+
expect(
|
|
132
|
+
getSearchedTreeNodeById({
|
|
133
|
+
tree,
|
|
134
|
+
searchOptions: { id: ['department 1.1test', 'department 1.2test'] },
|
|
135
|
+
}),
|
|
136
|
+
).toEqual(expected);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { TreeNodeId, TreeNodeProps } from '../types';
|
|
2
|
+
import { findAllChildNodeIds } from './findAllChildNodeIds';
|
|
3
|
+
|
|
4
|
+
export function checkNestedNodesSelection(nodes: TreeNodeProps[], selectedKeys: TreeNodeId[]) {
|
|
5
|
+
const allIds = findAllChildNodeIds(nodes);
|
|
6
|
+
|
|
7
|
+
const selected = allIds.filter(id => selectedKeys.includes(id));
|
|
8
|
+
const someSelected = selected.length > 0;
|
|
9
|
+
const allSelected = someSelected && allIds.length === selected.length;
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
someSelected: !allSelected && someSelected,
|
|
13
|
+
allSelected,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { TreeNodeId, TreeNodeProps } from '../types';
|
|
2
|
+
|
|
3
|
+
export function findAllChildNodeIds(nodes: TreeNodeProps[]): TreeNodeId[] {
|
|
4
|
+
const stack = [...nodes];
|
|
5
|
+
const ids: TreeNodeId[] = [];
|
|
6
|
+
|
|
7
|
+
let node: TreeNodeProps | undefined;
|
|
8
|
+
|
|
9
|
+
while ((node = stack.pop())) {
|
|
10
|
+
if (!node.disabled) {
|
|
11
|
+
ids.push(node.id);
|
|
12
|
+
|
|
13
|
+
if (node.nested?.length) {
|
|
14
|
+
stack.push(...node.nested);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return ids;
|
|
20
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { TreeNodeId, TreeNodeProps } from '../types';
|
|
2
|
+
|
|
3
|
+
export function findAllExpandedChildNodeIds(nodes: TreeNodeProps[], expandedNodes: TreeNodeId[]) {
|
|
4
|
+
const stack = [...nodes];
|
|
5
|
+
const ids: TreeNodeId[] = [];
|
|
6
|
+
|
|
7
|
+
let node: TreeNodeProps | undefined;
|
|
8
|
+
|
|
9
|
+
while ((node = stack.shift())) {
|
|
10
|
+
ids.push(node.id);
|
|
11
|
+
|
|
12
|
+
if (node.nested?.length && expandedNodes.includes(node.id)) {
|
|
13
|
+
stack.unshift(...node.nested);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return ids;
|
|
18
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ExtendedTreeNodeProps } from '../types';
|
|
2
|
+
import { extractTreeNodeTitle } from './extractTreeNodeTitle';
|
|
3
|
+
|
|
4
|
+
const isMatchedTreeItem = (search: string) => {
|
|
5
|
+
const searchLower = search.toLocaleLowerCase();
|
|
6
|
+
return (treeItem: ExtendedTreeNodeProps) => {
|
|
7
|
+
const currentValue = extractTreeNodeTitle(treeItem);
|
|
8
|
+
return currentValue.toLocaleLowerCase().includes(searchLower);
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type SearchParams = {
|
|
13
|
+
tree: ExtendedTreeNodeProps[];
|
|
14
|
+
searchOptions?: Partial<{
|
|
15
|
+
query: string;
|
|
16
|
+
includeChildrenMatchedParent: boolean;
|
|
17
|
+
}>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const getSearchedTreeItems = ({ tree, searchOptions }: SearchParams) => {
|
|
21
|
+
if (!searchOptions?.query) return tree;
|
|
22
|
+
|
|
23
|
+
const { query = '', includeChildrenMatchedParent } = searchOptions;
|
|
24
|
+
|
|
25
|
+
const matchFunc = isMatchedTreeItem(query);
|
|
26
|
+
|
|
27
|
+
const searchItems = (treeItems: ExtendedTreeNodeProps[]): ExtendedTreeNodeProps[] =>
|
|
28
|
+
treeItems.reduce<ExtendedTreeNodeProps[]>((acc, item) => {
|
|
29
|
+
const hasMatchingTitle = matchFunc(item);
|
|
30
|
+
const needDeepSearch = !(hasMatchingTitle && includeChildrenMatchedParent);
|
|
31
|
+
|
|
32
|
+
const matchedChildren = item.nested && (needDeepSearch ? searchItems(item.nested) : item.nested);
|
|
33
|
+
|
|
34
|
+
if (hasMatchingTitle || matchedChildren?.length) {
|
|
35
|
+
const newItem = { ...item };
|
|
36
|
+
|
|
37
|
+
if (matchedChildren?.length) {
|
|
38
|
+
newItem.nested = matchedChildren;
|
|
39
|
+
} else {
|
|
40
|
+
delete newItem.nested;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
acc.push(newItem);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return acc;
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
return searchItems(tree);
|
|
50
|
+
};
|