leptos_use/core/
element_maybe_signal.rs

1use leptos::prelude::*;
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_local(
38///     document().query_selector("div > p.some-class").unwrap().unwrap()
39/// );
40/// use_element_size(el_signal); // Signal<web_sys::Element>
41///
42/// let (el_signal_send_wrapper, _set_el_signal_send_wrapper) = signal(
43///     SendWrapper::new(
44///         document().query_selector("div > p.some-class").unwrap().unwrap()
45///     )
46/// );
47/// use_element_size(el_signal_send_wrapper); // Signal<SendWrapper<web_sys::Element>>
48///
49/// let el = NodeRef::<Div>::new();
50/// use_element_size(el); // NodeRef
51///
52///
53/// # view! {
54/// # }
55/// # }
56/// ```
57#[cfg_attr(not(debug_assertions), repr(transparent))]
58pub struct ElementMaybeSignal<T: 'static> {
59    #[cfg(debug_assertions)]
60    defined_at: &'static std::panic::Location<'static>,
61    inner: ElementMaybeSignalType<T>,
62}
63
64impl<T> Clone for ElementMaybeSignal<T> {
65    fn clone(&self) -> Self {
66        *self
67    }
68}
69
70impl<T> Copy for ElementMaybeSignal<T> {}
71
72pub enum ElementMaybeSignalType<T: 'static> {
73    Static(StoredValue<Option<T>, LocalStorage>),
74    Dynamic(Signal<Option<T>, LocalStorage>),
75}
76
77impl<T> Clone for ElementMaybeSignalType<T> {
78    fn clone(&self) -> Self {
79        *self
80    }
81}
82
83impl<T> Copy for ElementMaybeSignalType<T> {}
84
85impl<T: 'static> Default for ElementMaybeSignalType<T> {
86    fn default() -> Self {
87        Self::Static(StoredValue::new_local(None))
88    }
89}
90
91impl<T> Default for ElementMaybeSignal<T> {
92    fn default() -> Self {
93        Self {
94            inner: ElementMaybeSignalType::default(),
95            #[cfg(debug_assertions)]
96            defined_at: std::panic::Location::caller(),
97        }
98    }
99}
100
101impl<T> DefinedAt for ElementMaybeSignal<T> {
102    fn defined_at(&self) -> Option<&'static std::panic::Location<'static>> {
103        #[cfg(debug_assertions)]
104        {
105            Some(self.defined_at)
106        }
107        #[cfg(not(debug_assertions))]
108        {
109            None
110        }
111    }
112}
113
114impl<T> With for ElementMaybeSignal<T> {
115    type Value = Option<T>;
116
117    fn try_with<O>(&self, f: impl FnOnce(&Option<T>) -> O) -> Option<O> {
118        match &self.inner {
119            ElementMaybeSignalType::Static(v) => v.try_with_value(f),
120            ElementMaybeSignalType::Dynamic(s) => s.try_with(f),
121        }
122    }
123}
124
125impl<T> WithUntracked for ElementMaybeSignal<T> {
126    type Value = Option<T>;
127
128    fn try_with_untracked<O>(&self, f: impl FnOnce(&Option<T>) -> O) -> Option<O> {
129        match &self.inner {
130            ElementMaybeSignalType::Static(t) => t.try_with_value(f),
131            ElementMaybeSignalType::Dynamic(s) => s.try_with_untracked(f),
132        }
133    }
134}
135
136pub trait IntoElementMaybeSignal<T, Marker> {
137    fn into_element_maybe_signal(self) -> ElementMaybeSignal<T>;
138}
139
140impl<El, T, Marker> IntoElementMaybeSignal<T, Marker> for El
141where
142    El: IntoElementMaybeSignalType<T, Marker>,
143{
144    fn into_element_maybe_signal(self) -> ElementMaybeSignal<T> {
145        ElementMaybeSignal {
146            inner: self.into_element_maybe_signal_type(),
147            #[cfg(debug_assertions)]
148            defined_at: std::panic::Location::caller(),
149        }
150    }
151}
152
153pub trait IntoElementMaybeSignalType<T, Marker> {
154    fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T>;
155}
156
157// From static element //////////////////////////////////////////////////////////////
158
159/// Handles `window()` or `document()`
160impl<T, Js> IntoElementMaybeSignalType<T, web_sys::Element> for Js
161where
162    T: From<Js> + Clone,
163{
164    fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T> {
165        ElementMaybeSignalType::Static(StoredValue::new_local(Some(T::from(self).clone())))
166    }
167}
168
169/// Handles `window().body()`
170impl<T, Js> IntoElementMaybeSignalType<T, Option<web_sys::Element>> for Option<Js>
171where
172    T: From<Js> + Clone,
173{
174    fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T> {
175        ElementMaybeSignalType::Static(StoredValue::new_local(self.map(|el| T::from(el).clone())))
176    }
177}
178
179/// Handles `use_window()` or `use_document()`
180impl<T, E, Js> IntoElementMaybeSignalType<T, Option<Option<web_sys::Element>>> for Js
181where
182    Js: Deref<Target = Option<E>>,
183    E: Clone,
184    T: From<E> + Clone,
185{
186    fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T> {
187        ElementMaybeSignalType::Static(StoredValue::new_local(
188            self.as_ref().map(|e| T::from(e.clone())),
189        ))
190    }
191}
192
193// From string (selector) ///////////////////////////////////////////////////////////////
194
195pub struct StrMarker;
196
197/// Handles `"body"` or `"#app"`
198impl<T, V> IntoElementMaybeSignalType<T, StrMarker> for V
199where
200    V: AsRef<str>,
201    T: From<web_sys::Element> + Clone,
202{
203    fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T> {
204        if cfg!(feature = "ssr") {
205            ElementMaybeSignalType::Static(StoredValue::new_local(None))
206        } else {
207            ElementMaybeSignalType::Dynamic(el_signal_by_sel(self.as_ref()))
208        }
209    }
210}
211
212pub fn el_by_sel<T>(sel: &str) -> Option<T>
213where
214    T: From<web_sys::Element> + Clone,
215{
216    document()
217        .query_selector(sel)
218        .unwrap_or_default()
219        .map(|el| T::from(el).clone())
220}
221
222pub fn el_signal_by_sel<T>(sel: &str) -> Signal<Option<T>, LocalStorage>
223where
224    T: From<web_sys::Element> + Clone + 'static,
225{
226    let (el_signal, set_el_signal) = signal_local(None);
227
228    let sel = sel.to_string();
229
230    set_timeout(
231        move || {
232            if let Some(el) = el_by_sel(&sel) {
233                set_el_signal.set(Some(el));
234            } else {
235                let stop_observer = StoredValue::new_local(Rc::new(|| {}) as Rc<dyn Fn()>);
236
237                let UseMutationObserverReturn { stop, .. } = use_mutation_observer_with_options(
238                    document().body().unwrap(),
239                    move |_, _| {
240                        if let Some(el) = el_by_sel(&sel) {
241                            set_el_signal.set(Some(el));
242                            stop_observer.get_value()();
243                        } else {
244                            set_el_signal.set(None)
245                        }
246                    },
247                    UseMutationObserverOptions::default()
248                        .child_list(true)
249                        .subtree(true),
250                );
251
252                stop_observer.set_value(Rc::new(stop));
253            }
254        },
255        Duration::ZERO,
256    );
257
258    el_signal.into()
259}
260
261pub struct SignalStrMarker;
262
263/// Handles `Signal<&str>`
264impl<T, V, I> IntoElementMaybeSignalType<T, SignalStrMarker> for V
265where
266    V: Get<Value = I> + 'static,
267    I: AsRef<str>,
268    T: From<web_sys::Element> + Clone,
269{
270    fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T> {
271        if cfg!(feature = "ssr") {
272            ElementMaybeSignalType::Static(StoredValue::new_local(None))
273        } else {
274            ElementMaybeSignalType::Dynamic(Signal::derive_local(move || {
275                document()
276                    .query_selector(self.get().as_ref())
277                    .unwrap_or_default()
278                    .map(|el| T::from(el).clone())
279            }))
280        }
281    }
282}
283
284// From signal ///////////////////////////////////////////////////////////////
285
286pub struct SignalMarker;
287
288/// Handles `Signal<web_sys::*>`
289impl<T, V, E> IntoElementMaybeSignalType<T, SignalMarker> for V
290where
291    V: Get<Value = E> + 'static,
292    T: From<E> + Clone,
293{
294    fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T> {
295        ElementMaybeSignalType::Dynamic(Signal::derive_local(move || Some(T::from(self.get()))))
296    }
297}
298
299pub struct SendWrapperSignalMarker;
300
301/// Handles `Signal<SendWrapper<web_sys::*>>`
302impl<T, V, E> IntoElementMaybeSignalType<T, SendWrapperSignalMarker> for V
303where
304    E: Clone,
305    V: Get<Value = SendWrapper<E>> + 'static,
306    T: From<E> + Clone,
307{
308    fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T> {
309        ElementMaybeSignalType::Dynamic(Signal::derive_local(move || {
310            Some(T::from((*self.get()).clone()))
311        }))
312    }
313}
314
315pub struct OptionSignalMarker;
316
317/// Handles `Signal<Option<web_sys::*>>` and `NodeRef` and `ElementMaybeSignal`
318impl<T, V, E> IntoElementMaybeSignalType<T, OptionSignalMarker> for V
319where
320    V: Get<Value = Option<E>> + 'static,
321    T: From<E> + Clone,
322{
323    fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T> {
324        ElementMaybeSignalType::Dynamic(Signal::derive_local(move || self.get().map(T::from)))
325    }
326}