feat(lepots-v0.7): CSSTransition and ClassList

This commit is contained in:
luoxiao 2024-07-08 23:13:15 +08:00 committed by luoxiaozero
parent 918d924342
commit 8ccef81e44
6 changed files with 118 additions and 128 deletions

View file

@ -56,7 +56,6 @@ pub fn ComponentsPage() -> impl IntoView {
<SiteHeader/> <SiteHeader/>
<Layout has_sider=true position=LayoutPosition::Absolute attr:style="top: 64px;"> <Layout has_sider=true position=LayoutPosition::Absolute attr:style="top: 64px;">
<div class="demo-components__sider"> <div class="demo-components__sider">
{move || select_name.get()}
<NavDrawer selected_value=select_name> <NavDrawer selected_value=select_name>
{ {
gen_menu_data().into_iter().map(|data| { gen_menu_data().into_iter().map(|data| {

View file

@ -59,7 +59,7 @@ pub fn AccordionItem(
<div <div
class="thaw-accordion-panel" class="thaw-accordion-panel"
node_ref=panel_ref node_ref=panel_ref
// style=("display: none", display.with(|d| d.is_none())) style=move || display.get().unwrap_or_default()
> >
{children()} {children()}
</div> </div>

View file

@ -1,10 +1,8 @@
use leptos::prelude::*; use leptos::{either::Either, prelude::*};
use thaw_components::OptionComp;
use thaw_utils::{class_list, mount_style}; use thaw_utils::{class_list, mount_style};
#[component] #[component]
pub fn Table( pub fn Table(
#[prop(optional, into)] style: MaybeProp<String>,
#[prop(optional, into)] class: MaybeProp<String>, #[prop(optional, into)] class: MaybeProp<String>,
children: Children, children: Children,
) -> impl IntoView { ) -> impl IntoView {
@ -31,9 +29,13 @@ pub fn TableHeaderCell(#[prop(optional)] children: Option<Children>) -> impl Int
view! { view! {
<th class="thaw-table-header-cell"> <th class="thaw-table-header-cell">
<button class="thaw-table-header-cell__button" role="presentation"> <button class="thaw-table-header-cell__button" role="presentation">
<OptionComp value=children let:children> {
{children()} if let Some(children) = children {
</OptionComp> Either::Left(children())
} else {
Either::Right(())
}
}
</button> </button>
</th> </th>
} }
@ -61,9 +63,13 @@ pub fn TableRow(children: Children) -> impl IntoView {
pub fn TableCell(#[prop(optional)] children: Option<Children>) -> impl IntoView { pub fn TableCell(#[prop(optional)] children: Option<Children>) -> impl IntoView {
view! { view! {
<td class="thaw-table-cell"> <td class="thaw-table-cell">
<OptionComp value=children let:children> {
{children()} if let Some(children) = children {
</OptionComp> Either::Left(children())
} else {
Either::Right(())
}
}
</td> </td>
} }
} }

View file

@ -27,6 +27,10 @@ where
IV: IntoView + 'static, IV: IntoView + 'static,
{ {
let display = RwSignal::new((!show.get_untracked()).then_some("display: none;")); let display = RwSignal::new((!show.get_untracked()).then_some("display: none;"));
let next_frame = NextFrame::new();
let end_handle = StoredValue::new(None::<EventListenerHandle>);
let end_count = StoredValue::new(None::<usize>);
let finish = StoredValue::new(None::<Box<dyn FnOnce() + Send + Sync>>);
Effect::new(move |_| { Effect::new(move |_| {
let target_ref = node_ref.get(); let target_ref = node_ref.get();
@ -35,27 +39,23 @@ where
}; };
let class_list = el.class_list(); let class_list = el.class_list();
let next_frame = NextFrame::use_();
let end_handle = StoredValue::new(None::<EventListenerHandle>);
let end_count = StoredValue::new(None::<usize>);
let finish = StoredValue::new(None::<Callback<()>>);
let on_end = Callback::new({ let on_end = {
let el = send_wrapper::SendWrapper::new(el.clone()); let el = send_wrapper::SendWrapper::new(el.clone());
move |remove: Callback<()>| { move |remove: Box<dyn FnOnce() + Send + Sync>| {
let Some(CSSTransitionInfo { let Some(CSSTransitionInfo {
types, types,
prop_count, prop_count,
timeout, timeout,
}) = get_transition_info(&el) }) = get_transition_info(&el)
else { else {
remove.call(()); remove();
return; return;
}; };
finish.set_value(Some(Callback::new(move |_| { finish.set_value(Some(Box::new(move || {
end_count.set_value(None); end_count.set_value(None);
remove.call(()); remove();
end_handle.update_value(|h| { end_handle.update_value(|h| {
h.take().map(|h| { h.take().map(|h| {
h.remove(); h.remove();
@ -65,9 +65,9 @@ where
set_timeout( set_timeout(
move || { move || {
finish.try_update_value(|v| { if let Some(Some(f)) = finish.try_update_value(|f| f.take()) {
v.take().map(|f| f.call(())); f();
}); }
}, },
Duration::from_millis(timeout + 1), Duration::from_millis(timeout + 1),
); );
@ -86,9 +86,9 @@ where
}; };
*v >= prop_count *v >= prop_count
}) { }) {
finish.update_value(|v| { if let Some(Some(f)) = finish.try_update_value(|f| f.take()) {
v.take().map(|f| f.call(())); f();
}); }
} }
}; };
let handle = match types { let handle = match types {
@ -105,22 +105,31 @@ where
}; };
end_handle.set_value(Some(handle)); end_handle.set_value(Some(handle));
} }
});
let on_finish = move || {
finish.update_value(|v| {
v.take().map(|f| f.call(()));
});
}; };
let on_enter_fn = { let on_finish = move || {
let class_list = class_list.clone(); if let Some(Some(f)) = finish.try_update_value(|f| f.take()) {
let class_list = send_wrapper::SendWrapper::new(class_list); f();
let on_before_enter = on_before_enter.clone(); }
let on_after_enter = on_after_enter.clone(); };
let on_enter = on_enter.clone();
let on_end = on_end.clone(); let name = name.clone();
Callback::new(move |name: String| { let effect = RenderEffect::new(move |prev: Option<bool>| {
let show = show.get();
let prev = if let Some(prev) = prev {
prev
} else if show && appear {
false
} else {
return show;
};
let name = name.get_untracked();
if show && !prev {
on_finish();
{
// on_enter
if let Some(on_before_enter) = on_before_enter.as_ref() { if let Some(on_before_enter) = on_before_enter.as_ref() {
on_before_enter.call(()); on_before_enter.call(());
} }
@ -132,36 +141,29 @@ where
display.set(None); display.set(None);
let class_list = class_list.clone(); let class_list = class_list.clone();
let on_after_enter = on_after_enter.clone();
let on_end = on_end.clone(); let on_end = on_end.clone();
let on_enter = on_enter.clone();
next_frame.run(move || { next_frame.run(move || {
let _ = class_list.remove_1(&enter_from); let _ = class_list.remove_1(&enter_from);
let _ = class_list.add_1(&enter_to); let _ = class_list.add_1(&enter_to);
let remove = Callback::new(move |_| { let class_list = send_wrapper::SendWrapper::new(class_list);
let remove = Box::new(move || {
let _ = class_list.remove_2(&enter_active, &enter_to); let _ = class_list.remove_2(&enter_active, &enter_to);
if let Some(on_after_enter) = on_after_enter.as_ref() { if let Some(on_after_enter) = on_after_enter.as_ref() {
on_after_enter.call(()); on_after_enter.call(());
} }
}); });
on_end.call(remove); on_end(remove);
if let Some(on_enter) = on_enter { if let Some(on_enter) = on_enter.as_ref() {
on_enter.call(()); on_enter.call(());
} }
}); });
}) }
}; } else if !show && prev {
on_finish();
let on_leave_fn = { {
let class_list = class_list.clone(); // on_leave
let class_list = send_wrapper::SendWrapper::new(class_list);
let on_before_leave = on_before_leave.clone();
let on_after_leave = on_after_leave.clone();
let on_leave = on_leave.clone();
let on_end = on_end.clone();
Callback::new(move |name: String| {
if let Some(on_before_leave) = on_before_leave.as_ref() { if let Some(on_before_leave) = on_before_leave.as_ref() {
on_before_leave.call(()); on_before_leave.call(());
} }
@ -179,46 +181,27 @@ where
let _ = class_list.remove_1(&leave_from); let _ = class_list.remove_1(&leave_from);
let _ = class_list.add_1(&leave_to); let _ = class_list.add_1(&leave_to);
let remove = Callback::new(move |_| { let class_list = send_wrapper::SendWrapper::new(class_list);
let remove = Box::new(move || {
let _ = class_list.remove_2(&leave_active, &leave_to); let _ = class_list.remove_2(&leave_active, &leave_to);
display.set(Some("display: none;")); display.set(Some("display: none;"));
if let Some(on_after_leave) = on_after_leave.as_ref() { if let Some(on_after_leave) = on_after_leave.as_ref() {
on_after_leave.call(()); on_after_leave.call(());
} }
}); });
on_end.call(remove); on_end(remove);
if let Some(on_leave) = on_leave { if let Some(on_leave) = on_leave {
on_leave.call(()); on_leave.call(());
} }
}); });
}) }
};
let name = name.clone();
let _ = RenderEffect::new(move |prev: Option<bool>| {
let show = show.get();
let prev = if let Some(prev) = prev {
prev
} else if show && appear {
false
} else {
return show;
};
let name = name.get_untracked();
if show && !prev {
on_finish();
on_enter_fn.call(name);
} else if !show && prev {
on_finish();
on_leave_fn.call(name);
} }
show show
}); });
on_cleanup(move || { on_cleanup(move || {
drop(effect);
end_handle.update_value(|handle| { end_handle.update_value(|handle| {
if let Some(handle) = handle.take() { if let Some(handle) = handle.take() {
handle.remove(); handle.remove();

View file

@ -1,12 +1,8 @@
#[cfg(not(feature = "ssr"))] #[cfg(not(feature = "ssr"))]
use leptos::prelude::RenderEffect; use leptos::prelude::RenderEffect;
use leptos::{ use leptos::{
logging::log,
prelude::{MaybeProp, Memo, Oco, RwSignal}, prelude::{MaybeProp, Memo, Oco, RwSignal},
reactive_graph::{ reactive_graph::traits::{Get, Update, With, WithUntracked},
graph::{AnySubscriber, ToAnySubscriber},
traits::{Get, Update, With, WithUntracked},
},
tachys::renderer::DomRenderer, tachys::renderer::DomRenderer,
}; };
use std::{collections::HashSet, sync::Arc}; use std::{collections::HashSet, sync::Arc};
@ -14,7 +10,8 @@ use std::{collections::HashSet, sync::Arc};
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct ClassList { pub struct ClassList {
value: RwSignal<HashSet<Oco<'static, str>>>, value: RwSignal<HashSet<Oco<'static, str>>>,
effects: Vec<AnySubscriber>, effects_oco: Vec<Arc<RenderEffect<Oco<'static, str>>>>,
effects_option_oco: Vec<Arc<RenderEffect<Option<Oco<'static, str>>>>>,
effects_bool: Vec<Arc<RenderEffect<bool>>>, effects_bool: Vec<Arc<RenderEffect<bool>>>,
} }
@ -58,7 +55,7 @@ impl ClassList {
} }
name name
}); });
self.effects.push(effect.to_any_subscriber()); self.effects_oco.push(effect.into());
} }
} }
Class::FnOptionString(f) => { Class::FnOptionString(f) => {
@ -99,7 +96,7 @@ impl ClassList {
} }
name name
}); });
self.effects.push(effect.to_any_subscriber()); self.effects_option_oco.push(effect.into());
} }
} }
Class::Fn(name, f) => { Class::Fn(name, f) => {
@ -117,7 +114,6 @@ impl ClassList {
let effect = RenderEffect::new(move |old| { let effect = RenderEffect::new(move |old| {
let name = name.clone(); let name = name.clone();
let new = f(); let new = f();
log!("ClassList: {name} {new} {old:#?}");
if old.is_none() { if old.is_none() {
if new { if new {
self.value.update(|set| { self.value.update(|set| {
@ -209,11 +205,17 @@ where
fn build(self, el: &R::Element) -> Self::State { fn build(self, el: &R::Element) -> Self::State {
let el = el.to_owned(); let el = el.to_owned();
RenderEffect::new(move |prev| { RenderEffect::new(move |prev| {
if let Some(_) = prev {
unreachable!()
} else {
let mut class = String::new(); let mut class = String::new();
self.write_class_string(&mut class); self.write_class_string(&mut class);
if let Some(state) = prev {
let (el, prev_class) = state;
if class != prev_class {
R::set_attribute(&el, "class", &class);
(el, class)
} else {
(el, prev_class)
}
} else {
if !class.is_empty() { if !class.is_empty() {
R::set_attribute(&el, "class", &class); R::set_attribute(&el, "class", &class);
} }

View file

@ -20,7 +20,7 @@ impl Default for NextFrame {
impl Copy for NextFrame {} impl Copy for NextFrame {}
impl NextFrame { impl NextFrame {
pub fn use_() -> Self { pub fn new() -> Self {
let next_frame = NextFrame::default(); let next_frame = NextFrame::default();
on_cleanup(move || { on_cleanup(move || {