@assistant-ui/react
Version:
TypeScript/React library for AI Chat
95 lines (94 loc) • 3.52 kB
JavaScript
"use client";
// src/primitives/thread/useThreadViewportAutoScroll.tsx
import { useComposedRefs } from "@radix-ui/react-compose-refs";
import { useCallback, useRef } from "react";
import { useAssistantEvent } from "../../context/index.js";
import { useOnResizeContent } from "../../utils/hooks/useOnResizeContent.js";
import { useOnScrollToBottom } from "../../utils/hooks/useOnScrollToBottom.js";
import { useManagedRef } from "../../utils/hooks/useManagedRef.js";
import { writableStore } from "../../context/ReadonlyStore.js";
import { useThreadViewportStore } from "../../context/react/ThreadViewportContext.js";
var useThreadViewportAutoScroll = ({
autoScroll,
scrollToBottomOnRunStart = true,
scrollToBottomOnInitialize = true,
scrollToBottomOnThreadSwitch = true
}) => {
const divRef = useRef(null);
const threadViewportStore = useThreadViewportStore();
if (autoScroll === void 0) {
autoScroll = threadViewportStore.getState().turnAnchor !== "top";
}
const lastScrollTop = useRef(0);
const scrollingToBottomBehaviorRef = useRef(null);
const scrollToBottom = useCallback((behavior) => {
const div = divRef.current;
if (!div) return;
scrollingToBottomBehaviorRef.current = behavior;
div.scrollTo({ top: div.scrollHeight, behavior });
}, []);
const handleScroll = () => {
const div = divRef.current;
if (!div) return;
const isAtBottom = threadViewportStore.getState().isAtBottom;
const newIsAtBottom = Math.abs(div.scrollHeight - div.scrollTop - div.clientHeight) < 1 || div.scrollHeight <= div.clientHeight;
if (!newIsAtBottom && lastScrollTop.current < div.scrollTop) {
} else {
if (newIsAtBottom) {
scrollingToBottomBehaviorRef.current = null;
}
const shouldUpdate = newIsAtBottom || scrollingToBottomBehaviorRef.current === null;
if (shouldUpdate && newIsAtBottom !== isAtBottom) {
writableStore(threadViewportStore).setState({
isAtBottom: newIsAtBottom
});
}
}
lastScrollTop.current = div.scrollTop;
};
const resizeRef = useOnResizeContent(() => {
const scrollBehavior = scrollingToBottomBehaviorRef.current;
if (scrollBehavior) {
scrollToBottom(scrollBehavior);
} else if (autoScroll && threadViewportStore.getState().isAtBottom) {
scrollToBottom("instant");
}
handleScroll();
});
const scrollRef = useManagedRef((el) => {
el.addEventListener("scroll", handleScroll);
return () => {
el.removeEventListener("scroll", handleScroll);
};
});
useOnScrollToBottom(({ behavior }) => {
scrollToBottom(behavior);
});
useAssistantEvent("thread.run-start", () => {
if (!scrollToBottomOnRunStart) return;
scrollingToBottomBehaviorRef.current = "auto";
requestAnimationFrame(() => {
scrollToBottom("auto");
});
});
useAssistantEvent("thread.initialize", () => {
if (!scrollToBottomOnInitialize) return;
scrollingToBottomBehaviorRef.current = "instant";
requestAnimationFrame(() => {
scrollToBottom("instant");
});
});
useAssistantEvent("thread-list-item.switched-to", () => {
if (!scrollToBottomOnThreadSwitch) return;
scrollingToBottomBehaviorRef.current = "instant";
requestAnimationFrame(() => {
scrollToBottom("instant");
});
});
const autoScrollRef = useComposedRefs(resizeRef, scrollRef, divRef);
return autoScrollRef;
};
export {
useThreadViewportAutoScroll
};
//# sourceMappingURL=useThreadViewportAutoScroll.js.map