yewlish-utils 0.1.12

Common utils for yewlish component set
Documentation
use std::rc::Rc;
use web_sys::wasm_bindgen::prelude::Closure;
use web_sys::wasm_bindgen::JsCast;
use yew::prelude::*;

pub enum Node {
    Element(web_sys::Element),
    Ref(NodeRef),
}

impl From<&web_sys::Element> for Node {
    fn from(element: &web_sys::Element) -> Self {
        Node::Element(element.clone())
    }
}

impl From<&NodeRef> for Node {
    fn from(node_ref: &NodeRef) -> Self {
        Node::Ref(node_ref.clone())
    }
}

#[hook]
pub fn use_interaction_outside<T>(nodes: Vec<Node>, callback: T)
where
    T: Fn(Event) + 'static,
{
    let callback = Callback::from(callback);
    let callback_ref = Rc::new(callback);

    use_effect_with(callback_ref, move |callback_ref| {
        let callback = callback_ref.clone();
        let callback_ref = Rc::downgrade(callback_ref);

        let closure = Closure::wrap(Box::new(move |event: web_sys::Event| {
            let target = event.target_unchecked_into::<web_sys::Element>();

            if !nodes.iter().any(|node| match &node {
                Node::Element(element) => element.contains(Some(&target)),
                Node::Ref(node_ref) => {
                    if let Some(node) = node_ref.cast::<web_sys::Element>() {
                        node.contains(Some(&target))
                    } else {
                        false
                    }
                }
            }) {
                callback.emit(event);
            }
        }) as Box<dyn FnMut(_)>);

        if let Some(window) = web_sys::window() {
            let _ = window
                .add_event_listener_with_callback("mousedown", closure.as_ref().unchecked_ref());

            let _ = window
                .add_event_listener_with_callback("touchstart", closure.as_ref().unchecked_ref());

            let _ = window
                .add_event_listener_with_callback("focusin", closure.as_ref().unchecked_ref());
        }

        move || {
            if callback_ref.upgrade().is_some() {
                if let Some(window) = web_sys::window() {
                    let _ = window.remove_event_listener_with_callback(
                        "mousedown",
                        closure.as_ref().unchecked_ref(),
                    );

                    let _ = window.remove_event_listener_with_callback(
                        "touchstart",
                        closure.as_ref().unchecked_ref(),
                    );

                    let _ = window.remove_event_listener_with_callback(
                        "focusin",
                        closure.as_ref().unchecked_ref(),
                    );
                }
            }
        }
    });
}