yggdrasil/hooks/
near_viewport.rs1use leptos::prelude::*;
4
5pub fn use_near_viewport(target_id: String, root_margin: &'static str) -> ReadSignal<bool> {
22 let (visible, set_visible) = signal(false);
23
24 #[cfg(target_arch = "wasm32")]
25 {
26 use wasm_bindgen::{closure::Closure, JsCast};
27
28 type Watch = (
29 web_sys::IntersectionObserver,
30 Closure<dyn FnMut(js_sys::Array, web_sys::IntersectionObserver)>,
31 );
32 let watcher = StoredValue::new_local(None::<Watch>);
33 Effect::new(move |_| {
34 if visible.get_untracked() || watcher.with_value(|w| w.is_some()) {
36 return;
37 }
38 let Some(target) = web_sys::window()
39 .and_then(|window| window.document())
40 .and_then(|document| document.get_element_by_id(&target_id))
41 else {
42 return;
43 };
44 let callback = Closure::new(
45 move |entries: js_sys::Array, observer: web_sys::IntersectionObserver| {
46 if entries.iter().any(|value| {
47 value
48 .unchecked_into::<web_sys::IntersectionObserverEntry>()
49 .is_intersecting()
50 }) {
51 observer.disconnect();
52 let _ = set_visible.try_set(true);
53 }
54 },
55 );
56 let init = web_sys::IntersectionObserverInit::new();
57 init.set_root_margin(root_margin);
58 match web_sys::IntersectionObserver::new_with_options(
59 callback.as_ref().unchecked_ref(),
60 &init,
61 ) {
62 Ok(observer) => {
63 observer.observe(&target);
64 watcher.set_value(Some((observer, callback)));
65 }
66 Err(_) => {
67 }
70 }
71 });
72 on_cleanup(move || {
73 if let Some(Some((observer, _callback))) = watcher.try_update_value(Option::take) {
74 observer.disconnect();
75 }
76 });
77 }
78 #[cfg(not(target_arch = "wasm32"))]
79 {
80 let _ = (target_id, root_margin, set_visible);
81 }
82
83 visible
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use leptos::reactive::owner::Owner;
90
91 #[test]
92 fn server_side_never_activates() {
93 Owner::new().with(|| {
94 let visible = use_near_viewport("target".to_string(), "0px 0px 100% 0px");
95 assert!(!visible.get_untracked());
96 });
97 }
98}