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