dioxus_html/events/
mounted.rs

1//! Handles querying data from the renderer
2
3use std::{
4    fmt::{Debug, Display, Formatter},
5    future::Future,
6    pin::Pin,
7};
8
9/// An Element that has been rendered and allows reading and modifying information about it.
10///
11/// Different platforms will have different implementations and different levels of support for this trait. Renderers that do not support specific features will return `None` for those queries.
12// we can not use async_trait here because it does not create a trait that is object safe
13pub trait RenderedElementBacking: std::any::Any {
14    /// return self as Any
15    fn as_any(&self) -> &dyn std::any::Any;
16
17    /// Get the number of pixels that an element's content is scrolled
18    fn get_scroll_offset(&self) -> Pin<Box<dyn Future<Output = MountedResult<PixelsVector2D>>>> {
19        Box::pin(async { Err(MountedError::NotSupported) })
20    }
21
22    /// Get the size of an element's content, including content not visible on the screen due to overflow
23    #[allow(clippy::type_complexity)]
24    fn get_scroll_size(&self) -> Pin<Box<dyn Future<Output = MountedResult<PixelsSize>>>> {
25        Box::pin(async { Err(MountedError::NotSupported) })
26    }
27
28    /// Get the bounding rectangle of the element relative to the viewport (this does not include the scroll position)
29    #[allow(clippy::type_complexity)]
30    fn get_client_rect(&self) -> Pin<Box<dyn Future<Output = MountedResult<PixelsRect>>>> {
31        Box::pin(async { Err(MountedError::NotSupported) })
32    }
33
34    /// Scroll to make the element visible
35    fn scroll_to(
36        &self,
37        _behavior: ScrollBehavior,
38    ) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {
39        Box::pin(async { Err(MountedError::NotSupported) })
40    }
41
42    /// Set the focus on the element
43    fn set_focus(&self, _focus: bool) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {
44        Box::pin(async { Err(MountedError::NotSupported) })
45    }
46}
47
48impl RenderedElementBacking for () {
49    fn as_any(&self) -> &dyn std::any::Any {
50        self
51    }
52}
53
54/// The way that scrolling should be performed
55#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
56#[doc(alias = "ScrollIntoViewOptions")]
57pub enum ScrollBehavior {
58    /// Scroll to the element immediately
59    #[cfg_attr(feature = "serialize", serde(rename = "instant"))]
60    Instant,
61    /// Scroll to the element smoothly
62    #[cfg_attr(feature = "serialize", serde(rename = "smooth"))]
63    Smooth,
64}
65
66/// An Element that has been rendered and allows reading and modifying information about it.
67///
68/// Different platforms will have different implementations and different levels of support for this trait. Renderers that do not support specific features will return `None` for those queries.
69pub struct MountedData {
70    inner: Box<dyn RenderedElementBacking>,
71}
72
73impl Debug for MountedData {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        f.debug_struct("MountedData").finish()
76    }
77}
78
79impl<E: RenderedElementBacking> From<E> for MountedData {
80    fn from(e: E) -> Self {
81        Self { inner: Box::new(e) }
82    }
83}
84
85impl MountedData {
86    /// Create a new MountedData
87    pub fn new(registry: impl RenderedElementBacking + 'static) -> Self {
88        Self {
89            inner: Box::new(registry),
90        }
91    }
92
93    /// Get the number of pixels that an element's content is scrolled
94    #[doc(alias = "scrollTop")]
95    #[doc(alias = "scrollLeft")]
96    pub async fn get_scroll_offset(&self) -> MountedResult<PixelsVector2D> {
97        self.inner.get_scroll_offset().await
98    }
99
100    /// Get the size of an element's content, including content not visible on the screen due to overflow
101    #[doc(alias = "scrollWidth")]
102    #[doc(alias = "scrollHeight")]
103    pub async fn get_scroll_size(&self) -> MountedResult<PixelsSize> {
104        self.inner.get_scroll_size().await
105    }
106
107    /// Get the bounding rectangle of the element relative to the viewport (this does not include the scroll position)
108    #[doc(alias = "getBoundingClientRect")]
109    pub async fn get_client_rect(&self) -> MountedResult<PixelsRect> {
110        self.inner.get_client_rect().await
111    }
112
113    /// Scroll to make the element visible
114    #[doc(alias = "scrollIntoView")]
115    pub fn scroll_to(
116        &self,
117        behavior: ScrollBehavior,
118    ) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {
119        self.inner.scroll_to(behavior)
120    }
121
122    /// Set the focus on the element
123    #[doc(alias = "focus")]
124    #[doc(alias = "blur")]
125    pub fn set_focus(&self, focus: bool) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {
126        self.inner.set_focus(focus)
127    }
128
129    /// Downcast this event to a concrete event type
130    #[inline(always)]
131    pub fn downcast<T: 'static>(&self) -> Option<&T> {
132        self.inner.as_any().downcast_ref::<T>()
133    }
134}
135
136use dioxus_core::Event;
137
138use crate::geometry::{PixelsRect, PixelsSize, PixelsVector2D};
139
140pub type MountedEvent = Event<MountedData>;
141
142impl_event! [
143    MountedData;
144
145    #[doc(alias = "ref")]
146    #[doc(alias = "createRef")]
147    #[doc(alias = "useRef")]
148    /// The onmounted event is fired when the element is first added to the DOM. This event gives you a [`MountedData`] object and lets you interact with the raw DOM element.
149    ///
150    /// This event is fired once per element. If you need to access the element multiple times, you can store the [`MountedData`] object in a [`use_signal`] hook and use it as needed.
151    ///
152    /// # Examples
153    ///
154    /// ```rust, no_run
155    /// # use dioxus::prelude::*;
156    /// fn App() -> Element {
157    ///     let mut header_element = use_signal(|| None);
158    ///
159    ///     rsx! {
160    ///         div {
161    ///             h1 {
162    ///                 // The onmounted event will run the first time the h1 element is mounted
163    ///                 onmounted: move |element| header_element.set(Some(element.data())),
164    ///                 "Scroll to top example"
165    ///             }
166    ///
167    ///             for i in 0..100 {
168    ///                 div { "Item {i}" }
169    ///             }
170    ///
171    ///             button {
172    ///                 // When you click the button, if the header element has been mounted, we scroll to that element
173    ///                 onclick: move |_| async move {
174    ///                     if let Some(header) = header_element.cloned() {
175    ///                         let _ = header.scroll_to(ScrollBehavior::Smooth).await;
176    ///                     }
177    ///                 },
178    ///                 "Scroll to top"
179    ///             }
180    ///         }
181    ///     }
182    /// }
183    /// ```
184    ///
185    /// The `MountedData` struct contains cross platform APIs that work on the desktop, mobile, liveview and web platforms. For the web platform, you can also downcast the `MountedData` event to the `web-sys::Element` type for more web specific APIs:
186    ///
187    /// ```rust, no_run
188    /// # use dioxus::prelude::*;
189    /// # use dioxus_web::WebEventExt;
190    /// fn App() -> Element {
191    ///     rsx! {
192    ///         div {
193    ///             id: "some-id",
194    ///             onmounted: move |element| {
195    ///                 // You can use the web_event trait to downcast the element to a web specific event. For the mounted event, this will be a web_sys::Element
196    ///                 let web_sys_element = element.as_web_event();
197    ///                 assert_eq!(web_sys_element.id(), "some-id");
198    ///             }
199    ///         }
200    ///     }
201    /// }
202    /// ```
203    onmounted
204];
205
206/// The MountedResult type for the MountedData
207pub type MountedResult<T> = Result<T, MountedError>;
208
209#[derive(Debug)]
210/// The error type for the MountedData
211#[non_exhaustive]
212pub enum MountedError {
213    /// The renderer does not support the requested operation
214    NotSupported,
215    /// The element was not found
216    OperationFailed(Box<dyn std::error::Error>),
217}
218
219impl Display for MountedError {
220    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
221        match self {
222            MountedError::NotSupported => {
223                write!(f, "The renderer does not support the requested operation")
224            }
225            MountedError::OperationFailed(e) => {
226                write!(f, "The operation failed: {}", e)
227            }
228        }
229    }
230}
231
232impl std::error::Error for MountedError {}