react-native-reorderable-list 0.13.0 → 0.14.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 +69 -9
- package/lib/commonjs/components/NestedReorderableList.js +2 -0
- package/lib/commonjs/components/NestedReorderableList.js.map +1 -1
- package/lib/commonjs/components/ReorderableList.js +4 -3
- package/lib/commonjs/components/ReorderableList.js.map +1 -1
- package/lib/commonjs/components/{ReorderableListCore/useReorderableListCore.js → ReorderableListCore.js} +187 -96
- package/lib/commonjs/components/ReorderableListCore.js.map +1 -0
- package/lib/commonjs/components/ScrollViewContainer.js +26 -7
- package/lib/commonjs/components/ScrollViewContainer.js.map +1 -1
- package/lib/commonjs/components/{ReorderableListCore/animationDefaults.js → constants.js} +26 -2
- package/lib/commonjs/components/constants.js.map +1 -0
- package/lib/commonjs/contexts/ScrollViewContainerContext.js.map +1 -1
- package/lib/module/components/NestedReorderableList.js +2 -0
- package/lib/module/components/NestedReorderableList.js.map +1 -1
- package/lib/module/components/ReorderableList.js +4 -3
- package/lib/module/components/ReorderableList.js.map +1 -1
- package/lib/module/components/{ReorderableListCore/useReorderableListCore.js → ReorderableListCore.js} +188 -95
- package/lib/module/components/ReorderableListCore.js.map +1 -0
- package/lib/module/components/ScrollViewContainer.js +28 -8
- package/lib/module/components/ScrollViewContainer.js.map +1 -1
- package/lib/module/components/constants.js +52 -0
- package/lib/module/components/constants.js.map +1 -0
- package/lib/module/contexts/ScrollViewContainerContext.js.map +1 -1
- package/lib/typescript/components/NestedReorderableList.d.ts.map +1 -1
- package/lib/typescript/components/ReorderableList.d.ts.map +1 -1
- package/lib/typescript/components/{ReorderableListCore/ReorderableListCore.d.ts → ReorderableListCore.d.ts} +2 -1
- package/lib/typescript/components/ReorderableListCore.d.ts.map +1 -0
- package/lib/typescript/components/ScrollViewContainer.d.ts +2 -1
- package/lib/typescript/components/ScrollViewContainer.d.ts.map +1 -1
- package/lib/typescript/components/{ReorderableListCore/animationDefaults.d.ts → constants.d.ts} +6 -1
- package/lib/typescript/components/constants.d.ts.map +1 -0
- package/lib/typescript/contexts/ScrollViewContainerContext.d.ts +1 -0
- package/lib/typescript/contexts/ScrollViewContainerContext.d.ts.map +1 -1
- package/lib/typescript/types/props.d.ts +9 -0
- package/lib/typescript/types/props.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/NestedReorderableList.tsx +2 -0
- package/src/components/ReorderableList.tsx +3 -2
- package/src/components/{ReorderableListCore/useReorderableListCore.ts → ReorderableListCore.tsx} +321 -184
- package/src/components/ScrollViewContainer.tsx +44 -13
- package/src/components/{ReorderableListCore/animationDefaults.ts → constants.ts} +34 -0
- package/src/contexts/ScrollViewContainerContext.ts +1 -0
- package/src/types/props.ts +9 -0
- package/lib/commonjs/components/ReorderableListCore/ReorderableListCore.js +0 -121
- package/lib/commonjs/components/ReorderableListCore/ReorderableListCore.js.map +0 -1
- package/lib/commonjs/components/ReorderableListCore/animationDefaults.js.map +0 -1
- package/lib/commonjs/components/ReorderableListCore/autoscrollConfig.js +0 -31
- package/lib/commonjs/components/ReorderableListCore/autoscrollConfig.js.map +0 -1
- package/lib/commonjs/components/ReorderableListCore/index.js +0 -17
- package/lib/commonjs/components/ReorderableListCore/index.js.map +0 -1
- package/lib/commonjs/components/ReorderableListCore/useReorderableListCore.js.map +0 -1
- package/lib/module/components/ReorderableListCore/ReorderableListCore.js +0 -114
- package/lib/module/components/ReorderableListCore/ReorderableListCore.js.map +0 -1
- package/lib/module/components/ReorderableListCore/animationDefaults.js +0 -28
- package/lib/module/components/ReorderableListCore/animationDefaults.js.map +0 -1
- package/lib/module/components/ReorderableListCore/autoscrollConfig.js +0 -25
- package/lib/module/components/ReorderableListCore/autoscrollConfig.js.map +0 -1
- package/lib/module/components/ReorderableListCore/index.js +0 -2
- package/lib/module/components/ReorderableListCore/index.js.map +0 -1
- package/lib/module/components/ReorderableListCore/useReorderableListCore.js.map +0 -1
- package/lib/typescript/components/ReorderableListCore/ReorderableListCore.d.ts.map +0 -1
- package/lib/typescript/components/ReorderableListCore/animationDefaults.d.ts.map +0 -1
- package/lib/typescript/components/ReorderableListCore/autoscrollConfig.d.ts +0 -6
- package/lib/typescript/components/ReorderableListCore/autoscrollConfig.d.ts.map +0 -1
- package/lib/typescript/components/ReorderableListCore/index.d.ts +0 -2
- package/lib/typescript/components/ReorderableListCore/index.d.ts.map +0 -1
- package/lib/typescript/components/ReorderableListCore/useReorderableListCore.d.ts +0 -183
- package/lib/typescript/components/ReorderableListCore/useReorderableListCore.d.ts.map +0 -1
- package/src/components/ReorderableListCore/ReorderableListCore.tsx +0 -175
- package/src/components/ReorderableListCore/autoscrollConfig.ts +0 -31
- package/src/components/ReorderableListCore/index.ts +0 -1
package/src/components/{ReorderableListCore/useReorderableListCore.ts → ReorderableListCore.tsx}
RENAMED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
|
1
|
+
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
|
2
2
|
import {
|
|
3
|
+
CellRendererProps,
|
|
3
4
|
FlatList,
|
|
5
|
+
FlatListProps,
|
|
4
6
|
LayoutChangeEvent,
|
|
7
|
+
Platform,
|
|
5
8
|
ScrollView,
|
|
6
|
-
unstable_batchedUpdates,
|
|
7
9
|
} from 'react-native';
|
|
8
10
|
|
|
9
11
|
import {
|
|
10
12
|
Gesture,
|
|
13
|
+
GestureDetector,
|
|
11
14
|
GestureUpdateEvent,
|
|
15
|
+
NativeGesture,
|
|
12
16
|
PanGestureHandlerEventPayload,
|
|
13
17
|
State,
|
|
14
18
|
} from 'react-native-gesture-handler';
|
|
@@ -16,87 +20,88 @@ import Animated, {
|
|
|
16
20
|
AnimatedRef,
|
|
17
21
|
Easing,
|
|
18
22
|
SharedValue,
|
|
23
|
+
measure,
|
|
19
24
|
runOnJS,
|
|
20
25
|
runOnUI,
|
|
21
26
|
scrollTo,
|
|
22
27
|
useAnimatedReaction,
|
|
23
28
|
useAnimatedRef,
|
|
24
29
|
useAnimatedScrollHandler,
|
|
30
|
+
useComposedEventHandler,
|
|
31
|
+
useDerivedValue,
|
|
25
32
|
useSharedValue,
|
|
26
33
|
withDelay,
|
|
27
34
|
withTiming,
|
|
28
35
|
} from 'react-native-reanimated';
|
|
29
36
|
|
|
37
|
+
import {ReorderableListContext} from '../contexts';
|
|
38
|
+
import {ReorderableListProps, ReorderableListState} from '../types';
|
|
30
39
|
import {
|
|
40
|
+
AUTOSCROLL_CONFIG,
|
|
31
41
|
OPACITY_ANIMATION_CONFIG_DEFAULT,
|
|
32
42
|
SCALE_ANIMATION_CONFIG_DEFAULT,
|
|
33
|
-
} from './
|
|
34
|
-
import {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const version = React.version.split('.');
|
|
45
|
-
const hasAutomaticBatching = version.length
|
|
46
|
-
? parseInt(version[0], 10) >= 18
|
|
47
|
-
: false;
|
|
48
|
-
|
|
49
|
-
interface UseReorderableListCoreArgs<T> {
|
|
50
|
-
ref: React.ForwardedRef<FlatList<T>>;
|
|
51
|
-
autoscrollThreshold: number;
|
|
52
|
-
autoscrollThresholdOffset: {top?: number; bottom?: number} | undefined;
|
|
53
|
-
autoscrollSpeedScale: number;
|
|
54
|
-
autoscrollDelay: number;
|
|
55
|
-
autoscrollActivationDelta: number;
|
|
56
|
-
animationDuration: number;
|
|
57
|
-
onReorder: (event: ReorderableListReorderEvent) => void;
|
|
58
|
-
onDragStart?: (event: ReorderableListDragStartEvent) => void;
|
|
59
|
-
onDragEnd?: (event: ReorderableListDragEndEvent) => void;
|
|
60
|
-
onIndexChange?: (event: ReorderableListIndexChangeEvent) => void;
|
|
61
|
-
onLayout?: (event: LayoutChangeEvent) => void;
|
|
43
|
+
} from './constants';
|
|
44
|
+
import {ReorderableListCell} from './ReorderableListCell';
|
|
45
|
+
|
|
46
|
+
const AnimatedFlatList = Animated.createAnimatedComponent(
|
|
47
|
+
FlatList,
|
|
48
|
+
) as unknown as <T>(
|
|
49
|
+
props: FlatListProps<T> & {ref?: React.Ref<FlatList<T>>},
|
|
50
|
+
) => React.ReactElement;
|
|
51
|
+
|
|
52
|
+
interface ReorderableListCoreProps<T> extends ReorderableListProps<T> {
|
|
53
|
+
// Not optional but undefined to force passing the prop.
|
|
62
54
|
scrollViewContainerRef: React.RefObject<ScrollView> | undefined;
|
|
55
|
+
scrollViewPageY: SharedValue<number> | undefined;
|
|
63
56
|
scrollViewHeightY: SharedValue<number> | undefined;
|
|
64
57
|
scrollViewScrollOffsetY: SharedValue<number> | undefined;
|
|
65
58
|
scrollViewScrollEnabled: SharedValue<boolean> | undefined;
|
|
66
|
-
|
|
59
|
+
outerScrollGesture: NativeGesture | undefined;
|
|
67
60
|
initialScrollViewScrollEnabled: boolean | undefined;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
shouldUpdateActiveItem: boolean | undefined;
|
|
71
|
-
panEnabled: boolean;
|
|
72
|
-
panActivateAfterLongPress: number | undefined;
|
|
61
|
+
scrollable: boolean | undefined;
|
|
62
|
+
scrollEnabled: boolean | undefined;
|
|
73
63
|
}
|
|
74
64
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
65
|
+
const ReorderableListCore = <T,>(
|
|
66
|
+
{
|
|
67
|
+
autoscrollThreshold = 0.1,
|
|
68
|
+
autoscrollThresholdOffset,
|
|
69
|
+
autoscrollSpeedScale = 1,
|
|
70
|
+
autoscrollDelay = AUTOSCROLL_CONFIG.delay,
|
|
71
|
+
autoscrollActivationDelta = 5,
|
|
72
|
+
animationDuration = 200,
|
|
73
|
+
onLayout,
|
|
74
|
+
onReorder,
|
|
75
|
+
onScroll,
|
|
76
|
+
onDragStart,
|
|
77
|
+
onDragEnd,
|
|
78
|
+
onIndexChange,
|
|
79
|
+
scrollViewContainerRef,
|
|
80
|
+
scrollViewPageY,
|
|
81
|
+
scrollViewHeightY,
|
|
82
|
+
scrollViewScrollOffsetY,
|
|
83
|
+
scrollViewScrollEnabled,
|
|
84
|
+
scrollable,
|
|
85
|
+
outerScrollGesture,
|
|
86
|
+
cellAnimations,
|
|
87
|
+
shouldUpdateActiveItem,
|
|
88
|
+
panGesture,
|
|
89
|
+
panEnabled = true,
|
|
90
|
+
panActivateAfterLongPress,
|
|
91
|
+
data,
|
|
92
|
+
...rest
|
|
93
|
+
}: ReorderableListCoreProps<T>,
|
|
94
|
+
ref: React.ForwardedRef<FlatList<T>>,
|
|
95
|
+
) => {
|
|
96
|
+
// FlatList will default to true if we pass explicitly undefined,
|
|
97
|
+
// but internally we would treat it as false, so we force true.
|
|
98
|
+
const initialScrollEnabled =
|
|
99
|
+
typeof rest.scrollEnabled === 'undefined' ? true : rest.scrollEnabled;
|
|
100
|
+
const initialScrollViewScrollEnabled =
|
|
101
|
+
typeof rest.initialScrollViewScrollEnabled === 'undefined'
|
|
102
|
+
? true
|
|
103
|
+
: rest.initialScrollViewScrollEnabled;
|
|
104
|
+
|
|
100
105
|
const flatListRef = useAnimatedRef<FlatList>();
|
|
101
106
|
const [activeIndex, setActiveIndex] = useState(-1);
|
|
102
107
|
const scrollEnabled = useSharedValue(initialScrollEnabled);
|
|
@@ -107,7 +112,7 @@ export const useReorderableListCore = <T>({
|
|
|
107
112
|
const startItemDragCenterY = useSharedValue<number>(0);
|
|
108
113
|
const flatListScrollOffsetY = useSharedValue(0);
|
|
109
114
|
const flatListHeightY = useSharedValue(0);
|
|
110
|
-
const
|
|
115
|
+
const flatListPageY = useSharedValue(0);
|
|
111
116
|
// The scroll y translation of the list since drag start
|
|
112
117
|
const dragScrollTranslationY = useSharedValue(0);
|
|
113
118
|
// The initial scroll offset y of the list on drag start
|
|
@@ -119,6 +124,9 @@ export const useReorderableListCore = <T>({
|
|
|
119
124
|
const draggedHeight = useSharedValue(0);
|
|
120
125
|
const itemOffset = useSharedValue<number[]>([]);
|
|
121
126
|
const itemHeight = useSharedValue<number[]>([]);
|
|
127
|
+
// We need to track data length since itemOffset and itemHeight might contain more data than we need.
|
|
128
|
+
// e.g. items are removed from the list, in which case layout data for those items is set to 0.
|
|
129
|
+
const itemCount = useSharedValue(data.length);
|
|
122
130
|
const autoscrollTrigger = useSharedValue(-1);
|
|
123
131
|
const lastAutoscrollTrigger = useSharedValue(-1);
|
|
124
132
|
const dragY = useSharedValue(0);
|
|
@@ -135,11 +143,41 @@ export const useReorderableListCore = <T>({
|
|
|
135
143
|
const dragDirection = useSharedValue(0);
|
|
136
144
|
const lastDragDirectionPivot = useSharedValue<number | null>(null);
|
|
137
145
|
const autoscrollDelta = useSharedValue(autoscrollActivationDelta);
|
|
146
|
+
const prevItemCount = useRef(data.length);
|
|
147
|
+
|
|
148
|
+
// Position of the list relative to the scroll container
|
|
149
|
+
const nestedFlatListPositionY = useDerivedValue(
|
|
150
|
+
() => flatListPageY.value - (scrollViewPageY?.value || 0),
|
|
151
|
+
);
|
|
138
152
|
|
|
139
153
|
useEffect(() => {
|
|
140
154
|
duration.value = animationDuration;
|
|
141
155
|
autoscrollDelta.value = autoscrollActivationDelta;
|
|
142
|
-
}, [
|
|
156
|
+
}, [
|
|
157
|
+
duration,
|
|
158
|
+
animationDuration,
|
|
159
|
+
autoscrollDelta,
|
|
160
|
+
autoscrollActivationDelta,
|
|
161
|
+
itemCount,
|
|
162
|
+
]);
|
|
163
|
+
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
itemCount.value = data.length;
|
|
166
|
+
|
|
167
|
+
// This could be done unmount of the removed cell, however it leads to bugs.
|
|
168
|
+
// Surprisingly the unmount gets sometimes called after the onLayout event
|
|
169
|
+
// setting all layout data to 0 and breaking the list. So we solve it like this.
|
|
170
|
+
if (data.length < prevItemCount.current) {
|
|
171
|
+
for (let i = data.length; i < prevItemCount.current; i++) {
|
|
172
|
+
runOnUI(() => {
|
|
173
|
+
itemHeight.value[i] = 0;
|
|
174
|
+
itemOffset.value[i] = 0;
|
|
175
|
+
})();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
prevItemCount.current = data.length;
|
|
180
|
+
}, [data.length, itemHeight, itemOffset, itemCount]);
|
|
143
181
|
|
|
144
182
|
const listContextValue = useMemo(
|
|
145
183
|
() => ({
|
|
@@ -234,8 +272,10 @@ export const useReorderableListCore = <T>({
|
|
|
234
272
|
|
|
235
273
|
const panGestureHandler = useMemo(
|
|
236
274
|
() =>
|
|
237
|
-
Gesture.Pan()
|
|
275
|
+
(panGesture || Gesture.Pan())
|
|
238
276
|
.onBegin(e => {
|
|
277
|
+
'worklet';
|
|
278
|
+
|
|
239
279
|
// prevent new dragging until item is completely released
|
|
240
280
|
if (state.value === ReorderableListState.IDLE) {
|
|
241
281
|
startY.value = e.y;
|
|
@@ -246,6 +286,8 @@ export const useReorderableListCore = <T>({
|
|
|
246
286
|
}
|
|
247
287
|
})
|
|
248
288
|
.onUpdate(e => {
|
|
289
|
+
'worklet';
|
|
290
|
+
|
|
249
291
|
if (state.value === ReorderableListState.DRAGGED) {
|
|
250
292
|
setDragDirection(e);
|
|
251
293
|
}
|
|
@@ -262,9 +304,18 @@ export const useReorderableListCore = <T>({
|
|
|
262
304
|
gestureState.value = e.state;
|
|
263
305
|
}
|
|
264
306
|
})
|
|
265
|
-
.onEnd(e =>
|
|
266
|
-
|
|
307
|
+
.onEnd(e => {
|
|
308
|
+
'worklet';
|
|
309
|
+
|
|
310
|
+
gestureState.value = e.state;
|
|
311
|
+
})
|
|
312
|
+
.onFinalize(e => {
|
|
313
|
+
'worklet';
|
|
314
|
+
|
|
315
|
+
gestureState.value = e.state;
|
|
316
|
+
}),
|
|
267
317
|
[
|
|
318
|
+
panGesture,
|
|
268
319
|
state,
|
|
269
320
|
startY,
|
|
270
321
|
currentY,
|
|
@@ -278,7 +329,7 @@ export const useReorderableListCore = <T>({
|
|
|
278
329
|
],
|
|
279
330
|
);
|
|
280
331
|
|
|
281
|
-
const
|
|
332
|
+
const panGestureHandlerWithPropOptions = useMemo(() => {
|
|
282
333
|
if (typeof panActivateAfterLongPress === 'number') {
|
|
283
334
|
panGestureHandler.activateAfterLongPress(panActivateAfterLongPress);
|
|
284
335
|
}
|
|
@@ -291,8 +342,9 @@ export const useReorderableListCore = <T>({
|
|
|
291
342
|
}, [panActivateAfterLongPress, panEnabled, panGestureHandler]);
|
|
292
343
|
|
|
293
344
|
const gestureHandler = useMemo(
|
|
294
|
-
() =>
|
|
295
|
-
|
|
345
|
+
() =>
|
|
346
|
+
Gesture.Simultaneous(Gesture.Native(), panGestureHandlerWithPropOptions),
|
|
347
|
+
[panGestureHandlerWithPropOptions],
|
|
296
348
|
);
|
|
297
349
|
|
|
298
350
|
const setScrollEnabled = useCallback(
|
|
@@ -354,15 +406,7 @@ export const useReorderableListCore = <T>({
|
|
|
354
406
|
runOnUI(resetSharedValues)();
|
|
355
407
|
|
|
356
408
|
if (fromIndex !== toIndex) {
|
|
357
|
-
|
|
358
|
-
onReorder({from: fromIndex, to: toIndex});
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
if (!hasAutomaticBatching) {
|
|
362
|
-
unstable_batchedUpdates(updateState);
|
|
363
|
-
} else {
|
|
364
|
-
updateState();
|
|
365
|
-
}
|
|
409
|
+
onReorder({from: fromIndex, to: toIndex});
|
|
366
410
|
}
|
|
367
411
|
};
|
|
368
412
|
|
|
@@ -414,7 +458,7 @@ export const useReorderableListCore = <T>({
|
|
|
414
458
|
const currentHeight = itemHeight.value[currentIndex.value];
|
|
415
459
|
const currentCenter = currentOffset + currentHeight * 0.5;
|
|
416
460
|
|
|
417
|
-
const max =
|
|
461
|
+
const max = itemCount.value;
|
|
418
462
|
const possibleIndex =
|
|
419
463
|
relativeDragCenterY < currentCenter
|
|
420
464
|
? Math.max(0, currentIndex.value - 1)
|
|
@@ -441,6 +485,7 @@ export const useReorderableListCore = <T>({
|
|
|
441
485
|
}, [
|
|
442
486
|
currentIndex,
|
|
443
487
|
currentItemDragCenterY,
|
|
488
|
+
itemCount,
|
|
444
489
|
itemOffset,
|
|
445
490
|
itemHeight,
|
|
446
491
|
flatListScrollOffsetY,
|
|
@@ -543,7 +588,7 @@ export const useReorderableListCore = <T>({
|
|
|
543
588
|
},
|
|
544
589
|
);
|
|
545
590
|
|
|
546
|
-
const
|
|
591
|
+
const computeHiddenArea = useCallback(() => {
|
|
547
592
|
'worklet';
|
|
548
593
|
if (!scrollViewScrollOffsetY || !scrollViewHeightY) {
|
|
549
594
|
return {top: 0, bottom: 0};
|
|
@@ -569,103 +614,121 @@ export const useReorderableListCore = <T>({
|
|
|
569
614
|
flatListHeightY,
|
|
570
615
|
]);
|
|
571
616
|
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
'worklet';
|
|
575
|
-
const offsetTop = Math.max(0, autoscrollThresholdOffset?.top || 0);
|
|
576
|
-
const offsetBottom = Math.max(0, autoscrollThresholdOffset?.bottom || 0);
|
|
577
|
-
const threshold = Math.max(0, Math.min(autoscrollThreshold, 0.4));
|
|
578
|
-
const visibleHeight =
|
|
579
|
-
flatListHeightY.value -
|
|
580
|
-
(hiddenArea.top + hiddenArea.bottom) -
|
|
581
|
-
(offsetTop + offsetBottom);
|
|
617
|
+
const computeThresholdArea = useCallback(() => {
|
|
618
|
+
'worklet';
|
|
582
619
|
|
|
583
|
-
|
|
584
|
-
const top = area + offsetTop;
|
|
585
|
-
const bottom = flatListHeightY.value - area - offsetBottom;
|
|
620
|
+
const hiddenArea = computeHiddenArea();
|
|
586
621
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
622
|
+
const offsetTop = Math.max(0, autoscrollThresholdOffset?.top || 0);
|
|
623
|
+
const offsetBottom = Math.max(0, autoscrollThresholdOffset?.bottom || 0);
|
|
624
|
+
const threshold = Math.max(0, Math.min(autoscrollThreshold, 0.4));
|
|
625
|
+
const visibleHeight =
|
|
626
|
+
flatListHeightY.value -
|
|
627
|
+
(hiddenArea.top + hiddenArea.bottom) -
|
|
628
|
+
(offsetTop + offsetBottom);
|
|
591
629
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
},
|
|
610
|
-
[autoscrollThreshold, autoscrollThresholdOffset, flatListHeightY],
|
|
611
|
-
);
|
|
630
|
+
const area = visibleHeight * threshold;
|
|
631
|
+
const top = area + offsetTop;
|
|
632
|
+
const bottom = flatListHeightY.value - area - offsetBottom;
|
|
633
|
+
|
|
634
|
+
return {top, bottom};
|
|
635
|
+
}, [
|
|
636
|
+
computeHiddenArea,
|
|
637
|
+
autoscrollThreshold,
|
|
638
|
+
autoscrollThresholdOffset,
|
|
639
|
+
flatListHeightY,
|
|
640
|
+
]);
|
|
641
|
+
|
|
642
|
+
const computeContainerThresholdArea = useCallback(() => {
|
|
643
|
+
'worklet';
|
|
644
|
+
if (!scrollViewHeightY) {
|
|
645
|
+
return {top: -Infinity, bottom: Infinity};
|
|
646
|
+
}
|
|
612
647
|
|
|
613
|
-
|
|
648
|
+
const offsetTop = Math.max(0, autoscrollThresholdOffset?.top || 0);
|
|
649
|
+
const offsetBottom = Math.max(0, autoscrollThresholdOffset?.bottom || 0);
|
|
650
|
+
const threshold = Math.max(0, Math.min(autoscrollThreshold, 0.4));
|
|
651
|
+
const visibleHeight = scrollViewHeightY.value - (offsetTop + offsetBottom);
|
|
652
|
+
|
|
653
|
+
const area = visibleHeight * threshold;
|
|
654
|
+
const top = area + offsetTop;
|
|
655
|
+
const bottom = visibleHeight - area - offsetBottom;
|
|
656
|
+
|
|
657
|
+
return {top, bottom};
|
|
658
|
+
}, [autoscrollThreshold, autoscrollThresholdOffset, scrollViewHeightY]);
|
|
659
|
+
|
|
660
|
+
const shouldScrollContainer = useCallback(
|
|
614
661
|
(y: number) => {
|
|
615
662
|
'worklet';
|
|
616
|
-
const
|
|
617
|
-
const
|
|
663
|
+
const containerThresholdArea = computeContainerThresholdArea();
|
|
664
|
+
const nestedListHiddenArea = computeHiddenArea();
|
|
618
665
|
|
|
619
|
-
//
|
|
666
|
+
// We should scroll the container if there's a hidden part of the nested list.
|
|
667
|
+
// We might have floating errors like 0.0001 which we should ignore.
|
|
620
668
|
return (
|
|
621
|
-
(
|
|
622
|
-
(
|
|
669
|
+
(nestedListHiddenArea.top > 0.01 && y <= containerThresholdArea.top) ||
|
|
670
|
+
(nestedListHiddenArea.bottom > 0.01 &&
|
|
671
|
+
y >= containerThresholdArea.bottom)
|
|
623
672
|
);
|
|
624
673
|
},
|
|
625
|
-
[
|
|
674
|
+
[computeHiddenArea, computeContainerThresholdArea],
|
|
626
675
|
);
|
|
627
676
|
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
'worklet';
|
|
631
|
-
const hiddenArea = calculateHiddenArea();
|
|
677
|
+
const getRelativeContainerY = useCallback(() => {
|
|
678
|
+
'worklet';
|
|
632
679
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
680
|
+
return (
|
|
681
|
+
currentY.value +
|
|
682
|
+
nestedFlatListPositionY.value -
|
|
683
|
+
scrollViewDragInitialScrollOffsetY.value
|
|
684
|
+
);
|
|
685
|
+
}, [currentY, nestedFlatListPositionY, scrollViewDragInitialScrollOffsetY]);
|
|
638
686
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
}
|
|
687
|
+
const getRelativeListY = useCallback(() => {
|
|
688
|
+
'worklet';
|
|
642
689
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
const thresholdArea = calculateThresholdArea(hiddenArea);
|
|
646
|
-
if (y <= thresholdArea.top) {
|
|
647
|
-
return -1;
|
|
648
|
-
}
|
|
690
|
+
return currentY.value + scrollViewDragScrollTranslationY.value;
|
|
691
|
+
}, [currentY, scrollViewDragScrollTranslationY]);
|
|
649
692
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
693
|
+
const scrollDirection = useCallback(() => {
|
|
694
|
+
'worklet';
|
|
695
|
+
|
|
696
|
+
const relativeContainerY = getRelativeContainerY();
|
|
697
|
+
if (shouldScrollContainer(relativeContainerY)) {
|
|
698
|
+
const containerThresholdArea = computeContainerThresholdArea();
|
|
699
|
+
if (relativeContainerY <= containerThresholdArea.top) {
|
|
700
|
+
return -1;
|
|
653
701
|
}
|
|
654
702
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
703
|
+
if (relativeContainerY >= containerThresholdArea.bottom) {
|
|
704
|
+
return 1;
|
|
705
|
+
}
|
|
706
|
+
} else if (scrollable) {
|
|
707
|
+
const relativeListY = getRelativeListY();
|
|
708
|
+
const thresholdArea = computeThresholdArea();
|
|
709
|
+
|
|
710
|
+
if (relativeListY <= thresholdArea.top) {
|
|
711
|
+
return -1;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (relativeListY >= thresholdArea.bottom) {
|
|
715
|
+
return 1;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return 0;
|
|
720
|
+
}, [
|
|
721
|
+
shouldScrollContainer,
|
|
722
|
+
computeThresholdArea,
|
|
723
|
+
computeContainerThresholdArea,
|
|
724
|
+
getRelativeContainerY,
|
|
725
|
+
getRelativeListY,
|
|
726
|
+
scrollable,
|
|
727
|
+
]);
|
|
665
728
|
|
|
666
729
|
useAnimatedReaction(
|
|
667
|
-
() => currentY.value
|
|
668
|
-
|
|
730
|
+
() => currentY.value,
|
|
731
|
+
() => {
|
|
669
732
|
if (
|
|
670
733
|
state.value === ReorderableListState.DRAGGED ||
|
|
671
734
|
state.value === ReorderableListState.AUTOSCROLL
|
|
@@ -676,7 +739,7 @@ export const useReorderableListCore = <T>({
|
|
|
676
739
|
// 1. Within the threshold area (top or bottom of list)
|
|
677
740
|
// 2. Have dragged in the same direction as the scroll
|
|
678
741
|
// 3. Not already in autoscroll mode
|
|
679
|
-
if (dragDirection.value === scrollDirection(
|
|
742
|
+
if (dragDirection.value === scrollDirection()) {
|
|
680
743
|
// When the first two conditions are met and it's already in autoscroll mode, we let it continue (no-op)
|
|
681
744
|
if (state.value !== ReorderableListState.AUTOSCROLL) {
|
|
682
745
|
state.value = ReorderableListState.AUTOSCROLL;
|
|
@@ -697,9 +760,8 @@ export const useReorderableListCore = <T>({
|
|
|
697
760
|
autoscrollTrigger.value !== lastAutoscrollTrigger.value &&
|
|
698
761
|
state.value === ReorderableListState.AUTOSCROLL
|
|
699
762
|
) {
|
|
700
|
-
let y = currentY.value + scrollViewDragScrollTranslationY.value;
|
|
701
763
|
const autoscrollIncrement =
|
|
702
|
-
|
|
764
|
+
dragDirection.value *
|
|
703
765
|
AUTOSCROLL_CONFIG.increment *
|
|
704
766
|
autoscrollSpeedScale;
|
|
705
767
|
|
|
@@ -708,7 +770,13 @@ export const useReorderableListCore = <T>({
|
|
|
708
770
|
let listRef =
|
|
709
771
|
flatListRef as unknown as AnimatedRef<Animated.ScrollView>;
|
|
710
772
|
|
|
711
|
-
|
|
773
|
+
// Checking on every autoscroll whether to scroll the container,
|
|
774
|
+
// this allows to smoothly pass the scroll from the container to the nested list
|
|
775
|
+
// without any gesture input.
|
|
776
|
+
if (
|
|
777
|
+
scrollViewScrollOffsetY &&
|
|
778
|
+
shouldScrollContainer(getRelativeContainerY())
|
|
779
|
+
) {
|
|
712
780
|
scrollOffset = scrollViewScrollOffsetY.value;
|
|
713
781
|
listRef =
|
|
714
782
|
scrollViewContainerRef as unknown as AnimatedRef<Animated.ScrollView>;
|
|
@@ -750,7 +818,7 @@ export const useReorderableListCore = <T>({
|
|
|
750
818
|
}
|
|
751
819
|
});
|
|
752
820
|
|
|
753
|
-
//
|
|
821
|
+
// container scroll handler
|
|
754
822
|
useAnimatedReaction(
|
|
755
823
|
() => scrollViewScrollOffsetY?.value,
|
|
756
824
|
value => {
|
|
@@ -826,35 +894,104 @@ export const useReorderableListCore = <T>({
|
|
|
826
894
|
|
|
827
895
|
const handleFlatListLayout = useCallback(
|
|
828
896
|
(e: LayoutChangeEvent) => {
|
|
829
|
-
nestedFlatListPositionY.value = e.nativeEvent.layout.y;
|
|
830
897
|
flatListHeightY.value = e.nativeEvent.layout.height;
|
|
831
898
|
|
|
899
|
+
// If nested in a scroll container.
|
|
900
|
+
if (scrollViewScrollOffsetY) {
|
|
901
|
+
// Timeout fixes a bug where measure returns height 0.
|
|
902
|
+
setTimeout(() => {
|
|
903
|
+
runOnUI(() => {
|
|
904
|
+
const measurement = measure(flatListRef);
|
|
905
|
+
if (!measurement) {
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
flatListPageY.value = measurement.pageY;
|
|
910
|
+
})();
|
|
911
|
+
}, 100);
|
|
912
|
+
}
|
|
913
|
+
|
|
832
914
|
onLayout?.(e);
|
|
833
915
|
},
|
|
834
|
-
[
|
|
916
|
+
[
|
|
917
|
+
flatListRef,
|
|
918
|
+
flatListPageY,
|
|
919
|
+
flatListHeightY,
|
|
920
|
+
scrollViewScrollOffsetY,
|
|
921
|
+
onLayout,
|
|
922
|
+
],
|
|
835
923
|
);
|
|
836
924
|
|
|
837
|
-
const handleRef = (
|
|
838
|
-
|
|
925
|
+
const handleRef = useCallback(
|
|
926
|
+
(value: FlatList<T>) => {
|
|
927
|
+
flatListRef(value);
|
|
928
|
+
|
|
929
|
+
if (typeof ref === 'function') {
|
|
930
|
+
ref(value);
|
|
931
|
+
} else if (ref) {
|
|
932
|
+
ref.current = value;
|
|
933
|
+
}
|
|
934
|
+
},
|
|
935
|
+
[flatListRef, ref],
|
|
936
|
+
);
|
|
839
937
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
938
|
+
const combinedGesture = useMemo(() => {
|
|
939
|
+
// android is able to handle nested scroll view, but not the full height ones like iOS
|
|
940
|
+
if (outerScrollGesture && !(Platform.OS === 'android' && scrollable)) {
|
|
941
|
+
return Gesture.Simultaneous(outerScrollGesture, gestureHandler);
|
|
844
942
|
}
|
|
845
|
-
};
|
|
846
943
|
|
|
847
|
-
|
|
848
|
-
|
|
944
|
+
return gestureHandler;
|
|
945
|
+
}, [scrollable, outerScrollGesture, gestureHandler]);
|
|
946
|
+
|
|
947
|
+
const composedScrollHandler = useComposedEventHandler([
|
|
849
948
|
handleScroll,
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
949
|
+
onScroll || null,
|
|
950
|
+
]);
|
|
951
|
+
|
|
952
|
+
const renderAnimatedCell = useCallback(
|
|
953
|
+
({cellKey, ...props}: CellRendererProps<T>) => (
|
|
954
|
+
<ReorderableListCell
|
|
955
|
+
{...props}
|
|
956
|
+
// forces remount with key change on reorder
|
|
957
|
+
key={`${cellKey}+${props.index}`}
|
|
958
|
+
itemOffset={itemOffset}
|
|
959
|
+
itemHeight={itemHeight}
|
|
960
|
+
dragY={dragY}
|
|
961
|
+
draggedIndex={draggedIndex}
|
|
962
|
+
animationDuration={duration}
|
|
963
|
+
startDrag={startDrag}
|
|
964
|
+
/>
|
|
965
|
+
),
|
|
966
|
+
[itemOffset, itemHeight, dragY, draggedIndex, duration, startDrag],
|
|
967
|
+
);
|
|
968
|
+
|
|
969
|
+
return (
|
|
970
|
+
<ReorderableListContext.Provider value={listContextValue}>
|
|
971
|
+
<GestureDetector gesture={combinedGesture}>
|
|
972
|
+
<AnimatedFlatList
|
|
973
|
+
{...rest}
|
|
974
|
+
ref={handleRef}
|
|
975
|
+
data={data}
|
|
976
|
+
CellRendererComponent={renderAnimatedCell}
|
|
977
|
+
onLayout={handleFlatListLayout}
|
|
978
|
+
onScroll={composedScrollHandler}
|
|
979
|
+
scrollEventThrottle={1}
|
|
980
|
+
horizontal={false}
|
|
981
|
+
removeClippedSubviews={false}
|
|
982
|
+
numColumns={1}
|
|
983
|
+
/>
|
|
984
|
+
</GestureDetector>
|
|
985
|
+
</ReorderableListContext.Provider>
|
|
986
|
+
);
|
|
860
987
|
};
|
|
988
|
+
|
|
989
|
+
const MemoizedReorderableListCore = React.memo(
|
|
990
|
+
React.forwardRef(ReorderableListCore),
|
|
991
|
+
) as <T>(
|
|
992
|
+
props: ReorderableListCoreProps<T> & {
|
|
993
|
+
ref?: React.ForwardedRef<FlatList<T> | null>;
|
|
994
|
+
},
|
|
995
|
+
) => React.ReactElement;
|
|
996
|
+
|
|
997
|
+
export {MemoizedReorderableListCore as ReorderableListCore};
|