浏览代码

Merge branch 'master' into fix-links-liveview

Evan Almloff 2 年之前
父节点
当前提交
77275d40c1

+ 8 - 0
.devcontainer/Dockerfile

@@ -0,0 +1,8 @@
+ARG VARIANT="nightly-bookworm-slim"
+FROM rustlang/rust:${VARIANT}
+ENV DEBIAN_FRONTEND noninteractive
+RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
+
+RUN apt-get update && export DEBIAN_FRONTEND=noninteractive
+
+RUN apt-get -qq install build-essential libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev

+ 26 - 0
.devcontainer/README.md

@@ -0,0 +1,26 @@
+# Dev Container
+
+A dev container in the most simple context allows one to create a consistent development environment within a docker container that can easily be opened locally or remotely via codespaces such that contributors don't need to install anything to contribute.
+
+## Useful Links
+
+- <https://code.visualstudio.com/docs/devcontainers/containers>
+- <https://containers.dev/>
+- <https://github.com/devcontainers>
+- <https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers>
+
+## Using A Dev Container
+
+### Locally
+
+To use this dev container locally, make sure Docker is installed and in VSCode install the `ms-vscode-remote.remote-containers` extension. Then from the root of Dioxus you can type `Ctrl + Shift + P`, then choose `Dev Containers: Rebuild and Reopen in Devcontainer`.
+
+### Codespaces
+
+[Codespaces Setup](https://docs.github.com/en/codespaces/developing-in-codespaces/creating-a-codespace-for-a-repository#creating-a-codespace-for-a-repository)
+
+## Troubleshooting
+
+If having difficulty commiting with github, and you use ssh or gpg keys, you may need to ensure that the keys are being shared properly between your host and VSCode.
+
+Though VSCode does a pretty good job sharing credentials between host and devcontainer, to save some time you can always just reopen the container locally to commit with `Ctrl + Shift + P`, then choose `Dev Containers: Reopen Folder Locally`

+ 37 - 0
.devcontainer/devcontainer.json

@@ -0,0 +1,37 @@
+{
+    "name": "dioxus",
+    "remoteUser": "vscode",
+    "build": {
+        "dockerfile": "./Dockerfile",
+        "context": "."
+    },
+    "features": {
+        "ghcr.io/devcontainers/features/common-utils:2": {
+            "installZsh": "true",
+            "username": "vscode",
+            "uid": "1000",
+            "gid": "1000",
+            "upgradePackages": "true"
+        }
+    },
+    "containerEnv": {
+        "RUST_LOG": "INFO"
+    },
+    "customizations": {
+        "vscode": {
+            "settings": {
+                "files.watcherExclude": {
+                    "**/target/**": true
+                },
+                "[rust]": {
+                    "editor.formatOnSave": true
+                }
+            },
+            "extensions": [
+                "rust-lang.rust-analyzer",
+                "tamasfe.even-better-toml",
+                "serayuzgur.crates"
+            ]
+        }
+    }
+}

+ 1 - 2
docs/guide/examples/event_prevent_default.rs

@@ -10,8 +10,7 @@ fn App(cx: Scope) -> Element {
     // ANCHOR: prevent_default
 cx.render(rsx! {
     input {
-        prevent_default: "oninput",
-        prevent_default: "onclick",
+        prevent_default: "oninput onclick",
     }
 })
     // ANCHOR_END: prevent_default

+ 1 - 1
docs/guide/src/en/custom_renderer/index.md

@@ -15,7 +15,7 @@ Essentially, your renderer needs to process edits and generate events to update
 
 Internally, Dioxus handles the tree relationship, diffing, memory management, and the event system, leaving as little as possible required for renderers to implement themselves.
 
-For reference, check out the [javascript interpreter](https://github.com/DioxusLabs/dioxus/tree/master/packages/interpreter) or [tui renderer](https://github.com/DioxusLabs/dioxus/tree/master/packages/tui) as a starting point for your custom renderer.
+For reference, check out the [javascript interpreter](https://github.com/DioxusLabs/dioxus/tree/master/packages/interpreter) or [tui renderer](https://github.com/DioxusLabs/dioxus/tree/master/packages/dioxus-tui) as a starting point for your custom renderer.
 
 ## Templates
 

+ 1 - 3
docs/guide/src/en/getting_started/tui.md

@@ -12,14 +12,12 @@ TUI support is currently quite experimental. But, if you're willing to venture i
 
 - It uses flexbox for the layout
 - It only supports a subset of the attributes and elements
-- Regular widgets will not work in the tui render, but the tui renderer has its own widget components that start with a capital letter. See the [widgets example](https://github.com/DioxusLabs/dioxus/blob/master/packages/tui/examples/widgets.rs)
+- Regular widgets will not work in the tui render, but the tui renderer has its own widget components that start with a capital letter. See the [widgets example](https://github.com/DioxusLabs/dioxus/blob/master/packages/dioxus-tui/examples/widgets.rs)
 - 1px is one character line height. Your regular CSS px does not translate
 - If your app panics, your terminal is wrecked. This will be fixed eventually
 
-
 ## Getting Set up
 
-
 Start by making a new package and adding Dioxus and the TUI renderer as dependancies.
 
 ```shell

+ 2 - 2
docs/guide/src/en/interactivity/event_handlers.md

@@ -31,7 +31,7 @@ Some events will trigger first on the element the event originated at upward. Fo
 
 > For more information about event propigation see [the mdn docs on event bubling](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_bubbling)
 
-If you want to prevent this behavior, you can call `stop_propogation()` on the event:
+If you want to prevent this behavior, you can call `stop_propagation()` on the event:
 
 ```rust
 {{#include ../../../examples/event_nested.rs:rsx}}
@@ -41,7 +41,7 @@ If you want to prevent this behavior, you can call `stop_propogation()` on the e
 
 Some events have a default behavior. For keyboard events, this might be entering the typed character. For mouse events, this might be selecting some text.
 
-In some instances, might want to avoid this default behavior. For this, you can add the `prevent_default` attribute with the name of the handler whose default behavior you want to stop. This attribute is special: you can attach it multiple times for multiple attributes:
+In some instances, might want to avoid this default behavior. For this, you can add the `prevent_default` attribute with the name of the handler whose default behavior you want to stop. This attribute can be used for multiple handlers using their name separated by spaces:
 
 ```rust
 {{#include ../../../examples/event_prevent_default.rs:prevent_default}}

+ 1 - 1
docs/guide/src/en/interactivity/router.md

@@ -84,4 +84,4 @@ rsx!{
 
 ## More reading
 
-This page is just a very brief overview of the router. For more information, check out [the router book](https://dioxuslabs.com/router/guide/) or some of [the router examples](https://github.com/DioxusLabs/dioxus/blob/master/examples/router.rs).
+This page is just a very brief overview of the router. For more information, check out [the router book](https://dioxuslabs.com/docs/0.3/router/) or some of [the router examples](https://github.com/DioxusLabs/dioxus/blob/master/examples/router.rs).

+ 1 - 1
docs/guide/src/pt-br/interactivity/router.md

@@ -80,4 +80,4 @@ rsx!{
 
 Esta página é apenas uma breve visão geral do roteador para mostrar que existe uma solução poderosa já construída para um problema muito comum. Para obter mais informações sobre o roteador, confira seu livro ou confira alguns dos exemplos.
 
-O roteador tem sua própria documentação! [Disponível aqui](https://dioxuslabs.com/router/guide/).
+O roteador tem sua própria documentação! [Disponível aqui](https://dioxuslabs.com/docs/0.3/router/).

+ 32 - 0
examples/inputs.rs

@@ -37,6 +37,7 @@ const FIELDS: &[(&str, &str)] = &[
 fn app(cx: Scope) -> Element {
     cx.render(rsx! {
         div { margin_left: "30px",
+            select_example(cx),
             div {
                 // handling inputs on divs will catch all input events below
                 // so the value of our input event will be either huey, dewey, louie, or true/false (because of the checkboxe)
@@ -134,3 +135,34 @@ fn app(cx: Scope) -> Element {
         }
     })
 }
+
+fn select_example(cx: Scope) -> Element {
+    cx.render(rsx! {
+    div {
+        select {
+            id: "selection",
+            name: "selection",
+            multiple: true,
+            oninput: move |evt| {
+                println!("{evt:?}");
+            },
+            option {
+                value : "Option 1",
+                label : "Option 1",
+            }
+            option {
+                value : "Option 2",
+                label : "Option 2",
+                selected : true,
+            },
+            option {
+                value : "Option 3",
+                label : "Option 3",
+            }
+        }
+        label {
+            r#for: "selection",
+            "select element"
+        }
+    }})
+}

+ 1 - 1
packages/core/Cargo.toml

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

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

@@ -4,7 +4,8 @@ use serde::Deserialize;
 
 #[derive(Debug, Deserialize)]
 pub(crate) struct FileDiologRequest {
-    accept: String,
+    #[serde(default)]
+    accept: Option<String>,
     multiple: bool,
     pub event: String,
     pub target: usize,
@@ -16,6 +17,8 @@ pub(crate) fn get_file_event(request: &FileDiologRequest) -> Vec<PathBuf> {
 
     let filters: Vec<_> = request
         .accept
+        .as_deref()
+        .unwrap_or_default()
         .split(',')
         .filter_map(|s| Filters::from_str(s).ok())
         .collect();

+ 8 - 8
packages/desktop/src/protocol.rs

@@ -15,12 +15,12 @@ fn module_loader(root_name: &str) -> String {
     let inputs = document.querySelectorAll("input");
     for (let input of inputs) {
       if (!input.getAttribute("data-dioxus-file-listener")) {
-        input.setAttribute("data-dioxus-file-listener", true);
-        input.addEventListener("click", (event) => {
-          let target = event.target;
-          // prevent file inputs from opening the file dialog on click
-          const type = target.getAttribute("type");
-          if (type === "file") {
+        // prevent file inputs from opening the file dialog on click
+        const type = input.getAttribute("type");
+        if (type === "file") {
+          input.setAttribute("data-dioxus-file-listener", true);
+          input.addEventListener("click", (event) => {
+            let target = event.target;
             let target_id = find_real_id(target);
             if (target_id !== null) {
               const send = (event_name) => {
@@ -30,8 +30,8 @@ fn module_loader(root_name: &str) -> String {
               send("change&input");
             }
             event.preventDefault();
-          }
-        });
+          });
+        }
       }
     }"#,
     );

+ 0 - 0
packages/dioxus-tui/examples/all_events.rs → packages/dioxus-tui/examples/all_terminal_events.rs


+ 0 - 67
packages/dioxus-tui/examples/components.rs

@@ -1,67 +0,0 @@
-#![allow(non_snake_case)]
-
-use dioxus::prelude::*;
-use dioxus_tui::Config;
-
-fn main() {
-    dioxus_tui::launch_cfg(app, Config::default());
-}
-
-#[derive(Props, PartialEq)]
-struct QuadrentProps {
-    color: String,
-    text: String,
-}
-
-fn Quadrant(cx: Scope<QuadrentProps>) -> Element {
-    cx.render(rsx! {
-        div {
-            border_width: "1px",
-            width: "50%",
-            height: "100%",
-            background_color: "{cx.props.color}",
-            justify_content: "center",
-            align_items: "center",
-
-            "{cx.props.text}"
-        }
-    })
-}
-
-fn app(cx: Scope) -> Element {
-    cx.render(rsx! {
-        div {
-            width: "100%",
-            height: "100%",
-            flex_direction: "column",
-
-            div {
-                width: "100%",
-                height: "50%",
-                flex_direction: "row",
-                Quadrant{
-                    color: "red".to_string(),
-                    text: "[A]".to_string()
-                },
-                Quadrant{
-                    color: "black".to_string(),
-                    text: "[B]".to_string()
-                }
-            }
-
-            div {
-                width: "100%",
-                height: "50%",
-                flex_direction: "row",
-                Quadrant{
-                    color: "green".to_string(),
-                    text: "[C]".to_string()
-                },
-                Quadrant{
-                    color: "blue".to_string(),
-                    text: "[D]".to_string()
-                }
-            }
-        }
-    })
-}

+ 0 - 0
packages/dioxus-tui/examples/stress.rs → packages/dioxus-tui/examples/many_small_edit_stress.rs


+ 39 - 32
packages/dioxus-tui/examples/quadrants.rs

@@ -1,7 +1,31 @@
+#![allow(non_snake_case)]
+
 use dioxus::prelude::*;
+use dioxus_tui::Config;
 
 fn main() {
-    dioxus_tui::launch(app);
+    dioxus_tui::launch_cfg(app, Config::default());
+}
+
+#[derive(Props, PartialEq)]
+struct QuadrentProps {
+    color: String,
+    text: String,
+}
+
+fn Quadrant(cx: Scope<QuadrentProps>) -> Element {
+    cx.render(rsx! {
+        div {
+            border_width: "1px",
+            width: "50%",
+            height: "100%",
+            background_color: "{cx.props.color}",
+            justify_content: "center",
+            align_items: "center",
+
+            "{cx.props.text}"
+        }
+    })
 }
 
 fn app(cx: Scope) -> Element {
@@ -15,22 +39,13 @@ fn app(cx: Scope) -> Element {
                 width: "100%",
                 height: "50%",
                 flex_direction: "row",
-                div {
-                    border_width: "1px",
-                    width: "50%",
-                    height: "100%",
-                    background_color: "red",
-                    justify_content: "center",
-                    align_items: "center",
-                    "[A]"
-                }
-                div {
-                    width: "50%",
-                    height: "100%",
-                    background_color: "black",
-                    justify_content: "center",
-                    align_items: "center",
-                    "[B]"
+                Quadrant{
+                    color: "red".to_string(),
+                    text: "[A]".to_string()
+                },
+                Quadrant{
+                    color: "black".to_string(),
+                    text: "[B]".to_string()
                 }
             }
 
@@ -38,21 +53,13 @@ fn app(cx: Scope) -> Element {
                 width: "100%",
                 height: "50%",
                 flex_direction: "row",
-                div {
-                    width: "50%",
-                    height: "100%",
-                    background_color: "green",
-                    justify_content: "center",
-                    align_items: "center",
-                    "[C]"
-                }
-                div {
-                    width: "50%",
-                    height: "100%",
-                    background_color: "blue",
-                    justify_content: "center",
-                    align_items: "center",
-                    "[D]"
+                Quadrant{
+                    color: "green".to_string(),
+                    text: "[C]".to_string()
+                },
+                Quadrant{
+                    color: "blue".to_string(),
+                    text: "[D]".to_string()
                 }
             }
         }

+ 0 - 0
packages/dioxus-tui/examples/readme.rs → packages/dioxus-tui/examples/readme_hello_world.rs


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

@@ -34,6 +34,10 @@ use std::future::Future;
 /// don't care about actions in your app being synchronized, you can use [`use_callback`]
 /// hook to spawn multiple tasks and run them concurrently.
 ///
+/// ### Notice
+/// In order to use ``rx.next().await``, you will need to extend the ``Stream`` trait (used by ``UnboundedReceiver``)
+/// by adding the ``futures-util`` crate as a dependency and adding ``StreamExt`` into scope via ``use futures_util::stream::StreamExt;``
+///
 /// ## Example
 ///
 /// ```rust, ignore

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

@@ -359,6 +359,11 @@ builder_constructors! {
     /// element.
     header None {};
 
+    /// Build a
+    /// [`<hgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hgroup)
+    /// element.
+    hgroup None {};
+
     /// Build a
     /// [`<h1>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h1)
     /// element.

+ 5 - 1
packages/html/src/events/form.rs

@@ -14,7 +14,11 @@ pub struct FormData {
 
     #[cfg_attr(
         feature = "serialize",
-        serde(skip_serializing, deserialize_with = "deserialize_file_engine")
+        serde(
+            default,
+            skip_serializing,
+            deserialize_with = "deserialize_file_engine"
+        )
     )]
     pub files: Option<std::sync::Arc<dyn FileEngine>>,
 }

+ 31 - 117
packages/interpreter/src/interpreter.js

@@ -353,105 +353,9 @@ class Interpreter {
       case "NewEventListener":
         let bubbles = event_bubbles(edit.name);
 
-        // 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`);
-            let shouldPreventDefault = target.getAttribute(
-              `dioxus-prevent-default`
-            );
-
-            if (event.type === "click") {
-              // todo call prevent default if it's the right type of event
-              if (this.config.intercept_link_redirects) {
-                let a_element = target.closest("a");
-                if (a_element != null) {
-                  event.preventDefault();
-                  if (
-                    shouldPreventDefault !== `onclick` &&
-                    a_element.getAttribute(`dioxus-prevent-default`) !==
-                      `onclick`
-                  ) {
-                    const href = a_element.getAttribute("href");
-                    if (href !== "" && href !== null && href !== undefined) {
-                      window.ipc.postMessage(
-                        serializeIpcMessage("browser_open", { href })
-                      );
-                    }
-                  }
-                }
-              }
-
-              // also prevent buttons from submitting
-              if (target.tagName === "BUTTON" && event.type == "submit") {
-                event.preventDefault();
-              }
-            }
-            // walk the tree to find the real element
-            while (realId == null) {
-              // we've reached the root we don't want to send an event
-              if (target.parentElement === null) {
-                return;
-              }
-
-              target = target.parentElement;
-              realId = target.getAttribute(`data-dioxus-id`);
-            }
-
-            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 (
-              target.tagName === "FORM" &&
-              (event.type === "submit" || event.type === "input")
-            ) {
-              for (let x = 0; x < target.elements.length; x++) {
-                let element = target.elements[x];
-                let name = element.getAttribute("name");
-                if (name != null) {
-                  if (element.getAttribute("type") === "checkbox") {
-                    // @ts-ignore
-                    contents.values[name] = element.checked ? "true" : "false";
-                  } else if (element.getAttribute("type") === "radio") {
-                    if (element.checked) {
-                      contents.values[name] = element.value;
-                    }
-                  } else {
-                    // @ts-ignore
-                    contents.values[name] =
-                      element.value ?? element.textContent;
-                  }
-                }
-              }
-            }
-
-            if (realId === null) {
-              return;
-            }
-            window.ipc.postMessage(
-              serializeIpcMessage("user_event", {
-                name: edit.name,
-                element: parseInt(realId),
-                data: contents,
-                bubbles,
-              })
-            );
-          }
-        };
-        this.NewEventListener(edit.name, edit.id, bubbles, handler);
+        this.NewEventListener(edit.name, edit.id, bubbles, (event) => {
+          handler(event, edit.name, bubbles, this.config);
+        });
         break;
     }
   }
@@ -459,25 +363,34 @@ class Interpreter {
 
 // this handler is only provided on the desktop and liveview implementations since this
 // method is not used by the web implementation
-function handler(event, name, bubbles) {
+function handler(event, name, bubbles, config) {
   let target = event.target;
   if (target != null) {
-    let shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`);
+    let preventDefaultRequests = target.getAttribute(`dioxus-prevent-default`);
 
     if (event.type === "click") {
-      // Prevent redirects from links
-      let a_element = target.closest("a");
-      if (a_element != null) {
-        event.preventDefault();
-        if (
-          shouldPreventDefault !== `onclick` &&
-          a_element.getAttribute(`dioxus-prevent-default`) !== `onclick`
-        ) {
-          const href = a_element.getAttribute("href");
-          if (href !== "" && href !== null && href !== undefined) {
-            window.ipc.postMessage(
-              serializeIpcMessage("browser_open", { href })
-            );
+      // todo call prevent default if it's the right type of event
+      if (config.intercept_link_redirects) {
+        let a_element = target.closest("a");
+        if (a_element != null) {
+          event.preventDefault();
+
+          let elementShouldPreventDefault =
+            preventDefaultRequests && preventDefaultRequests.includes(`onclick`);
+          let aElementShouldPreventDefault = a_element.getAttribute(
+            `dioxus-prevent-default`
+          );
+          let linkShouldPreventDefault =
+            aElementShouldPreventDefault &&
+            aElementShouldPreventDefault.includes(`onclick`);
+
+          if (!elementShouldPreventDefault && !linkShouldPreventDefault) {
+            const href = a_element.getAttribute("href");
+            if (href !== "" && href !== null && href !== undefined) {
+              window.ipc.postMessage(
+                serializeIpcMessage("browser_open", { href })
+              );
+            }
           }
         }
       }
@@ -490,9 +403,10 @@ function handler(event, name, bubbles) {
 
     const realId = find_real_id(target);
 
-    shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`);
-
-    if (shouldPreventDefault === `on${event.type}`) {
+    if (
+      preventDefaultRequests &&
+      preventDefaultRequests.includes(`on${event.type}`)
+    ) {
       event.preventDefault();
     }
 

+ 1 - 1
packages/native-core/src/tree.rs

@@ -178,7 +178,7 @@ impl<'a> TreeMut for TreeMutView<'a> {
 fn set_height(tree: &mut TreeMutView<'_>, node: NodeId, height: u16) {
     let children = {
         let mut node_data_mut = &mut tree.1;
-        let mut node = (&mut node_data_mut).get(node).unwrap();
+        let node = (&mut node_data_mut).get(node).unwrap();
         node.height = height;
         node.children.clone()
     };

+ 0 - 0
packages/rink/examples/counter.rs → packages/rink/examples/counter_button.rs


+ 1 - 1
packages/rink/src/widget.rs

@@ -26,7 +26,7 @@ impl<'a> RinkBuffer<'a> {
             // panic!("({x}, {y}) is not in {area:?}");
             return;
         }
-        let mut cell = self.buf.get_mut(x, y);
+        let cell = self.buf.get_mut(x, y);
         cell.bg = convert(self.cfg.rendering_mode, new.bg.blend(cell.bg));
         if new.symbol.is_empty() {
             if !cell.symbol.is_empty() {

+ 0 - 0
packages/router/examples/simple.rs → packages/router/examples/simple_routes.rs


+ 8 - 4
packages/web/src/dom.rs

@@ -55,13 +55,17 @@ impl WebsysDom {
                 let element = walk_event_for_id(event);
                 let bubbles = dioxus_html::event_bubbles(name.as_str());
                 if let Some((element, target)) = element {
-                    if target
+                    if let Some(prevent_requests) = target
                         .get_attribute("dioxus-prevent-default")
                         .as_deref()
-                        .map(|f| f.trim_start_matches("on"))
-                        == Some(&name)
+                        .map(|f| f.split_whitespace())
                     {
-                        event.prevent_default();
+                        if prevent_requests
+                            .map(|f| f.trim_start_matches("on"))
+                            .any(|f| f == name)
+                        {
+                            event.prevent_default();
+                        }
                     }
 
                     let data = virtual_event_from_websys_event(event.clone(), target);