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.
- 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 +3 -7
- package/lib/commonjs/components/ReorderableListCell.js.map +1 -1
- package/lib/commonjs/components/{ReorderableList/ReorderableList.js → ReorderableListCore/ReorderableListCore.js} +35 -12
- package/lib/commonjs/components/ReorderableListCore/ReorderableListCore.js.map +1 -0
- package/lib/commonjs/components/ReorderableListCore/constants.ios.js.map +1 -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} +182 -75
- 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/ReorderableCellContext.js.map +1 -1
- package/lib/commonjs/contexts/ReorderableListContext.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/hooks/useReorderableDragEnd.js +23 -8
- package/lib/commonjs/hooks/useReorderableDragEnd.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 +3 -7
- package/lib/module/components/ReorderableListCell.js.map +1 -1
- package/lib/module/components/{ReorderableList/ReorderableList.js → ReorderableListCore/ReorderableListCore.js} +38 -15
- package/lib/module/components/ReorderableListCore/ReorderableListCore.js.map +1 -0
- package/lib/module/components/ReorderableListCore/constants.ios.js.map +1 -0
- package/lib/module/components/{ReorderableList → ReorderableListCore}/constants.js.map +1 -1
- 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} +181 -74
- 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/ReorderableCellContext.js.map +1 -1
- package/lib/module/contexts/ReorderableListContext.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/hooks/useReorderableDragEnd.js +23 -8
- package/lib/module/hooks/useReorderableDragEnd.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 +1 -2
- 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.map +1 -0
- package/lib/typescript/components/ReorderableListCore/constants.ios.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/ReorderableCellContext.d.ts +0 -1
- package/lib/typescript/contexts/ReorderableCellContext.d.ts.map +1 -1
- package/lib/typescript/contexts/ReorderableListContext.d.ts +1 -0
- package/lib/typescript/contexts/ReorderableListContext.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/hooks/useReorderableDragEnd.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 +1 -1
- package/src/components/NestedReorderableList.tsx +43 -0
- package/src/components/ReorderableList.tsx +29 -0
- package/src/components/ReorderableListCell.tsx +2 -7
- package/src/components/ReorderableListCore/ReorderableListCore.tsx +162 -0
- package/src/components/ReorderableListCore/index.ts +1 -0
- package/src/components/{ReorderableList/useReorderableList.ts → ReorderableListCore/useReorderableListCore.ts} +264 -85
- package/src/components/ScrollViewContainer.tsx +74 -0
- package/src/components/index.ts +2 -0
- package/src/contexts/ReorderableCellContext.ts +0 -1
- package/src/contexts/ReorderableListContext.ts +1 -0
- package/src/contexts/ScrollViewContainerContext.ts +18 -0
- package/src/contexts/index.ts +1 -0
- package/src/hooks/useReorderableDragEnd.ts +28 -12
- 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.map +0 -1
- 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.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.map +0 -1
- 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 -119
- package/src/components/ReorderableList/index.ts +0 -1
- /package/lib/commonjs/components/{ReorderableList → ReorderableListCore}/constants.ios.js +0 -0
- /package/lib/commonjs/components/{ReorderableList → ReorderableListCore}/constants.js +0 -0
- /package/lib/module/components/{ReorderableList → ReorderableListCore}/constants.ios.js +0 -0
- /package/lib/module/components/{ReorderableList → ReorderableListCore}/constants.js +0 -0
- /package/lib/typescript/components/{ReorderableList → ReorderableListCore}/constants.d.ts +0 -0
- /package/lib/typescript/components/{ReorderableList → ReorderableListCore}/constants.ios.d.ts +0 -0
- /package/src/components/{ReorderableList → ReorderableListCore}/constants.ios.ts +0 -0
- /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
|
|
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
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
66
|
-
const
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
152
|
-
|
|
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
|
-
[
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
(
|
|
360
|
-
? -AUTOSCROLL_INCREMENT
|
|
361
|
-
: AUTOSCROLL_INCREMENT) * autoscrollSpeedScale;
|
|
519
|
+
scrollDirection(y) * AUTOSCROLL_INCREMENT * autoscrollSpeedScale;
|
|
362
520
|
|
|
363
521
|
if (autoscrollIncrement !== 0) {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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(
|
|
537
|
+
setCurrentIndex(y);
|
|
375
538
|
}
|
|
376
539
|
},
|
|
377
540
|
);
|
|
378
541
|
|
|
542
|
+
// flatlist scroll handler
|
|
379
543
|
const handleScroll = useAnimatedScrollHandler(e => {
|
|
380
|
-
|
|
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
|
-
|
|
551
|
+
flatListScrollOffsetY.value - dragInitialScrollOffsetY.value;
|
|
388
552
|
}
|
|
389
553
|
|
|
390
554
|
if (state.value === ReorderableListState.AUTO_SCROLL) {
|
|
391
|
-
dragY.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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
643
|
+
nestedFlatListPositionY.value = e.nativeEvent.layout.y;
|
|
644
|
+
flatListHeightY.value = e.nativeEvent.layout.height;
|
|
451
645
|
|
|
452
|
-
|
|
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
|
-
|
|
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
|
+
};
|
package/src/components/index.ts
CHANGED
|
@@ -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);
|
package/src/contexts/index.ts
CHANGED