react-native-reorderable-list 0.3.0 → 0.5.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.
Files changed (162) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +118 -59
  3. package/lib/commonjs/components/ReorderableList/ReorderableList.js +99 -0
  4. package/lib/commonjs/components/ReorderableList/ReorderableList.js.map +1 -0
  5. package/lib/commonjs/components/ReorderableList/constants.ios.js +10 -0
  6. package/lib/commonjs/components/ReorderableList/constants.ios.js.map +1 -0
  7. package/lib/commonjs/components/ReorderableList/constants.js +10 -0
  8. package/lib/commonjs/components/ReorderableList/constants.js.map +1 -0
  9. package/lib/commonjs/components/ReorderableList/index.js +17 -0
  10. package/lib/commonjs/components/ReorderableList/index.js.map +1 -0
  11. package/lib/commonjs/components/ReorderableList/useReorderableList.js +311 -0
  12. package/lib/commonjs/components/ReorderableList/useReorderableList.js.map +1 -0
  13. package/lib/commonjs/components/ReorderableListCell.js +93 -0
  14. package/lib/commonjs/components/ReorderableListCell.js.map +1 -0
  15. package/lib/commonjs/components/ReorderableListItem.js +87 -0
  16. package/lib/commonjs/components/ReorderableListItem.js.map +1 -0
  17. package/lib/commonjs/components/index.js +39 -0
  18. package/lib/commonjs/components/index.js.map +1 -0
  19. package/lib/commonjs/contexts/ReorderableCellContext.js +10 -0
  20. package/lib/commonjs/contexts/ReorderableCellContext.js.map +1 -0
  21. package/lib/commonjs/contexts/ReorderableListContext.js +10 -0
  22. package/lib/commonjs/contexts/ReorderableListContext.js.map +1 -0
  23. package/lib/commonjs/contexts/index.js +28 -0
  24. package/lib/commonjs/contexts/index.js.map +1 -0
  25. package/lib/commonjs/hooks/index.js +50 -0
  26. package/lib/commonjs/hooks/index.js.map +1 -0
  27. package/lib/commonjs/hooks/useContext.js +16 -0
  28. package/lib/commonjs/hooks/useContext.js.map +1 -0
  29. package/lib/commonjs/hooks/useReorderableDrag.js +16 -0
  30. package/lib/commonjs/hooks/useReorderableDrag.js.map +1 -0
  31. package/lib/commonjs/hooks/useReorderableDragEnd.js +28 -0
  32. package/lib/commonjs/hooks/useReorderableDragEnd.js.map +1 -0
  33. package/lib/commonjs/hooks/useReorderableDragStart.js +22 -0
  34. package/lib/commonjs/hooks/useReorderableDragStart.js.map +1 -0
  35. package/lib/commonjs/index.js +41 -0
  36. package/lib/commonjs/index.js.map +1 -0
  37. package/lib/commonjs/types/index.js +28 -0
  38. package/lib/commonjs/types/index.js.map +1 -0
  39. package/lib/commonjs/types/misc.js +14 -0
  40. package/lib/commonjs/types/misc.js.map +1 -0
  41. package/lib/commonjs/types/props.js +6 -0
  42. package/lib/commonjs/types/props.js.map +1 -0
  43. package/lib/commonjs/utils.js +23 -0
  44. package/lib/commonjs/utils.js.map +1 -0
  45. package/lib/module/components/ReorderableList/ReorderableList.js +91 -0
  46. package/lib/module/components/ReorderableList/ReorderableList.js.map +1 -0
  47. package/lib/module/components/ReorderableList/constants.ios.js +4 -0
  48. package/lib/module/components/ReorderableList/constants.ios.js.map +1 -0
  49. package/lib/module/components/ReorderableList/constants.js +4 -0
  50. package/lib/module/components/ReorderableList/constants.js.map +1 -0
  51. package/lib/module/components/ReorderableList/index.js +2 -0
  52. package/lib/module/components/ReorderableList/index.js.map +1 -0
  53. package/lib/module/components/ReorderableList/useReorderableList.js +302 -0
  54. package/lib/module/components/ReorderableList/useReorderableList.js.map +1 -0
  55. package/lib/module/components/ReorderableListCell.js +85 -0
  56. package/lib/module/components/ReorderableListCell.js.map +1 -0
  57. package/lib/module/components/ReorderableListItem.js +78 -0
  58. package/lib/module/components/ReorderableListItem.js.map +1 -0
  59. package/lib/module/components/index.js +4 -0
  60. package/lib/module/components/index.js.map +1 -0
  61. package/lib/module/contexts/ReorderableCellContext.js +3 -0
  62. package/lib/module/contexts/ReorderableCellContext.js.map +1 -0
  63. package/lib/module/contexts/ReorderableListContext.js +3 -0
  64. package/lib/module/contexts/ReorderableListContext.js.map +1 -0
  65. package/lib/module/contexts/index.js +3 -0
  66. package/lib/module/contexts/index.js.map +1 -0
  67. package/lib/module/hooks/index.js +5 -0
  68. package/lib/module/hooks/index.js.map +1 -0
  69. package/lib/module/hooks/useContext.js +9 -0
  70. package/lib/module/hooks/useContext.js.map +1 -0
  71. package/lib/module/hooks/useReorderableDrag.js +9 -0
  72. package/lib/module/hooks/useReorderableDrag.js.map +1 -0
  73. package/lib/module/hooks/useReorderableDragEnd.js +21 -0
  74. package/lib/module/hooks/useReorderableDragEnd.js.map +1 -0
  75. package/lib/module/hooks/useReorderableDragStart.js +15 -0
  76. package/lib/module/hooks/useReorderableDragStart.js.map +1 -0
  77. package/lib/module/index.js +6 -0
  78. package/lib/module/index.js.map +1 -0
  79. package/lib/module/types/index.js +3 -0
  80. package/lib/module/types/index.js.map +1 -0
  81. package/lib/module/types/misc.js +8 -0
  82. package/lib/module/types/misc.js.map +1 -0
  83. package/lib/module/types/props.js +2 -0
  84. package/lib/module/types/props.js.map +1 -0
  85. package/lib/module/utils.js +16 -0
  86. package/lib/module/utils.js.map +1 -0
  87. package/lib/typescript/components/ReorderableList/ReorderableList.d.ts +8 -0
  88. package/lib/typescript/components/ReorderableList/ReorderableList.d.ts.map +1 -0
  89. package/lib/typescript/components/ReorderableList/constants.d.ts +3 -0
  90. package/lib/typescript/components/ReorderableList/constants.d.ts.map +1 -0
  91. package/lib/typescript/components/ReorderableList/constants.ios.d.ts +3 -0
  92. package/lib/typescript/components/ReorderableList/constants.ios.d.ts.map +1 -0
  93. package/lib/typescript/components/ReorderableList/index.d.ts +2 -0
  94. package/lib/typescript/components/ReorderableList/index.d.ts.map +1 -0
  95. package/lib/typescript/components/ReorderableList/useReorderableList.d.ts +34 -0
  96. package/lib/typescript/components/ReorderableList/useReorderableList.d.ts.map +1 -0
  97. package/lib/typescript/components/ReorderableListCell.d.ts +20 -0
  98. package/lib/typescript/components/ReorderableListCell.d.ts.map +1 -0
  99. package/lib/typescript/components/ReorderableListItem.d.ts +4 -0
  100. package/lib/typescript/components/ReorderableListItem.d.ts.map +1 -0
  101. package/lib/typescript/components/index.d.ts +4 -0
  102. package/lib/typescript/components/index.d.ts.map +1 -0
  103. package/lib/typescript/contexts/ReorderableCellContext.d.ts +11 -0
  104. package/lib/typescript/contexts/ReorderableCellContext.d.ts.map +1 -0
  105. package/lib/typescript/contexts/ReorderableListContext.d.ts +9 -0
  106. package/lib/typescript/contexts/ReorderableListContext.d.ts.map +1 -0
  107. package/lib/typescript/contexts/index.d.ts +3 -0
  108. package/lib/typescript/contexts/index.d.ts.map +1 -0
  109. package/lib/typescript/hooks/index.d.ts +5 -0
  110. package/lib/typescript/hooks/index.d.ts.map +1 -0
  111. package/lib/typescript/hooks/useContext.d.ts +3 -0
  112. package/lib/typescript/hooks/useContext.d.ts.map +1 -0
  113. package/lib/typescript/hooks/useReorderableDrag.d.ts +2 -0
  114. package/lib/typescript/hooks/useReorderableDrag.d.ts.map +1 -0
  115. package/lib/typescript/hooks/useReorderableDragEnd.d.ts +2 -0
  116. package/lib/typescript/hooks/useReorderableDragEnd.d.ts.map +1 -0
  117. package/lib/typescript/hooks/useReorderableDragStart.d.ts +2 -0
  118. package/lib/typescript/hooks/useReorderableDragStart.d.ts.map +1 -0
  119. package/lib/typescript/index.d.ts +7 -0
  120. package/lib/typescript/index.d.ts.map +1 -0
  121. package/lib/typescript/types/index.d.ts +3 -0
  122. package/lib/typescript/types/index.d.ts.map +1 -0
  123. package/lib/typescript/types/misc.d.ts +7 -0
  124. package/lib/typescript/types/misc.d.ts.map +1 -0
  125. package/lib/typescript/types/props.d.ts +90 -0
  126. package/lib/typescript/types/props.d.ts.map +1 -0
  127. package/lib/typescript/utils.d.ts +12 -0
  128. package/lib/typescript/utils.d.ts.map +1 -0
  129. package/package.json +199 -39
  130. package/src/components/ReorderableList/ReorderableList.tsx +123 -0
  131. package/src/components/ReorderableList/constants.ios.ts +3 -0
  132. package/src/components/ReorderableList/constants.ts +3 -0
  133. package/src/components/ReorderableList/index.ts +1 -0
  134. package/src/components/ReorderableList/useReorderableList.ts +487 -0
  135. package/src/components/ReorderableListCell.tsx +143 -0
  136. package/src/components/ReorderableListItem.tsx +108 -0
  137. package/src/components/index.ts +3 -0
  138. package/src/contexts/ReorderableCellContext.ts +14 -0
  139. package/src/contexts/ReorderableListContext.ts +12 -0
  140. package/src/contexts/index.ts +2 -0
  141. package/src/hooks/index.ts +4 -0
  142. package/src/hooks/useContext.ts +11 -0
  143. package/src/hooks/useReorderableDrag.ts +7 -0
  144. package/src/hooks/useReorderableDragEnd.ts +25 -0
  145. package/src/hooks/useReorderableDragStart.ts +18 -0
  146. package/src/index.ts +26 -0
  147. package/src/types/index.ts +2 -0
  148. package/src/types/misc.ts +6 -0
  149. package/src/types/props.ts +101 -0
  150. package/src/utils.ts +15 -0
  151. package/dist/components/ReorderableList.d.ts +0 -7
  152. package/dist/components/ReorderableList.js +0 -311
  153. package/dist/components/ReorderableListItem.d.ts +0 -14
  154. package/dist/components/ReorderableListItem.js +0 -57
  155. package/dist/hooks/useAnimatedSharedValues.d.ts +0 -3
  156. package/dist/hooks/useAnimatedSharedValues.js +0 -33
  157. package/dist/index.d.ts +0 -4
  158. package/dist/index.js +0 -2
  159. package/dist/types/misc.d.ts +0 -15
  160. package/dist/types/misc.js +0 -7
  161. package/dist/types/props.d.ts +0 -27
  162. package/dist/types/props.js +0 -1
@@ -0,0 +1,487 @@
1
+ import React, {useCallback, useMemo} from 'react';
2
+ import {
3
+ FlatList,
4
+ LayoutChangeEvent,
5
+ NativeScrollEvent,
6
+ unstable_batchedUpdates,
7
+ } from 'react-native';
8
+
9
+ import {Gesture, State} from 'react-native-gesture-handler';
10
+ import Animated, {
11
+ AnimatedRef,
12
+ Easing,
13
+ cancelAnimation,
14
+ measure,
15
+ runOnJS,
16
+ runOnUI,
17
+ scrollTo,
18
+ useAnimatedReaction,
19
+ useAnimatedRef,
20
+ useAnimatedScrollHandler,
21
+ useSharedValue,
22
+ withDelay,
23
+ withTiming,
24
+ } from 'react-native-reanimated';
25
+
26
+ import {AUTOSCROLL_INCREMENT} from './constants';
27
+ import {ReorderableListState} from '../../types';
28
+ import type {ReorderableListReorderEvent} from '../../types';
29
+
30
+ const version = React.version.split('.');
31
+ const hasAutomaticBatching = version.length
32
+ ? parseInt(version[0], 10) >= 18
33
+ : false;
34
+
35
+ interface UseReorderableListArgs<T> {
36
+ ref: React.ForwardedRef<FlatList<T>>;
37
+ autoscrollThreshold: number;
38
+ autoscrollSpeedScale: number;
39
+ autoscrollDelay: number;
40
+ animationDuration: number;
41
+ dragReorderThreshold: number;
42
+ onReorder: (event: ReorderableListReorderEvent) => void;
43
+ onScroll?: (event: NativeScrollEvent) => void;
44
+ onLayout?: (e: LayoutChangeEvent) => void;
45
+ }
46
+
47
+ export const useReorderableList = <T>({
48
+ ref,
49
+ autoscrollThreshold,
50
+ autoscrollSpeedScale,
51
+ autoscrollDelay,
52
+ animationDuration,
53
+ dragReorderThreshold,
54
+ onLayout,
55
+ onReorder,
56
+ onScroll,
57
+ }: UseReorderableListArgs<T>) => {
58
+ const scrollEnabled = useSharedValue(true);
59
+ const flatList = useAnimatedRef<FlatList>();
60
+ const gestureState = useSharedValue<State>(State.UNDETERMINED);
61
+ const currentY = useSharedValue(0);
62
+ const currentTranslationY = useSharedValue(0);
63
+ const containerPositionY = useSharedValue(0);
64
+ const currentScrollOffsetY = useSharedValue(0);
65
+ const dragScrollTranslationY = useSharedValue(0);
66
+ const dragInitialScrollOffsetY = useSharedValue(0);
67
+ const draggedHeight = useSharedValue(0);
68
+ const itemOffset = useSharedValue<number[]>([]);
69
+ const itemHeight = useSharedValue<number[]>([]);
70
+ const topAutoscrollArea = useSharedValue(0);
71
+ const bottomAutoscrollArea = useSharedValue(0);
72
+ const autoscrollTrigger = useSharedValue(-1);
73
+ const lastAutoscrollTrigger = useSharedValue(-1);
74
+ const previousY = useSharedValue(0);
75
+ const dragY = useSharedValue(0);
76
+ const previousDirection = useSharedValue(0);
77
+ const previousIndex = useSharedValue(-1);
78
+ const currentIndex = useSharedValue(-1);
79
+ const draggedIndex = useSharedValue(-1);
80
+ const releasedIndex = useSharedValue(-1);
81
+ const state = useSharedValue<ReorderableListState>(ReorderableListState.IDLE);
82
+
83
+ // animation duration as a shared value allows to avoid re-rendering of all cells on value change
84
+ const duration = useSharedValue(animationDuration);
85
+ if (duration.value !== animationDuration) {
86
+ duration.value = animationDuration;
87
+ }
88
+
89
+ const listContextValue = useMemo(
90
+ () => ({
91
+ draggedHeight,
92
+ currentIndex,
93
+ draggedIndex,
94
+ }),
95
+ [draggedHeight, currentIndex, draggedIndex],
96
+ );
97
+
98
+ const startY = useSharedValue(0);
99
+
100
+ const panGestureHandler = useMemo(
101
+ () =>
102
+ Gesture.Pan()
103
+ .onBegin(e => {
104
+ // prevent new dragging until item is completely released
105
+ if (state.value === ReorderableListState.IDLE) {
106
+ const relativeY = e.absoluteY - containerPositionY.value;
107
+
108
+ startY.value = relativeY;
109
+ currentY.value = relativeY;
110
+ currentTranslationY.value = e.translationY;
111
+ if (draggedIndex.value >= 0) {
112
+ dragY.value = e.translationY;
113
+ }
114
+ gestureState.value = e.state;
115
+ }
116
+ })
117
+ .onUpdate(e => {
118
+ if (state.value !== ReorderableListState.RELEASING) {
119
+ currentY.value = startY.value + e.translationY;
120
+ currentTranslationY.value = e.translationY;
121
+ if (draggedIndex.value >= 0) {
122
+ dragY.value = e.translationY + dragScrollTranslationY.value;
123
+ }
124
+ gestureState.value = e.state;
125
+ }
126
+ })
127
+ .onEnd(e => (gestureState.value = e.state))
128
+ .onFinalize(e => (gestureState.value = e.state)),
129
+ [
130
+ containerPositionY,
131
+ currentTranslationY,
132
+ currentY,
133
+ dragScrollTranslationY,
134
+ draggedIndex,
135
+ gestureState,
136
+ dragY,
137
+ startY,
138
+ state,
139
+ ],
140
+ );
141
+
142
+ const gestureHandler = useMemo(
143
+ () => Gesture.Simultaneous(Gesture.Native(), panGestureHandler),
144
+ [panGestureHandler],
145
+ );
146
+
147
+ const setScrollEnabled = useCallback(
148
+ (enabled: boolean) => {
149
+ scrollEnabled.value = enabled;
150
+ flatList.current?.setNativeProps({scrollEnabled: enabled});
151
+ },
152
+ [flatList, scrollEnabled],
153
+ );
154
+
155
+ const resetSharedValues = useCallback(() => {
156
+ 'worklet';
157
+
158
+ // reset indexes before arrays to avoid triggering animated reactions
159
+ draggedIndex.value = -1;
160
+ // current index is reset on item render for the on end event
161
+ dragY.value = 0;
162
+ // released flag is reset after release is triggered in the item
163
+ state.value = ReorderableListState.IDLE;
164
+ dragScrollTranslationY.value = 0;
165
+ }, [draggedIndex, dragY, state, dragScrollTranslationY]);
166
+
167
+ const reorder = (fromIndex: number, toIndex: number) => {
168
+ if (fromIndex !== toIndex) {
169
+ const updateState = () => {
170
+ onReorder({from: fromIndex, to: toIndex});
171
+ };
172
+
173
+ if (!hasAutomaticBatching) {
174
+ unstable_batchedUpdates(updateState);
175
+ } else {
176
+ updateState();
177
+ }
178
+ }
179
+
180
+ runOnUI(resetSharedValues)();
181
+ };
182
+
183
+ const getIndexFromY = useCallback(
184
+ (y: number) => {
185
+ 'worklet';
186
+
187
+ const relativeY = currentScrollOffsetY.value + y;
188
+ const count = itemOffset.value.length;
189
+
190
+ for (let i = 0; i < count; i++) {
191
+ if (currentIndex.value === i) {
192
+ continue;
193
+ }
194
+
195
+ const direction = i > currentIndex.value ? 1 : -1;
196
+ const threshold = Math.max(0, Math.min(1, dragReorderThreshold));
197
+ const height = itemHeight.value[i];
198
+ const offset = itemOffset.value[i] + height * threshold * direction;
199
+
200
+ if (
201
+ (i === 0 && relativeY <= offset) ||
202
+ (i === count - 1 && relativeY >= offset + height) ||
203
+ (relativeY >= offset && relativeY <= offset + height)
204
+ ) {
205
+ return {index: i, direction};
206
+ }
207
+ }
208
+
209
+ return {
210
+ index: currentIndex.value,
211
+ direction: previousDirection.value,
212
+ };
213
+ },
214
+ [
215
+ dragReorderThreshold,
216
+ currentIndex,
217
+ currentScrollOffsetY,
218
+ previousDirection,
219
+ itemOffset,
220
+ itemHeight,
221
+ ],
222
+ );
223
+
224
+ const setCurrentIndex = useCallback(
225
+ (y: number) => {
226
+ 'worklet';
227
+
228
+ const {index: newIndex, direction: newDirection} = getIndexFromY(y);
229
+ const delta = Math.abs(previousY.value - y);
230
+
231
+ if (
232
+ currentIndex.value !== newIndex &&
233
+ // if the same two items re-swap index check delta and direction to avoid swap flickering
234
+ (previousIndex.value !== newIndex ||
235
+ (previousDirection.value !== newDirection && delta >= 5))
236
+ ) {
237
+ const itemDirection = newIndex > currentIndex.value;
238
+ const index1 = itemDirection ? currentIndex.value : newIndex;
239
+ const index2 = itemDirection ? newIndex : currentIndex.value;
240
+
241
+ const newOffset1 = itemOffset.value[index1];
242
+ const newHeight1 = itemHeight.value[index2];
243
+ const newOffset2 =
244
+ itemOffset.value[index2] +
245
+ (itemHeight.value[index2] - itemHeight.value[index1]);
246
+ const newHeight2 = itemHeight.value[index1];
247
+
248
+ itemOffset.value[index1] = newOffset1;
249
+ itemHeight.value[index1] = newHeight1;
250
+ itemOffset.value[index2] = newOffset2;
251
+ itemHeight.value[index2] = newHeight2;
252
+
253
+ previousY.value = y;
254
+ previousDirection.value = newDirection;
255
+ previousIndex.value = currentIndex.value;
256
+ currentIndex.value = newIndex;
257
+ }
258
+ },
259
+ [
260
+ currentIndex,
261
+ previousIndex,
262
+ previousDirection,
263
+ previousY,
264
+ itemOffset,
265
+ itemHeight,
266
+ getIndexFromY,
267
+ ],
268
+ );
269
+
270
+ useAnimatedReaction(
271
+ () => gestureState.value,
272
+ () => {
273
+ if (
274
+ gestureState.value !== State.ACTIVE &&
275
+ gestureState.value !== State.BEGAN &&
276
+ (state.value === ReorderableListState.DRAGGING ||
277
+ state.value === ReorderableListState.AUTO_SCROLL)
278
+ ) {
279
+ state.value = ReorderableListState.RELEASING;
280
+ releasedIndex.value = draggedIndex.value;
281
+
282
+ // enable back scroll on releasing
283
+ runOnJS(setScrollEnabled)(true);
284
+
285
+ // they are actually swapped on drag translation
286
+ const currentItemOffset = itemOffset.value[draggedIndex.value];
287
+ const currentItemHeight = itemHeight.value[draggedIndex.value];
288
+ const draggedItemOffset = itemOffset.value[currentIndex.value];
289
+ const draggedItemHeight = itemHeight.value[currentIndex.value];
290
+
291
+ const newTopPosition =
292
+ currentIndex.value > draggedIndex.value
293
+ ? draggedItemOffset - currentItemOffset
294
+ : draggedItemOffset -
295
+ currentItemOffset +
296
+ (draggedItemHeight - currentItemHeight);
297
+
298
+ if (dragY.value !== newTopPosition) {
299
+ // animate dragged item to its new position on release
300
+ dragY.value = withTiming(
301
+ newTopPosition,
302
+ {
303
+ duration: duration.value,
304
+ easing: Easing.out(Easing.ease),
305
+ },
306
+ () => {
307
+ runOnJS(reorder)(draggedIndex.value, currentIndex.value);
308
+ },
309
+ );
310
+ } else {
311
+ // user might drag and release the item without moving it so,
312
+ // since the animation end callback is not executed in that case
313
+ // we need to reset values as the reorder function would do
314
+ resetSharedValues();
315
+ }
316
+ }
317
+ },
318
+ );
319
+
320
+ useAnimatedReaction(
321
+ () => currentY.value,
322
+ y => {
323
+ if (
324
+ state.value === ReorderableListState.DRAGGING ||
325
+ state.value === ReorderableListState.AUTO_SCROLL
326
+ ) {
327
+ setCurrentIndex(y);
328
+
329
+ if (y <= topAutoscrollArea.value || y >= bottomAutoscrollArea.value) {
330
+ if (state.value !== ReorderableListState.AUTO_SCROLL) {
331
+ // trigger autoscroll
332
+ lastAutoscrollTrigger.value = autoscrollTrigger.value;
333
+ autoscrollTrigger.value *= -1;
334
+ }
335
+ state.value = ReorderableListState.AUTO_SCROLL;
336
+ } else {
337
+ state.value = ReorderableListState.DRAGGING;
338
+ }
339
+ }
340
+ },
341
+ );
342
+
343
+ useAnimatedReaction(
344
+ () => autoscrollTrigger.value,
345
+ () => {
346
+ if (
347
+ autoscrollTrigger.value !== lastAutoscrollTrigger.value &&
348
+ state.value === ReorderableListState.AUTO_SCROLL
349
+ ) {
350
+ const autoscrollIncrement =
351
+ (currentY.value <= topAutoscrollArea.value
352
+ ? -AUTOSCROLL_INCREMENT
353
+ : AUTOSCROLL_INCREMENT) * autoscrollSpeedScale;
354
+
355
+ if (autoscrollIncrement !== 0) {
356
+ scrollTo(
357
+ flatList as unknown as AnimatedRef<Animated.ScrollView>,
358
+ 0,
359
+ currentScrollOffsetY.value + autoscrollIncrement,
360
+ true,
361
+ );
362
+ }
363
+
364
+ // when autoscrolling user may not be moving his finger so we need
365
+ // to update the current position of the dragged item here
366
+ setCurrentIndex(currentY.value);
367
+ }
368
+ },
369
+ );
370
+
371
+ const handleScroll = useAnimatedScrollHandler(e => {
372
+ currentScrollOffsetY.value = e.contentOffset.y;
373
+
374
+ // checking if the list is not scrollable instead of the scrolling state
375
+ // fixes a bug on iOS where the item is shifted after autoscrolling and then
376
+ // moving await from autoscroll area
377
+ if (!scrollEnabled.value) {
378
+ dragScrollTranslationY.value =
379
+ currentScrollOffsetY.value - dragInitialScrollOffsetY.value;
380
+ }
381
+
382
+ if (state.value === ReorderableListState.AUTO_SCROLL) {
383
+ dragY.value = currentTranslationY.value + dragScrollTranslationY.value;
384
+
385
+ cancelAnimation(autoscrollTrigger);
386
+
387
+ lastAutoscrollTrigger.value = autoscrollTrigger.value;
388
+ autoscrollTrigger.value = withDelay(
389
+ autoscrollDelay,
390
+ withTiming(autoscrollTrigger.value * -1, {duration: 0}),
391
+ );
392
+ } else {
393
+ }
394
+
395
+ if (onScroll) {
396
+ onScroll(e);
397
+ }
398
+ });
399
+
400
+ const startDrag = useCallback(
401
+ (index: number) => {
402
+ 'worklet';
403
+
404
+ // allow new drag when item is completely released
405
+ if (state.value === ReorderableListState.IDLE) {
406
+ draggedHeight.value = itemHeight.value[index];
407
+ draggedIndex.value = index;
408
+ previousIndex.value = -1;
409
+ currentIndex.value = index;
410
+ state.value = ReorderableListState.DRAGGING;
411
+ dragInitialScrollOffsetY.value = currentScrollOffsetY.value;
412
+
413
+ runOnJS(setScrollEnabled)(false);
414
+ }
415
+ },
416
+ [
417
+ setScrollEnabled,
418
+ currentIndex,
419
+ previousIndex,
420
+ draggedHeight,
421
+ draggedIndex,
422
+ state,
423
+ currentScrollOffsetY,
424
+ dragInitialScrollOffsetY,
425
+ itemHeight,
426
+ ],
427
+ );
428
+
429
+ const measureFlatList = useCallback(() => {
430
+ 'worklet';
431
+
432
+ const measurement = measure(flatList);
433
+ if (measurement === null) {
434
+ return;
435
+ }
436
+
437
+ containerPositionY.value = measurement.pageY;
438
+ }, [flatList, containerPositionY]);
439
+
440
+ const handleFlatListLayout = useCallback(
441
+ (e: LayoutChangeEvent) => {
442
+ runOnUI(measureFlatList)();
443
+
444
+ const threshold = Math.max(0, Math.min(autoscrollThreshold, 0.4));
445
+ const {height} = e.nativeEvent.layout;
446
+
447
+ topAutoscrollArea.value = height * threshold;
448
+ bottomAutoscrollArea.value = height * (1 - threshold);
449
+
450
+ if (onLayout) {
451
+ onLayout(e);
452
+ }
453
+ },
454
+ [
455
+ measureFlatList,
456
+ topAutoscrollArea,
457
+ bottomAutoscrollArea,
458
+ autoscrollThreshold,
459
+ onLayout,
460
+ ],
461
+ );
462
+
463
+ const handleRef = (value: FlatList<T>) => {
464
+ flatList(value);
465
+
466
+ if (typeof ref === 'function') {
467
+ ref(value);
468
+ } else if (ref) {
469
+ ref.current = value;
470
+ }
471
+ };
472
+
473
+ return {
474
+ gestureHandler,
475
+ handleScroll,
476
+ handleFlatListLayout,
477
+ handleRef,
478
+ startDrag,
479
+ listContextValue,
480
+ itemOffset,
481
+ itemHeight,
482
+ draggedIndex,
483
+ releasedIndex,
484
+ dragY,
485
+ duration,
486
+ };
487
+ };
@@ -0,0 +1,143 @@
1
+ import React, {memo, useCallback, useMemo} from 'react';
2
+ import type {LayoutChangeEvent} from 'react-native';
3
+
4
+ import Animated, {
5
+ Easing,
6
+ SharedValue,
7
+ runOnUI,
8
+ useAnimatedReaction,
9
+ useAnimatedStyle,
10
+ useSharedValue,
11
+ withTiming,
12
+ } from 'react-native-reanimated';
13
+
14
+ import {ReorderableCellContext, ReorderableListContext} from '../contexts';
15
+ import {useContext} from '../hooks';
16
+
17
+ interface ReorderableListCellProps<T, U> {
18
+ index: number;
19
+ startDrag: (index: number) => void;
20
+ itemOffset: SharedValue<number[]>;
21
+ itemHeight: SharedValue<number[]>;
22
+ dragY: SharedValue<number>;
23
+ draggedIndex: SharedValue<number>;
24
+ releasedIndex: SharedValue<number>;
25
+ children: React.ReactNode;
26
+ onLayout?: (e: LayoutChangeEvent) => void;
27
+ // animation duration as a shared value allows to avoid re-renders on value change
28
+ animationDuration: SharedValue<number>;
29
+ item: T;
30
+ extraData: U;
31
+ }
32
+
33
+ export const ReorderableListCell = memo(
34
+ <T, U>({
35
+ index,
36
+ startDrag,
37
+ children,
38
+ onLayout,
39
+ itemOffset,
40
+ itemHeight,
41
+ dragY,
42
+ draggedIndex,
43
+ releasedIndex,
44
+ animationDuration,
45
+ }: ReorderableListCellProps<T, U>) => {
46
+ const dragHandler = useCallback(() => {
47
+ 'worklet';
48
+
49
+ startDrag(index);
50
+ }, [startDrag, index]);
51
+
52
+ const contextValue = useMemo(
53
+ () => ({
54
+ index,
55
+ dragHandler,
56
+ draggedIndex,
57
+ releasedIndex,
58
+ }),
59
+ [index, dragHandler, draggedIndex, releasedIndex],
60
+ );
61
+ const {currentIndex, draggedHeight} = useContext(ReorderableListContext);
62
+
63
+ const itemZIndex = useSharedValue(0);
64
+ const itemPositionY = useSharedValue(0);
65
+ const itemDragY = useSharedValue(0);
66
+ const itemIndex = useSharedValue(index);
67
+
68
+ useAnimatedReaction(
69
+ () => dragY.value,
70
+ () => {
71
+ if (
72
+ itemIndex.value === draggedIndex.value &&
73
+ currentIndex.value >= 0 &&
74
+ draggedIndex.value >= 0
75
+ ) {
76
+ itemDragY.value = dragY.value;
77
+ }
78
+ },
79
+ );
80
+
81
+ useAnimatedReaction(
82
+ () => currentIndex.value,
83
+ () => {
84
+ if (
85
+ itemIndex.value !== draggedIndex.value &&
86
+ currentIndex.value >= 0 &&
87
+ draggedIndex.value >= 0
88
+ ) {
89
+ const moveDown = currentIndex.value > draggedIndex.value;
90
+ const startMove = Math.min(draggedIndex.value, currentIndex.value);
91
+ const endMove = Math.max(draggedIndex.value, currentIndex.value);
92
+ let newValue = 0;
93
+
94
+ if (itemIndex.value >= startMove && itemIndex.value <= endMove) {
95
+ newValue = moveDown ? -draggedHeight.value : draggedHeight.value;
96
+ }
97
+
98
+ if (newValue !== itemPositionY.value) {
99
+ itemPositionY.value = withTiming(newValue, {
100
+ duration: animationDuration.value,
101
+ easing: Easing.out(Easing.ease),
102
+ });
103
+ }
104
+ }
105
+ },
106
+ );
107
+
108
+ useAnimatedReaction(
109
+ () => draggedIndex.value === index,
110
+ newValue => {
111
+ itemZIndex.value = newValue ? 999 : 0;
112
+ },
113
+ );
114
+
115
+ const animatedStyle = useAnimatedStyle(() => ({
116
+ zIndex: itemZIndex.value,
117
+ transform: [
118
+ {translateY: itemDragY.value},
119
+ {translateY: itemPositionY.value},
120
+ ],
121
+ }));
122
+
123
+ const handleLayout = (e: LayoutChangeEvent) => {
124
+ runOnUI((y: number, height: number) => {
125
+ itemOffset.value[index] = y;
126
+ itemHeight.value[index] = height;
127
+ })(e.nativeEvent.layout.y, e.nativeEvent.layout.height);
128
+
129
+ if (onLayout) {
130
+ onLayout(e);
131
+ }
132
+ };
133
+
134
+ return (
135
+ <ReorderableCellContext.Provider value={contextValue}>
136
+ <Animated.View style={animatedStyle} onLayout={handleLayout}>
137
+ {children}
138
+ </Animated.View>
139
+ </ReorderableCellContext.Provider>
140
+ );
141
+ },
142
+ (prev, next) => prev.item === next.item && prev.extraData === next.extraData,
143
+ );