2023-05-26 18:09:01 +01:00
|
|
|
use crate::core::ElementMaybeSignal;
|
|
|
|
use crate::use_debounce_fn_with_arg;
|
|
|
|
use crate::utils::{CloneableFn, CloneableFnWithArg, CloneableFnWithReturn};
|
2023-05-15 02:08:22 +01:00
|
|
|
use leptos::*;
|
|
|
|
|
2023-05-26 18:09:01 +01:00
|
|
|
/// We have to check if the scroll amount is close enough to some threshold in order to
|
|
|
|
/// more accurately calculate arrivedState. This is because scrollTop/scrollLeft are non-rounded
|
|
|
|
/// numbers, while scrollHeight/scrollWidth and clientHeight/clientWidth are rounded.
|
|
|
|
/// https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#determine_if_an_element_has_been_totally_scrolled
|
|
|
|
const ARRIVED_STATE_THRESHOLD_PIXELS: f64 = 1.0;
|
|
|
|
|
2023-05-19 22:28:26 +01:00
|
|
|
#[allow(unused_variables)]
|
2023-05-26 18:09:01 +01:00
|
|
|
pub fn use_scroll_with_options<El, T>(
|
2023-05-19 22:28:26 +01:00
|
|
|
cx: Scope,
|
|
|
|
element: El,
|
|
|
|
options: UseScrollOptions,
|
|
|
|
) -> UseScrollReturn
|
|
|
|
where
|
2023-05-26 18:09:01 +01:00
|
|
|
(Scope, El): Into<ElementMaybeSignal<T, web_sys::Element>>,
|
|
|
|
T: Into<web_sys::Element> + Clone + 'static,
|
2023-05-19 22:28:26 +01:00
|
|
|
{
|
|
|
|
// TODO : implement
|
|
|
|
|
2023-05-26 18:09:01 +01:00
|
|
|
let (internal_x, set_internal_x) = create_signal(cx, 0.0);
|
|
|
|
let (internal_y, set_internal_y) = create_signal(cx, 0.0);
|
|
|
|
|
|
|
|
let signal = (cx, element).into();
|
|
|
|
let behavior = options.behavior;
|
|
|
|
|
|
|
|
let scroll_to = move |x: Option<f64>, y: Option<f64>| {
|
|
|
|
let element = signal.get_untracked();
|
|
|
|
|
|
|
|
if let Some(element) = element {
|
|
|
|
let element = element.into();
|
|
|
|
|
|
|
|
let mut scroll_options = web_sys::ScrollToOptions::new();
|
|
|
|
scroll_options.behavior(behavior.into());
|
2023-05-19 22:28:26 +01:00
|
|
|
|
2023-05-26 18:09:01 +01:00
|
|
|
if let Some(x) = x {
|
|
|
|
scroll_options.left(x);
|
|
|
|
}
|
|
|
|
if let Some(y) = y {
|
|
|
|
scroll_options.top(y);
|
|
|
|
}
|
|
|
|
|
|
|
|
element.scroll_to_with_scroll_to_options(&scroll_options);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let scroll = scroll_to.clone();
|
|
|
|
let set_x = Box::new(move |x| scroll(Some(x), None));
|
|
|
|
|
|
|
|
let scroll = scroll_to.clone();
|
|
|
|
let set_y = Box::new(move |y| scroll(None, Some(y)));
|
|
|
|
|
|
|
|
let (is_scrolling, set_is_scrolling) = create_signal(cx, false);
|
|
|
|
let (arrived_state, set_arrived_state) = create_signal(
|
2023-05-19 22:28:26 +01:00
|
|
|
cx,
|
|
|
|
Directions {
|
2023-05-26 18:09:01 +01:00
|
|
|
left: true,
|
2023-05-19 22:28:26 +01:00
|
|
|
right: false,
|
2023-05-26 18:09:01 +01:00
|
|
|
top: true,
|
2023-05-19 22:28:26 +01:00
|
|
|
bottom: false,
|
|
|
|
},
|
|
|
|
);
|
2023-05-26 18:09:01 +01:00
|
|
|
let (directions, set_directions) = create_signal(
|
2023-05-19 22:28:26 +01:00
|
|
|
cx,
|
|
|
|
Directions {
|
|
|
|
left: false,
|
|
|
|
right: false,
|
|
|
|
top: false,
|
|
|
|
bottom: false,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-05-26 18:09:01 +01:00
|
|
|
let on_stop = options.on_stop;
|
|
|
|
let on_scroll_end = move |e| {
|
|
|
|
if !is_scrolling.get() {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
set_is_scrolling(false);
|
|
|
|
set_directions.update(|directions| {
|
|
|
|
directions.left = false;
|
|
|
|
directions.right = false;
|
|
|
|
directions.top = false;
|
|
|
|
directions.bottom = false;
|
|
|
|
on_stop(e);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
let on_scroll_end_debounced =
|
|
|
|
use_debounce_fn_with_arg(on_scroll_end, options.throttle + options.idle);
|
|
|
|
|
2023-05-19 22:28:26 +01:00
|
|
|
UseScrollReturn {
|
2023-05-26 18:09:01 +01:00
|
|
|
x: internal_x.into(),
|
|
|
|
set_x,
|
|
|
|
y: internal_y.into(),
|
|
|
|
set_y,
|
2023-05-19 22:28:26 +01:00
|
|
|
is_scrolling: is_scrolling.into(),
|
|
|
|
arrived_state: arrived_state.into(),
|
|
|
|
directions: directions.into(),
|
|
|
|
}
|
|
|
|
}
|
2023-05-19 00:58:48 +01:00
|
|
|
|
|
|
|
/// Options for [`use_scroll`].
|
2023-05-15 02:08:22 +01:00
|
|
|
pub struct UseScrollOptions {
|
|
|
|
/// Throttle time in milliseconds for the scroll events. Defaults to 0 (disabled).
|
2023-05-26 18:09:01 +01:00
|
|
|
pub throttle: f64,
|
2023-05-15 02:08:22 +01:00
|
|
|
|
|
|
|
/// After scrolling ends we wait idle + throttle milliseconds before we consider scrolling to have stopped.
|
|
|
|
/// Defaults to 200.
|
2023-05-26 18:09:01 +01:00
|
|
|
pub idle: f64,
|
2023-05-19 00:58:48 +01:00
|
|
|
|
|
|
|
/// Threshold in pixels when we consider a side to have arrived (`UseScrollReturn::arrived_state`).
|
|
|
|
pub offset: ScrollOffset,
|
|
|
|
|
|
|
|
/// Callback when scrolling is happening.
|
2023-05-26 18:09:01 +01:00
|
|
|
pub on_scroll: Box<dyn CloneableFn>,
|
2023-05-19 00:58:48 +01:00
|
|
|
|
|
|
|
/// Callback when scrolling stops (after `idle` + `throttle` milliseconds have passed).
|
2023-05-26 18:09:01 +01:00
|
|
|
pub on_stop: Box<dyn CloneableFnWithArg<web_sys::Event>>,
|
2023-05-19 00:58:48 +01:00
|
|
|
|
|
|
|
/// Options passed to the `addEventListener("scroll", ...)` call
|
2023-05-26 18:09:01 +01:00
|
|
|
pub event_listener_options: web_sys::AddEventListenerOptions,
|
2023-05-19 00:58:48 +01:00
|
|
|
|
|
|
|
/// When changing the `x` or `y` signals this specifies the scroll behaviour.
|
|
|
|
/// Can be `Auto` (= not smooth) or `Smooth`. Defaults to `Auto`.
|
|
|
|
pub behavior: ScrollBehavior,
|
|
|
|
}
|
|
|
|
|
2023-05-26 18:09:01 +01:00
|
|
|
impl Default for UseScrollOptions {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
throttle: 0.0,
|
|
|
|
idle: 200.0,
|
|
|
|
offset: Default::default(),
|
|
|
|
on_scroll: Default::default(),
|
|
|
|
on_stop: Default::default(),
|
|
|
|
event_listener_options: Default::default(),
|
|
|
|
behavior: Default::default(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Default, Copy, Clone)]
|
2023-05-19 00:58:48 +01:00
|
|
|
pub enum ScrollBehavior {
|
|
|
|
#[default]
|
|
|
|
Auto,
|
|
|
|
Smooth,
|
2023-05-15 02:08:22 +01:00
|
|
|
}
|
|
|
|
|
2023-05-26 18:09:01 +01:00
|
|
|
impl Into<web_sys::ScrollBehavior> for ScrollBehavior {
|
|
|
|
fn into(self) -> web_sys::ScrollBehavior {
|
|
|
|
match self {
|
|
|
|
ScrollBehavior::Auto => web_sys::ScrollBehavior::Auto,
|
|
|
|
ScrollBehavior::Smooth => web_sys::ScrollBehavior::Smooth,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-19 22:28:26 +01:00
|
|
|
pub struct UseScrollReturn {
|
2023-05-19 00:58:48 +01:00
|
|
|
pub x: Signal<f64>,
|
2023-05-19 22:28:26 +01:00
|
|
|
pub set_x: Box<dyn Fn(f64)>,
|
2023-05-19 00:58:48 +01:00
|
|
|
pub y: Signal<f64>,
|
2023-05-19 22:28:26 +01:00
|
|
|
pub set_y: Box<dyn Fn(f64)>,
|
2023-05-19 00:58:48 +01:00
|
|
|
pub is_scrolling: Signal<bool>,
|
|
|
|
pub arrived_state: Signal<Directions>,
|
|
|
|
pub directions: Signal<Directions>,
|
2023-05-15 02:08:22 +01:00
|
|
|
}
|
|
|
|
|
2023-05-19 00:58:48 +01:00
|
|
|
#[derive(Copy, Clone)]
|
2023-05-15 02:08:22 +01:00
|
|
|
pub struct Directions {
|
|
|
|
pub left: bool,
|
|
|
|
pub right: bool,
|
|
|
|
pub top: bool,
|
|
|
|
pub bottom: bool,
|
|
|
|
}
|
2023-05-15 02:12:27 +01:00
|
|
|
|
2023-05-26 18:09:01 +01:00
|
|
|
#[derive(Default, Copy, Clone)]
|
2023-05-19 00:58:48 +01:00
|
|
|
pub struct ScrollOffset {
|
|
|
|
pub left: f64,
|
|
|
|
pub top: f64,
|
|
|
|
pub right: f64,
|
|
|
|
pub bottom: f64,
|
|
|
|
}
|