dioxus_floating/
scrollable_view.rs

1use std::rc::Rc;
2
3use dioxus::{html::geometry::PixelsVector2D, prelude::*};
4
5use crate::ScrollState;
6
7/// A scrollable container that provides context for floating elements.
8///
9/// `ScrollableView` is the core component of the library. It tracks its own
10/// position, dimensions, and scroll state, providing this data via context
11/// to child hooks like `use_placement`.
12///
13/// # Note on Styles:
14/// Ensure you provide height and overflow styles (e.g., `h-full overflow-auto`)
15/// via the `class` or `style` props, as the component does not apply them by default.
16///
17/// # Example
18///
19/// ```rust,norun
20/// use dioxus::prelude::*;
21/// use dioxus_floating::{use_scroll_context, ScrollableView, ScrollState};
22///
23/// #[component]
24/// fn MyComponent() -> Element {
25///     rsx! {
26///         ScrollableView {
27///             class: "h-screen w-full overflow-y-auto",
28///             on_scroll: move |state: ScrollState| println!("Scrolled to: {}", state.state.y),
29///         
30///             div { class: "h-[2000px]", "Very long content..." }
31///         
32///             // Any floating elements inside will be positioned correctly
33///             MyDropdown {}
34///         }
35///     }
36/// }
37///
38/// #[component]
39/// fn MyDropdown() -> Element { let ctx = use_scroll_context(); rsx! {} }
40/// ```
41#[component]
42pub fn ScrollableView(
43    #[props(default)] id: Option<String>,
44    #[props(default)] class: String,
45    #[props(default)] style: String,
46    children: Element,
47    #[props(into)] on_scroll: Option<EventHandler<ScrollState>>,
48) -> Element {
49    let floating = crate::use_floating();
50
51    let mut scrollable_ref = use_signal(|| Option::<Rc<MountedData>>::None);
52    let mut scroll_state = use_signal(|| Option::<ScrollState>::None);
53
54    use_context_provider(move || ScrollableContext {
55        scrollable_ref,
56        scroll_state,
57    });
58
59    rsx! {
60        div { id: id, class: class, style: style,
61            onmounted: move |evt: MountedEvent| {
62                scrollable_ref.set(Some(evt.data.clone()));
63                let mounted_data = evt.data.clone();
64                spawn(async move {
65                    let state = floating.generate_scroll_state_from_mounted(mounted_data).await;
66                    scroll_state.set(Some(state));
67                });
68            },
69            onresize: move |evt: ResizeEvent| {
70                scroll_state.with_mut(move |sstate| {
71                    if let Some(state) = sstate {
72                        if let Ok(size) = evt.get_border_box_size() {
73                            state.bounds = size;
74                        }
75
76                        *sstate = Some(state.to_owned());
77                    }
78                });
79                if let Some(scrollable) = scrollable_ref() {
80                    spawn(async move {
81                        if let Ok(size) = scrollable.get_scroll_size().await {
82                            scroll_state.with_mut(move |sstate| {
83                                if let Some(state) = sstate {
84                                    state.size = size;
85                                    *sstate = Some(state.to_owned());
86                                }
87                            });
88                        }
89                    });
90                }
91            },
92            onscroll: move |evt: ScrollEvent| {
93                let new_state = floating.generate_scroll_state(evt);
94                scroll_state.set(Some(new_state));
95                if let Some(cb) = on_scroll { cb.call(new_state); }
96            },
97
98            {children}
99        }
100    }
101}
102
103/// Context provided by the [ScrollableView] component.
104///
105/// It contains reactive signals for the scroll state and a reference to the
106/// underlying DOM element, along with methods to programmatically control scrolling.
107#[derive(Debug, Clone, Copy)]
108pub struct ScrollableContext {
109    /// A reactive signal containing the [MountedData] of the scrollable container.
110    pub scrollable_ref: Signal<Option<Rc<MountedData>>>,
111
112    /// A reactive signal containing the current [ScrollState] (dimensions, offset, etc.).
113    pub scroll_state: Signal<Option<ScrollState>>,
114}
115
116impl ScrollableContext {
117    /// Forces a re-calculation of the scroll content size and current offset.
118    /// Useful when the content inside changes but the container's outer bounds remain the same.
119    pub async fn reload(&mut self) {
120        if let Some(data) = self.scrollable_ref.peek().as_ref() {
121            // Мы используем логику из Floating, которую ты уже написал
122            let floating = crate::Floating::default();
123            let new_state = floating
124                .generate_scroll_state_from_mounted(data.clone())
125                .await;
126
127            // Обновляем сигнал
128            self.scroll_state.set(Some(new_state));
129        }
130    }
131
132    /// Programmatically scrolls the container by a given offset.
133    ///
134    /// # Example
135    /// ```rust
136    /// use dioxus::prelude::*;
137    /// use dioxus::html::geometry::PixelsVector2D;
138    /// use dioxus_floating::use_scroll_context;
139    ///
140    /// #[component]
141    /// fn MyComponent() -> Element {
142    ///     let ctx = use_scroll_context();
143    ///     use_effect(move || {
144    ///         spawn(async move {
145    ///             ctx.scroll(PixelsVector2D::new(0.0, 100.0), ScrollBehavior::Smooth).await;
146    ///         });
147    ///     });
148    ///     rsx! {}
149    /// }
150    /// ```
151    pub async fn scroll(&self, coordinates: PixelsVector2D, behavior: ScrollBehavior) {
152        if let Some(data) = self.scrollable_ref.peek().as_ref() {
153            let _ = data.scroll(coordinates, behavior).await;
154        }
155    }
156
157    /// Scrolls to a specific position (e.g., top or bottom) based on the behavior.
158    pub async fn scroll_to(&self, behavior: ScrollBehavior) {
159        if let Some(data) = self.scrollable_ref.peek().as_ref() {
160            let _ = data.scroll_to(behavior).await;
161        }
162    }
163
164    /// Scrolls the container using advanced options (like specific element alignment).
165    pub async fn scroll_to_with_options(&self, options: ScrollToOptions) {
166        if let Some(data) = self.scrollable_ref.peek().as_ref() {
167            let _ = data.scroll_to_with_options(options).await;
168        }
169    }
170}