react-native-reanimated-dnd 1.0.5 â 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/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/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/types/sortable.d.ts +130 -7
- package/lib/types/sortable.js +1 -1
- package/package.json +1 -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,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);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,lowerBound,autoScrollDirection,itemsCount,itemHeight,containerHeight,children,style,animatedStyle:customAnimatedStyle,onMove,onDragStart,onDrop,onDragging}){const
|
|
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;(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);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}}
|
|
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()};
|
|
@@ -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;(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);for(const id in object){if(object[id]===from){newObject[id]=to}if(
|
|
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}}
|
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;(function(ScrollDirection){ScrollDirection["None"]="none";ScrollDirection["Up"]="up";ScrollDirection["Down"]="down"})(ScrollDirection||(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={}));
|