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.
Files changed (36) hide show
  1. package/README.md +12 -1
  2. package/lib/commonjs/TreeView.js +4 -0
  3. package/lib/commonjs/TreeView.js.map +1 -1
  4. package/lib/commonjs/components/NodeList.js +1 -1
  5. package/lib/commonjs/helpers/flattenTree.helper.js +2 -2
  6. package/lib/commonjs/helpers/toggleCheckbox.helper.js +80 -71
  7. package/lib/commonjs/helpers/toggleCheckbox.helper.js.map +1 -1
  8. package/lib/commonjs/index.js.map +1 -1
  9. package/lib/commonjs/store/treeView.store.js +16 -1
  10. package/lib/commonjs/store/treeView.store.js.map +1 -1
  11. package/lib/module/TreeView.js +4 -0
  12. package/lib/module/TreeView.js.map +1 -1
  13. package/lib/module/components/NodeList.js +1 -1
  14. package/lib/module/helpers/flattenTree.helper.js +2 -2
  15. package/lib/module/helpers/toggleCheckbox.helper.js +80 -71
  16. package/lib/module/helpers/toggleCheckbox.helper.js.map +1 -1
  17. package/lib/module/index.js.map +1 -1
  18. package/lib/module/store/treeView.store.js +16 -1
  19. package/lib/module/store/treeView.store.js.map +1 -1
  20. package/lib/typescript/TreeView.d.ts.map +1 -1
  21. package/lib/typescript/helpers/flattenTree.helper.d.ts +1 -1
  22. package/lib/typescript/helpers/toggleCheckbox.helper.d.ts.map +1 -1
  23. package/lib/typescript/index.d.ts +2 -2
  24. package/lib/typescript/index.d.ts.map +1 -1
  25. package/lib/typescript/store/treeView.store.d.ts +3 -1
  26. package/lib/typescript/store/treeView.store.d.ts.map +1 -1
  27. package/lib/typescript/types/treeView.types.d.ts +5 -0
  28. package/lib/typescript/types/treeView.types.d.ts.map +1 -1
  29. package/package.json +1 -1
  30. package/src/TreeView.tsx +9 -0
  31. package/src/components/NodeList.tsx +1 -1
  32. package/src/helpers/flattenTree.helper.ts +2 -2
  33. package/src/helpers/toggleCheckbox.helper.ts +82 -74
  34. package/src/index.tsx +4 -2
  35. package/src/store/treeView.store.ts +18 -1
  36. 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
- * Recursive function to check if any descendants of a node are checked.
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
- // If the node has children, recursively check all children.
77
+ // Check if any direct child is checked, without requiring all descendants.
97
78
  for (const childNode of node.children) {
98
- anyChecked = anyChecked || areAnyDescendantsChecked(childNode.id);
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
- * Function to update the indeterminate and checked state of a node and its ancestors.
112
- * @param {string} nodeId - The id of the node to be updated.
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
- // Update the node's state based on the state of its descendants.
118
- if (areAllDescendantsChecked(nodeId)) {
119
- tempChecked.add(nodeId);
120
- tempIndeterminate.delete(nodeId);
121
- } else if (areAnyDescendantsChecked(nodeId)) {
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(nodeId);
144
- tempIndeterminate.delete(nodeId);
106
+ tempChecked.delete(id);
107
+ tempIndeterminate.delete(id);
108
+ if (toChildren) {
109
+ recursivelyUpdateChildren(id, false);
110
+ }
145
111
  }
146
- };
147
112
 
148
- // Toggle the clicked nodes and their children.
149
- ids.forEach((id) => {
150
- const isChecked = tempChecked.has(id);
151
- toggleNodeAndChildren(id, forceCheck === undefined ? !isChecked : forceCheck);
113
+ // Skip updating parent nodes if toParents is false
114
+ if (toParents) {
115
+ updateParentNodes(id);
116
+ }
152
117
  });
153
118
 
154
- // Update the state of all affected nodes.
155
- ids.forEach((id) => {
156
- let currentNodeId: string | undefined = id;
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
- updateNodeAndAncestorsState(currentNodeId);
159
- currentNodeId = childToParentMap.get(currentNodeId);
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
  }