react-native-tree-multi-select 3.0.0-beta.2 → 3.0.0-beta.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 +85 -25
- package/lib/module/TreeView.js +36 -31
- package/lib/module/TreeView.js.map +1 -1
- package/lib/module/components/CheckboxView.js +8 -4
- package/lib/module/components/CheckboxView.js.map +1 -1
- package/lib/module/components/CustomExpandCollapseIcon.js +2 -2
- package/lib/module/components/CustomExpandCollapseIcon.js.map +1 -1
- package/lib/module/components/DragOverlay.js +17 -5
- package/lib/module/components/DragOverlay.js.map +1 -1
- package/lib/module/components/DropIndicator.js +2 -2
- package/lib/module/components/DropIndicator.js.map +1 -1
- package/lib/module/components/NodeList.js +80 -56
- package/lib/module/components/NodeList.js.map +1 -1
- package/lib/module/constants/treeView.constants.js +3 -0
- package/lib/module/constants/treeView.constants.js.map +1 -1
- package/lib/module/helpers/expandCollapse.helper.js.map +1 -1
- package/lib/module/helpers/moveTreeNode.helper.js +30 -0
- package/lib/module/helpers/moveTreeNode.helper.js.map +1 -1
- package/lib/module/helpers/selectAll.helper.js.map +1 -1
- package/lib/module/helpers/toggleCheckbox.helper.js +44 -61
- package/lib/module/helpers/toggleCheckbox.helper.js.map +1 -1
- package/lib/module/hooks/useDragDrop.js +141 -34
- package/lib/module/hooks/useDragDrop.js.map +1 -1
- package/lib/module/{handlers/ScrollToNodeHandler.js → hooks/useScrollToNode.js} +27 -26
- package/lib/module/hooks/useScrollToNode.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/jest.setup.js +14 -1
- package/lib/module/jest.setup.js.map +1 -1
- package/lib/module/store/treeView.store.js +3 -0
- package/lib/module/store/treeView.store.js.map +1 -1
- package/lib/module/utils/typedMemo.js +3 -3
- package/lib/module/utils/typedMemo.js.map +1 -1
- package/lib/module/utils/useDeepCompareEffect.js +5 -5
- package/lib/module/utils/useDeepCompareEffect.js.map +1 -1
- package/lib/typescript/src/TreeView.d.ts +3 -3
- package/lib/typescript/src/TreeView.d.ts.map +1 -1
- package/lib/typescript/src/components/CheckboxView.d.ts +1 -2
- package/lib/typescript/src/components/CheckboxView.d.ts.map +1 -1
- package/lib/typescript/src/components/CustomExpandCollapseIcon.d.ts +1 -2
- package/lib/typescript/src/components/CustomExpandCollapseIcon.d.ts.map +1 -1
- package/lib/typescript/src/components/DragOverlay.d.ts +1 -0
- package/lib/typescript/src/components/DragOverlay.d.ts.map +1 -1
- package/lib/typescript/src/components/DropIndicator.d.ts +1 -2
- package/lib/typescript/src/components/DropIndicator.d.ts.map +1 -1
- package/lib/typescript/src/components/NodeList.d.ts.map +1 -1
- package/lib/typescript/src/constants/treeView.constants.d.ts +2 -0
- package/lib/typescript/src/constants/treeView.constants.d.ts.map +1 -1
- package/lib/typescript/src/helpers/expandCollapse.helper.d.ts +2 -2
- package/lib/typescript/src/helpers/expandCollapse.helper.d.ts.map +1 -1
- package/lib/typescript/src/helpers/moveTreeNode.helper.d.ts.map +1 -1
- package/lib/typescript/src/helpers/selectAll.helper.d.ts +4 -4
- package/lib/typescript/src/helpers/selectAll.helper.d.ts.map +1 -1
- package/lib/typescript/src/helpers/toggleCheckbox.helper.d.ts +3 -0
- package/lib/typescript/src/helpers/toggleCheckbox.helper.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useDragDrop.d.ts +18 -7
- package/lib/typescript/src/hooks/useDragDrop.d.ts.map +1 -1
- package/lib/typescript/src/{handlers/ScrollToNodeHandler.d.ts → hooks/useScrollToNode.d.ts} +13 -15
- package/lib/typescript/src/hooks/useScrollToNode.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +4 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/jest.setup.d.ts +1 -1
- package/lib/typescript/src/jest.setup.d.ts.map +1 -1
- package/lib/typescript/src/store/treeView.store.d.ts +2 -1
- package/lib/typescript/src/store/treeView.store.d.ts.map +1 -1
- package/lib/typescript/src/types/dragDrop.types.d.ts +19 -0
- package/lib/typescript/src/types/dragDrop.types.d.ts.map +1 -1
- package/lib/typescript/src/types/treeView.types.d.ts +149 -35
- package/lib/typescript/src/types/treeView.types.d.ts.map +1 -1
- package/lib/typescript/src/utils/typedMemo.d.ts +1 -1
- package/lib/typescript/src/utils/typedMemo.d.ts.map +1 -1
- package/lib/typescript/src/utils/useDeepCompareEffect.d.ts +2 -2
- package/lib/typescript/src/utils/useDeepCompareEffect.d.ts.map +1 -1
- package/package.json +32 -15
- package/src/TreeView.tsx +57 -35
- package/src/components/CheckboxView.tsx +7 -4
- package/src/components/CustomExpandCollapseIcon.tsx +2 -2
- package/src/components/DragOverlay.tsx +19 -6
- package/src/components/DropIndicator.tsx +2 -2
- package/src/components/NodeList.tsx +90 -58
- package/src/constants/treeView.constants.ts +4 -1
- package/src/helpers/expandCollapse.helper.ts +5 -5
- package/src/helpers/moveTreeNode.helper.ts +33 -0
- package/src/helpers/selectAll.helper.ts +10 -10
- package/src/helpers/toggleCheckbox.helper.ts +57 -69
- package/src/hooks/useDragDrop.ts +182 -46
- package/src/{handlers/ScrollToNodeHandler.tsx → hooks/useScrollToNode.ts} +48 -45
- package/src/index.tsx +9 -0
- package/src/jest.setup.ts +14 -1
- package/src/store/treeView.store.ts +6 -1
- package/src/types/dragDrop.types.ts +21 -0
- package/src/types/treeView.types.ts +157 -41
- package/src/utils/typedMemo.ts +3 -3
- package/src/utils/useDeepCompareEffect.ts +13 -7
- package/lib/module/handlers/ScrollToNodeHandler.js.map +0 -1
- package/lib/typescript/src/handlers/ScrollToNodeHandler.d.ts.map +0 -1
|
@@ -49,8 +49,8 @@ export function handleToggleExpand<ID>(storeId: string, id: ID) {
|
|
|
49
49
|
/**
|
|
50
50
|
* Expand all nodes in the tree.
|
|
51
51
|
*/
|
|
52
|
-
export function expandAll(storeId: string) {
|
|
53
|
-
const treeViewStore = getTreeViewStore(storeId);
|
|
52
|
+
export function expandAll<ID>(storeId: string) {
|
|
53
|
+
const treeViewStore = getTreeViewStore<ID>(storeId);
|
|
54
54
|
const { nodeMap, updateExpanded } = treeViewStore.getState();
|
|
55
55
|
// Create a new Set containing the IDs of all nodes
|
|
56
56
|
const newExpanded = new Set(nodeMap.keys());
|
|
@@ -60,11 +60,11 @@ export function expandAll(storeId: string) {
|
|
|
60
60
|
/**
|
|
61
61
|
* Collapse all nodes in the tree.
|
|
62
62
|
*/
|
|
63
|
-
export function collapseAll(storeId: string) {
|
|
64
|
-
const treeViewStore = getTreeViewStore(storeId);
|
|
63
|
+
export function collapseAll<ID>(storeId: string) {
|
|
64
|
+
const treeViewStore = getTreeViewStore<ID>(storeId);
|
|
65
65
|
const { updateExpanded } = treeViewStore.getState();
|
|
66
66
|
// Clear the expanded state
|
|
67
|
-
updateExpanded(new Set
|
|
67
|
+
updateExpanded(new Set());
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
|
@@ -18,6 +18,9 @@ export function moveTreeNode<ID>(
|
|
|
18
18
|
): TreeNode<ID>[] {
|
|
19
19
|
if (draggedNodeId === targetNodeId) return data;
|
|
20
20
|
|
|
21
|
+
// Prevent moving a node into its own descendant (would create a cycle)
|
|
22
|
+
if (isDescendant(data, draggedNodeId, targetNodeId)) return data;
|
|
23
|
+
|
|
21
24
|
// Step 1: Deep clone the tree
|
|
22
25
|
const cloned = deepCloneTree(data);
|
|
23
26
|
|
|
@@ -32,6 +35,36 @@ export function moveTreeNode<ID>(
|
|
|
32
35
|
return cloned;
|
|
33
36
|
}
|
|
34
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Check if `candidateDescendantId` is a descendant of `ancestorId` in the tree.
|
|
40
|
+
*/
|
|
41
|
+
function isDescendant<ID>(
|
|
42
|
+
nodes: TreeNode<ID>[],
|
|
43
|
+
ancestorId: ID,
|
|
44
|
+
candidateDescendantId: ID,
|
|
45
|
+
): boolean {
|
|
46
|
+
for (const node of nodes) {
|
|
47
|
+
if (node.id === ancestorId) {
|
|
48
|
+
// Found the ancestor — search its subtree for the candidate
|
|
49
|
+
return containsNode(node.children ?? [], candidateDescendantId);
|
|
50
|
+
}
|
|
51
|
+
if (node.children && isDescendant(node.children, ancestorId, candidateDescendantId)) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Check if a node with the given ID exists anywhere in the subtree. */
|
|
59
|
+
function containsNode<ID>(nodes: TreeNode<ID>[], nodeId: ID): boolean {
|
|
60
|
+
for (const node of nodes) {
|
|
61
|
+
if (node.id === nodeId) return true;
|
|
62
|
+
if (node.children && containsNode(node.children, nodeId)) return true;
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Deep clone a tree structure so mutations don't affect the original. */
|
|
35
68
|
function deepCloneTree<ID>(nodes: TreeNode<ID>[]): TreeNode<ID>[] {
|
|
36
69
|
return nodes.map(node => ({
|
|
37
70
|
...node,
|
|
@@ -7,13 +7,13 @@ import { toggleCheckboxes } from "./toggleCheckbox.helper";
|
|
|
7
7
|
*
|
|
8
8
|
* If there is no search text, then it selects all nodes; otherwise, it selects all visible nodes.
|
|
9
9
|
*/
|
|
10
|
-
export function selectAllFiltered(storeId: string) {
|
|
11
|
-
const treeViewStore = getTreeViewStore(storeId);
|
|
10
|
+
export function selectAllFiltered<ID>(storeId: string) {
|
|
11
|
+
const treeViewStore = getTreeViewStore<ID>(storeId);
|
|
12
12
|
const { searchText, innerMostChildrenIds } = treeViewStore.getState();
|
|
13
13
|
|
|
14
14
|
// If there's no search text, select all nodes
|
|
15
15
|
if (!searchText) {
|
|
16
|
-
selectAll(storeId);
|
|
16
|
+
selectAll<ID>(storeId);
|
|
17
17
|
} else {
|
|
18
18
|
// If there's search text, only select the visible nodes
|
|
19
19
|
toggleCheckboxes(storeId, innerMostChildrenIds, true);
|
|
@@ -25,13 +25,13 @@ export function selectAllFiltered(storeId: string) {
|
|
|
25
25
|
*
|
|
26
26
|
* If there is no search text, then it unselects all nodes; otherwise, it unselects all visible nodes.
|
|
27
27
|
*/
|
|
28
|
-
export function unselectAllFiltered(storeId: string) {
|
|
29
|
-
const treeViewStore = getTreeViewStore(storeId);
|
|
28
|
+
export function unselectAllFiltered<ID>(storeId: string) {
|
|
29
|
+
const treeViewStore = getTreeViewStore<ID>(storeId);
|
|
30
30
|
const { searchText, innerMostChildrenIds } = treeViewStore.getState();
|
|
31
31
|
|
|
32
32
|
// If there's no search text, unselect all nodes
|
|
33
33
|
if (!searchText) {
|
|
34
|
-
unselectAll(storeId);
|
|
34
|
+
unselectAll<ID>(storeId);
|
|
35
35
|
} else {
|
|
36
36
|
// If there's search text, only unselect the visible nodes
|
|
37
37
|
toggleCheckboxes(storeId, innerMostChildrenIds, false);
|
|
@@ -43,8 +43,8 @@ export function unselectAllFiltered(storeId: string) {
|
|
|
43
43
|
*
|
|
44
44
|
* This function selects all nodes by adding all node ids to the checked set and clearing the indeterminate set.
|
|
45
45
|
*/
|
|
46
|
-
export function selectAll(storeId: string) {
|
|
47
|
-
const treeViewStore = getTreeViewStore(storeId);
|
|
46
|
+
export function selectAll<ID>(storeId: string) {
|
|
47
|
+
const treeViewStore = getTreeViewStore<ID>(storeId);
|
|
48
48
|
const {
|
|
49
49
|
nodeMap,
|
|
50
50
|
updateChecked,
|
|
@@ -64,8 +64,8 @@ export function selectAll(storeId: string) {
|
|
|
64
64
|
*
|
|
65
65
|
* This function unselects all nodes by clearing both the checked and indeterminate sets.
|
|
66
66
|
*/
|
|
67
|
-
export function unselectAll(storeId: string) {
|
|
68
|
-
const treeViewStore = getTreeViewStore(storeId);
|
|
67
|
+
export function unselectAll<ID>(storeId: string) {
|
|
68
|
+
const treeViewStore = getTreeViewStore<ID>(storeId);
|
|
69
69
|
const { updateChecked, updateIndeterminate } = treeViewStore.getState();
|
|
70
70
|
// Update the state to mark all nodes as unchecked
|
|
71
71
|
|
|
@@ -1,5 +1,56 @@
|
|
|
1
|
+
import type { CheckboxValueType, TreeNode } from "../types/treeView.types";
|
|
1
2
|
import { getTreeViewStore } from "../store/treeView.store";
|
|
2
3
|
|
|
4
|
+
/** Derive the tri-state checkbox value from checked and indeterminate booleans. */
|
|
5
|
+
export function getCheckboxValue(
|
|
6
|
+
isChecked: boolean,
|
|
7
|
+
isIndeterminate: boolean
|
|
8
|
+
): CheckboxValueType {
|
|
9
|
+
if (isIndeterminate) return "indeterminate";
|
|
10
|
+
return isChecked;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Update a parent node's checked/indeterminate state based on its children.
|
|
15
|
+
* Mutates tempChecked and tempIndeterminate in-place.
|
|
16
|
+
*/
|
|
17
|
+
function updateParentCheckState<ID>(
|
|
18
|
+
parentId: ID,
|
|
19
|
+
children: TreeNode<ID>[],
|
|
20
|
+
tempChecked: Set<ID>,
|
|
21
|
+
tempIndeterminate: Set<ID>,
|
|
22
|
+
): void {
|
|
23
|
+
let allChecked = true;
|
|
24
|
+
let anyCheckedOrIndeterminate = false;
|
|
25
|
+
|
|
26
|
+
for (const child of children) {
|
|
27
|
+
const isChecked = tempChecked.has(child.id);
|
|
28
|
+
const isIndeterminate = tempIndeterminate.has(child.id);
|
|
29
|
+
|
|
30
|
+
if (isChecked) {
|
|
31
|
+
anyCheckedOrIndeterminate = true;
|
|
32
|
+
} else if (isIndeterminate) {
|
|
33
|
+
anyCheckedOrIndeterminate = true;
|
|
34
|
+
allChecked = false;
|
|
35
|
+
} else {
|
|
36
|
+
allChecked = false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!allChecked && anyCheckedOrIndeterminate) break;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (allChecked) {
|
|
43
|
+
tempChecked.add(parentId);
|
|
44
|
+
tempIndeterminate.delete(parentId);
|
|
45
|
+
} else if (anyCheckedOrIndeterminate) {
|
|
46
|
+
tempChecked.delete(parentId);
|
|
47
|
+
tempIndeterminate.add(parentId);
|
|
48
|
+
} else {
|
|
49
|
+
tempChecked.delete(parentId);
|
|
50
|
+
tempIndeterminate.delete(parentId);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
3
54
|
/**
|
|
4
55
|
* Function to toggle checkbox state for a tree structure.
|
|
5
56
|
* It sets the checked and indeterminate state for all affected nodes in the tree after an action to check/uncheck is made.
|
|
@@ -106,7 +157,7 @@ export function toggleCheckboxes<ID>(
|
|
|
106
157
|
while (stack.length > 0) {
|
|
107
158
|
const nodeId = stack.pop()!;
|
|
108
159
|
const node = nodeMap.get(nodeId);
|
|
109
|
-
if (!node) continue;
|
|
160
|
+
if (!node) continue;
|
|
110
161
|
|
|
111
162
|
if (childrenChecked) {
|
|
112
163
|
tempChecked.add(nodeId);
|
|
@@ -157,43 +208,8 @@ export function toggleCheckboxes<ID>(
|
|
|
157
208
|
*/
|
|
158
209
|
function updateNodeState(nodeId: ID) {
|
|
159
210
|
const node = nodeMap.get(nodeId);
|
|
160
|
-
if (!node
|
|
161
|
-
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
let allChildrenChecked = true;
|
|
166
|
-
let anyChildCheckedOrIndeterminate = false;
|
|
167
|
-
|
|
168
|
-
for (const child of node.children) {
|
|
169
|
-
const isChecked = tempChecked.has(child.id);
|
|
170
|
-
const isIndeterminate = tempIndeterminate.has(child.id);
|
|
171
|
-
|
|
172
|
-
if (isChecked) {
|
|
173
|
-
anyChildCheckedOrIndeterminate = true;
|
|
174
|
-
} else if (isIndeterminate) {
|
|
175
|
-
anyChildCheckedOrIndeterminate = true;
|
|
176
|
-
allChildrenChecked = false;
|
|
177
|
-
} else {
|
|
178
|
-
allChildrenChecked = false;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// If both conditions are met, we can break early.
|
|
182
|
-
if (!allChildrenChecked && anyChildCheckedOrIndeterminate) {
|
|
183
|
-
break;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (allChildrenChecked) {
|
|
188
|
-
tempChecked.add(nodeId);
|
|
189
|
-
tempIndeterminate.delete(nodeId);
|
|
190
|
-
} else if (anyChildCheckedOrIndeterminate) {
|
|
191
|
-
tempChecked.delete(nodeId);
|
|
192
|
-
tempIndeterminate.add(nodeId);
|
|
193
|
-
} else {
|
|
194
|
-
tempChecked.delete(nodeId);
|
|
195
|
-
tempIndeterminate.delete(nodeId);
|
|
196
|
-
}
|
|
211
|
+
if (!node?.children?.length) return;
|
|
212
|
+
updateParentCheckState(nodeId, node.children, tempChecked, tempIndeterminate);
|
|
197
213
|
}
|
|
198
214
|
|
|
199
215
|
// Update the state object with the new checked and indeterminate sets.
|
|
@@ -225,7 +241,7 @@ export function recalculateCheckedStates<ID>(storeId: string) {
|
|
|
225
241
|
const tempIndeterminate = new Set(indeterminate);
|
|
226
242
|
|
|
227
243
|
// Collect parent nodes and clean up leaf nodes that shouldn't be indeterminate.
|
|
228
|
-
// A leaf node (no children) can never be indeterminate
|
|
244
|
+
// A leaf node (no children) can never be indeterminate - this can happen when
|
|
229
245
|
// all children of a formerly-indeterminate parent are dragged away.
|
|
230
246
|
const parentNodes: ID[] = [];
|
|
231
247
|
for (const [id, node] of nodeMap) {
|
|
@@ -260,37 +276,9 @@ export function recalculateCheckedStates<ID>(storeId: string) {
|
|
|
260
276
|
// Update each parent based on its children's current state
|
|
261
277
|
for (const parentId of parentNodes) {
|
|
262
278
|
const node = nodeMap.get(parentId);
|
|
279
|
+
/* istanbul ignore next -- parentNodes is built from nodeMap entries with children above; same nodeMap, same iteration — unreachable unless nodeMap mutates between loops */
|
|
263
280
|
if (!node?.children?.length) continue;
|
|
264
|
-
|
|
265
|
-
let allChecked = true;
|
|
266
|
-
let anyCheckedOrIndeterminate = false;
|
|
267
|
-
|
|
268
|
-
for (const child of node.children) {
|
|
269
|
-
const isChecked = tempChecked.has(child.id);
|
|
270
|
-
const isIndeterminate = tempIndeterminate.has(child.id);
|
|
271
|
-
|
|
272
|
-
if (isChecked) {
|
|
273
|
-
anyCheckedOrIndeterminate = true;
|
|
274
|
-
} else if (isIndeterminate) {
|
|
275
|
-
anyCheckedOrIndeterminate = true;
|
|
276
|
-
allChecked = false;
|
|
277
|
-
} else {
|
|
278
|
-
allChecked = false;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (!allChecked && anyCheckedOrIndeterminate) break;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (allChecked) {
|
|
285
|
-
tempChecked.add(parentId);
|
|
286
|
-
tempIndeterminate.delete(parentId);
|
|
287
|
-
} else if (anyCheckedOrIndeterminate) {
|
|
288
|
-
tempChecked.delete(parentId);
|
|
289
|
-
tempIndeterminate.add(parentId);
|
|
290
|
-
} else {
|
|
291
|
-
tempChecked.delete(parentId);
|
|
292
|
-
tempIndeterminate.delete(parentId);
|
|
293
|
-
}
|
|
281
|
+
updateParentCheckState(parentId, node.children, tempChecked, tempIndeterminate);
|
|
294
282
|
}
|
|
295
283
|
|
|
296
284
|
updateChecked(tempChecked);
|