fix: Drawer and Modal esc close

This commit is contained in:
luoxiao 2024-04-07 23:36:22 +08:00 committed by luoxiaozero
parent d5410375bb
commit 51a509cb5e
5 changed files with 167 additions and 149 deletions

View file

@ -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>
} }
} }

View file

@ -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>
} }
} }

View file

@ -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"]

View 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()
}

View file

@ -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;