summaryrefslogtreecommitdiffhomepage
path: root/packages/ui/src/hooks
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-12-29 14:23:16 -0600
committerAdam <[email protected]>2025-12-29 14:23:41 -0600
commit68b4038196d6275cc274dc776d89002e2592b55b (patch)
tree33b610d7cefee4dfd4bf94960fb9174fa899fd5c /packages/ui/src/hooks
parent31092149002eb69111f4b94a16f875e101c61432 (diff)
downloadopencode-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.tsx86
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,