mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-04 08:28:36 +00:00
fix: scroll thread to bottom on composer focus
This commit is contained in:
parent
052e7f1559
commit
c5a735549a
@ -282,10 +282,30 @@ export const ThreadViewport = forwardRef<ThreadViewportHandle, ThreadViewportPro
|
|||||||
}, [messages, atBottom, scrollToBottom]);
|
}, [messages, atBottom, scrollToBottom]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
|
if (keyboardInsetBottom > 0) {
|
||||||
|
userReadingHistoryRef.current = false;
|
||||||
|
scrollToBottom(false, 8, { force: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (userReadingHistoryRef.current) return;
|
if (userReadingHistoryRef.current) return;
|
||||||
scrollToBottom(false, 4);
|
scrollToBottom(false, 4);
|
||||||
}, [keyboardInsetBottom, scrollToBottom]);
|
}, [keyboardInsetBottom, scrollToBottom]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const scrollEl = scrollRef.current;
|
||||||
|
if (!scrollEl) return;
|
||||||
|
|
||||||
|
const onComposerFocus = () => {
|
||||||
|
const active = document.activeElement;
|
||||||
|
if (!hasMessages || !isKeyboardEditableElement(active) || !scrollEl.contains(active)) return;
|
||||||
|
userReadingHistoryRef.current = false;
|
||||||
|
scrollToBottom(false, 8, { force: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("focusin", onComposerFocus);
|
||||||
|
return () => document.removeEventListener("focusin", onComposerFocus);
|
||||||
|
}, [hasMessages, scrollToBottom]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (scrollToBottomSignal <= 0) return;
|
if (scrollToBottomSignal <= 0) return;
|
||||||
userReadingHistoryRef.current = false;
|
userReadingHistoryRef.current = false;
|
||||||
|
|||||||
@ -210,8 +210,7 @@ describe("ThreadViewport", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => expect(scroller).toHaveStyle({ bottom: "320px" }));
|
await waitFor(() => expect(scroller).toHaveStyle({ bottom: "320px" }));
|
||||||
const button = screen.getByRole("button", { name: "Scroll to bottom" });
|
expect(screen.queryByRole("button", { name: "Scroll to bottom" })).not.toBeInTheDocument();
|
||||||
expect(button.parentElement).toHaveStyle({ bottom: "512px" });
|
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
visualViewport.viewport.dispatchEvent(new Event("resize"));
|
visualViewport.viewport.dispatchEvent(new Event("resize"));
|
||||||
@ -222,6 +221,48 @@ describe("ThreadViewport", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("scrolls recent messages into view when the composer receives focus", async () => {
|
||||||
|
const scrollIntoView = vi.fn();
|
||||||
|
const originalScrollIntoView = HTMLElement.prototype.scrollIntoView;
|
||||||
|
HTMLElement.prototype.scrollIntoView = scrollIntoView;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { container } = render(
|
||||||
|
<ThreadViewport
|
||||||
|
messages={messages}
|
||||||
|
isStreaming={false}
|
||||||
|
composer={<textarea aria-label="Message input" />}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
const scroller = container.firstElementChild?.firstElementChild as HTMLElement;
|
||||||
|
Object.defineProperties(scroller, {
|
||||||
|
scrollHeight: { configurable: true, value: 2400 },
|
||||||
|
clientHeight: { configurable: true, value: 600 },
|
||||||
|
scrollTop: { configurable: true, value: 0 },
|
||||||
|
});
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
scroller.dispatchEvent(new Event("scroll"));
|
||||||
|
});
|
||||||
|
scrollIntoView.mockClear();
|
||||||
|
|
||||||
|
const input = screen.getByLabelText("Message input");
|
||||||
|
act(() => {
|
||||||
|
input.focus();
|
||||||
|
fireEvent.focusIn(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(scrollIntoView).toHaveBeenCalledWith({
|
||||||
|
block: "end",
|
||||||
|
behavior: "auto",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
HTMLElement.prototype.scrollIntoView = originalScrollIntoView;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("hides the scroll-to-bottom button when disabled for the welcome view", () => {
|
it("hides the scroll-to-bottom button when disabled for the welcome view", () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<ThreadViewport
|
<ThreadViewport
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user