hooks.ts 1.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. import { useCallback, useEffect, useRef, useState } from 'react'
  2. interface UseAutoScrollProps {
  3. enabled?: boolean
  4. }
  5. export function useAutoScroll({ enabled = true }: UseAutoScrollProps = {}) {
  6. const [container, setContainer] = useState<HTMLDivElement | null>(null)
  7. const [isSticky, setIsSticky] = useState(true)
  8. const isStickyRef = useRef(true)
  9. const lastScrollHeightRef = useRef<number>(null)
  10. const ref = useCallback((element: HTMLDivElement | null) => {
  11. if (element) {
  12. setContainer(element)
  13. }
  14. }, [])
  15. const scrollToEnd = useCallback(() => {
  16. if (container) {
  17. isStickyRef.current = true
  18. setIsSticky(true)
  19. container.scrollTo({
  20. top: container.scrollHeight,
  21. behavior: 'smooth',
  22. })
  23. }
  24. }, [container])
  25. useEffect(() => {
  26. if (!container || !enabled) return
  27. let timeoutId: NodeJS.Timeout
  28. const resizeObserver = new ResizeObserver(() => {
  29. clearTimeout(timeoutId)
  30. timeoutId = setTimeout(() => {
  31. if (
  32. lastScrollHeightRef.current != null &&
  33. container.scrollHeight !== lastScrollHeightRef.current
  34. ) {
  35. lastScrollHeightRef.current = container.scrollHeight
  36. if (isStickyRef.current) {
  37. scrollToEnd()
  38. }
  39. }
  40. }, 100)
  41. })
  42. const handleScroll = () => {
  43. const isAtBottom =
  44. Math.abs(container.scrollHeight - container.scrollTop - container.clientHeight) < 10
  45. isStickyRef.current = isAtBottom
  46. setIsSticky(isAtBottom)
  47. }
  48. // Observe all children of the container
  49. Array.from(container.children).forEach((child) => {
  50. resizeObserver.observe(child)
  51. })
  52. container.addEventListener('scroll', handleScroll)
  53. return () => {
  54. clearTimeout(timeoutId)
  55. resizeObserver.disconnect()
  56. container.removeEventListener('scroll', handleScroll)
  57. }
  58. }, [container, enabled, scrollToEnd])
  59. return { ref, isSticky, scrollToEnd, setIsSticky }
  60. }