浏览代码

Merge pull request #32 from DioxusLabs/jk/reworking_components

wip: rework components, safety, and tasks
Jonathan Kelley 3 年之前
父节点
当前提交
0e6c845690
共有 58 个文件被更改,包括 4187 次插入3809 次删除
  1. 1 1
      examples/borrowed.rs
  2. 13 11
      examples/crm.rs
  3. 9 5
      examples/rsx_usage.rs
  4. 1 1
      examples/ssr.rs
  5. 6 4
      examples/weather_app.rs
  6. 2 2
      packages/core-macro/src/rsx/element.rs
  7. 1 3
      packages/core/Cargo.toml
  8. 16 0
      packages/core/README.md
  9. 102 0
      packages/core/architecture.md
  10. 2 2
      packages/core/benches/jsframework.rs
  11. 9 0
      packages/core/examples/props_expand.rs
  12. 44 0
      packages/core/examples/works.rs
  13. 6 41
      packages/core/src/component.rs
  14. 0 17
      packages/core/src/coroutines.rs
  15. 394 268
      packages/core/src/diff.rs
  16. 0 4
      packages/core/src/diff_stack.rs
  17. 0 50
      packages/core/src/heuristics.rs
  18. 20 15
      packages/core/src/hooklist.rs
  19. 165 73
      packages/core/src/lazynodes.rs
  20. 11 30
      packages/core/src/lib.rs
  21. 115 43
      packages/core/src/nodes.rs
  22. 27 45
      packages/core/src/old/bumpframe.rs
  23. 1 1
      packages/core/src/old/childiter.rs
  24. 6 3
      packages/core/src/old/debug_dom.rs
  25. 15 0
      packages/core/src/old/events.rs
  26. 74 74
      packages/core/src/old/hooks.rs
  27. 0 0
      packages/core/src/old/noderef.rs
  28. 3 3
      packages/core/src/old/resources.rs
  29. 105 121
      packages/core/src/old/scheduler.rs
  30. 0 0
      packages/core/src/old/threadsafe.rs
  31. 220 329
      packages/core/src/scope.rs
  32. 290 0
      packages/core/src/scopearena.rs
  33. 0 39
      packages/core/src/tasks.rs
  34. 26 15
      packages/core/src/test_dom.rs
  35. 0 57
      packages/core/src/util.rs
  36. 361 164
      packages/core/src/virtual_dom.rs
  37. 4 4
      packages/core/tests/borrowedstate.rs
  38. 22 21
      packages/core/tests/create_dom.rs
  39. 1 1
      packages/core/tests/diffing.rs
  40. 2 4
      packages/core/tests/display_vdom.rs
  41. 2 2
      packages/core/tests/lifecycle.rs
  42. 2 2
      packages/core/tests/sharedstate.rs
  43. 15 14
      packages/core/tests/vdom_rebuild.rs
  44. 1 1
      packages/desktop/Cargo.toml
  45. 1 4
      packages/desktop/src/desktop_context.rs
  46. 1 0
      packages/desktop/src/err.rs
  47. 6 6
      packages/desktop/src/events.rs
  48. 0 1
      packages/desktop/src/lib.rs
  49. 9 8
      packages/hooks/src/use_shared_state.rs
  50. 0 1
      packages/hooks/src/useref.rs
  51. 0 1
      packages/hooks/src/usestate.rs
  52. 8 3
      packages/html/Cargo.toml
  53. 0 33
      packages/html/src/attrval.rs
  54. 1126 0
      packages/html/src/elements.rs
  55. 5 91
      packages/html/src/events.rs
  56. 925 0
      packages/html/src/global_attributes.rs
  57. 7 2183
      packages/html/src/lib.rs
  58. 5 8
      src/lib.rs

+ 1 - 1
examples/borrowed.rs

@@ -21,7 +21,7 @@ fn main() {
 }
 
 fn App((cx, props): Scope<()>) -> Element {
-    let text: &mut Vec<String> = cx.use_hook(|_| vec![String::from("abc=def")], |f| f, |_| {});
+    let text: &mut Vec<String> = cx.use_hook(|_| vec![String::from("abc=def")], |f| f);
 
     let first = text.get_mut(0).unwrap();
 

+ 13 - 11
examples/crm.rs

@@ -90,15 +90,17 @@ static App: FC<()> = |(cx, _)| {
         }
     };
 
-    rsx!(cx, body {
-        link {
-            rel: "stylesheet"
-            href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css"
-            integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5"
-            crossorigin: "anonymous"
-        }
-        margin_left: "35%"
-        h1 {"Dioxus CRM Example"}
-        {scene}
-    })
+    cx.render(rsx!(
+        body {
+           link {
+               rel: "stylesheet"
+               href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css"
+               integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5"
+               crossorigin: "anonymous"
+           }
+           margin_left: "35%"
+           h1 {"Dioxus CRM Example"}
+           {scene}
+       }
+    ))
 };

+ 9 - 5
examples/rsx_usage.rs

@@ -102,12 +102,9 @@ pub static Example: FC<()> = |(cx, props)| {
             }}
 
             // Matching
-            // Matching will throw a Rust error about "no two closures are the same type"
-            // To fix this, call "render" method or use the "in" syntax to produce VNodes.
-            // There's nothing we can do about it, sorry :/ (unless you want *really* unhygienic macros)
             {match true {
-                true => rsx!(cx, h1 {"Top text"}),
-                false => cx.render(rsx!( h1 {"Bottom text"}))
+                true => rsx!( h1 {"Top text"}),
+                false => rsx!( h1 {"Bottom text"})
             }}
 
             // Conditional rendering
@@ -174,10 +171,17 @@ pub static Example: FC<()> = |(cx, props)| {
 
             // Can take children too!
             Taller { a: "asd", div {"hello world!"} }
+
+            // helper functions
+            {helper(cx, "hello world!")}
         }
     })
 };
 
+fn helper(cx: Context, text: &str) -> Element {
+    rsx!(cx, p { "{text}" })
+}
+
 mod baller {
     use super::*;
     #[derive(Props, PartialEq)]

+ 1 - 1
examples/ssr.rs

@@ -21,6 +21,6 @@ static App: FC<()> = |(cx, props)| {
 struct MyProps<'a> {
     text: &'a str,
 }
-fn App2<'a>(cx: Context<'a>, props: &'a MyProps) -> Element<'a> {
+fn App2(cx: Context, props: &MyProps) -> Element {
     None
 }

+ 6 - 4
examples/weather_app.rs

@@ -24,14 +24,16 @@ static App: FC<()> = |(cx, props)| {
                 .await
                 .unwrap();
         },
-        |cx, props| {
+        |props| {
             //
-            rsx!(WeatherDisplay {})
+            cx.render(rsx!(WeatherDisplay {}))
         },
     );
 
-    rsx!(cx, div {
-        {body}
+    cx.render(rsx! {
+        div {
+            {body}
+        }
     })
 };
 

+ 2 - 2
packages/core-macro/src/rsx/element.rs

@@ -210,12 +210,12 @@ impl ToTokens for ElementAttrNamed {
 
             ElementAttr::EventClosure { name, closure } => {
                 quote! {
-                    dioxus::events::on::#name(__cx, #closure)
+                    dioxus_elements::on::#name(__cx, #closure)
                 }
             }
             ElementAttr::EventTokens { name, tokens } => {
                 quote! {
-                    dioxus::events::on::#name(__cx, #tokens)
+                    dioxus_elements::on::#name(__cx, #tokens)
                 }
             }
         };

+ 1 - 3
packages/core/Cargo.toml

@@ -40,8 +40,6 @@ indexmap = "1.7.0"
 # Serialize the Edits for use in Webview/Liveview instances
 serde = { version = "1", features = ["derive"], optional = true }
 
-serde_repr = { version = "0.1.7", optional = true }
-
 [dev-dependencies]
 anyhow = "1.0.42"
 dioxus-html = { path = "../html" }
@@ -54,7 +52,7 @@ dioxus-core-macro = { path = "../core-macro", version = "0.1.2" }
 
 [features]
 default = []
-serialize = ["serde", "serde_repr"]
+serialize = ["serde"]
 debug_vdom = []
 
 [[bench]]

+ 16 - 0
packages/core/README.md

@@ -40,3 +40,19 @@ We have big goals for Dioxus. The final implementation must:
 - Be "live". Components should be able to be both server rendered and client rendered without needing frontend APIs.
 - Be modular. Components and hooks should be work anywhere without worrying about target platform.
 
+
+
+## Safety
+
+Dioxus deals with arenas, lifetimes, asynchronous tasks, custom allocators, pinning, and a lot more foundational low-level work that is very difficult to implement with 0 unsafe.
+
+If you don't want to use a crate that uses unsafe, then this crate is not for you.
+
+however, we are always interested in decreasing the scope of the core VirtualDom to make it easier to review.
+
+We'd also be happy to welcome PRs that can eliminate unsafe code while still upholding the numerous variants required to execute certain features.
+
+There's a few invariants that are very important:
+
+- References to `ScopeInner` and `Props` passed into components are *always* valid for as long as the component exists. Even if the scope backing is resized to fit more scopes, the scope has to stay the same place in memory.
+

+ 102 - 0
packages/core/architecture.md

@@ -99,3 +99,105 @@ Internally, the flow of suspense works like this:
 9. diff that node with the new node with a low priority on its own fiber
 10. return the patches back to the event loop
 11. apply the patches to the real dom
+
+/*
+Welcome to Dioxus's cooperative, priority-based scheduler.
+
+I hope you enjoy your stay.
+
+Some essential reading:
+- https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js#L197-L200
+- https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js#L440
+- https://github.com/WICG/is-input-pending
+- https://web.dev/rail/
+- https://indepth.dev/posts/1008/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react
+
+# What's going on?
+
+Dioxus is a framework for "user experience" - not just "user interfaces." Part of the "experience" is keeping the UI
+snappy and "jank free" even under heavy work loads. Dioxus already has the "speed" part figured out - but there's no
+point in being "fast" if you can't also be "responsive."
+
+As such, Dioxus can manually decide on what work is most important at any given moment in time. With a properly tuned
+priority system, Dioxus can ensure that user interaction is prioritized and committed as soon as possible (sub 100ms).
+The controller responsible for this priority management is called the "scheduler" and is responsible for juggling many
+different types of work simultaneously.
+
+# How does it work?
+
+Per the RAIL guide, we want to make sure that A) inputs are handled ASAP and B) animations are not blocked.
+React-three-fiber is a testament to how amazing this can be - a ThreeJS scene is threaded in between work periods of
+React, and the UI still stays snappy!
+
+While it's straightforward to run code ASAP and be as "fast as possible", what's not  _not_ straightforward is how to do
+this while not blocking the main thread. The current prevailing thought is to stop working periodically so the browser
+has time to paint and run animations. When the browser is finished, we can step in and continue our work.
+
+React-Fiber uses the "Fiber" concept to achieve a pause-resume functionality. This is worth reading up on, but not
+necessary to understand what we're doing here. In Dioxus, our DiffMachine is guided by DiffInstructions - essentially
+"commands" that guide the Diffing algorithm through the tree. Our "diff_scope" method is async - we can literally pause
+our DiffMachine "mid-sentence" (so to speak) by just stopping the poll on the future. The DiffMachine periodically yields
+so Rust's async machinery can take over, allowing us to customize when exactly to pause it.
+
+React's "should_yield" method is more complex than ours, and I assume we'll move in that direction as Dioxus matures. For
+now, Dioxus just assumes a TimeoutFuture, and selects! on both the Diff algorithm and timeout. If the DiffMachine finishes
+before the timeout, then Dioxus will work on any pending work in the interim. If there is no pending work, then the changes
+are committed, and coroutines are polled during the idle period. However, if the timeout expires, then the DiffMachine
+future is paused and saved (self-referentially).
+
+# Priority System
+
+So far, we've been able to thread our Dioxus work between animation frames - the main thread is not blocked! But that
+doesn't help us _under load_. How do we still stay snappy... even if we're doing a lot of work? Well, that's where
+priorities come into play. The goal with priorities is to schedule shorter work as a "high" priority and longer work as
+a "lower" priority. That way, we can interrupt long-running low-priority work with short-running high-priority work.
+
+React's priority system is quite complex.
+
+There are 5 levels of priority and 2 distinctions between UI events (discrete, continuous). I believe React really only
+uses 3 priority levels and "idle" priority isn't used... Regardless, there's some batching going on.
+
+For Dioxus, we're going with a 4 tier priority system:
+- Sync: Things that need to be done by the next frame, like TextInput on controlled elements
+- High: for events that block all others - clicks, keyboard, and hovers
+- Medium: for UI events caused by the user but not directly - scrolls/forms/focus (all other events)
+- Low: set_state called asynchronously, and anything generated by suspense
+
+In "Sync" state, we abort our "idle wait" future, and resolve the sync queue immediately and escape. Because we completed
+work before the next rAF, any edits can be immediately processed before the frame ends. Generally though, we want to leave
+as much time to rAF as possible. "Sync" is currently only used by onInput - we'll leave some docs telling people not to
+do anything too arduous from onInput.
+
+For the rest, we defer to the rIC period and work down each queue from high to low.
+*/
+
+
+
+Strategy:
+- When called, check for any UI events that might've been received since the last frame.
+- Dump all UI events into a "pending discrete" queue and a "pending continuous" queue.
+
+- If there are any pending discrete events, then elevate our priority level. If our priority level is already "high,"
+    then we need to finish the high priority work first. If the current work is "low" then analyze what scopes
+    will be invalidated by this new work. If this interferes with any in-flight medium or low work, then we need
+    to bump the other work out of the way, or choose to process it so we don't have any conflicts.
+    'static components have a leg up here since their work can be re-used among multiple scopes.
+    "High priority" is only for blocking! Should only be used on "clicks"
+
+- If there are no pending discrete events, then check for continuous events. These can be completely batched
+
+- we batch completely until we run into a discrete event
+- all continuous events are batched together
+- so D C C C C C would be two separate events - D and C. IE onclick and onscroll
+- D C C C C C C D C C C D would be D C D C D in 5 distinct phases.
+
+- !listener bubbling is not currently implemented properly and will need to be implemented somehow in the future
+    - we need to keep track of element parents to be able to traverse properly
+
+
+Open questions:
+- what if we get two clicks from the component during the same slice?
+    - should we batch?
+    - react says no - they are continuous
+    - but if we received both - then we don't need to diff, do we? run as many as we can and then finally diff?
+

+ 2 - 2
packages/core/benches/jsframework.rs

@@ -24,7 +24,7 @@ criterion_group!(mbenches, create_rows);
 criterion_main!(mbenches);
 
 fn create_rows(c: &mut Criterion) {
-    static App: FC<()> = |(cx, _)| {
+    static App: FC<()> = |cx, _| {
         let mut rng = SmallRng::from_entropy();
         let rows = (0..10_000_usize).map(|f| {
             let label = Label::new(&mut rng);
@@ -58,7 +58,7 @@ struct RowProps {
     row_id: usize,
     label: Label,
 }
-fn Row((cx, props): Scope<RowProps>) -> Element {
+fn Row(cx: Context, props: &RowProps) -> Element {
     let [adj, col, noun] = props.label.0;
     cx.render(rsx! {
         tr {

+ 9 - 0
packages/core/examples/props_expand.rs

@@ -0,0 +1,9 @@
+use dioxus_core as dioxus;
+use dioxus_core_macro::*;
+
+fn main() {}
+
+#[derive(Props)]
+struct ChildProps<'a> {
+    name: &'a str,
+}

+ 44 - 0
packages/core/examples/works.rs

@@ -0,0 +1,44 @@
+use dioxus::prelude::*;
+use dioxus_core as dioxus;
+use dioxus_core_macro::*;
+use dioxus_html as dioxus_elements;
+
+fn main() {
+    let _ = VirtualDom::new(Parent);
+}
+
+fn Parent(cx: Context, props: &()) -> Element {
+    let value = cx.use_hook(|_| String::new(), |f| &*f);
+
+    cx.render(rsx! {
+        div {
+            Child { name: value }
+            Fragment { "asd" }
+        }
+    })
+}
+
+#[derive(Props)]
+struct ChildProps<'a> {
+    name: &'a str,
+}
+
+fn Child(cx: Context, props: &ChildProps) -> Element {
+    cx.render(rsx! {
+        div {
+            h1 { "it's nested" }
+            Child2 { name: props.name }
+        }
+    })
+}
+
+#[derive(Props)]
+struct Grandchild<'a> {
+    name: &'a str,
+}
+
+fn Child2(cx: Context, props: &Grandchild) -> Element {
+    cx.render(rsx! {
+        div { "Hello {props.name}!" }
+    })
+}

+ 6 - 41
packages/core/src/component.rs

@@ -5,43 +5,7 @@
 //! if the type supports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder
 //! that ensures compile-time required and optional fields on cx.
 
-use crate::{
-    innerlude::{Context, Element, VAnchor, VFragment, VNode},
-    LazyNodes, ScopeChildren,
-};
-/// A component is a wrapper around a Context and some Props that share a lifetime
-///
-///
-/// # Example
-///
-/// With memoized state:
-/// ```rust
-/// struct State {}
-///
-/// fn Example((cx, props): Scope<State>) -> DomTree {
-///     // ...
-/// }
-/// ```
-///
-/// With borrowed state:
-/// ```rust
-/// struct State<'a> {
-///     name: &'a str
-/// }
-///
-/// fn Example<'a>((cx, props): Scope<'a, State>) -> DomTree<'a> {
-///     // ...
-/// }
-/// ```
-///
-/// With owned state as a closure:
-/// ```rust
-/// static Example: FC<()> = |(cx, props)| {
-///     // ...
-/// };
-/// ```
-///
-pub type Scope<'a, T> = (Context<'a>, &'a T);
+use crate::innerlude::{Context, Element, LazyNodes, ScopeChildren};
 
 pub struct FragmentProps<'a> {
     children: ScopeChildren<'a>,
@@ -51,7 +15,7 @@ pub struct FragmentBuilder<'a, const BUILT: bool> {
     children: Option<ScopeChildren<'a>>,
 }
 impl<'a> FragmentBuilder<'a, false> {
-    pub fn children(mut self, children: ScopeChildren<'a>) -> FragmentBuilder<'a, true> {
+    pub fn children(self, children: ScopeChildren<'a>) -> FragmentBuilder<'a, true> {
         FragmentBuilder {
             children: Some(children),
         }
@@ -75,7 +39,7 @@ impl<'a> Properties for FragmentProps<'a> {
         FragmentBuilder { children: None }
     }
 
-    unsafe fn memoize(&self, other: &Self) -> bool {
+    unsafe fn memoize(&self, _other: &Self) -> bool {
         false
     }
 }
@@ -102,7 +66,7 @@ impl<'a> Properties for FragmentProps<'a> {
 /// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
 ///
 #[allow(non_upper_case_globals, non_snake_case)]
-pub fn Fragment<'a>((cx, props): Scope<'a, FragmentProps<'a>>) -> Element<'a> {
+pub fn Fragment<'a>(cx: Context<'a>, props: &'a FragmentProps<'a>) -> Element {
     cx.render(Some(LazyNodes::new(|f| {
         f.fragment_from_iter(&props.children)
     })))
@@ -173,6 +137,7 @@ impl EmptyBuilder {
 
 /// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern
 /// to initialize a component's props.
-pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element<'a>) -> T::Builder {
+pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Context<'a>, &'a T) -> Element) -> T::Builder {
+    // pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder {
     T::builder()
 }

+ 0 - 17
packages/core/src/coroutines.rs

@@ -1,17 +0,0 @@
-//! Coroutines are just a "futures unordered" buffer for tasks that can be submitted through the use_coroutine hook.
-//!
-//! The idea here is to move *coroutine* support as a layer on top of *tasks*
-
-use futures_util::{stream::FuturesUnordered, Future};
-
-pub struct CoroutineScheduler {
-    futures: FuturesUnordered<Box<dyn Future<Output = ()>>>,
-}
-
-impl CoroutineScheduler {
-    pub fn new() -> Self {
-        CoroutineScheduler {
-            futures: FuturesUnordered::new(),
-        }
-    }
-}

+ 394 - 268
packages/core/src/diff.rs

@@ -90,6 +90,7 @@
 
 use crate::innerlude::*;
 use fxhash::{FxHashMap, FxHashSet};
+use slab::Slab;
 use DomEdit::*;
 
 /// Our DiffMachine is an iterative tree differ.
@@ -104,76 +105,33 @@ use DomEdit::*;
 ///
 /// Funnily enough, this stack machine's entire job is to create instructions for another stack machine to execute. It's
 /// stack machines all the way down!
-pub(crate) struct DiffMachine<'bump> {
-    pub vdom: &'bump ResourcePool,
+pub struct DiffState<'bump> {
     pub mutations: Mutations<'bump>,
-    pub stack: DiffStack<'bump>,
+    pub(crate) stack: DiffStack<'bump>,
     pub seen_scopes: FxHashSet<ScopeId>,
-    pub cfg: DiffCfg,
-}
-
-pub(crate) struct DiffCfg {
     pub force_diff: bool,
 }
-impl Default for DiffCfg {
-    fn default() -> Self {
-        Self {
-            force_diff: Default::default(),
-        }
-    }
-}
 
-/// a "saved" form of a diff machine
-/// in regular diff machine, the &'bump reference is a stack borrow, but the
-/// bump lifetimes are heap borrows.
-pub(crate) struct SavedDiffWork<'bump> {
-    pub mutations: Mutations<'bump>,
-    pub stack: DiffStack<'bump>,
-    pub seen_scopes: FxHashSet<ScopeId>,
-}
-
-impl<'a> SavedDiffWork<'a> {
-    pub unsafe fn extend(self: SavedDiffWork<'a>) -> SavedDiffWork<'static> {
-        std::mem::transmute(self)
-    }
-
-    pub unsafe fn promote<'b>(self, vdom: &'b ResourcePool) -> DiffMachine<'b> {
-        let extended: SavedDiffWork<'b> = std::mem::transmute(self);
-        DiffMachine {
-            vdom,
-            cfg: DiffCfg::default(),
-            mutations: extended.mutations,
-            stack: extended.stack,
-            seen_scopes: extended.seen_scopes,
-        }
-    }
-}
-
-impl<'bump> DiffMachine<'bump> {
-    pub(crate) fn new(mutations: Mutations<'bump>, shared: &'bump ResourcePool) -> Self {
+impl<'bump> DiffState<'bump> {
+    pub(crate) fn new(mutations: Mutations<'bump>) -> Self {
         Self {
             mutations,
-            cfg: DiffCfg::default(),
             stack: DiffStack::new(),
-            vdom: shared,
-            seen_scopes: FxHashSet::default(),
+            seen_scopes: Default::default(),
+            force_diff: false,
         }
     }
+}
 
-    pub fn save(self) -> SavedDiffWork<'bump> {
-        SavedDiffWork {
-            mutations: self.mutations,
-            stack: self.stack,
-            seen_scopes: self.seen_scopes,
-        }
-    }
+impl<'bump> ScopeArena {
+    pub fn diff_scope(&'bump self, state: &mut DiffState<'bump>, id: &ScopeId) {
+        // if let Some(component) = self.get_scope(id) {
 
-    pub fn diff_scope(&mut self, id: ScopeId) {
-        if let Some(component) = self.vdom.get_scope_mut(id) {
-            let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
-            self.stack.push(DiffInstruction::Diff { new, old });
-            self.work(|| false);
-        }
+        let (old, new) = (self.wip_head(id), self.fin_head(id));
+
+        state.stack.push(DiffInstruction::Diff { old, new });
+        self.work(state, || false);
+        // }
     }
 
     /// Progress the diffing for this "fiber"
@@ -183,14 +141,21 @@ impl<'bump> DiffMachine<'bump> {
     /// We do depth-first to maintain high cache locality (nodes were originally generated recursively).
     ///
     /// Returns a `bool` indicating that the work completed properly.
-    pub fn work(&mut self, mut deadline_expired: impl FnMut() -> bool) -> bool {
-        while let Some(instruction) = self.stack.pop() {
+    pub fn work(
+        &'bump self,
+        state: &mut DiffState<'bump>,
+        mut deadline_expired: impl FnMut() -> bool,
+    ) -> bool {
+        while let Some(instruction) = state.stack.pop() {
             match instruction {
-                DiffInstruction::Diff { old, new } => self.diff_node(old, new),
-                DiffInstruction::Create { node } => self.create_node(node),
-                DiffInstruction::Mount { and } => self.mount(and),
-                DiffInstruction::PrepareMove { node } => self.prepare_move_node(node),
-                DiffInstruction::PopScope => self.stack.pop_off_scope(),
+                DiffInstruction::Diff { old, new } => self.diff_node(state, old, new),
+                DiffInstruction::Create { node } => self.create_node(state, node),
+                DiffInstruction::Mount { and } => self.mount(state, and),
+                DiffInstruction::PrepareMove { node } => {
+                    let num_on_stack = self.push_all_nodes(state, node);
+                    state.stack.add_child_count(num_on_stack);
+                }
+                DiffInstruction::PopScope => state.stack.pop_off_scope(),
             };
 
             if deadline_expired() {
@@ -202,48 +167,84 @@ impl<'bump> DiffMachine<'bump> {
         true
     }
 
-    fn prepare_move_node(&mut self, node: &'bump VNode<'bump>) {
-        for el in RealChildIterator::new(node, self.vdom) {
-            self.mutations.push_root(el.mounted_id());
-            self.stack.add_child_count(1);
+    // recursively push all the nodes of a tree onto the stack and return how many are there
+    fn push_all_nodes(
+        &'bump self,
+        state: &mut DiffState<'bump>,
+        node: &'bump VNode<'bump>,
+    ) -> usize {
+        match node {
+            VNode::Text(_) | VNode::Anchor(_) | VNode::Suspended(_) => {
+                state.mutations.push_root(node.mounted_id());
+                1
+            }
+
+            VNode::Linked(linked) => {
+                todo!("load linked");
+                0
+                // let num_on_stack = linked.children.iter().map(|child| {
+                //     self.push_all_nodes(state, child)
+                // }).sum();
+                // state.mutations.push_root(node.mounted_id());
+                // num_on_stack + 1
+            }
+
+            VNode::Fragment(_) | VNode::Component(_) => {
+                //
+                let mut added = 0;
+                for child in node.children() {
+                    added += self.push_all_nodes(state, child);
+                }
+                added
+            }
+
+            VNode::Element(el) => {
+                let mut num_on_stack = 0;
+                for child in el.children.iter() {
+                    num_on_stack += self.push_all_nodes(state, child);
+                }
+                state.mutations.push_root(el.dom_id.get().unwrap());
+
+                num_on_stack + 1
+            }
         }
     }
 
-    fn mount(&mut self, and: MountType<'bump>) {
-        let nodes_created = self.stack.pop_nodes_created();
+    fn mount(&'bump self, state: &mut DiffState<'bump>, and: MountType<'bump>) {
+        let nodes_created = state.stack.pop_nodes_created();
         match and {
             // add the nodes from this virtual list to the parent
             // used by fragments and components
             MountType::Absorb => {
-                self.stack.add_child_count(nodes_created);
+                state.stack.add_child_count(nodes_created);
             }
 
             MountType::Replace { old } => {
                 if let Some(old_id) = old.try_mounted_id() {
-                    self.mutations.replace_with(old_id, nodes_created as u32);
-                    self.remove_nodes(Some(old), true);
+                    state.mutations.replace_with(old_id, nodes_created as u32);
+                    self.remove_nodes(state, Some(old), true);
                 } else {
                     if let Some(id) = self.find_first_element_id(old) {
-                        self.mutations.replace_with(id, nodes_created as u32);
+                        state.mutations.replace_with(id, nodes_created as u32);
                     }
-                    self.remove_nodes(Some(old), true);
+                    self.remove_nodes(state, Some(old), true);
                 }
             }
 
             MountType::Append => {
-                self.mutations.edits.push(AppendChildren {
+                state.mutations.edits.push(AppendChildren {
                     many: nodes_created as u32,
                 });
             }
 
             MountType::InsertAfter { other_node } => {
                 let root = self.find_last_element(other_node).unwrap();
-                self.mutations.insert_after(root, nodes_created as u32);
+                state.mutations.insert_after(root, nodes_created as u32);
             }
 
             MountType::InsertBefore { other_node } => {
                 let root = self.find_first_element_id(other_node).unwrap();
-                self.mutations.insert_before(root, nodes_created as u32);
+                state.mutations.insert_before(root, nodes_created as u32);
             }
         }
     }
@@ -252,42 +253,63 @@ impl<'bump> DiffMachine<'bump> {
     //  Tools for creating new nodes
     // =================================
 
-    fn create_node(&mut self, node: &'bump VNode<'bump>) {
+    fn create_node(&'bump self, state: &mut DiffState<'bump>, node: &'bump VNode<'bump>) {
         match node {
-            VNode::Text(vtext) => self.create_text_node(vtext, node),
-            VNode::Suspended(suspended) => self.create_suspended_node(suspended, node),
-            VNode::Anchor(anchor) => self.create_anchor_node(anchor, node),
-            VNode::Element(element) => self.create_element_node(element, node),
-            VNode::Fragment(frag) => self.create_fragment_node(frag),
-            VNode::Component(component) => self.create_component_node(component),
+            VNode::Text(vtext) => self.create_text_node(state, vtext, node),
+            VNode::Suspended(suspended) => self.create_suspended_node(state, suspended, node),
+            VNode::Anchor(anchor) => self.create_anchor_node(state, anchor, node),
+            VNode::Element(element) => self.create_element_node(state, element, node),
+            VNode::Fragment(frag) => self.create_fragment_node(state, frag),
+            VNode::Component(component) => self.create_component_node(state, component),
+            VNode::Linked(linked) => self.create_linked_node(state, linked),
         }
     }
 
-    fn create_text_node(&mut self, vtext: &'bump VText<'bump>, node: &'bump VNode<'bump>) {
-        let real_id = self.vdom.reserve_node(node);
-        self.mutations.create_text_node(vtext.text, real_id);
+    fn create_text_node(
+        &'bump self,
+        state: &mut DiffState<'bump>,
+        vtext: &'bump VText<'bump>,
+        node: &'bump VNode<'bump>,
+    ) {
+        let real_id = self.reserve_node(node);
+        state.mutations.create_text_node(vtext.text, real_id);
         vtext.dom_id.set(Some(real_id));
-        self.stack.add_child_count(1);
+        state.stack.add_child_count(1);
     }
 
-    fn create_suspended_node(&mut self, suspended: &'bump VSuspended, node: &'bump VNode<'bump>) {
-        let real_id = self.vdom.reserve_node(node);
-        self.mutations.create_placeholder(real_id);
+    fn create_suspended_node(
+        &'bump self,
+        state: &mut DiffState<'bump>,
+        suspended: &'bump VSuspended,
+        node: &'bump VNode<'bump>,
+    ) {
+        let real_id = self.reserve_node(node);
+        state.mutations.create_placeholder(real_id);
 
         suspended.dom_id.set(Some(real_id));
-        self.stack.add_child_count(1);
+        state.stack.add_child_count(1);
 
-        self.attach_suspended_node_to_scope(suspended);
+        self.attach_suspended_node_to_scope(state, suspended);
     }
 
-    fn create_anchor_node(&mut self, anchor: &'bump VAnchor, node: &'bump VNode<'bump>) {
-        let real_id = self.vdom.reserve_node(node);
-        self.mutations.create_placeholder(real_id);
+    fn create_anchor_node(
+        &'bump self,
+        state: &mut DiffState<'bump>,
+        anchor: &'bump VAnchor,
+        node: &'bump VNode<'bump>,
+    ) {
+        let real_id = self.reserve_node(node);
+        state.mutations.create_placeholder(real_id);
         anchor.dom_id.set(Some(real_id));
-        self.stack.add_child_count(1);
+        state.stack.add_child_count(1);
     }
 
-    fn create_element_node(&mut self, element: &'bump VElement<'bump>, node: &'bump VNode<'bump>) {
+    fn create_element_node(
+        &'bump self,
+        state: &mut DiffState<'bump>,
+        element: &'bump VElement<'bump>,
+        node: &'bump VNode<'bump>,
+    ) {
         let VElement {
             tag_name,
             listeners,
@@ -298,75 +320,90 @@ impl<'bump> DiffMachine<'bump> {
             ..
         } = element;
 
-        let real_id = self.vdom.reserve_node(node);
+        let real_id = self.reserve_node(node);
 
         dom_id.set(Some(real_id));
 
-        self.mutations.create_element(tag_name, *namespace, real_id);
+        state
+            .mutations
+            .create_element(tag_name, *namespace, real_id);
 
-        self.stack.add_child_count(1);
+        state.stack.add_child_count(1);
 
-        if let Some(cur_scope_id) = self.stack.current_scope() {
-            let scope = self.vdom.get_scope(cur_scope_id).unwrap();
+        if let Some(cur_scope_id) = state.stack.current_scope() {
+            let scope = self.get_scope(&cur_scope_id).unwrap();
 
-            listeners.iter().for_each(|listener| {
+            for listener in *listeners {
                 self.attach_listener_to_scope(listener, scope);
                 listener.mounted_node.set(Some(real_id));
-                self.mutations.new_event_listener(listener, cur_scope_id);
-            });
+                state.mutations.new_event_listener(listener, cur_scope_id);
+            }
         } else {
             log::warn!("create element called with no scope on the stack - this is an error for a live dom");
         }
 
         for attr in *attributes {
-            self.mutations.set_attribute(attr, real_id.as_u64());
+            state.mutations.set_attribute(attr, real_id.as_u64());
         }
 
         if !children.is_empty() {
-            self.stack.create_children(children, MountType::Append);
+            state.stack.create_children(children, MountType::Append);
         }
     }
 
-    fn create_fragment_node(&mut self, frag: &'bump VFragment<'bump>) {
-        self.stack.create_children(frag.children, MountType::Absorb);
+    fn create_fragment_node(
+        &'bump self,
+        state: &mut DiffState<'bump>,
+        frag: &'bump VFragment<'bump>,
+    ) {
+        state
+            .stack
+            .create_children(frag.children, MountType::Absorb);
     }
 
-    fn create_component_node(&mut self, vcomponent: &'bump VComponent<'bump>) {
-        let caller = vcomponent.caller;
+    fn create_component_node(
+        &'bump self,
+        state: &mut DiffState<'bump>,
+        vcomponent: &'bump VComponent<'bump>,
+    ) {
+        // let caller = vcomponent.caller;
 
-        let parent_idx = self.stack.current_scope().unwrap();
+        let parent_idx = state.stack.current_scope().unwrap();
 
-        let shared = self.vdom.channel.clone();
+        let shared = self.sender.clone();
 
         // Insert a new scope into our component list
-        let parent_scope = self.vdom.get_scope(parent_idx).unwrap();
-
-        let new_idx = self.vdom.insert_scope_with_key(|new_idx| {
-            ScopeInner::new(
-                caller,
-                new_idx,
-                Some(parent_idx),
-                parent_scope.height + 1,
-                parent_scope.subtree(),
-                shared,
-            )
-        });
+        let parent_scope = self.get_scope(&parent_idx).unwrap();
+
+        let new_idx: ScopeId = todo!();
+        // self
+        //     .new_with_key(fc_ptr, vcomp, parent_scope, height, subtree, sender);
+
+        // .(|new_idx| {
+        //     // ScopeInner::new(
+        //         vcomponent,
+        //         new_idx,
+        //         Some(parent_idx),
+        //         parent_scope.height + 1,
+        //         parent_scope.subtree(),
+        //         shared,
+        //     // )
+        // });
 
         // Actually initialize the caller's slot with the right address
         vcomponent.associated_scope.set(Some(new_idx));
 
         if !vcomponent.can_memoize {
-            let cur_scope = self.vdom.get_scope_mut(parent_idx).unwrap();
-            let extended = vcomponent as *const VComponent;
-            let extended: *const VComponent<'static> = unsafe { std::mem::transmute(extended) };
-            cur_scope.borrowed_props.borrow_mut().push(extended);
+            let cur_scope = self.get_scope(&parent_idx).unwrap();
+            let extended = unsafe { std::mem::transmute(vcomponent) };
+            cur_scope.items.get_mut().borrowed_props.push(extended);
         }
 
         // TODO:
         //  add noderefs to current noderef list Noderefs
         //  add effects to current effect list Effects
 
-        let new_component = self.vdom.get_scope_mut(new_idx).unwrap();
+        let new_component = self.get_scope(&new_idx).unwrap();
 
         log::debug!(
             "initializing component {:?} with height {:?}",
@@ -375,58 +412,79 @@ impl<'bump> DiffMachine<'bump> {
         );
 
         // Run the scope for one iteration to initialize it
-        if new_component.run_scope(self.vdom) {
-            // Take the node that was just generated from running the component
-            let nextnode = new_component.frames.fin_head();
-            self.stack.create_component(new_idx, nextnode);
+        //
+        todo!("run scope");
+        // if new_component.run_scope(self) {
+        //     // Take the node that was just generated from running the component
+        //     let nextnode = new_component.frames.fin_head();
+        //     state.stack.create_component(new_idx, nextnode);
 
-            //
-            /*
-            tree_item {
+        //     //
+        //     /*
+        //     tree_item {
 
-            }
+        //     }
 
-            */
-            if new_component.is_subtree_root.get() {
-                self.stack.push_subtree();
-            }
-        }
+        //     */
+        //     if new_component.is_subtree_root.get() {
+        //         state.stack.push_subtree();
+        //     }
+        // }
 
         // Finally, insert this scope as a seen node.
-        self.seen_scopes.insert(new_idx);
+        state.seen_scopes.insert(new_idx);
+    }
+
+    fn create_linked_node(&'bump self, state: &mut DiffState<'bump>, link: &'bump NodeLink) {
+        todo!()
     }
 
     // =================================
     //  Tools for diffing nodes
     // =================================
 
-    pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {
+    pub fn diff_node(
+        &'bump self,
+        state: &mut DiffState<'bump>,
+        old_node: &'bump VNode<'bump>,
+        new_node: &'bump VNode<'bump>,
+    ) {
         use VNode::*;
         match (old_node, new_node) {
             // Check the most common cases first
-            (Text(old), Text(new)) => self.diff_text_nodes(old, new),
+            (Text(old), Text(new)) => self.diff_text_nodes(state, old, new),
             (Component(old), Component(new)) => {
-                self.diff_component_nodes(old_node, new_node, old, new)
+                self.diff_component_nodes(state, old_node, new_node, old, new)
             }
-            (Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new),
+            (Fragment(old), Fragment(new)) => self.diff_fragment_nodes(state, old, new),
             (Anchor(old), Anchor(new)) => new.dom_id.set(old.dom_id.get()),
-            (Suspended(old), Suspended(new)) => self.diff_suspended_nodes(old, new),
-            (Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node),
+            (Suspended(old), Suspended(new)) => self.diff_suspended_nodes(state, old, new),
+            (Element(old), Element(new)) => {
+                self.diff_element_nodes(state, old, new, old_node, new_node)
+            }
+            (Linked(old), Linked(new)) => self.diff_linked_nodes(state, old, new),
 
             // Anything else is just a basic replace and create
             (
-                Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_) | Suspended(_),
-                Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_) | Suspended(_),
-            ) => self
+                Linked(_) | Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_)
+                | Suspended(_),
+                Linked(_) | Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_)
+                | Suspended(_),
+            ) => state
                 .stack
                 .create_node(new_node, MountType::Replace { old: old_node }),
         }
     }
 
-    fn diff_text_nodes(&mut self, old: &'bump VText<'bump>, new: &'bump VText<'bump>) {
+    fn diff_text_nodes(
+        &'bump self,
+        state: &mut DiffState<'bump>,
+        old: &'bump VText<'bump>,
+        new: &'bump VText<'bump>,
+    ) {
         if let Some(root) = old.dom_id.get() {
             if old.text != new.text {
-                self.mutations.set_text(new.text, root.as_u64());
+                state.mutations.set_text(new.text, root.as_u64());
             }
 
             new.dom_id.set(Some(root));
@@ -434,7 +492,8 @@ impl<'bump> DiffMachine<'bump> {
     }
 
     fn diff_element_nodes(
-        &mut self,
+        &'bump self,
+        state: &mut DiffState<'bump>,
         old: &'bump VElement<'bump>,
         new: &'bump VElement<'bump>,
         old_node: &'bump VNode<'bump>,
@@ -449,11 +508,11 @@ impl<'bump> DiffMachine<'bump> {
         if new.tag_name != old.tag_name || new.namespace != old.namespace {
             // maybe make this an instruction?
             // issue is that we need the "vnode" but this method only has the velement
-            self.stack.push_nodes_created(0);
-            self.stack.push(DiffInstruction::Mount {
+            state.stack.push_nodes_created(0);
+            state.stack.push(DiffInstruction::Mount {
                 and: MountType::Replace { old: old_node },
             });
-            self.create_element_node(new, new_node);
+            self.create_element_node(state, new, new_node);
             return;
         }
 
@@ -473,15 +532,15 @@ impl<'bump> DiffMachine<'bump> {
         if old.attributes.len() == new.attributes.len() {
             for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) {
                 if old_attr.value != new_attr.value || new_attr.is_volatile {
-                    self.mutations.set_attribute(new_attr, root.as_u64());
+                    state.mutations.set_attribute(new_attr, root.as_u64());
                 }
             }
         } else {
             for attribute in old.attributes {
-                self.mutations.remove_attribute(attribute, root.as_u64());
+                state.mutations.remove_attribute(attribute, root.as_u64());
             }
             for attribute in new.attributes {
-                self.mutations.set_attribute(attribute, root.as_u64())
+                state.mutations.set_attribute(attribute, root.as_u64())
             }
         }
 
@@ -493,44 +552,47 @@ impl<'bump> DiffMachine<'bump> {
         // We also need to make sure that all listeners are properly attached to the parent scope (fix_listener)
         //
         // TODO: take a more efficient path than this
-        if let Some(cur_scope_id) = self.stack.current_scope() {
-            let scope = self.vdom.get_scope(cur_scope_id).unwrap();
+        if let Some(cur_scope_id) = state.stack.current_scope() {
+            let scope = self.get_scope(&cur_scope_id).unwrap();
 
             if old.listeners.len() == new.listeners.len() {
                 for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
                     if old_l.event != new_l.event {
-                        self.mutations
+                        state
+                            .mutations
                             .remove_event_listener(old_l.event, root.as_u64());
-                        self.mutations.new_event_listener(new_l, cur_scope_id);
+                        state.mutations.new_event_listener(new_l, cur_scope_id);
                     }
                     new_l.mounted_node.set(old_l.mounted_node.get());
                     self.attach_listener_to_scope(new_l, scope);
                 }
             } else {
                 for listener in old.listeners {
-                    self.mutations
+                    state
+                        .mutations
                         .remove_event_listener(listener.event, root.as_u64());
                 }
                 for listener in new.listeners {
                     listener.mounted_node.set(Some(root));
-                    self.mutations.new_event_listener(listener, cur_scope_id);
+                    state.mutations.new_event_listener(listener, cur_scope_id);
                     self.attach_listener_to_scope(listener, scope);
                 }
             }
         }
 
         if old.children.is_empty() && !new.children.is_empty() {
-            self.mutations.edits.push(PushRoot {
+            state.mutations.edits.push(PushRoot {
                 root: root.as_u64(),
             });
-            self.stack.create_children(new.children, MountType::Append);
+            state.stack.create_children(new.children, MountType::Append);
         } else {
-            self.diff_children(old.children, new.children);
+            self.diff_children(state, old.children, new.children);
         }
     }
 
     fn diff_component_nodes(
-        &mut self,
+        &'bump self,
+        state: &mut DiffState<'bump>,
         old_node: &'bump VNode<'bump>,
         new_node: &'bump VNode<'bump>,
 
@@ -543,50 +605,74 @@ impl<'bump> DiffMachine<'bump> {
         if old.user_fc == new.user_fc {
             log::debug!("Diffing component {:?} - {:?}", new.user_fc, scope_addr);
             //
-            self.stack.scope_stack.push(scope_addr);
+            state.stack.scope_stack.push(scope_addr);
 
             // Make sure the new component vnode is referencing the right scope id
             new.associated_scope.set(Some(scope_addr));
 
             // make sure the component's caller function is up to date
-            let scope = self.vdom.get_scope_mut(scope_addr).unwrap();
-            scope.update_scope_dependencies(new.caller);
+            let scope = self.get_scope(&scope_addr).unwrap();
+            let mut items = scope.items.borrow_mut();
 
             // React doesn't automatically memoize, but we do.
-            let props_are_the_same = old.comparator.unwrap();
+            let props_are_the_same = todo!("reworking component memoization");
+            // let props_are_the_same = todo!("reworking component memoization");
+            // let props_are_the_same = old.comparator.unwrap();
 
-            if self.cfg.force_diff || !props_are_the_same(new) {
-                let succeeded = scope.run_scope(self.vdom);
+            // if self.cfg.force_diff || !props_are_the_same(new) {
+            //     let succeeded = scope.run_scope(self);
 
-                if succeeded {
-                    self.diff_node(scope.frames.wip_head(), scope.frames.fin_head());
-                }
-            }
+            //     if succeeded {
+            //         self.diff_node(scope.frames.wip_head(), scope.frames.fin_head());
+            //     }
+            // }
 
-            self.stack.scope_stack.pop();
+            state.stack.scope_stack.pop();
         } else {
-            self.stack
+            state
+                .stack
                 .create_node(new_node, MountType::Replace { old: old_node });
         }
     }
 
-    fn diff_fragment_nodes(&mut self, old: &'bump VFragment<'bump>, new: &'bump VFragment<'bump>) {
+    fn diff_fragment_nodes(
+        &'bump self,
+        state: &mut DiffState<'bump>,
+        old: &'bump VFragment<'bump>,
+        new: &'bump VFragment<'bump>,
+    ) {
         // This is the case where options or direct vnodes might be used.
         // In this case, it's faster to just skip ahead to their diff
         if old.children.len() == 1 && new.children.len() == 1 {
-            self.diff_node(&old.children[0], &new.children[0]);
+            self.diff_node(state, &old.children[0], &new.children[0]);
             return;
         }
 
         debug_assert!(!old.children.is_empty());
         debug_assert!(!new.children.is_empty());
 
-        self.diff_children(old.children, new.children);
+        self.diff_children(state, old.children, new.children);
     }
 
-    fn diff_suspended_nodes(&mut self, old: &'bump VSuspended, new: &'bump VSuspended) {
+    fn diff_suspended_nodes(
+        &'bump self,
+        state: &mut DiffState<'bump>,
+        old: &'bump VSuspended,
+        new: &'bump VSuspended,
+    ) {
         new.dom_id.set(old.dom_id.get());
-        self.attach_suspended_node_to_scope(new);
+        self.attach_suspended_node_to_scope(state, new);
+    }
+
+    fn diff_linked_nodes(
+        &'bump self,
+        state: &mut DiffState<'bump>,
+        old: &'bump NodeLink,
+        new: &'bump NodeLink,
+    ) {
+        todo!();
+        // new.dom_id.set(old.dom_id.get());
+        // self.attach_linked_node_to_scope(state, new);
     }
 
     // =============================================
@@ -608,26 +694,32 @@ impl<'bump> DiffMachine<'bump> {
     //
     // Fragment nodes cannot generate empty children lists, so we can assume that when a list is empty, it belongs only
     // to an element, and appending makes sense.
-    fn diff_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
+    fn diff_children(
+        &'bump self,
+        state: &mut DiffState<'bump>,
+        old: &'bump [VNode<'bump>],
+        new: &'bump [VNode<'bump>],
+    ) {
         // Remember, fragments can never be empty (they always have a single child)
         match (old, new) {
             ([], []) => {}
             ([], _) => {
                 // we need to push the
-                self.stack.create_children(new, MountType::Append);
+                state.stack.create_children(new, MountType::Append);
             }
             (_, []) => {
-                self.remove_nodes(old, true);
+                self.remove_nodes(state, old, true);
             }
             ([VNode::Anchor(old_anchor)], [VNode::Anchor(new_anchor)]) => {
                 old_anchor.dom_id.set(new_anchor.dom_id.get());
             }
             ([VNode::Anchor(_)], _) => {
-                self.stack
+                state
+                    .stack
                     .create_children(new, MountType::Replace { old: &old[0] });
             }
             (_, [VNode::Anchor(_)]) => {
-                self.replace_and_create_many_with_one(old, &new[0]);
+                self.replace_and_create_many_with_one(state, old, &new[0]);
             }
             _ => {
                 let new_is_keyed = new[0].key().is_some();
@@ -643,9 +735,9 @@ impl<'bump> DiffMachine<'bump> {
                 );
 
                 if new_is_keyed && old_is_keyed {
-                    self.diff_keyed_children(old, new);
+                    self.diff_keyed_children(state, old, new);
                 } else {
-                    self.diff_non_keyed_children(old, new);
+                    self.diff_non_keyed_children(state, old, new);
                 }
             }
         }
@@ -659,20 +751,25 @@ impl<'bump> DiffMachine<'bump> {
     //     [... parent]
     //
     // the change list stack is in the same state when this function returns.
-    fn diff_non_keyed_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
+    fn diff_non_keyed_children(
+        &'bump self,
+        state: &mut DiffState<'bump>,
+        old: &'bump [VNode<'bump>],
+        new: &'bump [VNode<'bump>],
+    ) {
         // Handled these cases in `diff_children` before calling this function.
         debug_assert!(!new.is_empty());
         debug_assert!(!old.is_empty());
 
         for (new, old) in new.iter().zip(old.iter()).rev() {
-            self.stack.push(DiffInstruction::Diff { new, old });
+            state.stack.push(DiffInstruction::Diff { new, old });
         }
 
         use std::cmp::Ordering;
         match old.len().cmp(&new.len()) {
-            Ordering::Greater => self.remove_nodes(&old[new.len()..], true),
+            Ordering::Greater => self.remove_nodes(state, &old[new.len()..], true),
             Ordering::Less => {
-                self.stack.create_children(
+                state.stack.create_children(
                     &new[old.len()..],
                     MountType::InsertAfter {
                         other_node: old.last().unwrap(),
@@ -701,7 +798,12 @@ impl<'bump> DiffMachine<'bump> {
     // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
     //
     // The stack is empty upon entry.
-    fn diff_keyed_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
+    fn diff_keyed_children(
+        &'bump self,
+        state: &mut DiffState<'bump>,
+        old: &'bump [VNode<'bump>],
+        new: &'bump [VNode<'bump>],
+    ) {
         if cfg!(debug_assertions) {
             let mut keys = fxhash::FxHashSet::default();
             let mut assert_unique_keys = |children: &'bump [VNode<'bump>]| {
@@ -729,7 +831,7 @@ impl<'bump> DiffMachine<'bump> {
         //
         // `shared_prefix_count` is the count of how many nodes at the start of
         // `new` and `old` share the same keys.
-        let (left_offset, right_offset) = match self.diff_keyed_ends(old, new) {
+        let (left_offset, right_offset) = match self.diff_keyed_ends(state, old, new) {
             Some(count) => count,
             None => return,
         };
@@ -739,7 +841,7 @@ impl<'bump> DiffMachine<'bump> {
         //     right_offset,
         // );
 
-        // log::debug!("stack before lo is {:#?}", self.stack.instructions);
+        // log::debug!("stack before lo is {:#?}", state.stack.instructions);
         // Ok, we now hopefully have a smaller range of children in the middle
         // within which to re-order nodes with the same keys, remove old nodes with
         // now-unused keys, and create new nodes with fresh keys.
@@ -753,14 +855,14 @@ impl<'bump> DiffMachine<'bump> {
         );
         if new_middle.is_empty() {
             // remove the old elements
-            self.remove_nodes(old_middle, true);
+            self.remove_nodes(state, old_middle, true);
         } else if old_middle.is_empty() {
             // there were no old elements, so just create the new elements
             // we need to find the right "foothold" though - we shouldn't use the "append" at all
             if left_offset == 0 {
                 // insert at the beginning of the old list
                 let foothold = &old[old.len() - right_offset];
-                self.stack.create_children(
+                state.stack.create_children(
                     new_middle,
                     MountType::InsertBefore {
                         other_node: foothold,
@@ -769,7 +871,7 @@ impl<'bump> DiffMachine<'bump> {
             } else if right_offset == 0 {
                 // insert at the end  the old list
                 let foothold = old.last().unwrap();
-                self.stack.create_children(
+                state.stack.create_children(
                     new_middle,
                     MountType::InsertAfter {
                         other_node: foothold,
@@ -778,7 +880,7 @@ impl<'bump> DiffMachine<'bump> {
             } else {
                 // inserting in the middle
                 let foothold = &old[left_offset - 1];
-                self.stack.create_children(
+                state.stack.create_children(
                     new_middle,
                     MountType::InsertAfter {
                         other_node: foothold,
@@ -786,10 +888,10 @@ impl<'bump> DiffMachine<'bump> {
                 );
             }
         } else {
-            self.diff_keyed_middle(old_middle, new_middle);
+            self.diff_keyed_middle(state, old_middle, new_middle);
         }
 
-        log::debug!("stack after km is {:#?}", self.stack.instructions);
+        log::debug!("stack after km is {:#?}", state.stack.instructions);
     }
 
     /// Diff both ends of the children that share keys.
@@ -798,7 +900,8 @@ impl<'bump> DiffMachine<'bump> {
     ///
     /// If there is no offset, then this function returns None and the diffing is complete.
     fn diff_keyed_ends(
-        &mut self,
+        &'bump self,
+        state: &mut DiffState<'bump>,
         old: &'bump [VNode<'bump>],
         new: &'bump [VNode<'bump>],
     ) -> Option<(usize, usize)> {
@@ -809,14 +912,14 @@ impl<'bump> DiffMachine<'bump> {
             if old.key() != new.key() {
                 break;
             }
-            self.stack.push(DiffInstruction::Diff { old, new });
+            state.stack.push(DiffInstruction::Diff { old, new });
             left_offset += 1;
         }
 
         // If that was all of the old children, then create and append the remaining
         // new children and we're finished.
         if left_offset == old.len() {
-            self.stack.create_children(
+            state.stack.create_children(
                 &new[left_offset..],
                 MountType::InsertAfter {
                     other_node: old.last().unwrap(),
@@ -828,7 +931,7 @@ impl<'bump> DiffMachine<'bump> {
         // And if that was all of the new children, then remove all of the remaining
         // old children and we're finished.
         if left_offset == new.len() {
-            self.remove_nodes(&old[left_offset..], true);
+            self.remove_nodes(state, &old[left_offset..], true);
             return None;
         }
 
@@ -839,7 +942,7 @@ impl<'bump> DiffMachine<'bump> {
             if old.key() != new.key() {
                 break;
             }
-            self.diff_node(old, new);
+            self.diff_node(state, old, new);
             right_offset += 1;
         }
 
@@ -859,7 +962,12 @@ impl<'bump> DiffMachine<'bump> {
     // This function will load the appropriate nodes onto the stack and do diffing in place.
     //
     // Upon exit from this function, it will be restored to that same state.
-    fn diff_keyed_middle(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
+    fn diff_keyed_middle(
+        &'bump self,
+        state: &mut DiffState<'bump>,
+        old: &'bump [VNode<'bump>],
+        new: &'bump [VNode<'bump>],
+    ) {
         /*
         1. Map the old keys into a numerical ordering based on indices.
         2. Create a map of old key to its index
@@ -923,7 +1031,7 @@ impl<'bump> DiffMachine<'bump> {
             log::debug!("old_key_to_old_index, {:#?}", old_key_to_old_index);
             log::debug!("new_index_to_old_index, {:#?}", new_index_to_old_index);
             log::debug!("shared_keys, {:#?}", shared_keys);
-            self.replace_and_create_many_with_many(old, new);
+            self.replace_and_create_many_with_many(state, old, new);
             return;
         }
 
@@ -967,15 +1075,15 @@ impl<'bump> DiffMachine<'bump> {
         // add mount instruction for the last items not covered by the lis
         let first_lis = *lis_sequence.first().unwrap();
         if first_lis > 0 {
-            self.stack.push_nodes_created(0);
-            self.stack.push(DiffInstruction::Mount {
+            state.stack.push_nodes_created(0);
+            state.stack.push(DiffInstruction::Mount {
                 and: MountType::InsertBefore {
                     other_node: &new[first_lis],
                 },
             });
 
             for (idx, new_node) in new[..first_lis].iter().enumerate().rev() {
-                apply(idx, new_node, &mut self.stack);
+                apply(idx, new_node, &mut state.stack);
             }
         }
 
@@ -984,14 +1092,14 @@ impl<'bump> DiffMachine<'bump> {
         let mut last = *lis_iter.next().unwrap();
         for next in lis_iter {
             if last - next > 1 {
-                self.stack.push_nodes_created(0);
-                self.stack.push(DiffInstruction::Mount {
+                state.stack.push_nodes_created(0);
+                state.stack.push(DiffInstruction::Mount {
                     and: MountType::InsertBefore {
                         other_node: &new[last],
                     },
                 });
                 for (idx, new_node) in new[(next + 1)..last].iter().enumerate().rev() {
-                    apply(idx + next + 1, new_node, &mut self.stack);
+                    apply(idx + next + 1, new_node, &mut state.stack);
                 }
             }
             last = *next;
@@ -1000,19 +1108,19 @@ impl<'bump> DiffMachine<'bump> {
         // add mount instruction for the first items not covered by the lis
         let last = *lis_sequence.last().unwrap();
         if last < (new.len() - 1) {
-            self.stack.push_nodes_created(0);
-            self.stack.push(DiffInstruction::Mount {
+            state.stack.push_nodes_created(0);
+            state.stack.push(DiffInstruction::Mount {
                 and: MountType::InsertAfter {
                     other_node: &new[last],
                 },
             });
             for (idx, new_node) in new[(last + 1)..].iter().enumerate().rev() {
-                apply(idx + last + 1, new_node, &mut self.stack);
+                apply(idx + last + 1, new_node, &mut state.stack);
             }
         }
 
         for idx in lis_sequence.iter().rev() {
-            self.stack.push(DiffInstruction::Diff {
+            state.stack.push(DiffInstruction::Diff {
                 new: &new[*idx],
                 old: &old[new_index_to_old_index[*idx]],
             });
@@ -1023,7 +1131,7 @@ impl<'bump> DiffMachine<'bump> {
     //  Utilities
     // =====================
 
-    fn find_last_element(&mut self, vnode: &'bump VNode<'bump>) -> Option<ElementId> {
+    fn find_last_element(&'bump self, vnode: &'bump VNode<'bump>) -> Option<ElementId> {
         let mut search_node = Some(vnode);
 
         loop {
@@ -1032,20 +1140,22 @@ impl<'bump> DiffMachine<'bump> {
                 VNode::Element(t) => break t.dom_id.get(),
                 VNode::Suspended(t) => break t.dom_id.get(),
                 VNode::Anchor(t) => break t.dom_id.get(),
-
+                VNode::Linked(_) => {
+                    todo!()
+                }
                 VNode::Fragment(frag) => {
                     search_node = frag.children.last();
                 }
                 VNode::Component(el) => {
                     let scope_id = el.associated_scope.get().unwrap();
-                    let scope = self.vdom.get_scope(scope_id).unwrap();
-                    search_node = Some(scope.root_node());
+                    // let scope = self.get_scope(&scope_id).unwrap();
+                    search_node = Some(self.root_node(&scope_id));
                 }
             }
         }
     }
 
-    fn find_first_element_id(&mut self, vnode: &'bump VNode<'bump>) -> Option<ElementId> {
+    fn find_first_element_id(&'bump self, vnode: &'bump VNode<'bump>) -> Option<ElementId> {
         let mut search_node = Some(vnode);
 
         loop {
@@ -1056,8 +1166,11 @@ impl<'bump> DiffMachine<'bump> {
                 }
                 VNode::Component(el) => {
                     let scope_id = el.associated_scope.get().unwrap();
-                    let scope = self.vdom.get_scope(scope_id).unwrap();
-                    search_node = Some(scope.root_node());
+                    // let scope = self.get_scope(&scope_id).unwrap();
+                    search_node = Some(self.root_node(&scope_id));
+                }
+                VNode::Linked(link) => {
+                    todo!("linked")
                 }
                 VNode::Text(t) => break t.dom_id.get(),
                 VNode::Element(t) => break t.dom_id.get(),
@@ -1068,23 +1181,26 @@ impl<'bump> DiffMachine<'bump> {
     }
 
     fn replace_and_create_many_with_one(
-        &mut self,
+        &'bump self,
+        state: &mut DiffState<'bump>,
         old: &'bump [VNode<'bump>],
         new: &'bump VNode<'bump>,
     ) {
         if let Some(first_old) = old.get(0) {
-            self.remove_nodes(&old[1..], true);
-            self.stack
+            self.remove_nodes(state, &old[1..], true);
+            state
+                .stack
                 .create_node(new, MountType::Replace { old: first_old });
         } else {
-            self.stack.create_node(new, MountType::Append {});
+            state.stack.create_node(new, MountType::Append {});
         }
     }
 
     /// schedules nodes for garbage collection and pushes "remove" to the mutation stack
     /// remove can happen whenever
     fn remove_nodes(
-        &mut self,
+        &'bump self,
+        state: &mut DiffState<'bump>,
         nodes: impl IntoIterator<Item = &'bump VNode<'bump>>,
         gen_muts: bool,
     ) {
@@ -1093,50 +1209,54 @@ impl<'bump> DiffMachine<'bump> {
             match node {
                 VNode::Text(t) => {
                     let id = t.dom_id.get().unwrap();
-                    self.vdom.collect_garbage(id);
+                    self.collect_garbage(id);
 
                     if gen_muts {
-                        self.mutations.remove(id.as_u64());
+                        state.mutations.remove(id.as_u64());
                     }
                 }
                 VNode::Suspended(s) => {
                     let id = s.dom_id.get().unwrap();
-                    self.vdom.collect_garbage(id);
+                    self.collect_garbage(id);
 
                     if gen_muts {
-                        self.mutations.remove(id.as_u64());
+                        state.mutations.remove(id.as_u64());
                     }
                 }
                 VNode::Anchor(a) => {
                     let id = a.dom_id.get().unwrap();
-                    self.vdom.collect_garbage(id);
+                    self.collect_garbage(id);
 
                     if gen_muts {
-                        self.mutations.remove(id.as_u64());
+                        state.mutations.remove(id.as_u64());
                     }
                 }
                 VNode::Element(e) => {
                     let id = e.dom_id.get().unwrap();
 
                     if gen_muts {
-                        self.mutations.remove(id.as_u64());
+                        state.mutations.remove(id.as_u64());
                     }
 
-                    self.remove_nodes(e.children, false);
+                    self.remove_nodes(state, e.children, false);
                 }
 
                 VNode::Fragment(f) => {
-                    self.remove_nodes(f.children, gen_muts);
+                    self.remove_nodes(state, f.children, gen_muts);
+                }
+
+                VNode::Linked(l) => {
+                    todo!()
                 }
 
                 VNode::Component(c) => {
                     let scope_id = c.associated_scope.get().unwrap();
-                    let scope = self.vdom.get_scope_mut(scope_id).unwrap();
-                    let root = scope.root_node();
-                    self.remove_nodes(Some(root), gen_muts);
+                    // let scope = self.get_scope(&scope_id).unwrap();
+                    let root = self.root_node(&scope_id);
+                    self.remove_nodes(state, Some(root), gen_muts);
 
                     log::debug!("Destroying scope {:?}", scope_id);
-                    let mut s = self.vdom.try_remove(scope_id).unwrap();
+                    let mut s = self.try_remove(&scope_id).unwrap();
                     s.hooks.clear_hooks();
                 }
             }
@@ -1147,38 +1267,44 @@ impl<'bump> DiffMachine<'bump> {
     ///
     /// The new nodes *will* be created - don't create them yourself!
     fn replace_and_create_many_with_many(
-        &mut self,
+        &'bump self,
+        state: &mut DiffState<'bump>,
         old: &'bump [VNode<'bump>],
         new: &'bump [VNode<'bump>],
     ) {
         if let Some(first_old) = old.get(0) {
-            self.remove_nodes(&old[1..], true);
-            self.stack
+            self.remove_nodes(state, &old[1..], true);
+            state
+                .stack
                 .create_children(new, MountType::Replace { old: first_old })
         } else {
-            self.stack.create_children(new, MountType::Append {});
+            state.stack.create_children(new, MountType::Append {});
         }
     }
 
     /// Adds a listener closure to a scope during diff.
-    fn attach_listener_to_scope<'a>(&mut self, listener: &'a Listener<'a>, scope: &ScopeInner) {
-        let mut queue = scope.listeners.borrow_mut();
-        let long_listener: &'a Listener<'static> = unsafe { std::mem::transmute(listener) };
-        queue.push(long_listener as *const _)
+    fn attach_listener_to_scope(&'bump self, listener: &'bump Listener<'bump>, scope: &Scope) {
+        let long_listener = unsafe { std::mem::transmute(listener) };
+        scope.items.borrow_mut().listeners.push(long_listener)
     }
 
-    fn attach_suspended_node_to_scope(&mut self, suspended: &'bump VSuspended) {
-        if let Some(scope) = self
+    fn attach_suspended_node_to_scope(
+        &'bump self,
+        state: &mut DiffState<'bump>,
+        suspended: &'bump VSuspended,
+    ) {
+        if let Some(scope) = state
             .stack
             .current_scope()
-            .and_then(|id| self.vdom.get_scope_mut(id))
+            .and_then(|id| self.get_scope(&id))
         {
             // safety: this lifetime is managed by the logic on scope
-            let extended: &VSuspended<'static> = unsafe { std::mem::transmute(suspended) };
+            let extended = unsafe { std::mem::transmute(suspended) };
             scope
-                .suspended_nodes
+                .items
                 .borrow_mut()
-                .insert(suspended.task_id, extended as *const _);
+                .suspended_nodes
+                .insert(suspended.task_id, extended);
         }
     }
 }

+ 0 - 4
packages/core/src/diff_stack.rs

@@ -49,10 +49,6 @@ impl<'bump> DiffStack<'bump> {
         }
     }
 
-    pub fn is_empty(&self) -> bool {
-        self.instructions.is_empty()
-    }
-
     pub fn pop(&mut self) -> Option<DiffInstruction<'bump>> {
         self.instructions.pop()
     }

+ 0 - 50
packages/core/src/heuristics.rs

@@ -1,50 +0,0 @@
-use std::collections::HashMap;
-
-use fxhash::FxHashMap;
-
-use crate::FC;
-
-/// Provides heuristics to the "SharedResources" object for improving allocation performance.
-///
-/// This heuristics engine records the memory footprint of bump arenas and hook lists for each component. These records are
-/// then used later on to optimize the initial allocation for future components. This helps save large allocations later on
-/// that would slow down the diffing and initialization process.
-///
-///
-pub struct HeuristicsEngine {
-    heuristics: FxHashMap<FcSlot, Heuristic>,
-}
-
-pub type FcSlot = *const ();
-
-pub struct Heuristic {
-    hooks: u32,
-    bump_size: u64,
-}
-
-impl HeuristicsEngine {
-    pub(crate) fn new() -> Self {
-        Self {
-            heuristics: FxHashMap::default(),
-        }
-    }
-
-    fn recommend<T>(&mut self, fc: FC<T>, heuristic: Heuristic) {
-        let g = fc as FcSlot;
-        let e = self.heuristics.entry(g);
-    }
-
-    fn get_recommendation<T>(&mut self, fc: FC<T>) -> &Heuristic {
-        let id = fc as FcSlot;
-
-        self.heuristics.entry(id).or_insert(Heuristic {
-            bump_size: 100,
-            hooks: 10,
-        })
-    }
-}
-
-#[test]
-fn types_work() {
-    let engine = HeuristicsEngine::new();
-}

+ 20 - 15
packages/core/src/hooklist.rs

@@ -1,10 +1,9 @@
 use std::{
     any::Any,
-    cell::{Cell, RefCell, UnsafeCell},
+    cell::{Cell, RefCell},
 };
 
-type UnsafeInnerHookState = UnsafeCell<Box<dyn Any>>;
-type HookCleanup = Box<dyn FnOnce(Box<dyn Any>)>;
+use bumpalo::Bump;
 
 /// An abstraction over internally stored data using a hook-based memory layout.
 ///
@@ -15,7 +14,8 @@ type HookCleanup = Box<dyn FnOnce(Box<dyn Any>)>;
 /// Todo: this could use its very own bump arena, but that might be a tad overkill
 #[derive(Default)]
 pub(crate) struct HookList {
-    vals: RefCell<Vec<(UnsafeInnerHookState, HookCleanup)>>,
+    arena: Bump,
+    vals: RefCell<Vec<*mut dyn Any>>,
     idx: Cell<usize>,
 }
 
@@ -23,7 +23,7 @@ impl HookList {
     pub(crate) fn next<T: 'static>(&self) -> Option<&mut T> {
         self.vals.borrow().get(self.idx.get()).and_then(|inn| {
             self.idx.set(self.idx.get() + 1);
-            let raw_box = unsafe { &mut *inn.0.get() };
+            let raw_box = unsafe { &mut **inn };
             raw_box.downcast_mut::<T>()
         })
     }
@@ -34,14 +34,13 @@ impl HookList {
     ///
     /// This should only be ran by Dioxus itself before "running scope".
     /// Dioxus knows how to descend through the tree to prevent mutable aliasing.
-    pub(crate) unsafe fn reset(&mut self) {
+    pub(crate) unsafe fn reset(&self) {
         self.idx.set(0);
     }
 
-    pub(crate) fn push_hook<T: 'static>(&self, new: T, cleanup: HookCleanup) {
-        self.vals
-            .borrow_mut()
-            .push((UnsafeCell::new(Box::new(new)), cleanup))
+    pub(crate) fn push_hook<T: 'static>(&self, new: T) {
+        let val = self.arena.alloc(new);
+        self.vals.borrow_mut().push(val)
     }
 
     pub(crate) fn len(&self) -> usize {
@@ -57,10 +56,16 @@ impl HookList {
     }
 
     pub fn clear_hooks(&mut self) {
-        log::debug!("clearing hooks...");
-        self.vals
-            .borrow_mut()
-            .drain(..)
-            .for_each(|(state, cleanup)| cleanup(state.into_inner()));
+        self.vals.borrow_mut().drain(..).for_each(|state| {
+            let as_mut = unsafe { &mut *state };
+            let boxed = unsafe { bumpalo::boxed::Box::from_raw(as_mut) };
+            drop(boxed);
+        });
+    }
+
+    /// Get the ammount of memory a hooklist uses
+    /// Used in heuristics
+    pub fn get_hook_arena_size(&self) -> usize {
+        self.arena.allocated_bytes()
     }
 }

+ 165 - 73
packages/core/src/lazynodes.rs

@@ -27,89 +27,118 @@ pub struct LazyNodes<'a, 'b> {
     inner: StackNodeStorage<'a, 'b>,
 }
 
-type StackHeapSize = [usize; 0];
+type StackHeapSize = [usize; 8];
 
 enum StackNodeStorage<'a, 'b> {
     Stack(LazyStack),
-    Heap(Box<dyn FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b>),
+    Heap(Box<dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> + 'b>),
 }
 
 impl<'a, 'b> LazyNodes<'a, 'b> {
-    pub fn new<F>(val: F) -> Self
+    pub fn new<F>(_val: F) -> Self
     where
         F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
     {
-        unsafe {
-            let mut ptr: *const _ = &val as &dyn FnOnce(NodeFactory<'a>) -> VNode<'a>;
+        // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
+        let mut slot = Some(_val);
 
-            assert_eq!(
-                ptr as *const u8, &val as *const _ as *const u8,
-                "MISUSE: Closure returned different pointer"
-            );
-            assert_eq!(
-                std::mem::size_of_val(&*ptr),
-                std::mem::size_of::<F>(),
-                "MISUSE: Closure returned a subset pointer"
-            );
-            let words = ptr_as_slice(&mut ptr);
-            assert!(
-                words[0] == &val as *const _ as usize,
-                "BUG: Pointer layout is not (data_ptr, info...)"
-            );
+        let val = move |fac: Option<NodeFactory<'a>>| {
+            let inner = slot.take().unwrap();
+            fac.map(inner)
+        };
 
-            // - Ensure that Self is aligned same as data requires
-            assert!(
-                std::mem::align_of::<F>() <= std::mem::align_of::<Self>(),
-                "TODO: Enforce alignment >{} (requires {})",
-                std::mem::align_of::<Self>(),
-                std::mem::align_of::<F>()
-            );
+        unsafe { LazyNodes::new_inner(val) }
+    }
 
-            let info = &words[1..];
-            let data = words[0] as *mut ();
-            let size = mem::size_of::<F>();
+    unsafe fn new_inner<F>(val: F) -> Self
+    where
+        F: FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> + 'b,
+    {
+        let mut ptr: *const _ = &val as &dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>>;
+
+        assert_eq!(
+            ptr as *const u8, &val as *const _ as *const u8,
+            "MISUSE: Closure returned different pointer"
+        );
+        assert_eq!(
+            std::mem::size_of_val(&*ptr),
+            std::mem::size_of::<F>(),
+            "MISUSE: Closure returned a subset pointer"
+        );
+
+        let words = ptr_as_slice(&mut ptr);
+        assert!(
+            words[0] == &val as *const _ as usize,
+            "BUG: Pointer layout is not (data_ptr, info...)"
+        );
+
+        // - Ensure that Self is aligned same as data requires
+        assert!(
+            std::mem::align_of::<F>() <= std::mem::align_of::<Self>(),
+            "TODO: Enforce alignment >{} (requires {})",
+            std::mem::align_of::<Self>(),
+            std::mem::align_of::<F>()
+        );
+
+        let info = &words[1..];
+        let data = words[0] as *mut ();
+        let size = mem::size_of::<F>();
+
+        let stored_size = info.len() * mem::size_of::<usize>() + size;
+        let max_size = mem::size_of::<StackHeapSize>();
+
+        if stored_size > max_size {
+            log::debug!(
+                    "lazy nodes was too large to fit into stack. falling back to heap. max: {}, actual {:?}",
+                    max_size,
+                    stored_size
+                );
+
+            Self {
+                inner: StackNodeStorage::Heap(Box::new(val)),
+            }
+        } else {
+            log::debug!(
+                "lazy nodes fits on stack! max: {}, actual: {:?}",
+                max_size,
+                stored_size
+            );
+            let mut buf: StackHeapSize = StackHeapSize::default();
 
-            if info.len() * mem::size_of::<usize>() + size > mem::size_of::<StackHeapSize>() {
-                log::debug!("lazy nodes was too large to fit into stack. falling back to heap");
+            assert!(info.len() + round_to_words(size) <= buf.as_ref().len());
 
-                Self {
-                    inner: StackNodeStorage::Heap(Box::new(val)),
-                }
-            } else {
-                log::debug!("lazy nodes fits on stack!");
-                let mut buf: StackHeapSize = StackHeapSize::default();
-
-                assert!(info.len() + round_to_words(size) <= buf.as_ref().len());
-
-                // Place pointer information at the end of the region
-                // - Allows the data to be at the start for alignment purposes
-                {
-                    let info_ofs = buf.as_ref().len() - info.len();
-                    let info_dst = &mut buf.as_mut()[info_ofs..];
-                    for (d, v) in Iterator::zip(info_dst.iter_mut(), info.iter()) {
-                        *d = *v;
-                    }
+            // Place pointer information at the end of the region
+            // - Allows the data to be at the start for alignment purposes
+            {
+                let info_ofs = buf.as_ref().len() - info.len();
+                let info_dst = &mut buf.as_mut()[info_ofs..];
+                for (d, v) in Iterator::zip(info_dst.iter_mut(), info.iter()) {
+                    *d = *v;
                 }
+            }
 
-                let src_ptr = data as *const u8;
-                let dataptr = buf.as_mut()[..].as_mut_ptr() as *mut u8;
-                for i in 0..size {
-                    *dataptr.add(i) = *src_ptr.add(i);
-                }
+            let src_ptr = data as *const u8;
+            let dataptr = buf.as_mut()[..].as_mut_ptr() as *mut u8;
+            for i in 0..size {
+                *dataptr.add(i) = *src_ptr.add(i);
+            }
 
-                std::mem::forget(val);
+            std::mem::forget(val);
 
-                Self {
-                    inner: StackNodeStorage::Stack(LazyStack { _align: [], buf }),
-                }
+            Self {
+                inner: StackNodeStorage::Stack(LazyStack {
+                    _align: [],
+                    buf,
+                    dropped: false,
+                }),
             }
         }
     }
 
     pub fn call(self, f: NodeFactory<'a>) -> VNode<'a> {
         match self.inner {
-            StackNodeStorage::Heap(lazy) => lazy(f),
-            StackNodeStorage::Stack(stack) => stack.call(f),
+            StackNodeStorage::Heap(mut lazy) => lazy(Some(f)).unwrap(),
+            StackNodeStorage::Stack(mut stack) => stack.call(f),
         }
     }
 }
@@ -117,34 +146,51 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
 struct LazyStack {
     _align: [u64; 0],
     buf: StackHeapSize,
+    dropped: bool,
 }
 
 impl LazyStack {
-    unsafe fn create_boxed<'a>(&mut self) -> Box<dyn FnOnce(NodeFactory<'a>) -> VNode<'a>> {
+    fn call<'a>(&mut self, f: NodeFactory<'a>) -> VNode<'a> {
         let LazyStack { buf, .. } = self;
         let data = buf.as_ref();
 
-        let info_size = mem::size_of::<*mut dyn FnOnce(NodeFactory<'a>) -> VNode<'a>>()
-            / mem::size_of::<usize>()
-            - 1;
+        let info_size =
+            mem::size_of::<*mut dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>>>()
+                / mem::size_of::<usize>()
+                - 1;
 
         let info_ofs = data.len() - info_size;
 
-        let g: *mut dyn FnOnce(NodeFactory<'a>) -> VNode<'a> =
-            make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]);
+        let g: *mut dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> =
+            unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
 
-        Box::from_raw(g)
-    }
+        self.dropped = true;
 
-    fn call(mut self, f: NodeFactory) -> VNode {
-        let boxed = unsafe { self.create_boxed() };
-        boxed(f)
+        let clos = unsafe { &mut *g };
+        clos(Some(f)).unwrap()
     }
 }
 impl Drop for LazyStack {
     fn drop(&mut self) {
-        let boxed = unsafe { self.create_boxed() };
-        mem::drop(boxed);
+        if !self.dropped {
+            let LazyStack { buf, .. } = self;
+            let data = buf.as_ref();
+
+            let info_size = mem::size_of::<
+                *mut dyn FnMut(Option<NodeFactory<'_>>) -> Option<VNode<'_>>,
+            >() / mem::size_of::<usize>()
+                - 1;
+
+            let info_ofs = data.len() - info_size;
+
+            let g: *mut dyn FnMut(Option<NodeFactory<'_>>) -> Option<VNode<'_>> =
+                unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
+
+            self.dropped = true;
+
+            let clos = unsafe { &mut *g };
+            clos(None);
+        }
     }
 }
 
@@ -177,7 +223,7 @@ fn round_to_words(len: usize) -> usize {
 fn it_works() {
     let bump = bumpalo::Bump::new();
 
-    simple_logger::init();
+    simple_logger::init().unwrap();
 
     let factory = NodeFactory { bump: &bump };
 
@@ -191,3 +237,49 @@ fn it_works() {
 
     dbg!(g);
 }
+
+#[test]
+fn it_drops() {
+    use std::rc::Rc;
+    let bump = bumpalo::Bump::new();
+
+    simple_logger::init().unwrap();
+
+    let factory = NodeFactory { bump: &bump };
+
+    struct DropInner {
+        id: i32,
+    }
+    impl Drop for DropInner {
+        fn drop(&mut self) {
+            log::debug!("dropping inner");
+        }
+    }
+    let val = Rc::new(10);
+
+    {
+        let it = (0..10)
+            .map(|i| {
+                let val = val.clone();
+
+                NodeFactory::annotate_lazy(move |f| {
+                    log::debug!("hell closure");
+                    let inner = DropInner { id: i };
+                    f.text(format_args!("hello world {:?}, {:?}", inner.id, val))
+                })
+            })
+            .collect::<Vec<_>>();
+
+        let caller = NodeFactory::annotate_lazy(|f| {
+            log::debug!("main closure");
+            f.fragment_from_iter(it)
+        })
+        .unwrap();
+    }
+
+    // let nodes = caller.call(factory);
+
+    // dbg!(nodes);
+
+    dbg!(Rc::strong_count(&val));
+}

+ 11 - 30
packages/core/src/lib.rs

@@ -12,68 +12,49 @@ Navigating this crate:
 
 Some utilities
 */
-pub(crate) mod bumpframe;
-pub(crate) mod childiter;
 pub(crate) mod component;
-pub(crate) mod coroutines;
 pub(crate) mod diff;
 pub(crate) mod diff_stack;
-pub(crate) mod events;
-pub(crate) mod heuristics;
 pub(crate) mod hooklist;
-pub(crate) mod hooks;
 pub(crate) mod lazynodes;
 pub(crate) mod mutations;
 pub(crate) mod nodes;
-pub(crate) mod resources;
-pub(crate) mod scheduler;
 pub(crate) mod scope;
-pub(crate) mod tasks;
+pub(crate) mod scopearena;
 pub(crate) mod test_dom;
-pub(crate) mod threadsafe;
 pub(crate) mod util;
 pub(crate) mod virtual_dom;
 
-#[cfg(feature = "debug_vdom")]
-pub mod debug_dom;
-
 pub(crate) mod innerlude {
-    pub(crate) use crate::bumpframe::*;
-    pub(crate) use crate::childiter::*;
     pub use crate::component::*;
     pub(crate) use crate::diff::*;
     pub use crate::diff_stack::*;
-    pub use crate::events::*;
-    pub use crate::heuristics::*;
     pub(crate) use crate::hooklist::*;
-    pub use crate::hooks::*;
     pub use crate::lazynodes::*;
     pub use crate::mutations::*;
     pub use crate::nodes::*;
-    pub(crate) use crate::resources::*;
-    pub use crate::scheduler::*;
     pub use crate::scope::*;
-    pub use crate::tasks::*;
+    pub use crate::scopearena::*;
     pub use crate::test_dom::*;
-    pub use crate::threadsafe::*;
     pub use crate::util::*;
     pub use crate::virtual_dom::*;
 
-    pub type Element<'a> = Option<VNode<'a>>;
-    pub type FC<P> = for<'a> fn(Scope<'a, P>) -> Element<'a>;
+    pub type Element = Option<NodeLink>;
+    pub type FC<P> = for<'a> fn(Context<'a>, &'a P) -> Element;
 }
 
 pub use crate::innerlude::{
-    Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, LazyNodes, MountType,
-    Mutations, NodeFactory, Properties, SchedulerMsg, ScopeChildren, ScopeId, TaskHandle, TestDom,
-    ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC,
+    Attribute, Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, LazyNodes,
+    Listener, MountType, Mutations, NodeFactory, Properties, SchedulerMsg, ScopeChildren, ScopeId,
+    TestDom, UserEvent, VAnchor, VElement, VFragment, VNode, VSuspended, VirtualDom, FC,
 };
 
 pub mod prelude {
-    pub use crate::component::{fc_to_builder, Fragment, Properties, Scope};
-    pub use crate::hooks::*;
+    pub use crate::component::{fc_to_builder, Fragment, Properties};
     pub use crate::innerlude::Context;
-    pub use crate::innerlude::{DioxusElement, Element, LazyNodes, NodeFactory, ScopeChildren, FC};
+    pub use crate::innerlude::{
+        DioxusElement, Element, LazyNodes, NodeFactory, Scope, ScopeChildren, FC,
+    };
     pub use crate::nodes::VNode;
     pub use crate::VirtualDom;
 }

+ 115 - 43
packages/core/src/nodes.rs

@@ -4,7 +4,7 @@
 //! cheap and *very* fast to construct - building a full tree should be quick.
 
 use crate::{
-    innerlude::{empty_cell, Context, Element, ElementId, Properties, Scope, ScopeId, ScopeInner},
+    innerlude::{empty_cell, Context, Element, ElementId, Properties, Scope, ScopeId},
     lazynodes::LazyNodes,
 };
 use bumpalo::{boxed::Box as BumpBox, Bump};
@@ -14,6 +14,19 @@ use std::{
     fmt::{Arguments, Debug, Formatter},
 };
 
+/// A cached node is a "pointer" to a "rendered" node in a particular scope
+///
+/// It does not provide direct access to the node, so it doesn't carry any lifetime information with it
+///
+/// It is used during the diffing/rendering process as a runtime key into an existing set of nodes. The "render" key
+/// is essentially a unique key to guarantee safe usage of the Node.
+#[derive(Clone, Debug)]
+pub struct NodeLink {
+    pub(crate) link_idx: usize,
+    pub(crate) gen_id: u32,
+    pub(crate) scope_id: ScopeId,
+}
+
 /// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM.
 ///
 /// VNodes are designed to be lightweight and used with with a bump allocator. To create a VNode, you can use either of:
@@ -118,6 +131,13 @@ pub enum VNode<'src> {
     /// }
     /// ```
     Anchor(&'src VAnchor),
+
+    /// A type of node that links this node to another scope or render cycle
+    ///
+    /// Is essentially a "pointer" to a "rendered" node in a particular scope
+    ///
+    /// Used in portals
+    Linked(NodeLink),
 }
 
 impl<'src> VNode<'src> {
@@ -127,9 +147,11 @@ impl<'src> VNode<'src> {
             VNode::Element(el) => el.key,
             VNode::Component(c) => c.key,
             VNode::Fragment(f) => f.key,
+
             VNode::Text(_t) => None,
             VNode::Suspended(_s) => None,
             VNode::Anchor(_f) => None,
+            VNode::Linked(_c) => None,
         }
     }
 
@@ -149,11 +171,20 @@ impl<'src> VNode<'src> {
             VNode::Element(el) => el.dom_id.get(),
             VNode::Anchor(el) => el.dom_id.get(),
             VNode::Suspended(el) => el.dom_id.get(),
+            VNode::Linked(_) => None,
             VNode::Fragment(_) => None,
             VNode::Component(_) => None,
         }
     }
 
+    pub fn children(&self) -> &[VNode<'src>] {
+        match &self {
+            VNode::Fragment(f) => f.children,
+            VNode::Component(c) => todo!("children are not accessible through this"),
+            _ => &[],
+        }
+    }
+
     // Create an "owned" version of the vnode.
     pub fn decouple(&self) -> VNode<'src> {
         match self {
@@ -165,7 +196,11 @@ impl<'src> VNode<'src> {
             VNode::Fragment(f) => VNode::Fragment(VFragment {
                 children: f.children,
                 key: f.key,
-                is_static: f.is_static,
+            }),
+            VNode::Linked(c) => VNode::Linked(NodeLink {
+                gen_id: c.gen_id,
+                scope_id: c.scope_id,
+                link_idx: c.link_idx,
             }),
         }
     }
@@ -175,17 +210,24 @@ impl Debug for VNode<'_> {
     fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
         match &self {
             VNode::Element(el) => s
-                .debug_struct("VElement")
+                .debug_struct("VNode::VElement")
                 .field("name", &el.tag_name)
                 .field("key", &el.key)
                 .finish(),
 
-            VNode::Text(t) => write!(s, "VText {{ text: {} }}", t.text),
-            VNode::Anchor(_) => write!(s, "VAnchor"),
+            VNode::Text(t) => write!(s, "VNode::VText {{ text: {} }}", t.text),
+            VNode::Anchor(_) => write!(s, "VNode::VAnchor"),
 
-            VNode::Fragment(frag) => write!(s, "VFragment {{ children: {:?} }}", frag.children),
-            VNode::Suspended { .. } => write!(s, "VSuspended"),
-            VNode::Component(comp) => write!(s, "VComponent {{ fc: {:?}}}", comp.user_fc),
+            VNode::Fragment(frag) => {
+                write!(s, "VNode::VFragment {{ children: {:?} }}", frag.children)
+            }
+            VNode::Suspended { .. } => write!(s, "VNode::VSuspended"),
+            VNode::Component(comp) => write!(s, "VNode::VComponent {{ fc: {:?}}}", comp.user_fc),
+            VNode::Linked(c) => write!(
+                s,
+                "VNode::VCached {{ gen_id: {}, scope_id: {:?} }}",
+                c.gen_id, c.scope_id
+            ),
         }
     }
 }
@@ -209,8 +251,6 @@ pub struct VFragment<'src> {
     pub key: Option<&'src str>,
 
     pub children: &'src [VNode<'src>],
-
-    pub is_static: bool,
 }
 
 /// An element like a "div" with children, listeners, and attributes.
@@ -309,6 +349,7 @@ pub struct Listener<'bump> {
     pub(crate) callback: RefCell<Option<BumpBox<'bump, dyn FnMut(Box<dyn Any + Send>) + 'bump>>>,
 }
 
+pub type VCompCaller<'src> = BumpBox<'src, dyn Fn(Context) -> Element + 'src>;
 /// Virtual Components for custom user-defined components
 /// Only supports the functional syntax
 pub struct VComponent<'src> {
@@ -316,21 +357,19 @@ pub struct VComponent<'src> {
 
     pub associated_scope: Cell<Option<ScopeId>>,
 
-    pub is_static: bool,
-
     // Function pointer to the FC that was used to generate this component
     pub user_fc: *const (),
 
-    pub(crate) caller: BumpBox<'src, dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> + 'src>,
-
-    pub(crate) comparator: Option<BumpBox<'src, dyn Fn(&VComponent) -> bool + 'src>>,
-
-    pub(crate) drop_props: RefCell<Option<BumpBox<'src, dyn FnMut()>>>,
-
     pub(crate) can_memoize: bool,
 
+    pub(crate) hard_allocation: Cell<Option<*const ()>>,
+
     // Raw pointer into the bump arena for the props of the component
-    pub(crate) raw_props: *const (),
+    pub(crate) bump_props: *const (),
+
+    // during the "teardown" process we'll take the caller out so it can be dropped properly
+    pub(crate) caller: Option<VCompCaller<'src>>,
+    pub(crate) comparator: Option<BumpBox<'src, dyn Fn(&VComponent) -> bool + 'src>>,
 }
 
 pub struct VSuspended<'a> {
@@ -338,7 +377,7 @@ pub struct VSuspended<'a> {
     pub dom_id: Cell<Option<ElementId>>,
 
     #[allow(clippy::type_complexity)]
-    pub callback: RefCell<Option<BumpBox<'a, dyn FnMut() -> Element<'a> + 'a>>>,
+    pub callback: RefCell<Option<BumpBox<'a, dyn FnMut() -> Element + 'a>>>,
 }
 
 /// This struct provides an ergonomic API to quickly build VNodes.
@@ -475,16 +514,30 @@ impl<'a> NodeFactory<'a> {
 
     pub fn component<P>(
         &self,
-        component: fn(Scope<'a, P>) -> Element<'a>,
+        component: fn(Context<'a>, &'a P) -> Element,
         props: P,
         key: Option<Arguments>,
     ) -> VNode<'a>
     where
         P: Properties + 'a,
     {
+        /*
+        our strategy:
+        - unsafe hackery
+        - lol
+
+        - we don't want to hit the global allocator
+        - allocate into our bump arena
+        - if the props aren't static, then we convert them into a box which we pass off between renders
+        */
+
+        // let bump = self.bump();
+
+        // // later, we'll do a hard allocation
+        // let raw_ptr = bump.alloc(props);
         let bump = self.bump();
         let props = bump.alloc(props);
-        let raw_props = props as *mut P as *mut ();
+        let bump_props = props as *mut P as *mut ();
         let user_fc = component as *const ();
 
         let comparator: &mut dyn Fn(&VComponent) -> bool = bump.alloc_with(|| {
@@ -496,7 +549,7 @@ impl<'a> NodeFactory<'a> {
                     // - Non-static P are autoderived to memoize as false
                     // - This comparator is only called on a corresponding set of bumpframes
                     let props_memoized = unsafe {
-                        let real_other: &P = &*(other.raw_props as *const _ as *const P);
+                        let real_other: &P = &*(other.bump_props as *const _ as *const P);
                         props.memoize(real_other)
                     };
 
@@ -508,7 +561,6 @@ impl<'a> NodeFactory<'a> {
                 }
             }
         });
-        let comparator = Some(unsafe { BumpBox::from_raw(comparator) });
 
         let drop_props = {
             // create a closure to drop the props
@@ -518,7 +570,7 @@ impl<'a> NodeFactory<'a> {
                 move || unsafe {
                     log::debug!("dropping props!");
                     if !has_dropped {
-                        let real_other = raw_props as *mut _ as *mut P;
+                        let real_other = bump_props as *mut _ as *mut P;
                         let b = BumpBox::from_raw(real_other);
                         std::mem::drop(b);
 
@@ -536,33 +588,41 @@ impl<'a> NodeFactory<'a> {
 
         let key = key.map(|f| self.raw_text(f).0);
 
-        let caller: &'a mut dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> =
-            bump.alloc(move |scope: &ScopeInner| -> Element {
+        let caller: &'a mut dyn Fn(&Scope) -> Element =
+            bump.alloc(move |scope: &Scope| -> Element {
                 log::debug!("calling component renderr {:?}", scope.our_arena_idx);
-                let props: &'_ P = unsafe { &*(raw_props as *const P) };
-
-                let scp: &'a ScopeInner = unsafe { std::mem::transmute(scope) };
-                let s: Scope<'a, P> = (scp, props);
-
-                let res: Element = component(s);
+                let props: &'_ P = unsafe { &*(bump_props as *const P) };
+                let res = component(scope, props);
+                // let res = component((Context { scope }, props));
                 unsafe { std::mem::transmute(res) }
             });
 
-        let caller = unsafe { BumpBox::from_raw(caller) };
+        let can_memoize = P::IS_STATIC;
 
         VNode::Component(bump.alloc(VComponent {
             user_fc,
             comparator,
-            raw_props,
+            bump_props,
             caller,
-            is_static: P::IS_STATIC,
             key,
-            can_memoize: P::IS_STATIC,
+            can_memoize,
             drop_props,
             associated_scope: Cell::new(None),
         }))
     }
 
+    pub fn listener(
+        self,
+        event: &'static str,
+        callback: BumpBox<'a, dyn FnMut(Box<dyn Any + Send>) + 'a>,
+    ) -> Listener<'a> {
+        Listener {
+            mounted_node: Cell::new(None),
+            event,
+            callback: RefCell::new(Some(callback)),
+        }
+    }
+
     pub fn fragment_from_iter(
         self,
         node_iter: impl IntoIterator<Item = impl IntoVNode<'a>>,
@@ -604,14 +664,12 @@ impl<'a> NodeFactory<'a> {
         VNode::Fragment(VFragment {
             children,
             key: None,
-            is_static: false,
         })
     }
 
-    pub fn annotate_lazy<'z, 'b, F>(f: F) -> Option<LazyNodes<'z, 'b>>
-    where
-        F: FnOnce(NodeFactory<'z>) -> VNode<'z> + 'b,
-    {
+    pub fn annotate_lazy<'z, 'b>(
+        f: impl FnOnce(NodeFactory<'z>) -> VNode<'z> + 'b,
+    ) -> Option<LazyNodes<'z, 'b>> {
         Some(LazyNodes::new(f))
     }
 }
@@ -672,6 +730,21 @@ impl IntoVNode<'_> for Option<()> {
     }
 }
 
+// Conveniently, we also support "None"
+impl IntoVNode<'_> for Option<NodeLink> {
+    fn into_vnode(self, cx: NodeFactory) -> VNode {
+        todo!()
+        // cx.fragment_from_iter(None as Option<VNode>)
+    }
+}
+// Conveniently, we also support "None"
+impl IntoVNode<'_> for NodeLink {
+    fn into_vnode(self, cx: NodeFactory) -> VNode {
+        todo!()
+        // cx.fragment_from_iter(None as Option<VNode>)
+    }
+}
+
 impl<'a> IntoVNode<'a> for Option<VNode<'a>> {
     fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
         self.unwrap_or_else(|| cx.fragment_from_iter(None as Option<VNode>))
@@ -684,7 +757,6 @@ impl<'a> IntoVNode<'a> for Option<LazyNodes<'a, '_>> {
             Some(lazy) => lazy.call(cx),
             None => VNode::Fragment(VFragment {
                 children: &[],
-                is_static: false,
                 key: None,
             }),
         }

+ 27 - 45
packages/core/src/bumpframe.rs → packages/core/src/old/bumpframe.rs

@@ -3,69 +3,51 @@ use bumpalo::Bump;
 use std::cell::Cell;
 
 pub(crate) struct ActiveFrame {
-    // We use a "generation" for users of contents in the bump frames to ensure their data isn't broken
-    pub generation: Cell<usize>,
+    pub cur_generation: Cell<usize>,
 
     // The double-buffering situation that we will use
     pub frames: [BumpFrame; 2],
 }
 
-pub(crate) struct BumpFrame {
-    pub bump: Bump,
-    pub(crate) head_node: VNode<'static>,
-
-    #[cfg(test)]
-    // used internally for debugging
-    _name: &'static str,
-}
-
 impl ActiveFrame {
     pub fn new() -> Self {
         let b1 = Bump::new();
         let b2 = Bump::new();
 
         let frame_a = BumpFrame {
-            head_node: VNode::Fragment(VFragment {
-                key: None,
-                children: &[],
-                is_static: false,
-            }),
             bump: b1,
-
-            #[cfg(test)]
-            _name: "wip",
+            generation: 0.into(),
         };
         let frame_b = BumpFrame {
-            head_node: VNode::Fragment(VFragment {
-                key: None,
-                children: &[],
-                is_static: false,
-            }),
             bump: b2,
-
-            #[cfg(test)]
-            _name: "fin",
+            generation: 0.into(),
         };
+
         Self {
-            generation: 0.into(),
             frames: [frame_a, frame_b],
+            cur_generation: 0.into(),
         }
     }
 
-    pub unsafe fn reset_wip_frame(&mut self) {
-        self.wip_frame_mut().bump.reset()
+    pub unsafe fn reset_wip_frame(&self) {
+        // todo: unsafecell or something
+        let bump = self.wip_frame() as *const _ as *mut BumpFrame;
+        let g = &mut *bump;
+        g.bump.reset();
+
+        // self.wip_frame_mut().bump.reset()
     }
 
     /// The "work in progress frame" represents the frame that is currently being worked on.
     pub fn wip_frame(&self) -> &BumpFrame {
-        match self.generation.get() & 1 == 0 {
+        match self.cur_generation.get() & 1 == 0 {
             true => &self.frames[0],
             false => &self.frames[1],
         }
     }
 
     pub fn wip_frame_mut(&mut self) -> &mut BumpFrame {
-        match self.generation.get() & 1 == 0 {
+        match self.cur_generation.get() & 1 == 0 {
             true => &mut self.frames[0],
             false => &mut self.frames[1],
         }
@@ -73,26 +55,26 @@ impl ActiveFrame {
 
     /// The finished frame represents the frame that has been "finished" and cannot be modified again
     pub fn finished_frame(&self) -> &BumpFrame {
-        match self.generation.get() & 1 == 1 {
+        match self.cur_generation.get() & 1 == 1 {
             true => &self.frames[0],
             false => &self.frames[1],
         }
     }
 
-    /// Give out our self-referential item with our own borrowed lifetime
-    pub fn fin_head<'b>(&'b self) -> &'b VNode<'b> {
-        let cur_head = &self.finished_frame().head_node;
-        unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) }
-    }
+    // /// Give out our self-referential item with our own borrowed lifetime
+    // pub fn fin_head<'b>(&'b self) -> &'b VNode<'b> {
+    //     let cur_head = &self.finished_frame().head_node;
+    //     unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) }
+    // }
 
-    /// Give out our self-referential item with our own borrowed lifetime
-    pub fn wip_head<'b>(&'b self) -> &'b VNode<'b> {
-        let cur_head = &self.wip_frame().head_node;
-        unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) }
-    }
+    // /// Give out our self-referential item with our own borrowed lifetime
+    // pub fn wip_head<'b>(&'b self) -> &'b VNode<'b> {
+    //     let cur_head = &self.wip_frame().head_node;
+    //     unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) }
+    // }
 
     pub fn cycle_frame(&mut self) {
-        self.generation.set(self.generation.get() + 1);
+        self.cur_generation.set(self.cur_generation.get() + 1);
     }
 }
 
@@ -119,6 +101,6 @@ mod tests {
             assert_eq!(fin._name, "wip");
             frames.cycle_frame();
         }
-        assert_eq!(frames.generation.get(), 10);
+        assert_eq!(frames.cur_generation.get(), 10);
     }
 }

+ 1 - 1
packages/core/src/childiter.rs → packages/core/src/old/childiter.rs

@@ -62,7 +62,7 @@ impl<'a> Iterator for RealChildIterator<'a> {
                     VNode::Component(sc) => {
                         let scope = self
                             .scopes
-                            .get_scope(sc.associated_scope.get().unwrap())
+                            .get_scope(&sc.associated_scope.get().unwrap())
                             .unwrap();
 
                         // Simply swap the current node on the stack with the root of the component

+ 6 - 3
packages/core/src/debug_dom.rs → packages/core/src/old/debug_dom.rs

@@ -1,4 +1,4 @@
-use crate::{innerlude::ScopeInner, virtual_dom::VirtualDom, VNode};
+use crate::{innerlude::ScopeState, virtual_dom::VirtualDom, VNode};
 
 impl std::fmt::Display for VirtualDom {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -24,7 +24,7 @@ impl std::fmt::Display for VirtualDom {
 pub(crate) struct ScopeRenderer<'a> {
     pub skip_components: bool,
     pub show_fragments: bool,
-    pub _scope: &'a ScopeInner,
+    pub _scope: &'a ScopeState,
     pub _pre_render: bool,
     pub _newline: bool,
     pub _indent: bool,
@@ -48,6 +48,9 @@ impl<'a> ScopeRenderer<'a> {
         };
 
         match &node {
+            VNode::Linked(_) => {
+                write!(f, "Linked").unwrap();
+            }
             VNode::Text(text) => {
                 write_indent(f, il);
                 writeln!(f, "\"{}\"", text.text)?
@@ -115,7 +118,7 @@ impl<'a> ScopeRenderer<'a> {
             VNode::Component(vcomp) => {
                 let idx = vcomp.associated_scope.get().unwrap();
                 if !self.skip_components {
-                    let new_node = vdom.get_scope(idx).unwrap().root_node();
+                    let new_node = vdom.get_scope(&idx).unwrap().root_node();
                     self.render(vdom, new_node, f, il)?;
                 }
             }

+ 15 - 0
packages/core/src/old/events.rs

@@ -0,0 +1,15 @@
+//! An event system that's less confusing than Traits + RC;
+//! This should hopefully make it easier to port to other platforms.
+//!
+//! Unfortunately, it is less efficient than the original, but hopefully it's negligible.
+
+use crate::{
+    innerlude::Listener,
+    innerlude::{ElementId, NodeFactory, ScopeId},
+};
+use bumpalo::boxed::Box as BumpBox;
+use std::{
+    any::Any,
+    cell::{Cell, RefCell},
+    fmt::Debug,
+};

+ 74 - 74
packages/core/src/hooks.rs → packages/core/src/old/hooks.rs

@@ -13,73 +13,73 @@ use crate::innerlude::*;
 use futures_util::FutureExt;
 use std::{any::Any, cell::RefCell, future::Future, ops::Deref, rc::Rc};
 
-/// Awaits the given task, forcing the component to re-render when the value is ready.
-///
-/// Returns the handle to the task and the value (if it is ready, else None).
-///
-/// ```
-/// static Example: FC<()> = |(cx, props)| {
-///     let (task, value) = use_task(|| async {
-///         timer::sleep(Duration::from_secs(1)).await;
-///         "Hello World"
-///     });
-///
-///     match contents {
-///         Some(contents) => rsx!(cx, div { "{title}" }),
-///         None => rsx!(cx, div { "Loading..." }),
-///     }
-/// };
-/// ```
-pub fn use_coroutine<'src, Out, Fut, Init>(
-    cx: Context<'src>,
-    task_initializer: Init,
-) -> (&'src TaskHandle, &'src Option<Out>)
-where
-    Out: 'static,
-    Fut: Future<Output = Out> + 'static,
-    Init: FnOnce() -> Fut + 'src,
-{
-    struct TaskHook<T> {
-        handle: TaskHandle,
-        task_dump: Rc<RefCell<Option<T>>>,
-        value: Option<T>,
-    }
-
-    todo!()
-
-    // // whenever the task is complete, save it into th
-    // cx.use_hook(
-    //     move |_| {
-    //         let task_fut = task_initializer();
-
-    //         let task_dump = Rc::new(RefCell::new(None));
-
-    //         let slot = task_dump.clone();
-
-    //         let updater = cx.schedule_update_any();
-    //         let originator = cx.scope.our_arena_idx;
-
-    //         let handle = cx.submit_task(Box::pin(task_fut.then(move |output| async move {
-    //             *slot.as_ref().borrow_mut() = Some(output);
-    //             updater(originator);
-    //             originator
-    //         })));
-
-    //         TaskHook {
-    //             task_dump,
-    //             value: None,
-    //             handle,
-    //         }
-    //     },
-    //     |hook| {
-    //         if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() {
-    //             hook.value = Some(val);
-    //         }
-    //         (&hook.handle, &hook.value)
-    //     },
-    //     |_| {},
-    // )
-}
+// /// Awaits the given task, forcing the component to re-render when the value is ready.
+// ///
+// /// Returns the handle to the task and the value (if it is ready, else None).
+// ///
+// /// ```
+// /// static Example: FC<()> = |(cx, props)| {
+// ///     let (task, value) = use_task(|| async {
+// ///         timer::sleep(Duration::from_secs(1)).await;
+// ///         "Hello World"
+// ///     });
+// ///
+// ///     match contents {
+// ///         Some(contents) => rsx!(cx, div { "{title}" }),
+// ///         None => rsx!(cx, div { "Loading..." }),
+// ///     }
+// /// };
+// /// ```
+// pub fn use_coroutine<'src, Out, Fut, Init>(
+//     cx: Context<'src>,
+//     task_initializer: Init,
+// ) -> (&'src TaskHandle, &'src Option<Out>)
+// where
+//     Out: 'static,
+//     Fut: Future<Output = Out> + 'static,
+//     Init: FnOnce() -> Fut + 'src,
+// {
+//     struct TaskHook<T> {
+//         handle: TaskHandle,
+//         task_dump: Rc<RefCell<Option<T>>>,
+//         value: Option<T>,
+//     }
+
+//     todo!()
+
+//     // // whenever the task is complete, save it into th
+//     // cx.use_hook(
+//     //     move |_| {
+//     //         let task_fut = task_initializer();
+
+//     //         let task_dump = Rc::new(RefCell::new(None));
+
+//     //         let slot = task_dump.clone();
+
+//     //         let updater = cx.schedule_update_any();
+//     //         let originator = cx.scope.our_arena_idx;
+
+//     //         let handle = cx.submit_task(Box::pin(task_fut.then(move |output| async move {
+//     //             *slot.as_ref().borrow_mut() = Some(output);
+//     //             updater(originator);
+//     //             originator
+//     //         })));
+
+//     //         TaskHook {
+//     //             task_dump,
+//     //             value: None,
+//     //             handle,
+//     //         }
+//     //     },
+//     //     |hook| {
+//     //         if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() {
+//     //             hook.value = Some(val);
+//     //         }
+//     //         (&hook.handle, &hook.value)
+//     //     },
+//     //     |_| {},
+//     // )
+// }
 
 /// Asynchronously render new nodes once the given future has completed.
 ///
@@ -95,11 +95,11 @@ pub fn use_suspense<'src, Out, Fut, Cb>(
     cx: Context<'src>,
     task_initializer: impl FnOnce() -> Fut,
     user_callback: Cb,
-) -> Element<'src>
+) -> Element
 where
     Fut: Future<Output = Out>,
     Out: 'static,
-    Cb: FnMut(&Out) -> Element<'src> + 'src,
+    Cb: FnMut(&Out) -> Element + 'src,
 {
     /*
     General strategy:
@@ -167,10 +167,10 @@ where
     // )
 }
 
-pub(crate) struct SuspenseHook {
-    pub handle: TaskHandle,
-    pub value: Rc<RefCell<Option<Box<dyn Any>>>>,
-}
+// pub(crate) struct SuspenseHook {
+//     pub handle: TaskHandle,
+//     pub value: Rc<RefCell<Option<Box<dyn Any>>>>,
+// }
 
 #[derive(Clone, Copy)]
 pub struct NodeRef<'src, T: 'static>(&'src RefCell<Option<T>>);
@@ -183,5 +183,5 @@ impl<'a, T> Deref for NodeRef<'a, T> {
 }
 
 pub fn use_node_ref<T, P>(cx: Context) -> NodeRef<T> {
-    cx.use_hook(|_| RefCell::new(None), |f| NodeRef { 0: f }, |_| {})
+    cx.use_hook(|_| RefCell::new(None), |f| NodeRef { 0: f })
 }

+ 0 - 0
packages/core/src/noderef.rs → packages/core/src/old/noderef.rs


+ 3 - 3
packages/core/src/resources.rs → packages/core/src/old/resources.rs

@@ -32,18 +32,18 @@ pub(crate) struct ResourcePool {
 
 impl ResourcePool {
     /// this is unsafe because the caller needs to track which other scopes it's already using
-    pub fn get_scope(&self, idx: ScopeId) -> Option<&ScopeInner> {
+    pub fn get_scope(&self, idx: &ScopeId) -> Option<&ScopeInner> {
         let inner = unsafe { &*self.components.get() };
         inner.get(idx.0)
     }
 
     /// this is unsafe because the caller needs to track which other scopes it's already using
-    pub fn get_scope_mut(&self, idx: ScopeId) -> Option<&mut ScopeInner> {
+    pub fn get_scope_mut(&self, idx: &ScopeId) -> Option<&mut ScopeInner> {
         let inner = unsafe { &mut *self.components.get() };
         inner.get_mut(idx.0)
     }
 
-    pub fn try_remove(&self, id: ScopeId) -> Option<ScopeInner> {
+    pub fn try_remove(&self, id: &ScopeId) -> Option<ScopeInner> {
         let inner = unsafe { &mut *self.components.get() };
         Some(inner.remove(id.0))
         // .try_remove(id.0)

+ 105 - 121
packages/core/src/scheduler.rs → packages/core/src/old/scheduler.rs

@@ -68,14 +68,17 @@ do anything too arduous from onInput.
 
 For the rest, we defer to the rIC period and work down each queue from high to low.
 */
-use crate::heuristics::*;
+
 use crate::innerlude::*;
+use bumpalo::Bump;
 use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
 use futures_util::{pin_mut, stream::FuturesUnordered, Future, FutureExt, StreamExt};
 use fxhash::FxHashMap;
 use fxhash::FxHashSet;
 use indexmap::IndexSet;
 use slab::Slab;
+use std::pin::Pin;
+use std::task::Poll;
 use std::{
     any::{Any, TypeId},
     cell::{Cell, UnsafeCell},
@@ -85,11 +88,7 @@ use std::{
 
 #[derive(Clone)]
 pub(crate) struct EventChannel {
-    pub task_counter: Rc<Cell<u64>>,
-    pub cur_subtree: Rc<Cell<u32>>,
     pub sender: UnboundedSender<SchedulerMsg>,
-    pub schedule_any_immediate: Rc<dyn Fn(ScopeId)>,
-    pub submit_task: Rc<dyn Fn(FiberTask) -> TaskHandle>,
     pub get_shared_context: GetSharedContext,
 }
 
@@ -101,16 +100,6 @@ pub enum SchedulerMsg {
 
     // setstate
     Immediate(ScopeId),
-
-    // tasks
-    Task(TaskMsg),
-}
-
-pub enum TaskMsg {
-    ToggleTask(u64),
-    PauseTask(u64),
-    ResumeTask(u64),
-    DropTask(u64),
 }
 
 /// The scheduler holds basically everything around "working"
@@ -126,16 +115,26 @@ pub enum TaskMsg {
 ///
 /// We can prevent this safety issue from occurring if we track which scopes are invalidated when starting a new task.
 ///
+/// There's a lot of raw pointers here...
+///
+/// Since we're building self-referential structures for each component, we need to make sure that the referencs stay stable
+/// The best way to do that is a bump allocator.
+///
+///
 ///
 pub(crate) struct Scheduler {
-    /// All mounted components are arena allocated to make additions, removals, and references easy to work with
-    /// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
-    ///
-    /// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas
-    /// and rusts's guarantees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
-    pub pool: ResourcePool,
+    // /// All mounted components are arena allocated to make additions, removals, and references easy to work with
+    // /// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
+    // ///
+    // /// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas
+    // /// and rusts's guarantees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
+    // pub pool: ResourcePool,
+    //
+    pub component_arena: Bump,
+
+    pub free_components: VecDeque<*mut ScopeInner>,
 
-    pub heuristics: HeuristicsEngine,
+    pub heuristics: FxHashMap<FcSlot, Heuristic>,
 
     pub receiver: UnboundedReceiver<SchedulerMsg>,
 
@@ -145,104 +144,70 @@ pub(crate) struct Scheduler {
     // Every component that has futures that need to be polled
     pub pending_futures: FxHashSet<ScopeId>,
 
-    // In-flight futures
-    pub async_tasks: FuturesUnordered<FiberTask>,
-
     // // scheduler stuff
     // pub current_priority: EventPriority,
     pub ui_events: VecDeque<UserEvent>,
 
     pub pending_immediates: VecDeque<ScopeId>,
 
-    pub pending_tasks: VecDeque<UserEvent>,
-
     pub batched_events: VecDeque<UserEvent>,
 
     pub garbage_scopes: HashSet<ScopeId>,
 
     pub dirty_scopes: IndexSet<ScopeId>,
+
     pub saved_state: Option<SavedDiffWork<'static>>,
+
     pub in_progress: bool,
 }
 
+pub type FcSlot = *const ();
+
+pub struct Heuristic {
+    hook_arena_size: usize,
+    node_arena_size: usize,
+}
+
 impl Scheduler {
     pub(crate) fn new(
         sender: UnboundedSender<SchedulerMsg>,
         receiver: UnboundedReceiver<SchedulerMsg>,
+        component_capacity: usize,
+        element_capacity: usize,
     ) -> Self {
         /*
         Preallocate 2000 elements and 100 scopes to avoid dynamic allocation.
         Perhaps this should be configurable from some external config?
         */
-        let components = Rc::new(UnsafeCell::new(Slab::with_capacity(100)));
-        let raw_elements = Rc::new(UnsafeCell::new(Slab::with_capacity(2000)));
 
-        let heuristics = HeuristicsEngine::new();
-
-        let task_counter = Rc::new(Cell::new(0));
-        let cur_subtree = Rc::new(Cell::new(0));
+        // let components = Rc::new(UnsafeCell::new(Slab::with_capacity(component_capacity)));
+        let raw_elements = Rc::new(UnsafeCell::new(Slab::with_capacity(element_capacity)));
 
         let channel = EventChannel {
-            cur_subtree,
-            task_counter: task_counter.clone(),
             sender: sender.clone(),
-            schedule_any_immediate: {
-                let sender = sender.clone();
-                Rc::new(move |id| {
-                    //
-                    log::debug!("scheduling immediate! {:?}", id);
-                    sender.unbounded_send(SchedulerMsg::Immediate(id)).unwrap()
-                })
-            },
-            // todo: we want to get the futures out of the scheduler message
-            // the scheduler message should be send/sync
-            submit_task: {
-                Rc::new(move |fiber_task| {
-                    let task_id = task_counter.get();
-                    task_counter.set(task_id + 1);
-
-                    todo!();
-                    // sender
-                    //     .unbounded_send(SchedulerMsg::Task(TaskMsg::SubmitTask(
-                    //         fiber_task, task_id,
-                    //     )))
-                    //     .unwrap();
-                    TaskHandle {
-                        our_id: task_id,
-                        sender: sender.clone(),
-                    }
-                })
-            },
             get_shared_context: {
-                let components = components.clone();
-                Rc::new(move |id, ty| {
-                    let components = unsafe { &*components.get() };
-                    let mut search: Option<&ScopeInner> = components.get(id.0);
-                    while let Some(inner) = search.take() {
-                        if let Some(shared) = inner.shared_contexts.borrow().get(&ty) {
-                            return Some(shared.clone());
-                        } else {
-                            search = inner.parent_idx.map(|id| components.get(id.0)).flatten();
-                        }
-                    }
-                    None
-                })
+                todo!()
+                // let components = components.clone();
+                // Rc::new(move |id, ty| {
+                //     let components = unsafe { &*components.get() };
+                //     let mut search: Option<&ScopeInner> = components.get(id.0);
+                //     while let Some(inner) = search.take() {
+                //         if let Some(shared) = inner.shared_contexts.borrow().get(&ty) {
+                //             return Some(shared.clone());
+                //         } else {
+                //             search = inner.parent_idx.map(|id| components.get(id.0)).flatten();
+                //         }
+                //     }
+                //     None
+                // })
             },
         };
 
-        let pool = ResourcePool {
-            components,
-            raw_elements,
-            channel,
-        };
-
-        let async_tasks = FuturesUnordered::new();
-
-        // push a task that would never resolve - prevents us from immediately aborting the scheduler
-        async_tasks.push(Box::pin(async {
-            std::future::pending::<()>().await;
-            ScopeId(0)
-        }) as FiberTask);
+        // let pool = ResourcePool {
+        //     components,
+        //     raw_elements,
+        //     channel,
+        // };
 
         let saved_state = SavedDiffWork {
             mutations: Mutations::new(),
@@ -251,22 +216,15 @@ impl Scheduler {
         };
 
         Self {
-            pool,
-
+            // pool,
             receiver,
 
-            async_tasks,
-
             pending_garbage: FxHashSet::default(),
 
-            heuristics,
-
             ui_events: VecDeque::new(),
 
             pending_immediates: VecDeque::new(),
 
-            pending_tasks: VecDeque::new(),
-
             batched_events: VecDeque::new(),
 
             garbage_scopes: HashSet::new(),
@@ -275,6 +233,8 @@ impl Scheduler {
             dirty_scopes: Default::default(),
             saved_state: Some(saved_state),
             in_progress: false,
+
+            heuristics: todo!(),
         }
     }
 
@@ -282,7 +242,7 @@ impl Scheduler {
     pub fn handle_ui_event(&mut self, event: UserEvent) -> bool {
         let (discrete, priority) = event_meta(&event);
 
-        if let Some(scope) = self.pool.get_scope_mut(event.scope) {
+        if let Some(scope) = self.get_scope_mut(&event.scope) {
             if let Some(element) = event.mounted_dom_id {
                 // TODO: bubble properly here
                 scope.call_listener(event, element);
@@ -308,7 +268,7 @@ impl Scheduler {
 
     fn prepare_work(&mut self) {
         // while let Some(trigger) = self.ui_events.pop_back() {
-        //     if let Some(scope) = self.pool.get_scope_mut(trigger.scope) {}
+        //     if let Some(scope) = self.get_scope_mut(&trigger.scope) {}
         // }
     }
 
@@ -328,15 +288,9 @@ impl Scheduler {
     unsafe fn load_work(&mut self) -> SavedDiffWork<'static> {
         self.saved_state.take().unwrap().extend()
     }
-    pub fn handle_task(&mut self, evt: TaskMsg) {
-        //
-    }
 
     pub fn handle_channel_msg(&mut self, msg: SchedulerMsg) {
         match msg {
-            //
-            SchedulerMsg::Task(msg) => todo!(),
-
             SchedulerMsg::Immediate(_) => todo!(),
 
             SchedulerMsg::UiEvent(event) => {
@@ -344,7 +298,7 @@ impl Scheduler {
 
                 let (discrete, priority) = event_meta(&event);
 
-                if let Some(scope) = self.pool.get_scope_mut(event.scope) {
+                if let Some(scope) = self.get_scope_mut(&event.scope) {
                     if let Some(element) = event.mounted_dom_id {
                         // TODO: bubble properly here
                         scope.call_listener(event, element);
@@ -374,19 +328,19 @@ impl Scheduler {
         let saved_state = unsafe { self.load_work() };
 
         // We have to split away some parts of ourself - current lane is borrowed mutably
-        let shared = self.pool.clone();
+        let shared = self.clone();
         let mut machine = unsafe { saved_state.promote(&shared) };
 
         let mut ran_scopes = FxHashSet::default();
 
         if machine.stack.is_empty() {
-            let shared = self.pool.clone();
+            let shared = self.clone();
 
             self.dirty_scopes
-                .retain(|id| shared.get_scope(*id).is_some());
+                .retain(|id| shared.get_scope(id).is_some());
             self.dirty_scopes.sort_by(|a, b| {
-                let h1 = shared.get_scope(*a).unwrap().height;
-                let h2 = shared.get_scope(*b).unwrap().height;
+                let h1 = shared.get_scope(a).unwrap().height;
+                let h2 = shared.get_scope(b).unwrap().height;
                 h1.cmp(&h2).reverse()
             });
 
@@ -396,8 +350,8 @@ impl Scheduler {
                     ran_scopes.insert(scopeid);
                     log::debug!("about to run scope {:?}", scopeid);
 
-                    if let Some(component) = self.pool.get_scope_mut(scopeid) {
-                        if component.run_scope(&self.pool) {
+                    if let Some(component) = self.get_scope_mut(&scopeid) {
+                        if component.run_scope(&self) {
                             let (old, new) =
                                 (component.frames.wip_head(), component.frames.fin_head());
                             // let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
@@ -494,7 +448,6 @@ impl Scheduler {
         while self.has_any_work() {
             while let Ok(Some(msg)) = self.receiver.try_next() {
                 match msg {
-                    SchedulerMsg::Task(t) => todo!(),
                     SchedulerMsg::Immediate(im) => {
                         self.dirty_scopes.insert(im);
                     }
@@ -506,7 +459,7 @@ impl Scheduler {
 
             // switch our priority, pop off any work
             while let Some(event) = self.ui_events.pop_front() {
-                if let Some(scope) = self.pool.get_scope_mut(event.scope) {
+                if let Some(scope) = self.get_scope_mut(&event.scope) {
                     if let Some(element) = event.mounted_dom_id {
                         log::info!("Calling listener {:?}, {:?}", event.scope, element);
 
@@ -519,7 +472,6 @@ impl Scheduler {
                                     self.dirty_scopes.insert(im);
                                 }
                                 SchedulerMsg::UiEvent(e) => self.ui_events.push_back(e),
-                                SchedulerMsg::Task(_) => todo!(),
                             }
                         }
                     }
@@ -562,19 +514,19 @@ impl Scheduler {
     ///
     /// Typically used to kickstart the VirtualDOM after initialization.
     pub fn rebuild(&mut self, base_scope: ScopeId) -> Mutations {
-        let mut shared = self.pool.clone();
+        let mut shared = self.clone();
         let mut diff_machine = DiffMachine::new(Mutations::new(), &mut shared);
 
         // TODO: drain any in-flight work
         let cur_component = self
             .pool
-            .get_scope_mut(base_scope)
+            .get_scope_mut(&base_scope)
             .expect("The base scope should never be moved");
 
         log::debug!("rebuild {:?}", base_scope);
 
         // We run the component. If it succeeds, then we can diff it and add the changes to the dom.
-        if cur_component.run_scope(&self.pool) {
+        if cur_component.run_scope(&self) {
             diff_machine
                 .stack
                 .create_node(cur_component.frames.fin_head(), MountType::Append);
@@ -596,13 +548,13 @@ impl Scheduler {
     pub fn hard_diff(&mut self, base_scope: ScopeId) -> Mutations {
         let cur_component = self
             .pool
-            .get_scope_mut(base_scope)
+            .get_scope_mut(&base_scope)
             .expect("The base scope should never be moved");
 
         log::debug!("hard diff {:?}", base_scope);
 
-        if cur_component.run_scope(&self.pool) {
-            let mut diff_machine = DiffMachine::new(Mutations::new(), &mut self.pool);
+        if cur_component.run_scope(&self) {
+            let mut diff_machine = DiffMachine::new(Mutations::new(), &mut self);
             diff_machine.cfg.force_diff = true;
             diff_machine.diff_scope(base_scope);
             diff_machine.mutations
@@ -611,3 +563,35 @@ impl Scheduler {
         }
     }
 }
+
+impl Future for Scheduler {
+    type Output = ();
+
+    fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
+        let mut all_pending = true;
+
+        for fut in self.pending_futures.iter() {
+            let scope = self
+                .pool
+                .get_scope_mut(&fut)
+                .expect("Scope should never be moved");
+
+            let items = scope.items.get_mut();
+            for task in items.tasks.iter_mut() {
+                let t = task.as_mut();
+                let g = unsafe { Pin::new_unchecked(t) };
+                match g.poll(cx) {
+                    Poll::Ready(r) => {
+                        all_pending = false;
+                    }
+                    Poll::Pending => {}
+                }
+            }
+        }
+
+        match all_pending {
+            true => Poll::Pending,
+            false => Poll::Ready(()),
+        }
+    }
+}

+ 0 - 0
packages/core/src/threadsafe.rs → packages/core/src/old/threadsafe.rs


+ 220 - 329
packages/core/src/scope.rs

@@ -1,44 +1,36 @@
 use crate::innerlude::*;
 
+use futures_channel::mpsc::UnboundedSender;
 use fxhash::FxHashMap;
 use std::{
     any::{Any, TypeId},
     cell::{Cell, RefCell},
     collections::HashMap,
     future::Future,
-    pin::Pin,
     rc::Rc,
 };
 
-use crate::{innerlude::*, lazynodes::LazyNodes};
 use bumpalo::{boxed::Box as BumpBox, Bump};
-use std::ops::Deref;
 
 /// Components in Dioxus use the "Context" object to interact with their lifecycle.
 ///
 /// This lets components access props, schedule updates, integrate hooks, and expose shared state.
 ///
-/// Note: all of these methods are *imperative* - they do not act as hooks! They are meant to be used by hooks
-/// to provide complex behavior. For instance, calling "add_shared_state" on every render is considered a leak. This method
-/// exists for the `use_provide_state` hook to provide a shared state object.
-///
 /// For the most part, the only method you should be using regularly is `render`.
 ///
 /// ## Example
 ///
 /// ```ignore
-/// #[derive(Properties)]
-/// struct Props {
+/// #[derive(Props)]
+/// struct ExampleProps {
 ///     name: String
 /// }
 ///
-/// fn example(cx: Context<Props>) -> VNode {
-///     html! {
-///         <div> "Hello, {cx.name}" </div>
-///     }
+/// fn Example((cx, props): Scope<Props>) -> Element {
+///     cx.render(rsx!{ div {"Hello, {props.name}"} })
 /// }
 /// ```
-pub type Context<'a> = &'a ScopeInner;
+pub type Context<'a> = &'a Scope;
 
 /// Every component in Dioxus is represented by a `Scope`.
 ///
@@ -49,17 +41,33 @@ pub type Context<'a> = &'a ScopeInner;
 ///
 /// We expose the `Scope` type so downstream users can traverse the Dioxus VirtualDOM for whatever
 /// use case they might have.
-pub struct ScopeInner {
+pub struct Scope {
     // Book-keeping about our spot in the arena
-    pub(crate) parent_idx: Option<ScopeId>,
+
+    // safety:
+    //
+    // pointers to scopes are *always* valid since they are bump allocated and never freed until this scope is also freed
+    // this is just a bit of a hack to not need an Rc to the ScopeArena.
+    // todo: replace this will ScopeId and provide a connection to scope arena directly
+    pub(crate) parent_scope: Option<*mut Scope>,
+
     pub(crate) our_arena_idx: ScopeId,
+
     pub(crate) height: u32,
+
     pub(crate) subtree: Cell<u32>,
+
     pub(crate) is_subtree_root: Cell<bool>,
 
-    // Nodes
-    pub(crate) frames: ActiveFrame,
-    pub(crate) caller: BumpBox<'static, dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>>,
+    pub(crate) generation: Cell<u32>,
+
+    // The double-buffering situation that we will use
+    pub(crate) frames: [BumpFrame; 2],
+
+    pub(crate) old_root: RefCell<Option<NodeLink>>,
+    pub(crate) new_root: RefCell<Option<NodeLink>>,
+
+    pub(crate) caller: *mut dyn Fn(&Scope) -> Element,
 
     /*
     we care about:
@@ -67,12 +75,7 @@ pub struct ScopeInner {
     - borrowed props (and how to drop them when the parent is dropped)
     - suspended nodes (and how to call their callback when their associated tasks are complete)
     */
-    pub(crate) listeners: RefCell<Vec<*const Listener<'static>>>,
-    pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
-    pub(crate) suspended_nodes: RefCell<FxHashMap<u64, *const VSuspended<'static>>>,
-
-    pub(crate) tasks: RefCell<Vec<BumpBox<'static, dyn Future<Output = ()>>>>,
-    pub(crate) pending_effects: RefCell<Vec<BumpBox<'static, dyn FnMut()>>>,
+    pub(crate) items: RefCell<SelfReferentialItems<'static>>,
 
     // State
     pub(crate) hooks: HookList,
@@ -80,34 +83,19 @@ pub struct ScopeInner {
     // todo: move this into a centralized place - is more memory efficient
     pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
 
-    // whenever set_state is called, we fire off a message to the scheduler
-    // this closure _is_ the method called by schedule_update that marks this component as dirty
-    pub(crate) memoized_updater: Rc<dyn Fn()>,
-
-    pub(crate) shared: EventChannel,
+    pub(crate) sender: UnboundedSender<SchedulerMsg>,
 }
 
-/// Public interface for Scopes.
-impl ScopeInner {
-    /// Get the root VNode for this Scope.
-    ///
-    /// This VNode is the "entrypoint" VNode. If the component renders multiple nodes, then this VNode will be a fragment.
-    ///
-    /// # Example
-    /// ```rust
-    /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} }));
-    /// dom.rebuild();
-    ///
-    /// let base = dom.base_scope();
-    ///
-    /// if let VNode::VElement(node) = base.root_node() {
-    ///     assert_eq!(node.tag_name, "div");
-    /// }
-    /// ```
-    pub fn root_node(&self) -> &VNode {
-        self.frames.fin_head()
-    }
+pub struct SelfReferentialItems<'a> {
+    pub(crate) listeners: Vec<&'a Listener<'a>>,
+    pub(crate) borrowed_props: Vec<&'a VComponent<'a>>,
+    pub(crate) suspended_nodes: FxHashMap<u64, &'a VSuspended<'a>>,
+    pub(crate) tasks: Vec<BumpBox<'a, dyn Future<Output = ()>>>,
+    pub(crate) pending_effects: Vec<BumpBox<'a, dyn FnMut()>>,
+}
 
+// Public methods exposed to libraries and components
+impl Scope {
     /// Get the subtree ID that this scope belongs to.
     ///
     /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
@@ -128,16 +116,6 @@ impl ScopeInner {
         self.subtree.get()
     }
 
-    pub(crate) fn new_subtree(&self) -> Option<u32> {
-        if self.is_subtree_root.get() {
-            None
-        } else {
-            let cur = self.shared.cur_subtree.get();
-            self.shared.cur_subtree.set(cur + 1);
-            Some(cur)
-        }
-    }
-
     /// Get the height of this Scope - IE the number of scopes above it.
     ///
     /// A Scope with a height of `0` is the root scope - there are no other scopes above it.
@@ -173,7 +151,10 @@ impl ScopeInner {
     /// assert_eq!(base.parent(), None);
     /// ```
     pub fn parent(&self) -> Option<ScopeId> {
-        self.parent_idx
+        match self.parent_scope {
+            Some(p) => Some(unsafe { &*p }.our_arena_idx),
+            None => None,
+        }
     }
 
     /// Get the ID of this Scope within this Dioxus VirtualDOM.
@@ -192,250 +173,50 @@ impl ScopeInner {
     pub fn scope_id(&self) -> ScopeId {
         self.our_arena_idx
     }
-}
 
-// The type of closure that wraps calling components
-/// The type of task that gets sent to the task scheduler
-/// Submitting a fiber task returns a handle to that task, which can be used to wake up suspended nodes
-pub type FiberTask = Pin<Box<dyn Future<Output = ScopeId>>>;
-
-/// Private interface for Scopes.
-impl ScopeInner {
-    // we are being created in the scope of an existing component (where the creator_node lifetime comes into play)
-    // we are going to break this lifetime by force in order to save it on ourselves.
-    // To make sure that the lifetime isn't truly broken, we receive a Weak RC so we can't keep it around after the parent dies.
-    // This should never happen, but is a good check to keep around
-    //
-    // Scopes cannot be made anywhere else except for this file
-    // Therefore, their lifetimes are connected exclusively to the virtual dom
-    pub(crate) fn new(
-        caller: BumpBox<dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>>,
-        our_arena_idx: ScopeId,
-        parent_idx: Option<ScopeId>,
-        height: u32,
-        subtree: u32,
-        shared: EventChannel,
-    ) -> Self {
-        let schedule_any_update = shared.schedule_any_immediate.clone();
-
-        let memoized_updater = Rc::new(move || schedule_any_update(our_arena_idx));
-
-        // wipe away the associated lifetime - we are going to manually manage the one-way lifetime graph
-        let caller = unsafe { std::mem::transmute(caller) };
-
-        Self {
-            memoized_updater,
-            shared,
-            caller,
-            parent_idx,
-            our_arena_idx,
-            height,
-            subtree: Cell::new(subtree),
-            is_subtree_root: Cell::new(false),
-            tasks: Default::default(),
-            frames: ActiveFrame::new(),
-            hooks: Default::default(),
-
-            pending_effects: Default::default(),
-            suspended_nodes: Default::default(),
-            shared_contexts: Default::default(),
-            listeners: Default::default(),
-            borrowed_props: Default::default(),
-        }
-    }
-
-    pub(crate) fn update_scope_dependencies(
-        &mut self,
-        caller: &dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>,
-    ) {
-        log::debug!("Updating scope dependencies {:?}", self.our_arena_idx);
-        let caller = caller as *const _;
-        self.caller = unsafe { std::mem::transmute(caller) };
-    }
-
-    /// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from
-    /// causing UB in our tree.
-    ///
-    /// This works by cleaning up our references from the bottom of the tree to the top. The directed graph of components
-    /// essentially forms a dependency tree that we can traverse from the bottom to the top. As we traverse, we remove
-    /// any possible references to the data in the hook list.
-    ///
-    /// References to hook data can only be stored in listeners and component props. During diffing, we make sure to log
-    /// all listeners and borrowed props so we can clear them here.
-    ///
-    /// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will
-    /// be dropped.
-    pub(crate) fn ensure_drop_safety(&mut self, pool: &ResourcePool) {
-        // make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
-        // run the hooks (which hold an &mut Reference)
-        // right now, we don't drop
-        self.borrowed_props
-            .get_mut()
-            .drain(..)
-            .map(|li| unsafe { &*li })
-            .for_each(|comp| {
-                // First drop the component's undropped references
-                let scope_id = comp
-                    .associated_scope
-                    .get()
-                    .expect("VComponents should be associated with a valid Scope");
-
-                if let Some(scope) = pool.get_scope_mut(scope_id) {
-                    scope.ensure_drop_safety(pool);
-
-                    let mut drop_props = comp.drop_props.borrow_mut().take().unwrap();
-                    drop_props();
-                }
-            });
-
-        // Now that all the references are gone, we can safely drop our own references in our listeners.
-        self.listeners
-            .get_mut()
-            .drain(..)
-            .map(|li| unsafe { &*li })
-            .for_each(|listener| drop(listener.callback.borrow_mut().take()));
-    }
-
-    /// A safe wrapper around calling listeners
-    pub(crate) fn call_listener(&mut self, event: UserEvent, element: ElementId) {
-        let listners = self.listeners.borrow_mut();
-
-        let raw_listener = listners.iter().find(|lis| {
-            let search = unsafe { &***lis };
-            if search.event == event.name {
-                let search_id = search.mounted_node.get();
-                search_id.map(|f| f == element).unwrap_or(false)
-            } else {
-                false
-            }
-        });
-
-        if let Some(raw_listener) = raw_listener {
-            let listener = unsafe { &**raw_listener };
-            let mut cb = listener.callback.borrow_mut();
-            if let Some(cb) = cb.as_mut() {
-                (cb)(event.event);
-            }
-        } else {
-            log::warn!("An event was triggered but there was no listener to handle it");
-        }
-    }
-
-    /*
-    General strategy here is to load up the appropriate suspended task and then run it.
-    Suspended nodes cannot be called repeatedly.
-    */
-    pub(crate) fn call_suspended_node<'a>(&'a mut self, task_id: u64) {
-        let mut nodes = self.suspended_nodes.borrow_mut();
-
-        if let Some(suspended) = nodes.remove(&task_id) {
-            let sus: &'a VSuspended<'static> = unsafe { &*suspended };
-            let sus: &'a VSuspended<'a> = unsafe { std::mem::transmute(sus) };
-            let mut boxed = sus.callback.borrow_mut().take().unwrap();
-            let new_node: Element<'a> = boxed();
-        }
-    }
-
-    // run the list of effects
-    pub(crate) fn run_effects(&mut self, pool: &ResourcePool) {
-        todo!()
-        // let mut effects = self.frames.effects.borrow_mut();
-        // let mut effects = effects.drain(..).collect::<Vec<_>>();
-
-        // for effect in effects {
-        //     let effect = unsafe { &*effect };
-        //     let effect = effect.as_ref();
-
-        //     let mut effect = effect.borrow_mut();
-        //     let mut effect = effect.as_mut();
-
-        //     effect.run(pool);
-        // }
-    }
-
-    /// Render this component.
-    ///
-    /// Returns true if the scope completed successfully and false if running failed (IE a None error was propagated).
-    pub(crate) fn run_scope<'sel>(&'sel mut self, pool: &ResourcePool) -> bool {
-        // Cycle to the next frame and then reset it
-        // This breaks any latent references, invalidating every pointer referencing into it.
-        // Remove all the outdated listeners
-        self.ensure_drop_safety(pool);
-
-        // Safety:
-        // - We dropped the listeners, so no more &mut T can be used while these are held
-        // - All children nodes that rely on &mut T are replaced with a new reference
-        unsafe { self.hooks.reset() };
-
-        // Safety:
-        // - We've dropped all references to the wip bump frame
-        unsafe { self.frames.reset_wip_frame() };
-
-        // just forget about our suspended nodes while we're at it
-        self.suspended_nodes.get_mut().clear();
-
-        // guarantee that we haven't screwed up - there should be no latent references anywhere
-        debug_assert!(self.listeners.borrow().is_empty());
-        debug_assert!(self.suspended_nodes.borrow().is_empty());
-        debug_assert!(self.borrowed_props.borrow().is_empty());
-
-        log::debug!("Borrowed stuff is successfully cleared");
-
-        // Cast the caller ptr from static to one with our own reference
-        let render: &dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> = unsafe { &*self.caller };
-
-        // Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders.
-        if let Some(builder) = render(self) {
-            let new_head = builder.into_vnode(NodeFactory {
-                bump: &self.frames.wip_frame().bump,
-            });
-            log::debug!("Render is successful");
-
-            // the user's component succeeded. We can safely cycle to the next frame
-            self.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
-            self.frames.cycle_frame();
-
-            true
-        } else {
-            false
-        }
-    }
     /// Create a subscription that schedules a future render for the reference component
     ///
     /// ## Notice: you should prefer using prepare_update and get_scope_id
     pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
-        self.memoized_updater.clone()
+        // pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
+        let chan = self.sender.clone();
+        let id = self.scope_id();
+        Rc::new(move || {
+            let _ = chan.unbounded_send(SchedulerMsg::Immediate(id));
+        })
+    }
+
+    /// Schedule an update for any component given its ScopeId.
+    ///
+    /// A component's ScopeId can be obtained from `use_hook` or the [`Context::scope_id`] method.
+    ///
+    /// This method should be used when you want to schedule an update for a component
+    pub fn schedule_update_any(&self) -> Rc<dyn Fn(ScopeId)> {
+        let chan = self.sender.clone();
+        Rc::new(move |id| {
+            let _ = chan.unbounded_send(SchedulerMsg::Immediate(id));
+        })
     }
 
     /// Get the [`ScopeId`] of a mounted component.
     ///
     /// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted.
     pub fn needs_update(&self) {
-        (self.memoized_updater)()
+        self.needs_update_any(self.scope_id())
     }
 
     /// Get the [`ScopeId`] of a mounted component.
     ///
     /// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted.
     pub fn needs_update_any(&self, id: ScopeId) {
-        (self.shared.schedule_any_immediate)(id)
-    }
-
-    /// Schedule an update for any component given its ScopeId.
-    ///
-    /// A component's ScopeId can be obtained from `use_hook` or the [`Context::scope_id`] method.
-    ///
-    /// This method should be used when you want to schedule an update for a component
-    pub fn schedule_update_any(&self) -> Rc<dyn Fn(ScopeId)> {
-        self.shared.schedule_any_immediate.clone()
+        let _ = self.sender.unbounded_send(SchedulerMsg::Immediate(id));
     }
 
     /// Get the [`ScopeId`] of a mounted component.
     ///
     /// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted.
     pub fn bump(&self) -> &Bump {
-        let bump = &self.frames.wip_frame().bump;
-        bump
+        &self.wip_frame().bump
     }
 
     /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
@@ -445,21 +226,28 @@ impl ScopeInner {
     /// ## Example
     ///
     /// ```ignore
-    /// fn Component(cx: Context<()>) -> VNode {
+    /// fn Component(cx: Scope, props: &Props) -> Element {
     ///     // Lazy assemble the VNode tree
-    ///     let lazy_tree = html! {<div> "Hello World" </div>};
+    ///     let lazy_nodes = rsx!("hello world");
     ///
     ///     // Actually build the tree and allocate it
     ///     cx.render(lazy_tree)
     /// }
     ///```
-    pub fn render<'src>(
-        &'src self,
-        lazy_nodes: Option<LazyNodes<'src, '_>>,
-    ) -> Option<VNode<'src>> {
-        let bump = &self.frames.wip_frame().bump;
+    pub fn render<'src>(&'src self, lazy_nodes: Option<LazyNodes<'src, '_>>) -> Option<NodeLink> {
+        let bump = &self.wip_frame().bump;
         let factory = NodeFactory { bump };
-        lazy_nodes.map(|f| f.call(factory))
+        let node = lazy_nodes.map(|f| f.call(factory))?;
+
+        let idx = self
+            .wip_frame()
+            .add_node(unsafe { std::mem::transmute(node) });
+
+        Some(NodeLink {
+            gen_id: self.generation.get(),
+            scope_id: self.our_arena_idx,
+            link_idx: idx,
+        })
     }
 
     /// Push an effect to be ran after the component has been successfully mounted to the dom
@@ -475,8 +263,9 @@ impl ScopeInner {
         // erase the 'src lifetime for self-referential storage
         let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
 
-        self.pending_effects.borrow_mut().push(self_ref_fut);
-        self.pending_effects.borrow().len() - 1
+        let mut items = self.items.borrow_mut();
+        items.pending_effects.push(self_ref_fut);
+        items.pending_effects.len() - 1
     }
 
     /// Pushes the future onto the poll queue to be polled
@@ -491,8 +280,9 @@ impl ScopeInner {
         // erase the 'src lifetime for self-referential storage
         let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
 
-        self.tasks.borrow_mut().push(self_ref_fut);
-        self.tasks.borrow().len() - 1
+        let mut items = self.items.borrow_mut();
+        items.tasks.push(self_ref_fut);
+        items.tasks.len() - 1
     }
 
     /// This method enables the ability to expose state to children further down the VirtualDOM Tree.
@@ -519,7 +309,7 @@ impl ScopeInner {
     ///     rsx!(cx, div { "hello {state.0}" })
     /// }
     /// ```
-    pub fn provide_state<T>(self, value: T)
+    pub fn provide_state<T>(&self, value: T)
     where
         T: 'static,
     {
@@ -531,11 +321,21 @@ impl ScopeInner {
     }
 
     /// Try to retrieve a SharedState with type T from the any parent Scope.
-    pub fn consume_state<T: 'static>(self) -> Option<Rc<T>> {
-        let getter = &self.shared.get_shared_context;
-        let ty = TypeId::of::<T>();
-        let idx = self.our_arena_idx;
-        getter(idx, ty).map(|f| f.downcast().unwrap())
+    pub fn consume_state<T: 'static>(&self) -> Option<Rc<T>> {
+        if let Some(shared) = self.shared_contexts.borrow().get(&TypeId::of::<T>()) {
+            Some(shared.clone().downcast::<T>().unwrap())
+        } else {
+            let mut search_parent = self.parent_scope;
+
+            while let Some(parent_ptr) = search_parent {
+                let parent = unsafe { &*parent_ptr };
+                if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::<T>()) {
+                    return Some(shared.clone().downcast::<T>().unwrap());
+                }
+                search_parent = parent.parent_scope;
+            }
+            None
+        }
     }
 
     /// Create a new subtree with this scope as the root of the subtree.
@@ -553,7 +353,7 @@ impl ScopeInner {
     ///     rsx!(cx, div { "Subtree {id}"})
     /// };
     /// ```
-    pub fn create_subtree(self) -> Option<u32> {
+    pub fn create_subtree(&self) -> Option<u32> {
         self.new_subtree()
     }
 
@@ -570,7 +370,7 @@ impl ScopeInner {
     ///     rsx!(cx, div { "Subtree {id}"})
     /// };
     /// ```
-    pub fn get_current_subtree(self) -> u32 {
+    pub fn get_current_subtree(&self) -> u32 {
         self.subtree()
     }
 
@@ -580,8 +380,9 @@ impl ScopeInner {
     ///
     /// - Initializer: closure used to create the initial hook state
     /// - Runner: closure used to output a value every time the hook is used
-    /// - Cleanup: closure used to teardown the hook once the dom is cleaned up
     ///
+    /// To "cleanup" the hook, implement `Drop` on the stored hook value. Whenever the component is dropped, the hook
+    /// will be dropped as well.
     ///
     /// # Example
     ///
@@ -591,42 +392,132 @@ impl ScopeInner {
     ///     use_hook(
     ///         || Rc::new(RefCell::new(initial_value())),
     ///         |state| state,
-    ///         |_| {},
     ///     )
     /// }
     /// ```
-    pub fn use_hook<'src, State, Output, Init, Run, Cleanup>(
+    pub fn use_hook<'src, State: 'static, Output: 'src>(
         &'src self,
-        initializer: Init,
-        runner: Run,
-        cleanup: Cleanup,
-    ) -> Output
-    where
-        State: 'static,
-        Output: 'src,
-        Init: FnOnce(usize) -> State,
-        Run: FnOnce(&'src mut State) -> Output,
-        Cleanup: FnOnce(Box<State>) + 'static,
-    {
-        // If the idx is the same as the hook length, then we need to add the current hook
+        initializer: impl FnOnce(usize) -> State,
+        runner: impl FnOnce(&'src mut State) -> Output,
+    ) -> Output {
         if self.hooks.at_end() {
-            self.hooks.push_hook(
-                initializer(self.hooks.len()),
-                Box::new(|raw| {
-                    let s = raw.downcast::<State>().unwrap();
-                    cleanup(s);
-                }),
-            );
+            self.hooks.push_hook(initializer(self.hooks.len()));
         }
 
-        runner(self.hooks.next::<State>().expect(HOOK_ERR_MSG))
-    }
-}
-
-const HOOK_ERR_MSG: &str = r###"
+        const HOOK_ERR_MSG: &str = r###"
 Unable to retrieve the hook that was initialized at this index.
 Consult the `rules of hooks` to understand how to use hooks properly.
 
 You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
 Functions prefixed with "use" should never be called conditionally.
 "###;
+
+        runner(self.hooks.next::<State>().expect(HOOK_ERR_MSG))
+    }
+}
+
+// Important internal methods
+impl Scope {
+    /// The "work in progress frame" represents the frame that is currently being worked on.
+    pub(crate) fn wip_frame(&self) -> &BumpFrame {
+        match self.generation.get() & 1 == 0 {
+            true => &self.frames[0],
+            false => &self.frames[1],
+        }
+    }
+    pub(crate) fn fin_frame(&self) -> &BumpFrame {
+        match self.generation.get() & 1 == 1 {
+            true => &self.frames[0],
+            false => &self.frames[1],
+        }
+    }
+
+    pub unsafe fn reset_wip_frame(&self) {
+        // todo: unsafecell or something
+        let bump = self.wip_frame() as *const _ as *mut Bump;
+        let g = &mut *bump;
+        g.reset();
+    }
+
+    pub fn cycle_frame(&self) {
+        self.generation.set(self.generation.get() + 1);
+    }
+
+    /// A safe wrapper around calling listeners
+    pub(crate) fn call_listener(&self, event: UserEvent, element: ElementId) {
+        let listners = &mut self.items.borrow_mut().listeners;
+
+        let listener = listners.iter().find(|lis| {
+            let search = lis;
+            if search.event == event.name {
+                let search_id = search.mounted_node.get();
+                search_id.map(|f| f == element).unwrap_or(false)
+            } else {
+                false
+            }
+        });
+
+        if let Some(listener) = listener {
+            let mut cb = listener.callback.borrow_mut();
+            if let Some(cb) = cb.as_mut() {
+                (cb)(event.event);
+            }
+        } else {
+            log::warn!("An event was triggered but there was no listener to handle it");
+        }
+    }
+
+    // General strategy here is to load up the appropriate suspended task and then run it.
+    // Suspended nodes cannot be called repeatedly.
+    pub(crate) fn call_suspended_node<'a>(&'a mut self, task_id: u64) {
+        let mut nodes = &mut self.items.get_mut().suspended_nodes;
+
+        if let Some(suspended) = nodes.remove(&task_id) {
+            let sus: &'a VSuspended<'static> = unsafe { &*suspended };
+            let sus: &'a VSuspended<'a> = unsafe { std::mem::transmute(sus) };
+            let mut boxed = sus.callback.borrow_mut().take().unwrap();
+            let new_node: Element = boxed();
+        }
+    }
+
+    // run the list of effects
+    pub(crate) fn run_effects(&mut self) {
+        for mut effect in self.items.get_mut().pending_effects.drain(..) {
+            effect();
+        }
+    }
+
+    pub(crate) fn new_subtree(&self) -> Option<u32> {
+        todo!()
+        // if self.is_subtree_root.get() {
+        //     None
+        // } else {
+        //     let cur = self.shared.cur_subtree.get();
+        //     self.shared.cur_subtree.set(cur + 1);
+        //     Some(cur)
+        // }
+    }
+}
+
+pub struct BumpFrame {
+    pub bump: Bump,
+    pub nodes: RefCell<Vec<VNode<'static>>>,
+}
+impl BumpFrame {
+    pub fn new() -> Self {
+        let bump = Bump::new();
+
+        let node = &*bump.alloc(VText {
+            text: "asd",
+            dom_id: Default::default(),
+            is_static: false,
+        });
+        let nodes = RefCell::new(vec![VNode::Text(unsafe { std::mem::transmute(node) })]);
+        Self { bump, nodes }
+    }
+    fn add_node(&self, node: VNode<'static>) -> usize {
+        let mut nodes = self.nodes.borrow_mut();
+        nodes.push(node);
+        nodes.len() - 1
+    }
+}

+ 290 - 0
packages/core/src/scopearena.rs

@@ -0,0 +1,290 @@
+use slab::Slab;
+use std::{
+    borrow::BorrowMut,
+    cell::{Cell, RefCell},
+};
+
+use bumpalo::{boxed::Box as BumpBox, Bump};
+use futures_channel::mpsc::UnboundedSender;
+
+use crate::innerlude::*;
+
+pub type FcSlot = *const ();
+// pub heuristics: FxHashMap<FcSlot, Heuristic>,
+
+pub struct Heuristic {
+    hook_arena_size: usize,
+    node_arena_size: usize,
+}
+
+// a slab-like arena with stable references even when new scopes are allocated
+// uses a bump arena as a backing
+//
+// has an internal heuristics engine to pre-allocate arenas to the right size
+pub(crate) struct ScopeArena {
+    bump: Bump,
+    scopes: Vec<*mut Scope>,
+    free_scopes: Vec<ScopeId>,
+    nodes: RefCell<Slab<*const VNode<'static>>>,
+    pub(crate) sender: UnboundedSender<SchedulerMsg>,
+}
+
+impl ScopeArena {
+    pub fn new(sender: UnboundedSender<SchedulerMsg>) -> Self {
+        Self {
+            bump: Bump::new(),
+            scopes: Vec::new(),
+            free_scopes: Vec::new(),
+            nodes: RefCell::new(Slab::new()),
+            sender,
+        }
+    }
+
+    pub fn get_scope(&self, id: &ScopeId) -> Option<&Scope> {
+        unsafe { Some(&*self.scopes[id.0]) }
+    }
+
+    pub fn new_with_key(
+        &mut self,
+        fc_ptr: *const (),
+        caller: *mut dyn Fn(&Scope) -> Element,
+        parent_scope: Option<*mut Scope>,
+        height: u32,
+        subtree: u32,
+    ) -> ScopeId {
+        if let Some(id) = self.free_scopes.pop() {
+            // have already called drop on it - the slot is still chillin tho
+            let scope = unsafe { &mut *self.scopes[id.0 as usize] };
+
+            todo!("override the scope contents");
+            id
+        } else {
+            let scope_id = ScopeId(self.scopes.len());
+
+            let old_root = NodeLink {
+                link_idx: 0,
+                gen_id: 0,
+                scope_id,
+            };
+            let new_root = NodeLink {
+                link_idx: 0,
+                gen_id: 0,
+                scope_id,
+            };
+
+            let new_scope = Scope {
+                sender: self.sender.clone(),
+                parent_scope,
+                our_arena_idx: scope_id,
+                height,
+                subtree: Cell::new(subtree),
+                is_subtree_root: Cell::new(false),
+                frames: [BumpFrame::new(), BumpFrame::new()],
+
+                hooks: Default::default(),
+                shared_contexts: Default::default(),
+                caller,
+                generation: 0.into(),
+
+                old_root: RefCell::new(Some(old_root)),
+                new_root: RefCell::new(Some(new_root)),
+                // old_root: RefCell::new(Some(old_root)),
+                // new_root: RefCell::new(None),
+                items: RefCell::new(SelfReferentialItems {
+                    listeners: Default::default(),
+                    borrowed_props: Default::default(),
+                    suspended_nodes: Default::default(),
+                    tasks: Default::default(),
+                    pending_effects: Default::default(),
+                }),
+            };
+
+            let stable = self.bump.alloc(new_scope);
+            self.scopes.push(stable);
+            scope_id
+        }
+    }
+
+    pub fn try_remove(&self, id: &ScopeId) -> Option<Scope> {
+        todo!()
+    }
+
+    pub fn reserve_node(&self, node: &VNode) -> ElementId {
+        let mut els = self.nodes.borrow_mut();
+        let entry = els.vacant_entry();
+        let key = entry.key();
+        let id = ElementId(key);
+        let node = node as *const _;
+        let node = unsafe { std::mem::transmute(node) };
+        entry.insert(node);
+        id
+
+        // let nodes = self.nodes.borrow_mut();
+        // let id = nodes.insert(());
+        // let node_id = ElementId(id);
+        // node = Some(node_id);
+        // node_id
+    }
+
+    pub fn collect_garbage(&self, id: ElementId) {
+        todo!()
+    }
+
+    // These methods would normally exist on `scope` but they need access to *all* of the scopes
+
+    /// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from
+    /// causing UB in our tree.
+    ///
+    /// This works by cleaning up our references from the bottom of the tree to the top. The directed graph of components
+    /// essentially forms a dependency tree that we can traverse from the bottom to the top. As we traverse, we remove
+    /// any possible references to the data in the hook list.
+    ///
+    /// References to hook data can only be stored in listeners and component props. During diffing, we make sure to log
+    /// all listeners and borrowed props so we can clear them here.
+    ///
+    /// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will
+    /// be dropped.
+    pub(crate) fn ensure_drop_safety(&self, scope_id: &ScopeId) {
+        let scope = self.get_scope(scope_id).unwrap();
+
+        // make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
+        // run the hooks (which hold an &mut Reference)
+        // right now, we don't drop
+        scope
+            .items
+            .borrow_mut()
+            .borrowed_props
+            .drain(..)
+            .for_each(|comp| {
+                // First drop the component's undropped references
+                let scope_id = comp
+                    .associated_scope
+                    .get()
+                    .expect("VComponents should be associated with a valid Scope");
+
+                todo!("move this onto virtualdom");
+                // let scope = unsafe { &mut *scope_id };
+
+                // scope.ensure_drop_safety();
+
+                todo!("drop the component's props");
+                // let mut drop_props = comp.drop_props.borrow_mut().take().unwrap();
+                // drop_props();
+            });
+
+        // Now that all the references are gone, we can safely drop our own references in our listeners.
+        scope
+            .items
+            .borrow_mut()
+            .listeners
+            .drain(..)
+            .map(|li| unsafe { &*li })
+            .for_each(|listener| drop(listener.callback.borrow_mut().take()));
+    }
+
+    pub(crate) fn run_scope(&self, id: &ScopeId) -> bool {
+        let scope = self
+            .get_scope(id)
+            .expect("The base scope should never be moved");
+
+        // Cycle to the next frame and then reset it
+        // This breaks any latent references, invalidating every pointer referencing into it.
+        // Remove all the outdated listeners
+        self.ensure_drop_safety(id);
+
+        // Safety:
+        // - We dropped the listeners, so no more &mut T can be used while these are held
+        // - All children nodes that rely on &mut T are replaced with a new reference
+        unsafe { scope.hooks.reset() };
+
+        // Safety:
+        // - We've dropped all references to the wip bump frame with "ensure_drop_safety"
+        unsafe { scope.reset_wip_frame() };
+
+        {
+            let mut items = scope.items.borrow_mut();
+
+            // just forget about our suspended nodes while we're at it
+            items.suspended_nodes.clear();
+
+            // guarantee that we haven't screwed up - there should be no latent references anywhere
+            debug_assert!(items.listeners.is_empty());
+            debug_assert!(items.suspended_nodes.is_empty());
+            debug_assert!(items.borrowed_props.is_empty());
+
+            log::debug!("Borrowed stuff is successfully cleared");
+
+            // temporarily cast the vcomponent to the right lifetime
+            // let vcomp = scope.load_vcomp();
+        }
+
+        let render: &dyn Fn(&Scope) -> Element = unsafe { &*scope.caller };
+
+        // Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders.
+        scope.wip_frame().nodes.borrow_mut().clear();
+        if let Some(key) = render(scope) {
+            dbg!(key);
+
+            dbg!(&scope.wip_frame().nodes.borrow_mut());
+            // let mut old = scope.old_root.borrow_mut();
+            // let mut new = scope.new_root.borrow_mut();
+
+            // let new_old = new.clone();
+            // *old = new_old;
+            // *new = Some(key);
+
+            // dbg!(&old);
+            // dbg!(&new);
+
+            // the user's component succeeded. We can safely cycle to the next frame
+            // scope.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
+            scope.cycle_frame();
+
+            true
+        } else {
+            false
+        }
+    }
+
+    pub fn wip_head(&self, id: &ScopeId) -> &VNode {
+        let scope = self.get_scope(id).unwrap();
+        let wip_frame = scope.wip_frame();
+        let nodes = wip_frame.nodes.borrow();
+        let node = nodes.get(0).unwrap();
+        unsafe { std::mem::transmute(node) }
+        // let root = scope.old_root.borrow();
+        // let link = root.as_ref().unwrap();
+        // dbg!(link);
+
+        // // for now, components cannot pass portals through each other
+        // assert_eq!(link.scope_id, *id);
+        // // assert_eq!(link.gen_id, scope.generation.get() - 1);
+
+        // // let items = scope.items.borrow();
+        // let nodes = scope.wip_frame().nodes.borrow();
+        // let node = nodes.get(link.link_idx).unwrap();
+        // unsafe { std::mem::transmute(node) }
+    }
+
+    pub fn fin_head(&self, id: &ScopeId) -> &VNode {
+        let scope = self.get_scope(id).unwrap();
+        let wip_frame = scope.fin_frame();
+        let nodes = wip_frame.nodes.borrow();
+        let node = nodes.get(0).unwrap();
+        unsafe { std::mem::transmute(node) }
+        // let scope = self.get_scope(id).unwrap();
+        // let root = scope.new_root.borrow();
+        // let link = root.as_ref().unwrap();
+
+        // // for now, components cannot pass portals through each other
+        // assert_eq!(link.scope_id, *id);
+        // // assert_eq!(link.gen_id, scope.generation.get());
+
+        // let nodes = scope.fin_frame().nodes.borrow();
+        // let node = nodes.get(link.link_idx).unwrap();
+        // unsafe { std::mem::transmute(node) }
+    }
+    pub fn root_node(&self, id: &ScopeId) -> &VNode {
+        self.wip_head(id)
+    }
+}

+ 0 - 39
packages/core/src/tasks.rs

@@ -1,39 +0,0 @@
-use crate::innerlude::*;
-use futures_channel::mpsc::UnboundedSender;
-
-pub struct TaskHandle {
-    pub(crate) sender: UnboundedSender<SchedulerMsg>,
-    pub(crate) our_id: u64,
-}
-
-impl TaskHandle {
-    /// Toggles this coroutine off/on.
-    ///
-    /// This method is not synchronous - your task will not stop immediately.
-    pub fn toggle(&self) {
-        self.sender
-            .unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id)))
-            .unwrap()
-    }
-
-    /// This method is not synchronous - your task will not stop immediately.
-    pub fn resume(&self) {
-        self.sender
-            .unbounded_send(SchedulerMsg::Task(TaskMsg::ResumeTask(self.our_id)))
-            .unwrap()
-    }
-
-    /// This method is not synchronous - your task will not stop immediately.
-    pub fn stop(&self) {
-        self.sender
-            .unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id)))
-            .unwrap()
-    }
-
-    /// This method is not synchronous - your task will not stop immediately.
-    pub fn restart(&self) {
-        self.sender
-            .unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id)))
-            .unwrap()
-    }
-}

+ 26 - 15
packages/core/src/test_dom.rs

@@ -6,15 +6,16 @@ use crate::nodes::IntoVNode;
 
 pub struct TestDom {
     bump: Bump,
-    scheduler: Scheduler,
+    // scheduler: Scheduler,
 }
 
 impl TestDom {
     pub fn new() -> TestDom {
         let bump = Bump::new();
         let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
-        let scheduler = Scheduler::new(sender, receiver);
-        TestDom { bump, scheduler }
+        todo!()
+        // let scheduler = Scheduler::new(sender, receiver, 10, 100);
+        // TestDom { bump, scheduler }
     }
 
     pub fn new_factory(&self) -> NodeFactory {
@@ -32,7 +33,9 @@ impl TestDom {
 
     pub fn diff<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
         let mutations = Mutations::new();
-        let mut machine = DiffMachine::new(mutations, &self.scheduler.pool);
+        let mut machine: DiffState = todo!();
+        // let mut machine = DiffState::new(mutations);
+        // let mut machine = DiffState::new(mutations);
         machine.stack.push(DiffInstruction::Diff { new, old });
         machine.mutations
     }
@@ -40,13 +43,17 @@ impl TestDom {
     pub fn create<'a>(&'a self, left: Option<LazyNodes<'a, '_>>) -> Mutations<'a> {
         let old = self.bump.alloc(self.render_direct(left));
 
-        let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
+        let mut machine: DiffState = todo!();
+        // let mut machine = DiffState::new(Mutations::new());
+        // let mut machine = DiffState::new(Mutations::new());
 
         machine.stack.create_node(old, MountType::Append);
 
-        machine.work(&mut || false);
+        todo!()
 
-        machine.mutations
+        // machine.work(&mut || false);
+
+        // machine.mutations
     }
 
     pub fn lazy_diff<'a>(
@@ -56,22 +63,26 @@ impl TestDom {
     ) -> (Mutations<'a>, Mutations<'a>) {
         let (old, new) = (self.render(left), self.render(right));
 
-        let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
+        let mut machine: DiffState = todo!();
+        // let mut machine = DiffState::new(Mutations::new());
 
         machine.stack.create_node(old, MountType::Append);
 
-        machine.work(|| false);
-        let create_edits = machine.mutations;
+        todo!()
+
+        // machine.work(|| false);
+        // let create_edits = machine.mutations;
 
-        let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
+        // let mut machine: DiffState = todo!();
+        // // let mut machine = DiffState::new(Mutations::new());
 
-        machine.stack.push(DiffInstruction::Diff { old, new });
+        // machine.stack.push(DiffInstruction::Diff { old, new });
 
-        machine.work(&mut || false);
+        // machine.work(&mut || false);
 
-        let edits = machine.mutations;
+        // let edits = machine.mutations;
 
-        (create_edits, edits)
+        // (create_edits, edits)
     }
 }
 

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

@@ -7,63 +7,6 @@ pub fn empty_cell() -> Cell<Option<ElementId>> {
     Cell::new(None)
 }
 
-pub fn type_name_of<T>(_: T) -> &'static str {
-    std::any::type_name::<T>()
-}
-
-use std::future::Future;
-use std::pin::Pin;
-use std::task::{Context, Poll};
-
-// use crate::task::{Context, Poll};
-
-/// Cooperatively gives up a timeslice to the task scheduler.
-///
-/// Calling this function will move the currently executing future to the back
-/// of the execution queue, making room for other futures to execute. This is
-/// especially useful after running CPU-intensive operations inside a future.
-///
-/// See also [`task::spawn_blocking`].
-///
-/// [`task::spawn_blocking`]: fn.spawn_blocking.html
-///
-/// # Examples
-///
-/// Basic usage:
-///
-/// ```
-/// # async_std::task::block_on(async {
-/// #
-/// use async_std::task;
-///
-/// task::yield_now().await;
-/// #
-/// # })
-/// ```
-#[inline]
-pub async fn yield_now() {
-    YieldNow(false).await
-}
-
-struct YieldNow(bool);
-
-impl Future for YieldNow {
-    type Output = ();
-
-    // The futures executor is implemented as a FIFO queue, so all this future
-    // does is re-schedule the future back to the end of the queue, giving room
-    // for other futures to progress.
-    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
-        if !self.0 {
-            self.0 = true;
-            cx.waker().wake_by_ref();
-            Poll::Pending
-        } else {
-            Poll::Ready(())
-        }
-    }
-}
-
 /// A component's unique identifier.
 ///
 /// `ScopeId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is

+ 361 - 164
packages/core/src/virtual_dom.rs

@@ -19,10 +19,14 @@
 //! This module includes just the barebones for a complete VirtualDOM API.
 //! Additional functionality is defined in the respective files.
 
-use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
-
 use crate::innerlude::*;
-use std::{any::Any, rc::Rc};
+use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
+use futures_util::{Future, StreamExt};
+use fxhash::FxHashSet;
+use indexmap::IndexSet;
+use std::pin::Pin;
+use std::task::Poll;
+use std::{any::Any, collections::VecDeque};
 
 /// An integrated virtual node system that progresses events and diffs UI trees.
 ///
@@ -55,18 +59,22 @@ use std::{any::Any, rc::Rc};
 /// }
 /// ```
 pub struct VirtualDom {
-    scheduler: Scheduler,
-
     base_scope: ScopeId,
 
-    root_fc: Box<dyn Any>,
+    _root_caller: *mut dyn Fn(&Scope) -> Element,
 
-    root_props: Rc<dyn Any>,
+    pub(crate) scopes: ScopeArena,
 
-    // we need to keep the allocation around, but we don't necessarily use it
-    _root_caller: Box<dyn Any>,
+    receiver: UnboundedReceiver<SchedulerMsg>,
+    pub(crate) sender: UnboundedSender<SchedulerMsg>,
+
+    // Every component that has futures that need to be polled
+    pending_futures: FxHashSet<ScopeId>,
+    pending_messages: VecDeque<SchedulerMsg>,
+    dirty_scopes: IndexSet<ScopeId>,
 }
 
+// Methods to create the VirtualDom
 impl VirtualDom {
     /// Create a new VirtualDOM with a component that does not have special props.
     ///
@@ -127,7 +135,7 @@ impl VirtualDom {
         Self::new_with_props_and_scheduler(root, root_props, sender, receiver)
     }
 
-    /// Launch the VirtualDom, but provide your own channel for receiving and sending messages into the scheduler.
+    /// Launch the VirtualDom, but provide your own channel for receiving and sending messages into the scheduler
     ///
     /// This is useful when the VirtualDom must be driven from outside a thread and it doesn't make sense to wait for the
     /// VirtualDom to be created just to retrieve its channel receiver.
@@ -137,98 +145,281 @@ impl VirtualDom {
         sender: UnboundedSender<SchedulerMsg>,
         receiver: UnboundedReceiver<SchedulerMsg>,
     ) -> Self {
-        let root_fc = Box::new(root);
-
-        let root_props: Rc<dyn Any> = Rc::new(root_props);
+        let mut scopes = ScopeArena::new(sender.clone());
 
-        let props = root_props.clone();
-
-        let root_caller: Box<dyn Fn(&ScopeInner) -> Element> =
-            Box::new(move |scope: &ScopeInner| {
-                let props = props.downcast_ref::<P>().unwrap();
-                let node = root((scope, props));
-                // cast into the right lifetime
-                unsafe { std::mem::transmute(node) }
-            });
-        let caller = unsafe { bumpalo::boxed::Box::from_raw(root_caller.as_mut() as *mut _) };
-
-        let scheduler = Scheduler::new(sender, receiver);
-
-        let base_scope = scheduler.pool.insert_scope_with_key(|myidx| {
-            ScopeInner::new(caller, myidx, None, 0, 0, scheduler.pool.channel.clone())
-        });
+        let caller = Box::new(move |f: &Scope| -> Element { root(f, &root_props) });
+        let caller_ref: *mut dyn Fn(&Scope) -> Element = Box::into_raw(caller);
+        let base_scope = scopes.new_with_key(root as _, caller_ref, None, 0, 0);
 
         Self {
-            _root_caller: Box::new(root_caller),
-            root_fc,
+            scopes,
             base_scope,
-            scheduler,
-            root_props,
+            receiver,
+            // todo: clean this up manually?
+            _root_caller: caller_ref,
+            pending_messages: VecDeque::new(),
+            pending_futures: Default::default(),
+            dirty_scopes: Default::default(),
+            sender,
         }
     }
 
-    /// Get the [`Scope`] for the root component.
+    /// Get the [`ScopeState`] for the root component.
     ///
     /// This is useful for traversing the tree from the root for heuristics or alternsative renderers that use Dioxus
     /// directly.
-    pub fn base_scope(&self) -> &ScopeInner {
-        self.scheduler.pool.get_scope(self.base_scope).unwrap()
+    ///
+    /// # Example
+    pub fn base_scope(&self) -> &Scope {
+        self.get_scope(&self.base_scope).unwrap()
     }
 
-    /// Get the [`Scope`] for a component given its [`ScopeId`]
-    pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeInner> {
-        self.scheduler.pool.get_scope(id)
+    /// Get the [`ScopeState`] for a component given its [`ScopeId`]
+    ///
+    /// # Example
+    ///
+    ///
+    ///
+    pub fn get_scope<'a>(&'a self, id: &ScopeId) -> Option<&'a Scope> {
+        self.scopes.get_scope(id)
     }
 
-    /// Update the root props of this VirtualDOM.
+    /// Get an [`UnboundedSender`] handle to the channel used by the scheduler.
     ///
-    /// This method returns None if the old props could not be removed. The entire VirtualDOM will be rebuilt immediately,
-    /// so calling this method will block the main thread until computation is done.
+    /// # Example
     ///
-    /// ## Example
     ///
-    /// ```rust
-    /// #[derive(Props, PartialEq)]
-    /// struct AppProps {
-    ///     route: &'static str
-    /// }
-    /// static App: FC<AppProps> = |(cx, props)|cx.render(rsx!{ "route is {cx.route}" });
+    ///    
+    pub fn get_scheduler_channel(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
+        self.sender.clone()
+    }
+
+    /// Check if the [`VirtualDom`] has any pending updates or work to be done.
+    ///
+    /// # Example
     ///
-    /// let mut dom = VirtualDom::new_with_props(App, AppProps { route: "start" });
     ///
-    /// let mutations = dom.update_root_props(AppProps { route: "end" }).unwrap();
+    ///
+    pub fn has_any_work(&self) -> bool {
+        !(self.dirty_scopes.is_empty() && self.pending_messages.is_empty())
+    }
+
+    /// Waits for the scheduler to have work
+    /// This lets us poll async tasks during idle periods without blocking the main thread.
+    pub async fn wait_for_work(&mut self) {
+        // todo: poll the events once even if there is work to do to prevent starvation
+
+        // if there's no futures in the virtualdom, just wait for a scheduler message and put it into the queue to be processed
+        if self.pending_futures.is_empty() {
+            self.pending_messages
+                .push_front(self.receiver.next().await.unwrap());
+        } else {
+            struct PollTasks<'a> {
+                pending_futures: &'a FxHashSet<ScopeId>,
+                scopes: &'a ScopeArena,
+            }
+
+            impl<'a> Future for PollTasks<'a> {
+                type Output = ();
+
+                fn poll(
+                    self: Pin<&mut Self>,
+                    cx: &mut std::task::Context<'_>,
+                ) -> Poll<Self::Output> {
+                    let mut all_pending = true;
+
+                    // Poll every scope manually
+                    for fut in self.pending_futures.iter() {
+                        let scope = self
+                            .scopes
+                            .get_scope(fut)
+                            .expect("Scope should never be moved");
+
+                        let mut items = scope.items.borrow_mut();
+                        for task in items.tasks.iter_mut() {
+                            let task = task.as_mut();
+
+                            // todo: does this make sense?
+                            // I don't usually write futures by hand
+                            // I think the futures neeed to be pinned using bumpbox or something
+                            // right now, they're bump allocated so this shouldn't matter anyway - they're not going to move
+                            let unpinned = unsafe { Pin::new_unchecked(task) };
+
+                            if unpinned.poll(cx).is_ready() {
+                                all_pending = false
+                            }
+                        }
+                    }
+
+                    // Resolve the future if any singular task is ready
+                    match all_pending {
+                        true => Poll::Pending,
+                        false => Poll::Ready(()),
+                    }
+                }
+            }
+
+            // Poll both the futures and the scheduler message queue simulataneously
+            use futures_util::future::{select, Either};
+
+            let scheduler_fut = self.receiver.next();
+            let tasks_fut = PollTasks {
+                pending_futures: &self.pending_futures,
+                scopes: &self.scopes,
+            };
+
+            match select(tasks_fut, scheduler_fut).await {
+                // Futures don't generate work
+                Either::Left((_, _)) => {}
+
+                // Save these messages in FIFO to be processed later
+                Either::Right((msg, _)) => self.pending_messages.push_front(msg.unwrap()),
+            }
+        }
+    }
+
+    /// Run the virtualdom with a deadline.
+    ///
+    /// This method will progress async tasks until the deadline is reached. If tasks are completed before the deadline,
+    /// and no tasks are pending, this method will return immediately. If tasks are still pending, then this method will
+    /// exhaust the deadline working on them.
+    ///
+    /// This method is useful when needing to schedule the virtualdom around other tasks on the main thread to prevent
+    /// "jank". It will try to finish whatever work it has by the deadline to free up time for other work.
+    ///
+    /// Due to platform differences in how time is handled, this method accepts a future that resolves when the deadline
+    /// is exceeded. However, the deadline won't be met precisely, so you might want to build some wiggle room into the
+    /// deadline closure manually.
+    ///
+    /// The deadline is polled before starting to diff components. This strikes a balance between the overhead of checking
+    /// the deadline and just completing the work. However, if an individual component takes more than 16ms to render, then
+    /// the screen will "jank" up. In debug, this will trigger an alert.
+    ///
+    /// If there are no in-flight fibers when this method is called, it will await any possible tasks, aborting early if
+    /// the provided deadline future resolves.
+    ///
+    /// For use in the web, it is expected that this method will be called to be executed during "idle times" and the
+    /// mutations to be applied during the "paint times" IE "animation frames". With this strategy, it is possible to craft
+    /// entirely jank-free applications that perform a ton of work.
+    ///
+    /// # Example
+    ///
+    /// ```no_run
+    /// static App: FC<()> = |(cx, props)|rsx!(cx, div {"hello"} );
+    ///
+    /// let mut dom = VirtualDom::new(App);
+    ///
+    /// loop {
+    ///     let mut timeout = TimeoutFuture::from_ms(16);
+    ///     let deadline = move || (&mut timeout).now_or_never();
+    ///
+    ///     let mutations = dom.run_with_deadline(deadline).await;
+    ///
+    ///     apply_mutations(mutations);
+    /// }
     /// ```
-    pub fn update_root_props<P>(&mut self, root_props: P) -> Option<Mutations>
-    where
-        P: 'static,
-    {
-        let root_scope = self.scheduler.pool.get_scope_mut(self.base_scope).unwrap();
+    ///
+    /// ## Mutations
+    ///
+    /// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also
+    /// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has
+    /// applied the edits.
+    ///
+    /// Mutations are the only link between the RealDOM and the VirtualDOM.
+    pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec<Mutations> {
+        let mut committed_mutations = vec![];
 
-        // Pre-emptively drop any downstream references of the old props
-        root_scope.ensure_drop_safety(&self.scheduler.pool);
+        while self.has_any_work() {
+            while let Ok(Some(msg)) = self.receiver.try_next() {
+                self.pending_messages.push_front(msg);
+            }
 
-        let mut root_props: Rc<dyn Any> = Rc::new(root_props);
+            while let Some(msg) = self.pending_messages.pop_back() {
+                match msg {
+                    SchedulerMsg::Immediate(id) => {
+                        self.dirty_scopes.insert(id);
+                    }
+                    SchedulerMsg::UiEvent(event) => {
+                        if let Some(element) = event.mounted_dom_id {
+                            log::info!("Calling listener {:?}, {:?}", event.scope_id, element);
+
+                            let scope = self.scopes.get_scope(&event.scope_id).unwrap();
+
+                            // TODO: bubble properly here
+                            scope.call_listener(event, element);
+
+                            while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
+                                self.pending_messages.push_front(dirty_scope);
+                            }
+                        } else {
+                            log::debug!("User event without a targetted ElementId. Not currently supported.\nUnsure how to proceed. {:?}", event);
+                        }
+                    }
+                }
+            }
 
-        if let Some(props_ptr) = root_props.downcast_ref::<P>().map(|p| p as *const P) {
-            // Swap the old props and new props
-            std::mem::swap(&mut self.root_props, &mut root_props);
+            let mut diff_state: DiffState = DiffState::new(Mutations::new());
 
-            let root = *self.root_fc.downcast_ref::<FC<P>>().unwrap();
+            let mut ran_scopes = FxHashSet::default();
 
-            let root_caller: Box<dyn Fn(&ScopeInner) -> Element> =
-                Box::new(move |scope: &ScopeInner| unsafe {
-                    let props: &'_ P = &*(props_ptr as *const P);
-                    std::mem::transmute(root((scope, props)))
-                });
+            // todo: the 2021 version of rust will let us not have to force the borrow
+            let scopes = &self.scopes;
 
-            root_scope.update_scope_dependencies(&root_caller);
+            // Sort the scopes by height. Theoretically, we'll de-duplicate scopes by height
+            self.dirty_scopes
+                .retain(|id| scopes.get_scope(id).is_some());
 
-            drop(root_props);
+            self.dirty_scopes.sort_by(|a, b| {
+                let h1 = scopes.get_scope(a).unwrap().height;
+                let h2 = scopes.get_scope(b).unwrap().height;
+                h1.cmp(&h2).reverse()
+            });
 
-            Some(self.rebuild())
-        } else {
-            None
+            if let Some(scopeid) = self.dirty_scopes.pop() {
+                log::info!("handling dirty scope {:?}", scopeid);
+
+                if !ran_scopes.contains(&scopeid) {
+                    ran_scopes.insert(scopeid);
+
+                    log::debug!("about to run scope {:?}", scopeid);
+
+                    if self.scopes.run_scope(&scopeid) {
+                        let (old, new) = (
+                            self.scopes.wip_head(&scopeid),
+                            self.scopes.fin_head(&scopeid),
+                        );
+                        diff_state.stack.scope_stack.push(scopeid);
+                        diff_state.stack.push(DiffInstruction::Diff { new, old });
+                    }
+                }
+            }
+
+            let work_completed = self.scopes.work(&mut diff_state, &mut deadline);
+
+            if work_completed {
+                let DiffState {
+                    mutations,
+                    seen_scopes,
+                    stack,
+                    ..
+                } = diff_state;
+
+                for scope in seen_scopes {
+                    self.dirty_scopes.remove(&scope);
+                }
+
+                // I think the stack should be empty at the end of diffing?
+                debug_assert_eq!(stack.scope_stack.len(), 0);
+
+                committed_mutations.push(mutations);
+            } else {
+                // leave the work in an incomplete state
+                log::debug!("don't have a mechanism to pause work (yet)");
+                return committed_mutations;
+            }
         }
+
+        committed_mutations
     }
 
     /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch
@@ -242,14 +433,27 @@ impl VirtualDom {
     ///
     /// # Example
     /// ```
-    /// static App: FC<()> = |(cx, props)|cx.render(rsx!{ "hello world" });
+    /// static App: FC<()> = |(cx, props)| cx.render(rsx!{ "hello world" });
     /// let mut dom = VirtualDom::new();
     /// let edits = dom.rebuild();
     ///
     /// apply_edits(edits);
     /// ```
     pub fn rebuild(&mut self) -> Mutations {
-        self.scheduler.rebuild(self.base_scope)
+        let mut diff_machine = DiffState::new(Mutations::new());
+
+        let scope_id = self.base_scope;
+        if self.scopes.run_scope(&scope_id) {
+            diff_machine
+                .stack
+                .create_node(self.scopes.fin_head(&scope_id), MountType::Append);
+
+            diff_machine.stack.scope_stack.push(scope_id);
+
+            self.scopes.work(&mut diff_machine, || false);
+        }
+
+        diff_machine.mutations
     }
 
     /// Compute a manual diff of the VirtualDOM between states.
@@ -286,107 +490,100 @@ impl VirtualDom {
     ///
     /// let edits = dom.diff();
     /// ```
-    pub fn diff(&mut self) -> Mutations {
-        self.scheduler.hard_diff(self.base_scope)
-    }
+    pub fn hard_diff<'a>(&'a mut self, scope_id: &ScopeId) -> Option<Mutations<'a>> {
+        log::debug!("hard diff {:?}", scope_id);
 
-    /// Runs the virtualdom immediately, not waiting for any suspended nodes to complete.
-    ///
-    /// This method will not wait for any suspended nodes to complete. If there is no pending work, then this method will
-    /// return "None"
-    pub fn run_immediate(&mut self) -> Option<Vec<Mutations>> {
-        if self.scheduler.has_any_work() {
-            Some(self.scheduler.work_sync())
+        if self.scopes.run_scope(scope_id) {
+            let mut diff_machine = DiffState::new(Mutations::new());
+
+            diff_machine.force_diff = true;
+
+            self.scopes.diff_scope(&mut diff_machine, scope_id);
+
+            dbg!(&diff_machine.mutations);
+
+            Some(diff_machine.mutations)
         } else {
             None
         }
     }
+}
 
-    /// Run the virtualdom with a deadline.
-    ///
-    /// This method will progress async tasks until the deadline is reached. If tasks are completed before the deadline,
-    /// and no tasks are pending, this method will return immediately. If tasks are still pending, then this method will
-    /// exhaust the deadline working on them.
-    ///
-    /// This method is useful when needing to schedule the virtualdom around other tasks on the main thread to prevent
-    /// "jank". It will try to finish whatever work it has by the deadline to free up time for other work.
-    ///
-    /// Due to platform differences in how time is handled, this method accepts a future that resolves when the deadline
-    /// is exceeded. However, the deadline won't be met precisely, so you might want to build some wiggle room into the
-    /// deadline closure manually.
+pub enum SchedulerMsg {
+    // events from the host
+    UiEvent(UserEvent),
+
+    // setstate
+    Immediate(ScopeId),
+}
+
+#[derive(Debug)]
+pub struct UserEvent {
+    /// The originator of the event trigger
+    pub scope_id: ScopeId,
+
+    pub priority: EventPriority,
+
+    /// The optional real node associated with the trigger
+    pub mounted_dom_id: Option<ElementId>,
+
+    /// The event type IE "onclick" or "onmouseover"
     ///
-    /// The deadline is polled before starting to diff components. This strikes a balance between the overhead of checking
-    /// the deadline and just completing the work. However, if an individual component takes more than 16ms to render, then
-    /// the screen will "jank" up. In debug, this will trigger an alert.
+    /// The name that the renderer will use to mount the listener.
+    pub name: &'static str,
+
+    /// Event Data
+    pub event: Box<dyn Any + Send>,
+}
+
+/// Priority of Event Triggers.
+///
+/// Internally, Dioxus will abort work that's taking too long if new, more important work arrives. Unlike React, Dioxus
+/// won't be afraid to pause work or flush changes to the RealDOM. This is called "cooperative scheduling". Some Renderers
+/// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
+///
+/// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
+///
+/// React has a 5-tier priority system. However, they break things into "Continuous" and "Discrete" priority. For now,
+/// we keep it simple, and just use a 3-tier priority system.
+///
+/// - NoPriority = 0
+/// - LowPriority = 1
+/// - NormalPriority = 2
+/// - UserBlocking = 3
+/// - HighPriority = 4
+/// - ImmediatePriority = 5
+///
+/// We still have a concept of discrete vs continuous though - discrete events won't be batched, but continuous events will.
+/// This means that multiple "scroll" events will be processed in a single frame, but multiple "click" events will be
+/// flushed before proceeding. Multiple discrete events is highly unlikely, though.
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
+pub enum EventPriority {
+    /// Work that must be completed during the EventHandler phase.
     ///
-    /// If there are no in-flight fibers when this method is called, it will await any possible tasks, aborting early if
-    /// the provided deadline future resolves.
+    /// Currently this is reserved for controlled inputs.
+    Immediate = 3,
+
+    /// "High Priority" work will not interrupt other high priority work, but will interrupt medium and low priority work.
     ///
-    /// For use in the web, it is expected that this method will be called to be executed during "idle times" and the
-    /// mutations to be applied during the "paint times" IE "animation frames". With this strategy, it is possible to craft
-    /// entirely jank-free applications that perform a ton of work.
+    /// This is typically reserved for things like user interaction.
     ///
-    /// # Example
+    /// React calls these "discrete" events, but with an extra category of "user-blocking" (Immediate).
+    High = 2,
+
+    /// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important
+    /// than "High Priority" events and will take precedence over low priority events.
     ///
-    /// ```no_run
-    /// static App: FC<()> = |(cx, props)|rsx!(cx, div {"hello"} );
-    /// let mut dom = VirtualDom::new(App);
-    /// loop {
-    ///     let deadline = TimeoutFuture::from_ms(16);
-    ///     let mutations = dom.run_with_deadline(deadline).await;
-    ///     apply_mutations(mutations);
-    /// }
-    /// ```
+    /// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
     ///
-    /// ## Mutations
+    /// React calls these "continuous" events (e.g. mouse move, mouse wheel, touch move, etc).
+    Medium = 1,
+
+    /// "Low Priority" work will always be preempted unless the work is significantly delayed, in which case it will be
+    /// advanced to the front of the work queue until completed.
     ///
-    /// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also
-    /// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has
-    /// applied the edits.
+    /// The primary user of Low Priority work is the asynchronous work system (Suspense).
     ///
-    /// Mutations are the only link between the RealDOM and the VirtualDOM.
-    pub fn run_with_deadline(&mut self, deadline: impl FnMut() -> bool) -> Vec<Mutations<'_>> {
-        self.scheduler.work_with_deadline(deadline)
-    }
-
-    pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
-        self.scheduler.pool.channel.sender.clone()
-    }
-
-    /// Waits for the scheduler to have work
-    /// This lets us poll async tasks during idle periods without blocking the main thread.
-    pub async fn wait_for_work(&mut self) {
-        // todo: poll the events once even if there is work to do to prevent starvation
-        if self.scheduler.has_any_work() {
-            return;
-        }
-
-        use futures_util::StreamExt;
-
-        // Wait for any new events if we have nothing to do
-
-        let tasks_fut = self.scheduler.async_tasks.next();
-        let scheduler_fut = self.scheduler.receiver.next();
-
-        use futures_util::future::{select, Either};
-        match select(tasks_fut, scheduler_fut).await {
-            // poll the internal futures
-            Either::Left((_id, _)) => {
-                //
-            }
-
-            // wait for an external event
-            Either::Right((msg, _)) => match msg.unwrap() {
-                SchedulerMsg::Task(t) => {
-                    self.scheduler.handle_task(t);
-                }
-                SchedulerMsg::Immediate(im) => {
-                    self.scheduler.dirty_scopes.insert(im);
-                }
-                SchedulerMsg::UiEvent(evt) => {
-                    self.scheduler.ui_events.push_back(evt);
-                }
-            },
-        }
-    }
+    /// This is considered "idle" work or "background" work.
+    Low = 0,
 }

+ 4 - 4
packages/core/tests/borrowedstate.rs

@@ -10,8 +10,8 @@ fn test_borrowed_state() {
     let _ = VirtualDom::new(Parent);
 }
 
-fn Parent((cx, _): Scope<()>) -> Element {
-    let value = cx.use_hook(|_| String::new(), |f| &*f, |_| {});
+fn Parent(cx: Context, props: &()) -> Element {
+    let value = cx.use_hook(|_| String::new(), |f| &*f);
 
     cx.render(rsx! {
         div {
@@ -28,7 +28,7 @@ struct ChildProps<'a> {
     name: &'a str,
 }
 
-fn Child<'a>((cx, props): Scope<'a, ChildProps>) -> Element<'a> {
+fn Child(cx: Context, props: &ChildProps) -> Element {
     cx.render(rsx! {
         div {
             h1 { "it's nested" }
@@ -42,7 +42,7 @@ struct Grandchild<'a> {
     name: &'a str,
 }
 
-fn Child2<'a>((cx, props): Scope<'a, Grandchild>) -> Element<'a> {
+fn Child2(cx: Context, props: &Grandchild) -> Element {
     cx.render(rsx! {
         div { "Hello {props.name}!" }
     })

+ 22 - 21
packages/core/tests/create_dom.rs

@@ -21,7 +21,7 @@ fn new_dom<P: 'static + Send>(app: FC<P>, props: P) -> VirtualDom {
 
 #[test]
 fn test_original_diff() {
-    static APP: FC<()> = |(cx, props)| {
+    static APP: FC<()> = |cx, props| {
         cx.render(rsx! {
             div {
                 div {
@@ -57,17 +57,17 @@ fn test_original_diff() {
 
 #[test]
 fn create() {
-    static APP: FC<()> = |(cx, props)| {
+    static APP: FC<()> = |cx, props| {
         cx.render(rsx! {
             div {
                 div {
                     "Hello, world!"
                     div {
                         div {
-                            // Fragment {
-                            //     "hello"
-                            //     "world"
-                            // }
+                            Fragment {
+                                "hello"
+                                "world"
+                            }
                         }
                     }
                 }
@@ -120,7 +120,7 @@ fn create() {
 
 #[test]
 fn create_list() {
-    static APP: FC<()> = |(cx, props)| {
+    static APP: FC<()> = |cx, props| {
         cx.render(rsx! {
             {(0..3).map(|f| rsx!{ div {
                 "hello"
@@ -169,7 +169,7 @@ fn create_list() {
 
 #[test]
 fn create_simple() {
-    static APP: FC<()> = |(cx, props)| {
+    static APP: FC<()> = |cx, props| {
         cx.render(rsx! {
             div {}
             div {}
@@ -207,7 +207,7 @@ fn create_simple() {
 }
 #[test]
 fn create_components() {
-    static App: FC<()> = |(cx, props)| {
+    static App: FC<()> = |cx, props| {
         cx.render(rsx! {
             Child { "abc1" }
             Child { "abc2" }
@@ -220,7 +220,7 @@ fn create_components() {
         children: ScopeChildren<'a>,
     }
 
-    fn Child<'a>((cx, props): Scope<'a, ChildProps<'a>>) -> Element {
+    fn Child<'a>(cx: Context<'a>, props: &ChildProps<'a>) -> Element {
         cx.render(rsx! {
             h1 {}
             div { {&props.children} }
@@ -273,7 +273,7 @@ fn create_components() {
 }
 #[test]
 fn anchors() {
-    static App: FC<()> = |(cx, props)| {
+    static App: FC<()> = |cx, props| {
         cx.render(rsx! {
             {true.then(|| rsx!{ div { "hello" } })}
             {false.then(|| rsx!{ div { "goodbye" } })}
@@ -302,17 +302,18 @@ fn anchors() {
 
 #[test]
 fn suspended() {
-    static App: FC<()> = |(cx, props)| {
-        let val = use_suspense(cx, || async {}, |cx, p| todo!());
+    todo!()
+    // static App: FC<()> = |cx, props| {
+    //     let val = use_suspense(cx, || async {}, |p| todo!());
 
-        cx.render(rsx! { {val} })
-    };
+    //     cx.render(rsx! { {val} })
+    // };
 
-    let mut dom = new_dom(App, ());
-    let mutations = dom.rebuild();
+    // let mut dom = new_dom(App, ());
+    // let mutations = dom.rebuild();
 
-    assert_eq!(
-        mutations.edits,
-        [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 },]
-    );
+    // assert_eq!(
+    //     mutations.edits,
+    //     [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 },]
+    // );
 }

+ 1 - 1
packages/core/tests/diffing.rs

@@ -5,7 +5,7 @@
 //!
 //! It does not validated that component lifecycles work properly. This is done in another test file.
 
-use dioxus::{nodes::VSuspended, prelude::*, DomEdit, TestDom};
+use dioxus::{prelude::*, DomEdit, TestDom, VSuspended};
 use dioxus_core as dioxus;
 use dioxus_core_macro::*;
 use dioxus_html as dioxus_elements;

+ 2 - 4
packages/core/tests/display_vdom.rs

@@ -13,7 +13,7 @@ mod test_logging;
 
 #[test]
 fn please_work() {
-    static App: FC<()> = |(cx, props)| {
+    static App: FC<()> = |cx, props| {
         cx.render(rsx! {
             div {
                 hidden: "true"
@@ -27,7 +27,7 @@ fn please_work() {
         })
     };
 
-    static Child: FC<()> = |(cx, props)| {
+    static Child: FC<()> = |cx, props| {
         cx.render(rsx! {
             div { "child" }
         })
@@ -35,6 +35,4 @@ fn please_work() {
 
     let mut dom = VirtualDom::new(App);
     dom.rebuild();
-
-    println!("{}", dom);
 }

+ 2 - 2
packages/core/tests/lifecycle.rs

@@ -20,7 +20,7 @@ fn manual_diffing() {
         value: Shared<&'static str>,
     }
 
-    static App: FC<AppProps> = |(cx, props)| {
+    static App: FC<AppProps> = |cx, props| {
         let val = props.value.lock().unwrap();
         cx.render(rsx! { div { "{val}" } })
     };
@@ -37,7 +37,7 @@ fn manual_diffing() {
 
     *value.lock().unwrap() = "goodbye";
 
-    let edits = dom.diff();
+    let edits = dom.rebuild();
 
     log::debug!("edits: {:?}", edits);
 }

+ 2 - 2
packages/core/tests/sharedstate.rs

@@ -13,12 +13,12 @@ mod test_logging;
 fn shared_state_test() {
     struct MySharedState(&'static str);
 
-    static App: FC<()> = |(cx, props)| {
+    static App: FC<()> = |cx, props| {
         cx.provide_state(MySharedState("world!"));
         cx.render(rsx!(Child {}))
     };
 
-    static Child: FC<()> = |(cx, props)| {
+    static Child: FC<()> = |cx, props| {
         let shared = cx.consume_state::<MySharedState>()?;
         cx.render(rsx!("Hello, {shared.0}"))
     };

+ 15 - 14
packages/core/tests/vdom_rebuild.rs

@@ -17,7 +17,7 @@ use dioxus_html as dioxus_elements;
 
 #[test]
 fn app_runs() {
-    static App: FC<()> = |(cx, props)| {
+    static App: FC<()> = |cx, props| {
         //
         cx.render(rsx!( div{"hello"} ))
     };
@@ -28,7 +28,7 @@ fn app_runs() {
 
 #[test]
 fn fragments_work() {
-    static App: FC<()> = |(cx, props)| {
+    static App: FC<()> = |cx, props| {
         cx.render(rsx!(
             div{"hello"}
             div{"goodbye"}
@@ -42,7 +42,7 @@ fn fragments_work() {
 
 #[test]
 fn lists_work() {
-    static App: FC<()> = |(cx, props)| {
+    static App: FC<()> = |cx, props| {
         cx.render(rsx!(
             h1 {"hello"}
             {(0..6).map(|f| rsx!(span{ "{f}" }))}
@@ -55,7 +55,7 @@ fn lists_work() {
 
 #[test]
 fn conditional_rendering() {
-    static App: FC<()> = |(cx, props)| {
+    static App: FC<()> = |cx, props| {
         cx.render(rsx!(
             h1 {"hello"}
             {true.then(|| rsx!(span{ "a" }))}
@@ -72,13 +72,13 @@ fn conditional_rendering() {
 
 #[test]
 fn child_components() {
-    static App: FC<()> = |(cx, props)| {
+    static App: FC<()> = |cx, props| {
         cx.render(rsx!(
             {true.then(|| rsx!(Child { }))}
             {false.then(|| rsx!(Child { }))}
         ))
     };
-    static Child: FC<()> = |(cx, props)| {
+    static Child: FC<()> = |cx, props| {
         cx.render(rsx!(
             h1 {"hello"}
             h1 {"goodbye"}
@@ -91,13 +91,14 @@ fn child_components() {
 
 #[test]
 fn suspended_works() {
-    static App: FC<()> = |(cx, props)| {
-        let title = use_suspense(cx, || async { "bob" }, move |cx, f| todo!());
-        // let title = use_suspense(cx, || async { "bob" }, move |cx, f| rsx! { "{f}"});
-        cx.render(rsx!("hello" { title }))
-    };
+    todo!()
+    // static App: FC<()> = |cx, props| {
+    //     let title = use_suspense(cx, || async { "bob" }, move |cx, f| todo!());
+    //     // let title = use_suspense(cx, || async { "bob" }, move |cx, f| rsx! { "{f}"});
+    //     cx.render(rsx!("hello" { title }))
+    // };
 
-    let mut vdom = VirtualDom::new(App);
-    let edits = vdom.rebuild();
-    dbg!(edits);
+    // let mut vdom = VirtualDom::new(App);
+    // let edits = vdom.rebuild();
+    // dbg!(edits);
 }

+ 1 - 1
packages/desktop/Cargo.toml

@@ -24,6 +24,7 @@ tokio = { version = "1.12.0", features = [
     "rt",
 ], optional = true, default-features = false }
 dioxus-core-macro = { path = "../core-macro" }
+dioxus-html = { path = "../html", features = ["serialize"] }
 
 [features]
 default = ["tokio_runtime"]
@@ -31,6 +32,5 @@ tokio_runtime = ["tokio"]
 
 
 [dev-dependencies]
-dioxus-html = { path = "../html" }
 dioxus-hooks = { path = "../hooks" }
 simple_logger = "1.13.0"

+ 1 - 4
packages/desktop/src/desktop_context.rs

@@ -57,7 +57,7 @@ pub struct WebviewWindowProps<'a> {
 ///
 ///
 ///
-pub fn WebviewWindow<'a>((cx, props): Scope<'a, WebviewWindowProps>) -> Element<'a> {
+pub fn WebviewWindow((cx, props): Scope<WebviewWindowProps>) -> Element {
     let dtcx = cx.consume_state::<RefCell<DesktopContext>>()?;
 
     cx.use_hook(
@@ -67,9 +67,6 @@ pub fn WebviewWindow<'a>((cx, props): Scope<'a, WebviewWindowProps>) -> Element<
         |state| {
             //
         },
-        |hook| {
-            //
-        },
     );
 
     // render the children directly

+ 1 - 0
packages/desktop/src/err.rs

@@ -0,0 +1 @@
+

+ 6 - 6
packages/desktop/src/events.rs

@@ -4,7 +4,8 @@
 use std::sync::Arc;
 use std::{any::Any, rc::Rc};
 
-use dioxus_core::{events::on::MouseEvent, ElementId, EventPriority, ScopeId, UserEvent};
+use dioxus_core::{ElementId, EventPriority, ScopeId, UserEvent};
+use dioxus_html::on::*;
 
 #[derive(serde::Serialize, serde::Deserialize)]
 struct ImEvent {
@@ -23,7 +24,7 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
         contents,
     } = ims.into_iter().next().unwrap();
 
-    let scope = ScopeId(scope as usize);
+    let scope_id = ScopeId(scope as usize);
     let mounted_dom_id = Some(ElementId(mounted_dom_id as usize));
 
     let name = event_name_from_typ(&event);
@@ -31,15 +32,14 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
 
     UserEvent {
         name,
-        event,
-        scope,
+        priority: EventPriority::Low,
+        scope_id,
         mounted_dom_id,
+        event,
     }
 }
 
 fn make_synthetic_event(name: &str, val: serde_json::Value) -> Box<dyn Any + Send> {
-    use dioxus_core::events::on::*;
-
     match name {
         "copy" | "cut" | "paste" => {
             //

+ 0 - 1
packages/desktop/src/lib.rs

@@ -13,7 +13,6 @@ use std::sync::mpsc::channel;
 use std::sync::{Arc, RwLock};
 
 use cfg::DesktopConfig;
-use dioxus_core::scheduler::SchedulerMsg;
 use dioxus_core::*;
 use serde::{Deserialize, Serialize};
 

+ 9 - 8
packages/hooks/src/use_shared_state.rs

@@ -82,13 +82,6 @@ pub fn use_shared_state<'a, T: 'static>(cx: Context<'a>) -> Option<UseSharedStat
                 _ => None,
             }
         },
-        |f| {
-            // we need to unsubscribe when our component is unounted
-            if let Some(root) = &f.root {
-                let mut root = root.borrow_mut();
-                root.consumers.remove(&f.scope_id);
-            }
-        },
     )
 }
 
@@ -98,6 +91,15 @@ struct SharedStateInner<T: 'static> {
     scope_id: ScopeId,
     needs_notification: Cell<bool>,
 }
+impl<T> Drop for SharedStateInner<T> {
+    fn drop(&mut self) {
+        // we need to unsubscribe when our component is unounted
+        if let Some(root) = &self.root {
+            let mut root = root.borrow_mut();
+            root.consumers.remove(&self.scope_id);
+        }
+    }
+}
 
 pub struct UseSharedState<'a, T: 'static> {
     pub(crate) cx: Context<'a>,
@@ -172,6 +174,5 @@ pub fn use_provide_state<'a, T: 'static>(cx: Context<'a>, f: impl FnOnce() -> T)
             cx.provide_state(state)
         },
         |inner| {},
-        |_| {},
     )
 }

+ 0 - 1
packages/hooks/src/useref.rs

@@ -16,7 +16,6 @@ pub fn use_ref<T: 'static>(cx: Context, f: impl FnOnce() -> T) -> UseRef<T> {
             inner.update_scheuled.set(false);
             UseRef { inner }
         },
-        |_| {},
     )
 }
 

+ 0 - 1
packages/hooks/src/usestate.rs

@@ -69,7 +69,6 @@ pub fn use_state<'a, T: 'static>(
 
             UseState { inner: &*hook }
         },
-        |_| {},
     )
 }
 struct UseStateInner<T: 'static> {

+ 8 - 3
packages/html/Cargo.toml

@@ -9,7 +9,12 @@ description = "HTML Element pack for Dioxus - a concurrent renderer-agnostic Vir
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-dioxus-core = { path = "../core", version = "0.1.2" }
+dioxus-core = { path = "../core", version = "0.1.3" }
+# Serialize the Edits for use in Webview/Liveview instances
+serde = { version = "1", features = ["derive"], optional = true }
+serde_repr = { version = "0.1.7", optional = true }
 
-[dev-dependencies]
-scraper = "0.12.0"
+
+[features]
+default = []
+serialize = ["serde", "serde_repr"]

+ 0 - 33
packages/html/src/attrval.rs

@@ -1,33 +0,0 @@
-//! This module is not included anywhere.
-//!
-//! It is a prototype for a system that supports non-string attribute values.
-
-trait AsAttributeValue: Sized {
-    fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a>;
-}
-enum AttributeValue<'a> {
-    Int(i32),
-    Float(f32),
-    Str(&'a str),
-    Bool(bool),
-}
-impl<'b> AsAttributeValue for Arguments<'b> {
-    fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a> {
-        todo!()
-    }
-}
-impl AsAttributeValue for &'static str {
-    fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a> {
-        todo!()
-    }
-}
-impl AsAttributeValue for f32 {
-    fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a> {
-        todo!()
-    }
-}
-impl AsAttributeValue for i32 {
-    fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a> {
-        todo!()
-    }
-}

+ 1126 - 0
packages/html/src/elements.rs

@@ -0,0 +1,1126 @@
+use crate::{GlobalAttributes, SvgAttributes};
+use dioxus_core::*;
+use std::fmt::Arguments;
+
+macro_rules! builder_constructors {
+    (
+        $(
+            $(#[$attr:meta])*
+            $name:ident {
+                $(
+                    $(#[$attr_method:meta])*
+                    $fil:ident: $vil:ident,
+                )*
+            };
+         )*
+    ) => {
+        $(
+            #[allow(non_camel_case_types)]
+            $(#[$attr])*
+            pub struct $name;
+
+            impl DioxusElement for $name {
+                const TAG_NAME: &'static str = stringify!($name);
+                const NAME_SPACE: Option<&'static str> = None;
+            }
+
+            impl GlobalAttributes for $name {}
+
+            impl $name {
+                $(
+                    $(#[$attr_method])*
+                    pub fn $fil<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
+                        cx.attr(stringify!($fil), val, None, false)
+                    }
+                )*
+            }
+        )*
+    };
+
+    ( $(
+        $(#[$attr:meta])*
+        $name:ident <> $namespace:tt {
+            $($fil:ident: $vil:ident,)*
+        };
+    )* ) => {
+        $(
+            #[allow(non_camel_case_types)]
+            $(#[$attr])*
+            pub struct $name;
+
+            impl DioxusElement for $name {
+                const TAG_NAME: &'static str = stringify!($name);
+                const NAME_SPACE: Option<&'static str> = Some($namespace);
+            }
+
+            impl SvgAttributes for $name {}
+
+            impl $name {
+                $(
+                    pub fn $fil<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
+                        cx.attr(stringify!($fil), val, Some(stringify!($namespace)), false)
+                    }
+                )*
+            }
+        )*
+    };
+}
+
+// Organized in the same order as
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Element
+//
+// Does not include obsolete elements.
+//
+// This namespace represents a collection of modern HTML-5 compatiable elements.
+//
+// This list does not include obsolete, deprecated, experimental, or poorly supported elements.
+builder_constructors! {
+    // Document metadata
+
+    /// Build a
+    /// [`<base>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base)
+    /// element.
+    ///
+    base {
+        href: Uri,
+        target: Target,
+    };
+
+    /// Build a
+    /// [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head)
+    /// element.
+    head {};
+
+    /// Build a
+    /// [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link)
+    /// element.
+    link {
+        // as: Mime,
+        crossorigin: CrossOrigin,
+        href: Uri,
+        hreflang: LanguageTag,
+        media: String, // FIXME media query
+        rel: LinkType,
+        sizes: String, // FIXME
+        title: String, // FIXME
+        r#type: Mime,
+        integrity: String,
+    };
+
+    /// Build a
+    /// [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta)
+    /// element.
+    meta {
+        charset: String, // FIXME IANA standard names
+        content: String,
+        http_equiv: HTTPEquiv,
+        name: Metadata,
+    };
+
+    /// Build a
+    /// [`<style>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style)
+    /// element.
+    style {
+        r#type: Mime,
+        media: String, // FIXME media query
+        nonce: Nonce,
+        title: String, // FIXME
+    };
+
+    /// Build a
+    /// [`<title>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title)
+    /// element.
+    title { };
+
+    // Sectioning root
+
+    /// Build a
+    /// [`<body>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/body)
+    /// element.
+    body {};
+
+    // ------------------
+    // Content sectioning
+    // ------------------
+
+    /// Build a
+    /// [`<address>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/address)
+    /// element.
+    address {};
+
+    /// Build a
+    /// [`<article>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/article)
+    /// element.
+    article {};
+
+    /// Build a
+    /// [`<aside>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/aside)
+    /// element.
+    aside {};
+
+    /// Build a
+    /// [`<footer>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/footer)
+    /// element.
+    footer {};
+
+    /// Build a
+    /// [`<header>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/header)
+    /// element.
+    header {};
+
+    /// Build a
+    /// [`<h1>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h1)
+    /// element.
+    ///
+    /// # About
+    /// - The HTML `<h1>` element is found within the `<body>` tag.
+    /// - Headings can range from `<h1>` to `<h6>`.
+    /// - The most important heading is `<h1>` and the least important heading is `<h6>`.
+    /// - The `<h1>` heading is the first heading in the document.
+    /// - The `<h1>` heading is usually a large bolded font.
+    ///
+    /// # Usage
+    ///
+    /// ```
+    /// html!(<h1> A header element </h1>)
+    /// rsx!(h1 { "A header element" })
+    /// LazyNodes::new(|f| f.el(h1).children([f.text("A header element")]).finish())
+    /// ```
+    h1 {};
+
+
+    /// Build a
+    /// [`<h2>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h2)
+    /// element.
+    ///
+    /// # About
+    /// - The HTML `<h2>` element is found within the `<body>` tag.
+    /// - Headings can range from `<h1>` to `<h6>`.
+    /// - The most important heading is `<h1>` and the least important heading is `<h6>`.
+    /// - The `<h2>` heading is the second heading in the document.
+    /// - The `<h2>` heading is usually a large bolded font.
+    ///
+    /// # Usage
+    /// ```
+    /// html!(<h2> A header element </h2>)
+    /// rsx!(h2 { "A header element" })
+    /// LazyNodes::new(|f| f.el(h2).children([f.text("A header element")]).finish())
+    /// ```
+    h2 {};
+
+
+    /// Build a
+    /// [`<h3>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h3)
+    /// element.
+    ///
+    /// # About
+    /// - The HTML <h1> element is found within the <body> tag.
+    /// - Headings can range from <h1> to <h6>.
+    /// - The most important heading is <h1> and the least important heading is <h6>.
+    /// - The <h1> heading is the first heading in the document.
+    /// - The <h1> heading is usually a large bolded font.
+    h3 {};
+    /// Build a
+    /// [`<h4>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h4)
+    /// element.
+    h4 {};
+    /// Build a
+    /// [`<h5>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h5)
+    /// element.
+    h5 {};
+    /// Build a
+    /// [`<h6>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h6)
+    /// element.
+    h6 {};
+
+    /// Build a
+    /// [`<main>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/main)
+    /// element.
+    main {};
+    /// Build a
+    /// [`<nav>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/nav)
+    /// element.
+    nav {};
+    /// Build a
+    /// [`<section>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/section)
+    /// element.
+    section {};
+
+    // Text content
+
+    /// Build a
+    /// [`<blockquote>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote)
+    /// element.
+    blockquote {
+        cite: Uri,
+    };
+    /// Build a
+    /// [`<dd>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dd)
+    /// element.
+    dd {};
+
+    /// Build a
+    /// [`<div>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div)
+    /// element.
+    ///
+    /// Part of the HTML namespace. Only works in HTML-compatible renderers
+    ///
+    /// ## Definition and Usage
+    /// - The <div> tag defines a division or a section in an HTML document.
+    /// - The <div> tag is used as a container for HTML elements - which is then styled with CSS or manipulated with  JavaScript.
+    /// - The <div> tag is easily styled by using the class or id attribute.
+    /// - Any sort of content can be put inside the <div> tag!
+    ///
+    /// Note: By default, browsers always place a line break before and after the <div> element.
+    ///
+    /// ## Usage
+    /// ```
+    /// html!(<div> A header element </div>)
+    /// rsx!(div { "A header element" })
+    /// LazyNodes::new(|f| f.element(div, &[], &[], &[], None))
+    /// ```
+    ///
+    /// ## References:
+    /// - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div
+    /// - https://www.w3schools.com/tags/tag_div.asp
+    div {};
+
+    /// Build a
+    /// [`<dl>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl)
+    /// element.
+    dl {};
+
+    /// Build a
+    /// [`<dt>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dt)
+    /// element.
+    dt {};
+
+    /// Build a
+    /// [`<figcaption>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figcaption)
+    /// element.
+    figcaption {};
+
+    /// Build a
+    /// [`<figure>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure)
+    /// element.
+    figure {};
+
+    /// Build a
+    /// [`<hr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hr)
+    /// element.
+    hr {};
+
+    /// Build a
+    /// [`<li>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li)
+    /// element.
+    li {
+        value: isize,
+    };
+
+    /// Build a
+    /// [`<ol>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol)
+    /// element.
+    ol {
+        reversed: Bool,
+        start: isize,
+        r#type: OrderedListType,
+    };
+
+    /// Build a
+    /// [`<p>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p)
+    /// element.
+    p {};
+
+    /// Build a
+    /// [`<pre>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre)
+    /// element.
+    pre {};
+
+    /// Build a
+    /// [`<ul>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul)
+    /// element.
+    ul {};
+
+
+    // Inline text semantics
+
+    /// Build a
+    /// [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a)
+    /// element.
+    a {
+        download: String,
+        href: Uri,
+        hreflang: LanguageTag,
+        target: Target,
+        r#type: Mime,
+        // ping: SpacedList<Uri>,
+        // rel: SpacedList<LinkType>,
+        ping: SpacedList,
+        rel: SpacedList,
+    };
+
+    /// Build a
+    /// [`<abbr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/abbr)
+    /// element.
+    abbr {};
+
+    /// Build a
+    /// [`<b>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/b)
+    /// element.
+    b {};
+
+    /// Build a
+    /// [`<bdi>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdi)
+    /// element.
+    bdi {};
+
+    /// Build a
+    /// [`<bdo>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdo)
+    /// element.
+    bdo {};
+
+    /// Build a
+    /// [`<br>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/br)
+    /// element.
+    br {};
+
+    /// Build a
+    /// [`<cite>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/cite)
+    /// element.
+    cite {};
+
+    /// Build a
+    /// [`<code>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code)
+    /// element.
+    code {
+        language: String,
+    };
+
+    /// Build a
+    /// [`<data>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/data)
+    /// element.
+    data {
+        value: String,
+    };
+
+    /// Build a
+    /// [`<dfn>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dfn)
+    /// element.
+    dfn {};
+
+    /// Build a
+    /// [`<em>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/em)
+    /// element.
+    em {};
+
+    /// Build a
+    /// [`<i>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/i)
+    /// element.
+    i {};
+
+    /// Build a
+    /// [`<kbd>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/kbd)
+    /// element.
+    kbd {};
+
+    /// Build a
+    /// [`<mark>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark)
+    /// element.
+    mark {};
+
+    /// Build a
+    /// [`<q>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/q)
+    /// element.
+    q {
+        cite: Uri,
+    };
+
+
+    /// Build a
+    /// [`<rp>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rp)
+    /// element.
+    rp {};
+
+
+    /// Build a
+    /// [`<rt>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rt)
+    /// element.
+    rt {};
+
+
+    /// Build a
+    /// [`<ruby>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ruby)
+    /// element.
+    ruby {};
+
+    /// Build a
+    /// [`<s>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/s)
+    /// element.
+    s {};
+
+    /// Build a
+    /// [`<samp>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/samp)
+    /// element.
+    samp {};
+
+    /// Build a
+    /// [`<small>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/small)
+    /// element.
+    small {};
+
+    /// Build a
+    /// [`<span>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/span)
+    /// element.
+    span {};
+
+    /// Build a
+    /// [`<strong>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/strong)
+    /// element.
+    strong {};
+
+    /// Build a
+    /// [`<sub>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sub)
+    /// element.
+    sub {};
+
+    /// Build a
+    /// [`<sup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sup)
+    /// element.
+    sup {};
+
+    /// Build a
+    /// [`<time>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time)
+    /// element.
+    time {};
+
+    /// Build a
+    /// [`<u>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/u)
+    /// element.
+    u {};
+
+    /// Build a
+    /// [`<var>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/var)
+    /// element.
+    var {};
+
+    /// Build a
+    /// [`<wbr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/wbr)
+    /// element.
+    wbr {};
+
+
+    // Image and multimedia
+
+    /// Build a
+    /// [`<area>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/area)
+    /// element.
+    area {
+        alt: String,
+        coords: String, // TODO could perhaps be validated
+        download: Bool,
+        href: Uri,
+        hreflang: LanguageTag,
+        shape: AreaShape,
+        target: Target,
+        // ping: SpacedList<Uri>,
+        // rel: SpacedSet<LinkType>,
+    };
+
+    /// Build a
+    /// [`<audio>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio)
+    /// element.
+    audio {
+        autoplay: Bool,
+        controls: Bool,
+        crossorigin: CrossOrigin,
+        muted: Bool,
+        preload: Preload,
+        src: Uri,
+        r#loop: Bool,
+    };
+
+    /// Build a
+    /// [`<img>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img)
+    /// element.
+    img {
+        alt: String,
+        crossorigin: CrossOrigin,
+        decoding: ImageDecoding,
+        height: usize,
+        ismap: Bool,
+        src: Uri,
+        srcset: String, // FIXME this is much more complicated
+        usemap: String, // FIXME should be a fragment starting with '#'
+        width: usize,
+        referrerpolicy: String,
+        // sizes: SpacedList<String>, // FIXME it's not really just a string
+    };
+
+    /// Build a
+    /// [`<map>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/map)
+    /// element.
+    map {
+        name: Id,
+    };
+
+    /// Build a
+    /// [`<track>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track)
+    /// element.
+    track {
+        default: Bool,
+        kind: VideoKind,
+        label: String,
+        src: Uri,
+        srclang: LanguageTag,
+    };
+
+    /// Build a
+    /// [`<video>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video)
+    /// element.
+    video {
+        autoplay: Bool,
+        controls: Bool,
+        crossorigin: CrossOrigin,
+        height: usize,
+        r#loop: Bool,
+        muted: Bool,
+        preload: Preload,
+        playsinline: Bool,
+        poster: Uri,
+        src: Uri,
+        width: usize,
+    };
+
+
+    // Embedded content
+
+    /// Build a
+    /// [`<embed>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed)
+    /// element.
+    embed {
+        height: usize,
+        src: Uri,
+        r#type: Mime,
+        width: usize,
+    };
+
+    /// Build a
+    /// [`<iframe>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe)
+    /// element.
+    iframe {
+        allow: FeaturePolicy,
+        allowfullscreen: Bool,
+        allowpaymentrequest: Bool,
+        height: usize,
+        name: Id,
+        referrerpolicy: ReferrerPolicy,
+        src: Uri,
+        srcdoc: Uri,
+        width: usize,
+
+        marginWidth: String,
+        align: String,
+        longdesc: String,
+
+        scrolling: String,
+        marginHeight: String,
+        frameBorder: String,
+        // sandbox: SpacedSet<Sandbox>,
+    };
+
+    /// Build a
+    /// [`<object>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object)
+    /// element.
+    object {
+        data: Uri,
+        form: Id,
+        height: usize,
+        name: Id,
+        r#type: Mime,
+        typemustmatch: Bool,
+        usemap: String, // TODO should be a fragment starting with '#'
+        width: usize,
+    };
+
+    /// Build a
+    /// [`<param>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/param)
+    /// element.
+    param {
+        name: String,
+        value: String,
+    };
+
+    /// Build a
+    /// [`<picture>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture)
+    /// element.
+    picture {};
+
+    /// Build a
+    /// [`<source>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source)
+    /// element.
+    source {
+        src: Uri,
+        r#type: Mime,
+    };
+
+
+    // Scripting
+
+    /// Build a
+    /// [`<canvas>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas)
+    /// element.
+    canvas {
+        height: usize,
+        width: usize,
+    };
+
+    /// Build a
+    /// [`<noscript>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/noscript)
+    /// element.
+    noscript {};
+
+    /// Build a
+    /// [`<script>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script)
+    /// element.
+    script {
+        crossorigin: CrossOrigin,
+        defer: Bool,
+        integrity: Integrity,
+        nomodule: Bool,
+        nonce: Nonce,
+        src: Uri,
+        text: String,
+        r#async: Bool,
+        r#type: String, // TODO could be an enum
+    };
+
+
+    // Demarcating edits
+
+    /// Build a
+    /// [`<del>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/del)
+    /// element.
+    del {
+        cite: Uri,
+        datetime: Datetime,
+    };
+
+    /// Build a
+    /// [`<ins>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ins)
+    /// element.
+    ins {
+        cite: Uri,
+        datetime: Datetime,
+    };
+
+
+    // Table content
+
+    /// Build a
+    /// [`<caption>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/caption)
+    /// element.
+    caption {};
+
+    /// Build a
+    /// [`<col>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/col)
+    /// element.
+    col {
+        span: usize,
+    };
+
+    /// Build a
+    /// [`<colgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/colgroup)
+    /// element.
+    colgroup {
+        span: usize,
+    };
+
+    /// Build a
+    /// [`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table)
+    /// element.
+    table {};
+
+    /// Build a
+    /// [`<tbody>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tbody)
+    /// element.
+    tbody {};
+
+    /// Build a
+    /// [`<td>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td)
+    /// element.
+    td {
+        colspan: usize,
+        rowspan: usize,
+        // headers: SpacedSet<Id>,
+    };
+
+    /// Build a
+    /// [`<tfoot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tfoot)
+    /// element.
+    tfoot {};
+
+    /// Build a
+    /// [`<th>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th)
+    /// element.
+    th {
+        abbr: String,
+        colspan: usize,
+        rowspan: usize,
+        scope: TableHeaderScope,
+        // headers: SpacedSet<Id>,
+    };
+
+    /// Build a
+    /// [`<thead>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/thead)
+    /// element.
+    thead {};
+
+    /// Build a
+    /// [`<tr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tr)
+    /// element.
+    tr {};
+
+
+    // Forms
+
+    /// Build a
+    /// [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button)
+    /// element.
+    button {
+        autofocus: Bool,
+        disabled: Bool,
+        form: Id,
+        formaction: Uri,
+        formenctype: FormEncodingType,
+        formmethod: FormMethod,
+        formnovalidate: Bool,
+        formtarget: Target,
+        name: Id,
+        r#type: ButtonType,
+        value: String,
+    };
+
+    /// Build a
+    /// [`<datalist>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist)
+    /// element.
+    datalist {};
+
+    /// Build a
+    /// [`<fieldset>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset)
+    /// element.
+    fieldset {};
+
+    /// Build a
+    /// [`<form>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form)
+    /// element.
+    form {
+        // accept-charset: SpacedList<CharacterEncoding>,
+        action: Uri,
+        autocomplete: OnOff,
+        enctype: FormEncodingType,
+        method: FormMethod,
+        name: Id,
+        novalidate: Bool,
+        target: Target,
+    };
+
+    /// Build a
+    /// [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input)
+    /// element.
+    input {
+        accept: String,
+        alt: String,
+        autocomplete: String,
+        autofocus: Bool,
+        capture: String,
+        checked: Bool,
+        disabled: Bool,
+        form: Id,
+        formaction: Uri,
+        formenctype: FormEncodingType,
+        formmethod: FormDialogMethod,
+        formnovalidate: Bool,
+        formtarget: Target,
+        height: isize,
+        list: Id,
+        max: String,
+        maxlength: usize,
+        min: String,
+        minlength: usize,
+        multiple: Bool,
+        name: Id,
+        pattern: String,
+        placeholder: String,
+        readonly: Bool,
+        required: Bool,
+        size: usize,
+        spellcheck: Bool,
+        src: Uri,
+        step: String,
+        tabindex: usize,
+
+        width: isize,
+
+        // Manual implementations below...
+        // r#type: InputType,
+        // value: String,
+    };
+
+    /// Build a
+    /// [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label)
+    /// element.
+    label {
+        form: Id,
+        // r#for: Id,
+    };
+
+    /// Build a
+    /// [`<legend>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/legend)
+    /// element.
+    legend {};
+
+    /// Build a
+    /// [`<meter>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meter)
+    /// element.
+    meter {
+        value: isize,
+        min: isize,
+        max: isize,
+        low: isize,
+        high: isize,
+        optimum: isize,
+        form: Id,
+    };
+
+    /// Build a
+    /// [`<optgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup)
+    /// element.
+    optgroup {
+        disabled: Bool,
+        label: String,
+    };
+
+    /// Build a
+    /// [`<option>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option)
+    /// element.
+    option {
+        disabled: Bool,
+        label: String,
+
+
+        value: String,
+
+        // defined below
+        // selected: Bool,
+    };
+
+    /// Build a
+    /// [`<output>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/output)
+    /// element.
+    output {
+        form: Id,
+        name: Id,
+        // r#for: SpacedSet<Id>,
+    };
+
+    /// Build a
+    /// [`<progress>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/progress)
+    /// element.
+    progress {
+        max: f64,
+        value: f64,
+    };
+
+    /// Build a
+    /// [`<select>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select)
+    /// element.
+    select {
+        // defined below
+        // value: String,
+        autocomplete: String,
+        autofocus: Bool,
+        disabled: Bool,
+        form: Id,
+        multiple: Bool,
+        name: Id,
+        required: Bool,
+        size: usize,
+    };
+
+    /// Build a
+    /// [`<textarea>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea)
+    /// element.
+    textarea {
+        autocomplete: OnOff,
+        autofocus: Bool,
+        cols: usize,
+        disabled: Bool,
+        form: Id,
+        maxlength: usize,
+        minlength: usize,
+        name: Id,
+        placeholder: String,
+        readonly: Bool,
+        required: Bool,
+        rows: usize,
+        spellcheck: BoolOrDefault,
+        wrap: Wrap,
+    };
+
+
+    // Interactive elements
+
+    /// Build a
+    /// [`<details>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details)
+    /// element.
+    details {
+        open: Bool,
+    };
+
+
+
+    /// Build a
+    /// [`<summary>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary)
+    /// element.
+    summary {};
+
+    // Web components
+
+    /// Build a
+    /// [`<slot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot)
+    /// element.
+    slot {};
+
+    /// Build a
+    /// [`<template>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template)
+    /// element.
+    template {};
+}
+
+impl input {
+    /// The type of input
+    ///
+    /// Here are the different input types you can use in HTML:
+    ///
+    /// - `button`
+    /// - `checkbox`
+    /// - `color`
+    /// - `date`
+    /// - `datetime-local`
+    /// - `email`
+    /// - `file`
+    /// - `hidden`
+    /// - `image`
+    /// - `month`
+    /// - `number`
+    /// - `password`
+    /// - `radio`
+    /// - `range`
+    /// - `reset`
+    /// - `search`
+    /// - `submit`
+    /// - `tel`
+    /// - `text`
+    /// - `time`
+    /// - `url`
+    /// - `week`    
+    pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
+        cx.attr("type", val, None, false)
+    }
+
+    pub fn value<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
+        cx.attr("value", val, None, true)
+    }
+}
+
+/*
+volatile attributes
+*/
+
+impl select {
+    pub fn value<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
+        cx.attr("value", val, None, true)
+    }
+}
+
+impl option {
+    pub fn selected<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
+        cx.attr("selected", val, None, true)
+    }
+}
+
+impl textarea {
+    pub fn value<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
+        cx.attr("value", val, None, true)
+    }
+}
+impl label {
+    pub fn r#for<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
+        cx.attr("for", val, None, false)
+    }
+}
+
+builder_constructors! {
+    // SVG components
+    /// Build a
+    /// [`<svg>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg)
+    /// element.
+    svg <> "http://www.w3.org/2000/svg" { };
+
+    /// Build a
+    /// [`<path>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path)
+    /// element.
+    path <> "http://www.w3.org/2000/svg" {
+
+    };
+
+    /// Build a
+    /// [`<circle>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/circle)
+    /// element.
+    circle <>  "http://www.w3.org/2000/svg" {
+
+    };
+
+    /// Build a
+    /// [`<ellipse>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/ellipse)
+    /// element.
+    ellipse <> "http://www.w3.org/2000/svg" {
+
+    };
+
+    /// Build a
+    /// [`<line>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/line)
+    /// element.
+    line <> "http://www.w3.org/2000/svg" {
+
+    };
+
+    /// Build a
+    /// [`<polygon>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polygon)
+    /// element.
+    polygon <> "http://www.w3.org/2000/svg" {
+
+    };
+
+    /// Build a
+    /// [`<polyline>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polyline)
+    /// element.
+    polyline <> "http://www.w3.org/2000/svg" {
+
+    };
+
+    /// Build a
+    /// [`<rect>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/rect)
+    /// element.
+    rect <> "http://www.w3.org/2000/svg" {
+
+    };
+
+    /// Build a
+    /// [`<image>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image)
+    /// element.
+    image <> "http://www.w3.org/2000/svg" {
+
+    };
+
+}

+ 5 - 91
packages/core/src/events.rs → packages/html/src/events.rs

@@ -1,89 +1,7 @@
-//! An event system that's less confusing than Traits + RC;
-//! This should hopefully make it easier to port to other platforms.
-//!
-//! Unfortunately, it is less efficient than the original, but hopefully it's negligible.
-
-use crate::{
-    innerlude::Listener,
-    innerlude::{ElementId, NodeFactory, ScopeId},
-};
 use bumpalo::boxed::Box as BumpBox;
-use std::{
-    any::Any,
-    cell::{Cell, RefCell},
-    fmt::Debug,
-};
-
-pub use on::*;
-
-#[derive(Debug)]
-pub struct UserEvent {
-    /// The originator of the event trigger
-    pub scope: ScopeId,
-
-    /// The optional real node associated with the trigger
-    pub mounted_dom_id: Option<ElementId>,
-
-    /// The event type IE "onclick" or "onmouseover"
-    ///
-    /// The name that the renderer will use to mount the listener.
-    pub name: &'static str,
-
-    /// The type of event
-    pub event: Box<dyn Any + Send>,
-}
-
-/// Priority of Event Triggers.
-///
-/// Internally, Dioxus will abort work that's taking too long if new, more important work arrives. Unlike React, Dioxus
-/// won't be afraid to pause work or flush changes to the RealDOM. This is called "cooperative scheduling". Some Renderers
-/// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
-///
-/// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
-///
-/// React has a 5-tier priority system. However, they break things into "Continuous" and "Discrete" priority. For now,
-/// we keep it simple, and just use a 3-tier priority system.
-///
-/// - NoPriority = 0
-/// - LowPriority = 1
-/// - NormalPriority = 2
-/// - UserBlocking = 3
-/// - HighPriority = 4
-/// - ImmediatePriority = 5
-///
-/// We still have a concept of discrete vs continuous though - discrete events won't be batched, but continuous events will.
-/// This means that multiple "scroll" events will be processed in a single frame, but multiple "click" events will be
-/// flushed before proceeding. Multiple discrete events is highly unlikely, though.
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
-pub enum EventPriority {
-    /// Work that must be completed during the EventHandler phase.
-    ///
-    /// Currently this is reserved for controlled inputs.
-    Immediate = 3,
-
-    /// "High Priority" work will not interrupt other high priority work, but will interrupt medium and low priority work.
-    ///
-    /// This is typically reserved for things like user interaction.
-    ///
-    /// React calls these "discrete" events, but with an extra category of "user-blocking" (Immediate).
-    High = 2,
-
-    /// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important
-    /// than "High Priority" events and will take precedence over low priority events.
-    ///
-    /// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
-    ///
-    /// React calls these "continuous" events (e.g. mouse move, mouse wheel, touch move, etc).
-    Medium = 1,
-
-    /// "Low Priority" work will always be preempted unless the work is significantly delayed, in which case it will be
-    /// advanced to the front of the work queue until completed.
-    ///
-    /// The primary user of Low Priority work is the asynchronous work system (Suspense).
-    ///
-    /// This is considered "idle" work or "background" work.
-    Low = 0,
-}
+use dioxus_core::exports::bumpalo;
+use dioxus_core::*;
+use std::any::Any;
 
 pub mod on {
     use super::*;
@@ -125,11 +43,7 @@ pub mod on {
                         // ie copy
                         let shortname: &'static str = &event_name[2..];
 
-                        Listener {
-                            event: shortname,
-                            mounted_node: Cell::new(None),
-                            callback: RefCell::new(Some(callback)),
-                        }
+                        c.listener(shortname, callback)
                     }
                 )*
             )*
@@ -1148,7 +1062,7 @@ impl KeyCode {
     }
 }
 
-pub(crate) fn event_meta(event: &UserEvent) -> (bool, EventPriority) {
+pub(crate) fn _event_meta(event: &UserEvent) -> (bool, EventPriority) {
     use EventPriority::*;
 
     match event.name {

+ 925 - 0
packages/html/src/global_attributes.rs

@@ -0,0 +1,925 @@
+use dioxus_core::*;
+use std::fmt::Arguments;
+
+macro_rules! no_namespace_trait_methods {
+    (
+        $(
+            $(#[$attr:meta])*
+            $name:ident;
+        )*
+    ) => {
+        $(
+            $(#[$attr])*
+            fn $name<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
+                cx.attr(stringify!($name), val, None, false)
+            }
+        )*
+    };
+}
+macro_rules! style_trait_methods {
+    (
+        $(
+            $(#[$attr:meta])*
+            $name:ident: $lit:literal,
+        )*
+    ) => {
+        $(
+            #[inline]
+            $(#[$attr])*
+            fn $name<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
+                cx.attr($lit, val, Some("style"), false)
+            }
+        )*
+    };
+}
+macro_rules! aria_trait_methods {
+    (
+        $(
+            $(#[$attr:meta])*
+            $name:ident: $lit:literal,
+        )*
+    ) => {
+        $(
+            $(#[$attr])*
+            fn $name<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
+                cx.attr($lit, val, None, false)
+            }
+        )*
+    };
+}
+
+pub trait GlobalAttributes {
+    no_namespace_trait_methods! {
+        accesskey;
+
+        /// The HTML class attribute is used to specify a class for an HTML element.
+        ///
+        /// ## Details
+        /// Multiple HTML elements can share the same class.
+        ///
+        /// The class global attribute is a space-separated list of the case-sensitive classes of the element.
+        /// Classes allow CSS and Javascript to select and access specific elements via the class selectors or
+        /// functions like the DOM method document.getElementsByClassName.
+        ///
+        /// ## Example
+        ///
+        /// ### HTML:
+        /// ```html
+        /// <p class="note editorial">Above point sounds a bit obvious. Remove/rewrite?</p>
+        /// ```
+        ///
+        /// ### CSS:
+        /// ```css
+        /// .note {
+        ///     font-style: italic;
+        ///     font-weight: bold;
+        /// }
+        ///
+        /// .editorial {
+        ///     background: rgb(255, 0, 0, .25);
+        ///     padding: 10px;
+        /// }
+        /// ```
+        class;
+        contenteditable;
+        data;
+        dir;
+        draggable;
+        hidden;
+        id;
+        lang;
+        spellcheck;
+        style;
+        tabindex;
+        title;
+        translate;
+
+        /// dangerous_inner_html is Dioxus's replacement for using innerHTML in the browser DOM. In general, setting
+        /// HTML from code is risky because it’s easy to inadvertently expose your users to a cross-site scripting (XSS)
+        /// attack. So, you can set HTML directly from Dioxus, but you have to type out dangerous_inner_html to remind
+        /// yourself that it’s dangerous
+        dangerous_inner_html;
+    }
+
+    // This macro creates an explicit method call for each of the style attributes.
+    //
+    // The left token specifies the name of the attribute in the rsx! macro, and the right string literal specifies the
+    // actual name of the attribute generated.
+    //
+    // This roughly follows the html spec
+    style_trait_methods! {
+        /// Specifies the alignment of flexible container's items within the flex container.
+        align_content: "align-content",
+
+        /// Specifies the default alignment for items within the flex container.
+        align_items: "align-items",
+
+        /// Specifies the alignment for selected items within the flex container.
+        align_self: "align-self",
+
+        /// Specifies the keyframe_based animations.
+        animation: "animation",
+
+        /// Specifies when the animation will start.
+        animation_delay: "animation-delay",
+
+        /// Specifies whether the animation should play in reverse on alternate cycles or not.
+        animation_direction: "animation-direction",
+
+        /// Specifies the number of seconds or milliseconds an animation should take to complete one cycle
+        animation_duration: "animation-duration",
+
+        /// Specifies how a CSS animation should apply styles to its target before and after it is executing
+        animation_fill_mode: "animation-fill-mode",
+
+        /// Specifies the number of times an animation cycle should be played before stopping.
+        animation_iteration_count: "animation-iteration-count",
+
+        /// Specifies the name of @keyframes defined animations that should be applied to the selected element
+        animation_name: "animation-name",
+
+        /// Specifies whether the animation is running or paused.
+        animation_play_state: "animation-play-state",
+
+        /// Specifies how a CSS animation should progress over the duration of each cycle.
+        animation_timing_function: "animation-timing-function",
+
+        /// Specifies whether or not the "back" side of a transformed element is visible when facing the user.
+        backface_visibility: "backface-visibility",
+
+        /// Defines a variety of background properties within one declaration.
+        background: "background",
+
+        /// Specify whether the background image is fixed in the viewport or scrolls.
+        background_attachment: "background-attachment",
+
+        /// Specifies the painting area of the background.
+        background_clip: "background-clip",
+
+        /// Defines an element's background color.
+        background_color: "background-color",
+
+        /// Defines an element's background image.
+        background_image: "background-image",
+
+        /// Specifies the positioning area of the background images.
+        background_origin: "background-origin",
+
+        /// Defines the origin of a background image.
+        background_position: "background-position",
+
+        /// Specify whether/how the background image is tiled.
+        background_repeat: "background-repeat",
+
+        /// Specifies the size of the background images.
+        background_size: "background-size",
+
+        /// Sets the width, style, and color for all four sides of an element's border.
+        border: "border",
+
+        /// Sets the width, style, and color of the bottom border of an element.
+        border_bottom: "border-bottom",
+
+        /// Sets the color of the bottom border of an element.
+        border_bottom_color: "border-bottom-color",
+
+        /// Defines the shape of the bottom_left border corner of an element.
+        border_bottom_left_radius: "border-bottom-left-radius",
+
+        /// Defines the shape of the bottom_right border corner of an element.
+        border_bottom_right_radius: "border-bottom-right-radius",
+
+        /// Sets the style of the bottom border of an element.
+        border_bottom_style: "border-bottom-style",
+
+        /// Sets the width of the bottom border of an element.
+        border_bottom_width: "border-bottom-width",
+
+        /// Specifies whether table cell borders are connected or separated.
+        border_collapse: "border-collapse",
+
+        /// Sets the color of the border on all the four sides of an element.
+        border_color: "border-color",
+
+        /// Specifies how an image is to be used in place of the border styles.
+        border_image: "border-image",
+
+        /// Specifies the amount by which the border image area extends beyond the border box.
+        border_image_outset: "border-image-outset",
+
+        /// Specifies whether the image_border should be repeated, rounded or stretched.
+        border_image_repeat: "border-image-repeat",
+
+        /// Specifies the inward offsets of the image_border.
+        border_image_slice: "border-image-slice",
+
+        /// Specifies the location of the image to be used as a border.
+        border_image_source: "border-image-source",
+
+        /// Specifies the width of the image_border.
+        border_image_width: "border-image-width",
+
+        /// Sets the width, style, and color of the left border of an element.
+        border_left: "border-left",
+
+        /// Sets the color of the left border of an element.
+        border_left_color: "border-left-color",
+
+        /// Sets the style of the left border of an element.
+        border_left_style: "border-left-style",
+
+        /// Sets the width of the left border of an element.
+        border_left_width: "border-left-width",
+
+        /// Defines the shape of the border corners of an element.
+        border_radius: "border-radius",
+
+        /// Sets the width, style, and color of the right border of an element.
+        border_right: "border-right",
+
+        /// Sets the color of the right border of an element.
+        border_right_color: "border-right-color",
+
+        /// Sets the style of the right border of an element.
+        border_right_style: "border-right-style",
+
+        /// Sets the width of the right border of an element.
+        border_right_width: "border-right-width",
+
+        /// Sets the spacing between the borders of adjacent table cells.
+        border_spacing: "border-spacing",
+
+        /// Sets the style of the border on all the four sides of an element.
+        border_style: "border-style",
+
+        /// Sets the width, style, and color of the top border of an element.
+        border_top: "border-top",
+
+        /// Sets the color of the top border of an element.
+        border_top_color: "border-top-color",
+
+        /// Defines the shape of the top_left border corner of an element.
+        border_top_left_radius: "border-top-left-radius",
+
+        /// Defines the shape of the top_right border corner of an element.
+        border_top_right_radius: "border-top-right-radius",
+
+        /// Sets the style of the top border of an element.
+        border_top_style: "border-top-style",
+
+        /// Sets the width of the top border of an element.
+        border_top_width: "border-top-width",
+
+        /// Sets the width of the border on all the four sides of an element.
+        border_width: "border-width",
+
+        /// Specify the location of the bottom edge of the positioned element.
+        bottom: "bottom",
+
+        /// Applies one or more drop_shadows to the element's box.
+        box_shadow: "box-shadow",
+
+        /// Alter the default CSS box model.
+        box_sizing: "box-sizing",
+
+        /// Specify the position of table's caption.
+        caption_side: "caption-side",
+
+        /// Specifies the placement of an element in relation to floating elements.
+        clear: "clear",
+
+        /// Defines the clipping region.
+        clip: "clip",
+
+        /// Specify the color of the text of an element.
+        color: "color",
+
+        /// Specifies the number of columns in a multi_column element.
+        column_count: "column-count",
+
+        /// Specifies how columns will be filled.
+        column_fill: "column-fill",
+
+        /// Specifies the gap between the columns in a multi_column element.
+        column_gap: "column-gap",
+
+        /// Specifies a straight line, or "rule", to be drawn between each column in a multi_column element.
+        column_rule: "column-rule",
+
+        /// Specifies the color of the rules drawn between columns in a multi_column layout.
+        column_rule_color: "column-rule-color",
+
+        /// Specifies the style of the rule drawn between the columns in a multi_column layout.
+        column_rule_style: "column-rule-style",
+
+        /// Specifies the width of the rule drawn between the columns in a multi_column layout.
+        column_rule_width: "column-rule-width",
+
+        /// Specifies how many columns an element spans across in a multi_column layout.
+        column_span: "column-span",
+
+        /// Specifies the optimal width of the columns in a multi_column element.
+        column_width: "column-width",
+
+        /// A shorthand property for setting column_width and column_count properties.
+        columns: "columns",
+
+        /// Inserts generated content.
+        content: "content",
+
+        /// Increments one or more counter values.
+        counter_increment: "counter-increment",
+
+        /// Creates or resets one or more counters.
+        counter_reset: "counter-reset",
+
+        /// Specify the type of cursor.
+        cursor: "cursor",
+
+        /// Define the text direction/writing direction.
+        direction: "direction",
+
+        /// Specifies how an element is displayed onscreen.
+        display: "display",
+
+        /// Show or hide borders and backgrounds of empty table cells.
+        empty_cells: "empty-cells",
+
+        /// Specifies the components of a flexible length.
+        flex: "flex",
+
+        /// Specifies the initial main size of the flex item.
+        flex_basis: "flex-basis",
+
+        /// Specifies the direction of the flexible items.
+        flex_direction: "flex-direction",
+
+        /// A shorthand property for the flex_direction and the flex_wrap properties.
+        flex_flow: "flex-flow",
+
+        /// Specifies how the flex item will grow relative to the other items inside the flex container.
+        flex_grow: "flex-grow",
+
+        /// Specifies how the flex item will shrink relative to the other items inside the flex container
+        flex_shrink: "flex-shrink",
+
+        /// Specifies whether the flexible items should wrap or not.
+        flex_wrap: "flex-wrap",
+
+        /// Specifies whether or not a box should float.
+        float: "float",
+
+        /// Defines a variety of font properties within one declaration.
+        font: "font",
+
+        /// Defines a list of fonts for element.
+        font_family: "font-family",
+
+        /// Defines the font size for the text.
+        font_size: "font-size",
+
+        /// Preserves the readability of text when font fallback occurs.
+        font_size_adjust: "font-size-adjust",
+
+        /// Selects a normal, condensed, or expanded face from a font.
+        font_stretch: "font-stretch",
+
+        /// Defines the font style for the text.
+        font_style: "font-style",
+
+        /// Specify the font variant.
+        font_variant: "font-variant",
+
+        /// Specify the font weight of the text.
+        font_weight: "font-weight",
+
+        /// Specify the height of an element.
+        height: "height",
+
+        /// Specifies how flex items are aligned along the main axis of the flex container after any flexible lengths and auto margins have been resolved.
+        justify_content: "auto margins have been resolved.",
+
+        /// Specify the location of the left edge of the positioned element.
+        left: "left",
+
+        /// Sets the extra spacing between letters.
+        letter_spacing: "letter-spacing",
+
+        /// Sets the height between lines of text.
+        line_height: "line-height",
+
+        /// Defines the display style for a list and list elements.
+        list_style: "list-style",
+
+        /// Specifies the image to be used as a list_item marker.
+        list_style_image: "list-style-image",
+
+        /// Specifies the position of the list_item marker.
+        list_style_position: "list-style-position",
+
+        /// Specifies the marker style for a list_item.
+        list_styler_type: "list-style-type",
+
+        /// Sets the margin on all four sides of the element.
+        margin: "margin",
+
+        /// Sets the bottom margin of the element.
+        margin_bottom: "margin-bottom",
+
+        /// Sets the left margin of the element.
+        margin_left: "margin-left",
+
+        /// Sets the right margin of the element.
+        margin_right: "margin-right",
+
+        /// Sets the top margin of the element.
+        margin_top: "margin-top",
+
+        /// Specify the maximum height of an element.
+        max_height: "max-height",
+
+        /// Specify the maximum width of an element.
+        max_width: "max-width",
+
+        /// Specify the minimum height of an element.
+        min_height: "min-height",
+
+        /// Specify the minimum width of an element.
+        min_width: "min-width",
+
+        /// Specifies the transparency of an element.
+        opacity: "opacity",
+
+        /// Specifies the order in which a flex items are displayed and laid out within a flex container.
+        order: "order",
+
+        /// Sets the width, style, and color for all four sides of an element's outline.
+        outline: "outline",
+
+        /// Sets the color of the outline.
+        outline_color: "outline-color",
+
+        /// Set the space between an outline and the border edge of an element.
+        outline_offset: "outline-offset",
+
+        /// Sets a style for an outline.
+        outline_style: "outline-style",
+
+        /// Sets the width of the outline.
+        outline_width: "outline-width",
+
+        /// Specifies the treatment of content that overflows the element's box.
+        overflow: "overflow",
+
+        /// Specifies the treatment of content that overflows the element's box horizontally.
+        overflow_x: "overflow-x",
+
+        /// Specifies the treatment of content that overflows the element's box vertically.
+        overflow_y: "overflow-y",
+
+        /// Sets the padding on all four sides of the element.
+        padding: "padding",
+
+        /// Sets the padding to the bottom side of an element.
+        padding_bottom: "padding-bottom",
+
+        /// Sets the padding to the left side of an element.
+        padding_left: "padding-left",
+
+        /// Sets the padding to the right side of an element.
+        padding_right: "padding-right",
+
+        /// Sets the padding to the top side of an element.
+        padding_top: "padding-top",
+
+        /// Insert a page breaks after an element.
+        page_break_after: "page-break-after",
+
+        /// Insert a page breaks before an element.
+        page_break_before: "page-break-before",
+
+        /// Insert a page breaks inside an element.
+        page_break_inside: "page-break-inside",
+
+        /// Defines the perspective from which all child elements of the object are viewed.
+        perspective: "perspective",
+
+        /// Defines the origin (the vanishing point for the 3D space) for the perspective property.
+        perspective_origin: "perspective-origin",
+
+        /// Specifies how an element is positioned.
+        position: "position",
+
+        /// The pointer-events CSS property sets under what circumstances (if any) a particular graphic element can
+        /// become the target of pointer events.
+        ///
+        /// MDN: [`pointer_events`](https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events)
+        pointer_events: "pointer-events",
+
+        /// Specifies quotation marks for embedded quotations.
+        quotes: "quotes",
+
+        /// Specifies whether or not an element is resizable by the user.
+        resize: "resize",
+
+        /// Specify the location of the right edge of the positioned element.
+        right: "right",
+
+        /// Specifies the length of the tab character.
+        tab_size: "tab-size",
+
+        /// Specifies a table layout algorithm.
+        table_layout: "table-layout",
+
+        /// Sets the horizontal alignment of inline content.
+        text_align: "text-align",
+        /// Specifies how the last line of a block or a line right before a forced line break is aligned when  is justify.",
+        text_align_last: "text-align-last",
+
+        /// Specifies the decoration added to text.
+        text_decoration: "text-decoration",
+
+        /// Specifies the color of the text_decoration_line.
+        text_decoration_color: "text-decoration-color",
+
+        /// Specifies what kind of line decorations are added to the element.
+        text_decoration_line: "text-decoration-line",
+
+        /// Specifies the style of the lines specified by the text_decoration_line property
+        text_decoration_style: "text-decoration-style",
+
+        /// Indent the first line of text.
+        text_indent: "text-indent",
+
+        /// Specifies the justification method to use when the text_align property is set to justify.
+        text_justify: "text-justify",
+
+        /// Specifies how the text content will be displayed, when it overflows the block containers.
+        text_overflow: "text-overflow",
+
+        /// Applies one or more shadows to the text content of an element.
+        text_shadow: "text-shadow",
+
+        /// Transforms the case of the text.
+        text_transform: "text-transform",
+
+        /// Specify the location of the top edge of the positioned element.
+        top: "top",
+
+        /// Applies a 2D or 3D transformation to an element.
+        transform: "transform",
+
+        /// Defines the origin of transformation for an element.
+        transform_origin: "transform-origin",
+
+        /// Specifies how nested elements are rendered in 3D space.
+        transform_style: "transform-style",
+
+        /// Defines the transition between two states of an element.
+        transition: "transition",
+
+        /// Specifies when the transition effect will start.
+        transition_delay: "transition-delay",
+
+        /// Specifies the number of seconds or milliseconds a transition effect should take to complete.
+        transition_duration: "transition-duration",
+
+        /// Specifies the names of the CSS properties to which a transition effect should be applied.
+        transition_property: "transition-property",
+
+        /// Specifies the speed curve of the transition effect.
+        transition_timing_function: "transition-timing-function",
+
+        /// Sets the vertical positioning of an element relative to the current text baseline.
+        vertical_align: "vertical-align",
+
+        /// Specifies whether or not an element is visible.
+        visibility: "visibility",
+
+        /// Specifies how white space inside the element is handled.
+        white_space: "white-space",
+
+        /// Specify the width of an element.
+        width: "width",
+
+        /// Specifies how to break lines within words.
+        word_break: "word-break",
+
+        /// Sets the spacing between words.
+        word_spacing: "word-spacing",
+
+        /// Specifies whether to break words when the content overflows the boundaries of its container.
+        word_wrap: "word-wrap",
+
+        /// Specifies a layering or stacking order for positioned elements.
+        z_index	: "z-index	",
+
+    }
+    aria_trait_methods! {
+        aria_current: "aria-current",
+        aria_details: "aria-details",
+        aria_disabled: "aria-disabled",
+        aria_hidden: "aria-hidden",
+        aria_invalid: "aria-invalid",
+        aria_keyshortcuts: "aria-keyshortcuts",
+        aria_label: "aria-label",
+        aria_roledescription: "aria-roledescription",
+        // Widget Attributes
+        aria_autocomplete: "aria-autocomplete",
+        aria_checked: "aria-checked",
+        aria_expanded: "aria-expanded",
+        aria_haspopup: "aria-haspopup",
+        aria_level: "aria-level",
+        aria_modal: "aria-modal",
+        aria_multiline: "aria-multiline",
+        aria_multiselectable: "aria-multiselectable",
+        aria_orientation: "aria-orientation",
+        aria_placeholder: "aria-placeholder",
+        aria_pressed: "aria-pressed",
+        aria_readonly: "aria-readonly",
+        aria_required: "aria-required",
+        aria_selected: "aria-selected",
+        aria_sort: "aria-sort",
+        aria_valuemax: "aria-valuemax",
+        aria_valuemin: "aria-valuemin",
+        aria_valuenow: "aria-valuenow",
+        aria_valuetext: "aria-valuetext",
+        // Live Region Attributes
+        aria_atomic: "aria-atomic",
+        aria_busy: "aria-busy",
+        aria_live: "aria-live",
+        aria_relevant: "aria-relevant",
+
+        aria_dropeffect: "aria-dropeffect",
+        aria_grabbed: "aria-grabbed",
+        // Relationship Attributes
+        aria_activedescendant: "aria-activedescendant",
+        aria_colcount: "aria-colcount",
+        aria_colindex: "aria-colindex",
+        aria_colspan: "aria-colspan",
+        aria_controls: "aria-controls",
+        aria_describedby: "aria-describedby",
+        aria_errormessage: "aria-errormessage",
+        aria_flowto: "aria-flowto",
+        aria_labelledby: "aria-labelledby",
+        aria_owns: "aria-owns",
+        aria_posinset: "aria-posinset",
+        aria_rowcount: "aria-rowcount",
+        aria_rowindex: "aria-rowindex",
+        aria_rowspan: "aria-rowspan",
+        aria_setsize: "aria-setsize",
+    }
+}
+
+pub trait SvgAttributes {
+    aria_trait_methods! {
+        accent_height: "accent-height",
+        accumulate: "accumulate",
+        additive: "additive",
+        alignment_baseline: "alignment-baseline",
+        alphabetic: "alphabetic",
+        amplitude: "amplitude",
+        arabic_form: "arabic-form",
+        ascent: "ascent",
+        attributeName: "attributeName",
+        attributeType: "attributeType",
+        azimuth: "azimuth",
+        baseFrequency: "baseFrequency",
+        baseline_shift: "baseline-shift",
+        baseProfile: "baseProfile",
+        bbox: "bbox",
+        begin: "begin",
+        bias: "bias",
+        by: "by",
+        calcMode: "calcMode",
+        cap_height: "cap-height",
+        class: "class",
+        clip: "clip",
+        clipPathUnits: "clipPathUnits",
+        clip_path: "clip-path",
+        clip_rule: "clip-rule",
+        color: "color",
+        color_interpolation: "color-interpolation",
+        color_interpolation_filters: "color-interpolation-filters",
+        color_profile: "color-profile",
+        color_rendering: "color-rendering",
+        contentScriptType: "contentScriptType",
+        contentStyleType: "contentStyleType",
+        crossorigin: "crossorigin",
+        cursor: "cursor",
+        cx: "cx",
+        cy: "cy",
+        d: "d",
+        decelerate: "decelerate",
+        descent: "descent",
+        diffuseConstant: "diffuseConstant",
+        direction: "direction",
+        display: "display",
+        divisor: "divisor",
+        dominant_baseline: "dominant-baseline",
+        dur: "dur",
+        dx: "dx",
+        dy: "dy",
+        edgeMode: "edgeMode",
+        elevation: "elevation",
+        enable_background: "enable-background",
+        end: "end",
+        exponent: "exponent",
+        fill: "fill",
+        fill_opacity: "fill-opacity",
+        fill_rule: "fill-rule",
+        filter: "filter",
+        filterRes: "filterRes",
+        filterUnits: "filterUnits",
+        flood_color: "flood-color",
+        flood_opacity: "flood-opacity",
+        font_family: "font-family",
+        font_size: "font-size",
+        font_size_adjust: "font-size-adjust",
+        font_stretch: "font-stretch",
+        font_style: "font-style",
+        font_variant: "font-variant",
+        font_weight: "font-weight",
+        format: "format",
+        from: "from",
+        fr: "fr",
+        fx: "fx",
+        fy: "fy",
+        g1: "g1",
+        g2: "g2",
+        glyph_name: "glyph-name",
+        glyph_orientation_horizontal: "glyph-orientation-horizontal",
+        glyph_orientation_vertical: "glyph-orientation-vertical",
+        glyphRef: "glyphRef",
+        gradientTransform: "gradientTransform",
+        gradientUnits: "gradientUnits",
+        hanging: "hanging",
+        height: "height",
+        href: "href",
+        hreflang: "hreflang",
+        horiz_adv_x: "horiz-adv-x",
+        horiz_origin_x: "horiz-origin-x",
+        id: "id",
+        ideographic: "ideographic",
+        image_rendering: "image-rendering",
+        _in: "_in",
+        in2: "in2",
+        intercept: "intercept",
+        k: "k",
+        k1: "k1",
+        k2: "k2",
+        k3: "k3",
+        k4: "k4",
+        kernelMatrix: "kernelMatrix",
+        kernelUnitLength: "kernelUnitLength",
+        kerning: "kerning",
+        keyPoints: "keyPoints",
+        keySplines: "keySplines",
+        keyTimes: "keyTimes",
+        lang: "lang",
+        lengthAdjust: "lengthAdjust",
+        letter_spacing: "letter-spacing",
+        lighting_color: "lighting-color",
+        limitingConeAngle: "limitingConeAngle",
+        local: "local",
+        marker_end: "marker-end",
+        marker_mid: "marker-mid",
+        marker_start: "marker_start",
+        markerHeight: "markerHeight",
+        markerUnits: "markerUnits",
+        markerWidth: "markerWidth",
+        mask: "mask",
+        maskContentUnits: "maskContentUnits",
+        maskUnits: "maskUnits",
+        mathematical: "mathematical",
+        max: "max",
+        media: "media",
+        method: "method",
+        min: "min",
+        mode: "mode",
+        name: "name",
+        numOctaves: "numOctaves",
+        offset: "offset",
+        opacity: "opacity",
+        operator: "operator",
+        order: "order",
+        orient: "orient",
+        orientation: "orientation",
+        origin: "origin",
+        overflow: "overflow",
+        overline_position: "overline-position",
+        overline_thickness: "overline-thickness",
+        panose_1: "panose-1",
+        paint_order: "paint-order",
+        path: "path",
+        pathLength: "pathLength",
+        patternContentUnits: "patternContentUnits",
+        patternTransform: "patternTransform",
+        patternUnits: "patternUnits",
+        ping: "ping",
+        pointer_events: "pointer-events",
+        points: "points",
+        pointsAtX: "pointsAtX",
+        pointsAtY: "pointsAtY",
+        pointsAtZ: "pointsAtZ",
+        preserveAlpha: "preserveAlpha",
+        preserveAspectRatio: "preserveAspectRatio",
+        primitiveUnits: "primitiveUnits",
+        r: "r",
+        radius: "radius",
+        referrerPolicy: "referrerPolicy",
+        refX: "refX",
+        refY: "refY",
+        rel: "rel",
+        rendering_intent: "rendering-intent",
+        repeatCount: "repeatCount",
+        repeatDur: "repeatDur",
+        requiredExtensions: "requiredExtensions",
+        requiredFeatures: "requiredFeatures",
+        restart: "restart",
+        result: "result",
+        rotate: "rotate",
+        rx: "rx",
+        ry: "ry",
+        scale: "scale",
+        seed: "seed",
+        shape_rendering: "shape-rendering",
+        slope: "slope",
+        spacing: "spacing",
+        specularConstant: "specularConstant",
+        specularExponent: "specularExponent",
+        speed: "speed",
+        spreadMethod: "spreadMethod",
+        startOffset: "startOffset",
+        stdDeviation: "stdDeviation",
+        stemh: "stemh",
+        stemv: "stemv",
+        stitchTiles: "stitchTiles",
+        stop_color: "stop_color",
+        stop_opacity: "stop_opacity",
+        strikethrough_position: "strikethrough-position",
+        strikethrough_thickness: "strikethrough-thickness",
+        string: "string",
+        stroke: "stroke",
+        stroke_dasharray: "stroke-dasharray",
+        stroke_dashoffset: "stroke-dashoffset",
+        stroke_linecap: "stroke-linecap",
+        stroke_linejoin: "stroke-linejoin",
+        stroke_miterlimit: "stroke-miterlimit",
+        stroke_opacity: "stroke-opacity",
+        stroke_width: "stroke-width",
+        style: "style",
+        surfaceScale: "surfaceScale",
+        systemLanguage: "systemLanguage",
+        tabindex: "tabindex",
+        tableValues: "tableValues",
+        target: "target",
+        targetX: "targetX",
+        targetY: "targetY",
+        text_anchor: "text-anchor",
+        text_decoration: "text-decoration",
+        text_rendering: "text-rendering",
+        textLength: "textLength",
+        to: "to",
+        transform: "transform",
+        transform_origin: "transform-origin",
+        r#type: "_type",
+        u1: "u1",
+        u2: "u2",
+        underline_position: "underline-position",
+        underline_thickness: "underline-thickness",
+        unicode: "unicode",
+        unicode_bidi: "unicode-bidi",
+        unicode_range: "unicode-range",
+        units_per_em: "units-per-em",
+        v_alphabetic: "v-alphabetic",
+        v_hanging: "v-hanging",
+        v_ideographic: "v-ideographic",
+        v_mathematical: "v-mathematical",
+        values: "values",
+        vector_effect: "vector-effect",
+        version: "version",
+        vert_adv_y: "vert-adv-y",
+        vert_origin_x: "vert-origin-x",
+        vert_origin_y: "vert-origin-y",
+        view_box: "viewBox",
+        view_target: "viewTarget",
+        visibility: "visibility",
+        width: "width",
+        widths: "widths",
+        word_spacing: "word-spacing",
+        writing_mode: "writing-mode",
+        x: "x",
+        x_height: "x-height",
+        x1: "x1",
+        x2: "x2",
+        xmlns: "xmlns",
+        x_channel_selector: "xChannelSelector",
+        y: "y",
+        y1: "y1",
+        y2: "y2",
+        y_channel_selector: "yChannelSelector",
+        z: "z",
+        zoomAndPan: "zoomAndPan",
+    }
+}

+ 7 - 2183
packages/html/src/lib.rs

@@ -1,4 +1,5 @@
 #![allow(non_snake_case)]
+
 //! # Dioxus Namespace for HTML
 //!
 //! This crate provides a set of compile-time correct HTML elements that can be used with the Rsx and Html macros.
@@ -12,2187 +13,10 @@
 //!
 //! Currently, we don't validate for structures, but do validate attributes.
 
-use std::fmt::Arguments;
-
-use dioxus_core::{nodes::Attribute, DioxusElement, NodeFactory};
-
-macro_rules! no_namespace_trait_methods {
-    (
-        $(
-            $(#[$attr:meta])*
-            $name:ident;
-        )*
-    ) => {
-        $(
-            $(#[$attr])*
-            fn $name<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-                cx.attr(stringify!($name), val, None, false)
-            }
-        )*
-    };
-}
-macro_rules! style_trait_methods {
-    (
-        $(
-            $(#[$attr:meta])*
-            $name:ident: $lit:literal,
-        )*
-    ) => {
-        $(
-            #[inline]
-            $(#[$attr])*
-            fn $name<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-                cx.attr($lit, val, Some("style"), false)
-            }
-        )*
-    };
-}
-macro_rules! aria_trait_methods {
-    (
-        $(
-            $(#[$attr:meta])*
-            $name:ident: $lit:literal,
-        )*
-    ) => {
-        $(
-            $(#[$attr])*
-            fn $name<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-                cx.attr($lit, val, None, false)
-            }
-        )*
-    };
-}
-
-pub trait GlobalAttributes {
-    no_namespace_trait_methods! {
-        accesskey;
-
-        /// The HTML class attribute is used to specify a class for an HTML element.
-        ///
-        /// ## Details
-        /// Multiple HTML elements can share the same class.
-        ///
-        /// The class global attribute is a space-separated list of the case-sensitive classes of the element.
-        /// Classes allow CSS and Javascript to select and access specific elements via the class selectors or
-        /// functions like the DOM method document.getElementsByClassName.
-        ///
-        /// ## Example
-        ///
-        /// ### HTML:
-        /// ```html
-        /// <p class="note editorial">Above point sounds a bit obvious. Remove/rewrite?</p>
-        /// ```
-        ///
-        /// ### CSS:
-        /// ```css
-        /// .note {
-        ///     font-style: italic;
-        ///     font-weight: bold;
-        /// }
-        ///
-        /// .editorial {
-        ///     background: rgb(255, 0, 0, .25);
-        ///     padding: 10px;
-        /// }
-        /// ```
-        class;
-        contenteditable;
-        data;
-        dir;
-        draggable;
-        hidden;
-        id;
-        lang;
-        spellcheck;
-        style;
-        tabindex;
-        title;
-        translate;
-
-        /// dangerous_inner_html is Dioxus's replacement for using innerHTML in the browser DOM. In general, setting
-        /// HTML from code is risky because it’s easy to inadvertently expose your users to a cross-site scripting (XSS)
-        /// attack. So, you can set HTML directly from Dioxus, but you have to type out dangerous_inner_html to remind
-        /// yourself that it’s dangerous
-        dangerous_inner_html;
-    }
-
-    // This macro creates an explicit method call for each of the style attributes.
-    //
-    // The left token specifies the name of the attribute in the rsx! macro, and the right string literal specifies the
-    // actual name of the attribute generated.
-    //
-    // This roughly follows the html spec
-    style_trait_methods! {
-        /// Specifies the alignment of flexible container's items within the flex container.
-        align_content: "align-content",
-
-        /// Specifies the default alignment for items within the flex container.
-        align_items: "align-items",
-
-        /// Specifies the alignment for selected items within the flex container.
-        align_self: "align-self",
-
-        /// Specifies the keyframe_based animations.
-        animation: "animation",
-
-        /// Specifies when the animation will start.
-        animation_delay: "animation-delay",
-
-        /// Specifies whether the animation should play in reverse on alternate cycles or not.
-        animation_direction: "animation-direction",
-
-        /// Specifies the number of seconds or milliseconds an animation should take to complete one cycle
-        animation_duration: "animation-duration",
-
-        /// Specifies how a CSS animation should apply styles to its target before and after it is executing
-        animation_fill_mode: "animation-fill-mode",
-
-        /// Specifies the number of times an animation cycle should be played before stopping.
-        animation_iteration_count: "animation-iteration-count",
-
-        /// Specifies the name of @keyframes defined animations that should be applied to the selected element
-        animation_name: "animation-name",
-
-        /// Specifies whether the animation is running or paused.
-        animation_play_state: "animation-play-state",
-
-        /// Specifies how a CSS animation should progress over the duration of each cycle.
-        animation_timing_function: "animation-timing-function",
-
-        /// Specifies whether or not the "back" side of a transformed element is visible when facing the user.
-        backface_visibility: "backface-visibility",
-
-        /// Defines a variety of background properties within one declaration.
-        background: "background",
-
-        /// Specify whether the background image is fixed in the viewport or scrolls.
-        background_attachment: "background-attachment",
-
-        /// Specifies the painting area of the background.
-        background_clip: "background-clip",
-
-        /// Defines an element's background color.
-        background_color: "background-color",
-
-        /// Defines an element's background image.
-        background_image: "background-image",
-
-        /// Specifies the positioning area of the background images.
-        background_origin: "background-origin",
-
-        /// Defines the origin of a background image.
-        background_position: "background-position",
-
-        /// Specify whether/how the background image is tiled.
-        background_repeat: "background-repeat",
-
-        /// Specifies the size of the background images.
-        background_size: "background-size",
-
-        /// Sets the width, style, and color for all four sides of an element's border.
-        border: "border",
-
-        /// Sets the width, style, and color of the bottom border of an element.
-        border_bottom: "border-bottom",
-
-        /// Sets the color of the bottom border of an element.
-        border_bottom_color: "border-bottom-color",
-
-        /// Defines the shape of the bottom_left border corner of an element.
-        border_bottom_left_radius: "border-bottom-left-radius",
-
-        /// Defines the shape of the bottom_right border corner of an element.
-        border_bottom_right_radius: "border-bottom-right-radius",
-
-        /// Sets the style of the bottom border of an element.
-        border_bottom_style: "border-bottom-style",
-
-        /// Sets the width of the bottom border of an element.
-        border_bottom_width: "border-bottom-width",
-
-        /// Specifies whether table cell borders are connected or separated.
-        border_collapse: "border-collapse",
-
-        /// Sets the color of the border on all the four sides of an element.
-        border_color: "border-color",
-
-        /// Specifies how an image is to be used in place of the border styles.
-        border_image: "border-image",
-
-        /// Specifies the amount by which the border image area extends beyond the border box.
-        border_image_outset: "border-image-outset",
-
-        /// Specifies whether the image_border should be repeated, rounded or stretched.
-        border_image_repeat: "border-image-repeat",
-
-        /// Specifies the inward offsets of the image_border.
-        border_image_slice: "border-image-slice",
-
-        /// Specifies the location of the image to be used as a border.
-        border_image_source: "border-image-source",
-
-        /// Specifies the width of the image_border.
-        border_image_width: "border-image-width",
-
-        /// Sets the width, style, and color of the left border of an element.
-        border_left: "border-left",
-
-        /// Sets the color of the left border of an element.
-        border_left_color: "border-left-color",
-
-        /// Sets the style of the left border of an element.
-        border_left_style: "border-left-style",
-
-        /// Sets the width of the left border of an element.
-        border_left_width: "border-left-width",
-
-        /// Defines the shape of the border corners of an element.
-        border_radius: "border-radius",
-
-        /// Sets the width, style, and color of the right border of an element.
-        border_right: "border-right",
-
-        /// Sets the color of the right border of an element.
-        border_right_color: "border-right-color",
-
-        /// Sets the style of the right border of an element.
-        border_right_style: "border-right-style",
-
-        /// Sets the width of the right border of an element.
-        border_right_width: "border-right-width",
-
-        /// Sets the spacing between the borders of adjacent table cells.
-        border_spacing: "border-spacing",
-
-        /// Sets the style of the border on all the four sides of an element.
-        border_style: "border-style",
-
-        /// Sets the width, style, and color of the top border of an element.
-        border_top: "border-top",
-
-        /// Sets the color of the top border of an element.
-        border_top_color: "border-top-color",
-
-        /// Defines the shape of the top_left border corner of an element.
-        border_top_left_radius: "border-top-left-radius",
-
-        /// Defines the shape of the top_right border corner of an element.
-        border_top_right_radius: "border-top-right-radius",
-
-        /// Sets the style of the top border of an element.
-        border_top_style: "border-top-style",
-
-        /// Sets the width of the top border of an element.
-        border_top_width: "border-top-width",
-
-        /// Sets the width of the border on all the four sides of an element.
-        border_width: "border-width",
-
-        /// Specify the location of the bottom edge of the positioned element.
-        bottom: "bottom",
-
-        /// Applies one or more drop_shadows to the element's box.
-        box_shadow: "box-shadow",
-
-        /// Alter the default CSS box model.
-        box_sizing: "box-sizing",
-
-        /// Specify the position of table's caption.
-        caption_side: "caption-side",
-
-        /// Specifies the placement of an element in relation to floating elements.
-        clear: "clear",
-
-        /// Defines the clipping region.
-        clip: "clip",
-
-        /// Specify the color of the text of an element.
-        color: "color",
-
-        /// Specifies the number of columns in a multi_column element.
-        column_count: "column-count",
-
-        /// Specifies how columns will be filled.
-        column_fill: "column-fill",
-
-        /// Specifies the gap between the columns in a multi_column element.
-        column_gap: "column-gap",
-
-        /// Specifies a straight line, or "rule", to be drawn between each column in a multi_column element.
-        column_rule: "column-rule",
-
-        /// Specifies the color of the rules drawn between columns in a multi_column layout.
-        column_rule_color: "column-rule-color",
-
-        /// Specifies the style of the rule drawn between the columns in a multi_column layout.
-        column_rule_style: "column-rule-style",
-
-        /// Specifies the width of the rule drawn between the columns in a multi_column layout.
-        column_rule_width: "column-rule-width",
-
-        /// Specifies how many columns an element spans across in a multi_column layout.
-        column_span: "column-span",
-
-        /// Specifies the optimal width of the columns in a multi_column element.
-        column_width: "column-width",
-
-        /// A shorthand property for setting column_width and column_count properties.
-        columns: "columns",
-
-        /// Inserts generated content.
-        content: "content",
-
-        /// Increments one or more counter values.
-        counter_increment: "counter-increment",
-
-        /// Creates or resets one or more counters.
-        counter_reset: "counter-reset",
-
-        /// Specify the type of cursor.
-        cursor: "cursor",
-
-        /// Define the text direction/writing direction.
-        direction: "direction",
-
-        /// Specifies how an element is displayed onscreen.
-        display: "display",
-
-        /// Show or hide borders and backgrounds of empty table cells.
-        empty_cells: "empty-cells",
-
-        /// Specifies the components of a flexible length.
-        flex: "flex",
-
-        /// Specifies the initial main size of the flex item.
-        flex_basis: "flex-basis",
-
-        /// Specifies the direction of the flexible items.
-        flex_direction: "flex-direction",
-
-        /// A shorthand property for the flex_direction and the flex_wrap properties.
-        flex_flow: "flex-flow",
-
-        /// Specifies how the flex item will grow relative to the other items inside the flex container.
-        flex_grow: "flex-grow",
-
-        /// Specifies how the flex item will shrink relative to the other items inside the flex container
-        flex_shrink: "flex-shrink",
-
-        /// Specifies whether the flexible items should wrap or not.
-        flex_wrap: "flex-wrap",
-
-        /// Specifies whether or not a box should float.
-        float: "float",
-
-        /// Defines a variety of font properties within one declaration.
-        font: "font",
-
-        /// Defines a list of fonts for element.
-        font_family: "font-family",
-
-        /// Defines the font size for the text.
-        font_size: "font-size",
-
-        /// Preserves the readability of text when font fallback occurs.
-        font_size_adjust: "font-size-adjust",
-
-        /// Selects a normal, condensed, or expanded face from a font.
-        font_stretch: "font-stretch",
-
-        /// Defines the font style for the text.
-        font_style: "font-style",
-
-        /// Specify the font variant.
-        font_variant: "font-variant",
-
-        /// Specify the font weight of the text.
-        font_weight: "font-weight",
-
-        /// Specify the height of an element.
-        height: "height",
-
-        /// Specifies how flex items are aligned along the main axis of the flex container after any flexible lengths and auto margins have been resolved.
-        justify_content: "auto margins have been resolved.",
-
-        /// Specify the location of the left edge of the positioned element.
-        left: "left",
-
-        /// Sets the extra spacing between letters.
-        letter_spacing: "letter-spacing",
-
-        /// Sets the height between lines of text.
-        line_height: "line-height",
-
-        /// Defines the display style for a list and list elements.
-        list_style: "list-style",
-
-        /// Specifies the image to be used as a list_item marker.
-        list_style_image: "list-style-image",
-
-        /// Specifies the position of the list_item marker.
-        list_style_position: "list-style-position",
-
-        /// Specifies the marker style for a list_item.
-        list_styler_type: "list-style-type",
-
-        /// Sets the margin on all four sides of the element.
-        margin: "margin",
-
-        /// Sets the bottom margin of the element.
-        margin_bottom: "margin-bottom",
-
-        /// Sets the left margin of the element.
-        margin_left: "margin-left",
-
-        /// Sets the right margin of the element.
-        margin_right: "margin-right",
-
-        /// Sets the top margin of the element.
-        margin_top: "margin-top",
-
-        /// Specify the maximum height of an element.
-        max_height: "max-height",
-
-        /// Specify the maximum width of an element.
-        max_width: "max-width",
-
-        /// Specify the minimum height of an element.
-        min_height: "min-height",
-
-        /// Specify the minimum width of an element.
-        min_width: "min-width",
-
-        /// Specifies the transparency of an element.
-        opacity: "opacity",
-
-        /// Specifies the order in which a flex items are displayed and laid out within a flex container.
-        order: "order",
-
-        /// Sets the width, style, and color for all four sides of an element's outline.
-        outline: "outline",
-
-        /// Sets the color of the outline.
-        outline_color: "outline-color",
-
-        /// Set the space between an outline and the border edge of an element.
-        outline_offset: "outline-offset",
-
-        /// Sets a style for an outline.
-        outline_style: "outline-style",
-
-        /// Sets the width of the outline.
-        outline_width: "outline-width",
-
-        /// Specifies the treatment of content that overflows the element's box.
-        overflow: "overflow",
-
-        /// Specifies the treatment of content that overflows the element's box horizontally.
-        overflow_x: "overflow-x",
-
-        /// Specifies the treatment of content that overflows the element's box vertically.
-        overflow_y: "overflow-y",
-
-        /// Sets the padding on all four sides of the element.
-        padding: "padding",
-
-        /// Sets the padding to the bottom side of an element.
-        padding_bottom: "padding-bottom",
-
-        /// Sets the padding to the left side of an element.
-        padding_left: "padding-left",
-
-        /// Sets the padding to the right side of an element.
-        padding_right: "padding-right",
-
-        /// Sets the padding to the top side of an element.
-        padding_top: "padding-top",
-
-        /// Insert a page breaks after an element.
-        page_break_after: "page-break-after",
-
-        /// Insert a page breaks before an element.
-        page_break_before: "page-break-before",
-
-        /// Insert a page breaks inside an element.
-        page_break_inside: "page-break-inside",
-
-        /// Defines the perspective from which all child elements of the object are viewed.
-        perspective: "perspective",
-
-        /// Defines the origin (the vanishing point for the 3D space) for the perspective property.
-        perspective_origin: "perspective-origin",
-
-        /// Specifies how an element is positioned.
-        position: "position",
-
-        /// The pointer-events CSS property sets under what circumstances (if any) a particular graphic element can
-        /// become the target of pointer events.
-        ///
-        /// MDN: [`pointer_events`](https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events)
-        pointer_events: "pointer-events",
-
-        /// Specifies quotation marks for embedded quotations.
-        quotes: "quotes",
-
-        /// Specifies whether or not an element is resizable by the user.
-        resize: "resize",
-
-        /// Specify the location of the right edge of the positioned element.
-        right: "right",
-
-        /// Specifies the length of the tab character.
-        tab_size: "tab-size",
-
-        /// Specifies a table layout algorithm.
-        table_layout: "table-layout",
-
-        /// Sets the horizontal alignment of inline content.
-        text_align: "text-align",
-        /// Specifies how the last line of a block or a line right before a forced line break is aligned when  is justify.",
-        text_align_last: "text-align-last",
-
-        /// Specifies the decoration added to text.
-        text_decoration: "text-decoration",
-
-        /// Specifies the color of the text_decoration_line.
-        text_decoration_color: "text-decoration-color",
-
-        /// Specifies what kind of line decorations are added to the element.
-        text_decoration_line: "text-decoration-line",
-
-        /// Specifies the style of the lines specified by the text_decoration_line property
-        text_decoration_style: "text-decoration-style",
-
-        /// Indent the first line of text.
-        text_indent: "text-indent",
-
-        /// Specifies the justification method to use when the text_align property is set to justify.
-        text_justify: "text-justify",
-
-        /// Specifies how the text content will be displayed, when it overflows the block containers.
-        text_overflow: "text-overflow",
-
-        /// Applies one or more shadows to the text content of an element.
-        text_shadow: "text-shadow",
-
-        /// Transforms the case of the text.
-        text_transform: "text-transform",
-
-        /// Specify the location of the top edge of the positioned element.
-        top: "top",
-
-        /// Applies a 2D or 3D transformation to an element.
-        transform: "transform",
-
-        /// Defines the origin of transformation for an element.
-        transform_origin: "transform-origin",
-
-        /// Specifies how nested elements are rendered in 3D space.
-        transform_style: "transform-style",
-
-        /// Defines the transition between two states of an element.
-        transition: "transition",
-
-        /// Specifies when the transition effect will start.
-        transition_delay: "transition-delay",
-
-        /// Specifies the number of seconds or milliseconds a transition effect should take to complete.
-        transition_duration: "transition-duration",
-
-        /// Specifies the names of the CSS properties to which a transition effect should be applied.
-        transition_property: "transition-property",
-
-        /// Specifies the speed curve of the transition effect.
-        transition_timing_function: "transition-timing-function",
-
-        /// Sets the vertical positioning of an element relative to the current text baseline.
-        vertical_align: "vertical-align",
-
-        /// Specifies whether or not an element is visible.
-        visibility: "visibility",
-
-        /// Specifies how white space inside the element is handled.
-        white_space: "white-space",
-
-        /// Specify the width of an element.
-        width: "width",
-
-        /// Specifies how to break lines within words.
-        word_break: "word-break",
-
-        /// Sets the spacing between words.
-        word_spacing: "word-spacing",
-
-        /// Specifies whether to break words when the content overflows the boundaries of its container.
-        word_wrap: "word-wrap",
-
-        /// Specifies a layering or stacking order for positioned elements.
-        z_index	: "z-index	",
-
-    }
-    aria_trait_methods! {
-        aria_current: "aria-current",
-        aria_details: "aria-details",
-        aria_disabled: "aria-disabled",
-        aria_hidden: "aria-hidden",
-        aria_invalid: "aria-invalid",
-        aria_keyshortcuts: "aria-keyshortcuts",
-        aria_label: "aria-label",
-        aria_roledescription: "aria-roledescription",
-        // Widget Attributes
-        aria_autocomplete: "aria-autocomplete",
-        aria_checked: "aria-checked",
-        aria_expanded: "aria-expanded",
-        aria_haspopup: "aria-haspopup",
-        aria_level: "aria-level",
-        aria_modal: "aria-modal",
-        aria_multiline: "aria-multiline",
-        aria_multiselectable: "aria-multiselectable",
-        aria_orientation: "aria-orientation",
-        aria_placeholder: "aria-placeholder",
-        aria_pressed: "aria-pressed",
-        aria_readonly: "aria-readonly",
-        aria_required: "aria-required",
-        aria_selected: "aria-selected",
-        aria_sort: "aria-sort",
-        aria_valuemax: "aria-valuemax",
-        aria_valuemin: "aria-valuemin",
-        aria_valuenow: "aria-valuenow",
-        aria_valuetext: "aria-valuetext",
-        // Live Region Attributes
-        aria_atomic: "aria-atomic",
-        aria_busy: "aria-busy",
-        aria_live: "aria-live",
-        aria_relevant: "aria-relevant",
-
-        aria_dropeffect: "aria-dropeffect",
-        aria_grabbed: "aria-grabbed",
-        // Relationship Attributes
-        aria_activedescendant: "aria-activedescendant",
-        aria_colcount: "aria-colcount",
-        aria_colindex: "aria-colindex",
-        aria_colspan: "aria-colspan",
-        aria_controls: "aria-controls",
-        aria_describedby: "aria-describedby",
-        aria_errormessage: "aria-errormessage",
-        aria_flowto: "aria-flowto",
-        aria_labelledby: "aria-labelledby",
-        aria_owns: "aria-owns",
-        aria_posinset: "aria-posinset",
-        aria_rowcount: "aria-rowcount",
-        aria_rowindex: "aria-rowindex",
-        aria_rowspan: "aria-rowspan",
-        aria_setsize: "aria-setsize",
-    }
-}
-
-macro_rules! builder_constructors {
-    (
-        $(
-            $(#[$attr:meta])*
-            $name:ident {
-                $(
-                    $(#[$attr_method:meta])*
-                    $fil:ident: $vil:ident,
-                )*
-            };
-         )*
-    ) => {
-        $(
-            #[allow(non_camel_case_types)]
-            $(#[$attr])*
-            pub struct $name;
-
-            impl DioxusElement for $name {
-                const TAG_NAME: &'static str = stringify!($name);
-                const NAME_SPACE: Option<&'static str> = None;
-            }
-
-            impl GlobalAttributes for $name {}
-
-            impl $name {
-                $(
-                    $(#[$attr_method])*
-                    pub fn $fil<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-                        cx.attr(stringify!($fil), val, None, false)
-                    }
-                )*
-            }
-        )*
-    };
-
-    ( $(
-        $(#[$attr:meta])*
-        $name:ident <> $namespace:tt {
-            $($fil:ident: $vil:ident,)*
-        };
-    )* ) => {
-        $(
-            #[allow(non_camel_case_types)]
-            $(#[$attr])*
-            pub struct $name;
-
-            impl DioxusElement for $name {
-                const TAG_NAME: &'static str = stringify!($name);
-                const NAME_SPACE: Option<&'static str> = Some($namespace);
-            }
-
-            impl SvgAttributes for $name {}
-
-            impl $name {
-                $(
-                    pub fn $fil<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-                        cx.attr(stringify!($fil), val, Some(stringify!($namespace)), false)
-                    }
-                )*
-            }
-        )*
-    };
-}
-
-// Organized in the same order as
-// https://developer.mozilla.org/en-US/docs/Web/HTML/Element
-//
-// Does not include obsolete elements.
-//
-// This namespace represents a collection of modern HTML-5 compatiable elements.
-//
-// This list does not include obsolete, deprecated, experimental, or poorly supported elements.
-builder_constructors! {
-    // Document metadata
-
-    /// Build a
-    /// [`<base>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base)
-    /// element.
-    ///
-    base {
-        href: Uri,
-        target: Target,
-    };
-
-    /// Build a
-    /// [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head)
-    /// element.
-    head {};
-
-    /// Build a
-    /// [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link)
-    /// element.
-    link {
-        // as: Mime,
-        crossorigin: CrossOrigin,
-        href: Uri,
-        hreflang: LanguageTag,
-        media: String, // FIXME media query
-        rel: LinkType,
-        sizes: String, // FIXME
-        title: String, // FIXME
-        r#type: Mime,
-        integrity: String,
-    };
-
-    /// Build a
-    /// [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta)
-    /// element.
-    meta {
-        charset: String, // FIXME IANA standard names
-        content: String,
-        http_equiv: HTTPEquiv,
-        name: Metadata,
-    };
-
-    /// Build a
-    /// [`<style>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style)
-    /// element.
-    style {
-        r#type: Mime,
-        media: String, // FIXME media query
-        nonce: Nonce,
-        title: String, // FIXME
-    };
-
-    /// Build a
-    /// [`<title>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title)
-    /// element.
-    title { };
-
-    // Sectioning root
-
-    /// Build a
-    /// [`<body>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/body)
-    /// element.
-    body {};
-
-    // ------------------
-    // Content sectioning
-    // ------------------
-
-    /// Build a
-    /// [`<address>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/address)
-    /// element.
-    address {};
-
-    /// Build a
-    /// [`<article>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/article)
-    /// element.
-    article {};
-
-    /// Build a
-    /// [`<aside>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/aside)
-    /// element.
-    aside {};
-
-    /// Build a
-    /// [`<footer>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/footer)
-    /// element.
-    footer {};
-
-    /// Build a
-    /// [`<header>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/header)
-    /// element.
-    header {};
-
-    /// Build a
-    /// [`<h1>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h1)
-    /// element.
-    ///
-    /// # About
-    /// - The HTML `<h1>` element is found within the `<body>` tag.
-    /// - Headings can range from `<h1>` to `<h6>`.
-    /// - The most important heading is `<h1>` and the least important heading is `<h6>`.
-    /// - The `<h1>` heading is the first heading in the document.
-    /// - The `<h1>` heading is usually a large bolded font.
-    ///
-    /// # Usage
-    ///
-    /// ```
-    /// html!(<h1> A header element </h1>)
-    /// rsx!(h1 { "A header element" })
-    /// LazyNodes::new(|f| f.el(h1).children([f.text("A header element")]).finish())
-    /// ```
-    h1 {};
-
-
-    /// Build a
-    /// [`<h2>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h2)
-    /// element.
-    ///
-    /// # About
-    /// - The HTML `<h2>` element is found within the `<body>` tag.
-    /// - Headings can range from `<h1>` to `<h6>`.
-    /// - The most important heading is `<h1>` and the least important heading is `<h6>`.
-    /// - The `<h2>` heading is the second heading in the document.
-    /// - The `<h2>` heading is usually a large bolded font.
-    ///
-    /// # Usage
-    /// ```
-    /// html!(<h2> A header element </h2>)
-    /// rsx!(h2 { "A header element" })
-    /// LazyNodes::new(|f| f.el(h2).children([f.text("A header element")]).finish())
-    /// ```
-    h2 {};
-
-
-    /// Build a
-    /// [`<h3>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h3)
-    /// element.
-    ///
-    /// # About
-    /// - The HTML <h1> element is found within the <body> tag.
-    /// - Headings can range from <h1> to <h6>.
-    /// - The most important heading is <h1> and the least important heading is <h6>.
-    /// - The <h1> heading is the first heading in the document.
-    /// - The <h1> heading is usually a large bolded font.
-    h3 {};
-    /// Build a
-    /// [`<h4>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h4)
-    /// element.
-    h4 {};
-    /// Build a
-    /// [`<h5>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h5)
-    /// element.
-    h5 {};
-    /// Build a
-    /// [`<h6>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h6)
-    /// element.
-    h6 {};
-
-    /// Build a
-    /// [`<main>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/main)
-    /// element.
-    main {};
-    /// Build a
-    /// [`<nav>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/nav)
-    /// element.
-    nav {};
-    /// Build a
-    /// [`<section>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/section)
-    /// element.
-    section {};
-
-    // Text content
-
-    /// Build a
-    /// [`<blockquote>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote)
-    /// element.
-    blockquote {
-        cite: Uri,
-    };
-    /// Build a
-    /// [`<dd>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dd)
-    /// element.
-    dd {};
-
-    /// Build a
-    /// [`<div>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div)
-    /// element.
-    ///
-    /// Part of the HTML namespace. Only works in HTML-compatible renderers
-    ///
-    /// ## Definition and Usage
-    /// - The <div> tag defines a division or a section in an HTML document.
-    /// - The <div> tag is used as a container for HTML elements - which is then styled with CSS or manipulated with  JavaScript.
-    /// - The <div> tag is easily styled by using the class or id attribute.
-    /// - Any sort of content can be put inside the <div> tag!
-    ///
-    /// Note: By default, browsers always place a line break before and after the <div> element.
-    ///
-    /// ## Usage
-    /// ```
-    /// html!(<div> A header element </div>)
-    /// rsx!(div { "A header element" })
-    /// LazyNodes::new(|f| f.element(div, &[], &[], &[], None))
-    /// ```
-    ///
-    /// ## References:
-    /// - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div
-    /// - https://www.w3schools.com/tags/tag_div.asp
-    div {};
-
-    /// Build a
-    /// [`<dl>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl)
-    /// element.
-    dl {};
-
-    /// Build a
-    /// [`<dt>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dt)
-    /// element.
-    dt {};
-
-    /// Build a
-    /// [`<figcaption>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figcaption)
-    /// element.
-    figcaption {};
-
-    /// Build a
-    /// [`<figure>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure)
-    /// element.
-    figure {};
-
-    /// Build a
-    /// [`<hr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hr)
-    /// element.
-    hr {};
-
-    /// Build a
-    /// [`<li>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li)
-    /// element.
-    li {
-        value: isize,
-    };
-
-    /// Build a
-    /// [`<ol>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol)
-    /// element.
-    ol {
-        reversed: Bool,
-        start: isize,
-        r#type: OrderedListType,
-    };
-
-    /// Build a
-    /// [`<p>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p)
-    /// element.
-    p {};
-
-    /// Build a
-    /// [`<pre>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre)
-    /// element.
-    pre {};
-
-    /// Build a
-    /// [`<ul>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul)
-    /// element.
-    ul {};
-
-
-    // Inline text semantics
-
-    /// Build a
-    /// [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a)
-    /// element.
-    a {
-        download: String,
-        href: Uri,
-        hreflang: LanguageTag,
-        target: Target,
-        r#type: Mime,
-        // ping: SpacedList<Uri>,
-        // rel: SpacedList<LinkType>,
-        ping: SpacedList,
-        rel: SpacedList,
-    };
-
-    /// Build a
-    /// [`<abbr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/abbr)
-    /// element.
-    abbr {};
-
-    /// Build a
-    /// [`<b>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/b)
-    /// element.
-    b {};
-
-    /// Build a
-    /// [`<bdi>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdi)
-    /// element.
-    bdi {};
-
-    /// Build a
-    /// [`<bdo>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdo)
-    /// element.
-    bdo {};
-
-    /// Build a
-    /// [`<br>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/br)
-    /// element.
-    br {};
-
-    /// Build a
-    /// [`<cite>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/cite)
-    /// element.
-    cite {};
-
-    /// Build a
-    /// [`<code>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code)
-    /// element.
-    code {
-        language: String,
-    };
-
-    /// Build a
-    /// [`<data>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/data)
-    /// element.
-    data {
-        value: String,
-    };
-
-    /// Build a
-    /// [`<dfn>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dfn)
-    /// element.
-    dfn {};
-
-    /// Build a
-    /// [`<em>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/em)
-    /// element.
-    em {};
-
-    /// Build a
-    /// [`<i>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/i)
-    /// element.
-    i {};
-
-    /// Build a
-    /// [`<kbd>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/kbd)
-    /// element.
-    kbd {};
-
-    /// Build a
-    /// [`<mark>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark)
-    /// element.
-    mark {};
-
-    /// Build a
-    /// [`<q>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/q)
-    /// element.
-    q {
-        cite: Uri,
-    };
-
-
-    /// Build a
-    /// [`<rp>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rp)
-    /// element.
-    rp {};
-
-
-    /// Build a
-    /// [`<rt>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rt)
-    /// element.
-    rt {};
-
-
-    /// Build a
-    /// [`<ruby>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ruby)
-    /// element.
-    ruby {};
-
-    /// Build a
-    /// [`<s>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/s)
-    /// element.
-    s {};
-
-    /// Build a
-    /// [`<samp>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/samp)
-    /// element.
-    samp {};
-
-    /// Build a
-    /// [`<small>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/small)
-    /// element.
-    small {};
-
-    /// Build a
-    /// [`<span>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/span)
-    /// element.
-    span {};
-
-    /// Build a
-    /// [`<strong>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/strong)
-    /// element.
-    strong {};
-
-    /// Build a
-    /// [`<sub>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sub)
-    /// element.
-    sub {};
-
-    /// Build a
-    /// [`<sup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sup)
-    /// element.
-    sup {};
-
-    /// Build a
-    /// [`<time>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time)
-    /// element.
-    time {};
-
-    /// Build a
-    /// [`<u>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/u)
-    /// element.
-    u {};
-
-    /// Build a
-    /// [`<var>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/var)
-    /// element.
-    var {};
-
-    /// Build a
-    /// [`<wbr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/wbr)
-    /// element.
-    wbr {};
-
-
-    // Image and multimedia
-
-    /// Build a
-    /// [`<area>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/area)
-    /// element.
-    area {
-        alt: String,
-        coords: String, // TODO could perhaps be validated
-        download: Bool,
-        href: Uri,
-        hreflang: LanguageTag,
-        shape: AreaShape,
-        target: Target,
-        // ping: SpacedList<Uri>,
-        // rel: SpacedSet<LinkType>,
-    };
-
-    /// Build a
-    /// [`<audio>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio)
-    /// element.
-    audio {
-        autoplay: Bool,
-        controls: Bool,
-        crossorigin: CrossOrigin,
-        muted: Bool,
-        preload: Preload,
-        src: Uri,
-        r#loop: Bool,
-    };
-
-    /// Build a
-    /// [`<img>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img)
-    /// element.
-    img {
-        alt: String,
-        crossorigin: CrossOrigin,
-        decoding: ImageDecoding,
-        height: usize,
-        ismap: Bool,
-        src: Uri,
-        srcset: String, // FIXME this is much more complicated
-        usemap: String, // FIXME should be a fragment starting with '#'
-        width: usize,
-        referrerpolicy: String,
-        // sizes: SpacedList<String>, // FIXME it's not really just a string
-    };
-
-    /// Build a
-    /// [`<map>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/map)
-    /// element.
-    map {
-        name: Id,
-    };
-
-    /// Build a
-    /// [`<track>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track)
-    /// element.
-    track {
-        default: Bool,
-        kind: VideoKind,
-        label: String,
-        src: Uri,
-        srclang: LanguageTag,
-    };
-
-    /// Build a
-    /// [`<video>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video)
-    /// element.
-    video {
-        autoplay: Bool,
-        controls: Bool,
-        crossorigin: CrossOrigin,
-        height: usize,
-        r#loop: Bool,
-        muted: Bool,
-        preload: Preload,
-        playsinline: Bool,
-        poster: Uri,
-        src: Uri,
-        width: usize,
-    };
-
-
-    // Embedded content
-
-    /// Build a
-    /// [`<embed>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed)
-    /// element.
-    embed {
-        height: usize,
-        src: Uri,
-        r#type: Mime,
-        width: usize,
-    };
-
-    /// Build a
-    /// [`<iframe>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe)
-    /// element.
-    iframe {
-        allow: FeaturePolicy,
-        allowfullscreen: Bool,
-        allowpaymentrequest: Bool,
-        height: usize,
-        name: Id,
-        referrerpolicy: ReferrerPolicy,
-        src: Uri,
-        srcdoc: Uri,
-        width: usize,
-
-        marginWidth: String,
-        align: String,
-        longdesc: String,
-
-        scrolling: String,
-        marginHeight: String,
-        frameBorder: String,
-        // sandbox: SpacedSet<Sandbox>,
-    };
-
-    /// Build a
-    /// [`<object>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object)
-    /// element.
-    object {
-        data: Uri,
-        form: Id,
-        height: usize,
-        name: Id,
-        r#type: Mime,
-        typemustmatch: Bool,
-        usemap: String, // TODO should be a fragment starting with '#'
-        width: usize,
-    };
-
-    /// Build a
-    /// [`<param>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/param)
-    /// element.
-    param {
-        name: String,
-        value: String,
-    };
-
-    /// Build a
-    /// [`<picture>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture)
-    /// element.
-    picture {};
-
-    /// Build a
-    /// [`<source>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source)
-    /// element.
-    source {
-        src: Uri,
-        r#type: Mime,
-    };
-
-
-    // Scripting
-
-    /// Build a
-    /// [`<canvas>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas)
-    /// element.
-    canvas {
-        height: usize,
-        width: usize,
-    };
-
-    /// Build a
-    /// [`<noscript>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/noscript)
-    /// element.
-    noscript {};
-
-    /// Build a
-    /// [`<script>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script)
-    /// element.
-    script {
-        crossorigin: CrossOrigin,
-        defer: Bool,
-        integrity: Integrity,
-        nomodule: Bool,
-        nonce: Nonce,
-        src: Uri,
-        text: String,
-        r#async: Bool,
-        r#type: String, // TODO could be an enum
-    };
-
-
-    // Demarcating edits
-
-    /// Build a
-    /// [`<del>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/del)
-    /// element.
-    del {
-        cite: Uri,
-        datetime: Datetime,
-    };
-
-    /// Build a
-    /// [`<ins>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ins)
-    /// element.
-    ins {
-        cite: Uri,
-        datetime: Datetime,
-    };
-
-
-    // Table content
-
-    /// Build a
-    /// [`<caption>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/caption)
-    /// element.
-    caption {};
-
-    /// Build a
-    /// [`<col>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/col)
-    /// element.
-    col {
-        span: usize,
-    };
-
-    /// Build a
-    /// [`<colgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/colgroup)
-    /// element.
-    colgroup {
-        span: usize,
-    };
-
-    /// Build a
-    /// [`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table)
-    /// element.
-    table {};
-
-    /// Build a
-    /// [`<tbody>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tbody)
-    /// element.
-    tbody {};
-
-    /// Build a
-    /// [`<td>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td)
-    /// element.
-    td {
-        colspan: usize,
-        rowspan: usize,
-        // headers: SpacedSet<Id>,
-    };
-
-    /// Build a
-    /// [`<tfoot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tfoot)
-    /// element.
-    tfoot {};
-
-    /// Build a
-    /// [`<th>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th)
-    /// element.
-    th {
-        abbr: String,
-        colspan: usize,
-        rowspan: usize,
-        scope: TableHeaderScope,
-        // headers: SpacedSet<Id>,
-    };
-
-    /// Build a
-    /// [`<thead>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/thead)
-    /// element.
-    thead {};
-
-    /// Build a
-    /// [`<tr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tr)
-    /// element.
-    tr {};
-
-
-    // Forms
-
-    /// Build a
-    /// [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button)
-    /// element.
-    button {
-        autofocus: Bool,
-        disabled: Bool,
-        form: Id,
-        formaction: Uri,
-        formenctype: FormEncodingType,
-        formmethod: FormMethod,
-        formnovalidate: Bool,
-        formtarget: Target,
-        name: Id,
-        r#type: ButtonType,
-        value: String,
-    };
-
-    /// Build a
-    /// [`<datalist>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist)
-    /// element.
-    datalist {};
-
-    /// Build a
-    /// [`<fieldset>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset)
-    /// element.
-    fieldset {};
-
-    /// Build a
-    /// [`<form>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form)
-    /// element.
-    form {
-        // accept-charset: SpacedList<CharacterEncoding>,
-        action: Uri,
-        autocomplete: OnOff,
-        enctype: FormEncodingType,
-        method: FormMethod,
-        name: Id,
-        novalidate: Bool,
-        target: Target,
-    };
-
-    /// Build a
-    /// [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input)
-    /// element.
-    input {
-        accept: String,
-        alt: String,
-        autocomplete: String,
-        autofocus: Bool,
-        capture: String,
-        checked: Bool,
-        disabled: Bool,
-        form: Id,
-        formaction: Uri,
-        formenctype: FormEncodingType,
-        formmethod: FormDialogMethod,
-        formnovalidate: Bool,
-        formtarget: Target,
-        height: isize,
-        list: Id,
-        max: String,
-        maxlength: usize,
-        min: String,
-        minlength: usize,
-        multiple: Bool,
-        name: Id,
-        pattern: String,
-        placeholder: String,
-        readonly: Bool,
-        required: Bool,
-        size: usize,
-        spellcheck: Bool,
-        src: Uri,
-        step: String,
-        tabindex: usize,
-
-        width: isize,
-
-        // Manual implementations below...
-        // r#type: InputType,
-        // value: String,
-    };
-
-    /// Build a
-    /// [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label)
-    /// element.
-    label {
-        form: Id,
-        // r#for: Id,
-    };
-
-    /// Build a
-    /// [`<legend>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/legend)
-    /// element.
-    legend {};
-
-    /// Build a
-    /// [`<meter>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meter)
-    /// element.
-    meter {
-        value: isize,
-        min: isize,
-        max: isize,
-        low: isize,
-        high: isize,
-        optimum: isize,
-        form: Id,
-    };
-
-    /// Build a
-    /// [`<optgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup)
-    /// element.
-    optgroup {
-        disabled: Bool,
-        label: String,
-    };
-
-    /// Build a
-    /// [`<option>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option)
-    /// element.
-    option {
-        disabled: Bool,
-        label: String,
-
-
-        value: String,
-
-        // defined below
-        // selected: Bool,
-    };
-
-    /// Build a
-    /// [`<output>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/output)
-    /// element.
-    output {
-        form: Id,
-        name: Id,
-        // r#for: SpacedSet<Id>,
-    };
-
-    /// Build a
-    /// [`<progress>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/progress)
-    /// element.
-    progress {
-        max: f64,
-        value: f64,
-    };
-
-    /// Build a
-    /// [`<select>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select)
-    /// element.
-    select {
-        // defined below
-        // value: String,
-        autocomplete: String,
-        autofocus: Bool,
-        disabled: Bool,
-        form: Id,
-        multiple: Bool,
-        name: Id,
-        required: Bool,
-        size: usize,
-    };
-
-    /// Build a
-    /// [`<textarea>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea)
-    /// element.
-    textarea {
-        autocomplete: OnOff,
-        autofocus: Bool,
-        cols: usize,
-        disabled: Bool,
-        form: Id,
-        maxlength: usize,
-        minlength: usize,
-        name: Id,
-        placeholder: String,
-        readonly: Bool,
-        required: Bool,
-        rows: usize,
-        spellcheck: BoolOrDefault,
-        wrap: Wrap,
-    };
-
-
-    // Interactive elements
-
-    /// Build a
-    /// [`<details>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details)
-    /// element.
-    details {
-        open: Bool,
-    };
-
-
-
-    /// Build a
-    /// [`<summary>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary)
-    /// element.
-    summary {};
-
-    // Web components
-
-    /// Build a
-    /// [`<slot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot)
-    /// element.
-    slot {};
-
-    /// Build a
-    /// [`<template>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template)
-    /// element.
-    template {};
-}
-
-impl input {
-    /// The type of input
-    ///
-    /// Here are the different input types you can use in HTML:
-    ///
-    /// - `button`
-    /// - `checkbox`
-    /// - `color`
-    /// - `date`
-    /// - `datetime-local`
-    /// - `email`
-    /// - `file`
-    /// - `hidden`
-    /// - `image`
-    /// - `month`
-    /// - `number`
-    /// - `password`
-    /// - `radio`
-    /// - `range`
-    /// - `reset`
-    /// - `search`
-    /// - `submit`
-    /// - `tel`
-    /// - `text`
-    /// - `time`
-    /// - `url`
-    /// - `week`    
-    pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-        cx.attr("type", val, None, false)
-    }
-
-    pub fn value<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-        cx.attr("value", val, None, true)
-    }
-}
-
-/*
-volatile attributes
-*/
-
-impl select {
-    pub fn value<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-        cx.attr("value", val, None, true)
-    }
-}
-
-impl option {
-    pub fn selected<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-        cx.attr("selected", val, None, true)
-    }
-}
-
-impl textarea {
-    pub fn value<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-        cx.attr("value", val, None, true)
-    }
-}
-impl label {
-    pub fn r#for<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-        cx.attr("for", val, None, false)
-    }
-}
-
-pub trait SvgAttributes {
-    aria_trait_methods! {
-        accent_height: "accent-height",
-        accumulate: "accumulate",
-        additive: "additive",
-        alignment_baseline: "alignment-baseline",
-        alphabetic: "alphabetic",
-        amplitude: "amplitude",
-        arabic_form: "arabic-form",
-        ascent: "ascent",
-        attributeName: "attributeName",
-        attributeType: "attributeType",
-        azimuth: "azimuth",
-        baseFrequency: "baseFrequency",
-        baseline_shift: "baseline-shift",
-        baseProfile: "baseProfile",
-        bbox: "bbox",
-        begin: "begin",
-        bias: "bias",
-        by: "by",
-        calcMode: "calcMode",
-        cap_height: "cap-height",
-        class: "class",
-        clip: "clip",
-        clipPathUnits: "clipPathUnits",
-        clip_path: "clip-path",
-        clip_rule: "clip-rule",
-        color: "color",
-        color_interpolation: "color-interpolation",
-        color_interpolation_filters: "color-interpolation-filters",
-        color_profile: "color-profile",
-        color_rendering: "color-rendering",
-        contentScriptType: "contentScriptType",
-        contentStyleType: "contentStyleType",
-        crossorigin: "crossorigin",
-        cursor: "cursor",
-        cx: "cx",
-        cy: "cy",
-        d: "d",
-        decelerate: "decelerate",
-        descent: "descent",
-        diffuseConstant: "diffuseConstant",
-        direction: "direction",
-        display: "display",
-        divisor: "divisor",
-        dominant_baseline: "dominant-baseline",
-        dur: "dur",
-        dx: "dx",
-        dy: "dy",
-        edgeMode: "edgeMode",
-        elevation: "elevation",
-        enable_background: "enable-background",
-        end: "end",
-        exponent: "exponent",
-        fill: "fill",
-        fill_opacity: "fill-opacity",
-        fill_rule: "fill-rule",
-        filter: "filter",
-        filterRes: "filterRes",
-        filterUnits: "filterUnits",
-        flood_color: "flood-color",
-        flood_opacity: "flood-opacity",
-        font_family: "font-family",
-        font_size: "font-size",
-        font_size_adjust: "font-size-adjust",
-        font_stretch: "font-stretch",
-        font_style: "font-style",
-        font_variant: "font-variant",
-        font_weight: "font-weight",
-        format: "format",
-        from: "from",
-        fr: "fr",
-        fx: "fx",
-        fy: "fy",
-        g1: "g1",
-        g2: "g2",
-        glyph_name: "glyph-name",
-        glyph_orientation_horizontal: "glyph-orientation-horizontal",
-        glyph_orientation_vertical: "glyph-orientation-vertical",
-        glyphRef: "glyphRef",
-        gradientTransform: "gradientTransform",
-        gradientUnits: "gradientUnits",
-        hanging: "hanging",
-        height: "height",
-        href: "href",
-        hreflang: "hreflang",
-        horiz_adv_x: "horiz-adv-x",
-        horiz_origin_x: "horiz-origin-x",
-        id: "id",
-        ideographic: "ideographic",
-        image_rendering: "image-rendering",
-        _in: "_in",
-        in2: "in2",
-        intercept: "intercept",
-        k: "k",
-        k1: "k1",
-        k2: "k2",
-        k3: "k3",
-        k4: "k4",
-        kernelMatrix: "kernelMatrix",
-        kernelUnitLength: "kernelUnitLength",
-        kerning: "kerning",
-        keyPoints: "keyPoints",
-        keySplines: "keySplines",
-        keyTimes: "keyTimes",
-        lang: "lang",
-        lengthAdjust: "lengthAdjust",
-        letter_spacing: "letter-spacing",
-        lighting_color: "lighting-color",
-        limitingConeAngle: "limitingConeAngle",
-        local: "local",
-        marker_end: "marker-end",
-        marker_mid: "marker-mid",
-        marker_start: "marker_start",
-        markerHeight: "markerHeight",
-        markerUnits: "markerUnits",
-        markerWidth: "markerWidth",
-        mask: "mask",
-        maskContentUnits: "maskContentUnits",
-        maskUnits: "maskUnits",
-        mathematical: "mathematical",
-        max: "max",
-        media: "media",
-        method: "method",
-        min: "min",
-        mode: "mode",
-        name: "name",
-        numOctaves: "numOctaves",
-        offset: "offset",
-        opacity: "opacity",
-        operator: "operator",
-        order: "order",
-        orient: "orient",
-        orientation: "orientation",
-        origin: "origin",
-        overflow: "overflow",
-        overline_position: "overline-position",
-        overline_thickness: "overline-thickness",
-        panose_1: "panose-1",
-        paint_order: "paint-order",
-        path: "path",
-        pathLength: "pathLength",
-        patternContentUnits: "patternContentUnits",
-        patternTransform: "patternTransform",
-        patternUnits: "patternUnits",
-        ping: "ping",
-        pointer_events: "pointer-events",
-        points: "points",
-        pointsAtX: "pointsAtX",
-        pointsAtY: "pointsAtY",
-        pointsAtZ: "pointsAtZ",
-        preserveAlpha: "preserveAlpha",
-        preserveAspectRatio: "preserveAspectRatio",
-        primitiveUnits: "primitiveUnits",
-        r: "r",
-        radius: "radius",
-        referrerPolicy: "referrerPolicy",
-        refX: "refX",
-        refY: "refY",
-        rel: "rel",
-        rendering_intent: "rendering-intent",
-        repeatCount: "repeatCount",
-        repeatDur: "repeatDur",
-        requiredExtensions: "requiredExtensions",
-        requiredFeatures: "requiredFeatures",
-        restart: "restart",
-        result: "result",
-        rotate: "rotate",
-        rx: "rx",
-        ry: "ry",
-        scale: "scale",
-        seed: "seed",
-        shape_rendering: "shape-rendering",
-        slope: "slope",
-        spacing: "spacing",
-        specularConstant: "specularConstant",
-        specularExponent: "specularExponent",
-        speed: "speed",
-        spreadMethod: "spreadMethod",
-        startOffset: "startOffset",
-        stdDeviation: "stdDeviation",
-        stemh: "stemh",
-        stemv: "stemv",
-        stitchTiles: "stitchTiles",
-        stop_color: "stop_color",
-        stop_opacity: "stop_opacity",
-        strikethrough_position: "strikethrough-position",
-        strikethrough_thickness: "strikethrough-thickness",
-        string: "string",
-        stroke: "stroke",
-        stroke_dasharray: "stroke-dasharray",
-        stroke_dashoffset: "stroke-dashoffset",
-        stroke_linecap: "stroke-linecap",
-        stroke_linejoin: "stroke-linejoin",
-        stroke_miterlimit: "stroke-miterlimit",
-        stroke_opacity: "stroke-opacity",
-        stroke_width: "stroke-width",
-        style: "style",
-        surfaceScale: "surfaceScale",
-        systemLanguage: "systemLanguage",
-        tabindex: "tabindex",
-        tableValues: "tableValues",
-        target: "target",
-        targetX: "targetX",
-        targetY: "targetY",
-        text_anchor: "text-anchor",
-        text_decoration: "text-decoration",
-        text_rendering: "text-rendering",
-        textLength: "textLength",
-        to: "to",
-        transform: "transform",
-        transform_origin: "transform-origin",
-        r#type: "_type",
-        u1: "u1",
-        u2: "u2",
-        underline_position: "underline-position",
-        underline_thickness: "underline-thickness",
-        unicode: "unicode",
-        unicode_bidi: "unicode-bidi",
-        unicode_range: "unicode-range",
-        units_per_em: "units-per-em",
-        v_alphabetic: "v-alphabetic",
-        v_hanging: "v-hanging",
-        v_ideographic: "v-ideographic",
-        v_mathematical: "v-mathematical",
-        values: "values",
-        vector_effect: "vector-effect",
-        version: "version",
-        vert_adv_y: "vert-adv-y",
-        vert_origin_x: "vert-origin-x",
-        vert_origin_y: "vert-origin-y",
-        view_box: "viewBox",
-        view_target: "viewTarget",
-        visibility: "visibility",
-        width: "width",
-        widths: "widths",
-        word_spacing: "word-spacing",
-        writing_mode: "writing-mode",
-        x: "x",
-        x_height: "x-height",
-        x1: "x1",
-        x2: "x2",
-        xmlns: "xmlns",
-        x_channel_selector: "xChannelSelector",
-        y: "y",
-        y1: "y1",
-        y2: "y2",
-        y_channel_selector: "yChannelSelector",
-        z: "z",
-        zoomAndPan: "zoomAndPan",
-    }
-}
-
-builder_constructors! {
-    // SVG components
-    /// Build a
-    /// [`<svg>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg)
-    /// element.
-    svg <> "http://www.w3.org/2000/svg" { };
-
-    /// Build a
-    /// [`<path>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path)
-    /// element.
-    path <> "http://www.w3.org/2000/svg" {
-
-    };
-
-    /// Build a
-    /// [`<circle>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/circle)
-    /// element.
-    circle <>  "http://www.w3.org/2000/svg" {
-
-    };
-
-    /// Build a
-    /// [`<ellipse>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/ellipse)
-    /// element.
-    ellipse <> "http://www.w3.org/2000/svg" {
-
-    };
-
-    /// Build a
-    /// [`<line>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/line)
-    /// element.
-    line <> "http://www.w3.org/2000/svg" {
-
-    };
-
-    /// Build a
-    /// [`<polygon>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polygon)
-    /// element.
-    polygon <> "http://www.w3.org/2000/svg" {
-
-    };
-
-    /// Build a
-    /// [`<polyline>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polyline)
-    /// element.
-    polyline <> "http://www.w3.org/2000/svg" {
-
-    };
-
-    /// Build a
-    /// [`<rect>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/rect)
-    /// element.
-    rect <> "http://www.w3.org/2000/svg" {
-
-    };
-
-    /// Build a
-    /// [`<image>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image)
-    /// element.
-    image <> "http://www.w3.org/2000/svg" {
-
-    };
-
-}
-
-/*
-Ideal format:
-    /// Build a
-    /// [`<h1>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h1)
-    /// element.
-    ///
-    /// # About
-    /// - The HTML `<h1>` element is found within the `<body>` tag.
-    /// - Headings can range from `<h1>` to `<h6>`.
-    /// - The most important heading is `<h1>` and the least important heading is `<h6>`.
-    /// - The `<h1>` heading is the first heading in the document.
-    /// - The `<h1>` heading is usually a large bolded font.
-    ///
-    /// # Usage
-    ///
-    /// ```
-    /// html!(<h1> A header element </h1>)
-    /// rsx!(h1 { "A header element" })
-    /// LazyNodes::new(|f| f.el(h1).children([f.text("A header element")]).finish())
-    /// ```
+mod elements;
+mod events;
+mod global_attributes;
 
-Full List:
----------
-base
-head
-link
-meta
-style
-title
-body
-address
-article
-aside
-footer
-header
-h1
-h1
-h2
-h2
-h3
-h4
-h5
-h6
-main
-nav
-section
-blockquote
-dd
-div
-dl
-dt
-figcaption
-figure
-hr
-li
-ol
-p
-pre
-ul
-a
-abbr
-b
-bdi
-bdo
-br
-cite
-code
-data
-dfn
-em
-i
-kbd
-mark
-q
-rp
-rt
-ruby
-s
-samp
-small
-span
-strong
-sub
-sup
-time
-u
-var
-wbr
-area
-audio
-img
-map
-track
-video
-embed
-iframe
-object
-param
-picture
-source
-canvas
-noscript
-script
-del
-ins
-caption
-col
-colgroup
-table
-tbody
-td
-tfoot
-th
-thead
-tr
-button
-datalist
-fieldset
-form
-input
-label
-legend
-meter
-optgroup
-option
-output
-progress
-select
-textarea
-details
-summary
-slot
-template
-*/
+pub use elements::*;
+pub use events::*;
+pub use global_attributes::*;

+ 5 - 8
src/lib.rs

@@ -170,8 +170,11 @@
 #[cfg(feature = "core")]
 pub use dioxus_core as core;
 
-#[cfg(feature = "core")]
-pub use dioxus_core::events;
+#[cfg(feature = "hooks")]
+pub use dioxus_hooks as hooks;
+
+#[cfg(feature = "ssr")]
+pub use dioxus_ssr as ssr;
 
 #[cfg(feature = "web")]
 pub use dioxus_web as web;
@@ -179,12 +182,6 @@ pub use dioxus_web as web;
 #[cfg(feature = "mobile")]
 pub use dioxus_mobile as mobile;
 
-#[cfg(feature = "ssr")]
-pub use dioxus_ssr as ssr;
-
-#[cfg(feature = "hooks")]
-pub use dioxus_hooks as hooks;
-
 #[cfg(feature = "desktop")]
 pub use dioxus_desktop as desktop;