add auto_scroll
This commit is contained in:
parent
8b2c7291d3
commit
7b9c584b5b
@ -48,6 +48,7 @@ _SUBMOD_ATTRS: dict[str, list[str]] = {
|
|||||||
"get_upload_url",
|
"get_upload_url",
|
||||||
"selected_files",
|
"selected_files",
|
||||||
],
|
],
|
||||||
|
"auto_scroll": ["auto_scroll"],
|
||||||
}
|
}
|
||||||
|
|
||||||
__getattr__, __dir__, __all__ = lazy_loader.attach(
|
__getattr__, __dir__, __all__ = lazy_loader.attach(
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
# ------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
|
|
||||||
from . import layout as layout
|
from . import layout as layout
|
||||||
|
from .auto_scroll import auto_scroll as auto_scroll
|
||||||
from .banner import ConnectionBanner as ConnectionBanner
|
from .banner import ConnectionBanner as ConnectionBanner
|
||||||
from .banner import ConnectionModal as ConnectionModal
|
from .banner import ConnectionModal as ConnectionModal
|
||||||
from .banner import ConnectionPulser as ConnectionPulser
|
from .banner import ConnectionPulser as ConnectionPulser
|
||||||
|
110
reflex/components/core/auto_scroll.py
Normal file
110
reflex/components/core/auto_scroll.py
Normal 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
|
Loading…
Reference in New Issue
Block a user