diff --git a/examples/counters/src/lib.rs b/examples/counters/src/lib.rs index 5460322da..acd496f7c 100644 --- a/examples/counters/src/lib.rs +++ b/examples/counters/src/lib.rs @@ -1,4 +1,4 @@ -use leptos::*; +use leptos::prelude::*; const MANY_COUNTERS: usize = 1000; @@ -77,39 +77,23 @@ pub fn Counters() -> impl IntoView { } #[component] -fn Counter( - id: usize, - value: ReadSignal, - set_value: WriteSignal, -) -> impl IntoView { +fn Counter(id: usize, value: ArcRwSignal) -> impl IntoView { + let value = RwSignal::from(value); let CounterUpdater { set_counters } = use_context().unwrap(); let input = move |ev| { - set_value - .set(event_target_value(&ev).parse::().unwrap_or_default()) + value.set(event_target_value(&ev).parse::().unwrap_or_default()) }; - // this will run when the scope is disposed, i.e., when this row is deleted - // because the signal was created in the parent scope, it won't be disposed - // of until the parent scope is. but we no longer need it, so we'll dispose of - // it when this row is deleted, instead. if we don't dispose of it here, - // this memory will "leak," i.e., the signal will continue to exist until the - // parent component is removed. in the case of this component, where it's the - // root, that's the lifetime of the program. - on_cleanup(move || { - log::debug!("deleted a row"); - value.dispose(); - }); - view! {
  • - + {value} - +
  • } diff --git a/reactive_graph/src/computed/arc_memo.rs b/reactive_graph/src/computed/arc_memo.rs index f2bbba5cd..e01f95e45 100644 --- a/reactive_graph/src/computed/arc_memo.rs +++ b/reactive_graph/src/computed/arc_memo.rs @@ -5,7 +5,7 @@ use crate::{ ToAnySource, ToAnySubscriber, }, signal::MappedSignalReadGuard, - traits::{DefinedAt, Readable}, + traits::{DefinedAt, ReadUntracked}, }; use core::fmt::Debug; use or_poisoned::OrPoisoned; @@ -158,10 +158,10 @@ impl Subscriber for ArcMemo { } } -impl Readable for ArcMemo { +impl ReadUntracked for ArcMemo { type Value = MappedSignalReadGuard, T>; - fn try_read(&self) -> Option { + fn try_read_untracked(&self) -> Option { self.update_if_necessary(); MappedSignalReadGuard::try_new(Arc::clone(&self.inner), |t| { diff --git a/reactive_graph/src/computed/async_derived/arc_async_derived.rs b/reactive_graph/src/computed/async_derived/arc_async_derived.rs index bdc5ae929..b2d10a03e 100644 --- a/reactive_graph/src/computed/async_derived/arc_async_derived.rs +++ b/reactive_graph/src/computed/async_derived/arc_async_derived.rs @@ -11,7 +11,7 @@ use crate::{ }, owner::Owner, signal::SignalReadGuard, - traits::{DefinedAt, Readable}, + traits::{DefinedAt, ReadUntracked}, }; use core::fmt::Debug; use futures::{FutureExt, StreamExt}; @@ -221,10 +221,10 @@ impl ArcAsyncDerived { } } -impl Readable for ArcAsyncDerived { +impl ReadUntracked for ArcAsyncDerived { type Value = SignalReadGuard>; - fn try_read(&self) -> Option { + fn try_read_untracked(&self) -> Option { SignalReadGuard::try_new(Arc::clone(&self.value)) } } diff --git a/reactive_graph/src/computed/async_derived/async_derived.rs b/reactive_graph/src/computed/async_derived/async_derived.rs index becb3bca4..76d9b218d 100644 --- a/reactive_graph/src/computed/async_derived/async_derived.rs +++ b/reactive_graph/src/computed/async_derived/async_derived.rs @@ -8,7 +8,7 @@ use crate::{ }, owner::{Stored, StoredData}, signal::{MappedSignalReadGuard, SignalReadGuard}, - traits::{DefinedAt, Readable}, + traits::{DefinedAt, ReadUntracked}, unwrap_signal, }; use core::fmt::Debug; @@ -146,11 +146,11 @@ impl IntoFuture for AsyncDerived { } } -impl Readable for AsyncDerived { +impl ReadUntracked for AsyncDerived { type Value = SignalReadGuard>; - fn try_read(&self) -> Option { - self.get_value().map(|inner| inner.read()) + fn try_read_untracked(&self) -> Option { + self.get_value().map(|inner| inner.read_untracked()) } } diff --git a/reactive_graph/src/computed/memo.rs b/reactive_graph/src/computed/memo.rs index 09f363e7f..9955b1e6d 100644 --- a/reactive_graph/src/computed/memo.rs +++ b/reactive_graph/src/computed/memo.rs @@ -2,7 +2,7 @@ use super::{inner::MemoInner, ArcMemo}; use crate::{ owner::{Stored, StoredData}, signal::MappedSignalReadGuard, - traits::{DefinedAt, Readable, Track}, + traits::{DefinedAt, ReadUntracked, Track}, }; use std::{fmt::Debug, panic::Location}; @@ -80,10 +80,10 @@ impl Track for Memo { } } -impl Readable for Memo { +impl ReadUntracked for Memo { type Value = MappedSignalReadGuard, T>; - fn try_read(&self) -> Option { - self.get_value().map(|inner| inner.read()) + fn try_read_untracked(&self) -> Option { + self.get_value().map(|inner| inner.read_untracked()) } } diff --git a/reactive_graph/src/nightly.rs b/reactive_graph/src/nightly.rs index e72f82b97..f5ff0cccc 100644 --- a/reactive_graph/src/nightly.rs +++ b/reactive_graph/src/nightly.rs @@ -4,7 +4,7 @@ use crate::{ ArcReadSignal, ArcRwSignal, ArcWriteSignal, ReadSignal, RwSignal, WriteSignal, }, - traits::{Readable, Set}, + traits::{Read, Set}, }; macro_rules! impl_get_fn_traits { @@ -12,7 +12,7 @@ macro_rules! impl_get_fn_traits { $( #[cfg(feature = "nightly")] impl FnOnce<()> for $ty { - type Output = ::Value; + type Output = ::Value; #[inline(always)] extern "rust-call" fn call_once(self, _args: ()) -> Self::Output { @@ -88,7 +88,7 @@ macro_rules! impl_get_fn_traits_send { $( #[cfg(feature = "nightly")] impl FnOnce<()> for $ty { - type Output = ::Value; + type Output = ::Value; #[inline(always)] extern "rust-call" fn call_once(self, _args: ()) -> Self::Output { diff --git a/reactive_graph/src/signal/arc_read.rs b/reactive_graph/src/signal/arc_read.rs index 9fc5a9d1f..d9e81a967 100644 --- a/reactive_graph/src/signal/arc_read.rs +++ b/reactive_graph/src/signal/arc_read.rs @@ -1,7 +1,7 @@ use super::{subscriber_traits::AsSubscriberSet, SignalReadGuard}; use crate::{ graph::SubscriberSet, - traits::{DefinedAt, IsDisposed, Readable}, + traits::{DefinedAt, IsDisposed, ReadUntracked}, }; use core::fmt::{Debug, Formatter, Result}; use std::{ @@ -82,10 +82,10 @@ impl AsSubscriberSet for ArcReadSignal { } } -impl Readable for ArcReadSignal { +impl ReadUntracked for ArcReadSignal { type Value = SignalReadGuard; - fn try_read(&self) -> Option { + fn try_read_untracked(&self) -> Option { SignalReadGuard::try_new(Arc::clone(&self.value)) } } diff --git a/reactive_graph/src/signal/arc_rw.rs b/reactive_graph/src/signal/arc_rw.rs index 863911014..d91603496 100644 --- a/reactive_graph/src/signal/arc_rw.rs +++ b/reactive_graph/src/signal/arc_rw.rs @@ -5,7 +5,7 @@ use super::{ use crate::{ graph::{ReactiveNode, SubscriberSet}, prelude::{IsDisposed, Trigger}, - traits::{DefinedAt, Readable, Writeable}, + traits::{DefinedAt, ReadUntracked, Writeable}, }; use core::fmt::{Debug, Formatter, Result}; use std::{ @@ -112,10 +112,10 @@ impl AsSubscriberSet for ArcRwSignal { } } -impl Readable for ArcRwSignal { +impl ReadUntracked for ArcRwSignal { type Value = SignalReadGuard; - fn try_read(&self) -> Option { + fn try_read_untracked(&self) -> Option { SignalReadGuard::try_new(Arc::clone(&self.value)) } } diff --git a/reactive_graph/src/signal/read.rs b/reactive_graph/src/signal/read.rs index 3078d3f2f..da8fa7ef5 100644 --- a/reactive_graph/src/signal/read.rs +++ b/reactive_graph/src/signal/read.rs @@ -4,7 +4,8 @@ use super::{ use crate::{ graph::SubscriberSet, owner::{Stored, StoredData}, - traits::{DefinedAt, IsDisposed, Readable}, + traits::{DefinedAt, IsDisposed, ReadUntracked}, + unwrap_signal, }; use core::fmt::Debug; use std::{ @@ -76,10 +77,28 @@ impl AsSubscriberSet for ReadSignal { } } -impl Readable for ReadSignal { +impl ReadUntracked for ReadSignal { type Value = SignalReadGuard; - fn try_read(&self) -> Option { - self.get_value().map(|inner| inner.read()) + fn try_read_untracked(&self) -> Option { + self.get_value().map(|inner| inner.read_untracked()) + } +} + +impl From> for ReadSignal { + #[track_caller] + fn from(value: ArcReadSignal) -> Self { + ReadSignal { + #[cfg(debug_assertions)] + defined_at: Location::caller(), + inner: Stored::new(value), + } + } +} + +impl From> for ArcReadSignal { + #[track_caller] + fn from(value: ReadSignal) -> Self { + value.get_value().unwrap_or_else(unwrap_signal!(value)) } } diff --git a/reactive_graph/src/signal/rw.rs b/reactive_graph/src/signal/rw.rs index 1f5bc61b1..f45d9f0f3 100644 --- a/reactive_graph/src/signal/rw.rs +++ b/reactive_graph/src/signal/rw.rs @@ -5,7 +5,7 @@ use super::{ use crate::{ graph::{ReactiveNode, SubscriberSet}, owner::{Stored, StoredData}, - traits::{DefinedAt, IsDisposed, Readable, Trigger, UpdateUntracked}, + traits::{DefinedAt, IsDisposed, ReadUntracked, Trigger, UpdateUntracked}, unwrap_signal, }; use core::fmt::Debug; @@ -146,11 +146,11 @@ impl AsSubscriberSet for RwSignal { } } -impl Readable for RwSignal { +impl ReadUntracked for RwSignal { type Value = SignalReadGuard; - fn try_read(&self) -> Option { - self.get_value().map(|inner| inner.read()) + fn try_read_untracked(&self) -> Option { + self.get_value().map(|inner| inner.read_untracked()) } } @@ -170,3 +170,21 @@ impl UpdateUntracked for RwSignal { self.get_value().and_then(|n| n.try_update_untracked(fun)) } } + +impl From> for RwSignal { + #[track_caller] + fn from(value: ArcRwSignal) -> Self { + RwSignal { + #[cfg(debug_assertions)] + defined_at: Location::caller(), + inner: Stored::new(value), + } + } +} + +impl From> for ArcRwSignal { + #[track_caller] + fn from(value: RwSignal) -> Self { + value.get_value().unwrap_or_else(unwrap_signal!(value)) + } +} diff --git a/reactive_graph/src/traits.rs b/reactive_graph/src/traits.rs index d82c11d41..459c9c547 100644 --- a/reactive_graph/src/traits.rs +++ b/reactive_graph/src/traits.rs @@ -13,20 +13,20 @@ //! - [`IsDisposed`] checks whether a signal is currently accessible. //! //! ## Base Traits -//! | Trait | Mode | Description | -//! |--------------|-------|---------------------------------------------------------------------------------------| -//! | [`Track`] | — | Tracks changes to this value, adding it as a source of the current reactive observer. | -//! | [`Trigger`] | — | Notifies subscribers that this value has changed. | -//! | [`Readable`] | Guard | Gives immutable access to the value of this signal. | -//! | [`Writeable`]| Guard | Gives mutable access to the value of this signal. +//! | Trait | Mode | Description | +//! |-------------------|-------|---------------------------------------------------------------------------------------| +//! | [`Track`] | — | Tracks changes to this value, adding it as a source of the current reactive observer. | +//! | [`Trigger`] | — | Notifies subscribers that this value has changed. | +//! | [`ReadUntracked`] | Guard | Gives immutable access to the value of this signal. | +//! | [`Writeable`] | Guard | Gives mutable access to the value of this signal. //! //! ## Derived Traits //! //! ### Access //! | Trait | Mode | Composition | Description //! |-------------------|---------------|-------------------------------|------------ -//! | [`WithUntracked`] | `fn(&T) -> U` | [`Readable`] | Applies closure to the current value of the signal and returns result. -//! | [`With`] | `fn(&T) -> U` | [`Readable`] + [`Track`] | Applies closure to the current value of the signal and returns result, with reactive tracking. +//! | [`WithUntracked`] | `fn(&T) -> U` | [`ReadUntracked`] | Applies closure to the current value of the signal and returns result. +//! | [`With`] | `fn(&T) -> U` | [`ReadUntracked`] + [`Track`] | Applies closure to the current value of the signal and returns result, with reactive tracking. //! | [`GetUntracked`] | `T` | [`WithUntracked`] + [`Clone`] | Clones the current value of the signal. //! | [`Get`] | `T` | [`GetUntracked`] + [`Track`] | Clones the current value of the signal, with reactive tracking. //! @@ -42,9 +42,9 @@ //! These traits are designed so that you can implement as few as possible, and the rest will be //! implemented automatically. //! -//! For example, if you have a struct for which you can implement [`Readable`] and [`Track`], then +//! For example, if you have a struct for which you can implement [`ReadUntracked`] and [`Track`], then //! [`WithUntracked`] and [`With`] will be implemented automatically (as will [`GetUntracked`] and -//! [`Get`] for `Clone` types). But if you cannot implement [`Readable`] (because, for example, +//! [`Get`] for `Clone` types). But if you cannot implement [`ReadUntracked`] (because, for example, //! there isn't an `RwLock` you can wrap in a [`SignalReadGuard`](crate::signal::SignalReadGuard), //! but you can still implement [`WithUntracked`] and [`Track`], the same traits will still be implemented. @@ -95,15 +95,43 @@ impl Track for T { } } -pub trait Readable: Sized + DefinedAt { +pub trait ReadUntracked: Sized + DefinedAt { + type Value: Deref; + + #[track_caller] + fn try_read_untracked(&self) -> Option; + + #[track_caller] + fn read_untracked(&self) -> Self::Value { + self.try_read_untracked() + .unwrap_or_else(unwrap_signal!(self)) + } +} + +pub trait Read { type Value: Deref; #[track_caller] fn try_read(&self) -> Option; #[track_caller] + fn read(&self) -> Self::Value; +} + +impl Read for T +where + T: Track + ReadUntracked, +{ + type Value = T::Value; + + fn try_read(&self) -> Option { + self.track(); + self.try_read_untracked() + } + fn read(&self) -> Self::Value { - self.try_read().unwrap_or_else(unwrap_signal!(self)) + self.track(); + self.read_untracked() } } @@ -144,15 +172,15 @@ pub trait WithUntracked: DefinedAt { impl WithUntracked for T where - T: DefinedAt + Readable, + T: DefinedAt + ReadUntracked, { - type Value = <::Value as Deref>::Target; + type Value = <::Value as Deref>::Target; fn try_with_untracked( &self, fun: impl FnOnce(&Self::Value) -> U, ) -> Option { - self.try_read().map(|value| fun(&value)) + self.try_read_untracked().map(|value| fun(&value)) } } diff --git a/reactive_graph/tests/async_derived.rs b/reactive_graph/tests/async_derived.rs index 1b64cc984..ff10fd2fc 100644 --- a/reactive_graph/tests/async_derived.rs +++ b/reactive_graph/tests/async_derived.rs @@ -2,7 +2,7 @@ use reactive_graph::{ computed::{ArcAsyncDerived, AsyncDerived, AsyncState}, executor::Executor, signal::RwSignal, - traits::{Get, Readable, Set, With, WithUntracked}, + traits::{Get, Read, Set, With, WithUntracked}, }; #[cfg(feature = "tokio")] diff --git a/reactive_graph/tests/signal.rs b/reactive_graph/tests/signal.rs index 5058a9321..5cbbccb91 100644 --- a/reactive_graph/tests/signal.rs +++ b/reactive_graph/tests/signal.rs @@ -1,7 +1,7 @@ use reactive_graph::{ signal::{arc_signal, signal, ArcRwSignal, RwSignal}, traits::{ - Get, GetUntracked, Readable, Set, Update, UpdateUntracked, With, + Get, GetUntracked, Read, Set, Update, UpdateUntracked, With, WithUntracked, Writeable, }, }; diff --git a/tachys/src/reactive_graph/guards.rs b/tachys/src/reactive_graph/guards.rs index 98c44af50..5e147664e 100644 --- a/tachys/src/reactive_graph/guards.rs +++ b/tachys/src/reactive_graph/guards.rs @@ -1,46 +1,271 @@ //! Implements the [`Render`] and [`RenderHtml`] traits for signal guard types. -use crate::{prelude::RenderHtml, renderer::Renderer, view::Render}; +use crate::{ + hydration::Cursor, + prelude::RenderHtml, + renderer::{CastFrom, Renderer}, + view::{ + strings::StrState, InfallibleRender, Mountable, Position, + PositionState, Render, ToTemplate, + }, +}; use reactive_graph::signal::SignalReadGuard; +use std::{ + fmt::Write, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, + num::{ + NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, + NonZeroIsize, NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, + NonZeroU8, NonZeroUsize, + }, +}; -impl Render for SignalReadGuard -where - T: PartialEq + Clone + Render, - Rndr: Renderer, -{ - type State = T::State; +// any changes here should also be made in src/view/primitives.rs +macro_rules! render_primitive { + ($($child_type:ty),* $(,)?) => { + $( + paste::paste! { + pub struct [](R::Text, $child_type) where R: Renderer; + + impl<'a, R: Renderer> Mountable for [] { + fn unmount(&mut self) { + self.0.unmount() + } + + fn mount( + &mut self, + parent: &::Element, + marker: Option<&::Node>, + ) { + R::insert_node(parent, self.0.as_ref(), marker); + } + + fn insert_before_this( + &self, + parent: &::Element, + child: &mut dyn Mountable, + ) -> bool { + child.mount(parent, Some(self.0.as_ref())); + true + } + } + + impl<'a, R: Renderer> Render for SignalReadGuard<$child_type> { + type State = []; + + fn build(self) -> Self::State { + let node = R::create_text_node(&self.to_string()); + [](node, *self) + } + + fn rebuild(self, state: &mut Self::State) { + let [](node, this) = state; + if &self != this { + crate::log(&format!("not equal")); + R::set_text(node, &self.to_string()); + *this = *self; + } + } + } + + impl<'a> InfallibleRender for SignalReadGuard<$child_type> {} + + impl<'a, R> RenderHtml for SignalReadGuard<$child_type> + where + R: Renderer, + R::Node: Clone, + R::Element: Clone, + { + const MIN_LENGTH: usize = 0; + + fn to_html_with_buf(self, buf: &mut String, position: &mut Position) { + // add a comment node to separate from previous sibling, if any + if matches!(position, Position::NextChildAfterText) { + buf.push_str("") + } + write!(buf, "{}", self); + *position = Position::NextChildAfterText; + } + + fn hydrate( + self, + cursor: &Cursor, + position: &PositionState, + ) -> Self::State { + if position.get() == Position::FirstChild { + cursor.child(); + } else { + cursor.sibling(); + } + + // separating placeholder marker comes before text node + if matches!(position.get(), Position::NextChildAfterText) { + cursor.sibling(); + } + + let node = cursor.current(); + let node = R::Text::cast_from(node) + .expect("couldn't cast text node from node"); + + if !FROM_SERVER { + R::set_text(&node, &self.to_string()); + } + position.set(Position::NextChildAfterText); + + [](node, *self) + } + } + + impl<'a> ToTemplate for SignalReadGuard<$child_type> { + const TEMPLATE: &'static str = " "; + + fn to_template( + buf: &mut String, + _class: &mut String, + _style: &mut String, + _inner_html: &mut String, + position: &mut Position, + ) { + if matches!(*position, Position::NextChildAfterText) { + buf.push_str("") + } + buf.push(' '); + *position = Position::NextChildAfterText; + } + } + } + )* + }; +} + +render_primitive![ + usize, + u8, + u16, + u32, + u64, + u128, + isize, + i8, + i16, + i32, + i64, + i128, + f32, + f64, + char, + bool, + IpAddr, + SocketAddr, + SocketAddrV4, + SocketAddrV6, + Ipv4Addr, + Ipv6Addr, + NonZeroI8, + NonZeroU8, + NonZeroI16, + NonZeroU16, + NonZeroI32, + NonZeroU32, + NonZeroI64, + NonZeroU64, + NonZeroI128, + NonZeroU128, + NonZeroIsize, + NonZeroUsize, +]; + +// strings +pub struct SignalReadGuardStringState { + node: R::Text, + str: String, +} + +impl Render for SignalReadGuard { + type State = SignalReadGuardStringState; fn build(self) -> Self::State { - todo!() + let node = R::create_text_node(&self); + SignalReadGuardStringState { + node, + str: self.to_string(), + } } fn rebuild(self, state: &mut Self::State) { - todo!() + let SignalReadGuardStringState { node, str } = state; + if *self != *str { + R::set_text(node, &self); + str.clear(); + str.push_str(&self); + } } } -impl RenderHtml for SignalReadGuard -where - T: PartialEq + Clone + RenderHtml, - Rndr: Renderer, - Rndr::Element: Clone, - Rndr::Node: Clone, -{ - const MIN_LENGTH: usize = T::MIN_LENGTH; +impl InfallibleRender for SignalReadGuard {} - fn to_html_with_buf( - self, - buf: &mut String, - position: &mut crate::view::Position, - ) { - todo!() +impl RenderHtml for SignalReadGuard +where + R: Renderer, + R::Node: Clone, + R::Element: Clone, +{ + const MIN_LENGTH: usize = 0; + + fn to_html_with_buf(self, buf: &mut String, position: &mut Position) { + <&str as RenderHtml>::to_html_with_buf(&self, buf, position) } fn hydrate( self, - cursor: &crate::hydration::Cursor, - position: &crate::view::PositionState, + cursor: &Cursor, + position: &PositionState, ) -> Self::State { - todo!() + let this: &str = self.as_ref(); + let StrState { node, .. } = + this.hydrate::(cursor, position); + SignalReadGuardStringState { + node, + str: self.to_string(), + } + } +} + +impl ToTemplate for SignalReadGuard { + const TEMPLATE: &'static str = <&str as ToTemplate>::TEMPLATE; + + fn to_template( + buf: &mut String, + class: &mut String, + style: &mut String, + inner_html: &mut String, + position: &mut Position, + ) { + <&str as ToTemplate>::to_template( + buf, class, style, inner_html, position, + ) + } +} + +impl Mountable for SignalReadGuardStringState { + fn unmount(&mut self) { + self.node.unmount() + } + + fn mount( + &mut self, + parent: &::Element, + marker: Option<&::Node>, + ) { + R::insert_node(parent, self.node.as_ref(), marker); + } + + fn insert_before_this( + &self, + parent: &::Element, + child: &mut dyn Mountable, + ) -> bool { + child.mount(parent, Some(self.node.as_ref())); + true } } diff --git a/tachys/src/view/primitives.rs b/tachys/src/view/primitives.rs index 6f98a0727..724aa186a 100644 --- a/tachys/src/view/primitives.rs +++ b/tachys/src/view/primitives.rs @@ -16,6 +16,7 @@ use std::{ }, }; +// any changes here should also be made in src/reactive_graph/guards.rs macro_rules! render_primitive { ($($child_type:ty),* $(,)?) => { $( diff --git a/tachys/src/view/strings.rs b/tachys/src/view/strings.rs index 417104545..b692f8567 100644 --- a/tachys/src/view/strings.rs +++ b/tachys/src/view/strings.rs @@ -6,7 +6,7 @@ use crate::{ hydration::Cursor, renderer::{CastFrom, Renderer}, }; -use std::{rc::Rc, sync::Arc}; +use std::{borrow::Cow, rc::Rc, sync::Arc}; pub struct StrState<'a, R: Renderer> { pub node: R::Text, @@ -121,6 +121,7 @@ where true } } + pub struct StringState { node: R::Text, str: String, @@ -380,3 +381,90 @@ impl Mountable for ArcStrState { true } } + +pub struct CowStrState<'a, R: Renderer> { + node: R::Text, + str: Cow<'a, str>, +} + +impl<'a, R: Renderer> Render for Cow<'a, str> { + type State = CowStrState<'a, R>; + + fn build(self) -> Self::State { + let node = R::create_text_node(&self); + CowStrState { node, str: self } + } + + fn rebuild(self, state: &mut Self::State) { + let CowStrState { node, str } = state; + if self != *str { + R::set_text(node, &self); + *str = self; + } + } +} + +impl<'a> InfallibleRender for Cow<'a, str> {} + +impl<'a, R> RenderHtml for Cow<'a, str> +where + R: Renderer, + R::Node: Clone, + R::Element: Clone, +{ + const MIN_LENGTH: usize = 0; + + fn to_html_with_buf(self, buf: &mut String, position: &mut Position) { + <&str as RenderHtml>::to_html_with_buf(&self, buf, position) + } + + fn hydrate( + self, + cursor: &Cursor, + position: &PositionState, + ) -> Self::State { + let this: &str = self.as_ref(); + let StrState { node, .. } = + this.hydrate::(cursor, position); + CowStrState { node, str: self } + } +} + +impl<'a> ToTemplate for Cow<'a, str> { + const TEMPLATE: &'static str = <&str as ToTemplate>::TEMPLATE; + + fn to_template( + buf: &mut String, + class: &mut String, + style: &mut String, + inner_html: &mut String, + position: &mut Position, + ) { + <&str as ToTemplate>::to_template( + buf, class, style, inner_html, position, + ) + } +} + +impl<'a, R: Renderer> Mountable for CowStrState<'a, R> { + fn unmount(&mut self) { + self.node.unmount() + } + + fn mount( + &mut self, + parent: &::Element, + marker: Option<&::Node>, + ) { + R::insert_node(parent, self.node.as_ref(), marker); + } + + fn insert_before_this( + &self, + parent: &::Element, + child: &mut dyn Mountable, + ) -> bool { + child.mount(parent, Some(self.node.as_ref())); + true + } +}