react-native-reanimated-dnd 1.0.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 (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +633 -0
  3. package/lib/components/Draggable.d.ts +5 -0
  4. package/lib/components/Draggable.js +265 -0
  5. package/lib/components/Droppable.d.ts +264 -0
  6. package/lib/components/Droppable.js +284 -0
  7. package/lib/components/Sortable.d.ts +184 -0
  8. package/lib/components/Sortable.js +225 -0
  9. package/lib/components/SortableItem.d.ts +158 -0
  10. package/lib/components/SortableItem.js +251 -0
  11. package/lib/components/sortableUtils.d.ts +21 -0
  12. package/lib/components/sortableUtils.js +50 -0
  13. package/lib/context/DropContext.d.ts +118 -0
  14. package/lib/context/DropContext.js +233 -0
  15. package/lib/hooks/index.d.ts +4 -0
  16. package/lib/hooks/index.js +5 -0
  17. package/lib/hooks/useDraggable.d.ts +101 -0
  18. package/lib/hooks/useDraggable.js +567 -0
  19. package/lib/hooks/useDroppable.d.ts +129 -0
  20. package/lib/hooks/useDroppable.js +261 -0
  21. package/lib/hooks/useSortable.d.ts +174 -0
  22. package/lib/hooks/useSortable.js +361 -0
  23. package/lib/hooks/useSortableList.d.ts +182 -0
  24. package/lib/hooks/useSortableList.js +211 -0
  25. package/lib/index.d.ts +11 -0
  26. package/lib/index.js +16 -0
  27. package/lib/types/context.d.ts +166 -0
  28. package/lib/types/context.js +80 -0
  29. package/lib/types/draggable.d.ts +313 -0
  30. package/lib/types/draggable.js +31 -0
  31. package/lib/types/droppable.d.ts +197 -0
  32. package/lib/types/droppable.js +1 -0
  33. package/lib/types/index.d.ts +4 -0
  34. package/lib/types/index.js +8 -0
  35. package/lib/types/sortable.d.ts +432 -0
  36. package/lib/types/sortable.js +6 -0
  37. package/package.json +59 -0
@@ -0,0 +1,233 @@
1
+ // context/DropContext.tsx
2
+ import React, { useRef, useState, useMemo, useCallback, forwardRef, useImperativeHandle, useEffect, } from "react";
3
+ import { SlotsContext, } from "../types/context";
4
+ /**
5
+ * Provider component that enables drag-and-drop functionality for its children.
6
+ *
7
+ * The DropProvider creates the context necessary for draggable and droppable components
8
+ * to communicate with each other. It manages the registration of drop zones, tracks
9
+ * active hover states, handles collision detection, and maintains the state of dropped items.
10
+ *
11
+ * @example
12
+ * Basic setup:
13
+ * ```typescript
14
+ * import { DropProvider } from './context/DropContext';
15
+ * import { Draggable, Droppable } from './components';
16
+ *
17
+ * function App() {
18
+ * return (
19
+ * <DropProvider>
20
+ * <View style={styles.container}>
21
+ * <Draggable data={{ id: '1', name: 'Item 1' }}>
22
+ * <Text>Drag me!</Text>
23
+ * </Draggable>
24
+ *
25
+ * <Droppable onDrop={(data) => console.log('Dropped:', data)}>
26
+ * <Text>Drop zone</Text>
27
+ * </Droppable>
28
+ * </View>
29
+ * </DropProvider>
30
+ * );
31
+ * }
32
+ * ```
33
+ *
34
+ * @example
35
+ * With callbacks and ref:
36
+ * ```typescript
37
+ * function AdvancedApp() {
38
+ * const dropProviderRef = useRef<DropProviderRef>(null);
39
+ * const [droppedItems, setDroppedItems] = useState({});
40
+ *
41
+ * const handleLayoutChange = () => {
42
+ * // Trigger position update after layout changes
43
+ * dropProviderRef.current?.requestPositionUpdate();
44
+ * };
45
+ *
46
+ * return (
47
+ * <DropProvider
48
+ * ref={dropProviderRef}
49
+ * onDroppedItemsUpdate={setDroppedItems}
50
+ * onDragStart={(data) => console.log('Drag started:', data)}
51
+ * onDragEnd={(data) => console.log('Drag ended:', data)}
52
+ * onDragging={({ x, y, itemData }) => {
53
+ * console.log(`${itemData.name} at (${x}, ${y})`);
54
+ * }}
55
+ * >
56
+ * <ScrollView onLayout={handleLayoutChange}>
57
+ * {/* Your draggable and droppable components *\/}
58
+ * </ScrollView>
59
+ * </DropProvider>
60
+ * );
61
+ * }
62
+ * ```
63
+ *
64
+ * @example
65
+ * Multiple drop zones with capacity:
66
+ * ```typescript
67
+ * function TaskBoard() {
68
+ * const [tasks, setTasks] = useState(initialTasks);
69
+ *
70
+ * return (
71
+ * <DropProvider
72
+ * onDroppedItemsUpdate={(dropped) => {
73
+ * // Update task positions based on drops
74
+ * updateTaskPositions(dropped);
75
+ * }}
76
+ * >
77
+ * <View style={styles.board}>
78
+ * {tasks.map(task => (
79
+ * <Draggable key={task.id} data={task}>
80
+ * <TaskCard task={task} />
81
+ * </Draggable>
82
+ * ))}
83
+ *
84
+ * <Droppable
85
+ * droppableId="todo"
86
+ * capacity={10}
87
+ * onDrop={(task) => moveTask(task.id, 'todo')}
88
+ * >
89
+ * <Column title="To Do" />
90
+ * </Droppable>
91
+ *
92
+ * <Droppable
93
+ * droppableId="in-progress"
94
+ * capacity={5}
95
+ * onDrop={(task) => moveTask(task.id, 'in-progress')}
96
+ * >
97
+ * <Column title="In Progress" />
98
+ * </Droppable>
99
+ *
100
+ * <Droppable
101
+ * droppableId="done"
102
+ * onDrop={(task) => moveTask(task.id, 'done')}
103
+ * >
104
+ * <Column title="Done" />
105
+ * </Droppable>
106
+ * </View>
107
+ * </DropProvider>
108
+ * );
109
+ * }
110
+ * ```
111
+ *
112
+ * @see {@link Draggable} for draggable components
113
+ * @see {@link Droppable} for droppable components
114
+ * @see {@link useDraggable} for draggable hook
115
+ * @see {@link useDroppable} for droppable hook
116
+ * @see {@link DropProviderRef} for imperative handle interface
117
+ * @see {@link DroppedItemsMap} for dropped items data structure
118
+ */
119
+ // The DropProvider component, now forwardRef
120
+ export const DropProvider = forwardRef(({ children, onLayoutUpdateComplete, onDroppedItemsUpdate, onDragging, onDragStart, onDragEnd, }, ref) => {
121
+ const slotsRef = useRef({});
122
+ const [activeHoverSlotId, setActiveHoverSlotIdState] = useState(null);
123
+ // New state for tracking dropped items
124
+ const [droppedItems, setDroppedItems] = useState({});
125
+ const positionUpdateListenersRef = useRef({});
126
+ const registerPositionUpdateListener = useCallback((id, listener) => {
127
+ positionUpdateListenersRef.current[id] = listener;
128
+ }, []);
129
+ const unregisterPositionUpdateListener = useCallback((id) => {
130
+ delete positionUpdateListenersRef.current[id];
131
+ }, []);
132
+ // Call the update callback whenever droppedItems changes
133
+ useEffect(() => {
134
+ if (onDroppedItemsUpdate) {
135
+ onDroppedItemsUpdate(droppedItems);
136
+ }
137
+ }, [droppedItems, onDroppedItemsUpdate]);
138
+ // Update method to use string IDs
139
+ const registerDroppedItem = useCallback((draggableId, droppableId, itemData) => {
140
+ setDroppedItems((prev) => ({
141
+ ...prev,
142
+ [draggableId]: {
143
+ droppableId,
144
+ data: itemData,
145
+ },
146
+ }));
147
+ }, []);
148
+ const unregisterDroppedItem = useCallback((draggableId) => {
149
+ setDroppedItems((prev) => {
150
+ const newItems = { ...prev };
151
+ delete newItems[draggableId];
152
+ return newItems;
153
+ });
154
+ }, []);
155
+ const getDroppedItems = useCallback(() => {
156
+ return droppedItems;
157
+ }, [droppedItems]);
158
+ // This is the actual function that does the work
159
+ const internalRequestPositionUpdate = useCallback(() => {
160
+ const listeners = positionUpdateListenersRef.current;
161
+ Object.values(listeners).forEach((listener) => {
162
+ listener();
163
+ });
164
+ onLayoutUpdateComplete === null || onLayoutUpdateComplete === void 0 ? void 0 : onLayoutUpdateComplete();
165
+ }, [onLayoutUpdateComplete]);
166
+ // Expose requestPositionUpdate and getDroppedItems via ref
167
+ useImperativeHandle(ref, () => ({
168
+ requestPositionUpdate: internalRequestPositionUpdate,
169
+ getDroppedItems,
170
+ }));
171
+ // Add a method to check if a droppable has capacity available
172
+ const hasAvailableCapacity = useCallback((droppableId) => {
173
+ // Find all draggables currently dropped on this droppable
174
+ const droppedCount = Object.values(droppedItems).filter((item) => item.droppableId === droppableId).length;
175
+ // Find the droppable's registered capacity
176
+ const droppableSlot = Object.values(slotsRef.current).find((slot) => slot.id === droppableId);
177
+ if (!droppableSlot)
178
+ return false; // Not found or not registered
179
+ // Use the slot's capacity if specified, default to 1
180
+ const capacity = droppableSlot.capacity !== undefined ? droppableSlot.capacity : 1;
181
+ // Check if more capacity is available
182
+ return droppedCount < capacity;
183
+ }, [droppedItems]);
184
+ // Create a wrapper for onDragStart that also triggers position update
185
+ const handleDragStart = useCallback((data) => {
186
+ if (onDragStart) {
187
+ onDragStart(data);
188
+ }
189
+ internalRequestPositionUpdate();
190
+ }, [onDragStart, internalRequestPositionUpdate]);
191
+ // Update the context value with the new method
192
+ const contextValue = useMemo(() => ({
193
+ register: (id, slot) => {
194
+ slotsRef.current[id] = slot;
195
+ },
196
+ unregister: (id) => {
197
+ delete slotsRef.current[id];
198
+ },
199
+ isRegistered: (id) => {
200
+ return slotsRef.current[id] !== undefined;
201
+ },
202
+ getSlots: () => slotsRef.current,
203
+ setActiveHoverSlot: (id) => setActiveHoverSlotIdState(id),
204
+ activeHoverSlotId,
205
+ registerPositionUpdateListener,
206
+ unregisterPositionUpdateListener,
207
+ requestPositionUpdate: internalRequestPositionUpdate,
208
+ registerDroppedItem,
209
+ unregisterDroppedItem,
210
+ getDroppedItems,
211
+ hasAvailableCapacity,
212
+ onDragging,
213
+ onDragStart: handleDragStart,
214
+ onDragEnd,
215
+ }), [
216
+ activeHoverSlotId,
217
+ registerPositionUpdateListener,
218
+ unregisterPositionUpdateListener,
219
+ internalRequestPositionUpdate,
220
+ registerDroppedItem,
221
+ unregisterDroppedItem,
222
+ getDroppedItems,
223
+ hasAvailableCapacity,
224
+ onDragging,
225
+ handleDragStart,
226
+ onDragEnd,
227
+ ]);
228
+ return (<SlotsContext.Provider value={contextValue}>
229
+ {children}
230
+ </SlotsContext.Provider>);
231
+ });
232
+ // Adding a display name for better debugging in React DevTools
233
+ DropProvider.displayName = "DropProvider";
@@ -0,0 +1,4 @@
1
+ export { useSortable } from "./useSortable";
2
+ export { useSortableList } from "./useSortableList";
3
+ export { useDraggable } from "./useDraggable";
4
+ export { useDroppable } from "./useDroppable";
@@ -0,0 +1,5 @@
1
+ // Export hooks
2
+ export { useSortable } from "./useSortable";
3
+ export { useSortableList } from "./useSortableList";
4
+ export { useDraggable } from "./useDraggable";
5
+ export { useDroppable } from "./useDroppable";
@@ -0,0 +1,101 @@
1
+ import { UseDraggableOptions, UseDraggableReturn } from "../types/draggable";
2
+ /**
3
+ * A powerful hook for creating draggable components with advanced features like
4
+ * collision detection, bounded dragging, axis constraints, and custom animations.
5
+ *
6
+ * This hook provides the core functionality for drag-and-drop interactions,
7
+ * handling gesture recognition, position tracking, collision detection with drop zones,
8
+ * and smooth animations.
9
+ *
10
+ * @template TData - The type of data associated with the draggable item
11
+ * @param options - Configuration options for the draggable behavior
12
+ * @returns Object containing props, gesture handlers, and state for the draggable component
13
+ *
14
+ * @example
15
+ * Basic draggable component:
16
+ * ```typescript
17
+ * import { useDraggable } from './hooks/useDraggable';
18
+ *
19
+ * function MyDraggable() {
20
+ * const { animatedViewProps, gesture, state } = useDraggable({
21
+ * data: { id: '1', name: 'Draggable Item' },
22
+ * onDragStart: (data) => console.log('Started dragging:', data.name),
23
+ * onDragEnd: (data) => console.log('Finished dragging:', data.name),
24
+ * });
25
+ *
26
+ * return (
27
+ * <GestureDetector gesture={gesture}>
28
+ * <Animated.View {...animatedViewProps}>
29
+ * <Text>Drag me!</Text>
30
+ * </Animated.View>
31
+ * </GestureDetector>
32
+ * );
33
+ * }
34
+ * ```
35
+ *
36
+ * @example
37
+ * Draggable with custom animation and bounds:
38
+ * ```typescript
39
+ * function BoundedDraggable() {
40
+ * const boundsRef = useRef<View>(null);
41
+ *
42
+ * const { animatedViewProps, gesture } = useDraggable({
43
+ * data: { id: '2', type: 'bounded' },
44
+ * dragBoundsRef: boundsRef,
45
+ * dragAxis: 'x', // Only horizontal movement
46
+ * animationFunction: (toValue) => {
47
+ * 'worklet';
48
+ * return withTiming(toValue, { duration: 300 });
49
+ * },
50
+ * collisionAlgorithm: 'center',
51
+ * });
52
+ *
53
+ * return (
54
+ * <View ref={boundsRef} style={styles.container}>
55
+ * <GestureDetector gesture={gesture}>
56
+ * <Animated.View {...animatedViewProps}>
57
+ * <Text>Bounded horizontal draggable</Text>
58
+ * </Animated.View>
59
+ * </GestureDetector>
60
+ * </View>
61
+ * );
62
+ * }
63
+ * ```
64
+ *
65
+ * @example
66
+ * Draggable with state tracking:
67
+ * ```typescript
68
+ * function StatefulDraggable() {
69
+ * const [dragState, setDragState] = useState(DraggableState.IDLE);
70
+ *
71
+ * const { animatedViewProps, gesture } = useDraggable({
72
+ * data: { id: '3', status: 'active' },
73
+ * onStateChange: setDragState,
74
+ * onDragging: ({ x, y, tx, ty }) => {
75
+ * console.log(`Position: (${x + tx}, ${y + ty})`);
76
+ * },
77
+ * });
78
+ *
79
+ * return (
80
+ * <GestureDetector gesture={gesture}>
81
+ * <Animated.View
82
+ * {...animatedViewProps}
83
+ * style={[
84
+ * animatedViewProps.style,
85
+ * { opacity: dragState === DraggableState.DRAGGING ? 0.7 : 1 }
86
+ * ]}
87
+ * >
88
+ * <Text>State: {dragState}</Text>
89
+ * </Animated.View>
90
+ * </GestureDetector>
91
+ * );
92
+ * }
93
+ * ```
94
+ *
95
+ * @see {@link DraggableState} for state management
96
+ * @see {@link CollisionAlgorithm} for collision detection options
97
+ * @see {@link AnimationFunction} for custom animations
98
+ * @see {@link UseDraggableOptions} for configuration options
99
+ * @see {@link UseDraggableReturn} for return value details
100
+ */
101
+ export declare const useDraggable: <TData = unknown>(options: UseDraggableOptions<TData>) => UseDraggableReturn;