yewlish-utils 0.1.12

Common utils for yewlish component set
Documentation
use std::cell::RefCell;
use std::rc::Rc;
use web_sys::{
    js_sys,
    wasm_bindgen::{prelude::Closure, JsCast},
    Element, IntersectionObserver, IntersectionObserverEntry, IntersectionObserverInit,
};
use yew::prelude::*;

type HandlerParams = (
    NodeRef,
    Rc<Option<IntersectionObserverInit>>,
    Callback<IntersectionObserverEntry>,
);

#[hook]
pub fn use_intersection_observer_lazy() -> (
    Rc<RefCell<Option<IntersectionObserver>>>,
    Callback<HandlerParams>,
) {
    let intersection_observer = use_mut_ref(|| None::<IntersectionObserver>);

    let create_intersection_observer = use_callback(
        intersection_observer.clone(),
        |(element_ref, options, handler): HandlerParams, intersection_observer| {
            let callback = Closure::wrap(Box::new(
                move |entries: js_sys::Array, _: IntersectionObserver| {
                    for entry in entries.iter() {
                        if let Some(entry) = entry.dyn_ref::<IntersectionObserverEntry>() {
                            handler.emit(entry.clone());
                        }
                    }
                },
            )
                as Box<dyn FnMut(js_sys::Array, IntersectionObserver)>);

            let observer = if let Some(opts) = options.as_ref() {
                IntersectionObserver::new_with_options(callback.as_ref().unchecked_ref(), opts)
            } else {
                IntersectionObserver::new(callback.as_ref().unchecked_ref())
            };

            match observer {
                Ok(ref observer) => {
                    if let Some(element) = element_ref.cast::<Element>() {
                        observer.observe(&element);
                        intersection_observer.replace(Some(observer.clone()));
                    } else {
                        log::error!("Failed to cast element to Element");
                    }
                }
                Err(err) => {
                    log::error!("Failed to create IntersectionObserver {err:?}");
                }
            }

            callback.forget();
        },
    );

    {
        let intersection_observer = intersection_observer.clone();

        use_effect_with((), move |()| {
            move || {
                if let Some(ref observer) = *intersection_observer.borrow() {
                    observer.disconnect();
                }
            }
        });
    }

    (intersection_observer, create_intersection_observer)
}

#[hook]
pub fn use_intersection_observer(
    element_ref: &NodeRef,
    options: Rc<Option<IntersectionObserverInit>>,
    handler: Callback<IntersectionObserverEntry>,
) -> Rc<RefCell<Option<IntersectionObserver>>> {
    let (intersection_observer, create_intersection_observer) = use_intersection_observer_lazy();

    {
        let element = element_ref.clone();

        use_effect_with((), move |()| {
            create_intersection_observer.emit((element.clone(), options.clone(), handler.clone()));
        });
    }

    intersection_observer
}