leptos_use/core/
elements_maybe_signal.rs

1use crate::core::{SignalMarker, SignalStrMarker, StrMarker};
2use leptos::prelude::*;
3use leptos::reactive::wrappers::read::Signal;
4use std::{ops::Deref, rc::Rc, time::Duration};
5use wasm_bindgen::JsCast;
6
7use crate::{
8    UseMutationObserverOptions, UseMutationObserverReturn, use_mutation_observer_with_options,
9};
10
11/// Used as an argument type to make it easily possible to pass either
12///
13/// * a `web_sys` element that implements `E` (for example `EventTarget` or `Element`),
14/// * an `Option<T>` where `T` is the web_sys element,
15/// * a `Signal<T>` where `T` is the web_sys element,
16/// * a `Signal<Option<T>>` where `T` is the web_sys element,
17/// * a `NodeRef`
18///
19/// into a function. Used for example in [`fn@crate::use_event_listener`].
20#[cfg_attr(not(debug_assertions), repr(transparent))]
21pub struct ElementsMaybeSignal<T: 'static> {
22    #[cfg(debug_assertions)]
23    defined_at: &'static std::panic::Location<'static>,
24    inner: ElementsMaybeSignalType<T>,
25}
26
27impl<T> Clone for ElementsMaybeSignal<T> {
28    fn clone(&self) -> Self {
29        *self
30    }
31}
32
33impl<T> Copy for ElementsMaybeSignal<T> {}
34
35pub enum ElementsMaybeSignalType<T: 'static> {
36    Static(StoredValue<Vec<Option<T>>, LocalStorage>),
37    Dynamic(Signal<Vec<Option<T>>, LocalStorage>),
38}
39
40impl<T> Clone for ElementsMaybeSignalType<T> {
41    fn clone(&self) -> Self {
42        *self
43    }
44}
45
46impl<T> Copy for ElementsMaybeSignalType<T> {}
47
48impl<T: 'static> Default for ElementsMaybeSignalType<T> {
49    fn default() -> Self {
50        Self::Static(StoredValue::new_local(vec![]))
51    }
52}
53
54impl<T> Default for ElementsMaybeSignal<T> {
55    fn default() -> Self {
56        Self {
57            inner: ElementsMaybeSignalType::default(),
58            #[cfg(debug_assertions)]
59            defined_at: std::panic::Location::caller(),
60        }
61    }
62}
63
64impl<T> DefinedAt for ElementsMaybeSignal<T> {
65    fn defined_at(&self) -> Option<&'static std::panic::Location<'static>> {
66        #[cfg(debug_assertions)]
67        {
68            Some(self.defined_at)
69        }
70        #[cfg(not(debug_assertions))]
71        {
72            None
73        }
74    }
75}
76
77impl<T> With for ElementsMaybeSignal<T> {
78    type Value = Vec<Option<T>>;
79
80    fn try_with<O>(&self, f: impl FnOnce(&Vec<Option<T>>) -> O) -> Option<O> {
81        match &self.inner {
82            ElementsMaybeSignalType::Static(v) => v.try_with_value(f),
83            ElementsMaybeSignalType::Dynamic(s) => s.try_with(f),
84        }
85    }
86}
87
88impl<T> WithUntracked for ElementsMaybeSignal<T> {
89    type Value = Vec<Option<T>>;
90
91    fn try_with_untracked<O>(&self, f: impl FnOnce(&Vec<Option<T>>) -> O) -> Option<O> {
92        match self.inner {
93            ElementsMaybeSignalType::Static(t) => t.try_with_value(f),
94            ElementsMaybeSignalType::Dynamic(s) => s.try_with_untracked(f),
95        }
96    }
97}
98
99pub trait IntoElementsMaybeSignal<T, Marker> {
100    fn into_elements_maybe_signal(self) -> ElementsMaybeSignal<T>;
101}
102
103impl<El, T, Marker> IntoElementsMaybeSignal<T, Marker> for El
104where
105    El: IntoElementsMaybeSignalType<T, Marker>,
106{
107    fn into_elements_maybe_signal(self) -> ElementsMaybeSignal<T> {
108        ElementsMaybeSignal {
109            inner: self.into_elements_maybe_signal_type(),
110            #[cfg(debug_assertions)]
111            defined_at: std::panic::Location::caller(),
112        }
113    }
114}
115
116pub trait IntoElementsMaybeSignalType<T, Marker> {
117    fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T>;
118}
119
120// From single static element //////////////////////////////////////////////////////////////
121
122/// Handles `window()` or `document()`
123impl<T, Js> IntoElementsMaybeSignalType<T, web_sys::Element> for Js
124where
125    T: From<Js> + Clone,
126{
127    fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
128        ElementsMaybeSignalType::Static(StoredValue::new_local(vec![Some(T::from(self).clone())]))
129    }
130}
131
132/// Handles `window().body()`
133impl<T, Js> IntoElementsMaybeSignalType<T, Option<web_sys::Element>> for Option<Js>
134where
135    T: From<Js> + Clone,
136{
137    fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
138        ElementsMaybeSignalType::Static(StoredValue::new_local(vec![
139            self.map(|el| T::from(el).clone()),
140        ]))
141    }
142}
143
144/// Handles `use_window()` or `use_document()`
145impl<T, E, Js> IntoElementsMaybeSignalType<T, Option<Option<web_sys::Element>>> for Js
146where
147    Js: Deref<Target = Option<E>>,
148    E: Clone,
149    T: From<E> + Clone,
150{
151    fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
152        ElementsMaybeSignalType::Static(StoredValue::new_local(vec![
153            self.as_ref().map(|e| T::from(e.clone())),
154        ]))
155    }
156}
157
158// From string (selector) ///////////////////////////////////////////////////////////////
159
160/// Handles `"body"` or `"#app"`
161impl<T, V> IntoElementsMaybeSignalType<T, StrMarker> for V
162where
163    V: AsRef<str>,
164    T: From<web_sys::Element> + Clone,
165{
166    fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
167        if cfg!(feature = "ssr") {
168            ElementsMaybeSignalType::Static(StoredValue::new_local(vec![]))
169        } else {
170            ElementsMaybeSignalType::Dynamic(els_signal_by_sel::<T>(self.as_ref()))
171        }
172    }
173}
174
175/// Handles `Signal<&str>`
176impl<T, V, I> IntoElementsMaybeSignalType<T, SignalStrMarker> for V
177where
178    V: Get<Value = I> + 'static,
179    I: AsRef<str>,
180    T: From<web_sys::Element> + Clone,
181{
182    fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
183        if cfg!(feature = "ssr") {
184            ElementsMaybeSignalType::Static(StoredValue::new_local(vec![]))
185        } else {
186            ElementsMaybeSignalType::Dynamic(Signal::derive_local(move || {
187                vec![
188                    document()
189                        .query_selector(self.get().as_ref())
190                        .unwrap_or_default()
191                        .map(|el| T::from(el).clone()),
192                ]
193            }))
194        }
195    }
196}
197
198// From single signal ///////////////////////////////////////////////////////////////
199
200/// Handles `Signal<web_sys::*>`
201impl<T, V, E> IntoElementsMaybeSignalType<T, SignalMarker> for V
202where
203    V: Get<Value = E> + 'static,
204    T: From<E> + Clone,
205{
206    fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
207        ElementsMaybeSignalType::Dynamic(Signal::derive_local(move || {
208            vec![Some(T::from(self.get()))]
209        }))
210    }
211}
212
213// From multiple static elements //////////////////////////////////////////////////////
214
215/// Handles `&[web_sys::*]`
216impl<'a, T, Js, C> IntoElementsMaybeSignalType<T, &'a [web_sys::Element]> for C
217where
218    Js: Clone + 'a,
219    T: From<Js>,
220    C: IntoIterator<Item = &'a Js>,
221{
222    fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
223        ElementsMaybeSignalType::Static(StoredValue::new_local(
224            self.into_iter().map(|t| Some(T::from(t.clone()))).collect(),
225        ))
226    }
227}
228
229/// Handles `&[Option<web_sys::*>]`
230impl<'a, T, Js, C> IntoElementsMaybeSignalType<T, &'a [Option<web_sys::Element>]> for C
231where
232    Js: Clone + 'a,
233    T: From<Js>,
234    C: IntoIterator<Item = &'a Option<Js>>,
235{
236    fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
237        ElementsMaybeSignalType::Static(StoredValue::new_local(
238            self.into_iter().map(|t| t.clone().map(T::from)).collect(),
239        ))
240    }
241}
242
243/// Handles `Vec<web_sys::*>`
244impl<T, Js, C> IntoElementsMaybeSignalType<T, Vec<web_sys::Element>> for C
245where
246    T: From<Js> + Clone,
247    C: IntoIterator<Item = Js>,
248{
249    fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
250        ElementsMaybeSignalType::Static(StoredValue::new_local(
251            self.into_iter().map(|t| Some(T::from(t))).collect(),
252        ))
253    }
254}
255
256/// Handles `Vec<Option<web_sys::*>>`
257impl<T, Js, C> IntoElementsMaybeSignalType<T, Vec<Option<web_sys::Element>>> for C
258where
259    T: From<Js> + Clone,
260    C: IntoIterator<Item = Option<Js>>,
261{
262    fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
263        ElementsMaybeSignalType::Static(StoredValue::new_local(
264            self.into_iter().map(|t| t.map(T::from)).collect(),
265        ))
266    }
267}
268
269// From multiple strings //////////////////////////////////////////////////////
270
271pub struct StrIterMarker;
272
273pub fn els_by_sel<T>(sel: &str) -> Vec<Option<T>>
274where
275    T: From<web_sys::Element> + Clone,
276{
277    let mut els: Vec<web_sys::Element> = Vec::new();
278
279    if let Ok(queried_els) = document().query_selector_all(sel.as_ref()) {
280        for i in 0..queried_els.length() {
281            if let Ok(el) = queried_els.get(i).expect("checked length").dyn_into() {
282                els.push(el);
283            }
284        }
285    }
286    els.into_iter().map(|v| Some(T::from(v))).collect()
287}
288
289pub fn els_signal_by_sel<T>(sel: &str) -> Signal<Vec<Option<T>>, LocalStorage>
290where
291    T: From<web_sys::Element> + Clone + 'static,
292{
293    let (el_signal, set_el_signal) = signal_local(Vec::new());
294
295    let sel = sel.to_string();
296
297    set_timeout(
298        move || {
299            let els = els_by_sel::<T>(&sel);
300            if !els.is_empty() {
301                set_el_signal.set(els);
302            } else {
303                let stop_observer = StoredValue::new_local(Rc::new(|| {}) as Rc<dyn Fn()>);
304
305                let UseMutationObserverReturn { stop, .. } = use_mutation_observer_with_options(
306                    document().body().unwrap(),
307                    move |_, _| {
308                        let els = els_by_sel(&sel);
309                        if !els.is_empty() {
310                            set_el_signal.set(els);
311                            stop_observer.get_value()();
312                        } else {
313                            set_el_signal.set(Vec::new());
314                        }
315                    },
316                    UseMutationObserverOptions::default()
317                        .child_list(true)
318                        .subtree(true),
319                );
320
321                stop_observer.set_value(Rc::new(stop));
322            }
323        },
324        Duration::ZERO,
325    );
326
327    el_signal.into()
328}
329
330/// Handles `["body", "#app"]`
331impl<T, V, C> IntoElementsMaybeSignalType<T, StrIterMarker> for C
332where
333    V: AsRef<str>,
334    T: From<web_sys::Element> + Clone,
335    C: IntoIterator<Item = V>,
336{
337    fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
338        if cfg!(feature = "ssr") {
339            ElementsMaybeSignalType::Static(StoredValue::new_local(vec![]))
340        } else {
341            self.into_iter()
342                .map(|sel| els_signal_by_sel::<T>(sel.as_ref()))
343                .collect::<Vec<_>>()
344                .into_elements_maybe_signal_type()
345        }
346    }
347}
348
349// From signal of multiple ////////////////////////////////////////////////////////////////
350
351pub struct SignalVecMarker;
352
353/// Handles `Signal<Vec<web_sys::*>>` and `Signal<Option<web_sys::*>>` and `NodeRef` and `ElementMaybeSignal`
354impl<T, Js, C, G> IntoElementsMaybeSignalType<T, SignalVecMarker> for G
355where
356    T: From<Js> + Clone,
357    G: Get<Value = C> + 'static,
358    C: IntoIterator<Item = Js>,
359{
360    fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
361        ElementsMaybeSignalType::Dynamic(Signal::derive_local(move || {
362            self.get().into_iter().map(|t| Some(T::from(t))).collect()
363        }))
364    }
365}
366
367pub struct SignalVecOptionMarker;
368
369/// Handles `Signal<Vec<Option<web_sys::*>>>`
370impl<T, Js, C, G> IntoElementsMaybeSignalType<T, SignalVecOptionMarker> for G
371where
372    T: From<Js> + Clone,
373    G: Get<Value = C> + 'static,
374    C: IntoIterator<Item = Option<Js>>,
375{
376    fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
377        ElementsMaybeSignalType::Dynamic(Signal::derive_local(move || {
378            self.get().into_iter().map(|t| t.map(T::from)).collect()
379        }))
380    }
381}
382
383// From multiple signals //////////////////////////////////////////////////////////////
384
385pub struct VecSignalMarker;
386
387/// Handles `Vec<Signal<web_sys::*>>`
388impl<T, Js, C, G> IntoElementsMaybeSignalType<T, VecSignalMarker> for C
389where
390    T: From<Js> + Clone,
391    C: IntoIterator<Item = G> + Clone + 'static,
392    G: Get<Value = Js>,
393{
394    fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
395        let signals = self.clone();
396
397        ElementsMaybeSignalType::Dynamic(Signal::derive_local(move || {
398            signals
399                .clone()
400                .into_iter()
401                .map(|t| Some(T::from(t.get())))
402                .collect()
403        }))
404    }
405}
406
407pub struct VecSignalOptionMarker;
408
409/// Handles `Vec<Signal<Option<web_sys::*>>>`, `Vec<NodeRef>`
410impl<T, Js, C, G> IntoElementsMaybeSignalType<T, VecSignalOptionMarker> for C
411where
412    T: From<Js> + Clone,
413    C: IntoIterator<Item = G> + Clone + 'static,
414    G: Get<Value = Option<Js>>,
415{
416    fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
417        let signals = self.clone();
418
419        ElementsMaybeSignalType::Dynamic(Signal::derive_local(move || {
420            signals
421                .clone()
422                .into_iter()
423                .map(|t| t.get().map(T::from))
424                .collect()
425        }))
426    }
427}
428
429// handles Vec<Signal<Vec<Option<web_sys::*>>>
430pub struct VecSignalVecOptionMarker;
431
432impl<T, Js, C, G> IntoElementsMaybeSignalType<T, VecSignalVecOptionMarker> for C
433where
434    T: TryFrom<Js> + Clone,
435    C: IntoIterator<Item = G> + Clone + 'static,
436    G: Get<Value = Vec<Option<Js>>>,
437{
438    fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
439        let signals = self.clone();
440
441        ElementsMaybeSignalType::Dynamic(Signal::derive_local(move || {
442            signals
443                .clone()
444                .into_iter()
445                .flat_map(|t| t.get())
446                .map(|j| T::try_from(j.unwrap()).ok())
447                .collect()
448        }))
449    }
450}