add auto_scroll

This commit is contained in:
Khaleel Al-Adhami 2025-02-10 11:12:32 -08:00
parent 8b2c7291d3
commit 7b9c584b5b
3 changed files with 112 additions and 0 deletions

View File

@ -48,6 +48,7 @@ _SUBMOD_ATTRS: dict[str, list[str]] = {
"get_upload_url",
"selected_files",
],
"auto_scroll": ["auto_scroll"],
}
__getattr__, __dir__, __all__ = lazy_loader.attach(

View File

@ -4,6 +4,7 @@
# ------------------------------------------------------
from . import layout as layout
from .auto_scroll import auto_scroll as auto_scroll
from .banner import ConnectionBanner as ConnectionBanner
from .banner import ConnectionModal as ConnectionModal
from .banner import ConnectionPulser as ConnectionPulser

View File

@ -0,0 +1,110 @@
"""A component that automatically scrolls to the bottom when new content is added."""
from __future__ import annotations
from reflex.components.el.elements.typography import Div
from reflex.constants.compiler import MemoizationDisposition, MemoizationMode
from reflex.utils.imports import ImportDict
from reflex.vars.base import Var
class AutoScroll(Div):
"""A div that automatically scrolls to the bottom when new content is added."""
_memoization_mode = MemoizationMode(disposition=MemoizationDisposition.ALWAYS)
@classmethod
def create(cls, *children, **props):
"""Create an AutoScroll component.
Args:
*children: The children of the component.
**props: The props of the component.
Returns:
An AutoScroll component.
"""
props.setdefault("overflow", "auto")
custom_attrs = props.pop("custom_attrs", {})
custom_attrs["ref"] = Var("containerRef")
return super().create(*children, **props, custom_attrs=custom_attrs)
def add_imports(self) -> ImportDict | list[ImportDict]:
"""Add imports required for the component.
Returns:
The imports required for the component.
"""
return {"react": ["useEffect", "useRef"]}
def add_hooks(self) -> list[str | Var]:
"""Add hooks required for the component.
Returns:
The hooks required for the component.
"""
return [
"const containerRef = useRef(null);",
"const wasNearBottom = useRef(false);",
"const hadScrollbar = useRef(false);",
"""
const checkIfNearBottom = () => {
if (!containerRef.current) return;
const container = containerRef.current;
const nearBottomThreshold = 50; // pixels from bottom to trigger auto-scroll
const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
wasNearBottom.current = distanceFromBottom <= nearBottomThreshold;
// Track if container had a scrollbar
hadScrollbar.current = container.scrollHeight > container.clientHeight;
};
""",
"""
const scrollToBottomIfNeeded = () => {
if (!containerRef.current) return;
const container = containerRef.current;
const hasScrollbarNow = container.scrollHeight > container.clientHeight;
// Scroll if:
// 1. User was near bottom, OR
// 2. Container didn't have scrollbar before but does now
if (wasNearBottom.current || (!hadScrollbar.current && hasScrollbarNow)) {
container.scrollTop = container.scrollHeight;
}
// Update scrollbar state for next check
hadScrollbar.current = hasScrollbarNow;};
""",
"""
useEffect(() => {
const container = containerRef.current;
if (!container) return;
// Create ResizeObserver to detect height changes
const resizeObserver = new ResizeObserver(() => {
scrollToBottomIfNeeded();
});
// Track scroll position before height changes
container.addEventListener('scroll', checkIfNearBottom);
// Initial check
checkIfNearBottom();
// Observe container for size changes
resizeObserver.observe(container);
return () => {
container.removeEventListener('scroll', checkIfNearBottom);
resizeObserver.disconnect();
};
});
""",
]
auto_scroll = AutoScroll.create