From 8d68261bab0eb7a7acd07e15d669bd095895fb8e Mon Sep 17 00:00:00 2001 From: Luke Chu <37006668+lukechu10@users.noreply.github.com> Date: Mon, 5 Apr 2021 22:06:34 -0700 Subject: [PATCH 01/15] Remove SignalVec --- maple-core/src/lib.rs | 18 +- maple-core/src/reactive.rs | 2 - maple-core/src/reactive/signal_vec.rs | 318 -------------------------- maple-core/src/render.rs | 93 +------- 4 files changed, 4 insertions(+), 427 deletions(-) delete mode 100644 maple-core/src/reactive/signal_vec.rs diff --git a/maple-core/src/lib.rs b/maple-core/src/lib.rs index 9deaa0502..7232df5c6 100644 --- a/maple-core/src/lib.rs +++ b/maple-core/src/lib.rs @@ -17,7 +17,6 @@ use generic_node::GenericNode; pub use maple_core_macro::template; -use prelude::SignalVec; pub mod easing; pub mod flow; @@ -28,6 +27,7 @@ pub mod reactive; pub mod render; pub mod utils; +/// Result of the [`template`] macro. #[derive(Debug, Clone, PartialEq, Eq)] pub struct TemplateResult { node: G, @@ -49,18 +49,6 @@ impl TemplateResult { } } -/// A [`SignalVec`](reactive::SignalVec) of [`TemplateResult`]s. -#[derive(Clone)] -pub struct TemplateList { - templates: reactive::SignalVec>, -} - -impl From>> for TemplateList { - fn from(templates: SignalVec>) -> Self { - Self { templates } - } -} - /// Render a [`TemplateResult`] into the DOM. /// Alias for [`render_to`] with `parent` being the `` tag. /// @@ -123,12 +111,12 @@ pub mod prelude { pub use crate::noderef::NodeRef; pub use crate::reactive::{ create_effect, create_effect_initial, create_memo, create_root, create_selector, - create_selector_with, on_cleanup, untrack, Signal, SignalVec, StateHandle, + create_selector_with, on_cleanup, untrack, Signal, StateHandle, }; pub use crate::render::Render; #[cfg(feature = "ssr")] pub use crate::render_to_string; + pub use crate::TemplateResult; #[cfg(feature = "dom")] pub use crate::{render, render_to}; - pub use crate::{TemplateList, TemplateResult}; } diff --git a/maple-core/src/reactive.rs b/maple-core/src/reactive.rs index 50a932876..cb946701e 100644 --- a/maple-core/src/reactive.rs +++ b/maple-core/src/reactive.rs @@ -2,12 +2,10 @@ mod effect; mod signal; -mod signal_vec; mod motion; pub use effect::*; pub use signal::*; -pub use signal_vec::*; pub use motion::*; /// Creates a new reactive root. Generally, you won't need this method as it is called automatically in [`render`](crate::render()). diff --git a/maple-core/src/reactive/signal_vec.rs b/maple-core/src/reactive/signal_vec.rs deleted file mode 100644 index 65bbc164d..000000000 --- a/maple-core/src/reactive/signal_vec.rs +++ /dev/null @@ -1,318 +0,0 @@ -use std::cell::RefCell; -use std::rc::Rc; - -use crate::{TemplateList, TemplateResult}; - -use super::*; -use crate::generic_node::GenericNode; - -/// A reactive [`Vec`]. -/// This is more effective than using a [`Signal`](Signal) because it allows fine grained -/// reactivity within the `Vec`. -pub struct SignalVec { - signal: Signal>>, - /// A list of past changes that is accessed by subscribers. - /// Cleared when all subscribers are called. - changes: Rc>>>, -} - -impl SignalVec { - /// Create a new empty `SignalVec`. - pub fn new() -> Self { - Self { - signal: Signal::new(RefCell::new(Vec::new())), - changes: Rc::new(RefCell::new(Vec::new())), - } - } - - /// Create a new `SignalVec` with existing values from a [`Vec`]. - pub fn with_values(values: Vec) -> Self { - Self { - signal: Signal::new(RefCell::new(values)), - changes: Rc::new(RefCell::new(Vec::new())), - } - } - - /// Get the current pending changes that will be applied to the `SignalVec`. - pub fn changes(&self) -> &Rc>>> { - &self.changes - } - - /// Returns the inner backing [`Signal`] used to store the data. This method should used with - /// care as unintentionally modifying the [`Vec`] will not trigger any updates and cause - /// potential future problems. - pub fn inner_signal(&self) -> &Signal>> { - &self.signal - } - - pub fn replace(&self, values: Vec) { - self.add_change(VecDiff::Replace { values }); - - self.trigger_and_apply_changes(); - } - - pub fn insert(&self, index: usize, value: T) { - self.add_change(VecDiff::Insert { index, value }); - - self.trigger_and_apply_changes(); - } - - pub fn update(&self, index: usize, value: T) { - self.add_change(VecDiff::Update { index, value }) - } - - pub fn remove(&self, index: usize) { - self.add_change(VecDiff::Remove { index }); - - self.trigger_and_apply_changes(); - } - - pub fn swap(&self, index1: usize, index2: usize) { - self.add_change(VecDiff::Swap { index1, index2 }); - - self.trigger_and_apply_changes(); - } - - pub fn push(&self, value: T) { - self.add_change(VecDiff::Push { value }); - - self.trigger_and_apply_changes(); - } - - pub fn pop(&self) { - self.add_change(VecDiff::Pop); - - self.trigger_and_apply_changes(); - } - - pub fn clear(&self) { - self.add_change(VecDiff::Clear); - - self.trigger_and_apply_changes(); - } - - fn add_change(&self, change: VecDiff) { - self.changes.borrow_mut().push(change); - } - - fn trigger_and_apply_changes(&self) { - self.signal.trigger_subscribers(); - - for change in self.changes.take() { - change.apply_to_vec(&mut self.signal.get().borrow_mut()); - } - } - - /// Creates a derived `SignalVec`. - /// - /// # Example - /// ``` - /// use maple_core::prelude::*; - /// - /// let my_vec = SignalVec::with_values(vec![1, 2, 3]); - /// let squared = my_vec.map(|x| *x * *x); - /// - /// assert_eq!(*squared.inner_signal().get().borrow(), vec![1, 4, 9]); - /// - /// my_vec.push(4); - /// assert_eq!(*squared.inner_signal().get().borrow(), vec![1, 4, 9, 16]); - /// - /// my_vec.swap(0, 1); - /// assert_eq!(*squared.inner_signal().get().borrow(), vec![4, 1, 9, 16]); - /// ``` - pub fn map(&self, f: impl Fn(&T) -> U + 'static) -> SignalVec { - let signal = self.inner_signal().clone(); - let changes = Rc::clone(&self.changes()); - let f = Rc::new(f); - - create_effect_initial(move || { - let derived = SignalVec::with_values( - signal.get().borrow().iter().map(|value| f(value)).collect(), - ); - - let effect = { - let derived = derived.clone(); - let signal = signal.clone(); - move || { - signal.get(); // subscribe to signal - for change in changes.borrow().iter() { - match change { - VecDiff::Replace { values } => { - derived.replace(values.iter().map(|value| f(value)).collect()) - } - VecDiff::Insert { index, value } => derived.insert(*index, f(value)), - VecDiff::Update { index, value } => derived.update(*index, f(value)), - VecDiff::Remove { index } => derived.remove(*index), - VecDiff::Swap { index1, index2 } => derived.swap(*index1, *index2), - VecDiff::Push { value } => derived.push(f(value)), - VecDiff::Pop => derived.pop(), - VecDiff::Clear => derived.clear(), - } - } - } - }; - - (Rc::new(effect), derived) - }) - } -} - -impl SignalVec> { - /// Create a [`TemplateList`] from the `SignalVec`. - pub fn template_list(&self) -> TemplateList { - TemplateList::from(self.clone()) - } -} - -impl SignalVec { - /// Create a [`Vec`] from a [`SignalVec`]. The returned [`Vec`] is cloned from the data which - /// requires `T` to be `Clone`. - /// - /// # Example - /// ``` - /// use maple_core::prelude::*; - /// - /// let signal = SignalVec::with_values(vec![1, 2, 3]); - /// assert_eq!(signal.to_vec(), vec![1, 2, 3]); - /// ``` - pub fn to_vec(&self) -> Vec { - self.signal.get().borrow().clone() - } -} - -impl Default for SignalVec { - fn default() -> Self { - Self::new() - } -} - -impl Clone for SignalVec { - fn clone(&self) -> Self { - Self { - signal: self.signal.clone(), - changes: Rc::clone(&self.changes), - } - } -} - -/// An enum describing the changes applied on a [`SignalVec`]. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum VecDiff { - Replace { values: Vec }, - Insert { index: usize, value: T }, - Update { index: usize, value: T }, - Remove { index: usize }, - Swap { index1: usize, index2: usize }, - Push { value: T }, - Pop, - Clear, -} - -impl VecDiff { - pub fn apply_to_vec(self, v: &mut Vec) { - match self { - VecDiff::Replace { values } => *v = values, - VecDiff::Insert { index, value } => v.insert(index, value), - VecDiff::Update { index, value } => v[index] = value, - VecDiff::Remove { index } => { - v.remove(index); - } - VecDiff::Swap { index1, index2 } => v.swap(index1, index2), - VecDiff::Push { value } => v.push(value), - VecDiff::Pop => { - v.pop(); - } - VecDiff::Clear => v.clear(), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn signal_vec() { - let my_vec = SignalVec::new(); - assert_eq!(*my_vec.inner_signal().get().borrow(), Vec::::new()); - - my_vec.push(3); - assert_eq!(*my_vec.inner_signal().get().borrow(), vec![3]); - - my_vec.push(4); - assert_eq!(*my_vec.inner_signal().get().borrow(), vec![3, 4]); - - my_vec.pop(); - assert_eq!(*my_vec.inner_signal().get().borrow(), vec![3]); - } - - #[test] - fn map() { - let my_vec = SignalVec::with_values(vec![1, 2, 3]); - let squared = my_vec.map(|x| *x * *x); - - assert_eq!(*squared.inner_signal().get().borrow(), vec![1, 4, 9]); - - my_vec.push(4); - assert_eq!(*squared.inner_signal().get().borrow(), vec![1, 4, 9, 16]); - - my_vec.pop(); - assert_eq!(*squared.inner_signal().get().borrow(), vec![1, 4, 9]); - } - - #[test] - fn map_chain() { - let my_vec = SignalVec::with_values(vec![1, 2, 3]); - let squared = my_vec.map(|x| *x * 2); - let quadrupled = squared.map(|x| *x * 2); - - assert_eq!(*quadrupled.inner_signal().get().borrow(), vec![4, 8, 12]); - - my_vec.push(4); - assert_eq!( - *quadrupled.inner_signal().get().borrow(), - vec![4, 8, 12, 16] - ); - - my_vec.pop(); - assert_eq!(*quadrupled.inner_signal().get().borrow(), vec![4, 8, 12]); - } - - #[test] - fn map_chain_temporary() { - let my_vec = SignalVec::with_values(vec![1, 2, 3]); - let quadrupled = my_vec.map(|x| *x * 2).map(|x| *x * 2); - - assert_eq!(*quadrupled.inner_signal().get().borrow(), vec![4, 8, 12]); - - my_vec.push(4); - assert_eq!( - *quadrupled.inner_signal().get().borrow(), - vec![4, 8, 12, 16] - ); - - my_vec.pop(); - assert_eq!(*quadrupled.inner_signal().get().borrow(), vec![4, 8, 12]); - } - - #[test] - fn map_inner_scope() { - let my_vec = SignalVec::with_values(vec![1, 2, 3]); - let quadrupled; - - let doubled = my_vec.map(|x| *x * 2); - assert_eq!(*doubled.inner_signal().get().borrow(), vec![2, 4, 6]); - - quadrupled = doubled.map(|x| *x * 2); - assert_eq!(*quadrupled.inner_signal().get().borrow(), vec![4, 8, 12]); - - drop(doubled); - assert_eq!(*quadrupled.inner_signal().get().borrow(), vec![4, 8, 12]); - - my_vec.push(4); - assert_eq!( - *quadrupled.inner_signal().get().borrow(), - vec![4, 8, 12, 16] - ); - } -} diff --git a/maple-core/src/render.rs b/maple-core/src/render.rs index 1ec92e5af..dc9cd9eb2 100644 --- a/maple-core/src/render.rs +++ b/maple-core/src/render.rs @@ -1,11 +1,9 @@ //! Trait for describing how something should be rendered into DOM nodes. use std::fmt; -use std::rc::Rc; use crate::generic_node::GenericNode; -use crate::reactive::VecDiff; -use crate::{TemplateList, TemplateResult}; +use crate::TemplateResult; /// Trait for describing how something should be rendered into DOM nodes. pub trait Render { @@ -39,95 +37,6 @@ impl Render for T { } } -impl Render for TemplateList { - fn render(&self) -> G { - let fragment = G::fragment(); - - for item in self - .templates - .inner_signal() - .get() - .borrow() - .clone() - .into_iter() - { - fragment.append_render(Box::new(move || { - let item = item.clone(); - Box::new(item) - })); - } - - fragment - } - - fn update_node(&self, parent: &G, node: &G) -> G { - let templates = self.templates.inner_signal().get(); // subscribe to templates - let changes = Rc::clone(&self.templates.changes()); - - for change in changes.borrow().iter() { - match change { - VecDiff::Replace { values } => { - let first = templates.borrow().first().map(|x| x.node.clone()); - - for value in values { - parent.insert_child_before(&value.node, first.as_ref()); - } - - for template in templates.borrow().iter() { - parent.remove_child(&template.node); - } - } - VecDiff::Insert { index, value } => { - parent.insert_child_before( - &value.node, - templates - .borrow() - .get(*index) - .map(|template| template.node.next_sibling()) - .flatten() - .as_ref(), - ); - } - VecDiff::Update { index, value } => { - parent.replace_child(&templates.borrow()[*index].node, &value.node); - } - VecDiff::Remove { index } => { - parent.remove_child(&templates.borrow()[*index].node); - } - VecDiff::Swap { index1, index2 } => { - let child1 = &templates.borrow()[*index1].node; - let child2 = &templates.borrow()[*index2].node; - parent.replace_child(child1, child2); - parent.replace_child(child2, child1); - } - VecDiff::Push { value } => { - parent.insert_child_before( - &value.node, - templates - .borrow() - .last() - .map(|last| last.node.next_sibling()) - .flatten() - .as_ref(), - ); - } - VecDiff::Pop => { - if let Some(last) = templates.borrow().last() { - parent.remove_child(&last.node); - } - } - VecDiff::Clear => { - for template in templates.borrow().iter() { - parent.remove_child(&template.node); - } - } - } - } - - node.clone() - } -} - impl Render for TemplateResult { fn render(&self) -> G { self.node.clone() From d538e77d1616cfc758a4d64b3e69bafa64022fdf Mon Sep 17 00:00:00 2001 From: Luke Chu <37006668+lukechu10@users.noreply.github.com> Date: Tue, 6 Apr 2021 09:56:24 -0700 Subject: [PATCH 02/15] Move TemplateResult into sub-module --- maple-core-macro/src/component.rs | 6 ++++- maple-core-macro/src/lib.rs | 2 +- maple-core/src/flow.rs | 33 +++++++++++++++------------ maple-core/src/lib.rs | 38 ++++++++----------------------- maple-core/src/render.rs | 4 ++-- maple-core/src/template_result.rs | 23 +++++++++++++++++++ 6 files changed, 58 insertions(+), 48 deletions(-) create mode 100644 maple-core/src/template_result.rs diff --git a/maple-core-macro/src/component.rs b/maple-core-macro/src/component.rs index 157df3c22..d2db0fd35 100644 --- a/maple-core-macro/src/component.rs +++ b/maple-core-macro/src/component.rs @@ -31,7 +31,11 @@ impl ToTokens for Component { args, } = self; - let quoted = quote! { ::maple_core::reactive::untrack(|| ::maple_core::TemplateResult::inner_element(&#path(#args))) }; + let quoted = quote! { + ::maple_core::reactive::untrack(|| + ::std::clone::Clone::clone(::maple_core::template_result::TemplateResult::inner_node(&#path(#args))) + ) + }; tokens.extend(quoted); } diff --git a/maple-core-macro/src/lib.rs b/maple-core-macro/src/lib.rs index d5b5e4f44..3b616819a 100644 --- a/maple-core-macro/src/lib.rs +++ b/maple-core-macro/src/lib.rs @@ -81,7 +81,7 @@ pub fn template(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as HtmlTree); let quoted = quote! { - ::maple_core::TemplateResult::new(#input) + ::maple_core::template_result::TemplateResult::new(#input) }; TokenStream::from(quoted) diff --git a/maple-core/src/flow.rs b/maple-core/src/flow.rs index b3ce9b45e..258270886 100644 --- a/maple-core/src/flow.rs +++ b/maple-core/src/flow.rs @@ -84,7 +84,7 @@ where if iterable.get().is_empty() { for (_, (owner, _value, template, _i)) in templates.borrow_mut().drain() { drop(owner); // destroy owner - template.node.remove_self(); + template.inner_node().remove_self(); } return; } @@ -114,7 +114,7 @@ where } for node in excess_nodes { - node.1 .0.node.remove_self(); + node.1 .0.inner_node().remove_self(); } } @@ -161,13 +161,13 @@ where if let Some(next_node) = templates.get(&key_fn(next_item)) { next_node .2 - .node - .insert_sibling_before(&new_template.unwrap().node); + .inner_node() + .insert_sibling_before(new_template.unwrap().inner_node()); } else { - marker.insert_sibling_before(&new_template.unwrap().node); + marker.insert_sibling_before(new_template.unwrap().inner_node()); } } else { - marker.insert_sibling_before(&new_template.unwrap().node); + marker.insert_sibling_before(new_template.unwrap().inner_node()); } } else if match previous_value { Some(prev) => prev.index, @@ -177,12 +177,12 @@ where // Location changed, move from old location to new location // Node was moved in the DOM. Move node to new index. - let node = templates.borrow().get(&key).unwrap().2.node.clone(); + let node = templates.borrow().get(&key).unwrap().2.inner_node().clone(); if let Some(next_item) = iterable.get().get(i + 1) { let templates = templates.borrow(); let next_node = templates.get(&key_fn(next_item)).unwrap(); - next_node.2.node.insert_sibling_before(&node); // Move to before next node + next_node.2.inner_node().insert_sibling_before(&node); // Move to before next node } else { marker.insert_sibling_before(&node); // Move to end. } @@ -211,8 +211,8 @@ where (owner, item.clone(), new_template.clone().unwrap(), i), ); - let parent = old_node.node.parent_node().unwrap(); - parent.replace_child(&new_template.unwrap().node, &old_node.node); + let parent = old_node.inner_node().parent_node().unwrap(); + parent.replace_child(new_template.unwrap().inner_node(), old_node.inner_node()); } } } @@ -275,7 +275,7 @@ where if props.iterable.get().is_empty() { for (owner, template) in templates.borrow_mut().drain(..) { drop(owner); // destroy owner - template.node.remove_self(); + template.inner_node().remove_self(); } return; } @@ -304,8 +304,11 @@ where (owner, new_template.as_ref().unwrap().clone()), ); - let parent = old_node.1.node.parent_node().unwrap(); - parent.replace_child(&new_template.unwrap().node, &old_node.1.node); + let parent = old_node.1.inner_node().parent_node().unwrap(); + parent.replace_child( + new_template.unwrap().inner_node(), + old_node.1.inner_node(), + ); } else { debug_assert!(templates.borrow().len() == i, "pushing new value scenario"); @@ -313,7 +316,7 @@ where .borrow_mut() .push((owner, new_template.as_ref().unwrap().clone())); - marker.insert_sibling_before(&new_template.unwrap().node); + marker.insert_sibling_before(&new_template.unwrap().inner_node()); } } } @@ -323,7 +326,7 @@ where let excess_nodes = templates.drain(props.iterable.get().len()..); for node in excess_nodes { - node.1.node.remove_self(); + node.1.inner_node().remove_self(); } } diff --git a/maple-core/src/lib.rs b/maple-core/src/lib.rs index 7232df5c6..61206ed11 100644 --- a/maple-core/src/lib.rs +++ b/maple-core/src/lib.rs @@ -15,7 +15,6 @@ #![deny(clippy::trait_duplication_in_bounds)] #![deny(clippy::type_repetition_in_bounds)] -use generic_node::GenericNode; pub use maple_core_macro::template; pub mod easing; @@ -25,36 +24,17 @@ pub mod macros; pub mod noderef; pub mod reactive; pub mod render; +pub mod template_result; pub mod utils; -/// Result of the [`template`] macro. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct TemplateResult { - node: G, -} - -impl TemplateResult { - /// Create a new [`TemplateResult`] from a [`GenericNode`]. - pub fn new(node: G) -> Self { - Self { node } - } - - /// Create a new [`TemplateResult`] with a blank comment node - pub fn empty() -> Self { - Self::new(G::marker()) - } - - pub fn inner_element(&self) -> G { - self.node.clone() - } -} - /// Render a [`TemplateResult`] into the DOM. /// Alias for [`render_to`] with `parent` being the `` tag. /// /// _This API requires the following crate features to be activated: `dom`_ #[cfg(feature = "dom")] -pub fn render(template_result: impl FnOnce() -> TemplateResult) { +pub fn render( + template_result: impl FnOnce() -> template_result::TemplateResult, +) { let window = web_sys::window().unwrap(); let document = window.document().unwrap(); @@ -67,12 +47,12 @@ pub fn render(template_result: impl FnOnce() -> TemplateResult TemplateResult, + template_result: impl FnOnce() -> template_result::TemplateResult, parent: &web_sys::Node, ) { let owner = reactive::create_root(|| { parent - .append_child(&template_result().node.inner_element()) + .append_child(&template_result().inner_node().inner_element()) .unwrap(); }); @@ -88,11 +68,11 @@ pub fn render_to( /// _This API requires the following crate features to be activated: `ssr`_ #[cfg(feature = "ssr")] pub fn render_to_string( - template_result: impl FnOnce() -> TemplateResult, + template_result: impl FnOnce() -> template_result::TemplateResult, ) -> String { let mut ret = None; let _owner = - reactive::create_root(|| ret = Some(format!("{}", template_result().inner_element()))); + reactive::create_root(|| ret = Some(format!("{}", template_result().inner_node()))); ret.unwrap() } @@ -116,7 +96,7 @@ pub mod prelude { pub use crate::render::Render; #[cfg(feature = "ssr")] pub use crate::render_to_string; - pub use crate::TemplateResult; + pub use crate::template_result::TemplateResult; #[cfg(feature = "dom")] pub use crate::{render, render_to}; } diff --git a/maple-core/src/render.rs b/maple-core/src/render.rs index dc9cd9eb2..58c84a6ef 100644 --- a/maple-core/src/render.rs +++ b/maple-core/src/render.rs @@ -3,7 +3,7 @@ use std::fmt; use crate::generic_node::GenericNode; -use crate::TemplateResult; +use crate::template_result::TemplateResult; /// Trait for describing how something should be rendered into DOM nodes. pub trait Render { @@ -39,6 +39,6 @@ impl Render for T { impl Render for TemplateResult { fn render(&self) -> G { - self.node.clone() + self.inner_node().clone() } } diff --git a/maple-core/src/template_result.rs b/maple-core/src/template_result.rs new file mode 100644 index 000000000..49f38fece --- /dev/null +++ b/maple-core/src/template_result.rs @@ -0,0 +1,23 @@ +use crate::generic_node::GenericNode; + +/// Result of the [`template`] macro. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TemplateResult { + node: G, +} + +impl TemplateResult { + /// Create a new [`TemplateResult`] from a [`GenericNode`]. + pub fn new(node: G) -> Self { + Self { node } + } + + /// Create a new [`TemplateResult`] with a blank comment node + pub fn empty() -> Self { + Self::new(G::marker()) + } + + pub fn inner_node(&self) -> &G { + &self.node + } +} From 354f0080bf361ffaceefbd7e4a08e3268d3a21ca Mon Sep 17 00:00:00 2001 From: Luke Chu <37006668+lukechu10@users.noreply.github.com> Date: Tue, 6 Apr 2021 14:09:14 -0700 Subject: [PATCH 03/15] wrap comments at 100 --- maple-core/src/flow.rs | 14 ++++++++---- maple-core/src/generic_node.rs | 38 +++++++++++++++++++------------ maple-core/src/lib.rs | 12 ++++++---- maple-core/src/reactive.rs | 7 +++--- maple-core/src/reactive/effect.rs | 13 +++++++---- maple-core/src/reactive/motion.rs | 3 ++- maple-core/src/reactive/signal.rs | 8 ++++--- maple-core/src/render.rs | 13 +++++++---- maple-core/src/template_result.rs | 9 +++++++- rustfmt.toml | 2 ++ 10 files changed, 77 insertions(+), 42 deletions(-) create mode 100644 rustfmt.toml diff --git a/maple-core/src/flow.rs b/maple-core/src/flow.rs index 258270886..03cc9dfb8 100644 --- a/maple-core/src/flow.rs +++ b/maple-core/src/flow.rs @@ -26,7 +26,8 @@ where } /// Keyed iteration. Use this instead of directly rendering an array of [`TemplateResult`]s. -/// Using this will minimize re-renders instead of re-rendering every single node on every state change. +/// Using this will minimize re-renders instead of re-rendering every single node on every state +/// change. /// /// For non keyed iteration, see [`Indexed`]. /// @@ -66,7 +67,8 @@ where type TemplateValue = (Owner, T, TemplateResult, usize /* index */); - // A tuple with a value of type `T` and the `TemplateResult` produces by calling `props.template` with the first value. + // A tuple with a value of type `T` and the `TemplateResult` produces by calling + // `props.template` with the first value. let templates: Rc>>> = Default::default(); let fragment = G::fragment(); @@ -182,7 +184,8 @@ where if let Some(next_item) = iterable.get().get(i + 1) { let templates = templates.borrow(); let next_node = templates.get(&key_fn(next_item)).unwrap(); - next_node.2.inner_node().insert_sibling_before(&node); // Move to before next node + next_node.2.inner_node().insert_sibling_before(&node); // Move to before + // next node } else { marker.insert_sibling_before(&node); // Move to end. } @@ -230,8 +233,9 @@ where pub template: F, } -/// Non keyed iteration (or keyed by index). Use this instead of directly rendering an array of [`TemplateResult`]s. -/// Using this will minimize re-renders instead of re-rendering every single node on every state change. +/// Non keyed iteration (or keyed by index). Use this instead of directly rendering an array of +/// [`TemplateResult`]s. Using this will minimize re-renders instead of re-rendering every single +/// node on every state change. /// /// For keyed iteration, see [`Keyed`]. /// diff --git a/maple-core/src/generic_node.rs b/maple-core/src/generic_node.rs index fcaa34d02..adad7090f 100644 --- a/maple-core/src/generic_node.rs +++ b/maple-core/src/generic_node.rs @@ -22,18 +22,21 @@ pub type EventListener = dyn Fn(Event); /// Abstraction over a rendering backend. /// -/// You would probably use this trait as a trait bound when you want to accept any rendering backend. -/// For example, components are often generic over [`GenericNode`] to be able to render to different backends. +/// You would probably use this trait as a trait bound when you want to accept any rendering +/// backend. For example, components are often generic over [`GenericNode`] to be able to render to +/// different backends. /// -/// Note that components are **NOT** represented by [`GenericNode`]. Instead, components are _disappearing_, meaning -/// that they are simply functions that generate [`GenericNode`]s inside a new reactive context. This means that there -/// is no overhead whatsoever when using components. +/// Note that components are **NOT** represented by [`GenericNode`]. Instead, components are +/// _disappearing_, meaning that they are simply functions that generate [`GenericNode`]s inside a +/// new reactive context. This means that there is no overhead whatsoever when using components. /// /// Maple ships with 2 rendering backends out of the box: /// * [`DomNode`] - Rendering in the browser (to real DOM nodes). -/// * [`SsrNode`] - Render to a static string (often on the server side for Server Side Rendering, aka. SSR). +/// * [`SsrNode`] - Render to a static string (often on the server side for Server Side Rendering, +/// aka. SSR). /// -/// To implement your own rendering backend, you will need to create a new struct which implements [`GenericNode`]. +/// To implement your own rendering backend, you will need to create a new struct which implements +/// [`GenericNode`]. pub trait GenericNode: fmt::Debug + Clone + PartialEq + Eq + 'static { /// Create a new element node. fn element(tag: &str) -> Self; @@ -41,12 +44,14 @@ pub trait GenericNode: fmt::Debug + Clone + PartialEq + Eq + 'static { /// Create a new text node. fn text_node(text: &str) -> Self; - /// Create a new fragment (list of nodes). A fragment is not necessarily wrapped around by an element. + /// Create a new fragment (list of nodes). A fragment is not necessarily wrapped around by an + /// element. fn fragment() -> Self; - /// Create a marker (dummy) node. For [`DomNode`], this is implemented by creating an empty comment node. - /// This is used, for example, in [`Keyed`] and [`Indexed`] for scenarios where you want to push a new item to the - /// end of the list. If the list is empty, a dummy node is needed to store the position of the component. + /// Create a marker (dummy) node. For [`DomNode`], this is implemented by creating an empty + /// comment node. This is used, for example, in [`Keyed`] and [`Indexed`] for scenarios + /// where you want to push a new item to the end of the list. If the list is empty, a dummy + /// node is needed to store the position of the component. fn marker() -> Self; /// Sets an attribute on a node. @@ -55,8 +60,9 @@ pub trait GenericNode: fmt::Debug + Clone + PartialEq + Eq + 'static { /// Appends a child to the node's children. fn append_child(&self, child: &Self); - /// Insert a new child node to this node's children. If `reference_node` is `Some`, the child will be inserted - /// before the reference node. Else if `None`, the child will be inserted at the end. + /// Insert a new child node to this node's children. If `reference_node` is `Some`, the child + /// will be inserted before the reference node. Else if `None`, the child will be inserted + /// at the end. fn insert_child_before(&self, new_node: &Self, reference_node: Option<&Self>); /// Remove a child node from this node's children. @@ -82,10 +88,12 @@ pub trait GenericNode: fmt::Debug + Clone + PartialEq + Eq + 'static { /// Add a [`EventListener`] to the event `name`. fn event(&self, name: &str, handler: Box); - /// Update inner text of the node. If the node has elements, all the elements are replaced with a new text node. + /// Update inner text of the node. If the node has elements, all the elements are replaced with + /// a new text node. fn update_inner_text(&self, text: &str); - /// Append an item that implements [`Render`] and automatically updates the DOM inside an effect. + /// Append an item that implements [`Render`] and automatically updates the DOM inside an + /// effect. fn append_render(&self, child: Box Box>>) { let parent = self.clone(); diff --git a/maple-core/src/lib.rs b/maple-core/src/lib.rs index 61206ed11..0d607d356 100644 --- a/maple-core/src/lib.rs +++ b/maple-core/src/lib.rs @@ -5,9 +5,12 @@ //! This is the API docs for maple. If you are looking for the usage docs, checkout the [README](https://github.com/lukechu10/maple). //! //! ## Features -//! - `dom` (_default_) - Enables rendering templates to DOM nodes. Only useful on `wasm32-unknown-unknown` target. -//! - `ssr` - Enables rendering templates to static strings (useful for Server Side Rendering / Pre-rendering). -//! - `serde` - Enables serializing and deserializing `Signal`s and other wrapper types using `serde`. +//! - `dom` (_default_) - Enables rendering templates to DOM nodes. Only useful on +//! `wasm32-unknown-unknown` target. +//! - `ssr` - Enables rendering templates to static strings (useful for Server Side Rendering / +//! Pre-rendering). +//! - `serde` - Enables serializing and deserializing `Signal`s and other wrapper types using +//! `serde`. #![allow(non_snake_case)] #![warn(clippy::clone_on_ref_ptr)] @@ -63,7 +66,8 @@ pub fn render_to( GLOBAL_OWNERS.with(|global_owners| global_owners.borrow_mut().push(owner)); } -/// Render a [`TemplateResult`] into a static [`String`]. Useful for rendering to a string on the server side. +/// Render a [`TemplateResult`] into a static [`String`]. Useful for rendering to a string on the +/// server side. /// /// _This API requires the following crate features to be activated: `ssr`_ #[cfg(feature = "ssr")] diff --git a/maple-core/src/reactive.rs b/maple-core/src/reactive.rs index cb946701e..098e7e061 100644 --- a/maple-core/src/reactive.rs +++ b/maple-core/src/reactive.rs @@ -1,14 +1,15 @@ //! Reactive primitives. mod effect; -mod signal; mod motion; +mod signal; pub use effect::*; -pub use signal::*; pub use motion::*; +pub use signal::*; -/// Creates a new reactive root. Generally, you won't need this method as it is called automatically in [`render`](crate::render()). +/// Creates a new reactive root. Generally, you won't need this method as it is called automatically +/// in [`render`](crate::render()). /// /// # Example /// ``` diff --git a/maple-core/src/reactive/effect.rs b/maple-core/src/reactive/effect.rs index 6f4d9d8c4..2727f9ba3 100644 --- a/maple-core/src/reactive/effect.rs +++ b/maple-core/src/reactive/effect.rs @@ -169,7 +169,8 @@ pub fn create_effect_initial( CONTEXTS.with(|contexts| { let initial_context_size = contexts.borrow().len(); - // Upgrade running now to make sure running is valid for the whole duration of the effect. + // Upgrade running now to make sure running is valid for the whole duration of + // the effect. let running = running.upgrade().unwrap(); // Recreate effect dependencies each time effect is called. @@ -322,8 +323,8 @@ where } /// Creates a memoized value from some signals. Also know as "derived stores". -/// Unlike [`create_memo`], this function will not notify dependents of a change if the output is the same. -/// That is why the output of the function must implement [`PartialEq`]. +/// Unlike [`create_memo`], this function will not notify dependents of a change if the output is +/// the same. That is why the output of the function must implement [`PartialEq`]. /// /// To specify a custom comparison function, use [`create_selector_with`]. pub fn create_selector(derived: F) -> StateHandle @@ -335,7 +336,8 @@ where } /// Creates a memoized value from some signals. Also know as "derived stores". -/// Unlike [`create_memo`], this function will not notify dependents of a change if the output is the same. +/// Unlike [`create_memo`], this function will not notify dependents of a change if the output is +/// the same. /// /// It takes a comparison function to compare the old and new value, which returns `true` if they /// are the same and `false` otherwise. @@ -655,7 +657,8 @@ mod tests { assert_eq!(*double.get(), 2); state.set(2); - assert_eq!(*double.get(), 2); // double value should still be true because state.get() was inside untracked + assert_eq!(*double.get(), 2); // double value should still be true because state.get() was + // inside untracked } #[test] diff --git a/maple-core/src/reactive/motion.rs b/maple-core/src/reactive/motion.rs index b4072669d..a305a4819 100644 --- a/maple-core/src/reactive/motion.rs +++ b/maple-core/src/reactive/motion.rs @@ -94,7 +94,8 @@ impl Tweened { /// If the value is being interpolated already due to a previous call to `set()`, the previous /// task will be canceled. /// - /// To immediately set the value without interpolating the value, use `signal().set(...)` instead. + /// To immediately set the value without interpolating the value, use `signal().set(...)` + /// instead. pub fn set(&self, new_value: T) { let start = self.signal().get_untracked().as_ref().clone(); let easing_fn = Rc::clone(&self.0.borrow().easing_fn); diff --git a/maple-core/src/reactive/signal.rs b/maple-core/src/reactive/signal.rs index 66b71f89b..b92a708da 100644 --- a/maple-core/src/reactive/signal.rs +++ b/maple-core/src/reactive/signal.rs @@ -158,8 +158,9 @@ impl Signal { } /// Calls all the subscribers without modifying the state. - /// This can be useful when using patterns such as inner mutability where the state updated will not be automatically triggered. - /// In the general case, however, it is preferable to use [`Signal::set`] instead. + /// This can be useful when using patterns such as inner mutability where the state updated will + /// not be automatically triggered. In the general case, however, it is preferable to use + /// [`Signal::set`] instead. pub fn trigger_subscribers(&self) { // Clone subscribers to prevent modifying list when calling callbacks. let subscribers = self.handle.0.borrow().subscribers.clone(); @@ -241,7 +242,8 @@ impl SignalInner { self.subscribers.insert(handler); } - /// Removes a handler from the subscriber list. If the handler is not a subscriber, does nothing. + /// Removes a handler from the subscriber list. If the handler is not a subscriber, does + /// nothing. fn unsubscribe(&mut self, handler: &Callback) { self.subscribers.remove(handler); } diff --git a/maple-core/src/render.rs b/maple-core/src/render.rs index 58c84a6ef..07c063c6d 100644 --- a/maple-core/src/render.rs +++ b/maple-core/src/render.rs @@ -7,15 +7,18 @@ use crate::template_result::TemplateResult; /// Trait for describing how something should be rendered into DOM nodes. pub trait Render { - /// Called during the initial render when creating the DOM nodes. Should return a [`GenericNode`]. + /// Called during the initial render when creating the DOM nodes. Should return a + /// [`GenericNode`]. fn render(&self) -> G; /// Called when the node should be updated with new state. - /// The default implementation of this will replace the child node completely with the result of calling `render` again. - /// Another implementation might be better suited to some specific types. - /// For example, text nodes can simply replace the inner text instead of recreating a new node. + /// The default implementation of this will replace the child node completely with the result of + /// calling `render` again. Another implementation might be better suited to some specific + /// types. For example, text nodes can simply replace the inner text instead of recreating a + /// new node. /// - /// Returns the new node. If the node is reused instead of replaced, the returned node is simply the node passed in. + /// Returns the new node. If the node is reused instead of replaced, the returned node is simply + /// the node passed in. fn update_node(&self, parent: &G, node: &G) -> G { let new_node = self.render(); parent.replace_child(&new_node, &node); diff --git a/maple-core/src/template_result.rs b/maple-core/src/template_result.rs index 49f38fece..3e652deea 100644 --- a/maple-core/src/template_result.rs +++ b/maple-core/src/template_result.rs @@ -1,6 +1,13 @@ use crate::generic_node::GenericNode; -/// Result of the [`template`] macro. +/// Internal type for [`TemplateResult`]. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TemplateResultType { + Node(G), + Fragment(Vec), +} + +/// Result of the [`template`] macro. Should not be constructed manually. #[derive(Debug, Clone, PartialEq, Eq)] pub struct TemplateResult { node: G, diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 000000000..31c3d14f1 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,2 @@ +comment_width = 100 +wrap_comments = true From 3fc53a2fe9ad29783379ac39bd6d29354133dd52 Mon Sep 17 00:00:00 2001 From: Luke Chu <37006668+lukechu10@users.noreply.github.com> Date: Tue, 6 Apr 2021 14:24:51 -0700 Subject: [PATCH 04/15] Make TemplateResult able to hold a fragment --- maple-core-macro/src/lib.rs | 2 +- maple-core/src/flow.rs | 4 ++-- maple-core/src/template_result.rs | 39 ++++++++++++++++++++++++++----- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/maple-core-macro/src/lib.rs b/maple-core-macro/src/lib.rs index 3b616819a..a7f8f13bb 100644 --- a/maple-core-macro/src/lib.rs +++ b/maple-core-macro/src/lib.rs @@ -81,7 +81,7 @@ pub fn template(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as HtmlTree); let quoted = quote! { - ::maple_core::template_result::TemplateResult::new(#input) + ::maple_core::template_result::TemplateResult::new_node(#input) }; TokenStream::from(quoted) diff --git a/maple-core/src/flow.rs b/maple-core/src/flow.rs index 03cc9dfb8..10aad87d9 100644 --- a/maple-core/src/flow.rs +++ b/maple-core/src/flow.rs @@ -221,7 +221,7 @@ where } }); - TemplateResult::new(fragment) + TemplateResult::new_node(fragment) } /// Props for [`Indexed`]. @@ -338,5 +338,5 @@ where } }); - TemplateResult::new(fragment) + TemplateResult::new_node(fragment) } diff --git a/maple-core/src/template_result.rs b/maple-core/src/template_result.rs index 3e652deea..436d86d7c 100644 --- a/maple-core/src/template_result.rs +++ b/maple-core/src/template_result.rs @@ -2,7 +2,7 @@ use crate::generic_node::GenericNode; /// Internal type for [`TemplateResult`]. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum TemplateResultType { +pub enum TemplateResultInner { Node(G), Fragment(Vec), } @@ -10,21 +10,48 @@ pub enum TemplateResultType { /// Result of the [`template`] macro. Should not be constructed manually. #[derive(Debug, Clone, PartialEq, Eq)] pub struct TemplateResult { - node: G, + inner: TemplateResultInner, } impl TemplateResult { /// Create a new [`TemplateResult`] from a [`GenericNode`]. - pub fn new(node: G) -> Self { - Self { node } + pub fn new_node(node: G) -> Self { + Self { + inner: TemplateResultInner::Node(node), + } + } + + /// Create a new [`TemplateResult`] from a `Vec` of [`GenericNode`]s. + pub fn new_fragment(fragment: Vec) -> Self { + Self { + inner: TemplateResultInner::Fragment(fragment), + } } /// Create a new [`TemplateResult`] with a blank comment node pub fn empty() -> Self { - Self::new(G::marker()) + Self::new_node(G::marker()) } + #[deprecated] pub fn inner_node(&self) -> &G { - &self.node + match &self.inner { + TemplateResultInner::Node(node) => node, + TemplateResultInner::Fragment(fragment) => fragment.last().unwrap(), + } + } + + pub fn first_node(&self) -> &G { + match &self.inner { + TemplateResultInner::Node(node) => node, + TemplateResultInner::Fragment(fragment) => fragment.first().unwrap(), + } + } + + pub fn last_node(&self) -> &G { + match &self.inner { + TemplateResultInner::Node(node) => node, + TemplateResultInner::Fragment(fragment) => fragment.last().unwrap(), + } } } From 422a4344285e02cbcad1d38efc73b3b5ffd9e385 Mon Sep 17 00:00:00 2001 From: Luke Chu <37006668+lukechu10@users.noreply.github.com> Date: Tue, 6 Apr 2021 14:44:23 -0700 Subject: [PATCH 05/15] Iter and IntoIter for TemplateResult --- maple-core/src/template_result.rs | 37 +++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/maple-core/src/template_result.rs b/maple-core/src/template_result.rs index 436d86d7c..980d9215a 100644 --- a/maple-core/src/template_result.rs +++ b/maple-core/src/template_result.rs @@ -54,4 +54,41 @@ impl TemplateResult { TemplateResultInner::Fragment(fragment) => fragment.last().unwrap(), } } + + pub fn iter(&self) -> Iter { + match &self.inner { + TemplateResultInner::Node(node) => Iter::Node(Some(node).into_iter()), + TemplateResultInner::Fragment(fragment) => Iter::Fragment(fragment.iter()), + } + } +} + +impl IntoIterator for TemplateResult { + type Item = G; + + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + match self.inner { + TemplateResultInner::Node(node) => vec![node].into_iter(), + TemplateResultInner::Fragment(fragment) => fragment.into_iter(), + } + } +} + +/// An iterator over references of the nodes in [`TemplateResult`]. Created using [`TemplateResult::iter`]. +pub enum Iter<'a, G: GenericNode> { + Node(std::option::IntoIter<&'a G>), + Fragment(std::slice::Iter<'a, G>), +} + +impl<'a, G: GenericNode> Iterator for Iter<'a, G> { + type Item = &'a G; + + fn next(&mut self) -> Option { + match self { + Iter::Node(node) => node.next(), + Iter::Fragment(fragment) => fragment.next(), + } + } } From 9c6249050d7768ca702c38b88f090039f85e170d Mon Sep 17 00:00:00 2001 From: Luke Chu <37006668+lukechu10@users.noreply.github.com> Date: Tue, 6 Apr 2021 15:01:42 -0700 Subject: [PATCH 06/15] Update flow.rs --- maple-core/src/flow.rs | 95 ++++++++++++++++++++----------- maple-core/src/template_result.rs | 28 ++++++++- 2 files changed, 87 insertions(+), 36 deletions(-) diff --git a/maple-core/src/flow.rs b/maple-core/src/flow.rs index 10aad87d9..54369a05f 100644 --- a/maple-core/src/flow.rs +++ b/maple-core/src/flow.rs @@ -86,7 +86,9 @@ where if iterable.get().is_empty() { for (_, (owner, _value, template, _i)) in templates.borrow_mut().drain() { drop(owner); // destroy owner - template.inner_node().remove_self(); + for node in &template { + node.remove_self() + } } return; } @@ -103,9 +105,9 @@ where .map(|x| (x.0.clone(), (x.1 .2.clone(), x.1 .3))) .collect::>(); - for node in &excess_nodes { - let removed_index = node.1 .1; - templates.remove(&node.0); + for template in &excess_nodes { + let removed_index = template.1 .1; + templates.remove(&template.0); // Offset indexes of other templates by 1. for (_, _, _, i) in templates.values_mut() { @@ -115,8 +117,10 @@ where } } - for node in excess_nodes { - node.1 .0.inner_node().remove_self(); + for template in excess_nodes { + for node in template.1 .0 { + node.remove_self(); + } } } @@ -160,16 +164,19 @@ where if let Some(next_item) = iterable.get().get(i + 1) { let templates = templates.borrow(); - if let Some(next_node) = templates.get(&key_fn(next_item)) { - next_node - .2 - .inner_node() - .insert_sibling_before(new_template.unwrap().inner_node()); + if let Some(next_template) = templates.get(&key_fn(next_item)) { + for node in &new_template.unwrap() { + next_template.2.first_node().insert_sibling_before(node); + } } else { - marker.insert_sibling_before(new_template.unwrap().inner_node()); + for node in &new_template.unwrap() { + marker.insert_sibling_before(node); + } } } else { - marker.insert_sibling_before(new_template.unwrap().inner_node()); + for node in &new_template.unwrap() { + marker.insert_sibling_before(node); + } } } else if match previous_value { Some(prev) => prev.index, @@ -179,15 +186,21 @@ where // Location changed, move from old location to new location // Node was moved in the DOM. Move node to new index. - let node = templates.borrow().get(&key).unwrap().2.inner_node().clone(); - - if let Some(next_item) = iterable.get().get(i + 1) { + { let templates = templates.borrow(); - let next_node = templates.get(&key_fn(next_item)).unwrap(); - next_node.2.inner_node().insert_sibling_before(&node); // Move to before - // next node - } else { - marker.insert_sibling_before(&node); // Move to end. + let template = &templates.get(&key).unwrap().2; + + if let Some(next_item) = iterable.get().get(i + 1) { + let next_node = templates.get(&key_fn(next_item)).unwrap(); + for node in template { + // Move to before next node. + next_node.2.first_node().insert_sibling_before(node); + } + } else { + for node in template { + marker.insert_sibling_before(node); // Move to end. + } + } } templates.borrow_mut().get_mut(&key).unwrap().3 = i; @@ -209,13 +222,19 @@ where let mut new_template = None; let owner = create_root(|| new_template = Some(template(item.clone()))); - let (_, _, old_node, _) = mem::replace( + let (_, _, old_template, _) = mem::replace( templates.get_mut(&key).unwrap(), (owner, item.clone(), new_template.clone().unwrap(), i), ); - let parent = old_node.inner_node().parent_node().unwrap(); - parent.replace_child(new_template.unwrap().inner_node(), old_node.inner_node()); + let parent = old_template.first_node().parent_node().unwrap(); + + for new_node in &new_template.unwrap() { + parent.insert_child_before(new_node, Some(old_template.first_node())); + } + for old_node in &old_template { + parent.remove_child(old_node); + } } } } @@ -279,7 +298,9 @@ where if props.iterable.get().is_empty() { for (owner, template) in templates.borrow_mut().drain(..) { drop(owner); // destroy owner - template.inner_node().remove_self(); + for node in template { + node.remove_self(); + } } return; } @@ -303,16 +324,18 @@ where let owner = create_root(|| new_template = Some((props.template)(item.clone()))); if templates.borrow().get(i).is_some() { - let old_node = mem::replace( + let old_template = mem::replace( &mut templates.borrow_mut()[i], (owner, new_template.as_ref().unwrap().clone()), ); - let parent = old_node.1.inner_node().parent_node().unwrap(); - parent.replace_child( - new_template.unwrap().inner_node(), - old_node.1.inner_node(), - ); + let parent = old_template.1.first_node().parent_node().unwrap(); + for node in &new_template.unwrap() { + parent.insert_child_before(node, Some(old_template.1.first_node())); + } + for old_node in &old_template.1 { + parent.remove_child(old_node); + } } else { debug_assert!(templates.borrow().len() == i, "pushing new value scenario"); @@ -320,7 +343,9 @@ where .borrow_mut() .push((owner, new_template.as_ref().unwrap().clone())); - marker.insert_sibling_before(&new_template.unwrap().inner_node()); + for node in &new_template.unwrap() { + marker.insert_sibling_before(node); + } } } } @@ -329,8 +354,10 @@ where let mut templates = templates.borrow_mut(); let excess_nodes = templates.drain(props.iterable.get().len()..); - for node in excess_nodes { - node.1.inner_node().remove_self(); + for template in excess_nodes { + for node in &template.1 { + node.remove_self(); + } } } diff --git a/maple-core/src/template_result.rs b/maple-core/src/template_result.rs index 980d9215a..723d180b2 100644 --- a/maple-core/src/template_result.rs +++ b/maple-core/src/template_result.rs @@ -41,17 +41,31 @@ impl TemplateResult { } } + /// Gets the first node in the [`TemplateResult`]. + /// + /// # Panics + /// + /// Panics if the fragment has no child nodes. pub fn first_node(&self) -> &G { match &self.inner { TemplateResultInner::Node(node) => node, - TemplateResultInner::Fragment(fragment) => fragment.first().unwrap(), + TemplateResultInner::Fragment(fragment) => { + fragment.first().expect("fragment has no child nodes") + } } } + /// Gets the last node in the [`TemplateResult`]. + /// + /// # Panics + /// + /// Panics if the fragment has no child nodes. pub fn last_node(&self) -> &G { match &self.inner { TemplateResultInner::Node(node) => node, - TemplateResultInner::Fragment(fragment) => fragment.last().unwrap(), + TemplateResultInner::Fragment(fragment) => { + fragment.last().expect("fragment has no child nodes") + } } } @@ -92,3 +106,13 @@ impl<'a, G: GenericNode> Iterator for Iter<'a, G> { } } } + +impl<'a, G: GenericNode> IntoIterator for &'a TemplateResult { + type Item = &'a G; + + type IntoIter = Iter<'a, G>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} From 7cb254056eeec50c05ba8ca123c1487dc27a7c07 Mon Sep 17 00:00:00 2001 From: Luke Chu <37006668+lukechu10@users.noreply.github.com> Date: Tue, 6 Apr 2021 15:08:18 -0700 Subject: [PATCH 07/15] Update render_* functions --- maple-core/src/lib.rs | 17 ++++++++++------- maple-core/src/template_result.rs | 5 +++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/maple-core/src/lib.rs b/maple-core/src/lib.rs index 0d607d356..7d2f86d9f 100644 --- a/maple-core/src/lib.rs +++ b/maple-core/src/lib.rs @@ -54,9 +54,9 @@ pub fn render_to( parent: &web_sys::Node, ) { let owner = reactive::create_root(|| { - parent - .append_child(&template_result().inner_node().inner_element()) - .unwrap(); + for node in template_result() { + parent.append_child(&node.inner_element()).unwrap(); + } }); thread_local! { @@ -74,11 +74,14 @@ pub fn render_to( pub fn render_to_string( template_result: impl FnOnce() -> template_result::TemplateResult, ) -> String { - let mut ret = None; - let _owner = - reactive::create_root(|| ret = Some(format!("{}", template_result().inner_node()))); + let mut ret = String::new(); + let _owner = reactive::create_root(|| { + for node in template_result() { + ret.push_str(&format!("{}", node)); + } + }); - ret.unwrap() + ret } /// The maple prelude. diff --git a/maple-core/src/template_result.rs b/maple-core/src/template_result.rs index 723d180b2..61be3ad63 100644 --- a/maple-core/src/template_result.rs +++ b/maple-core/src/template_result.rs @@ -23,6 +23,11 @@ impl TemplateResult { /// Create a new [`TemplateResult`] from a `Vec` of [`GenericNode`]s. pub fn new_fragment(fragment: Vec) -> Self { + debug_assert!( + !fragment.is_empty(), + "fragment must have at least 1 child node, use empty() instead" + ); + Self { inner: TemplateResultInner::Fragment(fragment), } From 437df19870af8c714ab6f5240fb02cadc14b3c8d Mon Sep 17 00:00:00 2001 From: Luke Chu <37006668+lukechu10@users.noreply.github.com> Date: Tue, 6 Apr 2021 15:15:41 -0700 Subject: [PATCH 08/15] Update Render trait --- maple-core/src/generic_node.rs | 6 ++++-- maple-core/src/render.rs | 30 ++++++++++++++++++------------ 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/maple-core/src/generic_node.rs b/maple-core/src/generic_node.rs index adad7090f..77b6a881e 100644 --- a/maple-core/src/generic_node.rs +++ b/maple-core/src/generic_node.rs @@ -97,7 +97,7 @@ pub trait GenericNode: fmt::Debug + Clone + PartialEq + Eq + 'static { fn append_render(&self, child: Box Box>>) { let parent = self.clone(); - let node = create_effect_initial(cloned!((parent) => move || { + let nodes = create_effect_initial(cloned!((parent) => move || { let node = RefCell::new(child().render()); let effect = cloned!((node) => move || { @@ -108,6 +108,8 @@ pub trait GenericNode: fmt::Debug + Clone + PartialEq + Eq + 'static { (Rc::new(effect), node) })); - parent.append_child(&node.borrow()); + for node in nodes.borrow().iter() { + parent.append_child(node); + } } } diff --git a/maple-core/src/render.rs b/maple-core/src/render.rs index 07c063c6d..22ccf4168 100644 --- a/maple-core/src/render.rs +++ b/maple-core/src/render.rs @@ -8,8 +8,8 @@ use crate::template_result::TemplateResult; /// Trait for describing how something should be rendered into DOM nodes. pub trait Render { /// Called during the initial render when creating the DOM nodes. Should return a - /// [`GenericNode`]. - fn render(&self) -> G; + /// `Vec` of [`GenericNode`]s. + fn render(&self) -> Vec; /// Called when the node should be updated with new state. /// The default implementation of this will replace the child node completely with the result of @@ -19,29 +19,35 @@ pub trait Render { /// /// Returns the new node. If the node is reused instead of replaced, the returned node is simply /// the node passed in. - fn update_node(&self, parent: &G, node: &G) -> G { - let new_node = self.render(); - parent.replace_child(&new_node, &node); - new_node + fn update_node(&self, parent: &G, node: &Vec) -> Vec { + let new_nodes = self.render(); + + for new_node in &new_nodes { + parent.replace_child(new_node, node.first().unwrap()); + } + + new_nodes } } impl Render for T { - fn render(&self) -> G { - G::text_node(&format!("{}", self)) + fn render(&self) -> Vec { + vec![G::text_node(&format!("{}", self))] } - fn update_node(&self, _parent: &G, node: &G) -> G { + fn update_node(&self, _parent: &G, node: &Vec) -> Vec { // replace `textContent` of `node` instead of recreating - node.update_inner_text(&format!("{}", self)); + node.first() + .unwrap() + .update_inner_text(&format!("{}", self)); node.clone() } } impl Render for TemplateResult { - fn render(&self) -> G { - self.inner_node().clone() + fn render(&self) -> Vec { + self.into_iter().cloned().collect() } } From 2a71204e525bc20fd137470a5701d91e71d15de5 Mon Sep 17 00:00:00 2001 From: Luke Chu <37006668+lukechu10@users.noreply.github.com> Date: Tue, 6 Apr 2021 15:19:04 -0700 Subject: [PATCH 09/15] Make Render accept slice --- maple-core/src/render.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/maple-core/src/render.rs b/maple-core/src/render.rs index 22ccf4168..3d1f5b881 100644 --- a/maple-core/src/render.rs +++ b/maple-core/src/render.rs @@ -19,7 +19,7 @@ pub trait Render { /// /// Returns the new node. If the node is reused instead of replaced, the returned node is simply /// the node passed in. - fn update_node(&self, parent: &G, node: &Vec) -> Vec { + fn update_node<'a>(&self, parent: &G, node: &'a [G]) -> Vec { let new_nodes = self.render(); for new_node in &new_nodes { @@ -35,14 +35,14 @@ impl Render for T { vec![G::text_node(&format!("{}", self))] } - fn update_node(&self, _parent: &G, node: &Vec) -> Vec { + fn update_node<'a>(&self, _parent: &G, node: &'a [G]) -> Vec { // replace `textContent` of `node` instead of recreating node.first() .unwrap() .update_inner_text(&format!("{}", self)); - node.clone() + node.to_vec() } } From 9c19d9cb2adbcd80faff72577de0d1657f602e1d Mon Sep 17 00:00:00 2001 From: Luke Chu <37006668+lukechu10@users.noreply.github.com> Date: Tue, 6 Apr 2021 15:25:52 -0700 Subject: [PATCH 10/15] Update template! macro --- maple-core-macro/src/component.rs | 2 +- maple-core-macro/src/element.rs | 4 +++- maple-core-macro/src/lib.rs | 14 ++++++++++++-- maple-core/src/template_result.rs | 8 -------- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/maple-core-macro/src/component.rs b/maple-core-macro/src/component.rs index d2db0fd35..f460fcd96 100644 --- a/maple-core-macro/src/component.rs +++ b/maple-core-macro/src/component.rs @@ -33,7 +33,7 @@ impl ToTokens for Component { let quoted = quote! { ::maple_core::reactive::untrack(|| - ::std::clone::Clone::clone(::maple_core::template_result::TemplateResult::inner_node(&#path(#args))) + #path(#args) ) }; diff --git a/maple-core-macro/src/element.rs b/maple-core-macro/src/element.rs index 9eec2f330..00909c9a0 100644 --- a/maple-core-macro/src/element.rs +++ b/maple-core-macro/src/element.rs @@ -100,7 +100,9 @@ impl ToTokens for Element { for child in &children.body { let quoted = match child { HtmlTree::Component(component) => quote_spanned! { component.span()=> - ::maple_core::generic_node::GenericNode::append_child(&element, &#component); + for node in &#component { + ::maple_core::generic_node::GenericNode::append_child(&element, node); + } }, HtmlTree::Element(element) => quote_spanned! { element.span()=> ::maple_core::generic_node::GenericNode::append_child(&element, &#element); diff --git a/maple-core-macro/src/lib.rs b/maple-core-macro/src/lib.rs index a7f8f13bb..0d8bef185 100644 --- a/maple-core-macro/src/lib.rs +++ b/maple-core-macro/src/lib.rs @@ -80,8 +80,18 @@ impl ToTokens for HtmlTree { pub fn template(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as HtmlTree); - let quoted = quote! { - ::maple_core::template_result::TemplateResult::new_node(#input) + let quoted = match input { + HtmlTree::Component(component) => quote! { + #component + }, + HtmlTree::Element(element) => quote! { + ::maple_core::template_result::TemplateResult::new_node(#element) + }, + HtmlTree::Text(text) => quote! { + ::maple_core::template_result::TemplateResult::new_node( + ::maple_core::generic_node::GenericNode::text(#text), + ) + }, }; TokenStream::from(quoted) diff --git a/maple-core/src/template_result.rs b/maple-core/src/template_result.rs index 61be3ad63..611db872e 100644 --- a/maple-core/src/template_result.rs +++ b/maple-core/src/template_result.rs @@ -38,14 +38,6 @@ impl TemplateResult { Self::new_node(G::marker()) } - #[deprecated] - pub fn inner_node(&self) -> &G { - match &self.inner { - TemplateResultInner::Node(node) => node, - TemplateResultInner::Fragment(fragment) => fragment.last().unwrap(), - } - } - /// Gets the first node in the [`TemplateResult`]. /// /// # Panics From 89190aa73aaa3de27d3aa2ab3fef17c5872d7403 Mon Sep 17 00:00:00 2001 From: Luke Chu <37006668+lukechu10@users.noreply.github.com> Date: Tue, 6 Apr 2021 15:43:44 -0700 Subject: [PATCH 11/15] Fix template! --- maple-core-macro/src/lib.rs | 11 +++++++---- maple-core-macro/tests/ui/root-pass.rs | 10 ++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 maple-core-macro/tests/ui/root-pass.rs diff --git a/maple-core-macro/src/lib.rs b/maple-core-macro/src/lib.rs index 0d8bef185..b6d41e0d2 100644 --- a/maple-core-macro/src/lib.rs +++ b/maple-core-macro/src/lib.rs @@ -87,10 +87,13 @@ pub fn template(input: TokenStream) -> TokenStream { HtmlTree::Element(element) => quote! { ::maple_core::template_result::TemplateResult::new_node(#element) }, - HtmlTree::Text(text) => quote! { - ::maple_core::template_result::TemplateResult::new_node( - ::maple_core::generic_node::GenericNode::text(#text), - ) + HtmlTree::Text(text) => match text { + text::Text::Text(_) => quote! { + ::maple_core::template_result::TemplateResult::new_node( + ::maple_core::generic_node::GenericNode::text_node(#text), + ) + }, + text::Text::Splice(_, _) => unimplemented!("splice at top level is not supported"), }, }; diff --git a/maple-core-macro/tests/ui/root-pass.rs b/maple-core-macro/tests/ui/root-pass.rs new file mode 100644 index 000000000..b3a37dd7f --- /dev/null +++ b/maple-core-macro/tests/ui/root-pass.rs @@ -0,0 +1,10 @@ +use maple_core::prelude::*; + +fn compile_pass() { + let _: TemplateResult = template! { "Raw text nodes!" }; + + // let spliced = 123; + // let _: TemplateResult = template! { (spliced) }; +} + +fn main() {} From 564f4e4f27120977dedd9df8072003d64f9f9936 Mon Sep 17 00:00:00 2001 From: Luke Chu <37006668+lukechu10@users.noreply.github.com> Date: Tue, 6 Apr 2021 16:00:10 -0700 Subject: [PATCH 12/15] Allow multiple children at template! root --- maple-core-macro/src/lib.rs | 80 +++++++++++++------ maple-core-macro/tests/ui/element-fail.stderr | 2 +- maple-core-macro/tests/ui/root-pass.rs | 6 ++ 3 files changed, 64 insertions(+), 24 deletions(-) diff --git a/maple-core-macro/src/lib.rs b/maple-core-macro/src/lib.rs index b6d41e0d2..a242ac841 100644 --- a/maple-core-macro/src/lib.rs +++ b/maple-core-macro/src/lib.rs @@ -65,11 +65,62 @@ impl Parse for HtmlTree { impl ToTokens for HtmlTree { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - match self { - Self::Component(component) => component.to_tokens(tokens), - Self::Element(element) => element.to_tokens(tokens), - Self::Text(text) => text.to_tokens(tokens), + let quoted = match self { + Self::Component(component) => quote! { + #component + }, + Self::Element(element) => quote! { + ::maple_core::template_result::TemplateResult::new_node(#element) + }, + Self::Text(text) => match text { + text::Text::Text(_) => quote! { + ::maple_core::template_result::TemplateResult::new_node( + ::maple_core::generic_node::GenericNode::text_node(#text), + ) + }, + text::Text::Splice(_, _) => unimplemented!("splice at top level is not supported"), + }, + }; + + tokens.extend(quoted); + } +} + +pub(crate) struct HtmlRoot { + children: Vec, +} + +impl Parse for HtmlRoot { + fn parse(input: ParseStream) -> Result { + let mut children = Vec::new(); + + while !input.is_empty() { + children.push(input.parse()?); } + + Ok(Self { children }) + } +} + +impl ToTokens for HtmlRoot { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let quoted = match self.children.as_slice() { + [] => quote! { + ::maple_core::template_result::TemplateResult::empty() + }, + [node] => node.to_token_stream(), + nodes => quote! { + ::maple_core::template_result::TemplateResult::new_fragment({ + let mut children = ::std::vec::Vec::new(); + #( for node in #nodes { + children.push(node); + } )* + children + }) + }, + }; + + tokens.extend(quoted); } } @@ -78,24 +129,7 @@ impl ToTokens for HtmlTree { /// TODO: write some more docs #[proc_macro] pub fn template(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as HtmlTree); - - let quoted = match input { - HtmlTree::Component(component) => quote! { - #component - }, - HtmlTree::Element(element) => quote! { - ::maple_core::template_result::TemplateResult::new_node(#element) - }, - HtmlTree::Text(text) => match text { - text::Text::Text(_) => quote! { - ::maple_core::template_result::TemplateResult::new_node( - ::maple_core::generic_node::GenericNode::text_node(#text), - ) - }, - text::Text::Splice(_, _) => unimplemented!("splice at top level is not supported"), - }, - }; + let input = parse_macro_input!(input as HtmlRoot); - TokenStream::from(quoted) + TokenStream::from(input.to_token_stream()) } diff --git a/maple-core-macro/tests/ui/element-fail.stderr b/maple-core-macro/tests/ui/element-fail.stderr index a43db5315..d74329d68 100644 --- a/maple-core-macro/tests/ui/element-fail.stderr +++ b/maple-core-macro/tests/ui/element-fail.stderr @@ -1,4 +1,4 @@ -error: unexpected token +error: expected a valid HTML node --> $DIR/element-fail.rs:4:45 | 4 | let _: TemplateResult = template! { p.my-class#id }; diff --git a/maple-core-macro/tests/ui/root-pass.rs b/maple-core-macro/tests/ui/root-pass.rs index b3a37dd7f..d138fefad 100644 --- a/maple-core-macro/tests/ui/root-pass.rs +++ b/maple-core-macro/tests/ui/root-pass.rs @@ -3,6 +3,12 @@ use maple_core::prelude::*; fn compile_pass() { let _: TemplateResult = template! { "Raw text nodes!" }; + let _: TemplateResult = template! { + p { "First" } + p { "Second" } + "Third" + }; + // let spliced = 123; // let _: TemplateResult = template! { (spliced) }; } From d355fdcc7b61b2c57912576aa444536a2ea47197 Mon Sep 17 00:00:00 2001 From: Luke Chu <37006668+lukechu10@users.noreply.github.com> Date: Tue, 6 Apr 2021 16:06:51 -0700 Subject: [PATCH 13/15] Add some integration tests --- maple-core/tests/integration/keyed.rs | 31 +++++++++++++++++++++ maple-core/tests/integration/main.rs | 18 +++++++++++++ maple-core/tests/integration/non_keyed.rs | 33 +++++++++++++++++++++++ 3 files changed, 82 insertions(+) diff --git a/maple-core/tests/integration/keyed.rs b/maple-core/tests/integration/keyed.rs index d8329ce7e..6287d8d42 100644 --- a/maple-core/tests/integration/keyed.rs +++ b/maple-core/tests/integration/keyed.rs @@ -183,3 +183,34 @@ fn nested_reactivity() { }); assert_eq!(p.text_content().unwrap(), "4235"); } + +#[wasm_bindgen_test] +fn template_top_level() { + let count = Signal::new(vec![1, 2]); + + let node = cloned!((count) => template! { + Keyed(KeyedProps { + iterable: count.handle(), + template: |item| template! { + li { (item) } + }, + key: |item| *item, + }) + }); + + render_to(|| node, &test_div()); + + let p = document().query_selector("#test-container").unwrap().unwrap(); + + assert_eq!(p.text_content().unwrap(), "12"); + + count.set({ + let mut tmp = (*count.get()).clone(); + tmp.push(3); + tmp + }); + assert_eq!(p.text_content().unwrap(), "123"); + + count.set(count.get()[1..].into()); + assert_eq!(p.text_content().unwrap(), "23"); +} diff --git a/maple-core/tests/integration/main.rs b/maple-core/tests/integration/main.rs index 9c8112282..90a79cd39 100644 --- a/maple-core/tests/integration/main.rs +++ b/maple-core/tests/integration/main.rs @@ -151,3 +151,21 @@ fn noderefs() { noderef.get::().unchecked_into() ); } + +#[wasm_bindgen_test] +fn fragments() { + let node = template! { + p { "1" } + p { "2" } + p { "3" } + }; + + render_to(|| node, &test_div()); + + let test_container = document().query_selector("#test-container").unwrap().unwrap(); + + assert_eq!( + test_container.text_content().unwrap(), + "123" + ); +} diff --git a/maple-core/tests/integration/non_keyed.rs b/maple-core/tests/integration/non_keyed.rs index d96481bdc..ae6c64f07 100644 --- a/maple-core/tests/integration/non_keyed.rs +++ b/maple-core/tests/integration/non_keyed.rs @@ -177,3 +177,36 @@ fn nested_reactivity() { }); assert_eq!(p.text_content().unwrap(), "4235"); } + +#[wasm_bindgen_test] +fn template_top_level() { + let count = Signal::new(vec![1, 2]); + + let node = cloned!((count) => template! { + Indexed(IndexedProps { + iterable: count.handle(), + template: |item| template! { + li { (item) } + }, + }) + }); + + render_to(|| node, &test_div()); + + let p = document() + .query_selector("#test-container") + .unwrap() + .unwrap(); + + assert_eq!(p.text_content().unwrap(), "12"); + + count.set({ + let mut tmp = (*count.get()).clone(); + tmp.push(3); + tmp + }); + assert_eq!(p.text_content().unwrap(), "123"); + + count.set(count.get()[1..].into()); + assert_eq!(p.text_content().unwrap(), "23"); +} From 414bdbe0d7f9d0034a1e1ea303649b3fef91a315 Mon Sep 17 00:00:00 2001 From: Luke Chu <37006668+lukechu10@users.noreply.github.com> Date: Tue, 6 Apr 2021 16:15:52 -0700 Subject: [PATCH 14/15] Add some more integration tests --- maple-core/tests/integration/keyed.rs | 72 ++++++++++++++++++++--- maple-core/tests/integration/main.rs | 22 +++---- maple-core/tests/integration/non_keyed.rs | 66 ++++++++++++++++++--- 3 files changed, 134 insertions(+), 26 deletions(-) diff --git a/maple-core/tests/integration/keyed.rs b/maple-core/tests/integration/keyed.rs index 6287d8d42..ca34d530d 100644 --- a/maple-core/tests/integration/keyed.rs +++ b/maple-core/tests/integration/keyed.rs @@ -16,7 +16,7 @@ fn append() { } }); - render_to(|| node, &test_div()); + render_to(|| node, &test_container()); let p = document().query_selector("ul").unwrap().unwrap(); @@ -49,7 +49,7 @@ fn swap_rows() { } }); - render_to(|| node, &test_div()); + render_to(|| node, &test_container()); let p = document().query_selector("ul").unwrap().unwrap(); assert_eq!(p.text_content().unwrap(), "123"); @@ -85,7 +85,7 @@ fn delete_row() { } }); - render_to(|| node, &test_div()); + render_to(|| node, &test_container()); let p = document().query_selector("ul").unwrap().unwrap(); assert_eq!(p.text_content().unwrap(), "123"); @@ -114,7 +114,7 @@ fn clear() { } }); - render_to(|| node, &test_div()); + render_to(|| node, &test_container()); let p = document().query_selector("ul").unwrap().unwrap(); assert_eq!(p.text_content().unwrap(), "123"); @@ -139,7 +139,7 @@ fn insert_front() { } }); - render_to(|| node, &test_div()); + render_to(|| node, &test_container()); let p = document().query_selector("ul").unwrap().unwrap(); assert_eq!(p.text_content().unwrap(), "123"); @@ -168,7 +168,7 @@ fn nested_reactivity() { } }); - render_to(|| node, &test_div()); + render_to(|| node, &test_container()); let p = document().query_selector("ul").unwrap().unwrap(); assert_eq!(p.text_content().unwrap(), "123"); @@ -184,6 +184,59 @@ fn nested_reactivity() { assert_eq!(p.text_content().unwrap(), "4235"); } +#[wasm_bindgen_test] +fn fragment_template() { + let count = Signal::new(vec![1, 2]); + + let node = cloned!((count) => template! { + div { + Keyed(KeyedProps { + iterable: count.handle(), + template: |item| template! { + span { "The value is: " } + strong { (item) } + }, + key: |item| *item, + }) + } + }); + + render_to(|| node, &test_container()); + + let p = document().query_selector("div").unwrap().unwrap(); + + assert_eq!( + p.inner_html(), + "\ +The value is: 1\ +The value is: 2\ +" + ); + + count.set({ + let mut tmp = (*count.get()).clone(); + tmp.push(3); + tmp + }); + assert_eq!( + p.inner_html(), + "\ +The value is: 1\ +The value is: 2\ +The value is: 3\ +" + ); + + count.set(count.get()[1..].into()); + assert_eq!( + p.inner_html(), + "\ +The value is: 2\ +The value is: 3\ +" + ); +} + #[wasm_bindgen_test] fn template_top_level() { let count = Signal::new(vec![1, 2]); @@ -198,9 +251,12 @@ fn template_top_level() { }) }); - render_to(|| node, &test_div()); + render_to(|| node, &test_container()); - let p = document().query_selector("#test-container").unwrap().unwrap(); + let p = document() + .query_selector("#test-container") + .unwrap() + .unwrap(); assert_eq!(p.text_content().unwrap(), "12"); diff --git a/maple-core/tests/integration/main.rs b/maple-core/tests/integration/main.rs index 90a79cd39..dd206a03f 100644 --- a/maple-core/tests/integration/main.rs +++ b/maple-core/tests/integration/main.rs @@ -16,21 +16,21 @@ fn document() -> Document { } /// Returns a [`Node`] referencing the test container with the contents cleared. -fn test_div() -> Node { +fn test_container() -> Node { if document() - .query_selector("div#test-container") + .query_selector("test-container#test-container") .unwrap() .is_none() { document() .body() .unwrap() - .insert_adjacent_html("beforeend", r#"
"#) + .insert_adjacent_html("beforeend", r#""#) .unwrap(); } let container = document() - .query_selector("div#test-container") + .query_selector("test-container#test-container") .unwrap() .unwrap(); @@ -45,7 +45,7 @@ fn hello_world() { p { "Hello World!" } }; - render_to(|| node, &test_div()); + render_to(|| node, &test_container()); assert_eq!( &document() @@ -65,7 +65,7 @@ fn hello_world_noderef() { p(ref=p_ref) { "Hello World!" } }; - render_to(|| node, &test_div()); + render_to(|| node, &test_container()); assert_eq!( &p_ref @@ -83,7 +83,7 @@ fn interpolation() { p { (text) } }; - render_to(|| node, &test_div()); + render_to(|| node, &test_container()); assert_eq!( document() @@ -104,7 +104,7 @@ fn reactive_text() { p { (count.get()) } }); - render_to(|| node, &test_div()); + render_to(|| node, &test_container()); let p = document().query_selector("p").unwrap().unwrap(); @@ -122,7 +122,7 @@ fn reactive_attribute() { span(attribute=count.get()) }); - render_to(|| node, &test_div()); + render_to(|| node, &test_container()); let span = document().query_selector("span").unwrap().unwrap(); @@ -142,7 +142,7 @@ fn noderefs() { } }; - render_to(|| node, &test_div()); + render_to(|| node, &test_container()); let input_ref = document().query_selector("input").unwrap().unwrap(); @@ -160,7 +160,7 @@ fn fragments() { p { "3" } }; - render_to(|| node, &test_div()); + render_to(|| node, &test_container()); let test_container = document().query_selector("#test-container").unwrap().unwrap(); diff --git a/maple-core/tests/integration/non_keyed.rs b/maple-core/tests/integration/non_keyed.rs index ae6c64f07..c1d309e33 100644 --- a/maple-core/tests/integration/non_keyed.rs +++ b/maple-core/tests/integration/non_keyed.rs @@ -15,7 +15,7 @@ fn append() { } }); - render_to(|| node, &test_div()); + render_to(|| node, &test_container()); let p = document().query_selector("ul").unwrap().unwrap(); @@ -47,7 +47,7 @@ fn swap_rows() { } }); - render_to(|| node, &test_div()); + render_to(|| node, &test_container()); let p = document().query_selector("ul").unwrap().unwrap(); assert_eq!(p.text_content().unwrap(), "123"); @@ -82,7 +82,7 @@ fn delete_row() { } }); - render_to(|| node, &test_div()); + render_to(|| node, &test_container()); let p = document().query_selector("ul").unwrap().unwrap(); assert_eq!(p.text_content().unwrap(), "123"); @@ -110,7 +110,7 @@ fn clear() { } }); - render_to(|| node, &test_div()); + render_to(|| node, &test_container()); let p = document().query_selector("ul").unwrap().unwrap(); assert_eq!(p.text_content().unwrap(), "123"); @@ -134,7 +134,7 @@ fn insert_front() { } }); - render_to(|| node, &test_div()); + render_to(|| node, &test_container()); let p = document().query_selector("ul").unwrap().unwrap(); assert_eq!(p.text_content().unwrap(), "123"); @@ -162,7 +162,7 @@ fn nested_reactivity() { } }); - render_to(|| node, &test_div()); + render_to(|| node, &test_container()); let p = document().query_selector("ul").unwrap().unwrap(); assert_eq!(p.text_content().unwrap(), "123"); @@ -178,6 +178,58 @@ fn nested_reactivity() { assert_eq!(p.text_content().unwrap(), "4235"); } +#[wasm_bindgen_test] +fn fragment_template() { + let count = Signal::new(vec![1, 2]); + + let node = cloned!((count) => template! { + div { + Indexed(IndexedProps { + iterable: count.handle(), + template: |item| template! { + span { "The value is: " } + strong { (item) } + }, + }) + } + }); + + render_to(|| node, &test_container()); + + let p = document().query_selector("div").unwrap().unwrap(); + + assert_eq!( + p.inner_html(), + "\ +The value is: 1\ +The value is: 2\ +" + ); + + count.set({ + let mut tmp = (*count.get()).clone(); + tmp.push(3); + tmp + }); + assert_eq!( + p.inner_html(), + "\ +The value is: 1\ +The value is: 2\ +The value is: 3\ +" + ); + + count.set(count.get()[1..].into()); + assert_eq!( + p.inner_html(), + "\ +The value is: 2\ +The value is: 3\ +" + ); +} + #[wasm_bindgen_test] fn template_top_level() { let count = Signal::new(vec![1, 2]); @@ -191,7 +243,7 @@ fn template_top_level() { }) }); - render_to(|| node, &test_div()); + render_to(|| node, &test_container()); let p = document() .query_selector("#test-container") From 3f21d39b3b01239b7ae06df5fd25b5a44cbce34f Mon Sep 17 00:00:00 2001 From: Luke Chu <37006668+lukechu10@users.noreply.github.com> Date: Tue, 6 Apr 2021 16:19:31 -0700 Subject: [PATCH 15/15] Add more docs --- docs/markdown/basics/template.md | 15 ++++++++++++++ maple-core/tests/integration/main.rs | 31 ++++++++++++++++++++++------ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/docs/markdown/basics/template.md b/docs/markdown/basics/template.md index 79ebe97dd..c052f4c89 100644 --- a/docs/markdown/basics/template.md +++ b/docs/markdown/basics/template.md @@ -38,3 +38,18 @@ template! { } } ``` + +Templates can also be fragments. + +```rust +template! { + p { "First child" } + p { "Second child" } +} +``` + +Or be empty. + +```rust +template! {} +``` diff --git a/maple-core/tests/integration/main.rs b/maple-core/tests/integration/main.rs index dd206a03f..d21e41bbb 100644 --- a/maple-core/tests/integration/main.rs +++ b/maple-core/tests/integration/main.rs @@ -25,7 +25,10 @@ fn test_container() -> Node { document() .body() .unwrap() - .insert_adjacent_html("beforeend", r#""#) + .insert_adjacent_html( + "beforeend", + r#""#, + ) .unwrap(); } @@ -39,6 +42,22 @@ fn test_container() -> Node { container.into() } +#[wasm_bindgen_test] +fn empty_template() { + let node = template! {}; + + render_to(|| node, &test_container()); + + assert_eq!( + document() + .query_selector("#test-container") + .unwrap() + .unwrap() + .inner_html(), + "" + ); +} + #[wasm_bindgen_test] fn hello_world() { let node = template! { @@ -162,10 +181,10 @@ fn fragments() { render_to(|| node, &test_container()); - let test_container = document().query_selector("#test-container").unwrap().unwrap(); + let test_container = document() + .query_selector("#test-container") + .unwrap() + .unwrap(); - assert_eq!( - test_container.text_content().unwrap(), - "123" - ); + assert_eq!(test_container.text_content().unwrap(), "123"); }