|
@@ -1,1006 +0,0 @@
|
|
-mod old {
|
|
|
|
-
|
|
|
|
- // #![feature(type_alias_impl_trait)]
|
|
|
|
- //
|
|
|
|
- use std::future::Future;
|
|
|
|
-
|
|
|
|
- trait Props {}
|
|
|
|
- struct Context<T: Props> {
|
|
|
|
- _props: std::marker::PhantomData<T>,
|
|
|
|
- }
|
|
|
|
- struct VNode {}
|
|
|
|
-
|
|
|
|
- // type FC<T: Props> = fn(&mut Context<T>) -> VNode;
|
|
|
|
- // type FC<T: Props> = fn(&mut Context<T>) -> Box<dyn Future<Output = VNode>>;
|
|
|
|
-
|
|
|
|
- impl Props for () {}
|
|
|
|
-
|
|
|
|
- // async fn some_component(g: &mut Context<()>) -> VNode {
|
|
|
|
- // rsx! {
|
|
|
|
- // <div>
|
|
|
|
-
|
|
|
|
- // </div>
|
|
|
|
- // }
|
|
|
|
- // }
|
|
|
|
- // Absolve ourselves of any type data about the context itself
|
|
|
|
- trait ContextApplier {
|
|
|
|
- fn use_hook<O, H>(
|
|
|
|
- &mut self,
|
|
|
|
- initializer: impl FnOnce() -> H,
|
|
|
|
- runner: impl Fn(&mut H) -> O,
|
|
|
|
- tear_down: impl Fn(&mut H),
|
|
|
|
- ) -> O;
|
|
|
|
- }
|
|
|
|
- impl<T: Props> ContextApplier for Context<T> {
|
|
|
|
- fn use_hook<O, H>(
|
|
|
|
- &mut self,
|
|
|
|
- initializer: impl FnOnce() -> H,
|
|
|
|
- runner: impl Fn(&mut H) -> O,
|
|
|
|
- tear_down: impl Fn(&mut H),
|
|
|
|
- ) -> O {
|
|
|
|
- todo!()
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- fn use_state<T>(c: &mut impl ContextApplier, g: impl Fn() -> T) -> T {
|
|
|
|
- c.use_hook(|| {}, |_| {}, |_| {});
|
|
|
|
- g()
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- enum SomeComponent {
|
|
|
|
- Imperative,
|
|
|
|
- Async,
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // impl<F, G> From<F> for SomeComponent
|
|
|
|
- // where
|
|
|
|
- // F: Fn() -> G,
|
|
|
|
- // G: Future<Output = ()>,
|
|
|
|
- // {
|
|
|
|
- // fn from(_: F) -> Self {
|
|
|
|
- // SomeComponent::Async
|
|
|
|
- // }
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- // impl From<fn() -> ()> for SomeComponent {
|
|
|
|
- // fn from(_: F) -> Self {
|
|
|
|
- // SomeComponent::Async
|
|
|
|
- // }
|
|
|
|
- // }
|
|
|
|
- // impl<F> Into<SomeComponent> for fn() -> F
|
|
|
|
- // where
|
|
|
|
- // F: Future<Output = ()>,
|
|
|
|
- // {
|
|
|
|
- // fn into(self) -> SomeComponent {
|
|
|
|
- // todo!()
|
|
|
|
- // }
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- // #[test]
|
|
|
|
- // fn test() {
|
|
|
|
- // let b: SomeComponent = test_comp.into();
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- // Does this make sense?
|
|
|
|
- // Any component labeled with async can halt its rendering, but won't be able to process updates?
|
|
|
|
- // Or, those updates can still happen virtually, just not propogated into the view?
|
|
|
|
- // async fn test_comp() -> () {
|
|
|
|
- // timer::new(300).await;
|
|
|
|
- // html! {
|
|
|
|
- // <div>
|
|
|
|
- // "hello world!"
|
|
|
|
- // </div>
|
|
|
|
- // }
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- // fn use_state<T: Props>(c: &mut Context<T>) {}
|
|
|
|
-
|
|
|
|
- // async fn another_component(ctx: &mut Context<()>) -> VNode {
|
|
|
|
- // // delay the re-render until component when the future is ready
|
|
|
|
- // // "use_future" loads the promise and provides a value (aka a loadable)
|
|
|
|
- // let value = use_effect(move || async {
|
|
|
|
- // get_value().join(timer::new(300));
|
|
|
|
- // set_value(blah);
|
|
|
|
- // });
|
|
|
|
-
|
|
|
|
- // rsx! {
|
|
|
|
- // <Suspense fallback={<div>"Loading..."</div>}>
|
|
|
|
- // <div>
|
|
|
|
- // "hello {name}!"
|
|
|
|
- // </div>
|
|
|
|
- // <Suspense />
|
|
|
|
- // }
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- /*
|
|
|
|
-
|
|
|
|
- Rationale
|
|
|
|
- Today, you can do use_async and do some async operations,
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- */
|
|
|
|
- // type FC<P: Props> = fn(&mut Context<P>) -> VNode;
|
|
|
|
-
|
|
|
|
- // static Example: FC<()> = |_| async {
|
|
|
|
- // // some async work
|
|
|
|
- // };
|
|
|
|
-
|
|
|
|
- // type FC2 = fn() -> impl Future<Output = ()>;
|
|
|
|
- // struct fc<P: Props>(fn(&mut Context<P>) -> G);
|
|
|
|
- // fn blah<P: Props, G: Future<Output = VNode>>(a: fn(&mut Context<P>) -> G) {}
|
|
|
|
-
|
|
|
|
- // static Example2: FC2<()> = fc(|_| async { VNode {} });
|
|
|
|
- // static Example2: () = blah(|_: &mut Context<()>| async { VNode {} });
|
|
|
|
-
|
|
|
|
- // static Example: FC<()> = |_| {
|
|
|
|
- // let g = async { VNode {} };
|
|
|
|
- // Box::new(g)
|
|
|
|
- // };
|
|
|
|
-
|
|
|
|
- // static Example2: = || {};
|
|
|
|
-
|
|
|
|
- // type FA<R: Future<Output = i32>> = fn(i32) -> R;
|
|
|
|
-
|
|
|
|
- // async fn my_component()
|
|
|
|
- // static MyThing: FA<dyn Future<Output = i32>> = |_| async { 10 };
|
|
|
|
-
|
|
|
|
- // type SomeFn = fn() -> ();
|
|
|
|
-
|
|
|
|
- // static MyFn: SomeFn = || {};
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-mod old2 {
|
|
|
|
- mod vdom {
|
|
|
|
- //! Virtual DOM implementation
|
|
|
|
- use super::*;
|
|
|
|
-
|
|
|
|
- pub struct VDom {
|
|
|
|
- patches: Vec<Patch>,
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- impl VDom {
|
|
|
|
- // fn new(root: ComponentFn) -> Self {
|
|
|
|
- // let scope = Scope::new();
|
|
|
|
- // Self {}
|
|
|
|
- // }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- mod nodes {}
|
|
|
|
-
|
|
|
|
- mod patch {}
|
|
|
|
-
|
|
|
|
- mod scope {
|
|
|
|
- //! Wrappers around components
|
|
|
|
-
|
|
|
|
- pub struct Scope {}
|
|
|
|
-
|
|
|
|
- impl Scope {
|
|
|
|
- fn new() -> Self {
|
|
|
|
- Self {}
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- mod context {}
|
|
|
|
-
|
|
|
|
- struct EventListener {}
|
|
|
|
-
|
|
|
|
- struct VNode {
|
|
|
|
- /// key-value pairs of attributes
|
|
|
|
- attributes: Vec<(&'static str, &'static str)>,
|
|
|
|
-
|
|
|
|
- /// onclick/onhover/on etc listeners
|
|
|
|
- /// goal is to standardize around a set of cross-platform listeners?
|
|
|
|
- listeners: Vec<EventListener>,
|
|
|
|
-
|
|
|
|
- /// Direct children, non arena-allocated
|
|
|
|
- children: Vec<VNode>,
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- enum ElementType {
|
|
|
|
- div,
|
|
|
|
- p,
|
|
|
|
- a,
|
|
|
|
- img,
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- struct ComponentContext {}
|
|
|
|
- type ComponentFn = fn(ctx: &ComponentContext) -> VNode;
|
|
|
|
-
|
|
|
|
- enum Patch {}
|
|
|
|
-
|
|
|
|
- mod tests {
|
|
|
|
- use super::*;
|
|
|
|
-
|
|
|
|
- /// Ensure components can be made from the raw components
|
|
|
|
- #[test]
|
|
|
|
- fn simple_test() {
|
|
|
|
- fn component(ctx: &ComponentContext) -> VNode {
|
|
|
|
- println!("Running component");
|
|
|
|
- VNode {}
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- let dom = VDom::new(component);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /// Ensure components can be made from the raw components
|
|
|
|
- #[test]
|
|
|
|
- fn simple_test_closure() {
|
|
|
|
- let component: ComponentFn = |ctx| {
|
|
|
|
- println!("Running component");
|
|
|
|
- VNode {}
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- let dom = VDom::new(component);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-mod text {
|
|
|
|
- //! Old methods that clouded the element implementation
|
|
|
|
- //! These all add a dedicated text renderer implementation
|
|
|
|
-
|
|
|
|
- mod vnode {
|
|
|
|
-
|
|
|
|
- impl From<&str> for VNode {
|
|
|
|
- fn from(other: &str) -> Self {
|
|
|
|
- VNode::text(other)
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- impl From<String> for VNode {
|
|
|
|
- fn from(other: String) -> Self {
|
|
|
|
- VNode::text(other.as_str())
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // -----------------------------------------------
|
|
|
|
- // Allow VNodes to be iterated for map-based UI
|
|
|
|
- // -----------------------------------------------
|
|
|
|
- impl IntoIterator for VNode {
|
|
|
|
- type Item = VNode;
|
|
|
|
- // TODO: Is this possible with an array [VNode] instead of a vec?
|
|
|
|
- type IntoIter = ::std::vec::IntoIter<VNode>;
|
|
|
|
-
|
|
|
|
- fn into_iter(self) -> Self::IntoIter {
|
|
|
|
- vec![self].into_iter()
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- impl Into<::std::vec::IntoIter<VNode>> for VNode {
|
|
|
|
- fn into(self) -> ::std::vec::IntoIter<VNode> {
|
|
|
|
- self.into_iter()
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // -----------------------------------------------
|
|
|
|
- // Allow debug/display adherent to the HTML spec
|
|
|
|
- // -----------------------------------------------
|
|
|
|
- use std::fmt;
|
|
|
|
- impl fmt::Debug for VNode {
|
|
|
|
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
- match self {
|
|
|
|
- VNode::Element(e) => write!(f, "Node::{:?}", e),
|
|
|
|
- VNode::Text(t) => write!(f, "Node::{:?}", t),
|
|
|
|
- VNode::Component(c) => write!(f, "Node::{:?}", c),
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Turn a VNode into an HTML string (delegate impl to variants)
|
|
|
|
- impl fmt::Display for VNode {
|
|
|
|
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
- match self {
|
|
|
|
- VNode::Element(element) => write!(f, "{}", element),
|
|
|
|
- VNode::Text(text) => write!(f, "{}", text),
|
|
|
|
- VNode::Component(c) => write!(f, "{}", c),
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- mod velement {
|
|
|
|
- // -----------------------------------------------
|
|
|
|
- // Allow debug/display adherent to the HTML spec
|
|
|
|
- // -----------------------------------------------
|
|
|
|
- use std::fmt;
|
|
|
|
- impl fmt::Debug for VElement {
|
|
|
|
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
- write!(
|
|
|
|
- f,
|
|
|
|
- "Element(<{}>, attrs: {:?}, children: {:?})",
|
|
|
|
- self.tag, self.attrs, self.children,
|
|
|
|
- )
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- impl fmt::Display for VElement {
|
|
|
|
- // Turn a VElement and all of it's children (recursively) into an HTML string
|
|
|
|
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
- write!(f, "<{}", self.tag).unwrap();
|
|
|
|
-
|
|
|
|
- for (attr, value) in self.attrs.iter() {
|
|
|
|
- write!(f, r#" {}="{}""#, attr, value)?;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- write!(f, ">")?;
|
|
|
|
-
|
|
|
|
- for child in self.children.iter() {
|
|
|
|
- write!(f, "{}", child.to_string())?;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if !crate::validation::is_self_closing(&self.tag) {
|
|
|
|
- write!(f, "</{}>", self.tag)?;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- Ok(())
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- mod vtext {
|
|
|
|
- // -----------------------------------------------
|
|
|
|
- // Convert from primitives directly into VText
|
|
|
|
- // -----------------------------------------------
|
|
|
|
- impl From<&str> for VText {
|
|
|
|
- fn from(text: &str) -> Self {
|
|
|
|
- VText {
|
|
|
|
- text: text.to_string(),
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- impl From<String> for VText {
|
|
|
|
- fn from(text: String) -> Self {
|
|
|
|
- VText { text }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // -----------------------------------------------
|
|
|
|
- // Allow debug/display adherent to the HTML spec
|
|
|
|
- // -----------------------------------------------
|
|
|
|
- use std::fmt;
|
|
|
|
- impl fmt::Debug for VText {
|
|
|
|
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
- write!(f, "Text({})", self.text)
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Turn a VText into an HTML string
|
|
|
|
- impl fmt::Display for VText {
|
|
|
|
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
- write!(f, "{}", self.text)
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- mod iterables {
|
|
|
|
-
|
|
|
|
- // TODO @Jon
|
|
|
|
- // Set this up so instead of the view trait, we can just take functions
|
|
|
|
- // Functions with no context should just be rendered
|
|
|
|
- // But functions with a context should be treated as regular components
|
|
|
|
-
|
|
|
|
- // impl<V: View> From<Vec<V>> for IterableNodes {
|
|
|
|
- // fn from(other: Vec<V>) -> Self {
|
|
|
|
- // IterableNodes(other.into_iter().map(|it| it.render()).collect())
|
|
|
|
- // }
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- // impl<V: View> From<&Vec<V>> for IterableNodes {
|
|
|
|
- // fn from(other: &Vec<V>) -> Self {
|
|
|
|
- // IterableNodes(other.iter().map(|it| it.render()).collect())
|
|
|
|
- // }
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- // impl<V: View> From<&[V]> for IterableNodes {
|
|
|
|
- // fn from(other: &[V]) -> Self {
|
|
|
|
- // IterableNodes(other.iter().map(|it| it.render()).collect())
|
|
|
|
- // }
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- impl From<&str> for IterableNodes {
|
|
|
|
- fn from(other: &str) -> Self {
|
|
|
|
- IterableNodes(vec![VNode::text(other)])
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- impl From<String> for IterableNodes {
|
|
|
|
- fn from(other: String) -> Self {
|
|
|
|
- IterableNodes(vec![VNode::text(other.as_str())])
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- mod tests {
|
|
|
|
- #[cfg(test)]
|
|
|
|
- mod tests {
|
|
|
|
- use super::*;
|
|
|
|
-
|
|
|
|
- #[test]
|
|
|
|
- fn self_closing_tag_to_string() {
|
|
|
|
- let node = VNode::element("br");
|
|
|
|
-
|
|
|
|
- // No </br> since self closing tag
|
|
|
|
- assert_eq!(&node.to_string(), "<br>");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- #[test]
|
|
|
|
- fn to_string() {
|
|
|
|
- let mut node = VNode::Element(VElement::new("div"));
|
|
|
|
- node.as_velement_mut()
|
|
|
|
- .unwrap()
|
|
|
|
- .attrs
|
|
|
|
- .insert("id".into(), "some-id".into());
|
|
|
|
-
|
|
|
|
- let mut child = VNode::Element(VElement::new("span"));
|
|
|
|
-
|
|
|
|
- let mut text = VNode::Text(VText::new("Hello world"));
|
|
|
|
-
|
|
|
|
- child.as_velement_mut().unwrap().children.push(text);
|
|
|
|
-
|
|
|
|
- node.as_velement_mut().unwrap().children.push(child);
|
|
|
|
-
|
|
|
|
- let expected = r#"<div id="some-id"><span>Hello world</span></div>"#;
|
|
|
|
-
|
|
|
|
- assert_eq!(node.to_string(), expected);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- mod ddiff {
|
|
|
|
- /// The diffing algorithm to compare two VNode trees and generate a list of patches to update the VDom.
|
|
|
|
- /// Currently, using an index-based patching algorithm
|
|
|
|
- ///
|
|
|
|
- pub mod diff {
|
|
|
|
- use super::*;
|
|
|
|
- use crate::nodes::{VNode, VText};
|
|
|
|
- use std::cmp::min;
|
|
|
|
- use std::collections::HashMap;
|
|
|
|
- use std::mem;
|
|
|
|
-
|
|
|
|
- // pub use apply_patches::patch;
|
|
|
|
-
|
|
|
|
- /// A Patch encodes an operation that modifies a real DOM element.
|
|
|
|
- ///
|
|
|
|
- /// To update the real DOM that a user sees you'll want to first diff your
|
|
|
|
- /// old virtual dom and new virtual dom.
|
|
|
|
- ///
|
|
|
|
- /// This diff operation will generate `Vec<Patch>` with zero or more patches that, when
|
|
|
|
- /// applied to your real DOM, will make your real DOM look like your new virtual dom.
|
|
|
|
- ///
|
|
|
|
- /// Each Patch has a u32 node index that helps us identify the real DOM node that it applies to.
|
|
|
|
- ///
|
|
|
|
- /// Our old virtual dom's nodes are indexed depth first, as shown in this illustration
|
|
|
|
- /// (0 being the root node, 1 being it's first child, 2 being it's first child's first child).
|
|
|
|
- ///
|
|
|
|
- /// ```text
|
|
|
|
- /// .─.
|
|
|
|
- /// ( 0 )
|
|
|
|
- /// `┬'
|
|
|
|
- /// ┌────┴──────┐
|
|
|
|
- /// │ │
|
|
|
|
- /// ▼ ▼
|
|
|
|
- /// .─. .─.
|
|
|
|
- /// ( 1 ) ( 4 )
|
|
|
|
- /// `┬' `─'
|
|
|
|
- /// ┌────┴───┐ │
|
|
|
|
- /// │ │ ├─────┬─────┐
|
|
|
|
- /// ▼ ▼ │ │ │
|
|
|
|
- /// .─. .─. ▼ ▼ ▼
|
|
|
|
- /// ( 2 ) ( 3 ) .─. .─. .─.
|
|
|
|
- /// `─' `─' ( 5 ) ( 6 ) ( 7 )
|
|
|
|
- /// `─' `─' `─'
|
|
|
|
- /// ```
|
|
|
|
- ///
|
|
|
|
- /// The patching process is tested in a real browser in crates/virtual-dom-rs/tests/diff_patch.rs
|
|
|
|
- #[derive(PartialEq)]
|
|
|
|
- pub enum Patch<'a> {
|
|
|
|
- /// Append a vector of child nodes to a parent node id.
|
|
|
|
- AppendChildren(NodeIdx, Vec<&'a VNode>),
|
|
|
|
- /// For a `node_i32`, remove all children besides the first `len`
|
|
|
|
- TruncateChildren(NodeIdx, usize),
|
|
|
|
- /// Replace a node with another node. This typically happens when a node's tag changes.
|
|
|
|
- /// ex: <div> becomes <span>
|
|
|
|
- Replace(NodeIdx, &'a VNode),
|
|
|
|
- /// Add attributes that the new node has that the old node does not
|
|
|
|
- AddAttributes(NodeIdx, HashMap<&'a str, &'a str>),
|
|
|
|
- /// Remove attributes that the old node had that the new node doesn't
|
|
|
|
- RemoveAttributes(NodeIdx, Vec<&'a str>),
|
|
|
|
- /// Change the text of a Text node.
|
|
|
|
- ChangeText(NodeIdx, &'a VText),
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- type NodeIdx = usize;
|
|
|
|
-
|
|
|
|
- impl<'a> Patch<'a> {
|
|
|
|
- /// Every Patch is meant to be applied to a specific node within the DOM. Get the
|
|
|
|
- /// index of the DOM node that this patch should apply to. DOM nodes are indexed
|
|
|
|
- /// depth first with the root node in the tree having index 0.
|
|
|
|
- pub fn node_idx(&self) -> usize {
|
|
|
|
- match self {
|
|
|
|
- Patch::AppendChildren(node_idx, _) => *node_idx,
|
|
|
|
- Patch::TruncateChildren(node_idx, _) => *node_idx,
|
|
|
|
- Patch::Replace(node_idx, _) => *node_idx,
|
|
|
|
- Patch::AddAttributes(node_idx, _) => *node_idx,
|
|
|
|
- Patch::RemoveAttributes(node_idx, _) => *node_idx,
|
|
|
|
- Patch::ChangeText(node_idx, _) => *node_idx,
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /// Given two VNode's generate Patch's that would turn the old virtual node's
|
|
|
|
- /// real DOM node equivalent into the new VNode's real DOM node equivalent.
|
|
|
|
- pub fn diff_vnodes<'a>(old: &'a VNode, new: &'a VNode) -> Vec<Patch<'a>> {
|
|
|
|
- diff_recursive(&old, &new, &mut 0)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- fn diff_recursive<'a, 'b>(
|
|
|
|
- old: &'a VNode,
|
|
|
|
- new: &'a VNode,
|
|
|
|
- cur_node_idx: &'b mut usize,
|
|
|
|
- ) -> Vec<Patch<'a>> {
|
|
|
|
- let mut patches = vec![];
|
|
|
|
- let mut replace = false;
|
|
|
|
-
|
|
|
|
- // Different enum variants, replace!
|
|
|
|
- // VNodes are of different types, and therefore will cause a re-render.
|
|
|
|
- // TODO: Handle previously-mounted children so they don't get re-mounted
|
|
|
|
- if mem::discriminant(old) != mem::discriminant(new) {
|
|
|
|
- replace = true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if let (VNode::Element(old_element), VNode::Element(new_element)) = (old, new) {
|
|
|
|
- // Replace if there are different element tags
|
|
|
|
- if old_element.tag != new_element.tag {
|
|
|
|
- replace = true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Replace if two elements have different keys
|
|
|
|
- // TODO: More robust key support. This is just an early stopgap to allow you to force replace
|
|
|
|
- // an element... say if it's event changed. Just change the key name for now.
|
|
|
|
- // In the future we want keys to be used to create a Patch::ReOrder to re-order siblings
|
|
|
|
- if old_element.attrs.get("key").is_some()
|
|
|
|
- && old_element.attrs.get("key") != new_element.attrs.get("key")
|
|
|
|
- {
|
|
|
|
- replace = true;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Handle replacing of a node
|
|
|
|
- if replace {
|
|
|
|
- patches.push(Patch::Replace(*cur_node_idx, &new));
|
|
|
|
- if let VNode::Element(old_element_node) = old {
|
|
|
|
- for child in old_element_node.children.iter() {
|
|
|
|
- increment_node_idx_for_children(child, cur_node_idx);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- return patches;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // The following comparison can only contain identical variants, other
|
|
|
|
- // cases have already been handled above by comparing variant
|
|
|
|
- // discriminants.
|
|
|
|
- match (old, new) {
|
|
|
|
- // We're comparing two text nodes
|
|
|
|
- (VNode::Text(old_text), VNode::Text(new_text)) => {
|
|
|
|
- if old_text != new_text {
|
|
|
|
- patches.push(Patch::ChangeText(*cur_node_idx, &new_text));
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // We're comparing two element nodes
|
|
|
|
- (VNode::Element(old_element), VNode::Element(new_element)) => {
|
|
|
|
- let mut add_attributes: HashMap<&str, &str> = HashMap::new();
|
|
|
|
- let mut remove_attributes: Vec<&str> = vec![];
|
|
|
|
-
|
|
|
|
- // TODO: -> split out into func
|
|
|
|
- for (new_attr_name, new_attr_val) in new_element.attrs.iter() {
|
|
|
|
- match old_element.attrs.get(new_attr_name) {
|
|
|
|
- Some(ref old_attr_val) => {
|
|
|
|
- if old_attr_val != &new_attr_val {
|
|
|
|
- add_attributes.insert(new_attr_name, new_attr_val);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- None => {
|
|
|
|
- add_attributes.insert(new_attr_name, new_attr_val);
|
|
|
|
- }
|
|
|
|
- };
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // TODO: -> split out into func
|
|
|
|
- for (old_attr_name, old_attr_val) in old_element.attrs.iter() {
|
|
|
|
- if add_attributes.get(&old_attr_name[..]).is_some() {
|
|
|
|
- continue;
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- match new_element.attrs.get(old_attr_name) {
|
|
|
|
- Some(ref new_attr_val) => {
|
|
|
|
- if new_attr_val != &old_attr_val {
|
|
|
|
- remove_attributes.push(old_attr_name);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- None => {
|
|
|
|
- remove_attributes.push(old_attr_name);
|
|
|
|
- }
|
|
|
|
- };
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if add_attributes.len() > 0 {
|
|
|
|
- patches.push(Patch::AddAttributes(*cur_node_idx, add_attributes));
|
|
|
|
- }
|
|
|
|
- if remove_attributes.len() > 0 {
|
|
|
|
- patches.push(Patch::RemoveAttributes(*cur_node_idx, remove_attributes));
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- let old_child_count = old_element.children.len();
|
|
|
|
- let new_child_count = new_element.children.len();
|
|
|
|
-
|
|
|
|
- if new_child_count > old_child_count {
|
|
|
|
- let append_patch: Vec<&'a VNode> =
|
|
|
|
- new_element.children[old_child_count..].iter().collect();
|
|
|
|
- patches.push(Patch::AppendChildren(*cur_node_idx, append_patch))
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if new_child_count < old_child_count {
|
|
|
|
- patches.push(Patch::TruncateChildren(*cur_node_idx, new_child_count))
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- let min_count = min(old_child_count, new_child_count);
|
|
|
|
- for index in 0..min_count {
|
|
|
|
- *cur_node_idx = *cur_node_idx + 1;
|
|
|
|
- let old_child = &old_element.children[index];
|
|
|
|
- let new_child = &new_element.children[index];
|
|
|
|
- patches.append(&mut diff_recursive(
|
|
|
|
- &old_child,
|
|
|
|
- &new_child,
|
|
|
|
- cur_node_idx,
|
|
|
|
- ))
|
|
|
|
- }
|
|
|
|
- if new_child_count < old_child_count {
|
|
|
|
- for child in old_element.children[min_count..].iter() {
|
|
|
|
- increment_node_idx_for_children(child, cur_node_idx);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- (VNode::Text(_), VNode::Element(_)) | (VNode::Element(_), VNode::Text(_)) => {
|
|
|
|
- unreachable!(
|
|
|
|
- "Unequal variant discriminants should already have been handled"
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
- _ => todo!("Diffing Not yet implemented for all node types"),
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- // new_root.create_element()
|
|
|
|
- patches
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- fn increment_node_idx_for_children<'a, 'b>(
|
|
|
|
- old: &'a VNode,
|
|
|
|
- cur_node_idx: &'b mut usize,
|
|
|
|
- ) {
|
|
|
|
- *cur_node_idx += 1;
|
|
|
|
- if let VNode::Element(element_node) = old {
|
|
|
|
- for child in element_node.children.iter() {
|
|
|
|
- increment_node_idx_for_children(&child, cur_node_idx);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // #[cfg(test)]
|
|
|
|
- // mod tests {
|
|
|
|
- // use super::*;
|
|
|
|
- // use crate::prelude::*;
|
|
|
|
- // type VirtualNode = VNode;
|
|
|
|
-
|
|
|
|
- // /// Test that we generate the right Vec<Patch> for some start and end virtual dom.
|
|
|
|
- // pub struct DiffTestCase<'a> {
|
|
|
|
- // // ex: "Patching root level nodes works"
|
|
|
|
- // pub description: &'static str,
|
|
|
|
- // // ex: html! { <div> </div> }
|
|
|
|
- // pub old: VNode,
|
|
|
|
- // // ex: html! { <strong> </strong> }
|
|
|
|
- // pub new: VNode,
|
|
|
|
- // // ex: vec![Patch::Replace(0, &html! { <strong></strong> })],
|
|
|
|
- // pub expected: Vec<Patch<'a>>,
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- // impl<'a> DiffTestCase<'a> {
|
|
|
|
- // pub fn test(&self) {
|
|
|
|
- // // ex: vec![Patch::Replace(0, &html! { <strong></strong> })],
|
|
|
|
- // let patches = diff_vnodes(&self.old, &self.new);
|
|
|
|
-
|
|
|
|
- // assert_eq!(patches, self.expected, "{}", self.description);
|
|
|
|
- // }
|
|
|
|
- // }
|
|
|
|
- // use super::*;
|
|
|
|
- // use crate::nodes::{VNode, VText};
|
|
|
|
- // use std::collections::HashMap;
|
|
|
|
-
|
|
|
|
- // #[test]
|
|
|
|
- // fn replace_node() {
|
|
|
|
- // DiffTestCase {
|
|
|
|
- // description: "Replace the root if the tag changed",
|
|
|
|
- // old: html! { <div> </div> },
|
|
|
|
- // new: html! { <span> </span> },
|
|
|
|
- // expected: vec![Patch::Replace(0, &html! { <span></span> })],
|
|
|
|
- // }
|
|
|
|
- // .test();
|
|
|
|
- // DiffTestCase {
|
|
|
|
- // description: "Replace a child node",
|
|
|
|
- // old: html! { <div> <b></b> </div> },
|
|
|
|
- // new: html! { <div> <strong></strong> </div> },
|
|
|
|
- // expected: vec![Patch::Replace(1, &html! { <strong></strong> })],
|
|
|
|
- // }
|
|
|
|
- // .test();
|
|
|
|
- // DiffTestCase {
|
|
|
|
- // description: "Replace node with a child",
|
|
|
|
- // old: html! { <div> <b>1</b> <b></b> </div> },
|
|
|
|
- // new: html! { <div> <i>1</i> <i></i> </div>},
|
|
|
|
- // expected: vec![
|
|
|
|
- // Patch::Replace(1, &html! { <i>1</i> }),
|
|
|
|
- // Patch::Replace(3, &html! { <i></i> }),
|
|
|
|
- // ], //required to check correct index
|
|
|
|
- // }
|
|
|
|
- // .test();
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- // #[test]
|
|
|
|
- // fn add_children() {
|
|
|
|
- // DiffTestCase {
|
|
|
|
- // description: "Added a new node to the root node",
|
|
|
|
- // old: html! { <div> <b></b> </div> },
|
|
|
|
- // new: html! { <div> <b></b> <span></span> </div> },
|
|
|
|
- // expected: vec![Patch::AppendChildren(0, vec![&html! { <span></span> }])],
|
|
|
|
- // }
|
|
|
|
- // .test();
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- // #[test]
|
|
|
|
- // fn remove_nodes() {
|
|
|
|
- // DiffTestCase {
|
|
|
|
- // description: "Remove all child nodes at and after child sibling index 1",
|
|
|
|
- // old: html! { <div> <b></b> <span></span> </div> },
|
|
|
|
- // new: html! { <div> </div> },
|
|
|
|
- // expected: vec![Patch::TruncateChildren(0, 0)],
|
|
|
|
- // }
|
|
|
|
- // .test();
|
|
|
|
- // DiffTestCase {
|
|
|
|
- // description: "Remove a child and a grandchild node",
|
|
|
|
- // old: html! {
|
|
|
|
- // <div>
|
|
|
|
- // <span>
|
|
|
|
- // <b></b>
|
|
|
|
- // // This `i` tag will get removed
|
|
|
|
- // <i></i>
|
|
|
|
- // </span>
|
|
|
|
- // // This `strong` tag will get removed
|
|
|
|
- // <strong></strong>
|
|
|
|
- // </div> },
|
|
|
|
- // new: html! {
|
|
|
|
- // <div>
|
|
|
|
- // <span>
|
|
|
|
- // <b></b>
|
|
|
|
- // </span>
|
|
|
|
- // </div> },
|
|
|
|
- // expected: vec![Patch::TruncateChildren(0, 1), Patch::TruncateChildren(1, 1)],
|
|
|
|
- // }
|
|
|
|
- // .test();
|
|
|
|
- // DiffTestCase {
|
|
|
|
- // description: "Removing child and change next node after parent",
|
|
|
|
- // old: html! { <div> <b> <i></i> <i></i> </b> <b></b> </div> },
|
|
|
|
- // new: html! { <div> <b> <i></i> </b> <i></i> </div>},
|
|
|
|
- // expected: vec![
|
|
|
|
- // Patch::TruncateChildren(1, 1),
|
|
|
|
- // Patch::Replace(4, &html! { <i></i> }),
|
|
|
|
- // ], //required to check correct index
|
|
|
|
- // }
|
|
|
|
- // .test();
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- // #[test]
|
|
|
|
- // fn add_attributes() {
|
|
|
|
- // let mut attributes = HashMap::new();
|
|
|
|
- // attributes.insert("id", "hello");
|
|
|
|
-
|
|
|
|
- // DiffTestCase {
|
|
|
|
- // old: html! { <div> </div> },
|
|
|
|
- // new: html! { <div id="hello"> </div> },
|
|
|
|
- // expected: vec![Patch::AddAttributes(0, attributes.clone())],
|
|
|
|
- // description: "Add attributes",
|
|
|
|
- // }
|
|
|
|
- // .test();
|
|
|
|
-
|
|
|
|
- // DiffTestCase {
|
|
|
|
- // old: html! { <div id="foobar"> </div> },
|
|
|
|
- // new: html! { <div id="hello"> </div> },
|
|
|
|
- // expected: vec![Patch::AddAttributes(0, attributes)],
|
|
|
|
- // description: "Change attribute",
|
|
|
|
- // }
|
|
|
|
- // .test();
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- // #[test]
|
|
|
|
- // fn remove_attributes() {
|
|
|
|
- // DiffTestCase {
|
|
|
|
- // old: html! { <div id="hey-there"></div> },
|
|
|
|
- // new: html! { <div> </div> },
|
|
|
|
- // expected: vec![Patch::RemoveAttributes(0, vec!["id"])],
|
|
|
|
- // description: "Add attributes",
|
|
|
|
- // }
|
|
|
|
- // .test();
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- // #[test]
|
|
|
|
- // fn change_attribute() {
|
|
|
|
- // let mut attributes = HashMap::new();
|
|
|
|
- // attributes.insert("id", "changed");
|
|
|
|
-
|
|
|
|
- // DiffTestCase {
|
|
|
|
- // description: "Add attributes",
|
|
|
|
- // old: html! { <div id="hey-there"></div> },
|
|
|
|
- // new: html! { <div id="changed"> </div> },
|
|
|
|
- // expected: vec![Patch::AddAttributes(0, attributes)],
|
|
|
|
- // }
|
|
|
|
- // .test();
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- // #[test]
|
|
|
|
- // fn replace_text_node() {
|
|
|
|
- // DiffTestCase {
|
|
|
|
- // description: "Replace text node",
|
|
|
|
- // old: html! { Old },
|
|
|
|
- // new: html! { New },
|
|
|
|
- // expected: vec![Patch::ChangeText(0, &VText::new("New"))],
|
|
|
|
- // }
|
|
|
|
- // .test();
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- // // Initially motivated by having two elements where all that changed was an event listener
|
|
|
|
- // // because right now we don't patch event listeners. So.. until we have a solution
|
|
|
|
- // // for that we can just give them different keys to force a replace.
|
|
|
|
- // #[test]
|
|
|
|
- // fn replace_if_different_keys() {
|
|
|
|
- // DiffTestCase {
|
|
|
|
- // description: "If two nodes have different keys always generate a full replace.",
|
|
|
|
- // old: html! { <div key="1"> </div> },
|
|
|
|
- // new: html! { <div key="2"> </div> },
|
|
|
|
- // expected: vec![Patch::Replace(0, &html! {<div key="2"> </div>})],
|
|
|
|
- // }
|
|
|
|
- // .test()
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- // // // TODO: Key support
|
|
|
|
- // // #[test]
|
|
|
|
- // // fn reorder_chldren() {
|
|
|
|
- // // let mut attributes = HashMap::new();
|
|
|
|
- // // attributes.insert("class", "foo");
|
|
|
|
- // //
|
|
|
|
- // // let old_children = vec![
|
|
|
|
- // // // old node 0
|
|
|
|
- // // html! { <div key="hello", id="same-id", style="",></div> },
|
|
|
|
- // // // removed
|
|
|
|
- // // html! { <div key="gets-removed",> { "This node gets removed"} </div>},
|
|
|
|
- // // // old node 2
|
|
|
|
- // // html! { <div key="world", class="changed-class",></div>},
|
|
|
|
- // // // removed
|
|
|
|
- // // html! { <div key="this-got-removed",> { "This node gets removed"} </div>},
|
|
|
|
- // // ];
|
|
|
|
- // //
|
|
|
|
- // // let new_children = vec![
|
|
|
|
- // // html! { <div key="world", class="foo",></div> },
|
|
|
|
- // // html! { <div key="new",> </div>},
|
|
|
|
- // // html! { <div key="hello", id="same-id",></div>},
|
|
|
|
- // // ];
|
|
|
|
- // //
|
|
|
|
- // // test(DiffTestCase {
|
|
|
|
- // // old: html! { <div> { old_children } </div> },
|
|
|
|
- // // new: html! { <div> { new_children } </div> },
|
|
|
|
- // // expected: vec![
|
|
|
|
- // // // TODO: Come up with the patch structure for keyed nodes..
|
|
|
|
- // // // keying should only work if all children have keys..
|
|
|
|
- // // ],
|
|
|
|
- // // description: "Add attributes",
|
|
|
|
- // // })
|
|
|
|
- // // }
|
|
|
|
- // }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- mod vcomponent {
|
|
|
|
- // -----------------------------------------------
|
|
|
|
- // Allow debug/display adherent to the HTML spec
|
|
|
|
- // -----------------------------------------------
|
|
|
|
-
|
|
|
|
- impl fmt::Debug for VComponent {
|
|
|
|
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
- // TODO: @JON Implement how components should be formatted when spit out to html
|
|
|
|
- // It probably can't be as straightforward as renderinng their VNodes
|
|
|
|
- // It _could_ be, but we can't really implement that directly
|
|
|
|
- // Instead, we should drop a vnode labeled with the component id/key
|
|
|
|
-
|
|
|
|
- // write!(
|
|
|
|
- // f,
|
|
|
|
- // "Element(<{}>, attrs: {:?}, children: {:?})",
|
|
|
|
- // self.tag, self.attrs, self.children,
|
|
|
|
- // )
|
|
|
|
- Ok(())
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- impl fmt::Display for VComponent {
|
|
|
|
- // Turn a VElement and all of it's children (recursively) into an HTML string
|
|
|
|
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
- // write!(f, "<{}", self.tag).unwrap();
|
|
|
|
-
|
|
|
|
- // for (attr, value) in self.attrs.iter() {
|
|
|
|
- // write!(f, r#" {}="{}""#, attr, value)?;
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- // write!(f, ">")?;
|
|
|
|
-
|
|
|
|
- // for child in self.children.iter() {
|
|
|
|
- // write!(f, "{}", child.to_string())?;
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- // if !crate::validation::is_self_closing(&self.tag) {
|
|
|
|
- // write!(f, "</{}>", self.tag)?;
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- Ok(())
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-// pub mod iterables {
|
|
|
|
-// use super::*;
|
|
|
|
-
|
|
|
|
-// /// Used by the html! macro for all braced child nodes so that we can use any type
|
|
|
|
-// /// that implements Into<IterableNodes>
|
|
|
|
-// ///
|
|
|
|
-// /// html! { <div> { nodes } </div> }
|
|
|
|
-// ///
|
|
|
|
-// /// nodes can be a String .. VNode .. Vec<VNode> ... etc
|
|
|
|
-// pub struct IterableNodes(Vec<VNode>);
|
|
|
|
-
|
|
|
|
-// impl IterableNodes {
|
|
|
|
-// /// Retrieve the first node mutably
|
|
|
|
-// pub fn first(&mut self) -> &mut VNode {
|
|
|
|
-// self.0.first_mut().unwrap()
|
|
|
|
-// }
|
|
|
|
-
|
|
|
|
-// /// Retrieve the last node mutably
|
|
|
|
-// pub fn last(&mut self) -> &mut VNode {
|
|
|
|
-// self.0.last_mut().unwrap()
|
|
|
|
-// }
|
|
|
|
-// }
|
|
|
|
-
|
|
|
|
-// impl IntoIterator for IterableNodes {
|
|
|
|
-// type Item = VNode;
|
|
|
|
-// // TODO: Is this possible with an array [VNode] instead of a vec?
|
|
|
|
-// type IntoIter = ::std::vec::IntoIter<VNode>;
|
|
|
|
-
|
|
|
|
-// fn into_iter(self) -> Self::IntoIter {
|
|
|
|
-// self.0.into_iter()
|
|
|
|
-// }
|
|
|
|
-// }
|
|
|
|
-
|
|
|
|
-// impl From<VNode> for IterableNodes {
|
|
|
|
-// fn from(other: VNode) -> Self {
|
|
|
|
-// IterableNodes(vec![other])
|
|
|
|
-// }
|
|
|
|
-// }
|
|
|
|
-
|
|
|
|
-// impl From<Vec<VNode>> for IterableNodes {
|
|
|
|
-// fn from(other: Vec<VNode>) -> Self {
|
|
|
|
-// IterableNodes(other)
|
|
|
|
-// }
|
|
|
|
-// }
|
|
|
|
-// }
|
|
|