diff --git a/Cargo.lock b/Cargo.lock index 8c0fc0c..aeb60a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -702,6 +702,7 @@ name = "leptos_toaster" version = "0.1.0" dependencies = [ "cfg-if", + "js-sys", "leptos", "wasm-bindgen", "web-sys", diff --git a/Cargo.toml b/Cargo.toml index b3a6a96..ea18eb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ keywords = ["leptos", "toaster", "toast"] [dependencies] cfg-if = "1.0.0" +js-sys = "0.3.67" leptos = { version = "0.6", features = ["nightly"] } wasm-bindgen = "0.2.90" web-sys = { version = "0.3.67", features = ["EventTarget", "HtmlElement", "DomRect", "Element"] } diff --git a/examples/basic/Cargo.lock b/examples/basic/Cargo.lock index 070b135..7533d6b 100644 --- a/examples/basic/Cargo.lock +++ b/examples/basic/Cargo.lock @@ -714,6 +714,7 @@ name = "leptos_toaster" version = "0.1.0" dependencies = [ "cfg-if", + "js-sys", "leptos", "wasm-bindgen", "web-sys", diff --git a/examples/basic/Trunk.toml b/examples/basic/Trunk.toml new file mode 100644 index 0000000..132512a --- /dev/null +++ b/examples/basic/Trunk.toml @@ -0,0 +1,5 @@ +[serve] +port = 8080 + +[watch] +watch = ["../../src", "./src"] diff --git a/examples/basic/src/main.rs b/examples/basic/src/main.rs index c2d8893..03b2dcd 100644 --- a/examples/basic/src/main.rs +++ b/examples/basic/src/main.rs @@ -1,5 +1,7 @@ use leptos::*; -use leptos_toaster::{Toast, ToastId, ToastVariant, Toaster, ToasterPosition, Toasts}; +use leptos_toaster::{ + Toast, ToastId, ToastOptions, ToastVariant, Toaster, ToasterPosition, Toasts, +}; fn main() { mount_to_body(|| view! { }) @@ -8,9 +10,7 @@ fn main() { #[component] fn App() -> impl IntoView { view! { - +

"Basic example"

diff --git a/src/toast_container.rs b/src/toast_container.rs index 05df409..48140c3 100644 --- a/src/toast_container.rs +++ b/src/toast_container.rs @@ -2,8 +2,12 @@ use crate::{ types::{decode_message, HeightT, Toast}, ToastId, ToasterPosition, }; +use js_sys::Date; use leptos::{leptos_dom::helpers::TimeoutHandle, *}; +use std::cmp::{max, min}; use std::time::Duration; +use wasm_bindgen::JsCast; +use web_sys::{HtmlElement, PointerEvent}; #[component] pub fn ToastContainer( @@ -71,7 +75,7 @@ pub fn ToastContainer( let delete_timeout_handle = RwSignal::>::new(None); - let delete_toast = move |_| { + let delete_toast = move || { removed.set(true); offset_before_remove.set(offset()); heights.update(|heights| { @@ -97,7 +101,7 @@ pub fn ToastContainer( if let Some(id) = ev.data().as_string() { if let Some(id) = decode_message(id) { if id == toast.id { - delete_toast(id); + delete_toast(); } } } @@ -108,11 +112,92 @@ pub fn ToastContainer( }); create_effect(move |_| { - if let Ok(handle) = set_timeout_with_handle(move || delete_toast(toast.id), duration) { + if let Ok(handle) = set_timeout_with_handle(delete_toast, duration) { delete_timeout_handle.set(Some(handle)); } }); + #[derive(Clone)] + struct Point { + x: i32, + y: i32, + } + let drag_start_time = RwSignal::>::new(None); + let pointer_start = RwSignal::>::new(None); + let swipe_amount = RwSignal::::new(0); + let handle_pointerdown = move |ev: PointerEvent| { + if !toast.options.dismissible { + return; + } + drag_start_time.set(Some(Date::new_0())); + offset_before_remove.set(offset()); + + if let Some(target) = ev.target() { + if let Some(element) = target.dyn_ref::() { + let _ = element.set_pointer_capture(ev.pointer_id()); + if element.tag_name() == "BUTTON" { + return; + } + swiping.set(true); + pointer_start.set(Some(Point { + x: ev.client_x(), + y: ev.client_y(), + })); + } + } + }; + + let handle_pointerup = move |_| { + if swipe_out() || !toast.options.dismissible { + return; + } + pointer_start.set(None); + let time_taken = Date::new_0().get_time() + - drag_start_time.with(|t| t.as_ref().map(|t| t.get_time()).unwrap_or(0.0)); + let velocity = swipe_amount.with(|a| a.abs() as f64) / time_taken; + + if swipe_amount.with(|a| a.abs() >= 20) || velocity > 0.11 { + offset_before_remove.set(offset()); + delete_toast(); + swipe_out.set(true); + return; + }; + + swipe_amount.set(0); + swiping.set(false); + }; + + let handle_pointermove = move |ev: PointerEvent| { + if !toast.options.dismissible { + return; + }; + let _pointer_start = if let Some(pointer_start) = pointer_start() { + pointer_start + } else { + return; + }; + + let y_position = ev.client_y() - _pointer_start.y; + let x_position = ev.client_x() - _pointer_start.x; + + let clamped_y = match position { + ToasterPosition::TopLeft | ToasterPosition::TopCenter | ToasterPosition::TopRight => { + min(0, y_position) + } + ToasterPosition::BottomRight + | ToasterPosition::BottomCenter + | ToasterPosition::BottomLeft => max(0, y_position), + }; + let swipe_start_threshold = if ev.pointer_type() == "touch" { 10 } else { 2 }; + let is_allowed_to_swipe = clamped_y.abs() > swipe_start_threshold; + + if is_allowed_to_swipe { + swipe_amount.set(y_position); + } else if x_position.abs() > swipe_start_threshold { + pointer_start.set(None); + } + }; + view! {
  • {toast.view}