react-native-tree-multi-select 3.0.0-beta.5 → 3.0.0-beta.7
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 +57 -26
- package/lib/module/TreeView.js +130 -24
- package/lib/module/TreeView.js.map +1 -1
- package/lib/module/components/DragOverlay.js +19 -2
- package/lib/module/components/DragOverlay.js.map +1 -1
- package/lib/module/components/NodeList.js +82 -29
- package/lib/module/components/NodeList.js.map +1 -1
- package/lib/module/constants/treeView.constants.js +5 -0
- package/lib/module/constants/treeView.constants.js.map +1 -1
- package/lib/module/helpers/moveTreeNode.helper.js +175 -47
- package/lib/module/helpers/moveTreeNode.helper.js.map +1 -1
- package/lib/module/helpers/toggleCheckbox.helper.js +5 -12
- package/lib/module/helpers/toggleCheckbox.helper.js.map +1 -1
- package/lib/module/helpers/treeNode.helper.js +49 -0
- package/lib/module/helpers/treeNode.helper.js.map +1 -1
- package/lib/module/hooks/useDragDrop.js +470 -186
- package/lib/module/hooks/useDragDrop.js.map +1 -1
- package/lib/module/hooks/useScrollToNode.js +17 -0
- package/lib/module/hooks/useScrollToNode.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/store/treeView.store.js +7 -0
- package/lib/module/store/treeView.store.js.map +1 -1
- package/lib/module/types/dragDrop.types.js +0 -2
- package/lib/typescript/src/TreeView.d.ts.map +1 -1
- package/lib/typescript/src/components/DragOverlay.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 +4 -0
- package/lib/typescript/src/constants/treeView.constants.d.ts.map +1 -1
- package/lib/typescript/src/helpers/moveTreeNode.helper.d.ts +32 -0
- package/lib/typescript/src/helpers/moveTreeNode.helper.d.ts.map +1 -1
- package/lib/typescript/src/helpers/toggleCheckbox.helper.d.ts.map +1 -1
- package/lib/typescript/src/helpers/treeNode.helper.d.ts +15 -0
- package/lib/typescript/src/helpers/treeNode.helper.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useDragDrop.d.ts +24 -6
- package/lib/typescript/src/hooks/useDragDrop.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useScrollToNode.d.ts +10 -0
- package/lib/typescript/src/hooks/useScrollToNode.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/store/treeView.store.d.ts +6 -0
- package/lib/typescript/src/store/treeView.store.d.ts.map +1 -1
- package/lib/typescript/src/types/dragDrop.types.d.ts +24 -12
- package/lib/typescript/src/types/dragDrop.types.d.ts.map +1 -1
- package/lib/typescript/src/types/treeView.types.d.ts +68 -12
- package/lib/typescript/src/types/treeView.types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/TreeView.tsx +158 -26
- package/src/components/DragOverlay.tsx +32 -3
- package/src/components/NodeList.tsx +82 -28
- package/src/constants/treeView.constants.ts +6 -1
- package/src/helpers/moveTreeNode.helper.ts +160 -43
- package/src/helpers/toggleCheckbox.helper.ts +5 -12
- package/src/helpers/treeNode.helper.ts +52 -1
- package/src/hooks/useDragDrop.ts +573 -214
- package/src/hooks/useScrollToNode.ts +21 -0
- package/src/index.tsx +3 -1
- package/src/store/treeView.store.ts +6 -0
- package/src/types/dragDrop.types.ts +25 -13
- package/src/types/treeView.types.ts +71 -11
- package/lib/module/components/DropIndicator.js +0 -79
- package/lib/module/components/DropIndicator.js.map +0 -1
- package/lib/typescript/src/components/DropIndicator.d.ts +0 -12
- package/lib/typescript/src/components/DropIndicator.d.ts.map +0 -1
- package/src/components/DropIndicator.tsx +0 -95
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { ComponentType, RefObject } from "react";
|
|
2
|
-
import type { StyleProp, TextProps, TouchableOpacityProps, ViewStyle } from "react-native";
|
|
2
|
+
import type { GestureResponderEvent, StyleProp, TextProps, TouchableOpacityProps, ViewStyle } from "react-native";
|
|
3
3
|
import type { FlashListProps } from "@shopify/flash-list";
|
|
4
4
|
import type { ScrollToNodeHandlerRef, ScrollToNodeParams } from "../hooks/useScrollToNode";
|
|
5
5
|
import type { CheckboxProps as _CheckboxProps } from "@futurejj/react-native-checkbox";
|
|
6
|
-
import type { DragCancelEvent, DragEndEvent, DragStartEvent, DropPosition } from "./dragDrop.types";
|
|
6
|
+
import type { DragCancelEvent, DragEndEvent, DragStartEvent, DropPosition, MoveResult } from "./dragDrop.types";
|
|
7
7
|
/** The tri-state value of a checkbox: checked, unchecked, or indeterminate */
|
|
8
8
|
export type CheckboxValueType = boolean | "indeterminate";
|
|
9
9
|
/** Props passed to a custom expand/collapse icon component */
|
|
@@ -60,12 +60,16 @@ export interface NodeRowProps<ID = string> {
|
|
|
60
60
|
}
|
|
61
61
|
/** Touch handlers to spread on a drag handle element within a custom node row */
|
|
62
62
|
export interface DragHandleProps {
|
|
63
|
-
|
|
63
|
+
/** Arms the long-press timer that initiates a drag. Fires on finger down. */
|
|
64
|
+
onTouchStart: (e: GestureResponderEvent) => void;
|
|
65
|
+
/** Cancels a pending long-press, or ends a drag the PanResponder never
|
|
66
|
+
* captured (finger lifted without movement). Fires on finger up. */
|
|
64
67
|
onTouchEnd: () => void;
|
|
68
|
+
/** Cancels a pending long-press when the system interrupts the touch. */
|
|
65
69
|
onTouchCancel: () => void;
|
|
66
70
|
}
|
|
67
71
|
/** Customization options for tree item appearance and behavior */
|
|
68
|
-
export interface TreeItemCustomizations<ID> {
|
|
72
|
+
export interface TreeItemCustomizations<ID = string> {
|
|
69
73
|
/** Style props for the built-in checkbox view */
|
|
70
74
|
checkBoxViewStyleProps?: BuiltInCheckBoxViewStyleProps;
|
|
71
75
|
/** Pixels of indentation per nesting level. Default: 15 */
|
|
@@ -80,7 +84,7 @@ export interface TreeItemCustomizations<ID> {
|
|
|
80
84
|
CustomNodeRowComponent?: ComponentType<NodeRowProps<ID>>;
|
|
81
85
|
}
|
|
82
86
|
/** Internal props for a single node in the list (extends TreeItemCustomizations) */
|
|
83
|
-
export interface NodeProps<ID> extends TreeItemCustomizations<ID> {
|
|
87
|
+
export interface NodeProps<ID = string> extends TreeItemCustomizations<ID> {
|
|
84
88
|
/** The flattened tree node data */
|
|
85
89
|
node: __FlattenedTreeNode__<ID>;
|
|
86
90
|
/** Nesting depth of this node */
|
|
@@ -97,8 +101,9 @@ export interface NodeProps<ID> extends TreeItemCustomizations<ID> {
|
|
|
97
101
|
onNodeTouchStart?: (nodeId: ID, pageY: number, locationY: number, nodeIndex: number) => void;
|
|
98
102
|
/** Callback when a touch ends on this node */
|
|
99
103
|
onNodeTouchEnd?: () => void;
|
|
100
|
-
/** Callback reporting this node's measured height
|
|
101
|
-
|
|
104
|
+
/** Callback reporting this node's measured height, keyed by its stable node id
|
|
105
|
+
* (used for accurate variable-height drop targeting) */
|
|
106
|
+
onItemLayout?: (id: ID, height: number) => void;
|
|
102
107
|
/** Customizations for drag-and-drop visuals */
|
|
103
108
|
dragDropCustomizations?: DragDropCustomizations<ID>;
|
|
104
109
|
}
|
|
@@ -126,8 +131,20 @@ export interface DragAndDropOptions<ID = string> {
|
|
|
126
131
|
autoScrollSpeed?: number;
|
|
127
132
|
/** Offset of the dragged overlay from the finger, in item-height units. Default: -2 (two items above finger) */
|
|
128
133
|
dragOverlayOffset?: number;
|
|
134
|
+
/** Advanced: extra vertical correction for the drag overlay, in item-height units,
|
|
135
|
+
* added on top of `dragOverlayOffset`. Compensates for Android reporting touch
|
|
136
|
+
* `locationY` differently from iOS. Default: -2 on Android, 0 on other platforms.
|
|
137
|
+
* Override only if the overlay sits noticeably off from the finger on a device. */
|
|
138
|
+
overlayYCorrection?: number;
|
|
129
139
|
/** Delay in ms before auto-expanding a collapsed node during drag hover. Default: 800 */
|
|
130
140
|
autoExpandDelay?: number;
|
|
141
|
+
/** Auto-expand a collapsed node while hovering "inside" it during a drag.
|
|
142
|
+
* Default: true. Set false to disable hover-to-expand entirely. */
|
|
143
|
+
autoExpand?: boolean;
|
|
144
|
+
/** Animate the drag overlay with a magnetic "snap" spring when the effective drop
|
|
145
|
+
* level changes. Default: true. Set false to keep the overlay tracking the level
|
|
146
|
+
* without the spring animation (e.g. for reduced-motion preferences). */
|
|
147
|
+
magneticSnap?: boolean;
|
|
131
148
|
/** Customizations for drag-and-drop visuals (overlay, indicator, opacity) */
|
|
132
149
|
customizations?: DragDropCustomizations<ID>;
|
|
133
150
|
/** Callback to determine if a node can be dropped on a specific target.
|
|
@@ -141,13 +158,14 @@ export interface DragAndDropOptions<ID = string> {
|
|
|
141
158
|
/** Callback to determine if a node can be dragged.
|
|
142
159
|
* Return false to prevent dragging this node. Default: all nodes are draggable. */
|
|
143
160
|
canDrag?: (node: TreeNode<ID>) => boolean;
|
|
144
|
-
/** Auto-scroll to the dropped node after a successful drop
|
|
161
|
+
/** Auto-scroll to the dropped node after a successful drop, if it ended up
|
|
162
|
+
* outside the viewport (no scroll when the node is already visible).
|
|
145
163
|
* Pass `false` to disable, `true` for defaults, or an object to customize.
|
|
146
164
|
* Default: `{ enabled: true, animated: true, viewPosition: 0.5 }` */
|
|
147
165
|
autoScrollToDroppedNode?: boolean | DropAutoScrollOptions;
|
|
148
166
|
}
|
|
149
167
|
/** Props for the NodeList component that renders the flattened tree */
|
|
150
|
-
export interface NodeListProps<ID> extends TreeItemCustomizations<ID> {
|
|
168
|
+
export interface NodeListProps<ID = string> extends TreeItemCustomizations<ID> {
|
|
151
169
|
/** Additional props passed to the underlying FlashList */
|
|
152
170
|
treeFlashListProps?: TreeFlatListProps;
|
|
153
171
|
/** Ref for programmatic scroll-to-node functionality */
|
|
@@ -231,9 +249,36 @@ export interface TreeViewRef<ID = string> {
|
|
|
231
249
|
scrollToNodeID: (scrollToNodeParams: ScrollToNodeParams<ID>) => void;
|
|
232
250
|
/** Get a map of child node IDs to their parent node IDs */
|
|
233
251
|
getChildToParentMap: () => Map<ID, ID>;
|
|
252
|
+
/** Get the current tree data held by the component (the original `data` prop
|
|
253
|
+
* before any move, or the reordered structure after a drag-and-drop / `moveNode`).
|
|
254
|
+
* Useful for reading the full tree without pushing it through the `onDragEnd` /
|
|
255
|
+
* `moveNode` move delta.
|
|
256
|
+
*
|
|
257
|
+
* NOTE: this returns a LIVE internal reference - treat it as read-only and do
|
|
258
|
+
* NOT mutate it in place. Mutating it desyncs the internal node maps and the
|
|
259
|
+
* reinit-skip diff. Clone it (e.g. with the exported `moveTreeNode`, or
|
|
260
|
+
* `structuredClone`) before modifying. */
|
|
261
|
+
getTreeData: () => TreeNode<ID>[];
|
|
234
262
|
/** Programmatically move a node to a new position in the tree.
|
|
235
|
-
* Works like a drag-and-drop but without user interaction.
|
|
236
|
-
|
|
263
|
+
* Works like a drag-and-drop but without user interaction.
|
|
264
|
+
* Returns a lightweight {@link MoveResult} describing the move, or `null` if it
|
|
265
|
+
* was a no-op / invalid (e.g. moving a node onto itself or into its own
|
|
266
|
+
* descendant, or - with `{ validate: true }` - blocked by `canDrop`,
|
|
267
|
+
* `maxDepth`, or `canNodeHaveChildren`).
|
|
268
|
+
*
|
|
269
|
+
* `{ validate: true }` enforces the `canDrop` / `maxDepth` / `canNodeHaveChildren`
|
|
270
|
+
* rules - but those rules live on the `dragAndDrop` prop, so validation only runs
|
|
271
|
+
* when a `dragAndDrop` prop is configured. Without it, `validate` is ignored (in
|
|
272
|
+
* dev a warning is logged) and the move proceeds subject only to the structural
|
|
273
|
+
* no-op checks.
|
|
274
|
+
*
|
|
275
|
+
* `{ scrollToNode: true }` scrolls the moved node into view after the move (the
|
|
276
|
+
* interactive drag does this automatically; programmatic moves do not by default).
|
|
277
|
+
* Pass a {@link DropAutoScrollOptions} object to customize the scroll. */
|
|
278
|
+
moveNode: (nodeId: ID, targetNodeId: ID, position: DropPosition, options?: {
|
|
279
|
+
validate?: boolean;
|
|
280
|
+
scrollToNode?: boolean | DropAutoScrollOptions;
|
|
281
|
+
}) => MoveResult<ID> | null;
|
|
237
282
|
}
|
|
238
283
|
/** Controls how checkbox selection propagates through the tree hierarchy */
|
|
239
284
|
export interface SelectionPropagation {
|
|
@@ -263,6 +308,10 @@ export interface DropIndicatorStyleProps {
|
|
|
263
308
|
highlightColor?: string;
|
|
264
309
|
/** Border color of the "inside" highlight. Default: "rgba(0, 120, 255, 0.5)" */
|
|
265
310
|
highlightBorderColor?: string;
|
|
311
|
+
/** Border width of the "inside" highlight box. Default: 2 */
|
|
312
|
+
highlightBorderWidth?: number;
|
|
313
|
+
/** Corner radius of the "inside" highlight box. Default: 4 */
|
|
314
|
+
highlightBorderRadius?: number;
|
|
266
315
|
}
|
|
267
316
|
/** Style props for customizing the drag overlay (the "lifted" node ghost) */
|
|
268
317
|
export interface DragOverlayStyleProps {
|
|
@@ -270,12 +319,19 @@ export interface DragOverlayStyleProps {
|
|
|
270
319
|
backgroundColor?: string;
|
|
271
320
|
/** Shadow color. Default: "#000" */
|
|
272
321
|
shadowColor?: string;
|
|
322
|
+
/** Shadow offset (iOS). Default: { width: 0, height: 2 } */
|
|
323
|
+
shadowOffset?: {
|
|
324
|
+
width: number;
|
|
325
|
+
height: number;
|
|
326
|
+
};
|
|
273
327
|
/** Shadow opacity. Default: 0.25 */
|
|
274
328
|
shadowOpacity?: number;
|
|
275
329
|
/** Shadow radius. Default: 4 */
|
|
276
330
|
shadowRadius?: number;
|
|
277
331
|
/** Android elevation. Default: 10 */
|
|
278
332
|
elevation?: number;
|
|
333
|
+
/** Stacking order of the overlay. Default: 9999 */
|
|
334
|
+
zIndex?: number;
|
|
279
335
|
/** Custom style applied to the overlay container */
|
|
280
336
|
style?: StyleProp<ViewStyle>;
|
|
281
337
|
}
|
|
@@ -297,7 +353,7 @@ export interface DragDropCustomizations<ID = string> {
|
|
|
297
353
|
/** Props passed to a custom drag overlay component */
|
|
298
354
|
export interface DragOverlayComponentProps<ID = string> {
|
|
299
355
|
/** The node being dragged */
|
|
300
|
-
node:
|
|
356
|
+
node: TreeNode<ID>;
|
|
301
357
|
/** The nesting level of the dragged node */
|
|
302
358
|
level: number;
|
|
303
359
|
/** The current checkbox value of the dragged node */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"treeView.types.d.ts","sourceRoot":"","sources":["../../../../src/types/treeView.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACjD,OAAO,KAAK,EACR,SAAS,EACT,SAAS,EACT,qBAAqB,EACrB,SAAS,EACZ,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EACR,sBAAsB,EACtB,kBAAkB,EACrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EACR,aAAa,IAAI,cAAc,EAClC,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"treeView.types.d.ts","sourceRoot":"","sources":["../../../../src/types/treeView.types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-tree-multi-select",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.7",
|
|
4
4
|
"description": "Super-fast, customizable tree view component for React Native with drag-and-drop reordering, multi-selection, and search filtering.",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
"./src/jest.setup.ts"
|
|
146
146
|
],
|
|
147
147
|
"transformIgnorePatterns": [
|
|
148
|
-
"node_modules/(?!(jest-)?react-native|@react-native-community|@react-navigation|@react-native/js-polyfills)"
|
|
148
|
+
"node_modules/(?!(jest-)?react-native|@react-native-community|@react-navigation|@react-native/js-polyfills|fast-is-equal)"
|
|
149
149
|
],
|
|
150
150
|
"modulePathIgnorePatterns": [
|
|
151
151
|
"<rootDir>/example/node_modules",
|
package/src/TreeView.tsx
CHANGED
|
@@ -5,10 +5,12 @@ import {
|
|
|
5
5
|
useEffect,
|
|
6
6
|
useId,
|
|
7
7
|
useImperativeHandle,
|
|
8
|
+
useMemo,
|
|
8
9
|
useRef,
|
|
9
10
|
type ForwardedRef,
|
|
10
11
|
} from "react";
|
|
11
12
|
import type {
|
|
13
|
+
DropAutoScrollOptions,
|
|
12
14
|
TreeNode,
|
|
13
15
|
TreeViewProps,
|
|
14
16
|
TreeViewRef
|
|
@@ -25,19 +27,24 @@ import {
|
|
|
25
27
|
toggleCheckboxes,
|
|
26
28
|
expandNodes,
|
|
27
29
|
collapseNodes,
|
|
28
|
-
recalculateCheckedStates,
|
|
29
30
|
moveTreeNode,
|
|
31
|
+
applyMoveToStore,
|
|
32
|
+
findNodePosition,
|
|
33
|
+
findNodePositionFromMaps,
|
|
34
|
+
getSubtreeDepthFromMap,
|
|
35
|
+
getNodeDepthFromParentMap,
|
|
30
36
|
} from "./helpers";
|
|
31
37
|
import { deleteTreeViewStore, getTreeViewStore, useTreeViewStore } from "./store/treeView.store";
|
|
32
38
|
import usePreviousState from "./utils/usePreviousState";
|
|
33
39
|
import { useShallow } from "zustand/react/shallow";
|
|
34
40
|
import useDeepCompareEffect from "./utils/useDeepCompareEffect";
|
|
35
41
|
import { typedMemo } from "./utils/typedMemo";
|
|
36
|
-
import
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
import {
|
|
43
|
+
scrollMovedNodeIntoView,
|
|
44
|
+
type ScrollToNodeHandlerRef,
|
|
45
|
+
type ScrollToNodeParams
|
|
39
46
|
} from "./hooks/useScrollToNode";
|
|
40
|
-
import type { DragEndEvent, DropPosition } from "./types/dragDrop.types";
|
|
47
|
+
import type { DragEndEvent, DropPosition, MoveResult } from "./types/dragDrop.types";
|
|
41
48
|
import { fastIsEqual } from "fast-is-equal";
|
|
42
49
|
|
|
43
50
|
function _innerTreeView<ID>(
|
|
@@ -93,6 +100,8 @@ function _innerTreeView<ID>(
|
|
|
93
100
|
setSelectionPropagation,
|
|
94
101
|
|
|
95
102
|
cleanUpTreeViewStore,
|
|
103
|
+
|
|
104
|
+
draggedNodeId,
|
|
96
105
|
} = useTreeViewStore<ID>(storeId)(useShallow(
|
|
97
106
|
state => ({
|
|
98
107
|
expanded: state.expanded,
|
|
@@ -112,6 +121,8 @@ function _innerTreeView<ID>(
|
|
|
112
121
|
setSelectionPropagation: state.setSelectionPropagation,
|
|
113
122
|
|
|
114
123
|
cleanUpTreeViewStore: state.cleanUpTreeViewStore,
|
|
124
|
+
|
|
125
|
+
draggedNodeId: state.draggedNodeId,
|
|
115
126
|
})
|
|
116
127
|
));
|
|
117
128
|
|
|
@@ -137,22 +148,38 @@ function _innerTreeView<ID>(
|
|
|
137
148
|
|
|
138
149
|
getChildToParentMap,
|
|
139
150
|
|
|
151
|
+
getTreeData,
|
|
152
|
+
|
|
140
153
|
moveNode,
|
|
141
154
|
}));
|
|
142
155
|
|
|
143
156
|
const scrollToNodeHandlerRef = useRef<ScrollToNodeHandlerRef<ID>>(null);
|
|
144
157
|
const prevSearchText = usePreviousState(searchText);
|
|
145
158
|
const internalDataRef = useRef<TreeNode<ID>[] | null>(null);
|
|
146
|
-
|
|
147
|
-
//
|
|
159
|
+
// Holds a `data` prop change that arrived mid-drag; applied once the drag ends so
|
|
160
|
+
// the destructive reinit never swaps the node maps out from under an active drag.
|
|
161
|
+
const pendingDataRef = useRef<TreeNode<ID>[] | null>(null);
|
|
162
|
+
|
|
163
|
+
// Wrap onDragEnd to capture the post-move tree before calling the consumer's
|
|
164
|
+
// callback. The reordered tree lives in the store (the event only carries the
|
|
165
|
+
// lightweight move delta); snapshotting it here lets a controlled consumer feed
|
|
166
|
+
// an equal tree back into `data` and skip re-initialization.
|
|
148
167
|
const wrappedOnDragEnd = useCallback((event: DragEndEvent<ID>) => {
|
|
149
|
-
internalDataRef.current =
|
|
168
|
+
internalDataRef.current = getTreeViewStore<ID>(storeId).getState().initialTreeViewData;
|
|
169
|
+
// A `data` change deferred during this drag predates the move that just
|
|
170
|
+
// committed; applying it would silently undo the drop after onDragEnd
|
|
171
|
+
// already told the consumer it happened. Discard it - a controlled
|
|
172
|
+
// consumer reacts to onDragEnd with fresh data anyway.
|
|
173
|
+
pendingDataRef.current = null;
|
|
150
174
|
onDragEnd?.(event);
|
|
151
|
-
}, [onDragEnd]);
|
|
175
|
+
}, [onDragEnd, storeId]);
|
|
152
176
|
|
|
153
|
-
|
|
177
|
+
// Reinitialize the store from a tree. Held in a ref so the value stays stable
|
|
178
|
+
// (no dep churn) while always capturing the latest props/store actions.
|
|
179
|
+
const applyDataRef = useRef<(nextData: TreeNode<ID>[]) => void>(() => { });
|
|
180
|
+
applyDataRef.current = (nextData: TreeNode<ID>[]) => {
|
|
154
181
|
// If data matches what was set internally from a drag-drop, skip reinitialize
|
|
155
|
-
if (internalDataRef.current !== null && fastIsEqual(
|
|
182
|
+
if (internalDataRef.current !== null && fastIsEqual(nextData, internalDataRef.current)) {
|
|
156
183
|
internalDataRef.current = null;
|
|
157
184
|
return;
|
|
158
185
|
}
|
|
@@ -160,12 +187,12 @@ function _innerTreeView<ID>(
|
|
|
160
187
|
|
|
161
188
|
cleanUpTreeViewStore();
|
|
162
189
|
|
|
163
|
-
updateInitialTreeViewData(
|
|
190
|
+
updateInitialTreeViewData(nextData);
|
|
164
191
|
|
|
165
192
|
if (selectionPropagation)
|
|
166
193
|
setSelectionPropagation(selectionPropagation);
|
|
167
194
|
|
|
168
|
-
initializeNodeMaps(storeId,
|
|
195
|
+
initializeNodeMaps(storeId, nextData);
|
|
169
196
|
|
|
170
197
|
// Check any pre-selected nodes
|
|
171
198
|
toggleCheckboxes(storeId, preselectedIds, true);
|
|
@@ -175,8 +202,27 @@ function _innerTreeView<ID>(
|
|
|
175
202
|
...preExpandedIds,
|
|
176
203
|
...(initialScrollNodeID ? [initialScrollNodeID] : [])
|
|
177
204
|
]);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
useDeepCompareEffect(() => {
|
|
208
|
+
// A reinit while a drag is in flight would replace nodeMap/childToParentMap
|
|
209
|
+
// under the drag's feet and corrupt the commit. Defer it until the drag ends.
|
|
210
|
+
if (getTreeViewStore<ID>(storeId).getState().draggedNodeId !== null) {
|
|
211
|
+
pendingDataRef.current = data;
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
applyDataRef.current(data);
|
|
178
215
|
}, [data]);
|
|
179
216
|
|
|
217
|
+
// Apply any data change that was deferred during a drag, once the drag ends.
|
|
218
|
+
useEffect(() => {
|
|
219
|
+
if (draggedNodeId === null && pendingDataRef.current !== null) {
|
|
220
|
+
const pending = pendingDataRef.current;
|
|
221
|
+
pendingDataRef.current = null;
|
|
222
|
+
applyDataRef.current(pending);
|
|
223
|
+
}
|
|
224
|
+
}, [draggedNodeId]);
|
|
225
|
+
|
|
180
226
|
function selectNodes(ids: ID[]) {
|
|
181
227
|
toggleCheckboxes(storeId, ids, true);
|
|
182
228
|
}
|
|
@@ -199,21 +245,93 @@ function _innerTreeView<ID>(
|
|
|
199
245
|
return treeViewStore.getState().childToParentMap;
|
|
200
246
|
}
|
|
201
247
|
|
|
202
|
-
function
|
|
248
|
+
function getTreeData() {
|
|
249
|
+
return getTreeViewStore<ID>(storeId).getState().initialTreeViewData;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function moveNode(
|
|
253
|
+
nodeId: ID,
|
|
254
|
+
targetNodeId: ID,
|
|
255
|
+
position: DropPosition,
|
|
256
|
+
options?: { validate?: boolean; scrollToNode?: boolean | DropAutoScrollOptions; }
|
|
257
|
+
): MoveResult<ID> | null {
|
|
203
258
|
const store = getTreeViewStore<ID>(storeId);
|
|
204
|
-
const currentData
|
|
205
|
-
|
|
259
|
+
const { initialTreeViewData: currentData, nodeMap, childToParentMap }
|
|
260
|
+
= store.getState();
|
|
261
|
+
|
|
262
|
+
// A programmatic move during an in-flight drag would reinitialize
|
|
263
|
+
// nodeMap/childToParentMap under the drag's feet and corrupt the pending
|
|
264
|
+
// commit (same guard as the deferred data-prop reinit above).
|
|
265
|
+
if (store.getState().draggedNodeId !== null) {
|
|
266
|
+
if (__DEV__) {
|
|
267
|
+
console.warn(
|
|
268
|
+
"[react-native-tree-multi-select] moveNode() ignored: a drag is in progress."
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
206
273
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
274
|
+
// Validation rules (canDrop / maxDepth / canNodeHaveChildren) live on the
|
|
275
|
+
// dragAndDrop prop, so `validate` only has rules to enforce when that prop is
|
|
276
|
+
// configured. Warn in dev if the caller asked to validate but nothing can.
|
|
277
|
+
if (__DEV__ && options?.validate
|
|
278
|
+
&& !(dragAndDrop?.canDrop || dragAndDrop?.maxDepth !== undefined || dragAndDrop?.canNodeHaveChildren)) {
|
|
279
|
+
console.warn(
|
|
280
|
+
"[react-native-tree-multi-select] moveNode({ validate: true }) was called, "
|
|
281
|
+
+ "but no validation rules are configured. canDrop / maxDepth / "
|
|
282
|
+
+ "canNodeHaveChildren are read from the `dragAndDrop` prop; without them the "
|
|
283
|
+
+ "move proceeds unvalidated."
|
|
284
|
+
);
|
|
285
|
+
}
|
|
210
286
|
|
|
211
|
-
|
|
212
|
-
|
|
287
|
+
// Optional validation mirrors the interactive drag constraints so a
|
|
288
|
+
// programmatic move can't silently build a tree the drag UI would reject.
|
|
289
|
+
if (options?.validate && dragAndDrop) {
|
|
290
|
+
const draggedNode = nodeMap.get(nodeId);
|
|
291
|
+
const targetNode = nodeMap.get(targetNodeId);
|
|
292
|
+
if (!draggedNode || !targetNode) return null;
|
|
293
|
+
if (position === "inside"
|
|
294
|
+
&& dragAndDrop.canNodeHaveChildren
|
|
295
|
+
&& !dragAndDrop.canNodeHaveChildren(targetNode)) return null;
|
|
296
|
+
if (dragAndDrop.canDrop
|
|
297
|
+
&& !dragAndDrop.canDrop(draggedNode, targetNode, position)) return null;
|
|
298
|
+
if (dragAndDrop.maxDepth !== undefined) {
|
|
299
|
+
const targetLevel = getNodeDepthFromParentMap(childToParentMap, targetNodeId);
|
|
300
|
+
const subtreeDepth = getSubtreeDepthFromMap(nodeMap, nodeId);
|
|
301
|
+
const baseLevel = position === "inside" ? targetLevel + 1 : targetLevel;
|
|
302
|
+
if (baseLevel + subtreeDepth > dragAndDrop.maxDepth) return null;
|
|
303
|
+
}
|
|
213
304
|
}
|
|
214
|
-
|
|
305
|
+
|
|
306
|
+
// The maps still describe the pre-move tree here, so the O(depth) lookup applies.
|
|
307
|
+
const previousPosition =
|
|
308
|
+
findNodePositionFromMaps(currentData, nodeMap, childToParentMap, nodeId);
|
|
309
|
+
const newData = moveTreeNode(currentData, nodeId, targetNodeId, position);
|
|
310
|
+
// moveTreeNode returns the original array reference on a no-op / invalid move
|
|
311
|
+
// (same node, dropping into own descendant, or node/target not found).
|
|
312
|
+
if (newData === currentData) return null;
|
|
313
|
+
|
|
314
|
+
// Same commit pipeline as the interactive drag path.
|
|
315
|
+
applyMoveToStore(storeId, newData, nodeId, targetNodeId, position);
|
|
215
316
|
|
|
216
317
|
internalDataRef.current = newData;
|
|
318
|
+
|
|
319
|
+
// Optionally scroll the moved node into view (the interactive drag does this
|
|
320
|
+
// automatically; programmatic moves opt in). Deferred so the expand/render settles.
|
|
321
|
+
if (options?.scrollToNode) {
|
|
322
|
+
scrollMovedNodeIntoView(scrollToNodeHandlerRef, nodeId, options.scrollToNode);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const newPosition = findNodePosition(newData, nodeId);
|
|
326
|
+
return {
|
|
327
|
+
draggedNodeId: nodeId,
|
|
328
|
+
targetNodeId,
|
|
329
|
+
position,
|
|
330
|
+
previousParentId: previousPosition?.parentId ?? null,
|
|
331
|
+
previousIndex: previousPosition?.index ?? -1,
|
|
332
|
+
newParentId: newPosition?.parentId ?? null,
|
|
333
|
+
newIndex: newPosition?.index ?? -1,
|
|
334
|
+
};
|
|
217
335
|
}
|
|
218
336
|
|
|
219
337
|
const getIds = useCallback((node: TreeNode<ID>): ID[] => {
|
|
@@ -262,6 +380,23 @@ function _innerTreeView<ID>(
|
|
|
262
380
|
};
|
|
263
381
|
}, [cleanUpTreeViewStore, storeId]);
|
|
264
382
|
|
|
383
|
+
// Consumers routinely pass `dragAndDrop` as an inline object literal, so its
|
|
384
|
+
// identity changes every render even when nothing differs. Stabilize by deep
|
|
385
|
+
// equality (callbacks compare by reference) so NodeList's memo - and with it
|
|
386
|
+
// the drag overlay - isn't churned by unrelated re-renders mid-drag.
|
|
387
|
+
const stableDragAndDropRef = useRef(dragAndDrop);
|
|
388
|
+
if (!fastIsEqual(stableDragAndDropRef.current, dragAndDrop)) {
|
|
389
|
+
stableDragAndDropRef.current = dragAndDrop;
|
|
390
|
+
}
|
|
391
|
+
const stableDragAndDrop = stableDragAndDropRef.current;
|
|
392
|
+
const dragAndDropWithWrappedEnd = useMemo(
|
|
393
|
+
() => stableDragAndDrop && {
|
|
394
|
+
...stableDragAndDrop,
|
|
395
|
+
onDragEnd: wrappedOnDragEnd,
|
|
396
|
+
},
|
|
397
|
+
[stableDragAndDrop, wrappedOnDragEnd]
|
|
398
|
+
);
|
|
399
|
+
|
|
265
400
|
return (
|
|
266
401
|
<NodeList
|
|
267
402
|
storeId={storeId}
|
|
@@ -279,10 +414,7 @@ function _innerTreeView<ID>(
|
|
|
279
414
|
|
|
280
415
|
CustomNodeRowComponent={CustomNodeRowComponent}
|
|
281
416
|
|
|
282
|
-
dragAndDrop={
|
|
283
|
-
...dragAndDrop,
|
|
284
|
-
onDragEnd: wrappedOnDragEnd,
|
|
285
|
-
}}
|
|
417
|
+
dragAndDrop={dragAndDropWithWrappedEnd}
|
|
286
418
|
/>
|
|
287
419
|
);
|
|
288
420
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { type ComponentType } from "react";
|
|
1
|
+
import { memo, type ComponentType } from "react";
|
|
2
2
|
import { Animated, StyleSheet, View } from "react-native";
|
|
3
|
+
import { fastIsEqual } from "fast-is-equal";
|
|
3
4
|
|
|
4
5
|
import type {
|
|
5
6
|
__FlattenedTreeNode__,
|
|
@@ -12,7 +13,6 @@ import { CustomExpandCollapseIcon } from "./CustomExpandCollapseIcon";
|
|
|
12
13
|
import { defaultIndentationMultiplier } from "../constants/treeView.constants";
|
|
13
14
|
import { getTreeViewStore } from "../store/treeView.store";
|
|
14
15
|
import { getCheckboxValue } from "../helpers";
|
|
15
|
-
import { typedMemo } from "../utils/typedMemo";
|
|
16
16
|
|
|
17
17
|
interface DragOverlayProps<ID> extends TreeItemCustomizations<ID> {
|
|
18
18
|
storeId: string;
|
|
@@ -54,9 +54,11 @@ function _DragOverlay<ID>(props: DragOverlayProps<ID>) {
|
|
|
54
54
|
overlayStyleProps && {
|
|
55
55
|
...(overlayStyleProps.backgroundColor != null && { backgroundColor: overlayStyleProps.backgroundColor }),
|
|
56
56
|
...(overlayStyleProps.shadowColor != null && { shadowColor: overlayStyleProps.shadowColor }),
|
|
57
|
+
...(overlayStyleProps.shadowOffset != null && { shadowOffset: overlayStyleProps.shadowOffset }),
|
|
57
58
|
...(overlayStyleProps.shadowOpacity != null && { shadowOpacity: overlayStyleProps.shadowOpacity }),
|
|
58
59
|
...(overlayStyleProps.shadowRadius != null && { shadowRadius: overlayStyleProps.shadowRadius }),
|
|
59
60
|
...(overlayStyleProps.elevation != null && { elevation: overlayStyleProps.elevation }),
|
|
61
|
+
...(overlayStyleProps.zIndex != null && { zIndex: overlayStyleProps.zIndex }),
|
|
60
62
|
},
|
|
61
63
|
overlayStyleProps?.style,
|
|
62
64
|
{ transform: [{ translateX: overlayX }, { translateY: overlayY }] },
|
|
@@ -100,7 +102,34 @@ function _DragOverlay<ID>(props: DragOverlayProps<ID>) {
|
|
|
100
102
|
);
|
|
101
103
|
}
|
|
102
104
|
|
|
103
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Re-rendering the overlay mid-drag makes React Native re-bind the Animated
|
|
107
|
+
* transform, which can flash the overlay at its un-translated position for a
|
|
108
|
+
* frame (visible when e.g. auto-expand re-renders the tree while dragging).
|
|
109
|
+
* Nothing the overlay renders can change mid-drag, so block re-renders unless
|
|
110
|
+
* a prop meaningfully differs - style/customization objects are compared by
|
|
111
|
+
* value because consumers routinely pass fresh (but equal) literals per render.
|
|
112
|
+
*/
|
|
113
|
+
const overlayPropsAreEqual = <ID,>(
|
|
114
|
+
prev: DragOverlayProps<ID>,
|
|
115
|
+
next: DragOverlayProps<ID>
|
|
116
|
+
) =>
|
|
117
|
+
prev.storeId === next.storeId &&
|
|
118
|
+
prev.overlayY === next.overlayY &&
|
|
119
|
+
prev.overlayX === next.overlayX &&
|
|
120
|
+
prev.node === next.node &&
|
|
121
|
+
prev.level === next.level &&
|
|
122
|
+
prev.indentationMultiplier === next.indentationMultiplier &&
|
|
123
|
+
prev.CheckboxComponent === next.CheckboxComponent &&
|
|
124
|
+
prev.ExpandCollapseIconComponent === next.ExpandCollapseIconComponent &&
|
|
125
|
+
prev.CustomNodeRowComponent === next.CustomNodeRowComponent &&
|
|
126
|
+
fastIsEqual(prev.checkBoxViewStyleProps, next.checkBoxViewStyleProps) &&
|
|
127
|
+
fastIsEqual(prev.dragDropCustomizations, next.dragDropCustomizations);
|
|
128
|
+
|
|
129
|
+
export const DragOverlay = memo(
|
|
130
|
+
_DragOverlay,
|
|
131
|
+
overlayPropsAreEqual
|
|
132
|
+
) as typeof _DragOverlay;
|
|
104
133
|
|
|
105
134
|
const styles = StyleSheet.create({
|
|
106
135
|
overlay: {
|