diff --git a/.docker/Dockerfile_pre_test b/.docker/Dockerfile_pre_test new file mode 100644 index 000000000..380bb5773 --- /dev/null +++ b/.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"] diff --git a/.docker/Dockerfile_test b/.docker/Dockerfile_test new file mode 100644 index 000000000..f6265c2bb --- /dev/null +++ b/.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"] diff --git a/.docker/README.md b/.docker/README.md new file mode 100644 index 000000000..57578f405 --- /dev/null +++ b/.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 \ No newline at end of file diff --git a/.docker/run_local_tests.sh b/.docker/run_local_tests.sh new file mode 100644 index 000000000..4684625d4 --- /dev/null +++ b/.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" diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..e31219bb6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,36 @@ +--- +name: Bug report +about: Create a report to help us improve Dioxus +--- + +**Problem** + + + +**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** + +- [ ] 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 \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_requst.md b/.github/ISSUE_TEMPLATE/feature_requst.md new file mode 100644 index 000000000..f8735b9a2 --- /dev/null +++ b/.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 + + + +## Implement Suggestion + + \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b941960b7..244313ef9 100644 --- a/.github/workflows/main.yml +++ b/.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 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 183fb282d..2b6ea5c23 100644 --- a/.github/workflows/windows.yml +++ b/.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' diff --git a/.gitignore b/.gitignore index 9534dc207..9df7e9f93 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target +/dist Cargo.lock .DS_Store diff --git a/.vscode/settings.json b/.vscode/settings.json index ac2195ccd..44bcb08e3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,4 +4,8 @@ "desktop", "router" ], + "editor.formatOnSave": true, + "[toml]": { + "editor.formatOnSave": false + } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 06b624794..b056a9590 100644 --- a/CHANGELOG.md +++ b/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 + + + + - 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 + + + +
view details + + * **Uncategorized** + - Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d)) +
+ ## v0.1.7 (2022-01-08) ### Bug Fixes diff --git a/Cargo.toml b/Cargo.toml index 884513a5b..5e3a0a032 100644 --- a/Cargo.toml +++ b/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] diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 8dbc080b8..000000000 --- a/LICENSE +++ /dev/null @@ -1 +0,0 @@ -MIT/Apache-2 diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 000000000..1b5ec8b78 --- /dev/null +++ b/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 diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 000000000..31aa79387 --- /dev/null +++ b/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. diff --git a/Makefile.toml b/Makefile.toml new file mode 100644 index 000000000..17c32fd15 --- /dev/null +++ b/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 diff --git a/README.md b/README.md index 52493b119..3a44bf3c1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@
-

🌗🚀 Dioxus

+

Dioxus

Frontend that scales.

@@ -26,9 +26,7 @@ CI status -
-
Awesome Page diff --git a/examples/borderless.rs b/examples/borderless.rs new file mode 100644 index 000000000..d52e9fb5a --- /dev/null +++ b/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" + } + } + } + )) +} diff --git a/examples/disabled.rs b/examples/disabled.rs index f72a8ff77..949c25369 100644 --- a/examples/disabled.rs +++ b/examples/disabled.rs @@ -18,11 +18,6 @@ fn app(cx: Scope) -> Element { disabled: "{disabled}", "lower button" } - - input { - value: "false", - } - } }) } diff --git a/examples/dog_app.rs b/examples/dog_app.rs index 331574a75..a30f3e5e8 100644 --- a/examples/dog_app.rs +++ b/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!"} diff --git a/examples/file_explorer.rs b/examples/file_explorer.rs index fe5ab23bb..728812027 100644 --- a/examples/file_explorer.rs +++ b/examples/file_explorer.rs @@ -56,7 +56,6 @@ fn app(cx: Scope) -> Element { ) }) } - }) } diff --git a/examples/filedragdrop.rs b/examples/filedragdrop.rs index 523405b39..e5644e2a0 100644 --- a/examples/filedragdrop.rs +++ b/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 }) }); } diff --git a/examples/framework_benchmark.rs b/examples/framework_benchmark.rs index 7cea4f5f7..8dba1cb91 100644 --- a/examples/framework_benchmark.rs +++ b/examples/framework_benchmark.rs @@ -17,9 +17,9 @@ impl Label { fn new_list(num: usize) -> Vec { 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(), diff --git a/examples/svg.rs b/examples/svg.rs new file mode 100644 index 000000000..6e5676fe3 --- /dev/null +++ b/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 + } + ) +} diff --git a/examples/todomvc.rs b/examples/todomvc.rs index e5f7d1dfa..937d6992c 100644 --- a/examples/todomvc.rs +++ b/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 { diff --git a/packages/core-macro/CHANGELOG.md b/packages/core-macro/CHANGELOG.md index 79cbe766f..cf1143e1e 100644 --- a/packages/core-macro/CHANGELOG.md +++ b/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 + + - flatten props attrs + - add "optional" flag for props + +### Bug Fixes + + - detection of f-string formatting in components + +### Commit Statistics + + + + - 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 + + + +
view details + + * **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)) +
+ ## 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 - - 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
view details * **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)) diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml index ee3dc2883..eb8210923 100644 --- a/packages/core-macro/Cargo.toml +++ b/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" diff --git a/packages/core-macro/src/rsx/component.rs b/packages/core-macro/src/rsx/component.rs index 5257022a4..4e9d449cf 100644 --- a/packages/core-macro/src/rsx/component.rs +++ b/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 ) }) } diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index a0aa9f28d..563106db3 100644 --- a/packages/core/CHANGELOG.md +++ b/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 + + - add comments for the Handler + +### New Features + + - add "spawn" method + - allow providing context to the root component + +### Bug Fixes + + - provide_root_context on root scopes + - proprogation of root context + - allow eventhandler to derive default + +### Commit Statistics + + + + - 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 + + + +
view details + + * **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)) +
+ ## 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 - - 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
view details * **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)) diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index f54c6d5d9..03b378e88 100644 --- a/packages/core/Cargo.toml +++ b/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" diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 499d9543e..d7189a710 100644 --- a/packages/core/src/diff.rs +++ b/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: //! - -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>, - 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> { - self.instructions.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(); - fn pop_off_scope(&mut self) { + self.scope_stack.push(scopeid); + self.element_stack.push(scope.container); + { + self.diff_node(old, new); + } + self.element_stack.pop(); self.scope_stack.pop(); + + self.mutations.mark_dirty_scope(scopeid); } - pub(crate) fn push(&mut self, instruction: DiffInstruction<'bump>) { - self.instructions.push(instruction) - } + 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 create_children(&mut self, children: &'bump [VNode<'bump>], and: MountType<'bump>) { - self.nodes_created_stack.push(0); - self.instructions.push(DiffInstruction::Mount { and }); + (Placeholder(old), Placeholder(new)) => { + self.diff_placeholder_nodes(old, new, old_node, new_node); + } - for child in children.iter().rev() { - self.instructions - .push(DiffInstruction::Create { node: child }); + (Element(old), Element(new)) => { + self.diff_element_nodes(old, new, old_node, new_node); + } + + (Component(old), Component(new)) => { + self.diff_component_nodes(old_node, new_node, *old, *new); + } + + (Fragment(old), Fragment(new)) => { + self.diff_fragment_nodes(old, new); + } + + ( + Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_), + Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_), + ) => self.replace_node(old_node, new_node), } } - // 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 { - 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(); - } - }; - - if deadline_expired() { - log::trace!("Deadline expired before we could finish!"); - return false; - } - } - - true - } - - // 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 - } - } - } - - 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); - } - - MountType::Replace { old } => { - self.replace_node(old, nodes_created); - } - - MountType::Append => { - self.mutations.edits.push(AppendChildren { - many: nodes_created as u32, - }); - } - - MountType::InsertAfter { other_node } => { - let root = self.find_last_element(other_node).unwrap(); - self.mutations.insert_after(root, nodes_created as u32); - } - - MountType::InsertBefore { other_node } => { - let root = self.find_first_element_id(other_node).unwrap(); - self.mutations.insert_before(root, nodes_created as u32); - } - } - } - - // ================================= - // 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()); - } + for attr in attributes.iter() { + self.mutations.set_attribute(attr, real_id.as_u64()); + } - // todo: the settext optimization - // - // if children.len() == 1 { - // if let VNode::Text(vtext) = children[0] { - // self.mutations.set_text(vtext.text, real_id.as_u64()); - // return; - // } - // } - - if !children.is_empty() { - self.stack.create_children(children, MountType::Append); + 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 = vcomponent.props.borrow_mut().take().unwrap(); + let props: Box = vcomponent.props.borrow_mut().take().unwrap(); let props: Box = 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); + + // Take the node that was just generated from running the component + let nextnode = self.scopes.fin_head(new_idx); + self.create_node(nextnode) + }; + + self.leave_scope(); + + created } - // ================================= - // Tools for diffing nodes - // ================================= - - 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); - - new.id.set(Some(root)); - } - } - - (Placeholder(old), Placeholder(new)) => { - if let Some(root) = old.id.get() { - self.scopes.update_node(new_node, root); - new.id.set(Some(root)) - } - } - - (Element(old), Element(new)) => self.diff_element_nodes(old, new, old_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) - } - - (Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new), - - // 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 }); - } - - // 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_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; } + + // 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 old.text != new.text { + self.mutations.set_text(new.text, root.as_u64()); + } + + self.scopes.update_node(new_node, root); + + new.id.set(Some(root)); + } + + 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()); + } + } else { + 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); } } - 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); - } - - // 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); + 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 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)); - // 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 + // 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"); - // this should auto drop the previous props - self.scopes.run_scope(scope_addr); - self.mutations.dirty_scopes.insert(scope_addr); + 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 + } + }; - 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); - }; + if should_diff { + let _old_props = scope + .props + .replace(unsafe { std::mem::transmute(Some(new_props)) }); - self.stack.scope_stack.pop(); + // this should auto drop the previous props + self.scopes.run_scope(scope_addr); + self.mutations.mark_dirty_scope(scope_addr); + + 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], - }, - }); + let mut nodes_created = 0; - for (idx, new_node) in new[..first_lis].iter().enumerate().rev() { - apply(idx, new_node, &mut self.stack); + // 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 { - 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 { - 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>, - gen_muts: bool, - ) { - // or cache the vec on the diff machine + pub fn remove_nodes(&mut self, nodes: impl IntoIterator>, 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 { + 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 { + 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 + } + } + } } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index dba4effbb..e81ddca87 100644 --- a/packages/core/src/lib.rs +++ b/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::*; diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index fa96edd1b..bd94d0b71 100644 --- a/packages/core/src/mutations.rs +++ b/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 diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index e30a07001..7a36f58ce 100644 --- a/packages/core/src/nodes.rs +++ b/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>>, } +impl<'a, T> Default for EventHandler<'a, T> { + fn default() -> Self { + Self { + callback: RefCell::new(None), + } + } +} + impl 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>, pub can_memoize: bool, - pub user_fc: *const (), + pub user_fc: ComponentPtr, + pub fn_name: &'static str, pub props: RefCell>>, } @@ -542,6 +551,7 @@ impl<'a> NodeFactory<'a> { component: fn(Scope<'a, P>) -> Element, props: P, key: Option, + 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), diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 77c648d18..00c3024bf 100644 --- a/packages/core/src/scopes.rs +++ b/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, pub bump: Bump, pub scopes: RefCell>, - pub heuristics: RefCell>, + pub heuristics: RefCell>, pub free_scopes: RefCell>, pub nodes: RefCell>>, pub tasks: Rc, @@ -82,7 +84,7 @@ impl ScopeArena { pub(crate) fn new_with_key( &self, - fc_ptr: *const (), + fc_ptr: ComponentPtr, vcomp: Box, parent_scope: Option, 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, @@ -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, - tasks: Rc, - (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 diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 4e426caad..a7092104c 100644 --- a/packages/core/src/virtual_dom.rs +++ b/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 { let mut committed_mutations = vec![]; @@ -467,36 +469,41 @@ 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; + + log::debug!("succesffuly resolved scopes {:?}", mutations.dirty_scopes); + 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; + // } } } - - 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 @@ -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) } diff --git a/packages/core/tests/.rustfmt.toml b/packages/core/tests/.rustfmt.toml new file mode 100644 index 000000000..70a9483a8 --- /dev/null +++ b/packages/core/tests/.rustfmt.toml @@ -0,0 +1 @@ +struct_lit_width = 80 diff --git a/packages/core/tests/create_dom.rs b/packages/core/tests/create_dom.rs index 044be289c..0cdc37c6d 100644 --- a/packages/core/tests/create_dom.rs +++ b/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 }, diff --git a/packages/core/tests/diffing.rs b/packages/core/tests/diffing.rs index 5b98ea80e..6bc9b5596 100644 --- a/packages/core/tests/diffing.rs +++ b/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 }, diff --git a/packages/core/tests/earlyabort.rs b/packages/core/tests/earlyabort.rs index 7dc2ae4d4..3999a7402 100644 --- a/packages/core/tests/earlyabort.rs +++ b/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 }, ] diff --git a/packages/core/tests/lifecycle.rs b/packages/core/tests/lifecycle.rs index 6c0df4e1e..f977ba96e 100644 --- a/packages/core/tests/lifecycle.rs +++ b/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); diff --git a/packages/core/tests/miri_stress.rs b/packages/core/tests/miri_stress.rs index bb4963b71..8ce030147 100644 --- a/packages/core/tests/miri_stress.rs +++ b/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); + } +} diff --git a/packages/core/tests/passthru.rs b/packages/core/tests/passthru.rs new file mode 100644 index 000000000..e00aa545c --- /dev/null +++ b/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 }, + ] + ) +} diff --git a/packages/core/tests/sharedstate.rs b/packages/core/tests/sharedstate.rs index 3f04aa154..70e823569 100644 --- a/packages/core/tests/sharedstate.rs +++ b/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::().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::().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); +} diff --git a/packages/core/tests/vdom_rebuild.rs b/packages/core/tests/vdom_rebuild.rs index 5d1dbcdf8..83b4ff35f 100644 --- a/packages/core/tests/vdom_rebuild.rs +++ b/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 }, diff --git a/packages/desktop/CHANGELOG.md b/packages/desktop/CHANGELOG.md index 245d05623..65657ab37 100644 --- a/packages/desktop/CHANGELOG.md +++ b/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 + + - fix web doc example and use &mut for builders everywhere + +### New Features + + - default asset server + - remove dioxus id on non-event elements + - also hide placeholder node + - wire up both desktop and web + - bool attr white list + - setup a typescript build + +### Bug Fixes + + - custom protocol receiver type + - clippy + - wry pathing + - cursor jumping and use set instead of lsit + - format code + - check `href` null + - add exclusion list + - check `href` null + - prevent `submit` default + +### Commit Statistics + + + + - 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 + + + +
view details + + * **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)) +
+ ## 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 - omg what a dumb mistake - bless up, no more segfaults - wire up event delegator for webview + - link open in browser + - move `rpc` to handler + - `open_browser` bool attribute + - link open in browser + - link open in browser ### Bug Fixes @@ -48,13 +123,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - tags - desktop and mobile - messed up how lifetimes worked, need to render once per component + - error pattern + - format code ### Commit Statistics - - 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
view details * **Uncategorized** - - 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)) + - 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)) + - 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)) diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 123013e5e..fa1bc88dc 100644 --- a/packages/desktop/Cargo.toml +++ b/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"] diff --git a/packages/desktop/examples/async.rs b/packages/desktop/examples/async.rs deleted file mode 100644 index f03c7b318..000000000 --- a/packages/desktop/examples/async.rs +++ /dev/null @@ -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!" - } - } - }) -} diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 48c82f5a8..9c0404477 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/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; -*/ -pub struct DesktopContext {} +/// Desktop-Window handle api context +/// +/// you can use this context control some window event +/// +/// you can use `cx.consume_context::` to get this context +/// +/// ```rust +/// let desktop = cx.consume_context::().unwrap(); +/// ``` +#[derive(Clone)] +pub struct DesktopContext { + proxy: ProxyType, +} impl DesktopContext { - fn add_window(&mut self) { - // + pub(crate) fn new(proxy: ProxyType) -> Self { + Self { proxy } } - fn close_window(&mut self) { - // + + /// 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); + } + + /// set window minimize state + pub fn minimize(&self, minimized: bool) { + let _ = self.proxy.send_event(UserWindowEvent::Minimize(minimized)); + } + + /// set window maximize state + pub fn maximize(&self, maximized: bool) { + let _ = self.proxy.send_event(UserWindowEvent::Maximize(maximized)); + } + + /// 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); } } -enum WindowHandlers { - Resized(Box), - Moved(Box), - CloseRequested(Box), - Destroyed(Box), - DroppedFile(Box), - HoveredFile(Box), - HoverFileCancelled(Box), - ReceivedTimeText(Box), - Focused(Box), -} - -#[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 -/// -/// -/// -/// -/// -/// -/// -/// -/// -pub fn WebviewWindow(cx: Scope) -> Element { - let dtcx = cx.consume_state::>()?; - - cx.use_hook(|_| {}); - - // render the children directly - todo!() - // cx.render(LazyNodes::new(move |f: NodeFactory| { - // f.fragment_from_iter(cx.children()) - // })) -} - -pub struct WindowHandle {} - -/// Get a handle to the current window from inside a component -pub fn use_current_window(cx: Scope) -> Option { - todo!() -} - -#[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; - - static App: Component = |cx| { - cx.render(rsx! { - // left window - WebviewWindow { - onclose: move |evt| {} - onopen: move |evt| {} - onfocused: move |evt| {} - - div { - - } - } - }) - }; +/// use this function can get the `DesktopContext` context. +pub fn use_window(cx: &ScopeState) -> &Rc { + cx.use_hook(|_| cx.consume_context::()) + .as_ref() + .unwrap() } diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 36b017077..9181d04dc 100644 --- a/packages/desktop/src/lib.rs +++ b/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( 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( } 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( // 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( // 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( 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 diff --git a/packages/hooks/CHANGELOG.md b/packages/hooks/CHANGELOG.md index 07e15ed64..253768068 100644 --- a/packages/hooks/CHANGELOG.md +++ b/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 + + - use_state + - exampels + +### Commit Statistics + + + + - 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 + + + +
view details + + * **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)) +
+ ## 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 - - 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
view details * **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)) diff --git a/packages/hooks/Cargo.toml b/packages/hooks/Cargo.toml index 375fef087..7d8e4d26a 100644 --- a/packages/hooks/Cargo.toml +++ b/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" } diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index 9b59601b5..a05e99d7d 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -33,7 +33,7 @@ impl ProvidedStateInner { /// 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 /// diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index eb3900981..7e8892be9 100644 --- a/packages/hooks/src/usestate.rs +++ b/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(); + { + 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); + // 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 UseState { /// } /// }) /// } - /// ``` + /// ``` #[must_use] pub fn current(&self) -> Rc { self.slot.borrow().clone() @@ -135,10 +138,10 @@ impl UseState { /// # 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 UseState { /// } /// ``` 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 UseState { /// # 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 UseState { /// } /// }) /// } - /// ``` + /// ``` pub fn needs_update(&self) { (self.update_callback)(); } @@ -270,10 +275,8 @@ impl UseState { } } -impl ToOwned for UseState { - type Owned = UseState; - - fn to_owned(&self) -> Self::Owned { +impl Clone for UseState { + fn clone(&self) -> Self { UseState { current_val: self.current_val.clone(), update_callback: self.update_callback.clone(), diff --git a/packages/html/CHANGELOG.md b/packages/html/CHANGELOG.md index 9648c0c8f..48c7f622c 100644 --- a/packages/html/CHANGELOG.md +++ b/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 + + - rustfmt + +### Commit Statistics + + + + - 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 + + + +
view details + + * **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)) +
+ ## 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 - - 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
view details * **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)) diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 3c0f07b9b..293d4c465 100644 --- a/packages/html/Cargo.toml +++ b/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 } diff --git a/packages/html/src/elements.rs b/packages/html/src/elements.rs index 1a2f36e57..c6b1cb7e0 100644 --- a/packages/html/src/elements.rs +++ b/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 diff --git a/packages/html/src/global_attributes.rs b/packages/html/src/global_attributes.rs index 1368f87e3..151a1539b 100644 --- a/packages/html/src/global_attributes.rs +++ b/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: + /// 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: + /// + 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", diff --git a/packages/interpreter/Cargo.toml b/packages/interpreter/Cargo.toml new file mode 100644 index 000000000..1e0f4bb51 --- /dev/null +++ b/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"] diff --git a/packages/jsinterpreter/README.md b/packages/interpreter/README.md similarity index 99% rename from packages/jsinterpreter/README.md rename to packages/interpreter/README.md index bab57ac16..767fe31c0 100644 --- a/packages/jsinterpreter/README.md +++ b/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. diff --git a/packages/web/src/bindings.rs b/packages/interpreter/src/bindings.rs similarity index 96% rename from packages/web/src/bindings.rs rename to packages/interpreter/src/bindings.rs index 9b0c2ebfb..fa96e3230 100644 --- a/packages/web/src/bindings.rs +++ b/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; diff --git a/packages/jsinterpreter/interpreter.ts b/packages/interpreter/src/interpreter.js similarity index 71% rename from packages/jsinterpreter/interpreter.ts rename to packages/interpreter/src/interpreter.js index fdd8c671d..dbcb73a96 100644 --- a/packages/jsinterpreter/interpreter.ts +++ b/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(); + // 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 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; diff --git a/packages/interpreter/src/lib.rs b/packages/interpreter/src/lib.rs new file mode 100644 index 000000000..ebca3cbf4 --- /dev/null +++ b/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; diff --git a/packages/jsinterpreter/interpreter.js b/packages/jsinterpreter/interpreter.js deleted file mode 100644 index ea50ec3d3..000000000 --- a/packages/jsinterpreter/interpreter.js +++ /dev/null @@ -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, -}; diff --git a/packages/jsinterpreter/tsconfig.json b/packages/jsinterpreter/tsconfig.json deleted file mode 100644 index f63034044..000000000 --- a/packages/jsinterpreter/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNext", - "module": "ES2015", - "lib": [ - "es2015", - "es5", - "es6", - "dom" - ], - "strict": true, - } -} diff --git a/packages/mobile/CHANGELOG.md b/packages/mobile/CHANGELOG.md index 284068810..4486eb279 100644 --- a/packages/mobile/CHANGELOG.md +++ b/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 + + + + - 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 + + + +
view details + + * **Uncategorized** + - Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d)) +
+ ## 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 - - 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
view details * **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)) diff --git a/packages/mobile/Cargo.toml b/packages/mobile/Cargo.toml index 373814260..cdf0a5c05 100644 --- a/packages/mobile/Cargo.toml +++ b/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 diff --git a/packages/mobile/Makefile.toml b/packages/mobile/Makefile.toml new file mode 100644 index 000000000..90f08a170 --- /dev/null +++ b/packages/mobile/Makefile.toml @@ -0,0 +1,7 @@ +[tasks.test] +command = "cargo" +args = [ + "test", + "--no-run", +] + diff --git a/packages/router/CHANGELOG.md b/packages/router/CHANGELOG.md index 7e257f854..86b157737 100644 --- a/packages/router/CHANGELOG.md +++ b/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 + + - enable use_router + - connect an onchange listener + - flatten props attrs + +### Bug Fixes + + - attach router listener to subscriber list + - use_route should subscribe to changes to the route + +### Commit Statistics + + + + - 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 + + + +
view details + + * **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)) +
+ ## 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 - - 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
view details * **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)) diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index 032dbe3bf..239a3c5d8 100644 --- a/packages/router/Cargo.toml +++ b/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", +] diff --git a/packages/router/Makefile.toml b/packages/router/Makefile.toml new file mode 100644 index 000000000..42b7f5c9a --- /dev/null +++ b/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}", +] \ No newline at end of file diff --git a/packages/router/src/components/link.rs b/packages/router/src/components/link.rs index 4c0ecb996..18e9fd92e 100644 --- a/packages/router/src/components/link.rs +++ b/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::() { return cx.render(rsx! { a { diff --git a/packages/router/src/components/route.rs b/packages/router/src/components/route.rs index 129953eea..e4dd2f9c8 100644 --- a/packages/router/src/components/route.rs +++ b/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)) diff --git a/packages/router/src/components/router.rs b/packages/router/src/components/router.rs index 240c3806a..bf23645f5 100644 --- a/packages/router/src/components/router.rs +++ b/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())) diff --git a/packages/router/src/hooks/use_route.rs b/packages/router/src/hooks/use_route.rs index 97dd6c048..31a157a8f 100644 --- a/packages/router/src/hooks/use_route.rs +++ b/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, } @@ -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::() - .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::() + .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::() + .expect("Cannot call use_route outside the scope of a Router component") + }) } diff --git a/packages/router/src/platform/mod.rs b/packages/router/src/platform/mod.rs index 7afc1316a..ec59a4703 100644 --- a/packages/router/src/platform/mod.rs +++ b/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); -} - -enum RouteChange { - LinkTo(String), - Back(String), + fn listen(&self, callback: Box); } diff --git a/packages/router/src/service.rs b/packages/router/src/service.rs index 9da2bef7e..a4ab4dc0d 100644 --- a/packages/router/src/service.rs +++ b/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, pub(crate) pending_events: Rc>>, - history: Rc>, slots: Rc>>, + onchange_listeners: Rc>>, root_found: Rc>>, cur_path_params: Rc>>, + + // history: Rc, + history: Rc>, 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>> = Default::default(); let pending_events: Rc>> = 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> { 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 let route_pieces = route.split('/').collect::>(); let path_pieces = clean_path(path).split('/').collect::>(); - 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; } diff --git a/packages/router/tests/route.rs b/packages/router/tests/route.rs new file mode 100644 index 000000000..89b7a64fa --- /dev/null +++ b/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::("id")?; + + cx.render(rsx! { + div { + + } + }) + } + + main(); + + let element = gloo_utils::document(); +} diff --git a/packages/router/webdriver.json b/packages/router/webdriver.json new file mode 100644 index 000000000..81413f24b --- /dev/null +++ b/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": [] + } + } + \ No newline at end of file diff --git a/packages/ssr/CHANGELOG.md b/packages/ssr/CHANGELOG.md index 73cee67b0..43e1960c1 100644 --- a/packages/ssr/CHANGELOG.md +++ b/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 + + - ssr respects bool attrs + +### Commit Statistics + + + + - 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 + + + +
view details + + * **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)) +
+ ## 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 - - 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
view details * **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)) diff --git a/packages/ssr/Cargo.toml b/packages/ssr/Cargo.toml index 754fbbb78..02f51f453 100644 --- a/packages/ssr/Cargo.toml +++ b/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] diff --git a/packages/web/CHANGELOG.md b/packages/web/CHANGELOG.md index b81747e95..3c27f3f94 100644 --- a/packages/web/CHANGELOG.md +++ b/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 + + - fix web doc example and use &mut for builders everywhere + +### New Features + + - add panic hook by default + - remove dioxus id on non-event elements + - wire up both desktop and web + - bool attr white list + +### Bug Fixes + + - webconfig should take &mut self + - format code + - ssr + hydration event listeners + - add exclusion list + +### Commit Statistics + + + + - 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 + + + +
view details + + * **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)) +
+ ## 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 - 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
view details * **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)) diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index a5befe7b3..9145fed8b 100644 --- a/packages/web/Cargo.toml +++ b/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" } diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index ffbef9f4b..6ca689d28 100644 --- a/packages/web/src/dom.rs +++ b/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) -> Self { // eventually, we just want to let the interpreter do all the work of decoding events into our event type let callback: Box = 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::() + .expect("not a valid element"); + + let typ = event.type_(); + + let decoded: anyhow::Result = 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::() { - 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::() { + 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 anyhow::Result { - let mut target = event - .target() - .expect("missing target") - .dyn_into::() - .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") diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index f4266a7c8..cef1bc14c 100644 --- a/packages/web/src/lib.rs +++ b/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;