Add auto scroll (#4790)
* add auto_scroll * add auto_scroll * add auto_scroll to global * use random id for maximum safety
This commit is contained in:
parent
f4165c9812
commit
3129ddab47
@ -248,6 +248,7 @@ COMPONENTS_CORE_MAPPING: dict = {
|
||||
"selected_files",
|
||||
"upload",
|
||||
],
|
||||
"components.core.auto_scroll": ["auto_scroll"],
|
||||
}
|
||||
|
||||
COMPONENTS_BASE_MAPPING: dict = {
|
||||
|
@ -34,6 +34,7 @@ from .components.component import Component as Component
|
||||
from .components.component import ComponentNamespace as ComponentNamespace
|
||||
from .components.component import NoSSRComponent as NoSSRComponent
|
||||
from .components.component import memo as memo
|
||||
from .components.core.auto_scroll import auto_scroll as auto_scroll
|
||||
from .components.core.banner import connection_banner as connection_banner
|
||||
from .components.core.banner import connection_modal as connection_modal
|
||||
from .components.core.breakpoints import breakpoints as breakpoints
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
111
reflex/components/core/auto_scroll.py
Normal file
111
reflex/components/core/auto_scroll.py
Normal file
@ -0,0 +1,111 @@
|
||||
"""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, get_unique_variable_name
|
||||
|
||||
|
||||
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")
|
||||
props.setdefault("id", get_unique_variable_name())
|
||||
return super().create(*children, **props)
|
||||
|
||||
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.
|
||||
"""
|
||||
ref_name = self.get_ref()
|
||||
return [
|
||||
"const containerRef = useRef(null);",
|
||||
"const wasNearBottom = useRef(false);",
|
||||
"const hadScrollbar = useRef(false);",
|
||||
f"""
|
||||
const checkIfNearBottom = () => {{
|
||||
if (!{ref_name}.current) return;
|
||||
|
||||
const container = {ref_name}.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;
|
||||
}};
|
||||
""",
|
||||
f"""
|
||||
const scrollToBottomIfNeeded = () => {{
|
||||
if (!{ref_name}.current) return;
|
||||
|
||||
const container = {ref_name}.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;
|
||||
}};
|
||||
""",
|
||||
f"""
|
||||
useEffect(() => {{
|
||||
const container = {ref_name}.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
|
103
reflex/components/core/auto_scroll.pyi
Normal file
103
reflex/components/core/auto_scroll.pyi
Normal file
@ -0,0 +1,103 @@
|
||||
"""Stub file for reflex/components/core/auto_scroll.py"""
|
||||
|
||||
# ------------------- DO NOT EDIT ----------------------
|
||||
# This file was generated by `reflex/utils/pyi_generator.py`!
|
||||
# ------------------------------------------------------
|
||||
from typing import Any, Dict, Optional, Union, overload
|
||||
|
||||
from reflex.components.el.elements.typography import Div
|
||||
from reflex.event import EventType
|
||||
from reflex.style import Style
|
||||
from reflex.utils.imports import ImportDict
|
||||
from reflex.vars.base import Var
|
||||
|
||||
class AutoScroll(Div):
|
||||
@overload
|
||||
@classmethod
|
||||
def create( # type: ignore
|
||||
cls,
|
||||
*children,
|
||||
access_key: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
auto_capitalize: Optional[
|
||||
Union[Var[Union[bool, int, str]], bool, int, str]
|
||||
] = None,
|
||||
content_editable: Optional[
|
||||
Union[Var[Union[bool, int, str]], bool, int, str]
|
||||
] = None,
|
||||
context_menu: Optional[
|
||||
Union[Var[Union[bool, int, str]], bool, int, str]
|
||||
] = None,
|
||||
dir: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
draggable: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
enter_key_hint: Optional[
|
||||
Union[Var[Union[bool, int, str]], bool, int, str]
|
||||
] = None,
|
||||
hidden: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
input_mode: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
item_prop: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
lang: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
role: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
slot: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
spell_check: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
tab_index: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
title: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None,
|
||||
style: Optional[Style] = None,
|
||||
key: Optional[Any] = None,
|
||||
id: Optional[Any] = None,
|
||||
class_name: Optional[Any] = None,
|
||||
autofocus: Optional[bool] = None,
|
||||
custom_attrs: Optional[Dict[str, Union[Var, Any]]] = None,
|
||||
on_blur: Optional[EventType[()]] = None,
|
||||
on_click: Optional[EventType[()]] = None,
|
||||
on_context_menu: Optional[EventType[()]] = None,
|
||||
on_double_click: Optional[EventType[()]] = None,
|
||||
on_focus: Optional[EventType[()]] = None,
|
||||
on_mount: Optional[EventType[()]] = None,
|
||||
on_mouse_down: Optional[EventType[()]] = None,
|
||||
on_mouse_enter: Optional[EventType[()]] = None,
|
||||
on_mouse_leave: Optional[EventType[()]] = None,
|
||||
on_mouse_move: Optional[EventType[()]] = None,
|
||||
on_mouse_out: Optional[EventType[()]] = None,
|
||||
on_mouse_over: Optional[EventType[()]] = None,
|
||||
on_mouse_up: Optional[EventType[()]] = None,
|
||||
on_scroll: Optional[EventType[()]] = None,
|
||||
on_unmount: Optional[EventType[()]] = None,
|
||||
**props,
|
||||
) -> "AutoScroll":
|
||||
"""Create an AutoScroll component.
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
dir: Defines the text direction. Allowed values are ltr (Left-To-Right) or rtl (Right-To-Left)
|
||||
draggable: Defines whether the element can be dragged.
|
||||
enter_key_hint: Hints what media types the media element is able to play.
|
||||
hidden: Defines whether the element is hidden.
|
||||
input_mode: Defines the type of the element.
|
||||
item_prop: Defines the name of the element for metadata purposes.
|
||||
lang: Defines the language used in the element.
|
||||
role: Defines the role of the element.
|
||||
slot: Assigns a slot in a shadow DOM shadow tree to an element.
|
||||
spell_check: Defines whether the element may be checked for spelling errors.
|
||||
tab_index: Defines the position of the current element in the tabbing order.
|
||||
title: Defines a tooltip for the element.
|
||||
style: The style of the component.
|
||||
key: A unique key for the component.
|
||||
id: The id for the component.
|
||||
class_name: The class name for the component.
|
||||
autofocus: Whether the component should take the focus once the page is loaded
|
||||
custom_attrs: custom attribute
|
||||
**props: The props of the component.
|
||||
|
||||
Returns:
|
||||
An AutoScroll component.
|
||||
"""
|
||||
...
|
||||
|
||||
def add_imports(self) -> ImportDict | list[ImportDict]: ...
|
||||
def add_hooks(self) -> list[str | Var]: ...
|
||||
|
||||
auto_scroll = AutoScroll.create
|
Loading…
Reference in New Issue
Block a user