From e0e67360aa22d54b9742e4d6f46c5c63672ae452 Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Mon, 8 Apr 2024 21:45:11 -0400 Subject: [PATCH] implement rendering traits for signals directly on stable --- examples/timer/src/lib.rs | 7 +- reactive_graph/src/signal/rw.rs | 16 +- tachys/src/reactive_graph/class.rs | 197 ++++++++-- tachys/src/reactive_graph/inner_html.rs | 155 ++++++++ tachys/src/reactive_graph/mod.rs | 479 ++++++++++++++---------- tachys/src/reactive_graph/property.rs | 150 ++++++++ tachys/src/reactive_graph/style.rs | 188 ++++++++-- 7 files changed, 923 insertions(+), 269 deletions(-) create mode 100644 tachys/src/reactive_graph/inner_html.rs create mode 100644 tachys/src/reactive_graph/property.rs diff --git a/examples/timer/src/lib.rs b/examples/timer/src/lib.rs index 4e75e1d39..ba5f96550 100644 --- a/examples/timer/src/lib.rs +++ b/examples/timer/src/lib.rs @@ -1,7 +1,7 @@ use leptos::{ - effect::Effect, leptos_dom::helpers::{set_interval_with_handle, IntervalHandle}, prelude::*, + reactive_graph::effect::Effect, signals::RwSignal, *, }; @@ -30,10 +30,9 @@ pub fn TimerDemo() -> impl IntoView {
{count_a}
"Count B (dynamic interval, currently " {interval} " ms)"
{count_b}
- // TODO impl Property directly on signal types in stable () { - interval.set.set(value); + if let Ok(value) = ev.target().value().parse::() { + interval.set(value); } }/> diff --git a/reactive_graph/src/signal/rw.rs b/reactive_graph/src/signal/rw.rs index bed07bfe6..4b7aeff9b 100644 --- a/reactive_graph/src/signal/rw.rs +++ b/reactive_graph/src/signal/rw.rs @@ -16,7 +16,7 @@ use std::{ sync::{Arc, RwLock}, }; -pub struct RwSignal { +pub struct RwSignal { #[cfg(debug_assertions)] defined_at: &'static Location<'static>, inner: StoredValue>, @@ -90,15 +90,15 @@ impl RwSignal { } } -impl Copy for RwSignal {} +impl Copy for RwSignal {} -impl Clone for RwSignal { +impl Clone for RwSignal { fn clone(&self) -> Self { *self } } -impl Debug for RwSignal { +impl Debug for RwSignal { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("RwSignal") .field("type", &std::any::type_name::()) @@ -107,21 +107,21 @@ impl Debug for RwSignal { } } -impl PartialEq for RwSignal { +impl PartialEq for RwSignal { fn eq(&self, other: &Self) -> bool { self.inner == other.inner } } -impl Eq for RwSignal {} +impl Eq for RwSignal {} -impl Hash for RwSignal { +impl Hash for RwSignal { fn hash(&self, state: &mut H) { self.inner.hash(state); } } -impl DefinedAt for RwSignal { +impl DefinedAt for RwSignal { fn defined_at(&self) -> Option<&'static Location<'static>> { #[cfg(debug_assertions)] { diff --git a/tachys/src/reactive_graph/class.rs b/tachys/src/reactive_graph/class.rs index 4cced11ae..deb991de1 100644 --- a/tachys/src/reactive_graph/class.rs +++ b/tachys/src/reactive_graph/class.rs @@ -144,33 +144,7 @@ where } fn rebuild(self, _state: &mut Self::State) { - // TODO — knowing how and whether to rebuild effects like this is tricky - // it's the one place I've run into "stale values" when experimenting with this model - - /* let (name, mut f) = self; - let prev_effect = std::mem::take(&mut state.0); - let prev_value = prev_effect.as_ref().and_then(|e| e.take_value()); - drop(prev_effect); - *state = RenderEffect::new_with_value( - move |prev| { - let include = f(); - match prev { - Some((class_list, prev)) => { - if include { - if !prev { - R::add_class(&class_list, name); - } - } else if prev { - R::remove_class(&class_list, name); - } - (class_list.clone(), include) - } - None => unreachable!(), - } - }, - prev_value, - ) - .into(); */ + // TODO rebuild? } } @@ -250,3 +224,172 @@ where ) } } + +#[cfg(not(feature = "nightly"))] +mod stable { + macro_rules! class_signal { + ($sig:ident) => { + impl IntoClass for $sig + where + C: IntoClass + Clone + Send + Sync + 'static, + C::State: 'static, + R: DomRenderer, + { + type State = RenderEffectState; + + fn html_len(&self) -> usize { + 0 + } + + fn to_html(self, class: &mut String) { + let value = self.get(); + value.to_html(class); + } + + fn hydrate( + self, + el: &R::Element, + ) -> Self::State { + (move || self.get()).hydrate::(el) + } + + fn build(self, el: &R::Element) -> Self::State { + (move || self.get()).build(el) + } + + fn rebuild(self, _state: &mut Self::State) { + // TODO rebuild here? + } + } + + impl IntoClass for (&'static str, $sig) + where + R: DomRenderer, + { + type State = RenderEffectState<(R::ClassList, bool)>; + + fn html_len(&self) -> usize { + self.0.len() + } + + fn to_html(self, class: &mut String) { + let (name, f) = self; + let include = f.get(); + if include { + <&str as IntoClass>::to_html(name, class); + } + } + + fn hydrate( + self, + el: &R::Element, + ) -> Self::State { + IntoClass::::hydrate::( + (self.0, move || self.1.get()), + el, + ) + } + + fn build(self, el: &R::Element) -> Self::State { + IntoClass::::build((self.0, move || self.1.get()), el) + } + + fn rebuild(self, _state: &mut Self::State) { + // TODO rebuild here? + } + } + }; + } + + macro_rules! class_signal_unsend { + ($sig:ident) => { + impl IntoClass for $sig + where + C: IntoClass + Clone + 'static, + C::State: 'static, + R: DomRenderer, + { + type State = RenderEffectState; + + fn html_len(&self) -> usize { + 0 + } + + fn to_html(self, class: &mut String) { + let value = self.get(); + value.to_html(class); + } + + fn hydrate( + self, + el: &R::Element, + ) -> Self::State { + (move || self.get()).hydrate::(el) + } + + fn build(self, el: &R::Element) -> Self::State { + (move || self.get()).build(el) + } + + fn rebuild(self, _state: &mut Self::State) { + // TODO rebuild here? + } + } + + impl IntoClass for (&'static str, $sig) + where + R: DomRenderer, + { + type State = RenderEffectState<(R::ClassList, bool)>; + + fn html_len(&self) -> usize { + self.0.len() + } + + fn to_html(self, class: &mut String) { + let (name, f) = self; + let include = f.get(); + if include { + <&str as IntoClass>::to_html(name, class); + } + } + + fn hydrate( + self, + el: &R::Element, + ) -> Self::State { + IntoClass::::hydrate::( + (self.0, move || self.1.get()), + el, + ) + } + + fn build(self, el: &R::Element) -> Self::State { + IntoClass::::build((self.0, move || self.1.get()), el) + } + + fn rebuild(self, _state: &mut Self::State) { + // TODO rebuild here? + } + } + }; + } + + use super::RenderEffectState; + use crate::{html::class::IntoClass, renderer::DomRenderer}; + use reactive_graph::{ + computed::{ArcMemo, Memo}, + signal::{ArcReadSignal, ArcRwSignal, ReadSignal, RwSignal}, + traits::Get, + wrappers::read::{ArcSignal, Signal}, + }; + + class_signal!(RwSignal); + class_signal!(ReadSignal); + class_signal!(Memo); + class_signal!(Signal); + class_signal_unsend!(ArcRwSignal); + class_signal_unsend!(ArcReadSignal); + class_signal!(ArcMemo); + class_signal!(ArcSignal); +} diff --git a/tachys/src/reactive_graph/inner_html.rs b/tachys/src/reactive_graph/inner_html.rs new file mode 100644 index 000000000..bf10fbcfa --- /dev/null +++ b/tachys/src/reactive_graph/inner_html.rs @@ -0,0 +1,155 @@ +use crate::{ + html::element::InnerHtmlValue, + renderer::{DomRenderer, Renderer}, +}; +use reactive_graph::effect::RenderEffect; + +impl InnerHtmlValue for F +where + F: FnMut() -> V + 'static, + V: InnerHtmlValue, + V::State: 'static, + R: DomRenderer, +{ + type State = RenderEffect; + + fn html_len(&self) -> usize { + 0 + } + + fn to_html(mut self, buf: &mut String) { + let value = self(); + value.to_html(buf); + } + + fn to_template(_buf: &mut String) {} + + fn hydrate( + mut self, + el: &::Element, + ) -> Self::State { + let el = el.to_owned(); + RenderEffect::new(move |prev| { + let value = self(); + if let Some(mut state) = prev { + value.rebuild(&mut state); + state + } else { + value.hydrate::(&el) + } + }) + } + + fn build(mut self, el: &::Element) -> Self::State { + let el = el.to_owned(); + RenderEffect::new(move |prev| { + let value = self(); + if let Some(mut state) = prev { + value.rebuild(&mut state); + state + } else { + value.build(&el) + } + }) + } + + fn rebuild(self, _state: &mut Self::State) {} +} + +#[cfg(not(feature = "nightly"))] +mod stable { + use crate::{ + html::element::InnerHtmlValue, + renderer::{DomRenderer, Renderer}, + }; + use reactive_graph::{ + computed::{ArcMemo, Memo}, + effect::RenderEffect, + signal::{ArcReadSignal, ArcRwSignal, ReadSignal, RwSignal}, + traits::Get, + wrappers::read::{ArcSignal, Signal}, + }; + + macro_rules! inner_html_signal { + ($sig:ident) => { + impl InnerHtmlValue for $sig + where + V: InnerHtmlValue + Send + Sync + Clone + 'static, + V::State: 'static, + R: DomRenderer, + { + type State = RenderEffect; + + fn html_len(&self) -> usize { + 0 + } + + fn to_html(self, buf: &mut String) { + let value = self.get(); + value.to_html(buf); + } + + fn to_template(_buf: &mut String) {} + + fn hydrate( + self, + el: &::Element, + ) -> Self::State { + (move || self.get()).hydrate::(el) + } + + fn build(self, el: &::Element) -> Self::State { + (move || self.get()).build(el) + } + + fn rebuild(self, _state: &mut Self::State) {} + } + }; + } + + macro_rules! inner_html_signal_unsend { + ($sig:ident) => { + impl InnerHtmlValue for $sig + where + V: InnerHtmlValue + Clone + 'static, + V::State: 'static, + R: DomRenderer, + { + type State = RenderEffect; + + fn html_len(&self) -> usize { + 0 + } + + fn to_html(self, buf: &mut String) { + let value = self.get(); + value.to_html(buf); + } + + fn to_template(_buf: &mut String) {} + + fn hydrate( + self, + el: &::Element, + ) -> Self::State { + (move || self.get()).hydrate::(el) + } + + fn build(self, el: &::Element) -> Self::State { + (move || self.get()).build(el) + } + + fn rebuild(self, _state: &mut Self::State) {} + } + }; + } + + inner_html_signal!(RwSignal); + inner_html_signal!(ReadSignal); + inner_html_signal!(Memo); + inner_html_signal!(Signal); + inner_html_signal_unsend!(ArcRwSignal); + inner_html_signal_unsend!(ArcReadSignal); + inner_html_signal!(ArcMemo); + inner_html_signal!(ArcSignal); +} diff --git a/tachys/src/reactive_graph/mod.rs b/tachys/src/reactive_graph/mod.rs index 10ea0d0c0..36bd63ed3 100644 --- a/tachys/src/reactive_graph/mod.rs +++ b/tachys/src/reactive_graph/mod.rs @@ -1,12 +1,8 @@ use crate::{ async_views::Suspend, - html::{ - attribute::{Attribute, AttributeValue}, - element::InnerHtmlValue, - property::IntoProperty, - }, + html::attribute::{Attribute, AttributeValue}, hydration::Cursor, - renderer::{DomRenderer, Renderer}, + renderer::Renderer, ssr::StreamBuilder, view::{ add_attr::AddAnyAttr, Mountable, Position, PositionState, Render, @@ -22,8 +18,10 @@ use reactive_graph::{ mod class; mod guards; +mod inner_html; pub mod node_ref; mod owned; +mod property; mod style; pub use owned::*; @@ -113,25 +111,7 @@ where #[track_caller] fn rebuild(self, _state: &mut Self::State) { - // TODO — knowing how and whether to rebuild effects like this is tricky - // it's the one place I've run into "stale values" when experimenting with this model - - /* let prev_effect = mem::take(&mut state.0); - let prev_value = prev_effect.as_ref().and_then(|e| e.take_value()); - drop(prev_effect); - *state = RenderEffect::new_with_value( - move |prev| { - let value = self(); - if let Some(mut state) = prev { - value.rebuild(&mut state); - state - } else { - value.build() - } - }, - prev_value, - ) - .into(); */ + // TODO rebuild } fn try_rebuild( @@ -374,17 +354,6 @@ where } } -// Extends to track suspense -impl Suspend { - pub fn track(self) -> Suspend> { - let Suspend { fallback, fut } = self; - Suspend { - fallback, - fut: ScopedFuture::new(fut), - } - } -} - // Dynamic attributes impl AttributeValue for F where @@ -449,184 +418,290 @@ where } fn rebuild(self, _key: &str, _state: &mut Self::State) { - // TODO — knowing how and whether to rebuild effects like this is tricky - // it's the one place I've run into "stale values" when experimenting with this model - - // TODO - /* let prev_effect = mem::take(&mut state.0); - let prev_value = prev_effect.as_ref().and_then(|e| e.take_value()); - drop(prev_effect); - let key = key.to_owned(); - *state = RenderEffect::new_with_value( - move |prev| { - crate::log(&format!( - "inside here, prev is some? {}", - prev.is_some() - )); - let value = self(); - if let Some(mut state) = prev { - value.rebuild(&key, &mut state); - state - } else { - unreachable!() - } - }, - prev_value, - ) - .into(); */ - } - - /* fn build(self) -> Self::State { - RenderEffect::new(move |prev| { - let value = self(); - if let Some(mut state) = prev { - value.rebuild(&mut state); - state - } else { - value.build() - } - }) - } - - #[track_caller] - fn rebuild(self, state: &mut Self::State) { - /* crate::log(&format!( - "[REBUILDING EFFECT] Is this a mistake? {}", - std::panic::Location::caller(), - )); */ - let old_effect = std::mem::replace(state, self.build()); - } */ -} - -// Dynamic properties -// These do update during hydration because properties don't exist in the DOM -impl IntoProperty for F -where - F: FnMut() -> V + 'static, - V: IntoProperty, - V::State: 'static, - R: DomRenderer, -{ - type State = RenderEffectState; - - fn hydrate( - mut self, - el: &::Element, - key: &str, - ) -> Self::State { - let key = R::intern(key); - let key = key.to_owned(); - let el = el.to_owned(); - - RenderEffect::new(move |prev| { - let value = self(); - if let Some(mut state) = prev { - value.rebuild(&mut state, &key); - state - } else { - value.hydrate::(&el, &key) - } - }) - .into() - } - - fn build( - mut self, - el: &::Element, - key: &str, - ) -> Self::State { - let key = R::intern(key); - let key = key.to_owned(); - let el = el.to_owned(); - - RenderEffect::new(move |prev| { - let value = self(); - if let Some(mut state) = prev { - value.rebuild(&mut state, &key); - state - } else { - value.build(&el, &key) - } - }) - .into() - } - - fn rebuild(self, _state: &mut Self::State, _key: &str) { - // TODO — knowing how and whether to rebuild effects like this is tricky - // it's the one place I've run into "stale values" when experimenting with this model - - /* let prev_effect = mem::take(&mut state.0); - let prev_value = prev_effect.as_ref().and_then(|e| e.take_value()); - drop(prev_effect); - let key = key.to_owned(); - *state = RenderEffect::new_with_value( - move |prev| { - let value = self(); - if let Some(mut state) = prev { - value.rebuild(&mut state, &key); - state - } else { - unreachable!() - } - }, - prev_value, - ) - .into(); */ + // TODO rebuild } } -impl InnerHtmlValue for F -where - F: FnMut() -> V + 'static, - V: InnerHtmlValue, - V::State: 'static, - R: DomRenderer, -{ - type State = RenderEffectState; +#[cfg(not(feature = "nightly"))] +mod stable { + use super::RenderEffectState; + use crate::{ + html::attribute::{Attribute, AttributeValue}, + hydration::Cursor, + renderer::Renderer, + ssr::StreamBuilder, + view::{Position, PositionState, Render, RenderHtml}, + }; + use any_error::Error as AnyError; + use reactive_graph::{ + computed::{ArcMemo, Memo}, + signal::{ArcReadSignal, ArcRwSignal, ReadSignal, RwSignal}, + traits::Get, + wrappers::read::{ArcSignal, Signal}, + }; - fn html_len(&self) -> usize { - 0 - } + macro_rules! signal_impl { + ($sig:ident) => { + impl Render for $sig + where + V: Render + Clone + Send + Sync + 'static, + V::State: 'static, + V::FallibleState: 'static, + R: Renderer, + { + type State = RenderEffectState; + type FallibleState = RenderEffectState< + Result>, + >; + // TODO how this should be handled? + type AsyncOutput = Self; - fn to_html(mut self, buf: &mut String) { - let value = self(); - value.to_html(buf); - } + #[track_caller] + fn build(self) -> Self::State { + (move || self.get()).build() + } - fn to_template(_buf: &mut String) {} + fn try_build(self) -> any_error::Result { + (move || self.get()).try_build() + } - fn hydrate( - mut self, - el: &::Element, - ) -> Self::State { - let el = el.to_owned(); - RenderEffect::new(move |prev| { - let value = self(); - if let Some(mut state) = prev { - value.rebuild(&mut state); - state - } else { - value.hydrate::(&el) + #[track_caller] + fn rebuild(self, _state: &mut Self::State) { + // TODO rebuild + } + + fn try_rebuild( + self, + state: &mut Self::FallibleState, + ) -> any_error::Result<()> { + (move || self.get()).try_rebuild(state) + } + + async fn resolve(self) -> Self::AsyncOutput { + self + } } - }) - .into() - } - fn build(mut self, el: &::Element) -> Self::State { - let el = el.to_owned(); - RenderEffect::new(move |prev| { - let value = self(); - if let Some(mut state) = prev { - value.rebuild(&mut state); - state - } else { - value.build(&el) + impl RenderHtml for $sig + where + V: RenderHtml + Clone + Send + Sync + 'static, + V::State: 'static, + V::FallibleState: 'static, + R: Renderer + 'static, + { + const MIN_LENGTH: usize = 0; + + fn html_len(&self) -> usize { + V::MIN_LENGTH + } + + fn to_html_with_buf( + self, + buf: &mut String, + position: &mut Position, + ) { + let value = self.get(); + value.to_html_with_buf(buf, position) + } + + fn to_html_async_with_buf( + self, + buf: &mut StreamBuilder, + position: &mut Position, + ) where + Self: Sized, + { + let value = self.get(); + value.to_html_async_with_buf::(buf, position); + } + + fn hydrate( + self, + cursor: &Cursor, + position: &PositionState, + ) -> Self::State { + (move || self.get()) + .hydrate::(cursor, position) + } } - }) - .into() + + impl AttributeValue for $sig + where + V: AttributeValue + Clone + Send + Sync + 'static, + V::State: 'static, + R: Renderer, + { + type State = RenderEffectState; + + fn html_len(&self) -> usize { + 0 + } + + fn to_html(self, key: &str, buf: &mut String) { + let value = self.get(); + value.to_html(key, buf); + } + + fn to_template(_key: &str, _buf: &mut String) {} + + fn hydrate( + mut self, + key: &str, + el: &::Element, + ) -> Self::State { + (move || self.get()).hydrate::(key, el) + } + + fn build( + self, + el: &::Element, + key: &str, + ) -> Self::State { + (move || self.get()).build(el, key) + } + + fn rebuild(self, _key: &str, _state: &mut Self::State) { + // TODO rebuild + } + } + }; } - fn rebuild(self, _state: &mut Self::State) {} + macro_rules! signal_impl_unsend { + ($sig:ident) => { + impl Render for $sig + where + V: Render + Clone + 'static, + V::State: 'static, + V::FallibleState: 'static, + R: Renderer, + { + type State = RenderEffectState; + type FallibleState = RenderEffectState< + Result>, + >; + // TODO how this should be handled? + type AsyncOutput = Self; + + #[track_caller] + fn build(self) -> Self::State { + (move || self.get()).build() + } + + fn try_build(self) -> any_error::Result { + (move || self.get()).try_build() + } + + #[track_caller] + fn rebuild(self, _state: &mut Self::State) { + // TODO rebuild + } + + fn try_rebuild( + self, + state: &mut Self::FallibleState, + ) -> any_error::Result<()> { + (move || self.get()).try_rebuild(state) + } + + async fn resolve(self) -> Self::AsyncOutput { + self + } + } + + impl RenderHtml for $sig + where + V: RenderHtml + Clone + Send + Sync + 'static, + V::State: 'static, + V::FallibleState: 'static, + R: Renderer + 'static, + { + const MIN_LENGTH: usize = 0; + + fn html_len(&self) -> usize { + V::MIN_LENGTH + } + + fn to_html_with_buf( + self, + buf: &mut String, + position: &mut Position, + ) { + let value = self.get(); + value.to_html_with_buf(buf, position) + } + + fn to_html_async_with_buf( + self, + buf: &mut StreamBuilder, + position: &mut Position, + ) where + Self: Sized, + { + let value = self.get(); + value.to_html_async_with_buf::(buf, position); + } + + fn hydrate( + self, + cursor: &Cursor, + position: &PositionState, + ) -> Self::State { + (move || self.get()) + .hydrate::(cursor, position) + } + } + + impl AttributeValue for $sig + where + V: AttributeValue + Clone + 'static, + V::State: 'static, + R: Renderer, + { + type State = RenderEffectState; + + fn html_len(&self) -> usize { + 0 + } + + fn to_html(self, key: &str, buf: &mut String) { + let value = self.get(); + value.to_html(key, buf); + } + + fn to_template(_key: &str, _buf: &mut String) {} + + fn hydrate( + mut self, + key: &str, + el: &::Element, + ) -> Self::State { + (move || self.get()).hydrate::(key, el) + } + + fn build( + self, + el: &::Element, + key: &str, + ) -> Self::State { + (move || self.get()).build(el, key) + } + + fn rebuild(self, _key: &str, _state: &mut Self::State) { + // TODO rebuild + } + } + }; + } + + signal_impl!(RwSignal); + signal_impl!(ReadSignal); + signal_impl!(Memo); + signal_impl!(Signal); + signal_impl_unsend!(ArcRwSignal); + signal_impl_unsend!(ArcReadSignal); + signal_impl!(ArcMemo); + signal_impl!(ArcSignal); } /* diff --git a/tachys/src/reactive_graph/property.rs b/tachys/src/reactive_graph/property.rs new file mode 100644 index 000000000..83a5ff70f --- /dev/null +++ b/tachys/src/reactive_graph/property.rs @@ -0,0 +1,150 @@ +use crate::{ + html::property::IntoProperty, + renderer::{DomRenderer, Renderer}, +}; +use reactive_graph::effect::RenderEffect; + +// These do update during hydration because properties don't exist in the DOM +impl IntoProperty for F +where + F: FnMut() -> V + 'static, + V: IntoProperty, + V::State: 'static, + R: DomRenderer, +{ + type State = RenderEffect; + + fn hydrate( + mut self, + el: &::Element, + key: &str, + ) -> Self::State { + let key = R::intern(key); + let key = key.to_owned(); + let el = el.to_owned(); + + RenderEffect::new(move |prev| { + let value = self(); + if let Some(mut state) = prev { + value.rebuild(&mut state, &key); + state + } else { + value.hydrate::(&el, &key) + } + }) + } + + fn build( + mut self, + el: &::Element, + key: &str, + ) -> Self::State { + let key = R::intern(key); + let key = key.to_owned(); + let el = el.to_owned(); + + RenderEffect::new(move |prev| { + let value = self(); + if let Some(mut state) = prev { + value.rebuild(&mut state, &key); + state + } else { + value.build(&el, &key) + } + }) + } + + fn rebuild(self, _state: &mut Self::State, _key: &str) { + // TODO rebuild + } +} + +#[cfg(not(feature = "nightly"))] +mod stable { + use crate::{ + html::property::IntoProperty, + renderer::{DomRenderer, Renderer}, + }; + use reactive_graph::{ + computed::{ArcMemo, Memo}, + effect::RenderEffect, + signal::{ArcReadSignal, ArcRwSignal, ReadSignal, RwSignal}, + traits::Get, + wrappers::read::{ArcSignal, Signal}, + }; + + macro_rules! property_signal { + ($sig:ident) => { + impl IntoProperty for $sig + where + V: IntoProperty + Send + Sync + Clone + 'static, + V::State: 'static, + R: DomRenderer, + { + type State = RenderEffect; + + fn hydrate( + self, + el: &::Element, + key: &str, + ) -> Self::State { + (move || self.get()).hydrate::(el, key) + } + + fn build( + self, + el: &::Element, + key: &str, + ) -> Self::State { + (move || self.get()).build(el, key) + } + + fn rebuild(self, _state: &mut Self::State, _key: &str) { + // TODO rebuild + } + } + }; + } + + macro_rules! property_signal_unsend { + ($sig:ident) => { + impl IntoProperty for $sig + where + V: IntoProperty + Clone + 'static, + V::State: 'static, + R: DomRenderer, + { + type State = RenderEffect; + + fn hydrate( + self, + el: &::Element, + key: &str, + ) -> Self::State { + (move || self.get()).hydrate::(el, key) + } + + fn build( + self, + el: &::Element, + key: &str, + ) -> Self::State { + (move || self.get()).build(el, key) + } + + fn rebuild(self, _state: &mut Self::State, _key: &str) { + // TODO rebuild + } + } + }; + } + + property_signal!(RwSignal); + property_signal!(ReadSignal); + property_signal!(Memo); + property_signal!(Signal); + property_signal_unsend!(ArcRwSignal); + property_signal_unsend!(ArcReadSignal); + property_signal!(ArcMemo); + property_signal!(ArcSignal); +} diff --git a/tachys/src/reactive_graph/style.rs b/tachys/src/reactive_graph/style.rs index 2ed8b47c4..71af6fdcc 100644 --- a/tachys/src/reactive_graph/style.rs +++ b/tachys/src/reactive_graph/style.rs @@ -1,4 +1,3 @@ -use super::RenderEffectState; use crate::{html::style::IntoStyle, renderer::DomRenderer}; use reactive_graph::effect::RenderEffect; use std::borrow::Cow; @@ -9,7 +8,7 @@ where S: Into>, R: DomRenderer, { - type State = RenderEffectState<(R::CssStyleDeclaration, Cow<'static, str>)>; + type State = RenderEffect<(R::CssStyleDeclaration, Cow<'static, str>)>; fn to_html(self, style: &mut String) { let (name, mut f) = self; @@ -75,30 +74,7 @@ where } fn rebuild(self, _state: &mut Self::State) { - // TODO — knowing how and whether to rebuild effects like this is tricky - // it's the one place I've run into "stale values" when experimenting with this model - - /* let (name, mut f) = self; - let prev_effect = std::mem::take(&mut state.0); - let prev_value = prev_effect.as_ref().and_then(|e| e.take_value()); - drop(prev_effect); - *state = RenderEffect::new_with_value( - move |prev| { - let value = f().into(); - if let Some(mut state) = prev { - let (style, prev) = &mut state; - if &value != prev { - R::set_css_property(&style, name, &value); - } - *prev = value; - state - } else { - todo!() - } - }, - prev_value, - ) - .into(); */ + // TODO rebuild } } @@ -111,9 +87,9 @@ where { type State = RenderEffect; - fn to_html(mut self, class: &mut String) { + fn to_html(mut self, style: &mut String) { let value = self(); - value.to_html(class); + value.to_html(style); } fn hydrate( @@ -139,3 +115,159 @@ where fn rebuild(self, _state: &mut Self::State) {} } + +#[cfg(not(feature = "nightly"))] +mod stable { + macro_rules! style_signal { + ($sig:ident) => { + impl IntoStyle for $sig + where + C: IntoStyle + Clone + Send + Sync + 'static, + C::State: 'static, + R: DomRenderer, + { + type State = RenderEffect; + + fn to_html(self, style: &mut String) { + let value = self.get(); + value.to_html(style); + } + + fn hydrate( + self, + el: &R::Element, + ) -> Self::State { + (move || self.get()).hydrate::(el) + } + + fn build(self, el: &R::Element) -> Self::State { + (move || self.get()).build(el) + } + + fn rebuild(self, _state: &mut Self::State) { + // TODO rebuild here? + } + } + + impl IntoStyle for (&'static str, $sig) + where + S: Into> + Send + Sync + Clone + 'static, + R: DomRenderer, + { + type State = + RenderEffect<(R::CssStyleDeclaration, Cow<'static, str>)>; + + fn to_html(self, style: &mut String) { + IntoStyle::::to_html( + (self.0, move || self.1.get()), + style, + ) + } + + fn hydrate( + self, + el: &R::Element, + ) -> Self::State { + IntoStyle::::hydrate::( + (self.0, move || self.1.get()), + el, + ) + } + + fn build(self, el: &R::Element) -> Self::State { + IntoStyle::::build((self.0, move || self.1.get()), el) + } + + fn rebuild(self, _state: &mut Self::State) { + // TODO rebuild here? + } + } + }; + } + + macro_rules! style_signal_unsend { + ($sig:ident) => { + impl IntoStyle for $sig + where + C: IntoStyle + Clone + 'static, + C::State: 'static, + R: DomRenderer, + { + type State = RenderEffect; + + fn to_html(self, style: &mut String) { + let value = self.get(); + value.to_html(style); + } + + fn hydrate( + self, + el: &R::Element, + ) -> Self::State { + (move || self.get()).hydrate::(el) + } + + fn build(self, el: &R::Element) -> Self::State { + (move || self.get()).build(el) + } + + fn rebuild(self, _state: &mut Self::State) { + // TODO rebuild here? + } + } + + impl IntoStyle for (&'static str, $sig) + where + S: Into> + Send + Sync + Clone + 'static, + R: DomRenderer, + { + type State = + RenderEffect<(R::CssStyleDeclaration, Cow<'static, str>)>; + + fn to_html(self, style: &mut String) { + IntoStyle::::to_html( + (self.0, move || self.1.get()), + style, + ) + } + + fn hydrate( + self, + el: &R::Element, + ) -> Self::State { + IntoStyle::::hydrate::( + (self.0, move || self.1.get()), + el, + ) + } + + fn build(self, el: &R::Element) -> Self::State { + IntoStyle::::build((self.0, move || self.1.get()), el) + } + + fn rebuild(self, _state: &mut Self::State) { + // TODO rebuild here? + } + } + }; + } + + use super::RenderEffect; + use crate::{html::style::IntoStyle, renderer::DomRenderer}; + use reactive_graph::{ + computed::{ArcMemo, Memo}, + signal::{ArcReadSignal, ArcRwSignal, ReadSignal, RwSignal}, + traits::Get, + wrappers::read::{ArcSignal, Signal}, + }; + use std::borrow::Cow; + + style_signal!(RwSignal); + style_signal!(ReadSignal); + style_signal!(Memo); + style_signal!(Signal); + style_signal_unsend!(ArcRwSignal); + style_signal_unsend!(ArcReadSignal); + style_signal!(ArcMemo); + style_signal!(ArcSignal); +}