Quellcode durchsuchen

Merge branch 'master' into jk/form-ma

Jonathan Kelley vor 3 Jahren
Ursprung
Commit
c4556d050e

+ 2 - 0
Cargo.toml

@@ -22,6 +22,7 @@ dioxus-ssr = { path = "./packages/ssr", version = "^0.1.3", 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]

+ 2 - 0
packages/desktop/Cargo.toml

@@ -31,6 +31,8 @@ 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"]

+ 4 - 4
packages/desktop/examples/async.rs

@@ -10,14 +10,14 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    let count = use_state(&cx, || 0);
+    let (count, set_count) = use_state(&cx, || 0);
 
     use_future(&cx, || {
-        let count = count.for_async();
+        let set_count = set_count.clone();
         async move {
             loop {
                 tokio::time::sleep(Duration::from_millis(1000)).await;
-                *count.modify() += 1;
+                set_count.modify(|f| f + 1)
             }
         }
     });
@@ -26,7 +26,7 @@ fn app(cx: Scope) -> Element {
         div {
             h1 { "High-Five counter: {count}" }
             button {
-                onclick: move |_| count.set(0),
+                onclick: move |_| set_count(0),
                 "Click me!"
             }
         }

+ 0 - 550
packages/desktop/src/interpreter.js

@@ -1,550 +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`);
-            let shouldPreventDefault = target.getAttribute(
-              `dioxus-prevent-default`
-            );
-
-            if (event.type == "click") {
-              event.preventDefault();
-              if (shouldPreventDefault !== `onclick`) {
-                console.log("click", event);
-                console.log("clickeded", event.target);
-                console.log("clickeded", event.target.tagName);
-                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`);
-            }
-
-            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 (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,
-};

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

@@ -224,7 +224,7 @@ pub fn launch_with_props<P: 'static + Send>(
                         } else if trimmed == "index.js" {
                             wry::http::ResponseBuilder::new()
                                 .mimetype("text/javascript")
-                                .body(include_bytes!("./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;

+ 16 - 0
packages/interpreter/Cargo.toml

@@ -0,0 +1,16 @@
+[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]

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

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

+ 16 - 0
packages/interpreter/build.rs

@@ -0,0 +1,16 @@
+use std::process::Command;
+
+fn main() {
+    println!("cargo:rerun-if-changed=interpreter.ts");
+
+    match Command::new("tsc").spawn() {
+        Ok(_) => println!("Was spawned :)"),
+        Err(e) => {
+            if let std::io::ErrorKind::NotFound = e.kind() {
+                println!("`tsc` was not found! Not going to generate new interpreter")
+            } else {
+                println!("Some strange error occurred :(");
+            }
+        }
+    }
+}

+ 13 - 17
packages/jsinterpreter/interpreter.js → packages/interpreter/gen/interpreter.js

@@ -198,18 +198,28 @@ export class Interpreter {
                 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`);
+                        let shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`);
+                        if (event.type == "click") {
+                            event.preventDefault();
+                            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();
@@ -217,20 +227,6 @@ export class Interpreter {
                         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 (target.tagName == "FORM") {
                             let formTarget = target;
                             for (let x = 0; x < formTarget.elements.length; x++) {

+ 16 - 20
packages/jsinterpreter/interpreter.ts → packages/interpreter/src/interpreter.ts

@@ -1,7 +1,3 @@
-
-
-
-
 export function main() {
   let root = window.document.getElementById("main");
   if (root != null) {
@@ -244,13 +240,27 @@ export class Interpreter {
         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;
 
           if (target != null) {
             let realId = target.getAttribute(`data-dioxus-id`);
+            let shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`);
+
+            if (event.type == "click") {
+              event.preventDefault();
+              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) {
@@ -258,7 +268,7 @@ export class Interpreter {
               realId = target.getAttribute(`data-dioxus-id`);
             }
 
-            const shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`);
+            shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`);
 
             let contents = serialize_event(event);
 
@@ -270,20 +280,6 @@ export class Interpreter {
               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 (target.tagName == "FORM") {
               let formTarget = target as HTMLFormElement;

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

@@ -0,0 +1 @@
+pub static INTERPRTER_JS: &str = include_str!("../gen/interpreter.js");

+ 2 - 0
packages/jsinterpreter/tsconfig.json → packages/interpreter/tsconfig.json

@@ -8,6 +8,8 @@
             "es6",
             "dom"
         ],
+        "rootDir": "src",
         "strict": true,
+        "outDir": "gen",
     }
 }

+ 3 - 0
packages/web/Cargo.toml

@@ -29,6 +29,9 @@ gloo-timers = { version = "0.2.1", features = ["futures"] }
 futures-util = "0.3.15"
 smallstr = "0.2.0"
 
+[build-dependencies]
+dioxus-interpreter-js = { path = "../interpreter", version = "^0.0.0" }
+
 [dependencies.web-sys]
 version = "0.3.51"
 features = [

+ 76 - 0
packages/web/build.rs

@@ -0,0 +1,76 @@
+use std::{fs::File, io::Write};
+
+fn main() {
+    let src = format!(
+        r###"use js_sys::Function;
+use wasm_bindgen::prelude::*;
+use web_sys::{{Element, Node}};
+
+/*
+This is an autogenerated file from build.rs.
+Do not edit this file directly.
+*/
+
+#[wasm_bindgen(inline_js = r##"{}"##)]
+extern "C" {{
+    pub type Interpreter;
+
+    #[wasm_bindgen(constructor)]
+    pub fn new(arg: Element) -> Interpreter;
+
+    #[wasm_bindgen(method)]
+    pub fn set_node(this: &Interpreter, id: usize, node: Node);
+
+    #[wasm_bindgen(method)]
+    pub fn PushRoot(this: &Interpreter, root: u64);
+
+    #[wasm_bindgen(method)]
+    pub fn AppendChildren(this: &Interpreter, many: u32);
+
+    #[wasm_bindgen(method)]
+    pub fn ReplaceWith(this: &Interpreter, root: u64, m: u32);
+
+    #[wasm_bindgen(method)]
+    pub fn InsertAfter(this: &Interpreter, root: u64, n: u32);
+
+    #[wasm_bindgen(method)]
+    pub fn InsertBefore(this: &Interpreter, root: u64, n: u32);
+
+    #[wasm_bindgen(method)]
+    pub fn Remove(this: &Interpreter, root: u64);
+
+    #[wasm_bindgen(method)]
+    pub fn CreateTextNode(this: &Interpreter, text: &str, root: u64);
+
+    #[wasm_bindgen(method)]
+    pub fn CreateElement(this: &Interpreter, tag: &str, root: u64);
+
+    #[wasm_bindgen(method)]
+    pub fn CreateElementNs(this: &Interpreter, tag: &str, root: u64, ns: &str);
+
+    #[wasm_bindgen(method)]
+    pub fn CreatePlaceholder(this: &Interpreter, root: u64);
+
+    #[wasm_bindgen(method)]
+    pub fn NewEventListener(this: &Interpreter, name: &str, root: u64, handler: &Function);
+
+    #[wasm_bindgen(method)]
+    pub fn RemoveEventListener(this: &Interpreter, root: u64, name: &str);
+
+    #[wasm_bindgen(method)]
+    pub fn SetText(this: &Interpreter, root: u64, text: &str);
+
+    #[wasm_bindgen(method)]
+    pub fn SetAttribute(this: &Interpreter, root: u64, field: &str, value: &str, ns: Option<&str>);
+
+    #[wasm_bindgen(method)]
+    pub fn RemoveAttribute(this: &Interpreter, root: u64, field: &str);
+}}
+"###,
+        dioxus_interpreter_js::INTERPRTER_JS
+    );
+
+    // write the bindings to a local file
+    let mut file = File::create("src/bindings.rs").unwrap();
+    file.write_all(src.as_bytes()).unwrap();
+}

+ 519 - 1
packages/web/src/bindings.rs

@@ -2,7 +2,525 @@ use js_sys::Function;
 use wasm_bindgen::prelude::*;
 use web_sys::{Element, Node};
 
-#[wasm_bindgen(module = "/src/interpreter.js")]
+/*
+This is an autogenerated file from build.rs.
+Do not edit this file directly.
+*/
+
+#[wasm_bindgen(inline_js = r##"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":
+                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") {
+                            event.preventDefault();
+                            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`);
+                        }
+                        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") {
+                            let formTarget = target;
+                            for (let x = 0; x < formTarget.elements.length; x++) {
+                                let element = formTarget.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 {
+                                        // @ts-ignore
+                                        contents.values[name] = element.value ?? element.textContent;
+                                    }
+                                }
+                            }
+                        }
+                        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,
+                values: {}
+            };
+        }
+        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,
+};
+"##)]
 extern "C" {
     pub type Interpreter;
 

+ 0 - 499
packages/web/src/interpreter.js

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