react-native-reorderable-list 0.6.0 → 0.7.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 (133) hide show
  1. package/README.md +28 -3
  2. package/lib/commonjs/components/NestedReorderableList.js +41 -0
  3. package/lib/commonjs/components/NestedReorderableList.js.map +1 -0
  4. package/lib/commonjs/components/ReorderableList.js +29 -0
  5. package/lib/commonjs/components/ReorderableList.js.map +1 -0
  6. package/lib/commonjs/components/ReorderableListCell.js +3 -7
  7. package/lib/commonjs/components/ReorderableListCell.js.map +1 -1
  8. package/lib/commonjs/components/{ReorderableList/ReorderableList.js → ReorderableListCore/ReorderableListCore.js} +35 -12
  9. package/lib/commonjs/components/ReorderableListCore/ReorderableListCore.js.map +1 -0
  10. package/lib/commonjs/components/ReorderableListCore/constants.ios.js.map +1 -0
  11. package/lib/commonjs/components/ReorderableListCore/constants.js.map +1 -0
  12. package/lib/commonjs/components/ReorderableListCore/index.js +17 -0
  13. package/lib/commonjs/components/ReorderableListCore/index.js.map +1 -0
  14. package/lib/commonjs/components/{ReorderableList/useReorderableList.js → ReorderableListCore/useReorderableListCore.js} +182 -75
  15. package/lib/commonjs/components/ReorderableListCore/useReorderableListCore.js.map +1 -0
  16. package/lib/commonjs/components/ScrollViewContainer.js +53 -0
  17. package/lib/commonjs/components/ScrollViewContainer.js.map +1 -0
  18. package/lib/commonjs/components/index.js +22 -0
  19. package/lib/commonjs/components/index.js.map +1 -1
  20. package/lib/commonjs/contexts/ReorderableCellContext.js.map +1 -1
  21. package/lib/commonjs/contexts/ReorderableListContext.js.map +1 -1
  22. package/lib/commonjs/contexts/ScrollViewContainerContext.js +10 -0
  23. package/lib/commonjs/contexts/ScrollViewContainerContext.js.map +1 -0
  24. package/lib/commonjs/contexts/index.js +11 -0
  25. package/lib/commonjs/contexts/index.js.map +1 -1
  26. package/lib/commonjs/hooks/useReorderableDragEnd.js +23 -8
  27. package/lib/commonjs/hooks/useReorderableDragEnd.js.map +1 -1
  28. package/lib/commonjs/index.js +12 -0
  29. package/lib/commonjs/index.js.map +1 -1
  30. package/lib/module/components/NestedReorderableList.js +33 -0
  31. package/lib/module/components/NestedReorderableList.js.map +1 -0
  32. package/lib/module/components/ReorderableList.js +21 -0
  33. package/lib/module/components/ReorderableList.js.map +1 -0
  34. package/lib/module/components/ReorderableListCell.js +3 -7
  35. package/lib/module/components/ReorderableListCell.js.map +1 -1
  36. package/lib/module/components/{ReorderableList/ReorderableList.js → ReorderableListCore/ReorderableListCore.js} +38 -15
  37. package/lib/module/components/ReorderableListCore/ReorderableListCore.js.map +1 -0
  38. package/lib/module/components/ReorderableListCore/constants.ios.js.map +1 -0
  39. package/lib/module/components/{ReorderableList → ReorderableListCore}/constants.js.map +1 -1
  40. package/lib/module/components/ReorderableListCore/index.js +2 -0
  41. package/lib/module/components/ReorderableListCore/index.js.map +1 -0
  42. package/lib/module/components/{ReorderableList/useReorderableList.js → ReorderableListCore/useReorderableListCore.js} +181 -74
  43. package/lib/module/components/ReorderableListCore/useReorderableListCore.js.map +1 -0
  44. package/lib/module/components/ScrollViewContainer.js +44 -0
  45. package/lib/module/components/ScrollViewContainer.js.map +1 -0
  46. package/lib/module/components/index.js +2 -0
  47. package/lib/module/components/index.js.map +1 -1
  48. package/lib/module/contexts/ReorderableCellContext.js.map +1 -1
  49. package/lib/module/contexts/ReorderableListContext.js.map +1 -1
  50. package/lib/module/contexts/ScrollViewContainerContext.js +3 -0
  51. package/lib/module/contexts/ScrollViewContainerContext.js.map +1 -0
  52. package/lib/module/contexts/index.js +1 -0
  53. package/lib/module/contexts/index.js.map +1 -1
  54. package/lib/module/hooks/useReorderableDragEnd.js +23 -8
  55. package/lib/module/hooks/useReorderableDragEnd.js.map +1 -1
  56. package/lib/module/index.js +2 -2
  57. package/lib/module/index.js.map +1 -1
  58. package/lib/typescript/components/NestedReorderableList.d.ts +5 -0
  59. package/lib/typescript/components/NestedReorderableList.d.ts.map +1 -0
  60. package/lib/typescript/components/ReorderableList.d.ts +5 -0
  61. package/lib/typescript/components/ReorderableList.d.ts.map +1 -0
  62. package/lib/typescript/components/ReorderableListCell.d.ts +1 -2
  63. package/lib/typescript/components/ReorderableListCell.d.ts.map +1 -1
  64. package/lib/typescript/components/ReorderableListCore/ReorderableListCore.d.ts +20 -0
  65. package/lib/typescript/components/ReorderableListCore/ReorderableListCore.d.ts.map +1 -0
  66. package/lib/typescript/components/ReorderableListCore/constants.d.ts.map +1 -0
  67. package/lib/typescript/components/ReorderableListCore/constants.ios.d.ts.map +1 -0
  68. package/lib/typescript/components/ReorderableListCore/index.d.ts +2 -0
  69. package/lib/typescript/components/ReorderableListCore/index.d.ts.map +1 -0
  70. package/lib/typescript/components/ReorderableListCore/useReorderableListCore.d.ts +44 -0
  71. package/lib/typescript/components/ReorderableListCore/useReorderableListCore.d.ts.map +1 -0
  72. package/lib/typescript/components/ScrollViewContainer.d.ts +4 -0
  73. package/lib/typescript/components/ScrollViewContainer.d.ts.map +1 -0
  74. package/lib/typescript/components/index.d.ts +2 -0
  75. package/lib/typescript/components/index.d.ts.map +1 -1
  76. package/lib/typescript/contexts/ReorderableCellContext.d.ts +0 -1
  77. package/lib/typescript/contexts/ReorderableCellContext.d.ts.map +1 -1
  78. package/lib/typescript/contexts/ReorderableListContext.d.ts +1 -0
  79. package/lib/typescript/contexts/ReorderableListContext.d.ts.map +1 -1
  80. package/lib/typescript/contexts/ScrollViewContainerContext.d.ts +15 -0
  81. package/lib/typescript/contexts/ScrollViewContainerContext.d.ts.map +1 -0
  82. package/lib/typescript/contexts/index.d.ts +1 -0
  83. package/lib/typescript/contexts/index.d.ts.map +1 -1
  84. package/lib/typescript/hooks/useReorderableDragEnd.d.ts.map +1 -1
  85. package/lib/typescript/index.d.ts +3 -3
  86. package/lib/typescript/index.d.ts.map +1 -1
  87. package/lib/typescript/types/props.d.ts +13 -1
  88. package/lib/typescript/types/props.d.ts.map +1 -1
  89. package/package.json +1 -1
  90. package/src/components/NestedReorderableList.tsx +43 -0
  91. package/src/components/ReorderableList.tsx +29 -0
  92. package/src/components/ReorderableListCell.tsx +2 -7
  93. package/src/components/ReorderableListCore/ReorderableListCore.tsx +162 -0
  94. package/src/components/ReorderableListCore/index.ts +1 -0
  95. package/src/components/{ReorderableList/useReorderableList.ts → ReorderableListCore/useReorderableListCore.ts} +264 -85
  96. package/src/components/ScrollViewContainer.tsx +74 -0
  97. package/src/components/index.ts +2 -0
  98. package/src/contexts/ReorderableCellContext.ts +0 -1
  99. package/src/contexts/ReorderableListContext.ts +1 -0
  100. package/src/contexts/ScrollViewContainerContext.ts +18 -0
  101. package/src/contexts/index.ts +1 -0
  102. package/src/hooks/useReorderableDragEnd.ts +28 -12
  103. package/src/index.ts +10 -1
  104. package/src/types/props.ts +21 -1
  105. package/lib/commonjs/components/ReorderableList/ReorderableList.js.map +0 -1
  106. package/lib/commonjs/components/ReorderableList/constants.ios.js.map +0 -1
  107. package/lib/commonjs/components/ReorderableList/constants.js.map +0 -1
  108. package/lib/commonjs/components/ReorderableList/index.js +0 -17
  109. package/lib/commonjs/components/ReorderableList/index.js.map +0 -1
  110. package/lib/commonjs/components/ReorderableList/useReorderableList.js.map +0 -1
  111. package/lib/module/components/ReorderableList/ReorderableList.js.map +0 -1
  112. package/lib/module/components/ReorderableList/constants.ios.js.map +0 -1
  113. package/lib/module/components/ReorderableList/index.js +0 -2
  114. package/lib/module/components/ReorderableList/index.js.map +0 -1
  115. package/lib/module/components/ReorderableList/useReorderableList.js.map +0 -1
  116. package/lib/typescript/components/ReorderableList/ReorderableList.d.ts +0 -8
  117. package/lib/typescript/components/ReorderableList/ReorderableList.d.ts.map +0 -1
  118. package/lib/typescript/components/ReorderableList/constants.d.ts.map +0 -1
  119. package/lib/typescript/components/ReorderableList/constants.ios.d.ts.map +0 -1
  120. package/lib/typescript/components/ReorderableList/index.d.ts +0 -2
  121. package/lib/typescript/components/ReorderableList/index.d.ts.map +0 -1
  122. package/lib/typescript/components/ReorderableList/useReorderableList.d.ts +0 -36
  123. package/lib/typescript/components/ReorderableList/useReorderableList.d.ts.map +0 -1
  124. package/src/components/ReorderableList/ReorderableList.tsx +0 -119
  125. package/src/components/ReorderableList/index.ts +0 -1
  126. /package/lib/commonjs/components/{ReorderableList → ReorderableListCore}/constants.ios.js +0 -0
  127. /package/lib/commonjs/components/{ReorderableList → ReorderableListCore}/constants.js +0 -0
  128. /package/lib/module/components/{ReorderableList → ReorderableListCore}/constants.ios.js +0 -0
  129. /package/lib/module/components/{ReorderableList → ReorderableListCore}/constants.js +0 -0
  130. /package/lib/typescript/components/{ReorderableList → ReorderableListCore}/constants.d.ts +0 -0
  131. /package/lib/typescript/components/{ReorderableList → ReorderableListCore}/constants.ios.d.ts +0 -0
  132. /package/src/components/{ReorderableList → ReorderableListCore}/constants.ios.ts +0 -0
  133. /package/src/components/{ReorderableList → ReorderableListCore}/constants.ts +0 -0
@@ -3,6 +3,7 @@ import {
3
3
  FlatList,
4
4
  LayoutChangeEvent,
5
5
  NativeScrollEvent,
6
+ ScrollView,
6
7
  unstable_batchedUpdates,
7
8
  } from 'react-native';
8
9
 
@@ -10,8 +11,8 @@ import {Gesture, State} from 'react-native-gesture-handler';
10
11
  import Animated, {
11
12
  AnimatedRef,
12
13
  Easing,
14
+ SharedValue,
13
15
  cancelAnimation,
14
- measure,
15
16
  runOnJS,
16
17
  runOnUI,
17
18
  scrollTo,
@@ -32,7 +33,7 @@ const hasAutomaticBatching = version.length
32
33
  ? parseInt(version[0], 10) >= 18
33
34
  : false;
34
35
 
35
- interface UseReorderableListArgs<T> {
36
+ interface UseReorderableListCoreArgs<T> {
36
37
  ref: React.ForwardedRef<FlatList<T>>;
37
38
  autoscrollThreshold: number;
38
39
  autoscrollSpeedScale: number;
@@ -43,9 +44,16 @@ interface UseReorderableListArgs<T> {
43
44
  onDragEnd?: (event: ReorderableListDragEndEvent) => void;
44
45
  onScroll?: (event: NativeScrollEvent) => void;
45
46
  onLayout?: (event: LayoutChangeEvent) => void;
47
+ scrollViewContainerRef: React.RefObject<ScrollView> | undefined;
48
+ scrollViewHeightY: SharedValue<number> | undefined;
49
+ scrollViewScrollOffsetY: SharedValue<number> | undefined;
50
+ scrollViewScrollEnabled: SharedValue<boolean> | undefined;
51
+ initialScrollEnabled: boolean | undefined;
52
+ initialScrollViewScrollEnabled: boolean | undefined;
53
+ nestedScrollable: boolean | undefined;
46
54
  }
47
55
 
48
- export const useReorderableList = <T>({
56
+ export const useReorderableListCore = <T>({
49
57
  ref,
50
58
  autoscrollThreshold,
51
59
  autoscrollSpeedScale,
@@ -56,21 +64,29 @@ export const useReorderableList = <T>({
56
64
  onDragEnd,
57
65
  onScroll,
58
66
  onLayout,
59
- }: UseReorderableListArgs<T>) => {
60
- const scrollEnabled = useSharedValue(true);
61
- const flatList = useAnimatedRef<FlatList>();
67
+ scrollViewContainerRef,
68
+ scrollViewHeightY,
69
+ scrollViewScrollOffsetY,
70
+ scrollViewScrollEnabled,
71
+ initialScrollEnabled,
72
+ initialScrollViewScrollEnabled,
73
+ nestedScrollable,
74
+ }: UseReorderableListCoreArgs<T>) => {
75
+ const flatListRef = useAnimatedRef<FlatList>();
76
+ const scrollEnabled = useSharedValue(initialScrollEnabled);
62
77
  const gestureState = useSharedValue<State>(State.UNDETERMINED);
63
78
  const currentY = useSharedValue(0);
64
79
  const currentTranslationY = useSharedValue(0);
65
- const containerPositionY = useSharedValue(0);
66
- const currentScrollOffsetY = useSharedValue(0);
80
+ const flatListScrollOffsetY = useSharedValue(0);
81
+ const flatListHeightY = useSharedValue(0);
82
+ const nestedFlatListPositionY = useSharedValue(0);
67
83
  const dragScrollTranslationY = useSharedValue(0);
68
84
  const dragInitialScrollOffsetY = useSharedValue(0);
85
+ const scrollViewDragScrollTranslationY = useSharedValue(0);
86
+ const scrollViewDragInitialScrollOffsetY = useSharedValue(0);
69
87
  const draggedHeight = useSharedValue(0);
70
88
  const itemOffset = useSharedValue<number[]>([]);
71
89
  const itemHeight = useSharedValue<number[]>([]);
72
- const topAutoscrollArea = useSharedValue(0);
73
- const bottomAutoscrollArea = useSharedValue(0);
74
90
  const autoscrollTrigger = useSharedValue(-1);
75
91
  const lastAutoscrollTrigger = useSharedValue(-1);
76
92
  const previousY = useSharedValue(0);
@@ -79,8 +95,10 @@ export const useReorderableList = <T>({
79
95
  const previousIndex = useSharedValue(-1);
80
96
  const currentIndex = useSharedValue(-1);
81
97
  const draggedIndex = useSharedValue(-1);
82
- const releasedIndex = useSharedValue(-1);
83
98
  const state = useSharedValue<ReorderableListState>(ReorderableListState.IDLE);
99
+ const dragEndHandlers = useSharedValue<
100
+ ((from: number, to: number) => void)[][]
101
+ >([]);
84
102
 
85
103
  // animation duration as a shared value allows to avoid re-rendering of all cells on value change
86
104
  const duration = useSharedValue(animationDuration);
@@ -93,8 +111,9 @@ export const useReorderableList = <T>({
93
111
  draggedHeight,
94
112
  currentIndex,
95
113
  draggedIndex,
114
+ dragEndHandlers,
96
115
  }),
97
- [draggedHeight, currentIndex, draggedIndex],
116
+ [draggedHeight, currentIndex, draggedIndex, dragEndHandlers],
98
117
  );
99
118
 
100
119
  const startY = useSharedValue(0);
@@ -105,10 +124,8 @@ export const useReorderableList = <T>({
105
124
  .onBegin(e => {
106
125
  // prevent new dragging until item is completely released
107
126
  if (state.value === ReorderableListState.IDLE) {
108
- const relativeY = e.absoluteY - containerPositionY.value;
109
-
110
- startY.value = relativeY;
111
- currentY.value = relativeY;
127
+ startY.value = e.y;
128
+ currentY.value = e.y;
112
129
  currentTranslationY.value = e.translationY;
113
130
  if (draggedIndex.value >= 0) {
114
131
  dragY.value = e.translationY;
@@ -121,7 +138,10 @@ export const useReorderableList = <T>({
121
138
  currentY.value = startY.value + e.translationY;
122
139
  currentTranslationY.value = e.translationY;
123
140
  if (draggedIndex.value >= 0) {
124
- dragY.value = e.translationY + dragScrollTranslationY.value;
141
+ dragY.value =
142
+ e.translationY +
143
+ dragScrollTranslationY.value +
144
+ scrollViewDragScrollTranslationY.value;
125
145
  }
126
146
  gestureState.value = e.state;
127
147
  }
@@ -129,10 +149,10 @@ export const useReorderableList = <T>({
129
149
  .onEnd(e => (gestureState.value = e.state))
130
150
  .onFinalize(e => (gestureState.value = e.state)),
131
151
  [
132
- containerPositionY,
133
152
  currentTranslationY,
134
153
  currentY,
135
154
  dragScrollTranslationY,
155
+ scrollViewDragScrollTranslationY,
136
156
  draggedIndex,
137
157
  gestureState,
138
158
  dragY,
@@ -148,27 +168,50 @@ export const useReorderableList = <T>({
148
168
 
149
169
  const setScrollEnabled = useCallback(
150
170
  (enabled: boolean) => {
151
- scrollEnabled.value = enabled;
152
- flatList.current?.setNativeProps({scrollEnabled: enabled});
171
+ // if scroll is enabled on list props then we can toggle it
172
+ if (initialScrollEnabled) {
173
+ scrollEnabled.value = enabled;
174
+ flatListRef.current?.setNativeProps({scrollEnabled: enabled});
175
+ }
176
+
177
+ if (
178
+ scrollViewContainerRef &&
179
+ scrollViewScrollEnabled &&
180
+ initialScrollViewScrollEnabled
181
+ ) {
182
+ scrollViewScrollEnabled.value = enabled;
183
+ scrollViewContainerRef.current?.setNativeProps({
184
+ scrollEnabled: enabled,
185
+ });
186
+ }
153
187
  },
154
- [flatList, scrollEnabled],
188
+ [
189
+ initialScrollEnabled,
190
+ flatListRef,
191
+ scrollEnabled,
192
+ initialScrollViewScrollEnabled,
193
+ scrollViewContainerRef,
194
+ scrollViewScrollEnabled,
195
+ ],
155
196
  );
156
197
 
157
198
  const resetSharedValues = useCallback(() => {
158
199
  'worklet';
159
200
 
160
- // must be reset before the reorder function is called
161
- // to avoid triggering on drag end event twice,
162
- // update with a delay to avoid reanimated batching the
163
- // change if updated immediately
164
- releasedIndex.value = withDelay(1, withTiming(-1, {duration: 0}));
165
- draggedIndex.value = -1;
166
201
  // current index is reset on item render for the on end event
167
- dragY.value = 0;
202
+ draggedIndex.value = -1;
168
203
  // released flag is reset after release is triggered in the item
169
204
  state.value = ReorderableListState.IDLE;
205
+ dragY.value = 0;
170
206
  dragScrollTranslationY.value = 0;
171
- }, [releasedIndex, draggedIndex, dragY, state, dragScrollTranslationY]);
207
+ scrollViewDragScrollTranslationY.value = 0;
208
+ }, [
209
+ dragY,
210
+ dragScrollTranslationY,
211
+ scrollViewDragScrollTranslationY,
212
+ draggedIndex,
213
+ state,
214
+ ]);
172
215
 
173
216
  const reorder = (fromIndex: number, toIndex: number) => {
174
217
  runOnUI(resetSharedValues)();
@@ -190,7 +233,7 @@ export const useReorderableList = <T>({
190
233
  (y: number) => {
191
234
  'worklet';
192
235
 
193
- const relativeY = currentScrollOffsetY.value + y;
236
+ const relativeY = flatListScrollOffsetY.value + y;
194
237
  const count = itemOffset.value.length;
195
238
 
196
239
  for (let i = 0; i < count; i++) {
@@ -220,7 +263,7 @@ export const useReorderableList = <T>({
220
263
  [
221
264
  dragReorderThreshold,
222
265
  currentIndex,
223
- currentScrollOffsetY,
266
+ flatListScrollOffsetY,
224
267
  previousDirection,
225
268
  itemOffset,
226
269
  itemHeight,
@@ -283,12 +326,17 @@ export const useReorderableList = <T>({
283
326
  state.value === ReorderableListState.AUTO_SCROLL)
284
327
  ) {
285
328
  state.value = ReorderableListState.RELEASING;
286
- releasedIndex.value = draggedIndex.value;
287
329
 
288
330
  // enable back scroll on releasing
289
331
  runOnJS(setScrollEnabled)(true);
290
332
  // trigger onDragEnd event
291
- onDragEnd?.({from: draggedIndex.value, to: currentIndex.value});
333
+ let e = {from: draggedIndex.value, to: currentIndex.value};
334
+ onDragEnd?.(e);
335
+
336
+ const handlers = dragEndHandlers.value[draggedIndex.value];
337
+ if (Array.isArray(handlers)) {
338
+ handlers.forEach(fn => fn(e.from, e.to));
339
+ }
292
340
 
293
341
  // they are actually swapped on drag translation
294
342
  const currentItemOffset = itemOffset.value[draggedIndex.value];
@@ -325,8 +373,119 @@ export const useReorderableList = <T>({
325
373
  },
326
374
  );
327
375
 
376
+ const calculateHiddenArea = useCallback(() => {
377
+ 'worklet';
378
+ if (!scrollViewScrollOffsetY || !scrollViewHeightY) {
379
+ return {top: 0, bottom: 0};
380
+ }
381
+
382
+ // hidden area cannot be negative
383
+ const top = Math.max(
384
+ 0,
385
+ scrollViewScrollOffsetY.value - nestedFlatListPositionY.value,
386
+ );
387
+ const bottom = Math.max(
388
+ 0,
389
+ nestedFlatListPositionY.value +
390
+ flatListHeightY.value -
391
+ (scrollViewScrollOffsetY.value + scrollViewHeightY.value),
392
+ );
393
+
394
+ return {top, bottom};
395
+ }, [
396
+ scrollViewScrollOffsetY,
397
+ scrollViewHeightY,
398
+ nestedFlatListPositionY,
399
+ flatListHeightY,
400
+ ]);
401
+
402
+ const calculateThresholdArea = useCallback(
403
+ (hiddenArea: {top: number; bottom: number}) => {
404
+ 'worklet';
405
+ const threshold = Math.max(0, Math.min(autoscrollThreshold, 0.4));
406
+ const visibleHeight =
407
+ flatListHeightY.value - (hiddenArea.top + hiddenArea.bottom);
408
+
409
+ const top = visibleHeight * threshold;
410
+ const bottom = flatListHeightY.value - top;
411
+
412
+ return {top, bottom};
413
+ },
414
+ [autoscrollThreshold, flatListHeightY],
415
+ );
416
+
417
+ const calculateThresholdAreaParent = useCallback(
418
+ (hiddenArea: {top: number; bottom: number}) => {
419
+ 'worklet';
420
+ const threshold = Math.max(0, Math.min(autoscrollThreshold, 0.4));
421
+ const top = flatListHeightY.value * threshold;
422
+ const bottom = flatListHeightY.value - top;
423
+
424
+ // if the hidden area is 0 then we don't have a threshold area
425
+ // we might have floating errors like 0.0001 which we should ignore
426
+ return {
427
+ top: hiddenArea.top > 0.1 ? top + hiddenArea.top : 0,
428
+ bottom: hiddenArea.bottom > 0.1 ? bottom - hiddenArea.bottom : 0,
429
+ };
430
+ },
431
+ [autoscrollThreshold, flatListHeightY],
432
+ );
433
+
434
+ const shouldScrollParent = useCallback(
435
+ (y: number) => {
436
+ 'worklet';
437
+ const hiddenArea = calculateHiddenArea();
438
+ const thresholdAreaParent = calculateThresholdAreaParent(hiddenArea);
439
+
440
+ // we might have floating errors like 0.0001 which we should ignore
441
+ return (
442
+ (hiddenArea.top > 0.1 && y <= thresholdAreaParent.top) ||
443
+ (hiddenArea.bottom > 0.1 && y >= thresholdAreaParent.bottom)
444
+ );
445
+ },
446
+ [calculateHiddenArea, calculateThresholdAreaParent],
447
+ );
448
+
449
+ const scrollDirection = useCallback(
450
+ (y: number) => {
451
+ 'worklet';
452
+ const hiddenArea = calculateHiddenArea();
453
+
454
+ if (shouldScrollParent(y)) {
455
+ const thresholdAreaParent = calculateThresholdAreaParent(hiddenArea);
456
+ if (y <= thresholdAreaParent.top) {
457
+ return -1;
458
+ }
459
+
460
+ if (y >= thresholdAreaParent.bottom) {
461
+ return 1;
462
+ }
463
+
464
+ return 0;
465
+ } else if (nestedScrollable) {
466
+ const thresholdArea = calculateThresholdArea(hiddenArea);
467
+ if (y <= thresholdArea.top) {
468
+ return -1;
469
+ }
470
+
471
+ if (y >= thresholdArea.bottom) {
472
+ return 1;
473
+ }
474
+ }
475
+
476
+ return 0;
477
+ },
478
+ [
479
+ nestedScrollable,
480
+ shouldScrollParent,
481
+ calculateHiddenArea,
482
+ calculateThresholdArea,
483
+ calculateThresholdAreaParent,
484
+ ],
485
+ );
486
+
328
487
  useAnimatedReaction(
329
- () => currentY.value,
488
+ () => currentY.value + scrollViewDragScrollTranslationY.value,
330
489
  y => {
331
490
  if (
332
491
  state.value === ReorderableListState.DRAGGING ||
@@ -334,7 +493,7 @@ export const useReorderableList = <T>({
334
493
  ) {
335
494
  setCurrentIndex(y);
336
495
 
337
- if (y <= topAutoscrollArea.value || y >= bottomAutoscrollArea.value) {
496
+ if (scrollDirection(y)) {
338
497
  if (state.value !== ReorderableListState.AUTO_SCROLL) {
339
498
  // trigger autoscroll
340
499
  lastAutoscrollTrigger.value = autoscrollTrigger.value;
@@ -355,40 +514,48 @@ export const useReorderableList = <T>({
355
514
  autoscrollTrigger.value !== lastAutoscrollTrigger.value &&
356
515
  state.value === ReorderableListState.AUTO_SCROLL
357
516
  ) {
517
+ let y = currentY.value + scrollViewDragScrollTranslationY.value;
358
518
  const autoscrollIncrement =
359
- (currentY.value <= topAutoscrollArea.value
360
- ? -AUTOSCROLL_INCREMENT
361
- : AUTOSCROLL_INCREMENT) * autoscrollSpeedScale;
519
+ scrollDirection(y) * AUTOSCROLL_INCREMENT * autoscrollSpeedScale;
362
520
 
363
521
  if (autoscrollIncrement !== 0) {
364
- scrollTo(
365
- flatList as unknown as AnimatedRef<Animated.ScrollView>,
366
- 0,
367
- currentScrollOffsetY.value + autoscrollIncrement,
368
- true,
369
- );
522
+ let scrollOffset = flatListScrollOffsetY.value;
523
+ let listRef =
524
+ flatListRef as unknown as AnimatedRef<Animated.ScrollView>;
525
+
526
+ if (shouldScrollParent(y) && scrollViewScrollOffsetY) {
527
+ scrollOffset = scrollViewScrollOffsetY.value;
528
+ listRef =
529
+ scrollViewContainerRef as unknown as AnimatedRef<Animated.ScrollView>;
530
+ }
531
+
532
+ scrollTo(listRef, 0, scrollOffset + autoscrollIncrement, true);
370
533
  }
371
534
 
372
535
  // when autoscrolling user may not be moving his finger so we need
373
536
  // to update the current position of the dragged item here
374
- setCurrentIndex(currentY.value);
537
+ setCurrentIndex(y);
375
538
  }
376
539
  },
377
540
  );
378
541
 
542
+ // flatlist scroll handler
379
543
  const handleScroll = useAnimatedScrollHandler(e => {
380
- currentScrollOffsetY.value = e.contentOffset.y;
544
+ flatListScrollOffsetY.value = e.contentOffset.y;
381
545
 
382
546
  // checking if the list is not scrollable instead of the scrolling state
383
547
  // fixes a bug on iOS where the item is shifted after autoscrolling and then
384
548
  // moving await from autoscroll area
385
549
  if (!scrollEnabled.value) {
386
550
  dragScrollTranslationY.value =
387
- currentScrollOffsetY.value - dragInitialScrollOffsetY.value;
551
+ flatListScrollOffsetY.value - dragInitialScrollOffsetY.value;
388
552
  }
389
553
 
390
554
  if (state.value === ReorderableListState.AUTO_SCROLL) {
391
- dragY.value = currentTranslationY.value + dragScrollTranslationY.value;
555
+ dragY.value =
556
+ currentTranslationY.value +
557
+ dragScrollTranslationY.value +
558
+ scrollViewDragScrollTranslationY.value;
392
559
 
393
560
  cancelAnimation(autoscrollTrigger);
394
561
 
@@ -397,79 +564,92 @@ export const useReorderableList = <T>({
397
564
  autoscrollDelay,
398
565
  withTiming(autoscrollTrigger.value * -1, {duration: 0}),
399
566
  );
400
- } else {
401
567
  }
402
568
 
403
- if (onScroll) {
404
- onScroll(e);
405
- }
569
+ onScroll?.(e);
406
570
  });
407
571
 
572
+ // parent scroll handler
573
+ useAnimatedReaction(
574
+ () => scrollViewScrollOffsetY?.value,
575
+ value => {
576
+ if (value && scrollViewScrollEnabled) {
577
+ // checking if the list is not scrollable instead of the scrolling state
578
+ // fixes a bug on iOS where the item is shifted after autoscrolling and then
579
+ // moving await from autoscroll area
580
+ if (!scrollViewScrollEnabled.value) {
581
+ scrollViewDragScrollTranslationY.value =
582
+ value - scrollViewDragInitialScrollOffsetY.value;
583
+ }
584
+
585
+ if (state.value === ReorderableListState.AUTO_SCROLL) {
586
+ dragY.value =
587
+ currentTranslationY.value + scrollViewDragScrollTranslationY.value;
588
+
589
+ cancelAnimation(autoscrollTrigger);
590
+
591
+ lastAutoscrollTrigger.value = autoscrollTrigger.value;
592
+ autoscrollTrigger.value = withDelay(
593
+ autoscrollDelay,
594
+ withTiming(autoscrollTrigger.value * -1, {duration: 0}),
595
+ );
596
+ }
597
+ }
598
+ },
599
+ );
600
+
408
601
  const startDrag = useCallback(
409
602
  (index: number) => {
410
603
  'worklet';
411
604
 
412
605
  // allow new drag when item is completely released
413
606
  if (state.value === ReorderableListState.IDLE) {
607
+ // resetting shared values again fixes a flickeing bug in nested lists where
608
+ // after scrolling the parent list it would offset the new dragged item in another nested list
609
+ resetSharedValues();
610
+
611
+ dragInitialScrollOffsetY.value = flatListScrollOffsetY.value;
612
+ scrollViewDragInitialScrollOffsetY.value = scrollViewScrollOffsetY
613
+ ? scrollViewScrollOffsetY.value
614
+ : 0;
615
+
414
616
  draggedHeight.value = itemHeight.value[index];
415
617
  draggedIndex.value = index;
416
618
  previousIndex.value = -1;
417
619
  currentIndex.value = index;
418
620
  state.value = ReorderableListState.DRAGGING;
419
- dragInitialScrollOffsetY.value = currentScrollOffsetY.value;
420
621
 
421
622
  runOnJS(setScrollEnabled)(false);
422
623
  }
423
624
  },
424
625
  [
626
+ resetSharedValues,
627
+ dragInitialScrollOffsetY,
628
+ scrollViewScrollOffsetY,
629
+ scrollViewDragInitialScrollOffsetY,
425
630
  setScrollEnabled,
426
631
  currentIndex,
427
632
  previousIndex,
428
633
  draggedHeight,
429
634
  draggedIndex,
430
635
  state,
431
- currentScrollOffsetY,
432
- dragInitialScrollOffsetY,
636
+ flatListScrollOffsetY,
433
637
  itemHeight,
434
638
  ],
435
639
  );
436
640
 
437
- const measureFlatList = useCallback(() => {
438
- 'worklet';
439
-
440
- const measurement = measure(flatList);
441
- if (measurement === null) {
442
- return;
443
- }
444
-
445
- containerPositionY.value = measurement.pageY;
446
- }, [flatList, containerPositionY]);
447
-
448
641
  const handleFlatListLayout = useCallback(
449
642
  (e: LayoutChangeEvent) => {
450
- runOnUI(measureFlatList)();
643
+ nestedFlatListPositionY.value = e.nativeEvent.layout.y;
644
+ flatListHeightY.value = e.nativeEvent.layout.height;
451
645
 
452
- const threshold = Math.max(0, Math.min(autoscrollThreshold, 0.4));
453
- const {height} = e.nativeEvent.layout;
454
-
455
- topAutoscrollArea.value = height * threshold;
456
- bottomAutoscrollArea.value = height * (1 - threshold);
457
-
458
- if (onLayout) {
459
- onLayout(e);
460
- }
646
+ onLayout?.(e);
461
647
  },
462
- [
463
- measureFlatList,
464
- topAutoscrollArea,
465
- bottomAutoscrollArea,
466
- autoscrollThreshold,
467
- onLayout,
468
- ],
648
+ [nestedFlatListPositionY, flatListHeightY, onLayout],
469
649
  );
470
650
 
471
651
  const handleRef = (value: FlatList<T>) => {
472
- flatList(value);
652
+ flatListRef(value);
473
653
 
474
654
  if (typeof ref === 'function') {
475
655
  ref(value);
@@ -488,7 +668,6 @@ export const useReorderableList = <T>({
488
668
  itemOffset,
489
669
  itemHeight,
490
670
  draggedIndex,
491
- releasedIndex,
492
671
  dragY,
493
672
  duration,
494
673
  };
@@ -0,0 +1,74 @@
1
+ import React, {useMemo} from 'react';
2
+ import {LayoutChangeEvent} from 'react-native';
3
+
4
+ import {Gesture, GestureDetector} from 'react-native-gesture-handler';
5
+ import Animated, {
6
+ useAnimatedRef,
7
+ useAnimatedScrollHandler,
8
+ useSharedValue,
9
+ } from 'react-native-reanimated';
10
+
11
+ import {ScrollViewContainerContext} from '../contexts/ScrollViewContainerContext';
12
+ import type {ScrollViewContainerProps} from '../types';
13
+
14
+ export const ScrollViewContainer: React.FC<ScrollViewContainerProps> = ({
15
+ onLayout,
16
+ onScroll,
17
+ scrollEnabled = true,
18
+ ...rest
19
+ }) => {
20
+ const scrollViewScrollEnabled = useSharedValue(scrollEnabled);
21
+ const scrollViewContainerRef = useAnimatedRef<Animated.ScrollView>();
22
+ const scrollViewScrollOffsetY = useSharedValue(0);
23
+ const scrollViewHeightY = useSharedValue(0);
24
+
25
+ const outerScrollGesture = useMemo(() => Gesture.Native(), []);
26
+
27
+ const handleScroll = useAnimatedScrollHandler(
28
+ e => {
29
+ scrollViewScrollOffsetY.value = e.contentOffset.y;
30
+
31
+ onScroll?.(e);
32
+ },
33
+ [scrollViewScrollOffsetY],
34
+ );
35
+
36
+ const contextValue = useMemo(
37
+ () => ({
38
+ scrollViewContainerRef,
39
+ scrollViewHeightY,
40
+ scrollViewScrollOffsetY,
41
+ scrollViewScrollEnabled,
42
+ outerScrollGesture,
43
+ initialScrollViewScrollEnabled: scrollEnabled,
44
+ }),
45
+ [
46
+ scrollViewContainerRef,
47
+ scrollViewHeightY,
48
+ scrollViewScrollOffsetY,
49
+ scrollViewScrollEnabled,
50
+ outerScrollGesture,
51
+ scrollEnabled,
52
+ ],
53
+ );
54
+
55
+ const handleLayout = (e: LayoutChangeEvent) => {
56
+ scrollViewHeightY.value = e.nativeEvent.layout.height;
57
+
58
+ onLayout?.(e);
59
+ };
60
+
61
+ return (
62
+ <ScrollViewContainerContext.Provider value={contextValue}>
63
+ <GestureDetector gesture={outerScrollGesture}>
64
+ <Animated.ScrollView
65
+ {...rest}
66
+ ref={scrollViewContainerRef}
67
+ onScroll={handleScroll}
68
+ onLayout={handleLayout}
69
+ scrollEnabled={scrollEnabled}
70
+ />
71
+ </GestureDetector>
72
+ </ScrollViewContainerContext.Provider>
73
+ );
74
+ };
@@ -1,3 +1,5 @@
1
1
  export * from './ReorderableList';
2
+ export * from './NestedReorderableList';
2
3
  export * from './ReorderableListCell';
3
4
  export * from './ReorderableListItem';
5
+ export * from './ScrollViewContainer';
@@ -6,7 +6,6 @@ interface ReorderableCellContextData {
6
6
  index: number;
7
7
  dragHandler: () => void;
8
8
  draggedIndex: SharedValue<number>;
9
- releasedIndex: SharedValue<number>;
10
9
  }
11
10
 
12
11
  export const ReorderableCellContext = React.createContext<
@@ -5,6 +5,7 @@ import type {SharedValue} from 'react-native-reanimated';
5
5
  interface ReorderableListContextData {
6
6
  currentIndex: SharedValue<number>;
7
7
  draggedHeight: SharedValue<number>;
8
+ dragEndHandlers: SharedValue<((from: number, to: number) => void)[][]>;
8
9
  }
9
10
 
10
11
  export const ReorderableListContext = React.createContext<
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import {ScrollView} from 'react-native';
3
+
4
+ import {NativeGesture} from 'react-native-gesture-handler';
5
+ import {SharedValue} from 'react-native-reanimated';
6
+
7
+ interface ScrollViewContainerContextData {
8
+ scrollViewContainerRef: React.RefObject<ScrollView>;
9
+ scrollViewHeightY: SharedValue<number>;
10
+ scrollViewScrollOffsetY: SharedValue<number>;
11
+ scrollViewScrollEnabled: SharedValue<boolean>;
12
+ outerScrollGesture: NativeGesture;
13
+ initialScrollViewScrollEnabled: boolean;
14
+ }
15
+
16
+ export const ScrollViewContainerContext = React.createContext<
17
+ ScrollViewContainerContextData | undefined
18
+ >(undefined);
@@ -1,2 +1,3 @@
1
1
  export * from './ReorderableCellContext';
2
2
  export * from './ReorderableListContext';
3
+ export * from './ScrollViewContainerContext';