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.
- package/README.md +28 -3
- package/lib/commonjs/components/NestedReorderableList.js +41 -0
- package/lib/commonjs/components/NestedReorderableList.js.map +1 -0
- package/lib/commonjs/components/ReorderableList.js +29 -0
- package/lib/commonjs/components/ReorderableList.js.map +1 -0
- package/lib/commonjs/components/ReorderableListCell.js +1 -3
- package/lib/commonjs/components/ReorderableListCell.js.map +1 -1
- package/lib/commonjs/components/{ReorderableList/ReorderableList.js → ReorderableListCore/ReorderableListCore.js} +35 -10
- package/lib/commonjs/components/ReorderableListCore/ReorderableListCore.js.map +1 -0
- package/lib/commonjs/components/ReorderableListCore/constants.js +31 -0
- package/lib/commonjs/components/ReorderableListCore/constants.js.map +1 -0
- package/lib/commonjs/components/ReorderableListCore/index.js +17 -0
- package/lib/commonjs/components/ReorderableListCore/index.js.map +1 -0
- package/lib/commonjs/components/{ReorderableList/useReorderableList.js → ReorderableListCore/useReorderableListCore.js} +176 -70
- package/lib/commonjs/components/ReorderableListCore/useReorderableListCore.js.map +1 -0
- package/lib/commonjs/components/ScrollViewContainer.js +53 -0
- package/lib/commonjs/components/ScrollViewContainer.js.map +1 -0
- package/lib/commonjs/components/index.js +22 -0
- package/lib/commonjs/components/index.js.map +1 -1
- package/lib/commonjs/contexts/ScrollViewContainerContext.js +10 -0
- package/lib/commonjs/contexts/ScrollViewContainerContext.js.map +1 -0
- package/lib/commonjs/contexts/index.js +11 -0
- package/lib/commonjs/contexts/index.js.map +1 -1
- package/lib/commonjs/index.js +12 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/components/NestedReorderableList.js +33 -0
- package/lib/module/components/NestedReorderableList.js.map +1 -0
- package/lib/module/components/ReorderableList.js +21 -0
- package/lib/module/components/ReorderableList.js.map +1 -0
- package/lib/module/components/ReorderableListCell.js +1 -3
- package/lib/module/components/ReorderableListCell.js.map +1 -1
- package/lib/module/components/{ReorderableList/ReorderableList.js → ReorderableListCore/ReorderableListCore.js} +39 -14
- package/lib/module/components/ReorderableListCore/ReorderableListCore.js.map +1 -0
- package/lib/module/components/ReorderableListCore/constants.js +25 -0
- package/lib/module/components/ReorderableListCore/constants.js.map +1 -0
- package/lib/module/components/ReorderableListCore/index.js +2 -0
- package/lib/module/components/ReorderableListCore/index.js.map +1 -0
- package/lib/module/components/{ReorderableList/useReorderableList.js → ReorderableListCore/useReorderableListCore.js} +177 -71
- package/lib/module/components/ReorderableListCore/useReorderableListCore.js.map +1 -0
- package/lib/module/components/ScrollViewContainer.js +44 -0
- package/lib/module/components/ScrollViewContainer.js.map +1 -0
- package/lib/module/components/index.js +2 -0
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/contexts/ScrollViewContainerContext.js +3 -0
- package/lib/module/contexts/ScrollViewContainerContext.js.map +1 -0
- package/lib/module/contexts/index.js +1 -0
- package/lib/module/contexts/index.js.map +1 -1
- package/lib/module/index.js +2 -2
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/components/NestedReorderableList.d.ts +5 -0
- package/lib/typescript/components/NestedReorderableList.d.ts.map +1 -0
- package/lib/typescript/components/ReorderableList.d.ts +5 -0
- package/lib/typescript/components/ReorderableList.d.ts.map +1 -0
- package/lib/typescript/components/ReorderableListCell.d.ts.map +1 -1
- package/lib/typescript/components/ReorderableListCore/ReorderableListCore.d.ts +20 -0
- package/lib/typescript/components/ReorderableListCore/ReorderableListCore.d.ts.map +1 -0
- package/lib/typescript/components/ReorderableListCore/constants.d.ts +6 -0
- package/lib/typescript/components/ReorderableListCore/constants.d.ts.map +1 -0
- package/lib/typescript/components/ReorderableListCore/index.d.ts +2 -0
- package/lib/typescript/components/ReorderableListCore/index.d.ts.map +1 -0
- package/lib/typescript/components/ReorderableListCore/useReorderableListCore.d.ts +44 -0
- package/lib/typescript/components/ReorderableListCore/useReorderableListCore.d.ts.map +1 -0
- package/lib/typescript/components/ScrollViewContainer.d.ts +4 -0
- package/lib/typescript/components/ScrollViewContainer.d.ts.map +1 -0
- package/lib/typescript/components/index.d.ts +2 -0
- package/lib/typescript/components/index.d.ts.map +1 -1
- package/lib/typescript/contexts/ScrollViewContainerContext.d.ts +15 -0
- package/lib/typescript/contexts/ScrollViewContainerContext.d.ts.map +1 -0
- package/lib/typescript/contexts/index.d.ts +1 -0
- package/lib/typescript/contexts/index.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +3 -3
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/types/props.d.ts +13 -1
- package/lib/typescript/types/props.d.ts.map +1 -1
- package/package.json +15 -7
- package/src/components/NestedReorderableList.tsx +43 -0
- package/src/components/ReorderableList.tsx +29 -0
- package/src/components/ReorderableListCell.tsx +1 -3
- package/src/components/ReorderableListCore/ReorderableListCore.tsx +162 -0
- package/src/components/ReorderableListCore/constants.ts +31 -0
- package/src/components/ReorderableListCore/index.ts +1 -0
- package/src/components/{ReorderableList/useReorderableList.ts → ReorderableListCore/useReorderableListCore.ts} +261 -89
- package/src/components/ScrollViewContainer.tsx +74 -0
- package/src/components/index.ts +2 -0
- package/src/contexts/ScrollViewContainerContext.ts +18 -0
- package/src/contexts/index.ts +1 -0
- package/src/index.ts +10 -1
- package/src/types/props.ts +21 -1
- package/lib/commonjs/components/ReorderableList/ReorderableList.js.map +0 -1
- package/lib/commonjs/components/ReorderableList/constants.ios.js +0 -10
- package/lib/commonjs/components/ReorderableList/constants.ios.js.map +0 -1
- package/lib/commonjs/components/ReorderableList/constants.js +0 -10
- package/lib/commonjs/components/ReorderableList/constants.js.map +0 -1
- package/lib/commonjs/components/ReorderableList/index.js +0 -17
- package/lib/commonjs/components/ReorderableList/index.js.map +0 -1
- package/lib/commonjs/components/ReorderableList/useReorderableList.js.map +0 -1
- package/lib/module/components/ReorderableList/ReorderableList.js.map +0 -1
- package/lib/module/components/ReorderableList/constants.ios.js +0 -4
- package/lib/module/components/ReorderableList/constants.ios.js.map +0 -1
- package/lib/module/components/ReorderableList/constants.js +0 -4
- package/lib/module/components/ReorderableList/constants.js.map +0 -1
- package/lib/module/components/ReorderableList/index.js +0 -2
- package/lib/module/components/ReorderableList/index.js.map +0 -1
- package/lib/module/components/ReorderableList/useReorderableList.js.map +0 -1
- package/lib/typescript/components/ReorderableList/ReorderableList.d.ts +0 -8
- package/lib/typescript/components/ReorderableList/ReorderableList.d.ts.map +0 -1
- package/lib/typescript/components/ReorderableList/constants.d.ts +0 -3
- package/lib/typescript/components/ReorderableList/constants.d.ts.map +0 -1
- package/lib/typescript/components/ReorderableList/constants.ios.d.ts +0 -3
- package/lib/typescript/components/ReorderableList/constants.ios.d.ts.map +0 -1
- package/lib/typescript/components/ReorderableList/index.d.ts +0 -2
- package/lib/typescript/components/ReorderableList/index.d.ts.map +0 -1
- package/lib/typescript/components/ReorderableList/useReorderableList.d.ts +0 -36
- package/lib/typescript/components/ReorderableList/useReorderableList.d.ts.map +0 -1
- package/src/components/ReorderableList/ReorderableList.tsx +0 -109
- package/src/components/ReorderableList/constants.ios.ts +0 -3
- package/src/components/ReorderableList/constants.ts +0 -3
- 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
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
66
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
155
|
-
|
|
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
|
-
[
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
(
|
|
363
|
-
|
|
364
|
-
|
|
516
|
+
scrollDirection(y) *
|
|
517
|
+
AUTOSCROLL_CONFIG.increment *
|
|
518
|
+
autoscrollSpeedScale;
|
|
365
519
|
|
|
366
520
|
if (autoscrollIncrement !== 0) {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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(
|
|
536
|
+
setCurrentIndex(y);
|
|
378
537
|
}
|
|
379
538
|
},
|
|
380
539
|
);
|
|
381
540
|
|
|
541
|
+
// flatlist scroll handler
|
|
382
542
|
const handleScroll = useAnimatedScrollHandler(e => {
|
|
383
|
-
|
|
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
|
|
547
|
+
// moving away from autoscroll area
|
|
388
548
|
if (!scrollEnabled.value) {
|
|
389
549
|
dragScrollTranslationY.value =
|
|
390
|
-
|
|
550
|
+
flatListScrollOffsetY.value - dragInitialScrollOffsetY.value;
|
|
391
551
|
}
|
|
392
552
|
|
|
393
553
|
if (state.value === ReorderableListState.AUTO_SCROLL) {
|
|
394
|
-
dragY.value =
|
|
395
|
-
|
|
396
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
638
|
+
nestedFlatListPositionY.value = e.nativeEvent.layout.y;
|
|
639
|
+
flatListHeightY.value = e.nativeEvent.layout.height;
|
|
454
640
|
|
|
455
|
-
|
|
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
|
-
|
|
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
|
+
};
|
package/src/components/index.ts
CHANGED
|
@@ -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);
|
package/src/contexts/index.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
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;
|