Skip to content

Commit 6375847

Browse files
authored
fix: Auto scroll not working when item is not dragged (#56)
## Description This PR fixes the issue with impossibility to auto scroll the scrollable container when the active item is not dragged (but its position exceeds the auto scroll threshold).
1 parent 6da6295 commit 6375847

File tree

4 files changed

+58
-73
lines changed

4 files changed

+58
-73
lines changed

packages/react-native-sortable/src/components/shared/DraggableView.tsx

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -204,18 +204,25 @@ export default function DraggableView({
204204
});
205205

206206
return (
207-
<Animated.View
208-
{...viewProps}
209-
style={[styles.decoration, style, animatedStyle, animatedDecorationStyle]}
210-
onLayout={({
211-
nativeEvent: {
212-
layout: { height, width }
213-
}
214-
}: LayoutChangeEvent) => {
215-
measureItem(key, { height, width });
216-
}}>
217-
<GestureDetector gesture={panGesture}>{children}</GestureDetector>
218-
</Animated.View>
207+
<GestureDetector gesture={panGesture}>
208+
<Animated.View
209+
{...viewProps}
210+
style={[
211+
styles.decoration,
212+
style,
213+
animatedStyle,
214+
animatedDecorationStyle
215+
]}
216+
onLayout={({
217+
nativeEvent: {
218+
layout: { height, width }
219+
}
220+
}: LayoutChangeEvent) => {
221+
measureItem(key, { height, width });
222+
}}>
223+
{children}
224+
</Animated.View>
225+
</GestureDetector>
219226
);
220227
}
221228

packages/react-native-sortable/src/constants/props.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const SHARED_PROPS: RequiredExcept<SharedProps, 'scrollableRef'> = {
88
activeItemShadowOpacity: 0.2,
99
autoScrollActivationOffset: 40,
1010
autoScrollEnabled: true,
11-
autoScrollSpeed: 0.5,
11+
autoScrollSpeed: 1,
1212
dragEnabled: true,
1313
inactiveItemOpacity: 0.5,
1414
inactiveItemScale: 1,

packages/react-native-sortable/src/contexts/shared/AutoScrollProvider.tsx

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@ import { View } from 'react-native';
33
import type { SharedValue } from 'react-native-reanimated';
44
import {
55
measure,
6+
runOnJS,
67
scrollTo,
78
useAnimatedReaction,
89
useAnimatedRef,
910
useDerivedValue,
11+
useFrameCallback,
1012
useScrollViewOffset,
1113
useSharedValue
1214
} from 'react-native-reanimated';
1315

14-
import { useAnimatableValue, useSmoothValueChange } from '../../hooks';
16+
import { OFFSET_EPS } from '../../constants';
17+
import { useAnimatableValue } from '../../hooks';
1518
import type { AutoScrollSettings } from '../../types';
1619
import { createEnhancedContext } from '../utils';
1720
import { useDragContext } from './DragProvider';
@@ -40,8 +43,8 @@ const { AutoScrollProvider, useAutoScrollContext } = createEnhancedContext(
4043

4144
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
4245
const scrollOffset = useScrollViewOffset(scrollableRef);
43-
const targetScrollOffset = useSharedValue(0);
44-
const dragStartScrollOffset = useAnimatableValue(0);
46+
const targetScrollOffset = useSharedValue(-1);
47+
const dragStartScrollOffset = useAnimatableValue(-1);
4548
const containerRef = useAnimatedRef<View>();
4649

4750
const activeItemHeight = useDerivedValue(() => {
@@ -60,10 +63,37 @@ const { AutoScrollProvider, useAutoScrollContext } = createEnhancedContext(
6063
const enabled = useAnimatableValue(autoScrollEnabled);
6164
const speed = useAnimatableValue(autoScrollSpeed);
6265

63-
const currentScrollToValue = useSmoothValueChange(
64-
dragStartScrollOffset,
65-
targetScrollOffset,
66-
speed
66+
// SMOOTH SCROLL POSITION UPDATER
67+
// Updates the scroll position smoothly
68+
// (quickly at first, then slower if the remaining distance is small)
69+
const frameCallback = useFrameCallback(() => {
70+
const currentOffset = scrollOffset.value;
71+
const targetOffset = targetScrollOffset.value;
72+
const diff = targetOffset - currentOffset;
73+
74+
if (Math.abs(diff) < OFFSET_EPS || targetOffset === -1) {
75+
targetScrollOffset.value = -1;
76+
return;
77+
}
78+
79+
const direction = diff > 0 ? 1 : -1;
80+
const step = speed.value * direction * Math.sqrt(Math.abs(diff));
81+
const nextOffset = currentOffset + step;
82+
83+
scrollTo(scrollableRef, 0, nextOffset, false);
84+
});
85+
86+
const toggleFrameCallback = useCallback(
87+
(isEnabled: boolean) => frameCallback.setActive(isEnabled),
88+
[frameCallback]
89+
);
90+
91+
// Enable/disable frame callback based on the auto scroll state
92+
useAnimatedReaction(
93+
() => enabled.value,
94+
isEnabled => {
95+
runOnJS(toggleFrameCallback)(isEnabled);
96+
}
6797
);
6898

6999
// AUTO SCROLL HANDLER
@@ -123,13 +153,6 @@ const { AutoScrollProvider, useAutoScrollContext } = createEnhancedContext(
123153
}
124154
);
125155

126-
useAnimatedReaction(
127-
() => currentScrollToValue.value,
128-
value => {
129-
scrollTo(scrollableRef, 0, value, false);
130-
}
131-
);
132-
133156
const updateStartScrollOffset = useCallback(() => {
134157
'worklet';
135158
dragStartScrollOffset.value = scrollOffset.value;

packages/react-native-sortable/src/hooks/reanimated.ts

Lines changed: 1 addition & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,9 @@
44
import {
55
isSharedValue,
66
type SharedValue,
7-
useAnimatedReaction,
8-
useDerivedValue,
9-
useSharedValue
7+
useDerivedValue
108
} from 'react-native-reanimated';
119

12-
import { OFFSET_EPS } from '../constants';
1310
import type { Animatable } from '../types';
1411

1512
export function useAnimatableValue<V>(value: Animatable<V>): SharedValue<V>;
@@ -28,45 +25,3 @@ export function useAnimatableValue<V, F extends (value: V) => any>(
2825
return modify ? modify(inputValue) : inputValue;
2926
}, [value, modify]);
3027
}
31-
32-
export function useSmoothValueChange(
33-
initialValue: SharedValue<number>,
34-
target: SharedValue<number>,
35-
speed: SharedValue<number>
36-
): SharedValue<number> {
37-
'worklet';
38-
const currentValue = useSharedValue(initialValue.value);
39-
const remainingDifference = useSharedValue(0);
40-
41-
useAnimatedReaction(
42-
() => initialValue.value,
43-
value => {
44-
currentValue.value = value;
45-
},
46-
[initialValue]
47-
);
48-
49-
useAnimatedReaction(
50-
() => ({
51-
mul: speed.value,
52-
remaining: remainingDifference.value,
53-
to: target.value
54-
}),
55-
({ mul, to }) => {
56-
const difference = to - currentValue.value;
57-
const direction = Math.sign(difference);
58-
const step = direction * Math.sqrt(Math.abs(difference)) * mul;
59-
60-
if (Math.abs(difference) > OFFSET_EPS) {
61-
currentValue.value = currentValue.value + step;
62-
} else {
63-
currentValue.value = to;
64-
}
65-
66-
remainingDifference.value = difference - step;
67-
},
68-
[target]
69-
);
70-
71-
return currentValue;
72-
}

0 commit comments

Comments
 (0)