feat(leptos-v0.7): ClassList

This commit is contained in:
luoxiao 2024-07-08 17:33:09 +08:00 committed by luoxiaozero
parent d1909554c4
commit 918d924342
4 changed files with 143 additions and 94 deletions

View file

@ -56,6 +56,7 @@ 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

@ -80,7 +80,7 @@ pub fn Icon(
icon_data.set(Some(icon.data.to_string())); icon_data.set(Some(icon.data.to_string()));
}); });
view! { let svg = view! {
<svg <svg
class=class_list!["thaw-icon", class.map(|c| move || c.get())] class=class_list!["thaw-icon", class.map(|c| move || c.get())]
style=move || take_signal(icon_style) style=move || take_signal(icon_style)
@ -94,10 +94,12 @@ pub fn Icon(
stroke-width=move || take(icon_stroke_width) stroke-width=move || take(icon_stroke_width)
stroke=move || take(icon_stroke) stroke=move || take(icon_stroke)
fill=move || take(icon_fill) fill=move || take(icon_fill)
inner_html=move || take(icon_data) // inner_html=move || take(icon_data)
on:click=on_click on:click=on_click
></svg> ></svg>
} };
svg.inner_html(move || take(icon_data))
} }
fn take_signal(signal: RwSignal<Option<MaybeSignal<String>>>) -> String { fn take_signal(signal: RwSignal<Option<MaybeSignal<String>>>) -> String {

View file

@ -5,13 +5,15 @@ mod toast_title;
mod toaster; mod toaster;
mod toaster_provider; mod toaster_provider;
use tachys::view::any_view::AnyView;
pub use toast::*; pub use toast::*;
pub use toast_body::*;
pub use toast_footer::*;
pub use toast_title::*; pub use toast_title::*;
pub use toaster_provider::*; pub use toaster_provider::*;
use leptos::prelude::*; use leptos::prelude::*;
use std::sync::mpsc::{channel, Receiver, Sender, TryIter}; use std::sync::mpsc::{channel, Receiver, Sender, TryIter};
use tachys::view::any_view::AnyView;
#[derive(Clone)] #[derive(Clone)]
pub struct ToasterInjection { pub struct ToasterInjection {

View file

@ -1,26 +1,34 @@
#[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::traits::{Get, Update, With, WithUntracked}, reactive_graph::{
graph::{AnySubscriber, ToAnySubscriber},
traits::{Get, Update, With, WithUntracked},
},
tachys::renderer::DomRenderer, tachys::renderer::DomRenderer,
}; };
use std::collections::HashSet; use std::{collections::HashSet, sync::Arc};
#[derive(Clone)] #[derive(Clone, Default)]
pub struct ClassList(RwSignal<HashSet<Oco<'static, str>>>); pub struct ClassList {
value: RwSignal<HashSet<Oco<'static, str>>>,
effects: Vec<AnySubscriber>,
effects_bool: Vec<Arc<RenderEffect<bool>>>,
}
impl ClassList { impl ClassList {
pub fn new() -> Self { pub fn new() -> Self {
Self(RwSignal::new(HashSet::new())) Default::default()
} }
pub fn add(self, value: impl IntoClass) -> Self { pub fn add(mut self, value: impl IntoClass) -> Self {
let class = value.into_class(); let class = value.into_class();
match class { match class {
Class::None => (), Class::None => (),
Class::String(name) => { Class::String(name) => {
self.0.update(move |set| { self.value.update(move |set| {
set.insert(name); set.insert(name);
}); });
} }
@ -33,22 +41,25 @@ impl ClassList {
}); });
} }
#[cfg(not(feature = "ssr"))] #[cfg(not(feature = "ssr"))]
let _ = RenderEffect::new(move |old_name| { {
let name = f(); let effect = RenderEffect::new(move |old_name| {
if let Some(old_name) = old_name { let name = f();
if old_name != name { if let Some(old_name) = old_name {
self.0.update(|set| { if old_name != name {
set.remove(&old_name); self.value.update(|set| {
set.remove(&old_name);
set.insert(name.clone());
});
}
} else {
self.value.update(|set| {
set.insert(name.clone()); set.insert(name.clone());
}); });
} }
} else { name
self.0.update(|set| { });
set.insert(name.clone()); self.effects.push(effect.to_any_subscriber());
}); }
}
name
});
} }
Class::FnOptionString(f) => { Class::FnOptionString(f) => {
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
@ -60,33 +71,36 @@ impl ClassList {
} }
} }
#[cfg(not(feature = "ssr"))] #[cfg(not(feature = "ssr"))]
let _ = RenderEffect::new(move |old_name| { {
let name = f(); let effect = RenderEffect::new(move |old_name| {
if let Some(old_name) = old_name { let name = f();
if old_name != name { if let Some(old_name) = old_name {
self.0.update(|set| match (old_name, name.clone()) { if old_name != name {
(None, Some(name)) => { self.value.update(|set| match (old_name, name.clone()) {
set.insert(name); (None, Some(name)) => {
} set.insert(name);
(Some(old_name), None) => { }
set.remove(&old_name); (Some(old_name), None) => {
} set.remove(&old_name);
(Some(old_name), Some(name)) => { }
set.remove(&old_name); (Some(old_name), Some(name)) => {
set.insert(name); set.remove(&old_name);
} set.insert(name);
_ => {} }
}); _ => {}
});
}
} else {
if let Some(name) = name.clone() {
self.value.update(|set| {
set.insert(name.clone());
});
}
} }
} else { name
if let Some(name) = name.clone() { });
self.0.update(|set| { self.effects.push(effect.to_any_subscriber());
set.insert(name.clone()); }
});
}
}
name
});
} }
Class::Fn(name, f) => { Class::Fn(name, f) => {
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
@ -99,34 +113,38 @@ impl ClassList {
}); });
} }
#[cfg(not(feature = "ssr"))] #[cfg(not(feature = "ssr"))]
let _ = RenderEffect::new(move |old| { {
let name = name.clone(); let effect = RenderEffect::new(move |old| {
let new = f(); let name = name.clone();
if old.is_none() { let new = f();
if new { log!("ClassList: {name} {new} {old:#?}");
self.0.update(|set| { if old.is_none() {
set.insert(name); if new {
self.value.update(|set| {
set.insert(name);
});
}
} else if old.as_ref() != Some(&new) {
self.value.update(|set| {
if new {
set.insert(name);
} else {
set.remove(&name);
}
}); });
} }
} else if old.as_ref() != Some(&new) { new
self.0.update(|set| { });
if new { self.effects_bool.push(effect.into());
set.insert(name); }
} else {
set.remove(&name);
}
});
}
new
});
} }
} }
self self
} }
fn to_class_string(self, class: &mut String) { fn write_class_string(&self, class: &mut String) {
self.0.with(|set| { self.value.with(|set| {
set.iter().enumerate().for_each(|(index, name)| { set.iter().enumerate().for_each(|(index, name)| {
if name.is_empty() { if name.is_empty() {
return; return;
@ -145,12 +163,12 @@ where
R: DomRenderer, R: DomRenderer,
{ {
type AsyncOutput = Self; type AsyncOutput = Self;
type State = (R::Element, String); type State = RenderEffect<(R::Element, String)>;
type Cloneable = Self; type Cloneable = Self;
type CloneableOwned = Self; type CloneableOwned = Self;
fn html_len(&self) -> usize { fn html_len(&self) -> usize {
self.0.with_untracked(|set| { self.value.with_untracked(|set| {
let mut len = 0; let mut len = 0;
set.iter().enumerate().for_each(|(index, name)| { set.iter().enumerate().for_each(|(index, name)| {
if name.is_empty() { if name.is_empty() {
@ -167,37 +185,63 @@ where
} }
fn to_html(self, class: &mut String) { fn to_html(self, class: &mut String) {
self.to_class_string(class); self.write_class_string(class);
} }
fn hydrate<const FROM_SERVER: bool>(self, el: &R::Element) -> Self::State { fn hydrate<const FROM_SERVER: bool>(self, el: &R::Element) -> Self::State {
let class_list = R::class_list(el); let el = el.to_owned();
let mut class = String::new(); RenderEffect::new(move |prev| {
self.to_class_string(&mut class); if let Some(_) = prev {
unreachable!()
if !FROM_SERVER { } else {
R::add_class(&class_list, &class); let mut class = String::new();
} self.write_class_string(&mut class);
(el.clone(), class) if !class.is_empty() {
if !FROM_SERVER {
R::set_attribute(&el, "class", &class);
}
}
(el.clone(), class)
}
})
} }
fn build(self, el: &R::Element) -> Self::State { fn build(self, el: &R::Element) -> Self::State {
let mut class = String::new(); let el = el.to_owned();
self.to_class_string(&mut class); RenderEffect::new(move |prev| {
if !class.is_empty() { if let Some(_) = prev {
R::set_attribute(el, "class", &class); unreachable!()
} } else {
(el.clone(), class) let mut class = String::new();
self.write_class_string(&mut class);
if !class.is_empty() {
R::set_attribute(&el, "class", &class);
}
(el.clone(), class)
}
})
} }
fn rebuild(self, state: &mut Self::State) { fn rebuild(self, state: &mut Self::State) {
let mut class = String::new(); let prev = state.take_value();
self.to_class_string(&mut class); *state = RenderEffect::new_with_value(
let (el, prev_class) = state; move |prev| {
if class != *prev_class { if let Some(state) = prev {
R::set_attribute(el, "class", &class); let mut class = String::new();
} self.write_class_string(&mut class);
*prev_class = class; let (el, prev_class) = state;
if class != *prev_class {
R::set_attribute(&el, "class", &class);
(el, class)
} else {
(el, prev_class)
}
} else {
unreachable!()
}
},
prev,
);
} }
fn into_cloneable(self) -> Self::Cloneable { fn into_cloneable(self) -> Self::Cloneable {