Skip to content

Commit 292a6a4

Browse files
committed
fix: Clicking the outer part of the Popover component does not close
1 parent 152cfc8 commit 292a6a4

File tree

2 files changed

+70
-56
lines changed

2 files changed

+70
-56
lines changed

thaw/src/popover/mod.rs

+44-56
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub use types::*;
44

55
use crate::ConfigInjection;
66
use leptos::{
7+
either::Either,
78
ev::{self, on},
89
html,
910
leptos_dom::helpers::TimeoutHandle,
@@ -12,7 +13,7 @@ use leptos::{
1213
};
1314
use std::time::Duration;
1415
use thaw_components::{Binder, CSSTransition, Follower};
15-
use thaw_utils::{add_event_listener, class_list, mount_style, BoxCallback};
16+
use thaw_utils::{class_list, mount_style, on_click_outside, BoxCallback};
1617

1718
#[component]
1819
pub fn Popover<T>(
@@ -41,7 +42,6 @@ where
4142
let config_provider = ConfigInjection::expect_context();
4243

4344
let popover_ref = NodeRef::<html::Div>::new();
44-
let target_ref = NodeRef::<thaw_utils::Element>::new();
4545
let is_show_popover = RwSignal::new(false);
4646
let show_popover_handle = StoredValue::new(None::<TimeoutHandle>);
4747

@@ -93,67 +93,55 @@ where
9393
.ok();
9494
});
9595
};
96-
#[cfg(any(feature = "csr", feature = "hydrate"))]
97-
{
98-
let handle = window_event_listener(ev::click, move |ev| {
99-
use leptos::wasm_bindgen::__rt::IntoJsResult;
100-
if trigger_type != PopoverTriggerType::Click {
101-
return;
102-
}
103-
if !is_show_popover.get_untracked() {
104-
return;
105-
}
106-
let el = ev.target();
107-
let mut el: Option<web_sys::Element> =
108-
el.into_js_result().map_or(None, |el| Some(el.into()));
109-
let body = document().body().unwrap();
110-
while let Some(current_el) = el {
111-
if current_el == *body {
112-
break;
113-
};
114-
let Some(popover_el) = popover_ref.get_untracked() else {
115-
break;
116-
};
117-
if current_el == **popover_el {
118-
return;
119-
}
120-
el = current_el.parent_element();
121-
}
122-
is_show_popover.set(false);
123-
});
124-
on_cleanup(move || handle.remove());
125-
}
126-
127-
Effect::new(move |_| {
128-
let Some(target_el) = target_ref.get() else {
129-
return;
130-
};
131-
let handler = add_event_listener(target_el, ev::click, move |event| {
132-
if trigger_type != PopoverTriggerType::Click {
133-
return;
134-
}
135-
event.stop_propagation();
136-
is_show_popover.update(|show| *show = !*show);
137-
});
138-
on_cleanup(move || handler.remove());
139-
});
14096

14197
let PopoverTrigger {
14298
children: trigger_children,
14399
} = popover_trigger;
100+
let trigger_children = trigger_children.into_inner()()
101+
.into_inner()
102+
.add_any_attr(tachys_class(("thaw-popover-trigger", true)))
103+
.add_any_attr(tachys_class(("thaw-popover-trigger--open", move || {
104+
is_show_popover.get()
105+
})));
106+
107+
let trigger_children = match trigger_type {
108+
PopoverTriggerType::Click => {
109+
let trigger_ref = NodeRef::<thaw_utils::Element>::new();
110+
on_click_outside(
111+
move || {
112+
if !is_show_popover.get_untracked() {
113+
return None;
114+
}
115+
let Some(trigger_el) = trigger_ref.get_untracked() else {
116+
return None;
117+
};
118+
let Some(popover_el) = popover_ref.get_untracked() else {
119+
return None;
120+
};
121+
Some(vec![popover_el.into(), trigger_el])
122+
},
123+
move || is_show_popover.set(false),
124+
);
125+
Either::Left(
126+
trigger_children
127+
.add_any_attr(node_ref(trigger_ref))
128+
.add_any_attr(on(ev::click, move |_| {
129+
is_show_popover.update(|show| {
130+
*show = !*show;
131+
});
132+
})),
133+
)
134+
}
135+
PopoverTriggerType::Hover => Either::Right(
136+
trigger_children
137+
.add_any_attr(on(ev::mouseenter, on_mouse_enter))
138+
.add_any_attr(on(ev::mouseleave, on_mouse_leave)),
139+
),
140+
};
144141

145142
view! {
146143
<Binder>
147-
{trigger_children
148-
.into_inner()()
149-
.into_inner()
150-
.add_any_attr(tachys_class(("thaw-popover-trigger", true)))
151-
.add_any_attr(
152-
tachys_class(("thaw-popover-trigger--open", move || is_show_popover.get())),
153-
)
154-
.add_any_attr(node_ref(target_ref))
155-
.add_any_attr(on(ev::mouseenter, on_mouse_enter))
156-
.add_any_attr(on(ev::mouseleave, on_mouse_leave))}
144+
{trigger_children}
157145
<Follower slot show=is_show_popover placement=position>
158146
<CSSTransition
159147
name="popover-transition"

thaw_utils/src/on_click_outside.rs

+26
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,29 @@ pub fn call_on_click_outside_with_list(refs: Vec<NodeRef<Div>>, on_click: BoxCal
4646
let _ = on_click;
4747
}
4848
}
49+
50+
pub fn on_click_outside<EF, CF>(els: EF, on_click: CF)
51+
where
52+
EF: Fn() -> Option<Vec<web_sys::Element>> + 'static,
53+
CF: Fn() + 'static,
54+
{
55+
#[cfg(any(feature = "csr", feature = "hydrate"))]
56+
{
57+
let handle = window_event_listener(::leptos::ev::click, move |ev| {
58+
let Some(els) = els() else {
59+
return;
60+
};
61+
let composed_path = ev.composed_path();
62+
if els.iter().any(|el| composed_path.includes(&el, 0)) {
63+
return;
64+
}
65+
on_click();
66+
});
67+
on_cleanup(move || handle.remove());
68+
}
69+
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
70+
{
71+
let _ = els;
72+
let _ = on_click;
73+
}
74+
}

0 commit comments

Comments
 (0)