From 68b4038196d6275cc274dc776d89002e2592b55b Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Mon, 29 Dec 2025 14:23:16 -0600 Subject: fix(desktop): more performance/scrolling fixes --- packages/ui/src/hooks/create-auto-scroll.tsx | 86 ++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 11 deletions(-) (limited to 'packages/ui/src/hooks') diff --git a/packages/ui/src/hooks/create-auto-scroll.tsx b/packages/ui/src/hooks/create-auto-scroll.tsx index b780f47c6..1fb2e0c81 100644 --- a/packages/ui/src/hooks/create-auto-scroll.tsx +++ b/packages/ui/src/hooks/create-auto-scroll.tsx @@ -19,38 +19,88 @@ export function createAutoScroll(options: AutoScrollOptions) { let autoScrollTimeout: ReturnType | undefined let isMouseDown = false let cleanupListeners: (() => void) | undefined + let scheduledScroll = false + let scheduledForce = false - function scrollToBottom() { - if (!scrollRef || store.userScrolled || !options.working()) return + function distanceFromBottom() { + if (!scrollRef) return 0 + return scrollRef.scrollHeight - scrollRef.clientHeight - scrollRef.scrollTop + } + function startAutoScroll() { isAutoScrolling = true if (autoScrollTimeout) clearTimeout(autoScrollTimeout) autoScrollTimeout = setTimeout(() => { isAutoScrolling = false }, 1000) + } + + function scrollToBottomNow() { + if (!scrollRef || store.userScrolled || !options.working()) return + + const distance = distanceFromBottom() + if (distance < 2) return + const behavior = distance > 96 ? "auto" : "smooth" + startAutoScroll() scrollRef.scrollTo({ top: scrollRef.scrollHeight, - behavior: "smooth", + behavior, }) } - function forceScrollToBottom() { + function forceScrollToBottomNow() { if (!scrollRef) return - setStore("userScrolled", false) - isAutoScrolling = true - if (autoScrollTimeout) clearTimeout(autoScrollTimeout) - autoScrollTimeout = setTimeout(() => { - isAutoScrolling = false - }, 1000) + if (store.userScrolled) setStore("userScrolled", false) + const distance = distanceFromBottom() + if (distance < 2) return + + startAutoScroll() scrollRef.scrollTo({ top: scrollRef.scrollHeight, - behavior: "smooth", + behavior: "auto", }) } + function scheduleScrollToBottom(force = false) { + if (typeof requestAnimationFrame === "undefined") { + if (force) { + forceScrollToBottomNow() + return + } + scrollToBottomNow() + return + } + + if (force) scheduledForce = true + if (scheduledScroll) return + + scheduledScroll = true + requestAnimationFrame(() => { + scheduledScroll = false + + const shouldForce = scheduledForce + scheduledForce = false + + if (shouldForce) { + forceScrollToBottomNow() + return + } + + scrollToBottomNow() + }) + } + + function scrollToBottom() { + scheduleScrollToBottom(false) + } + + function forceScrollToBottom() { + scheduleScrollToBottom(true) + } + function handleScroll() { if (!scrollRef) return @@ -132,6 +182,20 @@ export function createAutoScroll(options: AutoScrollOptions) { } }) + // Ensure pinned-to-bottom stays pinned during heavy DOM updates + createEffect(() => { + const el = store.contentRef + if (!el) return + + const observer = new MutationObserver(() => { + if (store.userScrolled) return + if (!options.working()) return + scheduleScrollToBottom(false) + }) + observer.observe(el, { childList: true, subtree: true, characterData: true }) + onCleanup(() => observer.disconnect()) + }) + // Handle content resize createResizeObserver( () => store.contentRef, -- cgit v1.2.3