feat(lepots-v0.7): Binder

This commit is contained in:
luoxiao 2024-07-10 00:05:22 +08:00 committed by luoxiaozero
parent 54b23447ee
commit 4262493f61
4 changed files with 83 additions and 138 deletions

View file

@ -1,5 +1,5 @@
use super::switch_version::SwitchVersion;
use leptos::{prelude::*, ev};
use leptos::{ev, prelude::*};
use leptos_meta::Style;
use leptos_router::hooks::use_navigate;
// use leptos_use::{storage::use_local_storage, utils::FromToStringCodec};
@ -7,6 +7,7 @@ use thaw::*;
#[component]
pub fn SiteHeader() -> impl IntoView {
let navigate = use_navigate();
let theme = Theme::use_rw_theme();
let theme_name = Memo::new(move |_| {
theme.with(|theme| {
@ -65,9 +66,11 @@ pub fn SiteHeader() -> impl IntoView {
.collect()
})
});
let on_search_select = move |path: String| {
let navigate = use_navigate();
let on_search_select = {
let navigate = navigate.clone();
move |path: String| {
navigate(&path, Default::default());
}
};
let auto_complete_ref = ComponentRef::<AutoCompleteRef>::new();
let handle = window_event_listener(ev::keydown, move |event| {
@ -132,7 +135,6 @@ pub fn SiteHeader() -> impl IntoView {
</Style>
<LayoutHeader attr:class=("demo-header", true)>
<Space on:click=move |_| {
let navigate = use_navigate();
navigate("/", Default::default());
}>
<img src="/logo.svg" style="width: 36px"/>
@ -274,5 +276,3 @@ fn gen_search_all_options() -> Vec<AutoCompleteOption> {
// menu_value
// }

View file

@ -1,14 +1,14 @@
use leptos::prelude::*;
use leptos_router::hooks::{use_navigate, use_query_map};
// use leptos_router::{use_navigate, use_query_map};
use thaw::*;
#[component]
pub fn Home() -> impl IntoView {
let query_map = use_query_map().get_untracked();
let navigate = use_navigate();
// mobile page
if let Some(path) = query_map.get("path") {
let navigate = use_navigate();
navigate(&path, Default::default());
}
view! {
@ -22,7 +22,6 @@ pub fn Home() -> impl IntoView {
<Button
appearance=ButtonAppearance::Primary
on_click=move |_| {
let navigate = use_navigate();
navigate("/components/button", Default::default());
}
>

View file

@ -3,7 +3,7 @@ mod auto_complete_option;
pub use auto_complete_option::AutoCompleteOption;
use crate::{ComponentRef, ConfigInjection, Input, InputPrefix, InputRef, InputSuffix};
use leptos::{context::Provider, html, prelude::*};
use leptos::{context::Provider, either::Either, html, prelude::*};
use thaw_components::{
Binder, CSSTransition, Follower, FollowerPlacement, FollowerWidth, OptionComp,
};
@ -194,10 +194,13 @@ pub fn AutoComplete(
node_ref=menu_ref
role="listbox"
>
<OptionComp value=children let:children>
{children()}
</OptionComp>
{
if let Some(children) = children {
Either::Left(children())
} else {
Either::Right(())
}
}
</div>
</CSSTransition>
</Provider>

View file

@ -81,117 +81,12 @@ where
children: follower_children,
} = follower;
let scroll_listener = StoredValue::new(None::<Callback<()>>);
let scrollable_element_handle_vec = StoredValue::<Vec<EventListenerHandle>>::new(vec![]);
let resize_handle = StoredValue::new(None::<WindowListenerHandle>);
let ensure_scroll_listener = Callback::new(move |_: ()| {
let target_ref = target_ref.get_untracked();
let Some(el) = target_ref.as_deref() else {
return;
};
let mut handle_vec = vec![];
let mut cursor = get_scroll_parent(&el);
loop {
if let Some(el) = cursor.take() {
cursor = get_scroll_parent(&el);
let handle = add_event_listener(el, ev::scroll, move |_| {
if let Some(scroll_listener) = scroll_listener.get_value() {
scroll_listener.call(());
}
});
handle_vec.push(handle);
} else {
break;
}
}
scrollable_element_handle_vec.set_value(handle_vec);
});
let add_scroll_listener = Callback::new(move |listener: Callback<()>| {
scroll_listener.update_value(|scroll_listener| {
if scroll_listener.is_none() {
ensure_scroll_listener.call(());
}
*scroll_listener = Some(listener);
})
});
let remove_scroll_listener = Callback::new(move |_| {
scrollable_element_handle_vec.update_value(|vec| {
vec.drain(..).for_each(|handle| handle.remove());
});
scroll_listener.set_value(None);
});
let add_resize_listener = Callback::new(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 = Callback::new(move |_| {
resize_handle.update_value(move |handle| {
if let Some(handle) = handle.take() {
handle.remove();
}
});
});
on_cleanup({
let remove_scroll_listener = remove_scroll_listener.clone();
let remove_resize_listener = remove_resize_listener.clone();
move || {
remove_scroll_listener.call(());
remove_resize_listener.call(());
}
});
view! {
{children()}
<FollowerContainer
show=follower_show
target_ref
width=follower_width
placement=follower_placement
add_scroll_listener
remove_scroll_listener
add_resize_listener
remove_resize_listener
>
{follower_children()}
</FollowerContainer>
}
}
#[component]
fn FollowerContainer<E>(
show: MaybeSignal<bool>,
target_ref: NodeRef<E>,
width: Option<FollowerWidth>,
placement: FollowerPlacement,
#[prop(into)] add_scroll_listener: Callback<Callback<()>>,
#[prop(into)] remove_scroll_listener: Callback<()>,
#[prop(into)] add_resize_listener: Callback<Callback<()>>,
#[prop(into)] remove_resize_listener: Callback<()>,
children: Children,
) -> impl IntoView
where
E: ElementType + 'static,
E::Output: JsCast + Clone + Deref<Target = web_sys::HtmlElement> + 'static,
{
let content_ref = NodeRef::<html::Div>::new();
let content_style = RwSignal::new(String::new());
let placement_str = RwSignal::new(placement.as_str());
let sync_position: Callback<()> = Callback::new(move |_| {
let placement_str = RwSignal::new(follower_placement.as_str());
let sync_position = move || {
let Some(content_ref) = content_ref.get_untracked() else {
return;
};
@ -201,7 +96,7 @@ where
let target_rect = target_ref.get_bounding_client_rect();
let content_rect = content_ref.get_bounding_client_rect();
let mut style = String::new();
if let Some(width) = width {
if let Some(width) = follower_width {
let width = match width {
FollowerWidth::Target => format!("width: {}px;", target_rect.width()),
FollowerWidth::MinTarget => format!("min-width: {}px;", target_rect.width()),
@ -214,7 +109,7 @@ where
left,
transform,
placement,
}) = get_follower_placement_offset(placement, target_rect, content_rect)
}) = get_follower_placement_offset(follower_placement, target_rect, content_rect)
{
placement_str.set(placement.as_str());
style.push_str(&format!(
@ -229,7 +124,51 @@ where
}
content_style.set(style);
};
let ensure_listener = move || {
let target_ref = target_ref.get_untracked();
let Some(el) = target_ref.as_deref() else {
return;
};
let mut handle_vec = vec![];
let mut cursor = get_scroll_parent(&el);
loop {
if let Some(el) = cursor.take() {
cursor = get_scroll_parent(&el);
let handle = add_event_listener(el, ev::scroll, move |_| {
sync_position();
});
handle_vec.push(handle);
} else {
break;
}
}
scrollable_element_handle_vec.set_value(handle_vec);
resize_handle.update_value(move |resize_handle| {
if let Some(handle) = resize_handle.take() {
handle.remove();
}
let handle = window_event_listener(ev::resize, move |_| {
sync_position();
});
*resize_handle = Some(handle);
});
};
let remove_listener = move || {
scrollable_element_handle_vec.update_value(|vec| {
vec.drain(..).for_each(|handle| handle.remove());
});
resize_handle.update_value(move |handle| {
if let Some(handle) = handle.take() {
handle.remove();
}
});
};
Effect::new(move |_| {
if target_ref.get().is_none() {
@ -238,31 +177,35 @@ where
if content_ref.get().is_none() {
return;
}
if show.get() {
request_animation_frame({
let sync_position = sync_position.clone();
move || {
sync_position.call(());
}
if follower_show.get() {
request_animation_frame(move || {
sync_position();
});
add_scroll_listener.call(sync_position.clone());
add_resize_listener.call(sync_position.clone());
remove_listener();
ensure_listener();
} else {
remove_scroll_listener.call(());
remove_resize_listener.call(());
remove_listener();
}
});
let children = with_hydration_off(|| {
on_cleanup(move || {
remove_listener();
});
let teleport_children = with_hydration_off(|| {
html::div().class("thaw-binder-follower-container").child(
html::div()
.class("thaw-binder-follower-content")
.attr("data-thaw-placement", move || placement_str.get())
.node_ref(content_ref)
.attr("style", move || content_style.get())
.child(children()),
.child(follower_children()),
)
});
view! { <Teleport element=children.into_any() immediate=show/> }
view! {
{children()}
<Teleport element=teleport_children.into_any() immediate=follower_show/>
}
}