diff --git a/src/auto_complete/auto-complete.css b/src/auto_complete/auto-complete.css index 44b192f..da27465 100644 --- a/src/auto_complete/auto-complete.css +++ b/src/auto_complete/auto-complete.css @@ -1,6 +1,5 @@ .thaw-auto-complete__menu { - position: relative; - display: inline-block; + width: 100%; max-height: 200px; padding: 5px; background-color: var(--thaw-background-color); @@ -9,7 +8,6 @@ box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); overflow: auto; - z-index: 2000; } .thaw-auto-complete__menu-item { padding: 6px 5px; diff --git a/src/auto_complete/mod.rs b/src/auto_complete/mod.rs index b3c068b..94eaaed 100644 --- a/src/auto_complete/mod.rs +++ b/src/auto_complete/mod.rs @@ -1,6 +1,11 @@ mod theme; -use crate::{mount_style, teleport::Teleport, use_theme, utils::StoredMaybeSignal, Input, Theme}; +use crate::{ + components::{Binder, Follower}, + mount_style, use_theme, + utils::StoredMaybeSignal, + Input, Theme, +}; use leptos::*; pub use theme::AutoCompleteTheme; @@ -37,93 +42,66 @@ pub fn AutoComplete( let is_show_menu = create_rw_signal(false); let auto_complete_ref = create_node_ref::(); - let auto_complete_menu_ref = create_node_ref::(); let options = StoredMaybeSignal::from(options); - let show_menu = move || { - is_show_menu.set(true); - let rect = auto_complete_ref - .get_untracked() - .unwrap() - .get_bounding_client_rect(); - - let auto_complete_menu = auto_complete_menu_ref.get_untracked().unwrap(); - _ = auto_complete_menu - .style("width", format!("{}px", rect.width())) - .style( - "transform", - format!( - "translateX({}px) translateY({}px)", - rect.x(), - rect.y() + rect.height() - ), - ); - }; - let allow_value = move |_| { if !is_show_menu.get_untracked() { - show_menu(); + is_show_menu.set(true); } true }; view! { -
- -
- -
- - {move || { - options - .get() - .into_iter() - .map(|v| { - let AutoCompleteOption { value: option_value, label } = v; - let on_click = move |_| { - if clear_after_select.get_untracked() { - value.set(String::new()); - } else { - value.set(option_value.clone()); - } - if let Some(on_select) = on_select { - on_select.call(option_value.clone()); - } - is_show_menu.set(false); - }; - let on_mousedown = move |ev: ev::MouseEvent| { - ev.prevent_default(); - }; - view! { -
- {label} -
- } - }) - .collect_view() - }} - + +
+
- + +
+ + {move || { + options + .get() + .into_iter() + .map(|v| { + let AutoCompleteOption { value: option_value, label } = v; + let on_click = move |_| { + if clear_after_select.get_untracked() { + value.set(String::new()); + } else { + value.set(option_value.clone()); + } + if let Some(on_select) = on_select { + on_select.call(option_value.clone()); + } + is_show_menu.set(false); + }; + let on_mousedown = move |ev: ev::MouseEvent| { + ev.prevent_default(); + }; + view! { +
+ {label} +
+ } + }) + .collect_view() + }} + +
+
+
} } diff --git a/src/components/binder/mod.rs b/src/components/binder/mod.rs index 13bb582..6e504ee 100644 --- a/src/components/binder/mod.rs +++ b/src/components/binder/mod.rs @@ -5,6 +5,7 @@ use crate::{ }; use leptos::{ html::{AnyElement, ElementDescriptor, ToHtmlElement}, + leptos_dom::helpers::WindowListenerHandle, *, }; @@ -26,32 +27,37 @@ pub fn Binder( show: follower_show, children: follower_children, } = follower; - let follower_scroll_listener = store_value(None::>); - let scrollable_element_and_handle_vec = - store_value::, Option)>>(vec![]); + + let scroll_listener = store_value(None::>); + let scrollable_element_handle_vec = store_value::>(vec![]); + let resize_handle = store_value(None::); + let ensure_scroll_listener = move || { let mut cursor = target_ref.get_untracked().map(|target| target.into_any()); + let mut scrollable_element_vec = vec![]; loop { cursor = get_scroll_parent(cursor); if let Some(cursor) = cursor.take() { - scrollable_element_and_handle_vec.update_value(|vec| vec.push((cursor, None))); + scrollable_element_vec.push(cursor); } else { break; } } - scrollable_element_and_handle_vec.update_value(|vec| { - vec.iter_mut().for_each(|ele| { - ele.1 = Some(add_event_listener(&ele.0, ev::scroll, move |_| { - if let Some(follower_scroll_listener) = follower_scroll_listener.get_value() { - follower_scroll_listener.call(()); + let handle_vec = scrollable_element_vec + .into_iter() + .map(|ele| { + add_event_listener(ele, ev::scroll, move |_| { + if let Some(scroll_listener) = scroll_listener.get_value() { + scroll_listener.call(()); } - })); + }) }) - }); + .collect(); + scrollable_element_handle_vec.set_value(handle_vec); }; let add_scroll_listener = move |listener: Callback<()>| { - follower_scroll_listener.update_value(|scroll_listener| { + scroll_listener.update_value(|scroll_listener| { if scroll_listener.is_none() { ensure_scroll_listener(); } @@ -60,22 +66,47 @@ pub fn Binder( }; let remove_scroll_listener = move |_| { - scrollable_element_and_handle_vec.update_value(|vec| { - let vec: Vec<_> = vec.drain(..).collect(); - for item in vec { - if let Some(handle) = item.1 { - handle.remove(); - } + scrollable_element_handle_vec.update_value(|vec| { + vec.drain(..).into_iter().for_each(|handle| handle.remove()); + }); + scroll_listener.set_value(None); + }; + + let add_resize_listener = move |listener: Callback<()>| { + resize_handle.update_value(move |resize_handle| { + if let Some(handle) = resize_handle.take() { + handle.remove(); + } + let handle = window_event_listener(ev::resize, move |_| { + listener.call(()); + }); + + *resize_handle = Some(handle); + }); + }; + + let remove_resize_listener = move |_| { + resize_handle.update_value(move |handle| { + if let Some(handle) = handle.take() { + handle.remove(); } }); }; on_cleanup(move || { remove_scroll_listener(()); + remove_resize_listener(()); }); view! { {children()} - + {follower_children()} } @@ -87,27 +118,26 @@ fn FollowerContainer( target_ref: NodeRef, #[prop(into)] add_scroll_listener: Callback>, #[prop(into)] remove_scroll_listener: Callback<()>, + #[prop(into)] add_resize_listener: Callback>, + #[prop(into)] remove_resize_listener: Callback<()>, children: Children, ) -> impl IntoView { let content_ref = create_node_ref::(); + let content_style = create_rw_signal(String::new()); let sync_position: Callback<()> = Callback::new(move |_| { - let Some(content_ref) = content_ref.get() else { - return; - }; let Some(target_ref) = target_ref.get().map(|target| target.into_any()) else { return; }; let target_rect = target_ref.get_bounding_client_rect(); - _ = content_ref - .style("width", format!("{}px", target_rect.width())) - .style( - "transform", - format!( - "translateX({}px) translateY({}px)", - target_rect.x(), - target_rect.y() + target_rect.height() - ), - ); + + let mut style = String::new(); + style.push_str(&format!("width: {}px;", target_rect.width())); + style.push_str(&format!( + "transform: translateX({}px) translateY({}px);", + target_rect.x(), + target_rect.y() + target_rect.height() + )); + content_style.set(style); }); let is_show = create_memo(move |_| { @@ -121,8 +151,10 @@ fn FollowerContainer( if is_show { sync_position.call(()); add_scroll_listener.call(sync_position); + add_resize_listener.call(sync_position); } else { remove_scroll_listener.call(()); + remove_resize_listener.call(()); } is_show }); @@ -135,7 +167,7 @@ fn FollowerContainer( "display: none;" } }> -
+
{children()}
diff --git a/src/utils/event_listener.rs b/src/utils/event_listener.rs index 6b5b8e7..0b9df50 100644 --- a/src/utils/event_listener.rs +++ b/src/utils/event_listener.rs @@ -1,8 +1,8 @@ -use leptos::*; +use leptos::{html::AnyElement, *}; use wasm_bindgen::{prelude::Closure, JsCast}; pub fn add_event_listener( - target: &web_sys::Element, + target: HtmlElement, event: E, cb: impl Fn(E::EventType) + 'static, ) -> EventListenerHandle @@ -29,19 +29,18 @@ impl EventListenerHandle { } fn add_event_listener_untyped( - target: &web_sys::Element, + target: HtmlElement, event_name: &str, cb: impl Fn(web_sys::Event) + 'static, ) -> EventListenerHandle { fn wel( - target: &web_sys::Element, + target: HtmlElement, cb: Box, event_name: &str, ) -> EventListenerHandle { let cb = Closure::wrap(cb).into_js_value(); _ = target.add_event_listener_with_callback(event_name, cb.unchecked_ref()); let event_name = event_name.to_string(); - let target = target.clone(); EventListenerHandle(Box::new(move || { _ = target.remove_event_listener_with_callback(&event_name, cb.unchecked_ref()); }))