diff --git a/thaw/src/drawer/mod.rs b/thaw/src/drawer/mod.rs index 5c8d4d2..4e66740 100644 --- a/thaw/src/drawer/mod.rs +++ b/thaw/src/drawer/mod.rs @@ -1,6 +1,6 @@ use crate::Card; -use leptos::{leptos_dom::helpers::WindowListenerHandle, *}; -use thaw_components::{CSSTransition, Teleport}; +use leptos::*; +use thaw_components::{CSSTransition, FocusTrap, Teleport}; use thaw_utils::{class_list, mount_style, use_lock_html_scroll, Model, OptionalProp}; #[component] @@ -60,31 +60,12 @@ pub fn Drawer( }; let is_lock = RwSignal::new(show.get_untracked()); - let esc_handle = StoredValue::new(None::); - Effect::new(move |prev| { + Effect::new(move |_| { let is_show = show.get(); if is_show { is_lock.set(true); is_css_transition.set(true); } - if close_on_esc { - if is_show && !prev.unwrap_or(false) { - let handle = window_event_listener(ev::keydown, move |e| { - if &e.code() == "Escape" { - show.set(false); - } - }); - esc_handle.set_value(Some(handle)); - } else { - esc_handle.update_value(|handle| { - if let Some(handle) = handle.take() { - handle.remove(); - } - }) - } - } - - is_show }); use_lock_html_scroll(is_lock.into()); let on_after_leave = move |_| { @@ -97,56 +78,53 @@ pub fn Drawer( show.set(false); } }; - - on_cleanup(move || { - esc_handle.update_value(|handle| { - if let Some(handle) = handle.take() { - handle.remove(); - } - }) - }); + let on_esc = move |_: ev::KeyboardEvent| { + show.set(false); + }; view! { -
- -
-
- - +
+
+ + + +
+ } } diff --git a/thaw/src/modal/mod.rs b/thaw/src/modal/mod.rs index 790d8fe..6140cfa 100644 --- a/thaw/src/modal/mod.rs +++ b/thaw/src/modal/mod.rs @@ -1,6 +1,6 @@ use crate::{Card, CardFooter, CardHeader, CardHeaderExtra, Icon}; -use leptos::{leptos_dom::helpers::WindowListenerHandle, *}; -use thaw_components::{CSSTransition, OptionComp, Teleport}; +use leptos::*; +use thaw_components::{CSSTransition, FocusTrap, OptionComp, Teleport}; use thaw_utils::{mount_style, use_click_position, Model}; #[slot] @@ -22,30 +22,11 @@ pub fn Modal( mount_style("modal", include_str!("./modal.css")); let displayed = RwSignal::new(show.get_untracked()); - let esc_handle = StoredValue::new(None::); Effect::new(move |prev| { let is_show = show.get(); if prev.is_some() && is_show { displayed.set(true); } - - if close_on_esc { - if is_show && !prev.unwrap_or(false) { - let handle = window_event_listener(ev::keydown, move |e| { - if &e.code() == "Escape" { - show.set(false); - } - }); - esc_handle.set_value(Some(handle)); - } else { - esc_handle.update_value(|handle| { - if let Some(handle) = handle.take() { - handle.remove(); - } - }) - } - } - is_show }); let on_mask_click = move |_| { @@ -53,6 +34,9 @@ pub fn Modal( show.set(false); } }; + let on_esc = move |_: ev::KeyboardEvent| { + show.set(false); + }; let mask_ref = NodeRef::::new(); let scroll_ref = NodeRef::::new(); @@ -79,77 +63,71 @@ pub fn Modal( let _ = modal_el.attr("style", format!("transform-origin: {}px {}px", x, y)); }); - on_cleanup(move || { - esc_handle.update_value(|handle| { - if let Some(handle) = handle.take() { - handle.remove(); - } - }) - }); - view! { -
- -
-
+
+ on:click=on_mask_click + ref=mask_ref + >
+
+ + + +
- +
} } diff --git a/thaw_components/Cargo.toml b/thaw_components/Cargo.toml index 39b9a2c..5d4a1ba 100644 --- a/thaw_components/Cargo.toml +++ b/thaw_components/Cargo.toml @@ -16,6 +16,7 @@ leptos = { version = "0.6.10" } thaw_utils = { workspace = true } web-sys = { version = "0.3.69", features = ["DomRect"] } cfg-if = "1.0.0" +uuid = { version = "1.7.0", features = ["v4"] } [features] csr = ["leptos/csr"] diff --git a/thaw_components/src/focus_trap/mod.rs b/thaw_components/src/focus_trap/mod.rs new file mode 100644 index 0000000..7bc8b56 --- /dev/null +++ b/thaw_components/src/focus_trap/mod.rs @@ -0,0 +1,59 @@ +use leptos::{leptos_dom::helpers::WindowListenerHandle, *}; +use std::cell::RefCell; + +#[cfg(any(feature = "csr", feature = "hydrate"))] +thread_local! { + static STACK: RefCell> = Default::default(); +} + +#[component] +pub fn FocusTrap( + disabled: bool, + #[prop(into)] active: MaybeSignal, + #[prop(into)] on_esc: Callback, + children: Children, +) -> impl IntoView { + #[cfg(any(feature = "csr", feature = "hydrate"))] + if disabled == false { + let esc_handle = StoredValue::new(None::); + let id = StoredValue::new(uuid::Uuid::new_v4()); + + let is_current_active = + move || STACK.with_borrow(|stack| id.with_value(|id| stack.last() == Some(id))); + let deactivate = move || { + esc_handle.update_value(|handle| { + if let Some(handle) = handle.take() { + handle.remove(); + } + }); + STACK.with_borrow_mut(|stack| stack.retain(|value| id.with_value(|id| id != value))); + }; + + Effect::new(move |prev| { + let is_active = active.get(); + if is_active && !prev.unwrap_or(false) { + let handle = window_event_listener(ev::keydown, move |e| { + if &e.code() == "Escape" { + if is_current_active() { + on_esc.call(e); + } + } + }); + esc_handle.set_value(Some(handle)); + STACK.with_borrow_mut(|stack| { + stack.push(id.get_value()); + }); + } else { + deactivate(); + } + + is_active + }); + + on_cleanup(move || { + deactivate(); + }); + } + + children() +} diff --git a/thaw_components/src/lib.rs b/thaw_components/src/lib.rs index c5f2e01..4901296 100644 --- a/thaw_components/src/lib.rs +++ b/thaw_components/src/lib.rs @@ -1,5 +1,6 @@ mod binder; mod css_transition; +mod focus_trap; mod if_comp; mod option_comp; mod teleport; @@ -7,6 +8,7 @@ mod wave; pub use binder::{Binder, Follower, FollowerPlacement, FollowerWidth}; pub use css_transition::CSSTransition; +pub use focus_trap::FocusTrap; pub use if_comp::{ElseIf, If, Then}; pub use option_comp::OptionComp; pub use teleport::Teleport;