Spaces:
Running
Running
File size: 1,782 Bytes
7bf1507 feb88db bf373ae 59de250 421ce25 cb5990d 421ce25 cb5990d 421ce25 cb5990d 421ce25 cb5990d 421ce25 cb5990d 421ce25 cb5990d 421ce25 7bf1507 421ce25 cb5990d feb88db cb5990d 421ce25 cb5990d 421ce25 cb5990d 070d5c7 cb5990d 070d5c7 421ce25 cb5990d 421ce25 cb5990d 98051f8 421ce25 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
import { navigating } from "$app/state";
import { tick } from "svelte";
export const snapScrollToBottom = (node: HTMLElement, dependency: unknown) => {
let isDetached = false;
const threshold = 50; // Distance from bottom to consider "attached"
const isNearBottom = () => {
const { scrollTop, scrollHeight, clientHeight } = node;
// Use Math.abs for float precision safety, though distances are usually positive
return Math.abs(scrollHeight - scrollTop - clientHeight) <= threshold;
};
const updateScrollPosition = () => {
if (!isDetached) {
node.scrollTo({ top: node.scrollHeight, behavior: "instant" });
}
};
const onScroll = () => {
// If the user is near the bottom, they are attached.
// If they scroll up (away from bottom), they detach.
if (isNearBottom()) {
isDetached = false;
} else {
isDetached = true;
}
};
const update = async (_options: { force?: boolean } = {}) => {
const { force = false } = _options;
if (!force && isDetached && !navigating.to) return;
// Wait for DOM updates (e.g. new message rendered)
await tick();
node.scrollTo({ top: node.scrollHeight, behavior: "instant" });
};
// Observe content size changes (e.g. streaming responses, images loading)
// This ensures we stay at the bottom even if the container size doesn't change
// but the content grows.
const observer = new ResizeObserver(() => {
updateScrollPosition();
});
if (node.firstElementChild) {
observer.observe(node.firstElementChild);
} else {
observer.observe(node);
}
node.addEventListener("scroll", onScroll);
// Check initial state
if (dependency) {
update({ force: true });
}
return {
update,
destroy: () => {
node.removeEventListener("scroll", onScroll);
observer.disconnect();
},
};
};
|