react-native-reanimated-dnd 1.1.0 → 2.0.0
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 +296 -655
- package/lib/components/Draggable.js +1 -1
- package/lib/components/Droppable.d.ts +0 -1
- package/lib/components/Droppable.js +1 -1
- package/lib/components/Sortable.js +1 -1
- package/lib/components/SortableGrid.d.ts +3 -0
- package/lib/components/SortableGrid.js +1 -0
- package/lib/components/SortableGridItem.d.ts +7 -0
- package/lib/components/SortableGridItem.js +1 -0
- package/lib/components/SortableItem.d.ts +2 -2
- package/lib/components/SortableItem.js +1 -1
- package/lib/components/sortableUtils.d.ts +21 -0
- package/lib/components/sortableUtils.js +1 -1
- package/lib/context/DropContext.js +1 -1
- package/lib/hooks/index.d.ts +2 -0
- package/lib/hooks/index.js +1 -1
- package/lib/hooks/safeMeasure.d.ts +3 -0
- package/lib/hooks/safeMeasure.js +1 -0
- package/lib/hooks/useDraggable.js +1 -1
- package/lib/hooks/useDroppable.js +1 -1
- package/lib/hooks/useGridSortable.d.ts +2 -0
- package/lib/hooks/useGridSortable.js +1 -0
- package/lib/hooks/useGridSortableList.d.ts +3 -0
- package/lib/hooks/useGridSortableList.js +1 -0
- package/lib/hooks/useHorizontalSortable.js +1 -1
- package/lib/hooks/useHorizontalSortableList.js +1 -1
- package/lib/hooks/useSortable.d.ts +13 -6
- package/lib/hooks/useSortable.js +1 -1
- package/lib/hooks/useSortableList.d.ts +15 -3
- package/lib/hooks/useSortableList.js +1 -1
- package/lib/index.d.ts +7 -0
- package/lib/index.js +1 -1
- package/lib/types/draggable.d.ts +3 -0
- package/lib/types/grid.d.ts +148 -0
- package/lib/types/grid.js +1 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +1 -1
- package/lib/types/sortable.d.ts +76 -21
- package/lib/utils/gridCalculations.d.ts +21 -0
- package/lib/utils/gridCalculations.js +1 -0
- package/package.json +23 -15
package/README.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# React Native Reanimated DnD 🎯
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
https://github.com/user-attachments/assets/023c0a9c-194a-45a9-bb88-ac8f16f5ffd5
|
|
6
|
+
|
|
5
7
|
</p>
|
|
6
8
|
<div align="center">
|
|
7
9
|
|
|
@@ -12,14 +14,14 @@ _Powerful, performant, and built for the modern React Native developer_
|
|
|
12
14
|
[](https://badge.fury.io/js/react-native-reanimated-dnd)
|
|
13
15
|
[](https://opensource.org/licenses/MIT)
|
|
14
16
|
[](https://www.typescriptlang.org/)
|
|
15
|
-
[](https://reactnative.dev/)
|
|
16
18
|
|
|
17
19
|
<br />
|
|
18
20
|
|
|
19
21
|
<a href="https://www.npmjs.com/package/react-native-reanimated-dnd" target="_blank">
|
|
20
22
|
<img src="https://img.shields.io/badge/📦%20View%20on%20NPM-cb3837?style=for-the-badge&logo=npm&logoColor=white&labelColor=1e293b&fontSize=24" alt="NPM Package" height="36"/>
|
|
21
23
|
</a>
|
|
22
|
-
<a href="https://
|
|
24
|
+
<a href="https://reanimated-dnd-docs.vercel.app/" target="_blank">
|
|
23
25
|
<img src="https://img.shields.io/badge/📖%20Read%20the%20Docs-4f46e5?style=for-the-badge&logo=gitbook&logoColor=white&labelColor=1e293b&color=6366f1&fontSize=24" alt="Documentation" height="36"/>
|
|
24
26
|
</a>
|
|
25
27
|
<a href="#-interactive-examples" target="_blank">
|
|
@@ -38,9 +40,9 @@ After countless attempts with drag-and-drop solutions that don't work or are sim
|
|
|
38
40
|
|
|
39
41
|
## ✨ Features
|
|
40
42
|
|
|
41
|
-
- 🚀 **High Performance** - Built with Reanimated
|
|
42
|
-
- 🏗️ **
|
|
43
|
-
- 📦 **Expo Compatible** -
|
|
43
|
+
- 🚀 **High Performance** - Built with Reanimated 4 and Worklets for buttery-smooth 60fps animations
|
|
44
|
+
- 🏗️ **New Architecture Ready** - Built for the modern React Native architecture used by Expo SDK 55+
|
|
45
|
+
- 📦 **Expo Compatible** - Tested against Expo SDK 55 and React Native 0.83
|
|
44
46
|
- 🪶 **Tiny Bundle Size** - Only 70kb unpacked size, won't bloat your app
|
|
45
47
|
- 🎯 **Flexible API** - From simple drag-and-drop to complex sortable lists
|
|
46
48
|
- 📱 **React Native First** - Designed specifically for mobile, not ported from web
|
|
@@ -48,8 +50,10 @@ After countless attempts with drag-and-drop solutions that don't work or are sim
|
|
|
48
50
|
- 🎨 **Infinitely Customizable** - Every animation, behavior, and style is configurable
|
|
49
51
|
- 📦 **Complete Component Suite** - Draggable, Droppable, Sortable, and more
|
|
50
52
|
- 🎪 **Smart Collision Detection** - Multiple algorithms (center, intersect, contain)
|
|
51
|
-
- 📜 **Sortable Lists** - Drag and drop to sort
|
|
52
|
-
|
|
53
|
+
- 📜 **Vertical & Horizontal Sortable Lists** - Drag and drop to sort lists in any direction with automatic scrolling
|
|
54
|
+
- 🔲 **Sortable Grids** - 2D grid drag-and-drop with flexible layouts, insert and swap modes
|
|
55
|
+
- ↕ **Dynamic Heights** - Sortable lists with variable item heights
|
|
56
|
+
- ⚡ **FlatList Performance** - Optional FlatList rendering for large datasets with virtualization
|
|
53
57
|
- 🎭 **Drag Handles** - Precise control with dedicated drag areas
|
|
54
58
|
- 🎬 **Custom Animations** - Spring, timing, or bring your own animation functions
|
|
55
59
|
- 📐 **Pixel-Perfect Positioning** - 9-point alignment system with custom offsets
|
|
@@ -59,7 +63,7 @@ After countless attempts with drag-and-drop solutions that don't work or are sim
|
|
|
59
63
|
|
|
60
64
|
## 📱 Interactive Examples
|
|
61
65
|
|
|
62
|
-
**See it in action!** A comprehensive example app with **
|
|
66
|
+
**See it in action!** A comprehensive example app with **18 interactive demos** showcasing every feature and use case.
|
|
63
67
|
|
|
64
68
|
<div align="center">
|
|
65
69
|
|
|
@@ -71,19 +75,18 @@ After countless attempts with drag-and-drop solutions that don't work or are sim
|
|
|
71
75
|
|
|
72
76
|
**📱 Scan & Play**
|
|
73
77
|
|
|
74
|
-
<img src="https://
|
|
78
|
+
<img src="https://raw.githubusercontent.com/entropyconquers/react-native-reanimated-dnd/main/documentation/images/expo-qr-code.png" alt="Expo QR Code" width="200" height="200" />
|
|
75
79
|
|
|
76
|
-
_Scan with
|
|
80
|
+
_Scan with Expo Go to try the demo instantly_
|
|
77
81
|
|
|
78
82
|
</td>
|
|
79
83
|
<td align="center" width="50%">
|
|
80
84
|
|
|
81
85
|
**🚀 Quick Start**
|
|
82
86
|
|
|
83
|
-
1. Install [Expo Go](https://expo.dev/
|
|
87
|
+
1. Install [Expo Go](https://expo.dev/go) on your phone (SDK 55)
|
|
84
88
|
2. Scan the QR code with your camera
|
|
85
|
-
3.
|
|
86
|
-
4. Explore 15 interactive examples!
|
|
89
|
+
3. Explore 18 interactive examples!
|
|
87
90
|
|
|
88
91
|
**Or browse the code:**
|
|
89
92
|
[**📂 View Example App →**](./example-app/README.md)
|
|
@@ -94,7 +97,7 @@ _Scan with your camera or Expo Go app_
|
|
|
94
97
|
|
|
95
98
|
### 📚 Complete Documentation
|
|
96
99
|
|
|
97
|
-
<a href="https://
|
|
100
|
+
<a href="https://reanimated-dnd-docs.vercel.app/" target="_blank">
|
|
98
101
|
<img src="https://img.shields.io/badge/📖%20Documentation-Visit%20Docs-4f46e5?style=for-the-badge&logo=gitbook&logoColor=white&labelColor=1e293b" alt="Documentation" />
|
|
99
102
|
</a>
|
|
100
103
|
|
|
@@ -104,743 +107,354 @@ _Comprehensive guides, API reference, and interactive examples_
|
|
|
104
107
|
|
|
105
108
|
The example app includes:
|
|
106
109
|
|
|
107
|
-
- 🎵 **Sortable Music Queue** - Complete list reordering with handles
|
|
108
|
-
-
|
|
109
|
-
-
|
|
110
|
+
- 🎵 **Sortable Music Queue** - Complete list reordering with drag handles
|
|
111
|
+
- ⇌ **Horizontal Sortable** - Reorderable horizontal scrolling list
|
|
112
|
+
- 🔲 **Sortable Grid** - 2D grid reordering with insert/swap modes
|
|
113
|
+
- ↕ **Dynamic Heights** - Sortable list with variable item heights
|
|
114
|
+
- 🎯 **Basic Drag & Drop** - Drag items to drop zones with pre-drag delay
|
|
115
|
+
- 🎪 **Drag Handles** - Dedicated drag regions for precise control
|
|
116
|
+
- 🎬 **Custom Animations** - Spring, timing, bounce & easing curves
|
|
117
|
+
- ✨ **Active Drop Styles** - Visual feedback on hover
|
|
118
|
+
- 📐 **Alignment & Offset** - Precise drop positioning with offsets
|
|
110
119
|
- 📦 **Boundary Constraints** - Axis-locked and bounded dragging
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
|
|
114
|
-
|
|
120
|
+
- 🎯 **Collision Detection** - Center, intersect & contain algorithms
|
|
121
|
+
- 🗺️ **Dropped Items Map** - Track items across multiple zones
|
|
122
|
+
- ⚡ **Drag State** - State enum & onStateChange lifecycle
|
|
123
|
+
- ⚙️ **Custom Draggable** - useDraggable hook implementation
|
|
115
124
|
|
|
116
|
-
|
|
125
|
+
## 🎬 Demo Showcase
|
|
117
126
|
|
|
118
127
|
<div align="center">
|
|
119
128
|
|
|
120
129
|
<table>
|
|
121
130
|
<tr>
|
|
122
|
-
<td align="center" width="
|
|
131
|
+
<td align="center" width="33%">
|
|
123
132
|
|
|
124
|
-
###
|
|
133
|
+
### 🎵 Sortable Music Queue
|
|
125
134
|
|
|
126
|
-
|
|
135
|
+
_Vertical list reordering with drag handles_
|
|
127
136
|
|
|
128
|
-
https://github.com/user-attachments/assets/
|
|
137
|
+
https://github.com/user-attachments/assets/6493c05f-571c-416d-b81d-90bee98738b4
|
|
129
138
|
|
|
130
139
|
**Features:** Auto-scrolling • Drag handles • Smooth transitions
|
|
131
140
|
|
|
132
141
|
</td>
|
|
133
|
-
<td align="center" width="
|
|
142
|
+
<td align="center" width="33%">
|
|
134
143
|
|
|
135
|
-
###
|
|
144
|
+
### ⇌ Horizontal Sortable
|
|
136
145
|
|
|
137
|
-
|
|
146
|
+
_Reorderable horizontal scrolling list_
|
|
138
147
|
|
|
139
|
-
https://github.com/user-attachments/assets/
|
|
148
|
+
https://github.com/user-attachments/assets/909c687e-146d-4233-a6e9-4bc3e7b69a35
|
|
140
149
|
|
|
141
|
-
**
|
|
150
|
+
**Features:** Horizontal scroll • Handle & full item modes
|
|
142
151
|
|
|
143
152
|
</td>
|
|
144
|
-
|
|
145
|
-
<tr>
|
|
146
|
-
<td align="center" width="50%">
|
|
153
|
+
<td align="center" width="33%">
|
|
147
154
|
|
|
148
|
-
###
|
|
155
|
+
### 🔲 Grid Sortable
|
|
149
156
|
|
|
150
|
-
|
|
157
|
+
_2D grid reordering with insert & swap_
|
|
151
158
|
|
|
152
|
-
https://github.com/user-attachments/assets/
|
|
159
|
+
https://github.com/user-attachments/assets/9647af01-098c-485f-92ba-cfc39d1bba0c
|
|
153
160
|
|
|
154
|
-
**Features:**
|
|
161
|
+
**Features:** Grid layout • Insert & swap modes
|
|
155
162
|
|
|
156
163
|
</td>
|
|
157
|
-
|
|
164
|
+
</tr>
|
|
165
|
+
<tr>
|
|
166
|
+
<td align="center" width="33%">
|
|
158
167
|
|
|
159
|
-
###
|
|
168
|
+
### ↕ Dynamic Heights
|
|
160
169
|
|
|
161
|
-
|
|
170
|
+
_Sortable list with variable item heights_
|
|
162
171
|
|
|
163
|
-
https://github.com/user-attachments/assets/
|
|
172
|
+
https://github.com/user-attachments/assets/b53078ca-51ab-4675-9389-e8fe69492370
|
|
164
173
|
|
|
165
|
-
**
|
|
174
|
+
**Features:** Variable heights • Expand/collapse • Auto-scroll
|
|
166
175
|
|
|
167
176
|
</td>
|
|
168
|
-
|
|
169
|
-
<tr>
|
|
170
|
-
<td align="center" width="50%">
|
|
177
|
+
<td align="center" width="33%">
|
|
171
178
|
|
|
172
|
-
###
|
|
179
|
+
### 🎯 Basic Drag & Drop
|
|
173
180
|
|
|
174
|
-
|
|
181
|
+
_Drag items to drop zones_
|
|
175
182
|
|
|
176
|
-
https://github.com/user-attachments/assets/
|
|
183
|
+
https://github.com/user-attachments/assets/05859d25-6749-41b7-ae7e-cad5f864a268
|
|
177
184
|
|
|
178
|
-
**
|
|
185
|
+
**Features:** Drop zones • Pre-drag delay • Visual feedback
|
|
179
186
|
|
|
180
187
|
</td>
|
|
181
|
-
<td align="center" width="
|
|
188
|
+
<td align="center" width="33%">
|
|
182
189
|
|
|
183
|
-
###
|
|
190
|
+
### 🎭 Drag Handles
|
|
184
191
|
|
|
185
|
-
|
|
192
|
+
_Dedicated regions for drag control_
|
|
186
193
|
|
|
187
|
-
https://github.com/user-attachments/assets/
|
|
194
|
+
https://github.com/user-attachments/assets/f8be7bea-0f24-4446-83be-06ede43fa02a
|
|
188
195
|
|
|
189
|
-
**
|
|
196
|
+
**Features:** Full handle • Bar handle • Header handle
|
|
190
197
|
|
|
191
198
|
</td>
|
|
192
199
|
</tr>
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
</div>
|
|
200
|
+
<tr>
|
|
201
|
+
<td align="center" width="33%">
|
|
196
202
|
|
|
197
|
-
|
|
203
|
+
### ✨ Active Drop Styles
|
|
198
204
|
|
|
199
|
-
|
|
200
|
-
npm install react-native-reanimated-dnd
|
|
201
|
-
```
|
|
205
|
+
_Visual hover effects on drop zones_
|
|
202
206
|
|
|
203
|
-
|
|
207
|
+
https://github.com/user-attachments/assets/d4124110-6eb7-4a15-8f97-3e58b392d62e
|
|
204
208
|
|
|
205
|
-
|
|
206
|
-
npm install react-native-reanimated react-native-gesture-handler
|
|
207
|
-
```
|
|
209
|
+
**Feedback:** Pulse effect • Glow effect • Hover states
|
|
208
210
|
|
|
209
|
-
|
|
211
|
+
</td>
|
|
212
|
+
<td align="center" width="33%">
|
|
210
213
|
|
|
211
|
-
|
|
212
|
-
- [React Native Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/docs/installation)
|
|
214
|
+
### 📐 Alignment & Offset
|
|
213
215
|
|
|
214
|
-
|
|
216
|
+
_Precise drop positioning with offsets_
|
|
215
217
|
|
|
216
|
-
|
|
218
|
+
https://github.com/user-attachments/assets/07fd39d0-a0d8-471c-86be-b73d70027163
|
|
217
219
|
|
|
218
|
-
|
|
219
|
-
import React from "react";
|
|
220
|
-
import { View, Text, StyleSheet } from "react-native";
|
|
221
|
-
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
|
222
|
-
import { Draggable, DropProvider } from "react-native-reanimated-dnd";
|
|
220
|
+
**Features:** 9-point alignment • X/Y offset controls
|
|
223
221
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
<GestureHandlerRootView style={styles.container}>
|
|
227
|
-
<DropProvider>
|
|
228
|
-
<View style={styles.content}>
|
|
229
|
-
<Draggable data={{ id: "1", title: "Drag me!" }}>
|
|
230
|
-
<View style={styles.draggableItem}>
|
|
231
|
-
<Text style={styles.itemText}>🎯 Drag me around!</Text>
|
|
232
|
-
</View>
|
|
233
|
-
</Draggable>
|
|
234
|
-
</View>
|
|
235
|
-
</DropProvider>
|
|
236
|
-
</GestureHandlerRootView>
|
|
237
|
-
);
|
|
238
|
-
}
|
|
222
|
+
</td>
|
|
223
|
+
<td align="center" width="33%">
|
|
239
224
|
|
|
240
|
-
|
|
241
|
-
container: {
|
|
242
|
-
flex: 1,
|
|
243
|
-
backgroundColor: "#000000",
|
|
244
|
-
},
|
|
245
|
-
content: {
|
|
246
|
-
flex: 1,
|
|
247
|
-
padding: 20,
|
|
248
|
-
justifyContent: "center",
|
|
249
|
-
alignItems: "center",
|
|
250
|
-
},
|
|
251
|
-
draggableItem: {
|
|
252
|
-
padding: 20,
|
|
253
|
-
backgroundColor: "#1C1C1E",
|
|
254
|
-
borderRadius: 12,
|
|
255
|
-
borderWidth: 1,
|
|
256
|
-
borderColor: "#3A3A3C",
|
|
257
|
-
shadowColor: "#000",
|
|
258
|
-
shadowOffset: { width: 0, height: 2 },
|
|
259
|
-
shadowOpacity: 0.25,
|
|
260
|
-
shadowRadius: 4,
|
|
261
|
-
elevation: 3,
|
|
262
|
-
},
|
|
263
|
-
itemText: {
|
|
264
|
-
color: "#FFFFFF",
|
|
265
|
-
fontSize: 16,
|
|
266
|
-
fontWeight: "600",
|
|
267
|
-
textAlign: "center",
|
|
268
|
-
},
|
|
269
|
-
});
|
|
270
|
-
```
|
|
225
|
+
### 📦 Bounded Dragging
|
|
271
226
|
|
|
272
|
-
|
|
227
|
+
_Constrain movement within boundaries_
|
|
273
228
|
|
|
274
|
-
|
|
275
|
-
import React from "react";
|
|
276
|
-
import { Alert, StyleSheet, Text, View } from "react-native";
|
|
277
|
-
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
|
278
|
-
import {
|
|
279
|
-
Draggable,
|
|
280
|
-
Droppable,
|
|
281
|
-
DropProvider,
|
|
282
|
-
} from "react-native-reanimated-dnd";
|
|
229
|
+
https://github.com/user-attachments/assets/ce43a226-ec42-49c1-b528-df6599d294e1
|
|
283
230
|
|
|
284
|
-
|
|
285
|
-
const handleDrop = (data: any, zoneId: string) => {
|
|
286
|
-
Alert.alert("Item Dropped", `"${data.title}" dropped in ${zoneId}`);
|
|
287
|
-
};
|
|
231
|
+
**Constraints:** Container bounds • Axis-locked • Custom limits
|
|
288
232
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
{/* Drop Zones */}
|
|
294
|
-
<View style={styles.dropZonesSection}>
|
|
295
|
-
<Text style={styles.sectionTitle}>Drop Zones</Text>
|
|
296
|
-
|
|
297
|
-
<Droppable
|
|
298
|
-
onDrop={(data) => handleDrop(data, "Zone 1")}
|
|
299
|
-
activeStyle={styles.dropZoneActive}
|
|
300
|
-
style={styles.droppable}
|
|
301
|
-
>
|
|
302
|
-
<View style={[styles.dropZoneBlue, styles.dropZone]}>
|
|
303
|
-
<Text style={styles.dropZoneText}>🎯 Zone 1</Text>
|
|
304
|
-
<Text style={styles.dropZoneSubtext}>Drop here</Text>
|
|
305
|
-
</View>
|
|
306
|
-
</Droppable>
|
|
307
|
-
|
|
308
|
-
<Droppable
|
|
309
|
-
onDrop={(data) => handleDrop(data, "Zone 2")}
|
|
310
|
-
activeStyle={styles.dropZoneActive}
|
|
311
|
-
style={styles.droppable}
|
|
312
|
-
>
|
|
313
|
-
<View style={[styles.dropZone, styles.dropZoneGreen]}>
|
|
314
|
-
<Text style={styles.dropZoneText}>🎯 Zone 2</Text>
|
|
315
|
-
<Text style={styles.dropZoneSubtext}>Drop here</Text>
|
|
316
|
-
</View>
|
|
317
|
-
</Droppable>
|
|
318
|
-
</View>
|
|
233
|
+
</td>
|
|
234
|
+
</tr>
|
|
235
|
+
<tr>
|
|
236
|
+
<td align="center" width="33%">
|
|
319
237
|
|
|
320
|
-
|
|
321
|
-
<View style={styles.draggableSection}>
|
|
322
|
-
<Text style={styles.sectionTitle}>Draggable Item</Text>
|
|
323
|
-
<Draggable data={{ id: "1", title: "Task Item" }}>
|
|
324
|
-
<View style={styles.draggableItem}>
|
|
325
|
-
<Text style={styles.itemText}>📦 Drag me to a zone</Text>
|
|
326
|
-
</View>
|
|
327
|
-
</Draggable>
|
|
328
|
-
</View>
|
|
329
|
-
</View>
|
|
330
|
-
</DropProvider>
|
|
331
|
-
</GestureHandlerRootView>
|
|
332
|
-
);
|
|
333
|
-
}
|
|
238
|
+
### 🎪 Collision Detection
|
|
334
239
|
|
|
335
|
-
|
|
336
|
-
container: {
|
|
337
|
-
flex: 1,
|
|
338
|
-
backgroundColor: "#000000",
|
|
339
|
-
},
|
|
340
|
-
content: {
|
|
341
|
-
flex: 1,
|
|
342
|
-
padding: 20,
|
|
343
|
-
justifyContent: "space-between",
|
|
344
|
-
},
|
|
345
|
-
sectionTitle: {
|
|
346
|
-
color: "#FFFFFF",
|
|
347
|
-
fontSize: 18,
|
|
348
|
-
fontWeight: "700",
|
|
349
|
-
marginBottom: 20,
|
|
350
|
-
textAlign: "center",
|
|
351
|
-
},
|
|
352
|
-
draggableSection: {
|
|
353
|
-
alignItems: "center",
|
|
354
|
-
paddingVertical: 40,
|
|
355
|
-
},
|
|
356
|
-
draggableItem: {
|
|
357
|
-
padding: 20,
|
|
358
|
-
backgroundColor: "#1C1C1E",
|
|
359
|
-
borderRadius: 12,
|
|
360
|
-
borderWidth: 1,
|
|
361
|
-
borderColor: "#3A3A3C",
|
|
362
|
-
shadowColor: "#000",
|
|
363
|
-
shadowOffset: { width: 0, height: 2 },
|
|
364
|
-
shadowOpacity: 0.25,
|
|
365
|
-
shadowRadius: 4,
|
|
366
|
-
elevation: 3,
|
|
367
|
-
},
|
|
368
|
-
itemText: {
|
|
369
|
-
color: "#FFFFFF",
|
|
370
|
-
fontSize: 16,
|
|
371
|
-
fontWeight: "600",
|
|
372
|
-
textAlign: "center",
|
|
373
|
-
},
|
|
374
|
-
dropZonesSection: {
|
|
375
|
-
flex: 1,
|
|
376
|
-
paddingVertical: 40,
|
|
377
|
-
},
|
|
378
|
-
droppable: {
|
|
379
|
-
marginBottom: 20,
|
|
380
|
-
overflow: "hidden",
|
|
381
|
-
borderRadius: 16,
|
|
382
|
-
},
|
|
383
|
-
dropZone: {
|
|
384
|
-
height: 140,
|
|
385
|
-
borderWidth: 2,
|
|
386
|
-
borderStyle: "dashed",
|
|
387
|
-
borderRadius: 16,
|
|
388
|
-
justifyContent: "center",
|
|
389
|
-
alignItems: "center",
|
|
390
|
-
padding: 20,
|
|
391
|
-
},
|
|
392
|
-
dropZoneBlue: {
|
|
393
|
-
borderColor: "#58a6ff",
|
|
394
|
-
backgroundColor: "rgba(88, 166, 255, 0.08)",
|
|
395
|
-
},
|
|
396
|
-
dropZoneGreen: {
|
|
397
|
-
borderColor: "#3fb950",
|
|
398
|
-
backgroundColor: "rgba(63, 185, 80, 0.08)",
|
|
399
|
-
},
|
|
400
|
-
dropZoneActive: {
|
|
401
|
-
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
|
402
|
-
borderStyle: "solid",
|
|
403
|
-
transform: [{ scale: 1.02 }],
|
|
404
|
-
},
|
|
405
|
-
dropZoneText: {
|
|
406
|
-
color: "#FFFFFF",
|
|
407
|
-
fontSize: 18,
|
|
408
|
-
fontWeight: "600",
|
|
409
|
-
textAlign: "center",
|
|
410
|
-
marginBottom: 8,
|
|
411
|
-
},
|
|
412
|
-
dropZoneSubtext: {
|
|
413
|
-
color: "#8E8E93",
|
|
414
|
-
fontSize: 14,
|
|
415
|
-
textAlign: "center",
|
|
416
|
-
},
|
|
417
|
-
});
|
|
418
|
-
```
|
|
240
|
+
_Center, intersect & contain algorithms_
|
|
419
241
|
|
|
420
|
-
|
|
242
|
+
https://github.com/user-attachments/assets/1ac1860a-acc1-469a-ae3e-644a22393b86
|
|
421
243
|
|
|
422
|
-
|
|
423
|
-
import React, { useCallback, useState } from "react";
|
|
424
|
-
import { StyleSheet, Text, View } from "react-native";
|
|
425
|
-
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
|
426
|
-
import {
|
|
427
|
-
Sortable,
|
|
428
|
-
SortableItem,
|
|
429
|
-
SortableRenderItemProps,
|
|
430
|
-
} from "react-native-reanimated-dnd";
|
|
431
|
-
|
|
432
|
-
interface Task {
|
|
433
|
-
id: string;
|
|
434
|
-
title: string;
|
|
435
|
-
completed: boolean;
|
|
436
|
-
}
|
|
244
|
+
**Algorithms:** Center • Intersect • Contain
|
|
437
245
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
{ id: "1", title: "Learn React Native", completed: false },
|
|
441
|
-
{ id: "2", title: "Build an app", completed: false },
|
|
442
|
-
{ id: "3", title: "Deploy to store", completed: true },
|
|
443
|
-
{ id: "4", title: "Celebrate success", completed: false },
|
|
444
|
-
]);
|
|
445
|
-
|
|
446
|
-
const renderTask = useCallback(
|
|
447
|
-
(props: SortableRenderItemProps<Task>) => {
|
|
448
|
-
const {
|
|
449
|
-
item,
|
|
450
|
-
id,
|
|
451
|
-
positions,
|
|
452
|
-
lowerBound,
|
|
453
|
-
autoScrollDirection,
|
|
454
|
-
itemsCount,
|
|
455
|
-
itemHeight,
|
|
456
|
-
} = props;
|
|
457
|
-
return (
|
|
458
|
-
<SortableItem
|
|
459
|
-
key={id}
|
|
460
|
-
data={item}
|
|
461
|
-
id={id}
|
|
462
|
-
positions={positions}
|
|
463
|
-
lowerBound={lowerBound}
|
|
464
|
-
autoScrollDirection={autoScrollDirection}
|
|
465
|
-
itemsCount={itemsCount}
|
|
466
|
-
itemHeight={itemHeight}
|
|
467
|
-
onMove={(itemId, from, to) => {
|
|
468
|
-
const newTasks = [...tasks];
|
|
469
|
-
const [movedTask] = newTasks.splice(from, 1);
|
|
470
|
-
newTasks.splice(to, 0, movedTask);
|
|
471
|
-
setTasks(newTasks);
|
|
472
|
-
}}
|
|
473
|
-
style={styles.taskItem}
|
|
474
|
-
>
|
|
475
|
-
<View style={styles.taskContent}>
|
|
476
|
-
<View style={styles.taskInfo}>
|
|
477
|
-
<Text style={styles.taskTitle}>{item.title}</Text>
|
|
478
|
-
<Text style={styles.taskStatus}>
|
|
479
|
-
{item.completed ? "✅ Completed" : "⏳ Pending"}
|
|
480
|
-
</Text>
|
|
481
|
-
</View>
|
|
482
|
-
|
|
483
|
-
{/* Drag Handle */}
|
|
484
|
-
<SortableItem.Handle style={styles.dragHandle}>
|
|
485
|
-
<View style={styles.dragIconContainer}>
|
|
486
|
-
<View style={styles.dragColumn}>
|
|
487
|
-
<View style={styles.dragDot} />
|
|
488
|
-
<View style={styles.dragDot} />
|
|
489
|
-
<View style={styles.dragDot} />
|
|
490
|
-
</View>
|
|
491
|
-
<View style={styles.dragColumn}>
|
|
492
|
-
<View style={styles.dragDot} />
|
|
493
|
-
<View style={styles.dragDot} />
|
|
494
|
-
<View style={styles.dragDot} />
|
|
495
|
-
</View>
|
|
496
|
-
</View>
|
|
497
|
-
</SortableItem.Handle>
|
|
498
|
-
</View>
|
|
499
|
-
</SortableItem>
|
|
500
|
-
);
|
|
501
|
-
},
|
|
502
|
-
[tasks]
|
|
503
|
-
);
|
|
246
|
+
</td>
|
|
247
|
+
<td align="center" width="33%">
|
|
504
248
|
|
|
505
|
-
|
|
506
|
-
<GestureHandlerRootView style={styles.container}>
|
|
507
|
-
<View style={styles.header}>
|
|
508
|
-
<Text style={styles.headerTitle}>📋 My Tasks</Text>
|
|
509
|
-
<Text style={styles.headerSubtitle}>Drag to reorder</Text>
|
|
510
|
-
</View>
|
|
511
|
-
|
|
512
|
-
<Sortable
|
|
513
|
-
data={tasks}
|
|
514
|
-
renderItem={renderTask}
|
|
515
|
-
itemHeight={80}
|
|
516
|
-
style={styles.list}
|
|
517
|
-
/>
|
|
518
|
-
</GestureHandlerRootView>
|
|
519
|
-
);
|
|
520
|
-
}
|
|
249
|
+
### 🗺️ Dropped Items Map
|
|
521
250
|
|
|
522
|
-
|
|
523
|
-
container: {
|
|
524
|
-
flex: 1,
|
|
525
|
-
backgroundColor: "#000000",
|
|
526
|
-
},
|
|
527
|
-
header: {
|
|
528
|
-
padding: 20,
|
|
529
|
-
paddingBottom: 16,
|
|
530
|
-
borderBottomWidth: 1,
|
|
531
|
-
borderBottomColor: "#2C2C2E",
|
|
532
|
-
},
|
|
533
|
-
headerTitle: {
|
|
534
|
-
color: "#FFFFFF",
|
|
535
|
-
fontSize: 24,
|
|
536
|
-
fontWeight: "700",
|
|
537
|
-
marginBottom: 4,
|
|
538
|
-
},
|
|
539
|
-
headerSubtitle: {
|
|
540
|
-
color: "#8E8E93",
|
|
541
|
-
fontSize: 14,
|
|
542
|
-
},
|
|
543
|
-
list: {
|
|
544
|
-
flex: 1,
|
|
545
|
-
backgroundColor: "#000000",
|
|
546
|
-
marginTop: 20,
|
|
547
|
-
paddingHorizontal: 20,
|
|
548
|
-
borderRadius: 20,
|
|
549
|
-
overflow: "hidden",
|
|
550
|
-
},
|
|
551
|
-
taskItem: {
|
|
552
|
-
height: 80,
|
|
553
|
-
|
|
554
|
-
backgroundColor: "transparent",
|
|
555
|
-
},
|
|
556
|
-
taskContent: {
|
|
557
|
-
flex: 1,
|
|
558
|
-
flexDirection: "row",
|
|
559
|
-
alignItems: "center",
|
|
560
|
-
paddingHorizontal: 20,
|
|
561
|
-
backgroundColor: "#1C1C1E",
|
|
562
|
-
|
|
563
|
-
borderWidth: 1,
|
|
564
|
-
borderColor: "#3A3A3C",
|
|
565
|
-
},
|
|
566
|
-
taskInfo: {
|
|
567
|
-
flex: 1,
|
|
568
|
-
paddingRight: 16,
|
|
569
|
-
},
|
|
570
|
-
taskTitle: {
|
|
571
|
-
color: "#FFFFFF",
|
|
572
|
-
fontSize: 16,
|
|
573
|
-
fontWeight: "600",
|
|
574
|
-
marginBottom: 4,
|
|
575
|
-
},
|
|
576
|
-
taskStatus: {
|
|
577
|
-
color: "#8E8E93",
|
|
578
|
-
fontSize: 14,
|
|
579
|
-
},
|
|
580
|
-
dragHandle: {
|
|
581
|
-
padding: 12,
|
|
582
|
-
borderRadius: 8,
|
|
583
|
-
backgroundColor: "rgba(255, 255, 255, 0.05)",
|
|
584
|
-
},
|
|
585
|
-
dragIconContainer: {
|
|
586
|
-
flexDirection: "row",
|
|
587
|
-
alignItems: "center",
|
|
588
|
-
gap: 3,
|
|
589
|
-
},
|
|
590
|
-
dragColumn: {
|
|
591
|
-
flexDirection: "column",
|
|
592
|
-
gap: 2,
|
|
593
|
-
},
|
|
594
|
-
dragDot: {
|
|
595
|
-
width: 3,
|
|
596
|
-
height: 3,
|
|
597
|
-
borderRadius: 1.5,
|
|
598
|
-
backgroundColor: "#6D6D70",
|
|
599
|
-
},
|
|
600
|
-
});
|
|
601
|
-
```
|
|
251
|
+
_Track items across multiple zones_
|
|
602
252
|
|
|
603
|
-
|
|
253
|
+
https://github.com/user-attachments/assets/e4effc88-7161-4c5c-af6e-c06152cfbd09
|
|
604
254
|
|
|
605
|
-
|
|
255
|
+
**Features:** Multi-zone tracking • Real-time mapping
|
|
606
256
|
|
|
607
|
-
|
|
257
|
+
</td>
|
|
258
|
+
<td align="center" width="33%">
|
|
608
259
|
|
|
609
|
-
|
|
260
|
+
### ⚡ Drag State
|
|
610
261
|
|
|
611
|
-
|
|
612
|
-
<Draggable
|
|
613
|
-
data={any} // Data associated with the item
|
|
614
|
-
onDragStart={(data) => void} // Called when dragging starts
|
|
615
|
-
onDragEnd={(data) => void} // Called when dragging ends
|
|
616
|
-
onDragging={(position) => void} // Called during dragging
|
|
617
|
-
onStateChange={(state) => void} // Called on state changes
|
|
618
|
-
dragDisabled={boolean} // Disable dragging
|
|
619
|
-
collisionAlgorithm="center|intersect|contain" // Collision detection method
|
|
620
|
-
dragAxis="x|y|both" // Constrain movement axis
|
|
621
|
-
dragBoundsRef={RefObject} // Boundary container reference
|
|
622
|
-
animationFunction={(toValue) => Animation} // Custom animation function
|
|
623
|
-
style={StyleProp<ViewStyle>} // Component styling
|
|
624
|
-
>
|
|
625
|
-
{children}
|
|
626
|
-
</Draggable>
|
|
627
|
-
```
|
|
262
|
+
_State lifecycle tracking & callbacks_
|
|
628
263
|
|
|
629
|
-
|
|
264
|
+
https://github.com/user-attachments/assets/bceb9e7c-8f29-4630-81b0-945a7bcf29c5
|
|
630
265
|
|
|
631
|
-
|
|
266
|
+
**States:** Idle • Dragging • Dropped
|
|
632
267
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
onActiveChange={(isActive) => void} // Called on hover state change
|
|
637
|
-
dropDisabled={boolean} // Disable drop functionality
|
|
638
|
-
dropAlignment="top-left|center|bottom-right|..." // Drop positioning
|
|
639
|
-
dropOffset={{ x: number, y: number }} // Position offset
|
|
640
|
-
activeStyle={StyleProp<ViewStyle>} // Style when active
|
|
641
|
-
capacity={number} // Maximum items allowed
|
|
642
|
-
droppableId={string} // Unique identifier
|
|
643
|
-
>
|
|
644
|
-
{children}
|
|
645
|
-
</Droppable>
|
|
646
|
-
```
|
|
268
|
+
</td>
|
|
269
|
+
</tr>
|
|
270
|
+
</table>
|
|
647
271
|
|
|
648
|
-
|
|
272
|
+
</div>
|
|
649
273
|
|
|
650
|
-
|
|
274
|
+
## 🚀 Installation
|
|
651
275
|
|
|
652
|
-
```
|
|
653
|
-
|
|
654
|
-
data={Array<{ id: string }>} // Array of items to render
|
|
655
|
-
renderItem={(props) => ReactNode} // Render function for items
|
|
656
|
-
itemHeight={number} // Height of each item
|
|
657
|
-
itemKeyExtractor={(item) => string} // Custom key extractor
|
|
658
|
-
style={StyleProp<ViewStyle>} // List container style
|
|
659
|
-
contentContainerStyle={StyleProp<ViewStyle>} // Content container style
|
|
660
|
-
/>
|
|
276
|
+
```bash
|
|
277
|
+
npm install react-native-reanimated-dnd
|
|
661
278
|
```
|
|
662
279
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
Individual item within a sortable list with gesture handling.
|
|
280
|
+
### Peer Dependencies
|
|
666
281
|
|
|
667
|
-
```
|
|
668
|
-
|
|
669
|
-
id={string} // Unique identifier
|
|
670
|
-
data={any} // Item data
|
|
671
|
-
positions={SharedValue} // Position tracking
|
|
672
|
-
onMove={(id, from, to) => void} // Called when item moves
|
|
673
|
-
onDragStart={(id, position) => void} // Called when dragging starts
|
|
674
|
-
onDrop={(id, position) => void} // Called when item is dropped
|
|
675
|
-
onDragging={(id, overItemId, y) => void} // Called during dragging
|
|
676
|
-
style={StyleProp<ViewStyle>} // Item styling
|
|
677
|
-
animatedStyle={StyleProp<AnimatedStyle>} // Animated styling
|
|
678
|
-
>
|
|
679
|
-
{children}
|
|
680
|
-
</SortableItem>
|
|
282
|
+
```bash
|
|
283
|
+
npm install react-native-reanimated react-native-gesture-handler react-native-worklets
|
|
681
284
|
```
|
|
682
285
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
#### `useDraggable(options)`
|
|
286
|
+
Follow the setup guides:
|
|
686
287
|
|
|
687
|
-
|
|
288
|
+
- [React Native Worklets](https://docs.swmansion.com/react-native-worklets/docs/getting-started/installation/)
|
|
289
|
+
- [React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/)
|
|
290
|
+
- [React Native Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/docs/installation)
|
|
688
291
|
|
|
689
|
-
|
|
292
|
+
Make sure your Babel config uses `"react-native-worklets/plugin"` as the last plugin and that your app is running on the New Architecture, which is required by Reanimated 4.
|
|
690
293
|
|
|
691
|
-
|
|
294
|
+
## 📋 Requirements
|
|
692
295
|
|
|
693
|
-
|
|
296
|
+
### Data Structure
|
|
694
297
|
|
|
695
|
-
|
|
298
|
+
All items in your data array **MUST** have an `id` property of type string:
|
|
696
299
|
|
|
697
|
-
|
|
300
|
+
```typescript
|
|
301
|
+
interface YourDataType {
|
|
302
|
+
id: string; // Required!
|
|
303
|
+
// ... your other properties
|
|
304
|
+
}
|
|
305
|
+
```
|
|
698
306
|
|
|
699
|
-
|
|
307
|
+
This is essential for the library to track items during reordering.
|
|
700
308
|
|
|
701
|
-
|
|
309
|
+
**Example:**
|
|
702
310
|
|
|
703
|
-
|
|
311
|
+
```typescript
|
|
312
|
+
// ✅ Good - Each item has a unique string id
|
|
313
|
+
const tasks = [
|
|
314
|
+
{ id: "1", title: "Learn React Native", completed: false },
|
|
315
|
+
{ id: "2", title: "Build an app", completed: false },
|
|
316
|
+
{ id: "3", title: "Deploy to store", completed: true },
|
|
317
|
+
];
|
|
704
318
|
|
|
705
|
-
|
|
319
|
+
// ❌ Bad - Missing id properties
|
|
320
|
+
const badTasks = [{ title: "Task 1" }, { title: "Task 2" }];
|
|
706
321
|
|
|
707
|
-
|
|
708
|
-
|
|
322
|
+
// ❌ Bad - Non-string ids
|
|
323
|
+
const badTasksWithNumbers = [
|
|
324
|
+
{ id: 1, title: "Task 1" },
|
|
325
|
+
{ id: 2, title: "Task 2" },
|
|
326
|
+
];
|
|
709
327
|
```
|
|
710
328
|
|
|
711
|
-
|
|
329
|
+
The library includes runtime validation in development mode that will warn you if items are missing valid ID properties.
|
|
712
330
|
|
|
713
|
-
|
|
331
|
+
## State Management Guidelines
|
|
714
332
|
|
|
715
|
-
|
|
716
|
-
enum DraggableState {
|
|
717
|
-
IDLE = "idle",
|
|
718
|
-
DRAGGING = "dragging",
|
|
719
|
-
ANIMATING = "animating",
|
|
720
|
-
}
|
|
721
|
-
```
|
|
333
|
+
**IMPORTANT**: Sortable components maintain their own internal state for optimal performance and animation consistency.
|
|
722
334
|
|
|
723
|
-
|
|
335
|
+
### Do NOT Do This
|
|
724
336
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
337
|
+
- Never update external state (arrays, Redux, Zustand, etc.) directly in `onMove` callbacks
|
|
338
|
+
- Never call `setItems()`, `setTasks()`, or similar functions during drag operations
|
|
339
|
+
- Never manually splice or reorder external arrays in response to drag events
|
|
728
340
|
|
|
729
|
-
|
|
341
|
+
### Correct Approach
|
|
730
342
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
| "top-right"
|
|
736
|
-
| "center-left"
|
|
737
|
-
| "center"
|
|
738
|
-
| "center-right"
|
|
739
|
-
| "bottom-left"
|
|
740
|
-
| "bottom-center"
|
|
741
|
-
| "bottom-right";
|
|
742
|
-
```
|
|
343
|
+
- Use `onMove` for logging, analytics, or side effects only
|
|
344
|
+
- Use `onDrop` with `allPositions` parameter for read-only position tracking
|
|
345
|
+
- Let sortable components handle their internal reordering automatically
|
|
346
|
+
- Use external state only for the initial data and for non-reordering updates
|
|
743
347
|
|
|
744
|
-
|
|
348
|
+
### Future Features
|
|
745
349
|
|
|
746
|
-
|
|
350
|
+
Programmatic list operations (add, update, delete, reorder items) that work correctly with internal state management will be added in upcoming releases. This will provide safe methods to modify sortable lists externally.
|
|
747
351
|
|
|
748
|
-
|
|
749
|
-
import { withTiming, withSpring, Easing } from "react-native-reanimated";
|
|
750
|
-
|
|
751
|
-
// Smooth timing animation
|
|
752
|
-
const smoothAnimation = (toValue) => {
|
|
753
|
-
"worklet";
|
|
754
|
-
return withTiming(toValue, {
|
|
755
|
-
duration: 300,
|
|
756
|
-
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
|
|
757
|
-
});
|
|
758
|
-
};
|
|
759
|
-
|
|
760
|
-
// Spring animation
|
|
761
|
-
const springAnimation = (toValue) => {
|
|
762
|
-
"worklet";
|
|
763
|
-
return withSpring(toValue, {
|
|
764
|
-
damping: 15,
|
|
765
|
-
stiffness: 150,
|
|
766
|
-
});
|
|
767
|
-
};
|
|
768
|
-
|
|
769
|
-
<Draggable animationFunction={springAnimation}>{/* content */}</Draggable>;
|
|
770
|
-
```
|
|
352
|
+
## 🏃♂️ Quick Start
|
|
771
353
|
|
|
772
|
-
###
|
|
354
|
+
### Basic Drag & Drop
|
|
773
355
|
|
|
774
356
|
```tsx
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
{/* Requires center point to be over drop zone */}
|
|
778
|
-
</Draggable>
|
|
779
|
-
|
|
780
|
-
// Forgiving intersection collision (default)
|
|
781
|
-
<Draggable collisionAlgorithm="intersect">
|
|
782
|
-
{/* Any overlap triggers collision */}
|
|
783
|
-
</Draggable>
|
|
784
|
-
|
|
785
|
-
// Strict containment collision
|
|
786
|
-
<Draggable collisionAlgorithm="contain">
|
|
787
|
-
{/* Entire draggable must be within drop zone */}
|
|
788
|
-
</Draggable>
|
|
789
|
-
```
|
|
357
|
+
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
|
358
|
+
import { Draggable, Droppable, DropProvider } from "react-native-reanimated-dnd";
|
|
790
359
|
|
|
791
|
-
|
|
360
|
+
export default function App() {
|
|
361
|
+
return (
|
|
362
|
+
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
363
|
+
<DropProvider>
|
|
364
|
+
<Droppable onDrop={(data) => console.log("Dropped:", data)}>
|
|
365
|
+
<View style={styles.dropZone}>
|
|
366
|
+
<Text>Drop here</Text>
|
|
367
|
+
</View>
|
|
368
|
+
</Droppable>
|
|
792
369
|
|
|
793
|
-
|
|
794
|
-
<
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
<View style={styles.dot} />
|
|
803
|
-
<View style={styles.dot} />
|
|
804
|
-
</View>
|
|
805
|
-
</SortableItem.Handle>
|
|
806
|
-
</View>
|
|
807
|
-
</SortableItem>
|
|
370
|
+
<Draggable data={{ id: "1", title: "Drag me!" }}>
|
|
371
|
+
<View style={styles.item}>
|
|
372
|
+
<Text>Drag me around!</Text>
|
|
373
|
+
</View>
|
|
374
|
+
</Draggable>
|
|
375
|
+
</DropProvider>
|
|
376
|
+
</GestureHandlerRootView>
|
|
377
|
+
);
|
|
378
|
+
}
|
|
808
379
|
```
|
|
809
380
|
|
|
810
|
-
###
|
|
381
|
+
### Sortable List
|
|
811
382
|
|
|
812
383
|
```tsx
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
384
|
+
import { Sortable, SortableItem, SortableRenderItemProps } from "react-native-reanimated-dnd";
|
|
385
|
+
|
|
386
|
+
const tasks = [
|
|
387
|
+
{ id: "1", title: "Learn React Native" },
|
|
388
|
+
{ id: "2", title: "Build an app" },
|
|
389
|
+
{ id: "3", title: "Deploy to store" },
|
|
390
|
+
];
|
|
391
|
+
|
|
392
|
+
function TaskList() {
|
|
393
|
+
const renderItem = useCallback((props: SortableRenderItemProps<typeof tasks[0]>) => {
|
|
394
|
+
const { item, id, ...rest } = props;
|
|
395
|
+
return (
|
|
396
|
+
<SortableItem key={id} id={id} data={item} {...rest}>
|
|
397
|
+
<View style={styles.task}>
|
|
398
|
+
<Text>{item.title}</Text>
|
|
399
|
+
<SortableItem.Handle>
|
|
400
|
+
<Text>⋮⋮</Text>
|
|
401
|
+
</SortableItem.Handle>
|
|
402
|
+
</View>
|
|
403
|
+
</SortableItem>
|
|
404
|
+
);
|
|
405
|
+
}, []);
|
|
406
|
+
|
|
407
|
+
return <Sortable data={tasks} renderItem={renderItem} itemHeight={60} />;
|
|
408
|
+
}
|
|
824
409
|
```
|
|
825
410
|
|
|
826
|
-
###
|
|
411
|
+
### Sortable Grid
|
|
827
412
|
|
|
828
413
|
```tsx
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
414
|
+
import { SortableGrid, SortableGridItem } from "react-native-reanimated-dnd";
|
|
415
|
+
|
|
416
|
+
const apps = [
|
|
417
|
+
{ id: "1", label: "Photos" },
|
|
418
|
+
{ id: "2", label: "Music" },
|
|
419
|
+
{ id: "3", label: "Settings" },
|
|
420
|
+
{ id: "4", label: "Mail" },
|
|
421
|
+
];
|
|
422
|
+
|
|
423
|
+
function AppGrid() {
|
|
424
|
+
const renderItem = useCallback((props) => {
|
|
425
|
+
const { item, id, ...rest } = props;
|
|
426
|
+
return (
|
|
427
|
+
<SortableGridItem key={id} id={id} {...rest}>
|
|
428
|
+
<View style={styles.gridItem}>
|
|
429
|
+
<Text>{item.label}</Text>
|
|
430
|
+
</View>
|
|
431
|
+
</SortableGridItem>
|
|
432
|
+
);
|
|
433
|
+
}, []);
|
|
434
|
+
|
|
435
|
+
return (
|
|
436
|
+
<SortableGrid
|
|
437
|
+
data={apps}
|
|
438
|
+
renderItem={renderItem}
|
|
439
|
+
dimensions={{ columns: 4, itemWidth: 80, itemHeight: 80, rowGap: 12, columnGap: 12 }}
|
|
440
|
+
/>
|
|
441
|
+
);
|
|
442
|
+
}
|
|
842
443
|
```
|
|
843
444
|
|
|
445
|
+
> **More examples:** [Quick Start Guide](https://reanimated-dnd-docs.vercel.app/docs/getting-started/quick-start) · [Sortable Lists](https://reanimated-dnd-docs.vercel.app/docs/examples/sortable-lists) · [Sortable Grids](https://reanimated-dnd-docs.vercel.app/docs/api/components/sortable-grid) · [All Examples](https://reanimated-dnd-docs.vercel.app/docs/examples/basic-drag-drop)
|
|
446
|
+
|
|
447
|
+
## 📚 Documentation
|
|
448
|
+
|
|
449
|
+
Visit **[reanimated-dnd-docs.vercel.app](https://reanimated-dnd-docs.vercel.app/)** for the full documentation:
|
|
450
|
+
|
|
451
|
+
- **[Getting Started](https://reanimated-dnd-docs.vercel.app/docs/getting-started/installation)** — Installation, setup, and quick start
|
|
452
|
+
- **[Components](https://reanimated-dnd-docs.vercel.app/docs/api/components/draggable)** — Draggable, Droppable, Sortable, SortableGrid, SortableItem
|
|
453
|
+
- **[Hooks](https://reanimated-dnd-docs.vercel.app/docs/api/hooks/useDraggable)** — useDraggable, useDroppable, useSortable, useGridSortable
|
|
454
|
+
- **[Guides](https://reanimated-dnd-docs.vercel.app/docs/guides/animations)** — Animations, collision algorithms, performance, accessibility
|
|
455
|
+
- **[Examples](https://reanimated-dnd-docs.vercel.app/docs/examples/basic-drag-drop)** — Interactive code examples for every feature
|
|
456
|
+
- **[API Reference](https://reanimated-dnd-docs.vercel.app/docs/api/overview)** — Complete types, enums, and utilities
|
|
457
|
+
|
|
844
458
|
## 🏃♂️ Running the Example App
|
|
845
459
|
|
|
846
460
|
1. Clone the repository:
|
|
@@ -850,33 +464,49 @@ git clone https://github.com/entropyconquers/react-native-reanimated-dnd.git
|
|
|
850
464
|
cd react-native-reanimated-dnd
|
|
851
465
|
```
|
|
852
466
|
|
|
853
|
-
2. Install dependencies:
|
|
467
|
+
2. Install dependencies (uses npm workspaces):
|
|
854
468
|
|
|
855
469
|
```bash
|
|
856
470
|
npm install
|
|
857
|
-
cd example-app
|
|
858
|
-
npm install
|
|
859
471
|
```
|
|
860
472
|
|
|
861
473
|
3. Run the example app:
|
|
862
474
|
|
|
863
475
|
```bash
|
|
864
476
|
# iOS
|
|
865
|
-
|
|
477
|
+
npm run start --workspace example-app
|
|
478
|
+
# then press 'i' for iOS or 'a' for Android
|
|
866
479
|
|
|
867
|
-
#
|
|
868
|
-
npx expo run:
|
|
480
|
+
# Or run directly:
|
|
481
|
+
npx expo run:ios --cwd example-app
|
|
482
|
+
npx expo run:android --cwd example-app
|
|
869
483
|
```
|
|
870
484
|
|
|
871
|
-
|
|
485
|
+
**Note:** Reanimated 4 requires the New Architecture, so you must use a development build (`npx expo run:ios` / `npx expo run:android`), not Expo Go.
|
|
486
|
+
|
|
487
|
+
The example app includes all 18 interactive examples showcasing every feature of the library.
|
|
872
488
|
|
|
873
489
|
## 🗺️ Project Roadmap
|
|
874
490
|
|
|
875
491
|
I am constantly working to improve React Native Reanimated DnD. Here's what's coming next:
|
|
876
492
|
|
|
877
|
-
###
|
|
493
|
+
### ✅ v2.0.0 (Current)
|
|
878
494
|
|
|
879
|
-
**
|
|
495
|
+
**Reanimated 4 + Worklets Migration**
|
|
496
|
+
|
|
497
|
+
- 🚀 **Reanimated 4 & Worklets** - Migrated from Reanimated 3 to Reanimated 4 with react-native-worklets
|
|
498
|
+
- 🏗️ **New Architecture** - Built for React Native's New Architecture (required by Reanimated 4)
|
|
499
|
+
- 📦 **Expo SDK 55** - Tested and compatible with Expo SDK 55 and React Native 0.83
|
|
500
|
+
- 🔧 **Handle Registration** - Replaced tree-walking handle detection with a registration pattern
|
|
501
|
+
- ⚡ **Improved Scheduling** - Uses `scheduleOnRN`/`scheduleOnUI` for better worklet-to-JS communication
|
|
502
|
+
- 🎯 **Pre-drag Delay** - New `preDragDelay` prop for distinguishing taps from drags
|
|
503
|
+
- 📐 **npm Workspaces** - Example app now uses workspace-based development setup
|
|
504
|
+
- 🔲 **Sortable Grids** - 2D grid drag-and-drop with flexible layouts and insert/swap modes
|
|
505
|
+
- ↕ **Dynamic Heights** - Sortable list support for variable item heights
|
|
506
|
+
|
|
507
|
+
### 🎯 Next Release
|
|
508
|
+
|
|
509
|
+
**Focus: Enhanced Functionality & New Features**
|
|
880
510
|
|
|
881
511
|
- 🐛 **Bug Fixes & Issues Resolution**
|
|
882
512
|
|
|
@@ -885,19 +515,6 @@ I am constantly working to improve React Native Reanimated DnD. Here's what's co
|
|
|
885
515
|
- Gesture handling improvements
|
|
886
516
|
- API Improvements
|
|
887
517
|
|
|
888
|
-
- 📐 **Sortable Grids**
|
|
889
|
-
|
|
890
|
-
- 2D grid drag-and-drop support
|
|
891
|
-
- Flexible grid layouts (2x2, 3x3, custom)
|
|
892
|
-
- Smart auto-positioning and gap management
|
|
893
|
-
- Responsive grid behavior
|
|
894
|
-
|
|
895
|
-
- ↔️ **Horizontal Sortable Lists**
|
|
896
|
-
|
|
897
|
-
- Full horizontal scrolling support
|
|
898
|
-
- Auto-scroll for out-of-view items
|
|
899
|
-
- Customizable scroll behavior
|
|
900
|
-
|
|
901
518
|
- 🪆 **Nested Sortable Lists**
|
|
902
519
|
|
|
903
520
|
- Multi-level hierarchy support
|
|
@@ -917,6 +534,30 @@ Vote on features you'd like to see by raising an issue.
|
|
|
917
534
|
|
|
918
535
|
**Have an idea?** [Open a feature request](https://github.com/entropyconquers/react-native-reanimated-dnd/issues/new?assignees=&labels=enhancement&template=feature_request.md) and let me know!
|
|
919
536
|
|
|
537
|
+
## 🤖 AI Integration Skill
|
|
538
|
+
|
|
539
|
+
Speed up development with the official [agent skill](https://agentskills.io). It teaches AI coding agents (Claude Code, Codex, Cursor, Gemini CLI, Copilot, and 30+ more) the full API so they generate correct code — no hallucinated props.
|
|
540
|
+
|
|
541
|
+
```bash
|
|
542
|
+
# Install via npx skills (auto-detects your agents)
|
|
543
|
+
npx skills add entropyconquers/react-native-reanimated-dnd
|
|
544
|
+
|
|
545
|
+
# Or install globally
|
|
546
|
+
npx skills add entropyconquers/react-native-reanimated-dnd -g
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
Once installed, just describe what you want:
|
|
550
|
+
|
|
551
|
+
> "Add a sortable list where I can reorder items by dragging"
|
|
552
|
+
|
|
553
|
+
> "Create a drag and drop interface with drop zones"
|
|
554
|
+
|
|
555
|
+
> "Make a reorderable 3-column grid"
|
|
556
|
+
|
|
557
|
+
Your agent will generate complete, working implementations with correct imports, props, and state management.
|
|
558
|
+
|
|
559
|
+
The skill ships in both `.claude/skills/` (Claude Code) and `.agents/skills/` (Codex, Cursor, Gemini CLI, Copilot, and others) for universal agent compatibility. See the [AI Integration Skill docs](https://reanimated-dnd-docs.vercel.app/docs/getting-started/ai-skill) for all installation options and the full agent compatibility table.
|
|
560
|
+
|
|
920
561
|
## 🤝 Contributing
|
|
921
562
|
|
|
922
563
|
Contributions are always welcome! We believe in building this library together with the community.
|
|
@@ -973,6 +614,6 @@ If this library has helped you build amazing apps, consider supporting its devel
|
|
|
973
614
|
|
|
974
615
|
**Made with ❤️ for the React Native community**
|
|
975
616
|
|
|
976
|
-
[⭐ Star on GitHub](https://github.com/entropyconquers/react-native-reanimated-dnd) • [📱 Try the Demo](https://github.com/entropyconquers/react-native-reanimated-dnd/tree/main/example-app) • [📖 Documentation](https://
|
|
617
|
+
[⭐ Star on GitHub](https://github.com/entropyconquers/react-native-reanimated-dnd) • [📱 Try the Demo](https://github.com/entropyconquers/react-native-reanimated-dnd/tree/main/example-app) • [📖 Documentation](https://reanimated-dnd-docs.vercel.app/)
|
|
977
618
|
|
|
978
619
|
</div>
|