react-native-reanimated-dnd 1.0.4 â 1.1.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 +421 -75
- package/lib/components/Draggable.js +1 -1
- package/lib/components/Droppable.js +1 -1
- package/lib/components/Sortable.d.ts +1 -3
- package/lib/components/Sortable.js +1 -1
- package/lib/components/SortableItem.d.ts +1 -1
- package/lib/components/SortableItem.js +1 -1
- package/lib/components/sortableUtils.d.ts +12 -0
- package/lib/components/sortableUtils.js +1 -1
- package/lib/context/DropContext.js +1 -1
- package/lib/hooks/useDraggable.js +1 -1
- package/lib/hooks/useDroppable.js +1 -1
- package/lib/hooks/useHorizontalSortable.d.ts +2 -0
- package/lib/hooks/useHorizontalSortable.js +1 -0
- package/lib/hooks/useHorizontalSortableList.d.ts +4 -0
- package/lib/hooks/useHorizontalSortableList.js +1 -0
- package/lib/hooks/useSortable.js +1 -1
- package/lib/hooks/useSortableList.js +1 -1
- package/lib/types/context.js +1 -1
- package/lib/types/draggable.js +1 -1
- package/lib/types/sortable.d.ts +130 -7
- package/lib/types/sortable.js +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -16,11 +16,14 @@ _Powerful, performant, and built for the modern React Native developer_
|
|
|
16
16
|
|
|
17
17
|
<br />
|
|
18
18
|
|
|
19
|
+
<a href="https://www.npmjs.com/package/react-native-reanimated-dnd" target="_blank">
|
|
20
|
+
<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
|
+
</a>
|
|
19
22
|
<a href="https://react-native-reanimated-dnd.netlify.app/" target="_blank">
|
|
20
|
-
<img src="https://img.shields.io/badge/ð%20Read%20the%20Docs-4f46e5?style=for-the-badge&logo=gitbook&logoColor=white&labelColor=1e293b&color=6366f1" alt="Documentation"
|
|
23
|
+
<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"/>
|
|
21
24
|
</a>
|
|
22
25
|
<a href="#-interactive-examples" target="_blank">
|
|
23
|
-
<img src="https://img.shields.io/badge/ðą%20Try%20Live%20Demo-
|
|
26
|
+
<img src="https://img.shields.io/badge/ðą%20Try%20Live%20Demo-fcba03?style=for-the-badge&logo=expo&logoColor=white&labelColor=1e293b&fontSize=24" alt="Live Demo" height="36"/>
|
|
24
27
|
</a>
|
|
25
28
|
|
|
26
29
|
</div>
|
|
@@ -36,6 +39,9 @@ After countless attempts with drag-and-drop solutions that don't work or are sim
|
|
|
36
39
|
## âĻ Features
|
|
37
40
|
|
|
38
41
|
- ð **High Performance** - Built with Reanimated 3 for buttery-smooth 60fps animations
|
|
42
|
+
- ðïļ **Full RN Fabric Support** - Works seamlessly with both New Architecture and Old Architecture
|
|
43
|
+
- ðĶ **Expo Compatible** - Zero configuration needed, works out of the box with Expo
|
|
44
|
+
- ðŠķ **Tiny Bundle Size** - Only 70kb unpacked size, won't bloat your app
|
|
39
45
|
- ðŊ **Flexible API** - From simple drag-and-drop to complex sortable lists
|
|
40
46
|
- ðą **React Native First** - Designed specifically for mobile, not ported from web
|
|
41
47
|
- ð§ **TypeScript Ready** - Full type safety with comprehensive definitions
|
|
@@ -67,7 +73,7 @@ After countless attempts with drag-and-drop solutions that don't work or are sim
|
|
|
67
73
|
|
|
68
74
|
<img src="https://github.com/user-attachments/assets/80f923f6-7c5f-42e9-9817-7770ee27a70b" alt="Expo QR Code" width="200" height="200" />
|
|
69
75
|
|
|
70
|
-
|
|
76
|
+
_Scan with your camera or Expo Go app_
|
|
71
77
|
|
|
72
78
|
</td>
|
|
73
79
|
<td align="center" width="50%">
|
|
@@ -92,7 +98,7 @@ After countless attempts with drag-and-drop solutions that don't work or are sim
|
|
|
92
98
|
<img src="https://img.shields.io/badge/ð%20Documentation-Visit%20Docs-4f46e5?style=for-the-badge&logo=gitbook&logoColor=white&labelColor=1e293b" alt="Documentation" />
|
|
93
99
|
</a>
|
|
94
100
|
|
|
95
|
-
|
|
101
|
+
_Comprehensive guides, API reference, and interactive examples_
|
|
96
102
|
|
|
97
103
|
</div>
|
|
98
104
|
|
|
@@ -116,7 +122,8 @@ The example app includes:
|
|
|
116
122
|
<td align="center" width="50%">
|
|
117
123
|
|
|
118
124
|
### ð Sortable Lists
|
|
119
|
-
|
|
125
|
+
|
|
126
|
+
_Drag and drop to reorder items with smooth animations_
|
|
120
127
|
|
|
121
128
|
https://github.com/user-attachments/assets/1cd1929c-724b-4dda-a916-f3e69f917f7b
|
|
122
129
|
|
|
@@ -126,7 +133,8 @@ https://github.com/user-attachments/assets/1cd1929c-724b-4dda-a916-f3e69f917f7b
|
|
|
126
133
|
<td align="center" width="50%">
|
|
127
134
|
|
|
128
135
|
### ðŊ Collision Detection
|
|
129
|
-
|
|
136
|
+
|
|
137
|
+
_Multiple algorithms for precise drop targeting_
|
|
130
138
|
|
|
131
139
|
https://github.com/user-attachments/assets/379040d7-8489-430b-bae4-3fcbde34264e
|
|
132
140
|
|
|
@@ -138,7 +146,8 @@ https://github.com/user-attachments/assets/379040d7-8489-430b-bae4-3fcbde34264e
|
|
|
138
146
|
<td align="center" width="50%">
|
|
139
147
|
|
|
140
148
|
### ðŠ Drag Handles
|
|
141
|
-
|
|
149
|
+
|
|
150
|
+
_Precise control with dedicated drag areas_
|
|
142
151
|
|
|
143
152
|
https://github.com/user-attachments/assets/ec051d5b-8ba0-41b7-86ae-379de26a97dd
|
|
144
153
|
|
|
@@ -148,7 +157,8 @@ https://github.com/user-attachments/assets/ec051d5b-8ba0-41b7-86ae-379de26a97dd
|
|
|
148
157
|
<td align="center" width="50%">
|
|
149
158
|
|
|
150
159
|
### ðĶ Bounded Dragging
|
|
151
|
-
|
|
160
|
+
|
|
161
|
+
_Constrain movement within specific boundaries_
|
|
152
162
|
|
|
153
163
|
https://github.com/user-attachments/assets/7bd5045b-47c4-4d9b-a0c5-eb89122ec9c0
|
|
154
164
|
|
|
@@ -160,7 +170,8 @@ https://github.com/user-attachments/assets/7bd5045b-47c4-4d9b-a0c5-eb89122ec9c0
|
|
|
160
170
|
<td align="center" width="50%">
|
|
161
171
|
|
|
162
172
|
### âĻ Active Drop Styles
|
|
163
|
-
|
|
173
|
+
|
|
174
|
+
_Visual feedback during drag operations_
|
|
164
175
|
|
|
165
176
|
https://github.com/user-attachments/assets/3b8a3d00-38ad-4532-bd42-173037ea61b9
|
|
166
177
|
|
|
@@ -170,7 +181,8 @@ https://github.com/user-attachments/assets/3b8a3d00-38ad-4532-bd42-173037ea61b9
|
|
|
170
181
|
<td align="center" width="50%">
|
|
171
182
|
|
|
172
183
|
### ð State Management
|
|
173
|
-
|
|
184
|
+
|
|
185
|
+
_Complete lifecycle tracking and callbacks_
|
|
174
186
|
|
|
175
187
|
https://github.com/user-attachments/assets/da5e526f-f2d2-4dc5-96b5-3fecc4faf57a
|
|
176
188
|
|
|
@@ -205,24 +217,18 @@ Follow the setup guides:
|
|
|
205
217
|
|
|
206
218
|
```tsx
|
|
207
219
|
import React from "react";
|
|
208
|
-
import { View, Text } from "react-native";
|
|
220
|
+
import { View, Text, StyleSheet } from "react-native";
|
|
209
221
|
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
|
210
222
|
import { Draggable, DropProvider } from "react-native-reanimated-dnd";
|
|
211
223
|
|
|
212
224
|
export default function App() {
|
|
213
225
|
return (
|
|
214
|
-
<GestureHandlerRootView style={
|
|
226
|
+
<GestureHandlerRootView style={styles.container}>
|
|
215
227
|
<DropProvider>
|
|
216
|
-
<View style={
|
|
228
|
+
<View style={styles.content}>
|
|
217
229
|
<Draggable data={{ id: "1", title: "Drag me!" }}>
|
|
218
|
-
<View
|
|
219
|
-
style={
|
|
220
|
-
padding: 20,
|
|
221
|
-
backgroundColor: "#007AFF",
|
|
222
|
-
borderRadius: 8,
|
|
223
|
-
}}
|
|
224
|
-
>
|
|
225
|
-
<Text style={{ color: "white" }}>Drag me around!</Text>
|
|
230
|
+
<View style={styles.draggableItem}>
|
|
231
|
+
<Text style={styles.itemText}>ðŊ Drag me around!</Text>
|
|
226
232
|
</View>
|
|
227
233
|
</Draggable>
|
|
228
234
|
</View>
|
|
@@ -230,13 +236,44 @@ export default function App() {
|
|
|
230
236
|
</GestureHandlerRootView>
|
|
231
237
|
);
|
|
232
238
|
}
|
|
239
|
+
|
|
240
|
+
const styles = StyleSheet.create({
|
|
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
|
+
});
|
|
233
270
|
```
|
|
234
271
|
|
|
235
272
|
### Drag & Drop with Multiple Zones
|
|
236
273
|
|
|
237
274
|
```tsx
|
|
238
275
|
import React from "react";
|
|
239
|
-
import {
|
|
276
|
+
import { Alert, StyleSheet, Text, View } from "react-native";
|
|
240
277
|
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
|
241
278
|
import {
|
|
242
279
|
Draggable,
|
|
@@ -245,95 +282,322 @@ import {
|
|
|
245
282
|
} from "react-native-reanimated-dnd";
|
|
246
283
|
|
|
247
284
|
export default function DragDropExample() {
|
|
248
|
-
const handleDrop = (data: any) => {
|
|
249
|
-
|
|
285
|
+
const handleDrop = (data: any, zoneId: string) => {
|
|
286
|
+
Alert.alert("Item Dropped", `"${data.title}" dropped in ${zoneId}`);
|
|
250
287
|
};
|
|
251
288
|
|
|
252
289
|
return (
|
|
253
|
-
<GestureHandlerRootView style={
|
|
290
|
+
<GestureHandlerRootView style={styles.container}>
|
|
254
291
|
<DropProvider>
|
|
255
|
-
<View style={
|
|
256
|
-
{/* Draggable Item */}
|
|
257
|
-
<Draggable data={{ id: "1", title: "Drag me!" }}>
|
|
258
|
-
<View style={styles.draggableItem}>
|
|
259
|
-
<Text>ðĶ Drag me to a zone</Text>
|
|
260
|
-
</View>
|
|
261
|
-
</Draggable>
|
|
262
|
-
|
|
292
|
+
<View style={styles.content}>
|
|
263
293
|
{/* Drop Zones */}
|
|
264
|
-
<
|
|
265
|
-
<
|
|
266
|
-
<Text>ðŊ Drop Zone 1</Text>
|
|
267
|
-
</View>
|
|
268
|
-
</Droppable>
|
|
294
|
+
<View style={styles.dropZonesSection}>
|
|
295
|
+
<Text style={styles.sectionTitle}>Drop Zones</Text>
|
|
269
296
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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>
|
|
319
|
+
|
|
320
|
+
{/* Draggable Item */}
|
|
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>
|
|
275
329
|
</View>
|
|
276
330
|
</DropProvider>
|
|
277
331
|
</GestureHandlerRootView>
|
|
278
332
|
);
|
|
279
333
|
}
|
|
334
|
+
|
|
335
|
+
const styles = StyleSheet.create({
|
|
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
|
+
});
|
|
280
418
|
```
|
|
281
419
|
|
|
282
420
|
### Sortable List
|
|
283
421
|
|
|
284
422
|
```tsx
|
|
285
|
-
import React, { useState } from "react";
|
|
286
|
-
import {
|
|
423
|
+
import React, { useCallback, useState } from "react";
|
|
424
|
+
import { StyleSheet, Text, View } from "react-native";
|
|
287
425
|
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
|
288
|
-
import {
|
|
426
|
+
import {
|
|
427
|
+
Sortable,
|
|
428
|
+
SortableItem,
|
|
429
|
+
SortableRenderItemProps,
|
|
430
|
+
} from "react-native-reanimated-dnd";
|
|
289
431
|
|
|
290
432
|
interface Task {
|
|
291
433
|
id: string;
|
|
292
434
|
title: string;
|
|
435
|
+
completed: boolean;
|
|
293
436
|
}
|
|
294
437
|
|
|
295
438
|
export default function SortableExample() {
|
|
296
439
|
const [tasks, setTasks] = useState<Task[]>([
|
|
297
|
-
{ id: "1", title: "Learn React Native" },
|
|
298
|
-
{ id: "2", title: "Build an app" },
|
|
299
|
-
{ id: "3", title: "Deploy to store" },
|
|
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 },
|
|
300
444
|
]);
|
|
301
445
|
|
|
302
|
-
const renderTask = (
|
|
303
|
-
<
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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]
|
|
324
503
|
);
|
|
325
504
|
|
|
326
505
|
return (
|
|
327
|
-
<GestureHandlerRootView style={
|
|
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
|
+
|
|
328
512
|
<Sortable
|
|
329
513
|
data={tasks}
|
|
330
514
|
renderItem={renderTask}
|
|
331
|
-
itemHeight={
|
|
332
|
-
style={
|
|
515
|
+
itemHeight={80}
|
|
516
|
+
style={styles.list}
|
|
333
517
|
/>
|
|
334
518
|
</GestureHandlerRootView>
|
|
335
519
|
);
|
|
336
520
|
}
|
|
521
|
+
|
|
522
|
+
const styles = StyleSheet.create({
|
|
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
|
+
});
|
|
337
601
|
```
|
|
338
602
|
|
|
339
603
|
## ð API Reference
|
|
@@ -606,9 +870,73 @@ npx expo run:android
|
|
|
606
870
|
|
|
607
871
|
The example app includes all 15 interactive examples showcasing every feature of the library.
|
|
608
872
|
|
|
873
|
+
## ðšïļ Project Roadmap
|
|
874
|
+
|
|
875
|
+
I am constantly working to improve React Native Reanimated DnD. Here's what's coming next:
|
|
876
|
+
|
|
877
|
+
### ðŊ Next Release (v2.0.0)
|
|
878
|
+
|
|
879
|
+
**Focus: Enhanced Functionality & Bug Fixes**
|
|
880
|
+
|
|
881
|
+
- ð **Bug Fixes & Issues Resolution**
|
|
882
|
+
|
|
883
|
+
- Address existing reported issues
|
|
884
|
+
- Performance optimizations
|
|
885
|
+
- Gesture handling improvements
|
|
886
|
+
- API Improvements
|
|
887
|
+
|
|
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
|
+
- ðŠ **Nested Sortable Lists**
|
|
902
|
+
|
|
903
|
+
- Multi-level hierarchy support
|
|
904
|
+
- Collapse/expand functionality
|
|
905
|
+
- Parent-child relationship management
|
|
906
|
+
- Tree-like data structure handling
|
|
907
|
+
|
|
908
|
+
- ð **Kanban Board Support**
|
|
909
|
+
- Cross-list dragging capabilities
|
|
910
|
+
- Multiple column support
|
|
911
|
+
- Inter-list item transfer
|
|
912
|
+
- Board-level state management
|
|
913
|
+
|
|
914
|
+
### ðĄ Community Requests
|
|
915
|
+
|
|
916
|
+
Vote on features you'd like to see by raising an issue.
|
|
917
|
+
|
|
918
|
+
**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
|
+
|
|
609
920
|
## ðĪ Contributing
|
|
610
921
|
|
|
611
|
-
Contributions are always welcome!
|
|
922
|
+
Contributions are always welcome! We believe in building this library together with the community.
|
|
923
|
+
|
|
924
|
+
**Ways to contribute:**
|
|
925
|
+
|
|
926
|
+
- ð Report bugs and issues
|
|
927
|
+
- âĻ Suggest new features
|
|
928
|
+
- ð§ Submit pull requests
|
|
929
|
+
- ð Improve documentation
|
|
930
|
+
- ð§Š Write tests
|
|
931
|
+
- ðŽ Help others in discussions
|
|
932
|
+
|
|
933
|
+
Please see our [**Contributing Guide**](CONTRIBUTING.md) for detailed information on:
|
|
934
|
+
|
|
935
|
+
- Setting up the development environment
|
|
936
|
+
- Code style guidelines
|
|
937
|
+
- Pull request process
|
|
938
|
+
- Testing requirements
|
|
939
|
+
- Community guidelines
|
|
612
940
|
|
|
613
941
|
## ð License
|
|
614
942
|
|
|
@@ -621,6 +949,24 @@ MIT ÂĐ [Vishesh Raheja](https://github.com/entropyconquers)
|
|
|
621
949
|
- Inspired by the React ecosystem's drag-and-drop libraries
|
|
622
950
|
- Special thanks to the React Native community for feedback and contributions
|
|
623
951
|
|
|
952
|
+
## â Support the Project
|
|
953
|
+
|
|
954
|
+
<div align="center">
|
|
955
|
+
<body>
|
|
956
|
+
If this library has helped you build amazing apps, consider supporting its development!
|
|
957
|
+
</br></br>
|
|
958
|
+
<a href="https://www.buymeacoffee.com/entropyconquers" target="_blank">
|
|
959
|
+
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me a Coffee" style="height: 60px; width: 217px;">
|
|
960
|
+
</a>
|
|
961
|
+
</br>
|
|
962
|
+
</br>
|
|
963
|
+
Your support helps maintain and improve this library for the entire React Native community! ð
|
|
964
|
+
</body>
|
|
965
|
+
</html>
|
|
966
|
+
|
|
967
|
+
</div>
|
|
968
|
+
<br/>
|
|
969
|
+
|
|
624
970
|
---
|
|
625
971
|
|
|
626
972
|
<div align="center">
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import React,{createContext,useContext}from"react";import Animated from"react-native-reanimated";import{GestureDetector}from"react-native-gesture-handler";import{useDraggable}from"../hooks/useDraggable";const DraggableContext=createContext(null)
|
|
1
|
+
import React,{createContext,useContext}from"react";import Animated from"react-native-reanimated";import{GestureDetector}from"react-native-gesture-handler";import{useDraggable}from"../hooks/useDraggable";const DraggableContext=createContext(null);const Handle=({children,style})=>{const draggableContext=useContext(DraggableContext);if(!draggableContext){console.warn("Draggable.Handle must be used within a Draggable component");return React.createElement(React.Fragment,null,children)}return React.createElement(GestureDetector,{gesture:draggableContext.gesture},React.createElement(Animated.View,{style},children))};const DraggableComponent=({style:componentStyle,children,...useDraggableHookOptions})=>{const{animatedViewProps,gesture,state,hasHandle,animatedViewRef}=useDraggable({...useDraggableHookOptions,children,handleComponent:Handle});const contextValue={gesture,state};const content=React.createElement(Animated.View,{ref:animatedViewRef,...animatedViewProps,style:[componentStyle,animatedViewProps.style],collapsable:false},React.createElement(DraggableContext.Provider,{value:contextValue},children));if(hasHandle){return content}else{return React.createElement(GestureDetector,{gesture},content)}};export const Draggable=Object.assign(DraggableComponent,{Handle});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import React from"react";import Animated from"react-native-reanimated";import{useDroppable}from"../hooks/useDroppable";let _nextDroppableId=1;export const _getUniqueDroppableId=()=>_nextDroppableId++;export const Droppable=({onDrop
|
|
1
|
+
import React from"react";import Animated from"react-native-reanimated";import{useDroppable}from"../hooks/useDroppable";let _nextDroppableId=1;export const _getUniqueDroppableId=()=>_nextDroppableId++;export const Droppable=({onDrop,dropDisabled,onActiveChange,dropAlignment,dropOffset,activeStyle,droppableId,capacity,style,children})=>{const{viewProps,animatedViewRef}=useDroppable({onDrop,dropDisabled,onActiveChange,dropAlignment,dropOffset,activeStyle,droppableId,capacity});return React.createElement(Animated.View,{ref:animatedViewRef,...viewProps,style:[style,viewProps.style],collapsable:false},children)};
|
|
@@ -1,5 +1,3 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { SortableProps } from "../types/sortable";
|
|
3
|
-
export declare
|
|
4
|
-
id: string;
|
|
5
|
-
}>({ data, renderItem, itemHeight, style, contentContainerStyle, itemKeyExtractor, }: SortableProps<TData>): React.JSX.Element;
|
|
3
|
+
export declare const Sortable: React.MemoExoticComponent<({ data, renderItem, ...props }: SortableProps<any>) => React.JSX.Element>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React,{memo,useCallback}from"react";import{StyleSheet}from"react-native";import Animated from"react-native-reanimated";import{GestureHandlerRootView,FlatList,ScrollView}from"react-native-gesture-handler";import{DropProvider}from"../context/DropContext";import{SortableDirection}from"../types/sortable";import{useSortableList}from"../hooks/useSortableList";import{useHorizontalSortableList}from"../hooks/useHorizontalSortableList";import{dataHash}from"./sortableUtils";const AnimatedFlatList=Animated.createAnimatedComponent(FlatList);const AnimatedScrollView=Animated.createAnimatedComponent(ScrollView);function SortableComponent({data,renderItem,direction=SortableDirection.Vertical,itemHeight,itemWidth,gap=0,paddingHorizontal=0,style,contentContainerStyle,itemKeyExtractor=item=>item.id,useFlatList=true}){if(direction===SortableDirection.Vertical&&!itemHeight){throw new Error("itemHeight is required when direction is vertical")}if(direction===SortableDirection.Horizontal&&!itemWidth){throw new Error("itemWidth is required when direction is horizontal")}if(direction===SortableDirection.Horizontal){const horizontalOptions={data,itemWidth,gap,paddingHorizontal,itemKeyExtractor};const{scrollViewRef:horizontalScrollViewRef,dropProviderRef:horizontalDropProviderRef,handleScroll:horizontalHandleScroll,handleScrollEnd:horizontalHandleScrollEnd,contentWidth,getItemProps:getHorizontalItemProps}=useHorizontalSortableList(horizontalOptions);const memoizedHorizontalRenderItem=useCallback((({item,index})=>{const itemProps=getHorizontalItemProps(item,index);const sortableItemProps={item,index,direction:SortableDirection.Horizontal,autoScrollHorizontalDirection:itemProps.autoScrollDirection,...itemProps};return renderItem(sortableItemProps)}),[getHorizontalItemProps,renderItem]);return React.createElement(GestureHandlerRootView,{style:styles.flex},React.createElement(DropProvider,{ref:horizontalDropProviderRef},useFlatList?React.createElement(AnimatedFlatList,{ref:horizontalScrollViewRef,data,keyExtractor:itemKeyExtractor,horizontal:true,renderItem:memoizedHorizontalRenderItem,onScroll:horizontalHandleScroll,scrollEventThrottle:16,style:[styles.scrollView,style],contentContainerStyle:[{width:contentWidth},contentContainerStyle],onScrollEndDrag:horizontalHandleScrollEnd,onMomentumScrollEnd:horizontalHandleScrollEnd,simultaneousHandlers:horizontalDropProviderRef,showsHorizontalScrollIndicator:false}):React.createElement(AnimatedScrollView,{ref:horizontalScrollViewRef,onScroll:horizontalHandleScroll,scrollEventThrottle:16,horizontal:true,style:[styles.scrollView,style],contentContainerStyle:[{width:contentWidth},contentContainerStyle],onScrollEndDrag:horizontalHandleScrollEnd,onMomentumScrollEnd:horizontalHandleScrollEnd,simultaneousHandlers:horizontalDropProviderRef,showsHorizontalScrollIndicator:false},data.map(((item,index)=>{const itemProps=getHorizontalItemProps(item,index);const sortableItemProps={item,index,direction:SortableDirection.Horizontal,autoScrollHorizontalDirection:itemProps.autoScrollDirection,...itemProps};return renderItem(sortableItemProps)})))))}const verticalOptions={data,itemHeight,itemKeyExtractor};const{scrollViewRef,dropProviderRef,handleScroll,handleScrollEnd,contentHeight,getItemProps}=useSortableList(verticalOptions);const memoizedVerticalRenderItem=useCallback((({item,index})=>{const itemProps=getItemProps(item,index);const sortableItemProps={item,index,direction:SortableDirection.Vertical,...itemProps};return renderItem(sortableItemProps)}),[getItemProps,renderItem]);return React.createElement(GestureHandlerRootView,{style:styles.flex},React.createElement(DropProvider,{ref:dropProviderRef},useFlatList?React.createElement(AnimatedFlatList,{ref:scrollViewRef,data,keyExtractor:itemKeyExtractor,renderItem:memoizedVerticalRenderItem,onScroll:handleScroll,scrollEventThrottle:16,style:[styles.scrollView,style],contentContainerStyle:[{height:contentHeight},contentContainerStyle],onScrollEndDrag:handleScrollEnd,onMomentumScrollEnd:handleScrollEnd,simultaneousHandlers:dropProviderRef,showsVerticalScrollIndicator:false}):React.createElement(AnimatedScrollView,{ref:scrollViewRef,onScroll:handleScroll,scrollEventThrottle:16,style:[styles.scrollView,style],contentContainerStyle:[{height:contentHeight},contentContainerStyle],onScrollEndDrag:handleScrollEnd,onMomentumScrollEnd:handleScrollEnd,simultaneousHandlers:dropProviderRef},data.map(((item,index)=>{const itemProps=getItemProps(item,index);const sortableItemProps={item,index,direction:SortableDirection.Vertical,...itemProps};return renderItem(sortableItemProps)})))))}export const Sortable=memo((({data,renderItem,...props})=>{const dataHashKey=dataHash(data);return React.createElement(SortableComponent,{data,renderItem,...props,key:dataHashKey})}));const styles=StyleSheet.create({flex:{flex:1},scrollView:{flex:1,position:"relative",backgroundColor:"white"}});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { SortableItemProps, SortableHandleProps } from "../types/sortable";
|
|
3
|
-
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;
|
|
3
|
+
export declare function SortableItem<T>({ id, data, positions, direction, lowerBound, leftBound, autoScrollDirection, autoScrollHorizontalDirection, itemsCount, itemHeight, itemWidth, gap, paddingHorizontal, containerHeight, containerWidth, children, style, animatedStyle: customAnimatedStyle, onMove, onDragStart, onDrop, onDragging, onDraggingHorizontal, }: SortableItemProps<T>): React.JSX.Element;
|
|
4
4
|
export declare namespace SortableItem {
|
|
5
5
|
var Handle: ({ children, style }: SortableHandleProps) => React.JSX.Element;
|
|
6
6
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import React,{createContext,useContext}from"react";import Animated from"react-native-reanimated";import{PanGestureHandler}from"react-native-gesture-handler";import{useSortable}from"../hooks/useSortable";const SortableContext=createContext(null)
|
|
1
|
+
import React,{createContext,useContext}from"react";import Animated from"react-native-reanimated";import{PanGestureHandler}from"react-native-gesture-handler";import{useSortable}from"../hooks/useSortable";import{useHorizontalSortable}from"../hooks/useHorizontalSortable";import{SortableDirection}from"../types/sortable";const SortableContext=createContext(null);const SortableHandle=({children,style})=>{const sortableContext=useContext(SortableContext);if(!sortableContext){console.warn("SortableHandle must be used within a SortableItem component");return React.createElement(React.Fragment,null,children)}return React.createElement(PanGestureHandler,{onGestureEvent:sortableContext.panGestureHandler},React.createElement(Animated.View,{style},children))};export function SortableItem({id,data,positions,direction=SortableDirection.Vertical,lowerBound,leftBound,autoScrollDirection,autoScrollHorizontalDirection,itemsCount,itemHeight,itemWidth,gap=0,paddingHorizontal=0,containerHeight,containerWidth,children,style,animatedStyle:customAnimatedStyle,onMove,onDragStart,onDrop,onDragging,onDraggingHorizontal}){if(direction===SortableDirection.Vertical&&(!itemHeight||!lowerBound||!autoScrollDirection)){throw new Error("itemHeight, lowerBound, and autoScrollDirection are required for vertical direction")}if(direction===SortableDirection.Horizontal&&(!itemWidth||!leftBound||!autoScrollHorizontalDirection)){throw new Error("itemWidth, leftBound, and autoScrollHorizontalDirection are required for horizontal direction")}if(direction===SortableDirection.Horizontal){const horizontalOptions={id,positions,leftBound,autoScrollDirection:autoScrollHorizontalDirection,itemsCount,itemWidth,gap,paddingHorizontal,containerWidth,onMove,onDragStart,onDrop,onDragging:onDraggingHorizontal,children,handleComponent:SortableHandle};const{animatedStyle:horizontalAnimatedStyle,panGestureHandler:horizontalPanGestureHandler,isMoving:horizontalIsMoving,hasHandle:horizontalHasHandle}=useHorizontalSortable(horizontalOptions);const combinedAnimatedStyle=[horizontalAnimatedStyle,customAnimatedStyle];const contextValue={panGestureHandler:horizontalPanGestureHandler};const content=React.createElement(Animated.View,{style:combinedAnimatedStyle},React.createElement(SortableContext.Provider,{value:contextValue},React.createElement(Animated.View,{style},children)));if(horizontalHasHandle){return content}else{return React.createElement(PanGestureHandler,{onGestureEvent:horizontalPanGestureHandler,activateAfterLongPress:200,shouldCancelWhenOutside:false},content)}}const verticalOptions={id,positions,lowerBound,autoScrollDirection,itemsCount,itemHeight,containerHeight,onMove,onDragStart,onDrop,onDragging,children,handleComponent:SortableHandle};const{animatedStyle:verticalAnimatedStyle,panGestureHandler:verticalPanGestureHandler,isMoving:verticalIsMoving,hasHandle:verticalHasHandle}=useSortable(verticalOptions);const combinedAnimatedStyle=[verticalAnimatedStyle,customAnimatedStyle];const contextValue={panGestureHandler:verticalPanGestureHandler};const content=React.createElement(Animated.View,{style:combinedAnimatedStyle},React.createElement(SortableContext.Provider,{value:contextValue},React.createElement(Animated.View,{style},children)));if(verticalHasHandle){return content}else{return React.createElement(PanGestureHandler,{onGestureEvent:verticalPanGestureHandler,activateAfterLongPress:200,shouldCancelWhenOutside:false},content)}}SortableItem.Handle=SortableHandle;
|
|
@@ -4,6 +4,11 @@ export declare enum ScrollDirection {
|
|
|
4
4
|
Up = "up",
|
|
5
5
|
Down = "down"
|
|
6
6
|
}
|
|
7
|
+
export declare enum HorizontalScrollDirection {
|
|
8
|
+
None = "none",
|
|
9
|
+
Left = "left",
|
|
10
|
+
Right = "right"
|
|
11
|
+
}
|
|
7
12
|
export declare function clamp(value: number, lowerBound: number, upperBound: number): number;
|
|
8
13
|
export declare function objectMove(object: {
|
|
9
14
|
[id: string]: number;
|
|
@@ -19,3 +24,10 @@ export declare function setPosition(positionY: number, itemsCount: number, posit
|
|
|
19
24
|
[id: string]: number;
|
|
20
25
|
}>, id: string, itemHeight: number): void;
|
|
21
26
|
export declare function setAutoScroll(positionY: number, lowerBound: number, upperBound: number, scrollThreshold: number, autoScroll: SharedValue<ScrollDirection>): void;
|
|
27
|
+
export declare function getItemXPosition(position: number, itemWidth: number, gap?: number, paddingHorizontal?: number): number;
|
|
28
|
+
export declare function getContentWidth(itemsCount: number, itemWidth: number, gap?: number, paddingHorizontal?: number): number;
|
|
29
|
+
export declare function setHorizontalPosition(positionX: number, itemsCount: number, positions: SharedValue<{
|
|
30
|
+
[id: string]: number;
|
|
31
|
+
}>, id: string, itemWidth: number, gap?: number, paddingHorizontal?: number): void;
|
|
32
|
+
export declare function setHorizontalAutoScroll(positionX: number, leftBound: number, rightBound: number, scrollThreshold: number, autoScrollDirection: SharedValue<HorizontalScrollDirection>): void;
|
|
33
|
+
export declare const dataHash: (data: any[]) => string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export var ScrollDirection
|
|
1
|
+
export var ScrollDirection;(function(ScrollDirection){ScrollDirection["None"]="none";ScrollDirection["Up"]="up";ScrollDirection["Down"]="down"})(ScrollDirection||(ScrollDirection={}));export var HorizontalScrollDirection;(function(HorizontalScrollDirection){HorizontalScrollDirection["None"]="none";HorizontalScrollDirection["Left"]="left";HorizontalScrollDirection["Right"]="right"})(HorizontalScrollDirection||(HorizontalScrollDirection={}));export function clamp(value,lowerBound,upperBound){"worklet";return Math.max(lowerBound,Math.min(value,upperBound))}export function objectMove(object,from,to){"worklet";const newObject=Object.assign({},object);for(const id in object){if(object[id]===from){newObject[id]=to}if(object[id]===to){newObject[id]=from}}return newObject}export function listToObject(list){const values=Object.values(list);const object={};for(let i=0;i<values.length;i++){object[values[i].id]=i}return object}export function setPosition(positionY,itemsCount,positions,id,itemHeight){"worklet";const newPosition=clamp(Math.floor(positionY/itemHeight),0,itemsCount-1);if(newPosition!==positions.value[id]){positions.value=objectMove(positions.value,positions.value[id],newPosition)}}export function setAutoScroll(positionY,lowerBound,upperBound,scrollThreshold,autoScroll){"worklet";if(positionY<=lowerBound+scrollThreshold){autoScroll.value=ScrollDirection.Up}else if(positionY>=upperBound-scrollThreshold){autoScroll.value=ScrollDirection.Down}else{autoScroll.value=ScrollDirection.None}}export function getItemXPosition(position,itemWidth,gap=0,paddingHorizontal=0){"worklet";return paddingHorizontal+position*(itemWidth+gap)}export function getContentWidth(itemsCount,itemWidth,gap=0,paddingHorizontal=0){"worklet";if(itemsCount===0){return paddingHorizontal*2}const totalItemsWidth=itemsCount*itemWidth;const totalGaps=Math.max(0,itemsCount-1)*gap;return totalItemsWidth+totalGaps+paddingHorizontal*2}export function setHorizontalPosition(positionX,itemsCount,positions,id,itemWidth,gap=0,paddingHorizontal=0){"worklet";const adjustedX=positionX-paddingHorizontal;const itemWithGapWidth=itemWidth+gap;const newPosition=clamp(Math.floor(adjustedX/itemWithGapWidth),0,itemsCount-1);if(newPosition!==positions.value[id]){positions.value=objectMove(positions.value,positions.value[id],newPosition)}}export function setHorizontalAutoScroll(positionX,leftBound,rightBound,scrollThreshold,autoScrollDirection){"worklet";const effectiveThreshold=Math.max(scrollThreshold,60);const leftEdge=leftBound+effectiveThreshold;const rightEdge=rightBound-effectiveThreshold;if(positionX<leftEdge){autoScrollDirection.value=HorizontalScrollDirection.Left}else if(positionX>rightEdge){autoScrollDirection.value=HorizontalScrollDirection.Right}else{autoScrollDirection.value=HorizontalScrollDirection.None}}export const dataHash=data=>{const str=data.reduce(((acc,item)=>acc+item.id),"");let hash=0;for(let i=0,len=str.length;i<len;i++){let chr=str.charCodeAt(i);hash=(hash<<5)-hash+chr;hash|=0}return hash.toString()};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import React,{useRef,useState,useMemo,useCallback,forwardRef,useImperativeHandle,useEffect}from"react";import{SlotsContext}from"../types/context";export const DropProvider=forwardRef((({children
|
|
1
|
+
import React,{useRef,useState,useMemo,useCallback,forwardRef,useImperativeHandle,useEffect}from"react";import{SlotsContext}from"../types/context";export const DropProvider=forwardRef((({children,onLayoutUpdateComplete,onDroppedItemsUpdate,onDragging,onDragStart,onDragEnd},ref)=>{const slotsRef=useRef({});const[activeHoverSlotId,setActiveHoverSlotIdState]=useState(null);const[droppedItems,setDroppedItems]=useState({});const positionUpdateListenersRef=useRef({});const registerPositionUpdateListener=useCallback(((id,listener)=>{positionUpdateListenersRef.current[id]=listener}),[]);const unregisterPositionUpdateListener=useCallback((id=>{delete positionUpdateListenersRef.current[id]}),[]);useEffect((()=>{if(onDroppedItemsUpdate){onDroppedItemsUpdate(droppedItems)}}),[droppedItems,onDroppedItemsUpdate]);const registerDroppedItem=useCallback(((draggableId,droppableId,itemData)=>{setDroppedItems((prev=>({...prev,[draggableId]:{droppableId,data:itemData}})))}),[]);const unregisterDroppedItem=useCallback((draggableId=>{setDroppedItems((prev=>{const newItems={...prev};delete newItems[draggableId];return newItems}))}),[]);const getDroppedItems=useCallback((()=>droppedItems),[droppedItems]);const internalRequestPositionUpdate=useCallback((()=>{const listeners=positionUpdateListenersRef.current;Object.values(listeners).forEach((listener=>{listener()}));onLayoutUpdateComplete===null||onLayoutUpdateComplete===void 0?void 0:onLayoutUpdateComplete()}),[onLayoutUpdateComplete]);useImperativeHandle(ref,(()=>({requestPositionUpdate:internalRequestPositionUpdate,getDroppedItems})));const hasAvailableCapacity=useCallback((droppableId=>{const droppedCount=Object.values(droppedItems).filter((item=>item.droppableId===droppableId)).length;const droppableSlot=Object.values(slotsRef.current).find((slot=>slot.id===droppableId));if(!droppableSlot){return false}const capacity=droppableSlot.capacity!==undefined?droppableSlot.capacity:1;return droppedCount<capacity}),[droppedItems]);const handleDragStart=useCallback((data=>{if(onDragStart){onDragStart(data)}internalRequestPositionUpdate()}),[onDragStart,internalRequestPositionUpdate]);const contextValue=useMemo((()=>({register:(id,slot)=>{slotsRef.current[id]=slot},unregister:id=>{delete slotsRef.current[id]},isRegistered:id=>slotsRef.current[id]!==undefined,getSlots:()=>slotsRef.current,setActiveHoverSlot:id=>setActiveHoverSlotIdState(id),activeHoverSlotId,registerPositionUpdateListener,unregisterPositionUpdateListener,requestPositionUpdate:internalRequestPositionUpdate,registerDroppedItem,unregisterDroppedItem,getDroppedItems,hasAvailableCapacity,onDragging,onDragStart:handleDragStart,onDragEnd})),[activeHoverSlotId,registerPositionUpdateListener,unregisterPositionUpdateListener,internalRequestPositionUpdate,registerDroppedItem,unregisterDroppedItem,getDroppedItems,hasAvailableCapacity,onDragging,handleDragStart,onDragEnd]);return React.createElement(SlotsContext.Provider,{value:contextValue},children)}));DropProvider.displayName="DropProvider";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import React,{useRef,useCallback,useContext,useEffect,useState}from"react";import{useSharedValue,useAnimatedStyle,withSpring,runOnJS,runOnUI,useAnimatedReaction,useAnimatedRef,measure}from"react-native-reanimated";import{Gesture}from"react-native-gesture-handler";import{SlotsContext}from"../types/context";import{DraggableState}from"../types/draggable";export const useDraggable=e=>{const{data:a,draggableId:t,dragDisabled:u=!1,onDragStart:r,onDragEnd:n,onDragging:l,onStateChange:s,animationFunction:i,dragBoundsRef:o,dragAxis:v="both",collisionAlgorithm:c="intersect",children:h,handleComponent:d}=e,g=useAnimatedRef(),[S,f]=useState(DraggableState.IDLE),[m,p]=useState(!1);useEffect((()=>{if(!h||!d)return void p(!1);const e=a=>{if(React.isValidElement(a)){if(a.type===d)return!0;if(a.props&&a.props.children&&React.Children.toArray(a.props.children).some(e))return!0}return!1};p(React.Children.toArray(h).some(e))}),[h,d]),useEffect((()=>{null==s||s(S)}),[S,s]);const b=useSharedValue(0),y=useSharedValue(0),x=useSharedValue(0),D=useSharedValue(0),O=useSharedValue(u),V=useSharedValue(v),k=useSharedValue(0),C=useSharedValue(0),J=useSharedValue(0),w=useSharedValue(0),E=useRef(!1),R=useRef(t||`draggable-${Math.random().toString(36).substr(2,9)}`).current,I=useSharedValue(0),A=useSharedValue(0),U=useSharedValue(0),L=useSharedValue(0),M=useSharedValue(!1),{getSlots:P,setActiveHoverSlot:G,activeHoverSlotId:X,registerPositionUpdateListener:Y,unregisterPositionUpdateListener:H,registerDroppedItem:Z,unregisterDroppedItem:B,hasAvailableCapacity:F,onDragging:N,onDragStart:$,onDragEnd:j}=useContext(SlotsContext);useEffect((()=>{O.value=u}),[u,O]),useEffect((()=>{V.value=v}),[v,V]);const q=useCallback((()=>{runOnUI((()=>{const e=measure(g);if(null===e)return;const a=b.value,t=y.value;if(0===a&&0===t){const u=e.pageX-a,r=e.pageY-t;k.value=u,C.value=r}J.value=e.width,w.value=e.height,E.current||(E.current=!0)}))()}),[g,k,C,J,w,b,y]),z=useCallback((()=>{const e=measure(g);if(null===e)return;const a=b.value,t=y.value;if(0===a&&0===t){const u=e.pageX-a,r=e.pageY-t;k.value=u,C.value=r}J.value=e.width,w.value=e.height,E.current||(E.current=!0)}),[g,k,C,J,w,b,y]),K=useCallback((()=>{const e=null==o?void 0:o.current;e?e.measure(((e,a,t,u,r,n)=>{"number"==typeof r&&"number"==typeof n&&t>0&&u>0&&runOnUI((()=>{I.value=r,A.value=n,U.value=t,L.value=u,M.value||(M.value=!0)}))()})):runOnUI((()=>{M.value&&(M.value=!1)}))()}),[o,I,A,U,L,M]);useEffect((()=>(Y(R,(()=>{q(),K()})),()=>{H(R)})),[R,Y,H,q,K]),useEffect((()=>{K()}),[K]);const Q=useCallback((e=>{q()}),[q]),T=useCallback(((e,a)=>{i?(b.value=i(e),y.value=i(a)):(b.value=withSpring(e),y.value=withSpring(a))}),[i,b,y]),W=useCallback(((e,a,t,u,r,n)=>{if("intersect"===n)return e<r.x+r.width&&e+t>r.x&&a<r.y+r.height&&a+u>r.y;if("contain"===n)return e>=r.x&&e+t<=r.x+r.width&&a>=r.y&&a+u<=r.y+r.height;{const n=e+t/2,l=a+u/2;return n>=r.x&&n<=r.x+r.width&&l>=r.y&&l<=r.y+r.height}}),[]),_=useCallback(((e,a,t,u,r,n,l)=>{const s=P(),i=u+e,o=r+a;let v,h,d=null,g=null;for(const e in s){const a=parseInt(e,10),t=s[a];if(W(i,o,n,l,t,c)&&F(t.id)){d=t,g=a;break}}if(d&&null!==g){d.onDrop&&runOnJS(d.onDrop)(t),runOnJS(Z)(R,d.id,t),runOnJS(f)(DraggableState.DROPPED);const e=d.dropAlignment||"center",a=d.dropOffset||{x:0,y:0};let s=0,i=0;switch(e){case"top-left":s=d.x,i=d.y;break;case"top-center":s=d.x+d.width/2-n/2,i=d.y;break;case"top-right":s=d.x+d.width-n,i=d.y;break;case"center-left":s=d.x,i=d.y+d.height/2-l/2;break;case"center":default:s=d.x+d.width/2-n/2,i=d.y+d.height/2-l/2;break;case"center-right":s=d.x+d.width-n,i=d.y+d.height/2-l/2;break;case"bottom-left":s=d.x,i=d.y+d.height-l;break;case"bottom-center":s=d.x+d.width/2-n/2,i=d.y+d.height-l;break;case"bottom-right":s=d.x+d.width-n,i=d.y+d.height-l}v=s+a.x-u,h=i+a.y-r}else v=0,h=0,runOnJS(f)(DraggableState.IDLE),runOnJS(B)(R);runOnUI(T)(v,h)}),[P,T,c,W,f,R,Z,B,F]),ee=useCallback(((e,a,t,u,r,n)=>{const l=P(),s=t+e,i=u+a;let o=null;for(const e in l){const a=parseInt(e,10),t=l[a];if(W(s,i,r,n,t,c)){o=a;break}}X!==o&&G(o)}),[P,G,X,c,W]),ae=React.useMemo((()=>Gesture.Pan().onBegin((()=>{z(),O.value||(x.value=b.value,D.value=y.value,runOnJS(f)(DraggableState.DRAGGING),r&&runOnJS(r)(a),$&&runOnJS($)(a))})).onUpdate((e=>{if(O.value)return;let t=x.value+e.translationX,u=D.value+e.translationY;if(M.value){const e=J.value,a=w.value,r=I.value-k.value,n=I.value+U.value-k.value-e,l=A.value-C.value,s=A.value+L.value-C.value-a;t=Math.max(r,Math.min(t,n)),u=Math.max(l,Math.min(u,s))}"x"===V.value?b.value=t:("y"===V.value||(b.value=t),y.value=u),l&&runOnJS(l)({x:k.value,y:C.value,tx:b.value,ty:y.value,itemData:a}),N&&runOnJS(N)({x:k.value,y:C.value,tx:b.value,ty:y.value,itemData:a}),runOnJS(ee)(b.value,y.value,k.value,C.value,J.value,w.value)})).onEnd((()=>{O.value||(n&&runOnJS(n)(a),j&&runOnJS(j)(a),runOnJS(_)(b.value,y.value,a,k.value,C.value,J.value,w.value),runOnJS(G)(null))}))),[O,x,D,b,y,k,C,J,w,r,n,a,_,ee,G,i,l,M,I,A,U,L,V,f,z,N,$,j]),te=useAnimatedStyle((()=>({transform:[{translateX:b.value},{translateY:y.value}]})),[b,y]);return useAnimatedReaction((()=>({txValue:b.value,tyValue:y.value,isZero:0===b.value&&0===y.value})),((e,a)=>{e.isZero&&a&&!a.isZero&&(runOnJS(f)(DraggableState.IDLE),runOnJS(B)(R))}),[f,B,R]),useEffect((()=>()=>{B(R)}),[R,B]),{animatedViewProps:{style:te,onLayout:Q},gesture:ae,state:S,animatedViewRef:g,hasHandle:m}};
|
|
1
|
+
import React,{useRef,useCallback,useContext,useEffect,useState}from"react";import{useSharedValue,useAnimatedStyle,withSpring,runOnJS,runOnUI,useAnimatedReaction,useAnimatedRef,measure}from"react-native-reanimated";import{Gesture}from"react-native-gesture-handler";import{SlotsContext}from"../types/context";import{DraggableState}from"../types/draggable";export const useDraggable=options=>{const{data,draggableId,dragDisabled=false,onDragStart,onDragEnd,onDragging,onStateChange,animationFunction,dragBoundsRef,dragAxis="both",collisionAlgorithm="intersect",children,handleComponent}=options;const animatedViewRef=useAnimatedRef();const[state,setState]=useState(DraggableState.IDLE);const[hasHandle,setHasHandle]=useState(false);useEffect((()=>{if(!children||!handleComponent){setHasHandle(false);return}const checkForHandle=child=>{if(React.isValidElement(child)){if(child.type===handleComponent){return true}if(child.props&&child.props.children){if(React.Children.toArray(child.props.children).some(checkForHandle)){return true}}}return false};setHasHandle(React.Children.toArray(children).some(checkForHandle))}),[children,handleComponent]);useEffect((()=>{onStateChange===null||onStateChange===void 0?void 0:onStateChange(state)}),[state,onStateChange]);const tx=useSharedValue(0);const ty=useSharedValue(0);const offsetX=useSharedValue(0);const offsetY=useSharedValue(0);const dragDisabledShared=useSharedValue(dragDisabled);const dragAxisShared=useSharedValue(dragAxis);const originX=useSharedValue(0);const originY=useSharedValue(0);const itemW=useSharedValue(0);const itemH=useSharedValue(0);const isOriginSet=useRef(false);const internalDraggableId=useRef(draggableId||`draggable-${Math.random().toString(36).substr(2,9)}`).current;const boundsX=useSharedValue(0);const boundsY=useSharedValue(0);const boundsWidth=useSharedValue(0);const boundsHeight=useSharedValue(0);const boundsAreSet=useSharedValue(false);const{getSlots,setActiveHoverSlot,activeHoverSlotId,registerPositionUpdateListener,unregisterPositionUpdateListener,registerDroppedItem,unregisterDroppedItem,hasAvailableCapacity,onDragging:contextOnDragging,onDragStart:contextOnDragStart,onDragEnd:contextOnDragEnd}=useContext(SlotsContext);useEffect((()=>{dragDisabledShared.value=dragDisabled}),[dragDisabled,dragDisabledShared]);useEffect((()=>{dragAxisShared.value=dragAxis}),[dragAxis,dragAxisShared]);const updateDraggablePosition=useCallback((()=>{runOnUI((()=>{"worklet";const measurement=measure(animatedViewRef);if(measurement===null){return}const currentTx=tx.value;const currentTy=ty.value;if(currentTx===0&¤tTy===0){const newOriginX=measurement.pageX-currentTx;const newOriginY=measurement.pageY-currentTy;originX.value=newOriginX;originY.value=newOriginY}itemW.value=measurement.width;itemH.value=measurement.height;if(!isOriginSet.current){isOriginSet.current=true}}))()}),[animatedViewRef,originX,originY,itemW,itemH,tx,ty]);const updateDraggablePositionWorklet=useCallback((()=>{"worklet";const measurement=measure(animatedViewRef);if(measurement===null){return}const currentTx=tx.value;const currentTy=ty.value;if(currentTx===0&¤tTy===0){const newOriginX=measurement.pageX-currentTx;const newOriginY=measurement.pageY-currentTy;originX.value=newOriginX;originY.value=newOriginY}itemW.value=measurement.width;itemH.value=measurement.height;if(!isOriginSet.current){isOriginSet.current=true}}),[animatedViewRef,originX,originY,itemW,itemH,tx,ty]);const updateBounds=useCallback((()=>{const currentBoundsView=dragBoundsRef===null||dragBoundsRef===void 0?void 0:dragBoundsRef.current;if(currentBoundsView){currentBoundsView.measure(((_x,_y,width,height,pageX,pageY)=>{if(typeof pageX==="number"&&typeof pageY==="number"&&width>0&&height>0){runOnUI((()=>{"worklet";boundsX.value=pageX;boundsY.value=pageY;boundsWidth.value=width;boundsHeight.value=height;if(!boundsAreSet.value){boundsAreSet.value=true}}))()}else{console.warn("useDraggable: dragBoundsRef measurement failed or returned invalid dimensions. Bounds may be stale or item unbounded.")}}))}else{runOnUI((()=>{"worklet";if(boundsAreSet.value){boundsAreSet.value=false}}))()}}),[dragBoundsRef,boundsX,boundsY,boundsWidth,boundsHeight,boundsAreSet]);useEffect((()=>{const handlePositionUpdate=()=>{updateDraggablePosition();updateBounds()};registerPositionUpdateListener(internalDraggableId,handlePositionUpdate);return()=>{unregisterPositionUpdateListener(internalDraggableId)}}),[internalDraggableId,registerPositionUpdateListener,unregisterPositionUpdateListener,updateDraggablePosition,updateBounds]);useEffect((()=>{updateBounds()}),[updateBounds]);const handleLayoutHandler=useCallback((event=>{updateDraggablePosition()}),[updateDraggablePosition]);const animateDragEndPosition=useCallback(((targetXValue,targetYValue)=>{"worklet";if(animationFunction){tx.value=animationFunction(targetXValue);ty.value=animationFunction(targetYValue)}else{tx.value=withSpring(targetXValue);ty.value=withSpring(targetYValue)}}),[animationFunction,tx,ty]);const performCollisionCheck=useCallback(((draggableX,draggableY,draggableW,draggableH,slot,algo)=>{if(algo==="intersect"){return draggableX<slot.x+slot.width&&draggableX+draggableW>slot.x&&draggableY<slot.y+slot.height&&draggableY+draggableH>slot.y}else if(algo==="contain"){return draggableX>=slot.x&&draggableX+draggableW<=slot.x+slot.width&&draggableY>=slot.y&&draggableY+draggableH<=slot.y+slot.height}else{const draggableCenterX=draggableX+draggableW/2;const draggableCenterY=draggableY+draggableH/2;return draggableCenterX>=slot.x&&draggableCenterX<=slot.x+slot.width&&draggableCenterY>=slot.y&&draggableCenterY<=slot.y+slot.height}}),[]);const processDropAndAnimate=useCallback(((currentTxVal,currentTyVal,draggableData,currentOriginX,currentOriginY,currentItemW,currentItemH)=>{const slots=getSlots();const currentDraggableX=currentOriginX+currentTxVal;const currentDraggableY=currentOriginY+currentTyVal;let hitSlotData=null;let hitSlotId=null;for(const key in slots){const slotId=parseInt(key,10);const s=slots[slotId];const isCollision=performCollisionCheck(currentDraggableX,currentDraggableY,currentItemW,currentItemH,s,collisionAlgorithm);if(isCollision){const hasCapacity=hasAvailableCapacity(s.id);if(hasCapacity){hitSlotData=s;hitSlotId=slotId;break}}}let finalTxValue;let finalTyValue;if(hitSlotData&&hitSlotId!==null){if(hitSlotData.onDrop){runOnJS(hitSlotData.onDrop)(draggableData)}runOnJS(registerDroppedItem)(internalDraggableId,hitSlotData.id,draggableData);runOnJS(setState)(DraggableState.DROPPED);const alignment=hitSlotData.dropAlignment||"center";const offset=hitSlotData.dropOffset||{x:0,y:0};let targetX=0;let targetY=0;switch(alignment){case"top-left":targetX=hitSlotData.x;targetY=hitSlotData.y;break;case"top-center":targetX=hitSlotData.x+hitSlotData.width/2-currentItemW/2;targetY=hitSlotData.y;break;case"top-right":targetX=hitSlotData.x+hitSlotData.width-currentItemW;targetY=hitSlotData.y;break;case"center-left":targetX=hitSlotData.x;targetY=hitSlotData.y+hitSlotData.height/2-currentItemH/2;break;case"center":targetX=hitSlotData.x+hitSlotData.width/2-currentItemW/2;targetY=hitSlotData.y+hitSlotData.height/2-currentItemH/2;break;case"center-right":targetX=hitSlotData.x+hitSlotData.width-currentItemW;targetY=hitSlotData.y+hitSlotData.height/2-currentItemH/2;break;case"bottom-left":targetX=hitSlotData.x;targetY=hitSlotData.y+hitSlotData.height-currentItemH;break;case"bottom-center":targetX=hitSlotData.x+hitSlotData.width/2-currentItemW/2;targetY=hitSlotData.y+hitSlotData.height-currentItemH;break;case"bottom-right":targetX=hitSlotData.x+hitSlotData.width-currentItemW;targetY=hitSlotData.y+hitSlotData.height-currentItemH;break;default:targetX=hitSlotData.x+hitSlotData.width/2-currentItemW/2;targetY=hitSlotData.y+hitSlotData.height/2-currentItemH/2}const draggableTargetX=targetX+offset.x;const draggableTargetY=targetY+offset.y;finalTxValue=draggableTargetX-currentOriginX;finalTyValue=draggableTargetY-currentOriginY}else{finalTxValue=0;finalTyValue=0;runOnJS(setState)(DraggableState.IDLE);runOnJS(unregisterDroppedItem)(internalDraggableId)}runOnUI(animateDragEndPosition)(finalTxValue,finalTyValue)}),[getSlots,animateDragEndPosition,collisionAlgorithm,performCollisionCheck,setState,internalDraggableId,registerDroppedItem,unregisterDroppedItem,hasAvailableCapacity]);const updateHoverState=useCallback(((currentTxVal,currentTyVal,currentOriginX,currentOriginY,currentItemW,currentItemH)=>{const slots=getSlots();const currentDraggableX=currentOriginX+currentTxVal;const currentDraggableY=currentOriginY+currentTyVal;let newHoveredSlotId=null;for(const key in slots){const slotId=parseInt(key,10);const s=slots[slotId];const isCollision=performCollisionCheck(currentDraggableX,currentDraggableY,currentItemW,currentItemH,s,collisionAlgorithm);if(isCollision){newHoveredSlotId=slotId;break}}if(activeHoverSlotId!==newHoveredSlotId){setActiveHoverSlot(newHoveredSlotId)}}),[getSlots,setActiveHoverSlot,activeHoverSlotId,collisionAlgorithm,performCollisionCheck]);const gesture=React.useMemo((()=>Gesture.Pan().onBegin((()=>{"worklet";updateDraggablePositionWorklet();if(dragDisabledShared.value){return}offsetX.value=tx.value;offsetY.value=ty.value;runOnJS(setState)(DraggableState.DRAGGING);if(onDragStart){runOnJS(onDragStart)(data)}if(contextOnDragStart){runOnJS(contextOnDragStart)(data)}})).onUpdate((event=>{"worklet";if(dragDisabledShared.value){return}let newTx=offsetX.value+event.translationX;let newTy=offsetY.value+event.translationY;if(boundsAreSet.value){const currentItemW=itemW.value;const currentItemH=itemH.value;const minTx=boundsX.value-originX.value;const maxTx=boundsX.value+boundsWidth.value-originX.value-currentItemW;const minTy=boundsY.value-originY.value;const maxTy=boundsY.value+boundsHeight.value-originY.value-currentItemH;newTx=Math.max(minTx,Math.min(newTx,maxTx));newTy=Math.max(minTy,Math.min(newTy,maxTy))}if(dragAxisShared.value==="x"){tx.value=newTx}else if(dragAxisShared.value==="y"){ty.value=newTy}else{tx.value=newTx;ty.value=newTy}if(onDragging){runOnJS(onDragging)({x:originX.value,y:originY.value,tx:tx.value,ty:ty.value,itemData:data})}if(contextOnDragging){runOnJS(contextOnDragging)({x:originX.value,y:originY.value,tx:tx.value,ty:ty.value,itemData:data})}runOnJS(updateHoverState)(tx.value,ty.value,originX.value,originY.value,itemW.value,itemH.value)})).onEnd((()=>{"worklet";if(dragDisabledShared.value){return}if(onDragEnd){runOnJS(onDragEnd)(data)}if(contextOnDragEnd){runOnJS(contextOnDragEnd)(data)}runOnJS(processDropAndAnimate)(tx.value,ty.value,data,originX.value,originY.value,itemW.value,itemH.value);runOnJS(setActiveHoverSlot)(null)}))),[dragDisabledShared,offsetX,offsetY,tx,ty,originX,originY,itemW,itemH,onDragStart,onDragEnd,data,processDropAndAnimate,updateHoverState,setActiveHoverSlot,animationFunction,onDragging,boundsAreSet,boundsX,boundsY,boundsWidth,boundsHeight,dragAxisShared,setState,updateDraggablePositionWorklet,contextOnDragging,contextOnDragStart,contextOnDragEnd]);const animatedStyleProp=useAnimatedStyle((()=>{"worklet";return{transform:[{translateX:tx.value},{translateY:ty.value}]}}),[tx,ty]);useAnimatedReaction((()=>({txValue:tx.value,tyValue:ty.value,isZero:tx.value===0&&ty.value===0})),((result,previous)=>{if(result.isZero&&previous&&!previous.isZero){runOnJS(setState)(DraggableState.IDLE);runOnJS(unregisterDroppedItem)(internalDraggableId)}}),[setState,unregisterDroppedItem,internalDraggableId]);useEffect((()=>()=>{unregisterDroppedItem(internalDraggableId)}),[internalDraggableId,unregisterDroppedItem]);return{animatedViewProps:{style:animatedStyleProp,onLayout:handleLayoutHandler},gesture,state,animatedViewRef,hasHandle}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useRef,useEffect,useContext,useCallback,useMemo}from"react";import{StyleSheet}from"react-native";import{useAnimatedRef,measure,runOnUI,runOnJS}from"react-native-reanimated";import{SlotsContext}from"../types/context";import{_getUniqueDroppableId}from"../components/Droppable";export const useDroppable=
|
|
1
|
+
import{useRef,useEffect,useContext,useCallback,useMemo}from"react";import{StyleSheet}from"react-native";import{useAnimatedRef,measure,runOnUI,runOnJS}from"react-native-reanimated";import{SlotsContext}from"../types/context";import{_getUniqueDroppableId}from"../components/Droppable";export const useDroppable=options=>{const{onDrop,dropDisabled,onActiveChange,dropAlignment,dropOffset,activeStyle,droppableId,capacity}=options;const animatedViewRef=useAnimatedRef();const id=useRef(_getUniqueDroppableId()).current;const stringId=useRef(droppableId||`droppable-${id}`).current;const instanceId=useRef(`droppable-${id}-${Math.random().toString(36).substr(2,9)}`).current;const{register,unregister,isRegistered,activeHoverSlotId:contextActiveHoverSlotId,registerPositionUpdateListener,unregisterPositionUpdateListener}=useContext(SlotsContext);const isActive=contextActiveHoverSlotId===id;const{processedActiveStyle,activeTransforms}=useMemo((()=>{if(!isActive||!activeStyle){return{processedActiveStyle:null,activeTransforms:[]}}const flattenedStyle=StyleSheet.flatten(activeStyle);let processedStyle={...flattenedStyle};let transforms=[];if(flattenedStyle.transform){if(Array.isArray(flattenedStyle.transform)){transforms=[...flattenedStyle.transform]}delete processedStyle.transform}return{processedActiveStyle:processedStyle,activeTransforms:transforms}}),[isActive,activeStyle]);const combinedActiveStyle=useMemo((()=>{if(!isActive||!activeStyle){return undefined}if(activeTransforms.length===0){return processedActiveStyle}return{...processedActiveStyle,transform:activeTransforms}}),[isActive,activeStyle,processedActiveStyle,activeTransforms]);useEffect((()=>{onActiveChange===null||onActiveChange===void 0?void 0:onActiveChange(isActive)}),[isActive,onActiveChange]);useEffect((()=>{console.log(`Droppable ${id} using string ID: ${stringId}, provided ID: ${droppableId||"none"}`)}),[id,stringId,droppableId]);const updateDroppablePosition=useCallback((()=>{runOnUI((()=>{"worklet";const measurement=measure(animatedViewRef);if(measurement===null){return}if(measurement.width>0&&measurement.height>0){runOnJS(register)(id,{id:droppableId||`droppable-${id}`,x:measurement.pageX,y:measurement.pageY,width:measurement.width,height:measurement.height,onDrop,dropAlignment:dropAlignment||"center",dropOffset:dropOffset||{x:0,y:0},capacity})}}))()}),[id,droppableId,onDrop,register,animatedViewRef,dropAlignment,dropOffset,capacity]);const handleLayoutHandler=useCallback((_event=>{updateDroppablePosition()}),[updateDroppablePosition]);useEffect((()=>{registerPositionUpdateListener(instanceId,updateDroppablePosition);return()=>{unregisterPositionUpdateListener(instanceId)}}),[instanceId,registerPositionUpdateListener,unregisterPositionUpdateListener,updateDroppablePosition]);useEffect((()=>{if(dropDisabled){unregister(id)}else{updateDroppablePosition()}}),[dropDisabled,id,unregister,updateDroppablePosition]);useEffect((()=>()=>{unregister(id)}),[id,unregister]);return{viewProps:{onLayout:handleLayoutHandler,style:combinedActiveStyle},isActive,activeStyle,animatedViewRef}};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useState,useRef,useEffect}from"react";import React from"react";import{runOnJS,runOnUI,useAnimatedGestureHandler,useAnimatedReaction,useAnimatedStyle,useDerivedValue,useSharedValue,withSpring,withTiming}from"react-native-reanimated";import{setHorizontalPosition,setHorizontalAutoScroll,getItemXPosition,getContentWidth}from"../components/sortableUtils";import{HorizontalScrollDirection}from"../types/sortable";export function useHorizontalSortable(options){const{id,positions,leftBound,autoScrollDirection,itemsCount,itemWidth,gap=0,paddingHorizontal=0,containerWidth=500,onMove,onDragStart,onDrop,onDragging,children,handleComponent}=options;const[isMoving,setIsMoving]=useState(false);const[hasHandle,setHasHandle]=useState(false);const movingSV=useSharedValue(false);const currentOverItemId=useSharedValue(null);const onDraggingLastCallTimestamp=useSharedValue(0);const THROTTLE_INTERVAL=50;const positionX=useSharedValue(0);const left=useSharedValue(0);const targetLeftBound=useSharedValue(0);useEffect((()=>{runOnUI((()=>{"worklet";const initialLeftVal=getItemXPosition(positions.value[id],itemWidth,gap,paddingHorizontal);const initialLeftBoundVal=leftBound.value;left.value=initialLeftVal;positionX.value=initialLeftVal;targetLeftBound.value=initialLeftBoundVal}))()}),[]);const calculatedContainerWidth=useRef(containerWidth).current;const rightBound=useDerivedValue((()=>leftBound.value+calculatedContainerWidth));useEffect((()=>{if(!children||!handleComponent){setHasHandle(false);return}const checkForHandle=child=>{if(React.isValidElement(child)){if(child.type===handleComponent){return true}if(child.props&&child.props.children){if(React.Children.toArray(child.props.children).some(checkForHandle)){return true}}}return false};setHasHandle(React.Children.toArray(children).some(checkForHandle))}),[children,handleComponent]);useAnimatedReaction((()=>positionX.value),((currentX,previousX)=>{if(currentX===null||!movingSV.value){return}if(previousX!==null&¤tX===previousX){return}const adjustedX=currentX-paddingHorizontal;const itemWithGapWidth=itemWidth+gap;const clampedPosition=Math.min(Math.max(0,Math.ceil(adjustedX/itemWithGapWidth)),itemsCount-1);let newOverItemId=null;for(const[itemIdIter,itemPosIter]of Object.entries(positions.value)){if(itemPosIter===clampedPosition&&itemIdIter!==id){newOverItemId=itemIdIter;break}}if(currentOverItemId.value!==newOverItemId){currentOverItemId.value=newOverItemId}if(onDragging){const now=Date.now();if(now-onDraggingLastCallTimestamp.value>THROTTLE_INTERVAL){runOnJS(onDragging)(id,newOverItemId,Math.round(currentX));onDraggingLastCallTimestamp.value=now}}left.value=currentX;setHorizontalPosition(currentX,itemsCount,positions,id,itemWidth,gap,paddingHorizontal);setHorizontalAutoScroll(currentX,leftBound.value,rightBound.value,itemWidth,autoScrollDirection)}),[movingSV,itemWidth,gap,paddingHorizontal,itemsCount,positions,id,onDragging,leftBound,rightBound,autoScrollDirection,currentOverItemId,left,onDraggingLastCallTimestamp]);useAnimatedReaction((()=>positions.value[id]),((currentPosition,previousPosition)=>{if(currentPosition!==null&&previousPosition!==null&¤tPosition!==previousPosition){if(!movingSV.value){const newLeft=getItemXPosition(currentPosition,itemWidth,gap,paddingHorizontal);left.value=withSpring(newLeft);if(onMove){runOnJS(onMove)(id,previousPosition,currentPosition)}}}}),[movingSV,itemWidth,gap,paddingHorizontal]);useAnimatedReaction((()=>autoScrollDirection.value),((scrollDirection,previousValue)=>{if(scrollDirection!==null&&previousValue!==null&&scrollDirection!==previousValue){switch(scrollDirection){case HorizontalScrollDirection.Left:{targetLeftBound.value=leftBound.value;targetLeftBound.value=withTiming(0,{duration:1500});break}case HorizontalScrollDirection.Right:{const contentWidth=getContentWidth(itemsCount,itemWidth,gap,paddingHorizontal);const maxScroll=Math.max(0,contentWidth-calculatedContainerWidth);targetLeftBound.value=leftBound.value;targetLeftBound.value=withTiming(maxScroll,{duration:1500});break}case HorizontalScrollDirection.None:{targetLeftBound.value=leftBound.value;break}}}}));useAnimatedReaction((()=>targetLeftBound.value),((targetLeftBoundValue,previousValue)=>{if(targetLeftBoundValue!==null&&previousValue!==null&&targetLeftBoundValue!==previousValue){if(movingSV.value){leftBound.value=targetLeftBoundValue}}}),[movingSV]);const panGestureHandler=useAnimatedGestureHandler({onStart(event,ctx){"worklet";ctx.initialItemContentX=getItemXPosition(positions.value[id],itemWidth,gap,paddingHorizontal);ctx.initialFingerAbsoluteX=event.absoluteX;ctx.initialLeftBound=leftBound.value;positionX.value=ctx.initialItemContentX;movingSV.value=true;runOnJS(setIsMoving)(true);if(onDragStart){runOnJS(onDragStart)(id,positions.value[id])}},onActive(event,ctx){"worklet";const fingerDxScreen=event.absoluteX-ctx.initialFingerAbsoluteX;const scrollDeltaSinceStart=leftBound.value-ctx.initialLeftBound;positionX.value=ctx.initialItemContentX+fingerDxScreen+scrollDeltaSinceStart},onFinish(){"worklet";const finishPosition=getItemXPosition(positions.value[id],itemWidth,gap,paddingHorizontal);left.value=withTiming(finishPosition);movingSV.value=false;runOnJS(setIsMoving)(false);if(onDrop){runOnJS(onDrop)(id,positions.value[id])}currentOverItemId.value=null}});const animatedStyle=useAnimatedStyle((()=>{"worklet";return{position:"absolute",top:0,bottom:0,left:left.value,width:itemWidth,zIndex:movingSV.value?1:0,backgroundColor:"transparent",shadowColor:"black",shadowOpacity:withSpring(movingSV.value?.2:0),shadowRadius:10}}),[movingSV,itemWidth]);return{animatedStyle,panGestureHandler,isMoving,hasHandle}}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { UseHorizontalSortableListOptions, UseHorizontalSortableListReturn } from "../types/sortable";
|
|
2
|
+
export declare function useHorizontalSortableList<TData extends {
|
|
3
|
+
id: string;
|
|
4
|
+
}>(options: UseHorizontalSortableListOptions<TData>): UseHorizontalSortableListReturn<TData>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useRef,useCallback}from"react";import{scrollTo,useAnimatedReaction,useAnimatedRef,useAnimatedScrollHandler,useSharedValue}from"react-native-reanimated";import{listToObject,getContentWidth}from"../components/sortableUtils";import{HorizontalScrollDirection}from"../types/sortable";export function useHorizontalSortableList(options){const{data,itemWidth,gap=0,paddingHorizontal=0,itemKeyExtractor=item=>item.id}=options;const positions=useSharedValue(listToObject(data));const scrollX=useSharedValue(0);const autoScroll=useSharedValue(HorizontalScrollDirection.None);const scrollViewRef=useAnimatedRef();const dropProviderRef=useRef(null);useAnimatedReaction((()=>scrollX.value),(scrolling=>{scrollTo(scrollViewRef,scrolling,0,false)}));const handleScroll=useAnimatedScrollHandler((event=>{scrollX.value=event.contentOffset.x}));const handleScrollEnd=useCallback((()=>{let localScrollTimeout=null;if(localScrollTimeout){clearTimeout(localScrollTimeout)}localScrollTimeout=setTimeout((()=>{var _a;(_a=dropProviderRef.current)===null||_a===void 0?void 0:_a.requestPositionUpdate()}),50)}),[]);const contentWidth=getContentWidth(data.length,itemWidth,gap,paddingHorizontal);const getItemProps=useCallback(((item,index)=>{const id=itemKeyExtractor(item,index);return{id,positions,leftBound:scrollX,autoScrollDirection:autoScroll,itemsCount:data.length,itemWidth,gap,paddingHorizontal}}),[data.length,itemWidth,gap,paddingHorizontal,itemKeyExtractor,positions,scrollX,autoScroll]);return{positions,scrollX,autoScroll,scrollViewRef,dropProviderRef,handleScroll,handleScrollEnd,contentWidth,getItemProps}}
|
package/lib/hooks/useSortable.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useState,useRef,useEffect}from"react";import React from"react";import{runOnJS,runOnUI,useAnimatedGestureHandler,useAnimatedReaction,useAnimatedStyle,useDerivedValue,useSharedValue,withSpring,withTiming}from"react-native-reanimated";export var ScrollDirection
|
|
1
|
+
import{useState,useRef,useEffect}from"react";import React from"react";import{runOnJS,runOnUI,useAnimatedGestureHandler,useAnimatedReaction,useAnimatedStyle,useDerivedValue,useSharedValue,withSpring,withTiming}from"react-native-reanimated";export var ScrollDirection;(function(ScrollDirection){ScrollDirection["None"]="none";ScrollDirection["Up"]="up";ScrollDirection["Down"]="down"})(ScrollDirection||(ScrollDirection={}));export function clamp(value,lowerBound,upperBound){"worklet";return Math.max(lowerBound,Math.min(value,upperBound))}export function objectMove(object,from,to){"worklet";const newObject=Object.assign({},object);const movedUp=to<from;for(const id in object){if(object[id]===from){newObject[id]=to;continue}const currentPosition=object[id];if(movedUp&¤tPosition>=to&¤tPosition<from){newObject[id]++}else if(currentPosition<=to&¤tPosition>from){newObject[id]--}}return newObject}export function listToObject(list){const values=Object.values(list);const object={};for(let i=0;i<values.length;i++){object[values[i].id]=i}return object}export function setPosition(positionY,itemsCount,positions,id,itemHeight){"worklet";const newPosition=clamp(Math.floor(positionY/itemHeight),0,itemsCount-1);if(newPosition!==positions.value[id]){positions.value=objectMove(positions.value,positions.value[id],newPosition)}}export function setAutoScroll(positionY,lowerBound,upperBound,scrollThreshold,autoScroll){"worklet";if(positionY<=lowerBound+scrollThreshold){autoScroll.value=ScrollDirection.Up}else if(positionY>=upperBound-scrollThreshold){autoScroll.value=ScrollDirection.Down}else{autoScroll.value=ScrollDirection.None}}export function useSortable(options){const{id,positions,lowerBound,autoScrollDirection,itemsCount,itemHeight,containerHeight=500,onMove,onDragStart,onDrop,onDragging,children,handleComponent}=options;const[isMoving,setIsMoving]=useState(false);const[hasHandle,setHasHandle]=useState(false);const movingSV=useSharedValue(false);const currentOverItemId=useSharedValue(null);const onDraggingLastCallTimestamp=useSharedValue(0);const THROTTLE_INTERVAL=50;const positionY=useSharedValue(0);const top=useSharedValue(0);const targetLowerBound=useSharedValue(0);useEffect((()=>{runOnUI((()=>{"worklet";const initialTopVal=positions.value[id]*itemHeight;const initialLowerBoundVal=lowerBound.value;top.value=initialTopVal;positionY.value=initialTopVal;targetLowerBound.value=initialLowerBoundVal}))()}),[]);const calculatedContainerHeight=useRef(containerHeight).current;const upperBound=useDerivedValue((()=>lowerBound.value+calculatedContainerHeight));useEffect((()=>{if(!children||!handleComponent){setHasHandle(false);return}const checkForHandle=child=>{if(React.isValidElement(child)){if(child.type===handleComponent){return true}if(child.props&&child.props.children){if(React.Children.toArray(child.props.children).some(checkForHandle)){return true}}}return false};setHasHandle(React.Children.toArray(children).some(checkForHandle))}),[children,handleComponent]);useAnimatedReaction((()=>positionY.value),((currentY,previousY)=>{if(currentY===null||!movingSV.value){return}if(previousY!==null&¤tY===previousY){return}const clampedPosition=Math.min(Math.max(0,Math.ceil(currentY/itemHeight)),itemsCount-1);let newOverItemId=null;for(const[itemIdIter,itemPosIter]of Object.entries(positions.value)){if(itemPosIter===clampedPosition&&itemIdIter!==id){newOverItemId=itemIdIter;break}}if(currentOverItemId.value!==newOverItemId){currentOverItemId.value=newOverItemId}if(onDragging){const now=Date.now();if(now-onDraggingLastCallTimestamp.value>THROTTLE_INTERVAL){runOnJS(onDragging)(id,newOverItemId,Math.round(currentY));onDraggingLastCallTimestamp.value=now}}top.value=currentY;setPosition(currentY,itemsCount,positions,id,itemHeight);setAutoScroll(currentY,lowerBound.value,upperBound.value,itemHeight,autoScrollDirection)}),[movingSV,itemHeight,itemsCount,positions,id,onDragging,lowerBound,upperBound,autoScrollDirection,currentOverItemId,top,onDraggingLastCallTimestamp]);useAnimatedReaction((()=>positions.value[id]),((currentPosition,previousPosition)=>{if(currentPosition!==null&&previousPosition!==null&¤tPosition!==previousPosition){if(!movingSV.value){top.value=withSpring(currentPosition*itemHeight);if(onMove){runOnJS(onMove)(id,previousPosition,currentPosition)}}}}),[movingSV]);useAnimatedReaction((()=>autoScrollDirection.value),((scrollDirection,previousValue)=>{if(scrollDirection!==null&&previousValue!==null&&scrollDirection!==previousValue){switch(scrollDirection){case ScrollDirection.Up:{targetLowerBound.value=lowerBound.value;targetLowerBound.value=withTiming(0,{duration:1500});break}case ScrollDirection.Down:{const contentHeight=itemsCount*itemHeight;const maxScroll=contentHeight-calculatedContainerHeight;targetLowerBound.value=lowerBound.value;targetLowerBound.value=withTiming(maxScroll,{duration:1500});break}case ScrollDirection.None:{targetLowerBound.value=lowerBound.value;break}}}}));useAnimatedReaction((()=>targetLowerBound.value),((targetLowerBoundValue,previousValue)=>{if(targetLowerBoundValue!==null&&previousValue!==null&&targetLowerBoundValue!==previousValue){if(movingSV.value){lowerBound.value=targetLowerBoundValue}}}),[movingSV]);const panGestureHandler=useAnimatedGestureHandler({onStart(event,ctx){"worklet";ctx.initialItemContentY=positions.value[id]*itemHeight;ctx.initialFingerAbsoluteY=event.absoluteY;ctx.initialLowerBound=lowerBound.value;positionY.value=ctx.initialItemContentY;movingSV.value=true;runOnJS(setIsMoving)(true);if(onDragStart){runOnJS(onDragStart)(id,positions.value[id])}},onActive(event,ctx){"worklet";const fingerDyScreen=event.absoluteY-ctx.initialFingerAbsoluteY;const scrollDeltaSinceStart=lowerBound.value-ctx.initialLowerBound;positionY.value=ctx.initialItemContentY+fingerDyScreen+scrollDeltaSinceStart},onFinish(){"worklet";const finishPosition=positions.value[id]*itemHeight;top.value=withTiming(finishPosition);movingSV.value=false;runOnJS(setIsMoving)(false);if(onDrop){runOnJS(onDrop)(id,positions.value[id])}currentOverItemId.value=null}});const animatedStyle=useAnimatedStyle((()=>{"worklet";return{position:"absolute",left:0,right:0,top:top.value,zIndex:movingSV.value?1:0,backgroundColor:"#000000",shadowColor:"black",shadowOpacity:withSpring(movingSV.value?.2:0),shadowRadius:10}}),[movingSV]);return{animatedStyle,panGestureHandler,isMoving,hasHandle}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useRef,useCallback}from"react";import{scrollTo,useAnimatedReaction,useAnimatedRef,useAnimatedScrollHandler,useSharedValue}from"react-native-reanimated";import{listToObject}from"../components/sortableUtils";import{ScrollDirection}from"../types/sortable";export function useSortableList(
|
|
1
|
+
import{useRef,useCallback}from"react";import{scrollTo,useAnimatedReaction,useAnimatedRef,useAnimatedScrollHandler,useSharedValue}from"react-native-reanimated";import{listToObject}from"../components/sortableUtils";import{ScrollDirection}from"../types/sortable";export function useSortableList(options){const{data,itemHeight,itemKeyExtractor=item=>item.id}=options;const positions=useSharedValue(listToObject(data));const scrollY=useSharedValue(0);const autoScroll=useSharedValue(ScrollDirection.None);const scrollViewRef=useAnimatedRef();const dropProviderRef=useRef(null);useAnimatedReaction((()=>scrollY.value),(scrolling=>{scrollTo(scrollViewRef,0,scrolling,false)}));const handleScroll=useAnimatedScrollHandler((event=>{scrollY.value=event.contentOffset.y}));const handleScrollEnd=useCallback((()=>{let localScrollTimeout=null;if(localScrollTimeout){clearTimeout(localScrollTimeout)}localScrollTimeout=setTimeout((()=>{var _a;(_a=dropProviderRef.current)===null||_a===void 0?void 0:_a.requestPositionUpdate()}),50)}),[]);const contentHeight=data.length*itemHeight;const getItemProps=useCallback(((item,index)=>{const id=itemKeyExtractor(item,index);return{id,positions,lowerBound:scrollY,autoScrollDirection:autoScroll,itemsCount:data.length,itemHeight}}),[data.length,itemHeight,itemKeyExtractor,positions,scrollY,autoScroll]);return{positions,scrollY,autoScroll,scrollViewRef,dropProviderRef,handleScroll,handleScrollEnd,contentHeight,getItemProps}}
|
package/lib/types/context.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createContext}from"react";const defaultSlotsContextValue={register:(
|
|
1
|
+
import{createContext}from"react";const defaultSlotsContextValue={register:(_id,_slot)=>{if(process.env.NODE_ENV!=="production"){console.warn("SlotsContext: register called without a Provider.")}},unregister:_id=>{if(process.env.NODE_ENV!=="production"){console.warn("SlotsContext: unregister called without a Provider.")}},getSlots:()=>{if(process.env.NODE_ENV!=="production"){console.warn("SlotsContext: getSlots called without a Provider.")}return{}},isRegistered:_id=>{if(process.env.NODE_ENV!=="production"){console.warn("SlotsContext: isRegistered called without a Provider.")}return false},setActiveHoverSlot:_id=>{if(process.env.NODE_ENV!=="production"){console.warn("SlotsContext: setActiveHoverSlot called without a Provider.")}},activeHoverSlotId:null,registerPositionUpdateListener:(_id,_listener)=>{if(process.env.NODE_ENV!=="production"){console.warn("SlotsContext: registerPositionUpdateListener called without a Provider.")}},unregisterPositionUpdateListener:_id=>{if(process.env.NODE_ENV!=="production"){console.warn("SlotsContext: unregisterPositionUpdateListener called without a Provider.")}},requestPositionUpdate:()=>{if(process.env.NODE_ENV!=="production"){console.warn("SlotsContext: requestPositionUpdate called without a Provider (internally).")}},registerDroppedItem:(_draggableId,_droppableId,_itemData)=>{if(process.env.NODE_ENV!=="production"){console.warn("SlotsContext: registerDroppedItem called without a Provider.")}},unregisterDroppedItem:_draggableId=>{if(process.env.NODE_ENV!=="production"){console.warn("SlotsContext: unregisterDroppedItem called without a Provider.")}},getDroppedItems:()=>{if(process.env.NODE_ENV!=="production"){console.warn("SlotsContext: getDroppedItems called without a Provider.")}return{}},hasAvailableCapacity:_droppableId=>{if(process.env.NODE_ENV!=="production"){console.warn("SlotsContext: hasAvailableCapacity called without a Provider.")}return false},onDragging:payload=>{if(process.env.NODE_ENV!=="production"){console.warn("SlotsContext: onDragging called without a Provider.")}},onDragStart:undefined,onDragEnd:undefined};export const SlotsContext=createContext(defaultSlotsContextValue);
|
package/lib/types/draggable.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export var DraggableState
|
|
1
|
+
export var DraggableState;(function(DraggableState){DraggableState["IDLE"]="IDLE";DraggableState["DRAGGING"]="DRAGGING";DraggableState["DROPPED"]="DROPPED"})(DraggableState||(DraggableState={}));
|
package/lib/types/sortable.d.ts
CHANGED
|
@@ -7,6 +7,15 @@ export declare enum ScrollDirection {
|
|
|
7
7
|
Up = "up",
|
|
8
8
|
Down = "down"
|
|
9
9
|
}
|
|
10
|
+
export declare enum HorizontalScrollDirection {
|
|
11
|
+
None = "none",
|
|
12
|
+
Left = "left",
|
|
13
|
+
Right = "right"
|
|
14
|
+
}
|
|
15
|
+
export declare enum SortableDirection {
|
|
16
|
+
Vertical = "vertical",
|
|
17
|
+
Horizontal = "horizontal"
|
|
18
|
+
}
|
|
10
19
|
export interface UseSortableOptions<T> {
|
|
11
20
|
id: string;
|
|
12
21
|
positions: SharedValue<{
|
|
@@ -57,11 +66,18 @@ export interface SortableItemProps<T> {
|
|
|
57
66
|
positions: SharedValue<{
|
|
58
67
|
[id: string]: number;
|
|
59
68
|
}>;
|
|
60
|
-
lowerBound
|
|
61
|
-
|
|
69
|
+
lowerBound?: SharedValue<number>;
|
|
70
|
+
leftBound?: SharedValue<number>;
|
|
71
|
+
autoScrollDirection?: SharedValue<ScrollDirection>;
|
|
72
|
+
autoScrollHorizontalDirection?: SharedValue<HorizontalScrollDirection>;
|
|
62
73
|
itemsCount: number;
|
|
63
|
-
|
|
74
|
+
direction?: SortableDirection;
|
|
75
|
+
itemHeight?: number;
|
|
76
|
+
itemWidth?: number;
|
|
77
|
+
gap?: number;
|
|
78
|
+
paddingHorizontal?: number;
|
|
64
79
|
containerHeight?: number;
|
|
80
|
+
containerWidth?: number;
|
|
65
81
|
children: ReactNode;
|
|
66
82
|
style?: StyleProp<ViewStyle>;
|
|
67
83
|
animatedStyle?: StyleProp<ViewStyle>;
|
|
@@ -69,14 +85,20 @@ export interface SortableItemProps<T> {
|
|
|
69
85
|
onDragStart?: (id: string, position: number) => void;
|
|
70
86
|
onDrop?: (id: string, position: number) => void;
|
|
71
87
|
onDragging?: (id: string, overItemId: string | null, yPosition: number) => void;
|
|
88
|
+
onDraggingHorizontal?: (id: string, overItemId: string | null, xPosition: number) => void;
|
|
72
89
|
}
|
|
73
90
|
export interface SortableProps<TData> {
|
|
74
91
|
data: TData[];
|
|
75
92
|
renderItem: (props: SortableRenderItemProps<TData>) => ReactNode;
|
|
76
|
-
|
|
93
|
+
direction?: SortableDirection;
|
|
94
|
+
itemHeight?: number;
|
|
95
|
+
itemWidth?: number;
|
|
96
|
+
gap?: number;
|
|
97
|
+
paddingHorizontal?: number;
|
|
77
98
|
style?: StyleProp<ViewStyle>;
|
|
78
99
|
contentContainerStyle?: StyleProp<ViewStyle>;
|
|
79
100
|
itemKeyExtractor?: (item: TData, index: number) => string;
|
|
101
|
+
useFlatList?: boolean;
|
|
80
102
|
}
|
|
81
103
|
export interface SortableRenderItemProps<TData> {
|
|
82
104
|
item: TData;
|
|
@@ -85,10 +107,16 @@ export interface SortableRenderItemProps<TData> {
|
|
|
85
107
|
positions: SharedValue<{
|
|
86
108
|
[id: string]: number;
|
|
87
109
|
}>;
|
|
88
|
-
|
|
89
|
-
|
|
110
|
+
direction?: SortableDirection;
|
|
111
|
+
lowerBound?: SharedValue<number>;
|
|
112
|
+
leftBound?: SharedValue<number>;
|
|
113
|
+
autoScrollDirection?: SharedValue<ScrollDirection>;
|
|
114
|
+
autoScrollHorizontalDirection?: SharedValue<HorizontalScrollDirection>;
|
|
90
115
|
itemsCount: number;
|
|
91
|
-
itemHeight
|
|
116
|
+
itemHeight?: number;
|
|
117
|
+
itemWidth?: number;
|
|
118
|
+
gap?: number;
|
|
119
|
+
paddingHorizontal?: number;
|
|
92
120
|
}
|
|
93
121
|
export interface SortableContextValue {
|
|
94
122
|
panGestureHandler: any;
|
|
@@ -97,3 +125,98 @@ export interface SortableHandleProps {
|
|
|
97
125
|
children: React.ReactNode;
|
|
98
126
|
style?: StyleProp<ViewStyle>;
|
|
99
127
|
}
|
|
128
|
+
export interface UseHorizontalSortableOptions<T> {
|
|
129
|
+
id: string;
|
|
130
|
+
positions: SharedValue<{
|
|
131
|
+
[id: string]: number;
|
|
132
|
+
}>;
|
|
133
|
+
leftBound: SharedValue<number>;
|
|
134
|
+
autoScrollDirection: SharedValue<HorizontalScrollDirection>;
|
|
135
|
+
itemsCount: number;
|
|
136
|
+
itemWidth: number;
|
|
137
|
+
gap?: number;
|
|
138
|
+
paddingHorizontal?: number;
|
|
139
|
+
containerWidth?: number;
|
|
140
|
+
onMove?: (id: string, from: number, to: number) => void;
|
|
141
|
+
onDragStart?: (id: string, position: number) => void;
|
|
142
|
+
onDrop?: (id: string, position: number) => void;
|
|
143
|
+
onDragging?: (id: string, overItemId: string | null, xPosition: number) => void;
|
|
144
|
+
}
|
|
145
|
+
export interface UseHorizontalSortableReturn {
|
|
146
|
+
animatedStyle: StyleProp<ViewStyle>;
|
|
147
|
+
panGestureHandler: any;
|
|
148
|
+
isMoving: boolean;
|
|
149
|
+
hasHandle: boolean;
|
|
150
|
+
}
|
|
151
|
+
export interface UseHorizontalSortableListOptions<TData> {
|
|
152
|
+
data: TData[];
|
|
153
|
+
itemWidth: number;
|
|
154
|
+
gap?: number;
|
|
155
|
+
paddingHorizontal?: number;
|
|
156
|
+
itemKeyExtractor?: (item: TData, index: number) => string;
|
|
157
|
+
}
|
|
158
|
+
export interface UseHorizontalSortableListReturn<TData> {
|
|
159
|
+
positions: any;
|
|
160
|
+
scrollX: any;
|
|
161
|
+
autoScroll: any;
|
|
162
|
+
scrollViewRef: any;
|
|
163
|
+
dropProviderRef: React.RefObject<DropProviderRef>;
|
|
164
|
+
handleScroll: any;
|
|
165
|
+
handleScrollEnd: () => void;
|
|
166
|
+
contentWidth: number;
|
|
167
|
+
getItemProps: (item: TData, index: number) => {
|
|
168
|
+
id: string;
|
|
169
|
+
positions: any;
|
|
170
|
+
leftBound: any;
|
|
171
|
+
autoScrollDirection: any;
|
|
172
|
+
itemsCount: number;
|
|
173
|
+
itemWidth: number;
|
|
174
|
+
gap: number;
|
|
175
|
+
paddingHorizontal: number;
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
export interface HorizontalSortableItemProps<T> {
|
|
179
|
+
id: string;
|
|
180
|
+
data: T;
|
|
181
|
+
positions: SharedValue<{
|
|
182
|
+
[id: string]: number;
|
|
183
|
+
}>;
|
|
184
|
+
leftBound: SharedValue<number>;
|
|
185
|
+
autoScrollDirection: SharedValue<HorizontalScrollDirection>;
|
|
186
|
+
itemsCount: number;
|
|
187
|
+
itemWidth: number;
|
|
188
|
+
gap?: number;
|
|
189
|
+
paddingHorizontal?: number;
|
|
190
|
+
containerWidth?: number;
|
|
191
|
+
children: ReactNode;
|
|
192
|
+
style?: StyleProp<ViewStyle>;
|
|
193
|
+
animatedStyle?: StyleProp<ViewStyle>;
|
|
194
|
+
onMove?: (id: string, from: number, to: number) => void;
|
|
195
|
+
onDragStart?: (id: string, position: number) => void;
|
|
196
|
+
onDrop?: (id: string, position: number) => void;
|
|
197
|
+
onDragging?: (id: string, overItemId: string | null, xPosition: number) => void;
|
|
198
|
+
}
|
|
199
|
+
export interface HorizontalSortableProps<TData> {
|
|
200
|
+
data: TData[];
|
|
201
|
+
renderItem: (props: HorizontalSortableRenderItemProps<TData>) => ReactNode;
|
|
202
|
+
itemWidth: number;
|
|
203
|
+
gap?: number;
|
|
204
|
+
paddingHorizontal?: number;
|
|
205
|
+
style?: StyleProp<ViewStyle>;
|
|
206
|
+
contentContainerStyle?: StyleProp<ViewStyle>;
|
|
207
|
+
itemKeyExtractor?: (item: TData, index: number) => string;
|
|
208
|
+
}
|
|
209
|
+
export interface HorizontalSortableRenderItemProps<TData> {
|
|
210
|
+
item: TData;
|
|
211
|
+
index: number;
|
|
212
|
+
id: string;
|
|
213
|
+
positions: SharedValue<{
|
|
214
|
+
[id: string]: number;
|
|
215
|
+
}>;
|
|
216
|
+
leftBound: SharedValue<number>;
|
|
217
|
+
autoScrollDirection: SharedValue<HorizontalScrollDirection>;
|
|
218
|
+
itemsCount: number;
|
|
219
|
+
itemWidth: number;
|
|
220
|
+
gap: number;
|
|
221
|
+
paddingHorizontal: number;
|
|
222
|
+
}
|
package/lib/types/sortable.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export var ScrollDirection
|
|
1
|
+
export var ScrollDirection;(function(ScrollDirection){ScrollDirection["None"]="none";ScrollDirection["Up"]="up";ScrollDirection["Down"]="down"})(ScrollDirection||(ScrollDirection={}));export var HorizontalScrollDirection;(function(HorizontalScrollDirection){HorizontalScrollDirection["None"]="none";HorizontalScrollDirection["Left"]="left";HorizontalScrollDirection["Right"]="right"})(HorizontalScrollDirection||(HorizontalScrollDirection={}));export var SortableDirection;(function(SortableDirection){SortableDirection["Vertical"]="vertical";SortableDirection["Horizontal"]="horizontal"})(SortableDirection||(SortableDirection={}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-reanimated-dnd",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A powerful drag-and-drop library for React Native using Reanimated 3",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "lib/index.js",
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
"sideEffects": false,
|
|
17
17
|
"scripts": {
|
|
18
18
|
"build": "tsc && npm run minify",
|
|
19
|
+
"build:safe": "tsc",
|
|
19
20
|
"build:clean": "rm -rf lib && npm run build",
|
|
21
|
+
"build:clean:safe": "rm -rf lib && npm run build:safe",
|
|
20
22
|
"minify": "node scripts/build.js",
|
|
21
23
|
"type-check": "tsc --noEmit",
|
|
22
24
|
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md,yml,yaml}\" --ignore-path .prettierignore",
|