diff options
| author | Adam <[email protected]> | 2025-12-29 14:23:16 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-12-29 14:23:41 -0600 |
| commit | 68b4038196d6275cc274dc776d89002e2592b55b (patch) | |
| tree | 33b610d7cefee4dfd4bf94960fb9174fa899fd5c /packages/ui/src/hooks | |
| parent | 31092149002eb69111f4b94a16f875e101c61432 (diff) | |
| download | opencode-68b4038196d6275cc274dc776d89002e2592b55b.tar.gz opencode-68b4038196d6275cc274dc776d89002e2592b55b.zip | |
fix(desktop): more performance/scrolling fixes
Diffstat (limited to 'packages/ui/src/hooks')
| -rw-r--r-- | packages/ui/src/hooks/create-auto-scroll.tsx | 86 |
1 files changed, 75 insertions, 11 deletions
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<typeof setTimeout> | 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, |
