mirror of
https://github.com/adoyle0/thaw.git
synced 2025-02-02 08:34:15 -05:00
fix: Drawer and Modal esc close
This commit is contained in:
parent
d5410375bb
commit
51a509cb5e
5 changed files with 167 additions and 149 deletions
|
@ -1,6 +1,6 @@
|
||||||
use crate::Card;
|
use crate::Card;
|
||||||
use leptos::{leptos_dom::helpers::WindowListenerHandle, *};
|
use leptos::*;
|
||||||
use thaw_components::{CSSTransition, Teleport};
|
use thaw_components::{CSSTransition, FocusTrap, Teleport};
|
||||||
use thaw_utils::{class_list, mount_style, use_lock_html_scroll, Model, OptionalProp};
|
use thaw_utils::{class_list, mount_style, use_lock_html_scroll, Model, OptionalProp};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
|
@ -60,31 +60,12 @@ pub fn Drawer(
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_lock = RwSignal::new(show.get_untracked());
|
let is_lock = RwSignal::new(show.get_untracked());
|
||||||
let esc_handle = StoredValue::new(None::<WindowListenerHandle>);
|
Effect::new(move |_| {
|
||||||
Effect::new(move |prev| {
|
|
||||||
let is_show = show.get();
|
let is_show = show.get();
|
||||||
if is_show {
|
if is_show {
|
||||||
is_lock.set(true);
|
is_lock.set(true);
|
||||||
is_css_transition.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());
|
use_lock_html_scroll(is_lock.into());
|
||||||
let on_after_leave = move |_| {
|
let on_after_leave = move |_| {
|
||||||
|
@ -97,56 +78,53 @@ pub fn Drawer(
|
||||||
show.set(false);
|
show.set(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let on_esc = move |_: ev::KeyboardEvent| {
|
||||||
on_cleanup(move || {
|
show.set(false);
|
||||||
esc_handle.update_value(|handle| {
|
};
|
||||||
if let Some(handle) = handle.take() {
|
|
||||||
handle.remove();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<div class="thaw-drawer-container" style=move || style.get()>
|
<FocusTrap disabled=!close_on_esc active=show.signal() on_esc>
|
||||||
<CSSTransition
|
<div class="thaw-drawer-container" style=move || style.get()>
|
||||||
node_ref=mask_ref
|
<CSSTransition
|
||||||
show=show.signal()
|
node_ref=mask_ref
|
||||||
name="fade-in-transition"
|
show=show.signal()
|
||||||
let:display
|
name="fade-in-transition"
|
||||||
>
|
let:display
|
||||||
<div
|
|
||||||
class="thaw-drawer-mask"
|
|
||||||
style=move || display.get()
|
|
||||||
on:click=on_mask_click
|
|
||||||
ref=mask_ref
|
|
||||||
></div>
|
|
||||||
</CSSTransition>
|
|
||||||
<CSSTransition
|
|
||||||
node_ref=drawer_ref
|
|
||||||
show=show.signal()
|
|
||||||
name=Memo::new(move |_| {
|
|
||||||
format!("slide-in-from-{}-transition", placement.get())
|
|
||||||
})
|
|
||||||
|
|
||||||
on_after_enter
|
|
||||||
on_after_leave
|
|
||||||
let:display
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class=class_list![
|
|
||||||
"thaw-drawer", move || format!("thaw-drawer--placement-{}", placement
|
|
||||||
.get()), class.map(| c | move || c.get())
|
|
||||||
]
|
|
||||||
|
|
||||||
style=move || display.get()
|
|
||||||
ref=drawer_ref
|
|
||||||
role="dialog"
|
|
||||||
aria-modal="true"
|
|
||||||
>
|
>
|
||||||
<Card title>{children()}</Card>
|
<div
|
||||||
</div>
|
class="thaw-drawer-mask"
|
||||||
</CSSTransition>
|
style=move || display.get()
|
||||||
</div>
|
on:click=on_mask_click
|
||||||
|
ref=mask_ref
|
||||||
|
></div>
|
||||||
|
</CSSTransition>
|
||||||
|
<CSSTransition
|
||||||
|
node_ref=drawer_ref
|
||||||
|
show=show.signal()
|
||||||
|
name=Memo::new(move |_| {
|
||||||
|
format!("slide-in-from-{}-transition", placement.get())
|
||||||
|
})
|
||||||
|
|
||||||
|
on_after_enter
|
||||||
|
on_after_leave
|
||||||
|
let:display
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class=class_list![
|
||||||
|
"thaw-drawer", move || format!("thaw-drawer--placement-{}", placement
|
||||||
|
.get()), class.map(| c | move || c.get())
|
||||||
|
]
|
||||||
|
|
||||||
|
style=move || display.get()
|
||||||
|
ref=drawer_ref
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
>
|
||||||
|
<Card title>{children()}</Card>
|
||||||
|
</div>
|
||||||
|
</CSSTransition>
|
||||||
|
</div>
|
||||||
|
</FocusTrap>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{Card, CardFooter, CardHeader, CardHeaderExtra, Icon};
|
use crate::{Card, CardFooter, CardHeader, CardHeaderExtra, Icon};
|
||||||
use leptos::{leptos_dom::helpers::WindowListenerHandle, *};
|
use leptos::*;
|
||||||
use thaw_components::{CSSTransition, OptionComp, Teleport};
|
use thaw_components::{CSSTransition, FocusTrap, OptionComp, Teleport};
|
||||||
use thaw_utils::{mount_style, use_click_position, Model};
|
use thaw_utils::{mount_style, use_click_position, Model};
|
||||||
|
|
||||||
#[slot]
|
#[slot]
|
||||||
|
@ -22,30 +22,11 @@ pub fn Modal(
|
||||||
mount_style("modal", include_str!("./modal.css"));
|
mount_style("modal", include_str!("./modal.css"));
|
||||||
|
|
||||||
let displayed = RwSignal::new(show.get_untracked());
|
let displayed = RwSignal::new(show.get_untracked());
|
||||||
let esc_handle = StoredValue::new(None::<WindowListenerHandle>);
|
|
||||||
Effect::new(move |prev| {
|
Effect::new(move |prev| {
|
||||||
let is_show = show.get();
|
let is_show = show.get();
|
||||||
if prev.is_some() && is_show {
|
if prev.is_some() && is_show {
|
||||||
displayed.set(true);
|
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 |_| {
|
let on_mask_click = move |_| {
|
||||||
|
@ -53,6 +34,9 @@ pub fn Modal(
|
||||||
show.set(false);
|
show.set(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let on_esc = move |_: ev::KeyboardEvent| {
|
||||||
|
show.set(false);
|
||||||
|
};
|
||||||
|
|
||||||
let mask_ref = NodeRef::<html::Div>::new();
|
let mask_ref = NodeRef::<html::Div>::new();
|
||||||
let scroll_ref = NodeRef::<html::Div>::new();
|
let scroll_ref = NodeRef::<html::Div>::new();
|
||||||
|
@ -79,77 +63,71 @@ pub fn Modal(
|
||||||
let _ = modal_el.attr("style", format!("transform-origin: {}px {}px", x, y));
|
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! {
|
view! {
|
||||||
<Teleport>
|
<Teleport>
|
||||||
<div
|
<FocusTrap disabled=!close_on_esc active=show.signal() on_esc>
|
||||||
class="thaw-modal-container"
|
|
||||||
style:z-index=move || z_index.get()
|
|
||||||
style=("--thaw-width", move || width.get())
|
|
||||||
>
|
|
||||||
<CSSTransition
|
|
||||||
node_ref=mask_ref
|
|
||||||
show=show.signal()
|
|
||||||
name="fade-in-transition"
|
|
||||||
let:display
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="thaw-modal-mask"
|
|
||||||
style=move || display.get()
|
|
||||||
on:click=on_mask_click
|
|
||||||
ref=mask_ref
|
|
||||||
></div>
|
|
||||||
</CSSTransition>
|
|
||||||
<div
|
<div
|
||||||
class="thaw-modal-scroll"
|
class="thaw-modal-container"
|
||||||
style=move || (!displayed.get()).then_some("display: none")
|
style:z-index=move || z_index.get()
|
||||||
ref=scroll_ref
|
style=("--thaw-width", move || width.get())
|
||||||
>
|
>
|
||||||
<CSSTransition
|
<CSSTransition
|
||||||
node_ref=modal_ref
|
node_ref=mask_ref
|
||||||
show=show.signal()
|
show=show.signal()
|
||||||
name="fade-in-scale-up-transition"
|
name="fade-in-transition"
|
||||||
on_enter
|
|
||||||
on_after_leave=move |_| displayed.set(false)
|
|
||||||
let:display
|
let:display
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="thaw-modal-body"
|
class="thaw-modal-mask"
|
||||||
ref=modal_ref
|
|
||||||
role="dialog"
|
|
||||||
aria-modal="true"
|
|
||||||
style=move || display.get()
|
style=move || display.get()
|
||||||
>
|
on:click=on_mask_click
|
||||||
<Card>
|
ref=mask_ref
|
||||||
<CardHeader slot>
|
></div>
|
||||||
<span class="thaw-model-title">{move || title.get()}</span>
|
|
||||||
</CardHeader>
|
|
||||||
<CardHeaderExtra slot>
|
|
||||||
<span
|
|
||||||
style="cursor: pointer;"
|
|
||||||
on:click=move |_| show.set(false)
|
|
||||||
>
|
|
||||||
<Icon icon=icondata_ai::AiCloseOutlined/>
|
|
||||||
</span>
|
|
||||||
</CardHeaderExtra>
|
|
||||||
{children()}
|
|
||||||
<CardFooter slot if_=modal_footer.is_some()>
|
|
||||||
<OptionComp value=modal_footer.as_ref() let:footer>
|
|
||||||
{(footer.children)()}
|
|
||||||
</OptionComp>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</CSSTransition>
|
</CSSTransition>
|
||||||
|
<div
|
||||||
|
class="thaw-modal-scroll"
|
||||||
|
style=move || (!displayed.get()).then_some("display: none")
|
||||||
|
ref=scroll_ref
|
||||||
|
>
|
||||||
|
<CSSTransition
|
||||||
|
node_ref=modal_ref
|
||||||
|
show=show.signal()
|
||||||
|
name="fade-in-scale-up-transition"
|
||||||
|
on_enter
|
||||||
|
on_after_leave=move |_| displayed.set(false)
|
||||||
|
let:display
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="thaw-modal-body"
|
||||||
|
ref=modal_ref
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
style=move || display.get()
|
||||||
|
>
|
||||||
|
<Card>
|
||||||
|
<CardHeader slot>
|
||||||
|
<span class="thaw-model-title">{move || title.get()}</span>
|
||||||
|
</CardHeader>
|
||||||
|
<CardHeaderExtra slot>
|
||||||
|
<span
|
||||||
|
style="cursor: pointer;"
|
||||||
|
on:click=move |_| show.set(false)
|
||||||
|
>
|
||||||
|
<Icon icon=icondata_ai::AiCloseOutlined/>
|
||||||
|
</span>
|
||||||
|
</CardHeaderExtra>
|
||||||
|
{children()}
|
||||||
|
<CardFooter slot if_=modal_footer.is_some()>
|
||||||
|
<OptionComp value=modal_footer.as_ref() let:footer>
|
||||||
|
{(footer.children)()}
|
||||||
|
</OptionComp>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</CSSTransition>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</FocusTrap>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ leptos = { version = "0.6.10" }
|
||||||
thaw_utils = { workspace = true }
|
thaw_utils = { workspace = true }
|
||||||
web-sys = { version = "0.3.69", features = ["DomRect"] }
|
web-sys = { version = "0.3.69", features = ["DomRect"] }
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
|
uuid = { version = "1.7.0", features = ["v4"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
csr = ["leptos/csr"]
|
csr = ["leptos/csr"]
|
||||||
|
|
59
thaw_components/src/focus_trap/mod.rs
Normal file
59
thaw_components/src/focus_trap/mod.rs
Normal file
|
@ -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<Vec<uuid::Uuid>> = Default::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn FocusTrap(
|
||||||
|
disabled: bool,
|
||||||
|
#[prop(into)] active: MaybeSignal<bool>,
|
||||||
|
#[prop(into)] on_esc: Callback<ev::KeyboardEvent>,
|
||||||
|
children: Children,
|
||||||
|
) -> impl IntoView {
|
||||||
|
#[cfg(any(feature = "csr", feature = "hydrate"))]
|
||||||
|
if disabled == false {
|
||||||
|
let esc_handle = StoredValue::new(None::<WindowListenerHandle>);
|
||||||
|
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()
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
mod binder;
|
mod binder;
|
||||||
mod css_transition;
|
mod css_transition;
|
||||||
|
mod focus_trap;
|
||||||
mod if_comp;
|
mod if_comp;
|
||||||
mod option_comp;
|
mod option_comp;
|
||||||
mod teleport;
|
mod teleport;
|
||||||
|
@ -7,6 +8,7 @@ mod wave;
|
||||||
|
|
||||||
pub use binder::{Binder, Follower, FollowerPlacement, FollowerWidth};
|
pub use binder::{Binder, Follower, FollowerPlacement, FollowerWidth};
|
||||||
pub use css_transition::CSSTransition;
|
pub use css_transition::CSSTransition;
|
||||||
|
pub use focus_trap::FocusTrap;
|
||||||
pub use if_comp::{ElseIf, If, Then};
|
pub use if_comp::{ElseIf, If, Then};
|
||||||
pub use option_comp::OptionComp;
|
pub use option_comp::OptionComp;
|
||||||
pub use teleport::Teleport;
|
pub use teleport::Teleport;
|
||||||
|
|
Loading…
Add table
Reference in a new issue