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.
- package/LICENSE +21 -0
- package/README.md +633 -0
- package/lib/components/Draggable.d.ts +5 -0
- package/lib/components/Draggable.js +265 -0
- package/lib/components/Droppable.d.ts +264 -0
- package/lib/components/Droppable.js +284 -0
- package/lib/components/Sortable.d.ts +184 -0
- package/lib/components/Sortable.js +225 -0
- package/lib/components/SortableItem.d.ts +158 -0
- package/lib/components/SortableItem.js +251 -0
- package/lib/components/sortableUtils.d.ts +21 -0
- package/lib/components/sortableUtils.js +50 -0
- package/lib/context/DropContext.d.ts +118 -0
- package/lib/context/DropContext.js +233 -0
- package/lib/hooks/index.d.ts +4 -0
- package/lib/hooks/index.js +5 -0
- package/lib/hooks/useDraggable.d.ts +101 -0
- package/lib/hooks/useDraggable.js +567 -0
- package/lib/hooks/useDroppable.d.ts +129 -0
- package/lib/hooks/useDroppable.js +261 -0
- package/lib/hooks/useSortable.d.ts +174 -0
- package/lib/hooks/useSortable.js +361 -0
- package/lib/hooks/useSortableList.d.ts +182 -0
- package/lib/hooks/useSortableList.js +211 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.js +16 -0
- package/lib/types/context.d.ts +166 -0
- package/lib/types/context.js +80 -0
- package/lib/types/draggable.d.ts +313 -0
- package/lib/types/draggable.js +31 -0
- package/lib/types/droppable.d.ts +197 -0
- package/lib/types/droppable.js +1 -0
- package/lib/types/index.d.ts +4 -0
- package/lib/types/index.js +8 -0
- package/lib/types/sortable.d.ts +432 -0
- package/lib/types/sortable.js +6 -0
- package/package.json +59 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { SortableItemProps, SortableHandleProps } from "../types/sortable";
|
|
3
|
+
/**
|
|
4
|
+
* A component for individual items within a sortable list.
|
|
5
|
+
*
|
|
6
|
+
* SortableItem provides the drag-and-drop functionality for individual list items,
|
|
7
|
+
* handling gesture recognition, position animations, and reordering logic.
|
|
8
|
+
* It can be used with or without drag handles for different interaction patterns.
|
|
9
|
+
*
|
|
10
|
+
* @template T - The type of data associated with this sortable item
|
|
11
|
+
* @param props - Configuration props for the sortable item
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* Basic sortable item (entire item is draggable):
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { SortableItem } from './components/SortableItem';
|
|
17
|
+
*
|
|
18
|
+
* function TaskItem({ task, positions, ...sortableProps }) {
|
|
19
|
+
* return (
|
|
20
|
+
* <SortableItem
|
|
21
|
+
* id={task.id}
|
|
22
|
+
* data={task}
|
|
23
|
+
* positions={positions}
|
|
24
|
+
* {...sortableProps}
|
|
25
|
+
* onMove={(id, from, to) => {
|
|
26
|
+
* console.log(`Task ${id} moved from ${from} to ${to}`);
|
|
27
|
+
* reorderTasks(id, from, to);
|
|
28
|
+
* }}
|
|
29
|
+
* >
|
|
30
|
+
* <View style={styles.taskContainer}>
|
|
31
|
+
* <Text style={styles.taskTitle}>{task.title}</Text>
|
|
32
|
+
* <Text style={styles.taskStatus}>{task.completed ? 'Done' : 'Pending'}</Text>
|
|
33
|
+
* </View>
|
|
34
|
+
* </SortableItem>
|
|
35
|
+
* );
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* Sortable item with drag handle:
|
|
41
|
+
* ```typescript
|
|
42
|
+
* function TaskItemWithHandle({ task, positions, ...sortableProps }) {
|
|
43
|
+
* return (
|
|
44
|
+
* <SortableItem
|
|
45
|
+
* id={task.id}
|
|
46
|
+
* data={task}
|
|
47
|
+
* positions={positions}
|
|
48
|
+
* {...sortableProps}
|
|
49
|
+
* >
|
|
50
|
+
* <View style={styles.taskContainer}>
|
|
51
|
+
* <View style={styles.taskContent}>
|
|
52
|
+
* <Text style={styles.taskTitle}>{task.title}</Text>
|
|
53
|
+
* <Text style={styles.taskDescription}>{task.description}</Text>
|
|
54
|
+
* </View>
|
|
55
|
+
*
|
|
56
|
+
* {/* Only this handle can initiate dragging *\/}
|
|
57
|
+
* <SortableItem.Handle style={styles.dragHandle}>
|
|
58
|
+
* <View style={styles.handleIcon}>
|
|
59
|
+
* <View style={styles.handleLine} />
|
|
60
|
+
* <View style={styles.handleLine} />
|
|
61
|
+
* <View style={styles.handleLine} />
|
|
62
|
+
* </View>
|
|
63
|
+
* </SortableItem.Handle>
|
|
64
|
+
* </View>
|
|
65
|
+
* </SortableItem>
|
|
66
|
+
* );
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* Sortable item with callbacks and state tracking:
|
|
72
|
+
* ```typescript
|
|
73
|
+
* function AdvancedTaskItem({ task, positions, ...sortableProps }) {
|
|
74
|
+
* const [isDragging, setIsDragging] = useState(false);
|
|
75
|
+
*
|
|
76
|
+
* return (
|
|
77
|
+
* <SortableItem
|
|
78
|
+
* id={task.id}
|
|
79
|
+
* data={task}
|
|
80
|
+
* positions={positions}
|
|
81
|
+
* {...sortableProps}
|
|
82
|
+
* onDragStart={(id, position) => {
|
|
83
|
+
* setIsDragging(true);
|
|
84
|
+
* hapticFeedback();
|
|
85
|
+
* analytics.track('drag_start', { taskId: id, position });
|
|
86
|
+
* }}
|
|
87
|
+
* onDrop={(id, position) => {
|
|
88
|
+
* setIsDragging(false);
|
|
89
|
+
* analytics.track('drag_end', { taskId: id, position });
|
|
90
|
+
* }}
|
|
91
|
+
* onDragging={(id, overItemId, yPosition) => {
|
|
92
|
+
* if (overItemId) {
|
|
93
|
+
* // Show visual feedback for item being hovered over
|
|
94
|
+
* highlightItem(overItemId);
|
|
95
|
+
* }
|
|
96
|
+
* }}
|
|
97
|
+
* style={[
|
|
98
|
+
* styles.taskItem,
|
|
99
|
+
* isDragging && styles.draggingItem
|
|
100
|
+
* ]}
|
|
101
|
+
* >
|
|
102
|
+
* <View style={styles.taskContent}>
|
|
103
|
+
* <Text style={styles.taskTitle}>{task.title}</Text>
|
|
104
|
+
* <Text style={styles.taskPriority}>Priority: {task.priority}</Text>
|
|
105
|
+
* {isDragging && (
|
|
106
|
+
* <Text style={styles.dragIndicator}>Dragging...</Text>
|
|
107
|
+
* )}
|
|
108
|
+
* </View>
|
|
109
|
+
* </SortableItem>
|
|
110
|
+
* );
|
|
111
|
+
* }
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* Sortable item with custom animations:
|
|
116
|
+
* ```typescript
|
|
117
|
+
* function AnimatedTaskItem({ task, positions, ...sortableProps }) {
|
|
118
|
+
* return (
|
|
119
|
+
* <SortableItem
|
|
120
|
+
* id={task.id}
|
|
121
|
+
* data={task}
|
|
122
|
+
* positions={positions}
|
|
123
|
+
* {...sortableProps}
|
|
124
|
+
* animatedStyle={{
|
|
125
|
+
* // Custom animated styles can be applied here
|
|
126
|
+
* shadowOpacity: 0.3,
|
|
127
|
+
* shadowRadius: 10,
|
|
128
|
+
* shadowColor: '#000',
|
|
129
|
+
* shadowOffset: { width: 0, height: 5 }
|
|
130
|
+
* }}
|
|
131
|
+
* >
|
|
132
|
+
* <Animated.View style={[
|
|
133
|
+
* styles.taskContainer,
|
|
134
|
+
* {
|
|
135
|
+
* backgroundColor: task.priority === 'high' ? '#ffebee' : '#f5f5f5'
|
|
136
|
+
* }
|
|
137
|
+
* ]}>
|
|
138
|
+
* <Text style={styles.taskTitle}>{task.title}</Text>
|
|
139
|
+
* <View style={styles.taskMeta}>
|
|
140
|
+
* <Text style={styles.taskDue}>{task.dueDate}</Text>
|
|
141
|
+
* <Text style={styles.taskAssignee}>{task.assignee}</Text>
|
|
142
|
+
* </View>
|
|
143
|
+
* </Animated.View>
|
|
144
|
+
* </SortableItem>
|
|
145
|
+
* );
|
|
146
|
+
* }
|
|
147
|
+
* ```
|
|
148
|
+
*
|
|
149
|
+
* @see {@link SortableItem.Handle} for creating drag handles
|
|
150
|
+
* @see {@link useSortable} for the underlying hook
|
|
151
|
+
* @see {@link Sortable} for the parent sortable list component
|
|
152
|
+
* @see {@link UseSortableOptions} for configuration options
|
|
153
|
+
* @see {@link UseSortableReturn} for hook return details
|
|
154
|
+
*/
|
|
155
|
+
export declare function SortableItem<T>({ id, data, positions, lowerBound, autoScrollDirection, itemsCount, itemHeight, containerHeight, children, style, animatedStyle: customAnimatedStyle, onMove, onDragStart, onDrop, onDragging, }: SortableItemProps<T>): React.JSX.Element;
|
|
156
|
+
export declare namespace SortableItem {
|
|
157
|
+
var Handle: ({ children, style }: SortableHandleProps) => React.JSX.Element;
|
|
158
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import React, { createContext, useContext } from "react";
|
|
2
|
+
import Animated from "react-native-reanimated";
|
|
3
|
+
import { PanGestureHandler } from "react-native-gesture-handler";
|
|
4
|
+
import { useSortable } from "../hooks/useSortable";
|
|
5
|
+
// Create a context to share gesture between SortableItem and SortableHandle
|
|
6
|
+
const SortableContext = createContext(null);
|
|
7
|
+
/**
|
|
8
|
+
* A handle component that can be used within SortableItem to create a specific
|
|
9
|
+
* draggable area. When a SortableHandle is present, only the handle area can
|
|
10
|
+
* initiate dragging, while the rest of the item remains non-draggable.
|
|
11
|
+
*
|
|
12
|
+
* @param props - Props for the handle component
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* Basic drag handle:
|
|
16
|
+
* ```typescript
|
|
17
|
+
* <SortableItem id="item-1" {...sortableProps}>
|
|
18
|
+
* <View style={styles.itemContent}>
|
|
19
|
+
* <Text>Item content (not draggable)</Text>
|
|
20
|
+
*
|
|
21
|
+
* <SortableItem.Handle style={styles.dragHandle}>
|
|
22
|
+
* <Icon name="drag-handle" size={20} />
|
|
23
|
+
* </SortableItem.Handle>
|
|
24
|
+
* </View>
|
|
25
|
+
* </SortableItem>
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* Custom styled handle:
|
|
30
|
+
* ```typescript
|
|
31
|
+
* <SortableItem id="item-2" {...sortableProps}>
|
|
32
|
+
* <View style={styles.card}>
|
|
33
|
+
* <Text style={styles.title}>Card Title</Text>
|
|
34
|
+
* <Text style={styles.content}>Card content...</Text>
|
|
35
|
+
*
|
|
36
|
+
* <SortableItem.Handle style={styles.customHandle}>
|
|
37
|
+
* <View style={styles.handleDots}>
|
|
38
|
+
* <View style={styles.dot} />
|
|
39
|
+
* <View style={styles.dot} />
|
|
40
|
+
* <View style={styles.dot} />
|
|
41
|
+
* </View>
|
|
42
|
+
* </SortableItem.Handle>
|
|
43
|
+
* </View>
|
|
44
|
+
* </SortableItem>
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
const SortableHandle = ({ children, style }) => {
|
|
48
|
+
const sortableContext = useContext(SortableContext);
|
|
49
|
+
if (!sortableContext) {
|
|
50
|
+
console.warn("SortableHandle must be used within a SortableItem component");
|
|
51
|
+
return <>{children}</>;
|
|
52
|
+
}
|
|
53
|
+
return (<PanGestureHandler onGestureEvent={sortableContext.panGestureHandler}>
|
|
54
|
+
<Animated.View style={style}>{children}</Animated.View>
|
|
55
|
+
</PanGestureHandler>);
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* A component for individual items within a sortable list.
|
|
59
|
+
*
|
|
60
|
+
* SortableItem provides the drag-and-drop functionality for individual list items,
|
|
61
|
+
* handling gesture recognition, position animations, and reordering logic.
|
|
62
|
+
* It can be used with or without drag handles for different interaction patterns.
|
|
63
|
+
*
|
|
64
|
+
* @template T - The type of data associated with this sortable item
|
|
65
|
+
* @param props - Configuration props for the sortable item
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* Basic sortable item (entire item is draggable):
|
|
69
|
+
* ```typescript
|
|
70
|
+
* import { SortableItem } from './components/SortableItem';
|
|
71
|
+
*
|
|
72
|
+
* function TaskItem({ task, positions, ...sortableProps }) {
|
|
73
|
+
* return (
|
|
74
|
+
* <SortableItem
|
|
75
|
+
* id={task.id}
|
|
76
|
+
* data={task}
|
|
77
|
+
* positions={positions}
|
|
78
|
+
* {...sortableProps}
|
|
79
|
+
* onMove={(id, from, to) => {
|
|
80
|
+
* console.log(`Task ${id} moved from ${from} to ${to}`);
|
|
81
|
+
* reorderTasks(id, from, to);
|
|
82
|
+
* }}
|
|
83
|
+
* >
|
|
84
|
+
* <View style={styles.taskContainer}>
|
|
85
|
+
* <Text style={styles.taskTitle}>{task.title}</Text>
|
|
86
|
+
* <Text style={styles.taskStatus}>{task.completed ? 'Done' : 'Pending'}</Text>
|
|
87
|
+
* </View>
|
|
88
|
+
* </SortableItem>
|
|
89
|
+
* );
|
|
90
|
+
* }
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* Sortable item with drag handle:
|
|
95
|
+
* ```typescript
|
|
96
|
+
* function TaskItemWithHandle({ task, positions, ...sortableProps }) {
|
|
97
|
+
* return (
|
|
98
|
+
* <SortableItem
|
|
99
|
+
* id={task.id}
|
|
100
|
+
* data={task}
|
|
101
|
+
* positions={positions}
|
|
102
|
+
* {...sortableProps}
|
|
103
|
+
* >
|
|
104
|
+
* <View style={styles.taskContainer}>
|
|
105
|
+
* <View style={styles.taskContent}>
|
|
106
|
+
* <Text style={styles.taskTitle}>{task.title}</Text>
|
|
107
|
+
* <Text style={styles.taskDescription}>{task.description}</Text>
|
|
108
|
+
* </View>
|
|
109
|
+
*
|
|
110
|
+
* {/* Only this handle can initiate dragging *\/}
|
|
111
|
+
* <SortableItem.Handle style={styles.dragHandle}>
|
|
112
|
+
* <View style={styles.handleIcon}>
|
|
113
|
+
* <View style={styles.handleLine} />
|
|
114
|
+
* <View style={styles.handleLine} />
|
|
115
|
+
* <View style={styles.handleLine} />
|
|
116
|
+
* </View>
|
|
117
|
+
* </SortableItem.Handle>
|
|
118
|
+
* </View>
|
|
119
|
+
* </SortableItem>
|
|
120
|
+
* );
|
|
121
|
+
* }
|
|
122
|
+
* ```
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* Sortable item with callbacks and state tracking:
|
|
126
|
+
* ```typescript
|
|
127
|
+
* function AdvancedTaskItem({ task, positions, ...sortableProps }) {
|
|
128
|
+
* const [isDragging, setIsDragging] = useState(false);
|
|
129
|
+
*
|
|
130
|
+
* return (
|
|
131
|
+
* <SortableItem
|
|
132
|
+
* id={task.id}
|
|
133
|
+
* data={task}
|
|
134
|
+
* positions={positions}
|
|
135
|
+
* {...sortableProps}
|
|
136
|
+
* onDragStart={(id, position) => {
|
|
137
|
+
* setIsDragging(true);
|
|
138
|
+
* hapticFeedback();
|
|
139
|
+
* analytics.track('drag_start', { taskId: id, position });
|
|
140
|
+
* }}
|
|
141
|
+
* onDrop={(id, position) => {
|
|
142
|
+
* setIsDragging(false);
|
|
143
|
+
* analytics.track('drag_end', { taskId: id, position });
|
|
144
|
+
* }}
|
|
145
|
+
* onDragging={(id, overItemId, yPosition) => {
|
|
146
|
+
* if (overItemId) {
|
|
147
|
+
* // Show visual feedback for item being hovered over
|
|
148
|
+
* highlightItem(overItemId);
|
|
149
|
+
* }
|
|
150
|
+
* }}
|
|
151
|
+
* style={[
|
|
152
|
+
* styles.taskItem,
|
|
153
|
+
* isDragging && styles.draggingItem
|
|
154
|
+
* ]}
|
|
155
|
+
* >
|
|
156
|
+
* <View style={styles.taskContent}>
|
|
157
|
+
* <Text style={styles.taskTitle}>{task.title}</Text>
|
|
158
|
+
* <Text style={styles.taskPriority}>Priority: {task.priority}</Text>
|
|
159
|
+
* {isDragging && (
|
|
160
|
+
* <Text style={styles.dragIndicator}>Dragging...</Text>
|
|
161
|
+
* )}
|
|
162
|
+
* </View>
|
|
163
|
+
* </SortableItem>
|
|
164
|
+
* );
|
|
165
|
+
* }
|
|
166
|
+
* ```
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* Sortable item with custom animations:
|
|
170
|
+
* ```typescript
|
|
171
|
+
* function AnimatedTaskItem({ task, positions, ...sortableProps }) {
|
|
172
|
+
* return (
|
|
173
|
+
* <SortableItem
|
|
174
|
+
* id={task.id}
|
|
175
|
+
* data={task}
|
|
176
|
+
* positions={positions}
|
|
177
|
+
* {...sortableProps}
|
|
178
|
+
* animatedStyle={{
|
|
179
|
+
* // Custom animated styles can be applied here
|
|
180
|
+
* shadowOpacity: 0.3,
|
|
181
|
+
* shadowRadius: 10,
|
|
182
|
+
* shadowColor: '#000',
|
|
183
|
+
* shadowOffset: { width: 0, height: 5 }
|
|
184
|
+
* }}
|
|
185
|
+
* >
|
|
186
|
+
* <Animated.View style={[
|
|
187
|
+
* styles.taskContainer,
|
|
188
|
+
* {
|
|
189
|
+
* backgroundColor: task.priority === 'high' ? '#ffebee' : '#f5f5f5'
|
|
190
|
+
* }
|
|
191
|
+
* ]}>
|
|
192
|
+
* <Text style={styles.taskTitle}>{task.title}</Text>
|
|
193
|
+
* <View style={styles.taskMeta}>
|
|
194
|
+
* <Text style={styles.taskDue}>{task.dueDate}</Text>
|
|
195
|
+
* <Text style={styles.taskAssignee}>{task.assignee}</Text>
|
|
196
|
+
* </View>
|
|
197
|
+
* </Animated.View>
|
|
198
|
+
* </SortableItem>
|
|
199
|
+
* );
|
|
200
|
+
* }
|
|
201
|
+
* ```
|
|
202
|
+
*
|
|
203
|
+
* @see {@link SortableItem.Handle} for creating drag handles
|
|
204
|
+
* @see {@link useSortable} for the underlying hook
|
|
205
|
+
* @see {@link Sortable} for the parent sortable list component
|
|
206
|
+
* @see {@link UseSortableOptions} for configuration options
|
|
207
|
+
* @see {@link UseSortableReturn} for hook return details
|
|
208
|
+
*/
|
|
209
|
+
export function SortableItem({ id, data, positions, lowerBound, autoScrollDirection, itemsCount, itemHeight, containerHeight, children, style, animatedStyle: customAnimatedStyle, onMove, onDragStart, onDrop, onDragging, }) {
|
|
210
|
+
// Use our custom hook for all the sortable logic
|
|
211
|
+
const sortableOptions = {
|
|
212
|
+
id,
|
|
213
|
+
positions,
|
|
214
|
+
lowerBound,
|
|
215
|
+
autoScrollDirection,
|
|
216
|
+
itemsCount,
|
|
217
|
+
itemHeight,
|
|
218
|
+
containerHeight,
|
|
219
|
+
onMove,
|
|
220
|
+
onDragStart,
|
|
221
|
+
onDrop,
|
|
222
|
+
onDragging,
|
|
223
|
+
children,
|
|
224
|
+
handleComponent: SortableHandle,
|
|
225
|
+
};
|
|
226
|
+
const { animatedStyle, panGestureHandler, isMoving, hasHandle } = useSortable(sortableOptions);
|
|
227
|
+
// Combine the default animated style with any custom styles
|
|
228
|
+
const combinedAnimatedStyle = [animatedStyle, customAnimatedStyle];
|
|
229
|
+
// Create the context value
|
|
230
|
+
const contextValue = {
|
|
231
|
+
panGestureHandler,
|
|
232
|
+
};
|
|
233
|
+
// Always provide the context to avoid issues when toggling handle modes
|
|
234
|
+
const content = (<Animated.View style={combinedAnimatedStyle}>
|
|
235
|
+
<SortableContext.Provider value={contextValue}>
|
|
236
|
+
<Animated.View style={style}>{children}</Animated.View>
|
|
237
|
+
</SortableContext.Provider>
|
|
238
|
+
</Animated.View>);
|
|
239
|
+
// If a handle is found, let the handle control the dragging
|
|
240
|
+
// Otherwise, the entire component is draggable with PanGestureHandler
|
|
241
|
+
if (hasHandle) {
|
|
242
|
+
return content;
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
return (<PanGestureHandler onGestureEvent={panGestureHandler} activateAfterLongPress={200} shouldCancelWhenOutside={false}>
|
|
246
|
+
{content}
|
|
247
|
+
</PanGestureHandler>);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Attach the SortableHandle as a static property
|
|
251
|
+
SortableItem.Handle = SortableHandle;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { SharedValue } from "react-native-reanimated";
|
|
2
|
+
export declare enum ScrollDirection {
|
|
3
|
+
None = "none",
|
|
4
|
+
Up = "up",
|
|
5
|
+
Down = "down"
|
|
6
|
+
}
|
|
7
|
+
export declare function clamp(value: number, lowerBound: number, upperBound: number): number;
|
|
8
|
+
export declare function objectMove(object: {
|
|
9
|
+
[id: string]: number;
|
|
10
|
+
}, from: number, to: number): {
|
|
11
|
+
[id: string]: number;
|
|
12
|
+
};
|
|
13
|
+
export declare function listToObject<T extends {
|
|
14
|
+
id: string;
|
|
15
|
+
}>(list: T[]): {
|
|
16
|
+
[id: string]: number;
|
|
17
|
+
};
|
|
18
|
+
export declare function setPosition(positionY: number, itemsCount: number, positions: SharedValue<{
|
|
19
|
+
[id: string]: number;
|
|
20
|
+
}>, id: string, itemHeight: number): void;
|
|
21
|
+
export declare function setAutoScroll(positionY: number, lowerBound: number, upperBound: number, scrollThreshold: number, autoScroll: SharedValue<ScrollDirection>): void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export var ScrollDirection;
|
|
2
|
+
(function (ScrollDirection) {
|
|
3
|
+
ScrollDirection["None"] = "none";
|
|
4
|
+
ScrollDirection["Up"] = "up";
|
|
5
|
+
ScrollDirection["Down"] = "down";
|
|
6
|
+
})(ScrollDirection || (ScrollDirection = {}));
|
|
7
|
+
export function clamp(value, lowerBound, upperBound) {
|
|
8
|
+
"worklet";
|
|
9
|
+
return Math.max(lowerBound, Math.min(value, upperBound));
|
|
10
|
+
}
|
|
11
|
+
export function objectMove(object, from, to) {
|
|
12
|
+
"worklet";
|
|
13
|
+
const newObject = Object.assign({}, object);
|
|
14
|
+
for (const id in object) {
|
|
15
|
+
if (object[id] === from) {
|
|
16
|
+
newObject[id] = to;
|
|
17
|
+
}
|
|
18
|
+
if (object[id] === to) {
|
|
19
|
+
newObject[id] = from;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return newObject;
|
|
23
|
+
}
|
|
24
|
+
export function listToObject(list) {
|
|
25
|
+
const values = Object.values(list);
|
|
26
|
+
const object = {};
|
|
27
|
+
for (let i = 0; i < values.length; i++) {
|
|
28
|
+
object[values[i].id] = i;
|
|
29
|
+
}
|
|
30
|
+
return object;
|
|
31
|
+
}
|
|
32
|
+
export function setPosition(positionY, itemsCount, positions, id, itemHeight) {
|
|
33
|
+
"worklet";
|
|
34
|
+
const newPosition = clamp(Math.floor(positionY / itemHeight), 0, itemsCount - 1);
|
|
35
|
+
if (newPosition !== positions.value[id]) {
|
|
36
|
+
positions.value = objectMove(positions.value, positions.value[id], newPosition);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function setAutoScroll(positionY, lowerBound, upperBound, scrollThreshold, autoScroll) {
|
|
40
|
+
"worklet";
|
|
41
|
+
if (positionY <= lowerBound + scrollThreshold) {
|
|
42
|
+
autoScroll.value = ScrollDirection.Up;
|
|
43
|
+
}
|
|
44
|
+
else if (positionY >= upperBound - scrollThreshold) {
|
|
45
|
+
autoScroll.value = ScrollDirection.Down;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
autoScroll.value = ScrollDirection.None;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DropProviderProps, DropProviderRef } from "../types/context";
|
|
3
|
+
/**
|
|
4
|
+
* Provider component that enables drag-and-drop functionality for its children.
|
|
5
|
+
*
|
|
6
|
+
* The DropProvider creates the context necessary for draggable and droppable components
|
|
7
|
+
* to communicate with each other. It manages the registration of drop zones, tracks
|
|
8
|
+
* active hover states, handles collision detection, and maintains the state of dropped items.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* Basic setup:
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { DropProvider } from './context/DropContext';
|
|
14
|
+
* import { Draggable, Droppable } from './components';
|
|
15
|
+
*
|
|
16
|
+
* function App() {
|
|
17
|
+
* return (
|
|
18
|
+
* <DropProvider>
|
|
19
|
+
* <View style={styles.container}>
|
|
20
|
+
* <Draggable data={{ id: '1', name: 'Item 1' }}>
|
|
21
|
+
* <Text>Drag me!</Text>
|
|
22
|
+
* </Draggable>
|
|
23
|
+
*
|
|
24
|
+
* <Droppable onDrop={(data) => console.log('Dropped:', data)}>
|
|
25
|
+
* <Text>Drop zone</Text>
|
|
26
|
+
* </Droppable>
|
|
27
|
+
* </View>
|
|
28
|
+
* </DropProvider>
|
|
29
|
+
* );
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* With callbacks and ref:
|
|
35
|
+
* ```typescript
|
|
36
|
+
* function AdvancedApp() {
|
|
37
|
+
* const dropProviderRef = useRef<DropProviderRef>(null);
|
|
38
|
+
* const [droppedItems, setDroppedItems] = useState({});
|
|
39
|
+
*
|
|
40
|
+
* const handleLayoutChange = () => {
|
|
41
|
+
* // Trigger position update after layout changes
|
|
42
|
+
* dropProviderRef.current?.requestPositionUpdate();
|
|
43
|
+
* };
|
|
44
|
+
*
|
|
45
|
+
* return (
|
|
46
|
+
* <DropProvider
|
|
47
|
+
* ref={dropProviderRef}
|
|
48
|
+
* onDroppedItemsUpdate={setDroppedItems}
|
|
49
|
+
* onDragStart={(data) => console.log('Drag started:', data)}
|
|
50
|
+
* onDragEnd={(data) => console.log('Drag ended:', data)}
|
|
51
|
+
* onDragging={({ x, y, itemData }) => {
|
|
52
|
+
* console.log(`${itemData.name} at (${x}, ${y})`);
|
|
53
|
+
* }}
|
|
54
|
+
* >
|
|
55
|
+
* <ScrollView onLayout={handleLayoutChange}>
|
|
56
|
+
* {/* Your draggable and droppable components *\/}
|
|
57
|
+
* </ScrollView>
|
|
58
|
+
* </DropProvider>
|
|
59
|
+
* );
|
|
60
|
+
* }
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* Multiple drop zones with capacity:
|
|
65
|
+
* ```typescript
|
|
66
|
+
* function TaskBoard() {
|
|
67
|
+
* const [tasks, setTasks] = useState(initialTasks);
|
|
68
|
+
*
|
|
69
|
+
* return (
|
|
70
|
+
* <DropProvider
|
|
71
|
+
* onDroppedItemsUpdate={(dropped) => {
|
|
72
|
+
* // Update task positions based on drops
|
|
73
|
+
* updateTaskPositions(dropped);
|
|
74
|
+
* }}
|
|
75
|
+
* >
|
|
76
|
+
* <View style={styles.board}>
|
|
77
|
+
* {tasks.map(task => (
|
|
78
|
+
* <Draggable key={task.id} data={task}>
|
|
79
|
+
* <TaskCard task={task} />
|
|
80
|
+
* </Draggable>
|
|
81
|
+
* ))}
|
|
82
|
+
*
|
|
83
|
+
* <Droppable
|
|
84
|
+
* droppableId="todo"
|
|
85
|
+
* capacity={10}
|
|
86
|
+
* onDrop={(task) => moveTask(task.id, 'todo')}
|
|
87
|
+
* >
|
|
88
|
+
* <Column title="To Do" />
|
|
89
|
+
* </Droppable>
|
|
90
|
+
*
|
|
91
|
+
* <Droppable
|
|
92
|
+
* droppableId="in-progress"
|
|
93
|
+
* capacity={5}
|
|
94
|
+
* onDrop={(task) => moveTask(task.id, 'in-progress')}
|
|
95
|
+
* >
|
|
96
|
+
* <Column title="In Progress" />
|
|
97
|
+
* </Droppable>
|
|
98
|
+
*
|
|
99
|
+
* <Droppable
|
|
100
|
+
* droppableId="done"
|
|
101
|
+
* onDrop={(task) => moveTask(task.id, 'done')}
|
|
102
|
+
* >
|
|
103
|
+
* <Column title="Done" />
|
|
104
|
+
* </Droppable>
|
|
105
|
+
* </View>
|
|
106
|
+
* </DropProvider>
|
|
107
|
+
* );
|
|
108
|
+
* }
|
|
109
|
+
* ```
|
|
110
|
+
*
|
|
111
|
+
* @see {@link Draggable} for draggable components
|
|
112
|
+
* @see {@link Droppable} for droppable components
|
|
113
|
+
* @see {@link useDraggable} for draggable hook
|
|
114
|
+
* @see {@link useDroppable} for droppable hook
|
|
115
|
+
* @see {@link DropProviderRef} for imperative handle interface
|
|
116
|
+
* @see {@link DroppedItemsMap} for dropped items data structure
|
|
117
|
+
*/
|
|
118
|
+
export declare const DropProvider: React.ForwardRefExoticComponent<DropProviderProps & React.RefAttributes<DropProviderRef>>;
|