leptos-use 0.15.7

Collection of essential Leptos utilities inspired by React-Use / VueUse / SolidJS-USE
Documentation
use crate::core::IntoElementMaybeSignal;
use default_struct_builder::DefaultBuilder;
use leptos::prelude::*;
use leptos::reactive::wrappers::read::Signal;

/// Reactive [bounding box](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) of an HTML element
///
/// ## Demo
///
/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_element_bounding)
///
/// ## Usage
///
/// ```
/// # use leptos::prelude::*;
/// # use leptos::html::Div;
/// # use leptos_use::{use_element_bounding, UseElementBoundingReturn};
/// #
/// # #[component]
/// # fn Demo() -> impl IntoView {
/// let el = NodeRef::<Div>::new();
/// let UseElementBoundingReturn {
///     x, y, top,right,bottom,left, width, height, ..
/// } = use_element_bounding(el);
///
/// view! { <div node_ref=el></div> }
/// # }
/// ```
///
/// ## SendWrapped Return
///
/// The returned closure `update` is a sendwrapped function. It can
/// only be called from the same thread that called `use_element_bounding`.
///
/// ## Server-Side Rendering
///
/// On the server the returned signals always are `0.0` and `update` is a no-op.
pub fn use_element_bounding<El, M>(
    target: El,
) -> UseElementBoundingReturn<impl Fn() + Clone + Send + Sync>
where
    El: IntoElementMaybeSignal<web_sys::Element, M>,
{
    use_element_bounding_with_options(target, UseElementBoundingOptions::default())
}

/// Version of [`use_element_bounding`] that takes a `UseElementBoundingOptions`. See [`use_element_bounding`] for how to use.
pub fn use_element_bounding_with_options<El, M>(
    target: El,
    options: UseElementBoundingOptions,
) -> UseElementBoundingReturn<impl Fn() + Clone + Send + Sync>
where
    El: IntoElementMaybeSignal<web_sys::Element, M>,
{
    let (height, set_height) = signal(0.0);
    let (width, set_width) = signal(0.0);
    let (left, set_left) = signal(0.0);
    let (right, set_right) = signal(0.0);
    let (top, set_top) = signal(0.0);
    let (bottom, set_bottom) = signal(0.0);
    let (x, set_x) = signal(0.0);
    let (y, set_y) = signal(0.0);

    let update;

    #[cfg(feature = "ssr")]
    {
        let _ = target;
        let _ = options;

        let _ = set_height;
        let _ = set_width;
        let _ = set_left;
        let _ = set_right;
        let _ = set_top;
        let _ = set_bottom;
        let _ = set_x;
        let _ = set_y;

        update = move || ();
    }

    #[cfg(not(feature = "ssr"))]
    {
        use crate::{
            sendwrap_fn, use_event_listener_with_options, use_resize_observer, use_window,
            UseEventListenerOptions,
        };
        use leptos::ev::{resize, scroll};

        let UseElementBoundingOptions {
            reset,
            window_resize,
            window_scroll,
            immediate,
        } = options;

        let target = target.into_element_maybe_signal();

        update = sendwrap_fn!(move || {
            let el = target.get_untracked();

            if let Some(el) = el {
                let rect = el.get_bounding_client_rect();

                set_height.set(rect.height());
                set_width.set(rect.width());
                set_left.set(rect.x());
                set_right.set(rect.x() + rect.width());
                set_top.set(rect.y());
                set_bottom.set(rect.y() + rect.height());
                set_x.set(rect.x());
                set_y.set(rect.y());
            } else if reset {
                set_height.set(0.0);
                set_width.set(0.0);
                set_left.set(0.0);
                set_right.set(0.0);
                set_top.set(0.0);
                set_bottom.set(0.0);
                set_x.set(0.0);
                set_y.set(0.0);
            }
        });

        use_resize_observer(target, {
            let update = update.clone();

            move |_, _| {
                update();
            }
        });

        Effect::watch(
            move || target.get(),
            {
                let update = update.clone();
                move |_, _, _| {
                    update();
                }
            },
            false,
        );

        if window_scroll {
            let _ = use_event_listener_with_options(
                use_window(),
                scroll,
                {
                    let update = update.clone();
                    move |_| update()
                },
                UseEventListenerOptions::default()
                    .capture(true)
                    .passive(true),
            );
        }

        if window_resize {
            let _ = use_event_listener_with_options(
                use_window(),
                resize,
                {
                    let update = update.clone();
                    move |_| update()
                },
                UseEventListenerOptions::default().passive(true),
            );
        }

        if immediate {
            update();
        }
    }

    UseElementBoundingReturn {
        height: height.into(),
        width: width.into(),
        left: left.into(),
        right: right.into(),
        top: top.into(),
        bottom: bottom.into(),
        x: x.into(),
        y: y.into(),
        update,
    }
}

/// Options for [`use_element_bounding_with_options`].
#[derive(DefaultBuilder)]
pub struct UseElementBoundingOptions {
    /// Reset values to 0 on component disposal
    ///
    /// Default: `true`
    pub reset: bool,

    /// Listen to window resize event
    ///
    /// Default: `true`
    pub window_resize: bool,

    /// Listen to window scroll event
    ///
    /// Default: `true`
    pub window_scroll: bool,

    /// Immediately call update
    ///
    /// Default: `true`
    pub immediate: bool,
}

impl Default for UseElementBoundingOptions {
    fn default() -> Self {
        Self {
            reset: true,
            window_resize: true,
            window_scroll: true,
            immediate: true,
        }
    }
}

/// Return type of [`use_element_bounding`].
pub struct UseElementBoundingReturn<F>
where
    F: Fn() + Clone + Send + Sync,
{
    /// Reactive version of [`BoudingClientRect.height`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/height)
    pub height: Signal<f64>,
    /// Reactive version of [`BoudingClientRect.width`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/width)
    pub width: Signal<f64>,
    /// Reactive version of [`BoudingClientRect.left`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/left)
    pub left: Signal<f64>,
    /// Reactive version of [`BoudingClientRect.right`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/right)
    pub right: Signal<f64>,
    /// Reactive version of [`BoudingClientRect.top`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/top)
    pub top: Signal<f64>,
    /// Reactive version of [`BoudingClientRect.bottom`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/bottom)
    pub bottom: Signal<f64>,
    /// Reactive version of [`BoudingClientRect.x`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/x)
    pub x: Signal<f64>,
    /// Reactive version of [`BoudingClientRect.y`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/y)
    pub y: Signal<f64>,
    /// Function to re-evaluate `get_bounding_client_rect()` and update the signals.
    pub update: F,
}