1- import { RefObject , useEffect , useLayoutEffect , useRef } from 'react'
1+ import { RefObject , useEffect , useLayoutEffect , useRef , useState } from 'react'
22import { pixelartIcons } from '../PixelartIcon'
33
44export const useScrollBehavior = (
@@ -12,6 +12,8 @@ export const useScrollBehavior = (
1212 }
1313) => {
1414 const openedWasAtBottom = useRef ( true ) // before new messages
15+ const [ currentlyAtBottom , setCurrentlyAtBottom ] = useState ( true )
16+ const scrollTimeoutRef = useRef < NodeJS . Timeout | null > ( null )
1517
1618 const isAtBottom = ( ) => {
1719 if ( ! elementRef . current ) return true
@@ -20,17 +22,30 @@ export const useScrollBehavior = (
2022 return distanceFromBottom < 1
2123 }
2224
23- const scrollToBottom = ( ) => {
24- if ( elementRef . current ) {
25- elementRef . current . scrollTop = elementRef . current . scrollHeight
26- setTimeout ( ( ) => {
27- if ( ! elementRef . current ) return
28- elementRef . current . scrollTo ( {
29- top : elementRef . current . scrollHeight ,
30- behavior : 'instant'
31- } )
32- } , 0 )
25+ const scrollToBottom = ( behavior : ScrollBehavior = 'instant' ) => {
26+ if ( ! elementRef . current ) return
27+
28+ // Clear any existing scroll timeout
29+ if ( scrollTimeoutRef . current ) {
30+ clearTimeout ( scrollTimeoutRef . current )
3331 }
32+
33+ const el = elementRef . current
34+
35+ // Immediate scroll
36+ el . scrollTop = el . scrollHeight
37+
38+ // Double-check after a short delay to ensure we're really at the bottom
39+ scrollTimeoutRef . current = setTimeout ( ( ) => {
40+ if ( ! elementRef . current ) return
41+ const el = elementRef . current
42+ el . scrollTo ( {
43+ top : el . scrollHeight ,
44+ behavior
45+ } )
46+ setCurrentlyAtBottom ( true )
47+ openedWasAtBottom . current = true
48+ } , 5 )
3449 }
3550
3651 // Handle scroll position tracking
@@ -39,18 +54,28 @@ export const useScrollBehavior = (
3954 if ( ! element ) return
4055
4156 const handleScroll = ( ) => {
42- openedWasAtBottom . current = isAtBottom ( )
57+ const atBottom = isAtBottom ( )
58+ openedWasAtBottom . current = atBottom
59+ setCurrentlyAtBottom ( atBottom )
4360 }
4461
4562 element . addEventListener ( 'scroll' , handleScroll )
46- return ( ) => element . removeEventListener ( 'scroll' , handleScroll )
63+ return ( ) => {
64+ element . removeEventListener ( 'scroll' , handleScroll )
65+ if ( scrollTimeoutRef . current ) {
66+ clearTimeout ( scrollTimeoutRef . current )
67+ }
68+ }
4769 } , [ ] )
4870
4971 // Handle opened state changes
5072 useLayoutEffect ( ( ) => {
5173 if ( opened ) {
52- openedWasAtBottom . current = true
53- } else {
74+ // Wait a frame before scrolling to ensure DOM has updated
75+ requestAnimationFrame ( ( ) => {
76+ scrollToBottom ( )
77+ } )
78+ } else if ( elementRef . current ) {
5479 scrollToBottom ( )
5580 }
5681 } , [ opened ] )
@@ -64,6 +89,8 @@ export const useScrollBehavior = (
6489
6590 return {
6691 scrollToBottom,
67- isAtBottom
92+ isAtBottom,
93+ wasAtBottom : ( ) => openedWasAtBottom . current ,
94+ currentlyAtBottom
6895 }
6996}
0 commit comments