import { RootState } from 'domains/store' import { selectUnreadEventIds } from 'domains/store/selectors' import { useVisibility } from 'domains/visibility/hooks' import { RefObject } from 'preact' import { MutableRef, useCallback, useEffect, useRef, useState, } from 'preact/hooks' import { useSelector } from 'react-redux' import { timeout } from 'ui/hooks/focus-helper-hooks' import { useEvents, useLoadedImageEventIds, useSeamlyIsLoading, } from 'ui/hooks/seamly-state-hooks' /** * Threshold defines how close to the bottom user needs to scroll for it to be treated as the bottom of the conversation. */ const THRESHOLD = 10 const useChatScroll = ( eventRefs: Record>, ): { containerRef: MutableRef unreadIds: (string | undefined)[] scrollToRef: () => void scrollToBottom: () => void } => { const containerRef = useRef(null) const [hasScrolledToBottom, setHasScrolledToBottom] = useState(true) const unreadIds = useSelector(selectUnreadEventIds) const events = useEvents() const isLoading = useSeamlyIsLoading() const { isOpen } = useVisibility() const loadedImageEventIds = useLoadedImageEventIds() const { processingFileUploads, isLastEventFromClient } = useSelector( ({ state }: RootState) => state, ) useEffect(() => { const element = containerRef.current if (!element) return () => {} const onScroll = () => { const { scrollHeight, scrollTop, clientHeight } = element const hasScrolledUp = Math.abs(scrollHeight - scrollTop - clientHeight) > THRESHOLD setHasScrolledToBottom(!hasScrolledUp) } element.addEventListener('scroll', onScroll) return () => element.removeEventListener('scroll', onScroll) }, []) const scrollToBottom = useCallback(() => { requestAnimationFrame(async () => { await timeout(30) containerRef.current?.scrollTo({ top: containerRef.current?.scrollHeight, left: 0, behavior: 'auto', }) }) }, []) useEffect(() => { if (isLastEventFromClient) { scrollToBottom() } }, [isLastEventFromClient, scrollToBottom]) useEffect(() => { if (hasScrolledToBottom) { scrollToBottom() } }, [ hasScrolledToBottom, unreadIds, events, isLoading, isOpen, loadedImageEventIds, processingFileUploads, scrollToBottom, ]) const scrollToRef = () => { const firstUnread = unreadIds.at(0) if (firstUnread !== undefined) { const ref = eventRefs[firstUnread]?.current // We scroll to the oldest unread event containerRef.current?.scrollTo({ top: ref?.offsetTop, behavior: 'auto', left: 0, }) } } return { containerRef, unreadIds, scrollToRef, scrollToBottom, } } export default useChatScroll