浏览代码

Merge branch 'master' into jk/docs

Jonathan Kelley 3 年之前
父节点
当前提交
6ad82823dd
共有 85 个文件被更改,包括 2591 次插入2275 次删除
  1. 20 0
      .docker/Dockerfile_pre_test
  2. 8 0
      .docker/Dockerfile_test
  3. 27 0
      .docker/README.md
  4. 37 0
      .docker/run_local_tests.sh
  5. 36 0
      .github/ISSUE_TEMPLATE/bug_report.md
  6. 16 0
      .github/ISSUE_TEMPLATE/feature_requst.md
  7. 5 2
      .github/workflows/main.yml
  8. 1 13
      .github/workflows/windows.yml
  9. 1 0
      .gitignore
  10. 4 0
      .vscode/settings.json
  11. 20 0
      CHANGELOG.md
  12. 12 10
      Cargo.toml
  13. 0 1
      LICENSE
  14. 176 0
      LICENSE-APACHE
  15. 23 0
      LICENSE-MIT
  16. 51 0
      Makefile.toml
  17. 1 3
      README.md
  18. 38 0
      examples/borderless.rs
  19. 0 5
      examples/disabled.rs
  20. 2 2
      examples/dog_app.rs
  21. 0 1
      examples/file_explorer.rs
  22. 1 1
      examples/filedragdrop.rs
  23. 2 2
      examples/framework_benchmark.rs
  24. 101 0
      examples/svg.rs
  25. 6 3
      examples/todomvc.rs
  26. 39 1
      packages/core-macro/CHANGELOG.md
  27. 1 1
      packages/core-macro/Cargo.toml
  28. 3 0
      packages/core-macro/src/rsx/component.rs
  29. 55 2
      packages/core/CHANGELOG.md
  30. 1 1
      packages/core/Cargo.toml
  31. 483 660
      packages/core/src/diff.rs
  32. 0 1
      packages/core/src/lib.rs
  33. 8 0
      packages/core/src/mutations.rs
  34. 24 13
      packages/core/src/nodes.rs
  35. 57 66
      packages/core/src/scopes.rs
  36. 55 48
      packages/core/src/virtual_dom.rs
  37. 1 0
      packages/core/tests/.rustfmt.toml
  38. 28 112
      packages/core/tests/create_dom.rs
  39. 60 196
      packages/core/tests/diffing.rs
  40. 4 16
      packages/core/tests/earlyabort.rs
  41. 38 81
      packages/core/tests/lifecycle.rs
  42. 121 6
      packages/core/tests/miri_stress.rs
  43. 108 0
      packages/core/tests/passthru.rs
  44. 96 5
      packages/core/tests/sharedstate.rs
  45. 2 8
      packages/core/tests/vdom_rebuild.rs
  46. 88 4
      packages/desktop/CHANGELOG.md
  47. 6 4
      packages/desktop/Cargo.toml
  48. 0 34
      packages/desktop/examples/async.rs
  49. 52 85
      packages/desktop/src/desktop_context.rs
  50. 51 2
      packages/desktop/src/lib.rs
  51. 35 1
      packages/hooks/CHANGELOG.md
  52. 2 2
      packages/hooks/Cargo.toml
  53. 1 1
      packages/hooks/src/use_shared_state.rs
  54. 23 20
      packages/hooks/src/usestate.rs
  55. 33 2
      packages/html/CHANGELOG.md
  56. 2 2
      packages/html/Cargo.toml
  57. 1 6
      packages/html/src/elements.rs
  58. 16 0
      packages/html/src/global_attributes.rs
  59. 24 0
      packages/interpreter/Cargo.toml
  60. 0 1
      packages/interpreter/README.md
  61. 1 1
      packages/interpreter/src/bindings.rs
  62. 71 206
      packages/interpreter/src/interpreter.js
  63. 7 0
      packages/interpreter/src/lib.rs
  64. 0 499
      packages/jsinterpreter/interpreter.js
  65. 0 13
      packages/jsinterpreter/tsconfig.json
  66. 22 1
      packages/mobile/CHANGELOG.md
  67. 6 1
      packages/mobile/Cargo.toml
  68. 7 0
      packages/mobile/Makefile.toml
  69. 57 1
      packages/router/CHANGELOG.md
  70. 13 4
      packages/router/Cargo.toml
  71. 10 0
      packages/router/Makefile.toml
  72. 1 1
      packages/router/src/components/link.rs
  73. 2 2
      packages/router/src/components/route.rs
  74. 1 0
      packages/router/src/components/router.rs
  75. 38 7
      packages/router/src/hooks/use_route.rs
  76. 1 6
      packages/router/src/platform/mod.rs
  77. 23 35
      packages/router/src/service.rs
  78. 63 0
      packages/router/tests/route.rs
  79. 11 0
      packages/router/webdriver.json
  80. 30 1
      packages/ssr/CHANGELOG.md
  81. 2 2
      packages/ssr/Cargo.toml
  82. 57 3
      packages/web/CHANGELOG.md
  83. 14 13
      packages/web/Cargo.toml
  84. 48 54
      packages/web/src/dom.rs
  85. 0 1
      packages/web/src/lib.rs

+ 20 - 0
.docker/Dockerfile_pre_test

@@ -0,0 +1,20 @@
+FROM rust:1.58-buster
+
+RUN apt update
+RUN apt install -y \
+    libglib2.0-dev \
+    libgtk-3-dev \
+    libsoup2.4-dev \
+    libappindicator3-dev \
+    libwebkit2gtk-4.0-dev \
+    firefox-esr
+# for kcov and Tarpaulin
+#liblzma-dev binutils-dev libcurl4-openssl-dev libdw-dev libelf-dev
+
+RUN cargo install cargo-make --debug
+# for test coverage
+#RUN cargo install cargo-tarpaulin
+# clean up a bit
+RUN cargo install cargo-cache && cargo cache -a
+
+CMD ["exit"]

+ 8 - 0
.docker/Dockerfile_test

@@ -0,0 +1,8 @@
+FROM dioxus-base-test-image
+
+RUN mkdir run_test
+COPY tmp /run_test
+WORKDIR /run_test
+RUN cargo make tests
+
+CMD ["exit"]

+ 27 - 0
.docker/README.md

@@ -0,0 +1,27 @@
+# Why this?
+
+This part is used to test whole package before pushing it
+
+# How to use it?
+
+Just run in the folder:
+`bash run_local_tests.sh`. If nothing fails, then you can push your code to the repo.
+or run:
+`bash run_local_tests.sh --with-full-docker-cleanup`
+for cleaning up images as well
+
+# How is it composed of?
+
+  1. `Dockerfile_pre_test` will build the base image for the tests to be run into
+  2. `Dockerfile_test` will run the actual tests based on 1.
+  3. `run_local_tests.sh` to wrap this up
+
+# Warning
+
+The task requires some amount of CPU work and disk space (5GB per tests). Some clean up is included in the script.
+
+# Requirements
+
+ * [docker](https://docs.docker.com/engine/install/)
+ * bash
+ * rsync

+ 37 - 0
.docker/run_local_tests.sh

@@ -0,0 +1,37 @@
+set -eux
+
+echo "Test script started"
+
+function run_script {
+    if [[ -d tmp ]]
+    then
+        rm -rf tmp
+    fi
+    mkdir tmp
+    # copy files first
+    rsync -a --progress ../ tmp --exclude target --exclude docker
+
+    # build base image
+    docker build -f Dockerfile_pre_test -t dioxus-base-test-image .
+    # run test
+    docker build -f Dockerfile_test -t dioxus-test-image .
+
+    # clean up
+    rm -rf tmp
+    if [ $# -ge 1 ]
+    then
+        echo "Got some parameter"
+        if [ $1 = "--with-full-docker-cleanup" ]
+        then
+        docker image rm dioxus-base-test-image
+        docker image rm dioxus-test-image
+        fi
+    fi
+}
+
+run_script || echo "Error occured.. cleaning a bit." && \
+    docker system prune -f;
+
+docker system prune -f
+
+echo "Script finished to execute"

+ 36 - 0
.github/ISSUE_TEMPLATE/bug_report.md

@@ -0,0 +1,36 @@
+---
+name: Bug report
+about: Create a report to help us improve Dioxus
+---
+
+**Problem**
+
+<!-- A clear and concise description of what the bug is. -->
+
+**Steps To Reproduce**
+
+Steps to reproduce the behavior:
+
+- 
+- 
+- 
+
+**Expected behavior**
+
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+
+If applicable, add screenshots to help explain your problem.
+
+**Environment:**
+ - Dioxus version: [e.g. v0.17, `master`]
+ - Rust version: [e.g. 1.43.0, `nightly`]
+ - OS info: [e.g. MacOS]
+ - App platform: [e.g. `web`, `desktop`]
+
+**Questionnaire**
+<!-- If you feel up to the challenge, please check one of the boxes below: -->
+- [ ] I'm interested in fixing this myself but don't know where to start
+- [ ] I would like to fix and I have a solution
+- [ ] I don't have time to fix this right now, but maybe later

+ 16 - 0
.github/ISSUE_TEMPLATE/feature_requst.md

@@ -0,0 +1,16 @@
+---
+name: Feature Request
+about: If you have any interesting advice, you can tell us.
+---
+
+## Specific Demand
+
+<!--
+What feature do you need, please describe it in detail.
+-->
+
+## Implement Suggestion
+
+<!--
+If you have any suggestion for complete this feature, you can tell us.
+-->

+ 5 - 2
.github/workflows/main.yml

@@ -33,10 +33,13 @@ jobs:
       - uses: Swatinem/rust-cache@v1
       - run: sudo apt-get update
       - run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev
+      - uses: davidB/rust-cargo-make@v1
+      - uses: browser-actions/setup-firefox@latest
+      - uses: jetli/wasm-pack-action@v0.3.0
       - uses: actions-rs/cargo@v1
         with:
-          command: test
-          args: --features "desktop, ssr, router"
+          command: make
+          args: tests
 
   fmt:
     name: Rustfmt

+ 1 - 13
.github/workflows/windows.yml

@@ -19,13 +19,7 @@ jobs:
       max-parallel: 2
       fail-fast: false
       matrix:
-        target:
-          [
-            i686-pc-windows-gnu,
-            i686-pc-windows-msvc,
-            x86_64-pc-windows-gnu,
-            x86_64-pc-windows-msvc,
-          ]
+        target: [x86_64-pc-windows-gnu, x86_64-pc-windows-msvc]
         cfg_release_channel: [stable]
 
     steps:
@@ -47,12 +41,6 @@ jobs:
           rustup target add ${{ matrix.target }}
         shell: powershell
 
-      - name: Add mingw32 to path for i686-gnu
-        run: |
-          echo "C:\msys64\mingw32\bin" >> $GITHUB_PATH
-        if: matrix.target == 'i686-pc-windows-gnu' && matrix.channel == 'nightly'
-        shell: bash
-
       - name: Add mingw64 to path for x86_64-gnu
         run: echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH
         if: matrix.target == 'x86_64-pc-windows-gnu' && matrix.channel == 'nightly'

+ 1 - 0
.gitignore

@@ -1,4 +1,5 @@
 /target
+/dist
 Cargo.lock
 .DS_Store
 

+ 4 - 0
.vscode/settings.json

@@ -4,4 +4,8 @@
     "desktop",
     "router"
   ],
+  "editor.formatOnSave": true,
+  "[toml]": {
+    "editor.formatOnSave": false
+  }
 }

+ 20 - 0
CHANGELOG.md

@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## Unreleased
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 1 commit contributed to the release over the course of 7 calendar days.
+ - 0 commits where understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' where seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+    - Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d))
+</details>
+
 ## v0.1.7 (2022-01-08)
 
 ### Bug Fixes

+ 12 - 10
Cargo.toml

@@ -1,27 +1,28 @@
 [package]
 name = "dioxus"
-version = "0.1.7"
+version = "0.1.8"
 authors = ["Jonathan Kelley"]
 edition = "2018"
 description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
-license = "MIT/Apache-2.0"
+license = "MIT OR Apache-2.0"
 repository = "https://github.com/DioxusLabs/dioxus/"
 homepage = "https://dioxuslabs.com"
 documentation = "https://dioxuslabs.com"
 keywords = ["dom", "ui", "gui", "react", "wasm"]
 
 [dependencies]
-dioxus-core = { path = "./packages/core", version = "^0.1.7" }
-dioxus-html = { path = "./packages/html", version = "^0.1.4", optional = true }
-dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.6", optional = true }
-dioxus-hooks = { path = "./packages/hooks", version = "^0.1.6", optional = true }
+dioxus-core = { path = "./packages/core", version = "^0.1.9" }
+dioxus-html = { path = "./packages/html", version = "^0.1.6", optional = true }
+dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.7", optional = true }
+dioxus-hooks = { path = "./packages/hooks", version = "^0.1.7", optional = true }
 
-dioxus-web = { path = "./packages/web", version = "^0.0.4", optional = true }
-dioxus-desktop = { path = "./packages/desktop", version = "^0.1.5", optional = true }
-dioxus-ssr = { path = "./packages/ssr", version = "^0.1.2", optional = true }
+dioxus-web = { path = "./packages/web", version = "^0.0.5", optional = true }
+dioxus-desktop = { path = "./packages/desktop", version = "^0.1.6", optional = true }
+dioxus-ssr = { path = "./packages/ssr", version = "^0.1.3", optional = true }
 
-dioxus-router = { path = "./packages/router", version = "^0.1.0", optional = true }
+dioxus-router = { path = "./packages/router", version = "^0.1.1", optional = true }
 dioxus-mobile = { path = "./packages/mobile", version = "^0.0.3", optional = true }
+dioxus-interpreter-js = { path = "./packages/interpreter", version = "^0.0.0", optional = true }
 # dioxus-liveview = { path = "./packages/liveview", optional = true }
 
 [features]
@@ -52,6 +53,7 @@ members = [
     "packages/ssr",
     "packages/desktop",
     "packages/mobile",
+    "packages/interpreter",
 ]
 
 [dev-dependencies]

+ 0 - 1
LICENSE

@@ -1 +0,0 @@
-MIT/Apache-2 

+ 176 - 0
LICENSE-APACHE

@@ -0,0 +1,176 @@
+                              Apache License
+                        Version 2.0, January 2004
+                     http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+   "License" shall mean the terms and conditions for use, reproduction,
+   and distribution as defined by Sections 1 through 9 of this document.
+
+   "Licensor" shall mean the copyright owner or entity authorized by
+   the copyright owner that is granting the License.
+
+   "Legal Entity" shall mean the union of the acting entity and all
+   other entities that control, are controlled by, or are under common
+   control with that entity. For the purposes of this definition,
+   "control" means (i) the power, direct or indirect, to cause the
+   direction or management of such entity, whether by contract or
+   otherwise, or (ii) ownership of fifty percent (50%) or more of the
+   outstanding shares, or (iii) beneficial ownership of such entity.
+
+   "You" (or "Your") shall mean an individual or Legal Entity
+   exercising permissions granted by this License.
+
+   "Source" form shall mean the preferred form for making modifications,
+   including but not limited to software source code, documentation
+   source, and configuration files.
+
+   "Object" form shall mean any form resulting from mechanical
+   transformation or translation of a Source form, including but
+   not limited to compiled object code, generated documentation,
+   and conversions to other media types.
+
+   "Work" shall mean the work of authorship, whether in Source or
+   Object form, made available under the License, as indicated by a
+   copyright notice that is included in or attached to the work
+   (an example is provided in the Appendix below).
+
+   "Derivative Works" shall mean any work, whether in Source or Object
+   form, that is based on (or derived from) the Work and for which the
+   editorial revisions, annotations, elaborations, or other modifications
+   represent, as a whole, an original work of authorship. For the purposes
+   of this License, Derivative Works shall not include works that remain
+   separable from, or merely link (or bind by name) to the interfaces of,
+   the Work and Derivative Works thereof.
+
+   "Contribution" shall mean any work of authorship, including
+   the original version of the Work and any modifications or additions
+   to that Work or Derivative Works thereof, that is intentionally
+   submitted to Licensor for inclusion in the Work by the copyright owner
+   or by an individual or Legal Entity authorized to submit on behalf of
+   the copyright owner. For the purposes of this definition, "submitted"
+   means any form of electronic, verbal, or written communication sent
+   to the Licensor or its representatives, including but not limited to
+   communication on electronic mailing lists, source code control systems,
+   and issue tracking systems that are managed by, or on behalf of, the
+   Licensor for the purpose of discussing and improving the Work, but
+   excluding communication that is conspicuously marked or otherwise
+   designated in writing by the copyright owner as "Not a Contribution."
+
+   "Contributor" shall mean Licensor and any individual or Legal Entity
+   on behalf of whom a Contribution has been received by Licensor and
+   subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   copyright license to reproduce, prepare Derivative Works of,
+   publicly display, publicly perform, sublicense, and distribute the
+   Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   (except as stated in this section) patent license to make, have made,
+   use, offer to sell, sell, import, and otherwise transfer the Work,
+   where such license applies only to those patent claims licensable
+   by such Contributor that are necessarily infringed by their
+   Contribution(s) alone or by combination of their Contribution(s)
+   with the Work to which such Contribution(s) was submitted. If You
+   institute patent litigation against any entity (including a
+   cross-claim or counterclaim in a lawsuit) alleging that the Work
+   or a Contribution incorporated within the Work constitutes direct
+   or contributory patent infringement, then any patent licenses
+   granted to You under this License for that Work shall terminate
+   as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+   Work or Derivative Works thereof in any medium, with or without
+   modifications, and in Source or Object form, provided that You
+   meet the following conditions:
+
+   (a) You must give any other recipients of the Work or
+       Derivative Works a copy of this License; and
+
+   (b) You must cause any modified files to carry prominent notices
+       stating that You changed the files; and
+
+   (c) You must retain, in the Source form of any Derivative Works
+       that You distribute, all copyright, patent, trademark, and
+       attribution notices from the Source form of the Work,
+       excluding those notices that do not pertain to any part of
+       the Derivative Works; and
+
+   (d) If the Work includes a "NOTICE" text file as part of its
+       distribution, then any Derivative Works that You distribute must
+       include a readable copy of the attribution notices contained
+       within such NOTICE file, excluding those notices that do not
+       pertain to any part of the Derivative Works, in at least one
+       of the following places: within a NOTICE text file distributed
+       as part of the Derivative Works; within the Source form or
+       documentation, if provided along with the Derivative Works; or,
+       within a display generated by the Derivative Works, if and
+       wherever such third-party notices normally appear. The contents
+       of the NOTICE file are for informational purposes only and
+       do not modify the License. You may add Your own attribution
+       notices within Derivative Works that You distribute, alongside
+       or as an addendum to the NOTICE text from the Work, provided
+       that such additional attribution notices cannot be construed
+       as modifying the License.
+
+   You may add Your own copyright statement to Your modifications and
+   may provide additional or different license terms and conditions
+   for use, reproduction, or distribution of Your modifications, or
+   for any such Derivative Works as a whole, provided Your use,
+   reproduction, and distribution of the Work otherwise complies with
+   the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+   any Contribution intentionally submitted for inclusion in the Work
+   by You to the Licensor shall be under the terms and conditions of
+   this License, without any additional terms or conditions.
+   Notwithstanding the above, nothing herein shall supersede or modify
+   the terms of any separate license agreement you may have executed
+   with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+   names, trademarks, service marks, or product names of the Licensor,
+   except as required for reasonable and customary use in describing the
+   origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+   agreed to in writing, Licensor provides the Work (and each
+   Contributor provides its Contributions) on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied, including, without limitation, any warranties or conditions
+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+   PARTICULAR PURPOSE. You are solely responsible for determining the
+   appropriateness of using or redistributing the Work and assume any
+   risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+   whether in tort (including negligence), contract, or otherwise,
+   unless required by applicable law (such as deliberate and grossly
+   negligent acts) or agreed to in writing, shall any Contributor be
+   liable to You for damages, including any direct, indirect, special,
+   incidental, or consequential damages of any character arising as a
+   result of this License or out of the use or inability to use the
+   Work (including but not limited to damages for loss of goodwill,
+   work stoppage, computer failure or malfunction, or any and all
+   other commercial damages or losses), even if such Contributor
+   has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+   the Work or Derivative Works thereof, You may choose to offer,
+   and charge a fee for, acceptance of support, warranty, indemnity,
+   or other liability obligations and/or rights consistent with this
+   License. However, in accepting such obligations, You may act only
+   on Your own behalf and on Your sole responsibility, not on behalf
+   of any other Contributor, and only if You agree to indemnify,
+   defend, and hold each Contributor harmless for any liability
+   incurred by, or claims asserted against, such Contributor by reason
+   of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS

+ 23 - 0
LICENSE-MIT

@@ -0,0 +1,23 @@
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.

+ 51 - 0
Makefile.toml

@@ -0,0 +1,51 @@
+[config]
+default_to_workspace = false
+min_version = "0.32.4"
+
+[env]
+CARGO_MAKE_CLIPPY_ARGS = "-- --deny=warnings"
+CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
+
+[config.modify_core_tasks]
+namespace = "core"
+private = true
+
+[tasks.tests-setup]
+private = true
+script = [
+  """
+    test_flags = array --headless --firefox
+    dioxus_test_features = set wasm_test
+    dioxus_test_flags = array_join ${test_flags} " "
+    echo "running tests with flags: ${dioxus_test_flags} and features: ${dioxus_test_features}"
+    set_env DIOXUS_TEST_FLAGS ${dioxus_test_flags}
+    set_env DIOXUS_TEST_FEATURES ${dioxus_test_features}
+    """,
+]
+script_runner = "@duckscript"
+
+[tasks.tests]
+category = "Testing"
+dependencies = ["tests-setup"]
+description = "Run all tests"
+env = {CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*"]}
+run_task = {name = ["test-flow", "test-with-browser"], fork = true}
+
+[tasks.build]
+command = "cargo"
+args = ["build"]
+
+[tasks.test-flow]
+dependencies = ["test"]
+private = true
+
+[tasks.test]
+dependencies = ["build"]
+command = "cargo"
+args = ["test", "--all-targets", "--workspace", "--exclude", "dioxus-router"]
+private = true
+
+[tasks.test-with-browser]
+env = { CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS = ["**/packages/router"] }
+private = true
+workspace = true

+ 1 - 3
README.md

@@ -1,5 +1,5 @@
 <div align="center">
-  <h1>🌗🚀 Dioxus</h1>
+  <h1>Dioxus</h1>
   <p>
     <strong>Frontend that scales.</strong>
   </p>
@@ -26,9 +26,7 @@
     <img src="https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg"
       alt="CI status" />
   </a>
-</div>
 
-<div align="center">
   <!--Awesome -->
   <a href="https://github.com/dioxuslabs/awesome-dioxus">
     <img src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg" alt="Awesome Page" />

+ 38 - 0
examples/borderless.rs

@@ -0,0 +1,38 @@
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus::desktop::launch_cfg(app, |cfg| {
+        cfg.with_window(|w| w.with_title("BorderLess Demo").with_decorations(false))
+    });
+}
+
+fn app(cx: Scope) -> Element {
+    let window = dioxus::desktop::use_window(&cx);
+
+    cx.render(rsx!(
+        link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", rel:"stylesheet" }
+        header {
+            class: "text-gray-400 bg-gray-900 body-font",
+            onmousedown: move |_| window.drag(),
+            div {
+                class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center",
+                a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0",
+                    span { class: "ml-3 text-xl", "Dioxus"}
+                }
+                nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center" }
+                button {
+                    class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
+                    onmousedown: |evt| evt.cancel_bubble(),
+                    onclick: move |_| window.minimize(true),
+                    "Minimize"
+                }
+                button {
+                    class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
+                    onmousedown: |evt| evt.cancel_bubble(),
+                    onclick: move |_| window.close(),
+                    "Close"
+                }
+            }
+        }
+    ))
+}

+ 0 - 5
examples/disabled.rs

@@ -18,11 +18,6 @@ fn app(cx: Scope) -> Element {
                 disabled: "{disabled}",
                 "lower button"
             }
-
-            input {
-                value: "false",
-            }
-
         }
     })
 }

+ 2 - 2
examples/dog_app.rs

@@ -16,7 +16,7 @@ struct ListBreeds {
 }
 
 fn app(cx: Scope) -> Element {
-    let fut = use_future(&cx, || async move {
+    let breeds = use_future(&cx, || async move {
         reqwest::get("https://dog.ceo/api/breeds/list/all")
             .await
             .unwrap()
@@ -26,7 +26,7 @@ fn app(cx: Scope) -> Element {
 
     let (breed, set_breed) = use_state(&cx, || None);
 
-    match fut.value() {
+    match breeds.value() {
         Some(Ok(breeds)) => cx.render(rsx! {
             div {
                 h1 {"Select a dog breed!"}

+ 0 - 1
examples/file_explorer.rs

@@ -56,7 +56,6 @@ fn app(cx: Scope) -> Element {
                 )
             })
         }
-
     })
 }
 

+ 1 - 1
examples/filedragdrop.rs

@@ -4,7 +4,7 @@ fn main() {
     dioxus::desktop::launch_with_props(app, (), |c| {
         c.with_file_drop_handler(|_w, e| {
             println!("{:?}", e);
-            false
+            true
         })
     });
 }

+ 2 - 2
examples/framework_benchmark.rs

@@ -17,9 +17,9 @@ 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 {
+        for x in 0..num {
             labels.push(Label {
-                key: 0,
+                key: x,
                 labels: [
                     ADJECTIVES.choose(&mut rng).unwrap(),
                     COLOURS.choose(&mut rng).unwrap(),

+ 101 - 0
examples/svg.rs

@@ -0,0 +1,101 @@
+// Thanks to @japsu and their project https://github.com/japsu/jatsi for the example!
+
+use dioxus::{events::MouseEvent, prelude::*};
+
+fn main() {
+    dioxus::desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    let (val, set_val) = use_state(&cx, || 5);
+
+    cx.render(rsx! {
+        div {
+            user_select: "none",
+            webkit_user_select: "none",
+            margin_left: "10%",
+            margin_right: "10%",
+            h1 { "Click die to generate a new value" }
+            div {
+                cursor: "pointer",
+                height: "80%",
+                width: "80%",
+                Die {
+                    value: *val,
+                    keep: true,
+                    onclick: move |_| {
+                        use rand::Rng;
+                        let mut rng = rand::thread_rng();
+                        set_val(rng.gen_range(1..6));
+                    }
+                }
+            }
+        }
+    })
+}
+
+#[derive(Props)]
+pub struct DieProps<'a> {
+    pub value: u64,
+    pub keep: bool,
+    pub onclick: EventHandler<'a, MouseEvent>,
+}
+
+const DOTS: [(i64, i64); 7] = [(-1, -1), (-1, -0), (-1, 1), (1, -1), (1, 0), (1, 1), (0, 0)];
+const DOTS_FOR_VALUE: [[bool; 7]; 6] = [
+    [false, false, false, false, false, false, true],
+    [false, false, true, true, false, false, false],
+    [false, false, true, true, false, false, true],
+    [true, false, true, true, false, true, false],
+    [true, false, true, true, false, true, true],
+    [true, true, true, true, true, true, false],
+];
+
+const OFFSET: i64 = 600;
+const DOT_RADIUS: &str = "200";
+const HELD_COLOR: &str = "#aaa";
+const UNHELD_COLOR: &str = "#ddd";
+
+// A six-sided die (D6) with dots.
+#[allow(non_snake_case)]
+pub fn Die<'a>(cx: Scope<'a, DieProps<'a>>) -> Element {
+    let &DieProps { value, keep, .. } = cx.props;
+
+    let active_dots = &DOTS_FOR_VALUE[(value - 1) as usize];
+    let fill = if keep { HELD_COLOR } else { UNHELD_COLOR };
+    let dots = DOTS
+        .iter()
+        .zip(active_dots.iter())
+        .filter(|(_, &active)| active)
+        .map(|((x, y), _)| {
+            let dcx = x * OFFSET;
+            let dcy = y * OFFSET;
+            rsx!(circle {
+                cx: "{dcx}",
+                cy: "{dcy}",
+                r: "{DOT_RADIUS}",
+                fill: "#333"
+            })
+        });
+
+    rsx!(cx,
+      svg {
+        onclick: move |e| cx.props.onclick.call(e),
+        prevent_default: "onclick",
+        "dioxus-prevent-default": "onclick",
+        class: "die",
+        view_box: "-1000 -1000 2000 2000",
+
+        rect {
+          x: "-1000",
+          y: "-1000",
+          width: "2000",
+          height: "2000",
+          rx: "{DOT_RADIUS}",
+          fill: "{fill}",
+        }
+
+        dots
+      }
+    )
+}

+ 6 - 3
examples/todomvc.rs

@@ -120,15 +120,18 @@ pub fn todo_entry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
 
     rsx!(cx, li {
         class: "{completed} {editing}",
-        onclick: move |_| set_is_editing(true),
-        onfocusout: move |_| set_is_editing(false),
         div { class: "view",
             input { class: "toggle", r#type: "checkbox", id: "cbg-{todo.id}", checked: "{todo.checked}",
                 onchange: move |evt| {
                     cx.props.set_todos.make_mut()[&cx.props.id].checked = evt.value.parse().unwrap();
                 }
             }
-            label { r#for: "cbg-{todo.id}", pointer_events: "none", "{todo.contents}" }
+            label {
+                r#for: "cbg-{todo.id}",
+                onclick: move |_| set_is_editing(true),
+                onfocusout: move |_| set_is_editing(false),
+                "{todo.contents}"
+            }
         }
         is_editing.then(|| rsx!{
             input {

+ 39 - 1
packages/core-macro/CHANGELOG.md

@@ -5,6 +5,43 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## Unreleased
+
+### New Features
+
+ - <csr-id-d2372717bd01fcff50af0572360e3f763d4c869d/> flatten props attrs
+ - <csr-id-47bc4e4a44a7d08d4f42102d13f0766d9d6bf358/> add "optional" flag for props
+
+### Bug Fixes
+
+ - <csr-id-90abd9c9a08c165384fae9c1f7c3fd098d512c48/> detection of f-string formatting in components
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 9 commits contributed to the release over the course of 12 calendar days.
+ - 3 commits where understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' where seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+    - Merge pull request #95 from DioxusLabs/jk/filedragindrop ([`ca0dd4a`](https://github.comgit//DioxusLabs/dioxus/commit/ca0dd4aa7192d483a195d420363f39d771f3e471))
+    - Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d))
+    - flatten props attrs ([`d237271`](https://github.comgit//DioxusLabs/dioxus/commit/d2372717bd01fcff50af0572360e3f763d4c869d))
+    - Merge pull request #108 from DioxusLabs/jk/fstring-component-fields ([`f4132d1`](https://github.comgit//DioxusLabs/dioxus/commit/f4132d1874f7495049fac23ba0a022ac137ad74f))
+    - detection of f-string formatting in components ([`90abd9c`](https://github.comgit//DioxusLabs/dioxus/commit/90abd9c9a08c165384fae9c1f7c3fd098d512c48))
+    - Enable clippy ([`b6903bf`](https://github.comgit//DioxusLabs/dioxus/commit/b6903bf558bc7a3d0fe6794a137c44fca0957d11))
+    - Merge pull request #107 from autarch/autarch/half-assed-router ([`8d3ac3f`](https://github.comgit//DioxusLabs/dioxus/commit/8d3ac3ff148aef9d10a393eda453a11c1e882f58))
+    - Merge pull request #127 from DioxusLabs/jk/handler-tweak ([`5bce294`](https://github.comgit//DioxusLabs/dioxus/commit/5bce294a86941090660b1cfd87809a2b0b76d2ce))
+    - add "optional" flag for props ([`47bc4e4`](https://github.comgit//DioxusLabs/dioxus/commit/47bc4e4a44a7d08d4f42102d13f0766d9d6bf358))
+</details>
+
 ## v0.1.6 (2022-01-08)
 
 ### Documentation
@@ -64,7 +101,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 <csr-read-only-do-not-edit/>
 
- - 138 commits contributed to the release over the course of 352 calendar days.
+ - 139 commits contributed to the release over the course of 352 calendar days.
  - 123 commits where understood as [conventional](https://www.conventionalcommits.org).
  - 0 issues like '(#ID)' where seen in commit messages
 
@@ -75,6 +112,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 <details><summary>view details</summary>
 
  * **Uncategorized**
+    - Release dioxus-core-macro v0.1.6 ([`db0a5bd`](https://github.comgit//DioxusLabs/dioxus/commit/db0a5bd6ec93803cddb3c6fda4172257b9c301c6))
     - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
     - component pass thru events ([`c439b0a`](https://github.comgit//DioxusLabs/dioxus/commit/c439b0ac7e09f70a04262b7c29938d8c52197b76))
     - Merge pull request #74 from mrxiaozhuox/master ([`47056fd`](https://github.comgit//DioxusLabs/dioxus/commit/47056fda4577bcbdaa2a6f63d82eec876e5a5aee))

+ 1 - 1
packages/core-macro/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "dioxus-core-macro"
-version = "0.1.6"
+version = "0.1.7"
 authors = ["Jonathan Kelley"]
 edition = "2021"
 description = "Core macro for Dioxus Virtual DOM"

+ 3 - 0
packages/core-macro/src/rsx/component.rs

@@ -135,11 +135,14 @@ impl ToTokens for Component {
             None => quote! { None },
         };
 
+        let fn_name = self.name.segments.last().unwrap().ident.to_string();
+
         tokens.append_all(quote! {
             __cx.component(
                 #name,
                 #builder,
                 #key_token,
+                #fn_name
             )
         })
     }

+ 55 - 2
packages/core/CHANGELOG.md

@@ -5,6 +5,58 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## Unreleased
+
+### Documentation
+
+ - <csr-id-036a0ff49a7dade0e04c9c07071a1ff49133ee24/> add comments for the Handler
+
+### New Features
+
+ - <csr-id-bad4b773b7bbab6a38d4f48d88d8c5a8787927ac/> add "spawn" method
+ - <csr-id-d2bd1751436ef4bec554bb361ba87aea1357036a/> allow providing context to the root component
+
+### Bug Fixes
+
+ - <csr-id-d9a07ddddb45ba00e35645ac0e0ad1a4d2dd996d/> provide_root_context on root scopes
+ - <csr-id-c8d528b3b18e320ca0cee1542ae5fc659c1cca81/> proprogation of root context
+ - <csr-id-e47ead5347ce778935f8f2127fcb14f520eda0ce/> allow eventhandler to derive default
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 18 commits contributed to the release over the course of 13 calendar days.
+ - 8 commits where understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' where seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+    - Merge pull request #169 from DioxusLabs/jk/router-userouter ([`3509602`](https://github.comgit//DioxusLabs/dioxus/commit/3509602c0bcd327a33bc8c95896775e24751da1a))
+    - Merge branch 'master' of github.com:DioxusLabs/dioxus ([`8899701`](https://github.comgit//DioxusLabs/dioxus/commit/88997019c5c74658bfd76344d6bb6f3565943b41))
+    - add miri stress test ([`e9792e9`](https://github.comgit//DioxusLabs/dioxus/commit/e9792e9b95521fcf0b0b4e240d7cf0397cd79b8f))
+    - add "spawn" method ([`bad4b77`](https://github.comgit//DioxusLabs/dioxus/commit/bad4b773b7bbab6a38d4f48d88d8c5a8787927ac))
+    - drop hooks before resetting bump arena ([`2e4f765`](https://github.comgit//DioxusLabs/dioxus/commit/2e4f7659327b69b74e20af59b6d5e65c699b6adb))
+    - provide_root_context on root scopes ([`d9a07dd`](https://github.comgit//DioxusLabs/dioxus/commit/d9a07ddddb45ba00e35645ac0e0ad1a4d2dd996d))
+    - Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d))
+    - Merge pull request #121 from DioxusLabs/jk/unify ([`b287a4c`](https://github.comgit//DioxusLabs/dioxus/commit/b287a4cab3eec9e6961a3f010846291f4f105747))
+    - Merge pull request #108 from DioxusLabs/jk/fstring-component-fields ([`f4132d1`](https://github.comgit//DioxusLabs/dioxus/commit/f4132d1874f7495049fac23ba0a022ac137ad74f))
+    - proprogation of root context ([`c8d528b`](https://github.comgit//DioxusLabs/dioxus/commit/c8d528b3b18e320ca0cee1542ae5fc659c1cca81))
+    - allow providing context to the root component ([`d2bd175`](https://github.comgit//DioxusLabs/dioxus/commit/d2bd1751436ef4bec554bb361ba87aea1357036a))
+    - Enable clippy ([`b6903bf`](https://github.comgit//DioxusLabs/dioxus/commit/b6903bf558bc7a3d0fe6794a137c44fca0957d11))
+    - Merge pull request #107 from autarch/autarch/half-assed-router ([`8d3ac3f`](https://github.comgit//DioxusLabs/dioxus/commit/8d3ac3ff148aef9d10a393eda453a11c1e882f58))
+    - Merge pull request #138 from mrxiaozhuox/master ([`8c7473d`](https://github.comgit//DioxusLabs/dioxus/commit/8c7473d1943dd133f388ec36116c9d8295861b97))
+    - Merge pull request #133 from mrxiaozhuox/master ([`887f69d`](https://github.comgit//DioxusLabs/dioxus/commit/887f69d5b47bdcde4fe0eab094c0cd0de23e4f3f))
+    - Don't expect all components to have a scope in ScopeArena.ensure_drop_safety ([`9b282d8`](https://github.comgit//DioxusLabs/dioxus/commit/9b282d877ba0fe2463687cde7ea899b7f8510425))
+    - add comments for the Handler ([`036a0ff`](https://github.comgit//DioxusLabs/dioxus/commit/036a0ff49a7dade0e04c9c07071a1ff49133ee24))
+    - allow eventhandler to derive default ([`e47ead5`](https://github.comgit//DioxusLabs/dioxus/commit/e47ead5347ce778935f8f2127fcb14f520eda0ce))
+</details>
+
 ## v0.1.7 (2022-01-08)
 
 ### Documentation
@@ -138,7 +190,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 <csr-read-only-do-not-edit/>
 
- - 437 commits contributed to the release over the course of 358 calendar days.
+ - 438 commits contributed to the release over the course of 358 calendar days.
  - 416 commits where understood as [conventional](https://www.conventionalcommits.org).
  - 0 issues like '(#ID)' where seen in commit messages
 
@@ -149,8 +201,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 <details><summary>view details</summary>
 
  * **Uncategorized**
-    - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
+    - Release dioxus-core v0.1.7 ([`16d73b2`](https://github.comgit//DioxusLabs/dioxus/commit/16d73b240fc446c6f6996c19ccf52bbcda2eca79))
     - add title to doc comment ([`d11f322`](https://github.comgit//DioxusLabs/dioxus/commit/d11f322f554e7dbf43b988c9cfda56498cc49872))
+    - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
     - better document the `EventHandler` type ([`be9f1a5`](https://github.comgit//DioxusLabs/dioxus/commit/be9f1a52ad2b04f101397ae34482ea7394df653b))
     - component pass thru events ([`c439b0a`](https://github.comgit//DioxusLabs/dioxus/commit/c439b0ac7e09f70a04262b7c29938d8c52197b76))
     - Merge pull request #74 from mrxiaozhuox/master ([`47056fd`](https://github.comgit//DioxusLabs/dioxus/commit/47056fda4577bcbdaa2a6f63d82eec876e5a5aee))

+ 1 - 1
packages/core/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "dioxus-core"
-version = "0.1.7"
+version = "0.1.9"
 authors = ["Jonathan Kelley"]
 edition = "2018"
 description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"

+ 483 - 660
packages/core/src/diff.rs

@@ -1,33 +1,36 @@
-//! This module contains the stateful DiffState and all methods to diff VNodes, their properties, and their children.
+#![warn(clippy::pedantic)]
+#![allow(clippy::cast_possible_truncation)]
+
+//! This module contains the stateful [`DiffState`] and all methods to diff [`VNodes`], their properties, and their children.
 //!
 //! The [`DiffState`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set
-//! of mutations for the RealDom to apply.
+//! of mutations for the [`RealDom`] to apply.
 //!
 //! ## Notice:
 //!
 //! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support
-//! Components, Fragments, Suspense, SubTree memoization, incremental diffing, cancellation, NodeRefs, pausing, priority
+//! Components, Fragments, Suspense, [`SubTree`] memoization, incremental diffing, cancellation, [`NodeRefs`], pausing, priority
 //! scheduling, and additional batching operations.
 //!
 //! ## Implementation Details:
 //!
 //! ### IDs for elements
 //! --------------------
-//! All nodes are addressed by their IDs. The RealDom provides an imperative interface for making changes to these nodes.
+//! All nodes are addressed by their IDs. The [`RealDom`] provides an imperative interface for making changes to these nodes.
 //! We don't necessarily require that DOM changes happen instantly during the diffing process, so the implementor may choose
 //! to batch nodes if it is more performant for their application. The element IDs are indices into the internal element
 //! array. The expectation is that implementors will use the ID as an index into a Vec of real nodes, allowing for passive
-//! garbage collection as the VirtualDOM replaces old nodes.
+//! garbage collection as the [`VirtualDOM`] replaces old nodes.
 //!
 //! When new vnodes are created through `cx.render`, they won't know which real node they correspond to. During diffing,
-//! we always make sure to copy over the ID. If we don't do this properly, the ElementId will be populated incorrectly
+//! we always make sure to copy over the ID. If we don't do this properly, the [`ElementId`] will be populated incorrectly
 //! and brick the user's page.
 //!
 //! ### Fragment Support
 //! --------------------
 //! Fragments (nodes without a parent) are supported through a combination of "replace with" and anchor vnodes. Fragments
 //! can be particularly challenging when they are empty, so the anchor node lets us "reserve" a spot for the empty
-//! fragment to be replaced with when it is no longer empty. This is guaranteed by logic in the NodeFactory - it is
+//! fragment to be replaced with when it is no longer empty. This is guaranteed by logic in the [`NodeFactory`] - it is
 //! impossible to craft a fragment with 0 elements - they must always have at least a single placeholder element. Adding
 //! "dummy" nodes _is_ inefficient, but it makes our diffing algorithm faster and the implementation is completely up to
 //! the platform.
@@ -41,13 +44,13 @@
 //! into a promise-like value. React will then work on the next "ready" fiber, checking back on the previous fiber once
 //! it has finished its new work. In Dioxus, we use a similar approach, but try to completely render the tree before
 //! switching sub-fibers. Instead, each future is submitted into a futures-queue and the node is manually loaded later on.
-//! Due to the frequent calls to "yield_now" we can get the pure "fetch-as-you-render" behavior of React Fiber.
+//! Due to the frequent calls to [`yield_now`] we can get the pure "fetch-as-you-render" behavior of React Fiber.
 //!
 //! We're able to use this approach because we use placeholder nodes - futures that aren't ready still get submitted to
 //! DOM, but as a placeholder.
 //!
 //! Right now, the "suspense" queue is intertwined with hooks. In the future, we should allow any future to drive attributes
-//! and contents, without the need for the "use_suspense" hook. In the interim, this is the quickest way to get Suspense working.
+//! and contents, without the need for the [`use_suspense`] hook. In the interim, this is the quickest way to get Suspense working.
 //!
 //! ## Subtree Memoization
 //! -----------------------
@@ -88,254 +91,78 @@
 //! More info on how to improve this diffing algorithm:
 //!  - <https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/>
 
-use crate::innerlude::*;
+use crate::innerlude::{
+    AnyProps, ElementId, Mutations, ScopeArena, ScopeId, VComponent, VElement, VFragment, VNode,
+    VPlaceholder, VText,
+};
 use fxhash::{FxHashMap, FxHashSet};
 use smallvec::{smallvec, SmallVec};
-use DomEdit::*;
-
-/// Our DiffState is an iterative tree differ.
-///
-/// It uses techniques of a stack machine to allow pausing and restarting of the diff algorithm. This
-/// was originally implemented using recursive techniques, but Rust lacks the ability to call async functions recursively,
-/// meaning we could not "pause" the original diffing algorithm.
-///
-/// Instead, we use a traditional stack machine approach to diff and create new nodes. The diff algorithm periodically
-/// calls "yield_now" which allows the machine to pause and return control to the caller. The caller can then wait for
-/// the next period of idle time, preventing our diff algorithm from blocking the main thread.
-///
-/// Funnily enough, this stack machine's entire job is to create instructions for another stack machine to execute. It's
-/// stack machines all the way down!
+
 pub(crate) struct DiffState<'bump> {
     pub(crate) scopes: &'bump ScopeArena,
     pub(crate) mutations: Mutations<'bump>,
-    pub(crate) stack: DiffStack<'bump>,
     pub(crate) force_diff: bool,
+    pub(crate) element_stack: SmallVec<[ElementId; 10]>,
+    pub(crate) scope_stack: SmallVec<[ScopeId; 5]>,
 }
 
-impl<'bump> DiffState<'bump> {
-    pub(crate) fn new(scopes: &'bump ScopeArena) -> Self {
+impl<'b> DiffState<'b> {
+    pub fn new(scopes: &'b ScopeArena) -> Self {
         Self {
             scopes,
             mutations: Mutations::new(),
-            stack: DiffStack::new(),
             force_diff: false,
-        }
-    }
-}
-
-/// The stack instructions we use to diff and create new nodes.
-#[derive(Debug)]
-pub(crate) enum DiffInstruction<'a> {
-    Diff {
-        old: &'a VNode<'a>,
-        new: &'a VNode<'a>,
-    },
-
-    Create {
-        node: &'a VNode<'a>,
-    },
-
-    /// pushes the node elements onto the stack for use in mount
-    PrepareMove {
-        node: &'a VNode<'a>,
-    },
-
-    Mount {
-        and: MountType<'a>,
-    },
-
-    PopScope,
-    PopElement,
-}
-
-#[derive(Debug, Clone, Copy)]
-pub(crate) enum MountType<'a> {
-    Absorb,
-    Append,
-    Replace { old: &'a VNode<'a> },
-    InsertAfter { other_node: &'a VNode<'a> },
-    InsertBefore { other_node: &'a VNode<'a> },
-}
-
-pub(crate) struct DiffStack<'bump> {
-    pub(crate) instructions: Vec<DiffInstruction<'bump>>,
-    pub(crate) nodes_created_stack: SmallVec<[usize; 10]>,
-    pub(crate) scope_stack: SmallVec<[ScopeId; 5]>,
-    pub(crate) element_stack: SmallVec<[ElementId; 10]>,
-}
-
-impl<'bump> DiffStack<'bump> {
-    fn new() -> Self {
-        Self {
-            instructions: Vec::with_capacity(1000),
-            nodes_created_stack: smallvec![],
-            scope_stack: smallvec![],
             element_stack: smallvec![],
+            scope_stack: smallvec![],
         }
     }
 
-    fn pop(&mut self) -> Option<DiffInstruction<'bump>> {
-        self.instructions.pop()
-    }
-
-    fn pop_off_scope(&mut self) {
-        self.scope_stack.pop();
-    }
-
-    pub(crate) fn push(&mut self, instruction: DiffInstruction<'bump>) {
-        self.instructions.push(instruction)
-    }
-
-    fn create_children(&mut self, children: &'bump [VNode<'bump>], and: MountType<'bump>) {
-        self.nodes_created_stack.push(0);
-        self.instructions.push(DiffInstruction::Mount { and });
-
-        for child in children.iter().rev() {
-            self.instructions
-                .push(DiffInstruction::Create { node: child });
-        }
-    }
-
-    // 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);
-    }
-
-    pub(crate) fn create_node(&mut self, node: &'bump VNode<'bump>, and: MountType<'bump>) {
-        self.nodes_created_stack.push(0);
-        self.instructions.push(DiffInstruction::Mount { and });
-        self.instructions.push(DiffInstruction::Create { node });
-    }
-
-    fn add_child_count(&mut self, count: usize) {
-        *self.nodes_created_stack.last_mut().unwrap() += count;
-    }
-
-    fn pop_nodes_created(&mut self) -> usize {
-        self.nodes_created_stack.pop().unwrap()
-    }
-
-    fn current_scope(&self) -> Option<ScopeId> {
-        self.scope_stack.last().copied()
-    }
-
-    fn create_component(&mut self, idx: ScopeId, node: &'bump VNode<'bump>) {
-        // Push the new scope onto the stack
-        self.scope_stack.push(idx);
-
-        self.instructions.push(DiffInstruction::PopScope);
-
-        // Run the creation algorithm with this scope on the stack
-        // ?? I think we treat components as fragments??
-        self.instructions.push(DiffInstruction::Create { node });
-    }
-}
-
-impl<'bump> DiffState<'bump> {
-    /// Progress the diffing for this "fiber"
-    ///
-    /// This method implements a depth-first iterative tree traversal.
-    ///
-    /// We do depth-first to maintain high cache locality (nodes were originally generated recursively).
-    ///
-    /// Returns a `bool` indicating that the work completed properly.
-    pub fn work(&mut self, mut deadline_expired: impl FnMut() -> bool) -> bool {
-        while let Some(instruction) = self.stack.pop() {
-            match instruction {
-                DiffInstruction::Diff { old, new } => self.diff_node(old, new),
-                DiffInstruction::Create { node } => self.create_node(node),
-                DiffInstruction::Mount { and } => self.mount(and),
-                DiffInstruction::PrepareMove { node } => {
-                    let num_on_stack = self.push_all_nodes(node);
-                    self.stack.add_child_count(num_on_stack);
-                }
-                DiffInstruction::PopScope => self.stack.pop_off_scope(),
-                DiffInstruction::PopElement => {
-                    self.stack.element_stack.pop();
-                }
-            };
+    pub fn diff_scope(&mut self, scopeid: ScopeId) {
+        let (old, new) = (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid));
+        let scope = self.scopes.get_scope(scopeid).unwrap();
 
-            if deadline_expired() {
-                log::trace!("Deadline expired before we could finish!");
-                return false;
-            }
+        self.scope_stack.push(scopeid);
+        self.element_stack.push(scope.container);
+        {
+            self.diff_node(old, new);
         }
+        self.element_stack.pop();
+        self.scope_stack.pop();
 
-        true
+        self.mutations.mark_dirty_scope(scopeid);
     }
 
-    // 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::Placeholder(_) => {
-                self.mutations.push_root(node.mounted_id());
-                1
-            }
-
-            VNode::Fragment(_) | VNode::Component(_) => {
-                //
-                let mut added = 0;
-                for child in node.children() {
-                    added += self.push_all_nodes(child);
-                }
-                added
-            }
-
-            VNode::Element(el) => {
-                let mut num_on_stack = 0;
-                for child in el.children.iter() {
-                    num_on_stack += self.push_all_nodes(child);
-                }
-                self.mutations.push_root(el.id.get().unwrap());
-
-                num_on_stack + 1
+    pub fn diff_node(&mut self, old_node: &'b VNode<'b>, new_node: &'b VNode<'b>) {
+        use VNode::{Component, Element, Fragment, Placeholder, Text};
+        match (old_node, new_node) {
+            (Text(old), Text(new)) => {
+                self.diff_text_nodes(old, new, old_node, new_node);
             }
-        }
-    }
 
-    fn mount(&mut self, and: MountType<'bump>) {
-        let nodes_created = self.stack.pop_nodes_created();
-        match and {
-            // add the nodes from this virtual list to the parent
-            // used by fragments and components
-            MountType::Absorb => {
-                self.stack.add_child_count(nodes_created);
+            (Placeholder(old), Placeholder(new)) => {
+                self.diff_placeholder_nodes(old, new, old_node, new_node);
             }
 
-            MountType::Replace { old } => {
-                self.replace_node(old, nodes_created);
+            (Element(old), Element(new)) => {
+                self.diff_element_nodes(old, new, old_node, new_node);
             }
 
-            MountType::Append => {
-                self.mutations.edits.push(AppendChildren {
-                    many: nodes_created as u32,
-                });
+            (Component(old), Component(new)) => {
+                self.diff_component_nodes(old_node, new_node, *old, *new);
             }
 
-            MountType::InsertAfter { other_node } => {
-                let root = self.find_last_element(other_node).unwrap();
-                self.mutations.insert_after(root, nodes_created as u32);
+            (Fragment(old), Fragment(new)) => {
+                self.diff_fragment_nodes(old, new);
             }
 
-            MountType::InsertBefore { other_node } => {
-                let root = self.find_first_element_id(other_node).unwrap();
-                self.mutations.insert_before(root, nodes_created as u32);
-            }
+            (
+                Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_),
+                Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_),
+            ) => self.replace_node(old_node, new_node),
         }
     }
 
-    // =================================
-    //  Tools for creating new nodes
-    // =================================
-
-    fn create_node(&mut self, node: &'bump VNode<'bump>) {
+    pub fn create_node(&mut self, node: &'b VNode<'b>) -> usize {
         match node {
             VNode::Text(vtext) => self.create_text_node(vtext, node),
             VNode::Placeholder(anchor) => self.create_anchor_node(anchor, node),
@@ -345,24 +172,21 @@ impl<'bump> DiffState<'bump> {
         }
     }
 
-    fn create_text_node(&mut self, vtext: &'bump VText<'bump>, node: &'bump VNode<'bump>) {
+    fn create_text_node(&mut self, text: &'b VText<'b>, node: &'b VNode<'b>) -> usize {
         let real_id = self.scopes.reserve_node(node);
-
-        self.mutations.create_text_node(vtext.text, real_id);
-        vtext.id.set(Some(real_id));
-        self.stack.add_child_count(1);
+        text.id.set(Some(real_id));
+        self.mutations.create_text_node(text.text, real_id);
+        1
     }
 
-    fn create_anchor_node(&mut self, anchor: &'bump VPlaceholder, node: &'bump VNode<'bump>) {
+    fn create_anchor_node(&mut self, anchor: &'b VPlaceholder, node: &'b VNode<'b>) -> usize {
         let real_id = self.scopes.reserve_node(node);
-
-        self.mutations.create_placeholder(real_id);
         anchor.id.set(Some(real_id));
-
-        self.stack.add_child_count(1);
+        self.mutations.create_placeholder(real_id);
+        1
     }
 
-    fn create_element_node(&mut self, element: &'bump VElement<'bump>, node: &'bump VNode<'bump>) {
+    fn create_element_node(&mut self, element: &'b VElement<'b>, node: &'b VNode<'b>) -> usize {
         let VElement {
             tag: tag_name,
             listeners,
@@ -372,56 +196,44 @@ impl<'bump> DiffState<'bump> {
             id: dom_id,
             parent: parent_id,
             ..
-        } = element;
+        } = &element;
 
-        // set the parent ID for event bubbling
-        self.stack.instructions.push(DiffInstruction::PopElement);
+        parent_id.set(self.element_stack.last().copied());
 
-        let parent = self.stack.element_stack.last().unwrap();
-        parent_id.set(Some(*parent));
-
-        // set the id of the element
         let real_id = self.scopes.reserve_node(node);
-        self.stack.element_stack.push(real_id);
+
         dom_id.set(Some(real_id));
 
-        self.mutations.create_element(tag_name, *namespace, real_id);
+        self.element_stack.push(real_id);
+        {
+            self.mutations.create_element(tag_name, *namespace, real_id);
 
-        self.stack.add_child_count(1);
+            let cur_scope_id = self.current_scope();
 
-        if let Some(cur_scope_id) = self.stack.current_scope() {
-            for listener in *listeners {
+            for listener in listeners.iter() {
                 listener.mounted_node.set(Some(real_id));
                 self.mutations.new_event_listener(listener, cur_scope_id);
             }
-        } else {
-            log::warn!("create element called with no scope on the stack - this is an error for a live dom");
-        }
-
-        for attr in *attributes {
-            self.mutations.set_attribute(attr, real_id.as_u64());
-        }
 
-        // 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;
-        //     }
-        // }
+            for attr in attributes.iter() {
+                self.mutations.set_attribute(attr, real_id.as_u64());
+            }
 
-        if !children.is_empty() {
-            self.stack.create_children(children, MountType::Append);
+            if !children.is_empty() {
+                self.create_and_append_children(children);
+            }
         }
+        self.element_stack.pop();
+
+        1
     }
 
-    fn create_fragment_node(&mut self, frag: &'bump VFragment<'bump>) {
-        self.stack.create_children(frag.children, MountType::Absorb);
+    fn create_fragment_node(&mut self, frag: &'b VFragment<'b>) -> usize {
+        self.create_children(frag.children)
     }
 
-    fn create_component_node(&mut self, vcomponent: &'bump VComponent<'bump>) {
-        let parent_idx = self.stack.current_scope().unwrap();
+    fn create_component_node(&mut self, vcomponent: &'b VComponent<'b>) -> usize {
+        let parent_idx = self.current_scope();
 
         // the component might already exist - if it does, we need to reuse it
         // this makes figure out when to drop the component more complicated
@@ -430,116 +242,121 @@ impl<'bump> DiffState<'bump> {
             idx
         } else {
             // Insert a new scope into our component list
-            let props: Box<dyn AnyProps + 'bump> = vcomponent.props.borrow_mut().take().unwrap();
+            let props: Box<dyn AnyProps + 'b> = vcomponent.props.borrow_mut().take().unwrap();
             let props: Box<dyn AnyProps + 'static> = unsafe { std::mem::transmute(props) };
-            let new_idx = self.scopes.new_with_key(
+            self.scopes.new_with_key(
                 vcomponent.user_fc,
                 props,
                 Some(parent_idx),
-                self.stack.element_stack.last().copied().unwrap(),
+                self.element_stack.last().copied().unwrap(),
                 0,
-            );
-
-            new_idx
+            )
         };
 
         // Actually initialize the caller's slot with the right address
         vcomponent.scope.set(Some(new_idx));
 
-        match vcomponent.can_memoize {
-            true => {
-                // todo: implement promotion logic. save us from boxing props that we don't need
-            }
-            false => {
-                // track this component internally so we know the right drop order
-            }
-        }
+        log::trace!(
+            "created component \"{}\", id: {:?} parent {:?} orig: {:?}",
+            vcomponent.fn_name,
+            new_idx,
+            parent_idx,
+            vcomponent.originator
+        );
 
-        // Run the scope for one iteration to initialize it
-        self.scopes.run_scope(new_idx);
+        // if vcomponent.can_memoize {
+        //     // todo: implement promotion logic. save us from boxing props that we don't need
+        // } else {
+        //     // track this component internally so we know the right drop order
+        // }
 
-        // Take the node that was just generated from running the component
-        let nextnode = self.scopes.fin_head(new_idx);
-        self.stack.create_component(new_idx, nextnode);
+        self.enter_scope(new_idx);
 
-        // Finally, insert this scope as a seen node.
-        self.mutations.dirty_scopes.insert(new_idx);
-    }
+        let created = {
+            // Run the scope for one iteration to initialize it
+            self.scopes.run_scope(new_idx);
+            self.mutations.mark_dirty_scope(new_idx);
 
-    // =================================
-    //  Tools for diffing nodes
-    // =================================
+            // Take the node that was just generated from running the component
+            let nextnode = self.scopes.fin_head(new_idx);
+            self.create_node(nextnode)
+        };
 
-    pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {
-        use VNode::*;
-        match (old_node, new_node) {
-            // Check the most common cases first
-            // these are *actual* elements, not wrappers around lists
-            (Text(old), Text(new)) => {
-                if let Some(root) = old.id.get() {
-                    if old.text != new.text {
-                        self.mutations.set_text(new.text, root.as_u64());
-                    }
-                    self.scopes.update_node(new_node, root);
+        self.leave_scope();
 
-                    new.id.set(Some(root));
-                }
-            }
+        created
+    }
 
-            (Placeholder(old), Placeholder(new)) => {
-                if let Some(root) = old.id.get() {
-                    self.scopes.update_node(new_node, root);
-                    new.id.set(Some(root))
-                }
-            }
+    pub(crate) fn diff_text_nodes(
+        &mut self,
+        old: &'b VText<'b>,
+        new: &'b VText<'b>,
+        _old_node: &'b VNode<'b>,
+        new_node: &'b VNode<'b>,
+    ) {
+        if std::ptr::eq(old, new) {
+            return;
+        }
 
-            (Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node),
+        // if the node is comming back not assigned, that means it was borrowed but removed
+        let root = match old.id.get() {
+            Some(id) => id,
+            None => self.scopes.reserve_node(new_node),
+        };
 
-            // These two sets are pointers to nodes but are not actually nodes themselves
-            (Component(old), Component(new)) => {
-                self.diff_component_nodes(old_node, new_node, *old, *new)
-            }
+        if old.text != new.text {
+            self.mutations.set_text(new.text, root.as_u64());
+        }
 
-            (Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new),
+        self.scopes.update_node(new_node, root);
 
-            // The normal pathway still works, but generates slightly weird instructions
-            // This pathway ensures uses the ReplaceAll, not the InsertAfter and remove
-            (Placeholder(_), Fragment(new)) => {
-                self.stack
-                    .create_children(new.children, MountType::Replace { old: old_node });
-            }
+        new.id.set(Some(root));
+    }
 
-            // Anything else is just a basic replace and create
-            (
-                Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_),
-                Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_),
-            ) => self
-                .stack
-                .create_node(new_node, MountType::Replace { old: old_node }),
+    pub(crate) fn diff_placeholder_nodes(
+        &mut self,
+        old: &'b VPlaceholder,
+        new: &'b VPlaceholder,
+        _old_node: &'b VNode<'b>,
+        new_node: &'b VNode<'b>,
+    ) {
+        if std::ptr::eq(old, new) {
+            return;
         }
+
+        // if the node is comming back not assigned, that means it was borrowed but removed
+        let root = match old.id.get() {
+            Some(id) => id,
+            None => self.scopes.reserve_node(new_node),
+        };
+
+        self.scopes.update_node(new_node, root);
+        new.id.set(Some(root));
     }
 
     fn diff_element_nodes(
         &mut self,
-        old: &'bump VElement<'bump>,
-        new: &'bump VElement<'bump>,
-        old_node: &'bump VNode<'bump>,
-        new_node: &'bump VNode<'bump>,
+        old: &'b VElement<'b>,
+        new: &'b VElement<'b>,
+        old_node: &'b VNode<'b>,
+        new_node: &'b VNode<'b>,
     ) {
-        let root = old.id.get().unwrap();
+        if std::ptr::eq(old, new) {
+            return;
+        }
+
+        // if the node is comming back not assigned, that means it was borrowed but removed
+        let root = match old.id.get() {
+            Some(id) => id,
+            None => self.scopes.reserve_node(new_node),
+        };
 
         // If the element type is completely different, the element needs to be re-rendered completely
         // This is an optimization React makes due to how users structure their code
         //
         // This case is rather rare (typically only in non-keyed lists)
         if new.tag != old.tag || new.namespace != old.namespace {
-            // maybe make this an instruction?
-            // issue is that we need the "vnode" but this method only has the velement
-            self.stack.push_nodes_created(0);
-            self.stack.push(DiffInstruction::Mount {
-                and: MountType::Replace { old: old_node },
-            });
-            self.create_element_node(new, new_node);
+            self.replace_node(old_node, new_node);
             return;
         }
 
@@ -570,7 +387,7 @@ impl<'bump> DiffState<'bump> {
                 self.mutations.remove_attribute(attribute, root.as_u64());
             }
             for attribute in new.attributes {
-                self.mutations.set_attribute(attribute, root.as_u64())
+                self.mutations.set_attribute(attribute, root.as_u64());
             }
         }
 
@@ -582,181 +399,123 @@ impl<'bump> DiffState<'bump> {
         // We also need to make sure that all listeners are properly attached to the parent scope (fix_listener)
         //
         // TODO: take a more efficient path than this
-        if let Some(cur_scope_id) = self.stack.current_scope() {
-            if old.listeners.len() == new.listeners.len() {
-                for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
-                    if old_l.event != new_l.event {
-                        self.mutations
-                            .remove_event_listener(old_l.event, root.as_u64());
-                        self.mutations.new_event_listener(new_l, cur_scope_id);
-                    }
-                    new_l.mounted_node.set(old_l.mounted_node.get());
-                }
-            } else {
-                for listener in old.listeners {
+        let cur_scope_id = self.current_scope();
+
+        if old.listeners.len() == new.listeners.len() {
+            for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
+                if old_l.event != new_l.event {
                     self.mutations
-                        .remove_event_listener(listener.event, root.as_u64());
-                }
-                for listener in new.listeners {
-                    listener.mounted_node.set(Some(root));
-                    self.mutations.new_event_listener(listener, cur_scope_id);
+                        .remove_event_listener(old_l.event, root.as_u64());
+                    self.mutations.new_event_listener(new_l, cur_scope_id);
                 }
+                new_l.mounted_node.set(old_l.mounted_node.get());
             }
-        }
-
-        if old.children.is_empty() && !new.children.is_empty() {
-            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);
         } else {
-            self.stack.element_stack.push(root);
-            self.stack.instructions.push(DiffInstruction::PopElement);
-            self.diff_children(old.children, new.children);
+            for listener in old.listeners {
+                self.mutations
+                    .remove_event_listener(listener.event, root.as_u64());
+            }
+            for listener in new.listeners {
+                listener.mounted_node.set(Some(root));
+                self.mutations.new_event_listener(listener, cur_scope_id);
+            }
         }
 
-        // todo: this is for the "settext" optimization
-        // it works, but i'm not sure if it's the direction we want to take right away
-        // I haven't benchmarked the performance imporvemenet yet. Perhaps
-        // we can make it a config?
-
-        // 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);
-        //     }
-        // }
+        match (old.children.len(), new.children.len()) {
+            (0, 0) => {}
+            (0, _) => {
+                let created = self.create_children(new.children);
+                self.mutations.append_children(created as u32);
+            }
+            (_, _) => self.diff_children(old.children, new.children),
+        };
     }
 
     fn diff_component_nodes(
         &mut self,
-        old_node: &'bump VNode<'bump>,
-        new_node: &'bump VNode<'bump>,
-        old: &'bump VComponent<'bump>,
-        new: &'bump VComponent<'bump>,
+        old_node: &'b VNode<'b>,
+        new_node: &'b VNode<'b>,
+        old: &'b VComponent<'b>,
+        new: &'b VComponent<'b>,
     ) {
-        let scope_addr = old.scope.get().unwrap();
-        log::trace!("diff_component_nodes: {:?}", scope_addr);
+        let scope_addr = old
+            .scope
+            .get()
+            .expect("existing component nodes should have a scope");
 
         if std::ptr::eq(old, new) {
-            log::trace!("skipping component diff - component is the sames");
             return;
         }
 
         // Make sure we're dealing with the same component (by function pointer)
         if old.user_fc == new.user_fc {
-            self.stack.scope_stack.push(scope_addr);
-
-            // Make sure the new component vnode is referencing the right scope id
-            new.scope.set(Some(scope_addr));
-
-            // make sure the component's caller function is up to date
-            let scope = self
-                .scopes
-                .get_scope(scope_addr)
-                .unwrap_or_else(|| panic!("could not find {:?}", scope_addr));
-
-            // take the new props out regardless
-            // when memoizing, push to the existing scope if memoization happens
-            let new_props = new.props.borrow_mut().take().unwrap();
-
-            let should_run = {
-                if old.can_memoize {
-                    let props_are_the_same = unsafe {
-                        scope
-                            .props
-                            .borrow()
-                            .as_ref()
-                            .unwrap()
-                            .memoize(new_props.as_ref())
-                    };
-                    !props_are_the_same || self.force_diff
-                } else {
-                    true
-                }
-            };
-
-            if should_run {
-                let _old_props = scope
+            self.enter_scope(scope_addr);
+            {
+                // Make sure the new component vnode is referencing the right scope id
+                new.scope.set(Some(scope_addr));
+
+                // make sure the component's caller function is up to date
+                let scope = self
+                    .scopes
+                    .get_scope(scope_addr)
+                    .unwrap_or_else(|| panic!("could not find {:?}", scope_addr));
+
+                // take the new props out regardless
+                // when memoizing, push to the existing scope if memoization happens
+                let new_props = new
                     .props
-                    .replace(unsafe { std::mem::transmute(Some(new_props)) });
+                    .borrow_mut()
+                    .take()
+                    .expect("new component props should exist");
+
+                let should_diff = {
+                    if old.can_memoize {
+                        // safety: we trust the implementation of "memoize"
+                        let props_are_the_same = unsafe {
+                            let new_ref = new_props.as_ref();
+                            scope.props.borrow().as_ref().unwrap().memoize(new_ref)
+                        };
+                        !props_are_the_same || self.force_diff
+                    } else {
+                        true
+                    }
+                };
 
-                // this should auto drop the previous props
-                self.scopes.run_scope(scope_addr);
-                self.mutations.dirty_scopes.insert(scope_addr);
+                if should_diff {
+                    let _old_props = scope
+                        .props
+                        .replace(unsafe { std::mem::transmute(Some(new_props)) });
 
-                self.diff_node(
-                    self.scopes.wip_head(scope_addr),
-                    self.scopes.fin_head(scope_addr),
-                );
-            } else {
-                log::trace!("memoized");
-                // memoization has taken place
-                drop(new_props);
-            };
+                    // this should auto drop the previous props
+                    self.scopes.run_scope(scope_addr);
+                    self.mutations.mark_dirty_scope(scope_addr);
 
-            self.stack.scope_stack.pop();
+                    self.diff_node(
+                        self.scopes.wip_head(scope_addr),
+                        self.scopes.fin_head(scope_addr),
+                    );
+                } else {
+                    // memoization has taken place
+                    drop(new_props);
+                };
+            }
+            self.leave_scope();
         } else {
-            self.stack
-                .create_node(new_node, MountType::Replace { old: old_node });
+            self.replace_node(old_node, new_node);
         }
     }
 
-    fn diff_fragment_nodes(&mut self, old: &'bump VFragment<'bump>, new: &'bump VFragment<'bump>) {
+    fn diff_fragment_nodes(&mut self, old: &'b VFragment<'b>, new: &'b VFragment<'b>) {
+        if std::ptr::eq(old, new) {
+            return;
+        }
+
         // This is the case where options or direct vnodes might be used.
         // In this case, it's faster to just skip ahead to their diff
         if old.children.len() == 1 && new.children.len() == 1 {
-            self.diff_node(&old.children[0], &new.children[0]);
+            if !std::ptr::eq(old, new) {
+                self.diff_node(&old.children[0], &new.children[0]);
+            }
             return;
         }
 
@@ -766,10 +525,6 @@ impl<'bump> DiffState<'bump> {
         self.diff_children(old.children, new.children);
     }
 
-    // =============================================
-    //  Utilities for creating new diff instructions
-    // =============================================
-
     // Diff the given set of old and new children.
     //
     // The parent must be on top of the change list stack when this function is
@@ -785,11 +540,15 @@ impl<'bump> DiffState<'bump> {
     //
     // Fragment nodes cannot generate empty children lists, so we can assume that when a list is empty, it belongs only
     // to an element, and appending makes sense.
-    fn diff_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
+    fn diff_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
+        if std::ptr::eq(old, new) {
+            return;
+        }
+
         // Remember, fragments can never be empty (they always have a single child)
         match (old, new) {
             ([], []) => {}
-            ([], _) => self.stack.create_children(new, MountType::Append),
+            ([], _) => self.create_and_append_children(new),
             (_, []) => self.remove_nodes(old, true),
             _ => {
                 let new_is_keyed = new[0].key().is_some();
@@ -821,29 +580,21 @@ impl<'bump> DiffState<'bump> {
     //     [... parent]
     //
     // the change list stack is in the same state when this function returns.
-    fn diff_non_keyed_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
+    fn diff_non_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
+        use std::cmp::Ordering;
+
         // Handled these cases in `diff_children` before calling this function.
         debug_assert!(!new.is_empty());
         debug_assert!(!old.is_empty());
 
-        for (new, old) in new.iter().zip(old.iter()).rev() {
-            self.stack.push(DiffInstruction::Diff { new, old });
-        }
-
-        use std::cmp::Ordering;
         match old.len().cmp(&new.len()) {
             Ordering::Greater => self.remove_nodes(&old[new.len()..], true),
-            Ordering::Less => {
-                self.stack.create_children(
-                    &new[old.len()..],
-                    MountType::InsertAfter {
-                        other_node: old.last().unwrap(),
-                    },
-                );
-            }
-            Ordering::Equal => {
-                // nothing - they're the same size
-            }
+            Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()),
+            Ordering::Equal => {}
+        }
+
+        for (new, old) in new.iter().zip(old.iter()) {
+            self.diff_node(old, new);
         }
     }
 
@@ -863,10 +614,10 @@ impl<'bump> DiffState<'bump> {
     // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
     //
     // The stack is empty upon entry.
-    fn diff_keyed_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
+    fn diff_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
         if cfg!(debug_assertions) {
             let mut keys = fxhash::FxHashSet::default();
-            let mut assert_unique_keys = |children: &'bump [VNode<'bump>]| {
+            let mut assert_unique_keys = |children: &'b [VNode<'b>]| {
                 keys.clear();
                 for child in children {
                     let key = child.key();
@@ -907,6 +658,7 @@ impl<'bump> DiffState<'bump> {
             !((old_middle.len() == new_middle.len()) && old_middle.is_empty()),
             "keyed children must have the same number of children"
         );
+
         if new_middle.is_empty() {
             // remove the old elements
             self.remove_nodes(old_middle, true);
@@ -916,30 +668,15 @@ impl<'bump> DiffState<'bump> {
             if left_offset == 0 {
                 // insert at the beginning of the old list
                 let foothold = &old[old.len() - right_offset];
-                self.stack.create_children(
-                    new_middle,
-                    MountType::InsertBefore {
-                        other_node: foothold,
-                    },
-                );
+                self.create_and_insert_before(new_middle, foothold);
             } else if right_offset == 0 {
                 // insert at the end  the old list
                 let foothold = old.last().unwrap();
-                self.stack.create_children(
-                    new_middle,
-                    MountType::InsertAfter {
-                        other_node: foothold,
-                    },
-                );
+                self.create_and_insert_after(new_middle, foothold);
             } else {
                 // inserting in the middle
                 let foothold = &old[left_offset - 1];
-                self.stack.create_children(
-                    new_middle,
-                    MountType::InsertAfter {
-                        other_node: foothold,
-                    },
-                );
+                self.create_and_insert_after(new_middle, foothold);
             }
         } else {
             self.diff_keyed_middle(old_middle, new_middle);
@@ -953,9 +690,8 @@ impl<'bump> DiffState<'bump> {
     /// If there is no offset, then this function returns None and the diffing is complete.
     fn diff_keyed_ends(
         &mut self,
-
-        old: &'bump [VNode<'bump>],
-        new: &'bump [VNode<'bump>],
+        old: &'b [VNode<'b>],
+        new: &'b [VNode<'b>],
     ) -> Option<(usize, usize)> {
         let mut left_offset = 0;
 
@@ -964,19 +700,14 @@ impl<'bump> DiffState<'bump> {
             if old.key() != new.key() {
                 break;
             }
-            self.stack.push(DiffInstruction::Diff { old, new });
+            self.diff_node(old, new);
             left_offset += 1;
         }
 
         // If that was all of the old children, then create and append the remaining
         // new children and we're finished.
         if left_offset == old.len() {
-            self.stack.create_children(
-                &new[left_offset..],
-                MountType::InsertAfter {
-                    other_node: old.last().unwrap(),
-                },
-            );
+            self.create_and_insert_after(&new[left_offset..], old.last().unwrap());
             return None;
         }
 
@@ -1014,7 +745,8 @@ impl<'bump> DiffState<'bump> {
     // This function will load the appropriate nodes onto the stack and do diffing in place.
     //
     // Upon exit from this function, it will be restored to that same self.
-    fn diff_keyed_middle(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
+    #[allow(clippy::too_many_lines)]
+    fn diff_keyed_middle(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
         /*
         1. Map the old keys into a numerical ordering based on indices.
         2. Create a map of old key to its index
@@ -1039,8 +771,8 @@ impl<'bump> DiffState<'bump> {
 
         // 0. Debug sanity checks
         // Should have already diffed the shared-key prefixes and suffixes.
-        debug_assert_ne!(new.first().map(|n| n.key()), old.first().map(|o| o.key()));
-        debug_assert_ne!(new.last().map(|n| n.key()), old.last().map(|o| o.key()));
+        debug_assert_ne!(new.first().map(VNode::key), old.first().map(VNode::key));
+        debug_assert_ne!(new.last().map(VNode::key), old.last().map(VNode::key));
 
         // 1. Map the old keys into a numerical ordering based on indices.
         // 2. Create a map of old key to its index
@@ -1072,10 +804,12 @@ impl<'bump> DiffState<'bump> {
         if shared_keys.is_empty() {
             if let Some(first_old) = old.get(0) {
                 self.remove_nodes(&old[1..], true);
-                self.stack
-                    .create_children(new, MountType::Replace { old: first_old })
+                let nodes_created = self.create_children(new);
+                self.replace_inner(first_old, nodes_created);
             } else {
-                self.stack.create_children(new, MountType::Append {});
+                // I think this is wrong - why are we appending?
+                // only valid of the if there are no trailing elements
+                self.create_and_append_children(new);
             }
             return;
         }
@@ -1103,33 +837,31 @@ impl<'bump> DiffState<'bump> {
             lis_sequence.pop();
         }
 
-        let apply = |new_idx, new_node: &'bump VNode<'bump>, stack: &mut DiffStack<'bump>| {
-            let old_index = new_index_to_old_index[new_idx];
-            if old_index == u32::MAX as usize {
-                stack.create_node(new_node, MountType::Absorb);
-            } else {
-                // this function should never take LIS indices
-                stack.push(DiffInstruction::PrepareMove { node: new_node });
-                stack.push(DiffInstruction::Diff {
-                    new: new_node,
-                    old: &old[old_index],
-                });
-            }
-        };
+        for idx in &lis_sequence {
+            self.diff_node(&old[new_index_to_old_index[*idx]], &new[*idx]);
+        }
 
-        // add mount instruction for the last items not covered by the lis
-        let first_lis = *lis_sequence.first().unwrap();
-        if first_lis > 0 {
-            self.stack.push_nodes_created(0);
-            self.stack.push(DiffInstruction::Mount {
-                and: MountType::InsertBefore {
-                    other_node: &new[first_lis],
-                },
-            });
-
-            for (idx, new_node) in new[..first_lis].iter().enumerate().rev() {
-                apply(idx, new_node, &mut self.stack);
+        let mut nodes_created = 0;
+
+        // add mount instruction for the first items not covered by the lis
+        let last = *lis_sequence.last().unwrap();
+        if last < (new.len() - 1) {
+            for (idx, new_node) in new[(last + 1)..].iter().enumerate() {
+                let new_idx = idx + last + 1;
+                let old_index = new_index_to_old_index[new_idx];
+                if old_index == u32::MAX as usize {
+                    nodes_created += self.create_node(new_node);
+                } else {
+                    self.diff_node(&old[old_index], new_node);
+                    nodes_created += self.push_all_nodes(new_node);
+                }
             }
+
+            self.mutations.insert_after(
+                self.find_last_element(&new[last]).unwrap(),
+                nodes_created as u32,
+            );
+            nodes_created = 0;
         }
 
         // for each spacing, generate a mount instruction
@@ -1137,85 +869,53 @@ impl<'bump> DiffState<'bump> {
         let mut last = *lis_iter.next().unwrap();
         for next in lis_iter {
             if last - next > 1 {
-                self.stack.push_nodes_created(0);
-                self.stack.push(DiffInstruction::Mount {
-                    and: MountType::InsertBefore {
-                        other_node: &new[last],
-                    },
-                });
-                for (idx, new_node) in new[(next + 1)..last].iter().enumerate().rev() {
-                    apply(idx + next + 1, new_node, &mut self.stack);
+                for (idx, new_node) in new[(next + 1)..last].iter().enumerate() {
+                    let new_idx = idx + next + 1;
+                    let old_index = new_index_to_old_index[new_idx];
+                    if old_index == u32::MAX as usize {
+                        nodes_created += self.create_node(new_node);
+                    } else {
+                        self.diff_node(&old[old_index], new_node);
+                        nodes_created += self.push_all_nodes(new_node);
+                    }
                 }
+
+                self.mutations.insert_before(
+                    self.find_first_element(&new[last]).unwrap(),
+                    nodes_created as u32,
+                );
+
+                nodes_created = 0;
             }
             last = *next;
         }
 
-        // add mount instruction for the first items not covered by the lis
-        let last = *lis_sequence.last().unwrap();
-        if last < (new.len() - 1) {
-            self.stack.push_nodes_created(0);
-            self.stack.push(DiffInstruction::Mount {
-                and: MountType::InsertAfter {
-                    other_node: &new[last],
-                },
-            });
-            for (idx, new_node) in new[(last + 1)..].iter().enumerate().rev() {
-                apply(idx + last + 1, new_node, &mut self.stack);
+        // add mount instruction for the last items not covered by the lis
+        let first_lis = *lis_sequence.first().unwrap();
+        if first_lis > 0 {
+            for (idx, new_node) in new[..first_lis].iter().enumerate() {
+                let old_index = new_index_to_old_index[idx];
+                if old_index == u32::MAX as usize {
+                    nodes_created += self.create_node(new_node);
+                } else {
+                    self.diff_node(&old[old_index], new_node);
+                    nodes_created += self.push_all_nodes(new_node);
+                }
             }
-        }
 
-        for idx in lis_sequence.iter().rev() {
-            self.stack.push(DiffInstruction::Diff {
-                new: &new[*idx],
-                old: &old[new_index_to_old_index[*idx]],
-            });
+            self.mutations.insert_before(
+                self.find_first_element(&new[first_lis]).unwrap(),
+                nodes_created as u32,
+            );
         }
     }
 
-    // =====================
-    //  Utilities
-    // =====================
-
-    fn find_last_element(&mut self, vnode: &'bump VNode<'bump>) -> Option<ElementId> {
-        let mut search_node = Some(vnode);
-
-        loop {
-            match &search_node.take().unwrap() {
-                VNode::Text(t) => break t.id.get(),
-                VNode::Element(t) => break t.id.get(),
-                VNode::Placeholder(t) => break t.id.get(),
-                VNode::Fragment(frag) => {
-                    search_node = frag.children.last();
-                }
-                VNode::Component(el) => {
-                    let scope_id = el.scope.get().unwrap();
-                    search_node = Some(self.scopes.root_node(scope_id));
-                }
-            }
-        }
+    fn replace_node(&mut self, old: &'b VNode<'b>, new: &'b VNode<'b>) {
+        let nodes_created = self.create_node(new);
+        self.replace_inner(old, nodes_created);
     }
 
-    fn find_first_element_id(&mut self, vnode: &'bump VNode<'bump>) -> Option<ElementId> {
-        let mut search_node = Some(vnode);
-
-        loop {
-            match &search_node.take().unwrap() {
-                // the ones that have a direct id
-                VNode::Fragment(frag) => {
-                    search_node = Some(&frag.children[0]);
-                }
-                VNode::Component(el) => {
-                    let scope_id = el.scope.get().unwrap();
-                    search_node = Some(self.scopes.root_node(scope_id));
-                }
-                VNode::Text(t) => break t.id.get(),
-                VNode::Element(t) => break t.id.get(),
-                VNode::Placeholder(t) => break t.id.get(),
-            }
-        }
-    }
-
-    fn replace_node(&mut self, old: &'bump VNode<'bump>, nodes_created: usize) {
+    fn replace_inner(&mut self, old: &'b VNode<'b>, nodes_created: usize) {
         match old {
             VNode::Element(el) => {
                 let id = old
@@ -1224,6 +924,7 @@ impl<'bump> DiffState<'bump> {
 
                 self.mutations.replace_with(id, nodes_created as u32);
                 self.remove_nodes(el.children, false);
+                self.scopes.collect_garbage(id);
             }
 
             VNode::Text(_) | VNode::Placeholder(_) => {
@@ -1232,41 +933,51 @@ impl<'bump> DiffState<'bump> {
                     .unwrap_or_else(|| panic!("broke on {:?}", old));
 
                 self.mutations.replace_with(id, nodes_created as u32);
+                self.scopes.collect_garbage(id);
             }
 
             VNode::Fragment(f) => {
-                self.replace_node(&f.children[0], nodes_created);
+                self.replace_inner(&f.children[0], nodes_created);
                 self.remove_nodes(f.children.iter().skip(1), true);
             }
 
             VNode::Component(c) => {
-                let node = self.scopes.fin_head(c.scope.get().unwrap());
-                self.replace_node(node, nodes_created);
-
+                log::trace!("Replacing component {:?}", old);
                 let scope_id = c.scope.get().unwrap();
+                let node = self.scopes.fin_head(scope_id);
 
-                // we can only remove components if they are actively being diffed
-                if self.stack.scope_stack.contains(&c.originator) {
+                self.enter_scope(scope_id);
+                {
+                    self.replace_inner(node, nodes_created);
+
+                    log::trace!("Replacing component x2 {:?}", old);
+
+                    let scope = self.scopes.get_scope(scope_id).unwrap();
+                    c.scope.set(None);
+                    let props = scope.props.take().unwrap();
+                    c.props.borrow_mut().replace(props);
                     self.scopes.try_remove(scope_id).unwrap();
+
+                    // // we can only remove components if they are actively being diffed
+                    // if self.scope_stack.contains(&c.originator) {
+                    //     log::trace!("Removing component {:?}", old);
+
+                    //     self.scopes.try_remove(scope_id).unwrap();
+                    // }
                 }
+                self.leave_scope();
             }
         }
     }
 
-    /// schedules nodes for garbage collection and pushes "remove" to the mutation stack
-    /// remove can happen whenever
-    pub(crate) fn remove_nodes(
-        &mut self,
-        nodes: impl IntoIterator<Item = &'bump VNode<'bump>>,
-        gen_muts: bool,
-    ) {
-        // or cache the vec on the diff machine
+    pub fn remove_nodes(&mut self, nodes: impl IntoIterator<Item = &'b VNode<'b>>, gen_muts: bool) {
         for node in nodes {
             match node {
                 VNode::Text(t) => {
                     // this check exists because our null node will be removed but does not have an ID
                     if let Some(id) = t.id.get() {
                         self.scopes.collect_garbage(id);
+                        t.id.set(None);
 
                         if gen_muts {
                             self.mutations.remove(id.as_u64());
@@ -1276,6 +987,7 @@ impl<'bump> DiffState<'bump> {
                 VNode::Placeholder(a) => {
                     let id = a.id.get().unwrap();
                     self.scopes.collect_garbage(id);
+                    a.id.set(None);
 
                     if gen_muts {
                         self.mutations.remove(id.as_u64());
@@ -1288,6 +1000,9 @@ impl<'bump> DiffState<'bump> {
                         self.mutations.remove(id.as_u64());
                     }
 
+                    self.scopes.collect_garbage(id);
+                    e.id.set(None);
+
                     self.remove_nodes(e.children, false);
                 }
 
@@ -1296,16 +1011,124 @@ impl<'bump> DiffState<'bump> {
                 }
 
                 VNode::Component(c) => {
-                    let scope_id = c.scope.get().unwrap();
-                    let root = self.scopes.root_node(scope_id);
-                    self.remove_nodes(Some(root), gen_muts);
+                    self.enter_scope(c.scope.get().unwrap());
+                    {
+                        let scope_id = c.scope.get().unwrap();
+                        let root = self.scopes.root_node(scope_id);
+                        self.remove_nodes([root], gen_muts);
 
-                    // we can only remove this node if the originator is actively
-                    if self.stack.scope_stack.contains(&c.originator) {
+                        let scope = self.scopes.get_scope(scope_id).unwrap();
+                        c.scope.set(None);
+
+                        let props = scope.props.take().unwrap();
+                        c.props.borrow_mut().replace(props);
                         self.scopes.try_remove(scope_id).unwrap();
                     }
+                    self.leave_scope();
                 }
             }
         }
     }
+
+    fn create_children(&mut self, nodes: &'b [VNode<'b>]) -> usize {
+        let mut created = 0;
+        for node in nodes {
+            created += self.create_node(node);
+        }
+        created
+    }
+
+    fn create_and_append_children(&mut self, nodes: &'b [VNode<'b>]) {
+        let created = self.create_children(nodes);
+        self.mutations.append_children(created as u32);
+    }
+
+    fn create_and_insert_after(&mut self, nodes: &'b [VNode<'b>], after: &'b VNode<'b>) {
+        let created = self.create_children(nodes);
+        let last = self.find_last_element(after).unwrap();
+        self.mutations.insert_after(last, created as u32);
+    }
+
+    fn create_and_insert_before(&mut self, nodes: &'b [VNode<'b>], before: &'b VNode<'b>) {
+        let created = self.create_children(nodes);
+        let first = self.find_first_element(before).unwrap();
+        self.mutations.insert_before(first, created as u32);
+    }
+
+    fn current_scope(&self) -> ScopeId {
+        self.scope_stack.last().copied().expect("no current scope")
+    }
+
+    fn enter_scope(&mut self, scope: ScopeId) {
+        self.scope_stack.push(scope);
+    }
+
+    fn leave_scope(&mut self) {
+        self.scope_stack.pop();
+    }
+
+    fn find_last_element(&self, vnode: &'b VNode<'b>) -> Option<ElementId> {
+        let mut search_node = Some(vnode);
+        loop {
+            match &search_node.take().unwrap() {
+                VNode::Text(t) => break t.id.get(),
+                VNode::Element(t) => break t.id.get(),
+                VNode::Placeholder(t) => break t.id.get(),
+                VNode::Fragment(frag) => search_node = frag.children.last(),
+                VNode::Component(el) => {
+                    let scope_id = el.scope.get().unwrap();
+                    search_node = Some(self.scopes.root_node(scope_id));
+                }
+            }
+        }
+    }
+
+    fn find_first_element(&self, vnode: &'b VNode<'b>) -> Option<ElementId> {
+        let mut search_node = Some(vnode);
+        loop {
+            match &search_node.take().expect("search node to have an ID") {
+                VNode::Text(t) => break t.id.get(),
+                VNode::Element(t) => break t.id.get(),
+                VNode::Placeholder(t) => break t.id.get(),
+                VNode::Fragment(frag) => search_node = Some(&frag.children[0]),
+                VNode::Component(el) => {
+                    let scope = el.scope.get().expect("element to have a scope assigned");
+                    search_node = Some(self.scopes.root_node(scope));
+                }
+            }
+        }
+    }
+
+    // recursively push all the nodes of a tree onto the stack and return how many are there
+    fn push_all_nodes(&mut self, node: &'b VNode<'b>) -> usize {
+        match node {
+            VNode::Text(_) | VNode::Placeholder(_) => {
+                self.mutations.push_root(node.mounted_id());
+                1
+            }
+
+            VNode::Fragment(frag) => {
+                let mut added = 0;
+                for child in frag.children {
+                    added += self.push_all_nodes(child);
+                }
+                added
+            }
+
+            VNode::Component(c) => {
+                let scope_id = c.scope.get().unwrap();
+                let root = self.scopes.root_node(scope_id);
+                self.push_all_nodes(root)
+            }
+
+            VNode::Element(el) => {
+                let mut num_on_stack = 0;
+                for child in el.children.iter() {
+                    num_on_stack += self.push_all_nodes(child);
+                }
+                self.mutations.push_root(el.id.get().unwrap());
+                num_on_stack + 1
+            }
+        }
+    }
 }

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

@@ -12,7 +12,6 @@ pub(crate) mod util;
 pub(crate) mod virtual_dom;
 
 pub(crate) mod innerlude {
-    pub(crate) use crate::diff::*;
     pub use crate::events::*;
     pub use crate::lazynodes::*;
     pub use crate::mutations::*;

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

@@ -143,6 +143,10 @@ impl<'a> Mutations<'a> {
         self.edits.push(InsertBefore { n, root });
     }
 
+    pub(crate) fn append_children(&mut self, n: u32) {
+        self.edits.push(AppendChildren { many: n });
+    }
+
     // Remove Nodes from the dom
     pub(crate) fn remove(&mut self, id: u64) {
         self.edits.push(Remove { root: id });
@@ -217,6 +221,10 @@ impl<'a> Mutations<'a> {
         let name = attribute.name;
         self.edits.push(RemoveAttribute { name, root });
     }
+
+    pub(crate) fn mark_dirty_scope(&mut self, scope: ScopeId) {
+        self.dirty_scopes.insert(scope);
+    }
 }
 
 // refs are only assigned once

+ 24 - 13
packages/core/src/nodes.rs

@@ -4,7 +4,7 @@
 //! cheap and *very* fast to construct - building a full tree should be quick.
 
 use crate::{
-    innerlude::{Element, Properties, Scope, ScopeId, ScopeState},
+    innerlude::{ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState},
     lazynodes::LazyNodes,
     AnyEvent, Component,
 };
@@ -147,13 +147,6 @@ impl<'src> VNode<'src> {
         }
     }
 
-    pub(crate) fn children(&self) -> &[VNode<'src>] {
-        match &self {
-            VNode::Fragment(f) => f.children,
-            _ => &[],
-        }
-    }
-
     // Create an "owned" version of the vnode.
     pub fn decouple(&self) -> VNode<'src> {
         match *self {
@@ -175,13 +168,21 @@ impl Debug for VNode<'_> {
                 .field("key", &el.key)
                 .field("attrs", &el.attributes)
                 .field("children", &el.children)
+                .field("id", &el.id)
                 .finish(),
             VNode::Text(t) => write!(s, "VNode::VText {{ text: {} }}", t.text),
-            VNode::Placeholder(_) => write!(s, "VNode::VPlaceholder"),
+            VNode::Placeholder(t) => write!(s, "VNode::VPlaceholder {{ id: {:?} }}", t.id),
             VNode::Fragment(frag) => {
                 write!(s, "VNode::VFragment {{ children: {:?} }}", frag.children)
             }
-            VNode::Component(comp) => write!(s, "VNode::VComponent {{ fc: {:?}}}", comp.user_fc),
+            VNode::Component(comp) => s
+                .debug_struct("VNode::VComponent")
+                .field("name", &comp.fn_name)
+                .field("fnptr", &comp.user_fc)
+                .field("key", &comp.key)
+                .field("scope", &comp.scope)
+                .field("originator", &comp.originator)
+                .finish(),
         }
     }
 }
@@ -351,11 +352,18 @@ type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>;
 /// }
 ///
 /// ```
-#[derive(Default)]
 pub struct EventHandler<'bump, T = ()> {
     pub callback: RefCell<Option<ExternalListenerCallback<'bump, T>>>,
 }
 
+impl<'a, T> Default for EventHandler<'a, T> {
+    fn default() -> Self {
+        Self {
+            callback: RefCell::new(None),
+        }
+    }
+}
+
 impl<T> EventHandler<'_, T> {
     /// Call this event handler with the appropriate event type
     pub fn call(&self, event: T) {
@@ -377,7 +385,8 @@ pub struct VComponent<'src> {
     pub originator: ScopeId,
     pub scope: Cell<Option<ScopeId>>,
     pub can_memoize: bool,
-    pub user_fc: *const (),
+    pub user_fc: ComponentPtr,
+    pub fn_name: &'static str,
     pub props: RefCell<Option<Box<dyn AnyProps + 'src>>>,
 }
 
@@ -542,6 +551,7 @@ impl<'a> NodeFactory<'a> {
         component: fn(Scope<'a, P>) -> Element,
         props: P,
         key: Option<Arguments>,
+        fn_name: &'static str,
     ) -> VNode<'a>
     where
         P: Properties + 'a,
@@ -550,8 +560,9 @@ impl<'a> NodeFactory<'a> {
             key: key.map(|f| self.raw_text(f).0),
             scope: Default::default(),
             can_memoize: P::IS_STATIC,
-            user_fc: component as *const (),
+            user_fc: component as ComponentPtr,
             originator: self.scope.scope_id(),
+            fn_name,
             props: RefCell::new(Some(Box::new(VComponentProps {
                 // local_props: RefCell::new(Some(props)),
                 // heap_props: RefCell::new(None),

+ 57 - 66
packages/core/src/scopes.rs

@@ -13,7 +13,9 @@ use std::{
     rc::Rc,
 };
 
-pub(crate) type FcSlot = *const ();
+/// for traceability, we use the raw fn pointer to identify the function
+/// we also get the component name, but that's not necessarily unique in the app
+pub(crate) type ComponentPtr = *mut std::os::raw::c_void;
 
 pub(crate) struct Heuristic {
     hook_arena_size: usize,
@@ -28,7 +30,7 @@ pub(crate) struct ScopeArena {
     pub scope_gen: Cell<usize>,
     pub bump: Bump,
     pub scopes: RefCell<FxHashMap<ScopeId, *mut ScopeState>>,
-    pub heuristics: RefCell<FxHashMap<FcSlot, Heuristic>>,
+    pub heuristics: RefCell<FxHashMap<ComponentPtr, Heuristic>>,
     pub free_scopes: RefCell<Vec<*mut ScopeState>>,
     pub nodes: RefCell<Slab<*const VNode<'static>>>,
     pub tasks: Rc<TaskQueue>,
@@ -82,7 +84,7 @@ impl ScopeArena {
 
     pub(crate) fn new_with_key(
         &self,
-        fc_ptr: *const (),
+        fc_ptr: ComponentPtr,
         vcomp: Box<dyn AnyProps>,
         parent_scope: Option<ScopeId>,
         container: ElementId,
@@ -94,7 +96,7 @@ impl ScopeArena {
 
         // Get the height of the scope
         let height = parent_scope
-            .map(|id| self.get_scope(id).map(|scope| scope.height))
+            .map(|id| self.get_scope(id).map(|scope| scope.height + 1))
             .flatten()
             .unwrap_or_default();
 
@@ -110,31 +112,62 @@ impl ScopeArena {
         if let Some(old_scope) = self.free_scopes.borrow_mut().pop() {
             // reuse the old scope
             let scope = unsafe { &mut *old_scope };
-            scope.props.get_mut().replace(vcomp);
+
+            scope.container = container;
+            scope.our_arena_idx = new_scope_id;
             scope.parent_scope = parent_scope;
             scope.height = height;
+            scope.fnptr = fc_ptr;
+            scope.props.get_mut().replace(vcomp);
             scope.subtree.set(subtree);
-            scope.our_arena_idx = new_scope_id;
-            scope.container = container;
+            scope.frames[0].reset();
+            scope.frames[1].reset();
+            scope.shared_contexts.get_mut().clear();
+            scope.items.get_mut().listeners.clear();
+            scope.items.get_mut().borrowed_props.clear();
+            scope.hook_idx.set(0);
+            scope.hook_vals.get_mut().clear();
+
             let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope);
             debug_assert!(any_item.is_none());
         } else {
             // else create a new scope
+            let (node_capacity, hook_capacity) = self
+                .heuristics
+                .borrow()
+                .get(&fc_ptr)
+                .map(|h| (h.node_arena_size, h.hook_arena_size))
+                .unwrap_or_default();
+
             self.scopes.borrow_mut().insert(
                 new_scope_id,
-                self.bump.alloc(ScopeState::new(
-                    height,
+                self.bump.alloc(ScopeState {
                     container,
-                    new_scope_id,
+                    our_arena_idx: new_scope_id,
                     parent_scope,
-                    vcomp,
-                    self.tasks.clone(),
-                    self.heuristics
-                        .borrow()
-                        .get(&fc_ptr)
-                        .map(|h| (h.node_arena_size, h.hook_arena_size))
-                        .unwrap_or_default(),
-                )),
+                    height,
+                    fnptr: fc_ptr,
+                    props: RefCell::new(Some(vcomp)),
+                    frames: [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)],
+
+                    // todo: subtrees
+                    subtree: Cell::new(0),
+                    is_subtree_root: Cell::new(false),
+
+                    generation: 0.into(),
+
+                    tasks: self.tasks.clone(),
+                    shared_contexts: Default::default(),
+
+                    items: RefCell::new(SelfReferentialItems {
+                        listeners: Default::default(),
+                        borrowed_props: Default::default(),
+                    }),
+
+                    hook_arena: Bump::new(),
+                    hook_vals: RefCell::new(Vec::with_capacity(hook_capacity)),
+                    hook_idx: Default::default(),
+                }),
             );
         }
 
@@ -189,8 +222,6 @@ impl ScopeArena {
     /// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will
     /// be dropped.
     pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) {
-        log::trace!("Ensuring drop safety for scope {:?}", scope_id);
-
         if let Some(scope) = self.get_scope(scope_id) {
             let mut items = scope.items.borrow_mut();
 
@@ -201,7 +232,6 @@ impl ScopeArena {
                 if let Some(scope_id) = comp.scope.get() {
                     self.ensure_drop_safety(scope_id);
                 }
-
                 drop(comp.props.take());
             });
 
@@ -217,18 +247,18 @@ impl ScopeArena {
         // Cycle to the next frame and then reset it
         // This breaks any latent references, invalidating every pointer referencing into it.
         // Remove all the outdated listeners
-        log::trace!("Running scope {:?}", id);
         self.ensure_drop_safety(id);
 
         // todo: we *know* that this is aliased by the contents of the scope itself
         let scope = unsafe { &mut *self.get_scope_raw(id).expect("could not find scope") };
 
+        log::trace!("running scope {:?} symbol: {:?}", id, scope.fnptr);
+
         // 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
         scope.hook_idx.set(0);
 
-        // book keeping to ensure safety around the borrowed data
         {
             // Safety:
             // - We've dropped all references to the wip bump frame with "ensure_drop_safety"
@@ -241,8 +271,6 @@ impl ScopeArena {
             debug_assert!(items.borrowed_props.is_empty());
         }
 
-        // safety: this is definitely not dropped
-
         /*
         If the component returns None, then we fill in a placeholder node. This will wipe what was there.
         An alternate approach is to leave the Real Dom the same, but that can lead to safety issues and a lot more checks.
@@ -284,14 +312,13 @@ impl ScopeArena {
 
         while let Some(id) = cur_el.take() {
             if let Some(el) = nodes.get(id.0) {
-                log::trace!("Found valid receiver element");
-
                 let real_el = unsafe { &**el };
+                log::debug!("looking for listener on {:?}", real_el);
+
                 if let VNode::Element(real_el) = real_el {
                     for listener in real_el.listeners.borrow().iter() {
                         if listener.event == event.name {
-                            log::trace!("Found valid receiver event");
-
+                            log::debug!("calling listener {:?}", listener.event);
                             if state.canceled.get() {
                                 // stop bubbling if canceled
                                 break;
@@ -421,6 +448,7 @@ pub struct ScopeState {
     pub(crate) container: ElementId,
     pub(crate) our_arena_idx: ScopeId,
     pub(crate) height: u32,
+    pub(crate) fnptr: ComponentPtr,
 
     // todo: subtrees
     pub(crate) is_subtree_root: Cell<bool>,
@@ -449,43 +477,6 @@ pub struct SelfReferentialItems<'a> {
 
 // Public methods exposed to libraries and components
 impl ScopeState {
-    fn new(
-        height: u32,
-        container: ElementId,
-        our_arena_idx: ScopeId,
-        parent_scope: Option<*mut ScopeState>,
-        vcomp: Box<dyn AnyProps>,
-        tasks: Rc<TaskQueue>,
-        (node_capacity, hook_capacity): (usize, usize),
-    ) -> Self {
-        ScopeState {
-            container,
-            our_arena_idx,
-            parent_scope,
-            height,
-            props: RefCell::new(Some(vcomp)),
-            frames: [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)],
-
-            // todo: subtrees
-            subtree: Cell::new(0),
-            is_subtree_root: Cell::new(false),
-
-            generation: 0.into(),
-
-            tasks,
-            shared_contexts: Default::default(),
-
-            items: RefCell::new(SelfReferentialItems {
-                listeners: Default::default(),
-                borrowed_props: Default::default(),
-            }),
-
-            hook_arena: Bump::new(),
-            hook_vals: RefCell::new(Vec::with_capacity(hook_capacity)),
-            hook_idx: Default::default(),
-        }
-    }
-
     /// Get the subtree ID that this scope belongs to.
     ///
     /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route

+ 55 - 48
packages/core/src/virtual_dom.rs

@@ -2,6 +2,7 @@
 //!
 //! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
 
+use crate::diff::DiffState;
 use crate::innerlude::*;
 use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
 use futures_util::{future::poll_fn, StreamExt};
@@ -212,7 +213,7 @@ impl VirtualDom {
         let scopes = ScopeArena::new(channel.0.clone());
 
         scopes.new_with_key(
-            root as *const _,
+            root as ComponentPtr,
             Box::new(VComponentProps {
                 props: root_props,
                 memo: |_a, _b| unreachable!("memo on root will neve be run"),
@@ -448,6 +449,7 @@ impl VirtualDom {
     ///     apply_mutations(mutations);
     /// }
     /// ```
+    #[allow(unused)]
     pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec<Mutations> {
         let mut committed_mutations = vec![];
 
@@ -467,35 +469,40 @@ impl VirtualDom {
                 h1.cmp(&h2).reverse()
             });
 
+            log::debug!("dirty_scopes: {:?}", self.dirty_scopes);
+
             if let Some(scopeid) = self.dirty_scopes.pop() {
                 if !ran_scopes.contains(&scopeid) {
                     ran_scopes.insert(scopeid);
 
                     self.scopes.run_scope(scopeid);
 
-                    let (old, new) = (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid));
-                    diff_state.stack.push(DiffInstruction::Diff { new, old });
-                    diff_state.stack.scope_stack.push(scopeid);
+                    diff_state.diff_scope(scopeid);
 
-                    let scope = scopes.get_scope(scopeid).unwrap();
-                    diff_state.stack.element_stack.push(scope.container);
-                }
-            }
+                    let DiffState { mutations, .. } = diff_state;
 
-            if diff_state.work(&mut deadline) {
-                let DiffState { mutations, .. } = diff_state;
+                    log::debug!("succesffuly resolved scopes {:?}", mutations.dirty_scopes);
+                    for scope in &mutations.dirty_scopes {
+                        self.dirty_scopes.remove(scope);
+                    }
 
-                for scope in &mutations.dirty_scopes {
-                    self.dirty_scopes.remove(scope);
+                    committed_mutations.push(mutations);
+
+                    // todo: pause the diff machine
+                    // if diff_state.work(&mut deadline) {
+                    //     let DiffState { mutations, .. } = diff_state;
+                    //     for scope in &mutations.dirty_scopes {
+                    //         self.dirty_scopes.remove(scope);
+                    //     }
+                    //     committed_mutations.push(mutations);
+                    // } else {
+                    //     // leave the work in an incomplete state
+                    //     //
+                    //     // todo: we should store the edits and re-apply them later
+                    //     // for now, we just dump the work completely (threadsafe)
+                    //     return committed_mutations;
+                    // }
                 }
-
-                committed_mutations.push(mutations);
-            } else {
-                // leave the work in an incomplete state
-                //
-                // todo: we should store the edits and re-apply them later
-                // for now, we just dump the work completely (threadsafe)
-                return committed_mutations;
             }
         }
 
@@ -524,13 +531,15 @@ impl VirtualDom {
         let mut diff_state = DiffState::new(&self.scopes);
 
         self.scopes.run_scope(scope_id);
-        diff_state
-            .stack
-            .create_node(self.scopes.fin_head(scope_id), MountType::Append);
 
-        diff_state.stack.element_stack.push(ElementId(0));
-        diff_state.stack.scope_stack.push(scope_id);
-        diff_state.work(|| false);
+        diff_state.element_stack.push(ElementId(0));
+        diff_state.scope_stack.push(scope_id);
+
+        let node = self.scopes.fin_head(scope_id);
+        let created = diff_state.create_node(node);
+
+        diff_state.mutations.append_children(created as u32);
+
         self.dirty_scopes.clear();
         assert!(self.dirty_scopes.is_empty());
 
@@ -577,12 +586,11 @@ impl VirtualDom {
         );
 
         diff_machine.force_diff = true;
-        diff_machine.stack.push(DiffInstruction::Diff { old, new });
-        diff_machine.stack.scope_stack.push(scope_id);
-
+        diff_machine.scope_stack.push(scope_id);
         let scope = diff_machine.scopes.get_scope(scope_id).unwrap();
-        diff_machine.stack.element_stack.push(scope.container);
-        diff_machine.work(|| false);
+        diff_machine.element_stack.push(scope.container);
+
+        diff_machine.diff_node(old, new);
 
         diff_machine.mutations
     }
@@ -621,10 +629,10 @@ impl VirtualDom {
     /// ```
     pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
         let mut machine = DiffState::new(&self.scopes);
-        machine.stack.push(DiffInstruction::Diff { new, old });
-        machine.stack.element_stack.push(ElementId(0));
-        machine.stack.scope_stack.push(ScopeId(0));
-        machine.work(|| false);
+        machine.element_stack.push(ElementId(0));
+        machine.scope_stack.push(ScopeId(0));
+        machine.diff_node(old, new);
+
         machine.mutations
     }
 
@@ -643,11 +651,11 @@ impl VirtualDom {
     /// ```
     pub fn create_vnodes<'a>(&'a self, nodes: LazyNodes<'a, '_>) -> Mutations<'a> {
         let mut machine = DiffState::new(&self.scopes);
-        machine.stack.element_stack.push(ElementId(0));
-        machine
-            .stack
-            .create_node(self.render_vnodes(nodes), MountType::Append);
-        machine.work(|| false);
+        machine.scope_stack.push(ScopeId(0));
+        machine.element_stack.push(ElementId(0));
+        let node = self.render_vnodes(nodes);
+        let created = machine.create_node(node);
+        machine.mutations.append_children(created as u32);
         machine.mutations
     }
 
@@ -672,16 +680,15 @@ impl VirtualDom {
         let (old, new) = (self.render_vnodes(left), self.render_vnodes(right));
 
         let mut create = DiffState::new(&self.scopes);
-        create.stack.scope_stack.push(ScopeId(0));
-        create.stack.element_stack.push(ElementId(0));
-        create.stack.create_node(old, MountType::Append);
-        create.work(|| false);
+        create.scope_stack.push(ScopeId(0));
+        create.element_stack.push(ElementId(0));
+        let created = create.create_node(old);
+        create.mutations.append_children(created as u32);
 
         let mut edit = DiffState::new(&self.scopes);
-        edit.stack.scope_stack.push(ScopeId(0));
-        edit.stack.element_stack.push(ElementId(0));
-        edit.stack.push(DiffInstruction::Diff { old, new });
-        edit.work(|| false);
+        edit.scope_stack.push(ScopeId(0));
+        edit.element_stack.push(ElementId(0));
+        edit.diff_node(old, new);
 
         (create.mutations, edit.mutations)
     }

+ 1 - 0
packages/core/tests/.rustfmt.toml

@@ -0,0 +1 @@
+struct_lit_width = 80

+ 28 - 112
packages/core/tests/create_dom.rs

@@ -36,18 +36,9 @@ fn test_original_diff() {
     assert_eq!(
         mutations.edits,
         [
-            CreateElement {
-                root: 1,
-                tag: "div"
-            },
-            CreateElement {
-                root: 2,
-                tag: "div"
-            },
-            CreateTextNode {
-                root: 3,
-                text: "Hello, world!"
-            },
+            CreateElement { root: 1, tag: "div" },
+            CreateElement { root: 2, tag: "div" },
+            CreateTextNode { root: 3, text: "Hello, world!" },
             AppendChildren { many: 1 },
             AppendChildren { many: 1 },
             AppendChildren { many: 1 },
@@ -81,34 +72,13 @@ fn create() {
     assert_eq!(
         mutations.edits,
         [
-            CreateElement {
-                root: 1,
-                tag: "div"
-            },
-            CreateElement {
-                root: 2,
-                tag: "div"
-            },
-            CreateTextNode {
-                root: 3,
-                text: "Hello, world!"
-            },
-            CreateElement {
-                root: 4,
-                tag: "div"
-            },
-            CreateElement {
-                root: 5,
-                tag: "div"
-            },
-            CreateTextNode {
-                root: 6,
-                text: "hello"
-            },
-            CreateTextNode {
-                root: 7,
-                text: "world"
-            },
+            CreateElement { root: 1, tag: "div" },
+            CreateElement { root: 2, tag: "div" },
+            CreateTextNode { root: 3, text: "Hello, world!" },
+            CreateElement { root: 4, tag: "div" },
+            CreateElement { root: 5, tag: "div" },
+            CreateTextNode { root: 6, text: "hello" },
+            CreateTextNode { root: 7, text: "world" },
             AppendChildren { many: 2 },
             AppendChildren { many: 1 },
             AppendChildren { many: 2 },
@@ -135,32 +105,14 @@ fn create_list() {
     assert_eq!(
         mutations.edits,
         [
-            CreateElement {
-                root: 1,
-                tag: "div"
-            },
-            CreateTextNode {
-                root: 2,
-                text: "hello"
-            },
+            CreateElement { root: 1, tag: "div" },
+            CreateTextNode { root: 2, text: "hello" },
             AppendChildren { many: 1 },
-            CreateElement {
-                root: 3,
-                tag: "div"
-            },
-            CreateTextNode {
-                root: 4,
-                text: "hello"
-            },
+            CreateElement { root: 3, tag: "div" },
+            CreateTextNode { root: 4, text: "hello" },
             AppendChildren { many: 1 },
-            CreateElement {
-                root: 5,
-                tag: "div"
-            },
-            CreateTextNode {
-                root: 6,
-                text: "hello"
-            },
+            CreateElement { root: 5, tag: "div" },
+            CreateTextNode { root: 6, text: "hello" },
             AppendChildren { many: 1 },
             AppendChildren { many: 3 },
         ]
@@ -185,22 +137,10 @@ fn create_simple() {
     assert_eq!(
         mutations.edits,
         [
-            CreateElement {
-                root: 1,
-                tag: "div"
-            },
-            CreateElement {
-                root: 2,
-                tag: "div"
-            },
-            CreateElement {
-                root: 3,
-                tag: "div"
-            },
-            CreateElement {
-                root: 4,
-                tag: "div"
-            },
+            CreateElement { root: 1, tag: "div" },
+            CreateElement { root: 2, tag: "div" },
+            CreateElement { root: 3, tag: "div" },
+            CreateElement { root: 4, tag: "div" },
             AppendChildren { many: 4 },
         ]
     );
@@ -235,36 +175,18 @@ fn create_components() {
         mutations.edits,
         [
             CreateElement { root: 1, tag: "h1" },
-            CreateElement {
-                root: 2,
-                tag: "div"
-            },
-            CreateTextNode {
-                root: 3,
-                text: "abc1"
-            },
+            CreateElement { root: 2, tag: "div" },
+            CreateTextNode { root: 3, text: "abc1" },
             AppendChildren { many: 1 },
             CreateElement { root: 4, tag: "p" },
             CreateElement { root: 5, tag: "h1" },
-            CreateElement {
-                root: 6,
-                tag: "div"
-            },
-            CreateTextNode {
-                root: 7,
-                text: "abc2"
-            },
+            CreateElement { root: 6, tag: "div" },
+            CreateTextNode { root: 7, text: "abc2" },
             AppendChildren { many: 1 },
             CreateElement { root: 8, tag: "p" },
             CreateElement { root: 9, tag: "h1" },
-            CreateElement {
-                root: 10,
-                tag: "div"
-            },
-            CreateTextNode {
-                root: 11,
-                text: "abc3"
-            },
+            CreateElement { root: 10, tag: "div" },
+            CreateTextNode { root: 11, text: "abc3" },
             AppendChildren { many: 1 },
             CreateElement { root: 12, tag: "p" },
             AppendChildren { many: 9 },
@@ -285,14 +207,8 @@ fn anchors() {
     assert_eq!(
         mutations.edits,
         [
-            CreateElement {
-                root: 1,
-                tag: "div"
-            },
-            CreateTextNode {
-                root: 2,
-                text: "hello"
-            },
+            CreateElement { root: 1, tag: "div" },
+            CreateTextNode { root: 2, text: "hello" },
             AppendChildren { many: 1 },
             CreatePlaceholder { root: 3 },
             AppendChildren { many: 2 },

+ 60 - 196
packages/core/tests/diffing.rs

@@ -1,4 +1,5 @@
 #![allow(unused, non_upper_case_globals)]
+
 //! Diffing Tests
 //!
 //! These tests only verify that the diffing algorithm works properly for single components.
@@ -31,26 +32,14 @@ fn html_and_rsx_generate_the_same_output() {
     assert_eq!(
         create.edits,
         [
-            CreateElement {
-                root: 1,
-                tag: "div"
-            },
-            CreateTextNode {
-                root: 2,
-                text: "Hello world"
-            },
+            CreateElement { root: 1, tag: "div" },
+            CreateTextNode { root: 2, text: "Hello world" },
             AppendChildren { many: 1 },
             AppendChildren { many: 1 },
         ]
     );
 
-    assert_eq!(
-        change.edits,
-        [SetText {
-            text: "Goodbye world",
-            root: 2
-        },]
-    );
+    assert_eq!(change.edits, [SetText { text: "Goodbye world", root: 2 },]);
 }
 
 /// Should result in 3 elements on the stack
@@ -67,32 +56,14 @@ fn fragments_create_properly() {
     assert_eq!(
         create.edits,
         [
-            CreateElement {
-                root: 1,
-                tag: "div"
-            },
-            CreateTextNode {
-                root: 2,
-                text: "Hello a"
-            },
+            CreateElement { root: 1, tag: "div" },
+            CreateTextNode { root: 2, text: "Hello a" },
             AppendChildren { many: 1 },
-            CreateElement {
-                root: 3,
-                tag: "div"
-            },
-            CreateTextNode {
-                root: 4,
-                text: "Hello b"
-            },
+            CreateElement { root: 3, tag: "div" },
+            CreateTextNode { root: 4, text: "Hello b" },
             AppendChildren { many: 1 },
-            CreateElement {
-                root: 5,
-                tag: "div"
-            },
-            CreateTextNode {
-                root: 6,
-                text: "Hello c"
-            },
+            CreateElement { root: 5, tag: "div" },
+            CreateTextNode { root: 6, text: "Hello c" },
             AppendChildren { many: 1 },
             AppendChildren { many: 3 },
         ]
@@ -116,10 +87,7 @@ fn empty_fragments_create_anchors() {
     assert_eq!(
         change.edits,
         [
-            CreateElement {
-                root: 2,
-                tag: "div"
-            },
+            CreateElement { root: 2, tag: "div" },
             ReplaceWith { m: 1, root: 1 }
         ]
     );
@@ -138,29 +106,15 @@ fn empty_fragments_create_many_anchors() {
         create.edits,
         [CreatePlaceholder { root: 1 }, AppendChildren { many: 1 }]
     );
+
     assert_eq!(
         change.edits,
         [
-            CreateElement {
-                root: 2,
-                tag: "div"
-            },
-            CreateElement {
-                root: 3,
-                tag: "div"
-            },
-            CreateElement {
-                root: 4,
-                tag: "div"
-            },
-            CreateElement {
-                root: 5,
-                tag: "div"
-            },
-            CreateElement {
-                root: 6,
-                tag: "div"
-            },
+            CreateElement { root: 2, tag: "div" },
+            CreateElement { root: 3, tag: "div" },
+            CreateElement { root: 4, tag: "div" },
+            CreateElement { root: 5, tag: "div" },
+            CreateElement { root: 6, tag: "div" },
             ReplaceWith { m: 5, root: 1 }
         ]
     );
@@ -188,32 +142,14 @@ fn empty_fragments_create_anchors_with_many_children() {
     assert_eq!(
         change.edits,
         [
-            CreateElement {
-                tag: "div",
-                root: 2,
-            },
-            CreateTextNode {
-                text: "hello: 0",
-                root: 3
-            },
+            CreateElement { tag: "div", root: 2 },
+            CreateTextNode { text: "hello: 0", root: 3 },
             AppendChildren { many: 1 },
-            CreateElement {
-                tag: "div",
-                root: 4,
-            },
-            CreateTextNode {
-                text: "hello: 1",
-                root: 5
-            },
+            CreateElement { tag: "div", root: 4 },
+            CreateTextNode { text: "hello: 1", root: 5 },
             AppendChildren { many: 1 },
-            CreateElement {
-                tag: "div",
-                root: 6,
-            },
-            CreateTextNode {
-                text: "hello: 2",
-                root: 7
-            },
+            CreateElement { tag: "div", root: 6 },
+            CreateTextNode { text: "hello: 2", root: 7 },
             AppendChildren { many: 1 },
             ReplaceWith { root: 1, m: 3 }
         ]
@@ -236,23 +172,11 @@ fn many_items_become_fragment() {
     assert_eq!(
         create.edits,
         [
-            CreateElement {
-                root: 1,
-                tag: "div"
-            },
-            CreateTextNode {
-                text: "hello",
-                root: 2
-            },
+            CreateElement { root: 1, tag: "div" },
+            CreateTextNode { text: "hello", root: 2 },
             AppendChildren { many: 1 },
-            CreateElement {
-                root: 3,
-                tag: "div"
-            },
-            CreateTextNode {
-                text: "hello",
-                root: 4
-            },
+            CreateElement { root: 3, tag: "div" },
+            CreateTextNode { text: "hello", root: 4 },
             AppendChildren { many: 1 },
             AppendChildren { many: 2 },
         ]
@@ -315,7 +239,7 @@ fn two_fragments_with_differrent_elements_are_differet() {
             // replace the divs with new h1s
             CreateElement { tag: "h1", root: 7 },
             ReplaceWith { root: 1, m: 1 },
-            CreateElement { tag: "h1", root: 8 },
+            CreateElement { tag: "h1", root: 1 }, // notice how 1 gets re-used
             ReplaceWith { root: 2, m: 1 },
         ]
     );
@@ -339,39 +263,27 @@ fn two_fragments_with_differrent_elements_are_differet_shorter() {
     assert_eq!(
         create.edits,
         [
-            CreateElement {
-                root: 1,
-                tag: "div"
-            },
-            CreateElement {
-                root: 2,
-                tag: "div"
-            },
-            CreateElement {
-                root: 3,
-                tag: "div"
-            },
-            CreateElement {
-                root: 4,
-                tag: "div"
-            },
-            CreateElement {
-                root: 5,
-                tag: "div"
-            },
+            CreateElement { root: 1, tag: "div" },
+            CreateElement { root: 2, tag: "div" },
+            CreateElement { root: 3, tag: "div" },
+            CreateElement { root: 4, tag: "div" },
+            CreateElement { root: 5, tag: "div" },
             CreateElement { root: 6, tag: "p" },
             AppendChildren { many: 6 },
         ]
     );
+
+    // note: key reuse is always the last node that got used
+    // slab maintains a linked list, essentially
     assert_eq!(
         change.edits,
         [
             Remove { root: 3 },
             Remove { root: 4 },
             Remove { root: 5 },
-            CreateElement { root: 7, tag: "h1" },
-            ReplaceWith { root: 1, m: 1 },
-            CreateElement { root: 8, tag: "h1" },
+            CreateElement { root: 5, tag: "h1" }, // 3 gets reused
+            ReplaceWith { root: 1, m: 1 },        // 1 gets deleted
+            CreateElement { root: 1, tag: "h1" }, // 1 gets reused
             ReplaceWith { root: 2, m: 1 },
         ]
     );
@@ -395,14 +307,8 @@ fn two_fragments_with_same_elements_are_differet() {
     assert_eq!(
         create.edits,
         [
-            CreateElement {
-                root: 1,
-                tag: "div"
-            },
-            CreateElement {
-                root: 2,
-                tag: "div"
-            },
+            CreateElement { root: 1, tag: "div" },
+            CreateElement { root: 2, tag: "div" },
             CreateElement { root: 3, tag: "p" },
             AppendChildren { many: 3 },
         ]
@@ -410,18 +316,9 @@ fn two_fragments_with_same_elements_are_differet() {
     assert_eq!(
         change.edits,
         [
-            CreateElement {
-                root: 4,
-                tag: "div"
-            },
-            CreateElement {
-                root: 5,
-                tag: "div"
-            },
-            CreateElement {
-                root: 6,
-                tag: "div"
-            },
+            CreateElement { root: 4, tag: "div" },
+            CreateElement { root: 5, tag: "div" },
+            CreateElement { root: 6, tag: "div" },
             InsertAfter { root: 2, n: 3 },
         ]
     );
@@ -628,14 +525,8 @@ fn keyed_diffing_additions() {
     assert_eq!(
         change.edits,
         [
-            CreateElement {
-                root: 6,
-                tag: "div"
-            },
-            CreateElement {
-                root: 7,
-                tag: "div"
-            },
+            CreateElement { root: 6, tag: "div" },
+            CreateElement { root: 7, tag: "div" },
             InsertAfter { n: 2, root: 5 }
         ]
     );
@@ -663,14 +554,8 @@ fn keyed_diffing_additions_and_moves_on_ends() {
         change.edits,
         [
             // create 11, 12
-            CreateElement {
-                tag: "div",
-                root: 5
-            },
-            CreateElement {
-                tag: "div",
-                root: 6
-            },
+            CreateElement { tag: "div", root: 5 },
+            CreateElement { tag: "div", root: 6 },
             InsertAfter { root: 3, n: 2 },
             // move 7 to the front
             PushRoot { root: 4 },
@@ -684,13 +569,13 @@ fn keyed_diffing_additions_and_moves_in_middle() {
     let dom = new_dom();
 
     let left = rsx!({
-        [/**/ 4, 5, 6, 7 /**/].iter().map(|f| {
+        [/**/ 1, 2, 3, 4 /**/].iter().map(|f| {
             rsx! { div { key: "{f}"  }}
         })
     });
 
     let right = rsx!({
-        [/**/ 7, 4, 13, 17, 5, 11, 12, 6 /**/].iter().map(|f| {
+        [/**/ 4, 1, 7, 8, 2, 5, 6, 3 /**/].iter().map(|f| {
             rsx! { div { key: "{f}"  }}
         })
     });
@@ -701,26 +586,14 @@ fn keyed_diffing_additions_and_moves_in_middle() {
     assert_eq!(
         change.edits,
         [
-            // create 13, 17
-            CreateElement {
-                tag: "div",
-                root: 5
-            },
-            CreateElement {
-                tag: "div",
-                root: 6
-            },
-            InsertBefore { root: 2, n: 2 },
-            // create 11, 12
-            CreateElement {
-                tag: "div",
-                root: 7
-            },
-            CreateElement {
-                tag: "div",
-                root: 8
-            },
+            // create 5, 6
+            CreateElement { tag: "div", root: 5 },
+            CreateElement { tag: "div", root: 6 },
             InsertBefore { root: 3, n: 2 },
+            // create 7, 8
+            CreateElement { tag: "div", root: 7 },
+            CreateElement { tag: "div", root: 8 },
+            InsertBefore { root: 2, n: 2 },
             // move 7
             PushRoot { root: 4 },
             InsertBefore { root: 1, n: 1 }
@@ -756,16 +629,10 @@ fn controlled_keyed_diffing_out_of_order() {
             // remove 7
 
             // create 9 and insert before 6
-            CreateElement {
-                root: 5,
-                tag: "div"
-            },
+            CreateElement { root: 5, tag: "div" },
             InsertBefore { n: 1, root: 3 },
             // create 0 and insert before 5
-            CreateElement {
-                root: 6,
-                tag: "div"
-            },
+            CreateElement { root: 6, tag: "div" },
             InsertBefore { n: 1, root: 2 },
         ]
     );
@@ -792,10 +659,7 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
     assert_eq!(
         changes.edits,
         [
-            CreateElement {
-                root: 6,
-                tag: "div"
-            },
+            CreateElement { root: 6, tag: "div" },
             InsertBefore { n: 1, root: 3 },
             PushRoot { root: 4 },
             InsertBefore { n: 1, root: 1 },

+ 4 - 16
packages/core/tests/earlyabort.rs

@@ -42,14 +42,8 @@ fn test_early_abort() {
     assert_eq!(
         edits.edits,
         [
-            CreateElement {
-                tag: "div",
-                root: 1,
-            },
-            CreateTextNode {
-                text: "Hello, world!",
-                root: 2,
-            },
+            CreateElement { tag: "div", root: 1 },
+            CreateTextNode { text: "Hello, world!", root: 2 },
             AppendChildren { many: 1 },
             AppendChildren { many: 1 },
         ]
@@ -65,14 +59,8 @@ fn test_early_abort() {
     assert_eq!(
         edits.edits,
         [
-            CreateElement {
-                tag: "div",
-                root: 2,
-            },
-            CreateTextNode {
-                text: "Hello, world!",
-                root: 4,
-            },
+            CreateElement { tag: "div", root: 1 }, // keys get reused
+            CreateTextNode { text: "Hello, world!", root: 2 }, // keys get reused
             AppendChildren { many: 1 },
             ReplaceWith { root: 3, m: 1 },
         ]

+ 38 - 81
packages/core/tests/lifecycle.rs

@@ -27,12 +27,7 @@ fn manual_diffing() {
     };
 
     let value = Arc::new(Mutex::new("Hello"));
-    let mut dom = VirtualDom::new_with_props(
-        App,
-        AppProps {
-            value: value.clone(),
-        },
-    );
+    let mut dom = VirtualDom::new_with_props(App, AppProps { value: value.clone() });
 
     let _ = dom.rebuild();
 
@@ -45,7 +40,7 @@ fn manual_diffing() {
 
 #[test]
 fn events_generate() {
-    static App: Component = |cx| {
+    fn app(cx: Scope) -> Element {
         let count = cx.use_hook(|_| 0);
 
         let inner = match *count {
@@ -66,7 +61,7 @@ fn events_generate() {
         cx.render(inner)
     };
 
-    let mut dom = VirtualDom::new(App);
+    let mut dom = VirtualDom::new(app);
     let mut channel = dom.get_scheduler_channel();
     assert!(dom.has_work());
 
@@ -74,28 +69,12 @@ fn events_generate() {
     assert_eq!(
         edits.edits,
         [
-            CreateElement {
-                tag: "div",
-                root: 1,
-            },
-            NewEventListener {
-                event_name: "click",
-                scope: ScopeId(0),
-                root: 1,
-            },
-            CreateElement {
-                tag: "div",
-                root: 2,
-            },
-            CreateTextNode {
-                text: "nested",
-                root: 3,
-            },
+            CreateElement { tag: "div", root: 1 },
+            NewEventListener { event_name: "click", scope: ScopeId(0), root: 1 },
+            CreateElement { tag: "div", root: 2 },
+            CreateTextNode { text: "nested", root: 3 },
             AppendChildren { many: 1 },
-            CreateTextNode {
-                text: "Click me!",
-                root: 4,
-            },
+            CreateTextNode { text: "Click me!", root: 4 },
             AppendChildren { many: 2 },
             AppendChildren { many: 1 },
         ]
@@ -104,7 +83,7 @@ fn events_generate() {
 
 #[test]
 fn components_generate() {
-    static App: Component = |cx| {
+    fn app(cx: Scope) -> Element {
         let render_phase = cx.use_hook(|_| 0);
         *render_phase += 1;
 
@@ -121,106 +100,84 @@ fn components_generate() {
         })
     };
 
-    static Child: Component = |cx| {
+    fn Child(cx: Scope) -> Element {
+        log::debug!("Running child");
         cx.render(rsx! {
             h1 {}
         })
-    };
+    }
 
-    let mut dom = VirtualDom::new(App);
+    let mut dom = VirtualDom::new(app);
     let edits = dom.rebuild();
     assert_eq!(
         edits.edits,
         [
-            CreateTextNode {
-                text: "Text0",
-                root: 1,
-            },
+            CreateTextNode { text: "Text0", root: 1 },
             AppendChildren { many: 1 },
         ]
     );
 
-    let edits = dom.hard_diff(ScopeId(0));
     assert_eq!(
-        edits.edits,
+        dom.hard_diff(ScopeId(0)).edits,
         [
-            CreateElement {
-                tag: "div",
-                root: 2,
-            },
+            CreateElement { tag: "div", root: 2 },
             ReplaceWith { root: 1, m: 1 },
         ]
     );
 
-    let edits = dom.hard_diff(ScopeId(0));
     assert_eq!(
-        edits.edits,
+        dom.hard_diff(ScopeId(0)).edits,
         [
-            CreateTextNode {
-                text: "Text2",
-                root: 3,
-            },
+            CreateTextNode { text: "Text2", root: 1 },
             ReplaceWith { root: 2, m: 1 },
         ]
     );
 
-    let edits = dom.hard_diff(ScopeId(0));
+    // child {}
     assert_eq!(
-        edits.edits,
+        dom.hard_diff(ScopeId(0)).edits,
         [
-            CreateElement { tag: "h1", root: 4 },
-            ReplaceWith { root: 3, m: 1 },
+            CreateElement { tag: "h1", root: 2 },
+            ReplaceWith { root: 1, m: 1 },
         ]
     );
 
-    let edits = dom.hard_diff(ScopeId(0));
+    // placeholder
     assert_eq!(
-        edits.edits,
-        [CreatePlaceholder { root: 5 }, ReplaceWith { root: 4, m: 1 },]
+        dom.hard_diff(ScopeId(0)).edits,
+        [CreatePlaceholder { root: 1 }, ReplaceWith { root: 2, m: 1 },]
     );
 
-    let edits = dom.hard_diff(ScopeId(0));
     assert_eq!(
-        edits.edits,
+        dom.hard_diff(ScopeId(0)).edits,
         [
-            CreateTextNode {
-                text: "text 3",
-                root: 6,
-            },
-            ReplaceWith { root: 5, m: 1 },
+            CreateTextNode { text: "text 3", root: 2 },
+            ReplaceWith { root: 1, m: 1 },
         ]
     );
 
-    let edits = dom.hard_diff(ScopeId(0));
     assert_eq!(
-        edits.edits,
+        dom.hard_diff(ScopeId(0)).edits,
         [
-            CreateTextNode {
-                text: "text 0",
-                root: 7,
-            },
-            CreateTextNode {
-                text: "text 1",
-                root: 8,
-            },
-            ReplaceWith { root: 6, m: 2 },
+            CreateTextNode { text: "text 0", root: 1 },
+            CreateTextNode { text: "text 1", root: 3 },
+            ReplaceWith { root: 2, m: 2 },
         ]
     );
 
-    let edits = dom.hard_diff(ScopeId(0));
     assert_eq!(
-        edits.edits,
+        dom.hard_diff(ScopeId(0)).edits,
         [
-            CreateElement { tag: "h1", root: 9 },
-            ReplaceWith { root: 7, m: 1 },
-            Remove { root: 8 },
+            CreateElement { tag: "h1", root: 2 },
+            ReplaceWith { root: 1, m: 1 },
+            Remove { root: 3 },
         ]
     );
 }
 
 #[test]
 fn component_swap() {
-    static App: Component = |cx| {
+    fn app(cx: Scope) -> Element {
         let render_phase = cx.use_hook(|_| 0);
         *render_phase += 1;
 
@@ -296,7 +253,7 @@ fn component_swap() {
         })
     };
 
-    let mut dom = VirtualDom::new(App);
+    let mut dom = VirtualDom::new(app);
     let edits = dom.rebuild();
     dbg!(&edits);
 

+ 121 - 6
packages/core/tests/miri_stress.rs

@@ -152,12 +152,7 @@ fn free_works_on_root_props() {
         }
     }
 
-    let mut dom = new_dom(
-        app,
-        Custom {
-            val: String::from("asd"),
-        },
-    );
+    let mut dom = new_dom(app, Custom { val: String::from("asd") });
     dom.rebuild();
 }
 
@@ -313,3 +308,123 @@ fn leak_thru_children() {
     dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
     dom.work_with_deadline(|| false);
 }
+
+#[test]
+fn test_pass_thru() {
+    #[inline_props]
+    fn Router<'a>(cx: Scope, children: Element<'a>) -> Element {
+        cx.render(rsx! {
+            div {
+                &cx.props.children
+            }
+        })
+    }
+
+    #[inline_props]
+    fn NavContainer<'a>(cx: Scope, children: Element<'a>) -> Element {
+        cx.render(rsx! {
+            header {
+                nav {
+                    &cx.props.children
+                }
+            }
+        })
+    }
+
+    fn NavMenu(cx: Scope) -> Element {
+        rsx!(cx,
+            NavBrand {}
+            div {
+                NavStart {}
+                NavEnd {}
+            }
+        )
+    }
+
+    fn NavBrand(cx: Scope) -> Element {
+        rsx!(cx, div {})
+    }
+
+    fn NavStart(cx: Scope) -> Element {
+        rsx!(cx, div {})
+    }
+
+    fn NavEnd(cx: Scope) -> Element {
+        rsx!(cx, div {})
+    }
+
+    #[inline_props]
+    fn MainContainer<'a>(
+        cx: Scope,
+        nav: Element<'a>,
+        body: Element<'a>,
+        footer: Element<'a>,
+    ) -> Element {
+        cx.render(rsx! {
+            div {
+                class: "columns is-mobile",
+                div {
+                    class: "column is-full",
+                    &cx.props.nav,
+                    &cx.props.body,
+                    &cx.props.footer,
+                }
+            }
+        })
+    }
+
+    fn app(cx: Scope) -> Element {
+        let nav = cx.render(rsx! {
+            NavContainer {
+                NavMenu {}
+            }
+        });
+        let body = cx.render(rsx! {
+            div {}
+        });
+        let footer = cx.render(rsx! {
+            div {}
+        });
+
+        cx.render(rsx! {
+            MainContainer {
+                nav: nav,
+                body: body,
+                footer: footer,
+            }
+        })
+    }
+
+    let mut dom = new_dom(app, ());
+    let _ = dom.rebuild();
+
+    for x in 0..40 {
+        dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+        dom.work_with_deadline(|| false);
+        dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+        dom.work_with_deadline(|| false);
+        dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+        dom.work_with_deadline(|| false);
+
+        dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
+        dom.work_with_deadline(|| false);
+        dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
+        dom.work_with_deadline(|| false);
+        dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
+        dom.work_with_deadline(|| false);
+
+        dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
+        dom.work_with_deadline(|| false);
+        dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
+        dom.work_with_deadline(|| false);
+        dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
+        dom.work_with_deadline(|| false);
+
+        dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
+        dom.work_with_deadline(|| false);
+        dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
+        dom.work_with_deadline(|| false);
+        dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
+        dom.work_with_deadline(|| false);
+    }
+}

+ 108 - 0
packages/core/tests/passthru.rs

@@ -0,0 +1,108 @@
+#![allow(unused, non_upper_case_globals)]
+
+//! Diffing Tests
+//!
+//! These tests only verify that the diffing algorithm works properly for single components.
+//!
+//! It does not validated that component lifecycles work properly. This is done in another test file.
+
+use dioxus::{prelude::*, DomEdit, ScopeId};
+use dioxus_core as dioxus;
+use dioxus_core_macro::*;
+use dioxus_html as dioxus_elements;
+
+mod test_logging;
+
+fn new_dom() -> VirtualDom {
+    const IS_LOGGING_ENABLED: bool = false;
+    test_logging::set_up_logging(IS_LOGGING_ENABLED);
+    VirtualDom::new(|cx| rsx!(cx, "hi"))
+}
+
+use DomEdit::*;
+
+/// Should push the text node onto the stack and modify it
+#[test]
+fn nested_passthru_creates() {
+    fn app(cx: Scope) -> Element {
+        cx.render(rsx! {
+            Child {
+                Child {
+                    Child {
+                        div {
+                            "hi"
+                        }
+                    }
+                }
+            }
+        })
+    };
+
+    #[inline_props]
+    fn Child<'a>(cx: Scope, children: Element<'a>) -> Element {
+        cx.render(rsx! {
+                children
+        })
+    };
+
+    let mut dom = VirtualDom::new(app);
+    let mut channel = dom.get_scheduler_channel();
+    assert!(dom.has_work());
+
+    let edits = dom.rebuild();
+    assert_eq!(
+        edits.edits,
+        [
+            CreateElement { tag: "div", root: 1 },
+            CreateTextNode { text: "hi", root: 2 },
+            AppendChildren { many: 1 },
+            AppendChildren { many: 1 },
+        ]
+    )
+}
+
+/// Should push the text node onto the stack and modify it
+#[test]
+fn nested_passthru_creates_add() {
+    fn app(cx: Scope) -> Element {
+        cx.render(rsx! {
+            Child {
+                "1"
+                Child {
+                    "2"
+                    Child {
+                        "3"
+                        div {
+                            "hi"
+                        }
+                    }
+                }
+            }
+        })
+    };
+
+    #[inline_props]
+    fn Child<'a>(cx: Scope, children: Element<'a>) -> Element {
+        cx.render(rsx! {
+                children
+        })
+    };
+
+    let mut dom = VirtualDom::new(app);
+    let mut channel = dom.get_scheduler_channel();
+    assert!(dom.has_work());
+
+    let edits = dom.rebuild();
+    assert_eq!(
+        edits.edits,
+        [
+            CreateTextNode { text: "1", root: 1 },
+            CreateTextNode { text: "2", root: 2 },
+            CreateTextNode { text: "3", root: 3 },
+            CreateElement { tag: "div", root: 4 },
+            CreateTextNode { text: "hi", root: 5 },
+            AppendChildren { many: 1 },
+            AppendChildren { many: 4 },
+        ]
+    )
+}

+ 96 - 5
packages/core/tests/sharedstate.rs

@@ -1,6 +1,6 @@
 #![allow(unused, non_upper_case_globals)]
 
-use dioxus::{prelude::*, DomEdit, Mutations};
+use dioxus::{prelude::*, DomEdit, Mutations, SchedulerMsg, ScopeId};
 use dioxus_core as dioxus;
 use dioxus_core_macro::*;
 use dioxus_html as dioxus_elements;
@@ -29,11 +29,102 @@ fn shared_state_test() {
     assert_eq!(
         edits,
         [
-            CreateTextNode {
-                root: 1,
-                text: "Hello, world!"
-            },
+            CreateTextNode { root: 1, text: "Hello, world!" },
             AppendChildren { many: 1 },
         ]
     );
 }
+
+#[test]
+fn swap_test() {
+    struct MySharedState(&'static str);
+
+    fn app(cx: Scope) -> Element {
+        let val = cx.use_hook(|_| 0);
+        *val += 1;
+
+        cx.provide_context(MySharedState("world!"));
+
+        let child = match *val % 2 {
+            0 => rsx!(
+                Child1 {
+                    Child1 { }
+                    Child2 { }
+                }
+            ),
+            _ => rsx!(
+                Child2 {
+                    Child2 { }
+                    Child2 { }
+                }
+            ),
+        };
+
+        cx.render(rsx!(
+            Router {
+                div { child }
+            }
+        ))
+    }
+
+    #[inline_props]
+    fn Router<'a>(cx: Scope, children: Element<'a>) -> Element<'a> {
+        cx.render(rsx!(div { children }))
+    }
+
+    #[inline_props]
+    fn Child1<'a>(cx: Scope, children: Element<'a>) -> Element {
+        let shared = cx.consume_context::<MySharedState>().unwrap();
+        println!("Child1: {}", shared.0);
+        cx.render(rsx! {
+            div {
+                "{shared.0}",
+                children
+            }
+        })
+    }
+
+    #[inline_props]
+    fn Child2<'a>(cx: Scope, children: Element<'a>) -> Element {
+        let shared = cx.consume_context::<MySharedState>().unwrap();
+        println!("Child2: {}", shared.0);
+        cx.render(rsx! {
+            h1 {
+                "{shared.0}",
+                children
+            }
+        })
+    }
+
+    let mut dom = VirtualDom::new(app);
+    let Mutations { edits, .. } = dom.rebuild();
+
+    dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+    dom.work_with_deadline(|| false);
+    dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+    dom.work_with_deadline(|| false);
+    dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+    dom.work_with_deadline(|| false);
+    dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+    dom.work_with_deadline(|| false);
+    dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+    dom.work_with_deadline(|| false);
+    dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+    dom.work_with_deadline(|| false);
+
+    // dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
+    // dom.work_with_deadline(|| false);
+
+    // dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
+    // dom.work_with_deadline(|| false);
+
+    // dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
+    // dom.work_with_deadline(|| false);
+
+    // dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+    // dom.work_with_deadline(|| false);
+    // dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+    // dom.work_with_deadline(|| false);
+    // dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+    // dom.work_with_deadline(|| false);
+}

+ 2 - 8
packages/core/tests/vdom_rebuild.rs

@@ -68,15 +68,9 @@ fn conditional_rendering() {
         mutations.edits,
         [
             CreateElement { root: 1, tag: "h1" },
-            CreateTextNode {
-                root: 2,
-                text: "hello"
-            },
+            CreateTextNode { root: 2, text: "hello" },
             AppendChildren { many: 1 },
-            CreateElement {
-                root: 3,
-                tag: "span"
-            },
+            CreateElement { root: 3, tag: "span" },
             CreateTextNode { root: 4, text: "a" },
             AppendChildren { many: 1 },
             CreatePlaceholder { root: 5 },

+ 88 - 4
packages/desktop/CHANGELOG.md

@@ -5,6 +5,76 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## Unreleased
+
+### Documentation
+
+ - <csr-id-a239d2ba6ac7f1f3d09de16c022ce8ca52cf0f63/> fix web doc example and use &mut for builders everywhere
+
+### New Features
+
+ - <csr-id-430cde7068d308f2783e33d278fd0c0efa659c1b/> default asset server
+ - <csr-id-95e93ed0bcf6c69990f4cf3c6448b2bf5da96c36/> remove dioxus id on non-event elements
+ - <csr-id-eb138848ec7a8978f0ed7c717374684d2315dc03/> also hide placeholder node
+ - <csr-id-05331ddd8033f6997d4916179b62f4d62f832988/> wire up both desktop and web
+ - <csr-id-8f4aa84f1a4f2443b34d81ee42490564e168de53/> bool attr white list
+ - <csr-id-5bf6c96f9fed04de949403202bafcbeadb5d2030/> setup a typescript build
+
+### Bug Fixes
+
+ - <csr-id-22308eb26a9ea48b14f5f5abb833aa90a4e3fc40/> custom protocol receiver type
+ - <csr-id-6bc45b1c5064a4e2b04d452c52a8167ad179691e/> clippy
+ - <csr-id-bad36162af764291f5a031b6233d151f61d745a4/> wry pathing
+ - <csr-id-be614e6535e6e13e6ff93e9c6a171c1c002e6b01/> cursor jumping  and use set instead of lsit
+ - <csr-id-92561612c727e73356d7d36e16af39aacf02a56d/> format code
+ - <csr-id-2073b400df55f0c6d8bed7371b2313be6c064e6e/> check `href` null
+ - <csr-id-21232285d9d84168d9003969ddd254fc22951e4b/> add exclusion list
+ - <csr-id-327f9015481809d8e5b9e69f26202e8d66dd198e/> check `href` null
+ - <csr-id-8089023a6c3a54957af9c9c05c9dee6088b059ef/> prevent `submit` default
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 26 commits contributed to the release over the course of 11 calendar days.
+ - 17 commits where understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' where seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+    - custom protocol receiver type ([`22308eb`](https://github.comgit//DioxusLabs/dioxus/commit/22308eb26a9ea48b14f5f5abb833aa90a4e3fc40))
+    - default asset server ([`430cde7`](https://github.comgit//DioxusLabs/dioxus/commit/430cde7068d308f2783e33d278fd0c0efa659c1b))
+    - fix web doc example and use &mut for builders everywhere ([`a239d2b`](https://github.comgit//DioxusLabs/dioxus/commit/a239d2ba6ac7f1f3d09de16c022ce8ca52cf0f63))
+    - Merge pull request #111 from DioxusLabs/jk/props-attrs ([`0369fe7`](https://github.comgit//DioxusLabs/dioxus/commit/0369fe72fb247409da300a54ef11ba9155d0efb3))
+    - clippy ([`6bc45b1`](https://github.comgit//DioxusLabs/dioxus/commit/6bc45b1c5064a4e2b04d452c52a8167ad179691e))
+    - Merge pull request #113 from DioxusLabs/jk/desktop-cursor-jump ([`20a2940`](https://github.comgit//DioxusLabs/dioxus/commit/20a29409b22510b001fdbee349724adb7b44d401))
+    - wry pathing ([`bad3616`](https://github.comgit//DioxusLabs/dioxus/commit/bad36162af764291f5a031b6233d151f61d745a4))
+    - remove dioxus id on non-event elements ([`95e93ed`](https://github.comgit//DioxusLabs/dioxus/commit/95e93ed0bcf6c69990f4cf3c6448b2bf5da96c36))
+    - also hide placeholder node ([`eb13884`](https://github.comgit//DioxusLabs/dioxus/commit/eb138848ec7a8978f0ed7c717374684d2315dc03))
+    - drag and drop support ([`9ae981a`](https://github.comgit//DioxusLabs/dioxus/commit/9ae981a1af4b5474ce16e27e070794d59128c12a))
+    - feat(events:focus): add missing `onfocusin` event ([`007d06d`](https://github.comgit//DioxusLabs/dioxus/commit/007d06d602f1adfaa51c87ec89b2afe90d8cdef9))
+    - cursor jumping  and use set instead of lsit ([`be614e6`](https://github.comgit//DioxusLabs/dioxus/commit/be614e6535e6e13e6ff93e9c6a171c1c002e6b01))
+    - Merge pull request #108 from DioxusLabs/jk/fstring-component-fields ([`f4132d1`](https://github.comgit//DioxusLabs/dioxus/commit/f4132d1874f7495049fac23ba0a022ac137ad74f))
+    - feat(example:todomvc): add editing support ([`9849f68`](https://github.comgit//DioxusLabs/dioxus/commit/9849f68f257200fac511c048bfb1a076243b86d3))
+    - Merge pull request #101 from alexkirsz/ci ([`29bf424`](https://github.comgit//DioxusLabs/dioxus/commit/29bf424b0976b95ff645bb128d0e758cf0186614))
+    - Merge pull request #139 from DioxusLabs/jk/provide-context-any ([`70f2ef4`](https://github.comgit//DioxusLabs/dioxus/commit/70f2ef43db5b6737bd9bcbfc1aa21c834ce4b395))
+    - Merge branch 'master' into jk/unify ([`824defa`](https://github.comgit//DioxusLabs/dioxus/commit/824defa2dbcc16d66588b3976699d89b65a8a068))
+    - wire up both desktop and web ([`05331dd`](https://github.comgit//DioxusLabs/dioxus/commit/05331ddd8033f6997d4916179b62f4d62f832988))
+    - format code ([`9256161`](https://github.comgit//DioxusLabs/dioxus/commit/92561612c727e73356d7d36e16af39aacf02a56d))
+    - Enable clippy ([`b6903bf`](https://github.comgit//DioxusLabs/dioxus/commit/b6903bf558bc7a3d0fe6794a137c44fca0957d11))
+    - bool attr white list ([`8f4aa84`](https://github.comgit//DioxusLabs/dioxus/commit/8f4aa84f1a4f2443b34d81ee42490564e168de53))
+    - setup a typescript build ([`5bf6c96`](https://github.comgit//DioxusLabs/dioxus/commit/5bf6c96f9fed04de949403202bafcbeadb5d2030))
+    - check `href` null ([`2073b40`](https://github.comgit//DioxusLabs/dioxus/commit/2073b400df55f0c6d8bed7371b2313be6c064e6e))
+    - add exclusion list ([`2123228`](https://github.comgit//DioxusLabs/dioxus/commit/21232285d9d84168d9003969ddd254fc22951e4b))
+    - check `href` null ([`327f901`](https://github.comgit//DioxusLabs/dioxus/commit/327f9015481809d8e5b9e69f26202e8d66dd198e))
+    - prevent `submit` default ([`8089023`](https://github.comgit//DioxusLabs/dioxus/commit/8089023a6c3a54957af9c9c05c9dee6088b059ef))
+</details>
+
 ## v0.1.5 (2022-01-08)
 
 ### Documentation
@@ -37,6 +107,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
  - <csr-id-f782e142118fb7acf1b88a0f3fbb03e4a5e3e91e/> omg what a dumb mistake
  - <csr-id-4a0068f09918adbc299150edcf777f342ced0dd3/> bless up, no more segfaults
  - <csr-id-7dfe89c9581f45a445f17f9fe4bb94e61f67e971/> wire up event delegator for webview
+ - <csr-id-46fd6ac3450ca5ebf9aecb2d59a5a92b2a68bdd0/> link open in browser
+ - <csr-id-f006f50317f4b75fac353bc988db057a281ba7f8/> move `rpc` to handler
+ - <csr-id-9e04ce5342850d2e0a01dde169807d6f6eb16566/> `open_browser` bool attribute
+ - <csr-id-c737c424b05ad8453e8770a14a0d210fb0c7c2fe/> link open in browser
+ - <csr-id-a0f60152bc7e5866f114ed469809ce8be70d17d4/> link open in browser
 
 ### Bug Fixes
 
@@ -48,13 +123,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
  - <csr-id-a33f7701fcf5f917fea8719253650b5ad92554fd/> tags
  - <csr-id-601078f9cf78a58d7502a377676ac94f3cf037bf/> desktop and mobile
  - <csr-id-ba9e1dbb8fa24048a6c9ccef8a8722688226a845/> messed up how lifetimes worked, need to render once per component
+ - <csr-id-62b637f8b0eaf616c49461fa23b9251a79abc147/> error pattern
+ - <csr-id-5233ee97d9314f7f0e0bdf05c56d2a9e4201a596/> format code
 
 ### Commit Statistics
 
 <csr-read-only-do-not-edit/>
 
- - 91 commits contributed to the release over the course of 151 calendar days.
- - 78 commits where understood as [conventional](https://www.conventionalcommits.org).
+ - 98 commits contributed to the release over the course of 151 calendar days.
+ - 84 commits where understood as [conventional](https://www.conventionalcommits.org).
  - 0 issues like '(#ID)' where seen in commit messages
 
 ### Commit Details
@@ -64,11 +141,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 <details><summary>view details</summary>
 
  * **Uncategorized**
+    - Release dioxus-desktop v0.1.5 ([`cd0dcac`](https://github.comgit//DioxusLabs/dioxus/commit/cd0dcacaf2862f26d29acb21d98f75d41b940e3f))
+    - handle bool attrs properly ([`8d685f4`](https://github.comgit//DioxusLabs/dioxus/commit/8d685f40b7e0ef6521c60310d8687291e9b9c48a))
+    - link open in browser ([`46fd6ac`](https://github.comgit//DioxusLabs/dioxus/commit/46fd6ac3450ca5ebf9aecb2d59a5a92b2a68bdd0))
     - Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111))
+    - error pattern ([`62b637f`](https://github.comgit//DioxusLabs/dioxus/commit/62b637f8b0eaf616c49461fa23b9251a79abc147))
+    - move `rpc` to handler ([`f006f50`](https://github.comgit//DioxusLabs/dioxus/commit/f006f50317f4b75fac353bc988db057a281ba7f8))
+    - `open_browser` bool attribute ([`9e04ce5`](https://github.comgit//DioxusLabs/dioxus/commit/9e04ce5342850d2e0a01dde169807d6f6eb16566))
     - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
-    - add exclusion list ([`2123228`](https://github.comgit//DioxusLabs/dioxus/commit/21232285d9d84168d9003969ddd254fc22951e4b))
-    - handle bool attrs properly ([`8d685f4`](https://github.comgit//DioxusLabs/dioxus/commit/8d685f40b7e0ef6521c60310d8687291e9b9c48a))
+    - format code ([`5233ee9`](https://github.comgit//DioxusLabs/dioxus/commit/5233ee97d9314f7f0e0bdf05c56d2a9e4201a596))
+    - link open in browser ([`c737c42`](https://github.comgit//DioxusLabs/dioxus/commit/c737c424b05ad8453e8770a14a0d210fb0c7c2fe))
     - Merge pull request #89 from DioxusLabs/jk/simplify-example-run ([`8b6aa8b`](https://github.comgit//DioxusLabs/dioxus/commit/8b6aa8b880b6cb5c95e0c0743aad4e4e74388e05))
+    - link open in browser ([`a0f6015`](https://github.comgit//DioxusLabs/dioxus/commit/a0f60152bc7e5866f114ed469809ce8be70d17d4))
     - Merge pull request #74 from mrxiaozhuox/master ([`47056fd`](https://github.comgit//DioxusLabs/dioxus/commit/47056fda4577bcbdaa2a6f63d82eec876e5a5aee))
     - Merge pull request #80 from DioxusLabs/jk/router2dotoh ([`cdc2d8e`](https://github.comgit//DioxusLabs/dioxus/commit/cdc2d8ec6d123245c2ea5f6d10af02b6a6833994))
     - clear warnigns ([`175a6a1`](https://github.comgit//DioxusLabs/dioxus/commit/175a6a199c6738d8d0c7646ba0ec3fc4406c6535))

+ 6 - 4
packages/desktop/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "dioxus-desktop"
-version = "0.1.5"
+version = "0.1.6"
 authors = ["Jonathan Kelley"]
 edition = "2018"
 description = "Dioxus VirtualDOM renderer for a remote webview instance"
@@ -12,7 +12,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-dioxus-core = { path = "../core", version = "^0.1.7", features = ["serialize"] }
+dioxus-core = { path = "../core", version = "^0.1.9", features = ["serialize"] }
 argh = "0.1.4"
 serde = "1.0.120"
 serde_json = "1.0.61"
@@ -27,10 +27,12 @@ tokio = { version = "1.12.0", features = [
     "rt",
     "time",
 ], optional = true, default-features = false }
-dioxus-core-macro = { path = "../core-macro", version = "^0.1.6" }
-dioxus-html = { path = "../html", features = ["serialize"], version = "^0.1.4" }
+dioxus-core-macro = { path = "../core-macro", version = "^0.1.7" }
+dioxus-html = { path = "../html", features = ["serialize"], version = "^0.1.6" }
 webbrowser = "0.5.5"
 mime_guess = "2.0.3"
+dioxus-interpreter-js = { path = "../interpreter", version = "^0.0.0" }
+
 
 [features]
 default = ["tokio_runtime"]

+ 0 - 34
packages/desktop/examples/async.rs

@@ -1,34 +0,0 @@
-use dioxus::prelude::*;
-use dioxus_core as dioxus;
-use dioxus_core_macro::*;
-use dioxus_hooks::*;
-use dioxus_html as dioxus_elements;
-use std::time::Duration;
-
-fn main() {
-    dioxus_desktop::launch(app);
-}
-
-fn app(cx: Scope) -> Element {
-    let (count, set_count) = use_state(&cx, || 0);
-
-    use_future(&cx, || {
-        to_owned![set_count];
-        async move {
-            loop {
-                tokio::time::sleep(Duration::from_millis(1000)).await;
-                set_count.modify(|v| v + 1)
-            }
-        }
-    });
-
-    cx.render(rsx! {
-        div {
-            h1 { "High-Five counter: {count}" }
-            button {
-                onclick: move |_| set_count(0),
-                "Click me!"
-            }
-        }
-    })
-}

+ 52 - 85
packages/desktop/src/desktop_context.rs

@@ -1,100 +1,67 @@
-use std::cell::RefCell;
+use std::rc::Rc;
 
-use dioxus::prelude::Scope;
-use dioxus_core as dioxus;
-use dioxus_core::{Context, Element, LazyNodes, NodeFactory, Properties};
-use dioxus_core_macro::Props;
+use dioxus_core::ScopeState;
+use wry::application::event_loop::EventLoopProxy;
 
-/*
-This module provides a set of Dioxus components to easily manage windows, tabs, etc.
+use crate::UserWindowEvent;
 
-Windows can be created anywhere in the tree, making them very flexible for things like modals, etc.
+type ProxyType = EventLoopProxy<UserWindowEvent>;
 
-*/
-pub struct DesktopContext {}
-
-impl DesktopContext {
-    fn add_window(&mut self) {
-        //
-    }
-    fn close_window(&mut self) {
-        //
-    }
-}
-
-enum WindowHandlers {
-    Resized(Box<dyn Fn()>),
-    Moved(Box<dyn Fn()>),
-    CloseRequested(Box<dyn Fn()>),
-    Destroyed(Box<dyn Fn()>),
-    DroppedFile(Box<dyn Fn()>),
-    HoveredFile(Box<dyn Fn()>),
-    HoverFileCancelled(Box<dyn Fn()>),
-    ReceivedTimeText(Box<dyn Fn()>),
-    Focused(Box<dyn Fn()>),
-}
-
-#[derive(Props)]
-pub struct WebviewWindowProps<'a> {
-    onclose: &'a dyn FnMut(()),
-
-    onopen: &'a dyn FnMut(()),
-
-    /// focuse me
-    onfocused: &'a dyn FnMut(()),
-
-    children: Element,
-}
-
-/// A handle to a
-///
-///
-///
+/// Desktop-Window handle api context
 ///
+/// you can use this context control some window event
 ///
+/// you can use `cx.consume_context::<DesktopContext>` to get this context
 ///
-///
-///
-///
-pub fn WebviewWindow(cx: Scope<WebviewWindowProps>) -> Element {
-    let dtcx = cx.consume_state::<RefCell<DesktopContext>>()?;
-
-    cx.use_hook(|_| {});
-
-    // render the children directly
-    todo!()
-    // cx.render(LazyNodes::new(move |f: NodeFactory| {
-    //     f.fragment_from_iter(cx.children())
-    // }))
+/// ```rust
+///     let desktop = cx.consume_context::<DesktopContext>().unwrap();
+/// ```
+#[derive(Clone)]
+pub struct DesktopContext {
+    proxy: ProxyType,
 }
 
-pub struct WindowHandle {}
+impl DesktopContext {
+    pub(crate) fn new(proxy: ProxyType) -> Self {
+        Self { proxy }
+    }
 
-/// Get a handle to the current window from inside a component
-pub fn use_current_window(cx: Scope) -> Option<WindowHandle> {
-    todo!()
-}
+    /// trigger the drag-window event
+    ///
+    /// Moves the window with the left mouse button until the button is released.
+    ///
+    /// you need use it in `onmousedown` event:
+    /// ```rust
+    /// onmousedown: move |_| { desktop.drag_window(); }
+    /// ```
+    pub fn drag(&self) {
+        let _ = self.proxy.send_event(UserWindowEvent::DragWindow);
+    }
 
-#[test]
-fn syntax_works() {
-    use dioxus_core as dioxus;
-    use dioxus_core::prelude::*;
-    use dioxus_core_macro::*;
-    use dioxus_hooks::*;
-    use dioxus_html as dioxus_elements;
+    /// set window minimize state
+    pub fn minimize(&self, minimized: bool) {
+        let _ = self.proxy.send_event(UserWindowEvent::Minimize(minimized));
+    }
 
-    static App: Component = |cx| {
-        cx.render(rsx! {
-            // left window
-            WebviewWindow {
-                onclose: move |evt| {}
-                onopen: move |evt| {}
-                onfocused: move |evt| {}
+    /// set window maximize state
+    pub fn maximize(&self, maximized: bool) {
+        let _ = self.proxy.send_event(UserWindowEvent::Maximize(maximized));
+    }
 
-                div {
+    /// close window
+    pub fn close(&self) {
+        let _ = self.proxy.send_event(UserWindowEvent::CloseWindow);
+    }
+
+    /// set window to focus
+    pub fn focus(&self) {
+        let _ = self.proxy.send_event(UserWindowEvent::FocusWindow);
+    }
+}
 
-                }
-            }
-        })
-    };
+/// use this function can get the `DesktopContext` context.
+pub fn use_window(cx: &ScopeState) -> &Rc<DesktopContext> {
+    cx.use_hook(|_| cx.consume_context::<DesktopContext>())
+        .as_ref()
+        .unwrap()
 }

+ 51 - 2
packages/desktop/src/lib.rs

@@ -51,10 +51,13 @@
 //! Make sure to read the [Dioxus Guide](https://dioxuslabs.com/guide) if you already haven't!
 
 pub mod cfg;
+pub mod desktop_context;
 pub mod escape;
 pub mod events;
 
 use cfg::DesktopConfig;
+pub use desktop_context::use_window;
+use desktop_context::DesktopContext;
 use dioxus_core::*;
 use std::{
     collections::{HashMap, VecDeque},
@@ -188,6 +191,7 @@ pub fn launch_with_props<P: 'static + Send>(
                                 let _ = proxy.send_event(UserWindowEvent::Update);
                             }
                             "browser_open" => {
+                                println!("browser_open");
                                 let data = req.params.unwrap();
                                 log::trace!("Open browser: {:?}", data);
                                 if let Some(arr) = data.as_array() {
@@ -220,7 +224,7 @@ pub fn launch_with_props<P: 'static + Send>(
                         } else if trimmed == "index.js" {
                             wry::http::ResponseBuilder::new()
                                 .mimetype("text/javascript")
-                                .body(include_bytes!("../../jsinterpreter/interpreter.js").to_vec())
+                                .body(dioxus_interpreter_js::INTERPRTER_JS.as_bytes().to_vec())
                         } else {
                             // Read the file content from file path
                             use std::fs::read;
@@ -244,7 +248,7 @@ pub fn launch_with_props<P: 'static + Send>(
 
                             // do not let path searching to go two layers beyond the caller level
                             let data = read(path_buf)?;
-                            let meta = format!("{mime}");
+                            let meta = format!("{}", mime);
 
                             wry::http::ResponseBuilder::new().mimetype(&meta).body(data)
                         }
@@ -282,6 +286,41 @@ pub fn launch_with_props<P: 'static + Send>(
                 //
                 match _evt {
                     UserWindowEvent::Update => desktop.try_load_ready_webviews(),
+                    UserWindowEvent::DragWindow => {
+                        // this loop just run once, because dioxus-desktop is unsupport multi-window.
+                        for webview in desktop.webviews.values() {
+                            let window = webview.window();
+                            // start to drag the window.
+                            // if the drag_window have any err. we don't do anything.
+                            let _ = window.drag_window();
+                        }
+                    }
+                    UserWindowEvent::CloseWindow => {
+                        // close window
+                        *control_flow = ControlFlow::Exit;
+                    }
+                    UserWindowEvent::Minimize(state) => {
+                        // this loop just run once, because dioxus-desktop is unsupport multi-window.
+                        for webview in desktop.webviews.values() {
+                            let window = webview.window();
+                            // change window minimized state.
+                            window.set_minimized(state);
+                        }
+                    }
+                    UserWindowEvent::Maximize(state) => {
+                        // this loop just run once, because dioxus-desktop is unsupport multi-window.
+                        for webview in desktop.webviews.values() {
+                            let window = webview.window();
+                            // change window maximized state.
+                            window.set_maximized(state);
+                        }
+                    }
+                    UserWindowEvent::FocusWindow => {
+                        for webview in desktop.webviews.values() {
+                            let window = webview.window();
+                            window.set_focus();
+                        }
+                    }
                 }
             }
             Event::MainEventsCleared => {}
@@ -296,6 +335,11 @@ pub fn launch_with_props<P: 'static + Send>(
 
 pub enum UserWindowEvent {
     Update,
+    DragWindow,
+    CloseWindow,
+    FocusWindow,
+    Minimize(bool),
+    Maximize(bool),
 }
 
 pub struct DesktopController {
@@ -322,6 +366,7 @@ impl DesktopController {
         let return_sender = sender.clone();
         let proxy = evt.clone();
 
+        let desktop_context_proxy = proxy.clone();
         std::thread::spawn(move || {
             // We create the runtime as multithreaded, so you can still "spawn" onto multiple threads
             let runtime = tokio::runtime::Builder::new_multi_thread()
@@ -333,6 +378,10 @@ impl DesktopController {
                 let mut dom =
                     VirtualDom::new_with_props_and_scheduler(root, props, (sender, receiver));
 
+                let window_context = DesktopContext::new(desktop_context_proxy);
+
+                dom.base_scope().provide_context(window_context);
+
                 let edits = dom.rebuild();
 
                 edit_queue

+ 35 - 1
packages/hooks/CHANGELOG.md

@@ -5,6 +5,39 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## Unreleased
+
+### Bug Fixes
+
+ - <csr-id-c092bd43edf1891c427722bc9ca04e11c9359069/> use_state
+ - <csr-id-a8952a9ee8d8831fa911cef4e7035293c3a9f88b/> exampels
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 9 commits contributed to the release over the course of 21 calendar days.
+ - 3 commits where understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' where seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+    - Merge pull request #169 from DioxusLabs/jk/router-userouter ([`3509602`](https://github.comgit//DioxusLabs/dioxus/commit/3509602c0bcd327a33bc8c95896775e24751da1a))
+    - Merge pull request #158 from DioxusLabs/jk/router-onchange ([`08988e1`](https://github.comgit//DioxusLabs/dioxus/commit/08988e1bfec35eadb30e50b5d677dbb36af91b9b))
+    - use_state ([`c092bd4`](https://github.comgit//DioxusLabs/dioxus/commit/c092bd43edf1891c427722bc9ca04e11c9359069))
+    - exampels ([`a8952a9`](https://github.comgit//DioxusLabs/dioxus/commit/a8952a9ee8d8831fa911cef4e7035293c3a9f88b))
+    - Merge branch 'master' into jk/update-hooks ([`5c4bd08`](https://github.comgit//DioxusLabs/dioxus/commit/5c4bd0881bc10572440d9272ceb8ca774431c406))
+    - modify usestate to be borrowed ([`58839f4`](https://github.comgit//DioxusLabs/dioxus/commit/58839f47bae3c49cadd4b41da9a5debebb1def99))
+    - Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d))
+    - Merge pull request #108 from DioxusLabs/jk/fstring-component-fields ([`f4132d1`](https://github.comgit//DioxusLabs/dioxus/commit/f4132d1874f7495049fac23ba0a022ac137ad74f))
+    - Enable clippy ([`b6903bf`](https://github.comgit//DioxusLabs/dioxus/commit/b6903bf558bc7a3d0fe6794a137c44fca0957d11))
+</details>
+
 ## v0.1.6 (2022-01-08)
 
 ### Documentation
@@ -52,7 +85,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 <csr-read-only-do-not-edit/>
 
- - 93 commits contributed to the release over the course of 358 calendar days.
+ - 94 commits contributed to the release over the course of 358 calendar days.
  - 82 commits where understood as [conventional](https://www.conventionalcommits.org).
  - 0 issues like '(#ID)' where seen in commit messages
 
@@ -63,6 +96,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 <details><summary>view details</summary>
 
  * **Uncategorized**
+    - Release dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`b804c69`](https://github.comgit//DioxusLabs/dioxus/commit/b804c691d5ade4776390bb3d334cc9cd8efa4a49))
     - Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111))
     - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
     - component pass thru events ([`c439b0a`](https://github.comgit//DioxusLabs/dioxus/commit/c439b0ac7e09f70a04262b7c29938d8c52197b76))

+ 2 - 2
packages/hooks/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "dioxus-hooks"
-version = "0.1.6"
+version = "0.1.7"
 authors = ["Jonathan Kelley"]
 edition = "2018"
 description = "Dioxus VirtualDOM renderer for a remote webview instance"
@@ -12,4 +12,4 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 # 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.7"}
+dioxus-core = { path = "../../packages/core", version = "^0.1.9" }

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

@@ -33,7 +33,7 @@ impl<T> ProvidedStateInner<T> {
 /// This hook provides some relatively light ergonomics around shared state.
 ///
 /// It is not a substitute for a proper state management system, but it is capable enough to provide use_state - type
-/// ergonimics in a pinch, with zero cost.
+/// ergonomics in a pinch, with zero cost.
 ///
 /// # Example
 ///

+ 23 - 20
packages/hooks/src/usestate.rs

@@ -39,16 +39,17 @@ pub fn use_state<'a, T: 'static>(
         let setter = Rc::new({
             crate::to_owned![update_callback, slot];
             move |new| {
-                let mut slot = slot.borrow_mut();
-
-                // if there's only one reference (weak or otherwise), we can just swap the values
-                // Typically happens when the state is set multiple times - we don't want to create a new Rc for each new value
-                if let Some(val) = Rc::get_mut(&mut slot) {
-                    *val = new;
-                } else {
-                    *slot = Rc::new(new);
+                {
+                    let mut slot = slot.borrow_mut();
+
+                    // if there's only one reference (weak or otherwise), we can just swap the values
+                    // Typically happens when the state is set multiple times - we don't want to create a new Rc for each new value
+                    if let Some(val) = Rc::get_mut(&mut slot) {
+                        *val = new;
+                    } else {
+                        *slot = Rc::new(new);
+                    }
                 }
-
                 update_callback();
             }
         });
@@ -61,6 +62,8 @@ pub fn use_state<'a, T: 'static>(
         }
     });
 
+    hook.current_val = hook.slot.borrow().clone();
+
     (hook.current_val.as_ref(), hook)
 }
 
@@ -90,7 +93,7 @@ impl<T: 'static> UseState<T> {
     ///         }
     ///     })
     /// }
-    /// ```    
+    /// ```
     #[must_use]
     pub fn current(&self) -> Rc<T> {
         self.slot.borrow().clone()
@@ -135,10 +138,10 @@ impl<T: 'static> UseState<T> {
     /// # use dioxus_hooks::*;
     /// fn component(cx: Scope) -> Element {
     ///     let (value, set_value) = use_state(&cx, || 0);
-    ///    
+    ///
     ///     // to increment the value
     ///     set_value.modify(|v| v + 1);
-    ///    
+    ///
     ///     // usage in async
     ///     cx.spawn({
     ///         let set_value = set_value.to_owned();
@@ -151,8 +154,10 @@ impl<T: 'static> UseState<T> {
     /// }
     /// ```
     pub fn modify(&self, f: impl FnOnce(&T) -> T) {
-        let curernt = self.slot.borrow();
-        let new_val = f(curernt.as_ref());
+        let new_val = {
+            let current = self.slot.borrow();
+            f(current.as_ref())
+        };
         (self.setter)(new_val);
     }
 
@@ -174,7 +179,7 @@ impl<T: 'static> UseState<T> {
     /// # use dioxus_hooks::*;
     /// fn component(cx: Scope) -> Element {
     ///     let (value, set_value) = use_state(&cx, || 0);
-    ///    
+    ///
     ///     let as_rc = set_value.get();
     ///     assert_eq!(as_rc.as_ref(), &0);
     ///
@@ -199,7 +204,7 @@ impl<T: 'static> UseState<T> {
     ///         }
     ///     })
     /// }
-    /// ```   
+    /// ```
     pub fn needs_update(&self) {
         (self.update_callback)();
     }
@@ -270,10 +275,8 @@ impl<T: Clone> UseState<T> {
     }
 }
 
-impl<T: 'static> ToOwned for UseState<T> {
-    type Owned = UseState<T>;
-
-    fn to_owned(&self) -> Self::Owned {
+impl<T: 'static> Clone for UseState<T> {
+    fn clone(&self) -> Self {
         UseState {
             current_val: self.current_val.clone(),
             update_callback: self.update_callback.clone(),

+ 33 - 2
packages/html/CHANGELOG.md

@@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## Unreleased
+
+### Bug Fixes
+
+ - <csr-id-43e78d56f72e23ada939f5688a6bd2fdd9b8292d/> rustfmt
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 7 commits contributed to the release over the course of 19 calendar days.
+ - 1 commit where understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' where seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+    - rustfmt ([`43e78d5`](https://github.comgit//DioxusLabs/dioxus/commit/43e78d56f72e23ada939f5688a6bd2fdd9b8292d))
+    - Add gap and row_gap to style_trait_methods ([`5f4a724`](https://github.comgit//DioxusLabs/dioxus/commit/5f4a72446e63441628e18bb19fd9b2d9f65f9d52))
+    - Merge pull request #111 from DioxusLabs/jk/props-attrs ([`0369fe7`](https://github.comgit//DioxusLabs/dioxus/commit/0369fe72fb247409da300a54ef11ba9155d0efb3))
+    - Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d))
+    - Merge pull request #113 from DioxusLabs/jk/desktop-cursor-jump ([`20a2940`](https://github.comgit//DioxusLabs/dioxus/commit/20a29409b22510b001fdbee349724adb7b44d401))
+    - feat(events:focus): add missing `onfocusin` event ([`007d06d`](https://github.comgit//DioxusLabs/dioxus/commit/007d06d602f1adfaa51c87ec89b2afe90d8cdef9))
+    - feat(example:todomvc): add editing support ([`9849f68`](https://github.comgit//DioxusLabs/dioxus/commit/9849f68f257200fac511c048bfb1a076243b86d3))
+</details>
+
 ## v0.1.4 (2022-01-08)
 
 ### Documentation
@@ -42,7 +72,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 <csr-read-only-do-not-edit/>
 
- - 60 commits contributed to the release over the course of 184 calendar days.
+ - 61 commits contributed to the release over the course of 184 calendar days.
  - 52 commits where understood as [conventional](https://www.conventionalcommits.org).
  - 0 issues like '(#ID)' where seen in commit messages
 
@@ -53,8 +83,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 <details><summary>view details</summary>
 
  * **Uncategorized**
-    - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
+    - Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111))
     - enable prevent_default everywhere ([`9dff700`](https://github.comgit//DioxusLabs/dioxus/commit/9dff700c220dd9e0da2ee028900e82fd24f9d0dd))
+    - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
     - component pass thru events ([`c439b0a`](https://github.comgit//DioxusLabs/dioxus/commit/c439b0ac7e09f70a04262b7c29938d8c52197b76))
     - memoize dom in the prescence of identical components ([`cb2782b`](https://github.comgit//DioxusLabs/dioxus/commit/cb2782b4bb34cdaadfff590bfee930ae3ac6536c))
     - new versions of everything ([`4ea5c99`](https://github.comgit//DioxusLabs/dioxus/commit/4ea5c990d72b1645724ab0a88ffea2baf28e2835))

+ 2 - 2
packages/html/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "dioxus-html"
-version = "0.1.4"
+version = "0.1.6"
 authors = ["Jonathan Kelley"]
 edition = "2018"
 description = "HTML Element pack for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
@@ -11,7 +11,7 @@ documentation = "https://docs.rs/dioxus"
 keywords = ["dom", "ui", "gui", "react", "wasm"]
 
 [dependencies]
-dioxus-core = { path = "../core", version ="^0.1.7"}
+dioxus-core = { path = "../core", version = "^0.1.9" }
 serde = { version = "1", features = ["derive"], optional = true }
 serde_repr = { version = "0.1", optional = true }
 

+ 1 - 6
packages/html/src/elements.rs

@@ -1047,7 +1047,7 @@ impl input {
     /// - `text`
     /// - `time`
     /// - `url`
-    /// - `week`    
+    /// - `week`
     pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
         cx.attr("type", val, None, false)
     }
@@ -1100,11 +1100,6 @@ impl label {
         cx.attr("for", val, None, false)
     }
 }
-impl a {
-    pub fn prevent_default<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
-        cx.attr("dioxus-prevent-default", val, None, false)
-    }
-}
 
 builder_constructors! {
     // SVG components

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

@@ -49,6 +49,10 @@ macro_rules! aria_trait_methods {
 }
 
 pub trait GlobalAttributes {
+    /// Prevent the default action for this element.
+    ///
+    /// For more information, see the MDN docs:
+    /// <https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault>
     fn prevent_default<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
         cx.attr("dioxus-prevent-default", val, None, false)
     }
@@ -603,6 +607,11 @@ pub trait GlobalAttributes {
         /// Specifies the speed curve of the transition effect.
         transition_timing_function: "transition-timing-function",
 
+        /// The user-select CSS property controls whether the user can select text.
+        /// This doesn't have any effect on content loaded as part of a browser's user interface (its chrome), except in textboxes.
+        user_select: "user-select",
+        webkit_user_select: "-webkit-user-select",
+
         /// Sets the vertical positioning of an element relative to the current text baseline.
         vertical_align: "vertical-align",
 
@@ -685,6 +694,13 @@ pub trait GlobalAttributes {
 }
 
 pub trait SvgAttributes {
+    /// Prevent the default action for this element.
+    ///
+    /// For more information, see the MDN docs:
+    /// <https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault>
+    fn prevent_default<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
+        cx.attr("dioxus-prevent-default", val, None, false)
+    }
     aria_trait_methods! {
         accent_height: "accent-height",
         accumulate: "accumulate",

+ 24 - 0
packages/interpreter/Cargo.toml

@@ -0,0 +1,24 @@
+[package]
+name = "dioxus-interpreter-js"
+version = "0.0.0"
+edition = "2018"
+authors = ["Jonathan Kelley"]
+description = "JS Intepreter for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/DioxusLabs/dioxus/"
+homepage = "https://dioxuslabs.com"
+documentation = "https://docs.rs/dioxus"
+keywords = ["dom", "ui", "gui", "react", "wasm"]
+
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+wasm-bindgen = { version = "0.2.79", optional = true }
+js-sys = { version = "0.3.56", optional = true }
+web-sys = { version = "0.3.56", optional = true, features = ["Element", "Node"] }
+
+
+[features]
+default = []
+web = ["wasm-bindgen", "js-sys", "web-sys"]

+ 0 - 1
packages/jsinterpreter/README.md → packages/interpreter/README.md

@@ -4,5 +4,4 @@ After diffing old and new trees, the Dioxus VirtualDom produces patches that are
 
 In renderers with support for JavaScript, we use the interpreter from this repository - written in TypeScript - to patch the Dom. This lets us circumvent any overhead on the Rust <-> Dom boundary and keep consistency in our interpreter implementation in web/webview targets.
 
-
 For now - both Dioxus Web and Dioxus Desktop (webview) use the same interpreter code with tweaks.

+ 1 - 1
packages/web/src/bindings.rs → packages/interpreter/src/bindings.rs

@@ -2,7 +2,7 @@ use js_sys::Function;
 use wasm_bindgen::prelude::*;
 use web_sys::{Element, Node};
 
-#[wasm_bindgen(module = "/../jsinterpreter/interpreter.js")]
+#[wasm_bindgen(module = "/src/interpreter.js")]
 extern "C" {
     pub type Interpreter;
 

+ 71 - 206
packages/jsinterpreter/interpreter.ts → packages/interpreter/src/interpreter.js

@@ -1,7 +1,3 @@
-
-
-
-
 export function main() {
   let root = window.document.getElementById("main");
   if (root != null) {
@@ -9,25 +5,14 @@ export function main() {
     window.rpc.call("initialize");
   }
 }
-
-declare global {
-  interface Window {
-    interpreter: Interpreter;
-    rpc: { call: (method: string, args?: any) => void };
-  }
-}
-
-
 export class Interpreter {
-  root: Element;
-  stack: Element[];
-  listeners: { [key: string]: number };
-  handlers: { [key: string]: (evt: Event) => void };
-  lastNodeWasText: boolean;
-  nodes: Element[];
-
-
-  constructor(root: Element) {
+  root;
+  stack;
+  listeners;
+  handlers;
+  lastNodeWasText;
+  nodes;
+  constructor(root) {
     this.root = root;
     this.stack = [root];
     this.listeners = {};
@@ -35,88 +20,70 @@ export class Interpreter {
     this.lastNodeWasText = false;
     this.nodes = [root];
   }
-
   top() {
     return this.stack[this.stack.length - 1];
   }
-
   pop() {
     return this.stack.pop();
   }
-
-  PushRoot(root: number) {
+  PushRoot(root) {
     const node = this.nodes[root];
     this.stack.push(node);
   }
-
-  AppendChildren(many: number) {
+  AppendChildren(many) {
     let root = this.stack[this.stack.length - (1 + many)];
-
     let to_add = this.stack.splice(this.stack.length - many);
-
     for (let i = 0; i < many; i++) {
       root.appendChild(to_add[i]);
     }
   }
-
-  ReplaceWith(root_id: number, m: number) {
-    let root = this.nodes[root_id] as Element;
+  ReplaceWith(root_id, m) {
+    let root = this.nodes[root_id];
     let els = this.stack.splice(this.stack.length - m);
-
     root.replaceWith(...els);
   }
-
-  InsertAfter(root: number, n: number) {
-    let old = this.nodes[root] as Element;
+  InsertAfter(root, n) {
+    let old = this.nodes[root];
     let new_nodes = this.stack.splice(this.stack.length - n);
     old.after(...new_nodes);
   }
-
-  InsertBefore(root: number, n: number) {
-    let old = this.nodes[root] as Element;
+  InsertBefore(root, n) {
+    let old = this.nodes[root];
     let new_nodes = this.stack.splice(this.stack.length - n);
     old.before(...new_nodes);
   }
-
-  Remove(root: number) {
-    let node = this.nodes[root] as Element;
+  Remove(root) {
+    let node = this.nodes[root];
     if (node !== undefined) {
       node.remove();
     }
   }
-
-  CreateTextNode(text: string, root: number) {
+  CreateTextNode(text, root) {
     // todo: make it so the types are okay
-    const node = document.createTextNode(text) as any as Element;
+    const node = document.createTextNode(text);
     this.nodes[root] = node;
     this.stack.push(node);
   }
-
-  CreateElement(tag: string, root: number) {
+  CreateElement(tag, root) {
     const el = document.createElement(tag);
     // el.setAttribute("data-dioxus-id", `${root}`);
-
     this.nodes[root] = el;
     this.stack.push(el);
   }
-
-  CreateElementNs(tag: string, root: number, ns: string) {
+  CreateElementNs(tag, root, ns) {
     let el = document.createElementNS(ns, tag);
     this.stack.push(el);
     this.nodes[root] = el;
   }
-
-  CreatePlaceholder(root: number) {
+  CreatePlaceholder(root) {
     let el = document.createElement("pre");
     el.hidden = true;
     this.stack.push(el);
     this.nodes[root] = el;
   }
-
-  NewEventListener(event_name: string, root: number, handler: (evt: Event) => void) {
+  NewEventListener(event_name, root, handler) {
     const element = this.nodes[root];
     element.setAttribute("data-dioxus-id", `${root}`);
-
     if (this.listeners[event_name] === undefined) {
       this.listeners[event_name] = 0;
       this.handlers[event_name] = handler;
@@ -125,48 +92,39 @@ export class Interpreter {
       this.listeners[event_name]++;
     }
   }
-
-  RemoveEventListener(root: number, event_name: string) {
+  RemoveEventListener(root, event_name) {
     const element = this.nodes[root];
     element.removeAttribute(`data-dioxus-id`);
-
     this.listeners[event_name]--;
-
     if (this.listeners[event_name] === 0) {
       this.root.removeEventListener(event_name, this.handlers[event_name]);
       delete this.listeners[event_name];
       delete this.handlers[event_name];
     }
   }
-
-
-  SetText(root: number, text: string) {
+  SetText(root, text) {
     this.nodes[root].textContent = text;
   }
-
-  SetAttribute(root: number, field: string, value: string, ns: string | undefined) {
+  SetAttribute(root, field, value, ns) {
     const name = field;
     const node = this.nodes[root];
-
     if (ns == "style") {
-
       // @ts-ignore
-      (node as HTMLElement).style[name] = value;
-
+      node.style[name] = value;
     } else if (ns != null || ns != undefined) {
       node.setAttributeNS(ns, name, value);
     } else {
       switch (name) {
         case "value":
-          if (value != (node as HTMLInputElement).value) {
-            (node as HTMLInputElement).value = value;
+          if (value != node.value) {
+            node.value = value;
           }
           break;
         case "checked":
-          (node as HTMLInputElement).checked = value === "true";
+          node.checked = value === "true";
           break;
         case "selected":
-          (node as HTMLOptionElement).selected = value === "true";
+          node.selected = value === "true";
           break;
         case "dangerous_inner_html":
           node.innerHTML = value;
@@ -181,33 +139,26 @@ export class Interpreter {
       }
     }
   }
-  RemoveAttribute(root: number, name: string) {
-
+  RemoveAttribute(root, name) {
     const node = this.nodes[root];
     node.removeAttribute(name);
-
     if (name === "value") {
-      (node as HTMLInputElement).value = "";
+      node.value = "";
     }
-
     if (name === "checked") {
-      (node as HTMLInputElement).checked = false;
+      node.checked = false;
     }
-
     if (name === "selected") {
-      (node as HTMLOptionElement).selected = false;
+      node.selected = false;
     }
   }
-
-  handleEdits(edits: DomEdit[]) {
+  handleEdits(edits) {
     this.stack.push(this.root);
-
     for (let edit of edits) {
       this.handleEdit(edit);
     }
   }
-
-  handleEdit(edit: DomEdit) {
+  handleEdit(edit) {
     switch (edit.type) {
       case "PushRoot":
         this.PushRoot(edit.root);
@@ -243,55 +194,48 @@ export class Interpreter {
         this.RemoveEventListener(edit.root, edit.event_name);
         break;
       case "NewEventListener":
-
-
-        // this handler is only provided on desktop implementations since this 
+        // this handler is only provided on desktop implementations since this
         // method is not used by the web implementation
-        let handler = (event: Event) => {
-          let target = event.target as Element | null;
-
+        let handler = (event) => {
+          let target = event.target;
           if (target != null) {
             let realId = target.getAttribute(`data-dioxus-id`);
+            let shouldPreventDefault = target.getAttribute(
+              `dioxus-prevent-default`
+            );
 
+            if (event.type == "click") {
+              // todo call prevent default if it's the right type of event
+              if (shouldPreventDefault !== `onclick`) {
+                if (target.tagName == "A") {
+                  const href = target.getAttribute("href");
+                  if (href !== "" && href !== null && href !== undefined) {
+                    window.rpc.call("browser_open", { href });
+                  }
+                }
+              }
+            }
             // walk the tree to find the real element
             while (realId == null && target.parentElement != null) {
               target = target.parentElement;
               realId = target.getAttribute(`data-dioxus-id`);
             }
 
-            const shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`);
-
+            shouldPreventDefault = target.getAttribute(
+              `dioxus-prevent-default`
+            );
             let contents = serialize_event(event);
-
             if (shouldPreventDefault === `on${event.type}`) {
-              event.preventDefault();
+              //   event.preventDefault();
             }
-
             if (event.type == "submit") {
-              event.preventDefault();
-            }
-
-            if (event.type == "click") {
-              event.preventDefault();
-              if (shouldPreventDefault !== `onclick`) {
-                if (target.tagName == "A") {
-                  const href = target.getAttribute("href")
-                  if (href !== "" && href !== null && href !== undefined && realId != null) {
-                    window.rpc.call("browser_open", {
-                      mounted_dom_id: parseInt(realId),
-                      href
-                    });
-                  }
-                }
-              }
+              //   event.preventDefault();
             }
-
             if (realId == null) {
               return;
             }
-
             window.rpc.call("user_event", {
-              event: (edit as NewEventListener).event_name,
+              event: edit.event_name,
               mounted_dom_id: parseInt(realId),
               contents: contents,
             });
@@ -311,26 +255,21 @@ export class Interpreter {
     }
   }
 }
-
-
-
-function serialize_event(event: Event) {
+function serialize_event(event) {
   switch (event.type) {
     case "copy":
     case "cut":
     case "past": {
       return {};
     }
-
     case "compositionend":
     case "compositionstart":
     case "compositionupdate": {
-      let { data } = (event as CompositionEvent);
+      let { data } = event;
       return {
         data,
       };
     }
-
     case "keydown":
     case "keypress":
     case "keyup": {
@@ -345,8 +284,7 @@ function serialize_event(event: Event) {
         location,
         repeat,
         which,
-      } = (event as KeyboardEvent);
-
+      } = event;
       return {
         char_code: charCode,
         key: key,
@@ -361,42 +299,35 @@ function serialize_event(event: Event) {
         locale: "locale",
       };
     }
-
     case "focus":
     case "blur": {
       return {};
     }
-
     case "change": {
-      let target = event.target as HTMLInputElement;
+      let target = event.target;
       let value;
       if (target.type === "checkbox" || target.type === "radio") {
         value = target.checked ? "true" : "false";
       } else {
         value = target.value ?? target.textContent;
       }
-
       return {
         value: value,
       };
     }
-
     case "input":
     case "invalid":
     case "reset":
     case "submit": {
-      let target = event.target as HTMLFormElement;
+      let target = event.target;
       let value = target.value ?? target.textContent;
-
       if (target.type == "checkbox") {
         value = target.checked ? "true" : "false";
       }
-
       return {
         value: value,
       };
     }
-
     case "click":
     case "contextmenu":
     case "doubleclick":
@@ -428,8 +359,7 @@ function serialize_event(event: Event) {
         screenX,
         screenY,
         shiftKey,
-      } = event as MouseEvent;
-
+      } = event;
       return {
         alt_key: altKey,
         button: button,
@@ -445,7 +375,6 @@ function serialize_event(event: Event) {
         shift_key: shiftKey,
       };
     }
-
     case "pointerdown":
     case "pointermove":
     case "pointerup":
@@ -479,7 +408,7 @@ function serialize_event(event: Event) {
         twist,
         pointerType,
         isPrimary,
-      } = event as PointerEvent;
+      } = event;
       return {
         alt_key: altKey,
         button: button,
@@ -505,21 +434,14 @@ function serialize_event(event: Event) {
         is_primary: isPrimary,
       };
     }
-
     case "select": {
       return {};
     }
-
     case "touchcancel":
     case "touchend":
     case "touchmove":
     case "touchstart": {
-      const {
-        altKey,
-        ctrlKey,
-        metaKey,
-        shiftKey,
-      } = event as TouchEvent;
+      const { altKey, ctrlKey, metaKey, shiftKey } = event;
       return {
         // changed_touches: event.changedTouches,
         // target_touches: event.targetTouches,
@@ -530,18 +452,11 @@ function serialize_event(event: Event) {
         shift_key: shiftKey,
       };
     }
-
     case "scroll": {
       return {};
     }
-
     case "wheel": {
-      const {
-        deltaX,
-        deltaY,
-        deltaZ,
-        deltaMode,
-      } = event as WheelEvent;
+      const { deltaX, deltaY, deltaZ, deltaMode } = event;
       return {
         delta_x: deltaX,
         delta_y: deltaY,
@@ -549,35 +464,24 @@ function serialize_event(event: Event) {
         delta_mode: deltaMode,
       };
     }
-
     case "animationstart":
     case "animationend":
     case "animationiteration": {
-      const {
-        animationName,
-        elapsedTime,
-        pseudoElement,
-      } = event as AnimationEvent;
+      const { animationName, elapsedTime, pseudoElement } = event;
       return {
         animation_name: animationName,
         elapsed_time: elapsedTime,
         pseudo_element: pseudoElement,
       };
     }
-
     case "transitionend": {
-      const {
-        propertyName,
-        elapsedTime,
-        pseudoElement,
-      } = event as TransitionEvent;
+      const { propertyName, elapsedTime, pseudoElement } = event;
       return {
         property_name: propertyName,
         elapsed_time: elapsedTime,
         pseudo_element: pseudoElement,
       };
     }
-
     case "abort":
     case "canplay":
     case "canplaythrough":
@@ -603,17 +507,14 @@ function serialize_event(event: Event) {
     case "waiting": {
       return {};
     }
-
     case "toggle": {
       return {};
     }
-
     default: {
       return {};
     }
   }
 }
-
 const bool_attrs = {
   allowfullscreen: true,
   allowpaymentrequest: true,
@@ -642,39 +543,3 @@ const bool_attrs = {
   selected: true,
   truespeed: true,
 };
-
-
-
-type PushRoot = { type: "PushRoot", root: number };
-type AppendChildren = { type: "AppendChildren", many: number };
-type ReplaceWith = { type: "ReplaceWith", root: number, m: number };
-type InsertAfter = { type: "InsertAfter", root: number, n: number };
-type InsertBefore = { type: "InsertBefore", root: number, n: number };
-type Remove = { type: "Remove", root: number };
-type CreateTextNode = { type: "CreateTextNode", text: string, root: number };
-type CreateElement = { type: "CreateElement", tag: string, root: number };
-type CreateElementNs = { type: "CreateElementNs", tag: string, root: number, ns: string };
-type CreatePlaceholder = { type: "CreatePlaceholder", root: number };
-type NewEventListener = { type: "NewEventListener", root: number, event_name: string, scope: number };
-type RemoveEventListener = { type: "RemoveEventListener", event_name: string, scope: number, root: number };
-type SetText = { type: "SetText", root: number, text: string };
-type SetAttribute = { type: "SetAttribute", root: number, field: string, value: string, ns: string | undefined };
-type RemoveAttribute = { type: "RemoveAttribute", root: number, name: string };
-
-
-type DomEdit =
-  PushRoot |
-  AppendChildren |
-  ReplaceWith |
-  InsertAfter |
-  InsertBefore |
-  Remove |
-  CreateTextNode |
-  CreateElement |
-  CreateElementNs |
-  CreatePlaceholder |
-  NewEventListener |
-  RemoveEventListener |
-  SetText |
-  SetAttribute |
-  RemoveAttribute;

+ 7 - 0
packages/interpreter/src/lib.rs

@@ -0,0 +1,7 @@
+pub static INTERPRTER_JS: &str = include_str!("./interpreter.js");
+
+#[cfg(feature = "web")]
+mod bindings;
+
+#[cfg(feature = "web")]
+pub use bindings::Interpreter;

+ 0 - 499
packages/jsinterpreter/interpreter.js

@@ -1,499 +0,0 @@
-export function main() {
-    let root = window.document.getElementById("main");
-    if (root != null) {
-        window.interpreter = new Interpreter(root);
-        window.rpc.call("initialize");
-    }
-}
-export class Interpreter {
-    root;
-    stack;
-    listeners;
-    handlers;
-    lastNodeWasText;
-    nodes;
-    constructor(root) {
-        this.root = root;
-        this.stack = [root];
-        this.listeners = {};
-        this.handlers = {};
-        this.lastNodeWasText = false;
-        this.nodes = [root];
-    }
-    top() {
-        return this.stack[this.stack.length - 1];
-    }
-    pop() {
-        return this.stack.pop();
-    }
-    PushRoot(root) {
-        const node = this.nodes[root];
-        this.stack.push(node);
-    }
-    AppendChildren(many) {
-        let root = this.stack[this.stack.length - (1 + many)];
-        let to_add = this.stack.splice(this.stack.length - many);
-        for (let i = 0; i < many; i++) {
-            root.appendChild(to_add[i]);
-        }
-    }
-    ReplaceWith(root_id, m) {
-        let root = this.nodes[root_id];
-        let els = this.stack.splice(this.stack.length - m);
-        root.replaceWith(...els);
-    }
-    InsertAfter(root, n) {
-        let old = this.nodes[root];
-        let new_nodes = this.stack.splice(this.stack.length - n);
-        old.after(...new_nodes);
-    }
-    InsertBefore(root, n) {
-        let old = this.nodes[root];
-        let new_nodes = this.stack.splice(this.stack.length - n);
-        old.before(...new_nodes);
-    }
-    Remove(root) {
-        let node = this.nodes[root];
-        if (node !== undefined) {
-            node.remove();
-        }
-    }
-    CreateTextNode(text, root) {
-        // todo: make it so the types are okay
-        const node = document.createTextNode(text);
-        this.nodes[root] = node;
-        this.stack.push(node);
-    }
-    CreateElement(tag, root) {
-        const el = document.createElement(tag);
-        // el.setAttribute("data-dioxus-id", `${root}`);
-        this.nodes[root] = el;
-        this.stack.push(el);
-    }
-    CreateElementNs(tag, root, ns) {
-        let el = document.createElementNS(ns, tag);
-        this.stack.push(el);
-        this.nodes[root] = el;
-    }
-    CreatePlaceholder(root) {
-        let el = document.createElement("pre");
-        el.hidden = true;
-        this.stack.push(el);
-        this.nodes[root] = el;
-    }
-    NewEventListener(event_name, root, handler) {
-        const element = this.nodes[root];
-        element.setAttribute("data-dioxus-id", `${root}`);
-        if (this.listeners[event_name] === undefined) {
-            this.listeners[event_name] = 0;
-            this.handlers[event_name] = handler;
-            this.root.addEventListener(event_name, handler);
-        }
-        else {
-            this.listeners[event_name]++;
-        }
-    }
-    RemoveEventListener(root, event_name) {
-        const element = this.nodes[root];
-        element.removeAttribute(`data-dioxus-id`);
-        this.listeners[event_name]--;
-        if (this.listeners[event_name] === 0) {
-            this.root.removeEventListener(event_name, this.handlers[event_name]);
-            delete this.listeners[event_name];
-            delete this.handlers[event_name];
-        }
-    }
-    SetText(root, text) {
-        this.nodes[root].textContent = text;
-    }
-    SetAttribute(root, field, value, ns) {
-        const name = field;
-        const node = this.nodes[root];
-        if (ns == "style") {
-            // @ts-ignore
-            node.style[name] = value;
-        }
-        else if (ns != null || ns != undefined) {
-            node.setAttributeNS(ns, name, value);
-        }
-        else {
-            switch (name) {
-                case "value":
-                    if (value != node.value) {
-                        node.value = value;
-                    }
-                    break;
-                case "checked":
-                    node.checked = value === "true";
-                    break;
-                case "selected":
-                    node.selected = value === "true";
-                    break;
-                case "dangerous_inner_html":
-                    node.innerHTML = value;
-                    break;
-                default:
-                    // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
-                    if (value == "false" && bool_attrs.hasOwnProperty(name)) {
-                        node.removeAttribute(name);
-                    }
-                    else {
-                        node.setAttribute(name, value);
-                    }
-            }
-        }
-    }
-    RemoveAttribute(root, name) {
-        const node = this.nodes[root];
-        node.removeAttribute(name);
-        if (name === "value") {
-            node.value = "";
-        }
-        if (name === "checked") {
-            node.checked = false;
-        }
-        if (name === "selected") {
-            node.selected = false;
-        }
-    }
-    handleEdits(edits) {
-        this.stack.push(this.root);
-        for (let edit of edits) {
-            this.handleEdit(edit);
-        }
-    }
-    handleEdit(edit) {
-        switch (edit.type) {
-            case "PushRoot":
-                this.PushRoot(edit.root);
-                break;
-            case "AppendChildren":
-                this.AppendChildren(edit.many);
-                break;
-            case "ReplaceWith":
-                this.ReplaceWith(edit.root, edit.m);
-                break;
-            case "InsertAfter":
-                this.InsertAfter(edit.root, edit.n);
-                break;
-            case "InsertBefore":
-                this.InsertBefore(edit.root, edit.n);
-                break;
-            case "Remove":
-                this.Remove(edit.root);
-                break;
-            case "CreateTextNode":
-                this.CreateTextNode(edit.text, edit.root);
-                break;
-            case "CreateElement":
-                this.CreateElement(edit.tag, edit.root);
-                break;
-            case "CreateElementNs":
-                this.CreateElementNs(edit.tag, edit.root, edit.ns);
-                break;
-            case "CreatePlaceholder":
-                this.CreatePlaceholder(edit.root);
-                break;
-            case "RemoveEventListener":
-                this.RemoveEventListener(edit.root, edit.event_name);
-                break;
-            case "NewEventListener":
-                // this handler is only provided on desktop implementations since this 
-                // method is not used by the web implementation
-                let handler = (event) => {
-                    let target = event.target;
-                    if (target != null) {
-                        let realId = target.getAttribute(`data-dioxus-id`);
-                        // walk the tree to find the real element
-                        while (realId == null && target.parentElement != null) {
-                            target = target.parentElement;
-                            realId = target.getAttribute(`data-dioxus-id`);
-                        }
-                        const shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`);
-                        let contents = serialize_event(event);
-                        if (shouldPreventDefault === `on${event.type}`) {
-                            event.preventDefault();
-                        }
-                        if (event.type == "submit") {
-                            event.preventDefault();
-                        }
-                        if (event.type == "click") {
-                            event.preventDefault();
-                            if (shouldPreventDefault !== `onclick`) {
-                                if (target.tagName == "A") {
-                                    const href = target.getAttribute("href");
-                                    if (href !== "" && href !== null && href !== undefined && realId != null) {
-                                        window.rpc.call("browser_open", {
-                                            mounted_dom_id: parseInt(realId),
-                                            href
-                                        });
-                                    }
-                                }
-                            }
-                        }
-                        if (realId == null) {
-                            return;
-                        }
-                        window.rpc.call("user_event", {
-                            event: edit.event_name,
-                            mounted_dom_id: parseInt(realId),
-                            contents: contents,
-                        });
-                    }
-                };
-                this.NewEventListener(edit.event_name, edit.root, handler);
-                break;
-            case "SetText":
-                this.SetText(edit.root, edit.text);
-                break;
-            case "SetAttribute":
-                this.SetAttribute(edit.root, edit.field, edit.value, edit.ns);
-                break;
-            case "RemoveAttribute":
-                this.RemoveAttribute(edit.root, edit.name);
-                break;
-        }
-    }
-}
-function serialize_event(event) {
-    switch (event.type) {
-        case "copy":
-        case "cut":
-        case "past": {
-            return {};
-        }
-        case "compositionend":
-        case "compositionstart":
-        case "compositionupdate": {
-            let { data } = event;
-            return {
-                data,
-            };
-        }
-        case "keydown":
-        case "keypress":
-        case "keyup": {
-            let { charCode, key, altKey, ctrlKey, metaKey, keyCode, shiftKey, location, repeat, which, } = event;
-            return {
-                char_code: charCode,
-                key: key,
-                alt_key: altKey,
-                ctrl_key: ctrlKey,
-                meta_key: metaKey,
-                key_code: keyCode,
-                shift_key: shiftKey,
-                location: location,
-                repeat: repeat,
-                which: which,
-                locale: "locale",
-            };
-        }
-        case "focus":
-        case "blur": {
-            return {};
-        }
-        case "change": {
-            let target = event.target;
-            let value;
-            if (target.type === "checkbox" || target.type === "radio") {
-                value = target.checked ? "true" : "false";
-            }
-            else {
-                value = target.value ?? target.textContent;
-            }
-            return {
-                value: value,
-            };
-        }
-        case "input":
-        case "invalid":
-        case "reset":
-        case "submit": {
-            let target = event.target;
-            let value = target.value ?? target.textContent;
-            if (target.type == "checkbox") {
-                value = target.checked ? "true" : "false";
-            }
-            return {
-                value: value,
-            };
-        }
-        case "click":
-        case "contextmenu":
-        case "doubleclick":
-        case "drag":
-        case "dragend":
-        case "dragenter":
-        case "dragexit":
-        case "dragleave":
-        case "dragover":
-        case "dragstart":
-        case "drop":
-        case "mousedown":
-        case "mouseenter":
-        case "mouseleave":
-        case "mousemove":
-        case "mouseout":
-        case "mouseover":
-        case "mouseup": {
-            const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, } = event;
-            return {
-                alt_key: altKey,
-                button: button,
-                buttons: buttons,
-                client_x: clientX,
-                client_y: clientY,
-                ctrl_key: ctrlKey,
-                meta_key: metaKey,
-                page_x: pageX,
-                page_y: pageY,
-                screen_x: screenX,
-                screen_y: screenY,
-                shift_key: shiftKey,
-            };
-        }
-        case "pointerdown":
-        case "pointermove":
-        case "pointerup":
-        case "pointercancel":
-        case "gotpointercapture":
-        case "lostpointercapture":
-        case "pointerenter":
-        case "pointerleave":
-        case "pointerover":
-        case "pointerout": {
-            const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, pointerId, width, height, pressure, tangentialPressure, tiltX, tiltY, twist, pointerType, isPrimary, } = event;
-            return {
-                alt_key: altKey,
-                button: button,
-                buttons: buttons,
-                client_x: clientX,
-                client_y: clientY,
-                ctrl_key: ctrlKey,
-                meta_key: metaKey,
-                page_x: pageX,
-                page_y: pageY,
-                screen_x: screenX,
-                screen_y: screenY,
-                shift_key: shiftKey,
-                pointer_id: pointerId,
-                width: width,
-                height: height,
-                pressure: pressure,
-                tangential_pressure: tangentialPressure,
-                tilt_x: tiltX,
-                tilt_y: tiltY,
-                twist: twist,
-                pointer_type: pointerType,
-                is_primary: isPrimary,
-            };
-        }
-        case "select": {
-            return {};
-        }
-        case "touchcancel":
-        case "touchend":
-        case "touchmove":
-        case "touchstart": {
-            const { altKey, ctrlKey, metaKey, shiftKey, } = event;
-            return {
-                // changed_touches: event.changedTouches,
-                // target_touches: event.targetTouches,
-                // touches: event.touches,
-                alt_key: altKey,
-                ctrl_key: ctrlKey,
-                meta_key: metaKey,
-                shift_key: shiftKey,
-            };
-        }
-        case "scroll": {
-            return {};
-        }
-        case "wheel": {
-            const { deltaX, deltaY, deltaZ, deltaMode, } = event;
-            return {
-                delta_x: deltaX,
-                delta_y: deltaY,
-                delta_z: deltaZ,
-                delta_mode: deltaMode,
-            };
-        }
-        case "animationstart":
-        case "animationend":
-        case "animationiteration": {
-            const { animationName, elapsedTime, pseudoElement, } = event;
-            return {
-                animation_name: animationName,
-                elapsed_time: elapsedTime,
-                pseudo_element: pseudoElement,
-            };
-        }
-        case "transitionend": {
-            const { propertyName, elapsedTime, pseudoElement, } = event;
-            return {
-                property_name: propertyName,
-                elapsed_time: elapsedTime,
-                pseudo_element: pseudoElement,
-            };
-        }
-        case "abort":
-        case "canplay":
-        case "canplaythrough":
-        case "durationchange":
-        case "emptied":
-        case "encrypted":
-        case "ended":
-        case "error":
-        case "loadeddata":
-        case "loadedmetadata":
-        case "loadstart":
-        case "pause":
-        case "play":
-        case "playing":
-        case "progress":
-        case "ratechange":
-        case "seeked":
-        case "seeking":
-        case "stalled":
-        case "suspend":
-        case "timeupdate":
-        case "volumechange":
-        case "waiting": {
-            return {};
-        }
-        case "toggle": {
-            return {};
-        }
-        default: {
-            return {};
-        }
-    }
-}
-const bool_attrs = {
-    allowfullscreen: true,
-    allowpaymentrequest: true,
-    async: true,
-    autofocus: true,
-    autoplay: true,
-    checked: true,
-    controls: true,
-    default: true,
-    defer: true,
-    disabled: true,
-    formnovalidate: true,
-    hidden: true,
-    ismap: true,
-    itemscope: true,
-    loop: true,
-    multiple: true,
-    muted: true,
-    nomodule: true,
-    novalidate: true,
-    open: true,
-    playsinline: true,
-    readonly: true,
-    required: true,
-    reversed: true,
-    selected: true,
-    truespeed: true,
-};

+ 0 - 13
packages/jsinterpreter/tsconfig.json

@@ -1,13 +0,0 @@
-{
-    "compilerOptions": {
-        "target": "ESNext",
-        "module": "ES2015",
-        "lib": [
-            "es2015",
-            "es5",
-            "es6",
-            "dom"
-        ],
-        "strict": true,
-    }
-}

+ 22 - 1
packages/mobile/CHANGELOG.md

@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## Unreleased
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 1 commit contributed to the release over the course of 7 calendar days.
+ - 0 commits where understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' where seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+    - Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d))
+</details>
+
 ## v0.0.3 (2022-01-08)
 
 ### Documentation
@@ -28,7 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 <csr-read-only-do-not-edit/>
 
- - 32 commits contributed to the release over the course of 151 calendar days.
+ - 33 commits contributed to the release over the course of 151 calendar days.
  - 21 commits where understood as [conventional](https://www.conventionalcommits.org).
  - 0 issues like '(#ID)' where seen in commit messages
 
@@ -39,6 +59,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 <details><summary>view details</summary>
 
  * **Uncategorized**
+    - Release dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`b804c69`](https://github.comgit//DioxusLabs/dioxus/commit/b804c691d5ade4776390bb3d334cc9cd8efa4a49))
     - Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111))
     - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
     - Merge pull request #74 from mrxiaozhuox/master ([`47056fd`](https://github.comgit//DioxusLabs/dioxus/commit/47056fda4577bcbdaa2a6f63d82eec876e5a5aee))

+ 6 - 1
packages/mobile/Cargo.toml

@@ -12,4 +12,9 @@ license = "MIT/Apache-2.0"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-dioxus-desktop = { path = "../desktop", version ="^0.1.5"}
+dioxus-desktop = { path = "../desktop", version = "^0.1.6" }
+
+[lib]
+doctest = false
+# tests suspended until package ready
+test = false

+ 7 - 0
packages/mobile/Makefile.toml

@@ -0,0 +1,7 @@
+[tasks.test]
+command = "cargo"
+args = [
+  "test",
+  "--no-run",
+]
+

+ 57 - 1
packages/router/CHANGELOG.md

@@ -5,6 +5,54 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## Unreleased
+
+### New Features
+
+ - <csr-id-e24957fc191476184816fe5dee249f691170d4ae/> enable use_router
+ - <csr-id-29ed7ebece26e9d53925af55f2f34a8fd8241405/> connect an onchange listener
+ - <csr-id-d2372717bd01fcff50af0572360e3f763d4c869d/> flatten props attrs
+
+### Bug Fixes
+
+ - <csr-id-5ee9d6c4348a2f51adac827f715fb138918d1dc6/> attach router listener to subscriber list
+ - <csr-id-a21e7d4dd168129da06f535f9dc4b1de724617cb/> use_route should subscribe to changes to the route
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 18 commits contributed to the release over the course of 14 calendar days.
+ - 7 commits where understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' where seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+    - Merge pull request #169 from DioxusLabs/jk/router-userouter ([`3509602`](https://github.comgit//DioxusLabs/dioxus/commit/3509602c0bcd327a33bc8c95896775e24751da1a))
+    - enable use_router ([`e24957f`](https://github.comgit//DioxusLabs/dioxus/commit/e24957fc191476184816fe5dee249f691170d4ae))
+    - add docs to router UseRouteListener ([`79e0993`](https://github.comgit//DioxusLabs/dioxus/commit/79e09934aa685d03d6e0b323723bc1cd537d74d9))
+    - Merge pull request #166 from DioxusLabs/jk/default-assets-desktop ([`ccbb955`](https://github.comgit//DioxusLabs/dioxus/commit/ccbb955b7b24bd4e1c5aa40e813a2872ae474a69))
+    - rustfmt ([`9da46eb`](https://github.comgit//DioxusLabs/dioxus/commit/9da46eb7bc207997ca7779c58fcb2a9645dfa9d0))
+    - Make log message in Link component trace level, not debug ([`72c6bb3`](https://github.comgit//DioxusLabs/dioxus/commit/72c6bb3d0b7253f084f7e3bcf55d458cb4adeedb))
+    - attach router listener to subscriber list ([`5ee9d6c`](https://github.comgit//DioxusLabs/dioxus/commit/5ee9d6c4348a2f51adac827f715fb138918d1dc6))
+    - connect an onchange listener ([`29ed7eb`](https://github.comgit//DioxusLabs/dioxus/commit/29ed7ebece26e9d53925af55f2f34a8fd8241405))
+    - use_route should subscribe to changes to the route ([`a21e7d4`](https://github.comgit//DioxusLabs/dioxus/commit/a21e7d4dd168129da06f535f9dc4b1de724617cb))
+    - Merge pull request #95 from DioxusLabs/jk/filedragindrop ([`ca0dd4a`](https://github.comgit//DioxusLabs/dioxus/commit/ca0dd4aa7192d483a195d420363f39d771f3e471))
+    - Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d))
+    - flatten props attrs ([`d237271`](https://github.comgit//DioxusLabs/dioxus/commit/d2372717bd01fcff50af0572360e3f763d4c869d))
+    - Merge pull request #108 from DioxusLabs/jk/fstring-component-fields ([`f4132d1`](https://github.comgit//DioxusLabs/dioxus/commit/f4132d1874f7495049fac23ba0a022ac137ad74f))
+    - Enable clippy ([`b6903bf`](https://github.comgit//DioxusLabs/dioxus/commit/b6903bf558bc7a3d0fe6794a137c44fca0957d11))
+    - Merge pull request #138 from mrxiaozhuox/master ([`8c7473d`](https://github.comgit//DioxusLabs/dioxus/commit/8c7473d1943dd133f388ec36116c9d8295861b97))
+    - Add a warning when Link it called outside of a Router context ([`6408058`](https://github.comgit//DioxusLabs/dioxus/commit/64080588d02c54a1d380116cbecdd17de16d2392))
+    - Merge pull request #133 from mrxiaozhuox/master ([`887f69d`](https://github.comgit//DioxusLabs/dioxus/commit/887f69d5b47bdcde4fe0eab094c0cd0de23e4f3f))
+    - Fix handling of re-renders in the Router ([`81c094e`](https://github.comgit//DioxusLabs/dioxus/commit/81c094ed29fcdc5c6099492dd6ab09a59b79252c))
+</details>
+
 ## v0.1.0 (2022-01-08)
 
 ### Documentation
@@ -33,7 +81,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 <csr-read-only-do-not-edit/>
 
- - 42 commits contributed to the release over the course of 352 calendar days.
+ - 50 commits contributed to the release over the course of 352 calendar days.
  - 35 commits where understood as [conventional](https://www.conventionalcommits.org).
  - 0 issues like '(#ID)' where seen in commit messages
 
@@ -44,8 +92,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 <details><summary>view details</summary>
 
  * **Uncategorized**
+    - Release dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`b804c69`](https://github.comgit//DioxusLabs/dioxus/commit/b804c691d5ade4776390bb3d334cc9cd8efa4a49))
+    - More WIP router implementation ([`e06eac1`](https://github.comgit//DioxusLabs/dioxus/commit/e06eac1ce5c6bb0a6680482574d82b16a141a626))
+    - Implement UseRoute segment method ([`c9408da`](https://github.comgit//DioxusLabs/dioxus/commit/c9408da7310423b1676fab6d41635f9a8000d89e))
     - Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111))
+    - Implement router matching for path parameters ([`f8a7e1c`](https://github.comgit//DioxusLabs/dioxus/commit/f8a7e1cd8255fd7d0116384247e0e305bb73bf3d))
+    - Commit WIP on router ([`3c6142f`](https://github.comgit//DioxusLabs/dioxus/commit/3c6142fb9d8b5f715adf0fb30c2f3534b9ecd923))
+    - Add more trace messages to the RouterService code ([`3a5b417`](https://github.comgit//DioxusLabs/dioxus/commit/3a5b417ad1639a31e9aac9f41b05d8b3f074b128))
     - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
+    - Fix typo in RouterService struct's "registered_routes" field name ([`d367e0f`](https://github.comgit//DioxusLabs/dioxus/commit/d367e0f89f1b31484bde42efaf10c3531aed1d64))
+    - Add title prop to Link ([`e22ba5b`](https://github.comgit//DioxusLabs/dioxus/commit/e22ba5b1e52fe00e08e0b6ac0abae29b6068b8f0))
     - add prevent default attribute and upgrade router ([`427b126`](https://github.comgit//DioxusLabs/dioxus/commit/427b126bc17336d5d14d56eb7fddb8e07752495f))
     - memoize dom in the prescence of identical components ([`cb2782b`](https://github.comgit//DioxusLabs/dioxus/commit/cb2782b4bb34cdaadfff590bfee930ae3ac6536c))
     - bump all versions ([`4f92ba4`](https://github.comgit//DioxusLabs/dioxus/commit/4f92ba41602d706449c1bddabd49829873ee72eb))

+ 13 - 4
packages/router/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "dioxus-router"
-version = "0.1.0"
+version = "0.1.1"
 edition = "2018"
 description = "Dioxus VirtualDOM renderer for the web browser using websys"
 license = "MIT/Apache-2.0"
@@ -11,9 +11,9 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-dioxus-core = { path = "../core", version ="^0.1.7", default-features = false }
-dioxus-html = { path = "../html", version ="^0.1.4", default-features = false }
-dioxus-core-macro = { path = "../core-macro", version ="^0.1.6"}
+dioxus-core = { path = "../core", version = "^0.1.9", default-features = false }
+dioxus-html = { path = "../html", version = "^0.1.6", default-features = false }
+dioxus-core-macro = { path = "../core-macro", version = "^0.1.7" }
 
 serde = "1"
 url = "2.2.2"
@@ -43,9 +43,18 @@ web = ["web-sys", "gloo", "js-sys", "wasm-bindgen"]
 desktop = []
 mobile = []
 derive = []
+wasm_test = []
 
 [dev-dependencies]
 console_error_panic_hook = "0.1.7"
 dioxus-web = { path = "../web" }
 log = "0.4.14"
 wasm-logger = "0.2.0"
+wasm-bindgen-test = "0.3"
+gloo-utils = "0.1.2"
+
+[dev-dependencies.web-sys]
+version = "0.3"
+features = [
+    "Document",
+]

+ 10 - 0
packages/router/Makefile.toml

@@ -0,0 +1,10 @@
+[tasks.test-with-browser]
+extend = "core::wasm-pack-base"
+command = "wasm-pack"
+args = [
+  "test",
+  "@@split(DIOXUS_TEST_FLAGS, )",
+  "--",
+  "--features",
+  "${DIOXUS_TEST_FEATURES}",
+]

+ 1 - 1
packages/router/src/components/link.rs

@@ -41,7 +41,7 @@ pub struct LinkProps<'a> {
 }
 
 pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
-    log::debug!("render Link to {}", cx.props.to);
+    // log::trace!("render Link to {}", cx.props.to);
     if let Some(service) = cx.consume_context::<RouterService>() {
         return cx.render(rsx! {
             a {

+ 2 - 2
packages/router/src/components/route.rs

@@ -30,7 +30,7 @@ pub fn Route<'a>(cx: Scope<'a, RouteProps<'a>>) -> Element {
             Some(ctx) => ctx.total_route.to_string(),
             None => cx.props.to.to_string(),
         };
-        log::trace!("total route for {} is {}", cx.props.to, total_route);
+        // log::trace!("total route for {} is {}", cx.props.to, total_route);
 
         // provide our route context
         let route_context = cx.provide_context(RouteContext {
@@ -48,7 +48,7 @@ pub fn Route<'a>(cx: Scope<'a, RouteProps<'a>>) -> Element {
         Some(RouteInner {})
     });
 
-    log::trace!("Checking route {}", cx.props.to);
+    // log::trace!("Checking route {}", cx.props.to);
 
     if router_root.should_render(cx.scope_id()) {
         cx.render(rsx!(&cx.props.children))

+ 1 - 0
packages/router/src/components/router.rs

@@ -17,6 +17,7 @@ pub struct RouterProps<'a> {
 
 #[allow(non_snake_case)]
 pub fn Router<'a>(cx: Scope<'a, RouterProps<'a>>) -> Element {
+    log::debug!("running router {:?}", cx.scope_id());
     let svc = cx.use_hook(|_| {
         let update = cx.schedule_update_any();
         cx.provide_context(RouterService::new(update, cx.scope_id()))

+ 38 - 7
packages/router/src/hooks/use_route.rs

@@ -1,4 +1,4 @@
-use dioxus_core::ScopeState;
+use dioxus_core::{ScopeId, ScopeState};
 use gloo::history::{HistoryResult, Location};
 use serde::de::DeserializeOwned;
 use std::{rc::Rc, str::FromStr};
@@ -8,6 +8,7 @@ use crate::RouterService;
 /// This struct provides is a wrapper around the internal router
 /// implementation, with methods for getting information about the current
 /// route.
+#[derive(Clone)]
 pub struct UseRoute {
     router: Rc<RouterService>,
 }
@@ -74,10 +75,40 @@ impl UseRoute {
 /// This hook provides access to information about the current location in the
 /// context of a [`Router`]. If this function is called outside of a `Router`
 /// component it will panic.
-pub fn use_route(cx: &ScopeState) -> UseRoute {
-    let router = cx
-        .consume_context::<RouterService>()
-        .expect("Cannot call use_route outside the scope of a Router component")
-        .clone();
-    UseRoute { router }
+pub fn use_route(cx: &ScopeState) -> &UseRoute {
+    &cx.use_hook(|_| {
+        let router = cx
+            .consume_context::<RouterService>()
+            .expect("Cannot call use_route outside the scope of a Router component");
+
+        router.subscribe_onchange(cx.scope_id());
+
+        UseRouteListener {
+            router: UseRoute { router },
+            scope: cx.scope_id(),
+        }
+    })
+    .router
+}
+
+// The entire purpose of this struct is to unubscribe this component when it is unmounted.
+// The UseRoute can be cloned into async contexts, so we can't rely on its drop to unubscribe.
+// Instead, we hide the drop implementation on this private type exclusive to the hook,
+// and reveal our cached version of UseRoute to the component.
+struct UseRouteListener {
+    router: UseRoute,
+    scope: ScopeId,
+}
+impl Drop for UseRouteListener {
+    fn drop(&mut self) {
+        self.router.router.unsubscribe_onchange(self.scope)
+    }
+}
+
+/// This hook provides access to the `RouterService` for the app.
+pub fn use_router(cx: &ScopeState) -> &RouterService {
+    cx.use_hook(|_| {
+        cx.consume_context::<RouterService>()
+            .expect("Cannot call use_route outside the scope of a Router component")
+    })
 }

+ 1 - 6
packages/router/src/platform/mod.rs

@@ -1,9 +1,4 @@
 pub trait RouterProvider {
     fn get_current_route(&self) -> String;
-    fn subscribe_to_route_changes(&self, callback: Box<dyn Fn(String)>);
-}
-
-enum RouteChange {
-    LinkTo(String),
-    Back(String),
+    fn listen(&self, callback: Box<dyn Fn()>);
 }

+ 23 - 35
packages/router/src/service.rs

@@ -1,19 +1,24 @@
 use gloo::history::{BrowserHistory, History, HistoryListener, Location};
 use std::{
     cell::{Cell, Ref, RefCell},
-    collections::HashMap,
+    collections::{HashMap, HashSet},
     rc::Rc,
 };
 
 use dioxus_core::ScopeId;
 
+use crate::platform::RouterProvider;
+
 pub struct RouterService {
     pub(crate) regen_route: Rc<dyn Fn(ScopeId)>,
     pub(crate) pending_events: Rc<RefCell<Vec<RouteEvent>>>,
-    history: Rc<RefCell<BrowserHistory>>,
     slots: Rc<RefCell<Vec<(ScopeId, String)>>>,
+    onchange_listeners: Rc<RefCell<HashSet<ScopeId>>>,
     root_found: Rc<Cell<Option<ScopeId>>>,
     cur_path_params: Rc<RefCell<HashMap<String, String>>>,
+
+    // history: Rc<dyn RouterProvider>,
+    history: Rc<RefCell<BrowserHistory>>,
     listener: HistoryListener,
 }
 
@@ -42,6 +47,7 @@ impl RouterService {
         let location = history.location();
         let path = location.path();
 
+        let onchange_listeners = Rc::new(RefCell::new(HashSet::new()));
         let slots: Rc<RefCell<Vec<(ScopeId, String)>>> = Default::default();
         let pending_events: Rc<RefCell<Vec<RouteEvent>>> = Default::default();
         let root_found = Rc::new(Cell::new(None));
@@ -51,14 +57,18 @@ impl RouterService {
             let regen_route = regen_route.clone();
             let root_found = root_found.clone();
             let slots = slots.clone();
+            let onchange_listeners = onchange_listeners.clone();
             move || {
                 root_found.set(None);
                 // checking if the route is valid is cheap, so we do it
                 for (slot, root) in slots.borrow_mut().iter().rev() {
-                    log::trace!("regenerating slot {:?} for root '{}'", slot, root);
                     regen_route(*slot);
                 }
 
+                for listener in onchange_listeners.borrow_mut().iter() {
+                    regen_route(*listener);
+                }
+
                 // also regenerate the root
                 regen_route(root_scope);
 
@@ -73,36 +83,30 @@ impl RouterService {
             regen_route,
             slots,
             pending_events,
+            onchange_listeners,
             cur_path_params: Rc::new(RefCell::new(HashMap::new())),
         }
     }
 
     pub fn push_route(&self, route: &str) {
-        log::trace!("Pushing route: {}", route);
         self.history.borrow_mut().push(route);
     }
 
     pub fn register_total_route(&self, route: String, scope: ScopeId, fallback: bool) {
         let clean = clean_route(route);
-        log::trace!("Registered route '{}' with scope id {:?}", clean, scope);
         self.slots.borrow_mut().push((scope, clean));
     }
 
     pub fn should_render(&self, scope: ScopeId) -> bool {
-        log::trace!("Should render scope id {:?}?", scope);
         if let Some(root_id) = self.root_found.get() {
-            log::trace!("  we already found a root with scope id {:?}", root_id);
             if root_id == scope {
-                log::trace!("    yes - it's a match");
                 return true;
             }
-            log::trace!("    no - it's not a match");
             return false;
         }
 
         let location = self.history.borrow().location();
         let path = location.path();
-        log::trace!("  current path is '{}'", path);
 
         let roots = self.slots.borrow();
 
@@ -111,23 +115,15 @@ impl RouterService {
         // fallback logic
         match root {
             Some((id, route)) => {
-                log::trace!(
-                    "  matched given scope id {:?} with route root '{}'",
-                    scope,
-                    route,
-                );
                 if let Some(params) = route_matches_path(route, path) {
-                    log::trace!("    and it matches the current path '{}'", path);
                     self.root_found.set(Some(*id));
                     *self.cur_path_params.borrow_mut() = params;
                     true
                 } else {
                     if route == "" {
-                        log::trace!("    and the route is the root, so we will use that without a better match");
                         self.root_found.set(Some(*id));
                         true
                     } else {
-                        log::trace!("    and the route '{}' is not the root nor does it match the current path", route);
                         false
                     }
                 }
@@ -143,6 +139,14 @@ impl RouterService {
     pub fn current_path_params(&self) -> Ref<HashMap<String, String>> {
         self.cur_path_params.borrow()
     }
+
+    pub fn subscribe_onchange(&self, id: ScopeId) {
+        self.onchange_listeners.borrow_mut().insert(id);
+    }
+
+    pub fn unsubscribe_onchange(&self, id: ScopeId) {
+        self.onchange_listeners.borrow_mut().remove(&id);
+    }
 }
 
 fn clean_route(route: String) -> String {
@@ -163,36 +167,20 @@ fn route_matches_path(route: &str, path: &str) -> Option<HashMap<String, String>
     let route_pieces = route.split('/').collect::<Vec<_>>();
     let path_pieces = clean_path(path).split('/').collect::<Vec<_>>();
 
-    log::trace!(
-        "  checking route pieces {:?} vs path pieces {:?}",
-        route_pieces,
-        path_pieces,
-    );
-
     if route_pieces.len() != path_pieces.len() {
-        log::trace!("    the routes are different lengths");
         return None;
     }
 
     let mut matches = HashMap::new();
     for (i, r) in route_pieces.iter().enumerate() {
-        log::trace!("    checking route piece '{}' vs path", r);
         // If this is a parameter then it matches as long as there's
         // _any_thing in that spot in the path.
         if r.starts_with(':') {
-            log::trace!(
-                "      route piece '{}' starts with a colon so it matches anything",
-                r,
-            );
             let param = &r[1..];
             matches.insert(param.to_string(), path_pieces[i].to_string());
             continue;
         }
-        log::trace!(
-            "      route piece '{}' must be an exact match for path piece '{}'",
-            r,
-            path_pieces[i],
-        );
+
         if path_pieces[i] != *r {
             return None;
         }

+ 63 - 0
packages/router/tests/route.rs

@@ -0,0 +1,63 @@
+#![cfg(target_arch = "wasm32")]
+
+use dioxus_core::prelude::*;
+use dioxus_core_macro::*;
+use dioxus_html as dioxus_elements;
+use dioxus_router::*;
+use gloo_utils::document;
+use serde::{Deserialize, Serialize};
+use wasm_bindgen_test::*;
+
+wasm_bindgen_test_configure!(run_in_browser);
+
+#[wasm_bindgen_test]
+fn simple_test() {
+    fn main() {
+        console_error_panic_hook::set_once();
+        wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
+        dioxus_web::launch(APP);
+    }
+
+    static APP: Component = |cx| {
+        cx.render(rsx! {
+            Router {
+                onchange: move |route| log::info!("route changed to {}", route),
+                Route { to: "/", Home {} }
+                Route { to: "blog"
+                    Route { to: "/", BlogList {} }
+                    Route { to: ":id", BlogPost {} }
+                }
+            }
+        })
+    };
+
+    fn Home(cx: Scope) -> Element {
+        cx.render(rsx! {
+            div {
+                h1 { "Home" }
+            }
+        })
+    }
+
+    fn BlogList(cx: Scope) -> Element {
+        cx.render(rsx! {
+            div {
+
+            }
+        })
+    }
+
+    fn BlogPost(cx: Scope) -> Element {
+        let id = use_route(&cx).segment::<usize>("id")?;
+
+        cx.render(rsx! {
+            div {
+
+            }
+        })
+    }
+
+    main();
+
+    let element = gloo_utils::document();
+}

+ 11 - 0
packages/router/webdriver.json

@@ -0,0 +1,11 @@
+{
+    "moz:firefoxOptions": {
+      "binary": "/usr/bin/firefox",
+      "prefs": {
+        "media.navigator.streams.fake": true,
+        "media.navigator.permission.disabled": true
+      },
+      "args": []
+    }
+  }
+  

+ 30 - 1
packages/ssr/CHANGELOG.md

@@ -5,6 +5,34 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## Unreleased
+
+### Bug Fixes
+
+ - <csr-id-255f58af63e002339b02bf91c5b85cc5ec917428/> ssr respects bool attrs
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 5 commits contributed to the release over the course of 21 calendar days.
+ - 1 commit where understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' where seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+    - Merge pull request #111 from DioxusLabs/jk/props-attrs ([`0369fe7`](https://github.comgit//DioxusLabs/dioxus/commit/0369fe72fb247409da300a54ef11ba9155d0efb3))
+    - Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d))
+    - Merge pull request #108 from DioxusLabs/jk/fstring-component-fields ([`f4132d1`](https://github.comgit//DioxusLabs/dioxus/commit/f4132d1874f7495049fac23ba0a022ac137ad74f))
+    - ssr respects bool attrs ([`255f58a`](https://github.comgit//DioxusLabs/dioxus/commit/255f58af63e002339b02bf91c5b85cc5ec917428))
+    - Enable clippy ([`b6903bf`](https://github.comgit//DioxusLabs/dioxus/commit/b6903bf558bc7a3d0fe6794a137c44fca0957d11))
+</details>
+
 ## v0.1.2 (2022-01-08)
 
 ### Documentation
@@ -47,7 +75,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 <csr-read-only-do-not-edit/>
 
- - 84 commits contributed to the release over the course of 357 calendar days.
+ - 85 commits contributed to the release over the course of 357 calendar days.
  - 75 commits where understood as [conventional](https://www.conventionalcommits.org).
  - 0 issues like '(#ID)' where seen in commit messages
 
@@ -58,6 +86,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 <details><summary>view details</summary>
 
  * **Uncategorized**
+    - Release dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`b804c69`](https://github.comgit//DioxusLabs/dioxus/commit/b804c691d5ade4776390bb3d334cc9cd8efa4a49))
     - Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111))
     - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
     - Merge pull request #74 from mrxiaozhuox/master ([`47056fd`](https://github.comgit//DioxusLabs/dioxus/commit/47056fda4577bcbdaa2a6f63d82eec876e5a5aee))

+ 2 - 2
packages/ssr/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "dioxus-ssr"
-version = "0.1.2"
+version = "0.1.3"
 authors = ["Jonathan Kelley"]
 edition = "2018"
 description = "Dioxus render-to-string"
@@ -13,7 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-dioxus-core = { path = "../core", version ="^0.1.7", features = ["serialize"] }
+dioxus-core = { path = "../core", version = "^0.1.9", features = ["serialize"] }
 
 
 [dev-dependencies]

+ 57 - 3
packages/web/CHANGELOG.md

@@ -5,6 +5,60 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## Unreleased
+
+### Documentation
+
+ - <csr-id-a239d2ba6ac7f1f3d09de16c022ce8ca52cf0f63/> fix web doc example and use &mut for builders everywhere
+
+### New Features
+
+ - <csr-id-1406c9020bb3102635737dc2d13e5b3171499a18/> add panic hook by default
+ - <csr-id-95e93ed0bcf6c69990f4cf3c6448b2bf5da96c36/> remove dioxus id on non-event elements
+ - <csr-id-05331ddd8033f6997d4916179b62f4d62f832988/> wire up both desktop and web
+ - <csr-id-8f4aa84f1a4f2443b34d81ee42490564e168de53/> bool attr white list
+
+### Bug Fixes
+
+ - <csr-id-ae676d9d816fcbf88d110157124443a5b3aef9eb/> webconfig should take &mut self
+ - <csr-id-92561612c727e73356d7d36e16af39aacf02a56d/> format code
+ - <csr-id-c1a5d4e11f5277e6ee644f670dc6fdd68f2202f1/> ssr + hydration event listeners
+ - <csr-id-21232285d9d84168d9003969ddd254fc22951e4b/> add exclusion list
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 17 commits contributed to the release over the course of 21 calendar days.
+ - 10 commits where understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' where seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+    - webconfig should take &mut self ([`ae676d9`](https://github.comgit//DioxusLabs/dioxus/commit/ae676d9d816fcbf88d110157124443a5b3aef9eb))
+    - fix web doc example and use &mut for builders everywhere ([`a239d2b`](https://github.comgit//DioxusLabs/dioxus/commit/a239d2ba6ac7f1f3d09de16c022ce8ca52cf0f63))
+    - add panic hook by default ([`1406c90`](https://github.comgit//DioxusLabs/dioxus/commit/1406c9020bb3102635737dc2d13e5b3171499a18))
+    - Merge pull request #111 from DioxusLabs/jk/props-attrs ([`0369fe7`](https://github.comgit//DioxusLabs/dioxus/commit/0369fe72fb247409da300a54ef11ba9155d0efb3))
+    - Merge pull request #113 from DioxusLabs/jk/desktop-cursor-jump ([`20a2940`](https://github.comgit//DioxusLabs/dioxus/commit/20a29409b22510b001fdbee349724adb7b44d401))
+    - remove dioxus id on non-event elements ([`95e93ed`](https://github.comgit//DioxusLabs/dioxus/commit/95e93ed0bcf6c69990f4cf3c6448b2bf5da96c36))
+    - feat(events:focus): add missing `onfocusin` event ([`007d06d`](https://github.comgit//DioxusLabs/dioxus/commit/007d06d602f1adfaa51c87ec89b2afe90d8cdef9))
+    - feat(example:todomvc): add editing support ([`9849f68`](https://github.comgit//DioxusLabs/dioxus/commit/9849f68f257200fac511c048bfb1a076243b86d3))
+    - Merge pull request #101 from alexkirsz/ci ([`29bf424`](https://github.comgit//DioxusLabs/dioxus/commit/29bf424b0976b95ff645bb128d0e758cf0186614))
+    - Merge pull request #139 from DioxusLabs/jk/provide-context-any ([`70f2ef4`](https://github.comgit//DioxusLabs/dioxus/commit/70f2ef43db5b6737bd9bcbfc1aa21c834ce4b395))
+    - Merge branch 'master' into jk/unify ([`824defa`](https://github.comgit//DioxusLabs/dioxus/commit/824defa2dbcc16d66588b3976699d89b65a8a068))
+    - wire up both desktop and web ([`05331dd`](https://github.comgit//DioxusLabs/dioxus/commit/05331ddd8033f6997d4916179b62f4d62f832988))
+    - format code ([`9256161`](https://github.comgit//DioxusLabs/dioxus/commit/92561612c727e73356d7d36e16af39aacf02a56d))
+    - ssr + hydration event listeners ([`c1a5d4e`](https://github.comgit//DioxusLabs/dioxus/commit/c1a5d4e11f5277e6ee644f670dc6fdd68f2202f1))
+    - web now links against the js interprter code ([`10db6ad`](https://github.comgit//DioxusLabs/dioxus/commit/10db6ad65bd23dabf60d0afbe6ff82cda8220a8b))
+    - bool attr white list ([`8f4aa84`](https://github.comgit//DioxusLabs/dioxus/commit/8f4aa84f1a4f2443b34d81ee42490564e168de53))
+    - add exclusion list ([`2123228`](https://github.comgit//DioxusLabs/dioxus/commit/21232285d9d84168d9003969ddd254fc22951e4b))
+</details>
+
 ## v0.0.4 (2022-01-08)
 
 ### Documentation
@@ -84,7 +138,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 <csr-read-only-do-not-edit/>
 
  - 203 commits contributed to the release over the course of 358 calendar days.
- - 190 commits where understood as [conventional](https://www.conventionalcommits.org).
+ - 189 commits where understood as [conventional](https://www.conventionalcommits.org).
  - 0 issues like '(#ID)' where seen in commit messages
 
 ### Commit Details
@@ -94,10 +148,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 <details><summary>view details</summary>
 
  * **Uncategorized**
+    - Release dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`b804c69`](https://github.comgit//DioxusLabs/dioxus/commit/b804c691d5ade4776390bb3d334cc9cd8efa4a49))
+    - handle bool attrs properly ([`8d685f4`](https://github.comgit//DioxusLabs/dioxus/commit/8d685f40b7e0ef6521c60310d8687291e9b9c48a))
     - Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111))
     - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
-    - add exclusion list ([`2123228`](https://github.comgit//DioxusLabs/dioxus/commit/21232285d9d84168d9003969ddd254fc22951e4b))
-    - handle bool attrs properly ([`8d685f4`](https://github.comgit//DioxusLabs/dioxus/commit/8d685f40b7e0ef6521c60310d8687291e9b9c48a))
     - Merge pull request #74 from mrxiaozhuox/master ([`47056fd`](https://github.comgit//DioxusLabs/dioxus/commit/47056fda4577bcbdaa2a6f63d82eec876e5a5aee))
     - Merge pull request #80 from DioxusLabs/jk/router2dotoh ([`cdc2d8e`](https://github.comgit//DioxusLabs/dioxus/commit/cdc2d8ec6d123245c2ea5f6d10af02b6a6833994))
     - memoize dom in the prescence of identical components ([`cb2782b`](https://github.comgit//DioxusLabs/dioxus/commit/cb2782b4bb34cdaadfff590bfee930ae3ac6536c))

+ 14 - 13
packages/web/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "dioxus-web"
-version = "0.0.4"
+version = "0.0.5"
 authors = ["Jonathan Kelley"]
 edition = "2018"
 description = "Dioxus VirtualDOM renderer for the web browser using websys"
@@ -11,26 +11,27 @@ documentation = "https://dioxuslabs.com"
 keywords = ["dom", "ui", "gui", "react", "wasm"]
 
 [dependencies]
-dioxus-core = { path = "../core", version = "^0.1.7" }
-dioxus-html = { path = "../html", version = "^0.1.4" }
-js-sys = "0.3"
-wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] }
+dioxus-core = { path = "../core", version = "^0.1.9" }
+dioxus-html = { path = "../html", version = "^0.1.6" }
+js-sys = "0.3.56"
+wasm-bindgen = { version = "0.2.79", features = ["enable-interning"] }
 lazy_static = "1.4.0"
-wasm-bindgen-futures = "0.4.20"
+wasm-bindgen-futures = "0.4.29"
 log = { version = "0.4.14", features = ["release_max_level_off"] }
 fxhash = "0.2.1"
 wasm-logger = "0.2.0"
 console_error_panic_hook = { version = "0.1.7", optional = true }
-wasm-bindgen-test = "0.3.21"
-once_cell = "1.8"
+wasm-bindgen-test = "0.3.29"
+once_cell = "1.9.0"
 async-channel = "1.6.1"
-anyhow = "1.0"
-gloo-timers = { version = "0.2.1", features = ["futures"] }
-futures-util = "0.3.15"
+anyhow = "1.0.53"
+gloo-timers = { version = "0.2.3", features = ["futures"] }
+futures-util = "0.3.19"
 smallstr = "0.2.0"
+dioxus-interpreter-js = { path = "../interpreter", version = "^0.0.0", features = ["web"] }
 
 [dependencies.web-sys]
-version = "0.3.51"
+version = "0.3.56"
 features = [
     "Comment",
     "Attr",
@@ -75,5 +76,5 @@ panic_hook = ["console_error_panic_hook"]
 
 [dev-dependencies]
 dioxus-core-macro = { path = "../core-macro" }
-wasm-bindgen-test = "0.3.28"
+wasm-bindgen-test = "0.3.29"
 dioxus-ssr = { path = "../ssr" }

+ 48 - 54
packages/web/src/dom.rs

@@ -7,8 +7,8 @@
 //! - tests to ensure dyn_into works for various event types.
 //! - Partial delegation?>
 
-use crate::bindings::Interpreter;
 use dioxus_core::{DomEdit, ElementId, SchedulerMsg, UserEvent};
+use dioxus_interpreter_js::Interpreter;
 use js_sys::Function;
 use std::{any::Any, rc::Rc, sync::Arc};
 use wasm_bindgen::{closure::Closure, JsCast};
@@ -28,17 +28,54 @@ impl WebsysDom {
     pub fn new(cfg: WebConfig, sender_callback: Rc<dyn Fn(SchedulerMsg)>) -> Self {
         // eventually, we just want to let the interpreter do all the work of decoding events into our event type
         let callback: Box<dyn FnMut(&Event)> = Box::new(move |event: &web_sys::Event| {
-            if let Ok(synthetic_event) = decode_trigger(event) {
+            let mut target = event
+                .target()
+                .expect("missing target")
+                .dyn_into::<Element>()
+                .expect("not a valid element");
+
+            let typ = event.type_();
+
+            let decoded: anyhow::Result<UserEvent> = loop {
+                match target.get_attribute("data-dioxus-id").map(|f| f.parse()) {
+                    Some(Ok(id)) => {
+                        break Ok(UserEvent {
+                            name: event_name_from_typ(&typ),
+                            data: virtual_event_from_websys_event(event.clone()),
+                            element: Some(ElementId(id)),
+                            scope_id: None,
+                            priority: dioxus_core::EventPriority::Medium,
+                        });
+                    }
+                    Some(Err(e)) => {
+                        break Err(e.into());
+                    }
+                    None => {
+                        // walk the tree upwards until we actually find an event target
+                        if let Some(parent) = target.parent_element() {
+                            target = parent;
+                        } else {
+                            break Ok(UserEvent {
+                                name: event_name_from_typ(&typ),
+                                data: virtual_event_from_websys_event(event.clone()),
+                                element: None,
+                                scope_id: None,
+                                priority: dioxus_core::EventPriority::Low,
+                            });
+                        }
+                    }
+                }
+            };
+
+            if let Ok(synthetic_event) = decoded {
                 // Try to prevent default if the attribute is set
-                if let Some(target) = event.target() {
-                    if let Some(node) = target.dyn_ref::<HtmlElement>() {
-                        if let Some(name) = node.get_attribute("dioxus-prevent-default") {
-                            if name == synthetic_event.name
-                                || name.trim_start_matches("on") == synthetic_event.name
-                            {
-                                log::trace!("Preventing default");
-                                event.prevent_default();
-                            }
+                if let Some(node) = target.dyn_ref::<HtmlElement>() {
+                    if let Some(name) = node.get_attribute("dioxus-prevent-default") {
+                        if name == synthetic_event.name
+                            || name.trim_start_matches("on") == synthetic_event.name
+                        {
+                            log::trace!("Preventing default");
+                            event.prevent_default();
                         }
                     }
                 }
@@ -274,49 +311,6 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc<dyn Any + Send
     }
 }
 
-/// This function decodes a websys event and produces an EventTrigger
-/// With the websys implementation, we attach a unique key to the nodes
-fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<UserEvent> {
-    let mut target = event
-        .target()
-        .expect("missing target")
-        .dyn_into::<Element>()
-        .expect("not a valid element");
-
-    let typ = event.type_();
-
-    loop {
-        match target.get_attribute("data-dioxus-id").map(|f| f.parse()) {
-            Some(Ok(id)) => {
-                return Ok(UserEvent {
-                    name: event_name_from_typ(&typ),
-                    data: virtual_event_from_websys_event(event.clone()),
-                    element: Some(ElementId(id)),
-                    scope_id: None,
-                    priority: dioxus_core::EventPriority::Medium,
-                });
-            }
-            Some(Err(e)) => {
-                return Err(e.into());
-            }
-            None => {
-                // walk the tree upwards until we actually find an event target
-                if let Some(parent) = target.parent_element() {
-                    target = parent;
-                } else {
-                    return Ok(UserEvent {
-                        name: event_name_from_typ(&typ),
-                        data: virtual_event_from_websys_event(event.clone()),
-                        element: None,
-                        scope_id: None,
-                        priority: dioxus_core::EventPriority::Low,
-                    });
-                }
-            }
-        }
-    }
-}
-
 pub(crate) fn load_document() -> Document {
     web_sys::window()
         .expect("should have access to the Window")

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

@@ -61,7 +61,6 @@ pub use dioxus_core as dioxus;
 use dioxus_core::prelude::Component;
 use futures_util::FutureExt;
 
-pub(crate) mod bindings;
 mod cache;
 mod cfg;
 mod dom;