thaw/thaw_utils/src/class_list.rs
Yu Sun 18658044c2
Use fully qualified syntax in the example of Drawer (#187)
* perf: replace `expect` with `unwrap_or_else`

The macro/functions in `expect` is not lazy, which means it will always be called

* style: remove needless borrowing

* perf: remove needless clone

* style: remove needless `format!`

* style: use `and_then` instead

* style: use `?` instead

* style: remove needless closure

* fix: use fully qualified syntax instead

https://github.com/rust-lang/rust/issues/48919

* style: formatted

* style: fix some other clippy issues
2024-05-07 23:36:36 +08:00

208 lines
5.4 KiB
Rust

#[cfg(not(feature = "ssr"))]
use leptos::create_render_effect;
use leptos::{Attribute, IntoAttribute, Oco, RwSignal, SignalUpdate, SignalWith};
use std::{collections::HashSet, rc::Rc};
pub struct ClassList(RwSignal<HashSet<Oco<'static, str>>>);
impl Default for ClassList {
fn default() -> Self {
Self::new()
}
}
impl ClassList {
pub fn new() -> Self {
Self(RwSignal::new(HashSet::new()))
}
pub fn add(self, value: impl IntoClass) -> Self {
let class = value.into_class();
match class {
Class::None => (),
Class::String(name) => {
self.0.update(move |set| {
set.insert(name);
});
}
Class::FnString(f) => {
#[cfg(feature = "ssr")]
{
let name = f();
self.0.update(|set| {
set.insert(name);
});
}
#[cfg(not(feature = "ssr"))]
create_render_effect(move |old_name| {
let name = f();
if let Some(old_name) = old_name {
if old_name != name {
self.0.update(|set| {
set.remove(&old_name);
set.insert(name.clone());
});
}
} else {
self.0.update(|set| {
set.insert(name.clone());
});
}
name
});
}
Class::Fn(name, f) => {
#[cfg(feature = "ssr")]
{
let new = f();
self.0.update(|set| {
if new {
set.insert(name);
}
});
}
#[cfg(not(feature = "ssr"))]
create_render_effect(move |old| {
let name = name.clone();
let new = f();
if old.is_none() {
if new {
self.0.update(|set| {
set.insert(name);
});
}
} else if old.as_ref() != Some(&new) {
self.0.update(|set| {
if new {
set.insert(name);
} else {
set.remove(&name);
}
});
}
new
});
}
}
self
}
}
impl IntoAttribute for ClassList {
fn into_attribute(self) -> Attribute {
Attribute::Fn(Rc::new(move || {
self.0.with(|set| {
let mut class = String::new();
set.iter().enumerate().for_each(|(index, name)| {
if name.is_empty() {
return;
}
if index != 0 {
class.push(' ');
}
class.push_str(name)
});
class.into_attribute()
})
}))
}
fn into_attribute_boxed(self: Box<Self>) -> Attribute {
self.into_attribute()
}
}
pub enum Class {
None,
String(Oco<'static, str>),
FnString(Box<dyn Fn() -> Oco<'static, str>>),
Fn(Oco<'static, str>, Box<dyn Fn() -> bool>),
}
pub trait IntoClass {
fn into_class(self) -> Class;
}
impl IntoClass for String {
fn into_class(self) -> Class {
Class::String(self.into())
}
}
impl IntoClass for &'static str {
fn into_class(self) -> Class {
Class::String(self.into())
}
}
impl<T, U> IntoClass for T
where
T: Fn() -> U + 'static,
U: ToString,
{
fn into_class(self) -> Class {
Class::FnString(Box::new(move || (self)().to_string().into()))
}
}
impl<T, U> IntoClass for Option<T>
where
T: Fn() -> U + 'static,
U: ToString,
{
fn into_class(self) -> Class {
if let Some(f) = self {
Class::FnString(Box::new(move || f().to_string().into()))
} else {
Class::None
}
}
}
impl<T> IntoClass for (&'static str, T)
where
T: Fn() -> bool + 'static,
{
fn into_class(self) -> Class {
Class::Fn(self.0.into(), Box::new(self.1))
}
}
impl<T> IntoClass for (String, T)
where
T: Fn() -> bool + 'static,
{
fn into_class(self) -> Class {
Class::Fn(self.0.into(), Box::new(self.1))
}
}
#[macro_export]
macro_rules! class_list {
($($name:expr),+) => {
{
use $crate::class_list::ClassList;
ClassList::new()$(.add($name))+
}
};
}
#[cfg(test)]
mod tests {
use leptos::{create_runtime, Attribute, IntoAttribute};
#[test]
fn macro_class_list() {
let rt = create_runtime();
let class_list = class_list!("aa", ("bb", || true), move || "cc");
if let Attribute::Fn(f) = class_list.into_attribute() {
if let Attribute::String(class) = f() {
assert!(class.contains("aa"));
assert!(class.contains("bb"));
assert!(class.contains("cc"));
}
}
rt.dispose();
}
}