react-native-tree-multi-select 1.4.1 → 1.5.0-beta.1
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 +12 -1
- package/lib/commonjs/TreeView.js +4 -0
- package/lib/commonjs/TreeView.js.map +1 -1
- package/lib/commonjs/components/NodeList.js +1 -1
- package/lib/commonjs/helpers/flattenTree.helper.js +2 -2
- package/lib/commonjs/helpers/toggleCheckbox.helper.js +80 -71
- package/lib/commonjs/helpers/toggleCheckbox.helper.js.map +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/store/treeView.store.js +16 -1
- package/lib/commonjs/store/treeView.store.js.map +1 -1
- package/lib/module/TreeView.js +4 -0
- package/lib/module/TreeView.js.map +1 -1
- package/lib/module/components/NodeList.js +1 -1
- package/lib/module/helpers/flattenTree.helper.js +2 -2
- package/lib/module/helpers/toggleCheckbox.helper.js +80 -71
- package/lib/module/helpers/toggleCheckbox.helper.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/store/treeView.store.js +16 -1
- package/lib/module/store/treeView.store.js.map +1 -1
- package/lib/typescript/TreeView.d.ts.map +1 -1
- package/lib/typescript/helpers/flattenTree.helper.d.ts +1 -1
- package/lib/typescript/helpers/toggleCheckbox.helper.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/store/treeView.store.d.ts +3 -1
- package/lib/typescript/store/treeView.store.d.ts.map +1 -1
- package/lib/typescript/types/treeView.types.d.ts +5 -0
- package/lib/typescript/types/treeView.types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/TreeView.tsx +9 -0
- package/src/components/NodeList.tsx +1 -1
- package/src/helpers/flattenTree.helper.ts +2 -2
- package/src/helpers/toggleCheckbox.helper.ts +82 -74
- package/src/index.tsx +4 -2
- package/src/store/treeView.store.ts +18 -1
- package/src/types/treeView.types.ts +7 -0
|
@@ -16,38 +16,19 @@ export function toggleCheckboxes(ids: string[], forceCheck?: boolean) {
|
|
|
16
16
|
updateIndeterminate,
|
|
17
17
|
|
|
18
18
|
nodeMap,
|
|
19
|
-
childToParentMap
|
|
19
|
+
childToParentMap,
|
|
20
|
+
selectionPropagationBehavior
|
|
20
21
|
} = useTreeViewStore.getState();
|
|
21
22
|
|
|
23
|
+
const { toChildren, toParents } = selectionPropagationBehavior;
|
|
24
|
+
|
|
22
25
|
// Create new sets for checked and indeterminate state so as not to mutate the original state.
|
|
23
26
|
const tempChecked = new Set(checked);
|
|
24
27
|
const tempIndeterminate = new Set(indeterminate);
|
|
25
28
|
|
|
26
29
|
// Maps for memoization of the recursive functions areAllDescendantsChecked and areAnyDescendantsChecked.
|
|
27
|
-
const memoAllDescendantsChecked = new Map();
|
|
28
|
-
const memoAnyDescendantsChecked = new Map();
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Recursive function to check/uncheck a node and all its children.
|
|
32
|
-
* @param {string} nodeId - The id of the node to be checked or unchecked.
|
|
33
|
-
* @param {boolean} isChecked - Whether the node should be checked or unchecked.
|
|
34
|
-
*/
|
|
35
|
-
const toggleNodeAndChildren = (nodeId: string, isChecked: boolean) => {
|
|
36
|
-
// Set or unset this node in the checked set, and remove it from the indeterminate set.
|
|
37
|
-
if (isChecked) {
|
|
38
|
-
tempChecked.add(nodeId);
|
|
39
|
-
tempIndeterminate.delete(nodeId);
|
|
40
|
-
} else {
|
|
41
|
-
tempChecked.delete(nodeId);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Get the node from the node map and recursively apply the same state to all its children.
|
|
45
|
-
const node = nodeMap.get(nodeId);
|
|
46
|
-
node?.children?.forEach((childNode) => {
|
|
47
|
-
if (isChecked) tempIndeterminate.delete(childNode.id);
|
|
48
|
-
toggleNodeAndChildren(childNode.id, isChecked);
|
|
49
|
-
});
|
|
50
|
-
};
|
|
30
|
+
const memoAllDescendantsChecked = new Map<string, boolean>();
|
|
31
|
+
const memoAnyDescendantsChecked = new Map<string, boolean>();
|
|
51
32
|
|
|
52
33
|
/**
|
|
53
34
|
* Recursive function to check if all descendants of a node are checked.
|
|
@@ -58,7 +39,7 @@ export function toggleCheckboxes(ids: string[], forceCheck?: boolean) {
|
|
|
58
39
|
const areAllDescendantsChecked = (nodeId: string): boolean => {
|
|
59
40
|
// If the result for this node is already in the map, return it.
|
|
60
41
|
if (memoAllDescendantsChecked.has(nodeId)) {
|
|
61
|
-
return memoAllDescendantsChecked.get(nodeId)
|
|
42
|
+
return memoAllDescendantsChecked.get(nodeId)!;
|
|
62
43
|
}
|
|
63
44
|
|
|
64
45
|
const node = nodeMap.get(nodeId);
|
|
@@ -79,23 +60,26 @@ export function toggleCheckboxes(ids: string[], forceCheck?: boolean) {
|
|
|
79
60
|
};
|
|
80
61
|
|
|
81
62
|
/**
|
|
82
|
-
*
|
|
83
|
-
* It uses memoization to avoid redundant calculations.
|
|
63
|
+
* Updated function to check if any descendants of a node are checked.
|
|
64
|
+
* It uses memoization to avoid redundant calculations and avoids unnecessarily deep recursion.
|
|
84
65
|
* @param {string} nodeId - The id of the node to be checked.
|
|
85
66
|
* @returns {boolean} - Whether any descendants of the node are checked.
|
|
86
67
|
*/
|
|
87
68
|
const areAnyDescendantsChecked = (nodeId: string): boolean => {
|
|
88
69
|
// If the result for this node is already in the map, return it.
|
|
89
70
|
if (memoAnyDescendantsChecked.has(nodeId)) {
|
|
90
|
-
return memoAnyDescendantsChecked.get(nodeId)
|
|
71
|
+
return memoAnyDescendantsChecked.get(nodeId)!;
|
|
91
72
|
}
|
|
92
73
|
|
|
93
74
|
const node = nodeMap.get(nodeId);
|
|
94
75
|
let anyChecked = false;
|
|
95
76
|
if (node?.children) {
|
|
96
|
-
//
|
|
77
|
+
// Check if any direct child is checked, without requiring all descendants.
|
|
97
78
|
for (const childNode of node.children) {
|
|
98
|
-
|
|
79
|
+
if (tempChecked.has(childNode.id) || areAnyDescendantsChecked(childNode.id)) {
|
|
80
|
+
anyChecked = true;
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
99
83
|
}
|
|
100
84
|
} else {
|
|
101
85
|
// If the node has no children, its state is equal to whether it is in the checked set.
|
|
@@ -107,58 +91,82 @@ export function toggleCheckboxes(ids: string[], forceCheck?: boolean) {
|
|
|
107
91
|
return anyChecked;
|
|
108
92
|
};
|
|
109
93
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const updateNodeAndAncestorsState = (nodeId: string) => {
|
|
115
|
-
const node = nodeMap.get(nodeId);
|
|
94
|
+
// Toggle the clicked nodes and their children.
|
|
95
|
+
ids.forEach((id) => {
|
|
96
|
+
const isChecked = tempChecked.has(id);
|
|
97
|
+
const newCheckedState = forceCheck === undefined ? !isChecked : forceCheck;
|
|
116
98
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
// Condition to check if all direct children and all descendants are checked.
|
|
123
|
-
|
|
124
|
-
/*
|
|
125
|
-
istanbul ignore next
|
|
126
|
-
|
|
127
|
-
NOTE: Below 2 lines in the condition are not covered in unit test
|
|
128
|
-
This condition will only be true if for some reason areAllDescendantsChecked(nodeId)
|
|
129
|
-
is false, while node?.children && node.children.every(childNode => areAllDescendantsChecked(childNode.id))
|
|
130
|
-
is true. Given the current logic of areAllDescendantsChecked,
|
|
131
|
-
this scenario is very unlikely to occur.
|
|
132
|
-
*/
|
|
133
|
-
if (node?.children && node.children.every(childNode => areAllDescendantsChecked(childNode.id))) {
|
|
134
|
-
// If a node's all direct children and all descendants are checked,
|
|
135
|
-
// remove this node from both checked and indeterminate sets.
|
|
136
|
-
tempChecked.delete(nodeId);
|
|
137
|
-
tempIndeterminate.delete(nodeId);
|
|
138
|
-
} else {
|
|
139
|
-
tempChecked.delete(nodeId);
|
|
140
|
-
tempIndeterminate.add(nodeId);
|
|
99
|
+
if (newCheckedState) {
|
|
100
|
+
tempChecked.add(id);
|
|
101
|
+
tempIndeterminate.delete(id);
|
|
102
|
+
if (toChildren) {
|
|
103
|
+
recursivelyUpdateChildren(id, true);
|
|
141
104
|
}
|
|
142
105
|
} else {
|
|
143
|
-
tempChecked.delete(
|
|
144
|
-
tempIndeterminate.delete(
|
|
106
|
+
tempChecked.delete(id);
|
|
107
|
+
tempIndeterminate.delete(id);
|
|
108
|
+
if (toChildren) {
|
|
109
|
+
recursivelyUpdateChildren(id, false);
|
|
110
|
+
}
|
|
145
111
|
}
|
|
146
|
-
};
|
|
147
112
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
113
|
+
// Skip updating parent nodes if toParents is false
|
|
114
|
+
if (toParents) {
|
|
115
|
+
updateParentNodes(id);
|
|
116
|
+
}
|
|
152
117
|
});
|
|
153
118
|
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
|
|
119
|
+
// Function to recursively update children nodes as per childrenChecked value
|
|
120
|
+
function recursivelyUpdateChildren(nodeId: string, childrenChecked: boolean) {
|
|
121
|
+
const node = nodeMap.get(nodeId);
|
|
122
|
+
if (node && node.children) {
|
|
123
|
+
node.children.forEach((childNode) => {
|
|
124
|
+
if (childrenChecked) {
|
|
125
|
+
tempChecked.add(childNode.id);
|
|
126
|
+
tempIndeterminate.delete(childNode.id);
|
|
127
|
+
} else {
|
|
128
|
+
tempChecked.delete(childNode.id);
|
|
129
|
+
tempIndeterminate.delete(childNode.id);
|
|
130
|
+
}
|
|
131
|
+
recursivelyUpdateChildren(childNode.id, childrenChecked);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Function to update parent nodes
|
|
137
|
+
function updateParentNodes(nodeId: string) {
|
|
138
|
+
let currentNodeId: string | undefined = nodeId;
|
|
157
139
|
while (currentNodeId) {
|
|
158
|
-
|
|
159
|
-
|
|
140
|
+
const parentNodeId = childToParentMap.get(currentNodeId);
|
|
141
|
+
if (parentNodeId) {
|
|
142
|
+
if (tempChecked.has(parentNodeId)) {
|
|
143
|
+
// If the parent node is currently checked, but not all child nodes are checked,
|
|
144
|
+
// move the parent node to an indeterminate state
|
|
145
|
+
if (!areAllDescendantsChecked(parentNodeId)) {
|
|
146
|
+
tempChecked.delete(parentNodeId);
|
|
147
|
+
tempIndeterminate.add(parentNodeId);
|
|
148
|
+
}
|
|
149
|
+
} else if (tempIndeterminate.has(parentNodeId)) {
|
|
150
|
+
// If the parent node is currently in an indeterminate state,
|
|
151
|
+
// then check if all descendants are checked
|
|
152
|
+
if (areAllDescendantsChecked(parentNodeId)) {
|
|
153
|
+
tempIndeterminate.delete(parentNodeId);
|
|
154
|
+
tempChecked.add(parentNodeId);
|
|
155
|
+
} else if (!areAnyDescendantsChecked(parentNodeId)) {
|
|
156
|
+
// If no descendants are checked, remove from indeterminate set
|
|
157
|
+
tempIndeterminate.delete(parentNodeId);
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
// If the parent node is not checked or indeterminate,
|
|
161
|
+
// check if any descendants are checked and update appropriately
|
|
162
|
+
if (areAnyDescendantsChecked(parentNodeId)) {
|
|
163
|
+
tempIndeterminate.add(parentNodeId);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
currentNodeId = parentNodeId;
|
|
160
168
|
}
|
|
161
|
-
}
|
|
169
|
+
}
|
|
162
170
|
|
|
163
171
|
// Update the state object with the new checked and indeterminate sets.
|
|
164
172
|
updateChecked(tempChecked);
|
package/src/index.tsx
CHANGED
|
@@ -7,7 +7,8 @@ import type {
|
|
|
7
7
|
ExpandIconProps,
|
|
8
8
|
CheckBoxViewProps,
|
|
9
9
|
CheckboxValueType,
|
|
10
|
-
BuiltInCheckBoxViewStyleProps
|
|
10
|
+
BuiltInCheckBoxViewStyleProps,
|
|
11
|
+
SelectionPropagationBehavior
|
|
11
12
|
} from "./types/treeView.types";
|
|
12
13
|
|
|
13
14
|
export * from "./TreeView";
|
|
@@ -22,5 +23,6 @@ export type {
|
|
|
22
23
|
ExpandIconProps,
|
|
23
24
|
CheckBoxViewProps,
|
|
24
25
|
CheckboxValueType,
|
|
25
|
-
BuiltInCheckBoxViewStyleProps
|
|
26
|
+
BuiltInCheckBoxViewStyleProps,
|
|
27
|
+
SelectionPropagationBehavior
|
|
26
28
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TreeNode } from "src/types/treeView.types";
|
|
1
|
+
import type { SelectionPropagationBehavior, TreeNode } from "src/types/treeView.types";
|
|
2
2
|
|
|
3
3
|
import { create } from 'zustand';
|
|
4
4
|
|
|
@@ -39,6 +39,11 @@ export type TreeViewState = {
|
|
|
39
39
|
innerMostChildrenIds: string[];
|
|
40
40
|
updateInnerMostChildrenIds: (innerMostChildrenIds: string[]) => void;
|
|
41
41
|
|
|
42
|
+
selectionPropagationBehavior: SelectionPropagationBehavior;
|
|
43
|
+
setSelectionPropagationBehavior: (
|
|
44
|
+
selectionPropagationBehavior: SelectionPropagationBehavior
|
|
45
|
+
) => void;
|
|
46
|
+
|
|
42
47
|
// Cleanup all states in this store
|
|
43
48
|
cleanUpTreeViewStore: () => void;
|
|
44
49
|
};
|
|
@@ -73,6 +78,17 @@ export const useTreeViewStore = create<TreeViewState>((set) => ({
|
|
|
73
78
|
innerMostChildrenIds: [],
|
|
74
79
|
updateInnerMostChildrenIds: (innerMostChildrenIds: string[]) => set({ innerMostChildrenIds }),
|
|
75
80
|
|
|
81
|
+
selectionPropagationBehavior: { toChildren: true, toParents: true },
|
|
82
|
+
setSelectionPropagationBehavior: (selectionPropagationBehavior) => set(
|
|
83
|
+
{
|
|
84
|
+
selectionPropagationBehavior: {
|
|
85
|
+
// Default selection propagation for parent and children to true if not mentioned
|
|
86
|
+
toChildren: selectionPropagationBehavior.toChildren ?? true,
|
|
87
|
+
toParents: selectionPropagationBehavior.toParents ?? true
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
),
|
|
91
|
+
|
|
76
92
|
cleanUpTreeViewStore: () =>
|
|
77
93
|
set({
|
|
78
94
|
checked: new Set(),
|
|
@@ -84,5 +100,6 @@ export const useTreeViewStore = create<TreeViewState>((set) => ({
|
|
|
84
100
|
searchText: "",
|
|
85
101
|
searchKeys: [""],
|
|
86
102
|
innerMostChildrenIds: [],
|
|
103
|
+
selectionPropagationBehavior: { toChildren: true, toParents: true },
|
|
87
104
|
}),
|
|
88
105
|
}));
|
|
@@ -74,6 +74,8 @@ export interface TreeViewProps extends NodeListProps {
|
|
|
74
74
|
preselectedIds?: string[];
|
|
75
75
|
|
|
76
76
|
preExpandedIds?: string[];
|
|
77
|
+
|
|
78
|
+
selectionPropagationBehavior?: SelectionPropagationBehavior;
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
type CheckboxProps = Omit<RNPaperCheckboxAndroidProps, "onPress" | "status">;
|
|
@@ -116,4 +118,9 @@ export interface TreeViewRef {
|
|
|
116
118
|
unselectNodes: (ids: string[]) => void;
|
|
117
119
|
|
|
118
120
|
setSearchText: (searchText: string, searchKeys?: string[]) => void;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface SelectionPropagationBehavior {
|
|
124
|
+
toChildren?: boolean;
|
|
125
|
+
toParents?: boolean;
|
|
119
126
|
}
|