leptos_use/core/
element_maybe_signal.rs

1use leptos::prelude::{guards::ReadGuard, *};
2use leptos::reactive::wrappers::read::Signal;
3use send_wrapper::SendWrapper;
4use std::{ops::Deref, rc::Rc, time::Duration};
5
6use crate::{
7    UseMutationObserverOptions, UseMutationObserverReturn, use_mutation_observer_with_options,
8};
9
10/// Used as an argument type to make it easily possible to pass either
11///
12/// * a `&str` for example "div > p.some-class",
13/// * a `web_sys` element that implements `E` (for example `EventTarget`, `Element` or `HtmlElement`),
14/// * an `Option<T>` where `T` is the web_sys element,
15/// * a `Signal<T>`, `RwSignal<T>`, `ReadSignal<T>` or `Memo<T>` where `T` is the web_sys element or a String,
16/// * a `Signal<Option<T>>` where `T` is the web_sys element,
17/// * a `Signal<SendWrapper<T>>` where `T` is the web_sys element,
18/// * a `NodeRef`
19///
20/// into a function. Used for example in [`fn@crate::use_event_listener`].
21///
22/// ```
23/// # use leptos::{html::Div, prelude::*};
24/// # use leptos_use::{use_element_size};
25/// # use send_wrapper::SendWrapper;
26/// #
27/// # #[component]
28/// # fn Demo() -> impl IntoView {
29/// let test = "div > p.some-class";
30/// use_element_size(&test); // &str
31/// use_element_size(document().body()); // Option<web_sys::Element>
32/// use_element_size(document().body().unwrap()); // web_sys::Element
33///
34/// let (string_signal, _set_string_signal) = signal("div > p.some-class".to_string());
35/// use_element_size(string_signal); // Signal<String>
36///
37/// let (el_signal, _set_el_signal) = signal(
38///     Some(SendWrapper::new(
39///         document().query_selector("div > p.some-class").unwrap().unwrap()
40///     ))
41/// );
42/// use_element_size(el_signal); // Signal<Option<SendWrapper<web_sys::Element>>>
43///
44/// let (el_signal_send_wrapper, _set_el_signal_send_wrapper) = signal(
45///     SendWrapper::new(
46///         document().query_selector("div > p.some-class").unwrap().unwrap()
47///     )
48/// );
49/// use_element_size(el_signal_send_wrapper); // Signal<SendWrapper<web_sys::Element>>
50///
51/// let el = NodeRef::<Div>::new();
52/// use_element_size(el); // NodeRef
53///
54///
55/// # view! {
56/// # }
57/// # }
58/// ```
59#[cfg_attr(not(debug_assertions), repr(transparent))]
60pub struct ElementMaybeSignal<T: 'static> {
61    #[cfg(debug_assertions)]
62    defined_at: &'static std::panic::Location<'static>,
63    inner: ElementMaybeSignalType<T>,
64}
65
66impl<T> Clone for ElementMaybeSignal<T> {
67    fn clone(&self) -> Self {
68        *self
69    }
70}
71
72impl<T> Copy for ElementMaybeSignal<T> {}
73
74pub struct ElementMaybeSignalType<T: 'static>(Signal<Option<SendWrapper<T>>>);
75
76impl<T> Clone for ElementMaybeSignalType<T> {
77    fn clone(&self) -> Self {
78        *self
79    }
80}
81
82impl<T> Copy for ElementMaybeSignalType<T> {}
83
84impl<T: 'static> Default for ElementMaybeSignalType<T> {
85    fn default() -> Self {
86        Self(Signal::stored(None))
87    }
88}
89
90impl<T> Default for ElementMaybeSignal<T> {
91    fn default() -> Self {
92        Self {
93            inner: ElementMaybeSignalType::default(),
94            #[cfg(debug_assertions)]
95            defined_at: std::panic::Location::caller(),
96        }
97    }
98}
99
100impl<T> DefinedAt for ElementMaybeSignal<T> {
101    fn defined_at(&self) -> Option<&'static std::panic::Location<'static>> {
102        #[cfg(debug_assertions)]
103        {
104            Some(self.defined_at)
105        }
106        #[cfg(not(debug_assertions))]
107        {
108            None
109        }
110    }
111}
112
113impl<T> ReadUntracked for ElementMaybeSignal<T> {
114    type Value =
115        ReadGuard<Option<SendWrapper<T>>, SignalReadGuard<Option<SendWrapper<T>>, SyncStorage>>;
116
117    fn try_read_untracked(&self) -> Option<Self::Value> {
118        self.inner.0.try_read_untracked()
119    }
120}
121
122impl<T> Track for ElementMaybeSignal<T> {
123    fn track(&self) {
124        self.inner.0.track();
125    }
126}
127
128pub trait IntoElementMaybeSignal<T, Marker> {
129    fn into_element_maybe_signal(self) -> ElementMaybeSignal<T>;
130}
131
132impl<El, T, Marker> IntoElementMaybeSignal<T, Marker> for El
133where
134    El: IntoElementMaybeSignalType<T, Marker>,
135{
136    fn into_element_maybe_signal(self) -> ElementMaybeSignal<T> {
137        ElementMaybeSignal {
138            inner: self.into_element_maybe_signal_type(),
139            #[cfg(debug_assertions)]
140            defined_at: std::panic::Location::caller(),
141        }
142    }
143}
144
145pub trait IntoElementMaybeSignalType<T, Marker> {
146    fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T>;
147}
148
149// From static element //////////////////////////////////////////////////////////////
150
151/// Handles `window()` or `document()`
152impl<T, Js> IntoElementMaybeSignalType<T, web_sys::Element> for Js
153where
154    T: From<Js> + Clone,
155{
156    fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T> {
157        ElementMaybeSignalType(Signal::stored(Some(SendWrapper::new(
158            T::from(self).clone(),
159        ))))
160    }
161}
162
163/// Handles `window().body()`
164impl<T, Js> IntoElementMaybeSignalType<T, Option<web_sys::Element>> for Option<Js>
165where
166    T: From<Js> + Clone,
167{
168    fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T> {
169        ElementMaybeSignalType(Signal::stored(
170            self.map(|el| SendWrapper::new(T::from(el).clone())),
171        ))
172    }
173}
174
175/// Handles `use_window()` or `use_document()`
176impl<T, E, Js> IntoElementMaybeSignalType<T, Option<Option<web_sys::Element>>> for Js
177where
178    Js: Deref<Target = Option<E>>,
179    E: Clone,
180    T: From<E> + Clone,
181{
182    fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T> {
183        ElementMaybeSignalType(Signal::stored(
184            self.as_ref().map(|e| SendWrapper::new(T::from(e.clone()))),
185        ))
186    }
187}
188
189// From string (selector) ///////////////////////////////////////////////////////////////
190
191pub struct StrMarker;
192
193/// Handles `"body"` or `"#app"`
194impl<T, V> IntoElementMaybeSignalType<T, StrMarker> for V
195where
196    V: AsRef<str>,
197    T: From<web_sys::Element> + Clone,
198{
199    fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T> {
200        if cfg!(feature = "ssr") {
201            ElementMaybeSignalType(Signal::stored(None))
202        } else {
203            ElementMaybeSignalType(el_signal_by_sel(self.as_ref()))
204        }
205    }
206}
207
208pub fn el_by_sel<T>(sel: &str) -> Option<T>
209where
210    T: From<web_sys::Element> + Clone,
211{
212    document()
213        .query_selector(sel)
214        .unwrap_or_default()
215        .map(|el| T::from(el).clone())
216}
217
218pub fn el_signal_by_sel<T>(sel: &str) -> Signal<Option<SendWrapper<T>>>
219where
220    T: From<web_sys::Element> + Clone + 'static,
221{
222    let (el_signal, set_el_signal) = signal(None);
223
224    let sel = sel.to_string();
225
226    set_timeout(
227        move || {
228            if let Some(el) = el_by_sel(&sel) {
229                set_el_signal.set(Some(SendWrapper::new(el)));
230            } else {
231                let stop_observer = StoredValue::new_local(Rc::new(|| {}) as Rc<dyn Fn()>);
232
233                let UseMutationObserverReturn { stop, .. } = use_mutation_observer_with_options(
234                    document().body().unwrap(),
235                    move |_, _| {
236                        if let Some(el) = el_by_sel(&sel) {
237                            set_el_signal.set(Some(SendWrapper::new(el)));
238                            stop_observer.get_value()();
239                        } else {
240                            set_el_signal.set(None)
241                        }
242                    },
243                    UseMutationObserverOptions::default()
244                        .child_list(true)
245                        .subtree(true),
246                );
247
248                stop_observer.set_value(Rc::new(stop));
249            }
250        },
251        Duration::ZERO,
252    );
253
254    el_signal.into()
255}
256
257pub struct SignalStrMarker;
258
259/// Handles `Signal<&str>`
260impl<T, V, I> IntoElementMaybeSignalType<T, SignalStrMarker> for V
261where
262    V: Get<Value = I> + Send + Sync + 'static,
263    I: AsRef<str>,
264    T: From<web_sys::Element> + Clone,
265{
266    fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T> {
267        if cfg!(feature = "ssr") {
268            ElementMaybeSignalType(Signal::stored(None))
269        } else {
270            ElementMaybeSignalType(Signal::derive(move || {
271                document()
272                    .query_selector(self.get().as_ref())
273                    .unwrap_or_default()
274                    .map(|el| SendWrapper::new(T::from(el).clone()))
275            }))
276        }
277    }
278}
279
280// From signal ///////////////////////////////////////////////////////////////
281
282pub struct SignalMarker;
283
284pub struct SendWrapperSignalMarker;
285
286/// Handles `Signal<SendWrapper<web_sys::*>>`
287impl<T, V, E> IntoElementMaybeSignalType<T, SendWrapperSignalMarker> for V
288where
289    E: Clone,
290    V: Get<Value = SendWrapper<E>> + Send + Sync + 'static,
291    T: From<E> + Clone,
292{
293    fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T> {
294        if cfg!(feature = "ssr") {
295            ElementMaybeSignalType(Signal::stored(None))
296        } else {
297            ElementMaybeSignalType(Signal::derive(move || {
298                Some(SendWrapper::new(T::from((self.get().take()).clone())))
299            }))
300        }
301    }
302}
303
304pub struct OptionSignalMarker;
305
306/// Handles `Signal<Option<web_sys::*>>` and `NodeRef`
307impl<T, V, E> IntoElementMaybeSignalType<T, OptionSignalMarker> for V
308where
309    V: Get<Value = Option<E>> + Send + Sync + 'static,
310    T: From<E> + Clone,
311{
312    fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T> {
313        ElementMaybeSignalType(Signal::derive(move || {
314            self.get().map(|v| SendWrapper::new(T::from(v)))
315        }))
316    }
317}
318
319pub struct OptionSendWrapperSignalMarker;
320
321/// Handles `Signal<Option<SendWrapper<web_sys::*>>>` and `ElementMaybeSignal`
322impl<T, V, E> IntoElementMaybeSignalType<T, OptionSendWrapperSignalMarker> for V
323where
324    V: Get<Value = Option<SendWrapper<E>>> + Send + Sync + 'static,
325    T: From<E> + Clone,
326{
327    fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T> {
328        ElementMaybeSignalType(Signal::derive(move || {
329            self.get().map(|v| SendWrapper::new(T::from(v.take())))
330        }))
331    }
332}