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 {}