Browse Source

Merge pull request #38 from DioxusLabs/jk/stack_dst

wip: docs and router
Jonathan Kelley 3 năm trước cách đây
mục cha
commit
016801299d
95 tập tin đã thay đổi với 2450 bổ sung8754 xóa
  1. 4 0
      Cargo.toml
  2. 38 204
      README.md
  3. 13 10
      docs/guide/src/README.md
  4. 11 10
      docs/guide/src/concepts/00-index.md
  5. 2 2
      docs/guide/src/concepts/10-concurrent-mode.md
  6. 3 3
      docs/guide/src/concepts/11-arena-memo.md
  7. 3 3
      docs/guide/src/concepts/12-signals.md
  8. 5 5
      docs/guide/src/concepts/components.md
  9. 7 7
      docs/guide/src/concepts/conditional_rendering.md
  10. 7 7
      docs/guide/src/concepts/interactivity.md
  11. 1 1
      docs/guide/src/concepts/lists.md
  12. 53 0
      docs/guide/src/concepts/suspense.md
  13. 1 1
      docs/guide/src/concepts/vnodes.md
  14. 15 25
      docs/guide/src/hello_world.md
  15. 10 0
      docs/guide/src/setup.md
  16. 145 0
      examples/README.md
  17. 1 1
      examples/async.rs
  18. 4 8
      examples/borrowed.rs
  19. 78 75
      examples/calculator.rs
  20. 1 1
      examples/core/alternative.rs
  21. 1 1
      examples/core/syntax.rs
  22. 1 1
      examples/crm.rs
  23. 1 1
      examples/desktop/crm.rs
  24. 1 1
      examples/desktop/todomvc.rs
  25. 87 93
      examples/framework_benchmark.rs
  26. 1 1
      examples/pattern_reducer.rs
  27. 2 2
      examples/web/basic.rs
  28. 1 1
      examples/web/crm2.rs
  29. 32 0
      notes/FAQ.md
  30. 1 1
      notes/Parity.md
  31. 5 5
      notes/SOLVEDPROBLEMS.md
  32. 1 1
      packages/core-macro/src/lib.rs
  33. 91 125
      packages/core-macro/src/router.rs
  34. 5 7
      packages/core-macro/src/rsx/body.rs
  35. 1 1
      packages/core-macro/src/rsxtemplate.rs
  36. 0 264
      packages/core-macro/styles/calc.rs
  37. 0 1150
      packages/core-macro/styles/codegen.rs
  38. 0 854
      packages/core-macro/styles/color.rs
  39. 0 2044
      packages/core-macro/styles/mod.rs
  40. 0 502
      packages/core-macro/styles/string/lexer.rs
  41. 0 1
      packages/core-macro/styles/string/mod.rs
  42. 0 2411
      packages/core-macro/styles/syn_parse.rs
  43. 9 6
      packages/core/Cargo.toml
  44. 20 0
      packages/core/README.md
  45. 5 0
      packages/core/architecture.md
  46. 7 12
      packages/core/benches/jsframework.rs
  47. 120 0
      packages/core/examples/rows.rs
  48. 412 0
      packages/core/flamegraph.svg
  49. 2 2
      packages/core/src/component.rs
  50. 108 88
      packages/core/src/diff.rs
  51. 11 3
      packages/core/src/lazynodes.rs
  52. 2 2
      packages/core/src/lib.rs
  53. 3 0
      packages/core/src/mutations.rs
  54. 58 91
      packages/core/src/nodes.rs
  55. 50 173
      packages/core/src/scope.rs
  56. 20 22
      packages/core/src/scopearena.rs
  57. 14 21
      packages/core/src/virtual_dom.rs
  58. 1 1
      packages/core/tests/diffing.rs
  59. 8 0
      packages/coroutines/Cargo.toml
  60. 8 0
      packages/coroutines/src/lib.rs
  61. 1 1
      packages/desktop/Cargo.toml
  62. 7 7
      packages/desktop/README.md
  63. 3 1
      packages/desktop/examples/async.rs
  64. 16 12
      packages/desktop/src/cfg.rs
  65. 0 31
      packages/desktop/src/dom.rs
  66. 0 1
      packages/desktop/src/err.rs
  67. 3 2
      packages/desktop/src/escape.rs
  68. 4 4
      packages/desktop/src/events.rs
  69. 2 3
      packages/desktop/src/index.html
  70. 1 1
      packages/desktop/src/index.js
  71. 180 195
      packages/desktop/src/lib.rs
  72. 2 1
      packages/hooks/Cargo.toml
  73. 1 1
      packages/hooks/README.md
  74. 3 0
      packages/hooks/src/lib.rs
  75. 1 1
      packages/hooks/src/use_shared_state.rs
  76. 1 1
      packages/hooks/src/usecollection.rs
  77. 75 0
      packages/hooks/src/usecoroutine.rs
  78. 5 0
      packages/hooks/src/useref.rs
  79. 15 8
      packages/hooks/src/usestate.rs
  80. 18 3
      packages/html/src/elements.rs
  81. 11 2
      packages/router/Cargo.toml
  82. 40 0
      packages/router/README.md
  83. 39 0
      packages/router/examples/simple.rs
  84. 77 39
      packages/router/src/lib.rs
  85. 0 25
      packages/router/src/utils.rs
  86. 1 1
      packages/ssr/README.md
  87. 3 6
      packages/ssr/src/lib.rs
  88. 16 2
      packages/web/Cargo.toml
  89. 243 0
      packages/web/examples/js_bench.rs
  90. 46 0
      packages/web/examples/simple.rs
  91. 5 0
      packages/web/src/cache.rs
  92. 1 1
      packages/web/src/cfg.rs
  93. 45 23
      packages/web/src/dom.rs
  94. 9 6
      packages/web/src/lib.rs
  95. 85 125
      src/lib.rs

+ 4 - 0
Cargo.toml

@@ -91,3 +91,7 @@ path = "./examples/webview.rs"
 required-features = ["desktop"]
 name = "tailwind"
 path = "./examples/tailwind.rs"
+
+
+# [patch.crates-io]
+# wasm-bindgen = { path = "../Tinkering/wasm-bindgen" }

+ 38 - 204
README.md

@@ -49,7 +49,7 @@
 Dioxus is a portable, performant, and ergonomic framework for building cross-platform user experiences in Rust.
 
 ```rust
-fn App(cx: Context, props: &()) -> Element {
+fn App(cx: Scope, props: &()) -> Element {
     let mut count = use_state(cx, || 0);
 
     cx.render(rsx!(
@@ -65,12 +65,11 @@ Dioxus can be used to deliver webapps, desktop apps, static sites, liveview apps
 If you know React, then you already know Dioxus.
 
 ### Unique features:
-- The most ergonomic and powerful state management of any Rust UI toolkit.
 - Desktop apps running natively (no Electron!) in less than 10 lines of code.
-- Starting a new app takes zero templates or special tools - get a new app running in just seconds.
-- Incredible inline documentation. Supports hover and guides for all HTML elements, listeners, and events.
-- Custom bump-allocator backing for all components. Nearly 0 allocations for steady-state components.
-- Multithreaded asynchronous coroutine scheduler for powerful async code.
+- Incredibly ergonomic and powerful state management.
+- Incredible inline documentation - hover and guides for all HTML elements, listeners, and events.
+- Extremely memory efficient - 0 global allocations for steady-state components.
+- Multithreaded asynchronous coroutine scheduler for first-class async support.
 - And more! Read the full release post here.
 
 ## Get Started with...
@@ -95,24 +94,42 @@ If you know React, then you already know Dioxus.
 
 See the awesome-dioxus page for a curated list of content in the Dioxus Ecosystem.
 
-## Why?
+## Why Dioxus and why Rust?
 
-TypeScript is a great addition to JavaScript, but comes with a lot of tweaking flags, a slight performance hit, and an uneven ecosystem where some of the most important packages are not properly typed. TypeScript provides a lot of great benefits to JS projects, but comes with its own "tax" that can slow down dev teams. Rust can be seen as a step up from TypeScript, supporting:
+TypeScript is a fantastic addition to JavaScript, but it's still fundamentally JavaScript. TS code runs slightly slower, has tons of configuration options, and not every package is properly typed. 
 
-- static types for _all_ libraries
-- advanced pattern matching
-- immutability by default
-- clean, composable iterators
-- a good module system
-- integrated documentation
-- inline built-in unit/integration testing
-- best-in-class error handling
-- simple and fast build system (compared to WebPack!)
-- powerful standard library (no need for lodash or underscore)
-- include_str! for integrating html/css/svg templates directly
-- various macros (`html!`, `rsx!`) for fast template iteration
+In contrast, Dioxus is written in Rust - which is almost like "TypeScript on steroids". 
 
-And much more. Dioxus makes Rust apps just as fast to write as React apps, but affords more robustness, giving your frontend team greater confidence in making big changes in shorter time. Dioxus also works on the server, on the web, on mobile, on desktop - and it runs completely natively so performance is never an issue.
+By using Rust, we gain:
+
+- Static types for *every* library
+- Immutability by default
+- A simple and intuitive module system
+- Integrated documentation (`go to source` _actually goes to source_)
+- Advanced pattern matching
+- Clean, efficient, composable iterators
+- Inline built-in unit/integration testing
+- Best-in-class error handling
+- Powerful and sane standard library
+- Flexible macro system
+- Access to `crates.io`
+
+Specifically, Dioxus provides us many other assurances:
+
+- Proper use of immutable datastructures
+- Guaranteed error handling (so you can sleep easy at night not worrying about `cannot read property of undefined`) 
+- Native performance on mobile
+- Direct access to system IO
+
+And much more. Dioxus makes Rust apps just as fast to write as React apps, but affords more robustness, giving your frontend team greater confidence in making big changes in shorter time. 
+
+### Why NOT Dioxus?
+You shouldn't use Dioxus if:
+
+- You don't like the React Hooks approach to frontend
+- You need a no-std renderer
+- You want to support browsers where Wasm or asm.js are not supported.
+- You need a Send+Sync UI solution (Dioxus is not currently ThreadSafe)
 
 # Parity with React
 
@@ -158,189 +175,6 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an
 - 👀 = not yet implemented or being worked on
 - ❓ = not sure if will or can implement
 
-## FAQ:
-
-### Aren't VDOMs just pure overhead? Why not something like Solid or Svelte?
-Remember: Dioxus is a library - not a compiler like Svelte. Plus, the inner VirtualDOM allows Dioxus to easily port into different runtimes, support SSR, and run remotely in the cloud. VDOMs tend to more ergonomic to work with and feel roughly like natural Rust code. The overhead of Dioxus is **extraordinarily** minimal... sure, there may be some overhead but on an order of magnitude lower than the time required to actually update the page.
-
-
-### Isn't the overhead for interacting with the DOM from Wasm too much?
-The overhead layer between Wasm and JS APIs is extremely poorly understood. Rust web benchmarks typically suffer from differences in how Rust and JS cache strings. In Dioxus, we solve most of these issues and our JS Framework Benchmark actually beats the Wasm Bindgen benchmark in many cases. Compared to a "pure vanilla JS" solution, Dioxus adds less than 5% of overhead and takes advantage of batched DOM manipulation.
-
-### Aren't Wasm binaries too huge to deploy in production?
-Wasm binary sizes are another poorly understood characteristic of Rust web apps. 50kb of Wasm and 50kb of JS are _not_ made equally. In JS, the code must be downloaded _first_ and _then_ JIT-ted. Just-in-time compiling 50kb of JavaScript takes a while which is why 50kb of JavaScript sounds like a lot! However, with Wasm, the code is downloaded and JIT-ted _simultaneously_ through the magic of streaming compilation. By the time the 50kb of Rust is finished downloading, it is already ready to go. Again, Dioxus beats out many benchmarks with time-to-interactivity.
-
-For reference, Dioxus `hello-world` clocks in at around 70kb gzip or 60kb brotli, and Dioxus supports SSR.
-
-### Why hooks? Why not MVC, classes, traits, messages, etc?
-There are plenty Rust Elm-like frameworks in the world - we were not interested in making another! Instead, we borrowed hooks from React. JS and Rust share many structural similarities, so if you're comfortable with React, then you'll be plenty comfortable with Dioxus.
-
-### Why a custom DSL? Why not just pure function calls?
-The `RSX` DSL is _barely_ a DSL. Rustaceans will find the DSL very similar to simply assembling nested structs, but without the syntactical overhead of "Default" everywhere or having to jump through hoops with the builder pattern. Between RSX, HTML, the Raw Factory API, and the NodeBuilder syntax, there's plenty of options to choose from.
-
-### What are the build times like? Why on earth would I choose Rust instead of JS/TS/Elm?
-Dioxus builds as roughly as fast as a complex WebPack-TypeScript site. Compile times will be slower than an equivalent TypeScript site, but not unbearably slow. The Wasm compiler backend for Rust is very fast. Iterating on small components is basically instant and larger apps takes a few seconds. In practice, the compiler guarantees of Rust balance out the rebuild times.
-
-### What about Yew/Seed/Sycamore/Dominator/Dodrio/Percy?
-- Yew and Seed use an Elm-like pattern and don't support SSR or any alternate rendering platforms
-- Sycamore and Dominator are more like SolidJS/Svelte, requiring no VDOM but has less naturally-Rusty state management
-- Percy isn't quite mature yet
-- Dodrio is the spiritual predecessor of Dioxus, but is currently an archived research project without the batteries of Dioxus
-
-### How do the mobile and desktop renderers work? Is it Electron?
-Currently, Dioxus uses your device's native WebView library to draw the page. None of your app code is actually running in the WebView thread, so you can access system resources instead of having to go through something like NodeJS. This means your app will use Safari on macOS/iOS, Edge (Chromium) on Windows, and whatever is the default Web Browser for Linux and Android. Because your code is compiled and running natively, performance is not a problem. You will have to use the various "Escape Hatches" to use browser-native APIs (like WebGL) and work around visual differences in how Safari and Chrome render the page.
-
-In the future, we are interested in using Webrenderer to provide a fully native renderer without having to go through the system WebView library. In practice, Dioxus mobile and desktop are great for CRUD-style apps, but the ergonomic cross-platform APIs (GPS, Camera, etc) are not there yet.
-
-### Why NOT Dioxus?
-You shouldn't use Dioxus if:
-- You don't like the React Hooks approach to frontend
-- You need a no-std renderer
-- You want to support browsers where Wasm or asm.js are not supported.
-
-
-## Show me some examples!
-
-In our collection of examples, guides, and tutorials, we have:
-- The book (an introductory course)
-- The guide (an in-depth analysis of everything in Dioxus)
-- The reference (a collection of examples with heavy documentation)
-- The general examples
-- The platform-specific examples (web, ssr, desktop, mobile, server)
-  
-Here's what a few common tasks look like in Dioxus:
-
-Nested components with children and internal state:
-```rust
-fn App(cx: Context, props: &()) -> Element {
-  cx.render(rsx!( Toggle { "Toggle me" } ))
-}
-
-#[derive(PartialEq, Props)]
-struct ToggleProps { children: Element }
-
-fn Toggle(cx: Context, props: &ToggleProps) -> Element {
-  let mut toggled = use_state(cx, || false);
-  cx.render(rsx!{
-    div {
-      {&props.children}
-      button { onclick: move |_| toggled.set(true),
-        {toggled.and_then(|| "On").or_else(|| "Off")}
-      }
-    }
-  })
-}
-```
-
-Controlled inputs:
-```rust
-fn App(cx: Context, props: &()) -> Element {
-  let value = use_state(cx, String::new);
-  cx.render(rsx!( 
-    input {
-      "type": "text",
-      value: "{value}",
-      oninput: move |evt| value.set(evt.value.clone())
-    }
-  ))
-}
-```
-
-Lists and Conditional rendering:
-```rust
-fn App(cx: Context, props: &()) -> Element {
-  let list = (0..10).map(|i| {
-    rsx!(li { key: "{i}", "Value: {i}" })
-  });
-  
-  let title = match list.len() {
-    0 => rsx!("Not enough"),
-    _ => rsx!("Plenty!"),
-  };
-
-  if should_show {
-    cx.render(rsx!( 
-      {title}
-      ul { {list} } 
-    ))
-  } else {
-    None
-  }
-}
-```
-
-Tiny components:
-```rust
-static App: FC<()> = |cx, _| rsx!(cx, div {"hello world!"});
-```
-
-Borrowed prop contents:
-```rust
-fn App(cx: Context, props: &()) -> Element {
-  let name = use_state(cx, || String::from("example"));
-  rsx!(cx, Child { title: name.as_str() })
-}
-
-#[derive(Props)]
-struct ChildProps<'a> { title: &'a str }
-
-fn Child(cx: Context, props: &ChildProps) -> Element {
-  rsx!(cx, "Hello {props.title}")
-}
-```
-
-Global State
-```rust
-struct GlobalState { name: String }
-
-fn App(cx: Context, props: &()) -> Element {
-  use_provide_shared_state(cx, || GlobalState { name: String::from("Toby") })
-  rsx!(cx, Leaf {})
-}
-
-fn Leaf(cx: Context, props: &()) -> Element {
-  let state = use_consume_shared_state::<GlobalState>(cx)?;
-  rsx!(cx, "Hello {state.name}")
-}
-```
-
-Router (inspired by Yew-Router)
-```rust
-#[derive(PartialEq, Clone,  Hash, Eq, Routable)]
-enum Route {
-  #[at("/")]
-  Home,
-  #[at("/post/{id}")]
-  Post(id)
-}
-
-fn App(cx: Context, props: &()) -> Element {
-  let route = use_router(cx, Route::parse);
-  cx.render(rsx!(div {
-    {match route {
-      Route::Home => rsx!( Home {} ),
-      Route::Post(id) => rsx!( Post { id: id })
-    }}
-  }))  
-}
-```
-
-Suspense 
-```rust
-fn App(cx: Context, props: &()) -> Element {
-  let doggo = use_suspense(cx,
-    || async { reqwest::get("https://dog.ceo/api/breeds/image/random").await.unwrap().json::<Response>().await.unwrap() },
-    |response| cx.render(rsx!( img { src: "{response.message}" }))
-  );
-  
-  cx.render(rsx!{
-    div {
-      "One doggo coming right up:"
-      {doggo}
-    }
-  })
-}
-```
 
 ## License
 

+ 13 - 10
docs/guide/src/README.md

@@ -5,7 +5,7 @@
 **Dioxus** is a framework and ecosystem for building fast, scalable, and robust user interfaces with the Rust programming language. This guide will help you get up-and-running with Dioxus running on the Web, Desktop, Mobile, and more.
 
 ```rust
-fn App((cx, props): Component<()>) -> Element {
+fn App(cx: Context, props: &()) -> Element {
     let mut count = use_state(cx, || 0);
 
     cx.render(rsx!(
@@ -16,11 +16,20 @@ fn App((cx, props): Component<()>) -> Element {
 };
 ```
 
-The Dioxus API and patterns closely resemble React - if this guide is lacking in any general concept or an error message is confusing, we recommend substituting "React" for "Dioxus" in your web search terms. A major goal of Dioxus is to provide a familiar toolkit for UI in Rust, so we've chosen to follow in the footsteps of popular UI frameworks (React, Redux, etc) - if you know React, then you already know Dioxus. If you don't know either, this guide will still help you!
-
+In general, Dioxus and React share many functional similarities. If this guide is lacking in any general concept or an error message is confusing, React's documentation might be more helpful. We are dedicated to providing a *familiar* toolkit for UI in Rust, so we've chosen to follow in the footsteps of popular UI frameworks (React, Redux, etc). If you know React, then you already know Dioxus. If you don't know either, this guide will still help you!
 
 > This is introduction book! For advanced topics, check out the [Reference Guide]() instead.
 
+## Multiplatform
+
+Dioxus is a *portable* toolkit, meaning the Core implementation can run anywhere with no platform-dependent linking. Unlike many other Rust frontend toolkits, Dioxus is not intrinsically linked to Web-Sys. In fact, every element and event listener can be swapped out at compile time. By default, Dioxus ships with the `Html` feature enabled which can be disabled depending on your target renderer.
+
+Right now, we have several 1st-party renderers:
+- WebSys (for WASM)
+- Tao/Tokio (for Desktop apps)
+- Tao/Tokio (for Mobile apps)
+- SSR (for generating static markup)
+
 ### Web Support
 ---
 
@@ -39,12 +48,7 @@ Examples:
 
 ### SSR Support
 ---
-Dioxus supports server-side rendering! In a pinch, you can literally "debug" the VirtualDom:
-
-```rust
-let dom = VirtualDom::new(App);
-println!("{:?}, dom");
-```
+Dioxus supports server-side rendering! 
 
 For rendering statically to an `.html` file or from a WebServer, then you'll want to make sure the `ssr` feature is enabled in the `dioxus` crate and use the `dioxus::ssr` API. We don't expect the SSR API to change drastically in the future.
 
@@ -52,7 +56,6 @@ For rendering statically to an `.html` file or from a WebServer, then you'll wan
 let contents = dioxus::ssr::render_vdom(&dom, |c| c);
 ```
 
-
 [Jump to the getting started guide for SSR.]()
 
 Examples:

+ 11 - 10
docs/guide/src/concepts/00-index.md

@@ -26,7 +26,7 @@ container.push(green_light);
 container.push(yellow_light);
 container.push(red_light);
 
-container.onclick(move |_| {
+container.set_onclick(move |_| {
     if red_light.enabled() {
         red_light.set_enabled(false);
         green_light.set_enabled(true);
@@ -45,22 +45,23 @@ As the UI grows in scale, our logic to keep each element in the proper state wou
 Instead, with Dioxus, we *declare* what we want our UI to look like:
 
 ```rust
-let state = "red";
+let mut state = use_state(cx, || "red");
 
-rsx!(
+cx.render(rsx!(
     Container {
-        Light { color: "red", enabled: {state == "red"}  }
-        Light { color: "yellow", enabled: {state == "yellow"}  }
-        Light { color: "green", enabled: {state == "green"}  }
-        onclick: |_| {
-            state = match state {
+        Light { color: "red", enabled: format_args!("{}", state == "red")  }
+        Light { color: "yellow", enabled: format_args!("{}", state == "yellow") }
+        Light { color: "green", enabled: format_args!("{}", state == "green") }
+
+        onclick: move |_| {
+            state.set(match *state {
                 "green" => "yellow",
                 "yellow" => "red",
                 "red" => "green",
-            }
+            })
         }
     }
-)
+))
 ```
 
 Remember: this concept is not new! Many frameworks are declarative - with React being the most popular. Declarative frameworks tend to be much more enjoyable to work with than imperative frameworks.

+ 2 - 2
docs/guide/src/concepts/10-concurrent-mode.md

@@ -50,8 +50,8 @@ async fn ExampleLoader(cx: Context<()>) -> Vnode {
     This API stores the result on the Context object, so the loaded data is taken as reference.
     */
     let name: &Result<SomeStructure> = use_fetch_data("http://example.com/json", ())
-                                        .place_holder(|(cx, props)|rsx!{<div> "loading..." </div>})
-                                        .delayed_place_holder(1000, |(cx, props)|rsx!{ <div> "still loading..." </div>})
+                                        .place_holder(|cx, props|rsx!{<div> "loading..." </div>})
+                                        .delayed_place_holder(1000, |cx, props|rsx!{ <div> "still loading..." </div>})
                                         .await;
 
     match name {

+ 3 - 3
docs/guide/src/concepts/11-arena-memo.md

@@ -21,9 +21,9 @@ fn test() -> DomTree {
     }
 }
 
-static TestComponent: FC<()> = |(cx, props)|html!{<div>"Hello world"</div>};
+static TestComponent: FC<()> = |cx, props|html!{<div>"Hello world"</div>};
 
-static TestComponent: FC<()> = |(cx, props)|{
+static TestComponent: FC<()> = |cx, props|{
     let g = "BLAH";
     html! {
         <div> "Hello world" </div>
@@ -31,7 +31,7 @@ static TestComponent: FC<()> = |(cx, props)|{
 };
 
 #[functional_component]
-static TestComponent: FC<{ name: String }> = |(cx, props)|html! { <div> "Hello {name}" </div> };
+static TestComponent: FC<{ name: String }> = |cx, props|html! { <div> "Hello {name}" </div> };
 ```
 
 ## Why this behavior?

+ 3 - 3
docs/guide/src/concepts/12-signals.md

@@ -96,7 +96,7 @@ Sometimes you want a signal to propagate across your app, either through far-awa
 
 ```rust
 const TITLE: Atom<String> = || "".to_string();
-const Provider: FC<()> = |(cx, props)|{
+const Provider: FC<()> = |cx, props|{
     let title = use_signal(&cx, &TITLE);
     rsx!(cx, input { value: title })
 };
@@ -105,7 +105,7 @@ const Provider: FC<()> = |(cx, props)|{
 If we use the `TITLE` atom in another component, we can cause updates to flow between components without calling render or diffing either component trees:
 
 ```rust
-const Receiver: FC<()> = |(cx, props)|{
+const Receiver: FC<()> = |cx, props|{
     let title = use_signal(&cx, &TITLE);
     log::info!("This will only be called once!");
     rsx!(cx,
@@ -132,7 +132,7 @@ Dioxus automatically understands how to use your signals when mixed with iterato
 
 ```rust
 const DICT: AtomFamily<String, String> = |_| {};
-const List: FC<()> = |(cx, props)|{
+const List: FC<()> = |cx, props|{
     let dict = use_signal(&cx, &DICT);
     cx.render(rsx!(
         ul {

+ 5 - 5
docs/guide/src/concepts/components.md

@@ -84,7 +84,7 @@ struct PostProps {
 
 And our render function:
 ```rust
-fn Post((cx, props): Scope<PostProps>) -> Element {
+fn Post(cx: Context, props: &PostProps) -> Element {
     cx.render(rsx!{
         div { class: "post-container"
             VoteButton {
@@ -120,7 +120,7 @@ struct VoteButtonProps {
     score: i32
 }
 
-fn VoteButton((cx, props): Scope<VoteButtonProps>) -> Element {
+fn VoteButton(cx: Context, props: &VoteButtonProps) -> Element {
     cx.render(rsx!{
         div { class: "votebutton"
             div { class: "arrow up" }
@@ -145,7 +145,7 @@ struct TitleCardProps<'a> {
     title: &'a str,
 }
 
-fn TitleCard<'a>((cx, props): Scope<'a, TitleCardProps>) -> Element<'a> {
+fn TitleCard(cx: Context, props: &TitleCardProps) -> Element {
     cx.render(rsx!{
         h1 { "{props.title}" }
     })
@@ -174,8 +174,8 @@ function Component(props) {
 Because Dioxus needs to work with the rules of Rust it uses the `Context` object to maintain some internal bookkeeping. That's what the `Context` object is: a place for the component to store state, manage listeners, and allocate elements. Advanced users of Dioxus will want to learn how to properly leverage the `Context` object to build robust and performant extensions for Dioxus.
 
 ```rust
-fn Post((cx /* <-- our Context object*/, props): Scope<PostProps>) -> Element {
-    cx.render(rsx!{ })
+fn Post(cx: Context, props: &PostProps) -> Element {
+    cx.render(rsx!("hello"))
 }
 ```
 ## Moving forward

+ 7 - 7
docs/guide/src/concepts/conditional_rendering.md

@@ -26,7 +26,7 @@ struct AppProps {
 Now that we have a "logged_in" flag accessible in our props, we can render two different screens:
 
 ```rust
-fn App((cx, props): Scope<AppProps>) -> Element {
+fn App(cx: Context, props: &AppProps) -> Element {
     if props.logged_in {
         cx.render(rsx!{
             DashboardScreen {}
@@ -48,7 +48,7 @@ Rust provides us algebraic datatypes: enums that can contain values. Using the `
 For instance, we could run a function that returns a Result:
 
 ```rust
-fn App((cx, props): Scope<()>) -> Element {
+fn App(cx: Context, props: &())-> Element {
     match get_name() {
         Ok(name) => cx.render(rsx!( "Hello, {name}!" )),
         Err(err) => cx.render(rsx!( "Sorry, I don't know your name, because an error occurred: {err}" )),
@@ -58,7 +58,7 @@ fn App((cx, props): Scope<()>) -> Element {
 
 We can even match against values:
 ```rust
-fn App((cx, props): Scope<()>) -> Element {
+fn App(cx: Context, props: &())-> Element {
     match get_name() {
         "jack" => cx.render(rsx!( "Hey Jack, how's Diane?" )),
         "diane" => cx.render(rsx!( "Hey Diana, how's Jack?" )),
@@ -67,12 +67,12 @@ fn App((cx, props): Scope<()>) -> Element {
 }
 ```
 
-Do note: the `rsx!` macro returns a `Closure`, an anonymous function that has a unique type. 
+Do note: the `rsx!` macro returns a `Closure`, an anonymous function that has a unique type. To turn our `rsx!` into Elements, we need to call `cx.render`.
 
 To make patterns like these less verbose, the `rsx!` macro accepts an optional first argument on which it will call `render`. Our previous component can be shortened with this alternative syntax:
 
 ```rust
-fn App((cx, props): Scope<()>) -> Element {
+fn App(cx: Context, props: &())-> Element {
     match get_name() {
         "jack" => rsx!(cx, "Hey Jack, how's Diane?" ),
         "diane" => rsx!(cx, "Hey Diana, how's Jack?" ),
@@ -83,13 +83,13 @@ fn App((cx, props): Scope<()>) -> Element {
 
 This syntax even enables us to write a one-line component:
 ```rust
-static App: Fc<()> = |(cx, props)| rsx!(cx, "hello world!");
+static App: FC<()> = |cx, props| rsx!(cx, "hello world!");
 ```
 
 Alternatively, for match statements, we can just return the builder itself and pass it into a final, single call to `cx.render`:
 
 ```rust
-fn App((cx, props): Scope<()>) -> Element {
+fn App(cx: Context, props: &())-> Element {
     let greeting = match get_name() {
         "jack" => rsx!("Hey Jack, how's Diane?" ),
         "diane" => rsx!("Hey Diana, how's Jack?" ),

+ 7 - 7
docs/guide/src/concepts/interactivity.md

@@ -35,7 +35,7 @@ fn main() {
 When Dioxus renders your app, it will pass an immutable reference of `PostProps` to your `Post` component. Here, you can pass the state down into children.
 
 ```rust
-fn App((cx, props): Scope<PostProps>) -> Element {
+fn App(cx: Context, props: &PostProps) -> Element {
     cx.render(rsx!{
         Title { title: &props.title }
         Score { score: &props.score }
@@ -63,8 +63,8 @@ Instead, you'll want to store state internally in your components and let *that*
 The most common hook you'll use for storing state is `use_state`. `use_state` provides a slot for some data that allows you to read and update the value without accidentally mutating it.
 
 ```rust
-fn App((cx, props): Scope<()>) -> Element {
-    let post = use_state(|| {
+fn App(cx: Context, props: &())-> Element {
+    let post = use_state(cx, || {
         PostData {
             id: Uuid::new_v4(),
             score: 10,
@@ -111,8 +111,8 @@ When responding to user-triggered events, we'll want to "listen" for an event on
 For example, let's say we provide a button to generate a new post. Whenever the user clicks the button, they get a new post. To achieve this functionality, we'll want to attach a function to the `on_click` method of `button`. Whenever the button is clicked, our function will run, and we'll get new Post data to work with.
 
 ```rust
-fn App((cx, props): Scope<()>) -> Element {
-    let post = use_state(|| PostData::new());
+fn App(cx: Context, props: &())-> Element {
+    let post = use_state(cx, || PostData::new());
 
     cx.render(rsx!{
         button {
@@ -134,8 +134,8 @@ We can use tasks in our components to build a tiny stopwatch that ticks every se
 
 ```rust
 
-fn App((cx, props): Scope<()>) -> Element {
-    let mut sec_elapsed = use_state(|| 0);
+fn App(cx: Context, props: &())-> Element {
+    let mut sec_elapsed = use_state(cx, || 0);
 
     cx.spawn_task(async move {
         TimeoutFuture::from_ms(1000).await;

+ 1 - 1
docs/guide/src/concepts/lists.md

@@ -74,7 +74,7 @@ struct PostListProps<'a> {
 Next, we're going to define our component:
 
 ```rust
-fn App((cx, props): Component<PostList>) -> Element {
+fn App(cx: Context, props: &PostList) -> Element {
     // First, we create a new iterator by mapping the post array
     let posts = props.posts.iter().map(|post| rsx!{
         Post {

+ 53 - 0
docs/guide/src/concepts/suspense.md

@@ -1 +1,54 @@
 # Suspense
+
+Suspense in Dioxus is enabled through placeholder nodes.
+
+just a component that renders nodes into the placeholder after the future is finished?
+
+in react, suspense is just completely pausing diffing while a 
+
+```rust
+let n = use_suspense(cx || {
+    cx.render(rsx!{
+        Suspense {
+            prom: fut,
+            callback: || {}
+        }
+    })
+})
+
+suspense () {
+    let value = use_state();
+    if first_render {
+        push_task({
+            value.set(fut.await);
+        });
+    } else {
+        callback(value)
+    }
+}
+
+
+let name = fetch_name().await;
+
+
+function ProfileDetails() {
+  // Try to read user info, although it might not have loaded yet
+  const user = resource.read();
+  return <h1>{user.name}</h1>;
+}
+
+
+fn ProfileDteails() {
+    let user = resource.suspend(cx, |l| rsx!("{l}"));
+
+    // waits for the resource to be ready and updates the placeholder with the tree
+    let name = resource.suspend_with(cx, |val| rsx!( div { "hello" "{user}" } ));
+
+    cx.render(rsx!(
+        div {
+            {user}
+            {name}
+        }
+    ))
+}
+```

+ 1 - 1
docs/guide/src/concepts/vnodes.md

@@ -107,7 +107,7 @@ To do this, we use the familiar struct-style syntax that Rust provides. Commas a
 ```rust
 rsx!(
     div {
-        hidden: true,
+        hidden: "true",
         background_color: "blue",
         class: "card color-{mycolor}"
     }

+ 15 - 25
docs/guide/src/hello_world.md

@@ -1,4 +1,4 @@
-# Hello World
+# Hello, World desktop app
 
 Let's put together a simple "hello world" desktop application to get acquainted with Dioxus. 
 
@@ -92,10 +92,10 @@ use dioxus::prelude::*;
 
 
 fn main() {
-    dioxus::desktop::start(App, |c| c);
+    dioxus::desktop::launch(App, |c| c);
 }
 
-fn App((cx, props): Component<()>) -> Element {
+fn App(cx: Context, props: &()) -> Element {
     cx.render(rsx! (
         div { "Hello, world!" }
     ))
@@ -108,7 +108,7 @@ At this point, you could call `cargo run` and be greeted with a simple `Hello, W
 
 ### Dissecting our example
 
-This bit of code imports everything from the the `prelude` module. This brings into scope the right traits, types, and macros needed for working with Dioxus.
+The `use` statement at the top of our app imports everything from the the `prelude` module. `use`-ing the prelude imports the right traits, types, and macros needed for working with Dioxus.
 
 ```rust
 use dioxus::prelude::*;
@@ -118,44 +118,34 @@ This initialization code launches a Tokio runtime on a helper thread where your
 
 ```rust
 fn main() {
-    dioxus::desktop::start(App, |c| c);
+    dioxus::desktop::launch(App, |c| c);
 }
 ```
 
 Finally, our app. Every component in Dioxus is a function that takes in `Context` and `Props` and returns an `Element`.
 
 ```rust
-fn App((cx, props): Component<()>) -> Element {
+fn App(cx: Context, props: &()) -> Element {
     cx.render(rsx! {
         div { "Hello, world!" }
     })    
 }
 ```
-In cases where props need to borrow from their parent, you will need to specify lifetimes using the function syntax:
 
-```rust
-fn App<'a>(cx: Component<'a, ()>) -> Element<'a> {
-    cx.render(rsx! {
-        div { "Hello, world!" }
-    })
-}
-```
+Writing `fn App(cx: Context, props: &()) -> Element {` might become tedious. Rust will also let you write functions as static closures, but these types of Components cannot have props that borrow data.
 
-Writing `fn App((cx, props): Component<()>) -> Element {` might become tedious. Rust will also let you write functions as static closures, but these types of Components cannot have props that borrow data.
 ```rust
-static App: Fc<()> = |(cx, props)| {
-    cx.render(rsx! {
-        div { "Hello, world!" }
-    })
-};
+static App: FC<()> = |cx, props| cx.render(rsx!(div { "Hello, world!" }));
 ```
 
-### The `Context` object
+### What is this `Context` object?
+
+Coming from React, the `Context` object might be confusing. In React, you'll want to store data between renders with hooks. However, hooks rely on global variables which make them difficult to integrate in multi-tenant systems like server-rendering. 
+
+In Dioxus, you are given an explicit `Context` object to control how the component renders and stores data. The `Context` object provides a handful of useful APIs for features like suspense, rendering, and more.
 
-In React, you'll want to store data between renders with hooks. However, hooks rely on global variables which make them difficult to integrate in multi-tenant systems like server-rendering. In Dioxus, you are given an explicit `Context` object to control how the component renders and stores data.
+## Moving on
 
-### The `rsx!` macro
+Congrats! You've built your first desktop application with Dioxus. Next, we're going to learn about the basics of building interactive user interfaces.
 
-Next, we're greeted with the `rsx!` macro. This lets us add a custom DSL for declaratively building the structure of our app. The semantics of this macro are similar to that of JSX and HTML, though with a familiar Rust-y interface. The `html!` macro is also available for writing components with a JSX/HTML syntax.
 
-The `rsx!` macro is lazy: it does not immediately produce elements or allocates, but rather builds a closure which can be rendered with `cx.render`.

+ 10 - 0
docs/guide/src/setup.md

@@ -20,6 +20,16 @@ Dioxus requires a few main things to get up and running:
 
 Dioxus integrates very well with the Rust-Analyzer IDE plugin which will provide appropriate syntax highlighting, code navigation, folding, and more.
 
+### Installing Rust
+
+Head over to [https://rust-lang.org](http://rust-lang.org) and install the Rust compiler. 
+
+Once installed, make sure to  install wasm32-unknown-unknown as a target if you're planning on deploying your app to the web.
+
+```
+rustup target add wasm32-unknown-uknown
+```
+
 ### Dioxus-CLI for dev server, bundling, etc.
 
 We also recommend installing the Dioxus CLI. The Dioxus CLI automates building and packaging for various targets and integrates with simulators, development servers, and app deployment. To install the CLI, you'll need cargo (should be automatically installed with Rust):

+ 145 - 0
examples/README.md

@@ -48,3 +48,148 @@ These web-specific examples must be run with `dioxus-cli` using `dioxus develop
 | ------- | ------------ |
 | asd     | this does    |
 | asd     | this does    |
+
+
+
+## Show me some examples!
+
+In our collection of examples, guides, and tutorials, we have:
+- The book (an introductory course)
+- The guide (an in-depth analysis of everything in Dioxus)
+- The reference (a collection of examples with heavy documentation)
+- The general examples
+- The platform-specific examples (web, ssr, desktop, mobile, server)
+  
+Here's what a few common tasks look like in Dioxus:
+
+Nested components with children and internal state:
+```rust
+fn App(cx: Context, props: &()) -> Element {
+  cx.render(rsx!( Toggle { "Toggle me" } ))
+}
+
+#[derive(PartialEq, Props)]
+struct ToggleProps { children: Element }
+
+fn Toggle(cx: Context, props: &ToggleProps) -> Element {
+  let mut toggled = use_state(cx, || false);
+  cx.render(rsx!{
+    div {
+      {&props.children}
+      button { onclick: move |_| toggled.set(true),
+        {toggled.and_then(|| "On").or_else(|| "Off")}
+      }
+    }
+  })
+}
+```
+
+Controlled inputs:
+```rust
+fn App(cx: Context, props: &()) -> Element {
+  let value = use_state(cx, String::new);
+  cx.render(rsx!( 
+    input {
+      "type": "text",
+      value: "{value}",
+      oninput: move |evt| value.set(evt.value.clone())
+    }
+  ))
+}
+```
+
+Lists and Conditional rendering:
+```rust
+fn App(cx: Context, props: &()) -> Element {
+  let list = (0..10).map(|i| {
+    rsx!(li { key: "{i}", "Value: {i}" })
+  });
+  
+  let title = match list.len() {
+    0 => rsx!("Not enough"),
+    _ => rsx!("Plenty!"),
+  };
+
+  if should_show {
+    cx.render(rsx!( 
+      {title}
+      ul { {list} } 
+    ))
+  } else {
+    None
+  }
+}
+```
+
+Tiny components:
+```rust
+static App: FC<()> = |cx, _| rsx!(cx, div {"hello world!"});
+```
+
+Borrowed prop contents:
+```rust
+fn App(cx: Context, props: &()) -> Element {
+  let name = use_state(cx, || String::from("example"));
+  rsx!(cx, Child { title: name.as_str() })
+}
+
+#[derive(Props)]
+struct ChildProps<'a> { title: &'a str }
+
+fn Child(cx: Context, props: &ChildProps) -> Element {
+  rsx!(cx, "Hello {props.title}")
+}
+```
+
+Global State
+```rust
+struct GlobalState { name: String }
+
+fn App(cx: Context, props: &()) -> Element {
+  use_provide_shared_state(cx, || GlobalState { name: String::from("Toby") })
+  rsx!(cx, Leaf {})
+}
+
+fn Leaf(cx: Context, props: &()) -> Element {
+  let state = use_consume_shared_state::<GlobalState>(cx)?;
+  rsx!(cx, "Hello {state.name}")
+}
+```
+
+Router (inspired by Yew-Router)
+```rust
+#[derive(PartialEq, Clone,  Hash, Eq, Routable)]
+enum Route {
+  #[at("/")]
+  Home,
+  #[at("/post/{id}")]
+  Post(id)
+}
+
+fn App(cx: Context, props: &()) -> Element {
+  let route = use_router(cx, Route::parse);
+  cx.render(rsx!(div {
+    {match route {
+      Route::Home => rsx!( Home {} ),
+      Route::Post(id) => rsx!( Post { id: id })
+    }}
+  }))  
+}
+```
+
+Suspense 
+```rust
+fn App(cx: Context, props: &()) -> Element {
+  let doggo = use_suspense(cx,
+    || async { reqwest::get("https://dog.ceo/api/breeds/image/random").await.unwrap().json::<Response>().await.unwrap() },
+    |response| cx.render(rsx!( img { src: "{response.message}" }))
+  );
+  
+  cx.render(rsx!{
+    div {
+      "One doggo coming right up:"
+      {doggo}
+    }
+  })
+}
+```

+ 1 - 1
examples/async.rs

@@ -11,7 +11,7 @@ async fn main() {
     dioxus::desktop::launch(App, |c| c);
 }
 
-pub static App: FC<()> = |(cx, _)| {
+pub static App: FC<()> = |cx, _| {
     let count = use_state(cx, || 0);
     let mut direction = use_state(cx, || 1);
 

+ 4 - 8
examples/borrowed.rs

@@ -20,7 +20,7 @@ fn main() {
     dioxus::desktop::launch(App, |c| c);
 }
 
-fn App((cx, props): Scope<()>) -> Element {
+fn App(cx: Context, props: &()) -> Element {
     let text: &mut Vec<String> = cx.use_hook(|_| vec![String::from("abc=def")], |f| f);
 
     let first = text.get_mut(0).unwrap();
@@ -39,11 +39,7 @@ struct C1Props<'a> {
     text: &'a mut String,
 }
 
-impl<'a> Drop for C1Props<'a> {
-    fn drop(&mut self) {}
-}
-
-fn Child1<'a>((cx, props): Scope<'a, C1Props>) -> Element<'a> {
+fn Child1(cx: Context, props: &C1Props) -> Element {
     let (left, right) = props.text.split_once("=").unwrap();
 
     cx.render(rsx! {
@@ -59,7 +55,7 @@ struct C2Props<'a> {
     text: &'a str,
 }
 
-fn Child2<'a>((cx, props): Scope<'a, C2Props>) -> Element<'a> {
+fn Child2(cx: Context, props: &C2Props) -> Element {
     cx.render(rsx! {
         Child3 {
             text: props.text
@@ -72,7 +68,7 @@ struct C3Props<'a> {
     text: &'a str,
 }
 
-fn Child3<'a>((cx, props): Scope<'a, C3Props>) -> Element<'a> {
+fn Child3(cx: Context, props: &C3Props) -> Element {
     cx.render(rsx! {
         div { "{props.text}"}
     })

+ 78 - 75
examples/calculator.rs

@@ -3,97 +3,82 @@ This example is a simple iOS-style calculator. This particular example can run a
 This calculator version uses React-style state management. All state is held as individual use_states.
 */
 
+use std::sync::Arc;
+
 use dioxus::events::*;
 use dioxus::prelude::*;
+use separator::Separatable;
 
 fn main() {
     dioxus::desktop::launch(APP, |cfg| cfg);
 }
 
-const APP: FC<()> = |(cx, _)| {
+const APP: FC<()> = |cx, _| {
     let cur_val = use_state(cx, || 0.0_f64);
     let operator = use_state(cx, || None as Option<&'static str>);
     let display_value = use_state(cx, || String::from(""));
 
-    let clear_display = display_value == "0";
-    let clear_text = if clear_display { "C" } else { "AC" };
-
-    let input_digit = move |num: u8| display_value.modify().push_str(num.to_string().as_str());
-
-    let input_dot = move || display_value.modify().push_str(".");
-
-    let perform_operation = move || {
-        if let Some(op) = operator.as_ref() {
-            let rhs = display_value.parse::<f64>().unwrap();
-            let new_val = match *op {
-                "+" => *cur_val + rhs,
-                "-" => *cur_val - rhs,
-                "*" => *cur_val * rhs,
-                "/" => *cur_val / rhs,
-                _ => unreachable!(),
-            };
-            cur_val.set(new_val);
-            display_value.set(new_val.to_string());
-            operator.set(None);
-        }
-    };
-
-    let toggle_sign = move |_| {
-        if display_value.starts_with("-") {
-            display_value.set(display_value.trim_start_matches("-").to_string())
-        } else {
-            display_value.set(format!("-{}", *display_value))
-        }
-    };
-
     let toggle_percent = move |_| todo!();
+    let input_digit = move |num: u8| display_value.modify().push_str(num.to_string().as_str());
 
-    let clear_key = move |_| {
-        display_value.set("0".to_string());
-        if !clear_display {
-            operator.set(None);
-            cur_val.set(0.0);
-        }
-    };
-
-    let keydownhandler = move |evt: KeyboardEvent| match evt.key_code {
-        KeyCode::Add => operator.set(Some("+")),
-        KeyCode::Subtract => operator.set(Some("-")),
-        KeyCode::Divide => operator.set(Some("/")),
-        KeyCode::Multiply => operator.set(Some("*")),
-        KeyCode::Num0 => input_digit(0),
-        KeyCode::Num1 => input_digit(1),
-        KeyCode::Num2 => input_digit(2),
-        KeyCode::Num3 => input_digit(3),
-        KeyCode::Num4 => input_digit(4),
-        KeyCode::Num5 => input_digit(5),
-        KeyCode::Num6 => input_digit(6),
-        KeyCode::Num7 => input_digit(7),
-        KeyCode::Num8 => input_digit(8),
-        KeyCode::Num9 => input_digit(9),
-        KeyCode::Backspace => {
-            if !display_value.as_str().eq("0") {
-                display_value.modify().pop();
+    rsx!(cx, div {
+        class: "calculator",
+        onkeydown: move |evt| match evt.key_code {
+            KeyCode::Add => operator.set(Some("+")),
+            KeyCode::Subtract => operator.set(Some("-")),
+            KeyCode::Divide => operator.set(Some("/")),
+            KeyCode::Multiply => operator.set(Some("*")),
+            KeyCode::Num0 => input_digit(0),
+            KeyCode::Num1 => input_digit(1),
+            KeyCode::Num2 => input_digit(2),
+            KeyCode::Num3 => input_digit(3),
+            KeyCode::Num4 => input_digit(4),
+            KeyCode::Num5 => input_digit(5),
+            KeyCode::Num6 => input_digit(6),
+            KeyCode::Num7 => input_digit(7),
+            KeyCode::Num8 => input_digit(8),
+            KeyCode::Num9 => input_digit(9),
+            KeyCode::Backspace => {
+                if !display_value.as_str().eq("0") {
+                    display_value.modify().pop();
+                }
             }
+            _ => {}
         }
-        _ => {}
-    };
-
-    use separator::Separatable;
-    let formatted_display = cur_val.separated_string();
-
-    rsx!(cx, div {
-        class: "calculator", onkeydown: {keydownhandler}
-        div { class: "calculator-display", "{formatted_display}" }
+        div { class: "calculator-display", {[format_args!("{}", cur_val.separated_string())]} }
         div { class: "input-keys"
             div { class: "function-keys"
-                CalculatorKey { name: "key-clear", onclick: {clear_key} "{clear_text}" }
-                CalculatorKey { name: "key-sign", onclick: {toggle_sign}, "±"}
-                CalculatorKey { name: "key-percent", onclick: {toggle_percent} "%"}
+                CalculatorKey {
+                    {[if display_value == "0" { "C" } else { "AC" }]}
+                    name: "key-clear",
+                    onclick: move |_| {
+                        display_value.set("0".to_string());
+                        if display_value != "0" {
+                            operator.set(None);
+                            cur_val.set(0.0);
+                        }
+                    }
+                }
+                CalculatorKey {
+                    "±"
+                    name: "key-sign",
+                    onclick: move |_| {
+                        if display_value.starts_with("-") {
+                            display_value.set(display_value.trim_start_matches("-").to_string())
+                        } else {
+                            display_value.set(format!("-{}", *display_value))
+                        }
+                    },
+                }
+                CalculatorKey {
+                    "%"
+                    onclick: {toggle_percent}
+                    name: "key-percent",
+                }
             }
             div { class: "digit-keys"
                 CalculatorKey { name: "key-0", onclick: move |_| input_digit(0), "0" }
-                CalculatorKey { name: "key-dot", onclick: move |_| input_dot(), "●" }
+                CalculatorKey { name: "key-dot", onclick: move |_| display_value.modify().push_str("."), "●" }
 
                 {(1..9).map(|k| rsx!{
                     CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| input_digit(k), "{k}" }
@@ -104,7 +89,25 @@ const APP: FC<()> = |(cx, _)| {
                 CalculatorKey { name: "key-multiply", onclick: move |_| operator.set(Some("*")) "×" }
                 CalculatorKey { name: "key-subtract", onclick: move |_| operator.set(Some("-")) "−" }
                 CalculatorKey { name: "key-add", onclick: move |_| operator.set(Some("+")) "+" }
-                CalculatorKey { name: "key-equals", onclick: move |_| perform_operation() "=" }
+                CalculatorKey {
+                    "="
+                    name: "key-equals",
+                    onclick: move |_| {
+                        if let Some(op) = operator.as_ref() {
+                            let rhs = display_value.parse::<f64>().unwrap();
+                            let new_val = match *op {
+                                "+" => *cur_val + rhs,
+                                "-" => *cur_val - rhs,
+                                "*" => *cur_val * rhs,
+                                "/" => *cur_val / rhs,
+                                _ => unreachable!(),
+                            };
+                            cur_val.set(new_val);
+                            display_value.set(new_val.to_string());
+                            operator.set(None);
+                        }
+                    },
+                }
             }
         }
     })
@@ -113,11 +116,11 @@ const APP: FC<()> = |(cx, _)| {
 #[derive(Props)]
 struct CalculatorKeyProps<'a> {
     name: &'static str,
-    onclick: &'a dyn Fn(MouseEvent),
-    children: ScopeChildren<'a>,
+    onclick: &'a dyn Fn(Arc<MouseEvent>),
+    children: Element,
 }
 
-fn CalculatorKey<'a>((cx, props): Scope<'a, CalculatorKeyProps<'a>>) -> Element {
+fn CalculatorKey<'a>(cx: Context, props: &CalculatorKeyProps) -> Element {
     rsx!(cx, button {
         class: "calculator-key {props.name}"
         onclick: {props.onclick}

+ 1 - 1
examples/core/alternative.rs

@@ -9,7 +9,7 @@ fn main() {
     println!("{}", dom);
 }
 
-pub static EXAMPLE: FC<()> = |(cx, _)| {
+pub static EXAMPLE: FC<()> = |cx, _| {
     let list = (0..10).map(|_f| {
         rsx! {
             "{_f}"

+ 1 - 1
examples/core/syntax.rs

@@ -31,7 +31,7 @@ fn html_usage() {
     // let p = rsx!(div { {f} });
 }
 
-static App2: FC<()> = |(cx, _)| cx.render(rsx!("hello world!"));
+static App2: FC<()> = |cx, _| cx.render(rsx!("hello world!"));
 
 static App: FC<()> = |cx, props| {
     let name = cx.use_state(|| 0);

+ 1 - 1
examples/crm.rs

@@ -19,7 +19,7 @@ pub struct Client {
     pub description: String,
 }
 
-static App: FC<()> = |(cx, _)| {
+static App: FC<()> = |cx, _| {
     let mut clients = use_ref(cx, || vec![] as Vec<Client>);
     let mut scene = use_state(cx, || Scene::ClientsList);
 

+ 1 - 1
examples/desktop/crm.rs

@@ -21,7 +21,7 @@ pub struct Client {
     pub description: String,
 }
 
-static App: FC<()> = |(cx, _)| {
+static App: FC<()> = |cx, _| {
     let mut scene = use_state(cx, || Scene::ClientsList);
     let clients = use_ref(cx, || vec![] as Vec<Client>);
 

+ 1 - 1
examples/desktop/todomvc.rs

@@ -32,7 +32,7 @@ pub struct TodoItem {
 }
 pub type Todos = HashMap<u32, TodoItem>;
 
-pub static App: FC<()> = |(cx, _)| {
+pub static App: FC<()> = |cx, _| {
     // Share our TodoList to the todos themselves
     use_provide_state(cx, Todos::new);
 

+ 87 - 93
examples/framework_benchmark.rs

@@ -1,50 +1,38 @@
-use dioxus::{events::MouseEvent, prelude::*};
-use fxhash::FxBuildHasher;
-use std::rc::Rc;
+use dioxus::prelude::*;
+use rand::prelude::*;
 
 fn main() {
-    dioxus::desktop::launch(App, |c| c);
+    dioxus::web::launch(App, |c| c);
+    // dioxus::desktop::launch(App, |c| c);
 }
 
-// We use a special immutable hashmap to make hashmap operations efficient
-type RowList = im_rc::HashMap<usize, Rc<str>, FxBuildHasher>;
-
-static App: FC<()> = |(cx, _props)| {
-    let mut items = use_state(cx, || RowList::default());
-
-    let create_rendered_rows = move |from, num| move |_| items.set(create_row_list(from, num));
-
-    let mut append_1_000_rows =
-        move |_| items.set(create_row_list(items.len(), 1000).union((*items).clone()));
-
-    let update_every_10th_row = move |_| {
-        let mut new_items = (*items).clone();
-        let mut small_rng = SmallRng::from_entropy();
-        new_items.iter_mut().step_by(10).for_each(|(_, val)| {
-            *val = create_new_row_label(&mut String::with_capacity(30), &mut small_rng)
-        });
-        items.set(new_items);
-    };
-    let clear_rows = move |_| items.set(RowList::default());
+#[derive(Clone, PartialEq)]
+struct Label {
+    key: usize,
+    labels: [&'static str; 3],
+}
 
-    let swap_rows = move |_| {
-        // this looks a bit ugly because we're using a hashmap instead of a vec
-        if items.len() > 998 {
-            let mut new_items = (*items).clone();
-            let a = new_items.get(&0).unwrap().clone();
-            *new_items.get_mut(&0).unwrap() = new_items.get(&998).unwrap().clone();
-            *new_items.get_mut(&998).unwrap() = a;
-            items.set(new_items);
+impl Label {
+    fn new_list(num: usize) -> Vec<Self> {
+        let mut rng = SmallRng::from_entropy();
+        let mut labels = Vec::with_capacity(num);
+        for _ in 0..num {
+            labels.push(Label {
+                key: 0,
+                labels: [
+                    ADJECTIVES.choose(&mut rng).unwrap(),
+                    COLOURS.choose(&mut rng).unwrap(),
+                    NOUNS.choose(&mut rng).unwrap(),
+                ],
+            });
         }
-    };
+        labels
+    }
+}
 
-    let rows = items.iter().map(|(key, value)| {
-        rsx!(Row {
-            key: "{key}",
-            row_id: *key as usize,
-            label: value.clone(),
-        })
-    });
+static App: FC<()> = |cx, _props| {
+    let mut items = use_ref(cx, || vec![]);
+    let mut selected = use_state(cx, || None);
 
     cx.render(rsx! {
         div { class: "container"
@@ -53,22 +41,49 @@ static App: FC<()> = |(cx, _props)| {
                     div { class: "col-md-6", h1 { "Dioxus" } }
                     div { class: "col-md-6"
                         div { class: "row"
-                            ActionButton { name: "Create 1,000 rows", id: "run", onclick: {create_rendered_rows(0, 1_000)} }
-                            ActionButton { name: "Create 10,000 rows", id: "runlots", onclick: {create_rendered_rows(0, 10_000)} }
-                            ActionButton { name: "Append 1,000 rows", id: "add", onclick: {append_1_000_rows} }
-                            ActionButton { name: "Update every 10th row", id: "update", onclick: {update_every_10th_row} }
-                            ActionButton { name: "Clear", id: "clear", onclick: {clear_rows} }
-                            ActionButton { name: "Swap rows", id: "swaprows", onclick: {swap_rows} }
+                            ActionButton { name: "Create 1,000 rows", id: "run",
+                                onclick: move || items.set(Label::new_list(1_000)),
+                            }
+                            ActionButton { name: "Create 10,000 rows", id: "runlots",
+                                onclick: move || items.set(Label::new_list(10_000)),
+                            }
+                            ActionButton { name: "Append 1,000 rows", id: "add",
+                                onclick: move || items.write().extend(Label::new_list(1_000)),
+                            }
+                            ActionButton { name: "Update every 10th row", id: "update",
+                                onclick: move || items.write().iter_mut().step_by(10).for_each(|item| item.labels[2] = "!!!"),
+                            }
+                            ActionButton { name: "Clear", id: "clear",
+                                onclick: move || items.write().clear(),
+                            }
+                            ActionButton { name: "Swap rows", id: "swaprows",
+                                onclick: move || items.write().swap(0, 998),
+                            }
                         }
                     }
                 }
             }
             table {
                 tbody {
-                    {rows}
+                    {items.read().iter().enumerate().map(|(id, item)| {
+                        let is_in_danger = if (*selected).map(|s| s == id).unwrap_or(false) {"danger"} else {""};
+                        rsx!(tr { class: "{is_in_danger}"
+                            td { class:"col-md-1" }
+                            td { class:"col-md-1", "{item.key}" }
+                            td { class:"col-md-1", onclick: move |_| selected.set(Some(id)),
+                                a { class: "lbl", {item.labels} }
+                            }
+                            td { class: "col-md-1"
+                                a { class: "remove", onclick: move |_| { items.write().remove(id); },
+                                    span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
+                                }
+                            }
+                            td { class: "col-md-6" }
+                        })
+                    })}
                 }
              }
-            span {}
+            // span { class: "preloadicon glyphicon glyphicon-remove" aria_hidden: "true" }
         }
     })
 };
@@ -77,59 +92,17 @@ static App: FC<()> = |(cx, _props)| {
 struct ActionButtonProps<'a> {
     name: &'static str,
     id: &'static str,
-    onclick: &'a dyn Fn(MouseEvent),
+    onclick: &'a dyn Fn(),
 }
 
-fn ActionButton<'a>((cx, props): Scope<'a, ActionButtonProps>) -> Element<'a> {
+fn ActionButton(cx: Context, props: &ActionButtonProps) -> Element {
     rsx!(cx, div { class: "col-sm-6 smallpad"
-        button { class:"btn btn-primary btn-block", r#type: "button", id: "{props.id}",  onclick: {props.onclick},
+        button { class:"btn btn-primary btn-block", r#type: "button", id: "{props.id}",  onclick: move |_| (props.onclick)(),
             "{props.name}"
         }
     })
 }
 
-#[derive(PartialEq, Props)]
-struct RowProps {
-    row_id: usize,
-    label: Rc<str>,
-}
-fn Row((cx, props): Scope<RowProps>) -> Element {
-    rsx!(cx, tr {
-        td { class:"col-md-1", "{props.row_id}" }
-        td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
-            a { class: "lbl", "{props.label}" }
-        }
-        td { class: "col-md-1"
-            a { class: "remove", onclick: move |_| {/* remove */}
-                span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
-            }
-        }
-        td { class: "col-md-6" }
-    })
-}
-
-use rand::prelude::*;
-fn create_new_row_label(label: &mut String, rng: &mut SmallRng) -> Rc<str> {
-    label.push_str(ADJECTIVES.choose(rng).unwrap());
-    label.push(' ');
-    label.push_str(COLOURS.choose(rng).unwrap());
-    label.push(' ');
-    label.push_str(NOUNS.choose(rng).unwrap());
-    Rc::from(label.as_ref())
-}
-
-fn create_row_list(from: usize, num: usize) -> RowList {
-    let mut small_rng = SmallRng::from_entropy();
-    let mut buf = String::with_capacity(35);
-    (from..num + from)
-        .map(|f| {
-            let o = (f, create_new_row_label(&mut buf, &mut small_rng));
-            buf.clear();
-            o
-        })
-        .collect::<RowList>()
-}
-
 static ADJECTIVES: &[&str] = &[
     "pretty",
     "large",
@@ -167,3 +140,24 @@ static NOUNS: &[&str] = &[
     "table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger",
     "pizza", "mouse", "keyboard",
 ];
+
+// #[derive(PartialEq, Props)]
+// struct RowProps<'a> {
+//     row_id: usize,
+//     label: &'a Label,
+// }
+
+// fn Row(cx: Context, props: &RowProps) -> Element {
+//     rsx!(cx, tr {
+//         td { class:"col-md-1", "{props.row_id}" }
+//         td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
+//             a { class: "lbl", {props.label.labels} }
+//         }
+//         td { class: "col-md-1"
+//             a { class: "remove", onclick: move |_| {/* remove */}
+//                 span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
+//             }
+//         }
+//         td { class: "col-md-6" }
+//     })
+// }

+ 1 - 1
examples/pattern_reducer.rs

@@ -11,7 +11,7 @@ fn main() {
     dioxus::desktop::launch(App, |c| c);
 }
 
-pub static App: FC<()> = |(cx, _)| {
+pub static App: FC<()> = |cx, _| {
     let state = use_state(cx, PlayerState::new);
 
     let is_playing = state.is_playing();

+ 2 - 2
examples/web/basic.rs

@@ -14,7 +14,7 @@ fn main() {
     dioxus_web::launch(APP, |c| c)
 }
 
-static APP: FC<()> = |(cx, _)| {
+static APP: FC<()> = |cx, _| {
     let mut count = use_state(cx, || 3);
     let content = use_state(cx, || String::from("h1"));
     let text_content = use_state(cx, || String::from("Hello, world!"));
@@ -86,4 +86,4 @@ fn render_list(cx: Context, count: usize) -> Element {
     rsx!(cx, ul { {items} })
 }
 
-static CHILD: FC<()> = |(cx, _)| rsx!(cx, div {"hello child"});
+static CHILD: FC<()> = |cx, _| rsx!(cx, div {"hello child"});

+ 1 - 1
examples/web/crm2.rs

@@ -28,7 +28,7 @@ pub struct Client {
     pub description: String,
 }
 
-static App: FC<()> = |(cx, _)| {
+static App: FC<()> = |cx, _| {
     let scene = use_state(cx, || Scene::ClientsList);
     let clients = use_ref(cx, || vec![] as Vec<Client>);
 

+ 32 - 0
notes/FAQ.md

@@ -0,0 +1,32 @@
+
+### Aren't VDOMs just pure overhead? Why not something like Solid or Svelte?
+Remember: Dioxus is a library - not a compiler like Svelte. Plus, the inner VirtualDOM allows Dioxus to easily port into different runtimes, support SSR, and run remotely in the cloud. VDOMs tend to more ergonomic to work with and feel roughly like natural Rust code. The overhead of Dioxus is **extraordinarily** minimal... sure, there may be some overhead but on an order of magnitude lower than the time required to actually update the page.
+
+
+### Isn't the overhead for interacting with the DOM from Wasm too much?
+The overhead layer between Wasm and JS APIs is extremely poorly understood. Rust web benchmarks typically suffer from differences in how Rust and JS cache strings. In Dioxus, we solve most of these issues and our JS Framework Benchmark actually beats the Wasm Bindgen benchmark in many cases. Compared to a "pure vanilla JS" solution, Dioxus adds less than 5% of overhead and takes advantage of batched DOM manipulation.
+
+### Aren't Wasm binaries too huge to deploy in production?
+Wasm binary sizes are another poorly understood characteristic of Rust web apps. 50kb of Wasm and 50kb of JS are _not_ made equally. In JS, the code must be downloaded _first_ and _then_ JIT-ted. Just-in-time compiling 50kb of JavaScript takes a while which is why 50kb of JavaScript sounds like a lot! However, with Wasm, the code is downloaded and JIT-ted _simultaneously_ through the magic of streaming compilation. By the time the 50kb of Rust is finished downloading, it is already ready to go. Again, Dioxus beats out many benchmarks with time-to-interactivity.
+
+For reference, Dioxus `hello-world` clocks in at around 70kb gzip or 60kb brotli, and Dioxus supports SSR.
+
+### Why hooks? Why not MVC, classes, traits, messages, etc?
+There are plenty Rust Elm-like frameworks in the world - we were not interested in making another! Instead, we borrowed hooks from React. JS and Rust share many structural similarities, so if you're comfortable with React, then you'll be plenty comfortable with Dioxus.
+
+### Why a custom DSL? Why not just pure function calls?
+The `RSX` DSL is _barely_ a DSL. Rustaceans will find the DSL very similar to simply assembling nested structs, but without the syntactical overhead of "Default" everywhere or having to jump through hoops with the builder pattern. Between RSX, HTML, the Raw Factory API, and the NodeBuilder syntax, there's plenty of options to choose from.
+
+### What are the build times like? Why on earth would I choose Rust instead of JS/TS/Elm?
+Dioxus builds as roughly as fast as a complex WebPack-TypeScript site. Compile times will be slower than an equivalent TypeScript site, but not unbearably slow. The Wasm compiler backend for Rust is very fast. Iterating on small components is basically instant and larger apps takes a few seconds. In practice, the compiler guarantees of Rust balance out the rebuild times.
+
+### What about Yew/Seed/Sycamore/Dominator/Dodrio/Percy?
+- Yew and Seed use an Elm-like pattern and don't support SSR or any alternate rendering platforms
+- Sycamore and Dominator are more like SolidJS/Svelte, requiring no VDOM but has less naturally-Rusty state management
+- Percy isn't quite mature yet
+- Dodrio is the spiritual predecessor of Dioxus, but is currently an archived research project without the batteries of Dioxus
+
+### How do the mobile and desktop renderers work? Is it Electron?
+Currently, Dioxus uses your device's native WebView library to draw the page. None of your app code is actually running in the WebView thread, so you can access system resources instead of having to go through something like NodeJS. This means your app will use Safari on macOS/iOS, Edge (Chromium) on Windows, and whatever is the default Web Browser for Linux and Android. Because your code is compiled and running natively, performance is not a problem. You will have to use the various "Escape Hatches" to use browser-native APIs (like WebGL) and work around visual differences in how Safari and Chrome render the page.
+
+In the future, we are interested in using Webrenderer to provide a fully native renderer without having to go through the system WebView library. In practice, Dioxus mobile and desktop are great for CRUD-style apps, but the ergonomic cross-platform APIs (GPS, Camera, etc) are not there yet.

+ 1 - 1
notes/Parity.md

@@ -12,7 +12,7 @@ https://github.com/rustwasm/gloo
 For example, resize observer would function like this:
 
 ```rust
-pub static Example: FC<()> = |(cx, props)|{
+pub static Example: FC<()> = |cx, props|{
     let observer = use_resize_observer();
 
     cx.render(rsx!(

+ 5 - 5
notes/SOLVEDPROBLEMS.md

@@ -153,13 +153,13 @@ Notice that LiveComponent receivers (the client-side interpretation of a LiveCom
 The `VNodeTree` type is a very special type that allows VNodes to be created using a pluggable allocator. The html! macro creates something that looks like:
 
 ```rust
-pub static Example: FC<()> = |(cx, props)|{
+pub static Example: FC<()> = |cx, props|{
     html! { <div> "blah" </div> }
 };
 
 // expands to...
 
-pub static Example: FC<()> = |(cx, props)|{
+pub static Example: FC<()> = |cx, props|{
     // This function converts a Fn(allocator) -> DomTree closure to a VNode struct that will later be evaluated.
     html_macro_to_vnodetree(move |allocator| {
         let mut node0 = allocator.alloc(VElement::div);
@@ -313,7 +313,7 @@ Here's how react does it:
 Any "dirty" node causes an entire subtree render. Calling "setState" at the very top will cascade all the way down. This is particularly bad for this component design:
 
 ```rust
-static APP: FC<()> = |(cx, props)|{
+static APP: FC<()> = |cx, props|{
     let title = use_context(Title);
     cx.render(html!{
         <div>
@@ -334,7 +334,7 @@ static APP: FC<()> = |(cx, props)|{
         </div>
     })
 };
-static HEAVY_LIST: FC<()> = |(cx, props)|{
+static HEAVY_LIST: FC<()> = |cx, props|{
     cx.render({
         {0.100.map(i => <BigElement >)}
     })
@@ -378,7 +378,7 @@ struct Props {
 
 }
 
-static Component: FC<Props> = |(cx, props)|{
+static Component: FC<Props> = |cx, props|{
 
 }
 ```

+ 1 - 1
packages/core-macro/src/lib.rs

@@ -30,7 +30,7 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
 ///
 /// ## Complete Reference Guide:
 /// ```
-/// const Example: FC<()> = |(cx, props)|{
+/// const Example: FC<()> = |cx, props|{
 ///     let formatting = "formatting!";
 ///     let formatting_tuple = ("a", "b");
 ///     let lazy_fmt = format_args!("lazily formatted text");

+ 91 - 125
packages/core-macro/src/router.rs

@@ -1,3 +1,5 @@
+#![allow(dead_code)]
+
 use proc_macro2::TokenStream;
 use quote::quote;
 use syn::parse::{Parse, ParseStream};
@@ -109,140 +111,104 @@ fn parse_variants_attributes(
 }
 
 impl Routable {
-    fn build_from_path(&self) -> TokenStream {
-        let from_path_matches = self.variants.iter().enumerate().map(|(i, variant)| {
-            let ident = &variant.ident;
-            let right = match &variant.fields {
-                Fields::Unit => quote! { Self::#ident },
-                Fields::Named(field) => {
-                    let fields = field.named.iter().map(|it| {
-                        //named fields have idents
-                        it.ident.as_ref().unwrap()
-                    });
-                    quote! { Self::#ident { #(#fields: params.get(stringify!(#fields))?.parse().ok()?,)* } }
-                }
-                Fields::Unnamed(_) => unreachable!(), // already checked
-            };
-
-            let left = self.ats.get(i).unwrap();
-            quote! {
-                #left => ::std::option::Option::Some(#right)
-            }
-        });
-
-        quote! {
-            fn from_path(path: &str, params: &::std::collections::HashMap<&str, &str>) -> ::std::option::Option<Self> {
-                match path {
-                    #(#from_path_matches),*,
-                    _ => ::std::option::Option::None,
-                }
-            }
-        }
-    }
-
-    fn build_to_path(&self) -> TokenStream {
-        let to_path_matches = self.variants.iter().enumerate().map(|(i, variant)| {
-            let ident = &variant.ident;
-            let mut right = self.ats.get(i).unwrap().value();
-
-            match &variant.fields {
-                Fields::Unit => quote! { Self::#ident => ::std::string::ToString::to_string(#right) },
-                Fields::Named(field) => {
-                    let fields = field
-                        .named
-                        .iter()
-                        .map(|it| it.ident.as_ref().unwrap())
-                        .collect::<Vec<_>>();
-
-                    for field in fields.iter() {
-                        // :param -> {param}
-                        // so we can pass it to `format!("...", param)`
-                        right = right.replace(&format!(":{}", field), &format!("{{{}}}", field))
-                    }
-
-                    quote! {
-                        Self::#ident { #(#fields),* } => ::std::format!(#right, #(#fields = #fields),*)
-                    }
-                }
-                Fields::Unnamed(_) => unreachable!(), // already checked
-            }
-        });
-
-        quote! {
-            fn to_path(&self) -> ::std::string::String {
-                match self {
-                    #(#to_path_matches),*,
-                }
-            }
-        }
-    }
+    // fn build_from_path(&self) -> TokenStream {
+    //     let from_path_matches = self.variants.iter().enumerate().map(|(i, variant)| {
+    //         let ident = &variant.ident;
+    //         let right = match &variant.fields {
+    //             Fields::Unit => quote! { Self::#ident },
+    //             Fields::Named(field) => {
+    //                 let fields = field.named.iter().map(|it| {
+    //                     //named fields have idents
+    //                     it.ident.as_ref().unwrap()
+    //                 });
+    //                 quote! { Self::#ident { #(#fields: params.get(stringify!(#fields))?.parse().ok()?,)* } }
+    //             }
+    //             Fields::Unnamed(_) => unreachable!(), // already checked
+    //         };
+
+    //         let left = self.ats.get(i).unwrap();
+    //         quote! {
+    //             #left => ::std::option::Option::Some(#right)
+    //         }
+    //     });
+
+    //     quote! {
+    //         fn from_path(path: &str, params: &::std::collections::HashMap<&str, &str>) -> ::std::option::Option<Self> {
+    //             match path {
+    //                 #(#from_path_matches),*,
+    //                 _ => ::std::option::Option::None,
+    //             }
+    //         }
+    //     }
+    // }
+
+    // fn build_to_path(&self) -> TokenStream {
+    //     let to_path_matches = self.variants.iter().enumerate().map(|(i, variant)| {
+    //         let ident = &variant.ident;
+    //         let mut right = self.ats.get(i).unwrap().value();
+
+    //         match &variant.fields {
+    //             Fields::Unit => quote! { Self::#ident => ::std::string::ToString::to_string(#right) },
+    //             Fields::Named(field) => {
+    //                 let fields = field
+    //                     .named
+    //                     .iter()
+    //                     .map(|it| it.ident.as_ref().unwrap())
+    //                     .collect::<Vec<_>>();
+
+    //                 for field in fields.iter() {
+    //                     // :param -> {param}
+    //                     // so we can pass it to `format!("...", param)`
+    //                     right = right.replace(&format!(":{}", field), &format!("{{{}}}", field))
+    //                 }
+
+    //                 quote! {
+    //                     Self::#ident { #(#fields),* } => ::std::format!(#right, #(#fields = #fields),*)
+    //                 }
+    //             }
+    //             Fields::Unnamed(_) => unreachable!(), // already checked
+    //         }
+    //     });
+
+    //     quote! {
+    //         fn to_path(&self) -> ::std::string::String {
+    //             match self {
+    //                 #(#to_path_matches),*,
+    //             }
+    //         }
+    //     }
+    // }
 }
 
 pub fn routable_derive_impl(input: Routable) -> TokenStream {
     let Routable {
-        ats,
-        not_found_route,
-        ident,
+        // ats,
+        // not_found_route,
+        // ident,
         ..
     } = &input;
 
-    let from_path = input.build_from_path();
-    let to_path = input.build_to_path();
-
-    let not_found_route = match not_found_route {
-        Some(route) => quote! { ::std::option::Option::Some(Self::#route) },
-        None => quote! { ::std::option::Option::None },
-    };
-
-    let cache_thread_local_ident = Ident::new(
-        &format!("__{}_ROUTER_CURRENT_ROUTE_CACHE", ident),
-        ident.span(),
-    );
+    // let from_path = input.build_from_path();
+    // let to_path = input.build_to_path();
 
     quote! {
-        // ::std::thread_local! {
-        //     #[doc(hidden)]
-        //     #[allow(non_upper_case_globals)]
-        //     static #cache_thread_local_ident: ::std::cell::RefCell<::std::option::Option<#ident>> = ::std::cell::RefCell::new(::std::option::Option::None);
+        // #[automatically_derived]
+        // impl ::dioxus::router::Routable for #ident {
+
+        //     fn recognize(pathname: &str) -> ::std::option::Option<Self> {
+        //         todo!()
+        //         // ::std::thread_local! {
+        //         //     static ROUTER: ::dioxus::router::__macro::Router = ::dioxus::router::__macro::build_router::<#ident>();
+        //         // }
+        //         // let route = ROUTER.with(|router| ::dioxus::router::__macro::recognize_with_router(router, pathname));
+        //         // {
+        //         //     let route = ::std::clone::Clone::clone(&route);
+        //         //     #cache_thread_local_ident.with(move |val| {
+        //         //         *val.borrow_mut() = route;
+        //         //     });
+        //         // }
+        //         // route
+        //     }
         // }
-
-        #[automatically_derived]
-        impl ::dioxus::router::Routable for #ident {
-            #from_path
-            #to_path
-
-            fn routes() -> ::std::vec::Vec<&'static str> {
-                ::std::vec![#(#ats),*]
-            }
-
-            fn not_found_route() -> ::std::option::Option<Self> {
-                #not_found_route
-            }
-
-            // fn current_route() -> ::std::option::Option<Self> {
-            //     #cache_thread_local_ident.with(|val| ::std::clone::Clone::clone(&*val.borrow()))
-            // }
-
-            fn recognize(pathname: &str) -> ::std::option::Option<Self> {
-                todo!()
-                // ::std::thread_local! {
-                //     static ROUTER: ::dioxus::router::__macro::Router = ::dioxus::router::__macro::build_router::<#ident>();
-                // }
-                // let route = ROUTER.with(|router| ::dioxus::router::__macro::recognize_with_router(router, pathname));
-                // {
-                //     let route = ::std::clone::Clone::clone(&route);
-                //     #cache_thread_local_ident.with(move |val| {
-                //         *val.borrow_mut() = route;
-                //     });
-                // }
-                // route
-            }
-
-            // fn cleanup() {
-            //     #cache_thread_local_ident.with(move |val| {
-            //         *val.borrow_mut() = ::std::option::Option::None;
-            //     });
-            // }
-        }
     }
 }

+ 5 - 7
packages/core-macro/src/rsx/body.rs

@@ -49,19 +49,17 @@ impl ToTokens for CallBody {
         match &self.custom_context {
             // The `in cx` pattern allows directly rendering
             Some(ident) => out_tokens.append_all(quote! {
-                #ident.render(NodeFactory::annotate_lazy(move |__cx: NodeFactory| -> VNode {
+                #ident.render(LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
                     use dioxus_elements::{GlobalAttributes, SvgAttributes};
                     #inner
                 }))
             }),
             // Otherwise we just build the LazyNode wrapper
             None => out_tokens.append_all(quote! {
-                {
-                    NodeFactory::annotate_lazy(move |__cx: NodeFactory| -> VNode {
-                        use dioxus_elements::{GlobalAttributes, SvgAttributes};
-                        #inner
-                    })
-                }
+                LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
+                    use dioxus_elements::{GlobalAttributes, SvgAttributes};
+                    #inner
+                })
             }),
         };
     }

+ 1 - 1
packages/core-macro/src/rsxtemplate.rs

@@ -72,7 +72,7 @@ impl ToTokens for RsxTemplate {
 
         // // create a lazy tree that accepts a bump allocator
         // let final_tokens = quote! {
-        //     dioxus::prelude::LazyNodes::new(move |(cx, props)|{
+        //     dioxus::prelude::LazyNodes::new(move |cx, props|{
         //         let bump = &cx.bump();
 
         //         #new_toks

+ 0 - 264
packages/core-macro/styles/calc.rs

@@ -1,264 +0,0 @@
-//! The `calc` functionality.
-use crate::LengthPercentage;
-use ::{
-    proc_macro2::TokenStream,
-    quote::{quote, ToTokens},
-    std::fmt,
-    syn::{
-        custom_keyword, parenthesized,
-        parse::{Parse, ParseStream},
-        Token,
-    },
-};
-
-/// Values that can be a calculaion (currently restricted to length & percentages)
-#[derive(Debug, Clone, PartialEq)]
-pub enum Calc {
-    Calculated(CalcSum),
-    Normal(LengthPercentage),
-}
-
-impl fmt::Display for Calc {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Calc::Calculated(inner) => write!(f, "calc({})", inner),
-            Calc::Normal(inner) => write!(f, "{}", inner),
-        }
-    }
-}
-
-impl Parse for Calc {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        custom_keyword!(calc);
-        if s.peek(calc) {
-            s.parse::<calc>()?;
-            let content;
-            parenthesized!(content in s);
-            Ok(Calc::Calculated(content.parse()?))
-        } else {
-            Ok(Calc::Normal(s.parse()?))
-        }
-    }
-}
-
-impl ToTokens for Calc {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            Calc::Calculated(inner) => quote!(style::Calc::Calculated(#inner)),
-            Calc::Normal(inner) => quote!(style::Calc::Normal(#inner)),
-        });
-    }
-}
-
-#[test]
-fn test_calc() {
-    for (input, output) in vec![
-        ("calc(10% - 20\"em\")", "calc(10% - 20em)"),
-        ("calc(100% + 5px)", "calc(100% + 5px)"),
-        ("calc(100% - 60px)", "calc(100% - 60px)"),
-    ] {
-        assert_eq!(&syn::parse_str::<Calc>(input).unwrap().to_string(), output);
-    }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub struct CalcSum {
-    pub first: CalcProduct,
-    pub rest: Vec<SumOp>,
-}
-
-impl fmt::Display for CalcSum {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "{}", self.first)?;
-        for op in self.rest.iter() {
-            write!(f, "{}", op)?;
-        }
-        Ok(())
-    }
-}
-
-impl Parse for CalcSum {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let first: CalcProduct = s.parse()?;
-        let mut rest: Vec<SumOp> = vec![];
-        while SumOp::peek(s) {
-            rest.push(s.parse()?);
-        }
-        Ok(CalcSum { first, rest })
-    }
-}
-
-impl ToTokens for CalcSum {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        let first = &self.first;
-        let rest = self.rest.iter();
-        tokens.extend(quote! {
-            style::calc::CalcSum {
-                first: #first,
-                rest: vec![#(#rest,)*]
-            }
-        });
-    }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum SumOp {
-    Add(CalcProduct),
-    Sub(CalcProduct),
-}
-
-impl SumOp {
-    fn peek(s: ParseStream) -> bool {
-        s.peek(Token![+]) || s.peek(Token![-])
-    }
-}
-
-impl fmt::Display for SumOp {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            SumOp::Add(inner) => write!(f, " + {}", inner),
-            SumOp::Sub(inner) => write!(f, " - {}", inner),
-        }
-    }
-}
-
-impl Parse for SumOp {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let lookahead = s.lookahead1();
-        if lookahead.peek(Token![+]) {
-            s.parse::<Token![+]>()?;
-            Ok(SumOp::Add(s.parse()?))
-        } else if lookahead.peek(Token![-]) {
-            s.parse::<Token![-]>()?;
-            Ok(SumOp::Sub(s.parse()?))
-        } else {
-            Err(lookahead.error())
-        }
-    }
-}
-
-impl ToTokens for SumOp {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            SumOp::Add(inner) => quote!(style::SumOp::Add(#inner)),
-            SumOp::Sub(inner) => quote!(style::SumOp::Sub(#inner)),
-        });
-    }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub struct CalcProduct {
-    pub first: CalcValue,
-    pub rest: Vec<ProductOp>,
-}
-
-impl fmt::Display for CalcProduct {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "{}", self.first)?;
-        for op in self.rest.iter() {
-            write!(f, "{}", op)?;
-        }
-        Ok(())
-    }
-}
-
-impl Parse for CalcProduct {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let first: CalcValue = s.parse()?;
-        let mut rest: Vec<ProductOp> = vec![];
-        while ProductOp::peek(s) {
-            rest.push(s.parse()?);
-        }
-        Ok(CalcProduct { first, rest })
-    }
-}
-
-impl ToTokens for CalcProduct {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        let first = &self.first;
-        let rest = self.rest.iter();
-        tokens.extend(quote! {
-            style::calc::CalcProduct {
-                first: #first,
-                rest: vec![#(#rest,)*]
-            }
-        });
-    }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum ProductOp {
-    Mul(CalcValue),
-    // todo Div(Number),
-}
-
-impl ProductOp {
-    pub fn peek(s: ParseStream) -> bool {
-        s.peek(Token![*]) // || s.peek(Token[/])
-    }
-}
-
-impl fmt::Display for ProductOp {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            ProductOp::Mul(inner) => write!(f, "*{}", inner),
-            //ProductOp::Div(inner) => write!(f, "/{}", inner),
-        }
-    }
-}
-
-impl Parse for ProductOp {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let lookahead = s.lookahead1();
-        if lookahead.peek(Token![*]) {
-            s.parse::<Token![*]>()?;
-            Ok(ProductOp::Mul(s.parse()?))
-        /*
-        } else if lookahead.peek(Token![/]) {
-            s.parse::<Token![/]>()?;
-            Ok(ProductOp::Div(s.parse()?))
-        */
-        } else {
-            Err(lookahead.error())
-        }
-    }
-}
-
-impl ToTokens for ProductOp {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            ProductOp::Mul(inner) => quote!(style::ProductOp::Mul(#inner)),
-            //ProductOp::Div(inner) => quote!(style::ProductOp::Div(#inner)),
-        });
-    }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum CalcValue {
-    LengthPercentage(LengthPercentage),
-    // todo more variants
-}
-
-impl Parse for CalcValue {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        Ok(CalcValue::LengthPercentage(s.parse()?))
-    }
-}
-
-impl fmt::Display for CalcValue {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            CalcValue::LengthPercentage(inner) => write!(f, "{}", inner),
-        }
-    }
-}
-
-impl ToTokens for CalcValue {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            CalcValue::LengthPercentage(inner) => {
-                quote!(style::CalcValue::LengthPercentage(#inner))
-            }
-        });
-    }
-}

+ 0 - 1150
packages/core-macro/styles/codegen.rs

@@ -1,1150 +0,0 @@
-use crate::*;
-use proc_macro2::TokenStream;
-use quote::{quote, ToTokens};
-
-macro_rules! path {
-    ($($t:tt)+) => {
-        ::quote::quote!(::style:: $($t)+)
-    };
-}
-
-impl ToTokens for DynamicStyles {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        let parts = self
-            .rules
-            .iter()
-            .filter(|style| !style.is_dummy())
-            .map(|style| style.to_token_stream());
-        tokens.extend(quote! {
-            {
-                let mut styles = style::Styles::new();
-                #(styles.push(#parts);)*
-                styles
-            }
-        })
-    }
-}
-
-impl ToTokens for DynamicStyle {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            DynamicStyle::Dynamic(block) => quote!(#block),
-            DynamicStyle::Literal(lit) => quote!(#lit),
-        })
-    }
-}
-
-impl ToTokens for Style {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        let path = quote!(::style::Style::);
-        tokens.extend(match self {
-            Style::Dummy => quote!(#path Dummy),
-            Style::Unchecked(v) => quote!(#path Unchecked(String::from(#v))),
-
-            Style::AlignContent(v) => quote!(#path AlignContent(#v)),
-            Style::AlignItems(v) => quote!(#path AlignItems(#v)),
-            Style::AlignSelf(v) => quote!(#path AlignSelf(#v)),
-            // all
-            // background
-            Style::BackgroundAttachment(v) => quote!(#path BackgroundAttachment(#v)),
-            Style::BackgroundBlendMode(v) => quote!(#path BackgroundBlendMode(#v)),
-            Style::BackgroundClip(v) => quote!(#path BackgroundClip(#v)),
-            Style::BackgroundColor(v) => quote!(#path BackgroundColor(#v)),
-            Style::BackgroundImage(v) => quote!(#path BackgroundImage(#v)),
-            Style::BackgroundOrigin(v) => quote!(#path BackgroundOrigin(#v)),
-            Style::BackgroundPosition(v) => quote!(#path BackgroundPosition(#v)),
-            Style::BackgroundRepeat(v) => quote!(#path BackgroundRepeat(#v)),
-            Style::BackgroundSize(v) => quote!(#path BackgroundSize(#v)),
-            Style::Border(v) => quote!(#path Border(#v)),
-            Style::BorderBottom(v) => quote!(#path BorderBottom(#v)),
-            Style::BorderBottomColor(v) => quote!(#path BorderBottomColor(#v)),
-            Style::BorderBottomLeftRadius(v) => quote!(#path BorderBottomLeftRadius(#v)),
-            Style::BorderBottomRightRadius(v) => quote!(#path BorderBottomRightRadius(#v)),
-            Style::BorderBottomStyle(v) => quote!(#path BorderBottomStyle(#v)),
-            Style::BorderBottomWidth(v) => quote!(#path BorderBottomWidth(#v)),
-            Style::BorderCollapse(v) => quote!(#path BorderCollapse(#v)),
-            Style::BorderColor(v) => quote!(#path BorderColor(#v)),
-            // border-image
-            // border-image-outset
-            // border-image-repeat
-            // border-image-slice
-            // border-image-source
-            // border-image-width
-            Style::BorderLeft(v) => quote!(#path BorderLeft(#v)),
-            Style::BorderLeftColor(v) => quote!(#path BorderLeftColor(#v)),
-            Style::BorderLeftStyle(v) => quote!(#path BorderLeftStyle(#v)),
-            Style::BorderLeftWidth(v) => quote!(#path BorderLeftWidth(#v)),
-            Style::BorderRadius(v) => quote!(#path BorderRadius(#v)),
-            Style::BorderRight(v) => quote!(#path BorderRight(#v)),
-            Style::BorderRightColor(v) => quote!(#path BorderRightColor(#v)),
-            Style::BorderRightStyle(v) => quote!(#path BorderRightStyle(#v)),
-            Style::BorderRightWidth(v) => quote!(#path BorderRightWidth(#v)),
-            // border-spacing
-            Style::BorderStyle(v) => quote!(#path BorderStyle(#v)),
-            Style::BorderTop(v) => quote!(#path BorderTop(#v)),
-            Style::BorderTopColor(v) => quote!(#path BorderTopColor(#v)),
-            Style::BorderTopLeftRadius(v) => quote!(#path BorderTopLeftRadius(#v)),
-            Style::BorderTopRightRadius(v) => quote!(#path BorderTopRightRadius(#v)),
-            Style::BorderTopStyle(v) => quote!(#path BorderTopStyle(#v)),
-            Style::BorderTopWidth(v) => quote!(#path BorderTopWidth(#v)),
-            Style::BorderWidth(v) => quote!(#path BorderWidth(#v)),
-            Style::Bottom(v) => quote!(#path Bottom(#v)),
-            // box-decoration-break
-            Style::BoxShadow(v) => quote!(#path BoxShadow(#v)),
-            Style::BoxSizing(v) => quote!(#path BoxSizing(#v)),
-            // break-after
-            // break-before
-            // break-inside
-            // caption-side
-            // caret-color
-            Style::Clear(v) => quote!(#path Clear(#v)),
-            // clip
-            // clip-path
-            // clip-rule
-            Style::ColumnCount(v) => quote!(#path ColumnCount(#v)),
-            Style::Color(v) => quote!(#path Color(#v)),
-            // contain
-            // content
-            // counter-increment
-            // counter-reset
-            // cue
-            // cue-after
-            // cue-before
-            Style::Cursor(v) => quote!(#path Cursor(#v)),
-            // direction
-            Style::Display(v) => quote!(#path Display(#v)),
-            // elevation
-            // empty-cells
-            // flex
-            Style::FlexBasis(v) => quote!(#path FlexBasis(#v)),
-            Style::FlexDirection(v) => quote!(#path FlexDirection(#v)),
-            // flex-flow
-            Style::FlexGrow(v) => quote!(#path FlexGrow(#v)),
-            Style::FlexShrink(v) => quote!(#path FlexShrink(#v)),
-            Style::FlexWrap(v) => quote!(#path FlexWrap(#v)),
-            Style::Float(v) => quote!(#path Float(#v)),
-            // font
-            Style::FontFamily(v) => quote!(#path FontFamily(#v)),
-            // font-feature-settings
-            // font-kerning
-            Style::FontSize(v) => quote!(#path FontSize(#v)),
-            // font-size-adjust
-            // font-stretch
-            Style::FontStyle(v) => quote!(#path FontStyle(#v)),
-            // font-synthesis
-            // font-variant
-            // font-variant-caps
-            // font-variant-east-asian
-            // font-variant-ligatures
-            // font-variant-numeric
-            // font-variant-position
-            Style::FontWeight(v) => quote!(#path FontWeight(#v)),
-            // glyph-orientation-vertical
-            // grid
-            // grid-area
-            // grid-auto-columns
-            // grid-auto-flow
-            // grid-auto-rows
-            // grid-column
-            // grid-column-end
-            // grid-column-start
-            // grid-row
-            // grid-row-end
-            // grid-row-start
-            // grid-template
-            // grid-template-areas
-            // grid-template-columns
-            // grid-template-rows
-            Style::Height(v) => quote!(#path Height(#v)),
-            // image-orientation
-            // image-rendering
-            // isolation
-            Style::JustifyContent(v) => quote!(#path JustifyContent(#v)),
-            Style::Left(v) => quote!(#path Left(#v)),
-            // letter-spacing
-            Style::LineHeight(v) => quote!(#path LineHeight(#v)),
-            // list-style
-            // list-style-image
-            // list-style-position
-            Style::ListStyleType(v) => quote!(#path ListStyleType(#v)),
-            Style::Margin(v) => quote!(#path Margin(#v)),
-            Style::MarginBottom(v) => quote!(#path MarginBottom(#v)),
-            Style::MarginLeft(v) => quote!(#path MarginLeft(#v)),
-            Style::MarginRight(v) => quote!(#path MarginRight(#v)),
-            Style::MarginTop(v) => quote!(#path MarginTop(#v)),
-            // mask
-            // mask-border
-            // mask-border-mode
-            // mask-border-outset
-            // mask-border-repeat
-            // mask-border-slice
-            // mask-border-source
-            // mask-border-width
-            // mask-clip
-            // mask-composite
-            // mask-image
-            // mask-mode
-            // mask-origin
-            // mask-position
-            // mask-repeat
-            // mask-size
-            // mask-type
-            Style::MaxHeight(v) => quote!(#path MaxHeight(#v)),
-            Style::MaxWidth(v) => quote!(#path MaxWidth(#v)),
-            Style::MinHeight(v) => quote!(#path MinHeight(#v)),
-            Style::MinWidth(v) => quote!(#path MinWidth(#v)),
-            // mix-blend-mode
-            Style::ObjectFit(v) => quote!(#path ObjectFit(#v)),
-            // object-position
-            // opacity
-            // order
-            // orphans
-            // outline
-            // outline-color
-            // outline-offset
-            // outline-style
-            // outline-width
-            Style::Overflow(v) => quote!(#path Overflow(#v)),
-            Style::OverflowX(v) => quote!(#path OverflowX(#v)),
-            Style::OverflowY(v) => quote!(#path OverflowY(#v)),
-            Style::Padding(v) => quote!(#path Padding(#v)),
-            Style::PaddingBottom(v) => quote!(#path PaddingBottom(#v)),
-            Style::PaddingLeft(v) => quote!(#path PaddingLeft(#v)),
-            Style::PaddingRight(v) => quote!(#path PaddingRight(#v)),
-            Style::PaddingTop(v) => quote!(#path PaddingTop(#v)),
-            // page-break-after
-            // page-break-before
-            // page-break-inside
-            // pause
-            // pause-after
-            // pause-before
-            // pitch
-            // pitch-range
-            // play-during
-            Style::Position(v) => quote!(#path Position(#v)),
-            // quotes
-            Style::Resize(v) => quote!(#path Resize(#v)),
-            // richness
-            Style::Right(v) => quote!(#path Right(#v)),
-            // scroll-margin
-            // scroll-margin-block
-            // scroll-margin-block-end
-            // scroll-margin-block-start
-            // scroll-margin-bottom
-            // scroll-margin-inline
-            // scroll-margin-inline-end
-            // scroll-margin-inline-start
-            // scroll-margin-left
-            // scroll-margin-right
-            // scroll-margin-top
-            // scroll-padding
-            // scroll-padding-block
-            // scroll-padding-block-end
-            // scroll-padding-block-start
-            // scroll-padding-bottom
-            // scroll-padding-inline
-            // scroll-padding-inline-end
-            // scroll-padding-inline-start
-            // scroll-padding-left
-            // scroll-padding-right
-            // scroll-padding-top
-            // scroll-snap-align
-            // scroll-snap-stop
-            // scroll-snap-type
-            // shape-image-threshold
-            // shape-margin
-            // shape-outside
-            // speak
-            // speak-header
-            // speak-numeral
-            // speak-punctuation
-            // speech-rate
-            // stress
-            // table-layout
-            Style::TextAlign(v) => quote!(#path TextAlign(#v)),
-            // text-combine-upright
-            // text-decoration
-            // text-decoration-color
-            // text-decoration-line
-            // text-decoration-style
-            // text-emphasis
-            // text-emphasis-color
-            // text-emphasis-position
-            // text-emphasis-style
-            // text-indent
-            // text-orientation
-            // text-overflow
-            // text-shadow
-            // text-transform
-            // text-underline-position
-            Style::Top(v) => quote!(#path Top(#v)),
-            // transform
-            // transform-box
-            // transform-origin
-            // unicode-bidi
-            // vertical-align
-            // visibility
-            // voice-family
-            // volume
-            // white-space
-            Style::WhiteSpace(v) => quote!(#path WhiteSpace(#v)),
-            Style::Widows(v) => quote!(#path Widows(#v)),
-            Style::Width(v) => quote!(#path Width(#v)),
-            // will-change
-            // word-spacing
-            // writing-mode
-            // z-index
-        });
-    }
-}
-
-impl ToTokens for AlignContent {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            AlignContent::FlexStart => path!(AlignContent::FlexStart),
-            AlignContent::Center => path!(AlignContent::Center),
-            AlignContent::FlexEnd => path!(style::AlignContent::FlexEnd),
-            AlignContent::SpaceAround => path!(AlignContent::SpaceAround),
-            AlignContent::SpaceBetween => path!(AlignContent::SpaceBetween),
-            AlignContent::Stretch => path!(AlignContent::Stretch),
-        });
-    }
-}
-
-impl ToTokens for Cursor {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            Cursor::Auto => path!(Cursor::Auto),
-            Cursor::Default => path!(Cursor::Default),
-            Cursor::None => path!(Cursor::None),
-            Cursor::ContextMenu => path!(Cursor::ContextMenu),
-            Cursor::Help => path!(Cursor::Help),
-            Cursor::Pointer => path!(Cursor::Pointer),
-            Cursor::Progress => path!(Cursor::Progress),
-            Cursor::Wait => path!(Cursor::Wait),
-            Cursor::Cell => path!(Cursor::Cell),
-            Cursor::Crosshair => path!(Cursor::Crosshair),
-            Cursor::Text => path!(Cursor::Text),
-            Cursor::VerticalText => path!(Cursor::VerticalText),
-            Cursor::Alias => path!(Cursor::Alias),
-            Cursor::Copy => path!(Cursor::Copy),
-            Cursor::Move => path!(Cursor::Move),
-            Cursor::NoDrop => path!(Cursor::NoDrop),
-            Cursor::NotAllowed => path!(Cursor::NotAllowed),
-            Cursor::Grab => path!(Cursor::Grab),
-            Cursor::Grabbing => path!(Cursor::Grabbing),
-            Cursor::EResize => path!(Cursor::EResize),
-            Cursor::NResize => path!(Cursor::NResize),
-            Cursor::NEResize => path!(Cursor::NEResize),
-            Cursor::NWResize => path!(Cursor::NWResize),
-            Cursor::SResize => path!(Cursor::SResize),
-            Cursor::SEResize => path!(Cursor::SEResize),
-            Cursor::SWResize => path!(Cursor::SWResize),
-            Cursor::WResize => path!(Cursor::WResize),
-            Cursor::EWResize => path!(Cursor::EWResize),
-            Cursor::NSResize => path!(Cursor::NSResize),
-            Cursor::NESWResize => path!(Cursor::NESWResize),
-            Cursor::NWSEResize => path!(Cursor::NWSEResize),
-            Cursor::ColResize => path!(Cursor::ColResize),
-            Cursor::RowResize => path!(Cursor::RowResize),
-            Cursor::AllScroll => path!(Cursor::AllScroll),
-            Cursor::ZoomIn => path!(Cursor::ZoomIn),
-            Cursor::ZoomOut => path!(Cursor::ZoomOut),
-        })
-    }
-}
-
-impl ToTokens for Display {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            Display::Block => path!(Display::Block),
-            Display::Flex => path!(Display::Flex),
-            Display::Inline => path!(Display::Inline),
-        });
-    }
-}
-
-impl ToTokens for FlexBasis {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            FlexBasis::Width(v) => path!(FlexBasis::Width(#v)),
-            FlexBasis::Content => path!(FlexBasis::Content),
-        });
-    }
-}
-
-impl ToTokens for FlexDirection {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            FlexDirection::Row => path!(FlexDirection::Row),
-            FlexDirection::Column => path!(FlexDirection::Column),
-        });
-    }
-}
-
-impl ToTokens for FlexWrap {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            FlexWrap::Wrap => path!(FlexWrap::Wrap),
-            FlexWrap::Nowrap => path!(FlexWrap::Nowrap),
-        });
-    }
-}
-
-impl ToTokens for AlignItems {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            AlignItems::Normal => path!(AlignItems::Normal),
-            AlignItems::Stretch => path!(AlignItems::Stretch),
-            AlignItems::Center => path!(AlignItems::Center),
-            AlignItems::Start => path!(AlignItems::Start),
-            AlignItems::End => path!(AlignItems::End),
-            AlignItems::FlexStart => path!(AlignItems::FlexStart),
-            AlignItems::FlexEnd => path!(AlignItems::FlexEnd),
-            AlignItems::Baseline => path!(AlignItems::Baseline),
-            AlignItems::FirstBaseline => path!(AlignItems::FirstBaseline),
-            AlignItems::LastBaseline => path!(AlignItems::LastBaseline),
-            AlignItems::SafeCenter => path!(AlignItems::SafeCenter),
-            AlignItems::UnsafeCenter => path!(AlignItems::UnsafeCenter),
-        });
-    }
-}
-
-impl ToTokens for AlignSelf {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            AlignSelf::Auto => path!(AlignSelf::Auto),
-            AlignSelf::Normal => path!(AlignSelf::Normal),
-            AlignSelf::Center => path!(AlignSelf::Center),
-            AlignSelf::Start => path!(AlignSelf::Start),
-            AlignSelf::End => path!(AlignSelf::End),
-            AlignSelf::SelfStart => path!(AlignSelf::SelfStart),
-            AlignSelf::SelfEnd => path!(AlignSelf::SelfEnd),
-            AlignSelf::FlexStart => path!(AlignSelf::FlexStart),
-            AlignSelf::FlexEnd => path!(AlignSelf::FlexEnd),
-            AlignSelf::Baseline => path!(AlignSelf::Baseline),
-            AlignSelf::FirstBaseline => path!(AlignSelf::FirstBaseline),
-            AlignSelf::LastBaseline => path!(AlignSelf::LastBaseline),
-            AlignSelf::Stretch => path!(AlignSelf::Stretch),
-            AlignSelf::SafeCenter => path!(AlignSelf::SafeCenter),
-            AlignSelf::UnsafeCenter => path!(AlignSelf::UnsafeCenter),
-        });
-    }
-}
-
-impl ToTokens for BackgroundAttachment {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            BackgroundAttachment::Scroll => path!(BackgroundAttachment::Scroll),
-            BackgroundAttachment::Fixed => path!(BackgroundAttachment::Fixed),
-            BackgroundAttachment::Local => path!(BackgroundAttachment::Local),
-        })
-    }
-}
-
-impl ToTokens for BlendMode {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            BlendMode::Normal => path!(BlendMode::Normal),
-            BlendMode::Multiply => path!(BlendMode::Multiply),
-            BlendMode::Screen => path!(BlendMode::Screen),
-            BlendMode::Overlay => path!(BlendMode::Overlay),
-            BlendMode::Darken => path!(BlendMode::Darken),
-            BlendMode::Lighten => path!(BlendMode::Lighten),
-            BlendMode::ColorDodge => path!(BlendMode::ColorDodge),
-            BlendMode::ColorBurn => path!(BlendMode::ColorBurn),
-            BlendMode::HardLight => path!(BlendMode::HardLight),
-            BlendMode::SoftLight => path!(BlendMode::SoftLight),
-            BlendMode::Difference => path!(BlendMode::Difference),
-            BlendMode::Exclusion => path!(BlendMode::Exclusion),
-            BlendMode::Hue => path!(BlendMode::Hue),
-            BlendMode::Saturation => path!(BlendMode::Saturation),
-            BlendMode::Color => path!(BlendMode::Color),
-            BlendMode::Luminosity => path!(BlendMode::Luminosity),
-        })
-    }
-}
-
-impl ToTokens for BackgroundBox {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            BackgroundBox::BorderBox => path!(BackgroundBox::BorderBox),
-            BackgroundBox::PaddingBox => path!(BackgroundBox::PaddingBox),
-            BackgroundBox::ContentBox => path!(BackgroundBox::ContentBox),
-        })
-    }
-}
-
-impl ToTokens for BackgroundImage {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            BackgroundImage::None => path!(BackgroundImage::None),
-            BackgroundImage::Url(url) => path!(BackgroundImage::Url(#url)),
-        })
-    }
-}
-
-impl ToTokens for BackgroundPosition {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            BackgroundPosition::Top => path!(BackgroundPosition::Top),
-            BackgroundPosition::Bottom => path!(BackgroundPosition::Bottom),
-            BackgroundPosition::Left => path!(BackgroundPosition::Left),
-            BackgroundPosition::Right => path!(BackgroundPosition::Right),
-            BackgroundPosition::Center => path!(BackgroundPosition::Center),
-        })
-    }
-}
-
-impl ToTokens for BackgroundRepeat {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            BackgroundRepeat::RepeatX => path!(BackgroundRepeat::RepeatX),
-            BackgroundRepeat::RepeatY => path!(BackgroundRepeat::RepeatY),
-            BackgroundRepeat::SingleOrDouble(v) => path!(BackgroundRepeat::SingleOrDouble(#v)),
-        })
-    }
-}
-
-impl ToTokens for BgRepeatPart {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            BgRepeatPart::Repeat => path!(BgRepeatPart::Repeat),
-            BgRepeatPart::Space => path!(BgRepeatPart::Space),
-            BgRepeatPart::Round => path!(BgRepeatPart::Round),
-            BgRepeatPart::NoRepeat => path!(BgRepeatPart::NoRepeat),
-        })
-    }
-}
-
-impl ToTokens for BackgroundSize {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            BackgroundSize::Cover => path!(BackgroundSize::Cover),
-            BackgroundSize::Contain => path!(BackgroundSize::Contain),
-            BackgroundSize::SingleOrDouble(v) => path!(BackgroundSize::SingleOrDouble(#v)),
-        })
-    }
-}
-
-impl ToTokens for BorderCollapse {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            BorderCollapse::Collapse => path!(BorderCollapse::Collapse),
-            BorderCollapse::Separate => path!(BorderCollapse::Separate),
-        })
-    }
-}
-
-impl ToTokens for JustifyContent {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            JustifyContent::FlexStart => path!(JustifyContent::FlexStart),
-            JustifyContent::Center => path!(JustifyContent::Center),
-            JustifyContent::FlexEnd => path!(JustifyContent::FlexEnd),
-            JustifyContent::SpaceAround => path!(JustifyContent::SpaceAround),
-            JustifyContent::SpaceBetween => path!(JustifyContent::SpaceBetween),
-        });
-    }
-}
-
-impl ToTokens for Float {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            Float::None => path!(Float::None),
-            Float::Left => path!(Float::Left),
-            Float::Right => path!(Float::Right),
-            Float::InlineStart => path!(Float::InlineStart),
-            Float::InlineEnd => path!(Float::InlineEnd),
-        })
-    }
-}
-
-impl ToTokens for FontWeight {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            FontWeight::Normal => path!(FontWeight::Normal),
-            FontWeight::Bold => path!(FontWeight::Bold),
-            FontWeight::Lighter => path!(FontWeight::Lighter),
-            FontWeight::Bolder => path!(FontWeight::Bolder),
-            FontWeight::Number(v) => path!(FontWeight::Number(#v)),
-        });
-    }
-}
-
-impl ToTokens for Font {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            Font::Named(inner) => path!(Font::Named(String::from(#inner))),
-            Font::Serif => path!(Font::Serif),
-            Font::SansSerif => path!(Font::SansSerif),
-            Font::Cursive => path!(Font::Cursive),
-            Font::Fantasy => path!(Font::Fantasy),
-            Font::Monospace => path!(Font::Monospace),
-        })
-    }
-}
-
-impl ToTokens for FontSize {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            FontSize::XXSmall => path!(FontSize::XXSmall),
-            FontSize::XSmall => path!(FontSize::XSmall),
-            FontSize::Small => path!(FontSize::Small),
-            FontSize::Medium => path!(FontSize::Medium),
-            FontSize::Large => path!(FontSize::Large),
-            FontSize::XLarge => path!(FontSize::XLarge),
-            FontSize::XXLarge => path!(FontSize::XXLarge),
-            FontSize::XXXLarge => path!(FontSize::XXXLarge),
-            FontSize::Larger => path!(FontSize::Larger),
-            FontSize::Smaller => path!(FontSize::Smaller),
-            FontSize::LengthPercentage(v) => path!(FontSize::LengthPercentage(#v)),
-        });
-    }
-}
-
-impl ToTokens for FontStyle {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            FontStyle::Normal => path!(FontStyle::Normal),
-            FontStyle::Italic => path!(FontStyle::Italic),
-            FontStyle::Oblique => path!(FontStyle::Oblique),
-        });
-    }
-}
-
-impl ToTokens for Border {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        let line_width = match self.line_width {
-            Some(line_width) => quote!(Some(#line_width)),
-            None => quote!(None),
-        };
-        let line_style = match self.line_style {
-            Some(line_style) => quote!(Some(#line_style)),
-            None => quote!(None),
-        };
-        let color = match self.color {
-            Some(color) => quote!(Some(#color)),
-            None => quote!(None),
-        };
-        tokens.extend(quote!(
-            style::Border {
-                line_width: #line_width,
-                line_style: #line_style,
-                color: #color,
-            }
-        ))
-    }
-}
-
-impl ToTokens for BoxShadow {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            BoxShadow::None => path!(BoxShadow::None),
-            BoxShadow::Shadows(list) => path!(BoxShadow::Shadows(#list)),
-        });
-    }
-}
-
-impl ToTokens for BoxSizing {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            BoxSizing::BorderBox => path!(BoxSizing::BorderBox),
-            BoxSizing::ContentBox => path!(BoxSizing::ContentBox),
-        });
-    }
-}
-
-impl ToTokens for Clear {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            Clear::None => path!(Clear::None),
-            Clear::Left => path!(Clear::Left),
-            Clear::Right => path!(Clear::Right),
-            Clear::Both => path!(Clear::Both),
-            Clear::InlineStart => path!(Clear::InlineStart),
-            Clear::InlineEnd => path!(Clear::InlineEnd),
-        })
-    }
-}
-
-impl ToTokens for ColumnCount {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            ColumnCount::Auto => path!(ColumnCount::Auto),
-            ColumnCount::Fixed(v) => path!(ColumnCount::Fixed(#v)),
-        })
-    }
-}
-
-impl ToTokens for Overflow {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            Overflow::Both(v) => path!(Overflow::Both(#v)),
-            Overflow::XY(x, y) => path!(Overflow::XY(#x, #y)),
-        })
-    }
-}
-
-impl ToTokens for OverflowXY {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            OverflowXY::Visible => path!(OverflowXY::Visible),
-            OverflowXY::Hidden => path!(OverflowXY::Hidden),
-            OverflowXY::Clip => path!(OverflowXY::Clip),
-            OverflowXY::Scroll => path!(OverflowXY::Scroll),
-            OverflowXY::Auto => path!(OverflowXY::Auto),
-        })
-    }
-}
-
-impl ToTokens for ObjectFit {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            ObjectFit::Fill => path!(ObjectFit::Fill),
-            ObjectFit::None => path!(ObjectFit::None),
-            ObjectFit::Contain { scale_down } => {
-                path!(ObjectFit::Contain { scale_down: #scale_down })
-            }
-            ObjectFit::Cover { scale_down } => path!(ObjectFit::Cover { scale_down: #scale_down }),
-        })
-    }
-}
-
-impl<T> ToTokens for Rect<T>
-where
-    T: ToTokens,
-{
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            Rect::All(v) => path!(Rect::All(#v)),
-            Rect::VerticalHorizontal(v, h) => path!(Rect::VerticalHorizontal(#v, #h)),
-            Rect::TopHorizontalBottom(t, h, b) => path!(Rect::TopHorizontalBottom(#t, #h, #b)),
-            Rect::TopRightBottomLeft(t, r, b, l) => path!(Rect::TopRightBottomLeft(#t, #r, #b, #l)),
-        });
-    }
-}
-
-impl ToTokens for LengthPercentage {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            LengthPercentage::Length(v) => path!(LengthPercentage::Length(#v)),
-            LengthPercentage::Percentage(v) => path!(LengthPercentage::Percentage(#v)),
-        });
-    }
-}
-
-impl ToTokens for AutoLengthPercentage {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            AutoLengthPercentage::LengthPercentage(v) => {
-                path!(AutoLengthPercentage::LengthPercentage(#v))
-            }
-            AutoLengthPercentage::Auto => path!(AutoLengthPercentage::Auto),
-        });
-    }
-}
-
-impl ToTokens for LineStyle {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            LineStyle::None => path!(LineStyle::None),
-            LineStyle::Hidden => path!(LineStyle::Hidden),
-            LineStyle::Dotted => path!(LineStyle::Dotted),
-            LineStyle::Dashed => path!(LineStyle::Dashed),
-            LineStyle::Solid => path!(LineStyle::Solid),
-            LineStyle::Double => path!(LineStyle::Double),
-            LineStyle::Groove => path!(LineStyle::Groove),
-            LineStyle::Ridge => path!(LineStyle::Ridge),
-            LineStyle::Inset => path!(LineStyle::Inset),
-            LineStyle::Outset => path!(LineStyle::Outset),
-        })
-    }
-}
-
-impl ToTokens for LineWidth {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            LineWidth::Length(length) => path!(LineWidth::Length(#length)),
-            LineWidth::Thin => path!(LineWidth::Thin),
-            LineWidth::Medium => path!(LineWidth::Medium),
-            LineWidth::Thick => path!(LineWidth::Thick),
-        })
-    }
-}
-
-impl ToTokens for LineHeight {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        self.0.to_tokens(tokens)
-    }
-}
-
-impl ToTokens for ListStyleType {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            ListStyleType::Disc => path!(ListStyleType::Disc),
-            ListStyleType::Circle => path!(ListStyleType::Circle),
-            ListStyleType::Square => path!(ListStyleType::Square),
-            ListStyleType::Decimal => path!(ListStyleType::Decimal),
-            ListStyleType::DecimalLeadingZero => path!(ListStyleType::DecimalLeadingZero),
-            ListStyleType::LowerRoman => path!(ListStyleType::LowerRoman),
-            ListStyleType::UpperRoman => path!(ListStyleType::UpperRoman),
-            ListStyleType::LowerGreek => path!(ListStyleType::LowerGreek),
-            ListStyleType::UpperGreek => path!(ListStyleType::UpperGreek),
-            ListStyleType::LowerLatin => path!(ListStyleType::LowerLatin),
-            ListStyleType::UpperLatin => path!(ListStyleType::UpperLatin),
-            ListStyleType::Armenian => path!(ListStyleType::Armenian),
-            ListStyleType::Georgian => path!(ListStyleType::Georgian),
-            ListStyleType::LowerAlpha => path!(ListStyleType::LowerAlpha),
-            ListStyleType::UpperAlpha => path!(ListStyleType::UpperAlpha),
-            ListStyleType::None => path!(ListStyleType::None),
-        })
-    }
-}
-
-impl ToTokens for Position {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            Position::Static => path!(Position::Static),
-            Position::Relative => path!(Position::Relative),
-            Position::Absolute => path!(Position::Absolute),
-            Position::Fixed => path!(Position::Fixed),
-        })
-    }
-}
-
-impl ToTokens for Resize {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            Resize::None => path!(Resize::None),
-            Resize::Both => path!(Resize::Both),
-            Resize::Horizontal => path!(Resize::Horizontal),
-            Resize::Vertical => path!(Resize::Vertical),
-        })
-    }
-}
-
-impl ToTokens for WhiteSpace {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            WhiteSpace::Normal => path!(WhiteSpace::Normal),
-            WhiteSpace::Pre => path!(WhiteSpace::Pre),
-            WhiteSpace::Nowrap => path!(WhiteSpace::Nowrap),
-            WhiteSpace::PreWrap => path!(WhiteSpace::PreWrap),
-            WhiteSpace::PreLine => path!(WhiteSpace::PreLine),
-        })
-    }
-}
-
-impl ToTokens for WidthHeight {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            WidthHeight::Auto => path!(WidthHeight::Auto),
-            WidthHeight::LengthPercentage(v) => path!(WidthHeight::LengthPercentage(#v)),
-            WidthHeight::MinContent => path!(WidthHeight::MinContent),
-            WidthHeight::MaxContent => path!(WidthHeight::MaxContent),
-            WidthHeight::FitContent(v) => path!(WidthHeight::FitContent(#v)),
-        })
-    }
-}
-
-impl ToTokens for MaxWidthHeight {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            MaxWidthHeight::None => path!(MaxWidthHeight::None),
-            MaxWidthHeight::LengthPercentage(v) => path!(MaxWidthHeight::LengthPercentage(#v)),
-            MaxWidthHeight::MinContent => path!(MaxWidthHeight::MinContent),
-            MaxWidthHeight::MaxContent => path!(MaxWidthHeight::MaxContent),
-            MaxWidthHeight::FitContent(v) => path!(MaxWidthHeight::FitContent(#v)),
-        })
-    }
-}
-
-impl ToTokens for Width21 {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            Width21::Auto => path!(Width21::Auto),
-            Width21::LengthPercentage(v) => path!(Width21::LengthPercentage(#v)),
-        })
-    }
-}
-
-impl ToTokens for Shadow {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        let color = match self.color.as_ref() {
-            Some(color) => quote!(Some(#color)),
-            None => quote!(None),
-        };
-        let length = &self.length;
-        let inset = &self.inset;
-        tokens.extend(quote! {
-            style::Shadow {
-                color: #color,
-                length: #length,
-                inset: #inset,
-            }
-        })
-    }
-}
-
-impl ToTokens for ShadowLength {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            ShadowLength::Offsets {
-                vertical,
-                horizontal,
-            } => path!(ShadowLength::Offsets {
-                vertical: #vertical,
-                horizontal: #horizontal,
-            }),
-            ShadowLength::OffsetsBlur {
-                vertical,
-                horizontal,
-                blur,
-            } => path!(ShadowLength::OffsetsBlur {
-                vertical: #vertical,
-                horizontal: #horizontal,
-                blur: #blur,
-            }),
-            ShadowLength::OffsetsBlurSpread {
-                vertical,
-                horizontal,
-                blur,
-                spread,
-            } => path!(ShadowLength::Offsets {
-                vertical: #vertical,
-                horizontal: #horizontal,
-                blur: #blur,
-                spread: #spread,
-            }),
-        })
-    }
-}
-
-impl ToTokens for TextAlign {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            TextAlign::Left => path!(TextAlign::Left),
-            TextAlign::Right => path!(TextAlign::Right),
-            TextAlign::Center => path!(TextAlign::Center),
-            TextAlign::Justify => path!(TextAlign::Justify),
-        });
-    }
-}
-
-impl ToTokens for Length {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            Length::Em(v) => path!(Length::Em(#v)),
-            Length::Ex(v) => path!(Length::Ex(#v)),
-            Length::In(v) => path!(Length::In(#v)),
-            Length::Cm(v) => path!(Length::Cm(#v)),
-            Length::Mm(v) => path!(Length::Mm(#v)),
-            Length::Pt(v) => path!(Length::Pt(#v)),
-            Length::Pc(v) => path!(Length::Pc(#v)),
-            Length::Px(v) => path!(Length::Px(#v)),
-            Length::Zero => path!(Length::Zero),
-        })
-    }
-}
-
-impl ToTokens for Percentage {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        let val = self.0;
-        tokens.extend(path!(Percentage(#val)));
-    }
-}
-
-impl ToTokens for DynamicColor {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            DynamicColor::Dynamic(block) => path!(DynamicColor::Literal(#block)),
-            DynamicColor::Literal(color) => path!(DynamicColor::Literal(#color)),
-        })
-    }
-}
-
-impl ToTokens for Color {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            Color::HexRGB(r, g, b) => path!(Color::HexRGB(#r, #g, #b)),
-            Color::HexRGBA(r, g, b, a) => path!(Color::HexRGB(#r, #g, #b, #a)),
-            Color::HSL(h, s, l) => path!(Color::HSL(#h, #s, #l)),
-            Color::HSLA(h, s, l, a) => path!(Color::HSLA(#h, #s, #l, #a)),
-            Color::IndianRed => path!(Color::IndianRed),
-            Color::LightCoral => path!(Color::LightCoral),
-            Color::Salmon => path!(Color::Salmon),
-            Color::DarkSalmon => path!(Color::DarkSalmon),
-            Color::LightSalmon => path!(Color::LightSalmon),
-            Color::Crimson => path!(Color::Crimson),
-            Color::Red => path!(Color::Red),
-            Color::FireBrick => path!(Color::FireBrick),
-            Color::DarkRed => path!(Color::DarkRed),
-            Color::Pink => path!(Color::Pink),
-            Color::LightPink => path!(Color::LightPink),
-            Color::HotPink => path!(Color::HotPink),
-            Color::DeepPink => path!(Color::DeepPink),
-            Color::MediumVioletRed => path!(Color::MediumVioletRed),
-            Color::PaleVioletRed => path!(Color::PaleVioletRed),
-            Color::Coral => path!(Color::Coral),
-            Color::Tomato => path!(Color::Tomato),
-            Color::OrangeRed => path!(Color::OrangeRed),
-            Color::DarkOrange => path!(Color::DarkOrange),
-            Color::Orange => path!(Color::Orange),
-            Color::Gold => path!(Color::Gold),
-            Color::Yellow => path!(Color::Yellow),
-            Color::LightYellow => path!(Color::LightYellow),
-            Color::LemonChiffon => path!(Color::LemonChiffon),
-            Color::LightGoldenrodYellow => path!(Color::LightGoldenrodYellow),
-            Color::PapayaWhip => path!(Color::PapayaWhip),
-            Color::Moccasin => path!(Color::Moccasin),
-            Color::PeachPuff => path!(Color::PeachPuff),
-            Color::PaleGoldenrod => path!(Color::PaleGoldenrod),
-            Color::Khaki => path!(Color::Khaki),
-            Color::DarkKhaki => path!(Color::DarkKhaki),
-            Color::Lavender => path!(Color::Lavender),
-            Color::Thistle => path!(Color::Thistle),
-            Color::Plum => path!(Color::Plum),
-            Color::Violet => path!(Color::Violet),
-            Color::Orchid => path!(Color::Orchid),
-            Color::Fuchsia => path!(Color::Fuchsia),
-            Color::Magenta => path!(Color::Magenta),
-            Color::MediumOrchid => path!(Color::MediumOrchid),
-            Color::MediumPurple => path!(Color::MediumPurple),
-            Color::RebeccaPurple => path!(Color::RebeccaPurple),
-            Color::BlueViolet => path!(Color::BlueViolet),
-            Color::DarkViolet => path!(Color::DarkViolet),
-            Color::DarkOrchid => path!(Color::DarkOrchid),
-            Color::DarkMagenta => path!(Color::DarkMagenta),
-            Color::Purple => path!(Color::Purple),
-            Color::Indigo => path!(Color::Indigo),
-            Color::SlateBlue => path!(Color::SlateBlue),
-            Color::DarkSlateBlue => path!(Color::DarkSlateBlue),
-            Color::MediumSlateBlue => path!(Color::MediumSlateBlue),
-            Color::GreenYellow => path!(Color::GreenYellow),
-            Color::Chartreuse => path!(Color::Chartreuse),
-            Color::LawnGreen => path!(Color::LawnGreen),
-            Color::Lime => path!(Color::Lime),
-            Color::LimeGreen => path!(Color::LimeGreen),
-            Color::PaleGreen => path!(Color::PaleGreen),
-            Color::LightGreen => path!(Color::LightGreen),
-            Color::MediumSpringGreen => path!(Color::MediumSpringGreen),
-            Color::SpringGreen => path!(Color::SpringGreen),
-            Color::MediumSeaGreen => path!(Color::MediumSeaGreen),
-            Color::SeaGreen => path!(Color::SeaGreen),
-            Color::ForestGreen => path!(Color::ForestGreen),
-            Color::Green => path!(Color::Green),
-            Color::DarkGreen => path!(Color::DarkGreen),
-            Color::YellowGreen => path!(Color::YellowGreen),
-            Color::OliveDrab => path!(Color::OliveDrab),
-            Color::Olive => path!(Color::Olive),
-            Color::DarkOliveGreen => path!(Color::DarkOliveGreen),
-            Color::MediumAquamarine => path!(Color::MediumAquamarine),
-            Color::DarkSeaGreen => path!(Color::DarkSeaGreen),
-            Color::LightSeaGreen => path!(Color::LightSeaGreen),
-            Color::DarkCyan => path!(Color::DarkCyan),
-            Color::Teal => path!(Color::Teal),
-            Color::Aqua => path!(Color::Aqua),
-            Color::Cyan => path!(Color::Cyan),
-            Color::LightCyan => path!(Color::LightCyan),
-            Color::PaleTurquoise => path!(Color::PaleTurquoise),
-            Color::Aquamarine => path!(Color::Aquamarine),
-            Color::Turquoise => path!(Color::Turquoise),
-            Color::MediumTurquoise => path!(Color::MediumTurquoise),
-            Color::DarkTurquoise => path!(Color::DarkTurquoise),
-            Color::CadetBlue => path!(Color::CadetBlue),
-            Color::SteelBlue => path!(Color::SteelBlue),
-            Color::LightSteelBlue => path!(Color::LightSteelBlue),
-            Color::PowderBlue => path!(Color::PowderBlue),
-            Color::LightBlue => path!(Color::LightBlue),
-            Color::SkyBlue => path!(Color::SkyBlue),
-            Color::LightSkyBlue => path!(Color::LightSkyBlue),
-            Color::DeepSkyBlue => path!(Color::DeepSkyBlue),
-            Color::DodgerBlue => path!(Color::DodgerBlue),
-            Color::CornflowerBlue => path!(Color::CornflowerBlue),
-            Color::RoyalBlue => path!(Color::RoyalBlue),
-            Color::Blue => path!(Color::Blue),
-            Color::MediumBlue => path!(Color::MediumBlue),
-            Color::DarkBlue => path!(Color::DarkBlue),
-            Color::Navy => path!(Color::Navy),
-            Color::MidnightBlue => path!(Color::MidnightBlue),
-            Color::Cornsilk => path!(Color::Cornsilk),
-            Color::BlanchedAlmond => path!(Color::BlanchedAlmond),
-            Color::Bisque => path!(Color::Bisque),
-            Color::NavajoWhite => path!(Color::NavajoWhite),
-            Color::Wheat => path!(Color::Wheat),
-            Color::BurlyWood => path!(Color::BurlyWood),
-            Color::Tan => path!(Color::Tan),
-            Color::RosyBrown => path!(Color::RosyBrown),
-            Color::SandyBrown => path!(Color::SandyBrown),
-            Color::Goldenrod => path!(Color::Goldenrod),
-            Color::DarkGoldenrod => path!(Color::DarkGoldenrod),
-            Color::Peru => path!(Color::Peru),
-            Color::Chocolate => path!(Color::Chocolate),
-            Color::SaddleBrown => path!(Color::SaddleBrown),
-            Color::Sienna => path!(Color::Sienna),
-            Color::Brown => path!(Color::Brown),
-            Color::Maroon => path!(Color::Maroon),
-            Color::White => path!(Color::White),
-            Color::Snow => path!(Color::Snow),
-            Color::HoneyDew => path!(Color::HoneyDew),
-            Color::MintCream => path!(Color::MintCream),
-            Color::Azure => path!(Color::Azure),
-            Color::AliceBlue => path!(Color::AliceBlue),
-            Color::GhostWhite => path!(Color::GhostWhite),
-            Color::WhiteSmoke => path!(Color::WhiteSmoke),
-            Color::SeaShell => path!(Color::SeaShell),
-            Color::Beige => path!(Color::Beige),
-            Color::OldLace => path!(Color::OldLace),
-            Color::FloralWhite => path!(Color::FloralWhite),
-            Color::Ivory => path!(Color::Ivory),
-            Color::AntiqueWhite => path!(Color::AntiqueWhite),
-            Color::Linen => path!(Color::Linen),
-            Color::LavenderBlush => path!(Color::LavenderBlush),
-            Color::MistyRose => path!(Color::MistyRose),
-            Color::Gainsboro => path!(Color::Gainsboro),
-            Color::LightGray => path!(Color::LightGray),
-            Color::Silver => path!(Color::Silver),
-            Color::DarkGray => path!(Color::DarkGray),
-            Color::Gray => path!(Color::Gray),
-            Color::DimGray => path!(Color::DimGray),
-            Color::LightSlateGray => path!(Color::LightSlateGray),
-            Color::SlateGray => path!(Color::SlateGray),
-            Color::DarkSlateGray => path!(Color::DarkSlateGray),
-            Color::Black => path!(Color::Black),
-        })
-    }
-}
-
-// Generic containers
-
-impl<T> ToTokens for NonemptyCommaList<T>
-where
-    T: ToTokens,
-{
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        let first = &self.first;
-        let rest = &self.rest;
-        tokens.extend(path! {
-            NonemptyCommaList {
-                first: #first,
-                rest: vec![#(#rest),*],
-            }
-        })
-    }
-}
-
-impl<T> ToTokens for SingleOrDouble<T>
-where
-    T: ToTokens,
-{
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        tokens.extend(match self {
-            SingleOrDouble::Single(t) => path!(SingleOrDouble::Single(#t)),
-            SingleOrDouble::Double { vert, horiz } => path!(SingleOrDouble::Double {
-                vert: #vert,
-                horiz: #horiz,
-            }),
-        })
-    }
-}

+ 0 - 854
packages/core-macro/styles/color.rs

@@ -1,854 +0,0 @@
-use std::fmt;
-
-/// A color that possibly is possibly code, rather than a literal
-#[derive(Debug, Clone, PartialEq)]
-pub enum DynamicColor {
-    Literal(Color),
-    /// The type of the block is not checked here (it is checked by typeck).
-    Dynamic(syn::Block),
-}
-
-impl DynamicColor {
-    pub fn is_dynamic(&self) -> bool {
-        match self {
-            DynamicColor::Dynamic(_) => true,
-            DynamicColor::Literal(_) => false,
-        }
-    }
-}
-
-impl fmt::Display for DynamicColor {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            DynamicColor::Dynamic(_) => Ok(()),
-            DynamicColor::Literal(color) => color.fmt(f),
-        }
-    }
-}
-
-// TODO other color variants.
-#[derive(Debug, Clone, Copy, PartialEq)]
-#[non_exhaustive]
-pub enum Color {
-    HexRGB(u8, u8, u8),
-    HexRGBA(u8, u8, u8, u8),
-    // Invariants: `0 <= .0 < 360`, `0 <= .1 < 100`, `0 <= .2 < 100`.
-    HSL(f64, f64, f64),
-    // Invariants: `0 <= .0 < 360`, `0 <= .1 < 100`, `0 <= .2 < 100`, `0 <= .3 < 1`.
-    HSLA(f64, f64, f64, f64),
-
-    // Red HTML Color Names
-    /// rgb(205, 92, 92)
-    IndianRed,
-    /// rgb(240, 128, 128)
-    LightCoral,
-    /// rgb(250, 128, 114)
-    Salmon,
-    /// rgb(233, 150, 122)
-    DarkSalmon,
-    /// rgb(255, 160, 122)
-    LightSalmon,
-    /// rgb(220, 20, 60)
-    Crimson,
-    /// rgb(255, 0, 0)
-    Red,
-    /// rgb(178, 34, 34)
-    FireBrick,
-    /// rgb(139, 0, 0)
-    DarkRed,
-    // Pink HTML Color Names
-    /// rgb(255, 192, 203)
-    Pink,
-    /// rgb(255, 182, 193)
-    LightPink,
-    /// rgb(255, 105, 180)
-    HotPink,
-    /// rgb(255, 20, 147)
-    DeepPink,
-    /// rgb(199, 21, 133)
-    MediumVioletRed,
-    /// rgb(219, 112, 147)
-    PaleVioletRed,
-    //Orange HTML Color Names
-    // /// rgb(255, 160, 122) redefined
-    // LightSalmon,
-    /// rgb(255, 127, 80)
-    Coral,
-    /// rgb(255, 99, 71)
-    Tomato,
-    /// rgb(255, 69, 0)
-    OrangeRed,
-    /// rgb(255, 140, 0)
-    DarkOrange,
-    /// rgb(255, 165, 0)
-    Orange,
-    // Yellow HTML Color Names
-    /// rgb(255, 215, 0)
-    Gold,
-    /// rgb(255, 255, 0)
-    Yellow,
-    /// rgb(255, 255, 224)
-    LightYellow,
-    /// rgb(255, 250, 205)
-    LemonChiffon,
-    /// rgb(250, 250, 210)
-    LightGoldenrodYellow,
-    /// rgb(255, 239, 213)
-    PapayaWhip,
-    /// rgb(255, 228, 181)
-    Moccasin,
-    /// rgb(255, 218, 185)
-    PeachPuff,
-    /// rgb(238, 232, 170)
-    PaleGoldenrod,
-    /// rgb(240, 230, 140)
-    Khaki,
-    /// rgb(189, 183, 107)
-    DarkKhaki,
-    // Purple HTML Color Names
-    /// rgb(230, 230, 250)
-    Lavender,
-    /// rgb(216, 191, 216)
-    Thistle,
-    /// rgb(221, 160, 221)
-    Plum,
-    /// rgb(238, 130, 238)
-    Violet,
-    /// rgb(218, 112, 214)
-    Orchid,
-    /// rgb(255, 0, 255)
-    Fuchsia,
-    /// rgb(255, 0, 255)
-    Magenta,
-    /// rgb(186, 85, 211)
-    MediumOrchid,
-    /// rgb(147, 112, 219)
-    MediumPurple,
-    /// rgb(102, 51, 153)
-    RebeccaPurple,
-    /// rgb(138, 43, 226)
-    BlueViolet,
-    /// rgb(148, 0, 211)
-    DarkViolet,
-    /// rgb(153, 50, 204)
-    DarkOrchid,
-    /// rgb(139, 0, 139)
-    DarkMagenta,
-    /// rgb(128, 0, 128)
-    Purple,
-    /// rgb(75, 0, 130)
-    Indigo,
-    /// rgb(106, 90, 205)
-    SlateBlue,
-    /// rgb(72, 61, 139)
-    DarkSlateBlue,
-    /// rgb(123, 104, 238)
-    MediumSlateBlue,
-    // Green HTML Color Names
-    /// rgb(173, 255, 47)
-    GreenYellow,
-    /// rgb(127, 255, 0)
-    Chartreuse,
-    /// rgb(124, 252, 0)
-    LawnGreen,
-    /// rgb(0, 255, 0)
-    Lime,
-    /// rgb(50, 205, 50)
-    LimeGreen,
-    /// rgb(152, 251, 152)
-    PaleGreen,
-    /// rgb(144, 238, 144)
-    LightGreen,
-    /// rgb(0, 250, 154)
-    MediumSpringGreen,
-    /// rgb(0, 255, 127)
-    SpringGreen,
-    /// rgb(60, 179, 113)
-    MediumSeaGreen,
-    /// rgb(46, 139, 87)
-    SeaGreen,
-    /// rgb(34, 139, 34)
-    ForestGreen,
-    /// rgb(0, 128, 0)
-    Green,
-    /// rgb(0, 100, 0)
-    DarkGreen,
-    /// rgb(154, 205, 50)
-    YellowGreen,
-    /// rgb(107, 142, 35)
-    OliveDrab,
-    /// rgb(128, 128, 0)
-    Olive,
-    /// rgb(85, 107, 47)
-    DarkOliveGreen,
-    /// rgb(102, 205, 170)
-    MediumAquamarine,
-    /// rgb(143, 188, 139)
-    DarkSeaGreen,
-    /// rgb(32, 178, 170)
-    LightSeaGreen,
-    /// rgb(0, 139, 139)
-    DarkCyan,
-    /// rgb(0, 128, 128)
-    Teal,
-    // Blue HTML Color Names
-    /// rgb(0, 255, 255)
-    Aqua,
-    /// rgb(0, 255, 255)
-    Cyan,
-    /// rgb(224, 255, 255)
-    LightCyan,
-    /// rgb(175, 238, 238)
-    PaleTurquoise,
-    /// rgb(127, 255, 212)
-    Aquamarine,
-    /// rgb(64, 224, 208)
-    Turquoise,
-    /// rgb(72, 209, 204)
-    MediumTurquoise,
-    /// rgb(0, 206, 209)
-    DarkTurquoise,
-    /// rgb(95, 158, 160)
-    CadetBlue,
-    /// rgb(70, 130, 180)
-    SteelBlue,
-    /// rgb(176, 196, 222)
-    LightSteelBlue,
-    /// rgb(176, 224, 230)
-    PowderBlue,
-    /// rgb(173, 216, 230)
-    LightBlue,
-    /// rgb(135, 206, 235)
-    SkyBlue,
-    /// rgb(135, 206, 250)
-    LightSkyBlue,
-    /// rgb(0, 191, 255)
-    DeepSkyBlue,
-    /// rgb(30, 144, 255)
-    DodgerBlue,
-    /// rgb(100, 149, 237)
-    CornflowerBlue,
-    // /// rgb(123, 104, 238) duplicate
-    //MediumSlateBlue,
-    /// rgb(65, 105, 225)
-    RoyalBlue,
-    /// rgb(0, 0, 255)
-    Blue,
-    /// rgb(0, 0, 205)
-    MediumBlue,
-    /// rgb(0, 0, 139)
-    DarkBlue,
-    /// rgb(0, 0, 128)
-    Navy,
-    /// rgb(25, 25, 112)
-    MidnightBlue,
-    // Brown HTML Color Names
-    /// rgb(255, 248, 220)
-    Cornsilk,
-    /// rgb(255, 235, 205)
-    BlanchedAlmond,
-    /// rgb(255, 228, 196)
-    Bisque,
-    /// rgb(255, 222, 173)
-    NavajoWhite,
-    /// rgb(245, 222, 179)
-    Wheat,
-    /// rgb(222, 184, 135)
-    BurlyWood,
-    /// rgb(210, 180, 140)
-    Tan,
-    /// rgb(188, 143, 143)
-    RosyBrown,
-    /// rgb(244, 164, 96)
-    SandyBrown,
-    /// rgb(218, 165, 32)
-    Goldenrod,
-    /// rgb(184, 134, 11)
-    DarkGoldenrod,
-    /// rgb(205, 133, 63)
-    Peru,
-    /// rgb(210, 105, 30)
-    Chocolate,
-    /// rgb(139, 69, 19)
-    SaddleBrown,
-    /// rgb(160, 82, 45)
-    Sienna,
-    /// rgb(165, 42, 42)
-    Brown,
-    /// rgb(128, 0, 0)
-    Maroon,
-    // White HTML Color Names
-    /// rgb(255, 255, 255)
-    White,
-    /// rgb(255, 250, 250)
-    Snow,
-    /// rgb(240, 255, 240)
-    HoneyDew,
-    /// rgb(245, 255, 250)
-    MintCream,
-    /// rgb(240, 255, 255)
-    Azure,
-    /// rgb(240, 248, 255)
-    AliceBlue,
-    /// rgb(248, 248, 255)
-    GhostWhite,
-    /// rgb(245, 245, 245)
-    WhiteSmoke,
-    /// rgb(255, 245, 238)
-    SeaShell,
-    /// rgb(245, 245, 220)
-    Beige,
-    /// rgb(253, 245, 230)
-    OldLace,
-    /// rgb(255, 250, 240)
-    FloralWhite,
-    /// rgb(255, 255, 240)
-    Ivory,
-    /// rgb(250, 235, 215)
-    AntiqueWhite,
-    /// rgb(250, 240, 230)
-    Linen,
-    /// rgb(255, 240, 245)
-    LavenderBlush,
-    /// rgb(255, 228, 225)
-    MistyRose,
-    // Gray HTML Color Names
-    /// rgb(220, 220, 220)
-    Gainsboro,
-    /// rgb(211, 211, 211)
-    LightGray,
-    /// rgb(192, 192, 192)
-    Silver,
-    /// rgb(169, 169, 169)
-    DarkGray,
-    /// rgb(128, 128, 128)
-    Gray,
-    /// rgb(105, 105, 105)
-    DimGray,
-    /// rgb(119, 136, 153)
-    LightSlateGray,
-    /// rgb(112, 128, 144)
-    SlateGray,
-    /// rgb(47, 79, 79)
-    DarkSlateGray,
-    /// rgb(0, 0, 0)
-    Black,
-}
-
-impl Color {
-    // todo similar for others
-    pub fn to_rgb(self) -> Color {
-        use Color::*;
-        match self {
-            HexRGB(r, g, b) => HexRGB(r, g, b),
-            HexRGBA(r, g, b, _) => HexRGB(r, g, b),
-            HSL(h, s, l) => {
-                let s = s * 0.01; // percent conversion
-                let l = l * 0.01; // percent conversion
-                let (r, g, b) = hsl_to_rgb(h, s, l);
-                HexRGB((r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8)
-            }
-            HSLA(h, s, l, _) => Color::to_rgb(HSL(h, s, l)),
-            IndianRed => HexRGB(205, 92, 92),
-            LightCoral => HexRGB(240, 128, 128),
-            Salmon => HexRGB(250, 128, 114),
-            DarkSalmon => HexRGB(233, 150, 122),
-            LightSalmon => HexRGB(255, 160, 122),
-            Crimson => HexRGB(220, 20, 60),
-            Red => HexRGB(255, 0, 0),
-            FireBrick => HexRGB(178, 34, 34),
-            DarkRed => HexRGB(139, 0, 0),
-            Pink => HexRGB(255, 192, 203),
-            LightPink => HexRGB(255, 182, 193),
-            HotPink => HexRGB(255, 105, 180),
-            DeepPink => HexRGB(255, 20, 147),
-            MediumVioletRed => HexRGB(199, 21, 133),
-            PaleVioletRed => HexRGB(219, 112, 147),
-            Coral => HexRGB(255, 127, 80),
-            Tomato => HexRGB(255, 99, 71),
-            OrangeRed => HexRGB(255, 69, 0),
-            DarkOrange => HexRGB(255, 140, 0),
-            Orange => HexRGB(255, 165, 0),
-            Gold => HexRGB(255, 215, 0),
-            Yellow => HexRGB(255, 255, 0),
-            LightYellow => HexRGB(255, 255, 224),
-            LemonChiffon => HexRGB(255, 250, 205),
-            LightGoldenrodYellow => HexRGB(250, 250, 210),
-            PapayaWhip => HexRGB(255, 239, 213),
-            Moccasin => HexRGB(255, 228, 181),
-            PeachPuff => HexRGB(255, 218, 185),
-            PaleGoldenrod => HexRGB(238, 232, 170),
-            Khaki => HexRGB(240, 230, 140),
-            DarkKhaki => HexRGB(189, 183, 107),
-            Lavender => HexRGB(230, 230, 250),
-            Thistle => HexRGB(216, 191, 216),
-            Plum => HexRGB(221, 160, 221),
-            Violet => HexRGB(238, 130, 238),
-            Orchid => HexRGB(218, 112, 214),
-            Fuchsia => HexRGB(255, 0, 255),
-            Magenta => HexRGB(255, 0, 255),
-            MediumOrchid => HexRGB(186, 85, 211),
-            MediumPurple => HexRGB(147, 112, 219),
-            RebeccaPurple => HexRGB(102, 51, 153),
-            BlueViolet => HexRGB(138, 43, 226),
-            DarkViolet => HexRGB(148, 0, 211),
-            DarkOrchid => HexRGB(153, 50, 204),
-            DarkMagenta => HexRGB(139, 0, 139),
-            Purple => HexRGB(128, 0, 128),
-            Indigo => HexRGB(75, 0, 130),
-            SlateBlue => HexRGB(106, 90, 205),
-            DarkSlateBlue => HexRGB(72, 61, 139),
-            MediumSlateBlue => HexRGB(123, 104, 238),
-            GreenYellow => HexRGB(173, 255, 47),
-            Chartreuse => HexRGB(127, 255, 0),
-            LawnGreen => HexRGB(124, 252, 0),
-            Lime => HexRGB(0, 255, 0),
-            LimeGreen => HexRGB(50, 205, 50),
-            PaleGreen => HexRGB(152, 251, 152),
-            LightGreen => HexRGB(144, 238, 144),
-            MediumSpringGreen => HexRGB(0, 250, 154),
-            SpringGreen => HexRGB(0, 255, 127),
-            MediumSeaGreen => HexRGB(60, 179, 113),
-            SeaGreen => HexRGB(46, 139, 87),
-            ForestGreen => HexRGB(34, 139, 34),
-            Green => HexRGB(0, 128, 0),
-            DarkGreen => HexRGB(0, 100, 0),
-            YellowGreen => HexRGB(154, 205, 50),
-            OliveDrab => HexRGB(107, 142, 35),
-            Olive => HexRGB(128, 128, 0),
-            DarkOliveGreen => HexRGB(85, 107, 47),
-            MediumAquamarine => HexRGB(102, 205, 170),
-            DarkSeaGreen => HexRGB(143, 188, 139),
-            LightSeaGreen => HexRGB(32, 178, 170),
-            DarkCyan => HexRGB(0, 139, 139),
-            Teal => HexRGB(0, 128, 128),
-            Aqua => HexRGB(0, 255, 255),
-            Cyan => HexRGB(0, 255, 255),
-            LightCyan => HexRGB(224, 255, 255),
-            PaleTurquoise => HexRGB(175, 238, 238),
-            Aquamarine => HexRGB(127, 255, 212),
-            Turquoise => HexRGB(64, 224, 208),
-            MediumTurquoise => HexRGB(72, 209, 204),
-            DarkTurquoise => HexRGB(0, 206, 209),
-            CadetBlue => HexRGB(95, 158, 160),
-            SteelBlue => HexRGB(70, 130, 180),
-            LightSteelBlue => HexRGB(176, 196, 222),
-            PowderBlue => HexRGB(176, 224, 230),
-            LightBlue => HexRGB(173, 216, 230),
-            SkyBlue => HexRGB(135, 206, 235),
-            LightSkyBlue => HexRGB(135, 206, 250),
-            DeepSkyBlue => HexRGB(0, 191, 255),
-            DodgerBlue => HexRGB(30, 144, 255),
-            CornflowerBlue => HexRGB(100, 149, 237),
-            RoyalBlue => HexRGB(65, 105, 225),
-            Blue => HexRGB(0, 0, 255),
-            MediumBlue => HexRGB(0, 0, 205),
-            DarkBlue => HexRGB(0, 0, 139),
-            Navy => HexRGB(0, 0, 128),
-            MidnightBlue => HexRGB(25, 25, 112),
-            Cornsilk => HexRGB(255, 248, 220),
-            BlanchedAlmond => HexRGB(255, 235, 205),
-            Bisque => HexRGB(255, 228, 196),
-            NavajoWhite => HexRGB(255, 222, 173),
-            Wheat => HexRGB(245, 222, 179),
-            BurlyWood => HexRGB(222, 184, 135),
-            Tan => HexRGB(210, 180, 140),
-            RosyBrown => HexRGB(188, 143, 143),
-            SandyBrown => HexRGB(244, 164, 96),
-            Goldenrod => HexRGB(218, 165, 32),
-            DarkGoldenrod => HexRGB(184, 134, 11),
-            Peru => HexRGB(205, 133, 63),
-            Chocolate => HexRGB(210, 105, 30),
-            SaddleBrown => HexRGB(139, 69, 19),
-            Sienna => HexRGB(160, 82, 45),
-            Brown => HexRGB(165, 42, 42),
-            Maroon => HexRGB(128, 0, 0),
-            White => HexRGB(255, 255, 255),
-            Snow => HexRGB(255, 250, 250),
-            HoneyDew => HexRGB(240, 255, 240),
-            MintCream => HexRGB(245, 255, 250),
-            Azure => HexRGB(240, 255, 255),
-            AliceBlue => HexRGB(240, 248, 255),
-            GhostWhite => HexRGB(248, 248, 255),
-            WhiteSmoke => HexRGB(245, 245, 245),
-            SeaShell => HexRGB(255, 245, 238),
-            Beige => HexRGB(245, 245, 220),
-            OldLace => HexRGB(253, 245, 230),
-            FloralWhite => HexRGB(255, 250, 240),
-            Ivory => HexRGB(255, 255, 240),
-            AntiqueWhite => HexRGB(250, 235, 215),
-            Linen => HexRGB(250, 240, 230),
-            LavenderBlush => HexRGB(255, 240, 245),
-            MistyRose => HexRGB(255, 228, 225),
-            Gainsboro => HexRGB(220, 220, 220),
-            LightGray => HexRGB(211, 211, 211),
-            Silver => HexRGB(192, 192, 192),
-            DarkGray => HexRGB(169, 169, 169),
-            Gray => HexRGB(128, 128, 128),
-            DimGray => HexRGB(105, 105, 105),
-            LightSlateGray => HexRGB(119, 136, 153),
-            SlateGray => HexRGB(112, 128, 144),
-            DarkSlateGray => HexRGB(47, 79, 79),
-            Black => HexRGB(0, 0, 0),
-        }
-    }
-
-    pub fn from_named(name: &str) -> Option<Self> {
-        // todo use a faster search (e.g. hashmap, aho-corasick)
-        use Color::*;
-        Some(match name {
-            "indianred" => IndianRed,
-            "lightcoral" => LightCoral,
-            "salmon" => Salmon,
-            "darksalmon" => DarkSalmon,
-            "lightsalmon" => LightSalmon,
-            "crimson" => Crimson,
-            "red" => Red,
-            "firebrick" => FireBrick,
-            "darkred" => DarkRed,
-            "pink" => Pink,
-            "lightpink" => LightPink,
-            "hotpink" => HotPink,
-            "deeppink" => DeepPink,
-            "mediumvioletred" => MediumVioletRed,
-            "palevioletred" => PaleVioletRed,
-            "coral" => Coral,
-            "tomato" => Tomato,
-            "orangered" => OrangeRed,
-            "darkorange" => DarkOrange,
-            "orange" => Orange,
-            "gold" => Gold,
-            "yellow" => Yellow,
-            "lightyellow" => LightYellow,
-            "lemonchiffon" => LemonChiffon,
-            "lightgoldenrodyellow" => LightGoldenrodYellow,
-            "papayawhip" => PapayaWhip,
-            "Moccasin" => Moccasin,
-            "Peachpuff" => PeachPuff,
-            "palegoldenrod" => PaleGoldenrod,
-            "khaki" => Khaki,
-            "darkkhaki" => DarkKhaki,
-            "lavender" => Lavender,
-            "thistle" => Thistle,
-            "plum" => Plum,
-            "violet" => Violet,
-            "orchid" => Orchid,
-            "fuchsia" => Fuchsia,
-            "magenta" => Magenta,
-            "mediumorchid" => MediumOrchid,
-            "mediumpurple" => MediumPurple,
-            "rebeccapurple" => RebeccaPurple,
-            "blueviolet" => BlueViolet,
-            "darkviolet" => DarkViolet,
-            "darkorchid" => DarkOrchid,
-            "darkmagenta" => DarkMagenta,
-            "purple" => Purple,
-            "indigo" => Indigo,
-            "slateblue" => SlateBlue,
-            "darkslateblue" => DarkSlateBlue,
-            "mediumslateblue" => MediumSlateBlue,
-            "greenyellow" => GreenYellow,
-            "chartreuse" => Chartreuse,
-            "lawngreen" => LawnGreen,
-            "lime" => Lime,
-            "limegreen" => LimeGreen,
-            "palegreen" => PaleGreen,
-            "lightgreen" => LightGreen,
-            "mediumspringgreen" => MediumSpringGreen,
-            "springgreen" => SpringGreen,
-            "mediumseagreen" => MediumSeaGreen,
-            "seagreen" => SeaGreen,
-            "forestgreen" => ForestGreen,
-            "green" => Green,
-            "darkgreen" => DarkGreen,
-            "yellowgreen" => YellowGreen,
-            "olivedrab" => OliveDrab,
-            "olive" => Olive,
-            "darkolivegreen" => DarkOliveGreen,
-            "mediumaquamarine" => MediumAquamarine,
-            "darkseagreen" => DarkSeaGreen,
-            "lightseagreen" => LightSeaGreen,
-            "darkcyan" => DarkCyan,
-            "teal" => Teal,
-            "aqua" => Aqua,
-            "cyan" => Cyan,
-            "lightcyan" => LightCyan,
-            "paleturquoise" => PaleTurquoise,
-            "aquamarine" => Aquamarine,
-            "turquoise" => Turquoise,
-            "mediumturquoise" => MediumTurquoise,
-            "darkturquoise" => DarkTurquoise,
-            "cadetblue" => CadetBlue,
-            "steelblue" => SteelBlue,
-            "lightsteelblue" => LightSteelBlue,
-            "powderblue" => PowderBlue,
-            "lightblue" => LightBlue,
-            "skyblue" => SkyBlue,
-            "lightskyblue" => LightSkyBlue,
-            "deepskyblue" => DeepSkyBlue,
-            "dodgerblue" => DodgerBlue,
-            "cornflowerblue" => CornflowerBlue,
-            "royalblue" => RoyalBlue,
-            "blue" => Blue,
-            "mediumblue" => MediumBlue,
-            "darkblue" => DarkBlue,
-            "navy" => Navy,
-            "midnightblue" => MidnightBlue,
-            "cornsilk" => Cornsilk,
-            "blanchedalmond" => BlanchedAlmond,
-            "bisque" => Bisque,
-            "navajowhite" => NavajoWhite,
-            "wheat" => Wheat,
-            "burlywood" => BurlyWood,
-            "tan" => Tan,
-            "rosybrown" => RosyBrown,
-            "sandybrown" => SandyBrown,
-            "goldenrod" => Goldenrod,
-            "darkgoldenrod" => DarkGoldenrod,
-            "peru" => Peru,
-            "chocolate" => Chocolate,
-            "saddlebrown" => SaddleBrown,
-            "sienna" => Sienna,
-            "brown" => Brown,
-            "maroon" => Maroon,
-            "white" => White,
-            "snow" => Snow,
-            "honeydew" => HoneyDew,
-            "mintcream" => MintCream,
-            "azure" => Azure,
-            "aliceblue" => AliceBlue,
-            "ghostwhite" => GhostWhite,
-            "whitesmoke" => WhiteSmoke,
-            "seashell" => SeaShell,
-            "beige" => Beige,
-            "oldlace" => OldLace,
-            "floralwhite" => FloralWhite,
-            "ivory" => Ivory,
-            "antiquewhite" => AntiqueWhite,
-            "linen" => Linen,
-            "lavenderblush" => LavenderBlush,
-            "mistyrose" => MistyRose,
-            "gainsboro" => Gainsboro,
-            "lightgray" => LightGray,
-            "silver" => Silver,
-            "darkgray" => DarkGray,
-            "gray" => Gray,
-            "dimgray" => DimGray,
-            "lightslategray" => LightSlateGray,
-            "slategray" => SlateGray,
-            "darkslategray" => DarkSlateGray,
-            "black" => Black,
-            _ => return None,
-        })
-    }
-}
-
-impl fmt::Display for Color {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        use Color::*;
-        match self {
-            HexRGB(r, g, b) => write!(f, "#{:02x}{:02x}{:02x}", r, g, b),
-            HexRGBA(r, g, b, a) => write!(f, "#{:02x}{:02x}{:02x}{:02x}", r, g, b, a),
-            HSL(h, s, l) => write!(f, "hsl({}, {}%, {}%)", h, s, l),
-            HSLA(h, s, l, a) => write!(f, "hsla({}, {}%, {}%, {})", h, s, l, a),
-            IndianRed => write!(f, "indianred"),
-            LightCoral => write!(f, "lightcoral"),
-            Salmon => write!(f, "salmon"),
-            DarkSalmon => write!(f, "darksalmon"),
-            LightSalmon => write!(f, "lightsalmon"),
-            Crimson => write!(f, "crimson"),
-            Red => write!(f, "red"),
-            FireBrick => write!(f, "firebrick"),
-            DarkRed => write!(f, "darkred"),
-            Pink => write!(f, "pink"),
-            LightPink => write!(f, "lightpink"),
-            HotPink => write!(f, "hotpink"),
-            DeepPink => write!(f, "deeppink"),
-            MediumVioletRed => write!(f, "mediumvioletred"),
-            PaleVioletRed => write!(f, "palevioletred"),
-            Coral => write!(f, "coral"),
-            Tomato => write!(f, "tomato"),
-            OrangeRed => write!(f, "orangered"),
-            DarkOrange => write!(f, "darkorange"),
-            Orange => write!(f, "orange"),
-            Gold => write!(f, "gold"),
-            Yellow => write!(f, "yellow"),
-            LightYellow => write!(f, "lightyellow"),
-            LemonChiffon => write!(f, "lemonchiffon"),
-            LightGoldenrodYellow => write!(f, "lightgoldenrodyellow"),
-            PapayaWhip => write!(f, "papayawhip"),
-            Moccasin => write!(f, "Moccasin"),
-            PeachPuff => write!(f, "Peachpuff"),
-            PaleGoldenrod => write!(f, "palegoldenrod"),
-            Khaki => write!(f, "khaki"),
-            DarkKhaki => write!(f, "darkkhaki"),
-            Lavender => write!(f, "lavender"),
-            Thistle => write!(f, "thistle"),
-            Plum => write!(f, "plum"),
-            Violet => write!(f, "violet"),
-            Orchid => write!(f, "orchid"),
-            Fuchsia => write!(f, "fuchsia"),
-            Magenta => write!(f, "magenta"),
-            MediumOrchid => write!(f, "mediumorchid"),
-            MediumPurple => write!(f, "mediumpurple"),
-            RebeccaPurple => write!(f, "rebeccapurple"),
-            BlueViolet => write!(f, "blueviolet"),
-            DarkViolet => write!(f, "darkviolet"),
-            DarkOrchid => write!(f, "darkorchid"),
-            DarkMagenta => write!(f, "darkmagenta"),
-            Purple => write!(f, "purple"),
-            Indigo => write!(f, "indigo"),
-            SlateBlue => write!(f, "slateblue"),
-            DarkSlateBlue => write!(f, "darkslateblue"),
-            MediumSlateBlue => write!(f, "mediumslateblue"),
-            GreenYellow => write!(f, "greenyellow"),
-            Chartreuse => write!(f, "chartreuse"),
-            LawnGreen => write!(f, "lawngreen"),
-            Lime => write!(f, "lime"),
-            LimeGreen => write!(f, "limegreen"),
-            PaleGreen => write!(f, "palegreen"),
-            LightGreen => write!(f, "lightgreen"),
-            MediumSpringGreen => write!(f, "mediumspringgreen"),
-            SpringGreen => write!(f, "springgreen"),
-            MediumSeaGreen => write!(f, "mediumseagreen"),
-            SeaGreen => write!(f, "seagreen"),
-            ForestGreen => write!(f, "forestgreen"),
-            Green => write!(f, "green"),
-            DarkGreen => write!(f, "darkgreen"),
-            YellowGreen => write!(f, "yellowgreen"),
-            OliveDrab => write!(f, "olivedrab"),
-            Olive => write!(f, "olive"),
-            DarkOliveGreen => write!(f, "darkolivegreen"),
-            MediumAquamarine => write!(f, "mediumaquamarine"),
-            DarkSeaGreen => write!(f, "darkseagreen"),
-            LightSeaGreen => write!(f, "lightseagreen"),
-            DarkCyan => write!(f, "darkcyan"),
-            Teal => write!(f, "teal"),
-            Aqua => write!(f, "aqua"),
-            Cyan => write!(f, "cyan"),
-            LightCyan => write!(f, "lightcyan"),
-            PaleTurquoise => write!(f, "paleturquoise"),
-            Aquamarine => write!(f, "aquamarine"),
-            Turquoise => write!(f, "turquoise"),
-            MediumTurquoise => write!(f, "mediumturquoise"),
-            DarkTurquoise => write!(f, "darkturquoise"),
-            CadetBlue => write!(f, "cadetblue"),
-            SteelBlue => write!(f, "steelblue"),
-            LightSteelBlue => write!(f, "lightsteelblue"),
-            PowderBlue => write!(f, "powderblue"),
-            LightBlue => write!(f, "lightblue"),
-            SkyBlue => write!(f, "skyblue"),
-            LightSkyBlue => write!(f, "lightskyblue"),
-            DeepSkyBlue => write!(f, "deepskyblue"),
-            DodgerBlue => write!(f, "dodgerblue"),
-            CornflowerBlue => write!(f, "cornflowerblue"),
-            RoyalBlue => write!(f, "royalblue"),
-            Blue => write!(f, "blue"),
-            MediumBlue => write!(f, "mediumblue"),
-            DarkBlue => write!(f, "darkblue"),
-            Navy => write!(f, "navy"),
-            MidnightBlue => write!(f, "midnightblue"),
-            Cornsilk => write!(f, "cornsilk"),
-            BlanchedAlmond => write!(f, "blanchedalmond"),
-            Bisque => write!(f, "bisque"),
-            NavajoWhite => write!(f, "navajowhite"),
-            Wheat => write!(f, "wheat"),
-            BurlyWood => write!(f, "burlywood"),
-            Tan => write!(f, "tan"),
-            RosyBrown => write!(f, "rosybrown"),
-            SandyBrown => write!(f, "sandybrown"),
-            Goldenrod => write!(f, "goldenrod"),
-            DarkGoldenrod => write!(f, "darkgoldenrod"),
-            Peru => write!(f, "peru"),
-            Chocolate => write!(f, "chocolate"),
-            SaddleBrown => write!(f, "saddlebrown"),
-            Sienna => write!(f, "sienna"),
-            Brown => write!(f, "brown"),
-            Maroon => write!(f, "maroon"),
-            White => write!(f, "white"),
-            Snow => write!(f, "snow"),
-            HoneyDew => write!(f, "honeydew"),
-            MintCream => write!(f, "mintcream"),
-            Azure => write!(f, "azure"),
-            AliceBlue => write!(f, "aliceblue"),
-            GhostWhite => write!(f, "ghostwhite"),
-            WhiteSmoke => write!(f, "whitesmoke"),
-            SeaShell => write!(f, "seashell"),
-            Beige => write!(f, "beige"),
-            OldLace => write!(f, "oldlace"),
-            FloralWhite => write!(f, "floralwhite"),
-            Ivory => write!(f, "ivory"),
-            AntiqueWhite => write!(f, "antiquewhite"),
-            Linen => write!(f, "linen"),
-            LavenderBlush => write!(f, "lavenderblush"),
-            MistyRose => write!(f, "mistyrose"),
-            Gainsboro => write!(f, "gainsboro"),
-            LightGray => write!(f, "lightgray"),
-            Silver => write!(f, "silver"),
-            DarkGray => write!(f, "darkgray"),
-            Gray => write!(f, "gray"),
-            DimGray => write!(f, "dimgray"),
-            LightSlateGray => write!(f, "lightslategray"),
-            SlateGray => write!(f, "slategray"),
-            DarkSlateGray => write!(f, "darkslategray"),
-            Black => write!(f, "black"),
-        }
-    }
-}
-
-fn hsl_to_rgb(h: f64, s: f64, l: f64) -> (f64, f64, f64) {
-    debug_assert!(h >= 0.0 && h < 360.0);
-    debug_assert!(s >= 0.0 && s <= 1.0);
-    debug_assert!(l >= 0.0 && l <= 1.0);
-    let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
-    let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
-    let m = l - c * 0.5;
-    let (rp, gp, bp) = if h < 60.0 {
-        (c, x, 0.0)
-    } else if h < 120.0 {
-        (x, c, 0.0)
-    } else if h < 180.0 {
-        (0.0, c, x)
-    } else if h < 240.0 {
-        (0.0, x, c)
-    } else if h < 300.0 {
-        (x, 0.0, c)
-    } else {
-        (c, 0.0, x)
-    };
-    (rp + m, gp + m, bp + m)
-}
-
-pub fn parse_hex(hex: &str) -> Option<Color> {
-    match hex.len() {
-        3 => {
-            let r = u8::from_str_radix(hex.get(0..1)?, 16).ok()?;
-            let g = u8::from_str_radix(hex.get(1..2)?, 16).ok()?;
-            let b = u8::from_str_radix(hex.get(2..3)?, 16).ok()?;
-            // #fff is equivalent to #ffffff
-            Some(Color::HexRGB(r << 4 | r, g << 4 | g, b << 4 | b))
-        }
-        6 => {
-            let r = u8::from_str_radix(hex.get(0..2)?, 16).ok()?;
-            let g = u8::from_str_radix(hex.get(2..4)?, 16).ok()?;
-            let b = u8::from_str_radix(hex.get(4..6)?, 16).ok()?;
-            Some(Color::HexRGB(r, g, b))
-        }
-        8 => {
-            let r = u8::from_str_radix(hex.get(0..2)?, 16).ok()?;
-            let g = u8::from_str_radix(hex.get(2..4)?, 16).ok()?;
-            let b = u8::from_str_radix(hex.get(4..6)?, 16).ok()?;
-            let a = u8::from_str_radix(hex.get(6..8)?, 16).ok()?;
-            Some(Color::HexRGBA(r, g, b, a))
-        }
-        _ => None,
-    }
-}
-
-#[test]
-fn test_color_convert() {
-    let color = Color::HSL(60.0, 0.0, 100.0);
-    assert_eq!(color.to_rgb(), Color::HexRGB(255, 255, 255));
-    let color = Color::HSL(0.0, 100.0, 50.0);
-    assert_eq!(color.to_rgb(), Color::HexRGB(255, 0, 0));
-}

+ 0 - 2044
packages/core-macro/styles/mod.rs

@@ -1,2044 +0,0 @@
-//! A module to type styles.
-// TODO most stuff here is on the stack, but there are a few heap-allocs here and there. It would
-// be good if we could just to allocate them in the bump arena when using bumpalo.
-mod calc;
-mod codegen;
-mod color;
-pub mod string;
-mod syn_parse;
-
-use std::{
-    fmt,
-    ops::{Deref, DerefMut},
-};
-
-pub use calc::*;
-pub use color::{Color, DynamicColor};
-// pub use crate::{
-//     calc::*,
-//     color::{Color, DynamicColor},
-// };
-
-pub struct DynamicStyles {
-    pub rules: Vec<DynamicStyle>,
-}
-
-impl From<Vec<DynamicStyle>> for DynamicStyles {
-    fn from(v: Vec<DynamicStyle>) -> Self {
-        Self { rules: v }
-    }
-}
-
-impl fmt::Display for DynamicStyles {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        for style in self
-            .rules
-            .iter()
-            .filter(|style| !(style.is_dynamic() || style.is_dummy()))
-        {
-            write!(f, "{};", style)?;
-        }
-        Ok(())
-    }
-}
-
-// TODO make container generic over heap (e.g. support bumpalo)
-#[derive(Debug, Clone, PartialEq)]
-pub struct Styles {
-    pub rules: Vec<Style>,
-}
-
-impl Styles {
-    pub fn new() -> Self {
-        Styles { rules: Vec::new() }
-    }
-
-    pub fn add(&mut self, style: Style) {
-        self.rules.push(style);
-    }
-
-    pub fn merge(&mut self, other: Styles) {
-        self.rules.extend(other.rules.into_iter())
-    }
-}
-
-impl From<DynamicStyles> for Styles {
-    fn from(dy: DynamicStyles) -> Self {
-        Styles {
-            rules: dy
-                .rules
-                .into_iter()
-                .filter_map(|dy_sty| match dy_sty {
-                    DynamicStyle::Dynamic(_) => None,
-                    DynamicStyle::Literal(l) => Some(l),
-                })
-                .collect(),
-        }
-    }
-}
-
-impl Deref for Styles {
-    type Target = Vec<Style>;
-    fn deref(&self) -> &Self::Target {
-        &self.rules
-    }
-}
-impl DerefMut for Styles {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.rules
-    }
-}
-
-impl From<Vec<Style>> for Styles {
-    fn from(v: Vec<Style>) -> Self {
-        Self { rules: v }
-    }
-}
-
-impl From<Styles> for Vec<Style> {
-    fn from(v: Styles) -> Self {
-        v.rules
-    }
-}
-
-impl fmt::Display for Styles {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        for style in self.rules.iter().filter(|style| !style.is_dummy()) {
-            write!(f, "{};", style)?;
-        }
-        Ok(())
-    }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum DynamicStyle {
-    /// A literal style.
-    Literal(Style),
-    /// Tokens to pass through directly to codegen.
-    Dynamic(syn::Block),
-}
-
-impl DynamicStyle {
-    pub fn is_dynamic(&self) -> bool {
-        match self {
-            DynamicStyle::Literal(style) => style.is_dynamic(),
-            DynamicStyle::Dynamic(_) => true,
-        }
-    }
-    pub fn is_dummy(&self) -> bool {
-        match self {
-            DynamicStyle::Literal(style) => style.is_dummy(),
-            DynamicStyle::Dynamic(_) => false,
-        }
-    }
-}
-
-impl From<Style> for DynamicStyle {
-    fn from(style: Style) -> Self {
-        DynamicStyle::Literal(style)
-    }
-}
-
-impl fmt::Display for DynamicStyle {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            DynamicStyle::Literal(style) => style.fmt(f),
-            DynamicStyle::Dynamic(_) => Ok(()),
-        }
-    }
-}
-
-/// a `Style` is one of the css key/value pairs, also sometimes known as rules.
-#[derive(Debug, Clone, PartialEq)]
-#[non_exhaustive]
-pub enum Style {
-    /// For when you don't want to include any style at all (useful in expressions like `if`)
-    Dummy,
-    /// For when you want to use some unimplemented css. This is not type checked!
-    Unchecked(String),
-
-    // *From w3 spec:*
-    /// align-content
-    AlignContent(AlignContent),
-    /// align-items
-    AlignItems(AlignItems),
-    /// align-self
-    AlignSelf(AlignSelf),
-    // all - todo when doing global values
-    // background
-    /// background-attachment
-    BackgroundAttachment(BackgroundAttachment),
-    /// background-blend-mode
-    BackgroundBlendMode(NonemptyCommaList<BlendMode>),
-    /// background-clip
-    BackgroundClip(BackgroundBox),
-    /// background-color
-    BackgroundColor(DynamicColor),
-    /// background-image
-    BackgroundImage(NonemptyCommaList<BackgroundImage>),
-    /// background-origin
-    BackgroundOrigin(BackgroundBox),
-    /// background-position
-    BackgroundPosition(BackgroundPosition),
-    /// background-repeat
-    BackgroundRepeat(NonemptyCommaList<BackgroundRepeat>),
-    /// background-size
-    BackgroundSize(BackgroundSize),
-    /// border
-    Border(Border),
-    /// border-bottom
-    BorderBottom(Border),
-    /// border-bottom-color
-    BorderBottomColor(Color),
-    /// border-bottom-left-radius
-    BorderBottomLeftRadius(SingleOrDouble<LengthPercentage>),
-    /// border-bottom-right-radius
-    BorderBottomRightRadius(SingleOrDouble<LengthPercentage>),
-    /// border-bottom-style
-    BorderBottomStyle(LineStyle),
-    /// border-bottom-width
-    BorderBottomWidth(LineWidth),
-    /// border-collapse
-    BorderCollapse(BorderCollapse),
-    /// border-color
-    BorderColor(Rect<Color>),
-    // border-image
-    // border-image-outset
-    // border-image-repeat
-    // border-image-slice
-    // border-image-source
-    // border-image-width
-    /// border-left
-    BorderLeft(Border),
-    /// border-left-color
-    BorderLeftColor(Color),
-    /// border-left-style
-    BorderLeftStyle(LineStyle),
-    /// border-left-width
-    BorderLeftWidth(LineWidth),
-    /// border-radius
-    BorderRadius(BorderRadius),
-    /// border-right
-    BorderRight(Border),
-    /// border-right-color
-    BorderRightColor(Color),
-    /// border-right-style
-    BorderRightStyle(LineStyle),
-    /// border-right-width
-    BorderRightWidth(LineWidth),
-    // border-spacing
-    /// border-style
-    BorderStyle(BorderStyle),
-    /// border-top
-    BorderTop(Border),
-    /// border-top-color
-    BorderTopColor(Color),
-    /// border-top-left-radius
-    BorderTopLeftRadius(SingleOrDouble<LengthPercentage>),
-    /// border-top-right-radius
-    BorderTopRightRadius(SingleOrDouble<LengthPercentage>),
-    /// border-top-style
-    BorderTopStyle(LineStyle),
-    /// border-top-width
-    BorderTopWidth(LineWidth),
-    /// border-width
-    BorderWidth(BorderWidth),
-    /// bottom
-    Bottom(AutoLengthPercentage),
-    // box-decoration-break
-    /// box-shadow
-    BoxShadow(BoxShadow),
-    /// box-sizing
-    BoxSizing(BoxSizing),
-    // break-after
-    // break-before
-    // break-inside
-    // caption-side
-    // caret-color
-    /// clear
-    Clear(Clear),
-    // clip
-    // clip-path
-    // clip-rule
-    /// color
-    Color(DynamicColor),
-    /// column-count (manually added)
-    ColumnCount(ColumnCount),
-    // contain
-    // content
-    // counter-increment
-    // counter-reset
-    // cue
-    // cue-after
-    // cue-before
-    /// cursor
-    Cursor(Cursor),
-    // direction
-    /// display https://www.w3.org/TR/css-display-3/#typedef-display-outside
-    Display(Display),
-    // elevation
-    // empty-cells
-    // flex
-    /// flex-basis
-    FlexBasis(FlexBasis),
-    /// flex-direction
-    FlexDirection(FlexDirection),
-    // flex-flow
-    /// flex-grow
-    FlexGrow(f64),
-    /// flex-shrink
-    FlexShrink(f64),
-    /// flex-wrap
-    FlexWrap(FlexWrap),
-    /// float
-    Float(Float),
-    // font
-    /// font-family
-    FontFamily(FontFamily),
-    // font-feature-settings
-    // font-kerning
-    /// font-size
-    FontSize(FontSize),
-    // font-size-adjust
-    // font-stretch
-    /// font-style
-    FontStyle(FontStyle),
-    // font-synthesis
-    // font-variant
-    // font-variant-caps
-    // font-variant-east-asian
-    // font-variant-ligatures
-    // font-variant-numeric
-    // font-variant-position
-    /// font-weight
-    FontWeight(FontWeight),
-    // glyph-orientation-vertical
-    // grid
-    // grid-area
-    // grid-auto-columns
-    // grid-auto-flow
-    // grid-auto-rows
-    // grid-column
-    // grid-column-end
-    // grid-column-start
-    // grid-row
-    // grid-row-end
-    // grid-row-start
-    // grid-template
-    // grid-template-areas
-    // grid-template-columns
-    // grid-template-rows
-    /// height
-    Height(WidthHeight),
-    // image-orientation
-    // image-rendering
-    // isolation
-    /// justify-content
-    JustifyContent(JustifyContent),
-    /// left
-    Left(AutoLengthPercentage),
-    // letter-spacing
-    /// line-height
-    LineHeight(LineHeight),
-    // list-style
-    // list-style-image
-    // list-style-position
-    /// list-style-type
-    ListStyleType(ListStyleType),
-    /// margin
-    Margin(Margin),
-    /// margin-bottom
-    MarginBottom(MarginWidth),
-    /// margin-left
-    MarginLeft(MarginWidth),
-    /// margin-right
-    MarginRight(MarginWidth),
-    /// margin-top
-    MarginTop(MarginWidth),
-    // mask
-    // mask-border
-    // mask-border-mode
-    // mask-border-outset
-    // mask-border-repeat
-    // mask-border-slice
-    // mask-border-source
-    // mask-border-width
-    // mask-clip
-    // mask-composite
-    // mask-image
-    // mask-mode
-    // mask-origin
-    // mask-position
-    // mask-repeat
-    // mask-size
-    // mask-type
-    /// max-height
-    MaxHeight(MaxWidthHeight),
-    /// max-width
-    MaxWidth(MaxWidthHeight),
-    /// min-height - current implementing CSS2 spec
-    MinHeight(Calc),
-    /// min-width - current implementing CSS2 spec
-    MinWidth(Calc),
-    // mix-blend-mode
-    /// object-fit - https://drafts.csswg.org/css-images-4/#the-object-fit
-    ObjectFit(ObjectFit),
-    // object-position
-    // opacity
-    // order
-    // orphans
-    // outline
-    // outline-color
-    // outline-offset
-    // outline-style
-    // outline-width
-    /// overflow - https://drafts.csswg.org/css-overflow-3/#propdef-overflow
-    Overflow(Overflow),
-    /// overflow-x manually added
-    OverflowX(OverflowXY),
-    /// overflow-y manually added
-    OverflowY(OverflowXY),
-    /// padding
-    Padding(Padding),
-    /// padding-bottom
-    PaddingBottom(PaddingWidth),
-    /// padding-left
-    PaddingLeft(PaddingWidth),
-    /// padding-right
-    PaddingRight(PaddingWidth),
-    /// padding-top
-    PaddingTop(PaddingWidth),
-    // page-break-after
-    // page-break-before
-    // page-break-inside
-    // pause
-    // pause-after
-    // pause-before
-    // pitch
-    // pitch-range
-    // play-during
-    /// position
-    Position(Position),
-    // quotes
-    /// resize
-    Resize(Resize),
-    // richness
-    /// right
-    Right(AutoLengthPercentage),
-    // scroll-margin
-    // scroll-margin-block
-    // scroll-margin-block-end
-    // scroll-margin-block-start
-    // scroll-margin-bottom
-    // scroll-margin-inline
-    // scroll-margin-inline-end
-    // scroll-margin-inline-start
-    // scroll-margin-left
-    // scroll-margin-right
-    // scroll-margin-top
-    // scroll-padding
-    // scroll-padding-block
-    // scroll-padding-block-end
-    // scroll-padding-block-start
-    // scroll-padding-bottom
-    // scroll-padding-inline
-    // scroll-padding-inline-end
-    // scroll-padding-inline-start
-    // scroll-padding-left
-    // scroll-padding-right
-    // scroll-padding-top
-    // scroll-snap-align
-    // scroll-snap-stop
-    // scroll-snap-type
-    // shape-image-threshold
-    // shape-margin
-    // shape-outside
-    // speak
-    // speak-header
-    // speak-numeral
-    // speak-punctuation
-    // speech-rate
-    // stress
-    // table-layout
-    /// text-align
-    TextAlign(TextAlign),
-    // text-combine-upright
-    // text-decoration
-    // text-decoration-color
-    // text-decoration-line
-    // text-decoration-style
-    // text-emphasis
-    // text-emphasis-color
-    // text-emphasis-position
-    // text-emphasis-style
-    // text-indent
-    // text-orientation
-    // text-overflow
-    // text-shadow
-    // text-transform
-    // text-underline-position
-    /// top
-    Top(AutoLengthPercentage),
-    // transform
-    // transform-box
-    // transform-origin
-    // unicode-bidi
-    // vertical-align
-    // visibility
-    // voice-family
-    // volume
-    /// white-space
-    WhiteSpace(WhiteSpace),
-    /// widows
-    Widows(u32),
-    /// width
-    Width(WidthHeight),
-    // will-change
-    // word-spacing
-    // writing-mode
-    // z-index
-}
-
-impl Style {
-    fn is_dummy(&self) -> bool {
-        match self {
-            Style::Dummy => true,
-            _ => false,
-        }
-    }
-
-    fn is_dynamic(&self) -> bool {
-        match self {
-            Style::BackgroundColor(value) => value.is_dynamic(),
-            Style::Color(value) => value.is_dynamic(),
-            _ => false,
-        }
-    }
-}
-
-impl fmt::Display for Style {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Style::Dummy => Ok(()),
-            Style::Unchecked(v) => write!(f, "{}", v),
-
-            Style::AlignContent(v) => write!(f, "align-content:{}", v),
-            Style::AlignItems(v) => write!(f, "align-items:{}", v),
-            Style::AlignSelf(v) => write!(f, "align-self:{}", v),
-            // all - deferred
-            // background
-            Style::BackgroundAttachment(v) => write!(f, "background-attachment:{}", v),
-            Style::BackgroundBlendMode(v) => write!(f, "background-blend-mode:{}", v),
-            Style::BackgroundClip(v) => write!(f, "background-clip:{}", v),
-            Style::BackgroundColor(v) => write!(f, "background-color:{}", v),
-            Style::BackgroundImage(v) => write!(f, "background-image:{}", v),
-            Style::BackgroundOrigin(v) => write!(f, "background-origin:{}", v),
-            Style::BackgroundPosition(v) => write!(f, "background-position:{}", v),
-            Style::BackgroundRepeat(v) => write!(f, "background-repeat:{}", v),
-            Style::BackgroundSize(v) => write!(f, "background-size:{}", v),
-            Style::Border(v) => write!(f, "border:{}", v),
-            Style::BorderBottom(v) => write!(f, "border-bottom:{}", v),
-            Style::BorderBottomColor(v) => write!(f, "border-bottom-color:{}", v),
-            Style::BorderBottomLeftRadius(v) => write!(f, "border-bottom-left-radius:{}", v),
-            Style::BorderBottomRightRadius(v) => write!(f, "border-bottom-right-radius:{}", v),
-            Style::BorderBottomStyle(v) => write!(f, "border-bottom-style:{}", v),
-            Style::BorderBottomWidth(v) => write!(f, "border-bottom-width:{}", v),
-            Style::BorderCollapse(v) => write!(f, "border-collapse:{}", v),
-            Style::BorderColor(v) => write!(f, "border-color:{}", v),
-            // border-image
-            // border-image-outset
-            // border-image-repeat
-            // border-image-slice
-            // border-image-source
-            // border-image-width
-            Style::BorderLeft(v) => write!(f, "border-left:{}", v),
-            Style::BorderLeftColor(v) => write!(f, "border-left-color:{}", v),
-            Style::BorderLeftStyle(v) => write!(f, "border-left-style:{}", v),
-            Style::BorderLeftWidth(v) => write!(f, "border-left-width:{}", v),
-            Style::BorderRadius(v) => write!(f, "border-radius:{}", v),
-            Style::BorderRight(v) => write!(f, "border-right:{}", v),
-            Style::BorderRightColor(v) => write!(f, "border-right-color:{}", v),
-            Style::BorderRightStyle(v) => write!(f, "border-right-style:{}", v),
-            Style::BorderRightWidth(v) => write!(f, "border-right-width:{}", v),
-            // border-spacing
-            Style::BorderStyle(v) => write!(f, "border-style:{}", v),
-            Style::BorderTop(v) => write!(f, "border-top:{}", v),
-            Style::BorderTopColor(v) => write!(f, "border-top-color:{}", v),
-            Style::BorderTopLeftRadius(v) => write!(f, "border-top-left-radius:{}", v),
-            Style::BorderTopRightRadius(v) => write!(f, "border-top-right-radius:{}", v),
-            Style::BorderTopStyle(v) => write!(f, "border-top-style:{}", v),
-            Style::BorderTopWidth(v) => write!(f, "border-top-width:{}", v),
-            Style::BorderWidth(v) => write!(f, "border-width:{}", v),
-            Style::Bottom(v) => write!(f, "bottom:{}", v),
-            // box-decoration-break
-            Style::BoxShadow(v) => write!(f, "box-shadow:{}", v),
-            Style::BoxSizing(v) => write!(f, "box-sizing:{}", v),
-            // break-after
-            // break-before
-            // break-inside
-            // caption-side
-            // caret-color
-            Style::Clear(v) => write!(f, "clear:{}", v),
-            // clip
-            // clip-path
-            // clip-rule
-            Style::Color(v) => write!(f, "color:{}", v),
-            Style::ColumnCount(v) => write!(f, "column-count:{}", v),
-            // contain
-            // content
-            // counter-increment
-            // counter-reset
-            // cue
-            // cue-after
-            // cue-before
-            Style::Cursor(v) => write!(f, "cursor:{}", v),
-            // direction
-            Style::Display(v) => write!(f, "display:{}", v),
-            // elevation
-            // empty-cells
-            // flex
-            Style::FlexBasis(v) => write!(f, "flex-basis:{}", v),
-            Style::FlexDirection(v) => write!(f, "flex-direction:{}", v),
-            // flex-flow
-            Style::FlexGrow(v) => write!(f, "flex-grow:{}", v),
-            Style::FlexShrink(v) => write!(f, "flex-shrink:{}", v),
-            Style::FlexWrap(v) => write!(f, "flex-wrap:{}", v),
-            Style::Float(v) => write!(f, "float:{}", v),
-            // font
-            Style::FontFamily(v) => write!(f, "font-family:{}", v),
-            // font-feature-settings
-            // font-kerning
-            Style::FontSize(v) => write!(f, "font-size:{}", v),
-            // font-size-adjust
-            // font-stretch
-            Style::FontStyle(v) => write!(f, "font-style:{}", v),
-            // font-synthesis
-            // font-variant
-            // font-variant-caps
-            // font-variant-east-asian
-            // font-variant-ligatures
-            // font-variant-numeric
-            // font-variant-position
-            Style::FontWeight(v) => write!(f, "font-weight:{}", v),
-            // glyph-orientation-vertical
-            // grid
-            // grid-area
-            // grid-auto-columns
-            // grid-auto-flow
-            // grid-auto-rows
-            // grid-column
-            // grid-column-end
-            // grid-column-start
-            // grid-row
-            // grid-row-end
-            // grid-row-start
-            // grid-template
-            // grid-template-areas
-            // grid-template-columns
-            // grid-template-rows
-            Style::Height(v) => write!(f, "height:{}", v),
-            // image-orientation
-            // image-rendering
-            // isolation
-            Style::JustifyContent(v) => write!(f, "justify-content:{}", v),
-            // left
-            Style::Left(v) => write!(f, "left:{}", v),
-            // letter-spacing
-            // line-height
-            Style::LineHeight(v) => write!(f, "line-height:{}", v),
-            // list-style
-            // list-style-image
-            // list-style-position
-            Style::ListStyleType(v) => write!(f, "list-style-type:{}", v),
-            Style::Margin(v) => write!(f, "margin:{}", v),
-            Style::MarginBottom(v) => write!(f, "margin-bottom:{}", v),
-            Style::MarginLeft(v) => write!(f, "margin-left:{}", v),
-            Style::MarginRight(v) => write!(f, "margin-right:{}", v),
-            Style::MarginTop(v) => write!(f, "margin-top:{}", v),
-            // mask
-            // mask-border
-            // mask-border-mode
-            // mask-border-outset
-            // mask-border-repeat
-            // mask-border-slice
-            // mask-border-source
-            // mask-border-width
-            // mask-clip
-            // mask-composite
-            // mask-image
-            // mask-mode
-            // mask-origin
-            // mask-position
-            // mask-repeat
-            // mask-size
-            // mask-type
-            Style::MaxHeight(v) => write!(f, "max-height:{}", v),
-            Style::MaxWidth(v) => write!(f, "max-width:{}", v),
-            Style::MinHeight(v) => write!(f, "min-height:{}", v),
-            Style::MinWidth(v) => write!(f, "min-width:{}", v),
-            // mix-blend-mode
-            Style::ObjectFit(v) => write!(f, "object-fit:{}", v),
-            // object-position
-            // opacity
-            // order
-            // orphans
-            // outline
-            // outline-color
-            // outline-offset
-            // outline-style
-            // outline-width
-            Style::Overflow(v) => write!(f, "overflow:{}", v),
-            Style::OverflowX(v) => write!(f, "overflow-x:{}", v),
-            Style::OverflowY(v) => write!(f, "overflow-y:{}", v),
-            Style::Padding(v) => write!(f, "padding:{}", v),
-            Style::PaddingBottom(v) => write!(f, "padding-bottom:{}", v),
-            Style::PaddingLeft(v) => write!(f, "padding-left:{}", v),
-            Style::PaddingRight(v) => write!(f, "padding-right:{}", v),
-            Style::PaddingTop(v) => write!(f, "padding-top:{}", v),
-            // padding-bottom
-            // padding-left
-            // padding-right
-            // padding-top
-            // page-break-after
-            // page-break-before
-            // page-break-inside
-            // pause
-            // pause-after
-            // pause-before
-            // pitch
-            // pitch-range
-            // play-during
-            Style::Position(v) => write!(f, "position:{}", v),
-            // uotes
-            Style::Resize(v) => write!(f, "resize:{}", v),
-            // richness
-            Style::Right(v) => write!(f, "right:{}", v),
-            // scroll-margin
-            // scroll-margin-block
-            // scroll-margin-block-end
-            // scroll-margin-block-start
-            // scroll-margin-bottom
-            // scroll-margin-inline
-            // scroll-margin-inline-end
-            // scroll-margin-inline-start
-            // scroll-margin-left
-            // scroll-margin-right
-            // scroll-margin-top
-            // scroll-padding
-            // scroll-padding-block
-            // scroll-padding-block-end
-            // scroll-padding-block-start
-            // scroll-padding-bottom
-            // scroll-padding-inline
-            // scroll-padding-inline-end
-            // scroll-padding-inline-start
-            // scroll-padding-left
-            // scroll-padding-right
-            // scroll-padding-top
-            // scroll-snap-align
-            // scroll-snap-stop
-            // scroll-snap-type
-            // shape-image-threshold
-            // shape-margin
-            // shape-outside
-            // speak
-            // speak-header
-            // speak-numeral
-            // speak-punctuation
-            // speech-rate
-            // stress
-            // table-layout
-            Style::TextAlign(v) => write!(f, "text-align:{}", v),
-            // text-combine-upright
-            // text-decoration
-            // text-decoration-color
-            // text-decoration-line
-            // text-decoration-style
-            // text-emphasis
-            // text-emphasis-color
-            // text-emphasis-position
-            // text-emphasis-style
-            // text-indent
-            // text-orientation
-            // text-overflow
-            // text-shadow
-            // text-transform
-            // text-underline-position
-            // top
-            Style::Top(v) => write!(f, "top:{}", v),
-            // transform
-            // transform-box
-            // transform-origin
-            // unicode-bidi
-            // vertical-align
-            // visibility
-            // voice-family
-            // volume
-            Style::WhiteSpace(v) => write!(f, "white-space:{}", v),
-            Style::Widows(v) => write!(f, "widows:{}", v),
-            Style::Width(v) => write!(f, "width:{}", v),
-            // will-change
-            // word-spacing
-            // writing-mode
-            // z-index
-        }
-    }
-}
-
-/// https://www.w3.org/TR/css-flexbox-1/#propdef-justify-content
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum AlignContent {
-    FlexStart,
-    Center,
-    FlexEnd,
-    SpaceBetween,
-    SpaceAround,
-    Stretch,
-}
-
-impl Default for AlignContent {
-    fn default() -> Self {
-        AlignContent::Stretch
-    }
-}
-
-impl fmt::Display for AlignContent {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            AlignContent::FlexStart => write!(f, "flex-start"),
-            AlignContent::Center => write!(f, "center"),
-            AlignContent::FlexEnd => write!(f, "flex-end"),
-            AlignContent::SpaceAround => write!(f, "space-around"),
-            AlignContent::SpaceBetween => write!(f, "space-between"),
-            AlignContent::Stretch => write!(f, "stretch"),
-        }
-    }
-}
-
-/// https://developer.mozilla.org/en-US/docs/Web/CSS/align-items
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum AlignItems {
-    Normal,
-    Stretch,
-    Center,
-    Start,
-    End,
-    FlexStart,
-    FlexEnd,
-    Baseline,
-    FirstBaseline,
-    LastBaseline,
-    SafeCenter,
-    UnsafeCenter,
-}
-
-impl fmt::Display for AlignItems {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            AlignItems::Normal => write!(f, "normal"),
-            AlignItems::Stretch => write!(f, "stretch"),
-            AlignItems::Center => write!(f, "center"),
-            AlignItems::Start => write!(f, "start"),
-            AlignItems::End => write!(f, "end"),
-            AlignItems::FlexStart => write!(f, "flex-start"),
-            AlignItems::FlexEnd => write!(f, "flex-end"),
-            AlignItems::Baseline => write!(f, "baseline"),
-            AlignItems::FirstBaseline => write!(f, "first baseline"),
-            AlignItems::LastBaseline => write!(f, "last baseline"),
-            AlignItems::SafeCenter => write!(f, "safe center"),
-            AlignItems::UnsafeCenter => write!(f, "unsafe center"),
-        }
-    }
-}
-
-/// https://developer.mozilla.org/en-US/docs/Web/CSS/align-self
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum AlignSelf {
-    Auto,
-    Normal,
-    Center,
-    Start,
-    End,
-    SelfStart,
-    SelfEnd,
-    FlexStart,
-    FlexEnd,
-    Baseline,
-    FirstBaseline,
-    LastBaseline,
-    Stretch,
-    SafeCenter,
-    UnsafeCenter,
-}
-
-impl fmt::Display for AlignSelf {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            AlignSelf::Auto => write!(f, "auto"),
-            AlignSelf::Normal => write!(f, "normal"),
-            AlignSelf::Center => write!(f, "center"),
-            AlignSelf::Start => write!(f, "start"),
-            AlignSelf::End => write!(f, "end"),
-            AlignSelf::SelfStart => write!(f, "self-start"),
-            AlignSelf::SelfEnd => write!(f, "self-end"),
-            AlignSelf::FlexStart => write!(f, "flex-start"),
-            AlignSelf::FlexEnd => write!(f, "flex-end"),
-            AlignSelf::Baseline => write!(f, "baseline"),
-            AlignSelf::FirstBaseline => write!(f, "first baseline"),
-            AlignSelf::LastBaseline => write!(f, "last baseline"),
-            AlignSelf::Stretch => write!(f, "stretch"),
-            AlignSelf::SafeCenter => write!(f, "safe center"),
-            AlignSelf::UnsafeCenter => write!(f, "unsafe center"),
-        }
-    }
-}
-
-/// https://developer.mozilla.org/en-US/docs/Web/CSS/background-attachment
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum BackgroundAttachment {
-    Scroll,
-    Fixed,
-    Local,
-}
-
-impl fmt::Display for BackgroundAttachment {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            BackgroundAttachment::Scroll => write!(f, "scroll"),
-            BackgroundAttachment::Fixed => write!(f, "fixed"),
-            BackgroundAttachment::Local => write!(f, "local"),
-        }
-    }
-}
-
-/// https://developer.mozilla.org/en-US/docs/Web/CSS/background-blend-mode
-#[derive(Debug, Clone, PartialEq)]
-pub enum BlendMode {
-    Normal,
-    Multiply,
-    Screen,
-    Overlay,
-    Darken,
-    Lighten,
-    ColorDodge,
-    ColorBurn,
-    HardLight,
-    SoftLight,
-    Difference,
-    Exclusion,
-    Hue,
-    Saturation,
-    Color,
-    Luminosity,
-}
-
-impl fmt::Display for BlendMode {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            BlendMode::Normal => write!(f, "normal"),
-            BlendMode::Multiply => write!(f, "multiply"),
-            BlendMode::Screen => write!(f, "screen"),
-            BlendMode::Overlay => write!(f, "overlay"),
-            BlendMode::Darken => write!(f, "darken"),
-            BlendMode::Lighten => write!(f, "lighten"),
-            BlendMode::ColorDodge => write!(f, "color-dodge"),
-            BlendMode::ColorBurn => write!(f, "color-burn"),
-            BlendMode::HardLight => write!(f, "hard-light"),
-            BlendMode::SoftLight => write!(f, "soft-light"),
-            BlendMode::Difference => write!(f, "difference"),
-            BlendMode::Exclusion => write!(f, "exclusion"),
-            BlendMode::Hue => write!(f, "hue"),
-            BlendMode::Saturation => write!(f, "saturation"),
-            BlendMode::Color => write!(f, "color"),
-            BlendMode::Luminosity => write!(f, "luminosity"),
-        }
-    }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum BackgroundBox {
-    BorderBox,
-    PaddingBox,
-    ContentBox,
-}
-
-impl fmt::Display for BackgroundBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            BackgroundBox::BorderBox => write!(f, "border-box"),
-            BackgroundBox::PaddingBox => write!(f, "padding-box"),
-            BackgroundBox::ContentBox => write!(f, "content-box"),
-        }
-    }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum BackgroundImage {
-    None,
-    Url(String),
-    // TODO other variants
-}
-
-impl fmt::Display for BackgroundImage {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            BackgroundImage::None => write!(f, "none"),
-            BackgroundImage::Url(url) => write!(f, "url({})", url),
-        }
-    }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum BackgroundPosition {
-    Top,
-    Bottom,
-    Left,
-    Right,
-    Center,
-    // TODO other variants
-}
-
-impl fmt::Display for BackgroundPosition {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            BackgroundPosition::Top => write!(f, "top"),
-            BackgroundPosition::Left => write!(f, "left"),
-            BackgroundPosition::Bottom => write!(f, "bottom"),
-            BackgroundPosition::Right => write!(f, "right"),
-            BackgroundPosition::Center => write!(f, "center"),
-        }
-    }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum BackgroundRepeat {
-    RepeatX,
-    RepeatY,
-    SingleOrDouble(SingleOrDouble<BgRepeatPart>),
-}
-
-impl fmt::Display for BackgroundRepeat {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            BackgroundRepeat::RepeatX => write!(f, "repeat-x"),
-            BackgroundRepeat::RepeatY => write!(f, "repeat-y"),
-            BackgroundRepeat::SingleOrDouble(v) => v.fmt(f),
-        }
-    }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum BgRepeatPart {
-    Repeat,
-    Space,
-    Round,
-    NoRepeat,
-}
-
-impl fmt::Display for BgRepeatPart {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            BgRepeatPart::Repeat => write!(f, "repeat"),
-            BgRepeatPart::Space => write!(f, "space"),
-            BgRepeatPart::Round => write!(f, "round"),
-            BgRepeatPart::NoRepeat => write!(f, "no-repeat"),
-        }
-    }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum BackgroundSize {
-    Cover,
-    Contain,
-    SingleOrDouble(SingleOrDouble<AutoLengthPercentage>),
-}
-
-impl fmt::Display for BackgroundSize {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            BackgroundSize::Cover => write!(f, "cover"),
-            BackgroundSize::Contain => write!(f, "contain"),
-            BackgroundSize::SingleOrDouble(v) => v.fmt(f),
-        }
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub struct Border {
-    pub line_width: Option<LineWidth>,
-    pub line_style: Option<LineStyle>,
-    pub color: Option<Color>,
-}
-
-impl Border {
-    fn new() -> Self {
-        Border {
-            line_width: None,
-            line_style: None,
-            color: None,
-        }
-    }
-
-    fn is_full(&self) -> bool {
-        match (&self.line_width, &self.line_style, &self.color) {
-            (Some(_), Some(_), Some(_)) => true,
-            _ => false,
-        }
-    }
-
-    fn has_line_width(&self) -> bool {
-        self.line_width.is_some()
-    }
-    fn has_line_style(&self) -> bool {
-        self.line_style.is_some()
-    }
-    fn has_color(&self) -> bool {
-        self.color.is_some()
-    }
-}
-
-impl fmt::Display for Border {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        fn space(yes: bool) -> &'static str {
-            if yes {
-                " "
-            } else {
-                ""
-            }
-        }
-        let mut cont = false;
-        if let Some(line_width) = self.line_width {
-            write!(f, "{}", line_width)?;
-            cont = true;
-        }
-        if let Some(line_style) = self.line_style {
-            write!(f, "{}{}", space(cont), line_style)?;
-            cont = true;
-        }
-        if let Some(color) = self.color {
-            write!(f, "{}{}", space(cont), color)?;
-        }
-        Ok(())
-    }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum BorderCollapse {
-    Collapse,
-    Separate,
-}
-
-impl fmt::Display for BorderCollapse {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            BorderCollapse::Collapse => write!(f, "collapse"),
-            BorderCollapse::Separate => write!(f, "separate"),
-        }
-    }
-}
-
-pub type BorderRadius = Calc;
-
-pub type BorderStyle = Rect<LineStyle>;
-
-pub type BorderWidth = Rect<LineWidth>;
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum BoxShadow {
-    None,
-    Shadows(NonemptyCommaList<Shadow>),
-}
-
-impl fmt::Display for BoxShadow {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            BoxShadow::None => f.write_str("none"),
-            BoxShadow::Shadows(list) => write!(f, "{}", list),
-        }
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum BoxSizing {
-    BorderBox,
-    ContentBox,
-}
-
-impl fmt::Display for BoxSizing {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            BoxSizing::BorderBox => f.write_str("border-box"),
-            BoxSizing::ContentBox => f.write_str("content-box"),
-        }
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum Clear {
-    None,
-    Left,
-    Right,
-    Both,
-    InlineStart,
-    InlineEnd,
-}
-
-impl fmt::Display for Clear {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Clear::None => f.write_str("none"),
-            Clear::Left => f.write_str("left"),
-            Clear::Right => f.write_str("right"),
-            Clear::Both => f.write_str("both"),
-            Clear::InlineStart => f.write_str("inline-start"),
-            Clear::InlineEnd => f.write_str("inline-end"),
-        }
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum ColumnCount {
-    Auto,
-    Fixed(u32),
-}
-
-impl fmt::Display for ColumnCount {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            ColumnCount::Auto => f.write_str("auto"),
-            ColumnCount::Fixed(inner) => write!(f, "{}", inner),
-        }
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum Cursor {
-    // todo url
-    Auto,
-    Default,
-    None,
-    ContextMenu,
-    Help,
-    Pointer,
-    Progress,
-    Wait,
-    Cell,
-    Crosshair,
-    Text,
-    VerticalText,
-    Alias,
-    Copy,
-    Move,
-    NoDrop,
-    NotAllowed,
-    Grab,
-    Grabbing,
-    EResize,
-    NResize,
-    NEResize,
-    NWResize,
-    SResize,
-    SEResize,
-    SWResize,
-    WResize,
-    EWResize,
-    NSResize,
-    NESWResize,
-    NWSEResize,
-    ColResize,
-    RowResize,
-    AllScroll,
-    ZoomIn,
-    ZoomOut,
-}
-
-impl fmt::Display for Cursor {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Cursor::Auto => f.write_str("auto"),
-            Cursor::Default => f.write_str("default"),
-            Cursor::None => f.write_str("none"),
-            Cursor::ContextMenu => f.write_str("context-menu"),
-            Cursor::Help => f.write_str("help"),
-            Cursor::Pointer => f.write_str("pointer"),
-            Cursor::Progress => f.write_str("progress"),
-            Cursor::Wait => f.write_str("wait"),
-            Cursor::Cell => f.write_str("cell"),
-            Cursor::Crosshair => f.write_str("crosshair"),
-            Cursor::Text => f.write_str("text"),
-            Cursor::VerticalText => f.write_str("vertical-text"),
-            Cursor::Alias => f.write_str("alias"),
-            Cursor::Copy => f.write_str("copy"),
-            Cursor::Move => f.write_str("move"),
-            Cursor::NoDrop => f.write_str("no-drop"),
-            Cursor::NotAllowed => f.write_str("not-allowed"),
-            Cursor::Grab => f.write_str("grab"),
-            Cursor::Grabbing => f.write_str("grabbing"),
-            Cursor::EResize => f.write_str("e-resize"),
-            Cursor::NResize => f.write_str("n-resize"),
-            Cursor::NEResize => f.write_str("ne-resize"),
-            Cursor::NWResize => f.write_str("nw-resize"),
-            Cursor::SResize => f.write_str("s-resize"),
-            Cursor::SEResize => f.write_str("se-resize"),
-            Cursor::SWResize => f.write_str("sw-resize"),
-            Cursor::WResize => f.write_str("w-resize"),
-            Cursor::EWResize => f.write_str("ew-resize"),
-            Cursor::NSResize => f.write_str("ns-resize"),
-            Cursor::NESWResize => f.write_str("nesw-resize"),
-            Cursor::NWSEResize => f.write_str("nwse-resize"),
-            Cursor::ColResize => f.write_str("col-resize"),
-            Cursor::RowResize => f.write_str("row-resize"),
-            Cursor::AllScroll => f.write_str("all-scroll"),
-            Cursor::ZoomIn => f.write_str("zoom-in"),
-            Cursor::ZoomOut => f.write_str("zoom-out"),
-        }
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum Display {
-    Block,
-    Flex,
-    Inline,
-    // todo incomplete
-}
-
-impl fmt::Display for Display {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Display::Block => f.write_str("block"),
-            Display::Flex => f.write_str("flex"),
-            Display::Inline => f.write_str("inline"),
-        }
-    }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum FlexBasis {
-    Width(Width21),
-    Content,
-}
-
-impl fmt::Display for FlexBasis {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            FlexBasis::Width(v) => fmt::Display::fmt(v, f),
-            FlexBasis::Content => f.write_str("content"),
-        }
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum FlexDirection {
-    Row,
-    Column,
-}
-
-impl fmt::Display for FlexDirection {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            FlexDirection::Row => f.write_str("row"),
-            FlexDirection::Column => f.write_str("column"),
-        }
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum FlexWrap {
-    Wrap,
-    Nowrap,
-}
-
-impl fmt::Display for FlexWrap {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            FlexWrap::Wrap => write!(f, "wrap"),
-            FlexWrap::Nowrap => write!(f, "nowrap"),
-        }
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum Float {
-    None,
-    Left,
-    Right,
-    InlineStart,
-    InlineEnd,
-}
-
-impl fmt::Display for Float {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Float::None => f.write_str("inline-end"),
-            Float::Left => f.write_str("left"),
-            Float::Right => f.write_str("right"),
-            Float::InlineStart => f.write_str("inline-start"),
-            Float::InlineEnd => f.write_str("inline-end"),
-        }
-    }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum Font {
-    // todo escape when `Display`ing
-    Named(String),
-    Serif,
-    SansSerif,
-    Cursive,
-    Fantasy,
-    Monospace,
-}
-
-impl fmt::Display for Font {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Font::Named(inner) => write!(f, "\"{}\"", inner),
-            Font::Serif => write!(f, "serif"),
-            Font::SansSerif => write!(f, "sans-serif"),
-            Font::Cursive => write!(f, "cursive"),
-            Font::Fantasy => write!(f, "fantasy"),
-            Font::Monospace => write!(f, "monospace"),
-        }
-    }
-}
-
-pub type FontFamily = NonemptyCommaList<Font>;
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum FontSize {
-    XXSmall,
-    XSmall,
-    Small,
-    Medium,
-    Large,
-    XLarge,
-    XXLarge,
-    XXXLarge,
-    Larger,
-    Smaller,
-    LengthPercentage(Calc),
-}
-
-impl fmt::Display for FontSize {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            FontSize::XXSmall => f.write_str("xx-small"),
-            FontSize::XSmall => f.write_str("x-small"),
-            FontSize::Small => f.write_str("small"),
-            FontSize::Medium => f.write_str("medium"),
-            FontSize::Large => f.write_str("large"),
-            FontSize::XLarge => f.write_str("x-large"),
-            FontSize::XXLarge => f.write_str("xx-large"),
-            FontSize::XXXLarge => f.write_str("xxx-large"),
-            FontSize::Larger => f.write_str("larger"),
-            FontSize::Smaller => f.write_str("smaller"),
-            FontSize::LengthPercentage(v) => fmt::Display::fmt(v, f),
-        }
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum FontStyle {
-    Normal,
-    Italic,
-    Oblique,
-}
-
-impl fmt::Display for FontStyle {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            FontStyle::Normal => f.write_str("normal"),
-            FontStyle::Italic => f.write_str("italic"),
-            FontStyle::Oblique => f.write_str("oblique"),
-        }
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum FontWeight {
-    Normal,
-    Bold,
-    Lighter,
-    Bolder,
-    /// Between 1 and 1000
-    Number(f64),
-}
-
-impl fmt::Display for FontWeight {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            FontWeight::Normal => f.write_str("normal"),
-            FontWeight::Bold => f.write_str("bold"),
-            FontWeight::Lighter => f.write_str("lighter"),
-            FontWeight::Bolder => f.write_str("bolder"),
-            FontWeight::Number(v) => fmt::Display::fmt(v, f),
-        }
-    }
-}
-
-/// https://www.w3.org/TR/css-flexbox-1/#propdef-justify-content
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum JustifyContent {
-    FlexStart,
-    Center,
-    FlexEnd,
-    SpaceBetween,
-    SpaceAround,
-}
-
-impl fmt::Display for JustifyContent {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            JustifyContent::FlexStart => write!(f, "flex-start"),
-            JustifyContent::Center => write!(f, "center"),
-            JustifyContent::FlexEnd => write!(f, "flex-end"),
-            JustifyContent::SpaceAround => write!(f, "space-around"),
-            JustifyContent::SpaceBetween => write!(f, "space-between"),
-        }
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum Length {
-    Em(f64),
-    Ex(f64),
-    In(f64),
-    Cm(f64),
-    Mm(f64),
-    Pt(f64),
-    Pc(f64),
-    Px(f64),
-    Zero,
-}
-
-impl fmt::Display for Length {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Length::Em(val) => write!(f, "{}em", val),
-            Length::Ex(val) => write!(f, "{}ex", val),
-            Length::In(val) => write!(f, "{}in", val),
-            Length::Cm(val) => write!(f, "{}cm", val),
-            Length::Mm(val) => write!(f, "{}mm", val),
-            Length::Pt(val) => write!(f, "{}pt", val),
-            Length::Pc(val) => write!(f, "{}pc", val),
-            Length::Px(val) => write!(f, "{}px", val),
-            Length::Zero => write!(f, "0"),
-        }
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum LengthPercentage {
-    Length(Length),
-    Percentage(Percentage),
-}
-
-impl fmt::Display for LengthPercentage {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            LengthPercentage::Length(v) => fmt::Display::fmt(v, f),
-            LengthPercentage::Percentage(v) => fmt::Display::fmt(v, f),
-        }
-    }
-}
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum LineStyle {
-    None,
-    Hidden,
-    Dotted,
-    Dashed,
-    Solid,
-    Double,
-    Groove,
-    Ridge,
-    Inset,
-    Outset,
-}
-
-impl fmt::Display for LineStyle {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            LineStyle::None => write!(f, "none"),
-            LineStyle::Hidden => write!(f, "hidden"),
-            LineStyle::Dotted => write!(f, "dotted"),
-            LineStyle::Dashed => write!(f, "dashed"),
-            LineStyle::Solid => write!(f, "solid"),
-            LineStyle::Double => write!(f, "double"),
-            LineStyle::Groove => write!(f, "groove"),
-            LineStyle::Ridge => write!(f, "ridge"),
-            LineStyle::Inset => write!(f, "inset"),
-            LineStyle::Outset => write!(f, "outset"),
-        }
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum LineWidth {
-    Length(Length),
-    Thin,
-    Medium,
-    Thick,
-}
-
-impl fmt::Display for LineWidth {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            LineWidth::Length(v) => fmt::Display::fmt(v, f),
-            LineWidth::Thin => write!(f, "thin"),
-            LineWidth::Medium => write!(f, "medium"),
-            LineWidth::Thick => write!(f, "thick"),
-        }
-    }
-}
-
-// TODO this isn't the full spec for lineheight
-// (https://www.w3.org/TR/CSS2/visudet.html#propdef-line-height)
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub struct LineHeight(f64);
-
-impl fmt::Display for LineHeight {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "{}", self.0)
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum ListStyleType {
-    Disc,
-    Circle,
-    Square,
-    Decimal,
-    DecimalLeadingZero,
-    LowerRoman,
-    UpperRoman,
-    LowerGreek,
-    UpperGreek,
-    LowerLatin,
-    UpperLatin,
-    Armenian,
-    Georgian,
-    LowerAlpha,
-    UpperAlpha,
-    None,
-}
-
-impl fmt::Display for ListStyleType {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            ListStyleType::Disc => write!(f, "disc"),
-            ListStyleType::Circle => write!(f, "circle"),
-            ListStyleType::Square => write!(f, "square"),
-            ListStyleType::Decimal => write!(f, "decimal"),
-            ListStyleType::DecimalLeadingZero => write!(f, "decimal-leading-zero"),
-            ListStyleType::LowerRoman => write!(f, "lower-roman"),
-            ListStyleType::UpperRoman => write!(f, "upper-roman"),
-            ListStyleType::LowerGreek => write!(f, "lower-greek"),
-            ListStyleType::UpperGreek => write!(f, "upper-greek"),
-            ListStyleType::LowerLatin => write!(f, "lower-latin"),
-            ListStyleType::UpperLatin => write!(f, "upper-latin"),
-            ListStyleType::Armenian => write!(f, "armenian"),
-            ListStyleType::Georgian => write!(f, "georgian"),
-            ListStyleType::LowerAlpha => write!(f, "lower-alpha"),
-            ListStyleType::UpperAlpha => write!(f, "upper-alpha"),
-            ListStyleType::None => write!(f, "none"),
-        }
-    }
-}
-
-pub type Margin = Rect<MarginWidth>;
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum AutoLengthPercentage {
-    LengthPercentage(Calc),
-    Auto,
-}
-
-impl fmt::Display for AutoLengthPercentage {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            AutoLengthPercentage::LengthPercentage(v) => fmt::Display::fmt(v, f),
-            AutoLengthPercentage::Auto => write!(f, "auto"),
-        }
-    }
-}
-
-pub type MarginWidth = AutoLengthPercentage;
-
-/// for max-width and max-height
-#[derive(Debug, Clone, PartialEq)]
-pub enum MaxWidthHeight {
-    None,
-    LengthPercentage(Calc),
-    MinContent,
-    MaxContent,
-    FitContent(Calc),
-}
-
-impl fmt::Display for MaxWidthHeight {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            MaxWidthHeight::None => write!(f, "none"),
-            MaxWidthHeight::LengthPercentage(v) => write!(f, "{}", v),
-            MaxWidthHeight::MinContent => write!(f, "min-content"),
-            MaxWidthHeight::MaxContent => write!(f, "max-content"),
-            MaxWidthHeight::FitContent(v) => write!(f, "fit-content({})", v),
-        }
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum ObjectFit {
-    Fill,
-    None,
-    Contain { scale_down: bool },
-    Cover { scale_down: bool },
-}
-
-impl fmt::Display for ObjectFit {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            ObjectFit::Fill => write!(f, "fill"),
-            ObjectFit::None => write!(f, "none"),
-            ObjectFit::Contain { scale_down } => {
-                if *scale_down {
-                    write!(f, "contain scale-down")
-                } else {
-                    write!(f, "contain")
-                }
-            }
-            ObjectFit::Cover { scale_down } => {
-                if *scale_down {
-                    write!(f, "cover scale-down")
-                } else {
-                    write!(f, "cover")
-                }
-            }
-        }
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum Overflow {
-    Both(OverflowXY),
-    XY(OverflowXY, OverflowXY),
-}
-
-impl fmt::Display for Overflow {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Overflow::Both(v) => write!(f, "{}", v),
-            Overflow::XY(x, y) => write!(f, "{} {}", x, y),
-        }
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum OverflowXY {
-    Visible,
-    Hidden,
-    Clip,
-    Scroll,
-    Auto,
-}
-
-impl fmt::Display for OverflowXY {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            OverflowXY::Visible => write!(f, "visible"),
-            OverflowXY::Hidden => write!(f, "hidden"),
-            OverflowXY::Clip => write!(f, "clip"),
-            OverflowXY::Scroll => write!(f, "scroll"),
-            OverflowXY::Auto => write!(f, "auto"),
-        }
-    }
-}
-
-pub type Padding = Rect<Calc>;
-
-/// for e.g. `padding-top`
-pub type PaddingWidth = Calc;
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub struct Percentage(pub f64);
-
-impl fmt::Display for Percentage {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "{}%", self.0)
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum Position {
-    Static,
-    Relative,
-    Absolute,
-    Fixed,
-}
-
-impl fmt::Display for Position {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Position::Static => write!(f, "static"),
-            Position::Relative => write!(f, "relative"),
-            Position::Absolute => write!(f, "absolute"),
-            Position::Fixed => write!(f, "fixed"),
-        }
-    }
-}
-
-/// For parsing things in groups of 1, 2, 3 or 4 for specifying the sides of a rectangle.
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum Rect<T> {
-    All(T),
-    VerticalHorizontal(T, T),
-    TopHorizontalBottom(T, T, T),
-    TopRightBottomLeft(T, T, T, T),
-}
-
-impl<T> fmt::Display for Rect<T>
-where
-    T: fmt::Display,
-{
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Rect::All(a) => write!(f, "{}", a),
-            Rect::VerticalHorizontal(v, h) => write!(f, "{} {}", v, h),
-            Rect::TopHorizontalBottom(t, h, b) => write!(f, "{} {} {}", t, h, b),
-            Rect::TopRightBottomLeft(t, r, b, l) => write!(f, "{} {} {} {}", t, r, b, l),
-        }
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum Resize {
-    None,
-    Both,
-    Horizontal,
-    Vertical,
-}
-
-impl fmt::Display for Resize {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Resize::None => write!(f, "none"),
-            Resize::Both => write!(f, "both"),
-            Resize::Horizontal => write!(f, "horizontal"),
-            Resize::Vertical => write!(f, "vertical"),
-        }
-    }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub struct Shadow {
-    pub color: Option<Color>,
-    pub length: ShadowLength,
-    pub inset: bool,
-}
-
-impl fmt::Display for Shadow {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        // we do it in this order because it makes spacing easier.
-        write!(f, "{}", self.length)?;
-        if let Some(color) = self.color {
-            write!(f, " {}", color)?;
-        }
-        if self.inset {
-            write!(f, " inset")?;
-        }
-        Ok(())
-    }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum ShadowLength {
-    Offsets {
-        horizontal: Length,
-        vertical: Length,
-    },
-    OffsetsBlur {
-        horizontal: Length,
-        vertical: Length,
-        blur: Length,
-    },
-    OffsetsBlurSpread {
-        horizontal: Length,
-        vertical: Length,
-        blur: Length,
-        spread: Length,
-    },
-}
-
-impl fmt::Display for ShadowLength {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            ShadowLength::Offsets {
-                horizontal,
-                vertical,
-            } => write!(f, "{} {}", horizontal, vertical),
-            ShadowLength::OffsetsBlur {
-                horizontal,
-                vertical,
-                blur,
-            } => write!(f, "{} {} {}", horizontal, vertical, blur),
-            ShadowLength::OffsetsBlurSpread {
-                horizontal,
-                vertical,
-                blur,
-                spread,
-            } => write!(f, "{} {} {} {}", horizontal, vertical, blur, spread),
-        }
-    }
-}
-
-#[test]
-fn test_shadow_length() {
-    for (input, output) in vec![
-        (
-            "0 10px",
-            ShadowLength::Offsets {
-                horizontal: Length::Zero,
-                vertical: Length::Px(10.0),
-            },
-        ),
-        (
-            "0 10px -10px",
-            ShadowLength::OffsetsBlur {
-                horizontal: Length::Zero,
-                vertical: Length::Px(10.0),
-                blur: Length::Px(-10.0),
-            },
-        ),
-    ] {
-        assert_eq!(syn::parse_str::<ShadowLength>(input).unwrap(), output)
-    }
-}
-
-#[derive(Debug, Copy, Clone, PartialEq)]
-pub enum TextAlign {
-    Left,
-    Right,
-    Center,
-    Justify,
-}
-
-impl fmt::Display for TextAlign {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            TextAlign::Left => write!(f, "left"),
-            TextAlign::Right => write!(f, "right"),
-            TextAlign::Center => write!(f, "center"),
-            TextAlign::Justify => write!(f, "justify"),
-        }
-    }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub struct Url {
-    // todo modifiers
-    url: String,
-}
-
-impl fmt::Display for Url {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "url(\"{}\")", self.url)
-    }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum WhiteSpace {
-    Normal,
-    Pre,
-    Nowrap,
-    PreWrap,
-    PreLine,
-}
-
-impl fmt::Display for WhiteSpace {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            WhiteSpace::Normal => write!(f, "normal"),
-            WhiteSpace::Pre => write!(f, "pre"),
-            WhiteSpace::Nowrap => write!(f, "nowrap"),
-            WhiteSpace::PreWrap => write!(f, "pre-wrap"),
-            WhiteSpace::PreLine => write!(f, "pre-line"),
-        }
-    }
-}
-
-/// values of `width` and `height`, `min-width`, `min-height`.
-#[derive(Debug, Clone, PartialEq)]
-pub enum WidthHeight {
-    Auto,
-    LengthPercentage(Calc),
-    MinContent,
-    MaxContent,
-    FitContent(Calc),
-}
-
-impl fmt::Display for WidthHeight {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            WidthHeight::Auto => write!(f, "auto"),
-            WidthHeight::LengthPercentage(v) => write!(f, "{}", v),
-            WidthHeight::MinContent => write!(f, "min-content"),
-            WidthHeight::MaxContent => write!(f, "max-content"),
-            WidthHeight::FitContent(v) => write!(f, "fit-content({})", v),
-        }
-    }
-}
-
-/// CSS2.1 width, for use with flexbox.
-#[derive(Debug, Clone, PartialEq)]
-pub enum Width21 {
-    Auto,
-    LengthPercentage(Calc),
-}
-
-impl fmt::Display for Width21 {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Width21::Auto => write!(f, "auto"),
-            Width21::LengthPercentage(v) => fmt::Display::fmt(v, f),
-        }
-    }
-}
-
-/// A generic container for a non-empty comma-separated list of values
-#[derive(Debug, Clone, PartialEq)]
-pub struct NonemptyCommaList<T> {
-    first: T,
-    rest: Vec<T>,
-}
-
-impl<T> fmt::Display for NonemptyCommaList<T>
-where
-    T: fmt::Display,
-{
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "{}", self.first)?;
-        for t in &self.rest {
-            write!(f, ",{}", t)?;
-        }
-        Ok(())
-    }
-}
-
-/// Matches one or two variables.
-#[derive(Debug, Clone, PartialEq)]
-pub enum SingleOrDouble<T> {
-    Single(T),
-    Double { horiz: T, vert: T },
-}
-
-impl<T> fmt::Display for SingleOrDouble<T>
-where
-    T: fmt::Display,
-{
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            SingleOrDouble::Single(t) => t.fmt(f),
-            SingleOrDouble::Double { vert, horiz } => write!(f, "{} {}", vert, horiz),
-        }
-    }
-}

+ 0 - 502
packages/core-macro/styles/string/lexer.rs

@@ -1,502 +0,0 @@
-//! Parse the various css types from strings directly (avoid pulling in syn if working at runtime)
-//!
-//! Differences to spec:
-//!  - Exponential floats are not supported for now.
-use std::{char, fmt, iter};
-
-const REPLACEMENT_CHAR: char = '�';
-
-#[derive(Copy, Clone, Debug, PartialEq)]
-#[non_exhaustive] // Don't allow user to create
-pub struct Span {
-    /// Inclusive
-    start: usize,
-    /// Exclusive
-    end: usize,
-}
-
-impl Span {
-    fn new(start: usize, end: usize) -> Self {
-        assert!(end > start, "end must be greater than start");
-        Span { start, end }
-    }
-
-    pub fn len(&self) -> usize {
-        self.end - self.start
-    }
-}
-
-#[derive(Debug)]
-pub struct InvalidChar {
-    ch: char,
-    pos: usize,
-}
-
-impl fmt::Display for InvalidChar {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(
-            f,
-            "invalid character `{}` found at position {}",
-            self.ch.escape_debug(),
-            self.pos
-        )
-    }
-}
-
-#[derive(Debug)]
-pub struct Lexer<'src> {
-    src: &'src str,
-    cursor: usize,
-}
-
-impl<'src> Lexer<'src> {
-    pub fn new(src: &'src str) -> Result<Lexer<'src>, InvalidChar> {
-        // Check that the user has already replaced characters as specified at
-        // https://www.w3.org/TR/css-syntax-3/#input-preprocessing
-        for (pos, ch) in src.char_indices() {
-            if ch == '\r' || ch == '\u{d}' || ch == '\0' {
-                return Err(InvalidChar { ch, pos });
-            }
-        }
-        Ok(Lexer { src, cursor: 0 })
-    }
-
-    fn len(&self) -> usize {
-        self.src.len()
-    }
-
-    fn remaining(&self) -> usize {
-        self.src.len() - self.cursor
-    }
-
-    pub fn next_token(&mut self) -> Option<Token> {
-        match self.peek() {
-            Some(token) => {
-                self.consume(&token);
-                Some(token)
-            }
-            None => None,
-        }
-    }
-
-    pub fn peek(&self) -> Option<Token> {
-        // https://www.w3.org/TR/css-syntax-3/#tokenizer-definitions
-        if let Some(comment) = self.comment() {
-            return Some(comment);
-        }
-        if let Some(tok) = self.whitespace() {
-            return Some(tok);
-        }
-        if let Some(tok) = self.string() {
-            return Some(tok);
-        }
-        match self.chars().next() {
-            Some(other) => Some(Token::new(
-                TokenKind::Error,
-                Span::new(self.cursor, self.cursor + other.len_utf8()),
-            )),
-            None => None,
-        }
-    }
-
-    pub fn peek_n(&self, n: usize) -> Option<Token> {
-        todo!()
-    }
-
-    pub fn is_empty(&self) -> bool {
-        todo!() //self.peek().is_none()
-    }
-
-    pub fn resolve_span(&self, span: Span) -> &'src str {
-        if span.end > self.len() {
-            panic!("End of requested span is past the end of the source");
-        }
-        &self.src[span.start..span.end]
-    }
-
-    /// Create another independent lexer at the given start point
-    fn fork(&self) -> Lexer {
-        Lexer {
-            src: self.src,
-            cursor: self.cursor,
-        }
-    }
-
-    pub fn consume(&mut self, tok: &Token) {
-        assert!(
-            tok.len() <= self.remaining(),
-            "trying to consume a token that would be bigger \
-            than all remaining text"
-        );
-        self.cursor += tok.len();
-    }
-
-    /// Resolve a position from cursor to position from start of src
-    fn resolve_pos(&self, pos: usize) -> usize {
-        self.cursor + pos
-    }
-
-    /// Create a span from the current position with the given length
-    fn span(&self, len: usize) -> Span {
-        debug_assert!(self.cursor + len <= self.len());
-        Span::new(self.cursor, self.cursor + len)
-    }
-
-    /// Create a span from the current position to the end
-    fn span_to_end(&self) -> Span {
-        Span::new(self.cursor, self.len())
-    }
-
-    /// Iterate over the remaining chars of the input
-    fn chars(&self) -> std::str::Chars {
-        self.src[self.cursor..].chars()
-    }
-
-    /// Iterate over the remaining chars of the input
-    fn char_indices(&self) -> std::str::CharIndices {
-        self.src[self.cursor..].char_indices()
-    }
-
-    /// Parse a comment
-    fn comment(&self) -> Option<Token> {
-        let mut ch_iter = self.char_indices().peekable();
-        if let Some((_, '/')) = ch_iter.next() {
-            if let Some((_, '*')) = ch_iter.next() {
-                loop {
-                    match ch_iter.next() {
-                        Some((_, '*')) => {
-                            if let Some((idx, '/')) = ch_iter.peek() {
-                                return Some(Token {
-                                    kind: TokenKind::Comment,
-                                    span: self.span(*idx + '/'.len_utf8()),
-                                });
-                            }
-                        }
-                        None => {
-                            return Some(Token::new(
-                                TokenKind::UnclosedComment,
-                                self.span_to_end(),
-                            ));
-                        }
-                        _ => (),
-                    }
-                }
-            }
-        }
-        None
-    }
-
-    /// Parse whitespace
-    fn whitespace(&self) -> Option<Token> {
-        let mut ch_iter = self.chars();
-        let mut len = match ch_iter.next() {
-            Some(ch) if ch.is_ascii_whitespace() => ch.len_utf8(),
-            _ => return None,
-        };
-        loop {
-            match ch_iter.next() {
-                Some(ch) if ch.is_ascii_whitespace() => len += ch.len_utf8(),
-                _ => break,
-            }
-        }
-        Some(Token {
-            kind: TokenKind::Whitespace,
-            span: self.span(len),
-        })
-    }
-
-    /// Parse either a single or double quoted string
-    fn string(&self) -> Option<Token> {
-        let mut ch_iter = self.char_indices().fuse().peekable();
-        let delim = match ch_iter.next() {
-            Some((_, '"')) => '"',
-            Some((_, '\'')) => '\'',
-            _ => return None,
-        };
-        let mut decoded_string = String::new();
-        loop {
-            match ch_iter.next() {
-                Some((end, ch)) if ch == delim => {
-                    return Some(Token {
-                        kind: TokenKind::String(decoded_string),
-                        span: self.span(end + 1), // '"'.len_utf8() == 1
-                    });
-                }
-                Some((end, '\n')) => {
-                    return Some(Token {
-                        kind: TokenKind::BadString(decoded_string),
-                        span: self.span(end + 1), // '\n'.len_utf8() == 1
-                    });
-                }
-                Some((_, '\\')) => match ch_iter.peek() {
-                    Some((_, ch)) => {
-                        if *ch == '\n' {
-                            // do nothing - skip the backslash and newline.
-                            ch_iter.next().unwrap();
-                        } else if let Some(decoded_ch) = unescape(&mut ch_iter) {
-                            decoded_string.push(decoded_ch);
-                        } else {
-                            decoded_string.push(ch_iter.next().unwrap().1);
-                        }
-                    }
-                    None => {
-                        // The spec says not to add the last '\'.
-                        // a bad string will be returned on next pass
-                        ch_iter.next().unwrap();
-                    }
-                },
-                Some((_, ch)) => decoded_string.push(ch),
-                None => {
-                    return Some(Token {
-                        kind: TokenKind::BadString(decoded_string),
-                        span: self.span_to_end(),
-                    })
-                }
-            }
-        }
-    }
-
-    /*
-    fn hash(&self) -> Option<Token> {
-        let mut iter = self.char_indices();
-        match iter.next() {
-            Some((_, '#')) => (),
-            None => return None,
-        };
-        match iter.next() {
-            Some((_, '\\')) => {}
-            _ => Some(Token {
-                kind: TokenKind::Delim('#'),
-                span: self.span(1),
-            }),
-        }
-    }
-    */
-}
-
-impl<'src> Iterator for Lexer<'src> {
-    type Item = Token;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        self.next_token()
-    }
-}
-
-#[derive(Debug, PartialEq)]
-#[non_exhaustive]
-pub struct Token {
-    pub kind: TokenKind,
-    pub span: Span,
-}
-
-impl Token {
-    fn new(kind: TokenKind, span: Span) -> Self {
-        Token { kind, span }
-    }
-
-    pub fn len(&self) -> usize {
-        self.span.len()
-    }
-}
-
-#[derive(Debug, PartialEq)]
-pub enum TokenKind {
-    Ident,
-    Function,
-    At,
-    Hash,
-    String(String),
-    BadString(String),
-    Url,
-    BadUrl,
-    Delim(char),
-    Number,
-    Percentage,
-    Dimension,
-    Whitespace,
-    /// <!--
-    CDO,
-    /// -->
-    CDC,
-    /// :
-    Colon,
-    /// ;
-    Semicolon,
-    /// ,
-    Comma,
-    /// [
-    LBracket,
-    /// ]
-    RBracket,
-    /// (
-    LParen,
-    /// )
-    RParen,
-    /// {
-    LBrace,
-    /// }
-    RBrace,
-    Comment,
-    UnclosedComment,
-    /// Could not parse the next token
-    Error,
-}
-
-// Helpers
-
-/// Hex to char (up to 6 characters, e.g. "ffffff").
-///
-/// For example `"5c" => '\'`. Returns None if first char is not hex.  Consumes the hex values.
-fn unescape(input: &mut iter::Peekable<impl Iterator<Item = (usize, char)>>) -> Option<char> {
-    fn hex_acc(acc: &mut u32, next: char) {
-        debug_assert!(*acc & 0xf0000000 == 0); // make sure we don't overflow
-        (*acc) = (*acc << 4) + next.to_digit(16).unwrap()
-    }
-
-    let (_, ch) = match input.peek() {
-        Some((idx, ch)) if ch.is_ascii_hexdigit() => input.next().unwrap(),
-        _ => return None,
-    };
-
-    let mut acc = 0;
-    let mut count = 0;
-    hex_acc(&mut acc, ch);
-
-    // Here we use that the length of all valid hexdigits in utf8 is 1.
-    while count < 5
-        && input
-            .peek()
-            .map(|(_, ch)| ch.is_ascii_hexdigit())
-            .unwrap_or(false)
-    {
-        let ch = input.next().unwrap().1;
-        hex_acc(&mut acc, ch);
-        count += 1;
-    }
-
-    // consume a whitespace char if it's there
-    if input
-        .peek()
-        .map(|(_, ch)| ch.is_ascii_whitespace())
-        .unwrap_or(false)
-    {
-        input.next().unwrap();
-    }
-
-    // maybe we could just directly use `char::from_u32(acc).unwrap_or(REPLACEMENT_CHAR)`
-    // null, surrogate, or too big
-    Some(
-        if acc == 0 || (acc >= 0xd800 && acc < 0xe000) || acc >= 0x110000 {
-            REPLACEMENT_CHAR
-        } else {
-            char::from_u32(acc).unwrap() // there should be no other invalid chars.
-        },
-    )
-}
-
-#[cfg(test)]
-mod test {
-    use super::{Lexer, Span, Token, TokenKind};
-
-    #[test]
-    fn comment() {
-        println!();
-        let mut input = Lexer::new("/* a valid comment */").unwrap();
-        match input.next_token() {
-            Some(Token {
-                kind: TokenKind::Comment,
-                span,
-            }) => {
-                assert_eq!(
-                    input.resolve_span(span),
-                    "/* a valid comment */".to_string()
-                );
-                assert_eq!(span.len(), 21);
-            }
-            _ => panic!("not a comment"),
-        };
-
-        let mut input = Lexer::new("/* a comment").unwrap();
-        match input.next_token() {
-            Some(Token {
-                kind: TokenKind::UnclosedComment,
-                span,
-            }) => {
-                assert_eq!(input.resolve_span(span), "/* a comment".to_string());
-                assert_eq!(span.len(), 12);
-            }
-            _ => panic!("not a comment"),
-        };
-
-        let mut input = Lexer::new("/!* not a comment").unwrap();
-        match input.next_token() {
-            Some(Token {
-                kind: TokenKind::Error,
-                span,
-            }) => {}
-            _ => panic!("not a comment"),
-        };
-    }
-
-    #[test]
-    fn string() {
-        println!("h");
-        let mut input = Lexer::new("\" a vali\\64\\e9 \\\n string \"").unwrap();
-        match input.next_token() {
-            Some(Token {
-                kind: TokenKind::String(s),
-                span,
-            }) => {
-                assert_eq!(s, " a validé string ".to_string());
-                assert_eq!(span.len(), 26);
-            }
-            _ => panic!("not a string"),
-        };
-
-        let mut input = Lexer::new("' a valid string '").unwrap();
-        match input.next_token() {
-            Some(Token {
-                kind: TokenKind::String(s),
-                span,
-            }) => {
-                assert_eq!(s, " a valid string ".to_string());
-                assert_eq!(span.len(), 18);
-            }
-            _ => panic!("not a string"),
-        };
-
-        let mut input = Lexer::new("\" a string").unwrap();
-        match input.next_token() {
-            Some(Token {
-                kind: TokenKind::BadString(s),
-                span,
-            }) => {
-                assert_eq!(s, " a string".to_string());
-                assert_eq!(span.len(), 10);
-            }
-            _ => panic!("not a string"),
-        };
-    }
-
-    #[test]
-    fn whitespace() {
-        println!();
-        let mut input = Lexer::new("\n\t ").unwrap();
-        match input.next_token() {
-            Some(Token {
-                kind: TokenKind::Whitespace,
-                span,
-            }) => {
-                assert_eq!(input.resolve_span(span), "\n\t ".to_string());
-                assert_eq!(span.len(), 3);
-            }
-            _ => panic!("not a string"),
-        };
-    }
-
-    #[test]
-    fn escape() {
-        let mut iter = "e9".char_indices().peekable();
-        assert_eq!(super::unescape(&mut iter), Some('é'));
-    }
-}

+ 0 - 1
packages/core-macro/styles/string/mod.rs

@@ -1 +0,0 @@
-pub mod lexer;

+ 0 - 2411
packages/core-macro/styles/syn_parse.rs

@@ -1,2411 +0,0 @@
-//! Implementation of `syn::parse::Parse` for styles, and associated helper data/functions.
-// TODO make all parsers use HyphenWord where appropriate.
-// TODO make all error messages nice
-// TODO 100% test coverage
-// TODO see if I can get https://github.com/rust-lang/rust/issues/67544 accepted. then change "em" to
-// em and "ex" to ex.
-// TODO Split out extra "Dynamic" layer for each type for use in proc macro (so we can have `{ <arbitary
-// rust code> }`)
-use crate::*;
-use proc_macro2::Span;
-use std::{
-    cell::RefCell,
-    collections::BTreeSet,
-    fmt::{self, Write},
-    ops::RangeBounds,
-    str,
-};
-use syn::{
-    ext::IdentExt,
-    parse::{discouraged::Speculative, Parse, ParseStream},
-    punctuated::Punctuated,
-    spanned::Spanned,
-    Ident, Token,
-};
-
-use super::{DynamicStyle, DynamicStyles, Styles};
-
-impl Parse for DynamicStyles {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let punc = s.parse_terminated::<_, Token![;]>(<DynamicStyle as Parse>::parse)?;
-        Ok(DynamicStyles::from(punc.into_iter().collect::<Vec<_>>()))
-    }
-}
-
-impl Parse for Styles {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let punc = s.parse_terminated::<_, Token![;]>(<Style as Parse>::parse)?;
-        Ok(Styles::from(punc.into_iter().collect::<Vec<_>>()))
-    }
-}
-
-impl Parse for DynamicStyle {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        // Pass through brackets
-        if s.peek(syn::token::Brace) {
-            Ok(DynamicStyle::Dynamic(s.parse()?))
-        } else {
-            Ok(DynamicStyle::Literal(s.parse()?))
-        }
-    }
-}
-
-impl Parse for Style {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        if s.peek(syn::LitStr) {
-            let unchecked: syn::LitStr = s.parse()?;
-            return Ok(Style::Unchecked(unchecked.value()));
-        }
-
-        let name: HyphenWord = s.parse()?;
-        if name.try_match("dummy") {
-            return Ok(Style::Dummy);
-        }
-
-        s.parse::<Token![:]>()?;
-
-        let output = if name.try_match("align-content") {
-            Style::AlignContent(s.parse()?)
-        } else if name.try_match("align-items") {
-            Style::AlignItems(s.parse()?)
-        } else if name.try_match("align-self") {
-            Style::AlignSelf(s.parse()?)
-        // all
-        // background
-        } else if name.try_match("background-attachment") {
-            Style::BackgroundAttachment(s.parse()?)
-        } else if name.try_match("background-blend-mode") {
-            Style::BackgroundBlendMode(s.parse()?)
-        } else if name.try_match("background-clip") {
-            Style::BackgroundClip(s.parse()?)
-        } else if name.try_match("background-color") {
-            Style::BackgroundColor(s.parse()?)
-        } else if name.try_match("background-image") {
-            Style::BackgroundImage(s.parse()?)
-        } else if name.try_match("background-origin") {
-            Style::BackgroundOrigin(s.parse()?)
-        } else if name.try_match("background-position") {
-            Style::BackgroundPosition(s.parse()?)
-        } else if name.try_match("background-repeat") {
-            Style::BackgroundRepeat(s.parse()?)
-        } else if name.try_match("background-size") {
-            Style::BackgroundSize(s.parse()?)
-        } else if name.try_match("border") {
-            Style::Border(s.parse()?)
-        } else if name.try_match("border-bottom") {
-            Style::BorderBottom(s.parse()?)
-        } else if name.try_match("border-bottom-color") {
-            Style::BorderBottomColor(s.parse()?)
-        } else if name.try_match("border-bottom-left-radius") {
-            Style::BorderBottomLeftRadius(s.parse()?)
-        } else if name.try_match("border-bottom-right-radius") {
-            Style::BorderBottomRightRadius(s.parse()?)
-        } else if name.try_match("border-bottom-style") {
-            Style::BorderBottomStyle(s.parse()?)
-        } else if name.try_match("border-bottom-width") {
-            Style::BorderBottomWidth(s.parse()?)
-        } else if name.try_match("border-collapse") {
-            Style::BorderCollapse(s.parse()?)
-        } else if name.try_match("border-color") {
-            Style::BorderColor(s.parse()?)
-        // border-image
-        // border-image-outset
-        // border-image-repeat
-        // border-image-slice
-        // border-image-source
-        // border-image-width
-        } else if name.try_match("border-left") {
-            Style::BorderLeft(s.parse()?)
-        } else if name.try_match("border-left-color") {
-            Style::BorderLeftColor(s.parse()?)
-        } else if name.try_match("border-left-style") {
-            Style::BorderLeftStyle(s.parse()?)
-        } else if name.try_match("border-left-width") {
-            Style::BorderLeftWidth(s.parse()?)
-        } else if name.try_match("border-radius") {
-            Style::BorderRadius(s.parse()?)
-        } else if name.try_match("border-right") {
-            Style::BorderRight(s.parse()?)
-        } else if name.try_match("border-right-color") {
-            Style::BorderRightColor(s.parse()?)
-        } else if name.try_match("border-right-style") {
-            Style::BorderRightStyle(s.parse()?)
-        } else if name.try_match("border-right-width") {
-            Style::BorderRightWidth(s.parse()?)
-        // border-spacing
-        } else if name.try_match("border-style") {
-            Style::BorderStyle(s.parse()?)
-        } else if name.try_match("border-top") {
-            Style::BorderTop(s.parse()?)
-        } else if name.try_match("border-top-color") {
-            Style::BorderTopColor(s.parse()?)
-        } else if name.try_match("border-top-left-radius") {
-            Style::BorderTopLeftRadius(s.parse()?)
-        } else if name.try_match("border-top-right-radius") {
-            Style::BorderTopRightRadius(s.parse()?)
-        } else if name.try_match("border-top-style") {
-            Style::BorderTopStyle(s.parse()?)
-        } else if name.try_match("border-top-width") {
-            Style::BorderTopWidth(s.parse()?)
-        } else if name.try_match("border-width") {
-            Style::BorderWidth(s.parse()?)
-        } else if name.try_match("bottom") {
-            Style::Bottom(s.parse()?)
-        // box-decoration-break
-        } else if name.try_match("box-shadow") {
-            Style::BoxShadow(s.parse()?)
-        } else if name.try_match("box-sizing") {
-            Style::BoxSizing(s.parse()?)
-        // break-after
-        // break-before
-        // break-inside
-        // caption-side
-        // caret-color
-        } else if name.try_match("clear") {
-            Style::Clear(s.parse()?)
-        // clip
-        // clip-path
-        // clip-rule
-        } else if name.try_match("column-count") {
-            Style::ColumnCount(s.parse()?)
-        } else if name.try_match("color") {
-            Style::Color(s.parse()?)
-        // contain
-        // content
-        // counter-increment
-        // counter-reset
-        // cue
-        // cue-after
-        // cue-before
-        } else if name.try_match("cursor") {
-            Style::Cursor(s.parse()?)
-        // direction
-        } else if name.try_match("display") {
-            Style::Display(s.parse()?)
-        // elevation
-        // empty-cells
-        // flex
-        } else if name.try_match("flex-basis") {
-            Style::FlexBasis(s.parse()?)
-        } else if name.try_match("flex-direction") {
-            Style::FlexDirection(s.parse()?)
-        // flex-flow
-        } else if name.try_match("flex-grow") {
-            let number: Number = s.parse()?;
-            if !number.suffix.is_empty() {
-                return Err(syn::Error::new(number.span, "expected number"));
-            }
-            Style::FlexGrow(number.value)
-        } else if name.try_match("flex-shrink") {
-            let number: Number = s.parse()?;
-            if !number.suffix.is_empty() {
-                return Err(syn::Error::new(number.span, "expected number"));
-            }
-            Style::FlexShrink(number.value)
-        } else if name.try_match("flex-wrap") {
-            Style::FlexWrap(s.parse()?)
-        } else if name.try_match("float") {
-            Style::Float(s.parse()?)
-        // font
-        } else if name.try_match("font-family") {
-            Style::FontFamily(s.parse()?)
-        // font-feature-settings
-        // font-kerning
-        } else if name.try_match("font-size") {
-            Style::FontSize(s.parse()?)
-        // font-size-adjust
-        // font-stretch
-        } else if name.try_match("font-style") {
-            Style::FontStyle(s.parse()?)
-        // font-synthesis
-        // font-variant
-        // font-variant-caps
-        // font-variant-east-asian
-        // font-variant-ligatures
-        // font-variant-numeric
-        // font-variant-position
-        } else if name.try_match("font-weight") {
-            Style::FontWeight(s.parse()?)
-        // glyph-orientation-vertical
-        // grid
-        // grid-area
-        // grid-auto-columns
-        // grid-auto-flow
-        // grid-auto-rows
-        // grid-column
-        // grid-column-end
-        // grid-column-start
-        // grid-row
-        // grid-row-end
-        // grid-row-start
-        // grid-template
-        // grid-template-areas
-        // grid-template-columns
-        // grid-template-rows
-        } else if name.try_match("height") {
-            Style::Height(s.parse()?)
-        // image-orientation
-        // image-rendering
-        // isolation
-        } else if name.try_match("justify-content") {
-            Style::JustifyContent(s.parse()?)
-        } else if name.try_match("left") {
-            Style::Left(s.parse()?)
-        // letter-spacing
-        } else if name.try_match("line-height") {
-            Style::LineHeight(s.parse()?)
-        // list-style
-        // list-style-image
-        // list-style-position
-        } else if name.try_match("list-style-type") {
-            Style::ListStyleType(s.parse()?)
-        } else if name.try_match("margin") {
-            Style::Margin(s.parse()?)
-        } else if name.try_match("margin-bottom") {
-            Style::MarginBottom(s.parse()?)
-        } else if name.try_match("margin-left") {
-            Style::MarginLeft(s.parse()?)
-        } else if name.try_match("margin-right") {
-            Style::MarginRight(s.parse()?)
-        } else if name.try_match("margin-top") {
-            Style::MarginTop(s.parse()?)
-        // mask
-        // mask-border
-        // mask-border-mode
-        // mask-border-outset
-        // mask-border-repeat
-        // mask-border-slice
-        // mask-border-source
-        // mask-border-width
-        // mask-clip
-        // mask-composite
-        // mask-image
-        // mask-mode
-        // mask-origin
-        // mask-position
-        // mask-repeat
-        // mask-size
-        // mask-type
-        } else if name.try_match("max-height") {
-            Style::MaxHeight(s.parse()?)
-        } else if name.try_match("max-width") {
-            Style::MaxWidth(s.parse()?)
-        } else if name.try_match("min-height") {
-            Style::MinHeight(s.parse()?)
-        } else if name.try_match("min-width") {
-            Style::MinWidth(s.parse()?)
-        // mix-blend-mode
-        } else if name.try_match("object-fit") {
-            Style::ObjectFit(s.parse()?)
-        // object-position
-        // opacity
-        // order
-        // orphans
-        // outline
-        // outline-color
-        // outline-offset
-        // outline-style
-        // outline-width
-        } else if name.try_match("overflow") {
-            Style::Overflow(s.parse()?)
-        } else if name.try_match("overflow-x") {
-            Style::OverflowX(s.parse()?)
-        } else if name.try_match("overflow-y") {
-            Style::OverflowY(s.parse()?)
-        } else if name.try_match("padding") {
-            Style::Padding(s.parse()?)
-        } else if name.try_match("padding-bottom") {
-            Style::PaddingBottom(s.parse()?)
-        } else if name.try_match("padding-left") {
-            Style::PaddingLeft(s.parse()?)
-        } else if name.try_match("padding-right") {
-            Style::PaddingRight(s.parse()?)
-        } else if name.try_match("padding-top") {
-            Style::PaddingTop(s.parse()?)
-        // page-break-after
-        // page-break-before
-        // page-break-inside
-        // pause
-        // pause-after
-        // pause-before
-        // pitch
-        // pitch-range
-        // play-during
-        } else if name.try_match("position") {
-            Style::Position(s.parse()?)
-        // quotes
-        } else if name.try_match("resize") {
-            Style::Resize(s.parse()?)
-        // richness
-        } else if name.try_match("right") {
-            Style::Right(s.parse()?)
-        // scroll-margin
-        // scroll-margin-block
-        // scroll-margin-block-end
-        // scroll-margin-block-start
-        // scroll-margin-bottom
-        // scroll-margin-inline
-        // scroll-margin-inline-end
-        // scroll-margin-inline-start
-        // scroll-margin-left
-        // scroll-margin-right
-        // scroll-margin-top
-        // scroll-padding
-        // scroll-padding-block
-        // scroll-padding-block-end
-        // scroll-padding-block-start
-        // scroll-padding-bottom
-        // scroll-padding-inline
-        // scroll-padding-inline-end
-        // scroll-padding-inline-start
-        // scroll-padding-left
-        // scroll-padding-right
-        // scroll-padding-top
-        // scroll-snap-align
-        // scroll-snap-stop
-        // scroll-snap-type
-        // shape-image-threshold
-        // shape-margin
-        // shape-outside
-        // speak
-        // speak-header
-        // speak-numeral
-        // speak-punctuation
-        // speech-rate
-        // stress
-        // table-layout
-        } else if name.try_match("text-align") {
-            Style::TextAlign(s.parse()?)
-        // text-combine-upright
-        // text-decoration
-        // text-decoration-color
-        // text-decoration-line
-        // text-decoration-style
-        // text-emphasis
-        // text-emphasis-color
-        // text-emphasis-position
-        // text-emphasis-style
-        // text-indent
-        // text-orientation
-        // text-overflow
-        // text-shadow
-        // text-transform
-        // text-underline-position
-        } else if name.try_match("top") {
-            Style::Top(s.parse()?)
-        // transform
-        // transform-box
-        // transform-origin
-        // unicode-bidi
-        // vertical-align
-        // visibility
-        // voice-family
-        // volume
-        } else if name.try_match("white-space") {
-            Style::WhiteSpace(s.parse()?)
-        } else if name.try_match("widows") {
-            Style::Widows(integer(s, 1..)?)
-        } else if name.try_match("width") {
-            Style::Width(s.parse()?)
-        // will-change
-        // word-spacing
-        // writing-mode
-        // z-index
-        } else {
-            return Err(name.error());
-        };
-
-        if !finished_rule(s) {
-            return Err(s.error("unexpected trailing tokens in style rule"));
-        }
-
-        Ok(output)
-    }
-}
-
-impl Parse for AlignContent {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let name: HyphenWord = s.parse()?;
-
-        if name.try_match("flex-start") {
-            Ok(AlignContent::FlexStart)
-        } else if name.try_match("flex-end") {
-            Ok(AlignContent::FlexEnd)
-        } else if name.try_match("center") {
-            Ok(AlignContent::Center)
-        } else if name.try_match("space-between") {
-            Ok(AlignContent::SpaceBetween)
-        } else if name.try_match("space-around") {
-            Ok(AlignContent::SpaceAround)
-        } else if name.try_match("stretch") {
-            Ok(AlignContent::Stretch)
-        } else {
-            Err(name.error())
-        }
-    }
-}
-
-#[test]
-fn test_align_content() {
-    for test in vec![
-        "flex-start",
-        "flex-end",
-        "center",
-        "space-between",
-        "space-around",
-        "stretch",
-    ] {
-        assert_eq!(
-            &syn::parse_str::<AlignContent>(test).unwrap().to_string(),
-            test
-        );
-    }
-    assert_eq!(
-        &syn::parse_str::<Style>("align-content:flex-start")
-            .unwrap()
-            .to_string(),
-        "align-content:flex-start"
-    );
-}
-
-impl Parse for AlignItems {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let word: HyphenWord = s.parse()?;
-        if word.try_match("normal") {
-            Ok(AlignItems::Normal)
-        } else if word.try_match("stretch") {
-            Ok(AlignItems::Stretch)
-        } else if word.try_match("center") {
-            Ok(AlignItems::Center)
-        } else if word.try_match("start") {
-            Ok(AlignItems::Start)
-        } else if word.try_match("end") {
-            Ok(AlignItems::End)
-        } else if word.try_match("flex-start") {
-            Ok(AlignItems::FlexStart)
-        } else if word.try_match("flex-end") {
-            Ok(AlignItems::FlexEnd)
-        } else if word.try_match("baseline") {
-            Ok(AlignItems::Baseline)
-        } else if word.try_match("first") {
-            let word: HyphenWord = s.parse()?;
-            if word.try_match("baseline") {
-                Ok(AlignItems::FirstBaseline)
-            } else {
-                Err(word.error())
-            }
-        } else if word.try_match("last") {
-            let word: HyphenWord = s.parse()?;
-            if word.try_match("baseline") {
-                Ok(AlignItems::LastBaseline)
-            } else {
-                Err(word.error())
-            }
-        } else if word.try_match("safe") {
-            let word: HyphenWord = s.parse()?;
-            if word.try_match("center") {
-                Ok(AlignItems::SafeCenter)
-            } else {
-                Err(word.error())
-            }
-        } else if word.try_match("unsafe") {
-            let word: HyphenWord = s.parse()?;
-            if word.try_match("center") {
-                Ok(AlignItems::UnsafeCenter)
-            } else {
-                Err(word.error())
-            }
-        } else {
-            Err(word.error())
-        }
-    }
-}
-
-impl Parse for AlignSelf {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let word: HyphenWord = s.parse()?;
-        if word.try_match("auto") {
-            Ok(AlignSelf::Auto)
-        } else if word.try_match("normal") {
-            Ok(AlignSelf::Normal)
-        } else if word.try_match("center") {
-            Ok(AlignSelf::Center)
-        } else if word.try_match("start") {
-            Ok(AlignSelf::Start)
-        } else if word.try_match("self-start") {
-            Ok(AlignSelf::SelfStart)
-        } else if word.try_match("self-end") {
-            Ok(AlignSelf::SelfEnd)
-        } else if word.try_match("flex-start") {
-            Ok(AlignSelf::FlexStart)
-        } else if word.try_match("flex-end") {
-            Ok(AlignSelf::FlexEnd)
-        } else if word.try_match("baseline") {
-            Ok(AlignSelf::Baseline)
-        } else if word.try_match("first") {
-            let word: HyphenWord = s.parse()?;
-            if word.try_match("baseline") {
-                Ok(AlignSelf::FirstBaseline)
-            } else {
-                Err(word.error())
-            }
-        } else if word.try_match("last") {
-            let word: HyphenWord = s.parse()?;
-            if word.try_match("baseline") {
-                Ok(AlignSelf::LastBaseline)
-            } else {
-                Err(word.error())
-            }
-        } else if word.try_match("stretch") {
-            Ok(AlignSelf::Stretch)
-        } else if word.try_match("safe") {
-            let word: HyphenWord = s.parse()?;
-            if word.try_match("center") {
-                Ok(AlignSelf::SafeCenter)
-            } else {
-                Err(word.error())
-            }
-        } else if word.try_match("unsafe") {
-            let word: HyphenWord = s.parse()?;
-            if word.try_match("center") {
-                Ok(AlignSelf::UnsafeCenter)
-            } else {
-                Err(word.error())
-            }
-        } else {
-            Err(word.error())
-        }
-    }
-}
-
-impl Parse for BackgroundAttachment {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let word: HyphenWord = s.parse()?;
-        if word.try_match("scroll") {
-            Ok(BackgroundAttachment::Scroll)
-        } else if word.try_match("fixed") {
-            Ok(BackgroundAttachment::Fixed)
-        } else if word.try_match("local") {
-            Ok(BackgroundAttachment::Local)
-        } else {
-            Err(word.error())
-        }
-    }
-}
-
-impl Parse for BlendMode {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let word: HyphenWord = s.parse()?;
-        if word.try_match("normal") {
-            Ok(BlendMode::Normal)
-        } else if word.try_match("multiply") {
-            Ok(BlendMode::Multiply)
-        } else if word.try_match("screen") {
-            Ok(BlendMode::Screen)
-        } else if word.try_match("overlay") {
-            Ok(BlendMode::Overlay)
-        } else if word.try_match("darken") {
-            Ok(BlendMode::Darken)
-        } else if word.try_match("lighten") {
-            Ok(BlendMode::Lighten)
-        } else if word.try_match("color-dodge") {
-            Ok(BlendMode::ColorDodge)
-        } else if word.try_match("color-burn") {
-            Ok(BlendMode::ColorBurn)
-        } else if word.try_match("hard-light") {
-            Ok(BlendMode::HardLight)
-        } else if word.try_match("soft-light") {
-            Ok(BlendMode::SoftLight)
-        } else if word.try_match("difference") {
-            Ok(BlendMode::Difference)
-        } else if word.try_match("exclusion") {
-            Ok(BlendMode::Exclusion)
-        } else if word.try_match("hue") {
-            Ok(BlendMode::Hue)
-        } else if word.try_match("saturation") {
-            Ok(BlendMode::Saturation)
-        } else if word.try_match("color") {
-            Ok(BlendMode::Color)
-        } else if word.try_match("luminosity") {
-            Ok(BlendMode::Luminosity)
-        } else {
-            Err(word.error())
-        }
-    }
-}
-
-impl Parse for BackgroundImage {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let peek = HyphenWord::peek_specific(s);
-        if peek.as_ref().map(|s| s.as_str()) == Some("url") {
-            let url;
-            syn::parenthesized!(url in s);
-            let url = url.parse::<syn::LitStr>()?;
-            Ok(BackgroundImage::Url(url.value()))
-        } else {
-            let word: HyphenWord = s.parse()?;
-            word.add_expected("url");
-            if word.try_match("none") {
-                Ok(BackgroundImage::None)
-            } else {
-                Err(word.error())
-            }
-        }
-    }
-}
-
-impl Parse for BackgroundBox {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let word: HyphenWord = s.parse()?;
-        if word.try_match("border-box") {
-            Ok(BackgroundBox::BorderBox)
-        } else if word.try_match("padding-box") {
-            Ok(BackgroundBox::PaddingBox)
-        } else if word.try_match("content-box") {
-            Ok(BackgroundBox::ContentBox)
-        } else {
-            Err(word.error())
-        }
-    }
-}
-
-impl Parse for BackgroundPosition {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let word: HyphenWord = s.parse()?;
-        if word.try_match("top") {
-            Ok(BackgroundPosition::Top)
-        } else if word.try_match("bottom") {
-            Ok(BackgroundPosition::Bottom)
-        } else if word.try_match("left") {
-            Ok(BackgroundPosition::Left)
-        } else if word.try_match("right") {
-            Ok(BackgroundPosition::Right)
-        } else if word.try_match("center") {
-            Ok(BackgroundPosition::Center)
-        } else {
-            Err(word.error())
-        }
-    }
-}
-
-impl Parse for BackgroundRepeat {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let word: HyphenWord = s.parse()?;
-        if word.try_match("repeat-x") {
-            Ok(BackgroundRepeat::RepeatX)
-        } else if word.try_match("repeat-y") {
-            Ok(BackgroundRepeat::RepeatY)
-        } else if let Ok(v) = s.parse() {
-            Ok(BackgroundRepeat::SingleOrDouble(v))
-        } else {
-            word.add_expected("repeat");
-            word.add_expected("space");
-            word.add_expected("round");
-            word.add_expected("no-repeat");
-            Err(word.error())
-        }
-    }
-}
-
-impl Parse for BgRepeatPart {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let word: HyphenWord = s.parse()?;
-        if word.try_match("repeat") {
-            Ok(BgRepeatPart::Repeat)
-        } else if word.try_match("space") {
-            Ok(BgRepeatPart::Space)
-        } else if word.try_match("round") {
-            Ok(BgRepeatPart::Round)
-        } else if word.try_match("no-repeat") {
-            Ok(BgRepeatPart::NoRepeat)
-        } else {
-            Err(word.error())
-        }
-    }
-}
-
-impl Parse for BackgroundSize {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let word: HyphenWord = s.parse()?;
-        if word.try_match("cover") {
-            Ok(BackgroundSize::Cover)
-        } else if word.try_match("contain") {
-            Ok(BackgroundSize::Contain)
-        } else if let Ok(v) = s.parse() {
-            Ok(BackgroundSize::SingleOrDouble(v))
-        } else {
-            word.add_expected("<length>");
-            word.add_expected("<percentage>");
-            word.add_expected("auto");
-            Err(word.error())
-        }
-    }
-}
-
-impl Parse for Border {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        fn line_width_error(span: Span) -> syn::Error {
-            syn::Error::new(span, "the border width was specified more than once")
-        }
-        fn line_style_error(span: Span) -> syn::Error {
-            syn::Error::new(span, "the border style was specified more than once")
-        }
-        fn color_error(span: Span) -> syn::Error {
-            syn::Error::new(span, "the border color was specified more than once")
-        }
-        let mut border = Border::new();
-        while !(border.is_full() || finished_rule(s)) {
-            let mut matched_something = false; // prevents an infinite loop when no matches
-            let width_fork = s.fork();
-            match width_fork.parse::<LineWidth>() {
-                Ok(line_width) => {
-                    if border.has_line_width() {
-                        return Err(line_width_error(width_fork.cursor().span()));
-                    }
-                    matched_something = true;
-                    border.line_width = Some(line_width);
-                    s.advance_to(&width_fork);
-                }
-                Err(_) => (),
-            }
-            let style_fork = s.fork();
-            match style_fork.parse::<LineStyle>() {
-                Ok(line_style) => {
-                    if border.has_line_style() {
-                        return Err(line_style_error(style_fork.cursor().span()));
-                    }
-                    matched_something = true;
-                    border.line_style = Some(line_style);
-                    s.advance_to(&style_fork);
-                }
-                Err(_) => (),
-            }
-            let color_fork = s.fork();
-            match color_fork.parse::<Color>() {
-                Ok(color) => {
-                    if border.has_color() {
-                        return Err(color_error(color_fork.cursor().span()));
-                    }
-                    matched_something = true;
-                    border.color = Some(color);
-                    s.advance_to(&color_fork);
-                }
-                Err(_) => (),
-            }
-            if !(matched_something || finished_rule(s)) {
-                return Err(syn::Error::new(
-                    s.cursor().span(),
-                    "unexpected input - expected one of border-width, border-style, color",
-                ));
-            }
-        }
-        Ok(border)
-    }
-}
-
-#[test]
-fn test_border_color() {
-    for (input, output) in vec![
-        ("black", Rect::All(Color::Black)),
-        (
-            "#fff blue",
-            Rect::VerticalHorizontal(Color::HexRGB(255, 255, 255), Color::Blue),
-        ),
-        (
-            "blue hsl(20, 5%, 100%) white",
-            Rect::TopHorizontalBottom(Color::Blue, Color::HSL(20.0, 5.0, 100.0), Color::White),
-        ),
-        (
-            "hsla(20, 5%, 100%, 0.2) #fff #ccc white",
-            Rect::TopRightBottomLeft(
-                Color::HSLA(20.0, 5.0, 100.0, 0.2),
-                Color::HexRGB(255, 255, 255),
-                Color::HexRGB(204, 204, 204),
-                Color::White,
-            ),
-        ),
-    ] {
-        assert_eq!(syn::parse_str::<Rect<Color>>(input).unwrap(), output);
-    }
-}
-
-#[test]
-fn test_border_width() {
-    for (input, output) in vec![
-        ("1px", BorderWidth::All(LineWidth::Length(Length::Px(1.0)))),
-        (
-            "1px 2\"em\"",
-            BorderWidth::VerticalHorizontal(
-                LineWidth::Length(Length::Px(1.0)),
-                LineWidth::Length(Length::Em(2.0)),
-            ),
-        ),
-        (
-            "2\"em\" medium thick",
-            BorderWidth::TopHorizontalBottom(
-                LineWidth::Length(Length::Em(2.0)),
-                LineWidth::Medium,
-                LineWidth::Thick,
-            ),
-        ),
-        (
-            "2\"em\" medium 1px thick",
-            BorderWidth::TopRightBottomLeft(
-                LineWidth::Length(Length::Em(2.0)),
-                LineWidth::Medium,
-                LineWidth::Length(Length::Px(1.0)),
-                LineWidth::Thick,
-            ),
-        ),
-    ] {
-        assert_eq!(syn::parse_str::<BorderWidth>(input).unwrap(), output);
-    }
-
-    for input in vec!["thi", "1px 1px 1px 1px 1px"] {
-        assert!(syn::parse_str::<BorderWidth>(input).is_err());
-    }
-}
-
-impl Parse for BorderCollapse {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let word: HyphenWord = s.parse()?;
-        if word.try_match("collapse") {
-            Ok(BorderCollapse::Collapse)
-        } else if word.try_match("separate") {
-            Ok(BorderCollapse::Separate)
-        } else {
-            Err(word.error())
-        }
-    }
-}
-
-impl Parse for BoxShadow {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        syn::custom_keyword!(none);
-        if s.peek(none) {
-            s.parse::<none>()?;
-            Ok(BoxShadow::None)
-        } else {
-            Ok(BoxShadow::Shadows(s.parse()?))
-        }
-    }
-}
-
-impl Parse for BoxSizing {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let word: HyphenWord = s.parse()?;
-        if word.try_match("border-box") {
-            Ok(BoxSizing::BorderBox)
-        } else if word.try_match("content-box") {
-            Ok(BoxSizing::ContentBox)
-        } else {
-            Err(word.error())
-        }
-    }
-}
-
-impl Parse for Clear {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let word: HyphenWord = s.parse()?;
-        if word.try_match("none") {
-            Ok(Clear::None)
-        } else if word.try_match("left") {
-            Ok(Clear::Left)
-        } else if word.try_match("right") {
-            Ok(Clear::Right)
-        } else if word.try_match("both") {
-            Ok(Clear::Both)
-        } else if word.try_match("inline-start") {
-            Ok(Clear::InlineStart)
-        } else if word.try_match("inline-end") {
-            Ok(Clear::InlineEnd)
-        } else {
-            Err(word.error())
-        }
-    }
-}
-
-impl Parse for ColumnCount {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        if s.peek(syn::LitInt) {
-            Ok(ColumnCount::Fixed(s.parse::<Integer<u32>>()?.into_inner()))
-        } else {
-            let word: HyphenWord = s.parse()?;
-            word.add_expected("integer");
-            if word.try_match("auto") {
-                Ok(ColumnCount::Auto)
-            } else {
-                Err(word.error())
-            }
-        }
-    }
-}
-
-#[test]
-fn test_clear() {
-    for (input, output) in vec![
-        ("none", Clear::None),
-        ("left", Clear::Left),
-        ("right", Clear::Right),
-        ("both", Clear::Both),
-        ("inline-start", Clear::InlineStart),
-        ("inline-end", Clear::InlineEnd),
-    ] {
-        assert_eq!(syn::parse_str::<Clear>(input).unwrap(), output);
-    }
-}
-
-impl Parse for Cursor {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let word: HyphenWord = s.parse()?;
-        if word.try_match("auto") {
-            Ok(Cursor::Auto)
-        } else if word.try_match("default") {
-            Ok(Cursor::Default)
-        } else if word.try_match("none") {
-            Ok(Cursor::None)
-        } else if word.try_match("context-menu") {
-            Ok(Cursor::ContextMenu)
-        } else if word.try_match("help") {
-            Ok(Cursor::Help)
-        } else if word.try_match("pointer") {
-            Ok(Cursor::Pointer)
-        } else if word.try_match("progress") {
-            Ok(Cursor::Progress)
-        } else if word.try_match("wait") {
-            Ok(Cursor::Wait)
-        } else if word.try_match("cell") {
-            Ok(Cursor::Cell)
-        } else if word.try_match("crosshair") {
-            Ok(Cursor::Crosshair)
-        } else if word.try_match("text") {
-            Ok(Cursor::Text)
-        } else if word.try_match("vertical-text") {
-            Ok(Cursor::VerticalText)
-        } else if word.try_match("alias") {
-            Ok(Cursor::Alias)
-        } else if word.try_match("copy") {
-            Ok(Cursor::Copy)
-        } else if word.try_match("move") {
-            Ok(Cursor::Move)
-        } else if word.try_match("no-drop") {
-            Ok(Cursor::NoDrop)
-        } else if word.try_match("not-allowed") {
-            Ok(Cursor::NotAllowed)
-        } else if word.try_match("grab") {
-            Ok(Cursor::Grab)
-        } else if word.try_match("grabbing") {
-            Ok(Cursor::Grabbing)
-        } else if word.try_match("e-resize") {
-            Ok(Cursor::EResize)
-        } else if word.try_match("n-resize") {
-            Ok(Cursor::NResize)
-        } else if word.try_match("ne-resize") {
-            Ok(Cursor::NEResize)
-        } else if word.try_match("nw-resize") {
-            Ok(Cursor::NWResize)
-        } else if word.try_match("s-resize") {
-            Ok(Cursor::SResize)
-        } else if word.try_match("se-resize") {
-            Ok(Cursor::SEResize)
-        } else if word.try_match("sw-resize") {
-            Ok(Cursor::SWResize)
-        } else if word.try_match("w-resize") {
-            Ok(Cursor::WResize)
-        } else if word.try_match("ew-resize") {
-            Ok(Cursor::EWResize)
-        } else if word.try_match("ns-resize") {
-            Ok(Cursor::NSResize)
-        } else if word.try_match("nesw-resize") {
-            Ok(Cursor::NESWResize)
-        } else if word.try_match("nwse-resize") {
-            Ok(Cursor::NWSEResize)
-        } else if word.try_match("col-resize") {
-            Ok(Cursor::ColResize)
-        } else if word.try_match("row-resize") {
-            Ok(Cursor::RowResize)
-        } else if word.try_match("all-scroll") {
-            Ok(Cursor::AllScroll)
-        } else if word.try_match("zoom-in") {
-            Ok(Cursor::ZoomIn)
-        } else if word.try_match("zoom-out") {
-            Ok(Cursor::ZoomOut)
-        } else {
-            Err(word.error())
-        }
-    }
-}
-
-impl Parse for Display {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let word: HyphenWord = s.parse()?;
-        if word.try_match("block") {
-            Ok(Display::Block)
-        } else if word.try_match("flex") {
-            Ok(Display::Flex)
-        } else if word.try_match("inline") {
-            Ok(Display::Inline)
-        } else {
-            Err(word.error())
-        }
-    }
-}
-
-impl Parse for FlexBasis {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        syn::custom_keyword!(content);
-
-        if s.peek(content) {
-            s.parse::<content>()?;
-            Ok(FlexBasis::Content)
-        } else {
-            let w: Width21 = s.parse()?;
-            Ok(FlexBasis::Width(w))
-        }
-    }
-}
-
-impl Parse for FlexDirection {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let word: HyphenWord = s.parse()?;
-        if word.try_match("column") {
-            Ok(FlexDirection::Column)
-        } else if word.try_match("row") {
-            Ok(FlexDirection::Row)
-        } else {
-            Err(word.error())
-        }
-    }
-}
-
-impl Parse for FlexWrap {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let word: HyphenWord = s.parse()?;
-        if word.try_match("wrap") {
-            Ok(FlexWrap::Wrap)
-        } else if word.try_match("nowrap") {
-            Ok(FlexWrap::Nowrap)
-        } else {
-            Err(word.error())
-        }
-    }
-}
-
-impl Parse for Float {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let word: HyphenWord = s.parse()?;
-        if word.try_match("none") {
-            Ok(Float::None)
-        } else if word.try_match("left") {
-            Ok(Float::Left)
-        } else if word.try_match("right") {
-            Ok(Float::Right)
-        } else if word.try_match("inline-start") {
-            Ok(Float::InlineStart)
-        } else if word.try_match("inline-end") {
-            Ok(Float::InlineEnd)
-        } else {
-            Err(word.error())
-        }
-    }
-}
-
-impl Parse for Font {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        if s.peek(syn::LitStr) {
-            Ok(Font::Named(s.parse::<syn::LitStr>()?.value()))
-        } else {
-            let name: HyphenWord = s.parse()?;
-            name.add_expected("named font");
-
-            if name.try_match("serif") {
-                Ok(Font::Serif)
-            } else if name.try_match("sans-serif") {
-                Ok(Font::SansSerif)
-            } else if name.try_match("cursive") {
-                Ok(Font::Cursive)
-            } else if name.try_match("fantasy") {
-                Ok(Font::Fantasy)
-            } else if name.try_match("monospace") {
-                Ok(Font::Fantasy)
-            } else {
-                Err(name.error())
-            }
-        }
-    }
-}
-
-#[test]
-fn test_font_family() {
-    for (input, output) in vec![
-        (
-            "cursive",
-            FontFamily {
-                first: Font::Cursive,
-                rest: vec![],
-            },
-        ),
-        (
-            "\"Amatic SC\", sans-serif",
-            FontFamily {
-                first: Font::Named("Amatic SC".to_string()),
-                rest: vec![Font::SansSerif],
-            },
-        ),
-    ] {
-        assert_eq!(syn::parse_str::<FontFamily>(input).unwrap(), output);
-    }
-
-    for val in vec![
-        "font-family:\"Font Awesome 5 Free\"",
-        "font-family:\"Some Name\",\"Another Name\",serif",
-    ] {
-        assert_eq!(&syn::parse_str::<Style>(val).unwrap().to_string(), val);
-    }
-}
-
-impl Parse for FontSize {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let word_fork = s.fork();
-        let name: HyphenWord = word_fork.parse()?;
-
-        if name.try_match("xx-small") {
-            s.advance_to(&word_fork);
-            Ok(FontSize::XXSmall)
-        } else if name.try_match("x-small") {
-            s.advance_to(&word_fork);
-            Ok(FontSize::XSmall)
-        } else if name.try_match("small") {
-            s.advance_to(&word_fork);
-            Ok(FontSize::Small)
-        } else if name.try_match("medium") {
-            s.advance_to(&word_fork);
-            Ok(FontSize::Medium)
-        } else if name.try_match("large") {
-            s.advance_to(&word_fork);
-            Ok(FontSize::Large)
-        } else if name.try_match("x-large") {
-            s.advance_to(&word_fork);
-            Ok(FontSize::XLarge)
-        } else if name.try_match("xx-large") {
-            s.advance_to(&word_fork);
-            Ok(FontSize::XXLarge)
-        } else if name.try_match("xxx-large") {
-            s.advance_to(&word_fork);
-            Ok(FontSize::XXXLarge)
-        } else if name.try_match("larger") {
-            s.advance_to(&word_fork);
-            Ok(FontSize::Larger)
-        } else if name.try_match("smaller") {
-            s.advance_to(&word_fork);
-            Ok(FontSize::Smaller)
-        } else {
-            s.parse().map(FontSize::LengthPercentage).map_err(|_| {
-                name.add_expected("length");
-                name.add_expected("percentage");
-                name.error()
-            })
-        }
-    }
-}
-impl Parse for FontStyle {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let name: HyphenWord = s.parse()?;
-
-        if name.try_match("normal") {
-            Ok(FontStyle::Normal)
-        } else if name.try_match("italic") {
-            Ok(FontStyle::Italic)
-        } else if name.try_match("oblique") {
-            Ok(FontStyle::Oblique)
-        } else {
-            Err(name.error())
-        }
-    }
-}
-
-#[test]
-fn test_font_style() {
-    for (input, output) in vec![
-        ("normal", FontStyle::Normal),
-        ("italic", FontStyle::Italic),
-        ("oblique", FontStyle::Oblique),
-    ] {
-        assert_eq!(syn::parse_str::<FontStyle>(input).unwrap(), output);
-    }
-
-    for input in vec!["norma", "normal trailing"] {
-        assert!(syn::parse_str::<FontStyle>(input).is_err());
-    }
-}
-
-impl Parse for FontWeight {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let name: HyphenWord = s.parse()?;
-        name.add_expected("number where 1 <= number <= 1000");
-
-        if name.try_match("normal") {
-            Ok(FontWeight::Normal)
-        } else if name.try_match("bold") {
-            Ok(FontWeight::Bold)
-        } else if name.try_match("lighter") {
-            Ok(FontWeight::Lighter)
-        } else if name.try_match("bolder") {
-            Ok(FontWeight::Bolder)
-        } else {
-            let n: Number = s.parse().map_err(|_| name.error())?;
-            if n.suffix.is_empty() && n.value >= 1.0 && n.value <= 1000.0 {
-                Ok(FontWeight::Number(n.value))
-            } else {
-                Err(name.error())
-            }
-        }
-    }
-}
-
-#[test]
-fn test_font_weight() {
-    for (input, output) in vec![
-        ("normal", FontWeight::Normal),
-        ("bold", FontWeight::Bold),
-        ("lighter", FontWeight::Lighter),
-        ("bolder", FontWeight::Bolder),
-        ("1", FontWeight::Number(1.0)),
-        ("1.0", FontWeight::Number(1.0)),
-        ("1000", FontWeight::Number(1000.0)),
-        ("1000.0", FontWeight::Number(1000.0)),
-        ("246.15", FontWeight::Number(246.15)),
-    ] {
-        match syn::parse_str::<FontWeight>(input) {
-            Ok(v) => assert_eq!(v, output),
-            Err(e) => panic!("error parsing {}: {}", input, e),
-        }
-    }
-}
-
-impl Parse for JustifyContent {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let name: HyphenWord = s.parse()?;
-
-        if name.try_match("flex-start") {
-            Ok(JustifyContent::FlexStart)
-        } else if name.try_match("flex-end") {
-            Ok(JustifyContent::FlexEnd)
-        } else if name.try_match("center") {
-            Ok(JustifyContent::Center)
-        } else if name.try_match("space-between") {
-            Ok(JustifyContent::SpaceBetween)
-        } else if name.try_match("space-around") {
-            Ok(JustifyContent::SpaceAround)
-        } else if name.try_match("start") {
-            // - not in level 1 spec
-            Ok(JustifyContent::FlexStart)
-        } else if name.try_match("end") {
-            // - not in level 1 spec
-            Ok(JustifyContent::FlexEnd)
-        } else {
-            Err(name.error())
-        }
-    }
-}
-
-impl Parse for Length {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let neg = if s.peek(Token![-]) {
-            s.parse::<Token![-]>()?;
-            true
-        } else {
-            false
-        };
-        let n: Number = s.parse()?;
-        Length::parse_from_number(n, neg)
-    }
-}
-
-impl Length {
-    fn parse_from_number(n: Number, neg: bool) -> syn::Result<Self> {
-        let neg = if neg { -1.0 } else { 1.0 };
-        if n.suffix == "em" {
-            Ok(Length::Em(n.value * neg))
-        } else if n.suffix == "ex" {
-            Ok(Length::Ex(n.value * neg))
-        } else if n.suffix == "in" {
-            Ok(Length::In(n.value * neg))
-        } else if n.suffix == "cm" {
-            Ok(Length::Cm(n.value * neg))
-        } else if n.suffix == "mm" {
-            Ok(Length::Mm(n.value * neg))
-        } else if n.suffix == "pt" {
-            Ok(Length::Pt(n.value * neg))
-        } else if n.suffix == "pc" {
-            Ok(Length::Pc(n.value * neg))
-        } else if n.suffix == "px" {
-            Ok(Length::Px(n.value * neg))
-        } else if n.suffix == "" && n.value == 0.0 {
-            Ok(Length::Zero)
-        } else {
-            // No matches so return error
-            Err(syn::Error::new(
-                n.span,
-                "expected one of `\"em\"`, `\"ex\"`, `in`, `cm`, `mm`, `pt`, `pc`, `px` after number, or 0",
-            ))
-        }
-    }
-}
-
-impl Parse for LineStyle {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let name = s.parse::<HyphenWord>()?;
-        if name.try_match("none") {
-            Ok(LineStyle::None)
-        } else if name.try_match("hidden") {
-            Ok(LineStyle::Hidden)
-        } else if name.try_match("dotted") {
-            Ok(LineStyle::Dotted)
-        } else if name.try_match("dashed") {
-            Ok(LineStyle::Dashed)
-        } else if name.try_match("solid") {
-            Ok(LineStyle::Solid)
-        } else if name.try_match("double") {
-            Ok(LineStyle::Double)
-        } else if name.try_match("groove") {
-            Ok(LineStyle::Groove)
-        } else if name.try_match("ridge") {
-            Ok(LineStyle::Ridge)
-        } else if name.try_match("inset") {
-            Ok(LineStyle::Inset)
-        } else if name.try_match("outset") {
-            Ok(LineStyle::Outset)
-        } else {
-            Err(name.error())
-        }
-    }
-}
-
-impl Parse for LineWidth {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let name = s.parse::<HyphenWord>()?;
-        if name.try_match("thin") {
-            Ok(LineWidth::Thin)
-        } else if name.try_match("medium") {
-            Ok(LineWidth::Medium)
-        } else if name.try_match("thick") {
-            Ok(LineWidth::Thick)
-        } else {
-            match s.parse::<Length>() {
-                Ok(l) => Ok(LineWidth::Length(l)),
-                Err(_) => {
-                    name.add_expected("length");
-                    Err(name.error())
-                }
-            }
-        }
-    }
-}
-
-#[test]
-fn test_parse_line_width() {
-    assert_eq!(
-        syn::parse_str::<LineWidth>("thin").unwrap(),
-        LineWidth::Thin
-    );
-}
-
-impl Parse for LineHeight {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        Ok(LineHeight(s.parse::<syn::LitFloat>()?.base10_parse()?))
-    }
-}
-
-impl Parse for ListStyleType {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let name: HyphenWord = s.parse()?;
-
-        if name.try_match("disc") {
-            Ok(ListStyleType::Disc)
-        } else if name.try_match("circle") {
-            Ok(ListStyleType::Circle)
-        } else if name.try_match("square") {
-            Ok(ListStyleType::Square)
-        } else if name.try_match("decimal") {
-            Ok(ListStyleType::Decimal)
-        } else if name.try_match("decimal-leading-zero") {
-            Ok(ListStyleType::DecimalLeadingZero)
-        } else if name.try_match("lower-roman") {
-            Ok(ListStyleType::LowerRoman)
-        } else if name.try_match("upper-roman") {
-            Ok(ListStyleType::UpperRoman)
-        } else if name.try_match("lower-greek") {
-            Ok(ListStyleType::LowerGreek)
-        } else if name.try_match("upper-greek") {
-            Ok(ListStyleType::UpperGreek)
-        } else if name.try_match("lower-latin") {
-            Ok(ListStyleType::LowerLatin)
-        } else if name.try_match("upper-latin") {
-            Ok(ListStyleType::UpperLatin)
-        } else if name.try_match("armenian") {
-            Ok(ListStyleType::Armenian)
-        } else if name.try_match("georgian") {
-            Ok(ListStyleType::Georgian)
-        } else if name.try_match("lower-alpha") {
-            Ok(ListStyleType::LowerAlpha)
-        } else if name.try_match("upper-alpha") {
-            Ok(ListStyleType::UpperAlpha)
-        } else if name.try_match("none") {
-            Ok(ListStyleType::None)
-        } else {
-            Err(name.error())
-        }
-    }
-}
-
-impl Parse for MaxWidthHeight {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let name = s.parse::<HyphenWord>()?;
-        name.add_expected("length");
-        name.add_expected("percentage");
-        if name.try_match("none") {
-            Ok(MaxWidthHeight::None)
-        } else if name.try_match("min-content") {
-            Ok(MaxWidthHeight::MinContent)
-        } else if name.try_match("max-content") {
-            Ok(MaxWidthHeight::MaxContent)
-        } else if name.try_match("fit-content") {
-            let content;
-            syn::parenthesized!(content in s);
-            Ok(MaxWidthHeight::FitContent(content.parse()?))
-        } else {
-            s.parse()
-                .map(|lp| MaxWidthHeight::LengthPercentage(lp))
-                .map_err(|_| name.error())
-        }
-    }
-}
-
-#[test]
-fn test_max_width_height() {
-    let style: Style = syn::parse_str("max-width: 200px").unwrap();
-    assert_eq!(&style.to_string(), "max-width:200px");
-}
-
-impl<T> Parse for Rect<T>
-where
-    T: Parse,
-{
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let first = s.parse::<T>()?;
-        let fork = s.fork();
-        let second = match fork.parse::<T>() {
-            Ok(v) => {
-                s.advance_to(&fork);
-                v
-            }
-            Err(_) => return Ok(Rect::All(first)),
-        };
-        let third = match fork.parse::<T>() {
-            Ok(v) => {
-                s.advance_to(&fork);
-                v
-            }
-            Err(_) => return Ok(Rect::VerticalHorizontal(first, second)),
-        };
-        match fork.parse::<T>() {
-            Ok(v) => {
-                s.advance_to(&fork);
-                Ok(Rect::TopRightBottomLeft(first, second, third, v))
-            }
-            Err(_) => Ok(Rect::TopHorizontalBottom(first, second, third)),
-        }
-    }
-}
-
-impl Parse for AutoLengthPercentage {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        syn::custom_keyword!(auto);
-        if s.peek(auto) {
-            s.parse::<auto>()?;
-            Ok(AutoLengthPercentage::Auto)
-        } else {
-            Ok(AutoLengthPercentage::LengthPercentage(s.parse()?))
-        }
-    }
-}
-
-impl Parse for ObjectFit {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let name: HyphenWord = s.parse()?;
-        if name.try_match("fill") {
-            Ok(ObjectFit::Fill)
-        } else if name.try_match("none") {
-            Ok(ObjectFit::None)
-        } else if name.try_match("contain") {
-            if s.is_empty() {
-                Ok(ObjectFit::Contain { scale_down: false })
-            } else {
-                let scale_down_word: HyphenWord = s.parse()?;
-                if scale_down_word.try_match("scale-down") {
-                    Ok(ObjectFit::Contain { scale_down: true })
-                } else {
-                    Err(scale_down_word.error())
-                }
-            }
-        } else if name.try_match("cover") {
-            if HyphenWord::peek(s) {
-                let scale_down_word: HyphenWord = s.parse()?;
-                if scale_down_word.try_match("scale-down") {
-                    Ok(ObjectFit::Cover { scale_down: true })
-                } else {
-                    Err(scale_down_word.error())
-                }
-            } else {
-                Ok(ObjectFit::Cover { scale_down: false })
-            }
-        } else if name.try_match("scale-down") {
-            if HyphenWord::peek(s) {
-                let cover_contain: HyphenWord = s.parse()?;
-                if cover_contain.try_match("cover") {
-                    Ok(ObjectFit::Cover { scale_down: true })
-                } else if cover_contain.try_match("contain") {
-                    Ok(ObjectFit::Contain { scale_down: true })
-                } else {
-                    Err(cover_contain.error())
-                }
-            } else {
-                // defaults to contain when cover/contain not present
-                Ok(ObjectFit::Contain { scale_down: true })
-            }
-        } else {
-            Err(name.error())
-        }
-    }
-}
-
-impl Parse for Overflow {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let first = s.parse::<OverflowXY>()?;
-        Ok(match s.parse::<OverflowXY>() {
-            Ok(second) => Overflow::XY(first, second),
-            Err(_) => Overflow::Both(first),
-        })
-    }
-}
-
-impl Parse for OverflowXY {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let name: HyphenWord = s.parse()?;
-
-        if name.try_match("visible") {
-            Ok(OverflowXY::Visible)
-        } else if name.try_match("hidden") {
-            Ok(OverflowXY::Hidden)
-        } else if name.try_match("clip") {
-            Ok(OverflowXY::Clip)
-        } else if name.try_match("scroll") {
-            Ok(OverflowXY::Scroll)
-        } else if name.try_match("auto") {
-            Ok(OverflowXY::Auto)
-        } else {
-            Err(name.error())
-        }
-    }
-}
-
-impl Parse for Position {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let name: HyphenWord = s.parse()?;
-        if name.try_match("static") {
-            Ok(Position::Static)
-        } else if name.try_match("relative") {
-            Ok(Position::Relative)
-        } else if name.try_match("absolute") {
-            Ok(Position::Absolute)
-        } else if name.try_match("fixed") {
-            Ok(Position::Fixed)
-        } else {
-            Err(name.error())
-        }
-    }
-}
-
-#[test]
-fn test_padding() {
-    for (input, output) in vec![(
-        "padding:1\"em\"",
-        Style::Padding(Padding::All(Calc::Normal(LengthPercentage::Length(
-            Length::Em(1.0),
-        )))),
-    )] {
-        assert_eq!(syn::parse_str::<Style>(input).unwrap(), output);
-    }
-}
-
-impl Parse for Percentage {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let n: Number = s.parse()?;
-        if n.suffix == "%" {
-            Ok(Percentage(n.value))
-        } else {
-            Err(syn::Error::new(n.span, "expected percentage"))
-        }
-    }
-}
-
-impl Parse for WhiteSpace {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let name: HyphenWord = s.parse()?;
-        if name.try_match("normal") {
-            Ok(WhiteSpace::Normal)
-        } else if name.try_match("pre") {
-            Ok(WhiteSpace::Pre)
-        } else if name.try_match("nowrap") {
-            Ok(WhiteSpace::Nowrap)
-        } else if name.try_match("pre-wrap") {
-            Ok(WhiteSpace::PreWrap)
-        } else if name.try_match("pre-line") {
-            Ok(WhiteSpace::PreLine)
-        } else {
-            Err(name.error())
-        }
-    }
-}
-
-impl Parse for Width21 {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        syn::custom_keyword!(auto);
-
-        if s.peek(auto) {
-            s.parse::<auto>()?;
-            Ok(Width21::Auto)
-        } else {
-            Ok(Width21::LengthPercentage(s.parse()?))
-        }
-    }
-}
-
-impl Parse for WidthHeight {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let fork = s.fork();
-        let name: HyphenWord = fork.parse()?;
-
-        if name.try_match("auto") {
-            s.advance_to(&fork);
-            Ok(WidthHeight::Auto)
-        } else if name.try_match("min-content") {
-            s.advance_to(&fork);
-            Ok(WidthHeight::MinContent)
-        } else if name.try_match("max-content") {
-            s.advance_to(&fork);
-            Ok(WidthHeight::MaxContent)
-        } else if name.try_match("fit-content") {
-            s.advance_to(&fork);
-            let content;
-            syn::parenthesized!(content in s);
-            let lp = content.parse()?;
-            if !content.is_empty() {
-                Err(content.error("trailing tokens"))
-            } else {
-                Ok(WidthHeight::FitContent(lp))
-            }
-        } else {
-            // todo error message
-            Ok(WidthHeight::LengthPercentage(s.parse()?))
-        }
-    }
-}
-
-#[test]
-fn test_width_height() {
-    for (input, output) in vec![
-        ("0", "0"),
-        ("1px", "1px"),
-        ("1\"em\"", "1em"),
-        ("calc(100% - 60px)", "calc(100% - 60px)"),
-    ] {
-        match syn::parse_str::<WidthHeight>(input) {
-            Ok(v) => assert_eq!(&v.to_string(), output),
-            Err(e) => panic!("Error in \"{}\": {}", input, e),
-        }
-    }
-}
-
-impl Parse for LengthPercentage {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        if s.peek2(Token![%]) {
-            Ok(LengthPercentage::Percentage(s.parse()?))
-        } else {
-            Ok(LengthPercentage::Length(s.parse()?))
-        }
-    }
-}
-
-#[test]
-fn test_length_percentage() {
-    for (input, output) in vec![
-        ("1\"em\"", LengthPercentage::Length(Length::Em(1.0))),
-        ("1.0px", LengthPercentage::Length(Length::Px(1.0))),
-        ("0", LengthPercentage::Length(Length::Zero)),
-    ] {
-        assert_eq!(syn::parse_str::<LengthPercentage>(input).unwrap(), output);
-    }
-}
-
-impl Parse for Resize {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let name: HyphenWord = s.parse()?;
-
-        if name.try_match("none") {
-            Ok(Resize::None)
-        } else if name.try_match("both") {
-            Ok(Resize::Both)
-        } else if name.try_match("horizontal") {
-            Ok(Resize::Horizontal)
-        } else if name.try_match("vertical") {
-            Ok(Resize::Vertical)
-        } else {
-            Err(name.error())
-        }
-    }
-}
-
-impl Parse for Shadow {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        syn::custom_keyword!(inset);
-        let mut inset_val = false;
-        let mut length: Option<ShadowLength> = None;
-        let mut color: Option<Color> = None;
-        // keep trying all three until we're done or there is an error
-        loop {
-            let mut parsed_something = false;
-            // inset (easiest)
-            if s.peek(inset) {
-                let inset_tok = s.parse::<inset>()?;
-                if inset_val {
-                    return Err(syn::Error::new(
-                        inset_tok.span(),
-                        "`inset` must be specified 0 or 1 times",
-                    ));
-                }
-                inset_val = true;
-                parsed_something = true;
-            }
-
-            // color
-            let fork = s.fork();
-            if let Ok(parsed_color) = fork.parse::<Color>() {
-                if color.is_some() {
-                    return Err(s.error("color must be specified 0 or 1 times"));
-                }
-                color = Some(parsed_color);
-                s.advance_to(&fork);
-                parsed_something = true;
-            }
-
-            // length
-            let fork = s.fork();
-            if let Ok(parsed_length) = fork.parse::<ShadowLength>() {
-                if length.is_some() {
-                    return Err(s.error("shadow length must be specified once"));
-                }
-                length = Some(parsed_length);
-                s.advance_to(&fork);
-                parsed_something = true;
-            }
-
-            // if we've failed to parse anything, end the loop.
-            if !parsed_something {
-                break;
-            }
-        }
-        if let Some(length) = length {
-            Ok(Shadow {
-                color,
-                length,
-                inset: inset_val,
-            })
-        } else {
-            Err(s.error("expected color, length, or `inset`"))
-        }
-    }
-}
-
-impl Parse for ShadowLength {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let horizontal: Length = s.parse()?;
-        let vertical: Length = s.parse()?;
-
-        // blur
-        let fork = s.fork();
-        let blur = match fork.parse::<Length>() {
-            Ok(blur) => {
-                s.advance_to(&fork);
-                blur
-            }
-            Err(_) => {
-                return Ok(ShadowLength::Offsets {
-                    horizontal,
-                    vertical,
-                });
-            }
-        };
-
-        // spread
-        let fork = s.fork();
-        match fork.parse::<Length>() {
-            Ok(spread) => {
-                s.advance_to(&fork);
-
-                Ok(ShadowLength::OffsetsBlurSpread {
-                    horizontal,
-                    vertical,
-                    blur,
-                    spread,
-                })
-            }
-            Err(_) => Ok(ShadowLength::OffsetsBlur {
-                horizontal,
-                vertical,
-                blur,
-            }),
-        }
-    }
-}
-
-impl Parse for TextAlign {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let word: HyphenWord = s.parse()?;
-        if word.try_match("left") {
-            Ok(TextAlign::Left)
-        } else if word.try_match("right") {
-            Ok(TextAlign::Right)
-        } else if word.try_match("center") {
-            Ok(TextAlign::Center)
-        } else if word.try_match("justify") {
-            Ok(TextAlign::Justify)
-        } else {
-            Err(word.error())
-        }
-    }
-}
-
-// color
-// =====
-
-impl Parse for DynamicColor {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        Ok(if s.peek(syn::token::Brace) {
-            DynamicColor::Dynamic(s.parse()?)
-        } else {
-            DynamicColor::Literal(s.parse()?)
-        })
-    }
-}
-
-impl Parse for Color {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        if s.peek(Token![#]) {
-            return parse_hex_color(s);
-        }
-        let fn_name: HyphenWord = s.parse()?;
-        if fn_name.try_match("hsl") {
-            parse_hsl_color(s, false)
-        } else if fn_name.try_match("hsla") {
-            parse_hsl_color(s, true)
-        } else {
-            if let Some(name) = fn_name.word.as_ref() {
-                if let Some(color) = Color::from_named(name) {
-                    return Ok(color);
-                }
-            }
-            fn_name.add_expected("named color");
-            Err(fn_name.error())
-        }
-    }
-}
-
-fn parse_hex_color(s: ParseStream) -> syn::Result<Color> {
-    const ERR_MSG: &'static str = "to avoid confusing rust, please enclose hex colors in `\"`";
-    s.parse::<Token![#]>()?;
-    if !(s.peek(syn::LitStr) || s.peek(Ident)) {
-        return Err(s.error(ERR_MSG));
-    }
-    if s.peek(syn::LitStr) {
-        let hex_str: syn::LitStr = s.parse()?;
-        color::parse_hex(&hex_str.value()).ok_or(syn::Error::new(hex_str.span(), ERR_MSG))
-    } else {
-        let hex_str: Ident = s.parse()?;
-        color::parse_hex(&hex_str.to_string()).ok_or(syn::Error::new(hex_str.span(), ERR_MSG))
-    }
-}
-
-fn parse_hsl_color(s: ParseStream, with_alpha: bool) -> syn::Result<Color> {
-    let content;
-    syn::parenthesized!(content in s);
-    let n: Number = content.parse()?;
-    n.empty_suffix()?;
-    let hue = n.value;
-    if hue < 0.0 || hue >= 360.0 {
-        return Err(syn::Error::new(
-            n.span,
-            "hue should be in the range `0 <= hue < 360`",
-        ));
-    }
-    content.parse::<Token![,]>()?;
-    let n: Number = content.parse()?;
-    if n.suffix != "%" {
-        return Err(syn::Error::new(
-            n.span,
-            "saturation should be a percentage (followed by `%`)",
-        ));
-    }
-    let sat = n.value;
-    if sat < 0.0 || sat > 100.0 {
-        return Err(syn::Error::new(
-            n.span,
-            "saturation should be in the range `0 <= sat < 100`",
-        ));
-    }
-    content.parse::<Token![,]>()?;
-    let n: Number = content.parse()?;
-    if n.suffix != "%" {
-        return Err(syn::Error::new(
-            n.span,
-            "saturation should be a percentage (followed by `%`)",
-        ));
-    }
-    let light = n.value;
-    if light < 0.0 || light > 100.0 {
-        return Err(syn::Error::new(
-            n.span,
-            "lightness should be in the range `0 <= light < 100`",
-        ));
-    }
-    // since we parse content in parentheses, we can assume no trailing characers
-    if !with_alpha {
-        return if content.is_empty() {
-            Ok(Color::HSL(hue, sat, light))
-        } else {
-            Err(content.error("trailing characters"))
-        };
-    }
-    // we are a hsla
-    content.parse::<Token![,]>()?;
-    let n: Number = content.parse()?;
-    n.empty_suffix()?;
-    let alpha = n.value;
-    if alpha < 0.0 || alpha > 1.0 {
-        return Err(syn::Error::new(
-            n.span,
-            "alpha should be in the range `0 <= alpha < 1`",
-        ));
-    }
-    if content.is_empty() {
-        Ok(Color::HSLA(hue, sat, light, alpha))
-    } else {
-        Err(content.error("unexpected trailing characters"))
-    }
-}
-
-#[test]
-fn test_color() {
-    for (input, output) in vec![
-        ("#ffffffff", Color::HexRGBA(255, 255, 255, 255)),
-        ("#ffffff", Color::HexRGB(255, 255, 255)),
-        ("#fff", Color::HexRGB(255, 255, 255)),
-        ("#\"fff\"", Color::HexRGB(255, 255, 255)),
-        ("hsl(100, 50%, 50%)", Color::HSL(100.0, 50.0, 50.0)),
-        ("hsla(60, 0%, 0%, 0.2)", Color::HSLA(60.0, 0.0, 0.0, 0.2)),
-        ("black", Color::Black),
-        ("yellow", Color::Yellow),
-    ] {
-        match syn::parse_str::<Color>(input) {
-            Ok(c) => assert_eq!(c, output),
-            Err(e) => panic!("error parsing color {}: {}", input, e),
-        }
-    }
-}
-
-// Util
-// ====
-
-impl<T> Parse for NonemptyCommaList<T>
-where
-    T: Parse,
-{
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let punctuated = Punctuated::<T, Token![,]>::parse_separated_nonempty(s)?;
-        let mut iter = punctuated.into_iter();
-        let first = iter.next().unwrap();
-        Ok(Self {
-            first,
-            rest: iter.collect(),
-        })
-    }
-}
-
-impl<T> Parse for SingleOrDouble<T>
-where
-    T: Parse,
-{
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let first = T::parse(s)?;
-        let fork = s.fork();
-        Ok(match T::parse(&fork) {
-            Ok(second) => {
-                s.advance_to(&fork);
-                SingleOrDouble::Double {
-                    vert: first,
-                    horiz: second,
-                }
-            }
-            Err(_) => SingleOrDouble::Single(first),
-        })
-    }
-}
-
-/// Either a float or an int, converted in either case to f64.
-///
-/// A trailing percent (`%`) character will be consumed if the number has no suffix. This is valid
-/// according to the CSS tokeniser spec.
-///
-/// TODO This only works for floats for now. Although JS only supports floats, integer literals are
-/// used in css.
-#[derive(Debug)]
-struct Number {
-    value: f64,
-    suffix: String,
-    span: Span,
-}
-
-impl Number {
-    fn empty_suffix(&self) -> syn::Result<()> {
-        if self.suffix != "" {
-            Err(syn::Error::new(
-                self.span,
-                "unexpected characters after number",
-            ))
-        } else {
-            Ok(())
-        }
-    }
-
-    #[cfg(test)]
-    fn check_value(&self, value: f64, suffix: &str) -> bool {
-        self.value == value && self.suffix == suffix
-    }
-}
-
-impl Parse for Number {
-    fn parse(s: ParseStream) -> syn::Result<Number> {
-        let lookahead = s.lookahead1();
-        let (value, mut span, mut suffix) = if lookahead.peek(syn::LitFloat) {
-            let tok = s.parse::<syn::LitFloat>()?;
-            let num = tok.base10_parse()?;
-            (num, tok.span(), tok.suffix().to_string())
-        } else if lookahead.peek(syn::LitInt) {
-            let tok = s.parse::<syn::LitInt>()?;
-            // u32 chosen because it can be safely converted into f64
-            let num = tok.base10_parse::<u32>()?;
-            (num.into(), tok.span(), tok.suffix().to_string())
-        } else {
-            return Err(lookahead.error());
-        };
-        if suffix.is_empty() {
-            // look for a `%` for the suffix
-            if s.peek(Token![%]) {
-                let tok = s.parse::<Token![%]>()?;
-                if let Some(extra_span) = span.join(tok.span) {
-                    span = extra_span;
-                }
-                suffix.push('%');
-            // work-around using literal strings because the lexer can't support suffixes beginning
-            // with `e` for floats: https://github.com/rust-lang/rust/issues/67544
-            } else if s.peek(syn::LitStr) {
-                let tok = s.parse::<syn::LitStr>()?;
-                if let Some(extra_span) = span.join(tok.span()) {
-                    span = extra_span;
-                }
-                suffix.push_str(&tok.value());
-            }
-        }
-        Ok(Number {
-            value,
-            suffix,
-            span,
-        })
-    }
-}
-
-#[test]
-fn test_number() {
-    for (input, value, suffix) in vec![
-        ("200", 200.0, ""),
-        ("200.0", 200.0, ""),
-        ("0", 0.0, ""),
-        ("0in", 0.0, "in"),
-    ] {
-        assert!(syn::parse_str::<Number>(input)
-            .unwrap()
-            .check_value(value, suffix),)
-    }
-}
-
-/// Something like `word-separated-hyphens`
-#[derive(Debug)]
-struct HyphenWord {
-    pub span: Span,
-    pub word: Option<String>,
-    /// List of tried matches - for building error.
-    tried: TryList,
-}
-
-impl HyphenWord {
-    pub fn new(span: Span, word: String) -> Self {
-        HyphenWord {
-            span,
-            word: Some(word),
-            tried: TryList::new(),
-        }
-    }
-
-    /// This allows HyphenWords to be empty. In this case the token cursor will not advance and the
-    /// returned word will be blank.
-    pub fn new_no_word(span: Span) -> Self {
-        HyphenWord {
-            span,
-            word: None,
-            tried: TryList::new(),
-        }
-    }
-
-    pub fn try_match(&self, other: &str) -> bool {
-        if Some(other) == self.word.as_ref().map(|s| s.as_str()) {
-            true
-        } else {
-            self.tried.add_literal(other);
-            false
-        }
-    }
-
-    pub fn add_expected(&self, ty: &str) {
-        self.tried.add(ty);
-    }
-
-    /// Panics if there were no calls to `try_match` before calling this function.
-    pub fn error(&self) -> syn::Error {
-        self.tried.to_error(self.span)
-    }
-
-    /// This is cheaper than peek-specific
-    pub fn peek(s: ParseStream) -> bool {
-        s.peek(Ident)
-    }
-
-    /// Peek the next HyphenWord without advancing the parser.
-    pub fn peek_specific(s: ParseStream) -> Option<String> {
-        let fork = s.fork();
-        match HyphenWord::parse(&fork) {
-            Ok(hw) => Some(hw.word.unwrap()),
-            Err(_) => None,
-        }
-    }
-}
-
-impl Parse for HyphenWord {
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        let fork = s.fork();
-        let first = match fork.call(Ident::parse_any) {
-            Ok(v) => {
-                s.advance_to(&fork);
-                v
-            }
-            Err(_) => return Ok(HyphenWord::new_no_word(s.cursor().span())),
-        };
-        let mut word = first.to_string();
-        let mut span = first.span();
-        // This is potentially unbounded. Probably not be a problem but making a note anyway.
-        while s.peek(Token![-]) {
-            let hyphen = s.parse::<Token![-]>()?;
-            if let Some(joined) = span.join(hyphen.span) {
-                span = joined;
-            }
-            let part = s.call(Ident::parse_any)?;
-            write!(word, "-{}", part).unwrap();
-            if let Some(joined) = span.join(part.span()) {
-                span = joined;
-            }
-        }
-        Ok(HyphenWord::new(span, word))
-    }
-}
-
-#[test]
-fn test_hyphen_word() {
-    let word: HyphenWord = syn::parse_str("first-second-third").unwrap();
-    assert_eq!(word.word, Some("first-second-third".to_string()));
-    assert!(syn::parse_str::<HyphenWord>("first-second-").is_err());
-    assert!(syn::parse_str::<HyphenWord>("a a").is_err());
-}
-
-/// Keeps track of a list of tokens that have been tried.
-#[derive(Debug)]
-pub struct TryList(RefCell<BTreeSet<String>>);
-
-impl TryList {
-    pub fn new() -> Self {
-        TryList(RefCell::new(BTreeSet::new()))
-    }
-
-    /// Same as add, but with quotes
-    pub fn add_literal(&self, lit: &str) {
-        self.add(format!("`{}`", lit));
-    }
-
-    pub fn add(&self, ty: impl Into<String>) {
-        self.0.borrow_mut().insert(ty.into());
-    }
-
-    fn to_error(&self, span: Span) -> syn::Error {
-        let tried = self.0.borrow();
-        let mut iter = tried.iter();
-        let start = iter.next().unwrap().to_owned();
-        let list = iter.fold(start, |mut acc, itm| {
-            write!(acc, ", {}", itm).unwrap();
-            acc
-        });
-        let error_msg = format!("expected one of {}", list);
-        syn::Error::new(span, error_msg)
-    }
-}
-
-/// Whether we are at the end of a rule. Either the stream will be empty, or there will be a
-/// semi-colon.
-fn finished_rule(s: ParseStream) -> bool {
-    s.is_empty() || s.peek(Token![;])
-}
-
-// Parsing integers
-
-#[derive(Debug, PartialEq)]
-struct Integer<T> {
-    value: T,
-}
-
-impl<T> Integer<T> {
-    fn into_inner(self) -> T {
-        self.value
-    }
-}
-
-impl<T> Parse for Integer<T>
-where
-    T: str::FromStr + fmt::Display + PartialOrd<T>,
-    <T as str::FromStr>::Err: fmt::Display,
-{
-    fn parse(s: ParseStream) -> syn::Result<Self> {
-        Ok(Integer {
-            value: integer(s, ..)?,
-        })
-    }
-}
-
-/// Parse an integer, with an optional allowed range.
-fn integer<T, R>(s: ParseStream, range: R) -> syn::Result<T>
-where
-    R: RangeBounds<T> + fmt::Debug,
-    T: str::FromStr + fmt::Display + PartialOrd<T>,
-    <T as str::FromStr>::Err: fmt::Display,
-{
-    let fixed = s.parse::<syn::LitInt>()?;
-    let span = fixed.span();
-    if fixed.suffix().is_empty() {
-        let fixed = fixed.base10_parse()?;
-        if range.contains(&fixed) {
-            Ok(fixed)
-        } else {
-            Err(syn::Error::new(
-                span,
-                format!(
-                    "expected a number in the range {:?}, found {}",
-                    range, fixed
-                ),
-            ))
-        }
-    } else {
-        Err(syn::Error::new(span, "the number should not have a suffix"))
-    }
-}
-
-#[test]
-fn test_parse_integer() {
-    let x: Integer<u8> = syn::parse_str("123").unwrap();
-    assert_eq!(x.into_inner(), 123);
-    let x: syn::Result<Integer<u8>> = syn::parse_str("256");
-    assert!(x.is_err());
-}
-
-// tests
-
-#[test]
-fn downstream_bug1() {
-    let s: Styles = syn::parse_str(
-        "display: flex;
-        flex-direction: column;
-        flex-grow: 1;
-        flex-shrink: 0;",
-    )
-    .unwrap();
-    assert_eq!(
-        s.rules,
-        vec![
-            Style::Display(Display::Flex),
-            Style::FlexDirection(FlexDirection::Column),
-            Style::FlexGrow(1.0),
-            Style::FlexShrink(0.0)
-        ]
-    )
-}
-
-#[test]
-#[ignore]
-fn inline_logic() {
-    todo!()
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    /// Use if you want to test that something parses, but not if it looks the same when
-    /// stringified. Example "border: 1px" -> "border:1px" but we still might want to check that
-    /// the former parses.
-    fn parse(input: &str) -> Style {
-        syn::parse_str(input).unwrap()
-    }
-
-    /// This function can be used to quickly write tests to check that a parse and a stringify are
-    /// opposites.
-    fn round_trip_style(input: &str) {
-        assert_eq!(&parse(input).to_string(), input);
-    }
-
-    #[test]
-    fn border_bottom_left_radius() {
-        round_trip_style("border-bottom-left-radius:30% 3px");
-    }
-
-    #[test]
-    fn border_bottom_right_radius() {
-        round_trip_style("border-bottom-right-radius:0 0");
-    }
-
-    #[test]
-    fn border_collapse() {
-        round_trip_style("border-collapse:collapse");
-    }
-
-    #[test]
-    fn border_width() {
-        round_trip_style("border-width:1px");
-        round_trip_style("border-width:0 2px 50pt 0");
-    }
-}

+ 9 - 6
packages/core/Cargo.toml

@@ -22,15 +22,13 @@ longest-increasing-subsequence = "0.1.0"
 # internall used
 log = { version = "0.4", features = ["release_max_level_off"] }
 
-futures-util = { version = "0.3.15", default-features = false, features = [
-    "std",
-] }
+futures-util = { version = "0.3.15", default-features = false }
 
 smallvec = "1.6.1"
 
 slab = "0.4.3"
 
-futures-channel = "0.3.16"
+futures-channel = "0.3.18"
 
 # used for noderefs
 once_cell = "1.8.0"
@@ -39,6 +37,8 @@ indexmap = "1.7.0"
 
 # Serialize the Edits for use in Webview/Liveview instances
 serde = { version = "1", features = ["derive"], optional = true }
+
+# todo: I want to get rid of this
 backtrace = "0.3.63"
 
 [dev-dependencies]
@@ -49,8 +49,7 @@ rand = { version = "0.8.4", features = ["small_rng"] }
 simple_logger = "1.13.0"
 dioxus-core-macro = { path = "../core-macro", version = "0.1.2" }
 dioxus-hooks = { path = "../hooks" }
-# async-std = { version = "1.9.0", features = ["attributes"] }
-# criterion = "0.3.5"
+criterion = "0.3.5"
 
 [features]
 default = []
@@ -64,3 +63,7 @@ harness = false
 [[bench]]
 name = "jsframework"
 harness = false
+
+[[example]]
+name = "rows"
+path = "./examples/rows.rs"

+ 20 - 0
packages/core/README.md

@@ -56,3 +56,23 @@ 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.
 
+
+
+## Suspense
+
+Suspense is done through combinators on values. 
+
+```rust
+let name = get_name(cx).suspend();
+
+rsx!(
+    div {
+        {name}
+        div {
+            div {
+    
+            }
+        }
+    }
+)
+```

+ 5 - 0
packages/core/architecture.md

@@ -201,3 +201,8 @@ Open questions:
     - 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?
 
+
+
+## Render functions
+
+One main issue of components is type eras

+ 7 - 12
packages/core/benches/jsframework.rs

@@ -19,7 +19,6 @@ use dioxus_core_macro::*;
 use dioxus_html as dioxus_elements;
 use rand::prelude::*;
 
-fn main() {}
 criterion_group!(mbenches, create_rows);
 criterion_main!(mbenches);
 
@@ -28,18 +27,14 @@ fn create_rows(c: &mut Criterion) {
         let mut rng = SmallRng::from_entropy();
         let rows = (0..10_000_usize).map(|f| {
             let label = Label::new(&mut rng);
-            rsx! {
-                Row {
-                    row_id: f,
-                    label: label
-                }
-            }
+            rsx!(Row {
+                row_id: f,
+                label: label
+            })
         });
-        cx.render(rsx! {
-            table {
-                tbody {
-                    {rows}
-                }
+        rsx!(cx, table {
+            tbody {
+                {rows}
             }
         })
     };

+ 120 - 0
packages/core/examples/rows.rs

@@ -0,0 +1,120 @@
+#![allow(non_snake_case, non_upper_case_globals)]
+//! This benchmark tests just the overhead of Dioxus itself.
+//!
+//! For the JS Framework Benchmark, both the framework and the browser is benchmarked together. Dioxus prepares changes
+//! to be made, but the change application phase will be just as performant as the vanilla wasm_bindgen code. In essence,
+//! we are measuring the overhead of Dioxus, not the performance of the "apply" phase.
+//!
+//! On my MBP 2019:
+//! - Dioxus takes 3ms to create 1_000 rows
+//! - Dioxus takes 30ms to create 10_000 rows
+//!
+//! As pure "overhead", these are amazing good numbers, mostly slowed down by hitting the global allocator.
+//! These numbers don't represent Dioxus with the heuristic engine installed, so I assume it'll be even faster.
+
+use criterion::{criterion_group, criterion_main, Criterion};
+use dioxus_core as dioxus;
+use dioxus_core::prelude::*;
+use dioxus_core_macro::*;
+use dioxus_html as dioxus_elements;
+use rand::prelude::*;
+
+fn main() {
+    static App: FC<()> = |cx, _| {
+        let mut rng = SmallRng::from_entropy();
+        let rows = (0..10_000_usize).map(|f| {
+            let label = Label::new(&mut rng);
+            rsx! {
+                Row {
+                    row_id: f,
+                    label: label
+                }
+            }
+        });
+        cx.render(rsx! {
+            table {
+                tbody {
+                    {rows}
+                }
+            }
+        })
+    };
+
+    let mut dom = VirtualDom::new(App);
+    let g = dom.rebuild();
+    assert!(g.edits.len() > 1);
+}
+
+#[derive(PartialEq, Props)]
+struct RowProps {
+    row_id: usize,
+    label: Label,
+}
+fn Row(cx: Context, props: &RowProps) -> Element {
+    let [adj, col, noun] = props.label.0;
+    cx.render(rsx! {
+        tr {
+            td { class:"col-md-1", "{props.row_id}" }
+            td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
+                a { class: "lbl", "{adj}" "{col}" "{noun}" }
+            }
+            td { class: "col-md-1"
+                a { class: "remove", onclick: move |_| {/* remove */}
+                    span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
+                }
+            }
+            td { class: "col-md-6" }
+        }
+    })
+}
+
+#[derive(PartialEq)]
+struct Label([&'static str; 3]);
+
+impl Label {
+    fn new(rng: &mut SmallRng) -> Self {
+        Label([
+            ADJECTIVES.choose(rng).unwrap(),
+            COLOURS.choose(rng).unwrap(),
+            NOUNS.choose(rng).unwrap(),
+        ])
+    }
+}
+
+static ADJECTIVES: &[&str] = &[
+    "pretty",
+    "large",
+    "big",
+    "small",
+    "tall",
+    "short",
+    "long",
+    "handsome",
+    "plain",
+    "quaint",
+    "clean",
+    "elegant",
+    "easy",
+    "angry",
+    "crazy",
+    "helpful",
+    "mushy",
+    "odd",
+    "unsightly",
+    "adorable",
+    "important",
+    "inexpensive",
+    "cheap",
+    "expensive",
+    "fancy",
+];
+
+static COLOURS: &[&str] = &[
+    "red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black",
+    "orange",
+];
+
+static NOUNS: &[&str] = &[
+    "table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger",
+    "pizza", "mouse", "keyboard",
+];

+ 412 - 0
packages/core/flamegraph.svg

@@ -0,0 +1,412 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg version="1.1" width="1200" height="518" onload="init(evt)" viewBox="0 0 1200 518" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:fg="http://github.com/jonhoo/inferno"><!--Flame graph stack visualization. See https://github.com/brendangregg/FlameGraph for latest version, and http://www.brendangregg.com/flamegraphs.html for examples.--><!--NOTES: --><defs><linearGradient id="background" y1="0" y2="1" x1="0" x2="0"><stop stop-color="#eeeeee" offset="5%"/><stop stop-color="#eeeeb0" offset="95%"/></linearGradient></defs><style type="text/css">
+text { font-family:"Verdana"; font-size:12px; fill:rgb(0,0,0); }
+#title { text-anchor:middle; font-size:17px; }
+#search { opacity:0.1; cursor:pointer; }
+#search:hover, #search.show { opacity:1; }
+#subtitle { text-anchor:middle; font-color:rgb(160,160,160); }
+#unzoom { cursor:pointer; }
+#frames > *:hover { stroke:black; stroke-width:0.5; cursor:pointer; }
+.hide { display:none; }
+.parent { opacity:0.5; }
+</style><script type="text/ecmascript"><![CDATA[var nametype = 'Function:';
+var fontsize = 12;
+var fontwidth = 0.59;
+var xpad = 10;
+var inverted = false;
+var searchcolor = 'rgb(230,0,230)';
+var fluiddrawing = true;
+var truncate_text_right = false;]]><![CDATA["use strict";
+var details, searchbtn, unzoombtn, matchedtxt, svg, searching, frames;
+function init(evt) {
+    details = document.getElementById("details").firstChild;
+    searchbtn = document.getElementById("search");
+    unzoombtn = document.getElementById("unzoom");
+    matchedtxt = document.getElementById("matched");
+    svg = document.getElementsByTagName("svg")[0];
+    frames = document.getElementById("frames");
+    total_samples = parseInt(frames.attributes.total_samples.value);
+    searching = 0;
+
+    // Use GET parameters to restore a flamegraph's state.
+    var restore_state = function() {
+        var params = get_params();
+        if (params.x && params.y)
+            zoom(find_group(document.querySelector('[*|x="' + params.x + '"][y="' + params.y + '"]')));
+        if (params.s)
+            search(params.s);
+    };
+
+    if (fluiddrawing) {
+        // Make width dynamic so the SVG fits its parent's width.
+        svg.removeAttribute("width");
+        // Edge requires us to have a viewBox that gets updated with size changes.
+        var isEdge = /Edge\/\d./i.test(navigator.userAgent);
+        if (!isEdge) {
+          svg.removeAttribute("viewBox");
+        }
+        var update_for_width_change = function() {
+            if (isEdge) {
+                svg.attributes.viewBox.value = "0 0 " + svg.width.baseVal.value + " " + svg.height.baseVal.value;
+            }
+
+            // Keep consistent padding on left and right of frames container.
+            frames.attributes.width.value = svg.width.baseVal.value - xpad * 2;
+
+            // Text truncation needs to be adjusted for the current width.
+            var el = frames.children;
+            for(var i = 0; i < el.length; i++) {
+                update_text(el[i]);
+            }
+
+            // Keep search elements at a fixed distance from right edge.
+            var svgWidth = svg.width.baseVal.value;
+            searchbtn.attributes.x.value = svgWidth - xpad - 100;
+            matchedtxt.attributes.x.value = svgWidth - xpad - 100;
+        };
+        window.addEventListener('resize', function() {
+            update_for_width_change();
+        });
+        // This needs to be done asynchronously for Safari to work.
+        setTimeout(function() {
+            unzoom();
+            update_for_width_change();
+            restore_state();
+        }, 0);
+    } else {
+        restore_state();
+    }
+}
+// event listeners
+window.addEventListener("click", function(e) {
+    var target = find_group(e.target);
+    if (target) {
+        if (target.nodeName == "a") {
+            if (e.ctrlKey === false) return;
+            e.preventDefault();
+        }
+        if (target.classList.contains("parent")) unzoom();
+        zoom(target);
+
+        // set parameters for zoom state
+        var el = target.querySelector("rect");
+        if (el && el.attributes && el.attributes.y && el.attributes["fg:x"]) {
+            var params = get_params()
+            params.x = el.attributes["fg:x"].value;
+            params.y = el.attributes.y.value;
+            history.replaceState(null, null, parse_params(params));
+        }
+    }
+    else if (e.target.id == "unzoom") {
+        unzoom();
+
+        // remove zoom state
+        var params = get_params();
+        if (params.x) delete params.x;
+        if (params.y) delete params.y;
+        history.replaceState(null, null, parse_params(params));
+    }
+    else if (e.target.id == "search") search_prompt();
+}, false)
+// mouse-over for info
+// show
+window.addEventListener("mouseover", function(e) {
+    var target = find_group(e.target);
+    if (target) details.nodeValue = nametype + " " + g_to_text(target);
+}, false)
+// clear
+window.addEventListener("mouseout", function(e) {
+    var target = find_group(e.target);
+    if (target) details.nodeValue = ' ';
+}, false)
+// ctrl-F for search
+window.addEventListener("keydown",function (e) {
+    if (e.keyCode === 114 || (e.ctrlKey && e.keyCode === 70)) {
+        e.preventDefault();
+        search_prompt();
+    }
+}, false)
+// functions
+function get_params() {
+    var params = {};
+    var paramsarr = window.location.search.substr(1).split('&');
+    for (var i = 0; i < paramsarr.length; ++i) {
+        var tmp = paramsarr[i].split("=");
+        if (!tmp[0] || !tmp[1]) continue;
+        params[tmp[0]]  = decodeURIComponent(tmp[1]);
+    }
+    return params;
+}
+function parse_params(params) {
+    var uri = "?";
+    for (var key in params) {
+        uri += key + '=' + encodeURIComponent(params[key]) + '&';
+    }
+    if (uri.slice(-1) == "&")
+        uri = uri.substring(0, uri.length - 1);
+    if (uri == '?')
+        uri = window.location.href.split('?')[0];
+    return uri;
+}
+function find_child(node, selector) {
+    var children = node.querySelectorAll(selector);
+    if (children.length) return children[0];
+    return;
+}
+function find_group(node) {
+    var parent = node.parentElement;
+    if (!parent) return;
+    if (parent.id == "frames") return node;
+    return find_group(parent);
+}
+function orig_save(e, attr, val) {
+    if (e.attributes["fg:orig_" + attr] != undefined) return;
+    if (e.attributes[attr] == undefined) return;
+    if (val == undefined) val = e.attributes[attr].value;
+    e.setAttribute("fg:orig_" + attr, val);
+}
+function orig_load(e, attr) {
+    if (e.attributes["fg:orig_"+attr] == undefined) return;
+    e.attributes[attr].value = e.attributes["fg:orig_" + attr].value;
+    e.removeAttribute("fg:orig_" + attr);
+}
+function g_to_text(e) {
+    var text = find_child(e, "title").firstChild.nodeValue;
+    return (text)
+}
+function g_to_func(e) {
+    var func = g_to_text(e);
+    // if there's any manipulation we want to do to the function
+    // name before it's searched, do it here before returning.
+    return (func);
+}
+function update_text(e) {
+    var r = find_child(e, "rect");
+    var t = find_child(e, "text");
+    var w = parseFloat(r.attributes.width.value) * frames.attributes.width.value / 100 - 3;
+    var txt = find_child(e, "title").textContent.replace(/\([^(]*\)$/,"");
+    t.attributes.x.value = format_percent((parseFloat(r.attributes.x.value) + (100 * 3 / frames.attributes.width.value)));
+    // Smaller than this size won't fit anything
+    if (w < 2 * fontsize * fontwidth) {
+        t.textContent = "";
+        return;
+    }
+    t.textContent = txt;
+    // Fit in full text width
+    if (/^ *\$/.test(txt) || t.getComputedTextLength() < w)
+        return;
+    if (truncate_text_right) {
+        // Truncate the right side of the text.
+        for (var x = txt.length - 2; x > 0; x--) {
+            if (t.getSubStringLength(0, x + 2) <= w) {
+                t.textContent = txt.substring(0, x) + "..";
+                return;
+            }
+        }
+    } else {
+        // Truncate the left side of the text.
+        for (var x = 2; x < txt.length; x++) {
+            if (t.getSubStringLength(x - 2, txt.length) <= w) {
+                t.textContent = ".." + txt.substring(x, txt.length);
+                return;
+            }
+        }
+    }
+    t.textContent = "";
+}
+// zoom
+function zoom_reset(e) {
+    if (e.tagName == "rect") {
+        e.attributes.x.value = format_percent(100 * parseInt(e.attributes["fg:x"].value) / total_samples);
+        e.attributes.width.value = format_percent(100 * parseInt(e.attributes["fg:w"].value) / total_samples);
+    }
+    if (e.childNodes == undefined) return;
+    for(var i = 0, c = e.childNodes; i < c.length; i++) {
+        zoom_reset(c[i]);
+    }
+}
+function zoom_child(e, x, zoomed_width_samples) {
+    if (e.tagName == "text") {
+        var parent_x = parseFloat(find_child(e.parentNode, "rect[x]").attributes.x.value);
+        e.attributes.x.value = format_percent(parent_x + (100 * 3 / frames.attributes.width.value));
+    } else if (e.tagName == "rect") {
+        e.attributes.x.value = format_percent(100 * (parseInt(e.attributes["fg:x"].value) - x) / zoomed_width_samples);
+        e.attributes.width.value = format_percent(100 * parseInt(e.attributes["fg:w"].value) / zoomed_width_samples);
+    }
+    if (e.childNodes == undefined) return;
+    for(var i = 0, c = e.childNodes; i < c.length; i++) {
+        zoom_child(c[i], x, zoomed_width_samples);
+    }
+}
+function zoom_parent(e) {
+    if (e.attributes) {
+        if (e.attributes.x != undefined) {
+            e.attributes.x.value = "0.0%";
+        }
+        if (e.attributes.width != undefined) {
+            e.attributes.width.value = "100.0%";
+        }
+    }
+    if (e.childNodes == undefined) return;
+    for(var i = 0, c = e.childNodes; i < c.length; i++) {
+        zoom_parent(c[i]);
+    }
+}
+function zoom(node) {
+    var attr = find_child(node, "rect").attributes;
+    var width = parseInt(attr["fg:w"].value);
+    var xmin = parseInt(attr["fg:x"].value);
+    var xmax = xmin + width;
+    var ymin = parseFloat(attr.y.value);
+    unzoombtn.classList.remove("hide");
+    var el = frames.children;
+    for (var i = 0; i < el.length; i++) {
+        var e = el[i];
+        var a = find_child(e, "rect").attributes;
+        var ex = parseInt(a["fg:x"].value);
+        var ew = parseInt(a["fg:w"].value);
+        // Is it an ancestor
+        if (!inverted) {
+            var upstack = parseFloat(a.y.value) > ymin;
+        } else {
+            var upstack = parseFloat(a.y.value) < ymin;
+        }
+        if (upstack) {
+            // Direct ancestor
+            if (ex <= xmin && (ex+ew) >= xmax) {
+                e.classList.add("parent");
+                zoom_parent(e);
+                update_text(e);
+            }
+            // not in current path
+            else
+                e.classList.add("hide");
+        }
+        // Children maybe
+        else {
+            // no common path
+            if (ex < xmin || ex >= xmax) {
+                e.classList.add("hide");
+            }
+            else {
+                zoom_child(e, xmin, width);
+                update_text(e);
+            }
+        }
+    }
+}
+function unzoom() {
+    unzoombtn.classList.add("hide");
+    var el = frames.children;
+    for(var i = 0; i < el.length; i++) {
+        el[i].classList.remove("parent");
+        el[i].classList.remove("hide");
+        zoom_reset(el[i]);
+        update_text(el[i]);
+    }
+}
+// search
+function reset_search() {
+    var el = document.querySelectorAll("#frames rect");
+    for (var i = 0; i < el.length; i++) {
+        orig_load(el[i], "fill")
+    }
+    var params = get_params();
+    delete params.s;
+    history.replaceState(null, null, parse_params(params));
+}
+function search_prompt() {
+    if (!searching) {
+        var term = prompt("Enter a search term (regexp " +
+            "allowed, eg: ^ext4_)", "");
+        if (term != null) {
+            search(term)
+        }
+    } else {
+        reset_search();
+        searching = 0;
+        searchbtn.classList.remove("show");
+        searchbtn.firstChild.nodeValue = "Search"
+        matchedtxt.classList.add("hide");
+        matchedtxt.firstChild.nodeValue = ""
+    }
+}
+function search(term) {
+    var re = new RegExp(term);
+    var el = frames.children;
+    var matches = new Object();
+    var maxwidth = 0;
+    for (var i = 0; i < el.length; i++) {
+        var e = el[i];
+        // Skip over frames which are either not visible, or below the zoomed-to frame
+        if (e.classList.contains("hide") || e.classList.contains("parent")) {
+            continue;
+        }
+        var func = g_to_func(e);
+        var rect = find_child(e, "rect");
+        if (func == null || rect == null)
+            continue;
+        // Save max width. Only works as we have a root frame
+        var w = parseInt(rect.attributes["fg:w"].value);
+        if (w > maxwidth)
+            maxwidth = w;
+        if (func.match(re)) {
+            // highlight
+            var x = parseInt(rect.attributes["fg:x"].value);
+            orig_save(rect, "fill");
+            rect.attributes.fill.value = searchcolor;
+            // remember matches
+            if (matches[x] == undefined) {
+                matches[x] = w;
+            } else {
+                if (w > matches[x]) {
+                    // overwrite with parent
+                    matches[x] = w;
+                }
+            }
+            searching = 1;
+        }
+    }
+    if (!searching)
+        return;
+    var params = get_params();
+    params.s = term;
+    history.replaceState(null, null, parse_params(params));
+
+    searchbtn.classList.add("show");
+    searchbtn.firstChild.nodeValue = "Reset Search";
+    // calculate percent matched, excluding vertical overlap
+    var count = 0;
+    var lastx = -1;
+    var lastw = 0;
+    var keys = Array();
+    for (k in matches) {
+        if (matches.hasOwnProperty(k))
+            keys.push(k);
+    }
+    // sort the matched frames by their x location
+    // ascending, then width descending
+    keys.sort(function(a, b){
+        return a - b;
+    });
+    // Step through frames saving only the biggest bottom-up frames
+    // thanks to the sort order. This relies on the tree property
+    // where children are always smaller than their parents.
+    for (var k in keys) {
+        var x = parseInt(keys[k]);
+        var w = matches[keys[k]];
+        if (x >= lastx + lastw) {
+            count += w;
+            lastx = x;
+            lastw = w;
+        }
+    }
+    // display matched percent
+    matchedtxt.classList.remove("hide");
+    var pct = 100 * count / maxwidth;
+    if (pct != 100) pct = pct.toFixed(1);
+    matchedtxt.firstChild.nodeValue = "Matched: " + pct + "%";
+}
+function format_percent(n) {
+    return n.toFixed(4) + "%";
+}
+]]></script><rect x="0" y="0" width="100%" height="518" fill="url(#background)"/><text id="title" x="50.0000%" y="24.00">Flame Graph</text><text id="details" x="10" y="501.00"> </text><text id="unzoom" class="hide" x="10" y="24.00">Reset Zoom</text><text id="search" x="1090" y="24.00">Search</text><text id="matched" x="1090" y="501.00"> </text><svg id="frames" x="10" width="1180" total_samples="55"><g><title>jsframework-a8f4acf5955e8e7f`core::ptr::drop_in_place&lt;dioxus_core::virtual_dom::VirtualDom&gt; (1 samples, 1.82%)</title><rect x="0.0000%" y="261" width="1.8182%" height="15" fill="rgb(227,0,7)" fg:x="0" fg:w="1"/><text x="0.2500%" y="271.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`core::ptr::drop_in_place&lt;alloc::boxed::Box&lt;dioxus_core::scopearena::ScopeArena&gt;&gt; (1 samples, 1.82%)</title><rect x="0.0000%" y="245" width="1.8182%" height="15" fill="rgb(217,0,24)" fg:x="0" fg:w="1"/><text x="0.2500%" y="255.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`&lt;bumpalo::Bump as core::ops::drop::Drop&gt;::drop (1 samples, 1.82%)</title><rect x="0.0000%" y="229" width="1.8182%" height="15" fill="rgb(221,193,54)" fg:x="0" fg:w="1"/><text x="0.2500%" y="239.50">j..</text></g><g><title>libsystem_malloc.dylib`free_medium (1 samples, 1.82%)</title><rect x="0.0000%" y="213" width="1.8182%" height="15" fill="rgb(248,212,6)" fg:x="0" fg:w="1"/><text x="0.2500%" y="223.50">l..</text></g><g><title>libsystem_kernel.dylib`madvise (1 samples, 1.82%)</title><rect x="0.0000%" y="197" width="1.8182%" height="15" fill="rgb(208,68,35)" fg:x="0" fg:w="1"/><text x="0.2500%" y="207.50">l..</text></g><g><title>libsystem_malloc.dylib`_malloc_zone_malloc (2 samples, 3.64%)</title><rect x="9.0909%" y="181" width="3.6364%" height="15" fill="rgb(232,128,0)" fg:x="5" fg:w="2"/><text x="9.3409%" y="191.50">libs..</text></g><g><title>libsystem_malloc.dylib`szone_malloc_should_clear (2 samples, 3.64%)</title><rect x="9.0909%" y="165" width="3.6364%" height="15" fill="rgb(207,160,47)" fg:x="5" fg:w="2"/><text x="9.3409%" y="175.50">libs..</text></g><g><title>libsystem_malloc.dylib`tiny_malloc_should_clear (2 samples, 3.64%)</title><rect x="9.0909%" y="149" width="3.6364%" height="15" fill="rgb(228,23,34)" fg:x="5" fg:w="2"/><text x="9.3409%" y="159.50">libs..</text></g><g><title>libsystem_malloc.dylib`tiny_malloc_from_free_list (1 samples, 1.82%)</title><rect x="10.9091%" y="133" width="1.8182%" height="15" fill="rgb(218,30,26)" fg:x="6" fg:w="1"/><text x="11.1591%" y="143.50">l..</text></g><g><title>libsystem_malloc.dylib`tiny_free_list_add_ptr (1 samples, 1.82%)</title><rect x="10.9091%" y="117" width="1.8182%" height="15" fill="rgb(220,122,19)" fg:x="6" fg:w="1"/><text x="11.1591%" y="127.50">l..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::create_element_node (7 samples, 12.73%)</title><rect x="1.8182%" y="229" width="12.7273%" height="15" fill="rgb(250,228,42)" fg:x="1" fg:w="7"/><text x="2.0682%" y="239.50">jsframework-a8f4acf..</text></g><g><title>jsframework-a8f4acf5955e8e7f`alloc::raw_vec::RawVec&lt;T,A&gt;::reserve::do_reserve_and_handle (3 samples, 5.45%)</title><rect x="9.0909%" y="213" width="5.4545%" height="15" fill="rgb(240,193,28)" fg:x="5" fg:w="3"/><text x="9.3409%" y="223.50">jsframe..</text></g><g><title>jsframework-a8f4acf5955e8e7f`alloc::raw_vec::finish_grow (3 samples, 5.45%)</title><rect x="9.0909%" y="197" width="5.4545%" height="15" fill="rgb(216,20,37)" fg:x="5" fg:w="3"/><text x="9.3409%" y="207.50">jsframe..</text></g><g><title>libsystem_malloc.dylib`realloc (1 samples, 1.82%)</title><rect x="12.7273%" y="181" width="1.8182%" height="15" fill="rgb(206,188,39)" fg:x="7" fg:w="1"/><text x="12.9773%" y="191.50">l..</text></g><g><title>libsystem_malloc.dylib`malloc_zone_realloc (1 samples, 1.82%)</title><rect x="12.7273%" y="165" width="1.8182%" height="15" fill="rgb(217,207,13)" fg:x="7" fg:w="1"/><text x="12.9773%" y="175.50">l..</text></g><g><title>libsystem_malloc.dylib`szone_realloc (1 samples, 1.82%)</title><rect x="12.7273%" y="149" width="1.8182%" height="15" fill="rgb(231,73,38)" fg:x="7" fg:w="1"/><text x="12.9773%" y="159.50">l..</text></g><g><title>libsystem_platform.dylib`_platform_memmove$VARIANT$Haswell (1 samples, 1.82%)</title><rect x="12.7273%" y="133" width="1.8182%" height="15" fill="rgb(225,20,46)" fg:x="7" fg:w="1"/><text x="12.9773%" y="143.50">l..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::scopearena::ScopeArena::fin_head (1 samples, 1.82%)</title><rect x="16.3636%" y="213" width="1.8182%" height="15" fill="rgb(210,31,41)" fg:x="9" fg:w="1"/><text x="16.6136%" y="223.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`&lt;bumpalo::Bump as core::default::Default&gt;::default (4 samples, 7.27%)</title><rect x="21.8182%" y="197" width="7.2727%" height="15" fill="rgb(221,200,47)" fg:x="12" fg:w="4"/><text x="22.0682%" y="207.50">jsframewor..</text></g><g><title>jsframework-a8f4acf5955e8e7f`&lt;bumpalo::Bump as core::ops::drop::Drop&gt;::drop (1 samples, 1.82%)</title><rect x="29.0909%" y="197" width="1.8182%" height="15" fill="rgb(226,26,5)" fg:x="16" fg:w="1"/><text x="29.3409%" y="207.50">j..</text></g><g><title>libsystem_malloc.dylib`free_tiny (1 samples, 1.82%)</title><rect x="29.0909%" y="181" width="1.8182%" height="15" fill="rgb(249,33,26)" fg:x="16" fg:w="1"/><text x="29.3409%" y="191.50">l..</text></g><g><title>libsystem_malloc.dylib`tiny_free_no_lock (1 samples, 1.82%)</title><rect x="29.0909%" y="165" width="1.8182%" height="15" fill="rgb(235,183,28)" fg:x="16" fg:w="1"/><text x="29.3409%" y="175.50">l..</text></g><g><title>libsystem_malloc.dylib`tiny_free_list_add_ptr (1 samples, 1.82%)</title><rect x="29.0909%" y="149" width="1.8182%" height="15" fill="rgb(221,5,38)" fg:x="16" fg:w="1"/><text x="29.3409%" y="159.50">l..</text></g><g><title>libsystem_malloc.dylib`malloc_zone_realloc (1 samples, 1.82%)</title><rect x="30.9091%" y="149" width="1.8182%" height="15" fill="rgb(247,18,42)" fg:x="17" fg:w="1"/><text x="31.1591%" y="159.50">l..</text></g><g><title>libsystem_platform.dylib`DYLD-STUB$$_platform_memmove (1 samples, 1.82%)</title><rect x="30.9091%" y="133" width="1.8182%" height="15" fill="rgb(241,131,45)" fg:x="17" fg:w="1"/><text x="31.1591%" y="143.50">l..</text></g><g><title>jsframework-a8f4acf5955e8e7f`alloc::raw_vec::RawVec&lt;T,A&gt;::reserve::do_reserve_and_handle (2 samples, 3.64%)</title><rect x="30.9091%" y="197" width="3.6364%" height="15" fill="rgb(249,31,29)" fg:x="17" fg:w="2"/><text x="31.1591%" y="207.50">jsfr..</text></g><g><title>jsframework-a8f4acf5955e8e7f`alloc::raw_vec::finish_grow (2 samples, 3.64%)</title><rect x="30.9091%" y="181" width="3.6364%" height="15" fill="rgb(225,111,53)" fg:x="17" fg:w="2"/><text x="31.1591%" y="191.50">jsfr..</text></g><g><title>libsystem_malloc.dylib`realloc (2 samples, 3.64%)</title><rect x="30.9091%" y="165" width="3.6364%" height="15" fill="rgb(238,160,17)" fg:x="17" fg:w="2"/><text x="31.1591%" y="175.50">libs..</text></g><g><title>libsystem_malloc.dylib`szone_size (1 samples, 1.82%)</title><rect x="32.7273%" y="149" width="1.8182%" height="15" fill="rgb(214,148,48)" fg:x="18" fg:w="1"/><text x="32.9773%" y="159.50">l..</text></g><g><title>jsframework-a8f4acf5955e8e7f`bumpalo::Bump::with_capacity (1 samples, 1.82%)</title><rect x="34.5455%" y="197" width="1.8182%" height="15" fill="rgb(232,36,49)" fg:x="19" fg:w="1"/><text x="34.7955%" y="207.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`bumpalo::Bump::with_capacity (2 samples, 3.64%)</title><rect x="36.3636%" y="181" width="3.6364%" height="15" fill="rgb(209,103,24)" fg:x="20" fg:w="2"/><text x="36.6136%" y="191.50">jsfr..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::scope::BumpFrame::new (3 samples, 5.45%)</title><rect x="36.3636%" y="197" width="5.4545%" height="15" fill="rgb(229,88,8)" fg:x="20" fg:w="3"/><text x="36.6136%" y="207.50">jsframe..</text></g><g><title>libsystem_malloc.dylib`_malloc_zone_malloc (1 samples, 1.82%)</title><rect x="40.0000%" y="181" width="1.8182%" height="15" fill="rgb(213,181,19)" fg:x="22" fg:w="1"/><text x="40.2500%" y="191.50">l..</text></g><g><title>libsystem_malloc.dylib`szone_malloc_should_clear (1 samples, 1.82%)</title><rect x="40.0000%" y="165" width="1.8182%" height="15" fill="rgb(254,191,54)" fg:x="22" fg:w="1"/><text x="40.2500%" y="175.50">l..</text></g><g><title>libsystem_malloc.dylib`tiny_malloc_should_clear (1 samples, 1.82%)</title><rect x="40.0000%" y="149" width="1.8182%" height="15" fill="rgb(241,83,37)" fg:x="22" fg:w="1"/><text x="40.2500%" y="159.50">l..</text></g><g><title>libsystem_malloc.dylib`tiny_malloc_from_free_list (1 samples, 1.82%)</title><rect x="40.0000%" y="133" width="1.8182%" height="15" fill="rgb(233,36,39)" fg:x="22" fg:w="1"/><text x="40.2500%" y="143.50">l..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::scopearena::ScopeArena::new_with_key (14 samples, 25.45%)</title><rect x="18.1818%" y="213" width="25.4545%" height="15" fill="rgb(226,3,54)" fg:x="10" fg:w="14"/><text x="18.4318%" y="223.50">jsframework-a8f4acf5955e8e7f`dioxus_core:..</text></g><g><title>jsframework-a8f4acf5955e8e7f`hashbrown::raw::RawTable&lt;T,A&gt;::insert (1 samples, 1.82%)</title><rect x="41.8182%" y="197" width="1.8182%" height="15" fill="rgb(245,192,40)" fg:x="23" fg:w="1"/><text x="42.0682%" y="207.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::element (6 samples, 10.91%)</title><rect x="43.6364%" y="117" width="10.9091%" height="15" fill="rgb(238,167,29)" fg:x="24" fg:w="6"/><text x="43.8864%" y="127.50">jsframework-a8f4..</text></g><g><title>jsframework-a8f4acf5955e8e7f`bumpalo::Bump::alloc_layout_slow (6 samples, 10.91%)</title><rect x="43.6364%" y="101" width="10.9091%" height="15" fill="rgb(232,182,51)" fg:x="24" fg:w="6"/><text x="43.8864%" y="111.50">jsframework-a8f4..</text></g><g><title>libsystem_malloc.dylib`_malloc_zone_malloc (2 samples, 3.64%)</title><rect x="50.9091%" y="85" width="3.6364%" height="15" fill="rgb(231,60,39)" fg:x="28" fg:w="2"/><text x="51.1591%" y="95.50">libs..</text></g><g><title>libsystem_malloc.dylib`szone_malloc_should_clear (2 samples, 3.64%)</title><rect x="50.9091%" y="69" width="3.6364%" height="15" fill="rgb(208,69,12)" fg:x="28" fg:w="2"/><text x="51.1591%" y="79.50">libs..</text></g><g><title>libsystem_malloc.dylib`tiny_malloc_should_clear (2 samples, 3.64%)</title><rect x="50.9091%" y="53" width="3.6364%" height="15" fill="rgb(235,93,37)" fg:x="28" fg:w="2"/><text x="51.1591%" y="63.50">libs..</text></g><g><title>libsystem_malloc.dylib`set_tiny_meta_header_in_use (1 samples, 1.82%)</title><rect x="52.7273%" y="37" width="1.8182%" height="15" fill="rgb(213,116,39)" fg:x="29" fg:w="1"/><text x="52.9773%" y="47.50">l..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::raw_element (12 samples, 21.82%)</title><rect x="54.5455%" y="117" width="21.8182%" height="15" fill="rgb(222,207,29)" fg:x="30" fg:w="12"/><text x="54.7955%" y="127.50">jsframework-a8f4acf5955e8e7f`dioxu..</text></g><g><title>jsframework-a8f4acf5955e8e7f`bumpalo::Bump::alloc_layout_slow (12 samples, 21.82%)</title><rect x="54.5455%" y="101" width="21.8182%" height="15" fill="rgb(206,96,30)" fg:x="30" fg:w="12"/><text x="54.7955%" y="111.50">jsframework-a8f4acf5955e8e7f`bumpa..</text></g><g><title>libsystem_malloc.dylib`_malloc_zone_malloc (1 samples, 1.82%)</title><rect x="74.5455%" y="85" width="1.8182%" height="15" fill="rgb(218,138,4)" fg:x="41" fg:w="1"/><text x="74.7955%" y="95.50">l..</text></g><g><title>libsystem_malloc.dylib`szone_malloc_should_clear (1 samples, 1.82%)</title><rect x="74.5455%" y="69" width="1.8182%" height="15" fill="rgb(250,191,14)" fg:x="41" fg:w="1"/><text x="74.7955%" y="79.50">l..</text></g><g><title>libsystem_malloc.dylib`small_malloc_should_clear (1 samples, 1.82%)</title><rect x="74.5455%" y="53" width="1.8182%" height="15" fill="rgb(239,60,40)" fg:x="41" fg:w="1"/><text x="74.7955%" y="63.50">l..</text></g><g><title>jsframework-a8f4acf5955e8e7f`&lt;&amp;T as core::fmt::Display&gt;::fmt (1 samples, 1.82%)</title><rect x="76.3636%" y="101" width="1.8182%" height="15" fill="rgb(206,27,48)" fg:x="42" fg:w="1"/><text x="76.6136%" y="111.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`bumpalo::collections::string::String::into_bump_str (1 samples, 1.82%)</title><rect x="78.1818%" y="101" width="1.8182%" height="15" fill="rgb(225,35,8)" fg:x="43" fg:w="1"/><text x="78.4318%" y="111.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::text (3 samples, 5.45%)</title><rect x="76.3636%" y="117" width="5.4545%" height="15" fill="rgb(250,213,24)" fg:x="42" fg:w="3"/><text x="76.6136%" y="127.50">jsframe..</text></g><g><title>jsframework-a8f4acf5955e8e7f`core::fmt::write (1 samples, 1.82%)</title><rect x="80.0000%" y="101" width="1.8182%" height="15" fill="rgb(247,123,22)" fg:x="44" fg:w="1"/><text x="80.2500%" y="111.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`&lt;&amp;mut W as core::fmt::Write&gt;::write_str (1 samples, 1.82%)</title><rect x="80.0000%" y="85" width="1.8182%" height="15" fill="rgb(231,138,38)" fg:x="44" fg:w="1"/><text x="80.2500%" y="95.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::call (22 samples, 40.00%)</title><rect x="43.6364%" y="149" width="40.0000%" height="15" fill="rgb(231,145,46)" fg:x="24" fg:w="22"/><text x="43.8864%" y="159.50">jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::c..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::new::_{{closure}} (22 samples, 40.00%)</title><rect x="43.6364%" y="133" width="40.0000%" height="15" fill="rgb(251,118,11)" fg:x="24" fg:w="22"/><text x="43.8864%" y="143.50">jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::n..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::empty_cell (1 samples, 1.82%)</title><rect x="81.8182%" y="117" width="1.8182%" height="15" fill="rgb(217,147,25)" fg:x="45" fg:w="1"/><text x="82.0682%" y="127.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::scope::Scope::render (23 samples, 41.82%)</title><rect x="43.6364%" y="165" width="41.8182%" height="15" fill="rgb(247,81,37)" fg:x="24" fg:w="23"/><text x="43.8864%" y="175.50">jsframework-a8f4acf5955e8e7f`dioxus_core::scope::Scope::render</text></g><g><title>libsystem_platform.dylib`_platform_memmove$VARIANT$Haswell (1 samples, 1.82%)</title><rect x="83.6364%" y="149" width="1.8182%" height="15" fill="rgb(209,12,38)" fg:x="46" fg:w="1"/><text x="83.8864%" y="159.50">l..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::create_node (40 samples, 72.73%)</title><rect x="14.5455%" y="229" width="72.7273%" height="15" fill="rgb(227,1,9)" fg:x="8" fg:w="40"/><text x="14.7955%" y="239.50">jsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::create_node</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::scopearena::ScopeArena::run_scope (24 samples, 43.64%)</title><rect x="43.6364%" y="213" width="43.6364%" height="15" fill="rgb(248,47,43)" fg:x="24" fg:w="24"/><text x="43.8864%" y="223.50">jsframework-a8f4acf5955e8e7f`dioxus_core::scopearena::ScopeArena::run_s..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::component::_{{closure}} (24 samples, 43.64%)</title><rect x="43.6364%" y="197" width="43.6364%" height="15" fill="rgb(221,10,30)" fg:x="24" fg:w="24"/><text x="43.8864%" y="207.50">jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::component..</text></g><g><title>jsframework-a8f4acf5955e8e7f`jsframework::Row (24 samples, 43.64%)</title><rect x="43.6364%" y="181" width="43.6364%" height="15" fill="rgb(210,229,1)" fg:x="24" fg:w="24"/><text x="43.8864%" y="191.50">jsframework-a8f4acf5955e8e7f`jsframework::Row</text></g><g><title>libsystem_platform.dylib`_platform_memmove$VARIANT$Haswell (1 samples, 1.82%)</title><rect x="85.4545%" y="165" width="1.8182%" height="15" fill="rgb(222,148,37)" fg:x="47" fg:w="1"/><text x="85.7045%" y="175.50">l..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::work (48 samples, 87.27%)</title><rect x="1.8182%" y="245" width="87.2727%" height="15" fill="rgb(234,67,33)" fg:x="1" fg:w="48"/><text x="2.0682%" y="255.50">jsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::work</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::mount (1 samples, 1.82%)</title><rect x="87.2727%" y="229" width="1.8182%" height="15" fill="rgb(247,98,35)" fg:x="48" fg:w="1"/><text x="87.5227%" y="239.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`&lt;core::option::Option&lt;dioxus_core::lazynodes::LazyNodes&gt; as dioxus_core::nodes::IntoVNode&gt;::into_vnode (1 samples, 1.82%)</title><rect x="89.0909%" y="133" width="1.8182%" height="15" fill="rgb(247,138,52)" fg:x="49" fg:w="1"/><text x="89.3409%" y="143.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::call (1 samples, 1.82%)</title><rect x="89.0909%" y="117" width="1.8182%" height="15" fill="rgb(213,79,30)" fg:x="49" fg:w="1"/><text x="89.3409%" y="127.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::new::_{{closure}} (1 samples, 1.82%)</title><rect x="89.0909%" y="101" width="1.8182%" height="15" fill="rgb(246,177,23)" fg:x="49" fg:w="1"/><text x="89.3409%" y="111.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::component (1 samples, 1.82%)</title><rect x="89.0909%" y="85" width="1.8182%" height="15" fill="rgb(230,62,27)" fg:x="49" fg:w="1"/><text x="89.3409%" y="95.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`main (51 samples, 92.73%)</title><rect x="0.0000%" y="421" width="92.7273%" height="15" fill="rgb(216,154,8)" fg:x="0" fg:w="51"/><text x="0.2500%" y="431.50">jsframework-a8f4acf5955e8e7f`main</text></g><g><title>jsframework-a8f4acf5955e8e7f`std::rt::lang_start_internal (51 samples, 92.73%)</title><rect x="0.0000%" y="405" width="92.7273%" height="15" fill="rgb(244,35,45)" fg:x="0" fg:w="51"/><text x="0.2500%" y="415.50">jsframework-a8f4acf5955e8e7f`std::rt::lang_start_internal</text></g><g><title>jsframework-a8f4acf5955e8e7f`std::rt::lang_start::_{{closure}} (51 samples, 92.73%)</title><rect x="0.0000%" y="389" width="92.7273%" height="15" fill="rgb(251,115,12)" fg:x="0" fg:w="51"/><text x="0.2500%" y="399.50">jsframework-a8f4acf5955e8e7f`std::rt::lang_start::_{{closure}}</text></g><g><title>jsframework-a8f4acf5955e8e7f`std::sys_common::backtrace::__rust_begin_short_backtrace (51 samples, 92.73%)</title><rect x="0.0000%" y="373" width="92.7273%" height="15" fill="rgb(240,54,50)" fg:x="0" fg:w="51"/><text x="0.2500%" y="383.50">jsframework-a8f4acf5955e8e7f`std::sys_common::backtrace::__rust_begin_short_backtrace</text></g><g><title>jsframework-a8f4acf5955e8e7f`jsframework::main (51 samples, 92.73%)</title><rect x="0.0000%" y="357" width="92.7273%" height="15" fill="rgb(233,84,52)" fg:x="0" fg:w="51"/><text x="0.2500%" y="367.50">jsframework-a8f4acf5955e8e7f`jsframework::main</text></g><g><title>jsframework-a8f4acf5955e8e7f`criterion::Criterion&lt;M&gt;::bench_function (51 samples, 92.73%)</title><rect x="0.0000%" y="341" width="92.7273%" height="15" fill="rgb(207,117,47)" fg:x="0" fg:w="51"/><text x="0.2500%" y="351.50">jsframework-a8f4acf5955e8e7f`criterion::Criterion&lt;M&gt;::bench_function</text></g><g><title>jsframework-a8f4acf5955e8e7f`criterion::benchmark_group::BenchmarkGroup&lt;M&gt;::bench_function (51 samples, 92.73%)</title><rect x="0.0000%" y="325" width="92.7273%" height="15" fill="rgb(249,43,39)" fg:x="0" fg:w="51"/><text x="0.2500%" y="335.50">jsframework-a8f4acf5955e8e7f`criterion::benchmark_group::BenchmarkGroup&lt;M&gt;::bench_function</text></g><g><title>jsframework-a8f4acf5955e8e7f`criterion::routine::Routine::test (51 samples, 92.73%)</title><rect x="0.0000%" y="309" width="92.7273%" height="15" fill="rgb(209,38,44)" fg:x="0" fg:w="51"/><text x="0.2500%" y="319.50">jsframework-a8f4acf5955e8e7f`criterion::routine::Routine::test</text></g><g><title>jsframework-a8f4acf5955e8e7f`&lt;alloc::vec::Vec&lt;T&gt; as alloc::vec::spec_from_iter::SpecFromIter&lt;T,I&gt;&gt;::from_iter (51 samples, 92.73%)</title><rect x="0.0000%" y="293" width="92.7273%" height="15" fill="rgb(236,212,23)" fg:x="0" fg:w="51"/><text x="0.2500%" y="303.50">jsframework-a8f4acf5955e8e7f`&lt;alloc::vec::Vec&lt;T&gt; as alloc::vec::spec_from_iter::SpecFromIter&lt;T,I&gt;&gt;::from_iter</text></g><g><title>jsframework-a8f4acf5955e8e7f`criterion::bencher::Bencher&lt;M&gt;::iter (51 samples, 92.73%)</title><rect x="0.0000%" y="277" width="92.7273%" height="15" fill="rgb(242,79,21)" fg:x="0" fg:w="51"/><text x="0.2500%" y="287.50">jsframework-a8f4acf5955e8e7f`criterion::bencher::Bencher&lt;M&gt;::iter</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::virtual_dom::VirtualDom::rebuild (50 samples, 90.91%)</title><rect x="1.8182%" y="261" width="90.9091%" height="15" fill="rgb(211,96,35)" fg:x="1" fg:w="50"/><text x="2.0682%" y="271.50">jsframework-a8f4acf5955e8e7f`dioxus_core::virtual_dom::VirtualDom::rebuild</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::scopearena::ScopeArena::run_scope (2 samples, 3.64%)</title><rect x="89.0909%" y="245" width="3.6364%" height="15" fill="rgb(253,215,40)" fg:x="49" fg:w="2"/><text x="89.3409%" y="255.50">jsfr..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::virtual_dom::VirtualDom::new_with_props_and_scheduler::_{{closure}} (2 samples, 3.64%)</title><rect x="89.0909%" y="229" width="3.6364%" height="15" fill="rgb(211,81,21)" fg:x="49" fg:w="2"/><text x="89.3409%" y="239.50">jsfr..</text></g><g><title>jsframework-a8f4acf5955e8e7f`core::ops::function::FnOnce::call_once (2 samples, 3.64%)</title><rect x="89.0909%" y="213" width="3.6364%" height="15" fill="rgb(208,190,38)" fg:x="49" fg:w="2"/><text x="89.3409%" y="223.50">jsfr..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::scope::Scope::render (2 samples, 3.64%)</title><rect x="89.0909%" y="197" width="3.6364%" height="15" fill="rgb(235,213,38)" fg:x="49" fg:w="2"/><text x="89.3409%" y="207.50">jsfr..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::call (2 samples, 3.64%)</title><rect x="89.0909%" y="181" width="3.6364%" height="15" fill="rgb(237,122,38)" fg:x="49" fg:w="2"/><text x="89.3409%" y="191.50">jsfr..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::new::_{{closure}} (2 samples, 3.64%)</title><rect x="89.0909%" y="165" width="3.6364%" height="15" fill="rgb(244,218,35)" fg:x="49" fg:w="2"/><text x="89.3409%" y="175.50">jsfr..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::fragment_from_iter (2 samples, 3.64%)</title><rect x="89.0909%" y="149" width="3.6364%" height="15" fill="rgb(240,68,47)" fg:x="49" fg:w="2"/><text x="89.3409%" y="159.50">jsfr..</text></g><g><title>jsframework-a8f4acf5955e8e7f`core::ops::function::impls::_&lt;impl core::ops::function::FnOnce&lt;A&gt; for &amp;mut F&gt;::call_once (1 samples, 1.82%)</title><rect x="90.9091%" y="133" width="1.8182%" height="15" fill="rgb(210,16,53)" fg:x="50" fg:w="1"/><text x="91.1591%" y="143.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`rand::rng::Rng::gen_range (1 samples, 1.82%)</title><rect x="90.9091%" y="117" width="1.8182%" height="15" fill="rgb(235,124,12)" fg:x="50" fg:w="1"/><text x="91.1591%" y="127.50">j..</text></g><g><title>all (55 samples, 100%)</title><rect x="0.0000%" y="469" width="100.0000%" height="15" fill="rgb(224,169,11)" fg:x="0" fg:w="55"/><text x="0.2500%" y="479.50"></text></g><g><title>0x1 (55 samples, 100.00%)</title><rect x="0.0000%" y="453" width="100.0000%" height="15" fill="rgb(250,166,2)" fg:x="0" fg:w="55"/><text x="0.2500%" y="463.50">0x1</text></g><g><title>libdyld.dylib`start (55 samples, 100.00%)</title><rect x="0.0000%" y="437" width="100.0000%" height="15" fill="rgb(242,216,29)" fg:x="0" fg:w="55"/><text x="0.2500%" y="447.50">libdyld.dylib`start</text></g><g><title>libsystem_kernel.dylib`__exit (4 samples, 7.27%)</title><rect x="92.7273%" y="421" width="7.2727%" height="15" fill="rgb(230,116,27)" fg:x="51" fg:w="4"/><text x="92.9773%" y="431.50">libsystem_..</text></g></svg></svg>

+ 2 - 2
packages/core/src/component.rs

@@ -5,12 +5,12 @@
 //! 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, LazyNodes, NodeLink};
+use crate::innerlude::{Context, Element, LazyNodes, VPortal};
 
 pub struct FragmentProps(Element);
 pub struct FragmentBuilder<const BUILT: bool>(Element);
 impl FragmentBuilder<false> {
-    pub fn children(self, children: Option<NodeLink>) -> FragmentBuilder<true> {
+    pub fn children(self, children: Option<VPortal>) -> FragmentBuilder<true> {
         FragmentBuilder(children)
     }
 }

+ 108 - 88
packages/core/src/diff.rs

@@ -109,7 +109,6 @@ pub struct DiffState<'bump> {
     scopes: &'bump ScopeArena,
     pub mutations: Mutations<'bump>,
     pub(crate) stack: DiffStack<'bump>,
-    pub seen_scopes: FxHashSet<ScopeId>,
     pub force_diff: bool,
 }
 
@@ -119,7 +118,6 @@ impl<'bump> DiffState<'bump> {
             scopes,
             mutations: Mutations::new(),
             stack: DiffStack::new(),
-            seen_scopes: Default::default(),
             force_diff: false,
         }
     }
@@ -198,12 +196,13 @@ impl<'bump> DiffStack<'bump> {
         }
     }
 
-    fn push_subtree(&mut self) {
-        self.nodes_created_stack.push(0);
-        self.instructions.push(DiffInstruction::Mount {
-            and: MountType::Append,
-        });
-    }
+    // todo: subtrees
+    // fn push_subtree(&mut self) {
+    //     self.nodes_created_stack.push(0);
+    //     self.instructions.push(DiffInstruction::Mount {
+    //         and: MountType::Append,
+    //     });
+    // }
 
     fn push_nodes_created(&mut self, count: usize) {
         self.nodes_created_stack.push(count);
@@ -284,12 +283,12 @@ impl<'bump> DiffState<'bump> {
     // recursively push all the nodes of a tree onto the stack and return how many are there
     fn push_all_nodes(&mut self, node: &'bump VNode<'bump>) -> usize {
         match node {
-            VNode::Text(_) | VNode::Anchor(_) | VNode::Suspended(_) => {
+            VNode::Text(_) | VNode::Placeholder(_) => {
                 self.mutations.push_root(node.mounted_id());
                 1
             }
 
-            VNode::Linked(linked) => {
+            VNode::Portal(linked) => {
                 let node = unsafe { &*linked.node };
                 let node: &VNode = unsafe { std::mem::transmute(node) };
                 self.push_all_nodes(node)
@@ -354,12 +353,11 @@ impl<'bump> DiffState<'bump> {
     fn create_node(&mut self, 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::Placeholder(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::Linked(linked) => self.create_linked_node(linked),
+            VNode::Component(component) => self.create_component_node(*component),
+            VNode::Portal(linked) => self.create_linked_node(linked),
         }
     }
 
@@ -371,17 +369,7 @@ impl<'bump> DiffState<'bump> {
         self.stack.add_child_count(1);
     }
 
-    fn create_suspended_node(&mut self, suspended: &'bump VSuspended, node: &'bump VNode<'bump>) {
-        let real_id = self.scopes.reserve_node(node);
-        self.mutations.create_placeholder(real_id);
-
-        suspended.dom_id.set(Some(real_id));
-        self.stack.add_child_count(1);
-
-        self.attach_suspended_node_to_scope(suspended);
-    }
-
-    fn create_anchor_node(&mut self, anchor: &'bump VAnchor, node: &'bump VNode<'bump>) {
+    fn create_anchor_node(&mut self, anchor: &'bump VPlaceholder, node: &'bump VNode<'bump>) {
         let real_id = self.scopes.reserve_node(node);
 
         self.mutations.create_placeholder(real_id);
@@ -433,6 +421,15 @@ impl<'bump> DiffState<'bump> {
             self.mutations.set_attribute(attr, real_id.as_u64());
         }
 
+        // todo: the settext optimization
+
+        // if children.len() == 1 {
+        //     if let VNode::Text(vtext) = children[0] {
+        //         self.mutations.set_text(vtext.text, real_id.as_u64());
+        //         return;
+        //     }
+        // }
+
         if !children.is_empty() {
             self.stack.create_children(children, MountType::Append);
         }
@@ -460,8 +457,6 @@ impl<'bump> DiffState<'bump> {
             self.scopes
                 .new_with_key(fc_ptr, caller, parent_scope, container, height, subtree);
 
-        log::debug!("container for scope {:?} is {:?}", new_idx, container);
-
         // Actually initialize the caller's slot with the right address
         vcomponent.associated_scope.set(Some(new_idx));
 
@@ -469,12 +464,12 @@ impl<'bump> DiffState<'bump> {
             let cur_scope = self.scopes.get_scope(&parent_idx).unwrap();
             let extended = unsafe { std::mem::transmute(vcomponent) };
             cur_scope.items.borrow_mut().borrowed_props.push(extended);
+        } else {
+            // the props are currently bump allocated but we need to move them to the heap
         }
 
-        // TODO:
-        //  add noderefs to current noderef list Noderefs
-        //  add effects to current effect list Effects
-        let new_component = self.scopes.get_scope(&new_idx).unwrap();
+        // TODO: add noderefs to current noderef list Noderefs
+        let _new_component = self.scopes.get_scope(&new_idx).unwrap();
 
         log::debug!(
             "initializing component {:?} with height {:?}",
@@ -488,16 +483,17 @@ impl<'bump> DiffState<'bump> {
             let nextnode = self.scopes.fin_head(&new_idx);
             self.stack.create_component(new_idx, nextnode);
 
-            if new_component.is_subtree_root.get() {
-                self.stack.push_subtree();
-            }
+            // todo: subtrees
+            // if new_component.is_subtree_root.get() {
+            //     self.stack.push_subtree();
+            // }
         }
 
         // Finally, insert this scope as a seen node.
-        self.seen_scopes.insert(new_idx);
+        self.mutations.dirty_scopes.insert(new_idx);
     }
 
-    fn create_linked_node(&mut self, link: &'bump NodeLink) {
+    fn create_linked_node(&mut self, link: &'bump VPortal) {
         if link.scope_id.get().is_none() {
             if let Some(cur_scope) = self.stack.current_scope() {
                 link.scope_id.set(Some(cur_scope));
@@ -519,20 +515,17 @@ impl<'bump> DiffState<'bump> {
                 self.diff_text_nodes(old, new, old_node, new_node);
             }
             (Component(old), Component(new)) => {
-                self.diff_component_nodes(old_node, new_node, old, new)
+                self.diff_component_nodes(old_node, new_node, *old, *new)
             }
             (Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new),
-            (Anchor(old), Anchor(new)) => new.dom_id.set(old.dom_id.get()),
-            (Suspended(old), Suspended(new)) => self.diff_suspended_nodes(old, new),
+            (Placeholder(old), Placeholder(new)) => new.dom_id.set(old.dom_id.get()),
             (Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node),
-            (Linked(old), Linked(new)) => self.diff_linked_nodes(old, new),
+            (Portal(old), Portal(new)) => self.diff_linked_nodes(old, new),
 
             // Anything else is just a basic replace and create
             (
-                Linked(_) | Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_)
-                | Suspended(_),
-                Linked(_) | Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_)
-                | Suspended(_),
+                Portal(_) | Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_),
+                Portal(_) | Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_),
             ) => self
                 .stack
                 .create_node(new_node, MountType::Replace { old: old_node }),
@@ -657,6 +650,64 @@ impl<'bump> DiffState<'bump> {
             self.stack.instructions.push(DiffInstruction::PopElement);
             self.diff_children(old.children, new.children);
         }
+
+        // todo: this is for the "settext" optimization
+        // it works, but i'm not sure if it's the direction we want to take
+
+        // match (old.children.len(), new.children.len()) {
+        //     (0, 0) => {}
+        //     (1, 1) => {
+        //         let old1 = &old.children[0];
+        //         let new1 = &new.children[0];
+
+        //         match (old1, new1) {
+        //             (VNode::Text(old_text), VNode::Text(new_text)) => {
+        //                 if old_text.text != new_text.text {
+        //                     self.mutations.set_text(new_text.text, root.as_u64());
+        //                 }
+        //             }
+        //             (VNode::Text(_old_text), _) => {
+        //                 self.stack.element_stack.push(root);
+        //                 self.stack.instructions.push(DiffInstruction::PopElement);
+        //                 self.stack.create_node(new1, MountType::Append);
+        //             }
+        //             (_, VNode::Text(new_text)) => {
+        //                 self.remove_nodes([old1], false);
+        //                 self.mutations.set_text(new_text.text, root.as_u64());
+        //             }
+        //             _ => {
+        //                 self.stack.element_stack.push(root);
+        //                 self.stack.instructions.push(DiffInstruction::PopElement);
+        //                 self.diff_children(old.children, new.children);
+        //             }
+        //         }
+        //     }
+        //     (0, 1) => {
+        //         if let VNode::Text(text) = &new.children[0] {
+        //             self.mutations.set_text(text.text, root.as_u64());
+        //         } else {
+        //             self.stack.element_stack.push(root);
+        //             self.stack.instructions.push(DiffInstruction::PopElement);
+        //         }
+        //     }
+        //     (0, _) => {
+        //         self.mutations.edits.push(PushRoot {
+        //             root: root.as_u64(),
+        //         });
+        //         self.stack.element_stack.push(root);
+        //         self.stack.instructions.push(DiffInstruction::PopElement);
+        //         self.stack.create_children(new.children, MountType::Append);
+        //     }
+        //     (_, 0) => {
+        //         self.remove_nodes(old.children, false);
+        //         self.mutations.set_text("", root.as_u64());
+        //     }
+        //     (_, _) => {
+        //         self.stack.element_stack.push(root);
+        //         self.stack.instructions.push(DiffInstruction::PopElement);
+        //         self.diff_children(old.children, new.children);
+        //     }
+        // }
     }
 
     fn diff_component_nodes(
@@ -688,6 +739,7 @@ impl<'bump> DiffState<'bump> {
                     .get_scope_mut(&scope_addr)
                     .unwrap_or_else(|| panic!("could not find {:?}", scope_addr))
             };
+
             scope.caller = unsafe { std::mem::transmute(new.caller) };
 
             // React doesn't automatically memoize, but we do.
@@ -721,12 +773,7 @@ impl<'bump> DiffState<'bump> {
         self.diff_children(old.children, new.children);
     }
 
-    fn diff_suspended_nodes(&mut self, old: &'bump VSuspended, new: &'bump VSuspended) {
-        new.dom_id.set(old.dom_id.get());
-        self.attach_suspended_node_to_scope(new);
-    }
-
-    fn diff_linked_nodes(&mut self, old: &'bump NodeLink, new: &'bump NodeLink) {
+    fn diff_linked_nodes(&mut self, old: &'bump VPortal, new: &'bump VPortal) {
         if !std::ptr::eq(old.node, new.node) {
             // if the ptrs are the same then theyr're the same
             let old: &VNode = unsafe { std::mem::transmute(&*old.node) };
@@ -769,14 +816,14 @@ impl<'bump> DiffState<'bump> {
             (_, []) => {
                 self.remove_nodes(old, true);
             }
-            ([VNode::Anchor(old_anchor)], [VNode::Anchor(new_anchor)]) => {
+            ([VNode::Placeholder(old_anchor)], [VNode::Placeholder(new_anchor)]) => {
                 old_anchor.dom_id.set(new_anchor.dom_id.get());
             }
-            ([VNode::Anchor(_)], _) => {
+            ([VNode::Placeholder(_)], _) => {
                 self.stack
                     .create_children(new, MountType::Replace { old: &old[0] });
             }
-            (_, [VNode::Anchor(_)]) => {
+            (_, [VNode::Placeholder(_)]) => {
                 let new: &'bump VNode<'bump> = &new[0];
                 if let Some(first_old) = old.get(0) {
                     self.remove_nodes(&old[1..], true);
@@ -1178,9 +1225,8 @@ impl<'bump> DiffState<'bump> {
             match &search_node.take().unwrap() {
                 VNode::Text(t) => break t.dom_id.get(),
                 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(l) => {
+                VNode::Placeholder(t) => break t.dom_id.get(),
+                VNode::Portal(l) => {
                     let node: &VNode = unsafe { std::mem::transmute(&*l.node) };
                     self.find_last_element(node);
                 }
@@ -1208,14 +1254,13 @@ impl<'bump> DiffState<'bump> {
                     let scope_id = el.associated_scope.get().unwrap();
                     search_node = Some(self.scopes.root_node(&scope_id));
                 }
-                VNode::Linked(link) => {
+                VNode::Portal(link) => {
                     let node = unsafe { std::mem::transmute(&*link.node) };
                     search_node = Some(node);
                 }
                 VNode::Text(t) => break t.dom_id.get(),
                 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::Placeholder(t) => break t.dom_id.get(),
             }
         }
     }
@@ -1233,7 +1278,7 @@ impl<'bump> DiffState<'bump> {
                 self.remove_nodes(el.children, false);
             }
 
-            VNode::Text(_) | VNode::Anchor(_) | VNode::Suspended(_) => {
+            VNode::Text(_) | VNode::Placeholder(_) => {
                 let id = old
                     .try_mounted_id()
                     .unwrap_or_else(|| panic!("broke on {:?}", old));
@@ -1255,7 +1300,7 @@ impl<'bump> DiffState<'bump> {
                 self.scopes.try_remove(&scope_id).unwrap();
             }
 
-            VNode::Linked(l) => {
+            VNode::Portal(l) => {
                 let node: &'bump VNode<'bump> = unsafe { std::mem::transmute(&*l.node) };
                 self.replace_node(node, nodes_created);
             }
@@ -1282,15 +1327,7 @@ impl<'bump> DiffState<'bump> {
                         }
                     }
                 }
-                VNode::Suspended(s) => {
-                    let id = s.dom_id.get().unwrap();
-                    self.scopes.collect_garbage(id);
-
-                    if gen_muts {
-                        self.mutations.remove(id.as_u64());
-                    }
-                }
-                VNode::Anchor(a) => {
+                VNode::Placeholder(a) => {
                     let id = a.dom_id.get().unwrap();
                     self.scopes.collect_garbage(id);
 
@@ -1312,7 +1349,7 @@ impl<'bump> DiffState<'bump> {
                     self.remove_nodes(f.children, gen_muts);
                 }
 
-                VNode::Linked(l) => {
+                VNode::Portal(l) => {
                     let node = unsafe { std::mem::transmute(&*l.node) };
                     self.remove_nodes(Some(node), gen_muts);
                 }
@@ -1337,21 +1374,4 @@ impl<'bump> DiffState<'bump> {
         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
-            .stack
-            .current_scope()
-            .and_then(|id| self.scopes.get_scope(&id))
-        {
-            todo!()
-            // // safety: this lifetime is managed by the logic on scope
-            // let extended = unsafe { std::mem::transmute(suspended) };
-            // scope
-            //     .items
-            //     .borrow_mut()
-            //     .suspended_nodes
-            //     .insert(suspended.task_id, extended);
-        }
-    }
 }

+ 11 - 3
packages/core/src/lazynodes.rs

@@ -35,6 +35,14 @@ enum StackNodeStorage<'a, 'b> {
 }
 
 impl<'a, 'b> LazyNodes<'a, 'b> {
+    
+    pub fn new_some<F>(_val: F) -> Option<Self>
+    where
+        F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
+    {
+        Some(Self::new(_val))
+    }
+
     pub fn new<F>(_val: F) -> Self
     where
         F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
@@ -221,7 +229,7 @@ fn it_works() {
 
     let factory = NodeFactory { bump: &bump };
 
-    let caller = NodeFactory::annotate_lazy(|f| {
+    let caller = LazyNodes::new_some(|f| {
         //
         f.text(format_args!("hello world!"))
     })
@@ -254,7 +262,7 @@ fn it_drops() {
             .map(|i| {
                 let val = val.clone();
 
-                NodeFactory::annotate_lazy(move |f| {
+                LazyNodes::new_some(move |f| {
                     log::debug!("hell closure");
                     let inner = DropInner { id: i };
                     f.text(format_args!("hello world {:?}, {:?}", inner.id, val))
@@ -262,7 +270,7 @@ fn it_drops() {
             })
             .collect::<Vec<_>>();
 
-        NodeFactory::annotate_lazy(|f| {
+        LazyNodes::new_some(|f| {
             log::debug!("main closure");
             f.fragment_from_iter(it)
         })

+ 2 - 2
packages/core/src/lib.rs

@@ -31,14 +31,14 @@ pub(crate) mod innerlude {
     pub use crate::scopearena::*;
     pub use crate::virtual_dom::*;
 
-    pub type Element = Option<NodeLink>;
+    pub type Element = Option<VPortal>;
     pub type FC<P> = for<'a> fn(Context<'a>, &'a P) -> Element;
 }
 
 pub use crate::innerlude::{
     Attribute, Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, IntoVNode,
     LazyNodes, Listener, MountType, Mutations, NodeFactory, Properties, SchedulerMsg, ScopeId,
-    UserEvent, VAnchor, VElement, VFragment, VNode, VSuspended, VirtualDom, FC,
+    UserEvent, VElement, VFragment, VNode, VirtualDom, FC,
 };
 
 pub mod prelude {

+ 3 - 0
packages/core/src/mutations.rs

@@ -14,6 +14,7 @@ use std::{any::Any, fmt::Debug};
 /// Mutations are the only link between the RealDOM and the VirtualDOM.
 pub struct Mutations<'a> {
     pub edits: Vec<DomEdit<'a>>,
+    pub dirty_scopes: FxHashSet<ScopeId>,
     pub refs: Vec<NodeRefMutation<'a>>,
     pub effects: Vec<&'a dyn FnMut()>,
 }
@@ -103,6 +104,7 @@ pub enum DomEdit<'bump> {
     },
 }
 
+use fxhash::FxHashSet;
 use DomEdit::*;
 
 impl<'a> Mutations<'a> {
@@ -110,6 +112,7 @@ impl<'a> Mutations<'a> {
         Self {
             edits: Vec::new(),
             refs: Vec::new(),
+            dirty_scopes: Default::default(),
             effects: Vec::new(),
         }
     }

+ 58 - 91
packages/core/src/nodes.rs

@@ -97,19 +97,9 @@ pub enum VNode<'src> {
     /// ```
     Component(&'src VComponent<'src>),
 
-    /// Suspended VNodes represent chunks of the UI tree that are not yet ready to be displayed.
+    /// Placeholders are a type of placeholder VNode used when fragments don't contain any children.
     ///
-    /// # Example
-    ///
-    /// ```rust, ignore
-    ///
-    ///
-    /// ```
-    Suspended(&'src VSuspended),
-
-    /// Anchors are a type of placeholder VNode used when fragments don't contain any children.
-    ///
-    /// Anchors cannot be directly constructed via public APIs.
+    /// Placeholders cannot be directly constructed via public APIs.
     ///
     /// # Example
     ///
@@ -123,7 +113,7 @@ pub enum VNode<'src> {
     ///     assert_eq!(root, VNode::Anchor);
     /// }
     /// ```
-    Anchor(&'src VAnchor),
+    Placeholder(&'src VPlaceholder),
 
     /// A VNode that is actually a pointer to some nodes rather than the nodes directly. Useful when rendering portals
     /// or eliding lifetimes on VNodes through runtime checks.
@@ -139,7 +129,7 @@ pub enum VNode<'src> {
     ///
     /// let node: NodeLink = vdom.render_vnode(rsx!( "hello" ));
     /// ```
-    Linked(NodeLink),
+    Portal(VPortal),
 }
 
 impl<'src> VNode<'src> {
@@ -149,11 +139,9 @@ 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,
+            VNode::Placeholder(_f) => None,
+            VNode::Portal(_c) => None,
         }
     }
 
@@ -171,10 +159,8 @@ impl<'src> VNode<'src> {
         match &self {
             VNode::Text(el) => el.dom_id.get(),
             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::Placeholder(el) => el.dom_id.get(),
+            VNode::Portal(_) => None,
             VNode::Fragment(_) => None,
             VNode::Component(_) => None,
         }
@@ -183,7 +169,6 @@ impl<'src> VNode<'src> {
     pub(crate) fn children(&self) -> &[VNode<'src>] {
         match &self {
             VNode::Fragment(f) => f.children,
-            VNode::Component(_c) => todo!("children are not accessible through this"),
             _ => &[],
         }
     }
@@ -194,13 +179,12 @@ impl<'src> VNode<'src> {
             VNode::Text(t) => VNode::Text(*t),
             VNode::Element(e) => VNode::Element(*e),
             VNode::Component(c) => VNode::Component(*c),
-            VNode::Suspended(s) => VNode::Suspended(*s),
-            VNode::Anchor(a) => VNode::Anchor(*a),
+            VNode::Placeholder(a) => VNode::Placeholder(*a),
             VNode::Fragment(f) => VNode::Fragment(VFragment {
                 children: f.children,
                 key: f.key,
             }),
-            VNode::Linked(c) => VNode::Linked(NodeLink {
+            VNode::Portal(c) => VNode::Portal(VPortal {
                 scope_id: c.scope_id.clone(),
                 link_idx: c.link_idx.clone(),
                 node: c.node,
@@ -217,16 +201,13 @@ impl Debug for VNode<'_> {
                 .field("name", &el.tag_name)
                 .field("key", &el.key)
                 .finish(),
-
             VNode::Text(t) => write!(s, "VNode::VText {{ text: {} }}", t.text),
-            VNode::Anchor(_) => write!(s, "VNode::VAnchor"),
-
+            VNode::Placeholder(_) => write!(s, "VNode::VAnchor"),
             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 {{ scope_id: {:?} }}", c.scope_id.get()),
+            VNode::Portal(c) => write!(s, "VNode::VCached {{ scope_id: {:?} }}", c.scope_id.get()),
         }
     }
 }
@@ -254,7 +235,7 @@ fn empty_cell() -> Cell<Option<ElementId>> {
 }
 
 /// A placeholder node only generated when Fragments don't have any children.
-pub struct VAnchor {
+pub struct VPlaceholder {
     pub dom_id: Cell<Option<ElementId>>,
 }
 
@@ -357,10 +338,37 @@ pub struct Listener<'bump> {
     pub event: &'static str,
 
     /// The actual callback that the user specified
+    #[allow(clippy::type_complexity)]
     pub(crate) callback:
         RefCell<Option<BumpBox<'bump, dyn FnMut(std::sync::Arc<dyn Any + Send + Sync>) + 'bump>>>,
 }
 
+/// 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.
+///
+/// Linked VNodes can only be made through the [`Context::render`] method
+///
+/// Typically, NodeLinks are found *not* in a VNode. When NodeLinks are in a VNode, the NodeLink was passed into
+/// an `rsx!` call.
+///
+/// todo: remove the raw pointer and use runtime checks instead
+#[derive(Debug)]
+pub struct VPortal {
+    pub(crate) link_idx: Cell<usize>,
+    pub(crate) scope_id: Cell<Option<ScopeId>>,
+    pub(crate) node: *const VNode<'static>,
+}
+
+impl PartialEq for VPortal {
+    fn eq(&self, other: &Self) -> bool {
+        self.node == other.node
+    }
+}
+
 /// Virtual Components for custom user-defined components
 /// Only supports the functional syntax
 pub struct VComponent<'src> {
@@ -379,45 +387,14 @@ pub struct VComponent<'src> {
     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) caller: &'src dyn Fn(&'src Scope) -> Element,
 
     pub(crate) comparator: Option<&'src dyn Fn(&VComponent) -> bool>,
 
+    // todo: re-add this
     pub(crate) drop_props: RefCell<Option<BumpBox<'src, dyn FnMut()>>>,
 }
 
-pub struct VSuspended {
-    pub task_id: usize,
-    pub scope: Cell<Option<ScopeId>>,
-    pub dom_id: Cell<Option<ElementId>>,
-    pub parent: Cell<Option<ElementId>>,
-}
-
-/// 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.
-///
-/// Linked VNodes can only be made through the [`Context::render`] method
-///
-/// Typically, NodeLinks are found *not* in a VNode. When NodeLinks are in a VNode, the NodeLink was passed into
-/// an `rsx!` call.
-#[derive(Debug)]
-pub struct NodeLink {
-    pub(crate) link_idx: Cell<usize>,
-    pub(crate) scope_id: Cell<Option<ScopeId>>,
-    pub(crate) node: *const VNode<'static>,
-}
-
-impl PartialEq for NodeLink {
-    fn eq(&self, other: &Self) -> bool {
-        self.node == other.node
-    }
-}
-
 /// This struct provides an ergonomic API to quickly build VNodes.
 ///
 /// NodeFactory is used to build VNodes in the component's memory space.
@@ -637,8 +614,8 @@ impl<'a> NodeFactory<'a> {
         callback: BumpBox<'a, dyn FnMut(Arc<dyn Any + Send + Sync>) + 'a>,
     ) -> Listener<'a> {
         Listener {
-            mounted_node: Cell::new(None),
             event,
+            mounted_node: Cell::new(None),
             callback: RefCell::new(Some(callback)),
         }
     }
@@ -647,15 +624,14 @@ impl<'a> NodeFactory<'a> {
         self,
         node_iter: impl IntoIterator<Item = impl IntoVNode<'a> + 'c> + 'b,
     ) -> VNode<'a> {
-        let bump = self.bump;
-        let mut nodes = bumpalo::collections::Vec::new_in(bump);
+        let mut nodes = bumpalo::collections::Vec::new_in(self.bump);
 
         for node in node_iter {
             nodes.push(node.into_vnode(self));
         }
 
         if nodes.is_empty() {
-            nodes.push(VNode::Anchor(bump.alloc(VAnchor {
+            nodes.push(VNode::Placeholder(self.bump.alloc(VPlaceholder {
                 dom_id: empty_cell(),
             })));
         }
@@ -670,26 +646,23 @@ impl<'a> NodeFactory<'a> {
         self,
         node_iter: impl IntoIterator<Item = impl IntoVNode<'a> + 'c> + 'b,
     ) -> VNode<'a> {
-        let bump = self.bump;
-        let mut nodes = bumpalo::collections::Vec::new_in(bump);
+        let mut nodes = bumpalo::collections::Vec::new_in(self.bump);
 
         for node in node_iter {
             nodes.push(node.into_vnode(self));
         }
 
         if nodes.is_empty() {
-            nodes.push(VNode::Anchor(bump.alloc(VAnchor {
+            nodes.push(VNode::Placeholder(self.bump.alloc(VPlaceholder {
                 dom_id: empty_cell(),
             })));
         }
 
         let children = nodes.into_bump_slice();
 
-        // todo: add a backtrace
         if cfg!(debug_assertions) && children.len() > 1 && children.last().unwrap().key().is_none()
         {
-            use backtrace::Backtrace;
-            let bt = Backtrace::new();
+            // todo: make the backtrace prettier or remove it altogether
             log::error!(
                 r#"
                 Warning: Each child in an array or iterator should have a unique "key" prop.
@@ -698,7 +671,7 @@ impl<'a> NodeFactory<'a> {
                 -------------
                 {:?}
                 "#,
-                bt
+                backtrace::Backtrace::new()
             );
         }
 
@@ -722,7 +695,7 @@ impl<'a> NodeFactory<'a> {
         }
 
         if nodes.is_empty() {
-            nodes.push(VNode::Anchor(bump.alloc(VAnchor {
+            nodes.push(VNode::Placeholder(bump.alloc(VPlaceholder {
                 dom_id: empty_cell(),
             })));
         }
@@ -753,18 +726,12 @@ impl<'a> NodeFactory<'a> {
             key: None,
         });
         let ptr = self.bump.alloc(frag) as *const _;
-        Some(NodeLink {
+        Some(VPortal {
             link_idx: Default::default(),
             scope_id: Default::default(),
             node: unsafe { std::mem::transmute(ptr) },
         })
     }
-
-    pub fn annotate_lazy<'z, 'b>(
-        f: impl FnOnce(NodeFactory<'z>) -> VNode<'z> + 'b,
-    ) -> Option<LazyNodes<'z, 'b>> {
-        Some(LazyNodes::new(f))
-    }
 }
 
 impl Debug for NodeFactory<'_> {
@@ -860,10 +827,10 @@ impl IntoVNode<'_> for Arguments<'_> {
 }
 
 // called cx.render from a helper function
-impl IntoVNode<'_> for Option<NodeLink> {
+impl IntoVNode<'_> for Option<VPortal> {
     fn into_vnode(self, _cx: NodeFactory) -> VNode {
         match self {
-            Some(node) => VNode::Linked(node),
+            Some(node) => VNode::Portal(node),
             None => {
                 todo!()
             }
@@ -873,10 +840,10 @@ impl IntoVNode<'_> for Option<NodeLink> {
 
 // essentially passing elements through props
 // just build a new element in place
-impl IntoVNode<'_> for &Option<NodeLink> {
+impl IntoVNode<'_> for &Option<VPortal> {
     fn into_vnode(self, _cx: NodeFactory) -> VNode {
         match self {
-            Some(node) => VNode::Linked(NodeLink {
+            Some(node) => VNode::Portal(VPortal {
                 link_idx: node.link_idx.clone(),
                 scope_id: node.scope_id.clone(),
                 node: node.node,
@@ -889,15 +856,15 @@ impl IntoVNode<'_> for &Option<NodeLink> {
     }
 }
 
-impl IntoVNode<'_> for NodeLink {
+impl IntoVNode<'_> for VPortal {
     fn into_vnode(self, _cx: NodeFactory) -> VNode {
-        VNode::Linked(self)
+        VNode::Portal(self)
     }
 }
 
-impl IntoVNode<'_> for &NodeLink {
+impl IntoVNode<'_> for &VPortal {
     fn into_vnode(self, _cx: NodeFactory) -> VNode {
-        VNode::Linked(NodeLink {
+        VNode::Portal(VPortal {
             link_idx: self.link_idx.clone(),
             scope_id: self.scope_id.clone(),
             node: self.node,

+ 50 - 173
packages/core/src/scope.rs

@@ -1,7 +1,6 @@
 use crate::innerlude::*;
 
 use futures_channel::mpsc::UnboundedSender;
-use fxhash::FxHashMap;
 use smallvec::SmallVec;
 use std::{
     any::{Any, TypeId},
@@ -44,6 +43,8 @@ pub type Context<'a> = &'a Scope;
 /// use case they might have.
 pub struct Scope {
     pub(crate) parent_scope: Option<*mut Scope>,
+
+    // parent element I think?
     pub(crate) container: ElementId,
 
     pub(crate) our_arena_idx: ScopeId,
@@ -62,7 +63,9 @@ pub struct Scope {
 
     pub(crate) items: RefCell<SelfReferentialItems<'static>>,
 
-    pub(crate) hooks: HookList,
+    pub(crate) hook_arena: Bump,
+    pub(crate) hook_vals: RefCell<SmallVec<[*mut dyn Any; 5]>>,
+    pub(crate) hook_idx: Cell<usize>,
 
     pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
 
@@ -72,15 +75,13 @@ pub struct Scope {
 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>,
     pub(crate) tasks: Vec<BumpBox<'a, dyn Future<Output = ()>>>,
-    pub(crate) pending_effects: Vec<BumpBox<'a, dyn FnMut()>>,
 }
 
 /// 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
-/// unmounted, then the `ScopeId` will be reused for a new component.
+/// `ScopeId` is a `usize` that is unique across the entire VirtualDOM and across time. ScopeIDs will never be reused
+/// once a component has been unmounted.
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 pub struct ScopeId(pub usize);
@@ -103,7 +104,9 @@ impl Scope {
     ///
     /// assert_eq!(base.subtree(), 0);
     /// ```
-    pub fn subtree(&self) -> u32 {
+    ///
+    /// todo: enable
+    pub(crate) fn _subtree(&self) -> u32 {
         self.subtree.get()
     }
 
@@ -122,14 +125,13 @@ impl Scope {
     ///     rsx!(cx, div { "Subtree {id}"})
     /// };
     /// ```
-    pub fn create_subtree(&self) -> Option<u32> {
+    ///
+    /// todo: enable subtree
+    pub(crate) fn _create_subtree(&self) -> Option<u32> {
         if self.is_subtree_root.get() {
             None
         } else {
             todo!()
-            // let cur = self.subtree().get();
-            // self.shared.cur_subtree.set(cur + 1);
-            // Some(cur)
         }
     }
 
@@ -140,7 +142,7 @@ impl Scope {
     /// # Example
     ///
     /// ```rust, ignore
-    /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} }));
+    /// let mut dom = VirtualDom::new(|cx, props| cx.render(rsx!{ div {} }));
     /// dom.rebuild();
     ///
     /// let base = dom.base_scope();
@@ -160,7 +162,7 @@ impl Scope {
     /// # Example
     ///
     /// ```rust, ignore
-    /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} }));
+    /// let mut dom = VirtualDom::new(|cx, props| cx.render(rsx!{ div {} }));
     /// dom.rebuild();
     ///
     /// let base = dom.base_scope();
@@ -168,6 +170,7 @@ impl Scope {
     /// assert_eq!(base.parent(), None);
     /// ```
     pub fn parent(&self) -> Option<ScopeId> {
+        // safety: the pointer to our parent is *always* valid thanks to the bump arena
         self.parent_scope.map(|p| unsafe { &*p }.our_arena_idx)
     }
 
@@ -178,7 +181,7 @@ impl Scope {
     /// # Example
     ///
     /// ```rust, ignore
-    /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} }));
+    /// let mut dom = VirtualDom::new(|cx, props| cx.render(rsx!{ div {} }));
     /// dom.rebuild();
     /// let base = dom.base_scope();
     ///
@@ -192,11 +195,8 @@ impl Scope {
     ///
     /// ## Notice: you should prefer using prepare_update and get_scope_id
     pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
-        // pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
-        let chan = self.sender.clone();
-        let id = self.scope_id();
+        let (chan, id) = (self.sender.clone(), self.scope_id());
         Rc::new(move || {
-            log::debug!("set on channel an update for scope {:?}", id);
             let _ = chan.unbounded_send(SchedulerMsg::Immediate(id));
         })
     }
@@ -284,25 +284,11 @@ impl Scope {
         }
     }
 
-    /// Push an effect to be ran after the component has been successfully mounted to the dom
-    /// Returns the effect's position in the stack
-    pub fn push_effect<'src>(&'src self, effect: impl FnOnce() + 'src) -> usize {
-        // this is some tricker to get around not being able to actually call fnonces
-        let mut slot = Some(effect);
-        let fut: &mut dyn FnMut() = self.bump().alloc(move || slot.take().unwrap()());
-
-        // wrap it in a type that will actually drop the contents
-        let boxed_fut = unsafe { BumpBox::from_raw(fut) };
-
-        // erase the 'src lifetime for self-referential storage
-        let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
-
-        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
+    /// Pushes the future onto the poll queue to be polled after the component renders.
+    ///
+    ///
+    ///
+    ///
     /// The future is forcibly dropped if the component is not ready by the next render
     pub fn push_task<'src, F: Future<Output = ()>>(
         &'src self,
@@ -313,7 +299,7 @@ impl Scope {
         F: 'src,
     {
         self.sender
-            .unbounded_send(SchedulerMsg::TaskPushed(self.our_arena_idx))
+            .unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx))
             .unwrap();
 
         // allocate the future
@@ -347,7 +333,7 @@ impl Scope {
     ///     cx.render(lazy_tree)
     /// }
     ///```
-    pub fn render<'src>(&'src self, rsx: Option<LazyNodes<'src, '_>>) -> Option<NodeLink> {
+    pub fn render<'src>(&'src self, rsx: Option<LazyNodes<'src, '_>>) -> Option<VPortal> {
         let frame = self.wip_frame();
         let bump = &frame.bump;
         let factory = NodeFactory { bump };
@@ -357,7 +343,7 @@ impl Scope {
         let node_ptr = node as *mut _;
         let node_ptr = unsafe { std::mem::transmute(node_ptr) };
 
-        let link = NodeLink {
+        let link = VPortal {
             scope_id: Cell::new(Some(self.our_arena_idx)),
             link_idx: Cell::new(0),
             node: node_ptr,
@@ -366,31 +352,6 @@ impl Scope {
         Some(link)
     }
 
-    pub fn suspend<'src, F: Future<Output = Element> + 'src>(
-        &'src self,
-        mut fut: impl FnMut() -> F,
-    ) -> Option<VNode> {
-        let channel = self.sender.clone();
-        let node_fut = fut();
-
-        let scope = self.scope_id();
-
-        // self.push_task(move || {
-        //
-        // async move {
-        //     //
-        //     let r = node_fut.await;
-        //     if let Some(node) = r {
-        //         channel
-        //             .unbounded_send(SchedulerMsg::Suspended { node, scope })
-        //             .unwrap();
-        //     }
-        // }
-        // });
-
-        todo!()
-    }
-
     /// Store a value between renders
     ///
     /// This is *the* foundational hook for all other hooks.
@@ -417,24 +378,35 @@ impl Scope {
         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()));
+        let mut vals = self.hook_vals.borrow_mut();
+        let hook_len = vals.len();
+        let cur_idx = self.hook_idx.get();
+
+        if cur_idx >= hook_len {
+            let val = self.hook_arena.alloc(initializer(hook_len));
+            vals.push(val);
         }
 
-        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.
+        let state = vals
+            .get(cur_idx)
+            .and_then(|inn| {
+                self.hook_idx.set(cur_idx + 1);
+                let raw_box = unsafe { &mut **inn };
+                raw_box.downcast_mut::<State>()
+            })
+            .expect(
+                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.
-"###;
+                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))
+        runner(state)
     }
-}
 
-// 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 {
@@ -471,25 +443,6 @@ impl Scope {
         self.generation.set(self.generation.get() + 1);
     }
 
-    // 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 = suspended;
-            // 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 fn root_node(&self) -> &VNode {
         let node = *self.wip_frame().nodes.borrow().get(0).unwrap();
         unsafe { std::mem::transmute(&*node) }
@@ -514,11 +467,7 @@ impl BumpFrame {
         Self { bump, nodes }
     }
 
-    pub fn allocated_bytes(&self) -> usize {
-        self.bump.allocated_bytes()
-    }
-
-    pub fn assign_nodelink(&self, node: &NodeLink) {
+    pub fn assign_nodelink(&self, node: &VPortal) {
         let mut nodes = self.nodes.borrow_mut();
 
         let len = nodes.len();
@@ -528,78 +477,6 @@ impl BumpFrame {
     }
 }
 
-/// An abstraction over internally stored data using a hook-based memory layout.
-///
-/// Hooks are allocated using Boxes and then our stored references are given out.
-///
-/// It's unsafe to "reset" the hooklist, but it is safe to add hooks into it.
-///
-/// Todo: this could use its very own bump arena, but that might be a tad overkill
-#[derive(Default)]
-pub(crate) struct HookList {
-    arena: Bump,
-    vals: RefCell<SmallVec<[*mut dyn Any; 5]>>,
-    idx: Cell<usize>,
-}
-
-impl HookList {
-    pub fn new(capacity: usize) -> Self {
-        Self {
-            arena: Bump::with_capacity(capacity),
-            ..Default::default()
-        }
-    }
-
-    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 };
-            raw_box.downcast_mut::<T>()
-        })
-    }
-
-    /// This resets the internal iterator count
-    /// It's okay that we've given out each hook, but now we have the opportunity to give it out again
-    /// Therefore, resetting is considered unsafe
-    ///
-    /// 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(&self) {
-        self.idx.set(0);
-    }
-
-    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 {
-        self.vals.borrow().len()
-    }
-
-    pub(crate) fn cur_idx(&self) -> usize {
-        self.idx.get()
-    }
-
-    pub(crate) fn at_end(&self) -> bool {
-        self.cur_idx() >= self.len()
-    }
-
-    pub fn clear(&mut self) {
-        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()
-    }
-}
-
 #[test]
 fn sizeof() {
     dbg!(std::mem::size_of::<Scope>());

+ 20 - 22
packages/core/src/scopearena.rs

@@ -92,15 +92,15 @@ impl ScopeArena {
         let new_scope_id = ScopeId(self.scope_counter.get());
         self.scope_counter.set(self.scope_counter.get() + 1);
 
-        log::debug!("new scope {:?} with parent {:?}", new_scope_id, container);
+        // log::debug!("new scope {:?} with parent {:?}", new_scope_id, container);
 
         if let Some(old_scope) = self.free_scopes.borrow_mut().pop() {
             let scope = unsafe { &mut *old_scope };
-            log::debug!(
-                "reusing scope {:?} as {:?}",
-                scope.our_arena_idx,
-                new_scope_id
-            );
+            // log::debug!(
+            //     "reusing scope {:?} as {:?}",
+            //     scope.our_arena_idx,
+            //     new_scope_id
+            // );
 
             scope.caller = caller;
             scope.parent_scope = parent_scope;
@@ -180,16 +180,17 @@ impl ScopeArena {
                 caller,
                 generation: 0.into(),
 
-                hooks: HookList::new(hook_capacity),
                 shared_contexts: Default::default(),
 
                 items: RefCell::new(SelfReferentialItems {
                     listeners: Default::default(),
                     borrowed_props: Default::default(),
-                    suspended_nodes: Default::default(),
                     tasks: Default::default(),
-                    pending_effects: Default::default(),
                 }),
+
+                hook_arena: Bump::new(),
+                hook_vals: RefCell::new(smallvec::SmallVec::with_capacity(hook_capacity)),
+                hook_idx: Default::default(),
             });
 
             let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope);
@@ -202,7 +203,7 @@ impl ScopeArena {
     pub fn try_remove(&self, id: &ScopeId) -> Option<()> {
         self.ensure_drop_safety(id);
 
-        log::debug!("removing scope {:?}", id);
+        // log::debug!("removing scope {:?}", id);
 
         // Safety:
         // - ensure_drop_safety ensures that no references to this scope are in use
@@ -210,7 +211,14 @@ impl ScopeArena {
         let scope = unsafe { &mut *self.scopes.borrow_mut().remove(id).unwrap() };
 
         // we're just reusing scopes so we need to clear it out
-        scope.hooks.clear();
+        scope.hook_vals.get_mut().drain(..).for_each(|state| {
+            let as_mut = unsafe { &mut *state };
+            let boxed = unsafe { bumpalo::boxed::Box::from_raw(as_mut) };
+            drop(boxed);
+        });
+        scope.hook_idx.set(0);
+        scope.hook_arena.reset();
+
         scope.shared_contexts.get_mut().clear();
         scope.parent_scope = None;
         scope.generation.set(0);
@@ -226,15 +234,11 @@ impl ScopeArena {
         let SelfReferentialItems {
             borrowed_props,
             listeners,
-            pending_effects,
-            suspended_nodes,
             tasks,
         } = scope.items.get_mut();
 
         borrowed_props.clear();
         listeners.clear();
-        pending_effects.clear();
-        suspended_nodes.clear();
         tasks.clear();
 
         self.free_scopes.borrow_mut().push(scope);
@@ -311,12 +315,10 @@ impl ScopeArena {
 
         let scope = unsafe { &mut *self.get_scope_mut(id).expect("could not find scope") };
 
-        log::debug!("found scope, about to run: {:?}", 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() };
+        scope.hook_idx.set(0);
 
         // Safety:
         // - We've dropped all references to the wip bump frame with "ensure_drop_safety"
@@ -326,16 +328,12 @@ impl ScopeArena {
             let mut items = scope.items.borrow_mut();
 
             // just forget about our suspended nodes while we're at it
-            items.suspended_nodes.clear();
             items.tasks.clear();
-            items.pending_effects.clear();
 
             // guarantee that we haven't screwed up - there should be no latent references anywhere
             debug_assert!(items.listeners.is_empty());
             debug_assert!(items.borrowed_props.is_empty());
-            debug_assert!(items.suspended_nodes.is_empty());
             debug_assert!(items.tasks.is_empty());
-            debug_assert!(items.pending_effects.is_empty());
 
             // Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders.
             scope.wip_frame().nodes.borrow_mut().clear();

+ 14 - 21
packages/core/src/virtual_dom.rs

@@ -8,12 +8,9 @@ use futures_util::{Future, StreamExt};
 use fxhash::FxHashSet;
 use indexmap::IndexSet;
 use smallvec::SmallVec;
-use std::pin::Pin;
-use std::sync::Arc;
-use std::task::Poll;
-use std::{any::Any, collections::VecDeque};
+use std::{any::Any, collections::VecDeque, pin::Pin, sync::Arc, task::Poll};
 
-/// A virtual node system that progresses user events and diffs UI trees.
+/// A virtual node s ystem that progresses user events and diffs UI trees.
 ///
 ///
 /// ## Guide
@@ -286,7 +283,7 @@ impl VirtualDom {
             if let Some(msg) = self.pending_messages.pop_back() {
                 match msg {
                     // just keep looping, the task is now saved but we should actually poll it
-                    SchedulerMsg::TaskPushed(id) => {
+                    SchedulerMsg::NewTask(id) => {
                         self.scopes.pending_futures.borrow_mut().insert(id);
                     }
                     SchedulerMsg::UiEvent(event) => {
@@ -297,7 +294,6 @@ impl VirtualDom {
                     SchedulerMsg::Immediate(s) => {
                         self.dirty_scopes.insert(s);
                     }
-                    SchedulerMsg::Suspended { scope } => todo!(),
                 }
             }
         }
@@ -352,7 +348,6 @@ impl VirtualDom {
         let mut committed_mutations = vec![];
 
         while !self.dirty_scopes.is_empty() {
-            log::debug!("working with deadline");
             let scopes = &self.scopes;
             let mut diff_state = DiffState::new(scopes);
 
@@ -361,6 +356,7 @@ impl VirtualDom {
             // Sort the scopes by height. Theoretically, we'll de-duplicate scopes by height
             self.dirty_scopes
                 .retain(|id| scopes.get_scope(id).is_some());
+
             self.dirty_scopes.sort_by(|a, b| {
                 let h1 = scopes.get_scope(a).unwrap().height;
                 let h2 = scopes.get_scope(b).unwrap().height;
@@ -386,18 +382,16 @@ impl VirtualDom {
             }
 
             if diff_state.work(&mut deadline) {
-                let DiffState {
-                    mutations,
-                    seen_scopes,
-                    ..
-                } = diff_state;
-
-                for scope in seen_scopes {
-                    self.dirty_scopes.remove(&scope);
+                let DiffState { mutations, .. } = diff_state;
+
+                for scope in &mutations.dirty_scopes {
+                    self.dirty_scopes.remove(scope);
                 }
 
                 committed_mutations.push(mutations);
             } else {
+                log::debug!("Could not finish work in time");
+
                 // leave the work in an incomplete state
                 return committed_mutations;
             }
@@ -539,6 +533,7 @@ impl VirtualDom {
     }
 }
 
+#[derive(Debug)]
 pub enum SchedulerMsg {
     // events from the host
     UiEvent(UserEvent),
@@ -547,9 +542,7 @@ pub enum SchedulerMsg {
     Immediate(ScopeId),
 
     // an async task pushed from an event handler (or just spawned)
-    TaskPushed(ScopeId),
-
-    Suspended { scope: ScopeId },
+    NewTask(ScopeId),
 }
 
 /// User Events are events that are shuttled from the renderer into the VirtualDom trhough the scheduler channel.
@@ -678,9 +671,9 @@ impl<'a> Future for PollTasks<'a> {
                 // 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 task_mut = task.as_mut();
-                let unpinned = unsafe { Pin::new_unchecked(task_mut) };
+                let pinned = unsafe { Pin::new_unchecked(task_mut) };
 
-                if unpinned.poll(cx).is_ready() {
+                if pinned.poll(cx).is_ready() {
                     all_pending = false
                 } else {
                     unfinished_tasks.push(task);

+ 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::{prelude::*, DomEdit, VSuspended};
+use dioxus::{prelude::*, DomEdit};
 use dioxus_core as dioxus;
 use dioxus_core_macro::*;
 use dioxus_html as dioxus_elements;

+ 8 - 0
packages/coroutines/Cargo.toml

@@ -0,0 +1,8 @@
+[package]
+name = "dioxus-coroutines"
+version = "0.0.0"
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]

+ 8 - 0
packages/coroutines/src/lib.rs

@@ -0,0 +1,8 @@
+#[cfg(test)]
+mod tests {
+    #[test]
+    fn it_works() {
+        let result = 2 + 2;
+        assert_eq!(result, 4);
+    }
+}

+ 1 - 1
packages/desktop/Cargo.toml

@@ -17,7 +17,7 @@ thiserror = "1.0.23"
 log = "0.4.13"
 html-escape = "0.2.9"
 wry = "0.12.2"
-futures-channel = "0.3.16"
+futures-channel = "0.3.18"
 tokio = { version = "1.12.0", features = [
     "sync",
     "rt-multi-thread",

+ 7 - 7
packages/desktop/README.md

@@ -7,7 +7,7 @@ fn main() {
     dioxus::desktop::launch(App, |c| c)
 }
 
-static App: FC<()> = |(cx, props)| {
+static App: FC<()> = |cx, props| {
     let (count, set_count) = use_state(cx, || 0);
 
     cx.render(rsx!(
@@ -34,7 +34,7 @@ Window management, system trays, notifications, and other desktop-related functi
 Managing windows is done by simply rendering content into a `WebviewWindow` component. 
 
 ```rust
-static App: FC<()> = |(cx, props)| {
+static App: FC<()> = |cx, props| {
     rsx!(cx, WebviewWindow { "hello world" } )
 }
 ```
@@ -46,7 +46,7 @@ Notifications also use a declarative approach. Sending a notification has never
 The api has been somewhat modeled after https://github.com/mikaelbr/node-notifier
 
 ```rust
-static Notifications: FC<()> = |(cx, props)| {
+static Notifications: FC<()> = |cx, props| {
     cx.render(rsx!(
         Notification {
             title: "title"
@@ -78,7 +78,7 @@ static Notifications: FC<()> = |(cx, props)| {
 Dioxus Desktop supports app trays, which can be built with native menu groups or with a custom window.
 
 ```rust
-static Tray: FC<()> = |(cx, props)| {
+static Tray: FC<()> = |cx, props| {
     cx.render(rsx!(
         GlobalTray {
             MenuGroup {
@@ -90,7 +90,7 @@ static Tray: FC<()> = |(cx, props)| {
 };
 
 // using a builder
-static Tray: FC<()> = |(cx, props)| {
+static Tray: FC<()> = |cx, props| {
     let menu = MenuGroup::builder(cx)
         .with_items([
             MenuGroupItem::builder()
@@ -107,7 +107,7 @@ static Tray: FC<()> = |(cx, props)| {
 }
 
 // or with a custom window
-static Tray: FC<()> = |(cx, props)| {
+static Tray: FC<()> = |cx, props| {
     rsx!(cx, GlobalTray { div { "custom buttons here" } })
 };
 ```
@@ -116,7 +116,7 @@ static Tray: FC<()> = |(cx, props)| {
 Declaring menus is convenient and cross-platform.
 
 ```rust
-static Menu: FC<()> = |(cx, props)| {
+static Menu: FC<()> = |cx, props| {
     cx.render(rsx!(
         MenuBarMajorItem { title: "File"
             MenuGroup {

+ 3 - 1
packages/desktop/examples/async.rs

@@ -11,14 +11,16 @@ use dioxus_hooks::*;
 use dioxus_html as dioxus_elements;
 
 fn main() {
+    simple_logger::init().unwrap();
     dioxus_desktop::launch(App, |c| c);
 }
 
 static App: FC<()> = |cx, props| {
     let mut count = use_state(cx, || 0);
+    log::debug!("count is {:?}", count);
 
     cx.push_task(|| async move {
-        tokio::time::sleep(Duration::from_millis(100)).await;
+        tokio::time::sleep(Duration::from_millis(1000)).await;
         count += 1;
     });
 

+ 16 - 12
packages/desktop/src/cfg.rs

@@ -1,14 +1,7 @@
-use std::ops::{Deref, DerefMut};
-
 use dioxus_core::DomEdit;
 use wry::{
-    application::{
-        error::OsError,
-        event_loop::{EventLoop, EventLoopWindowTarget},
-        menu::MenuBar,
-        window::{Fullscreen, Icon, Window, WindowBuilder},
-    },
-    webview::{RpcRequest, RpcResponse, WebView},
+    application::{event_loop::EventLoop, window::WindowBuilder},
+    webview::WebView,
 };
 
 pub struct DesktopConfig<'a> {
@@ -22,7 +15,7 @@ impl<'a> DesktopConfig<'a> {
     /// Initializes a new `WindowBuilder` with default values.
     #[inline]
     pub fn new() -> Self {
-        let mut window = WindowBuilder::new().with_title("Dioxus app");
+        let window = WindowBuilder::new().with_title("Dioxus app");
         Self {
             event_handler: None,
             window,
@@ -41,13 +34,24 @@ impl<'a> DesktopConfig<'a> {
         self
     }
 
-    pub fn with_window(&mut self, f: impl FnOnce(WindowBuilder) -> WindowBuilder) -> &mut Self {
+    pub fn with_window(
+        &mut self,
+        configure: impl FnOnce(WindowBuilder) -> WindowBuilder,
+    ) -> &mut Self {
         // gots to do a swap because the window builder only takes itself as muy self
         // I wish more people knew about returning &mut Self
         let mut builder = WindowBuilder::default().with_title("Dioxus App");
         std::mem::swap(&mut self.window, &mut builder);
-        builder = f(builder);
+        builder = configure(builder);
         std::mem::swap(&mut self.window, &mut builder);
         self
     }
+
+    pub fn with_event_handler(
+        &mut self,
+        handler: impl Fn(&mut EventLoop<()>, &mut WebView) + 'static,
+    ) -> &mut Self {
+        self.event_handler = Some(Box::new(handler));
+        self
+    }
 }

+ 0 - 31
packages/desktop/src/dom.rs

@@ -1,31 +0,0 @@
-//! webview dom
-
-use dioxus_core::DomEdit;
-
-// pub struct WebviewRegistry {}
-
-// impl WebviewRegistry {
-//     pub fn new() -> Self {
-//         Self {}
-//     }
-// }
-
-pub struct WebviewDom<'bump> {
-    pub edits: Vec<DomEdit<'bump>>,
-    pub node_counter: u64,
-    // pub registry: WebviewRegistry,
-}
-impl WebviewDom<'_> {
-    pub fn new() -> Self {
-        Self {
-            edits: Vec::new(),
-            node_counter: 0,
-            // registry,
-        }
-    }
-
-    // // Finish using the dom (for its edit list) and give back the node and event registry
-    // pub fn consume(self) -> WebviewRegistry {
-    //     self.registry
-    // }
-}

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

@@ -1 +0,0 @@
-

+ 3 - 2
packages/desktop/src/escape.rs

@@ -16,7 +16,8 @@ use std::fmt::{self, Write};
 ///
 /// view.eval(&format!("callback({});", web_view::escape(string)));
 /// ```
-pub fn escape(string: &str) -> Escaper {
+#[allow(unused)]
+pub fn escape_js_string(string: &str) -> Escaper {
     Escaper(string)
 }
 
@@ -76,6 +77,6 @@ impl<'a> fmt::Display for Escaper<'a> {
 #[test]
 fn test() {
     let plain = "ABC \n\r' abc \\  \u{2028}   \u{2029}123";
-    let escaped = escape(plain).to_string();
+    let escaped = escape_js_string(plain).to_string();
     assert!(escaped == "'ABC \\n\\r\\' abc \\\\  \\u2028   \\u2029123'");
 }

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

@@ -1,10 +1,10 @@
 //! Convert a serialized event to an event Trigger
 //!
 
+use std::any::Any;
 use std::sync::Arc;
-use std::{any::Any, rc::Rc};
 
-use dioxus_core::{ElementId, EventPriority, ScopeId, UserEvent};
+use dioxus_core::{ElementId, EventPriority, UserEvent};
 use dioxus_html::on::*;
 
 #[derive(serde::Serialize, serde::Deserialize)]
@@ -16,11 +16,11 @@ struct ImEvent {
 }
 
 pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
-    let mut ims: Vec<ImEvent> = serde_json::from_value(val).unwrap();
+    let ims: Vec<ImEvent> = serde_json::from_value(val).unwrap();
+
     let ImEvent {
         event,
         mounted_dom_id,
-        // scope,
         contents,
     } = ims.into_iter().next().unwrap();
 

+ 2 - 3
packages/desktop/src/index.html

@@ -19,10 +19,9 @@
 
 
 <body>
-    <div id="_dioxusroot">
+    <div id="main">
     </div>
 </body>
-<script type="text/javascript" src="index.js">
-</script>
+<script type="text/javascript" src="index.js"> </script>
 
 </html>

+ 1 - 1
packages/desktop/src/index.js

@@ -409,7 +409,7 @@ class Interpreter {
 }
 
 function main() {
-  let root = window.document.getElementById("_dioxusroot");
+  let root = window.document.getElementById("main");
   window.interpreter = new Interpreter(root);
   console.log(window.interpreter);
 

+ 180 - 195
packages/desktop/src/lib.rs

@@ -3,42 +3,32 @@
 //! Render the Dioxus VirtualDom using the platform's native WebView implementation.
 //!
 
-use std::borrow::BorrowMut;
-use std::cell::{Cell, RefCell};
-use std::collections::{HashMap, VecDeque};
-use std::ops::{Deref, DerefMut};
-use std::rc::Rc;
-use std::sync::atomic::AtomicBool;
-use std::sync::mpsc::channel;
-use std::sync::{Arc, RwLock};
+mod cfg;
+mod escape;
+mod events;
+// mod desktop_context;
 
 use cfg::DesktopConfig;
 use dioxus_core::*;
-use serde::{Deserialize, Serialize};
-
-pub use wry;
-
-use wry::application::accelerator::{Accelerator, SysMods};
-use wry::application::event::{ElementState, Event, StartCause, WindowEvent};
-use wry::application::event_loop::{self, ControlFlow, EventLoop, EventLoopWindowTarget};
-use wry::application::keyboard::{Key, KeyCode, ModifiersState};
-use wry::application::menu::{MenuBar, MenuItem, MenuItemAttributes};
-use wry::application::window::{Fullscreen, WindowId};
-use wry::webview::{WebView, WebViewBuilder};
+use std::{
+    collections::{HashMap, VecDeque},
+    sync::atomic::AtomicBool,
+    sync::{Arc, RwLock},
+};
+use tokio::task::LocalSet;
 use wry::{
-    application::menu,
-    application::window::{Window, WindowBuilder},
-    webview::{RpcRequest, RpcResponse},
+    application::{
+        accelerator::{Accelerator, SysMods},
+        event::{Event, StartCause, WindowEvent},
+        event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
+        keyboard::{KeyCode, ModifiersState},
+        menu::{MenuBar, MenuItem},
+        window::{Window, WindowId},
+    },
+    webview::RpcRequest,
+    webview::{WebView, WebViewBuilder},
 };
 
-mod cfg;
-mod desktop_context;
-mod dom;
-mod escape;
-mod events;
-
-static HTML_CONTENT: &'static str = include_str!("./index.html");
-
 pub fn launch(
     root: FC<()>,
     config_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
@@ -54,7 +44,7 @@ pub fn launch_with_props<P: Properties + 'static + Send + Sync>(
     run(root, props, builder)
 }
 
-#[derive(Serialize)]
+#[derive(serde::Serialize)]
 struct Response<'a> {
     pre_rendered: Option<String>,
     edits: Vec<DomEdit<'a>>,
@@ -65,88 +55,43 @@ pub fn run<T: 'static + Send + Sync>(
     props: T,
     user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
 ) {
-    // Generate the config
-    let mut cfg = DesktopConfig::new();
-    user_builder(&mut cfg);
-    let DesktopConfig {
-        window: window_cfg,
-        manual_edits,
-        pre_rendered,
-        ..
-    } = cfg;
-
-    // All of our webview windows are stored in a way that we can look them up later
-    // The "DesktopContext" will provide functionality for spawning these windows
-    let mut webviews = HashMap::<WindowId, WebView>::new();
-    let event_loop = EventLoop::new();
-
-    let props_shared = Cell::new(Some(props));
-
-    // create local modifier state
-    let modifiers = ModifiersState::default();
+    let mut desktop_cfg = DesktopConfig::new();
+    user_builder(&mut desktop_cfg);
 
+    let mut state = DesktopController::new_on_tokio(root, props);
     let quit_hotkey = Accelerator::new(SysMods::Cmd, KeyCode::KeyQ);
-
-    let edit_queue = Arc::new(RwLock::new(VecDeque::new()));
-    let is_ready: Arc<AtomicBool> = Default::default();
+    let modifiers = ModifiersState::default();
+    let event_loop = EventLoop::new();
 
     event_loop.run(move |window_event, event_loop, control_flow| {
         *control_flow = ControlFlow::Wait;
 
         match window_event {
-            Event::NewEvents(StartCause::Init) => {
-                let window = create_window(event_loop, &window_cfg);
-                let window_id = window.id();
-                let sender =
-                    launch_vdom_with_tokio(root, props_shared.take().unwrap(), edit_queue.clone());
-                let webview = create_webview(window, is_ready.clone(), sender);
-                webviews.insert(window_id, webview);
-            }
+            Event::NewEvents(StartCause::Init) => state.new_window(&desktop_cfg, event_loop),
 
             Event::WindowEvent {
                 event, window_id, ..
             } => match event {
                 WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
-                WindowEvent::Destroyed { .. } => {
-                    webviews.remove(&window_id);
-                    if webviews.is_empty() {
-                        *control_flow = ControlFlow::Exit;
-                    }
-                }
-                WindowEvent::Moved(pos) => {
-                    //
-                }
+                WindowEvent::Destroyed { .. } => state.close_window(window_id, control_flow),
 
                 WindowEvent::KeyboardInput { event, .. } => {
                     if quit_hotkey.matches(&modifiers, &event.physical_key) {
-                        webviews.remove(&window_id);
-                        if webviews.is_empty() {
-                            *control_flow = ControlFlow::Exit;
-                        }
+                        state.close_window(window_id, control_flow);
                     }
                 }
 
                 WindowEvent::Resized(_) | WindowEvent::Moved(_) => {
-                    if let Some(view) = webviews.get_mut(&window_id) {
+                    if let Some(view) = state.webviews.get_mut(&window_id) {
                         let _ = view.resize();
                     }
                 }
-                // TODO: we want to shuttle all of these events into the user's app
+
+                // TODO: we want to shuttle all of these events into the user's app or provide some handler
                 _ => {}
             },
 
-            Event::MainEventsCleared => {
-                // I hate this ready hack but it's needed to wait for the "onload" to occur
-                // We can't run any initializion scripts because the window isn't ready yet?
-                if is_ready.load(std::sync::atomic::Ordering::Relaxed) {
-                    let mut queue = edit_queue.write().unwrap();
-                    let (id, view) = webviews.iter_mut().next().unwrap();
-                    while let Some(edit) = queue.pop_back() {
-                        view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit))
-                            .unwrap();
-                    }
-                }
-            }
+            Event::MainEventsCleared => state.try_load_ready_webviews(),
             Event::Resumed => {}
             Event::Suspended => {}
             Event::LoopDestroyed => {}
@@ -156,120 +101,160 @@ pub fn run<T: 'static + Send + Sync>(
     })
 }
 
-// Create a new tokio runtime on a dedicated thread and then launch the apps VirtualDom.
-pub(crate) fn launch_vdom_with_tokio<P: Send + 'static>(
-    root: FC<P>,
-    props: P,
-    edit_queue: Arc<RwLock<VecDeque<String>>>,
-) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
-    let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
-    let return_sender = sender.clone();
-
-    std::thread::spawn(move || {
-        // We create the runtim as multithreaded, so you can still "spawn" onto multiple threads
-        let runtime = tokio::runtime::Builder::new_multi_thread()
-            .enable_all()
-            .build()
-            .unwrap();
-
-        runtime.block_on(async move {
-            let mut dom = VirtualDom::new_with_props_and_scheduler(root, props, sender, receiver);
-
-            let edits = dom.rebuild();
-
-            edit_queue
-                .write()
-                .unwrap()
-                .push_front(serde_json::to_string(&edits.edits).unwrap());
-
-            loop {
-                dom.wait_for_work().await;
-                let mut muts = dom.work_with_deadline(|| false);
-                while let Some(edit) = muts.pop() {
-                    edit_queue
-                        .write()
-                        .unwrap()
-                        .push_front(serde_json::to_string(&edit.edits).unwrap());
-                }
-            }
-        })
-    });
-
-    return_sender
+pub struct DesktopController {
+    webviews: HashMap<WindowId, WebView>,
+    sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
+    pending_edits: Arc<RwLock<VecDeque<String>>>,
+    quit_app_on_close: bool,
+    is_ready: Arc<AtomicBool>,
 }
 
-fn build_menu() -> MenuBar {
-    // create main menubar menu
-    let mut menu_bar_menu = MenuBar::new();
-
-    // create `first_menu`
-    let mut first_menu = MenuBar::new();
-
-    first_menu.add_native_item(MenuItem::About("Todos".to_string()));
-    first_menu.add_native_item(MenuItem::Services);
-    first_menu.add_native_item(MenuItem::Separator);
-    first_menu.add_native_item(MenuItem::Hide);
-    first_menu.add_native_item(MenuItem::HideOthers);
-    first_menu.add_native_item(MenuItem::ShowAll);
-
-    first_menu.add_native_item(MenuItem::Quit);
-    first_menu.add_native_item(MenuItem::CloseWindow);
+impl DesktopController {
+    // Launch the virtualdom on its own thread managed by tokio
+    // returns the desktop state
+    pub fn new_on_tokio<P: Send + 'static>(root: FC<P>, props: P) -> Self {
+        let edit_queue = Arc::new(RwLock::new(VecDeque::new()));
+        let pending_edits = edit_queue.clone();
+
+        let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
+        let return_sender = sender.clone();
+
+        std::thread::spawn(move || {
+            // We create the runtim as multithreaded, so you can still "spawn" onto multiple threads
+            let runtime = tokio::runtime::Builder::new_multi_thread()
+                .enable_all()
+                .build()
+                .unwrap();
+
+            runtime.block_on(async move {
+                // LocalSet::new().block_on(&runtime, async move {
+                let mut dom =
+                    VirtualDom::new_with_props_and_scheduler(root, props, sender, receiver);
+
+                let edits = dom.rebuild();
+
+                edit_queue
+                    .write()
+                    .unwrap()
+                    .push_front(serde_json::to_string(&edits.edits).unwrap());
+
+                loop {
+                    dom.wait_for_work().await;
+                    let mut muts = dom.work_with_deadline(|| false);
+                    while let Some(edit) = muts.pop() {
+                        log::debug!("found mutations {:?}", muts);
+                        edit_queue
+                            .write()
+                            .unwrap()
+                            .push_front(serde_json::to_string(&edit.edits).unwrap());
+                    }
+                }
+            })
+        });
 
-    // create second menu
-    let mut second_menu = MenuBar::new();
+        Self {
+            pending_edits,
+            sender: return_sender,
 
-    // second_menu.add_submenu("Sub menu", true, my_sub_menu);
-    second_menu.add_native_item(MenuItem::Copy);
-    second_menu.add_native_item(MenuItem::Paste);
-    second_menu.add_native_item(MenuItem::SelectAll);
+            webviews: HashMap::new(),
+            is_ready: Arc::new(AtomicBool::new(false)),
+            quit_app_on_close: true,
+        }
+    }
+
+    pub fn new_window(&mut self, cfg: &DesktopConfig, event_loop: &EventLoopWindowTarget<()>) {
+        let builder = cfg.window.clone().with_menu({
+            // create main menubar menu
+            let mut menu_bar_menu = MenuBar::new();
+
+            // create `first_menu`
+            let mut first_menu = MenuBar::new();
+
+            first_menu.add_native_item(MenuItem::About("Todos".to_string()));
+            first_menu.add_native_item(MenuItem::Services);
+            first_menu.add_native_item(MenuItem::Separator);
+            first_menu.add_native_item(MenuItem::Hide);
+            first_menu.add_native_item(MenuItem::HideOthers);
+            first_menu.add_native_item(MenuItem::ShowAll);
+
+            first_menu.add_native_item(MenuItem::Quit);
+            first_menu.add_native_item(MenuItem::CloseWindow);
+
+            // create second menu
+            let mut second_menu = MenuBar::new();
+
+            // second_menu.add_submenu("Sub menu", true, my_sub_menu);
+            second_menu.add_native_item(MenuItem::Copy);
+            second_menu.add_native_item(MenuItem::Paste);
+            second_menu.add_native_item(MenuItem::SelectAll);
+
+            menu_bar_menu.add_submenu("First menu", true, first_menu);
+            menu_bar_menu.add_submenu("Second menu", true, second_menu);
+
+            menu_bar_menu
+        });
+
+        let window = builder.build(event_loop).unwrap();
+        let window_id = window.id();
+
+        let (is_ready, sender) = (self.is_ready.clone(), self.sender.clone());
+
+        let webview = WebViewBuilder::new(window)
+            .unwrap()
+            .with_url("wry://index.html")
+            .unwrap()
+            .with_rpc_handler(move |_window: &Window, req: RpcRequest| {
+                match req.method.as_str() {
+                    "user_event" => {
+                        let event = events::trigger_from_serialized(req.params.unwrap());
+                        log::debug!("User event: {:?}", event);
+                        sender.unbounded_send(SchedulerMsg::UiEvent(event)).unwrap();
+                    }
+                    "initialize" => {
+                        is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
+                    }
+                    _ => {}
+                }
+                // response always driven through eval.
+                // unfortunately, it seems to be pretty slow, so we might want to look into an RPC form
+                None
+            })
+            // Any content that that uses the `wry://` scheme will be shuttled through this handler as a "special case"
+            // For now, we only serve two pieces of content which get included as bytes into the final binary.
+            .with_custom_protocol("wry".into(), move |request| {
+                let path = request.uri().replace("wry://", "");
+                let (data, meta) = match path.as_str() {
+                    "index.html" => (include_bytes!("./index.html").to_vec(), "text/html"),
+                    "index.html/index.js" => {
+                        (include_bytes!("./index.js").to_vec(), "text/javascript")
+                    }
+                    _ => unimplemented!("path {}", path),
+                };
 
-    menu_bar_menu.add_submenu("First menu", true, first_menu);
-    menu_bar_menu.add_submenu("Second menu", true, second_menu);
+                wry::http::ResponseBuilder::new().mimetype(meta).body(data)
+            })
+            .build()
+            .unwrap();
 
-    menu_bar_menu
-}
+        self.webviews.insert(window_id, webview);
+    }
 
-fn create_window(event_loop: &EventLoopWindowTarget<()>, cfg: &WindowBuilder) -> Window {
-    let builder = cfg.clone().with_menu(build_menu());
-    builder.build(event_loop).unwrap()
-}
+    pub fn close_window(&mut self, window_id: WindowId, control_flow: &mut ControlFlow) {
+        self.webviews.remove(&window_id);
 
-fn create_webview(
-    window: Window,
-    is_ready: Arc<AtomicBool>,
-    sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
-) -> WebView {
-    WebViewBuilder::new(window)
-        .unwrap()
-        .with_url("wry://index.html")
-        .unwrap()
-        .with_rpc_handler(move |_window: &Window, mut req: RpcRequest| {
-            match req.method.as_str() {
-                "user_event" => {
-                    let event = events::trigger_from_serialized(req.params.unwrap());
-                    log::debug!("User event: {:?}", event);
-                    sender.unbounded_send(SchedulerMsg::UiEvent(event)).unwrap();
-                }
-                "initialize" => {
-                    is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
-                }
-                _ => {}
+        if self.webviews.is_empty() && self.quit_app_on_close {
+            *control_flow = ControlFlow::Exit;
+        }
+    }
+
+    pub fn try_load_ready_webviews(&mut self) {
+        if self.is_ready.load(std::sync::atomic::Ordering::Relaxed) {
+            let mut queue = self.pending_edits.write().unwrap();
+            let (_id, view) = self.webviews.iter_mut().next().unwrap();
+            while let Some(edit) = queue.pop_back() {
+                view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit))
+                    .unwrap();
             }
-            // always driven through eval
-            None
-        })
-        // Any content that that uses the `wry://` scheme will be shuttled through this handler as a "special case"
-        // For now, we only serve two pieces of content which get included as bytes into the final binary.
-        .with_custom_protocol("wry".into(), move |request| {
-            let path = request.uri().replace("wry://", "");
-            let (data, meta) = match path.as_str() {
-                "index.html" => (include_bytes!("./index.html").to_vec(), "text/html"),
-                "index.html/index.js" => (include_bytes!("./index.js").to_vec(), "text/javascript"),
-                _ => unimplemented!("path {}", path),
-            };
-
-            wry::http::ResponseBuilder::new().mimetype(meta).body(data)
-        })
-        .build()
-        .unwrap()
+        }
+    }
 }

+ 2 - 1
packages/hooks/Cargo.toml

@@ -7,4 +7,5 @@ edition = "2018"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-dioxus-core = { path = "../../packages/core", version = "0.1.2" }
+dioxus-core = { path = "../../packages/core", version = "0.1.3" }
+futures = "0.3.18"

+ 1 - 1
packages/hooks/README.md

@@ -19,7 +19,7 @@ You can always use it "normally" with the `split` method:
 let value = use_state(cx, || 10);
 
 // "Classic" usage:
-let (value, set_value) = use_state(cx, || 0).classic();
+let (value, set_value) = use_state(cx, || 0).split();
 ```
 
 ## use_ref

+ 3 - 0
packages/hooks/src/lib.rs

@@ -6,3 +6,6 @@ pub use useref::*;
 
 mod use_shared_state;
 pub use use_shared_state::*;
+
+mod usecoroutine;
+pub use usecoroutine::*;

+ 1 - 1
packages/hooks/src/use_shared_state.rs

@@ -173,6 +173,6 @@ pub fn use_provide_state<'a, T: 'static>(cx: Context<'a>, f: impl FnOnce() -> T)
             });
             cx.provide_state(state)
         },
-        |inner| {},
+        |_inner| {},
     )
 }

+ 1 - 1
packages/hooks/src/usecollection.rs

@@ -36,7 +36,7 @@ uses the same memoization on top of the use_context API.
 
 Here's a fully-functional todo app using the use_map API:
 ```rust
-static TodoList: FC<()> = |(cx, props)|{
+static TodoList: FC<()> = |cx, props|{
     let todos = use_map(cx, || HashMap::new());
     let input = use_ref(|| None);
 

+ 75 - 0
packages/hooks/src/usecoroutine.rs

@@ -0,0 +1,75 @@
+use dioxus_core::Context;
+use futures::Future;
+use std::{
+    cell::{Cell, RefCell},
+    pin::Pin,
+    rc::Rc,
+};
+
+pub fn use_coroutine<'a, F: Future<Output = ()> + 'a>(
+    cx: Context<'a>,
+    f: impl FnOnce() -> F + 'a,
+) -> CoroutineHandle {
+    //
+    cx.use_hook(
+        move |_| State {
+            running: Default::default(),
+            fut: Default::default(),
+            submit: Default::default(),
+        },
+        |state| {
+            let fut_slot = state.fut.clone();
+            let running = state.running.clone();
+            let submit: Box<dyn FnOnce() + 'a> = Box::new(move || {
+                let g = async move {
+                    running.set(true);
+                    f().await;
+                    running.set(false);
+                };
+                let p: Pin<Box<dyn Future<Output = ()>>> = Box::pin(g);
+                fut_slot
+                    .borrow_mut()
+                    .replace(unsafe { std::mem::transmute(p) });
+            });
+
+            let submit = unsafe { std::mem::transmute(submit) };
+            state.submit.get_mut().replace(submit);
+
+            if state.running.get() {
+                let mut fut = state.fut.borrow_mut();
+                cx.push_task(|| fut.as_mut().unwrap().as_mut());
+            } else {
+                // make sure to drop the old future
+                if let Some(fut) = state.fut.borrow_mut().take() {
+                    drop(fut);
+                }
+            }
+            CoroutineHandle { cx, inner: state }
+        },
+    )
+}
+
+struct State {
+    running: Rc<Cell<bool>>,
+    submit: RefCell<Option<Box<dyn FnOnce()>>>,
+    fut: Rc<RefCell<Option<Pin<Box<dyn Future<Output = ()>>>>>>,
+}
+
+pub struct CoroutineHandle<'a> {
+    cx: Context<'a>,
+    inner: &'a State,
+}
+
+impl<'a> CoroutineHandle<'a> {
+    pub fn start(&self) {
+        if self.inner.running.get() {
+            return;
+        }
+        if let Some(submit) = self.inner.submit.borrow_mut().take() {
+            submit();
+            let mut fut = self.inner.fut.borrow_mut();
+            self.cx.push_task(|| fut.as_mut().unwrap().as_mut());
+        }
+    }
+    pub fn resume(&self) {}
+}

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

@@ -33,6 +33,11 @@ impl<'a, T> UseRef<'a, T> {
         self.inner.value.borrow()
     }
 
+    pub fn set(&self, new: T) {
+        *self.inner.value.borrow_mut() = new;
+        self.needs_update();
+    }
+
     pub fn read_write(&self) -> (Ref<'_, T>, &Self) {
         (self.read(), self)
     }

+ 15 - 8
packages/hooks/src/usestate.rs

@@ -1,7 +1,7 @@
 use dioxus_core::prelude::Context;
 use std::{
     cell::{Cell, Ref, RefCell, RefMut},
-    fmt::Display,
+    fmt::{Debug, Display},
     ops::Not,
     rc::Rc,
 };
@@ -35,7 +35,7 @@ use std::{
 ///
 /// Usage:
 /// ```ignore
-/// const Example: FC<()> = |(cx, props)|{
+/// const Example: FC<()> = |cx, props|{
 ///     let counter = use_state(cx, || 0);
 ///     let increment = |_| counter += 1;
 ///     let decrement = |_| counter += 1;
@@ -54,11 +54,14 @@ pub fn use_state<'a, T: 'static>(
     initial_state_fn: impl FnOnce() -> T,
 ) -> UseState<'a, T> {
     cx.use_hook(
-        move |_| UseStateInner {
-            current_val: initial_state_fn(),
-            update_callback: cx.schedule_update(),
-            wip: Rc::new(RefCell::new(None)),
-            update_scheuled: Cell::new(false),
+        move |_| {
+            //
+            UseStateInner {
+                current_val: initial_state_fn(),
+                update_callback: cx.schedule_update(),
+                wip: Rc::new(RefCell::new(None)),
+                update_scheuled: Cell::new(false),
+            }
         },
         move |hook| {
             hook.update_scheuled.set(false);
@@ -91,7 +94,11 @@ where
         UseState { inner: self.inner }
     }
 }
-
+impl<T: Debug> Debug for UseState<'_, T> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{:?}", self.inner.current_val)
+    }
+}
 impl<'a, T: 'static> UseState<'a, T> {
     /// Tell the Dioxus Scheduler that we need to be processed
     pub fn needs_update(&self) {

+ 18 - 3
packages/html/src/elements.rs

@@ -717,8 +717,7 @@ builder_constructors! {
         nonce: Nonce,
         src: Uri,
         text: String,
-        r#async: Bool,
-        r#type: String, // TODO could be an enum
+
     };
 
 
@@ -823,7 +822,6 @@ builder_constructors! {
         formnovalidate: Bool,
         formtarget: Target,
         name: Id,
-        r#type: ButtonType,
         value: String,
     };
 
@@ -1064,6 +1062,23 @@ impl input {
 volatile attributes
 */
 
+impl script {
+    // r#async: Bool,
+    // r#type: String, // TODO could be an enum
+    pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
+        cx.attr("type", val, None, false)
+    }
+    pub fn r#script<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
+        cx.attr("script", val, None, false)
+    }
+}
+
+impl button {
+    pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
+        cx.attr("type", val, None, false)
+    }
+}
+
 impl select {
     pub fn value<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
         cx.attr("value", val, None, true)

+ 11 - 2
packages/router/Cargo.toml

@@ -22,7 +22,7 @@ web-sys = { version = "0.3", features = [
     "Window",
 ], optional = true }
 
-serde = "1"
+serde = "1.0.130"
 
 serde_urlencoded = "0.7"
 
@@ -30,10 +30,19 @@ wasm-bindgen = "0.2"
 js-sys = "0.3"
 gloo = "0.4"
 route-recognizer = "0.3.1"
+url = "2.2.2"
+url_serde = "0.2.0"
 
 
 [features]
-default = ["web"]
+default = ["web", "derive"]
 web = ["web-sys"]
 desktop = []
 mobile = []
+derive = []
+
+[dev-dependencies]
+console_error_panic_hook = "0.1.7"
+dioxus-web = { path = "../web" }
+log = "0.4.14"
+wasm-logger = "0.2.0"

+ 40 - 0
packages/router/README.md

@@ -0,0 +1,40 @@
+# Router hook for Dioxus apps
+
+Dioxus-router provides a use_router hook that returns a different value depending on the route.
+The router is generic over any value, however it makes sense to return a different set of VNodes
+and feed them into the App's return VNodes.
+
+Using the router should feel similar to tide's routing framework where an "address" book is assembled at the head of the app.
+
+Here's an example of how to use the router hook:
+
+```rust
+#[derive(Clone, PartialEq, Serialize, Deserialize, Routable)]
+enum AppRoute {
+    Home, 
+    Posts,
+    NotFound
+}
+
+static App: FC<()> = |cx, props| {
+    let route = use_router(cx, AppRoute::parse);
+    
+    match route {
+        AppRoute::Home => rsx!(cx, Home {})
+        AppRoute::Posts => rsx!(cx, Posts {})
+        AppRoute::Notfound => rsx!(cx, Notfound {})
+    }
+};
+```
+
+Adding links into your app:
+
+```rust
+static Leaf: FC<()> = |cx, props| {
+    rsx!(cx, div { 
+        Link { to: AppRoute::Home } 
+    })
+}
+```
+
+Currently, the router is only supported in a web environment, but we plan to add 1st-party support via the context API when new renderers are available.

+ 39 - 0
packages/router/examples/simple.rs

@@ -0,0 +1,39 @@
+use dioxus_core::prelude::*;
+use dioxus_core_macro::*;
+use dioxus_html as dioxus_elements;
+use dioxus_router::*;
+
+fn main() {
+    console_error_panic_hook::set_once();
+    wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
+    dioxus_web::launch(App, |c| c);
+}
+
+static App: FC<()> = |cx, props| {
+    #[derive(Clone, Debug, PartialEq)]
+    enum Route {
+        Home,
+        About,
+        NotFound,
+    }
+
+    let route = use_router(cx, |s| match s {
+        "/" => Route::Home,
+        "/about" => Route::About,
+        _ => Route::NotFound,
+    });
+
+    cx.render(rsx! {
+        div {
+            {match route {
+                Route::Home => rsx!(h1 { "Home" }),
+                Route::About => rsx!(h1 { "About" }),
+                Route::NotFound => rsx!(h1 { "NotFound" }),
+            }}
+            nav {
+                Link { to: Route::Home, href: |_| "/".to_string() }
+                Link { to: Route::About, href: |_| "/about".to_string() }
+            }
+        }
+    })
+};

+ 77 - 39
packages/router/src/lib.rs

@@ -1,24 +1,29 @@
 mod utils;
 
-use std::{cell::RefCell, collections::HashMap, rc::Rc};
+use std::{cell::RefCell, rc::Rc};
 
 use dioxus_core as dioxus;
 use dioxus_core::prelude::*;
-use dioxus_core_macro::{format_args_f, rsx, Props};
+use dioxus_core_macro::{rsx, Props};
 use dioxus_html as dioxus_elements;
-use wasm_bindgen::JsValue;
-use web_sys::Event;
+use wasm_bindgen::{JsCast, JsValue};
+use web_sys::{window, Event};
 
-use crate::utils::fetch_base_url;
+use crate::utils::strip_slash_suffix;
+
+pub trait Routable: 'static + Send + Clone + PartialEq {}
+impl<T> Routable for T where T: 'static + Send + Clone + PartialEq {}
 
 pub struct RouterService<R: Routable> {
-    history: RefCell<Vec<R>>,
+    historic_routes: Vec<R>,
+    history_service: RefCell<web_sys::History>,
     base_ur: RefCell<Option<String>>,
 }
 
 impl<R: Routable> RouterService<R> {
     fn push_route(&self, r: R) {
-        self.history.borrow_mut().push(r);
+        todo!()
+        // self.historic_routes.borrow_mut().push(r);
     }
 
     fn get_current_route(&self) -> &str {
@@ -61,38 +66,71 @@ impl<R: Routable> RouterService<R> {
 /// This hould only be used once per app
 ///
 /// You can manually parse the route if you want, but the derived `parse` method on `Routable` will also work just fine
-pub fn use_router<R: Routable>(cx: Context, cfg: impl FnOnce(&str) -> R) -> Option<&R> {
+pub fn use_router<R: Routable>(cx: Context, mut parse: impl FnMut(&str) -> R + 'static) -> &R {
     // for the web, attach to the history api
     cx.use_hook(
         |f| {
             //
             use gloo::events::EventListener;
 
-            let base_url = fetch_base_url();
+            let base = window()
+                .unwrap()
+                .document()
+                .unwrap()
+                .query_selector("base[href]")
+                .ok()
+                .flatten()
+                .and_then(|base| {
+                    let base = JsCast::unchecked_into::<web_sys::HtmlBaseElement>(base).href();
+                    let url = web_sys::Url::new(&base).unwrap();
+
+                    if url.pathname() != "/" {
+                        Some(strip_slash_suffix(&base).to_string())
+                    } else {
+                        None
+                    }
+                });
+
+            let location = window().unwrap().location();
+            let pathname = location.pathname().unwrap();
+            let initial_route = parse(&pathname);
 
             let service: RouterService<R> = RouterService {
-                history: RefCell::new(vec![]),
-                base_ur: RefCell::new(base_url),
+                historic_routes: vec![initial_route],
+                history_service: RefCell::new(
+                    web_sys::window().unwrap().history().expect("no history"),
+                ),
+                base_ur: RefCell::new(base),
             };
 
-            cx.provide_state(service);
+            // let base = base_url();
+            // let url = route.to_path();
+            // pending_routes: RefCell::new(vec![]),
+            // service.history_service.push_state(data, title);
+
+            // cx.provide_state(service);
 
             let regenerate = cx.schedule_update();
 
-            // when "back" is called by the user, we want to to re-render the component
+            // // when "back" is called by the user, we want to to re-render the component
             let listener = EventListener::new(&web_sys::window().unwrap(), "popstate", move |_| {
                 //
                 regenerate();
             });
+
+            service
         },
-        |f| {
-            //
-        },
-    );
+        |state| {
+            let base = state.base_ur.borrow();
+            if let Some(base) = base.as_ref() {
+                //
+                let path = format!("{}{}", base, state.get_current_route());
+            }
+            let history = state.history_service.borrow();
 
-    todo!()
-    // let router = use_router_service::<R>(cx)?;
-    // Some(cfg(router.get_current_route()))
+            state.historic_routes.last().unwrap()
+        },
+    )
 }
 
 pub fn use_router_service<R: Routable>(cx: Context) -> Option<&Rc<RouterService<R>>> {
@@ -102,33 +140,33 @@ pub fn use_router_service<R: Routable>(cx: Context) -> Option<&Rc<RouterService<
 #[derive(Props)]
 pub struct LinkProps<R: Routable> {
     to: R,
+
+    /// The url that gets pushed to the history stack
+    ///
+    /// You can either put it your own inline method or just autoderive the route using `derive(Routable)`
+    ///
+    /// ```rust
+    ///
+    /// Link { to: Route::Home, href: |_| "home".to_string() }
+    ///
+    /// // or
+    ///
+    /// Link { to: Route::Home, href: Route::as_url }
+    ///
+    /// ```
+    href: fn(&R) -> String,
+
+    #[builder(default)]
     children: Element,
 }
 
-pub fn Link<'a, R: Routable>(cx: Context, props: &LinkProps<R>) -> Element {
+pub fn Link<R: Routable>(cx: Context, props: &LinkProps<R>) -> Element {
     let service = use_router_service::<R>(cx)?;
     cx.render(rsx! {
         a {
-            href: format_args!("{}", props.to.to_path()),
+            href: format_args!("{}", (props.href)(&props.to)),
             onclick: move |_| service.push_route(props.to.clone()),
             {&props.children},
         }
     })
 }
-
-pub trait Routable: Sized + Clone + 'static {
-    /// Converts path to an instance of the routes enum.
-    fn from_path(path: &str, params: &HashMap<&str, &str>) -> Option<Self>;
-
-    /// Converts the route to a string that can passed to the history API.
-    fn to_path(&self) -> String;
-
-    /// Lists all the available routes
-    fn routes() -> Vec<&'static str>;
-
-    /// The route to redirect to on 404
-    fn not_found_route() -> Option<Self>;
-
-    /// Match a route based on the path
-    fn recognize(pathname: &str) -> Option<Self>;
-}

+ 0 - 25
packages/router/src/utils.rs

@@ -1,31 +1,6 @@
 use wasm_bindgen::JsCast;
 use web_sys::window;
 
-pub fn fetch_base_url() -> Option<String> {
-    match window()
-        .unwrap()
-        .document()
-        .unwrap()
-        .query_selector("base[href]")
-    {
-        Ok(Some(base)) => {
-            let base = JsCast::unchecked_into::<web_sys::HtmlBaseElement>(base).href();
-
-            let url = web_sys::Url::new(&base).unwrap();
-            let base = url.pathname();
-
-            let base = if base != "/" {
-                strip_slash_suffix(&base)
-            } else {
-                return None;
-            };
-
-            Some(base.to_string())
-        }
-        _ => None,
-    }
-}
-
 pub(crate) fn strip_slash_suffix(path: &str) -> &str {
     path.strip_suffix('/').unwrap_or(path)
 }

+ 1 - 1
packages/ssr/README.md

@@ -5,7 +5,7 @@ Render a Dioxus VirtualDOM to a string.
 
 ```rust
 // Our app:
-const App: FC<()> = |(cx, props)| rsx!(cx, div {"hello world!"});
+const App: FC<()> = |cx, props| rsx!(cx, div {"hello world!"});
 
 // Build the virtualdom from our app
 let mut vdom = VirtualDOM::new(App);

+ 3 - 6
packages/ssr/src/lib.rs

@@ -110,7 +110,7 @@ pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
 ///
 /// ## Example
 /// ```ignore
-/// static App: FC<()> = |(cx, props)|cx.render(rsx!(div { "hello world" }));
+/// static App: FC<()> = |cx, props|cx.render(rsx!(div { "hello world" }));
 /// let mut vdom = VirtualDom::new(App);
 /// vdom.rebuild();
 ///
@@ -149,7 +149,7 @@ impl<'a> TextRenderer<'a, '_> {
                 }
                 write!(f, "{}", text.text)?
             }
-            VNode::Anchor(_anchor) => {
+            VNode::Placeholder(_anchor) => {
                 //
                 if self.cfg.indent {
                     for _ in 0..il {
@@ -158,7 +158,7 @@ impl<'a> TextRenderer<'a, '_> {
                 }
                 write!(f, "<!-- -->")?;
             }
-            VNode::Linked(link) => {
+            VNode::Portal(link) => {
                 todo!();
             }
             VNode::Element(el) => {
@@ -252,9 +252,6 @@ impl<'a> TextRenderer<'a, '_> {
                 } else {
                 }
             }
-            VNode::Suspended { .. } => {
-                // we can't do anything with suspended nodes
-            }
         }
         Ok(())
     }

+ 16 - 2
packages/web/Cargo.toml

@@ -11,10 +11,17 @@ license = "MIT/Apache-2.0"
 dioxus-core = { path = "../core", version = "0.1.2" }
 dioxus-html = { path = "../html" }
 js-sys = "0.3"
+# wasm-bindgen-shared = { path = "../../../Tinkering/wasm-bindgen/crates/shared" }
+# wasm-bindgen-macro-support = { path = "../../../Tinkering/wasm-bindgen/crates/macro-support" }
+# wasm-bindgen = { features = [
+
+
 wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] }
+# wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] }
+# wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] }
 lazy_static = "1.4.0"
 wasm-bindgen-futures = "0.4.20"
-log = "0.4.14"
+log = { version = "0.4.14", features = ["release_max_level_off"] }
 fxhash = "0.2.1"
 wasm-logger = "0.2.0"
 console_error_panic_hook = "0.1.6"
@@ -24,6 +31,7 @@ async-channel = "1.6.1"
 anyhow = "1.0"
 gloo-timers = { version = "0.2.1", features = ["futures"] }
 futures-util = "0.3.15"
+smallstr = "0.2.0"
 
 [dependencies.web-sys]
 version = "0.3.51"
@@ -65,6 +73,7 @@ features = [
     "IdleDeadline",
 ]
 
+
 [lib]
 crate-type = ["cdylib", "rlib"]
 
@@ -76,7 +85,12 @@ serde = { version = "1.0.126", features = ["derive"] }
 reqwest = { version = "0.11", features = ["json"] }
 dioxus-hooks = { path = "../hooks" }
 dioxus-core-macro = { path = "../core-macro" }
-# rand = { version="0.8.4", features=["small_rng"] }
+rand = { version = "0.8.4", features = ["small_rng"] }
+
+[dev-dependencies.getrandom]
+version = "0.2"
+features = ["js"]
+
 # surf = { version = "2.3.1", default-features = false, features = [
 #     "wasm-client",
 # ] }

+ 243 - 0
packages/web/examples/js_bench.rs

@@ -0,0 +1,243 @@
+use std::cell::Cell;
+
+use dioxus::prelude::*;
+use dioxus_core as dioxus;
+use dioxus_core_macro::*;
+use dioxus_hooks::{use_ref, use_state};
+use dioxus_html as dioxus_elements;
+use dioxus_web;
+use gloo_timers::future::TimeoutFuture;
+use rand::prelude::*;
+
+fn main() {
+    console_error_panic_hook::set_once();
+    if cfg!(debug_assertions) {
+        wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
+        log::debug!("hello world");
+    }
+
+    for a in ADJECTIVES {
+        wasm_bindgen::intern(*a);
+    }
+    for a in COLOURS {
+        wasm_bindgen::intern(*a);
+    }
+    for a in NOUNS {
+        wasm_bindgen::intern(*a);
+    }
+    for a in [
+        "container",
+        "jumbotron",
+        "row",
+        "Dioxus",
+        "col-md-6",
+        "col-md-1",
+        "Create 1,000 rows",
+        "run",
+        "Create 10,000 rows",
+        "runlots",
+        "Append 1,000 rows",
+        "add",
+        "Update every 10th row",
+        "update",
+        "Clear",
+        "clear",
+        "Swap rows",
+        "swaprows",
+        "preloadicon glyphicon glyphicon-remove", //
+        "aria-hidden",
+        "onclick",
+        "true",
+        "false",
+        "danger",
+        "type",
+        "id",
+        "class",
+        "glyphicon glyphicon-remove remove",
+        "dioxus-id",
+        "dioxus-event-click",
+        "dioxus",
+        "click",
+        "1.10",
+        "lbl",
+        "remove",
+        "dioxus-event",
+        "col-sm-6 smallpad",
+        "btn btn-primary btn-block",
+        "",
+        " ",
+    ] {
+        wasm_bindgen::intern(a);
+    }
+    for x in 0..100_000 {
+        wasm_bindgen::intern(&x.to_string());
+    }
+
+    dioxus_web::launch(App, |c| c.rootname("main"));
+}
+
+#[derive(Clone, PartialEq, Copy)]
+struct Label {
+    key: usize,
+    labels: [&'static str; 3],
+}
+
+static mut Counter: Cell<usize> = Cell::new(1);
+
+impl Label {
+    fn new_list(num: usize) -> Vec<Self> {
+        let mut rng = SmallRng::from_entropy();
+        let mut labels = Vec::with_capacity(num);
+
+        let offset = unsafe { Counter.get() };
+        unsafe { Counter.set(offset + num) };
+
+        for k in offset..(offset + num) {
+            labels.push(Label {
+                key: k,
+                labels: [
+                    ADJECTIVES.choose(&mut rng).unwrap(),
+                    COLOURS.choose(&mut rng).unwrap(),
+                    NOUNS.choose(&mut rng).unwrap(),
+                ],
+            });
+        }
+
+        labels
+    }
+}
+
+static App: FC<()> = |cx, _props| {
+    let mut items = use_ref(cx, || vec![]);
+    let mut selected = use_state(cx, || None);
+
+    cx.render(rsx! {
+        div { class: "container"
+            div { class: "jumbotron"
+                div { class: "row"
+                    div { class: "col-md-6", h1 { "Dioxus" } }
+                    div { class: "col-md-6"
+                        div { class: "row"
+                            ActionButton { name: "Create 1,000 rows", id: "run",
+                                onclick: move || items.set(Label::new_list(1_000)),
+                            }
+                            ActionButton { name: "Create 10,000 rows", id: "runlots",
+                                onclick: move || items.set(Label::new_list(10_000)),
+                            }
+                            ActionButton { name: "Append 1,000 rows", id: "add",
+                                onclick: move || items.write().extend(Label::new_list(1_000)),
+                            }
+                            ActionButton { name: "Update every 10th row", id: "update",
+                                onclick: move || items.write().iter_mut().step_by(10).for_each(|item| item.labels[2] = "!!!"),
+                            }
+                            ActionButton { name: "Clear", id: "clear",
+                                onclick: move || items.write().clear(),
+                            }
+                            ActionButton { name: "Swap Rows", id: "swaprows",
+                                onclick: move || items.write().swap(0, 998),
+                            }
+                        }
+                    }
+                }
+            }
+            table { class: "table table-hover table-striped test-data"
+                tbody { id: "tbody"
+                    {items.read().iter().enumerate().map(|(id, item)| {
+                        let [adj, col, noun] = item.labels;
+                        let is_in_danger = if (*selected).map(|s| s == id).unwrap_or(false) {"danger"} else {""};
+                        rsx!(tr { 
+                            class: "{is_in_danger}",
+                            key: "{id}",
+                            td { class:"col-md-1" }
+                            td { class:"col-md-1", "{item.key}" }
+                            td { class:"col-md-1", onclick: move |_| selected.set(Some(id)),
+                                a { class: "lbl", "{adj} {col} {noun}" }
+                            }
+                            td { class: "col-md-1"
+                                a { class: "remove", onclick: move |_| { items.write().remove(id); },
+                                    span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
+                                }
+                            }
+                            td { class: "col-md-6" }
+                        })
+                    })}
+                }
+             }
+            span { class: "preloadicon glyphicon glyphicon-remove" aria_hidden: "true" }
+        }
+    })
+};
+
+#[derive(Props)]
+struct ActionButtonProps<'a> {
+    name: &'static str,
+    id: &'static str,
+    onclick: &'a dyn Fn(),
+}
+
+fn ActionButton(cx: Context, props: &ActionButtonProps) -> Element {
+    rsx!(cx, div { class: "col-sm-6 smallpad"
+        button { class:"btn btn-primary btn-block", r#type: "button", id: "{props.id}",  onclick: move |_| (props.onclick)(),
+            "{props.name}"
+        }
+    })
+}
+
+static ADJECTIVES: &[&str] = &[
+    "pretty",
+    "large",
+    "big",
+    "small",
+    "tall",
+    "short",
+    "long",
+    "handsome",
+    "plain",
+    "quaint",
+    "clean",
+    "elegant",
+    "easy",
+    "angry",
+    "crazy",
+    "helpful",
+    "mushy",
+    "odd",
+    "unsightly",
+    "adorable",
+    "important",
+    "inexpensive",
+    "cheap",
+    "expensive",
+    "fancy",
+];
+
+static COLOURS: &[&str] = &[
+    "red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black",
+    "orange",
+];
+
+static NOUNS: &[&str] = &[
+    "table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger",
+    "pizza", "mouse", "keyboard",
+];
+
+// #[derive(PartialEq, Props)]
+// struct RowProps<'a> {
+//     row_id: usize,
+//     label: &'a Label,
+// }
+
+// fn Row(cx: Context, props: &RowProps) -> Element {
+//     rsx!(cx, tr {
+//         td { class:"col-md-1", "{props.row_id}" }
+//         td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
+//             a { class: "lbl", {props.label.labels} }
+//         }
+//         td { class: "col-md-1"
+//             a { class: "remove", onclick: move |_| {/* remove */}
+//                 span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
+//             }
+//         }
+//         td { class: "col-md-6" }
+//     })
+// }

+ 46 - 0
packages/web/examples/simple.rs

@@ -0,0 +1,46 @@
+//! Example: README.md showcase
+//!
+//! The example from the README.md.
+
+use dioxus::prelude::*;
+use dioxus_core as dioxus;
+use dioxus_core_macro::*;
+use dioxus_hooks::use_state;
+use dioxus_html as dioxus_elements;
+use dioxus_web;
+use gloo_timers::future::TimeoutFuture;
+
+fn main() {
+    wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
+    dioxus_web::launch(App, |c| c);
+}
+
+static App: FC<()> = |cx, props| {
+    let show = use_state(cx, || true);
+
+    let inner = match *show {
+        true => {
+            rsx!( div {
+                "hello world"
+            })
+        }
+        false => {
+            rsx!( div {
+                // h1 {
+                    "bello world"
+                // }
+            })
+        }
+    };
+
+    rsx!(cx, div {
+        button {
+            "toggle"
+            onclick: move |_| {
+                let cur = *show;
+                show.set(!cur);
+            }
+        }
+        {inner}
+    })
+};

+ 5 - 0
packages/web/src/cache.rs

@@ -7,6 +7,11 @@
 /// Eventually we might want to procedurally generate these strings for common words, phrases, and values.
 pub(crate) fn intern_cached_strings() {
     let cached_words = [
+        // Important tags to dioxus
+        "dioxus-id",
+        "dioxus",
+        "dioxus-event-click", // todo: more events
+        "click",
         // All the HTML Tags
         "a",
         "abbr",

+ 1 - 1
packages/web/src/cfg.rs

@@ -17,7 +17,7 @@ impl Default for WebConfig {
     fn default() -> Self {
         Self {
             hydrate: false,
-            rootname: "dioxusroot".to_string(),
+            rootname: "main".to_string(),
         }
     }
 }

+ 45 - 23
packages/web/src/dom.rs

@@ -213,7 +213,7 @@ impl WebsysDom {
 
     fn create_placeholder(&mut self, id: u64) {
         self.create_element("pre", None, id);
-        // self.set_attribute("hidden", "", None);
+        self.set_attribute("hidden", "", None, id);
     }
 
     fn create_text_node(&mut self, text: &str, id: u64) {
@@ -246,8 +246,15 @@ impl WebsysDom {
                 .unwrap(),
         };
 
+        use smallstr;
+        use smallstr::SmallString;
+        use std::fmt::Write;
+
+        let mut s: SmallString<[u8; 8]> = smallstr::SmallString::new();
+        write!(s, "{}", id).unwrap();
+
         let el2 = el.dyn_ref::<Element>().unwrap();
-        el2.set_attribute("dioxus-id", &format!("{}", id)).unwrap();
+        el2.set_attribute("dioxus-id", s.as_str()).unwrap();
 
         self.stack.push(el.clone());
         self.nodes[(id as usize)] = Some(el);
@@ -263,18 +270,21 @@ impl WebsysDom {
 
         let el = self.stack.top();
 
-        let el = el
-            .dyn_ref::<Element>()
-            .expect(&format!("not an element: {:?}", el));
+        let el = el.dyn_ref::<Element>().unwrap();
+        // let el = el.dyn_ref::<Element>().unwrap();
+        // .expect(&format!("not an element: {:?}", el));
 
         // let scope_id = scope.data().as_ffi();
-        let scope_id = scope.0 as u64;
+        // let scope_id = scope.0 as u64;
+        // "dioxus-event-click",
+        // "1.10"
+        // &format!("", scope_id, real_id),
+        // &format!("dioxus-event-{}", event),
+        // &format!("{}.{}", scope_id, real_id),
+        // &format!("dioxus-event-{}", event),
+        // &format!("{}.{}", scope_id, real_id),
 
-        el.set_attribute(
-            &format!("dioxus-event-{}", event),
-            &format!("{}.{}", scope_id, real_id),
-        )
-        .unwrap();
+        el.set_attribute("dioxus-event", event).unwrap();
 
         // el.set_attribute(&format!("dioxus-event"), &format!("{}", event))
         //     .unwrap();
@@ -488,6 +498,8 @@ unsafe impl Sync for DioxusWebsysEvent {}
 fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc<dyn Any + Send + Sync> {
     use dioxus_html::on::*;
     use dioxus_html::KeyCode;
+    // event.prevent_default();
+
     // use dioxus_core::events::on::*;
     match event.type_().as_str() {
         "copy" | "cut" | "paste" => Arc::new(ClipboardEvent {}),
@@ -682,30 +694,40 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<UserEvent> {
 
     use anyhow::Context;
 
+    let element_id = target
+        .get_attribute("dioxus-id")
+        .context("Could not find element id on event target")?
+        .parse()?;
+
     // The error handling here is not very descriptive and needs to be replaced with a zero-cost error system
     let val: String = target
-        .get_attribute(&format!("dioxus-event-{}", typ))
+        .get_attribute("dioxus-event")
         .context(format!("wrong format - received {:#?}", typ))?;
+    // .get_attribute(&format!("dioxus-event-{}", typ))
+    // .context(format!("wrong format - received {:#?}", typ))?;
 
     let mut fields = val.splitn(3, ".");
 
-    let gi_id = fields
-        .next()
-        .and_then(|f| f.parse::<u64>().ok())
-        .context("failed to parse gi id")?;
+    // let gi_id = fields
+    //     .next()
+    //     .and_then(|f| f.parse::<u64>().ok())
+    //     .context("failed to parse gi id")?;
 
-    let real_id = fields
-        .next()
-        .and_then(|raw_id| raw_id.parse::<u64>().ok())
-        .context("failed to parse real id")?;
+    // let real_id = fields
+    //     .next()
+    //     .and_then(|raw_id| raw_id.parse::<u64>().ok())
+    //     .context("failed to parse real id")?;
 
-    let triggered_scope = gi_id;
+    // let triggered_scope = gi_id;
 
     Ok(UserEvent {
         name: event_name_from_typ(&typ),
         data: virtual_event_from_websys_event(event.clone()),
-        element: Some(ElementId(real_id as usize)),
-        scope_id: Some(ScopeId(triggered_scope as usize)),
+        element: Some(ElementId(element_id)),
+        scope_id: None,
+        // scope_id: Some(ScopeId(triggered_scope as usize)),
+        // element: Some(ElementId(real_id as usize)),
+        // scope_id: Some(ScopeId(triggered_scope as usize)),
         priority: dioxus_core::EventPriority::Medium,
     })
 }

+ 9 - 6
packages/web/src/lib.rs

@@ -85,7 +85,7 @@ mod ric_raf;
 ///     dioxus_web::launch(App, |c| c);
 /// }
 ///
-/// static App: FC<()> = |(cx, props)| {
+/// static App: FC<()> = |cx, props| {
 ///     rsx!(cx, div {"hello world"})
 /// }
 /// ```
@@ -109,7 +109,7 @@ pub fn launch(root_component: FC<()>, configuration: impl FnOnce(WebConfig) -> W
 ///     name: String
 /// }
 ///
-/// static App: FC<RootProps> = |(cx, props)| {
+/// static App: FC<RootProps> = |cx, props| {
 ///     rsx!(cx, div {"hello {props.name}"})
 /// }
 /// ```
@@ -155,7 +155,7 @@ pub async fn run_with_props<T: 'static + Send>(root: FC<T>, root_props: T, cfg:
     // hydrating is simply running the dom for a single render. If the page is already written, then the corresponding
     // ElementIds should already line up because the web_sys dom has already loaded elements with the DioxusID into memory
     if !should_hydrate {
-        log::info!("Applying rebuild edits..., {:?}", mutations);
+        // log::info!("Applying rebuild edits..., {:?}", mutations);
         websys_dom.process_edits(&mut mutations.edits);
     }
 
@@ -166,17 +166,20 @@ pub async fn run_with_props<T: 'static + Send>(root: FC<T>, root_props: T, cfg:
         // if there is work then this future resolves immediately.
         dom.wait_for_work().await;
 
-        // wait for the mainthread to schedule us in
-        let mut deadline = work_loop.wait_for_idle_time().await;
+        // // wait for the mainthread to schedule us in
+        // let mut deadline = work_loop.wait_for_idle_time().await;
 
         // run the virtualdom work phase until the frame deadline is reached
-        let mutations = dom.work_with_deadline(|| (&mut deadline).now_or_never().is_some());
+        let mutations = dom.work_with_deadline(|| false);
+        // // run the virtualdom work phase until the frame deadline is reached
+        // let mutations = dom.work_with_deadline(|| (&mut deadline).now_or_never().is_some());
 
         // wait for the animation frame to fire so we can apply our changes
         work_loop.wait_for_raf().await;
 
         for mut edit in mutations {
             // actually apply our changes during the animation frame
+            // log::info!("Applying change edits..., {:?}", edit);
             websys_dom.process_edits(&mut edit.edits);
         }
     }

+ 85 - 125
src/lib.rs

@@ -4,166 +4,124 @@
 //!     <strong>A concurrent, functional, virtual DOM for Rust</strong>
 //!   </p>
 //! </div>
-//! Dioxus: a concurrent, functional, reactive virtual dom for any renderer in Rust.
 //!
-//! This crate aims to maintain a hook-based, renderer-agnostic framework for cross-platform UI development.
+//! # Resources
 //!
-//! ## Overview and Goals
-//! Dioxus' ultimate goal is to save you from writing new code when bringing your application to new platforms. We forsee
-//! a future where WebApps, Mobile Apps, Desktop Apps, and even AR apps can be written in the same language, ecosystem,
-//! and leverage the same platform-agnostic libraries.
+//! This overview is provides a brief introduction to Dioxus. For a more in-depth guide, make sure to check out:
+//! - [Getting Started](https://dioxuslabs.com/getting-started)
+//! - [Book](https://dioxuslabs.com/book)
+//! - [Reference](https://dioxuslabs.com/refernce-guide)
+
 //!
-//! In this aim we chose to use a variety of techniques:
-//! - We use a VirtualDOM to abstract the true renderer from application logic.
-//! - We use functions as components to limit the API churn for greater stability.
-//! - We use hooks as state to allow reusable logic across the whole ecosystem.
-//! - We support an extensible and compile-time safe DSL for building interfaces.
+//! # Overview and Goals
 //!
-//! Our guiding stars (in order of priority):
-//! - Ergonomics
-//! - Reusability
-//! - Speed and memory efficiency
-//! - Safety
+//! Dioxus makes it easy to quickly build complex user interfaces with Rust. Any Dioxus app can run in the web browser,
+//! as a desktop app, as a mobile app, or anywhere else provided you build the right renderer.
 //!
-//! ## Components
-//! The base unit of Dioxus is the `component`. Components can be easily created from just a function - no traits or
-//! proc macros required:
+//! Dioxus is heavily inspired by React, supporting many of the same concepts:
 //!
-//! ```
-//! use dioxus::prelude::*;
+//! - Hooks for state
+//! - VirtualDom & diffing
+//! - Concurrency & asynchronous rendering
+//! - JSX-like templating syntax
 //!
-//! fn Example(cx: Context<()>) -> DomTree {
-//!     html! { <div> "Hello, world!" </div> }
-//! }
-//! ```
-//! Components need to take a "Context" parameter which is generic over some properties. This defines how the component can be used
-//! and what properties can be used to specify it in the VNode output. Components without properties may be generic over
-//! `()`, and components with properties must declare their properties as a struct:
+//! If you know React, then you know Dioxus.
 //!
-//! ```
-//! #[derive(Props)]
-//! struct Props { name: String }
+//! Dioxus is *substantially* faster than many of the other Rust UI libraries (Yew/Percy) and is *significantly* faster
+//! than React, competitve with InfernoJS and frameworks like Svelte/SolidJS.
 //!
-//! fn Example(cx: Context<Props>) -> DomTree {
-//!     html! { <div> "Hello {cx.props.name}!" </div> }
-//! }
-//! ```
+//! ## Brief Overview
 //!
-//! Props that are valid for the `'pub static` lifetime automatically get memoized by Diouxs. This means the component won't
-//! re-render if its Props didn't change. However, Props that borrow data from their parent cannot be safely memoized, and
-//! will always re-render if their parent changes. To borrow data from a parent, your component needs to add explicit lifetimes,
-//! otherwise Rust will get confused about whether data is borrowed from either Props or Context. Since Dioxus manages
-//! these lifetimes internally, Context and your Props must share the same lifetime:
+//! All Dioxus apps are built by composing functions that take in a `Scope` and `Properties` and return an `Element`. A `Scope` holds
+//! relevant state data for the the currently-rendered component.
 //!
-//! ```
-//! #[derive(Props)]
-//! struct Props<'a> { name: &'a str }
+//! ```rust
+//! use dioxus::prelude::*;
 //!
-//! fn Example<'a>(cx: Context<'a, Props<'a>>) -> DomTree {
-//!     html! { <div> "Hello {cx.props.name}!" </div> }
+//! fn main() {
+//!     dioxus::desktop::launch(App);
 //! }
-//! ```
-//!
-//!
-//!
-//! The lifetimes might look a little messy, but are crucially important for Dioxus's efficiency and overall ergonimics.
-//! Components can also be crafted as pub static closures, enabling type inference without all the type signature noise. However,
-//! closure-style components cannot work with borrowed data due to limitations in Rust's lifetime system.
-//!
-//! To use custom properties for components, you'll need to derive the `Props` trait for your properties. This trait
-//! exposes a compile-time correct builder pattern (similar to typed-builder) that can be used in the `rsx!` and `html!`
-//! macros to build components. Component props may have default fields notated by the `Default` attribute:
-//!
-//! ```
-//! #[derive(Props)]
-//! struct Props {
-//!     name: String
 //!
-//!     #[props(default = false)]
-//!     checked: bool,
+//! fn App(cx: Scope, props: &()) -> Element {
+//!     let mut count = use_state(cx, || 0);
 //!
-//!     #[props(default, setter(strip_option, into))]
-//!     title: Option<String>
+//!     cx.render(rsx!(
+//!         div { "Count: {count}" }
+//!         button { onclick: move |_| count += 1, "Increment" }
+//!         button { onclick: move |_| count -= 1, "Decrement" }
+//!     ))
 //! }
 //! ```
 //!
-//! These flags roughly follow that of typed-builder, though tweaked to support the `Props` usecase.
+//! ## Components
 //!
-//! ## Hooks and State
-//! Dioxus uses hooks for state management. Hooks are a form of state persisted between calls of the function component.
+//! We can compose these function components to build a complex app. Each new component we design must take some Properties.
+//! For components with no explicit properties, we can use the `()` type. In Dioxus, all properties are memoized by default!
 //!
-//! ```
-//! pub pub static Example: FC<()> = |(cx, props)|{
-//!     let (val, set_val) = use_state(cx, || 0);
+//! ```rust
+//! fn App(cx: Scope, props &()) -> Element {
 //!     cx.render(rsx!(
-//!         button { onclick: move |_| set_val(val + 1) }
+//!         Header {
+//!             title: "My App",
+//!             color: "red",
+//!         }
 //!     ))
 //! }
-//! ````
-//!
-//! Instead of using a single struct to represent a component and its state, hooks use the "use_hook" building block
-//! which allows the persistence of data between function component renders. This primitive is exposed directly through
-//! the `Context` item:
 //! ```
-//! fn my_hook<'a>(cx: &impl Scoped<'a>) -> &'a String {
-//!     cx.use_hook(
-//!         // Initializer stores a value
-//!         |hook_idx| String::new("stored_data"),
 //!
-//!         // Runner returns the hook value every time the component is rendered
-//!         |hook| &*hook,
+//! Our `Header` component takes in a `title` and a `color` property, which we delcare on an explicit `HeaderProps` struct.
+//! ```
+//! // The `Props` derive macro lets us add additional functionality to how props are interpreted.
+//! #[derive(Props, PartialEq)]
+//! struct HeaderProps {
+//!     title: String,
+//!     color: String,
+//! }
 //!
-//!         // Cleanup runs after the component is unmounted
-//!         |hook| log::debug!("cleaning up hook with value {:#?}", hook)
-//!     )
+//! fn Header(cx: Scope, props: &HeaderProps) -> Element {
+//!     cx.render(rsx!(
+//!         div {
+//!             background_color: "{props.color}"
+//!             h1 { "{props.title}" }
+//!         }
+//!     ))
 //! }
 //! ```
-//! Under the hood, hooks store their data in a series of "memory cells". The first render defines the layout of these
-//! memory cells, and on each subsequent render, each `use_hook` call accesses its corresponding memory cell. If a hook
-//! accesses the wrong memory cell, `use_hook` will panic, and your app will crash. You can always use `try_use_hook` but
-//! these types of errors can be easily mitigated by following the rules of hooks:
-//!
-//! - Don’t call Hooks inside loops, conditions, or nested functions
-//! - Don't call hooks in changing order between renders
 //!
-//! Hooks provide a very powerful way to reuse stateful logic between components, simplify large complex components,
-//! and adopt more clear context subscription patterns to make components easier to read. The mechanics of hooks in Dioxus
-//! shares a great amount of similarity with React's hooks and there are many guides to hooks in React online.
+//! ## Hooks
 //!
-//! ## Supported Renderers
-//! Instead of being tightly coupled to a platform, browser, or toolkit, Dioxus implements a VirtualDOM object which
-//! can be consumed to draw the UI. The Dioxus VDOM is reactive and easily consumable by 3rd-party renderers via
-//! the `RealDom` trait. See [Implementing a Renderer](docs/8-custom-renderer.md), the `StringRenderer`, and `WebSys` render implementations for a template
-//! on how to implement your own custom renderer. We provide 1st-class support for these renderers:
+//! While components are reusable forms of UI elements, hooks are reusable forms of logic. The details of hooks are
+//! somewhat complicated. In essence, hooks let us save state between renders of our components and reuse the accompanying
+//! logic across different apps.
 //!
-//! - dioxus-desktop (via WebView)
-//! - dioxus-web (via WebSys)
-//! - dioxus-ssr (via StringRenderer)
-//! - dioxus-liveview (SSR + WebSys)
+//! Hooks are simply composition of other hooks. To create our first hook we can create a simple function that takes in
+//! an Scope. We can then call `use_hook` on the `Scope` to get a mutable reference to the stored value.
 //!
-//! In the main `dioxus` crate, these are all accessible through configuration flags.
+//! ```rust
+//! fn use_say_hello(cx: Scope) -> &mut String {
+//!     cx.use_hook(|_| "Hello".to_string(), |hook| hook)
+//! }
+//! ```
 //!
-//! ## Rendering to the Web
+//! If you want to extend Dioxus with some new functionality, you'll probably want to implement a new hook.
 //!
-//! Most dioxus apps will be initialized in roughly the same way. The `launch` method in `web` will immediately start a
-//! VirtualDOM and await it using `wasm_bindgen_futures`.
 //!
-//! An example app that starts a websys app and internally awaits works as follows:
+//! ## Features
 //!
-//! ```
-//! use dioxus::prelude::*;
-//! fn main() {
-//!     dioxus::web::launch(Example);
-//! }
-//!
-//! pub pub static Example: FC<()> = |(cx, props)|{
-//!     cx.render(rsx! {
-//!         div { "Hello World!" }
-//!     })
-//! };
-//! ```
+//! This overview doesn't cover everything. Make sure to check out the tutorial and reference guide on the official
+//! website for more details.
 //!
-//! In reality, you'll want to integrate analytics, logging, crash-protection and more.
+//! Beyond this overview, Dioxus supports:
+//! - Server-side rendering
+//! - Concurrent rendering (with async support)
+//! - Web/Desktop/Mobile support
+//! - Pre-rendering and rehydration
+//! - Fragments, Portals, and Suspense
+//! - Inline-styles
+//! - Custom event handlers
+//! - Custom elements
+//! - Basic fine-grained reactivity (IE SolidJS/Svelte)
+//! - and more!
 
 // Just a heads-up, the core functionality of dioxus rests in Dioxus-Core. This crate just wraps a bunch of utilities
 // together and exports their namespaces to something predicatble.
@@ -188,10 +146,12 @@ pub use dioxus_desktop as desktop;
 #[cfg(feature = "router")]
 pub use dioxus_router as router;
 
-pub mod debug {}
+pub mod events {
+    #[cfg(feature = "html")]
+    pub use dioxus_html::{on::*, KeyCode};
+}
 
 pub mod prelude {
-    //! A glob import that includes helper types like FC, rsx!, html!, and required traits
     pub use dioxus_core::prelude::*;
     pub use dioxus_core_macro::{format_args_f, rsx, Props, Routable};
     pub use dioxus_elements::{GlobalAttributes, SvgAttributes};