use crate::core::ElementMaybeSignal;
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder;
use leptos::reactive_graph::wrappers::read::Signal;
use leptos::prelude::*;
/// 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::
::new();
/// let UseElementBoundingReturn {
/// x, y, top,right,bottom,left, width, height, ..
/// } = use_element_bounding(el);
///
/// view! {
}
/// # }
/// ```
/// ## Server-Side Rendering
///
/// On the server the returned signals always are `0.0` and `update` is a no-op.
pub fn use_element_bounding
(target: El) -> UseElementBoundingReturn
where
El: Into> + Clone,
T: Into + Clone + 'static,
{
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(
target: El,
options: UseElementBoundingOptions,
) -> UseElementBoundingReturn
where
El: Into> + Clone,
T: Into + Clone + 'static,
{
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);
cfg_if! { if #[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;
let update = move || ();
} else {
use crate::{
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();
let update = {
let target = target.clone();
move || {
let el = target.get_untracked();
if let Some(el) = el {
let rect = el.into().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.clone(), {
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
where
F: Fn() + Clone,
{
/// Reactive version of [`BoudingClientRect.height`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/height)
pub height: Signal,
/// Reactive version of [`BoudingClientRect.width`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/width)
pub width: Signal,
/// Reactive version of [`BoudingClientRect.left`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/left)
pub left: Signal,
/// Reactive version of [`BoudingClientRect.right`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/right)
pub right: Signal,
/// Reactive version of [`BoudingClientRect.top`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/top)
pub top: Signal,
/// Reactive version of [`BoudingClientRect.bottom`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/bottom)
pub bottom: Signal,
/// Reactive version of [`BoudingClientRect.x`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/x)
pub x: Signal,
/// Reactive version of [`BoudingClientRect.y`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly/y)
pub y: Signal,
/// Function to re-evaluate `get_bounding_client_rect()` and update the signals.
pub update: F,
}