react-native-tree-multi-select 0.5.4 → 0.8.4
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/README.md +5 -6
- package/lib/commonjs/TreeView.js +42 -48
- package/lib/commonjs/TreeView.js.map +1 -1
- package/lib/commonjs/components/NodeList.js +98 -92
- package/lib/commonjs/components/NodeList.js.map +1 -1
- package/lib/commonjs/helpers/expandCollapse.helper.js +21 -8
- package/lib/commonjs/helpers/expandCollapse.helper.js.map +1 -1
- package/lib/commonjs/helpers/initNodeMap.helper.js +14 -3
- package/lib/commonjs/helpers/initNodeMap.helper.js.map +1 -1
- package/lib/commonjs/helpers/selectAll.helper.js +32 -14
- package/lib/commonjs/helpers/selectAll.helper.js.map +1 -1
- package/lib/commonjs/helpers/toggleCheckbox.helper.js +34 -27
- package/lib/commonjs/helpers/toggleCheckbox.helper.js.map +1 -1
- package/lib/commonjs/store/global.store.js +92 -0
- package/lib/commonjs/store/global.store.js.map +1 -0
- package/lib/module/TreeView.js +42 -46
- package/lib/module/TreeView.js.map +1 -1
- package/lib/module/components/NodeList.js +96 -92
- package/lib/module/components/NodeList.js.map +1 -1
- package/lib/module/helpers/expandCollapse.helper.js +21 -8
- package/lib/module/helpers/expandCollapse.helper.js.map +1 -1
- package/lib/module/helpers/initNodeMap.helper.js +14 -3
- package/lib/module/helpers/initNodeMap.helper.js.map +1 -1
- package/lib/module/helpers/selectAll.helper.js +32 -14
- package/lib/module/helpers/selectAll.helper.js.map +1 -1
- package/lib/module/helpers/toggleCheckbox.helper.js +34 -27
- package/lib/module/helpers/toggleCheckbox.helper.js.map +1 -1
- package/lib/module/store/global.store.js +85 -0
- package/lib/module/store/global.store.js.map +1 -0
- package/lib/typescript/TreeView.d.ts.map +1 -1
- package/lib/typescript/components/NodeList.d.ts.map +1 -1
- package/lib/typescript/helpers/expandCollapse.helper.d.ts.map +1 -1
- package/lib/typescript/helpers/initNodeMap.helper.d.ts.map +1 -1
- package/lib/typescript/helpers/selectAll.helper.d.ts.map +1 -1
- package/lib/typescript/helpers/toggleCheckbox.helper.d.ts.map +1 -1
- package/lib/typescript/store/global.store.d.ts +24 -0
- package/lib/typescript/store/global.store.d.ts.map +1 -0
- package/lib/typescript/types/treeView.types.d.ts +0 -4
- package/lib/typescript/types/treeView.types.d.ts.map +1 -1
- package/package.json +9 -10
- package/src/TreeView.tsx +53 -62
- package/src/components/NodeList.tsx +102 -107
- package/src/helpers/expandCollapse.helper.ts +12 -12
- package/src/helpers/initNodeMap.helper.ts +15 -5
- package/src/helpers/selectAll.helper.ts +20 -13
- package/src/helpers/toggleCheckbox.helper.ts +36 -24
- package/src/store/global.store.ts +111 -0
- package/src/types/treeView.types.ts +0 -5
- package/lib/commonjs/signals/global.signals.js +0 -42
- package/lib/commonjs/signals/global.signals.js.map +0 -1
- package/lib/module/signals/global.signals.js +0 -26
- package/lib/module/signals/global.signals.js.map +0 -1
- package/lib/typescript/signals/global.signals.d.ts +0 -11
- package/lib/typescript/signals/global.signals.d.ts.map +0 -1
- package/src/signals/global.signals.ts +0 -36
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
2
|
import {
|
|
3
3
|
View,
|
|
4
4
|
StyleSheet,
|
|
@@ -6,13 +6,6 @@ import {
|
|
|
6
6
|
TouchableOpacity,
|
|
7
7
|
type TouchableOpacityProps,
|
|
8
8
|
} from "react-native";
|
|
9
|
-
import {
|
|
10
|
-
computed,
|
|
11
|
-
effect,
|
|
12
|
-
Signal,
|
|
13
|
-
useComputed,
|
|
14
|
-
useSignal
|
|
15
|
-
} from "@preact/signals-react";
|
|
16
9
|
import { FlashList } from "@shopify/flash-list";
|
|
17
10
|
|
|
18
11
|
import type {
|
|
@@ -26,14 +19,7 @@ import type {
|
|
|
26
19
|
__FlattenedTreeNode__,
|
|
27
20
|
} from "../types/treeView.types";
|
|
28
21
|
|
|
29
|
-
import {
|
|
30
|
-
expanded,
|
|
31
|
-
globalData,
|
|
32
|
-
innerMostChildrenIds,
|
|
33
|
-
searchKeys,
|
|
34
|
-
searchText,
|
|
35
|
-
state
|
|
36
|
-
} from "../signals/global.signals";
|
|
22
|
+
import { useStore } from "../store/global.store";
|
|
37
23
|
import {
|
|
38
24
|
doesNodeContainSearchTerm,
|
|
39
25
|
handleToggleExpand,
|
|
@@ -64,74 +50,80 @@ function _NodeList(props: NodeListProps) {
|
|
|
64
50
|
ExpandCollapseTouchableComponent,
|
|
65
51
|
} = props;
|
|
66
52
|
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
53
|
+
const {
|
|
54
|
+
expanded,
|
|
55
|
+
globalData,
|
|
56
|
+
updatedInnerMostChildrenIds,
|
|
57
|
+
searchKeys,
|
|
58
|
+
searchText
|
|
59
|
+
} = useStore();
|
|
60
|
+
|
|
61
|
+
const [filteredTree, setFilteredTree] = useState<TreeNode[]>([]);
|
|
62
|
+
const [flattenedFilteredNodes, setFlattenedFilteredNodes]
|
|
63
|
+
= useState<__FlattenedTreeNode__[]>([]);
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const searchTrimmed = searchText.trim().toLowerCase();
|
|
67
|
+
|
|
68
|
+
const filterTreeData = (_nodes: TreeNode[]): TreeNode[] => {
|
|
69
|
+
let filtered: TreeNode[] = [];
|
|
70
|
+
|
|
71
|
+
for (let node of _nodes) {
|
|
72
|
+
if (!searchTrimmed || doesNodeContainSearchTerm(node, searchTrimmed, searchKeys)) {
|
|
73
|
+
// If node itself matches, include it and all its descendants
|
|
74
|
+
filtered.push(node);
|
|
75
|
+
} else if (node.children) {
|
|
76
|
+
// If node does not match, check its children and include them if they match
|
|
77
|
+
const childMatches = filterTreeData(node.children);
|
|
78
|
+
if (childMatches.length > 0) {
|
|
79
|
+
// If any children match, include the node, replacing its children with the matching ones
|
|
80
|
+
filtered.push({ ...node, children: childMatches });
|
|
85
81
|
}
|
|
86
82
|
}
|
|
83
|
+
}
|
|
87
84
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return filterTreeData(globalData.value);
|
|
92
|
-
});
|
|
93
|
-
}, []);
|
|
94
|
-
|
|
95
|
-
const flattenedFilteredNodes = React.useMemo(() => {
|
|
96
|
-
return computed(() => {
|
|
97
|
-
const flattenTreeData = (
|
|
98
|
-
_nodes: TreeNode[],
|
|
99
|
-
level: number = 0,
|
|
100
|
-
): __FlattenedTreeNode__[] => {
|
|
101
|
-
let flattened: __FlattenedTreeNode__[] = [];
|
|
102
|
-
for (let node of _nodes) {
|
|
103
|
-
flattened.push({ ...node, level });
|
|
104
|
-
if (node.children && expanded.value.has(node.id)) {
|
|
105
|
-
flattened = [...flattened, ...flattenTreeData(node.children, level + 1)];
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return flattened;
|
|
109
|
-
};
|
|
85
|
+
return filtered;
|
|
86
|
+
};
|
|
110
87
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}, []);
|
|
88
|
+
const tempFilterTree = filterTreeData(globalData);
|
|
89
|
+
setFilteredTree(tempFilterTree);
|
|
90
|
+
}, [searchText, searchKeys, globalData]);
|
|
115
91
|
|
|
116
92
|
React.useEffect(() => {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
93
|
+
const flattenTreeData = (
|
|
94
|
+
_nodes: TreeNode[],
|
|
95
|
+
level: number = 0,
|
|
96
|
+
): __FlattenedTreeNode__[] => {
|
|
97
|
+
let flattened: __FlattenedTreeNode__[] = [];
|
|
98
|
+
for (let node of _nodes) {
|
|
99
|
+
flattened.push({ ...node, level });
|
|
100
|
+
if (node.children && expanded.has(node.id)) {
|
|
101
|
+
flattened = [...flattened, ...flattenTreeData(node.children, level + 1)];
|
|
126
102
|
}
|
|
127
|
-
}
|
|
103
|
+
}
|
|
104
|
+
return flattened;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const tempFlattenTreeData = flattenTreeData(filteredTree);
|
|
108
|
+
setFlattenedFilteredNodes(tempFlattenTreeData);
|
|
109
|
+
}, [filteredTree, expanded]);
|
|
110
|
+
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
const allLeafIds: string[] = [];
|
|
113
|
+
const getLeafNodes = (_nodes: TreeNode[]) => {
|
|
114
|
+
for (let node of _nodes) {
|
|
115
|
+
if (node.children) {
|
|
116
|
+
getLeafNodes(node.children);
|
|
117
|
+
} else {
|
|
118
|
+
allLeafIds.push(node.id);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
128
122
|
|
|
129
|
-
|
|
123
|
+
getLeafNodes(filteredTree);
|
|
130
124
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
134
|
-
}, []);
|
|
125
|
+
updatedInnerMostChildrenIds(allLeafIds);
|
|
126
|
+
}, [filteredTree, updatedInnerMostChildrenIds]);
|
|
135
127
|
|
|
136
128
|
const nodeRenderer = React.useCallback((
|
|
137
129
|
{ item }: { item: __FlattenedTreeNode__; }
|
|
@@ -140,6 +132,7 @@ function _NodeList(props: NodeListProps) {
|
|
|
140
132
|
<Node
|
|
141
133
|
node={item}
|
|
142
134
|
level={item.level || 0}
|
|
135
|
+
|
|
143
136
|
checkBoxViewStyleProps={checkBoxViewStyleProps}
|
|
144
137
|
|
|
145
138
|
CheckboxComponent={CheckboxComponent}
|
|
@@ -162,7 +155,7 @@ function _NodeList(props: NodeListProps) {
|
|
|
162
155
|
removeClippedSubviews={true}
|
|
163
156
|
keyboardShouldPersistTaps="handled"
|
|
164
157
|
drawDistance={50}
|
|
165
|
-
data={flattenedFilteredNodes
|
|
158
|
+
data={flattenedFilteredNodes}
|
|
166
159
|
renderItem={nodeRenderer}
|
|
167
160
|
keyExtractor={keyExtractor}
|
|
168
161
|
ListHeaderComponent={<HeaderFooterView />}
|
|
@@ -189,10 +182,23 @@ interface NodeProps {
|
|
|
189
182
|
ExpandCollapseTouchableComponent?: React.ComponentType<TouchableOpacityProps>;
|
|
190
183
|
}
|
|
191
184
|
|
|
185
|
+
function getValue(
|
|
186
|
+
isChecked: boolean,
|
|
187
|
+
isIndeterminate: boolean
|
|
188
|
+
): CheckboxValueType {
|
|
189
|
+
if (isIndeterminate) {
|
|
190
|
+
return 'indeterminate';
|
|
191
|
+
} else if (isChecked) {
|
|
192
|
+
return true;
|
|
193
|
+
} else {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
192
198
|
const Node = React.memo(_Node);
|
|
193
199
|
function _Node(props: NodeProps) {
|
|
194
200
|
const {
|
|
195
|
-
node
|
|
201
|
+
node,
|
|
196
202
|
level,
|
|
197
203
|
|
|
198
204
|
checkBoxViewStyleProps,
|
|
@@ -202,36 +208,25 @@ function _Node(props: NodeProps) {
|
|
|
202
208
|
ExpandCollapseTouchableComponent = TouchableOpacity,
|
|
203
209
|
} = props;
|
|
204
210
|
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (isIndeterminate.value) {
|
|
217
|
-
return 'indeterminate';
|
|
218
|
-
} else if (isChecked.value) {
|
|
219
|
-
return true;
|
|
220
|
-
} else {
|
|
221
|
-
return false;
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
const isExpanded = useComputed(() => expanded.value.has(node.value.id));
|
|
211
|
+
const { expanded, checked, indeterminate } = useStore();
|
|
212
|
+
|
|
213
|
+
const isChecked = checked.has(node.id);
|
|
214
|
+
const isIndeterminate = indeterminate.has(node.id);
|
|
215
|
+
const isExpanded = expanded.has(node.id);
|
|
216
|
+
|
|
217
|
+
const [value, setValue] = useState(getValue(isChecked, isIndeterminate));
|
|
218
|
+
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
setValue(getValue(isChecked, isIndeterminate));
|
|
221
|
+
}, [isChecked, isIndeterminate]);
|
|
225
222
|
|
|
226
223
|
const _onToggleExpand = React.useCallback(() => {
|
|
227
|
-
handleToggleExpand(node.
|
|
228
|
-
|
|
229
|
-
}, []);
|
|
224
|
+
handleToggleExpand(node.id);
|
|
225
|
+
}, [node.id]);
|
|
230
226
|
|
|
231
227
|
const _onCheck = React.useCallback(() => {
|
|
232
|
-
toggleCheckboxes([node.
|
|
233
|
-
|
|
234
|
-
}, []);
|
|
228
|
+
toggleCheckboxes([node.id]);
|
|
229
|
+
}, [node.id]);
|
|
235
230
|
|
|
236
231
|
return (
|
|
237
232
|
<View style={[
|
|
@@ -240,17 +235,17 @@ function _Node(props: NodeProps) {
|
|
|
240
235
|
]}>
|
|
241
236
|
<View style={styles.nodeCheckboxAndArrowRow}>
|
|
242
237
|
<CheckboxComponent
|
|
243
|
-
text={node.
|
|
238
|
+
text={node.name}
|
|
244
239
|
onValueChange={_onCheck}
|
|
245
|
-
value={value
|
|
240
|
+
value={value}
|
|
246
241
|
{...checkBoxViewStyleProps} />
|
|
247
242
|
|
|
248
|
-
{node.
|
|
243
|
+
{node.children?.length ? (
|
|
249
244
|
<ExpandCollapseTouchableComponent
|
|
250
245
|
style={styles.nodeExpandableArrowTouchable}
|
|
251
246
|
onPress={_onToggleExpand}>
|
|
252
247
|
<ExpandCollapseIconComponent
|
|
253
|
-
isExpanded={isExpanded
|
|
248
|
+
isExpanded={isExpanded}
|
|
254
249
|
/>
|
|
255
250
|
</ExpandCollapseTouchableComponent>
|
|
256
251
|
) : null}
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { TreeNode } from "../types/treeView.types";
|
|
2
|
-
import {
|
|
3
|
-
expanded,
|
|
4
|
-
globalData,
|
|
5
|
-
nodeMap
|
|
6
|
-
} from "../signals/global.signals";
|
|
2
|
+
import { useStore } from "../store/global.store";
|
|
7
3
|
|
|
8
4
|
/**
|
|
9
5
|
* Toggle the expanded state of a tree node by its ID.
|
|
@@ -14,8 +10,10 @@ import {
|
|
|
14
10
|
* @param id - The ID of the tree node to toggle.
|
|
15
11
|
*/
|
|
16
12
|
export function handleToggleExpand(id: string) {
|
|
13
|
+
const { globalData, expanded, updateExpanded } = useStore.getState();
|
|
14
|
+
|
|
17
15
|
// Create a new Set based on the current expanded state
|
|
18
|
-
const newExpanded = new Set(expanded
|
|
16
|
+
const newExpanded = new Set(expanded);
|
|
19
17
|
|
|
20
18
|
/**
|
|
21
19
|
* Recursively deletes a node and its descendants from the expanded set.
|
|
@@ -52,9 +50,9 @@ export function handleToggleExpand(id: string) {
|
|
|
52
50
|
}
|
|
53
51
|
|
|
54
52
|
// Find the node to expand or collapse
|
|
55
|
-
const node = findNode(globalData
|
|
53
|
+
const node = findNode(globalData);
|
|
56
54
|
|
|
57
|
-
if (expanded.
|
|
55
|
+
if (expanded.has(id)) {
|
|
58
56
|
// If the node is currently expanded, collapse it and its descendants
|
|
59
57
|
newExpanded.delete(id);
|
|
60
58
|
if (node) {
|
|
@@ -66,23 +64,25 @@ export function handleToggleExpand(id: string) {
|
|
|
66
64
|
}
|
|
67
65
|
|
|
68
66
|
// Set the new expanded state
|
|
69
|
-
|
|
67
|
+
updateExpanded(newExpanded);
|
|
70
68
|
};
|
|
71
69
|
|
|
72
70
|
/**
|
|
73
71
|
* Expand all nodes in the tree.
|
|
74
72
|
*/
|
|
75
73
|
export function expandAll() {
|
|
74
|
+
const { nodeMap, updateExpanded } = useStore.getState();
|
|
76
75
|
// Create a new Set containing the IDs of all nodes
|
|
77
|
-
const newExpanded = new Set(nodeMap.
|
|
78
|
-
|
|
76
|
+
const newExpanded = new Set(nodeMap.keys());
|
|
77
|
+
updateExpanded(newExpanded);
|
|
79
78
|
};
|
|
80
79
|
|
|
81
80
|
/**
|
|
82
81
|
* Collapse all nodes in the tree.
|
|
83
82
|
*/
|
|
84
83
|
export function collapseAll() {
|
|
84
|
+
const { updateExpanded } = useStore.getState();
|
|
85
85
|
// Create an empty Set
|
|
86
86
|
const newExpanded = new Set<string>();
|
|
87
|
-
|
|
87
|
+
updateExpanded(newExpanded);
|
|
88
88
|
};
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { TreeNode } from "../types/treeView.types";
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "../signals/global.signals";
|
|
3
|
+
useStore
|
|
4
|
+
} from "../store/global.store";
|
|
6
5
|
import { toggleCheckboxes } from "./toggleCheckbox.helper";
|
|
7
6
|
|
|
8
7
|
/**
|
|
@@ -18,6 +17,14 @@ export function initializeNodeMaps(
|
|
|
18
17
|
initialData: TreeNode[],
|
|
19
18
|
preselectedIds: string[] = [],
|
|
20
19
|
) {
|
|
20
|
+
const {
|
|
21
|
+
updateNodeMap,
|
|
22
|
+
updateChildToParentMap
|
|
23
|
+
} = useStore.getState();
|
|
24
|
+
|
|
25
|
+
const tempNodeMap: Map<string, TreeNode> = new Map();;
|
|
26
|
+
const tempChildToParentMap: Map<string, string> = new Map();;
|
|
27
|
+
|
|
21
28
|
/**
|
|
22
29
|
* Recursively processes nodes, adding them to the nodeMap and childToParentMap.
|
|
23
30
|
*
|
|
@@ -30,9 +37,9 @@ export function initializeNodeMaps(
|
|
|
30
37
|
) => {
|
|
31
38
|
nodes.forEach((node) => {
|
|
32
39
|
// Each node is added to the nodeMap with its ID as the key
|
|
33
|
-
|
|
40
|
+
tempNodeMap.set(node.id, node);
|
|
34
41
|
// If the node has a parent, its ID is mapped to the parent's ID in the childToParentMap
|
|
35
|
-
if (parentId)
|
|
42
|
+
if (parentId) tempChildToParentMap.set(node.id, parentId);
|
|
36
43
|
// If the node has children, recursively process them
|
|
37
44
|
if (node.children) processNodes(node.children, node.id);
|
|
38
45
|
});
|
|
@@ -41,6 +48,9 @@ export function initializeNodeMaps(
|
|
|
41
48
|
// Begin processing with the initial tree data
|
|
42
49
|
processNodes(initialData);
|
|
43
50
|
|
|
51
|
+
updateNodeMap(tempNodeMap);
|
|
52
|
+
updateChildToParentMap(tempChildToParentMap);
|
|
53
|
+
|
|
44
54
|
// Check any preselected nodes
|
|
45
55
|
toggleCheckboxes(preselectedIds, true);
|
|
46
56
|
}
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
innerMostChildrenIds,
|
|
3
|
-
nodeMap,
|
|
4
|
-
searchText,
|
|
5
|
-
state
|
|
6
|
-
} from "../signals/global.signals";
|
|
1
|
+
import { useStore } from "../store/global.store";
|
|
7
2
|
import { toggleCheckboxes } from "./toggleCheckbox.helper";
|
|
8
3
|
|
|
9
4
|
/**
|
|
@@ -12,12 +7,15 @@ import { toggleCheckboxes } from "./toggleCheckbox.helper";
|
|
|
12
7
|
* If there is no search text, then it selects all nodes; otherwise, it selects all visible nodes.
|
|
13
8
|
*/
|
|
14
9
|
export function selectAllFiltered() {
|
|
10
|
+
const { searchText, innerMostChildrenIds } = useStore.getState();
|
|
11
|
+
|
|
12
|
+
|
|
15
13
|
// If there's no search text, select all nodes
|
|
16
|
-
if (!searchText
|
|
14
|
+
if (!searchText) {
|
|
17
15
|
selectAll();
|
|
18
16
|
} else {
|
|
19
17
|
// If there's search text, only select the visible nodes
|
|
20
|
-
toggleCheckboxes(innerMostChildrenIds
|
|
18
|
+
toggleCheckboxes(innerMostChildrenIds, true);
|
|
21
19
|
}
|
|
22
20
|
};
|
|
23
21
|
|
|
@@ -27,12 +25,14 @@ export function selectAllFiltered() {
|
|
|
27
25
|
* If there is no search text, then it unselects all nodes; otherwise, it unselects all visible nodes.
|
|
28
26
|
*/
|
|
29
27
|
export function unselectAllFiltered() {
|
|
28
|
+
const { searchText, innerMostChildrenIds } = useStore.getState();
|
|
29
|
+
|
|
30
30
|
// If there's no search text, unselect all nodes
|
|
31
|
-
if (!searchText
|
|
31
|
+
if (!searchText) {
|
|
32
32
|
unselectAll();
|
|
33
33
|
} else {
|
|
34
34
|
// If there's search text, only unselect the visible nodes
|
|
35
|
-
toggleCheckboxes(innerMostChildrenIds
|
|
35
|
+
toggleCheckboxes(innerMostChildrenIds, false);
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
38
|
|
|
@@ -42,10 +42,14 @@ export function unselectAllFiltered() {
|
|
|
42
42
|
* This function selects all nodes by adding all node ids to the checked set and clearing the indeterminate set.
|
|
43
43
|
*/
|
|
44
44
|
export function selectAll() {
|
|
45
|
+
const { nodeMap, updateChecked, updateIndeterminate } = useStore.getState();
|
|
46
|
+
|
|
45
47
|
// Create a new set containing the ids of all nodes
|
|
46
|
-
const newChecked = new Set(nodeMap.
|
|
48
|
+
const newChecked = new Set(nodeMap.keys());
|
|
47
49
|
// Update the state to mark all nodes as checked
|
|
48
|
-
|
|
50
|
+
|
|
51
|
+
updateChecked(newChecked);
|
|
52
|
+
updateIndeterminate(new Set());
|
|
49
53
|
};
|
|
50
54
|
|
|
51
55
|
/**
|
|
@@ -54,6 +58,9 @@ export function selectAll() {
|
|
|
54
58
|
* This function unselects all nodes by clearing both the checked and indeterminate sets.
|
|
55
59
|
*/
|
|
56
60
|
export function unselectAll() {
|
|
61
|
+
const { updateChecked, updateIndeterminate } = useStore.getState();
|
|
57
62
|
// Update the state to mark all nodes as unchecked
|
|
58
|
-
|
|
63
|
+
|
|
64
|
+
updateChecked(new Set());
|
|
65
|
+
updateIndeterminate(new Set());
|
|
59
66
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useStore } from "../store/global.store";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Function to toggle checkbox state for a tree structure.
|
|
@@ -8,9 +8,20 @@ import { childToParentMap, nodeMap, state } from "../signals/global.signals";
|
|
|
8
8
|
* If not provided, the check state will be toggled based on the current state.
|
|
9
9
|
*/
|
|
10
10
|
export function toggleCheckboxes(ids: string[], forceCheck?: boolean) {
|
|
11
|
+
const {
|
|
12
|
+
checked,
|
|
13
|
+
updateChecked,
|
|
14
|
+
|
|
15
|
+
indeterminate,
|
|
16
|
+
updateIndeterminate,
|
|
17
|
+
|
|
18
|
+
nodeMap,
|
|
19
|
+
childToParentMap
|
|
20
|
+
} = useStore.getState();
|
|
21
|
+
|
|
11
22
|
// Create new sets for checked and indeterminate state so as not to mutate the original state.
|
|
12
|
-
const
|
|
13
|
-
const
|
|
23
|
+
const tempChecked = new Set(checked);
|
|
24
|
+
const tempIndeterminate = new Set(indeterminate);
|
|
14
25
|
|
|
15
26
|
// Maps for memoization of the recursive functions areAllDescendantsChecked and areAnyDescendantsChecked.
|
|
16
27
|
const memoAllDescendantsChecked = new Map();
|
|
@@ -24,16 +35,16 @@ export function toggleCheckboxes(ids: string[], forceCheck?: boolean) {
|
|
|
24
35
|
const toggleNodeAndChildren = (nodeId: string, isChecked: boolean) => {
|
|
25
36
|
// Set or unset this node in the checked set, and remove it from the indeterminate set.
|
|
26
37
|
if (isChecked) {
|
|
27
|
-
|
|
28
|
-
|
|
38
|
+
tempChecked.add(nodeId);
|
|
39
|
+
tempIndeterminate.delete(nodeId);
|
|
29
40
|
} else {
|
|
30
|
-
|
|
41
|
+
tempChecked.delete(nodeId);
|
|
31
42
|
}
|
|
32
43
|
|
|
33
44
|
// Get the node from the node map and recursively apply the same state to all its children.
|
|
34
|
-
const node = nodeMap.
|
|
45
|
+
const node = nodeMap.get(nodeId);
|
|
35
46
|
node?.children?.forEach((childNode) => {
|
|
36
|
-
if (isChecked)
|
|
47
|
+
if (isChecked) tempIndeterminate.delete(childNode.id);
|
|
37
48
|
toggleNodeAndChildren(childNode.id, isChecked);
|
|
38
49
|
});
|
|
39
50
|
};
|
|
@@ -50,7 +61,7 @@ export function toggleCheckboxes(ids: string[], forceCheck?: boolean) {
|
|
|
50
61
|
return memoAllDescendantsChecked.get(nodeId);
|
|
51
62
|
}
|
|
52
63
|
|
|
53
|
-
const node = nodeMap.
|
|
64
|
+
const node = nodeMap.get(nodeId);
|
|
54
65
|
let allChecked = true;
|
|
55
66
|
if (node?.children) {
|
|
56
67
|
// If the node has children, recursively check all children.
|
|
@@ -59,7 +70,7 @@ export function toggleCheckboxes(ids: string[], forceCheck?: boolean) {
|
|
|
59
70
|
}
|
|
60
71
|
} else {
|
|
61
72
|
// If the node has no children, its state is equal to whether it is in the checked set.
|
|
62
|
-
allChecked =
|
|
73
|
+
allChecked = tempChecked.has(nodeId);
|
|
63
74
|
}
|
|
64
75
|
|
|
65
76
|
// Store the result in the map and return it.
|
|
@@ -79,7 +90,7 @@ export function toggleCheckboxes(ids: string[], forceCheck?: boolean) {
|
|
|
79
90
|
return memoAnyDescendantsChecked.get(nodeId);
|
|
80
91
|
}
|
|
81
92
|
|
|
82
|
-
const node = nodeMap.
|
|
93
|
+
const node = nodeMap.get(nodeId);
|
|
83
94
|
let anyChecked = false;
|
|
84
95
|
if (node?.children) {
|
|
85
96
|
// If the node has children, recursively check all children.
|
|
@@ -88,7 +99,7 @@ export function toggleCheckboxes(ids: string[], forceCheck?: boolean) {
|
|
|
88
99
|
}
|
|
89
100
|
} else {
|
|
90
101
|
// If the node has no children, its state is equal to whether it is in the checked set.
|
|
91
|
-
anyChecked =
|
|
102
|
+
anyChecked = tempChecked.has(nodeId);
|
|
92
103
|
}
|
|
93
104
|
|
|
94
105
|
// Store the result in the map and return it.
|
|
@@ -101,32 +112,32 @@ export function toggleCheckboxes(ids: string[], forceCheck?: boolean) {
|
|
|
101
112
|
* @param {string} nodeId - The id of the node to be updated.
|
|
102
113
|
*/
|
|
103
114
|
const updateNodeAndAncestorsState = (nodeId: string) => {
|
|
104
|
-
const node = nodeMap.
|
|
115
|
+
const node = nodeMap.get(nodeId);
|
|
105
116
|
const hasOnlyOneChild = node?.children && node.children.length === 1;
|
|
106
117
|
|
|
107
118
|
// Update the node's state based on the state of its descendants.
|
|
108
119
|
if (areAllDescendantsChecked(nodeId)) {
|
|
109
|
-
|
|
110
|
-
|
|
120
|
+
tempChecked.add(nodeId);
|
|
121
|
+
tempIndeterminate.delete(nodeId);
|
|
111
122
|
} else if (areAnyDescendantsChecked(nodeId)) {
|
|
112
123
|
if (hasOnlyOneChild) {
|
|
113
124
|
// If a node has only one child and it's not checked,
|
|
114
125
|
// remove this node from both checked and indeterminate sets.
|
|
115
|
-
|
|
116
|
-
|
|
126
|
+
tempChecked.delete(nodeId);
|
|
127
|
+
tempIndeterminate.delete(nodeId);
|
|
117
128
|
} else {
|
|
118
|
-
|
|
119
|
-
|
|
129
|
+
tempChecked.delete(nodeId);
|
|
130
|
+
tempIndeterminate.add(nodeId);
|
|
120
131
|
}
|
|
121
132
|
} else {
|
|
122
|
-
|
|
123
|
-
|
|
133
|
+
tempChecked.delete(nodeId);
|
|
134
|
+
tempIndeterminate.delete(nodeId);
|
|
124
135
|
}
|
|
125
136
|
};
|
|
126
137
|
|
|
127
138
|
// Toggle the clicked nodes and their children.
|
|
128
139
|
ids.forEach((id) => {
|
|
129
|
-
const isChecked =
|
|
140
|
+
const isChecked = tempChecked.has(id);
|
|
130
141
|
toggleNodeAndChildren(id, forceCheck === undefined ? !isChecked : forceCheck);
|
|
131
142
|
});
|
|
132
143
|
|
|
@@ -135,10 +146,11 @@ export function toggleCheckboxes(ids: string[], forceCheck?: boolean) {
|
|
|
135
146
|
let currentNodeId: string | undefined = id;
|
|
136
147
|
while (currentNodeId) {
|
|
137
148
|
updateNodeAndAncestorsState(currentNodeId);
|
|
138
|
-
currentNodeId = childToParentMap.
|
|
149
|
+
currentNodeId = childToParentMap.get(currentNodeId);
|
|
139
150
|
}
|
|
140
151
|
});
|
|
141
152
|
|
|
142
153
|
// Update the state object with the new checked and indeterminate sets.
|
|
143
|
-
|
|
154
|
+
updateChecked(tempChecked);
|
|
155
|
+
updateIndeterminate(tempIndeterminate);
|
|
144
156
|
};
|