implement rendering traits for signals directly on stable

This commit is contained in:
Greg Johnston 2024-04-08 21:45:11 -04:00
parent 439deea066
commit e0e67360aa
7 changed files with 923 additions and 269 deletions

View File

@ -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 {
<div>{count_a}</div>
<div>"Count B (dynamic interval, currently " {interval} " ms)"</div>
<div>{count_b}</div>
// TODO impl Property directly on signal types in stable
<input prop:value=interval on:input:target=move |ev| {
if let Ok(value) = ev.target().value().parse::<u64>() {
interval.set.set(value);
if let Ok(value) = ev.target().value().parse::<i32>() {
interval.set(value);
}
}/>
</div>

View File

@ -16,7 +16,7 @@ use std::{
sync::{Arc, RwLock},
};
pub struct RwSignal<T: Send + Sync + 'static> {
pub struct RwSignal<T: 'static> {
#[cfg(debug_assertions)]
defined_at: &'static Location<'static>,
inner: StoredValue<ArcRwSignal<T>>,
@ -90,15 +90,15 @@ impl<T: Send + Sync + 'static> RwSignal<T> {
}
}
impl<T: Send + Sync + 'static> Copy for RwSignal<T> {}
impl<T: 'static> Copy for RwSignal<T> {}
impl<T: Send + Sync + 'static> Clone for RwSignal<T> {
impl<T: 'static> Clone for RwSignal<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: Send + Sync + 'static> Debug for RwSignal<T> {
impl<T: 'static> Debug for RwSignal<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RwSignal")
.field("type", &std::any::type_name::<T>())
@ -107,21 +107,21 @@ impl<T: Send + Sync + 'static> Debug for RwSignal<T> {
}
}
impl<T: Send + Sync + 'static> PartialEq for RwSignal<T> {
impl<T: 'static> PartialEq for RwSignal<T> {
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
}
}
impl<T: Send + Sync + 'static> Eq for RwSignal<T> {}
impl<T: 'static> Eq for RwSignal<T> {}
impl<T: Send + Sync + 'static> Hash for RwSignal<T> {
impl<T: 'static> Hash for RwSignal<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.inner.hash(state);
}
}
impl<T: Send + Sync + 'static> DefinedAt for RwSignal<T> {
impl<T: 'static> DefinedAt for RwSignal<T> {
fn defined_at(&self) -> Option<&'static Location<'static>> {
#[cfg(debug_assertions)]
{

View File

@ -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<C, R> IntoClass<R> for $sig<C>
where
C: IntoClass<R> + Clone + Send + Sync + 'static,
C::State: 'static,
R: DomRenderer,
{
type State = RenderEffectState<C::State>;
fn html_len(&self) -> usize {
0
}
fn to_html(self, class: &mut String) {
let value = self.get();
value.to_html(class);
}
fn hydrate<const FROM_SERVER: bool>(
self,
el: &R::Element,
) -> Self::State {
(move || self.get()).hydrate::<FROM_SERVER>(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<R> IntoClass<R> for (&'static str, $sig<bool>)
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<R>>::to_html(name, class);
}
}
fn hydrate<const FROM_SERVER: bool>(
self,
el: &R::Element,
) -> Self::State {
IntoClass::<R>::hydrate::<FROM_SERVER>(
(self.0, move || self.1.get()),
el,
)
}
fn build(self, el: &R::Element) -> Self::State {
IntoClass::<R>::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<C, R> IntoClass<R> for $sig<C>
where
C: IntoClass<R> + Clone + 'static,
C::State: 'static,
R: DomRenderer,
{
type State = RenderEffectState<C::State>;
fn html_len(&self) -> usize {
0
}
fn to_html(self, class: &mut String) {
let value = self.get();
value.to_html(class);
}
fn hydrate<const FROM_SERVER: bool>(
self,
el: &R::Element,
) -> Self::State {
(move || self.get()).hydrate::<FROM_SERVER>(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<R> IntoClass<R> for (&'static str, $sig<bool>)
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<R>>::to_html(name, class);
}
}
fn hydrate<const FROM_SERVER: bool>(
self,
el: &R::Element,
) -> Self::State {
IntoClass::<R>::hydrate::<FROM_SERVER>(
(self.0, move || self.1.get()),
el,
)
}
fn build(self, el: &R::Element) -> Self::State {
IntoClass::<R>::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);
}

View File

@ -0,0 +1,155 @@
use crate::{
html::element::InnerHtmlValue,
renderer::{DomRenderer, Renderer},
};
use reactive_graph::effect::RenderEffect;
impl<F, V, R> InnerHtmlValue<R> for F
where
F: FnMut() -> V + 'static,
V: InnerHtmlValue<R>,
V::State: 'static,
R: DomRenderer,
{
type State = RenderEffect<V::State>;
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<const FROM_SERVER: bool>(
mut self,
el: &<R as Renderer>::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::<FROM_SERVER>(&el)
}
})
}
fn build(mut self, el: &<R as Renderer>::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<V, R> InnerHtmlValue<R> for $sig<V>
where
V: InnerHtmlValue<R> + Send + Sync + Clone + 'static,
V::State: 'static,
R: DomRenderer,
{
type State = RenderEffect<V::State>;
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<const FROM_SERVER: bool>(
self,
el: &<R as Renderer>::Element,
) -> Self::State {
(move || self.get()).hydrate::<FROM_SERVER>(el)
}
fn build(self, el: &<R as Renderer>::Element) -> Self::State {
(move || self.get()).build(el)
}
fn rebuild(self, _state: &mut Self::State) {}
}
};
}
macro_rules! inner_html_signal_unsend {
($sig:ident) => {
impl<V, R> InnerHtmlValue<R> for $sig<V>
where
V: InnerHtmlValue<R> + Clone + 'static,
V::State: 'static,
R: DomRenderer,
{
type State = RenderEffect<V::State>;
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<const FROM_SERVER: bool>(
self,
el: &<R as Renderer>::Element,
) -> Self::State {
(move || self.get()).hydrate::<FROM_SERVER>(el)
}
fn build(self, el: &<R as Renderer>::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);
}

View File

@ -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<const TRANSITION: bool, Fal, Fut> Suspend<TRANSITION, Fal, Fut> {
pub fn track(self) -> Suspend<TRANSITION, Fal, ScopedFuture<Fut>> {
let Suspend { fallback, fut } = self;
Suspend {
fallback,
fut: ScopedFuture::new(fut),
}
}
}
// Dynamic attributes
impl<F, V, R> AttributeValue<R> 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<F, V, R> IntoProperty<R> for F
where
F: FnMut() -> V + 'static,
V: IntoProperty<R>,
V::State: 'static,
R: DomRenderer,
{
type State = RenderEffectState<V::State>;
fn hydrate<const FROM_SERVER: bool>(
mut self,
el: &<R as Renderer>::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::<FROM_SERVER>(&el, &key)
}
})
.into()
}
fn build(
mut self,
el: &<R as Renderer>::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<F, V, R> InnerHtmlValue<R> for F
where
F: FnMut() -> V + 'static,
V: InnerHtmlValue<R>,
V::State: 'static,
R: DomRenderer,
{
type State = RenderEffectState<V::State>;
#[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<V, R> Render<R> for $sig<V>
where
V: Render<R> + Clone + Send + Sync + 'static,
V::State: 'static,
V::FallibleState: 'static,
R: Renderer,
{
type State = RenderEffectState<V::State>;
type FallibleState = RenderEffectState<
Result<V::FallibleState, Option<AnyError>>,
>;
// 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<Self::FallibleState> {
(move || self.get()).try_build()
}
fn hydrate<const FROM_SERVER: bool>(
mut self,
el: &<R as Renderer>::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::<FROM_SERVER>(&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: &<R as Renderer>::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<V, R> RenderHtml<R> for $sig<V>
where
V: RenderHtml<R> + 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<const OUT_OF_ORDER: bool>(
self,
buf: &mut StreamBuilder,
position: &mut Position,
) where
Self: Sized,
{
let value = self.get();
value.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position);
}
fn hydrate<const FROM_SERVER: bool>(
self,
cursor: &Cursor<R>,
position: &PositionState,
) -> Self::State {
(move || self.get())
.hydrate::<FROM_SERVER>(cursor, position)
}
}
})
.into()
impl<V, R> AttributeValue<R> for $sig<V>
where
V: AttributeValue<R> + Clone + Send + Sync + 'static,
V::State: 'static,
R: Renderer,
{
type State = RenderEffectState<V::State>;
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<const FROM_SERVER: bool>(
mut self,
key: &str,
el: &<R as Renderer>::Element,
) -> Self::State {
(move || self.get()).hydrate::<FROM_SERVER>(key, el)
}
fn build(
self,
el: &<R as Renderer>::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<V, R> Render<R> for $sig<V>
where
V: Render<R> + Clone + 'static,
V::State: 'static,
V::FallibleState: 'static,
R: Renderer,
{
type State = RenderEffectState<V::State>;
type FallibleState = RenderEffectState<
Result<V::FallibleState, Option<AnyError>>,
>;
// 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<Self::FallibleState> {
(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<V, R> RenderHtml<R> for $sig<V>
where
V: RenderHtml<R> + 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<const OUT_OF_ORDER: bool>(
self,
buf: &mut StreamBuilder,
position: &mut Position,
) where
Self: Sized,
{
let value = self.get();
value.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position);
}
fn hydrate<const FROM_SERVER: bool>(
self,
cursor: &Cursor<R>,
position: &PositionState,
) -> Self::State {
(move || self.get())
.hydrate::<FROM_SERVER>(cursor, position)
}
}
impl<V, R> AttributeValue<R> for $sig<V>
where
V: AttributeValue<R> + Clone + 'static,
V::State: 'static,
R: Renderer,
{
type State = RenderEffectState<V::State>;
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<const FROM_SERVER: bool>(
mut self,
key: &str,
el: &<R as Renderer>::Element,
) -> Self::State {
(move || self.get()).hydrate::<FROM_SERVER>(key, el)
}
fn build(
self,
el: &<R as Renderer>::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);
}
/*

View File

@ -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<F, V, R> IntoProperty<R> for F
where
F: FnMut() -> V + 'static,
V: IntoProperty<R>,
V::State: 'static,
R: DomRenderer,
{
type State = RenderEffect<V::State>;
fn hydrate<const FROM_SERVER: bool>(
mut self,
el: &<R as Renderer>::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::<FROM_SERVER>(&el, &key)
}
})
}
fn build(
mut self,
el: &<R as Renderer>::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<V, R> IntoProperty<R> for $sig<V>
where
V: IntoProperty<R> + Send + Sync + Clone + 'static,
V::State: 'static,
R: DomRenderer,
{
type State = RenderEffect<V::State>;
fn hydrate<const FROM_SERVER: bool>(
self,
el: &<R as Renderer>::Element,
key: &str,
) -> Self::State {
(move || self.get()).hydrate::<FROM_SERVER>(el, key)
}
fn build(
self,
el: &<R as Renderer>::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<V, R> IntoProperty<R> for $sig<V>
where
V: IntoProperty<R> + Clone + 'static,
V::State: 'static,
R: DomRenderer,
{
type State = RenderEffect<V::State>;
fn hydrate<const FROM_SERVER: bool>(
self,
el: &<R as Renderer>::Element,
key: &str,
) -> Self::State {
(move || self.get()).hydrate::<FROM_SERVER>(el, key)
}
fn build(
self,
el: &<R as Renderer>::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);
}

View File

@ -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<Cow<'static, str>>,
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<C::State>;
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<const FROM_SERVER: bool>(
@ -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<C, R> IntoStyle<R> for $sig<C>
where
C: IntoStyle<R> + Clone + Send + Sync + 'static,
C::State: 'static,
R: DomRenderer,
{
type State = RenderEffect<C::State>;
fn to_html(self, style: &mut String) {
let value = self.get();
value.to_html(style);
}
fn hydrate<const FROM_SERVER: bool>(
self,
el: &R::Element,
) -> Self::State {
(move || self.get()).hydrate::<FROM_SERVER>(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<R, S> IntoStyle<R> for (&'static str, $sig<S>)
where
S: Into<Cow<'static, str>> + Send + Sync + Clone + 'static,
R: DomRenderer,
{
type State =
RenderEffect<(R::CssStyleDeclaration, Cow<'static, str>)>;
fn to_html(self, style: &mut String) {
IntoStyle::<R>::to_html(
(self.0, move || self.1.get()),
style,
)
}
fn hydrate<const FROM_SERVER: bool>(
self,
el: &R::Element,
) -> Self::State {
IntoStyle::<R>::hydrate::<FROM_SERVER>(
(self.0, move || self.1.get()),
el,
)
}
fn build(self, el: &R::Element) -> Self::State {
IntoStyle::<R>::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<C, R> IntoStyle<R> for $sig<C>
where
C: IntoStyle<R> + Clone + 'static,
C::State: 'static,
R: DomRenderer,
{
type State = RenderEffect<C::State>;
fn to_html(self, style: &mut String) {
let value = self.get();
value.to_html(style);
}
fn hydrate<const FROM_SERVER: bool>(
self,
el: &R::Element,
) -> Self::State {
(move || self.get()).hydrate::<FROM_SERVER>(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<R, S> IntoStyle<R> for (&'static str, $sig<S>)
where
S: Into<Cow<'static, str>> + Send + Sync + Clone + 'static,
R: DomRenderer,
{
type State =
RenderEffect<(R::CssStyleDeclaration, Cow<'static, str>)>;
fn to_html(self, style: &mut String) {
IntoStyle::<R>::to_html(
(self.0, move || self.1.get()),
style,
)
}
fn hydrate<const FROM_SERVER: bool>(
self,
el: &R::Element,
) -> Self::State {
IntoStyle::<R>::hydrate::<FROM_SERVER>(
(self.0, move || self.1.get()),
el,
)
}
fn build(self, el: &R::Element) -> Self::State {
IntoStyle::<R>::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);
}