import { useRef, useEffect, useCallback, useState } from 'react'; import { FlatList, NativeScrollEvent, NativeSyntheticEvent } from 'react-native'; interface UseScrollToBottomOptions { /** * Whether to scroll to bottom on initial mount/load * @default true */ scrollOnMount?: boolean; /** * Delay before scrolling (ms) - useful for rendering completion * @default 100 */ delay?: number; /** * Distance from bottom (px) to consider "at bottom" * @default 100 */ threshold?: number; } /** * Hook for managing scroll-to-bottom behavior in chat interfaces * * ULTRATHINK SIMPLE SOLUTION: * - Tracks if user is at bottom * - Only auto-scrolls if user is at bottom * - Prevents scroll jumps on content replace * - Allows manual scroll up without interference * * @example * const { scrollViewRef, scrollToBottom, onContentSizeChange, onScroll } = useScrollToBottom(); * * */ export function useScrollToBottom(options: UseScrollToBottomOptions = {}) { const { scrollOnMount = true, delay = 100, threshold = 100, } = options; const scrollViewRef = useRef>(null); const hasMountedRef = useRef(false); const isNearBottomRef = useRef(true); // Assume at bottom initially const contentSizeRef = useRef({ width: 0, height: 0 }); const layoutHeightRef = useRef(0); // Track scroll position to determine if user is at bottom const onScroll = useCallback((event: NativeSyntheticEvent) => { const { contentOffset, contentSize, layoutMeasurement } = event.nativeEvent; // Calculate distance from bottom const distanceFromBottom = contentSize.height - (contentOffset.y + layoutMeasurement.height); // Update near-bottom state isNearBottomRef.current = distanceFromBottom <= threshold; // Store layout height for later use layoutHeightRef.current = layoutMeasurement.height; }, [threshold]); // Scroll to bottom - but only if user is already near bottom const scrollToBottom = useCallback((force: boolean = false) => { if (force || isNearBottomRef.current) { setTimeout(() => { scrollViewRef.current?.scrollToEnd({ animated: false }); }, delay); } }, [delay]); // Handle content size change - scroll if user is at bottom const onContentSizeChange = useCallback((width: number, height: number) => { const prevHeight = contentSizeRef.current.height; contentSizeRef.current = { width, height }; // Initial mount - always scroll to bottom if (!hasMountedRef.current && height > 0) { hasMountedRef.current = true; if (scrollOnMount) { setTimeout(() => { scrollViewRef.current?.scrollToEnd({ animated: false }); }, delay); } return; } // Content grew (new messages) - only scroll if user was at bottom if (height > prevHeight && isNearBottomRef.current) { setTimeout(() => { scrollViewRef.current?.scrollToEnd({ animated: false }); }, delay); } }, [scrollOnMount, delay]); return { scrollViewRef, scrollToBottom, onContentSizeChange, onScroll, }; }