import type { SharedValue } from 'react-native-reanimated'; import { interpolate } from 'react-native-reanimated'; import { scheduleOnRN } from 'react-native-worklets'; /** * Configuration for autoscroll behavior. */ export interface AutoscrollConfig { /** Pixels from viewport edge to trigger autoscroll */ edgeThreshold: number; /** Maximum scroll speed in pixels per frame at edge */ maxScrollSpeed: number; } /** * Input values for autoscroll calculation. */ export interface AutoscrollInput { /** Current scroll offset SharedValue */ scrollOffset: SharedValue; /** Total content height SharedValue */ contentHeight: SharedValue; /** Visible viewport height SharedValue */ visibleHeight: SharedValue; /** Y position of list top on screen SharedValue */ listTopY: SharedValue; /** Last scroll time SharedValue (for throttling) */ lastScrollTime: SharedValue; } /** Throttle interval for scroll calls (~30fps) */ const SCROLL_THROTTLE_MS = 33; /** * Check if autoscroll should trigger and perform scroll if needed. * * This function handles scrolling when the finger is near the top or bottom * edges of the list viewport. It throttles scroll calls to ~30fps to reduce * JS thread pressure. * * @worklet - This function runs on the UI thread * * @param absoluteY - Current finger absoluteY position * @param input - Autoscroll input SharedValues * @param config - Autoscroll configuration * @param scrollToOffset - Callback to scroll the list (will be scheduled on RN thread) */ export function checkAutoscroll( absoluteY: number, input: AutoscrollInput, config: AutoscrollConfig, scrollToOffset: (offset: number) => void, ): void { 'worklet'; const { scrollOffset, contentHeight, visibleHeight, listTopY, lastScrollTime } = input; const { edgeThreshold, maxScrollSpeed } = config; // Calculate finger position relative to list const fingerInList = absoluteY - listTopY.value; // Define edge zones const topEdge = edgeThreshold; const bottomEdge = visibleHeight.value - edgeThreshold; // Current time for throttling const now = Date.now(); const timeSinceLastScroll = now - lastScrollTime.value; // Check top edge - scroll up if (fingerInList < topEdge && scrollOffset.value > 0) { const speed = interpolate( fingerInList, [0, topEdge], [maxScrollSpeed, 0], 'clamp', ); const newOffset = Math.max(0, scrollOffset.value - speed); scrollOffset.value = newOffset; // Throttle actual scroll calls if (timeSinceLastScroll >= SCROLL_THROTTLE_MS) { lastScrollTime.value = now; scheduleOnRN(scrollToOffset, newOffset); } return; } // Check bottom edge - scroll down if (fingerInList > bottomEdge) { const maxOffset = Math.max(0, contentHeight.value - visibleHeight.value); if (scrollOffset.value < maxOffset) { const speed = interpolate( fingerInList, [bottomEdge, visibleHeight.value], [0, maxScrollSpeed], 'clamp', ); const newOffset = Math.min(maxOffset, scrollOffset.value + speed); scrollOffset.value = newOffset; // Throttle actual scroll calls if (timeSinceLastScroll >= SCROLL_THROTTLE_MS) { lastScrollTime.value = now; scheduleOnRN(scrollToOffset, newOffset); } } } }