react-native-reanimated-dnd 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +633 -0
- package/lib/components/Draggable.d.ts +5 -0
- package/lib/components/Draggable.js +265 -0
- package/lib/components/Droppable.d.ts +264 -0
- package/lib/components/Droppable.js +284 -0
- package/lib/components/Sortable.d.ts +184 -0
- package/lib/components/Sortable.js +225 -0
- package/lib/components/SortableItem.d.ts +158 -0
- package/lib/components/SortableItem.js +251 -0
- package/lib/components/sortableUtils.d.ts +21 -0
- package/lib/components/sortableUtils.js +50 -0
- package/lib/context/DropContext.d.ts +118 -0
- package/lib/context/DropContext.js +233 -0
- package/lib/hooks/index.d.ts +4 -0
- package/lib/hooks/index.js +5 -0
- package/lib/hooks/useDraggable.d.ts +101 -0
- package/lib/hooks/useDraggable.js +567 -0
- package/lib/hooks/useDroppable.d.ts +129 -0
- package/lib/hooks/useDroppable.js +261 -0
- package/lib/hooks/useSortable.d.ts +174 -0
- package/lib/hooks/useSortable.js +361 -0
- package/lib/hooks/useSortableList.d.ts +182 -0
- package/lib/hooks/useSortableList.js +211 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.js +16 -0
- package/lib/types/context.d.ts +166 -0
- package/lib/types/context.js +80 -0
- package/lib/types/draggable.d.ts +313 -0
- package/lib/types/draggable.js +31 -0
- package/lib/types/droppable.d.ts +197 -0
- package/lib/types/droppable.js +1 -0
- package/lib/types/index.d.ts +4 -0
- package/lib/types/index.js +8 -0
- package/lib/types/sortable.d.ts +432 -0
- package/lib/types/sortable.js +6 -0
- package/package.json +59 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { useRef, useEffect, useContext, useCallback, useMemo, } from "react";
|
|
2
|
+
import { StyleSheet } from "react-native";
|
|
3
|
+
import { useAnimatedRef, measure, runOnUI, runOnJS, } from "react-native-reanimated";
|
|
4
|
+
import { SlotsContext, } from "../types/context";
|
|
5
|
+
import { _getUniqueDroppableId } from "../components/Droppable";
|
|
6
|
+
/**
|
|
7
|
+
* A hook for creating drop zones that can receive draggable items.
|
|
8
|
+
*
|
|
9
|
+
* This hook handles the registration of drop zones, collision detection with draggable items,
|
|
10
|
+
* visual feedback during hover states, and proper positioning of dropped items within the zone.
|
|
11
|
+
* It integrates seamlessly with the drag-and-drop context to provide a complete solution.
|
|
12
|
+
*
|
|
13
|
+
* @template TData - The type of data that can be dropped on this droppable
|
|
14
|
+
* @param options - Configuration options for the droppable behavior
|
|
15
|
+
* @returns Object containing view props, active state, and internal references
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* Basic drop zone:
|
|
19
|
+
* ```typescript
|
|
20
|
+
* import { useDroppable } from './hooks/useDroppable';
|
|
21
|
+
*
|
|
22
|
+
* function BasicDropZone() {
|
|
23
|
+
* const { viewProps, isActive } = useDroppable({
|
|
24
|
+
* onDrop: (data) => {
|
|
25
|
+
* console.log('Item dropped:', data);
|
|
26
|
+
* // Handle the dropped item
|
|
27
|
+
* }
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* return (
|
|
31
|
+
* <Animated.View
|
|
32
|
+
* {...viewProps}
|
|
33
|
+
* style={[
|
|
34
|
+
* styles.dropZone,
|
|
35
|
+
* viewProps.style, // Important: include the active style
|
|
36
|
+
* isActive && styles.highlighted
|
|
37
|
+
* ]}
|
|
38
|
+
* >
|
|
39
|
+
* <Text>Drop items here</Text>
|
|
40
|
+
* </Animated.View>
|
|
41
|
+
* );
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* Drop zone with custom alignment and capacity:
|
|
47
|
+
* ```typescript
|
|
48
|
+
* function TaskColumn() {
|
|
49
|
+
* const [tasks, setTasks] = useState<Task[]>([]);
|
|
50
|
+
*
|
|
51
|
+
* const { viewProps, isActive } = useDroppable({
|
|
52
|
+
* droppableId: 'in-progress-column',
|
|
53
|
+
* onDrop: (task: Task) => {
|
|
54
|
+
* setTasks(prev => [...prev, task]);
|
|
55
|
+
* updateTaskStatus(task.id, 'in-progress');
|
|
56
|
+
* },
|
|
57
|
+
* dropAlignment: 'top-center',
|
|
58
|
+
* dropOffset: { x: 0, y: 10 },
|
|
59
|
+
* capacity: 10, // Max 10 tasks in this column
|
|
60
|
+
* activeStyle: {
|
|
61
|
+
* backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
|
62
|
+
* borderColor: '#3b82f6',
|
|
63
|
+
* borderWidth: 2,
|
|
64
|
+
* borderStyle: 'dashed'
|
|
65
|
+
* }
|
|
66
|
+
* });
|
|
67
|
+
*
|
|
68
|
+
* return (
|
|
69
|
+
* <Animated.View {...viewProps} style={[styles.column, viewProps.style]}>
|
|
70
|
+
* <Text style={styles.columnTitle}>In Progress ({tasks.length}/10)</Text>
|
|
71
|
+
* {tasks.map(task => (
|
|
72
|
+
* <TaskCard key={task.id} task={task} />
|
|
73
|
+
* ))}
|
|
74
|
+
* {isActive && (
|
|
75
|
+
* <Text style={styles.dropHint}>Release to add task</Text>
|
|
76
|
+
* )}
|
|
77
|
+
* </Animated.View>
|
|
78
|
+
* );
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* Conditional drop zone with validation:
|
|
84
|
+
* ```typescript
|
|
85
|
+
* function RestrictedDropZone() {
|
|
86
|
+
* const [canAcceptItems, setCanAcceptItems] = useState(true);
|
|
87
|
+
*
|
|
88
|
+
* const { viewProps, isActive } = useDroppable({
|
|
89
|
+
* onDrop: (data: FileData) => {
|
|
90
|
+
* if (data.type === 'image' && data.size < 5000000) {
|
|
91
|
+
* uploadFile(data);
|
|
92
|
+
* } else {
|
|
93
|
+
* showError('Only images under 5MB allowed');
|
|
94
|
+
* }
|
|
95
|
+
* },
|
|
96
|
+
* dropDisabled: !canAcceptItems,
|
|
97
|
+
* onActiveChange: (active) => {
|
|
98
|
+
* if (active) {
|
|
99
|
+
* setHoverFeedback('Drop your image here');
|
|
100
|
+
* } else {
|
|
101
|
+
* setHoverFeedback('');
|
|
102
|
+
* }
|
|
103
|
+
* },
|
|
104
|
+
* activeStyle: {
|
|
105
|
+
* backgroundColor: canAcceptItems ? 'rgba(34, 197, 94, 0.1)' : 'rgba(239, 68, 68, 0.1)',
|
|
106
|
+
* borderColor: canAcceptItems ? '#22c55e' : '#ef4444'
|
|
107
|
+
* }
|
|
108
|
+
* });
|
|
109
|
+
*
|
|
110
|
+
* return (
|
|
111
|
+
* <Animated.View
|
|
112
|
+
* {...viewProps}
|
|
113
|
+
* style={[
|
|
114
|
+
* styles.uploadZone,
|
|
115
|
+
* viewProps.style,
|
|
116
|
+
* !canAcceptItems && styles.disabled
|
|
117
|
+
* ]}
|
|
118
|
+
* >
|
|
119
|
+
* <Text>
|
|
120
|
+
* {canAcceptItems ? 'Drop images here' : 'Upload disabled'}
|
|
121
|
+
* </Text>
|
|
122
|
+
* {isActive && <Text>Release to upload</Text>}
|
|
123
|
+
* </Animated.View>
|
|
124
|
+
* );
|
|
125
|
+
* }
|
|
126
|
+
* ```
|
|
127
|
+
*
|
|
128
|
+
* @see {@link DropAlignment} for alignment options
|
|
129
|
+
* @see {@link DropOffset} for offset configuration
|
|
130
|
+
* @see {@link UseDroppableOptions} for configuration options
|
|
131
|
+
* @see {@link UseDroppableReturn} for return value details
|
|
132
|
+
*/
|
|
133
|
+
export const useDroppable = (options) => {
|
|
134
|
+
const { onDrop, dropDisabled, onActiveChange, dropAlignment, dropOffset, activeStyle, droppableId, capacity, } = options;
|
|
135
|
+
// Create animated ref first
|
|
136
|
+
const animatedViewRef = useAnimatedRef();
|
|
137
|
+
const id = useRef(_getUniqueDroppableId()).current;
|
|
138
|
+
const stringId = useRef(droppableId || `droppable-${id}`).current;
|
|
139
|
+
const instanceId = useRef(`droppable-${id}-${Math.random().toString(36).substr(2, 9)}`).current;
|
|
140
|
+
const { register, unregister, isRegistered, activeHoverSlotId: contextActiveHoverSlotId, registerPositionUpdateListener, unregisterPositionUpdateListener, } = useContext(SlotsContext);
|
|
141
|
+
const isActive = contextActiveHoverSlotId === id;
|
|
142
|
+
// Process active style to separate transforms from other styles
|
|
143
|
+
const { processedActiveStyle, activeTransforms } = useMemo(() => {
|
|
144
|
+
if (!isActive || !activeStyle) {
|
|
145
|
+
return { processedActiveStyle: null, activeTransforms: [] };
|
|
146
|
+
}
|
|
147
|
+
const flattenedStyle = StyleSheet.flatten(activeStyle);
|
|
148
|
+
let processedStyle = { ...flattenedStyle };
|
|
149
|
+
let transforms = [];
|
|
150
|
+
// Extract and process transforms if present
|
|
151
|
+
if (flattenedStyle.transform) {
|
|
152
|
+
if (Array.isArray(flattenedStyle.transform)) {
|
|
153
|
+
transforms = [...flattenedStyle.transform];
|
|
154
|
+
}
|
|
155
|
+
// Remove transform from the main style to avoid conflicts
|
|
156
|
+
delete processedStyle.transform;
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
processedActiveStyle: processedStyle,
|
|
160
|
+
activeTransforms: transforms,
|
|
161
|
+
};
|
|
162
|
+
}, [isActive, activeStyle]);
|
|
163
|
+
// Create the final style with transforms properly handled
|
|
164
|
+
const combinedActiveStyle = useMemo(() => {
|
|
165
|
+
if (!isActive || !activeStyle) {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
// If there are no transforms, just return the processed style
|
|
169
|
+
if (activeTransforms.length === 0) {
|
|
170
|
+
return processedActiveStyle;
|
|
171
|
+
}
|
|
172
|
+
// Add transforms to the style
|
|
173
|
+
return {
|
|
174
|
+
...processedActiveStyle,
|
|
175
|
+
transform: activeTransforms,
|
|
176
|
+
};
|
|
177
|
+
}, [isActive, activeStyle, processedActiveStyle, activeTransforms]);
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
onActiveChange === null || onActiveChange === void 0 ? void 0 : onActiveChange(isActive);
|
|
180
|
+
}, [isActive, onActiveChange]);
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
console.log(`Droppable ${id} using string ID: ${stringId}, provided ID: ${droppableId || "none"}`);
|
|
183
|
+
}, [id, stringId, droppableId]);
|
|
184
|
+
const updateDroppablePosition = useCallback(() => {
|
|
185
|
+
runOnUI(() => {
|
|
186
|
+
"worklet";
|
|
187
|
+
const measurement = measure(animatedViewRef);
|
|
188
|
+
if (measurement === null) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (measurement.width > 0 && measurement.height > 0) {
|
|
192
|
+
// Ensure valid dimensions before registering
|
|
193
|
+
runOnJS(register)(id, {
|
|
194
|
+
id: droppableId || `droppable-${id}`,
|
|
195
|
+
x: measurement.pageX,
|
|
196
|
+
y: measurement.pageY,
|
|
197
|
+
width: measurement.width,
|
|
198
|
+
height: measurement.height,
|
|
199
|
+
onDrop,
|
|
200
|
+
dropAlignment: dropAlignment || "center",
|
|
201
|
+
dropOffset: dropOffset || { x: 0, y: 0 },
|
|
202
|
+
capacity,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
})();
|
|
206
|
+
}, [
|
|
207
|
+
id,
|
|
208
|
+
droppableId,
|
|
209
|
+
onDrop,
|
|
210
|
+
register,
|
|
211
|
+
animatedViewRef,
|
|
212
|
+
dropAlignment,
|
|
213
|
+
dropOffset,
|
|
214
|
+
capacity,
|
|
215
|
+
]);
|
|
216
|
+
const handleLayoutHandler = useCallback((_event) => {
|
|
217
|
+
updateDroppablePosition();
|
|
218
|
+
}, [updateDroppablePosition]);
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
registerPositionUpdateListener(instanceId, updateDroppablePosition);
|
|
221
|
+
return () => {
|
|
222
|
+
unregisterPositionUpdateListener(instanceId);
|
|
223
|
+
};
|
|
224
|
+
}, [
|
|
225
|
+
instanceId,
|
|
226
|
+
registerPositionUpdateListener,
|
|
227
|
+
unregisterPositionUpdateListener,
|
|
228
|
+
updateDroppablePosition,
|
|
229
|
+
]);
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
if (dropDisabled) {
|
|
232
|
+
unregister(id);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
// Initial registration or re-registration if it became enabled
|
|
236
|
+
updateDroppablePosition();
|
|
237
|
+
}
|
|
238
|
+
// Not relying on isRegistered here for initial registration to ensure it always attempts
|
|
239
|
+
// to register if not disabled. The measure call inside updateDroppablePosition is the gatekeeper.
|
|
240
|
+
}, [
|
|
241
|
+
dropDisabled,
|
|
242
|
+
id,
|
|
243
|
+
unregister, // only unregister is truly a dependency for the disabled case
|
|
244
|
+
updateDroppablePosition, // for the enabled case
|
|
245
|
+
]);
|
|
246
|
+
useEffect(() => {
|
|
247
|
+
// Cleanup on unmount
|
|
248
|
+
return () => {
|
|
249
|
+
unregister(id);
|
|
250
|
+
};
|
|
251
|
+
}, [id, unregister]);
|
|
252
|
+
return {
|
|
253
|
+
viewProps: {
|
|
254
|
+
onLayout: handleLayoutHandler,
|
|
255
|
+
style: combinedActiveStyle,
|
|
256
|
+
},
|
|
257
|
+
isActive,
|
|
258
|
+
activeStyle,
|
|
259
|
+
animatedViewRef,
|
|
260
|
+
};
|
|
261
|
+
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { StyleProp, ViewStyle } from "react-native";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { SharedValue } from "react-native-reanimated";
|
|
4
|
+
export declare enum ScrollDirection {
|
|
5
|
+
None = "none",
|
|
6
|
+
Up = "up",
|
|
7
|
+
Down = "down"
|
|
8
|
+
}
|
|
9
|
+
export declare function clamp(value: number, lowerBound: number, upperBound: number): number;
|
|
10
|
+
export declare function objectMove(object: {
|
|
11
|
+
[id: string]: number;
|
|
12
|
+
}, from: number, to: number): {
|
|
13
|
+
[id: string]: number;
|
|
14
|
+
};
|
|
15
|
+
export declare function listToObject<T extends {
|
|
16
|
+
id: string;
|
|
17
|
+
}>(list: T[]): {
|
|
18
|
+
[id: string]: number;
|
|
19
|
+
};
|
|
20
|
+
export declare function setPosition(positionY: number, itemsCount: number, positions: SharedValue<{
|
|
21
|
+
[id: string]: number;
|
|
22
|
+
}>, id: string, itemHeight: number): void;
|
|
23
|
+
export declare function setAutoScroll(positionY: number, lowerBound: number, upperBound: number, scrollThreshold: number, autoScroll: SharedValue<ScrollDirection>): void;
|
|
24
|
+
/**
|
|
25
|
+
* @see {@link UseSortableOptions} for configuration options
|
|
26
|
+
* @see {@link UseSortableReturn} for return value details
|
|
27
|
+
* @see {@link useSortableList} for list-level management
|
|
28
|
+
* @see {@link SortableItem} for component implementation
|
|
29
|
+
* @see {@link Sortable} for high-level sortable list component
|
|
30
|
+
*/
|
|
31
|
+
export interface UseSortableOptions<T> {
|
|
32
|
+
id: string;
|
|
33
|
+
positions: SharedValue<{
|
|
34
|
+
[id: string]: number;
|
|
35
|
+
}>;
|
|
36
|
+
lowerBound: SharedValue<number>;
|
|
37
|
+
autoScrollDirection: SharedValue<ScrollDirection>;
|
|
38
|
+
itemsCount: number;
|
|
39
|
+
itemHeight: number;
|
|
40
|
+
containerHeight?: number;
|
|
41
|
+
onMove?: (id: string, from: number, to: number) => void;
|
|
42
|
+
onDragStart?: (id: string, position: number) => void;
|
|
43
|
+
onDrop?: (id: string, position: number) => void;
|
|
44
|
+
onDragging?: (id: string, overItemId: string | null, yPosition: number) => void;
|
|
45
|
+
children?: React.ReactNode;
|
|
46
|
+
handleComponent?: React.ComponentType<any>;
|
|
47
|
+
}
|
|
48
|
+
export interface UseSortableReturn {
|
|
49
|
+
animatedStyle: StyleProp<ViewStyle>;
|
|
50
|
+
panGestureHandler: any;
|
|
51
|
+
isMoving: boolean;
|
|
52
|
+
hasHandle: boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A hook for creating sortable list items with drag-and-drop reordering capabilities.
|
|
56
|
+
*
|
|
57
|
+
* This hook provides the core functionality for individual items within a sortable list,
|
|
58
|
+
* handling drag gestures, position animations, auto-scrolling, and reordering logic.
|
|
59
|
+
* It works in conjunction with useSortableList to provide a complete sortable solution.
|
|
60
|
+
*
|
|
61
|
+
* @template T - The type of data associated with the sortable item
|
|
62
|
+
* @param options - Configuration options for the sortable item behavior
|
|
63
|
+
* @returns Object containing animated styles, gesture handlers, and state for the sortable item
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* Basic sortable item:
|
|
67
|
+
* ```typescript
|
|
68
|
+
* import { useSortable } from './hooks/useSortable';
|
|
69
|
+
*
|
|
70
|
+
* function SortableTaskItem({ task, positions, ...sortableProps }) {
|
|
71
|
+
* const { animatedStyle, panGestureHandler, isMoving } = useSortable({
|
|
72
|
+
* id: task.id,
|
|
73
|
+
* positions,
|
|
74
|
+
* ...sortableProps,
|
|
75
|
+
* onMove: (id, from, to) => {
|
|
76
|
+
* console.log(`Task ${id} moved from ${from} to ${to}`);
|
|
77
|
+
* reorderTasks(id, from, to);
|
|
78
|
+
* }
|
|
79
|
+
* });
|
|
80
|
+
*
|
|
81
|
+
* return (
|
|
82
|
+
* <PanGestureHandler {...panGestureHandler}>
|
|
83
|
+
* <Animated.View style={[styles.taskItem, animatedStyle]}>
|
|
84
|
+
* <Text style={[styles.taskText, isMoving && styles.dragging]}>
|
|
85
|
+
* {task.title}
|
|
86
|
+
* </Text>
|
|
87
|
+
* </Animated.View>
|
|
88
|
+
* </PanGestureHandler>
|
|
89
|
+
* );
|
|
90
|
+
* }
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* Sortable item with drag handle:
|
|
95
|
+
* ```typescript
|
|
96
|
+
* import { useSortable } from './hooks/useSortable';
|
|
97
|
+
* import { SortableHandle } from './components/SortableItem';
|
|
98
|
+
*
|
|
99
|
+
* function TaskWithHandle({ task, ...sortableProps }) {
|
|
100
|
+
* const { animatedStyle, panGestureHandler, hasHandle } = useSortable({
|
|
101
|
+
* id: task.id,
|
|
102
|
+
* ...sortableProps,
|
|
103
|
+
* children: (
|
|
104
|
+
* <View style={styles.taskContent}>
|
|
105
|
+
* <Text>{task.title}</Text>
|
|
106
|
+
* <SortableHandle>
|
|
107
|
+
* <Icon name="drag-handle" />
|
|
108
|
+
* </SortableHandle>
|
|
109
|
+
* </View>
|
|
110
|
+
* )
|
|
111
|
+
* });
|
|
112
|
+
*
|
|
113
|
+
* return (
|
|
114
|
+
* <PanGestureHandler {...panGestureHandler}>
|
|
115
|
+
* <Animated.View style={[styles.taskItem, animatedStyle]}>
|
|
116
|
+
* <View style={styles.taskContent}>
|
|
117
|
+
* <Text>{task.title}</Text>
|
|
118
|
+
* <SortableHandle>
|
|
119
|
+
* <Icon name="drag-handle" />
|
|
120
|
+
* </SortableHandle>
|
|
121
|
+
* </View>
|
|
122
|
+
* </Animated.View>
|
|
123
|
+
* </PanGestureHandler>
|
|
124
|
+
* );
|
|
125
|
+
* }
|
|
126
|
+
* ```
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* Sortable item with callbacks and state tracking:
|
|
130
|
+
* ```typescript
|
|
131
|
+
* function AdvancedSortableItem({ item, ...sortableProps }) {
|
|
132
|
+
* const [isDragging, setIsDragging] = useState(false);
|
|
133
|
+
*
|
|
134
|
+
* const { animatedStyle, panGestureHandler } = useSortable({
|
|
135
|
+
* id: item.id,
|
|
136
|
+
* ...sortableProps,
|
|
137
|
+
* onDragStart: (id, position) => {
|
|
138
|
+
* setIsDragging(true);
|
|
139
|
+
* analytics.track('drag_start', { itemId: id, position });
|
|
140
|
+
* },
|
|
141
|
+
* onDrop: (id, position) => {
|
|
142
|
+
* setIsDragging(false);
|
|
143
|
+
* analytics.track('drag_end', { itemId: id, position });
|
|
144
|
+
* },
|
|
145
|
+
* onDragging: (id, overItemId, yPosition) => {
|
|
146
|
+
* if (overItemId) {
|
|
147
|
+
* showDropPreview(overItemId);
|
|
148
|
+
* }
|
|
149
|
+
* }
|
|
150
|
+
* });
|
|
151
|
+
*
|
|
152
|
+
* return (
|
|
153
|
+
* <PanGestureHandler {...panGestureHandler}>
|
|
154
|
+
* <Animated.View
|
|
155
|
+
* style={[
|
|
156
|
+
* styles.item,
|
|
157
|
+
* animatedStyle,
|
|
158
|
+
* isDragging && styles.dragging
|
|
159
|
+
* ]}
|
|
160
|
+
* >
|
|
161
|
+
* <Text>{item.title}</Text>
|
|
162
|
+
* </Animated.View>
|
|
163
|
+
* </PanGestureHandler>
|
|
164
|
+
* );
|
|
165
|
+
* }
|
|
166
|
+
* ```
|
|
167
|
+
*
|
|
168
|
+
* @see {@link UseSortableOptions} for configuration options
|
|
169
|
+
* @see {@link UseSortableReturn} for return value details
|
|
170
|
+
* @see {@link useSortableList} for list-level management
|
|
171
|
+
* @see {@link SortableItem} for component implementation
|
|
172
|
+
* @see {@link Sortable} for high-level sortable list component
|
|
173
|
+
*/
|
|
174
|
+
export declare function useSortable<T>(options: UseSortableOptions<T>): UseSortableReturn;
|