Ver código fonte

wip: lots of changes to diffing

Jonathan Kelley 4 anos atrás
pai
commit
ff0a3d1

+ 9 - 9
examples/README.md

@@ -11,7 +11,7 @@ These examples are fully-fledged micro apps. They can be ran with the `cargo run
 | [Global State Management](./statemanagement.rs)     | Share state between components              | 🛠      |
 | [Virtual Refs]()                                    | Cross-platform imperative elements          | 🛠      |
 | [Inline Styles](./inline-styles.rs)                 | Define styles for elements inline           | 🛠      |
-| [Conditional Rendering](./conditional-rendering.rs) | Hide/Show elements using conditionals       | ✅     |
+| [Conditional Rendering](./conditional-rendering.rs) | Hide/Show elements using conditionals       | ✅      |
 
 These examples are not necessarily meant to be run, but rather serve as a reference for the given functionality.
 
@@ -22,15 +22,15 @@ These examples are not necessarily meant to be run, but rather serve as a refere
 | [Global State Management](./statemanagement.rs)     | Share state between components                  | 🛠      |
 | [Virtual Refs]()                                    | Cross-platform imperative elements              | 🛠      |
 | [Inline Styles](./inline-styles.rs)                 | Define styles for elements inline               | 🛠      |
-| [Conditional Rendering](./conditional-rendering.rs) | Hide/Show elements using conditionals           | ✅     |
-| [Maps/Iterators](./iterators.rs)                    | Use iterators in the rsx! macro                 | 🛠      |
+| [Conditional Rendering](./conditional-rendering.rs) | Hide/Show elements using conditionals           | ✅      |
+| [Maps/Iterators](./iterators.rs)                    | Use iterators in the rsx! macro                 |       |
 | [Render To string](./tostring.rs)                   | Render a mounted virtualdom to a string         | 🛠      |
 | [Component Children](./children.rs)                 | Pass children into child components             | 🛠      |
 | [Function Driven children]()                        | Pass functions to make VNodes                   | 🛠      |
-| [Memoization & Borrowed Data](./memo.rs)            | Suppress renders, borrow from parents           | ✅     |
-| [Fragments](./fragments.rs)                         | Support root-less element groups                | ✅     |
+| [Memoization & Borrowed Data](./memo.rs)            | Suppress renders, borrow from parents           | ✅      |
+| [Fragments](./fragments.rs)                         | Support root-less element groups                | ✅      |
 | [Null/None Components](./empty.rs)                  | Return nothing!                                 | 🛠      |
-| [Spread Pattern for props](./spreadpattern.rs)      | Manually specify and override props             | ✅     |
+| [Spread Pattern for props](./spreadpattern.rs)      | Manually specify and override props             | ✅      |
 | [Controlled Inputs](./controlled-inputs.rs)         | this does                                       | 🛠      |
 | [Custom Elements]()                                 | Define custom elements                          | 🛠      |
 | [Web Components]()                                  | Custom elements to interface with WebComponents | 🛠      |
@@ -38,9 +38,9 @@ These examples are not necessarily meant to be run, but rather serve as a refere
 | [Asynchronous Data]()                               | Using suspense to wait for data                 | 🛠      |
 | [Fiber/Scheduled Rendering]()                       | this does                                       | 🛠      |
 | [CSS Compiled Styles]()                             | this does                                       | 🛠      |
-| [Anti-patterns](./antipatterns.rs)                  | A collection of discouraged patterns            | ✅     |
-| [Complete rsx reference](./rsx_usage.rs)            | A complete reference for all rsx! usage         | ✅     |
-| [Event Listeners](./listener.rs)                    | Attach closures to events on elements           | ✅     |
+| [Anti-patterns](./antipatterns.rs)                  | A collection of discouraged patterns            | ✅      |
+| [Complete rsx reference](./rsx_usage.rs)            | A complete reference for all rsx! usage         | ✅      |
+| [Event Listeners](./listener.rs)                    | Attach closures to events on elements           | ✅      |
 
 These web-specific examples must be run with `dioxus-cli` using `dioxus develop --example XYZ`
 

+ 2 - 1
examples/calculator.rs

@@ -18,7 +18,7 @@ enum Operator {
     Div,
 }
 
-static App: FC<()> = |cx| {
+const App: FC<()> = |cx| {
     let cur_val = use_state(cx, || 0.0_f64);
     let operator = use_state(cx, || None as Option<Operator>);
     let display_value = use_state(cx, || "".to_string());
@@ -52,6 +52,7 @@ static App: FC<()> = |cx| {
             display_value.set(format!("-{}", *display_value))
         }
     };
+
     let toggle_percent = move |_| todo!();
 
     let clear_key = move |_| {

+ 23 - 0
examples/manually.rs

@@ -0,0 +1,23 @@
+use dioxus_core::*;
+
+fn main() {
+    use DomEdit::*;
+
+    // .. should result in an "invalid node tree"
+    let edits = vec![
+        CreateElement { tag: "div", id: 0 },
+        // CreatePlaceholder { id: 1 },
+        CreateElement { tag: "h1", id: 2 },
+        CreateTextNode {
+            text: "hello world",
+            id: 3,
+        },
+        AppendChildren { many: 1 },
+        AppendChildren { many: 1 },
+        AppendChildren { many: 1 },
+        // ReplaceWith { many: 1 },
+    ];
+    dioxus_webview::WebviewRenderer::run_with_edits(App, (), |c| c, Some(edits)).expect("failed");
+}
+
+const App: FC<()> = |cx| todo!();

+ 117 - 123
examples/model.rs

@@ -28,129 +28,39 @@ fn main() {
     .expect("failed to launch dioxus app");
 }
 
-enum Operator {
-    Add,
-    Sub,
-    Mul,
-    Div,
-}
-
 static App: FC<()> = |cx| {
-    let (cur_val, set_cur_val) = use_state(cx, || 0.0_f64).classic();
-    let (operator, set_operator) = use_state(cx, || None as Option<Operator>).classic();
-    let (display_value, set_display_value) = use_state(cx, || "0".to_string()).classic();
+    let state = use_state(cx, || Calculator::new());
 
-    let clear_display = display_value.eq("0");
+    let clear_display = state.display_value.eq("0");
     let clear_text = if clear_display { "C" } else { "AC" };
-
-    let input_digit = move |num: u8| {
-        let mut new = if operator.is_some() {
-            String::new()
-        } else if display_value == "0" {
-            String::new()
-        } else {
-            display_value.clone()
-        };
-        if operator.is_some() {
-            let val = display_value.parse::<f64>().unwrap();
-            set_cur_val(val);
-        }
-
-        new.push_str(num.to_string().as_str());
-        set_display_value(new);
-    };
-
-    let input_dot = move || {
-        let mut new = display_value.clone();
-        new.push_str(".");
-        set_display_value(new);
-    };
-
-    let perform_operation = move || {
-        if let Some(op) = operator.as_ref() {
-            let rhs = display_value.parse::<f64>().unwrap();
-            let new_val = match op {
-                Operator::Add => *cur_val + rhs,
-                Operator::Sub => *cur_val - rhs,
-                Operator::Mul => *cur_val * rhs,
-                Operator::Div => *cur_val / rhs,
-            };
-            set_cur_val(new_val);
-            set_display_value(new_val.to_string());
-            set_operator(None);
-        }
-    };
-
-    let toggle_sign = move |_| {
-        if display_value.starts_with("-") {
-            set_display_value(display_value.trim_start_matches("-").to_string())
-        } else {
-            set_display_value(format!("-{}", *display_value))
-        }
-    };
-    let toggle_percent = move |_| todo!();
-
-    let clear_key = move |_| {
-        set_display_value("0".to_string());
-        if !clear_display {
-            set_operator(None);
-            set_cur_val(0.0);
-        }
-    };
-
-    let keydownhandler = move |evt: KeyboardEvent| match evt.key_code() {
-        KeyCode::Backspace => {
-            let mut new = display_value.clone();
-            if !new.as_str().eq("0") {
-                new.pop();
-            }
-            set_display_value(new);
-        }
-        KeyCode::_0 => input_digit(0),
-        KeyCode::_1 => input_digit(1),
-        KeyCode::_2 => input_digit(2),
-        KeyCode::_3 => input_digit(3),
-        KeyCode::_4 => input_digit(4),
-        KeyCode::_5 => input_digit(5),
-        KeyCode::_6 => input_digit(6),
-        KeyCode::_7 => input_digit(7),
-        KeyCode::_8 => input_digit(8),
-        KeyCode::_9 => input_digit(9),
-        KeyCode::Add => set_operator(Some(Operator::Add)),
-        KeyCode::Subtract => set_operator(Some(Operator::Sub)),
-        KeyCode::Divide => set_operator(Some(Operator::Div)),
-        KeyCode::Multiply => set_operator(Some(Operator::Mul)),
-        _ => {}
-    };
+    let formatted = state.formatted_display();
 
     cx.render(rsx! {
-        div {
-            id: "wrapper"
-            div { class: "app" onkeydown: {keydownhandler}
-                style { "{STYLE}" }
-                div { class: "calculator", 
-                    CalculatorDisplay { val: &display_value}
+        div { id: "wrapper"
+            div { class: "app", style { "{STYLE}" }
+                div { class: "calculator", onkeypress: move |evt| state.get_mut().handle_keydown(evt),
+                    div { class: "calculator-display", "{formatted}"}
                     div { class: "calculator-keypad"
                         div { class: "input-keys"
                             div { class: "function-keys"
-                                CalculatorKey { name: "key-clear", onclick: {clear_key} "{clear_text}" }
-                                CalculatorKey { name: "key-sign", onclick: {toggle_sign}, "±"}
-                                CalculatorKey { name: "key-percent", onclick: {toggle_percent} "%"}
+                                CalculatorKey { name: "key-clear", onclick: move |_| state.get_mut().clear_display(), "{clear_text}" }
+                                CalculatorKey { name: "key-sign", onclick: move |_| state.get_mut().toggle_sign(), "±"}
+                                CalculatorKey { name: "key-percent", onclick: move |_| state.get_mut().toggle_percent(), "%"}
                             }
                             div { class: "digit-keys"
-                                CalculatorKey { name: "key-0", onclick: move |_| input_digit(0), "0" }
-                                CalculatorKey { name: "key-dot", onclick: move |_|  input_dot(), "●" }
+                                CalculatorKey { name: "key-0", onclick: move |_| state.get_mut().input_digit(0), "0" }
+                                CalculatorKey { name: "key-dot", onclick: move |_|  state.get_mut().input_dot(), "●" }
                                 {(1..10).map(move |k| rsx!{
-                                    CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| input_digit(k), "{k}" }
+                                    CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_|  state.get_mut().input_digit(k), "{k}" }
                                 })}
                             }
                         }
                         div { class: "operator-keys"
-                            CalculatorKey { name: "key-divide", onclick: move |_| set_operator(Some(Operator::Div)) "÷" }
-                            CalculatorKey { name: "key-multiply", onclick: move |_| set_operator(Some(Operator::Mul)) "×" }
-                            CalculatorKey { name: "key-subtract", onclick: move |_| set_operator(Some(Operator::Sub)) "−" }
-                            CalculatorKey { name: "key-add", onclick: move |_| set_operator(Some(Operator::Add)) "+" }
-                            CalculatorKey { name: "key-equals", onclick: move |_| perform_operation() "=" }
+                            CalculatorKey { name:"key-divide", onclick: move |_| state.get_mut().set_operator(Operator::Div), "÷" }
+                            CalculatorKey { name:"key-multiply", onclick: move |_| state.get_mut().set_operator(Operator::Mul), "×" }
+                            CalculatorKey { name:"key-subtract", onclick: move |_| state.get_mut().set_operator(Operator::Sub), "−" }
+                            CalculatorKey { name:"key-add", onclick: move |_| state.get_mut().set_operator(Operator::Add), "+" }
+                            CalculatorKey { name:"key-equals", onclick: move |_| state.get_mut().perform_operation(), "=" }
                         }
                     }
                 }
@@ -161,10 +71,7 @@ static App: FC<()> = |cx| {
 
 #[derive(Props)]
 struct CalculatorKeyProps<'a> {
-    /// Name!
     name: &'static str,
-
-    /// Click!
     onclick: &'a dyn Fn(MouseEvent),
 }
 
@@ -178,19 +85,106 @@ fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> VNode<'a> {
     })
 }
 
-#[derive(Props, PartialEq)]
-struct CalculatorDisplayProps<'a> {
-    val: &'a str,
+#[derive(Clone)]
+struct Calculator {
+    display_value: String,
+    operator: Option<Operator>,
+    waiting_for_operand: bool,
+    cur_val: f64,
+}
+#[derive(Clone)]
+enum Operator {
+    Add,
+    Sub,
+    Mul,
+    Div,
 }
 
-fn CalculatorDisplay<'a>(cx: Context<'a, CalculatorDisplayProps>) -> VNode<'a> {
-    use separator::Separatable;
-    // Todo, add float support to the num-format crate
-    let formatted = cx.val.parse::<f64>().unwrap().separated_string();
-    // TODO: make it autoscaling with css
-    cx.render(rsx! {
-        div { class: "calculator-display"
-            div { class: "auto-scaling-text", "{formatted}" }
+impl Calculator {
+    fn new() -> Self {
+        Calculator {
+            display_value: "0".to_string(),
+            operator: None,
+            waiting_for_operand: false,
+            cur_val: 0.0,
         }
-    })
+    }
+    fn formatted_display(&self) -> String {
+        use separator::Separatable;
+        self.display_value
+            .parse::<f64>()
+            .unwrap()
+            .separated_string()
+    }
+    fn clear_display(&mut self) {
+        self.display_value = "0".to_string();
+    }
+    fn input_digit(&mut self, digit: u8) {
+        let content = digit.to_string();
+        if self.waiting_for_operand || self.display_value == "0" {
+            self.waiting_for_operand = false;
+            self.display_value = content;
+        } else {
+            self.display_value.push_str(content.as_str());
+        }
+    }
+    fn input_dot(&mut self) {
+        if self.display_value.find(".").is_none() {
+            self.display_value.push_str(".");
+        }
+    }
+    fn perform_operation(&mut self) {
+        if let Some(op) = &self.operator {
+            let rhs = self.display_value.parse::<f64>().unwrap();
+            let new_val = match op {
+                Operator::Add => self.cur_val + rhs,
+                Operator::Sub => self.cur_val - rhs,
+                Operator::Mul => self.cur_val * rhs,
+                Operator::Div => self.cur_val / rhs,
+            };
+            self.cur_val = new_val;
+            self.display_value = new_val.to_string();
+            self.operator = None;
+        }
+    }
+    fn toggle_sign(&mut self) {
+        if self.display_value.starts_with("-") {
+            self.display_value = self.display_value.trim_start_matches("-").to_string();
+        } else {
+            self.display_value = format!("-{}", self.display_value);
+        }
+    }
+    fn toggle_percent(&mut self) {
+        self.display_value = (self.display_value.parse::<f64>().unwrap() / 100.0).to_string();
+    }
+    fn backspace(&mut self) {
+        if !self.display_value.as_str().eq("0") {
+            self.display_value.pop();
+        }
+    }
+    fn set_operator(&mut self, operator: Operator) {
+        self.operator = Some(operator);
+        self.cur_val = self.display_value.parse::<f64>().unwrap();
+        self.waiting_for_operand = true;
+    }
+    fn handle_keydown(&mut self, evt: KeyboardEvent) {
+        match evt.key_code() {
+            KeyCode::Backspace => self.backspace(),
+            KeyCode::_0 => self.input_digit(0),
+            KeyCode::_1 => self.input_digit(1),
+            KeyCode::_2 => self.input_digit(2),
+            KeyCode::_3 => self.input_digit(3),
+            KeyCode::_4 => self.input_digit(4),
+            KeyCode::_5 => self.input_digit(5),
+            KeyCode::_6 => self.input_digit(6),
+            KeyCode::_7 => self.input_digit(7),
+            KeyCode::_8 => self.input_digit(8),
+            KeyCode::_9 => self.input_digit(9),
+            KeyCode::Add => self.operator = Some(Operator::Add),
+            KeyCode::Subtract => self.operator = Some(Operator::Sub),
+            KeyCode::Divide => self.operator = Some(Operator::Div),
+            KeyCode::Multiply => self.operator = Some(Operator::Mul),
+            _ => {}
+        }
+    }
 }

+ 0 - 1
examples/reference/README.md

@@ -21,7 +21,6 @@ This folder holds a bunch of useful reference code. However, none of the code is
 | [NodeRef](./noderefs.rs)                            | How to handle futures with suspense |
 | [Signals](./signals.rs)                             | How to handle futures with suspense |
 | [ToString](./tostring.rs)                           | How to handle futures with suspense |
-| [Fiber](./fiber.rs)                                 | How to handle futures with suspense |
 | [Global CSS](./global_css.rs)                       | How to handle futures with suspense |
 | [State Management](./statemanagement.rs)            | How to handle futures with suspense |
 | [Testing](./testing.rs)                             | How to handle futures with suspense |

+ 2 - 0
examples/reference/empty.rs

@@ -4,5 +4,7 @@
 //! This is a simple pattern that allows you to return no elements!
 
 fn main() {}
+
 use dioxus::prelude::*;
+
 static Example: FC<()> = |cx| cx.render(rsx! { Fragment {} });

+ 0 - 10
examples/reference/fiber.rs

@@ -1,10 +0,0 @@
-use dioxus::prelude::*;
-fn main() {}
-
-static Example: FC<()> = |cx| {
-    cx.render(rsx! {
-        div {
-
-        }
-    })
-};

+ 24 - 3
examples/reference/global_css.rs

@@ -1,10 +1,31 @@
+//! Examples: CSS
+//! -------------
+//!
+//! Originally taken from:
+//! - https://www.w3schools.com/html/tryit.asp?filename=tryhtml_css_internal
+//!
+//! Include global styles in your app!
+//!
+//! You can simply drop in a "style" tag and set the inner contents to your stylesheet.
+//! It's slightly more manual than React, but is less magical.
+//!
+//! A coming update with the assets system will make it possible to include global css from child components.
+
 use dioxus::prelude::*;
 fn main() {}
 
-static Example: FC<()> = |cx| {
-    cx.render(rsx! {
-        div {
+const STYLE: &str = r#"
+body {background-color: powderblue;}
+h1   {color: blue;}
+p    {color: red;}
+"#;
 
+const Example: FC<()> = |cx| {
+    cx.render(rsx! {
+        head { style { "{STYLE}" } }
+        body {
+            h1 {"This is a heading"}
+            p {"This is a paragraph"}
         }
     })
 };

+ 24 - 3
examples/reference/iterators.rs

@@ -1,17 +1,38 @@
-use dioxus::prelude::*;
+//! Example: Iterators
+//! ------------------
+//!
+//! This example demonstrates how to use iterators with Dioxus.
+//! Iterators must be used through the curly braces item in element bodies.
+//! While you might be inclined to `.collect::<>` into Html, Dioxus prefers you provide an iterator that
+//! resolves to VNodes. It's more efficient and easier to write than having to `collect` everywhere.
+//!
+//! This also makes it easy to write "pull"-style iterators that don't have a known size.
+
 fn main() {}
 
+use dioxus::prelude::*;
 static Example: FC<()> = |cx| {
     let g = use_state(cx, || 0);
+
     let v = (0..10).map(|f| {
         rsx! {
             li {
-                onclick: move |_| g.set(10)
+                onclick: move |_| g.set(f)
+                "ID: {f}"
+                ul {
+                    {(0..10).map(|k| rsx!{
+                        li {
+                            "Sub iterator: {f}.{k}"
+                        }
+                    })}
+                }
             }
         }
     });
+
     cx.render(rsx! {
-        div {
+        h3 {"Selected: {g}"}
+        ul {
             {v}
         }
     })

+ 0 - 1
examples/showcase.rs

@@ -11,7 +11,6 @@ mod reference {
     mod controlled_inputs;
     mod custom_elements;
     mod empty;
-    mod fiber;
     mod fragments;
     mod global_css;
     mod inline_styles;

+ 22 - 9
examples/testbed.rs

@@ -1,15 +1,28 @@
 use dioxus::prelude::*;
 
-fn main() {}
+fn main() {
+    env_logger::init();
+    dioxus::desktop::launch(Example, |c| c);
+}
 
-static App: FC<()> = |cx| {
-    //
+const STYLE: &str = r#"
+body {background-color: powderblue;}
+h1   {color: blue;}
+p    {color: red;}
+"#;
+
+const Example: FC<()> = |cx| {
+    cx.render(rsx! {
+        Child { }
+        Child { }
+    })
+};
+
+const Child: FC<()> = |cx| {
     cx.render(rsx!(
-        div {
-            h1 {}
-        }
+        h1 {"1" }
+        h1 {"2" }
+        h1 {"3" }
+        h1 {"4" }
     ))
 };
-
-#[test]
-fn blah() {}

+ 2 - 0
packages/core/Cargo.toml

@@ -37,6 +37,8 @@ slotmap = "1.0.3"
 appendlist = "1.4.0"
 
 futures-util = "0.3.15"
+smallvec = "1.6.1"
+
 
 [features]
 default = []

+ 10 - 83
packages/core/src/childiter.rs

@@ -19,12 +19,6 @@ impl<'a> RealChildIterator<'a> {
     }
 }
 
-// impl<'a> DoubleEndedIterator for ChildIterator<'a> {
-//     fn next_back(&mut self) -> Option<Self::Item> {
-//         todo!()
-//     }
-// }
-
 impl<'a> Iterator for RealChildIterator<'a> {
     type Item = &'a VNode<'a>;
 
@@ -37,6 +31,7 @@ impl<'a> Iterator for RealChildIterator<'a> {
             if let Some((count, node)) = self.stack.last_mut() {
                 match node {
                     // We can only exit our looping when we get "real" nodes
+                    // This includes fragments and components when they're empty (have a single root)
                     VNode::Element(_) | VNode::Text(_) => {
                         // We've recursed INTO an element/text
                         // We need to recurse *out* of it and move forward to the next
@@ -46,11 +41,17 @@ impl<'a> Iterator for RealChildIterator<'a> {
 
                     // If we get a fragment we push the next child
                     VNode::Fragment(frag) => {
-                        let _count = *count as usize;
-                        if _count >= frag.children.len() {
+                        let subcount = *count as usize;
+
+                        if frag.children.len() == 0 {
+                            should_pop = true;
+                            returned_node = Some(&**node);
+                        }
+
+                        if subcount >= frag.children.len() {
                             should_pop = true;
                         } else {
-                            should_push = Some(&frag.children[_count]);
+                            should_push = Some(&frag.children[subcount]);
                         }
                     }
 
@@ -88,77 +89,3 @@ impl<'a> Iterator for RealChildIterator<'a> {
         returned_node
     }
 }
-
-mod tests {
-    use super::*;
-    use crate as dioxus;
-    use crate::innerlude::*;
-    use crate::util::DebugDom;
-    use dioxus_core_macro::*;
-
-    // #[test]
-    // fn test_child_iterator() {
-    //     static App: FC<()> = |cx| {
-    //         cx.render(rsx! {
-    //             Fragment {
-    //                 div {}
-    //                 h1 {}
-    //                 h2 {}
-    //                 h3 {}
-    //                 Fragment {
-    //                     "internal node"
-    //                     div {
-    //                         "baller text shouldn't show up"
-    //                     }
-    //                     p {
-
-    //                     }
-    //                     Fragment {
-    //                         Fragment {
-    //                             "wow you really like framgents"
-    //                             Fragment {
-    //                                 "why are you like this"
-    //                                 Fragment {
-    //                                     "just stop now please"
-    //                                     Fragment {
-    //                                         "this hurts"
-    //                                         Fragment {
-    //                                             "who needs this many fragments?????"
-    //                                             Fragment {
-    //                                                 "just... fine..."
-    //                                                 Fragment {
-    //                                                     "no"
-    //                                                 }
-    //                                             }
-    //                                         }
-    //                                     }
-    //                                 }
-    //                             }
-    //                         }
-    //                     }
-    //                 }
-    //                 "my text node 1"
-    //                 "my text node 2"
-    //                 "my text node 3"
-    //                 "my text node 4"
-    //             }
-    //         })
-    //     };
-    //     let mut dom = VirtualDom::new(App);
-    //     let mut renderer = DebugDom::new();
-    //     dom.rebuild(&mut renderer).unwrap();
-    //     let starter = dom.base_scope().root();
-    //     let ite = RealChildIterator::new(starter, &dom.components);
-    //     for child in ite {
-    //         match child {
-    //             VNode::Element(el) => println!("Found: Element {}", el.tag_name),
-    //             VNode::Text(t) => println!("Found: Text {:?}", t.text),
-
-    //             // These would represent failing cases.
-    //             VNode::Fragment(_) => panic!("Found: Fragment"),
-    //             VNode::Suspended { real } => panic!("Found: Suspended"),
-    //             VNode::Component(_) => panic!("Found: Component"),
-    //         }
-    //     }
-    // }
-}

+ 196 - 93
packages/core/src/diff.rs

@@ -1,16 +1,14 @@
 //! This module contains the stateful DiffMachine and all methods to diff VNodes, their properties, and their children.
 //! The DiffMachine calculates the diffs between the old and new frames, updates the new nodes, and modifies the real dom.
 //!
-//! Notice:
-//! ------
-//!
+//! ## Notice:
 //! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support
 //! Components, Fragments, Suspense, SubTree memoization, and additional batching operations.
 //!
 //! ## Implementation Details:
 //!
 //! ### IDs for elements
-//!
+//! --------------------
 //! All nodes are addressed by their IDs. The RealDom provides an imperative interface for making changes to these nodes.
 //! We don't necessarily require that DOM changes happen instnatly during the diffing process, so the implementor may choose
 //! to batch nodes if it is more performant for their application. The expectation is that renderers use a Slotmap for nodes
@@ -21,6 +19,7 @@
 //! brick the user's page.
 //!
 //! ## Subtree Memoization
+//! -----------------------
 //! We also employ "subtree memoization" which saves us from having to check trees which take no dynamic content. We can
 //! detect if a subtree is "static" by checking if its children are "static". Since we dive into the tree depth-first, the
 //! calls to "create" propogate this information upwards. Structures like the one below are entirely static:
@@ -30,11 +29,21 @@
 //! Because the subtrees won't be diffed, their "real node" data will be stale (invalid), so its up to the reconciler to
 //! track nodes created in a scope and clean up all relevant data. Support for this is currently WIP
 //!
+//! ## Bloom Filter and Heuristics
+//! ------------------------------
+//! For all components, we employ some basic heuristics to speed up allocations and pre-size bump arenas. The heuristics are
+//! currently very rough, but will get better as time goes on. For FFI, we recommend using a bloom filter to cache strings.
+//!
+//! ## Garbage Collection
+//! ---------------------
+//! We roughly place the role of garbage collection onto the reconciler. Dioxus needs to manage the lifecycle of components
+//! but will not spend any time cleaning up old elements. It's the Reconciler's duty to understand which elements need to
+//! be cleaned up *after* the diffing is completed. The reconciler should schedule this garbage collection as the absolute
+//! lowest priority task, after all edits have been applied.
 //!
 //!
 //! Further Reading and Thoughts
 //! ----------------------------
-//!
 //! There are more ways of increasing diff performance here that are currently not implemented.
 //! More info on how to improve this diffing algorithm:
 //!  - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
@@ -58,11 +67,14 @@ use std::any::Any;
 pub trait RealDom<'a> {
     // Navigation
     fn push_root(&mut self, root: RealDomNode);
+    fn pop(&mut self);
 
     // Add Nodes to the dom
     // add m nodes from the stack
     fn append_children(&mut self, many: u32);
+
     // replace the n-m node on the stack with the m nodes
+    // ends with the last element of the chain on the top of the stack
     fn replace_with(&mut self, many: u32);
 
     // Remove Nodesfrom the dom
@@ -138,23 +150,14 @@ where
             seen_nodes: FxHashSet::default(),
         }
     }
+
     // Diff the `old` node with the `new` node. Emits instructions to modify a
     // physical DOM node that reflects `old` into something that reflects `new`.
     //
-    // Upon entry to this function, the physical DOM node must be on the top of the
-    // change list stack:
-    //
-    //     [... node]
+    // the real stack should be what it is coming in and out of this function (ideally empty)
     //
-    // The change list stack is in the same state when this function exits.
-    // In the case of Fragments, the parent node is on the stack
+    // each function call assumes the stack is fresh (empty).
     pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {
-        /*
-        For each valid case, we "commit traversal", meaning we save this current position in the tree.
-        Then, we diff and queue an edit event (via chagelist). s single trees - when components show up, we save that traversal and then re-enter later.
-        When re-entering, we reuse the EditList in DiffState
-        */
-        // log::debug!("diffing...");
         match (old_node, new_node) {
             // Handle the "sane" cases first.
             // The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment".
@@ -168,7 +171,9 @@ where
                     self.dom.push_root(old.dom_id.get());
                     log::debug!("Text has changed {}, {}", old.text, new.text);
                     self.dom.set_text(new.text);
+                    self.dom.pop();
                 }
+
                 new.dom_id.set(old.dom_id.get());
             }
 
@@ -181,13 +186,19 @@ where
                     self.dom.push_root(old.dom_id.get());
                     let meta = self.create(new_node);
                     self.dom.replace_with(meta.added_to_stack);
+                    self.dom.pop();
                     return;
                 }
-                new.dom_id.set(old.dom_id.get());
 
+                let oldid = old.dom_id.get();
+                new.dom_id.set(oldid);
+
+                // push it just in case
+                self.dom.push_root(oldid);
                 self.diff_listeners(old.listeners, new.listeners);
                 self.diff_attr(old.attributes, new.attributes, new.namespace);
                 self.diff_children(old.children, new.children);
+                self.dom.pop();
             }
 
             (VNode::Component(old), VNode::Component(new)) => {
@@ -278,44 +289,47 @@ where
                 if old.children.len() == new.children.len() {}
 
                 self.diff_children(old.children, new.children);
-                // todo!()
             }
 
-            // Okay - these are the "insane" cases where the structure is entirely different.
-            // The factory and rsx! APIs don't really produce structures like this, so we don't take any too complicated
-            // code paths.
-
-            // in the case where the old node was a fragment but the new nodes are text,
-            (VNode::Fragment(_) | VNode::Component(_), VNode::Element(_) | VNode::Text(_)) => {
-                // find the first real element int the old node
-                // let mut iter = RealChildIterator::new(old_node, self.components);
-                // if let Some(first) = iter.next() {
-                //     // replace the very first node with the creation of the element or text
-                // } else {
-                //     // there are no real elements in the old fragment...
-                //     // We need to load up the next real
-                // }
-                // if let VNode::Component(old) = old_node {
-                //     // schedule this component's destructor to be run
-                //     todo!()
-                // }
-            }
-            // In the case where real nodes are being replaced by potentially
-            (VNode::Element(_) | VNode::Text(_), VNode::Fragment(new)) => {
-                //
-            }
-            (VNode::Text(_), VNode::Element(_)) => {
-                self.create(new_node);
-                self.dom.replace_with(1);
-            }
-            (VNode::Element(_), VNode::Text(_)) => {
-                self.create(new_node);
-                self.dom.replace_with(1);
-            }
+            // The strategy here is to pick the first possible node from the previous set and use that as our replace with root
+            // We also walk the "real node" list to make sure all latent roots are claened up
+            // This covers the case any time a fragment or component shows up with pretty much anything else
+            (
+                VNode::Component(_) | VNode::Fragment(_) | VNode::Text(_) | VNode::Element(_),
+                VNode::Component(_) | VNode::Fragment(_) | VNode::Text(_) | VNode::Element(_),
+            ) => {
+                // Choose the node to use as the placeholder for replacewith
+                let back_node = match old_node {
+                    VNode::Element(_) | VNode::Text(_) => old_node
+                        .get_mounted_id(&self.components)
+                        .expect("Element and text always have a real node"),
+
+                    _ => {
+                        let mut old_iter = RealChildIterator::new(old_node, &self.components);
+
+                        let back_node = old_iter
+                            .next()
+                            .expect("Empty fragments should generate a placeholder.");
+
+                        // remove any leftovers
+                        for to_remove in old_iter {
+                            self.dom.push_root(to_remove);
+                            self.dom.remove();
+                        }
 
-            _ => {
-                //
+                        back_node
+                    }
+                };
+
+                // replace the placeholder or first node with the nodes generated from the "new"
+                self.dom.push_root(back_node);
+                let meta = self.create(new_node);
+                self.dom.replace_with(meta.added_to_stack);
             }
+
+            // TODO
+            (VNode::Suspended { .. }, _) => todo!(),
+            (_, VNode::Suspended { .. }) => todo!(),
         }
     }
 }
@@ -480,23 +494,19 @@ where
                 // Run the scope for one iteration to initialize it
                 new_component.run_scope().unwrap();
 
-                // By using "diff_node" instead of "create", we delegate the mutations to the child
-                // However, "diff_node" always expects a real node on the stack, so we put a placeholder so it knows where to start.
-                //
                 // TODO: we need to delete (IE relcaim this node, otherwise the arena will grow infinitely)
-                let _ = self.dom.create_placeholder();
-                self.diff_node(new_component.old_frame(), new_component.next_frame());
+                let nextnode = new_component.next_frame();
+                let meta = self.create(nextnode);
 
                 // Finally, insert this node as a seen node.
                 self.seen_nodes.insert(idx);
 
-                // Virtual Components don't result in new nodes on the stack
-                // However, we can skip them from future diffing if they take no children, have no props, take no key, etc.
-                CreateMeta::new(vcomponent.is_static, 0)
+                CreateMeta::new(vcomponent.is_static, meta.added_to_stack)
             }
 
             // Fragments are the only nodes that can contain dynamic content (IE through curlies or iterators).
             // We can never ignore their contents, so the prescence of a fragment indicates that we need always diff them.
+            // Fragments will just put all their nodes onto the stack after creation
             VNode::Fragment(frag) => {
                 let mut nodes_added = 0;
                 for child in frag.children.iter().rev() {
@@ -506,6 +516,7 @@ where
                     let new_meta = self.create(child);
                     nodes_added += new_meta.added_to_stack;
                 }
+                log::info!("This fragment added {} nodes to the stack", nodes_added);
 
                 // Never ignore
                 CreateMeta::new(false, nodes_added)
@@ -877,6 +888,39 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
         // KeyedPrefixResult::MoreWorkToDo(shared_prefix_count)
     }
 
+    // Remove all of a node's children.
+    //
+    // The change list stack must have this shape upon entry to this function:
+    //
+    //     [... parent]
+    //
+    // When this function returns, the change list stack is in the same state.
+    pub fn remove_all_children(&mut self, old: &'bump [VNode<'bump>]) {
+        // debug_assert!(self.dom.traversal_is_committed());
+        log::debug!("REMOVING CHILDREN");
+        for _child in old {
+            // registry.remove_subtree(child);
+        }
+        // Fast way to remove all children: set the node's textContent to an empty
+        // string.
+        todo!()
+        // self.dom.set_inner_text("");
+    }
+
+    // Create the given children and append them to the parent node.
+    //
+    // The parent node must currently be on top of the change list stack:
+    //
+    //     [... parent]
+    //
+    // When this function returns, the change list stack is in the same state.
+    pub fn create_and_append_children(&mut self, new: &'bump [VNode<'bump>]) {
+        for child in new {
+            let meta = self.create(child);
+            self.dom.append_children(meta.added_to_stack);
+        }
+    }
+
     // The most-general, expensive code path for keyed children diffing.
     //
     // We find the longest subsequence within `old` of children that are relatively
@@ -1217,39 +1261,6 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
     // Support methods
     // ======================
 
-    // Remove all of a node's children.
-    //
-    // The change list stack must have this shape upon entry to this function:
-    //
-    //     [... parent]
-    //
-    // When this function returns, the change list stack is in the same state.
-    pub fn remove_all_children(&mut self, old: &'bump [VNode<'bump>]) {
-        // debug_assert!(self.dom.traversal_is_committed());
-        log::debug!("REMOVING CHILDREN");
-        for _child in old {
-            // registry.remove_subtree(child);
-        }
-        // Fast way to remove all children: set the node's textContent to an empty
-        // string.
-        todo!()
-        // self.dom.set_inner_text("");
-    }
-
-    // Create the given children and append them to the parent node.
-    //
-    // The parent node must currently be on top of the change list stack:
-    //
-    //     [... parent]
-    //
-    // When this function returns, the change list stack is in the same state.
-    pub fn create_and_append_children(&mut self, new: &'bump [VNode<'bump>]) {
-        for child in new {
-            let meta = self.create(child);
-            self.dom.append_children(meta.added_to_stack);
-        }
-    }
-
     // Remove the current child and all of its following siblings.
     //
     // The change list stack must have this shape upon entry to this function:
@@ -1298,3 +1309,95 @@ enum KeyedPrefixResult {
     // the beginning of `new` and `old` we already processed.
     MoreWorkToDo(usize),
 }
+
+/// This iterator iterates through a list of virtual children and only returns real children (Elements or Text).
+///
+/// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like
+/// "InsertBefore".
+struct RealChildIterator<'a> {
+    scopes: &'a SharedArena,
+
+    // Heuristcally we should never bleed into 5 completely nested fragments/components
+    // Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
+    stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 5]>,
+}
+
+impl<'a> RealChildIterator<'a> {
+    fn new(starter: &'a VNode<'a>, scopes: &'a SharedArena) -> Self {
+        Self {
+            scopes,
+            stack: smallvec::smallvec![(0, starter)],
+        }
+    }
+}
+
+impl<'a> Iterator for RealChildIterator<'a> {
+    type Item = RealDomNode;
+
+    fn next(&mut self) -> Option<RealDomNode> {
+        let mut should_pop = false;
+        let mut returned_node = None;
+        let mut should_push = None;
+
+        while returned_node.is_none() {
+            if let Some((count, node)) = self.stack.last_mut() {
+                match node {
+                    // We can only exit our looping when we get "real" nodes
+                    // This includes fragments and components when they're empty (have a single root)
+                    VNode::Element(_) | VNode::Text(_) => {
+                        // We've recursed INTO an element/text
+                        // We need to recurse *out* of it and move forward to the next
+                        should_pop = true;
+                        returned_node = node.get_mounted_id(&self.scopes);
+                    }
+
+                    // If we get a fragment we push the next child
+                    VNode::Fragment(frag) => {
+                        let subcount = *count as usize;
+
+                        if frag.children.len() == 0 {
+                            should_pop = true;
+                            returned_node = node.get_mounted_id(&self.scopes);
+                        }
+
+                        if subcount >= frag.children.len() {
+                            should_pop = true;
+                        } else {
+                            should_push = Some(&frag.children[subcount]);
+                        }
+                    }
+
+                    // Immediately abort suspended nodes - can't do anything with them yet
+                    // VNode::Suspended => should_pop = true,
+                    VNode::Suspended { real } => todo!(),
+
+                    // For components, we load their root and push them onto the stack
+                    VNode::Component(sc) => {
+                        let scope = self.scopes.try_get(sc.ass_scope.get().unwrap()).unwrap();
+
+                        // Simply swap the current node on the stack with the root of the component
+                        *node = scope.root();
+                    }
+                }
+            } else {
+                // If there's no more items on the stack, we're done!
+                return None;
+            }
+
+            if should_pop {
+                self.stack.pop();
+                if let Some((id, _)) = self.stack.last_mut() {
+                    *id += 1;
+                }
+                should_pop = false;
+            }
+
+            if let Some(push) = should_push {
+                self.stack.push((0, push));
+                should_push = None;
+            }
+        }
+
+        returned_node
+    }
+}

+ 3 - 1
packages/core/src/nodebuilder.rs

@@ -693,10 +693,11 @@ impl<'a> NodeFactory<'a> {
         VNode::Fragment(self.bump().alloc(VFragment {
             children,
             key: NodeKey::new_opt(key),
+            void_root: Cell::new(None),
         }))
     }
 
-    pub fn virtual_child<T: Properties + 'a, C>(
+    pub fn virtual_child<T, C>(
         &self,
         f: FC<T>,
         props: T,
@@ -705,6 +706,7 @@ impl<'a> NodeFactory<'a> {
     ) -> VNode<'a>
     where
         C: 'a + AsRef<[VNode<'a>]>,
+        T: Properties + 'a,
     {
         let children: &'a C = self.bump().alloc(children);
         VNode::Component(self.bump().alloc(crate::nodes::VComponent::new(

+ 10 - 6
packages/core/src/nodes.rs

@@ -182,9 +182,9 @@ impl<'a> VNode<'a> {
         match self {
             VNode::Element(el) => Some(el.dom_id.get()),
             VNode::Text(te) => Some(te.dom_id.get()),
-            VNode::Fragment(_) => todo!(),
+            VNode::Fragment(frag) => frag.void_root.get(),
             VNode::Suspended { .. } => todo!(),
-            VNode::Component(el) => Some(el.mounted_root.get()),
+            VNode::Component(el) => todo!(),
         }
     }
 }
@@ -341,8 +341,7 @@ pub type VCompAssociatedScope = Option<ScopeIdx>;
 pub struct VComponent<'src> {
     pub key: NodeKey<'src>,
 
-    pub mounted_root: Cell<RealDomNode>,
-
+    // pub void_root: Cell<Option<RealDomNode>>,
     pub ass_scope: Cell<VCompAssociatedScope>,
 
     // todo: swap the RC out with
@@ -438,7 +437,7 @@ impl<'a> VComponent<'a> {
             key,
             caller,
             is_static,
-            mounted_root: Cell::new(RealDomNode::empty()),
+            // void_root: Cell::new(None),
         }
     }
 }
@@ -476,6 +475,7 @@ pub fn create_component_caller<'a, P: 'a>(
 pub struct VFragment<'src> {
     pub key: NodeKey<'src>,
     pub children: &'src [VNode<'src>],
+    pub void_root: Cell<Option<RealDomNode>>,
 }
 
 impl<'a> VFragment<'a> {
@@ -485,6 +485,10 @@ impl<'a> VFragment<'a> {
             None => NodeKey(None),
         };
 
-        Self { key, children }
+        Self {
+            key,
+            children,
+            void_root: Cell::new(None),
+        }
     }
 }

+ 6 - 2
packages/core/src/serialize.rs

@@ -18,8 +18,12 @@ pub enum DomEdit<'bump> {
     PushRoot {
         root: u64,
     },
-    AppendChild,
-    ReplaceWith,
+    AppendChildren {
+        many: u32,
+    },
+    ReplaceWith {
+        many: u32,
+    },
     Remove,
     RemoveAllChildren,
     CreateTextNode {

+ 1 - 0
packages/core/src/util.rs

@@ -87,6 +87,7 @@ impl DebugDom {
 }
 impl<'a> RealDom<'a> for DebugDom {
     fn push_root(&mut self, root: RealDomNode) {}
+    fn pop(&mut self) {}
 
     fn append_children(&mut self, many: u32) {}
 

+ 0 - 8
packages/core/src/virtual_dom.rs

@@ -152,8 +152,6 @@ impl VirtualDom {
             })
             .unwrap();
 
-        log::debug!("base scope is {:#?}", base_scope);
-
         Self {
             base_scope,
             event_queue,
@@ -318,12 +316,6 @@ impl VirtualDom {
 
             let (old, new) = (cur_component.old_frame(), cur_component.next_frame());
             diff_machine.diff_node(old, new);
-
-            // log::debug!(
-            //     "Processing update: {:#?} with height {}",
-            //     &update.idx,
-            //     cur_height
-            // );
         }
 
         Ok(())

+ 0 - 99
packages/webview/src/blah.html

@@ -1,99 +0,0 @@
-class OPTABLE {
-PushRoot(self, edit) {
-const id = edit.root;
-const node = self.nodes[id];
-self.stack.push(node);
-}
-AppendChild(self, edit) {
-// todo: prevent merging of text nodes
-const node = self.pop();
-self.top().appendChild(node);
-}
-ReplaceWith(self, edit) {
-const newNode = self.pop();
-const oldNode = self.pop();
-oldNode.replaceWith(newNode);
-self.stack.push(newNode);
-}
-Remove(self, edit) {
-const node = self.stack.pop();
-node.remove();
-}
-RemoveAllChildren(self, edit) {
-// todo - we never actually call this one
-}
-CreateTextNode(self, edit) {
-self.stack.push(document.createTextNode(edit.text));
-}
-CreateElement(self, edit) {
-const tagName = edit.tag;
-console.log(`creating element! ${edit}`);
-self.stack.push(document.createElement(tagName));
-}
-CreateElementNs(self, edit) {
-self.stack.push(document.createElementNS(edit.ns, edit.tag));
-}
-CreatePlaceholder(self, edit) {
-self.stack.push(document.createElement("pre"));
-}
-NewEventListener(self, edit) {
-// todo
-}
-RemoveEventListener(self, edit) {
-// todo
-}
-SetText(self, edit) {
-self.top().textContent = edit.text;
-}
-SetAttribute(self, edit) {
-const name = edit.field;
-const value = edit.value;
-const node = self.top(self.stack);
-node.setAttribute(name, value);
-
-// Some attributes are "volatile" and don't work through `setAttribute`.
-if ((name === "value", self)) {
-node.value = value;
-}
-if ((name === "checked", self)) {
-node.checked = true;
-}
-if ((name === "selected", self)) {
-node.selected = true;
-}
-}
-RemoveAttribute(self, edit) {
-const name = edit.field;
-const node = self.top(self.stack);
-node.removeAttribute(name);
-
-// Some attributes are "volatile" and don't work through `removeAttribute`.
-if ((name === "value", self)) {
-node.value = null;
-}
-if ((name === "checked", self)) {
-node.checked = false;
-}
-if ((name === "selected", self)) {
-node.selected = false;
-}
-}
-}
-
-// const op_table = new OPTABLE();
-// const interpreter = new Interpreter(window.document.body);
-
-// function EditListReceived(rawEditList) {
-// let editList = JSON.parse(rawEditList);
-// console.warn("hnelllo");
-// editList.forEach(function (edit, index) {
-// console.log(edit);
-// op_table[edit.type](interpreter, edit);
-// });
-// }
-
-// async function rinalize() {
-// console.log("initialize...");
-// let edits = await rpc.call("initiate");
-// console.error(edits);
-// }

+ 0 - 81
packages/webview/src/blah.js

@@ -1,81 +0,0 @@
-class OPTABLE {
-  PushRoot(self, edit) {
-    const id = edit.root;
-    const node = self.nodes[id];
-    self.stack.push(node);
-  }
-  AppendChild(self, edit) {
-    // todo: prevent merging of text nodes
-    const node = self.pop();
-    self.top().appendChild(node);
-  }
-  ReplaceWith(self, edit) {
-    const newNode = self.pop();
-    const oldNode = self.pop();
-    oldNode.replaceWith(newNode);
-    self.stack.push(newNode);
-  }
-  Remove(self, edit) {
-    const node = self.stack.pop();
-    node.remove();
-  }
-  RemoveAllChildren(self, edit) {
-    // todo - we never actually call this one
-  }
-  CreateTextNode(self, edit) {
-    self.stack.push(document.createTextNode(edit.text));
-  }
-  CreateElement(self, edit) {
-    const tagName = edit.tag;
-    console.log(`creating element! ${edit}`);
-    self.stack.push(document.createElement(tagName));
-  }
-  CreateElementNs(self, edit) {
-    self.stack.push(document.createElementNS(edit.ns, edit.tag));
-  }
-  CreatePlaceholder(self, edit) {
-    self.stack.push(document.createElement("pre"));
-  }
-  NewEventListener(self, edit) {
-    // todo
-  }
-  RemoveEventListener(self, edit) {
-    // todo
-  }
-  SetText(self, edit) {
-    self.top().textContent = edit.text;
-  }
-  SetAttribute(self, edit) {
-    const name = edit.field;
-    const value = edit.value;
-    const node = self.top(self.stack);
-    node.setAttribute(name, value);
-
-    // Some attributes are "volatile" and don't work through `setAttribute`.
-    if ((name === "value", self)) {
-      node.value = value;
-    }
-    if ((name === "checked", self)) {
-      node.checked = true;
-    }
-    if ((name === "selected", self)) {
-      node.selected = true;
-    }
-  }
-  RemoveAttribute(self, edit) {
-    const name = edit.field;
-    const node = self.top(self.stack);
-    node.removeAttribute(name);
-
-    // Some attributes are "volatile" and don't work through `removeAttribute`.
-    if ((name === "value", self)) {
-      node.value = null;
-    }
-    if ((name === "checked", self)) {
-      node.checked = false;
-    }
-    if ((name === "selected", self)) {
-      node.selected = false;
-    }
-  }
-}

+ 2 - 2
packages/webview/src/dom.rs

@@ -36,11 +36,11 @@ impl<'bump> RealDom<'bump> for WebviewDom<'bump> {
     }
 
     fn append_children(&mut self, many: u32) {
-        self.edits.push(AppendChild);
+        self.edits.push(AppendChildren { many });
     }
 
     fn replace_with(&mut self, many: u32) {
-        self.edits.push(ReplaceWith);
+        self.edits.push(ReplaceWith { many });
     }
 
     fn remove(&mut self) {

+ 129 - 116
packages/webview/src/index.html

@@ -3,136 +3,149 @@
 
 <head>
     <!-- <link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet" /> -->
-</head>
 
-<body>
-    <div></div>
-</body>
-<script>
-
-    class Interpreter {
-        constructor(root) {
-            this.stack = [root];
-            this.listeners = {
-
-            };
-            this.lastNodeWasText = false;
-            this.nodes = {
-                0: root
-            };
-        }
+    <script>
+        class Interpreter {
+            constructor(root) {
+                this.stack = [root];
+                this.listeners = {};
+                this.lastNodeWasText = false;
+                this.nodes = {
+                    0: root
+                };
+            }
 
-        top() {
-            return this.stack[this.stack.length - 1];
-        }
+            top() {
+                return this.stack[this.stack.length - 1];
+            }
 
-        pop() {
-            return this.stack.pop();
+            pop() {
+                return this.stack.pop();
+            }
         }
-    }
 
+        class OPTABLE {
+            PushRoot(self, edit) {
+                const id = edit.root;
+                const node = self.nodes[id];
+                self.stack.push(node);
+            }
+            AppendChildren(self, edit) {
+                let root = self.stack[self.stack.length - (edit.many + 1)];
+                for (let i = 0; i < edit.many; i++) {
+                    console.log("popping ", i, edit.many);
+                    let node = self.pop();
+                    root.appendChild(node);
+                }
+            }
+            ReplaceWith(self, edit) {
 
-    class OPTABLE {
-        PushRoot(self, edit) {
-            const id = edit.root;
-            const node = self.nodes[id];
-            self.stack.push(node);
-        }
-        AppendChild(self, edit) {
-            const node = self.pop();
-            self.top().appendChild(node);
-        }
-        ReplaceWith(self, edit) {
-            const newNode = self.pop();
-            const oldNode = self.pop();
-            oldNode.replaceWith(newNode);
-            self.stack.push(newNode);
-        }
-        Remove(self, edit) {
-            const node = self.stack.pop();
-            node.remove();
-        }
-        RemoveAllChildren(self, edit) {
-        }
-        CreateTextNode(self, edit) {
-            self.stack.push(document.createTextNode(edit.text));
-        }
-        CreateElement(self, edit) {
-            const tagName = edit.tag;
-            console.log(`creating element: `, edit);
-            self.stack.push(document.createElement(tagName));
-        }
-        CreateElementNs(self, edit) {
-            self.stack.push(document.createElementNS(edit.ns, edit.tag));
-        }
-        CreatePlaceholder(self, edit) {
-            self.stack.push(document.createElement("pre"));
-        }
-        NewEventListener(self, edit) {
-        }
-        RemoveEventListener(self, edit) {
-        }
-        SetText(self, edit) {
-            self.top().textContent = edit.text;
-        }
-        SetAttribute(self, edit) {
-            const name = edit.field;
-            const value = edit.value;
-            const node = self.top(self.stack);
-            node.setAttribute(name, value);
+                let root = self.stack[self.stack.length - (edit.many + 1)];
+                let els = [];
+
+                for (let i = 0; i < edit.many; i++) {
+                    els.push(self.pop());
+                }
 
-            if ((name === "value", self)) {
-                node.value = value;
+                root.replaceWith(...els);
             }
-            if ((name === "checked", self)) {
-                node.checked = true;
+            Remove(self, edit) {
+                const node = self.stack.pop();
+                node.remove();
             }
-            if ((name === "selected", self)) {
-                node.selected = true;
+            RemoveAllChildren(self, edit) {
             }
-        }
-        RemoveAttribute(self, edit) {
-            const name = edit.field;
-            const node = self.top(self.stack);
-            node.removeAttribute(name);
-
-            if ((name === "value", self)) {
-                node.value = null;
+            CreateTextNode(self, edit) {
+                self.stack.push(document.createTextNode(edit.text));
+            }
+            CreateElement(self, edit) {
+                const tagName = edit.tag;
+                console.log(`creating element: `, edit);
+                self.stack.push(document.createElement(tagName));
+            }
+            CreateElementNs(self, edit) {
+                self.stack.push(document.createElementNS(edit.ns, edit.tag));
+            }
+            CreatePlaceholder(self, edit) {
+                const a = `self.stack.push(document.createElement("pre"))`;
+                self.stack.push(document.createComment("vroot"));
+            }
+            NewEventListener(self, edit) {
             }
-            if ((name === "checked", self)) {
-                node.checked = false;
+            RemoveEventListener(self, edit) {
             }
-            if ((name === "selected", self)) {
-                node.selected = false;
+            SetText(self, edit) {
+                self.top().textContent = edit.text;
+            }
+            SetAttribute(self, edit) {
+                const name = edit.field;
+                const value = edit.value;
+                const node = self.top(self.stack);
+                node.setAttribute(name, value);
+
+                if ((name === "value", self)) {
+                    node.value = value;
+                }
+                if ((name === "checked", self)) {
+                    node.checked = true;
+                }
+                if ((name === "selected", self)) {
+                    node.selected = true;
+                }
+            }
+            RemoveAttribute(self, edit) {
+                const name = edit.field;
+                const node = self.top(self.stack);
+                node.removeAttribute(name);
+
+                if ((name === "value", self)) {
+                    node.value = null;
+                }
+                if ((name === "checked", self)) {
+                    node.checked = false;
+                }
+                if ((name === "selected", self)) {
+                    node.selected = false;
+                }
+            }
+        }
+
+        function EditListReceived(rawEditList) {
+            let editList = JSON.parse(rawEditList);
+            console.warn("hnelllo");
+            editList.forEach(function (edit, index) {
+                console.log(edit);
+                op_table[edit.type](interpreter, edit);
+            });
+        }
+
+        const op_table = new OPTABLE();
+
+        async function initialize() {
+            const reply = await rpc.call('initiate');
+            const interpreter = new Interpreter(window.document.getElementById("app").firstChild);
+            console.log(reply);
+
+            for (let x = 0; x < reply.length; x++) {
+                let edit = reply[x];
+                console.log(edit);
+                op_table[edit.type](interpreter, edit);
             }
+
+            console.log("stack completed: ", interpreter.stack);
         }
-    }
-
-
-
-    function EditListReceived(rawEditList) {
-        let editList = JSON.parse(rawEditList);
-        console.warn("hnelllo");
-        editList.forEach(function (edit, index) {
-            console.log(edit);
-            op_table[edit.type](interpreter, edit);
-        });
-    }
-
-    const op_table = new OPTABLE();
-    const interpreter = new Interpreter(window.document.body);
-
-    async function initialize() {
-        const reply = await rpc.call('initiate');
-        console.log(reply);
-        reply.forEach(function (edit, index) {
-            console.log(edit);
-            op_table[edit.type](interpreter, edit);
-        });
-    }
-    console.log("initializing...");
-    initialize();
-</script>
+        console.log("initializing...");
+        initialize();
+    </script>
+</head>
+
+
+
+<body>
+    <div id="app">
+        _
+    </div>
+</body>
 
 
 </html>

+ 27 - 13
packages/webview/src/lib.rs

@@ -47,6 +47,15 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
         root: FC<T>,
         props: T,
         user_builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
+    ) -> anyhow::Result<()> {
+        Self::run_with_edits(root, props, user_builder, None)
+    }
+
+    pub fn run_with_edits(
+        root: FC<T>,
+        props: T,
+        user_builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
+        redits: Option<Vec<DomEdit<'static>>>,
     ) -> anyhow::Result<()> {
         use wry::{
             application::{
@@ -72,21 +81,26 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
             .with_rpc_handler(move |window: &Window, mut req: RpcRequest| {
                 match req.method.as_str() {
                     "initiate" => {
-                        let mut lock = vdom.write().unwrap();
-                        let mut reg_lock = registry.write().unwrap();
-
-                        // Create the thin wrapper around the registry to collect the edits into
-                        let mut real = dom::WebviewDom::new(reg_lock.take().unwrap());
-
-                        // Serialize the edit stream
-                        let edits = {
-                            lock.rebuild(&mut real).unwrap();
-                            serde_json::to_value(&real.edits).unwrap()
+                        let edits = if let Some(edits) = &redits {
+                            serde_json::to_value(edits).unwrap()
+                        } else {
+                            let mut lock = vdom.write().unwrap();
+                            let mut reg_lock = registry.write().unwrap();
+
+                            // Create the thin wrapper around the registry to collect the edits into
+                            let mut real = dom::WebviewDom::new(reg_lock.take().unwrap());
+
+                            // Serialize the edit stream
+                            let edits = {
+                                lock.rebuild(&mut real).unwrap();
+                                serde_json::to_value(&real.edits).unwrap()
+                            };
+
+                            // Give back the registry into its slot
+                            *reg_lock = Some(real.consume());
+                            edits
                         };
 
-                        // Give back the registry into its slot
-                        *reg_lock = Some(real.consume());
-
                         // Return the edits into the webview runtime
                         Some(RpcResponse::new_result(req.id.take(), Some(edits)))
                     }