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 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-ff3b30?style=for-the-badge&logo=expo&logoColor=white&labelColor=1e293b" alt="Live Demo" />
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
- *Scan with your camera or Expo Go app*
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
- *Comprehensive guides, API reference, and interactive examples*
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
- *Drag and drop to reorder items with smooth animations*
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
- *Multiple algorithms for precise drop targeting*
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
- *Precise control with dedicated drag areas*
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
- *Constrain movement within specific boundaries*
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
- *Visual feedback during drag operations*
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
- *Complete lifecycle tracking and callbacks*
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={{ flex: 1 }}>
226
+ <GestureHandlerRootView style={styles.container}>
215
227
  <DropProvider>
216
- <View style={{ flex: 1, padding: 20 }}>
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 { View, Text } from "react-native";
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
- console.log("Item dropped:", data);
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={{ flex: 1 }}>
290
+ <GestureHandlerRootView style={styles.container}>
254
291
  <DropProvider>
255
- <View style={{ flex: 1, padding: 20 }}>
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
- <Droppable onDrop={handleDrop}>
265
- <View style={styles.dropZone}>
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
- <Droppable onDrop={handleDrop}>
271
- <View style={styles.dropZone}>
272
- <Text>ðŸŽŊ Drop Zone 2</Text>
273
- </View>
274
- </Droppable>
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 { View, Text } from "react-native";
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 { Sortable, SortableItem } from "react-native-reanimated-dnd";
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 = ({ item, id, positions, ...props }) => (
303
- <SortableItem
304
- key={id}
305
- id={id}
306
- positions={positions}
307
- {...props}
308
- onMove={(itemId, from, to) => {
309
- const newTasks = [...tasks];
310
- const [movedTask] = newTasks.splice(from, 1);
311
- newTasks.splice(to, 0, movedTask);
312
- setTasks(newTasks);
313
- }}
314
- >
315
- <View style={styles.taskItem}>
316
- <Text>{item.title}</Text>
317
-
318
- {/* Drag Handle */}
319
- <SortableItem.Handle style={styles.dragHandle}>
320
- <Text>â‹Ūâ‹Ū</Text>
321
- </SortableItem.Handle>
322
- </View>
323
- </SortableItem>
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={{ flex: 1 }}>
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={60}
332
- style={{ flex: 1, padding: 16 }}
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! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
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 function Sortable<TData extends {
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 from"react";import{StyleSheet}from"react-native";import Animated from"react-native-reanimated";import{GestureHandlerRootView,ScrollView}from"react-native-gesture-handler";import{DropProvider}from"../context/DropContext";import{useSortableList}from"../hooks/useSortableList";const AnimatedScrollView=Animated.createAnimatedComponent(ScrollView);export function Sortable({data,renderItem,itemHeight,style,contentContainerStyle,itemKeyExtractor=item=>item.id}){const sortableOptions={data,itemHeight,itemKeyExtractor};const{scrollViewRef,dropProviderRef,handleScroll,handleScrollEnd,contentHeight,getItemProps}=useSortableList(sortableOptions);return React.createElement(GestureHandlerRootView,{style:styles.flex},React.createElement(DropProvider,{ref:dropProviderRef},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,...itemProps};return renderItem(sortableItemProps)})))))}const styles=StyleSheet.create({flex:{flex:1},scrollView:{flex:1,position:"relative",backgroundColor:"white"}});
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 sortableOptions={id,positions,lowerBound,autoScrollDirection,itemsCount,itemHeight,containerHeight,onMove,onDragStart,onDrop,onDragging,children,handleComponent:SortableHandle};const{animatedStyle,panGestureHandler,isMoving,hasHandle}=useSortable(sortableOptions);const combinedAnimatedStyle=[animatedStyle,customAnimatedStyle];const contextValue={panGestureHandler};const content=React.createElement(Animated.View,{style:combinedAnimatedStyle},React.createElement(SortableContext.Provider,{value:contextValue},React.createElement(Animated.View,{style},children)));if(hasHandle){return content}else{return React.createElement(PanGestureHandler,{onGestureEvent:panGestureHandler,activateAfterLongPress:200,shouldCancelWhenOutside:false},content)}}SortableItem.Handle=SortableHandle;
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,2 @@
1
+ import { UseHorizontalSortableOptions, UseHorizontalSortableReturn } from "../types/sortable";
2
+ export declare function useHorizontalSortable<T>(options: UseHorizontalSortableOptions<T>): UseHorizontalSortableReturn;
@@ -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&&currentX===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&&currentPosition!==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}}
@@ -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(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 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&&currentY===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&&currentPosition!==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
+ 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&&currentPosition>=to&&currentPosition<from){newObject[id]++}else if(currentPosition<=to&&currentPosition>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&&currentY===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&&currentPosition!==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}}
@@ -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: SharedValue<number>;
61
- autoScrollDirection: SharedValue<ScrollDirection>;
69
+ lowerBound?: SharedValue<number>;
70
+ leftBound?: SharedValue<number>;
71
+ autoScrollDirection?: SharedValue<ScrollDirection>;
72
+ autoScrollHorizontalDirection?: SharedValue<HorizontalScrollDirection>;
62
73
  itemsCount: number;
63
- itemHeight: number;
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
- itemHeight: number;
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
- lowerBound: SharedValue<number>;
89
- autoScrollDirection: SharedValue<ScrollDirection>;
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: number;
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
+ }
@@ -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={}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-reanimated-dnd",
3
- "version": "1.0.5",
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",