import { ref, watch, onUnmounted, type Ref } from 'vue'; export function useAutoScroll(elementRef: Ref, options: { threshold?: number; } = {}) { const { threshold = 80 } = options; const isUserScrollingUp = ref(false); const shouldAutoScroll = ref(true); const scrollToBottom = (behavior: ScrollBehavior = 'smooth') => { const el = elementRef.value; if (!el) return; el.scrollTo({ top: el.scrollHeight, behavior, }); shouldAutoScroll.value = true; }; const handleScroll = () => { const el = elementRef.value; if (!el) return; const { scrollTop, scrollHeight, clientHeight } = el; const distanceFromBottom = scrollHeight - scrollTop - clientHeight; if (distanceFromBottom <= threshold) { if (isUserScrollingUp.value) { isUserScrollingUp.value = false; shouldAutoScroll.value = true; } } else { isUserScrollingUp.value = true; shouldAutoScroll.value = false; } }; let observer: MutationObserver | null = null; let timeout: NodeJS.Timeout | null = null; watch(elementRef, (newEl, oldEl) => { if (oldEl) { oldEl.removeEventListener('scroll', handleScroll); observer?.disconnect(); if (timeout) { clearTimeout(timeout); timeout = null; } } if (newEl) { newEl.addEventListener('scroll', handleScroll, { passive: true }); observer = new MutationObserver(() => { // Only auto-scroll if user hasn't scrolled up and is near bottom if (!isUserScrollingUp.value && shouldAutoScroll.value) { scrollToBottom('instant'); } }); observer.observe(newEl, { childList: true, subtree: true, characterData: true, }); } }); onUnmounted(() => { elementRef.value?.removeEventListener('scroll', handleScroll); observer?.disconnect(); if (timeout) { clearTimeout(timeout); } }); return { scrollToBottom, isUserScrollingUp, // expose for UI feedback (optional) }; }