2023-12-23 12:25:54 +08:00
|
|
|
#[cfg(not(feature = "ssr"))]
|
2024-06-13 16:00:14 +08:00
|
|
|
use leptos::prelude::RenderEffect;
|
2024-05-14 17:39:13 +08:00
|
|
|
use leptos::{
|
2024-06-13 16:00:14 +08:00
|
|
|
prelude::{MaybeProp, Memo, Oco, RwSignal},
|
|
|
|
reactive_graph::traits::{Get, Update, With, WithUntracked},
|
|
|
|
tachys::renderer::DomRenderer,
|
2024-05-14 17:39:13 +08:00
|
|
|
};
|
2024-06-13 16:00:14 +08:00
|
|
|
use std::collections::HashSet;
|
2023-12-23 12:25:54 +08:00
|
|
|
|
2024-06-13 16:00:14 +08:00
|
|
|
#[derive(Clone)]
|
2023-12-23 12:25:54 +08:00
|
|
|
pub struct ClassList(RwSignal<HashSet<Oco<'static, str>>>);
|
|
|
|
|
|
|
|
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 {
|
2024-01-31 20:13:26 +08:00
|
|
|
Class::None => (),
|
2023-12-23 12:25:54 +08:00
|
|
|
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"))]
|
2024-07-08 14:40:17 +08:00
|
|
|
let _ = RenderEffect::new(move |old_name| {
|
2023-12-23 12:25:54 +08:00
|
|
|
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
|
|
|
|
});
|
|
|
|
}
|
2024-05-14 17:39:13 +08:00
|
|
|
Class::FnOptionString(f) => {
|
|
|
|
#[cfg(feature = "ssr")]
|
|
|
|
{
|
|
|
|
if let Some(name) = f() {
|
|
|
|
self.0.update(|set| {
|
|
|
|
set.insert(name);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#[cfg(not(feature = "ssr"))]
|
2024-07-08 14:40:17 +08:00
|
|
|
let _ = RenderEffect::new(move |old_name| {
|
2024-05-14 17:39:13 +08:00
|
|
|
let name = f();
|
|
|
|
if let Some(old_name) = old_name {
|
|
|
|
if old_name != name {
|
|
|
|
self.0.update(|set| match (old_name, name.clone()) {
|
|
|
|
(None, Some(name)) => {
|
|
|
|
set.insert(name);
|
|
|
|
}
|
|
|
|
(Some(old_name), None) => {
|
|
|
|
set.remove(&old_name);
|
|
|
|
}
|
|
|
|
(Some(old_name), Some(name)) => {
|
|
|
|
set.remove(&old_name);
|
|
|
|
set.insert(name);
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if let Some(name) = name.clone() {
|
|
|
|
self.0.update(|set| {
|
|
|
|
set.insert(name.clone());
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
name
|
|
|
|
});
|
|
|
|
}
|
2023-12-23 12:25:54 +08:00
|
|
|
Class::Fn(name, f) => {
|
|
|
|
#[cfg(feature = "ssr")]
|
|
|
|
{
|
|
|
|
let new = f();
|
|
|
|
self.0.update(|set| {
|
|
|
|
if new {
|
|
|
|
set.insert(name);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
#[cfg(not(feature = "ssr"))]
|
2024-07-08 14:40:17 +08:00
|
|
|
let _ = RenderEffect::new(move |old| {
|
2023-12-23 12:25:54 +08:00
|
|
|
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
|
|
|
|
}
|
2024-06-13 16:00:14 +08:00
|
|
|
|
|
|
|
fn to_class_string(self, class: &mut String) {
|
|
|
|
self.0.with(|set| {
|
|
|
|
set.iter().enumerate().for_each(|(index, name)| {
|
|
|
|
if name.is_empty() {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if index != 0 {
|
|
|
|
class.push(' ');
|
|
|
|
}
|
|
|
|
class.push_str(name)
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2023-12-23 12:25:54 +08:00
|
|
|
}
|
|
|
|
|
2024-07-07 17:10:54 +08:00
|
|
|
impl<R> leptos::tachys::html::class::IntoClass<R> for ClassList
|
2024-06-13 16:00:14 +08:00
|
|
|
where
|
|
|
|
R: DomRenderer,
|
|
|
|
{
|
2024-07-07 17:10:54 +08:00
|
|
|
type AsyncOutput = Self;
|
2024-07-07 22:28:11 +08:00
|
|
|
type State = (R::Element, String);
|
2024-06-13 16:00:14 +08:00
|
|
|
type Cloneable = Self;
|
|
|
|
type CloneableOwned = Self;
|
|
|
|
|
|
|
|
fn html_len(&self) -> usize {
|
|
|
|
self.0.with_untracked(|set| {
|
|
|
|
let mut len = 0;
|
|
|
|
set.iter().enumerate().for_each(|(index, name)| {
|
|
|
|
if name.is_empty() {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if index != 0 {
|
|
|
|
len += 1;
|
|
|
|
}
|
|
|
|
len += name.len();
|
|
|
|
});
|
|
|
|
|
|
|
|
len
|
|
|
|
})
|
2023-12-23 12:25:54 +08:00
|
|
|
}
|
|
|
|
|
2024-06-13 16:00:14 +08:00
|
|
|
fn to_html(self, class: &mut String) {
|
|
|
|
self.to_class_string(class);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn hydrate<const FROM_SERVER: bool>(self, el: &R::Element) -> Self::State {
|
|
|
|
let class_list = R::class_list(el);
|
|
|
|
let mut class = String::new();
|
|
|
|
self.to_class_string(&mut class);
|
|
|
|
|
|
|
|
if !FROM_SERVER {
|
|
|
|
R::add_class(&class_list, &class);
|
|
|
|
}
|
2024-07-07 22:28:11 +08:00
|
|
|
(el.clone(), class)
|
2024-06-13 16:00:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
fn build(self, el: &R::Element) -> Self::State {
|
|
|
|
let mut class = String::new();
|
|
|
|
self.to_class_string(&mut class);
|
|
|
|
if !class.is_empty() {
|
2024-07-07 22:28:11 +08:00
|
|
|
R::set_attribute(el, "class", &class);
|
2024-06-13 16:00:14 +08:00
|
|
|
}
|
2024-07-07 22:28:11 +08:00
|
|
|
(el.clone(), class)
|
2024-06-13 16:00:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
fn rebuild(self, state: &mut Self::State) {
|
|
|
|
let mut class = String::new();
|
|
|
|
self.to_class_string(&mut class);
|
2024-07-07 22:28:11 +08:00
|
|
|
let (el, prev_class) = state;
|
2024-06-13 16:00:14 +08:00
|
|
|
if class != *prev_class {
|
2024-07-07 22:28:11 +08:00
|
|
|
R::set_attribute(el, "class", &class);
|
2024-06-13 16:00:14 +08:00
|
|
|
}
|
|
|
|
*prev_class = class;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn into_cloneable(self) -> Self::Cloneable {
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
fn into_cloneable_owned(self) -> Self::CloneableOwned {
|
|
|
|
self
|
2023-12-23 12:25:54 +08:00
|
|
|
}
|
2024-07-07 17:10:54 +08:00
|
|
|
|
|
|
|
fn dry_resolve(&mut self) {}
|
|
|
|
|
|
|
|
async fn resolve(self) -> Self::AsyncOutput {
|
|
|
|
self
|
|
|
|
}
|
2023-12-23 12:25:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
pub enum Class {
|
2024-01-31 20:13:26 +08:00
|
|
|
None,
|
2023-12-23 12:25:54 +08:00
|
|
|
String(Oco<'static, str>),
|
|
|
|
FnString(Box<dyn Fn() -> Oco<'static, str>>),
|
2024-05-14 17:39:13 +08:00
|
|
|
FnOptionString(Box<dyn Fn() -> Option<Oco<'static, str>>>),
|
2023-12-23 12:25:54 +08:00
|
|
|
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()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-31 20:13:26 +08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-23 12:25:54 +08:00
|
|
|
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))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-10 15:47:01 +08:00
|
|
|
impl IntoClass for (&'static str, bool) {
|
|
|
|
fn into_class(self) -> Class {
|
|
|
|
if self.1 {
|
|
|
|
Class::String(self.0.into())
|
|
|
|
} else {
|
|
|
|
Class::None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-06 22:21:17 +08:00
|
|
|
impl IntoClass for (&'static str, Memo<bool>) {
|
|
|
|
fn into_class(self) -> Class {
|
|
|
|
Class::Fn(self.0.into(), Box::new(move || self.1.get()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-14 17:39:13 +08:00
|
|
|
impl IntoClass for MaybeProp<String> {
|
|
|
|
fn into_class(self) -> Class {
|
|
|
|
Class::FnOptionString(Box::new(move || self.get().map(|c| Oco::from(c))))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-23 12:25:54 +08:00
|
|
|
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))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-19 22:02:43 +08:00
|
|
|
#[macro_export]
|
2023-12-23 12:25:54 +08:00
|
|
|
macro_rules! class_list {
|
|
|
|
($($name:expr),+) => {
|
|
|
|
{
|
2024-03-19 22:02:43 +08:00
|
|
|
use $crate::class_list::ClassList;
|
2023-12-23 12:25:54 +08:00
|
|
|
ClassList::new()$(.add($name))+
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-06-13 16:00:14 +08:00
|
|
|
// TODO
|
|
|
|
// #[cfg(test)]
|
|
|
|
// mod tests {
|
|
|
|
// use leptos::reactive_graph::Run;
|
2023-12-23 12:25:54 +08:00
|
|
|
|
2024-06-13 16:00:14 +08:00
|
|
|
// #[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();
|
|
|
|
// }
|
|
|
|
// }
|