mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(mothership): keep scroll position when user scrolls up during streaming (#5639)
* fix(mothership): keep scroll position when user scrolls up during streaming * chore(mothership): trim scroll fix comments
This commit is contained in:
+7
-3
@@ -176,19 +176,23 @@ function BoundedViewport({ children, isStreaming }: BoundedViewportProps) {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const rafRef = useRef<number | null>(null)
|
||||
const stickToBottomRef = useRef(true)
|
||||
const prevScrollTopRef = useRef(0)
|
||||
const [hasOverflow, setHasOverflow] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const el = ref.current
|
||||
if (!el) return
|
||||
// Any upward user input detaches auto-stick. A subsequent scroll-to-bottom
|
||||
// (wheel back down or dragging scrollbar) re-attaches it.
|
||||
// Upward user input detaches auto-stick; a downward scroll reaching the
|
||||
// bottom re-attaches it (a small upward flick can't re-stick itself).
|
||||
const handleWheel = (e: WheelEvent) => {
|
||||
if (e.deltaY < 0) stickToBottomRef.current = false
|
||||
}
|
||||
const handleScroll = () => {
|
||||
const distance = el.scrollHeight - el.scrollTop - el.clientHeight
|
||||
if (distance < BOTTOM_STICK_THRESHOLD_PX) stickToBottomRef.current = true
|
||||
if (distance < BOTTOM_STICK_THRESHOLD_PX && el.scrollTop > prevScrollTopRef.current) {
|
||||
stickToBottomRef.current = true
|
||||
}
|
||||
prevScrollTopRef.current = el.scrollTop
|
||||
}
|
||||
el.addEventListener('wheel', handleWheel, { passive: true })
|
||||
el.addEventListener('scroll', handleScroll, { passive: true })
|
||||
|
||||
+10
@@ -339,6 +339,16 @@ export function MothershipChat({
|
||||
rangeExtractor,
|
||||
})
|
||||
|
||||
/**
|
||||
* Instance property — silently ignored if passed as a `useVirtualizer`
|
||||
* option. Skips scroll compensation for the streaming last row: it starts
|
||||
* above the viewport but grows at its bottom edge, so the default dragged
|
||||
* the viewport down in lockstep with growth even after the user scrolled
|
||||
* away. Other rows keep the library default.
|
||||
*/
|
||||
virtualizer.shouldAdjustScrollPositionOnItemSizeChange = (item, _delta, instance) =>
|
||||
item.index !== lastIndex && item.start < (instance.scrollElement?.scrollTop ?? 0)
|
||||
|
||||
const scrolledChatRef = useRef<string | undefined | typeof UNSCROLLED>(UNSCROLLED)
|
||||
const userInputRef = useRef<UserInputHandle>(null)
|
||||
const messageQueueRef = useRef(messageQueue)
|
||||
|
||||
@@ -49,8 +49,8 @@ interface UseAutoScrollOptions {
|
||||
*
|
||||
* Stays pinned to the bottom while content streams in. Detaches immediately
|
||||
* on any upward user gesture (wheel, touch, scrollbar drag, keyboard). Once
|
||||
* detached, the user must scroll back to within {@link REATTACH_THRESHOLD} of
|
||||
* the bottom to re-engage. Each streaming start re-seeds stickiness from the
|
||||
* detached, the user must scroll back down to within {@link REATTACH_THRESHOLD}
|
||||
* of the bottom to re-engage. Each streaming start re-seeds stickiness from the
|
||||
* current scroll position, so a user who scrolled up beforehand stays put.
|
||||
*
|
||||
* Returns `ref` (callback ref for the scroll container) and `scrollToBottom`
|
||||
@@ -164,6 +164,10 @@ export function useAutoScroll(
|
||||
* (pointer held) or a recent keyboard scroll. A programmatic upward scroll, e.g.
|
||||
* a virtualizer re-pinning content on a row-size shrink, has neither and must not
|
||||
* be mistaken for the user scrolling away.
|
||||
*
|
||||
* Re-attach also requires a downward move once detached — a small upward
|
||||
* flick still lands within {@link REATTACH_THRESHOLD}, and would otherwise
|
||||
* re-stick on its own scroll event.
|
||||
*/
|
||||
const onScroll = () => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = el
|
||||
@@ -172,8 +176,9 @@ export function useAutoScroll(
|
||||
const userDriven =
|
||||
pointerDownRef.current ||
|
||||
performance.now() - lastUserGestureAtRef.current < USER_GESTURE_WINDOW
|
||||
const movedDown = scrollTop > prevScrollTopRef.current
|
||||
|
||||
if (distanceFromBottom <= threshold) {
|
||||
if (distanceFromBottom <= threshold && (!userDetachedRef.current || movedDown)) {
|
||||
stickyRef.current = true
|
||||
userDetachedRef.current = false
|
||||
} else if (
|
||||
|
||||
Reference in New Issue
Block a user