Skip to content

Commit 21a4304

Browse files
committed
Start cleaning up auto scroll implementation
1 parent c2dc5aa commit 21a4304

File tree

3 files changed

+86
-144
lines changed

3 files changed

+86
-144
lines changed

packages/react-native-sortables/src/providers/shared/AutoScrollProvider.tsx

Lines changed: 80 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
useSharedValue
1111
} from 'react-native-reanimated';
1212

13-
import { OFFSET_EPS } from '../../constants';
1413
import { useDebugContext } from '../../debug';
1514
import { useAnimatableValue } from '../../hooks';
1615
import type { AutoScrollContextType, AutoScrollSettings } from '../../types';
@@ -31,28 +30,21 @@ const { AutoScrollProvider, useAutoScrollContext } = createProvider(
3130
autoScrollSpeed,
3231
scrollableRef
3332
}) => {
34-
const {
35-
activeAnimationProgress,
36-
activeItemKey,
37-
containerRef,
38-
itemDimensions,
39-
touchPosition
40-
} = useCommonValuesContext();
33+
const { activeItemKey, containerRef, itemDimensions, touchPosition } =
34+
useCommonValuesContext();
4135
const debugContext = useDebugContext();
4236

4337
const debugRects = debugContext?.useDebugRects(['top', 'bottom']);
4438
const debugLine = debugContext?.useDebugLine();
4539

4640
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
4741
const scrollOffset = useScrollViewOffset(scrollableRef);
48-
const targetScrollOffset = useSharedValue<null | number>(null);
4942
const dragStartScrollOffset = useAnimatableValue<null | number>(null);
50-
const startContainerPageY = useSharedValue<null | number>(null);
51-
const prevScrollToOffset = useSharedValue<null | number>(null);
43+
const dragScrollOffsetDiff = useSharedValue(0);
5244

5345
const activeItemHeight = useDerivedValue(() => {
5446
const key = activeItemKey.value;
55-
return (key ? itemDimensions.value[key]?.height : null) ?? null;
47+
return key && itemDimensions.value[key]?.height;
5648
});
5749
const offsetThreshold = useAnimatableValue(
5850
autoScrollActivationOffset,
@@ -68,39 +60,86 @@ const { AutoScrollProvider, useAutoScrollContext } = createProvider(
6860

6961
const isFrameCallbackActive = useSharedValue(false);
7062

63+
const hideDebugViews = useCallback(() => {
64+
'worklet';
65+
debugRects?.top?.hide();
66+
debugRects?.bottom?.hide();
67+
debugLine?.hide();
68+
}, [debugLine, debugRects]);
69+
7170
// SMOOTH SCROLL POSITION UPDATER
7271
// Updates the scroll position smoothly
7372
// (quickly at first, then slower if the remaining distance is small)
7473
const frameCallback = useFrameCallback(() => {
75-
const targetOffset = targetScrollOffset.value;
76-
if (!isFrameCallbackActive.value || targetOffset === null) {
74+
if (
75+
activeItemHeight.value === null ||
76+
touchPosition.value === null ||
77+
dragStartScrollOffset.value === null
78+
) {
79+
hideDebugViews();
7780
return;
7881
}
79-
const currentOffset = scrollOffset.value;
80-
const diff = targetOffset - currentOffset;
8182

82-
if (Math.abs(diff) < OFFSET_EPS) {
83-
targetScrollOffset.value = null;
83+
const scrollableMeasurements = measure(scrollableRef);
84+
const containerMeasurements = measure(containerRef);
85+
86+
if (!scrollableMeasurements || !containerMeasurements) {
87+
hideDebugViews();
8488
return;
8589
}
8690

87-
const direction = diff > 0 ? 1 : -1;
88-
const step = speed.value * direction * Math.sqrt(Math.abs(diff));
89-
const nextOffset =
90-
targetOffset > currentOffset
91-
? Math.min(currentOffset + step, targetOffset)
92-
: Math.max(currentOffset + step, targetOffset);
91+
const scrollToOffset =
92+
dragStartScrollOffset.value + dragScrollOffsetDiff.value;
93+
if (Math.abs(scrollOffset.value - scrollToOffset) > 1) {
94+
console.log('scrollToOffset', scrollToOffset, scrollOffset.value);
95+
scrollTo(scrollableRef, 0, scrollToOffset, false);
96+
}
9397

94-
if (
95-
Math.abs(nextOffset - currentOffset) < 0.1 * OFFSET_EPS ||
96-
prevScrollToOffset.value === nextOffset
97-
) {
98-
targetScrollOffset.value = null;
99-
return;
98+
const threshold = offsetThreshold.value;
99+
const touchOffset = touchPosition.value.y;
100+
const { height: sH, pageY: sY } = scrollableMeasurements;
101+
const { height: cH, pageY: cY } = containerMeasurements;
102+
103+
if (debugRects) {
104+
debugRects.top.set({
105+
...DEBUG_COLORS,
106+
height: threshold.top,
107+
y: sY - cY
108+
});
109+
debugRects.bottom.set({
110+
...DEBUG_COLORS,
111+
height: threshold.bottom,
112+
positionOrigin: 'bottom',
113+
y: sY - cY + sH
114+
});
115+
}
116+
if (debugLine) {
117+
debugLine.set({
118+
color: DEBUG_COLORS.backgroundColor,
119+
y: touchOffset
120+
});
100121
}
101122

102-
scrollTo(scrollableRef, 0, nextOffset, false);
103-
prevScrollToOffset.value = nextOffset;
123+
const topDistance = sY + threshold.top - cY;
124+
const bottomDistance = cY + cH - (sY + sH - threshold.bottom);
125+
126+
const topOverflow = sY + threshold.top - (cY + touchOffset);
127+
const bottomOverflow = cY + touchOffset - (sY + sH - threshold.bottom);
128+
129+
const scrollOffsetDiff = scrollOffset.value - dragStartScrollOffset.value;
130+
// Scroll up
131+
if (topDistance > 0 && topOverflow > 0) {
132+
console.log('up dragScrollOffsetDiff.value', dragScrollOffsetDiff.value);
133+
dragScrollOffsetDiff.value = scrollOffsetDiff - 0.05 * topOverflow;
134+
}
135+
// Scroll down
136+
else if (bottomDistance > 0 && bottomOverflow > 0) {
137+
console.log(
138+
'down dragScrollOffsetDiff.value',
139+
dragScrollOffsetDiff.value
140+
);
141+
dragScrollOffsetDiff.value = scrollOffsetDiff + 0.05 * bottomOverflow;
142+
}
104143
}, false);
105144

106145
const toggleFrameCallback = useCallback(
@@ -111,127 +150,32 @@ const { AutoScrollProvider, useAutoScrollContext } = createProvider(
111150
// Enable/disable frame callback
112151
useAnimatedReaction(
113152
() => ({
114-
isEnabled: enabled.value,
115-
itemKey: activeItemKey.value,
116-
progress: activeAnimationProgress.value
153+
activeKey: activeItemKey.value,
154+
isEnabled: enabled.value
117155
}),
118-
({ isEnabled, itemKey, progress }) => {
119-
const shouldBeEnabled = isEnabled && itemKey !== null;
120-
if (
121-
isFrameCallbackActive.value === shouldBeEnabled ||
122-
(itemKey !== null && progress < 0.5)
123-
) {
156+
({ activeKey, isEnabled }) => {
157+
const shouldBeEnabled = isEnabled && activeKey !== null;
158+
if (isFrameCallbackActive.value === shouldBeEnabled) {
124159
return;
125160
}
126-
targetScrollOffset.value = null;
127-
startContainerPageY.value = null;
128-
prevScrollToOffset.value = null;
129161
runOnJS(toggleFrameCallback)(shouldBeEnabled);
130162
isFrameCallbackActive.value = shouldBeEnabled;
131163
}
132164
);
133165

134-
// AUTO SCROLL HANDLER
135-
// Automatically scrolls the container when the active item is near the edge
136-
useAnimatedReaction(
137-
() => {
138-
if (
139-
!enabled.value ||
140-
activeItemHeight.value === null ||
141-
!touchPosition.value
142-
) {
143-
return null;
144-
}
145-
146-
return {
147-
itemHeight: activeItemHeight.value,
148-
threshold: offsetThreshold.value,
149-
touchOffset: touchPosition.value.y
150-
};
151-
},
152-
props => {
153-
const hideDebugViews = () => {
154-
debugRects?.top?.hide();
155-
debugRects?.bottom?.hide();
156-
debugLine?.hide();
157-
};
158-
159-
if (!props) {
160-
hideDebugViews();
161-
return;
162-
}
163-
164-
const scrollableMeasurements = measure(scrollableRef);
165-
const containerMeasurements = measure(containerRef);
166-
167-
if (
168-
!scrollableMeasurements ||
169-
!containerMeasurements ||
170-
dragStartScrollOffset.value === null
171-
) {
172-
hideDebugViews();
173-
return;
174-
}
175-
176-
const { threshold, touchOffset } = props;
177-
const { height: sH, pageY: sY } = scrollableMeasurements;
178-
const { height: cH, pageY: cY } = containerMeasurements;
179-
180-
if (startContainerPageY.value === null) {
181-
startContainerPageY.value = cY;
182-
}
183-
184-
const topDistance = sY + threshold.top - cY;
185-
const bottomDistance = cY + cH - (sY + sH - threshold.bottom);
186-
187-
const topOverflow = sY + threshold.top - (cY + touchOffset);
188-
const bottomOverflow = cY + touchOffset - (sY + sH - threshold.bottom);
189-
190-
if (debugRects) {
191-
debugRects.top.set({
192-
...DEBUG_COLORS,
193-
height: threshold.top,
194-
y: sY - cY
195-
});
196-
debugRects.bottom.set({
197-
...DEBUG_COLORS,
198-
height: threshold.bottom,
199-
positionOrigin: 'bottom',
200-
y: sY - cY + sH
201-
});
202-
}
203-
if (debugLine) {
204-
debugLine.set({
205-
color: DEBUG_COLORS.backgroundColor,
206-
y: touchOffset
207-
});
208-
}
209-
210-
const deltaY = startContainerPageY.value - cY;
211-
const offsetY = dragStartScrollOffset.value + deltaY;
212-
// Scroll up
213-
if (topDistance > 0 && topOverflow > 0) {
214-
targetScrollOffset.value = offsetY - Math.min(topOverflow, topDistance);
215-
}
216-
// Scroll down
217-
else if (bottomDistance > 0 && bottomOverflow > 0) {
218-
targetScrollOffset.value =
219-
offsetY + Math.min(bottomOverflow, bottomDistance);
220-
}
221-
}
222-
);
223-
224166
const updateStartScrollOffset = useCallback(
225167
(providedOffset?: null | number) => {
226168
'worklet';
169+
dragScrollOffsetDiff.value = 0;
227170
dragStartScrollOffset.value =
228-
providedOffset === undefined ? scrollOffset.value : providedOffset;
171+
providedOffset !== undefined ? providedOffset : scrollOffset.value;
229172
},
230-
[dragStartScrollOffset, scrollOffset]
173+
[dragScrollOffsetDiff, dragStartScrollOffset, scrollOffset]
231174
);
232175

233176
return {
234177
value: {
178+
dragScrollOffsetDiff,
235179
dragStartScrollOffset,
236180
scrollOffset,
237181
updateStartScrollOffset

packages/react-native-sortables/src/providers/shared/DragProvider.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ const { DragProvider, useDragContext } = createProvider('Drag')<
7979
const { maybeUpdateSnapDimensions, tryMeasureContainerHeight } =
8080
useMeasurementsContext();
8181
const { updateLayer } = useLayerContext() ?? {};
82-
const { dragStartScrollOffset, scrollOffset, updateStartScrollOffset } =
82+
const { dragScrollOffsetDiff, updateStartScrollOffset } =
8383
useAutoScrollContext() ?? {};
8484
const debugContext = useDebugContext();
8585

@@ -114,10 +114,7 @@ const { DragProvider, useDragContext } = createProvider('Drag')<
114114
offsetX: snapOffsetX.value,
115115
offsetY: snapOffsetY.value,
116116
progress: activeAnimationProgress.value,
117-
scrollOffsetY:
118-
dragStartScrollOffset?.value === -1
119-
? 0
120-
: (scrollOffset?.value ?? 0) - (dragStartScrollOffset?.value ?? 0),
117+
scrollOffsetDiff: dragScrollOffsetDiff?.value ?? 0,
121118
snapDimensions: snapItemDimensions.value,
122119
snapOffset: snapItemOffset.value,
123120
startTouchPosition: dragStartTouchPosition.value,
@@ -133,7 +130,7 @@ const { DragProvider, useDragContext } = createProvider('Drag')<
133130
offsetX,
134131
offsetY,
135132
progress,
136-
scrollOffsetY,
133+
scrollOffsetDiff,
137134
snapDimensions,
138135
snapOffset,
139136
startTouchPosition,
@@ -155,7 +152,7 @@ const { DragProvider, useDragContext } = createProvider('Drag')<
155152

156153
touchPosition.value = {
157154
x: startTouchPosition.x + (translation?.x ?? 0),
158-
y: startTouchPosition.y + (translation?.y ?? 0) + scrollOffsetY
155+
y: startTouchPosition.y + (translation?.y ?? 0) + scrollOffsetDiff
159156
};
160157

161158
if (debugCross) {

packages/react-native-sortables/src/types/providers/shared.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ export type MeasurementsContextType = {
7575
export type AutoScrollContextType = {
7676
scrollOffset: SharedValue<number>;
7777
dragStartScrollOffset: SharedValue<null | number>;
78-
updateStartScrollOffset: (providedOffset?: null | number) => void;
78+
dragScrollOffsetDiff: SharedValue<null | number>;
79+
updateStartScrollOffset: (providedOffset?: null | null | number) => void;
7980
};
8081

8182
// DRAG

0 commit comments

Comments
 (0)