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