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.
Files changed (96) hide show
  1. package/README.md +85 -25
  2. package/lib/module/TreeView.js +36 -31
  3. package/lib/module/TreeView.js.map +1 -1
  4. package/lib/module/components/CheckboxView.js +8 -4
  5. package/lib/module/components/CheckboxView.js.map +1 -1
  6. package/lib/module/components/CustomExpandCollapseIcon.js +2 -2
  7. package/lib/module/components/CustomExpandCollapseIcon.js.map +1 -1
  8. package/lib/module/components/DragOverlay.js +17 -5
  9. package/lib/module/components/DragOverlay.js.map +1 -1
  10. package/lib/module/components/DropIndicator.js +2 -2
  11. package/lib/module/components/DropIndicator.js.map +1 -1
  12. package/lib/module/components/NodeList.js +80 -56
  13. package/lib/module/components/NodeList.js.map +1 -1
  14. package/lib/module/constants/treeView.constants.js +3 -0
  15. package/lib/module/constants/treeView.constants.js.map +1 -1
  16. package/lib/module/helpers/expandCollapse.helper.js.map +1 -1
  17. package/lib/module/helpers/moveTreeNode.helper.js +30 -0
  18. package/lib/module/helpers/moveTreeNode.helper.js.map +1 -1
  19. package/lib/module/helpers/selectAll.helper.js.map +1 -1
  20. package/lib/module/helpers/toggleCheckbox.helper.js +44 -61
  21. package/lib/module/helpers/toggleCheckbox.helper.js.map +1 -1
  22. package/lib/module/hooks/useDragDrop.js +141 -34
  23. package/lib/module/hooks/useDragDrop.js.map +1 -1
  24. package/lib/module/{handlers/ScrollToNodeHandler.js → hooks/useScrollToNode.js} +27 -26
  25. package/lib/module/hooks/useScrollToNode.js.map +1 -0
  26. package/lib/module/index.js +1 -0
  27. package/lib/module/index.js.map +1 -1
  28. package/lib/module/jest.setup.js +14 -1
  29. package/lib/module/jest.setup.js.map +1 -1
  30. package/lib/module/store/treeView.store.js +3 -0
  31. package/lib/module/store/treeView.store.js.map +1 -1
  32. package/lib/module/utils/typedMemo.js +3 -3
  33. package/lib/module/utils/typedMemo.js.map +1 -1
  34. package/lib/module/utils/useDeepCompareEffect.js +5 -5
  35. package/lib/module/utils/useDeepCompareEffect.js.map +1 -1
  36. package/lib/typescript/src/TreeView.d.ts +3 -3
  37. package/lib/typescript/src/TreeView.d.ts.map +1 -1
  38. package/lib/typescript/src/components/CheckboxView.d.ts +1 -2
  39. package/lib/typescript/src/components/CheckboxView.d.ts.map +1 -1
  40. package/lib/typescript/src/components/CustomExpandCollapseIcon.d.ts +1 -2
  41. package/lib/typescript/src/components/CustomExpandCollapseIcon.d.ts.map +1 -1
  42. package/lib/typescript/src/components/DragOverlay.d.ts +1 -0
  43. package/lib/typescript/src/components/DragOverlay.d.ts.map +1 -1
  44. package/lib/typescript/src/components/DropIndicator.d.ts +1 -2
  45. package/lib/typescript/src/components/DropIndicator.d.ts.map +1 -1
  46. package/lib/typescript/src/components/NodeList.d.ts.map +1 -1
  47. package/lib/typescript/src/constants/treeView.constants.d.ts +2 -0
  48. package/lib/typescript/src/constants/treeView.constants.d.ts.map +1 -1
  49. package/lib/typescript/src/helpers/expandCollapse.helper.d.ts +2 -2
  50. package/lib/typescript/src/helpers/expandCollapse.helper.d.ts.map +1 -1
  51. package/lib/typescript/src/helpers/moveTreeNode.helper.d.ts.map +1 -1
  52. package/lib/typescript/src/helpers/selectAll.helper.d.ts +4 -4
  53. package/lib/typescript/src/helpers/selectAll.helper.d.ts.map +1 -1
  54. package/lib/typescript/src/helpers/toggleCheckbox.helper.d.ts +3 -0
  55. package/lib/typescript/src/helpers/toggleCheckbox.helper.d.ts.map +1 -1
  56. package/lib/typescript/src/hooks/useDragDrop.d.ts +18 -7
  57. package/lib/typescript/src/hooks/useDragDrop.d.ts.map +1 -1
  58. package/lib/typescript/src/{handlers/ScrollToNodeHandler.d.ts → hooks/useScrollToNode.d.ts} +13 -15
  59. package/lib/typescript/src/hooks/useScrollToNode.d.ts.map +1 -0
  60. package/lib/typescript/src/index.d.ts +4 -3
  61. package/lib/typescript/src/index.d.ts.map +1 -1
  62. package/lib/typescript/src/jest.setup.d.ts +1 -1
  63. package/lib/typescript/src/jest.setup.d.ts.map +1 -1
  64. package/lib/typescript/src/store/treeView.store.d.ts +2 -1
  65. package/lib/typescript/src/store/treeView.store.d.ts.map +1 -1
  66. package/lib/typescript/src/types/dragDrop.types.d.ts +19 -0
  67. package/lib/typescript/src/types/dragDrop.types.d.ts.map +1 -1
  68. package/lib/typescript/src/types/treeView.types.d.ts +149 -35
  69. package/lib/typescript/src/types/treeView.types.d.ts.map +1 -1
  70. package/lib/typescript/src/utils/typedMemo.d.ts +1 -1
  71. package/lib/typescript/src/utils/typedMemo.d.ts.map +1 -1
  72. package/lib/typescript/src/utils/useDeepCompareEffect.d.ts +2 -2
  73. package/lib/typescript/src/utils/useDeepCompareEffect.d.ts.map +1 -1
  74. package/package.json +32 -15
  75. package/src/TreeView.tsx +57 -35
  76. package/src/components/CheckboxView.tsx +7 -4
  77. package/src/components/CustomExpandCollapseIcon.tsx +2 -2
  78. package/src/components/DragOverlay.tsx +19 -6
  79. package/src/components/DropIndicator.tsx +2 -2
  80. package/src/components/NodeList.tsx +90 -58
  81. package/src/constants/treeView.constants.ts +4 -1
  82. package/src/helpers/expandCollapse.helper.ts +5 -5
  83. package/src/helpers/moveTreeNode.helper.ts +33 -0
  84. package/src/helpers/selectAll.helper.ts +10 -10
  85. package/src/helpers/toggleCheckbox.helper.ts +57 -69
  86. package/src/hooks/useDragDrop.ts +182 -46
  87. package/src/{handlers/ScrollToNodeHandler.tsx → hooks/useScrollToNode.ts} +48 -45
  88. package/src/index.tsx +9 -0
  89. package/src/jest.setup.ts +14 -1
  90. package/src/store/treeView.store.ts +6 -1
  91. package/src/types/dragDrop.types.ts +21 -0
  92. package/src/types/treeView.types.ts +157 -41
  93. package/src/utils/typedMemo.ts +3 -3
  94. package/src/utils/useDeepCompareEffect.ts +13 -7
  95. package/lib/module/handlers/ScrollToNodeHandler.js.map +0 -1
  96. package/lib/typescript/src/handlers/ScrollToNodeHandler.d.ts.map +0 -1
package/src/TreeView.tsx CHANGED
@@ -1,4 +1,13 @@
1
- import React, { startTransition, useId } from "react";
1
+ import {
2
+ forwardRef,
3
+ startTransition,
4
+ useCallback,
5
+ useEffect,
6
+ useId,
7
+ useImperativeHandle,
8
+ useRef,
9
+ type ForwardedRef,
10
+ } from "react";
2
11
  import type {
3
12
  TreeNode,
4
13
  TreeViewProps,
@@ -15,9 +24,11 @@ import {
15
24
  collapseAll,
16
25
  toggleCheckboxes,
17
26
  expandNodes,
18
- collapseNodes
27
+ collapseNodes,
28
+ recalculateCheckedStates,
29
+ moveTreeNode,
19
30
  } from "./helpers";
20
- import { getTreeViewStore, useTreeViewStore } from "./store/treeView.store";
31
+ import { deleteTreeViewStore, getTreeViewStore, useTreeViewStore } from "./store/treeView.store";
21
32
  import usePreviousState from "./utils/usePreviousState";
22
33
  import { useShallow } from "zustand/react/shallow";
23
34
  import useDeepCompareEffect from "./utils/useDeepCompareEffect";
@@ -25,13 +36,13 @@ import { typedMemo } from "./utils/typedMemo";
25
36
  import type {
26
37
  ScrollToNodeHandlerRef,
27
38
  ScrollToNodeParams
28
- } from "./handlers/ScrollToNodeHandler";
29
- import type { DragEndEvent } from "./types/dragDrop.types";
39
+ } from "./hooks/useScrollToNode";
40
+ import type { DragEndEvent, DropPosition } from "./types/dragDrop.types";
30
41
  import { fastIsEqual } from "fast-is-equal";
31
42
 
32
43
  function _innerTreeView<ID>(
33
44
  props: TreeViewProps<ID>,
34
- ref: React.ForwardedRef<TreeViewRef<ID>>
45
+ ref: ForwardedRef<TreeViewRef<ID>>
35
46
  ) {
36
47
  const {
37
48
  data,
@@ -57,16 +68,11 @@ function _innerTreeView<ID>(
57
68
 
58
69
  CustomNodeRowComponent,
59
70
 
60
- dragEnabled,
61
- onDragEnd,
62
- longPressDuration,
63
- autoScrollThreshold,
64
- autoScrollSpeed,
65
- dragOverlayOffset,
66
- autoExpandDelay,
67
- dragDropCustomizations,
71
+ dragAndDrop,
68
72
  } = props;
69
73
 
74
+ const onDragEnd = dragAndDrop?.onDragEnd;
75
+
70
76
  const storeId = useId();
71
77
 
72
78
  const {
@@ -109,7 +115,7 @@ function _innerTreeView<ID>(
109
115
  })
110
116
  ));
111
117
 
112
- React.useImperativeHandle(ref, () => ({
118
+ useImperativeHandle(ref, () => ({
113
119
  selectAll: () => selectAll(storeId),
114
120
  unselectAll: () => unselectAll(storeId),
115
121
 
@@ -129,15 +135,17 @@ function _innerTreeView<ID>(
129
135
 
130
136
  scrollToNodeID,
131
137
 
132
- getChildToParentMap
138
+ getChildToParentMap,
139
+
140
+ moveNode,
133
141
  }));
134
142
 
135
- const scrollToNodeHandlerRef = React.useRef<ScrollToNodeHandlerRef<ID>>(null);
143
+ const scrollToNodeHandlerRef = useRef<ScrollToNodeHandlerRef<ID>>(null);
136
144
  const prevSearchText = usePreviousState(searchText);
137
- const internalDataRef = React.useRef<TreeNode<ID>[] | null>(null);
145
+ const internalDataRef = useRef<TreeNode<ID>[] | null>(null);
138
146
 
139
147
  // Wrap onDragEnd to set internalDataRef before calling consumer's callback
140
- const wrappedOnDragEnd = React.useCallback((event: DragEndEvent<ID>) => {
148
+ const wrappedOnDragEnd = useCallback((event: DragEndEvent<ID>) => {
141
149
  internalDataRef.current = event.newTreeData;
142
150
  onDragEnd?.(event);
143
151
  }, [onDragEnd]);
@@ -191,7 +199,24 @@ function _innerTreeView<ID>(
191
199
  return treeViewStore.getState().childToParentMap;
192
200
  }
193
201
 
194
- const getIds = React.useCallback((node: TreeNode<ID>): ID[] => {
202
+ function moveNode(nodeId: ID, targetNodeId: ID, position: DropPosition) {
203
+ const store = getTreeViewStore<ID>(storeId);
204
+ const currentData = store.getState().initialTreeViewData;
205
+ const newData = moveTreeNode(currentData, nodeId, targetNodeId, position);
206
+
207
+ store.getState().updateInitialTreeViewData(newData);
208
+ initializeNodeMaps(storeId, newData);
209
+ recalculateCheckedStates<ID>(storeId);
210
+
211
+ if (position === "inside") {
212
+ expandNodes(storeId, [targetNodeId]);
213
+ }
214
+ expandNodes(storeId, [nodeId], true);
215
+
216
+ internalDataRef.current = newData;
217
+ }
218
+
219
+ const getIds = useCallback((node: TreeNode<ID>): ID[] => {
195
220
  if (!node.children || node.children.length === 0) {
196
221
  return [node.id];
197
222
  } else {
@@ -199,15 +224,15 @@ function _innerTreeView<ID>(
199
224
  }
200
225
  }, []);
201
226
 
202
- React.useEffect(() => {
227
+ useEffect(() => {
203
228
  onCheck?.(Array.from(checked), Array.from(indeterminate));
204
229
  }, [onCheck, checked, indeterminate]);
205
230
 
206
- React.useEffect(() => {
231
+ useEffect(() => {
207
232
  onExpand?.(Array.from(expanded));
208
233
  }, [onExpand, expanded]);
209
234
 
210
- React.useEffect(() => {
235
+ useEffect(() => {
211
236
  if (searchText) {
212
237
  startTransition(() => {
213
238
  updateExpanded(new Set(initialTreeViewData.flatMap(
@@ -230,11 +255,12 @@ function _innerTreeView<ID>(
230
255
  updateExpanded
231
256
  ]);
232
257
 
233
- React.useEffect(() => {
258
+ useEffect(() => {
234
259
  return () => {
235
260
  cleanUpTreeViewStore();
261
+ deleteTreeViewStore(storeId);
236
262
  };
237
- }, [cleanUpTreeViewStore]);
263
+ }, [cleanUpTreeViewStore, storeId]);
238
264
 
239
265
  return (
240
266
  <NodeList
@@ -253,20 +279,16 @@ function _innerTreeView<ID>(
253
279
 
254
280
  CustomNodeRowComponent={CustomNodeRowComponent}
255
281
 
256
- dragEnabled={dragEnabled}
257
- onDragEnd={wrappedOnDragEnd}
258
- longPressDuration={longPressDuration}
259
- autoScrollThreshold={autoScrollThreshold}
260
- autoScrollSpeed={autoScrollSpeed}
261
- dragOverlayOffset={dragOverlayOffset}
262
- autoExpandDelay={autoExpandDelay}
263
- dragDropCustomizations={dragDropCustomizations}
282
+ dragAndDrop={dragAndDrop && {
283
+ ...dragAndDrop,
284
+ onDragEnd: wrappedOnDragEnd,
285
+ }}
264
286
  />
265
287
  );
266
288
  }
267
289
 
268
- const _TreeView = React.forwardRef(_innerTreeView) as <ID>(
269
- props: TreeViewProps<ID> & { ref?: React.ForwardedRef<TreeViewRef<ID>>; }
290
+ const _TreeView = forwardRef(_innerTreeView) as <ID>(
291
+ props: TreeViewProps<ID> & { ref?: ForwardedRef<TreeViewRef<ID>>; }
270
292
  ) => ReturnType<typeof _innerTreeView>;
271
293
 
272
294
  export const TreeView = typedMemo<typeof _TreeView>(_TreeView);
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import { memo, useCallback } from "react";
2
2
  import {
3
3
  Platform,
4
4
  StyleSheet,
@@ -13,6 +13,8 @@ import type {
13
13
  } from "../types/treeView.types";
14
14
  import { Checkbox } from "@futurejj/react-native-checkbox";
15
15
 
16
+ // Intentionally narrow: only re-render when the checkbox value or label text changes.
17
+ // Other props (callbacks, styles) are stable references from parent memoization.
16
18
  function arePropsEqual(
17
19
  prevProps: BuiltInCheckBoxViewProps,
18
20
  nextProps: BuiltInCheckBoxViewProps
@@ -23,7 +25,7 @@ function arePropsEqual(
23
25
  );
24
26
  }
25
27
 
26
- export const CheckboxView = React.memo(_CheckboxView, arePropsEqual);
28
+ export const CheckboxView = memo(_CheckboxView, arePropsEqual);
27
29
 
28
30
  function _CheckboxView(props: BuiltInCheckBoxViewProps) {
29
31
  const {
@@ -43,7 +45,7 @@ function _CheckboxView(props: BuiltInCheckBoxViewProps) {
43
45
  },
44
46
  } = props;
45
47
 
46
- const customCheckboxValToCheckboxValType = React.useCallback((
48
+ const customCheckboxValToCheckboxValType = useCallback((
47
49
  customCheckboxValueType: CheckboxValueType
48
50
  ) => {
49
51
  return customCheckboxValueType === "indeterminate"
@@ -59,7 +61,7 @@ function _CheckboxView(props: BuiltInCheckBoxViewProps) {
59
61
  *
60
62
  * @param newValue This represents the updated CheckBox value after it's clicked.
61
63
  */
62
- const onValueChangeModifier = React.useCallback(() => {
64
+ const onValueChangeModifier = useCallback(() => {
63
65
  // If the previous state was 'indeterminate', set checked to true
64
66
  if (value === "indeterminate") onValueChange(true);
65
67
  else onValueChange(!value);
@@ -106,6 +108,7 @@ export const defaultCheckboxViewStyles = StyleSheet.create({
106
108
  },
107
109
  checkboxTextStyle: {
108
110
  color: "black",
111
+ /* istanbul ignore next -- Platform.OS is never "android" in jest */
109
112
  marginTop: Platform.OS === "android" ? 2 : undefined,
110
113
  },
111
114
  });
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import { memo } from "react";
2
2
  import { type ExpandIconProps } from "../types/treeView.types";
3
3
 
4
4
  // Function to dynamically load FontAwesomeIcon from either Expo or React Native
@@ -20,7 +20,7 @@ function loadFontAwesomeIcon() {
20
20
  // Load the FontAwesomeIcon component
21
21
  const FontAwesomeIcon = loadFontAwesomeIcon();
22
22
 
23
- export const CustomExpandCollapseIcon = React.memo(
23
+ export const CustomExpandCollapseIcon = memo(
24
24
  _CustomExpandCollapseIcon
25
25
  );
26
26
 
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import { type ComponentType } from "react";
2
2
  import { Animated, StyleSheet, View } from "react-native";
3
3
 
4
4
  import type {
@@ -10,8 +10,12 @@ import type {
10
10
  import { CheckboxView } from "./CheckboxView";
11
11
  import { CustomExpandCollapseIcon } from "./CustomExpandCollapseIcon";
12
12
  import { defaultIndentationMultiplier } from "../constants/treeView.constants";
13
+ import { getTreeViewStore } from "../store/treeView.store";
14
+ import { getCheckboxValue } from "../helpers";
15
+ import { typedMemo } from "../utils/typedMemo";
13
16
 
14
17
  interface DragOverlayProps<ID> extends TreeItemCustomizations<ID> {
18
+ storeId: string;
15
19
  overlayY: Animated.Value;
16
20
  overlayX: Animated.Value;
17
21
  node: __FlattenedTreeNode__<ID>;
@@ -21,18 +25,24 @@ interface DragOverlayProps<ID> extends TreeItemCustomizations<ID> {
21
25
 
22
26
  function _DragOverlay<ID>(props: DragOverlayProps<ID>) {
23
27
  const {
28
+ storeId,
24
29
  overlayY,
25
30
  overlayX,
26
31
  node,
27
32
  level,
28
33
  indentationMultiplier = defaultIndentationMultiplier,
29
- CheckboxComponent = CheckboxView as React.ComponentType<CheckBoxViewProps>,
34
+ CheckboxComponent = CheckboxView as ComponentType<CheckBoxViewProps>,
30
35
  ExpandCollapseIconComponent = CustomExpandCollapseIcon,
31
36
  CustomNodeRowComponent,
32
37
  checkBoxViewStyleProps,
33
38
  dragDropCustomizations,
34
39
  } = props;
35
40
 
41
+ // Read the actual checked state for the dragged node
42
+ const store = getTreeViewStore<ID>(storeId);
43
+ const { checked, indeterminate } = store.getState();
44
+ const checkedValue = getCheckboxValue(checked.has(node.id), indeterminate.has(node.id));
45
+
36
46
  const overlayStyleProps = dragDropCustomizations?.dragOverlayStyleProps;
37
47
  const CustomOverlay = dragDropCustomizations?.CustomDragOverlayComponent;
38
48
 
@@ -52,13 +62,16 @@ function _DragOverlay<ID>(props: DragOverlayProps<ID>) {
52
62
  { transform: [{ translateX: overlayX }, { translateY: overlayY }] },
53
63
  ]}
54
64
  >
65
+ {/* Render priority: CustomDragOverlayComponent > CustomNodeRowComponent > built-in.
66
+ The overlay is display-only (pointerEvents="none" on parent), so handlers are no-ops.
67
+ isExpanded is always false because useDragDrop collapses the node at drag start. */}
55
68
  {CustomOverlay ? (
56
- <CustomOverlay node={node} level={level} />
69
+ <CustomOverlay node={node} level={level} checkedValue={checkedValue} />
57
70
  ) : CustomNodeRowComponent ? (
58
71
  <CustomNodeRowComponent
59
72
  node={node}
60
73
  level={level}
61
- checkedValue={false}
74
+ checkedValue={checkedValue}
62
75
  isExpanded={false}
63
76
  onCheck={() => {}}
64
77
  onExpand={() => {}}
@@ -73,7 +86,7 @@ function _DragOverlay<ID>(props: DragOverlayProps<ID>) {
73
86
  <CheckboxComponent
74
87
  text={node.name}
75
88
  onValueChange={() => {}}
76
- value={false}
89
+ value={checkedValue}
77
90
  {...checkBoxViewStyleProps}
78
91
  />
79
92
  {node.children?.length ? (
@@ -87,7 +100,7 @@ function _DragOverlay<ID>(props: DragOverlayProps<ID>) {
87
100
  );
88
101
  }
89
102
 
90
- export const DragOverlay = React.memo(_DragOverlay) as typeof _DragOverlay;
103
+ export const DragOverlay = typedMemo(_DragOverlay);
91
104
 
92
105
  const styles = StyleSheet.create({
93
106
  overlay: {
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import { memo } from "react";
2
2
  import { Animated, View, StyleSheet } from "react-native";
3
3
  import type { DropPosition } from "../types/dragDrop.types";
4
4
 
@@ -10,7 +10,7 @@ interface DropIndicatorProps {
10
10
  indentationMultiplier: number;
11
11
  }
12
12
 
13
- export const DropIndicator = React.memo(function DropIndicator(
13
+ export const DropIndicator = memo(function DropIndicator(
14
14
  props: DropIndicatorProps
15
15
  ) {
16
16
  const {
@@ -1,4 +1,10 @@
1
- import React from "react";
1
+ import {
2
+ useCallback,
3
+ useEffect,
4
+ useMemo,
5
+ useRef,
6
+ useState,
7
+ } from "react";
2
8
  import {
3
9
  View,
4
10
  StyleSheet,
@@ -9,7 +15,6 @@ import {
9
15
  import { FlashList } from "@shopify/flash-list";
10
16
 
11
17
  import type {
12
- CheckboxValueType,
13
18
  __FlattenedTreeNode__,
14
19
  DropIndicatorStyleProps,
15
20
  NodeListProps,
@@ -21,6 +26,7 @@ import { useTreeViewStore } from "../store/treeView.store";
21
26
  import {
22
27
  getFilteredTreeData,
23
28
  getFlattenedTreeData,
29
+ getCheckboxValue,
24
30
  getInnerMostChildrenIdsInTree,
25
31
  handleToggleExpand,
26
32
  toggleCheckboxes
@@ -29,10 +35,13 @@ import { CheckboxView } from "./CheckboxView";
29
35
  import { CustomExpandCollapseIcon } from "./CustomExpandCollapseIcon";
30
36
  import { DragOverlay } from "./DragOverlay";
31
37
  import type { DropPosition } from "../types/dragDrop.types";
32
- import { defaultIndentationMultiplier } from "../constants/treeView.constants";
38
+ import {
39
+ defaultIndentationMultiplier,
40
+ listHeaderFooterPadding
41
+ } from "../constants/treeView.constants";
33
42
  import { useShallow } from "zustand/react/shallow";
34
43
  import { typedMemo } from "../utils/typedMemo";
35
- import { ScrollToNodeHandler } from "../handlers/ScrollToNodeHandler";
44
+ import { useScrollToNode } from "../hooks/useScrollToNode";
36
45
  import { useDragDrop } from "../hooks/useDragDrop";
37
46
 
38
47
  const NodeList = typedMemo(_NodeList);
@@ -54,15 +63,29 @@ function _NodeList<ID>(props: NodeListProps<ID>) {
54
63
  ExpandCollapseTouchableComponent,
55
64
  CustomNodeRowComponent,
56
65
 
57
- dragEnabled,
66
+ dragAndDrop,
67
+ } = props;
68
+
69
+ const {
70
+ enabled: _dragEnabled,
71
+ onDragStart,
58
72
  onDragEnd,
73
+ onDragCancel,
59
74
  longPressDuration = 400,
60
75
  autoScrollThreshold = 60,
61
76
  autoScrollSpeed = 1.0,
62
77
  dragOverlayOffset = -4,
63
78
  autoExpandDelay = 800,
64
- dragDropCustomizations,
65
- } = props;
79
+ customizations: dragDropCustomizations,
80
+ canDrop: canDropCallback,
81
+ maxDepth,
82
+ canNodeHaveChildren,
83
+ canDrag,
84
+ } = dragAndDrop ?? {};
85
+
86
+ // When the dragAndDrop prop is provided, drag is enabled by default.
87
+ // Users can still toggle it off with enabled: false at runtime.
88
+ const dragEnabled = dragAndDrop ? (_dragEnabled ?? true) : false;
66
89
 
67
90
  const {
68
91
  expanded,
@@ -80,34 +103,43 @@ function _NodeList<ID>(props: NodeListProps<ID>) {
80
103
  })
81
104
  ));
82
105
 
83
- const flashListRef = React.useRef<FlashList<__FlattenedTreeNode__<ID>> | null>(null);
84
- const containerRef = React.useRef<View>(null);
85
- const internalDataRef = React.useRef<TreeNode<ID>[] | null>(null);
86
- const measuredItemHeightRef = React.useRef(0);
106
+ const flashListRef = useRef<FlashList<__FlattenedTreeNode__<ID>> | null>(null);
107
+ const containerRef = useRef<View>(null);
108
+ const internalDataRef = useRef<TreeNode<ID>[] | null>(null);
109
+ const measuredItemHeightRef = useRef(0);
87
110
 
88
- const handleItemLayout = React.useCallback((height: number) => {
111
+ const handleItemLayout = useCallback((height: number) => {
89
112
  if (measuredItemHeightRef.current === 0 && height > 0) {
90
113
  measuredItemHeightRef.current = height;
91
114
  }
92
115
  }, []);
93
116
 
94
- const [initialScrollIndex, setInitialScrollIndex] = React.useState<number>(-1);
117
+ const [initialScrollIndex, setInitialScrollIndex] = useState<number>(-1);
95
118
 
96
119
  // First we filter the tree as per the search term and keys
97
- const filteredTree = React.useMemo(() => getFilteredTreeData<ID>(
120
+ const filteredTree = useMemo(() => getFilteredTreeData<ID>(
98
121
  initialTreeViewData,
99
122
  searchText.trim().toLowerCase(),
100
123
  searchKeys
101
124
  ), [initialTreeViewData, searchText, searchKeys]);
102
125
 
103
126
  // Then we flatten the tree to make it "render-compatible" in a "flat" list
104
- const flattenedFilteredNodes = React.useMemo(() => getFlattenedTreeData<ID>(
127
+ const flattenedFilteredNodes = useMemo(() => getFlattenedTreeData<ID>(
105
128
  filteredTree,
106
129
  expanded,
107
130
  ), [filteredTree, expanded]);
108
131
 
132
+ useScrollToNode<ID>({
133
+ storeId,
134
+ scrollToNodeHandlerRef,
135
+ flashListRef,
136
+ flattenedFilteredNodes,
137
+ setInitialScrollIndex,
138
+ initialScrollNodeID,
139
+ });
140
+
109
141
  // And update the innermost children id -> required to un/select filtered tree
110
- React.useEffect(() => {
142
+ useEffect(() => {
111
143
  const updatedInnerMostChildrenIds = getInnerMostChildrenIdsInTree<ID>(
112
144
  filteredTree
113
145
  );
@@ -133,8 +165,10 @@ function _NodeList<ID>(props: NodeListProps<ID>) {
133
165
  flattenedNodes: flattenedFilteredNodes,
134
166
  flashListRef,
135
167
  containerRef,
136
- dragEnabled: dragEnabled ?? false,
168
+ dragEnabled,
169
+ onDragStart,
137
170
  onDragEnd,
171
+ onDragCancel,
138
172
  longPressDuration,
139
173
  autoScrollThreshold,
140
174
  autoScrollSpeed,
@@ -143,10 +177,14 @@ function _NodeList<ID>(props: NodeListProps<ID>) {
143
177
  dragOverlayOffset,
144
178
  autoExpandDelay,
145
179
  indentationMultiplier: effectiveIndentationMultiplier,
180
+ canDrop: canDropCallback,
181
+ maxDepth,
182
+ canNodeHaveChildren,
183
+ canDrag,
146
184
  });
147
185
 
148
186
  // Combined onScroll handler
149
- const handleScroll = React.useCallback((
187
+ const handleScroll = useCallback((
150
188
  event: NativeSyntheticEvent<NativeScrollEvent>
151
189
  ) => {
152
190
  scrollOffsetRef.current = event.nativeEvent.contentOffset.y;
@@ -156,7 +194,7 @@ function _NodeList<ID>(props: NodeListProps<ID>) {
156
194
  treeFlashListProps?.onScroll?.(event as any);
157
195
  }, [scrollOffsetRef, cancelLongPressTimer, treeFlashListProps]);
158
196
 
159
- const nodeRenderer = React.useCallback((
197
+ const nodeRenderer = useCallback((
160
198
  { item, index }: { item: __FlattenedTreeNode__<ID>; index: number; }
161
199
  ) => {
162
200
  return (
@@ -223,14 +261,6 @@ function _NodeList<ID>(props: NodeListProps<ID>) {
223
261
 
224
262
  return (
225
263
  <>
226
- <ScrollToNodeHandler
227
- ref={scrollToNodeHandlerRef}
228
- storeId={storeId}
229
- flashListRef={flashListRef}
230
- flattenedFilteredNodes={flattenedFilteredNodes}
231
- setInitialScrollIndex={setInitialScrollIndex}
232
- initialScrollNodeID={initialScrollNodeID} />
233
-
234
264
  {dragEnabled ? (
235
265
  <View
236
266
  ref={containerRef}
@@ -240,6 +270,7 @@ function _NodeList<ID>(props: NodeListProps<ID>) {
240
270
  {flashListElement}
241
271
  {isDragging && draggedNode && (
242
272
  <DragOverlay<ID>
273
+ storeId={storeId}
243
274
  overlayY={overlayY}
244
275
  overlayX={overlayX}
245
276
  node={draggedNode}
@@ -262,22 +293,10 @@ function _NodeList<ID>(props: NodeListProps<ID>) {
262
293
 
263
294
  function HeaderFooterView() {
264
295
  return (
265
- <View style={styles.defaultHeaderFooter} />
296
+ <View style={{ padding: listHeaderFooterPadding }} />
266
297
  );
267
298
  }
268
299
 
269
- function getValue(
270
- isChecked: boolean,
271
- isIndeterminate: boolean
272
- ): CheckboxValueType {
273
- if (isIndeterminate) {
274
- return "indeterminate";
275
- } else if (isChecked) {
276
- return true;
277
- } else {
278
- return false;
279
- }
280
- }
281
300
 
282
301
  const Node = typedMemo(_Node);
283
302
  function _Node<ID>(props: NodeProps<ID>) {
@@ -315,7 +334,7 @@ function _Node<ID>(props: NodeProps<ID>) {
315
334
  } = useTreeViewStore<ID>(storeId)(useShallow(
316
335
  state => ({
317
336
  isExpanded: state.expanded.has(node.id),
318
- value: getValue(
337
+ value: getCheckboxValue(
319
338
  state.checked.has(node.id),
320
339
  state.indeterminate.has(node.id)
321
340
  ),
@@ -330,39 +349,53 @@ function _Node<ID>(props: NodeProps<ID>) {
330
349
  // Track when this node was dragged so we can swallow the onPress/onCheck
331
350
  // that fires when the user lifts their finger after a long-press-initiated drag.
332
351
  // The flag is set during render (synchronous) and cleared on the next touch start.
333
- const wasDraggedRef = React.useRef(false);
352
+ // It is also cleared via effect when dragging ends, to prevent stale `true`
353
+ // values surviving FlashList recycling (where refs persist across items).
354
+ const wasDraggedRef = useRef(false);
334
355
  if (isDraggingGlobal && isBeingDragged) {
335
356
  wasDraggedRef.current = true;
336
357
  }
337
358
 
338
- const _onToggleExpand = React.useCallback(() => {
359
+ useEffect(() => {
360
+ if (!isDraggingGlobal) {
361
+ wasDraggedRef.current = false;
362
+ }
363
+ }, [isDraggingGlobal]);
364
+
365
+ const _onToggleExpand = useCallback(() => {
339
366
  if (wasDraggedRef.current) return;
340
367
  handleToggleExpand(storeId, node.id);
341
368
  }, [storeId, node.id]);
342
369
 
343
- const _onCheck = React.useCallback(() => {
370
+ const _onCheck = useCallback(() => {
344
371
  if (wasDraggedRef.current) return;
345
372
  toggleCheckboxes(storeId, [node.id]);
346
373
  }, [storeId, node.id]);
347
374
 
348
- const handleTouchStart = React.useCallback((e: any) => {
375
+ const handleTouchStart = useCallback((e: any) => {
349
376
  wasDraggedRef.current = false;
350
377
  if (!onNodeTouchStart) return;
351
378
  const { pageY, locationY } = e.nativeEvent;
352
379
  onNodeTouchStart(node.id, pageY, locationY, nodeIndex);
353
380
  }, [node.id, nodeIndex, onNodeTouchStart]);
354
381
 
355
- const handleTouchEnd = React.useCallback(() => {
382
+ const handleTouchEnd = useCallback(() => {
356
383
  onNodeTouchEnd?.();
357
384
  }, [onNodeTouchEnd]);
358
385
 
359
- // Determine opacity for drag state
360
- const dragOpacity = dragDropCustomizations?.draggedNodeOpacity ?? 0.3;
361
- const nodeOpacity = (isDraggingGlobal && (isBeingDragged || isDragInvalid))
362
- ? dragOpacity
363
- : 1.0;
364
-
365
- const handleLayout = React.useCallback((e: any) => {
386
+ // Determine opacity for drag state (separate values for dragged node vs invalid targets).
387
+ // When CustomNodeRowComponent is used, hand off all visual control
388
+ // (including drag opacity) to the custom component — it receives
389
+ // isDraggedNode / isInvalidDropTarget / isDragging props.
390
+ const draggedOpacity = dragDropCustomizations?.draggedNodeOpacity ?? 0.3;
391
+ const invalidOpacity = dragDropCustomizations?.invalidTargetOpacity ?? 0.3;
392
+ const nodeOpacity = CustomNodeRowComponent
393
+ ? 1.0
394
+ : isDraggingGlobal
395
+ ? (isBeingDragged ? draggedOpacity : isDragInvalid ? invalidOpacity : 1.0)
396
+ : 1.0;
397
+
398
+ const handleLayout = useCallback((e: any) => {
366
399
  onItemLayout?.(e.nativeEvent.layout.height);
367
400
  }, [onItemLayout]);
368
401
 
@@ -416,7 +449,6 @@ function _Node<ID>(props: NodeProps<ID>) {
416
449
  else {
417
450
  return (
418
451
  <View
419
- {...touchHandlers}
420
452
  onLayout={onItemLayout ? handleLayout : undefined}
421
453
  style={[
422
454
  { opacity: nodeOpacity },
@@ -431,9 +463,12 @@ function _Node<ID>(props: NodeProps<ID>) {
431
463
  isExpanded={isExpanded}
432
464
  onCheck={_onCheck}
433
465
  onExpand={_onToggleExpand}
434
- isDragTarget={isDragInvalid}
466
+ isInvalidDropTarget={isDragInvalid}
467
+ isDropTarget={isDropTarget}
468
+ dropPosition={nodeDropPosition ?? undefined}
435
469
  isDragging={isDraggingGlobal}
436
470
  isDraggedNode={isBeingDragged}
471
+ dragHandleProps={touchHandlers}
437
472
  />
438
473
  </View>
439
474
  );
@@ -503,9 +538,6 @@ function NodeDropIndicator({ position, level, indentationMultiplier, styleProps
503
538
  }
504
539
 
505
540
  const styles = StyleSheet.create({
506
- defaultHeaderFooter: {
507
- padding: 5
508
- },
509
541
  nodeExpandableArrowTouchable: {
510
542
  flex: 1
511
543
  },
@@ -1 +1,4 @@
1
- export const defaultIndentationMultiplier = 15;
1
+ export const defaultIndentationMultiplier = 15;
2
+
3
+ /** Padding used by the FlashList header/footer component. Total header height = 2 * this value. */
4
+ export const listHeaderFooterPadding = 5;