瀏覽代碼

feat: remove dioxus id on non-event elements

Jonathan Kelley 3 年之前
父節點
當前提交
95e93ed

+ 33 - 0
examples/nested_listeners.rs

@@ -0,0 +1,33 @@
+//! Nested Listeners
+//!
+//! This example showcases how to control event bubbling from child to parents.
+//!
+//! Both web and desktop support bubbling and bubble cancelation.
+
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus::desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    cx.render(rsx! {
+        div {
+            onclick: move |_| println!("clicked! top"),
+            button {
+                onclick: move |_| println!("clicked! bottom propoate"),
+                "Propogate"
+            }
+            button {
+                onclick: move |evt| {
+                    println!("clicked! bottom no bubbling");
+                    evt.cancel_bubble();
+                },
+                "Dont propogate"
+            }
+            button {
+                "Does not handle clicks"
+            }
+        }
+    })
+}

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

@@ -13,8 +13,10 @@
     </div>
 </body>
 
-<script>var exports = {};</script>
-<script type="text/javascript" src="index.js" type="module"></script>
-<script>main()</script>
+<script>
+    import("./index.js").then(function (module) {
+        module.main();
+    });
+</script>
 
 </html>

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

@@ -205,7 +205,7 @@ pub fn launch_with_props<P: 'static + Send>(
                         }
                         None
                     })
-                    .with_custom_protocol("dioxus".into(), move |request| {
+                    .with_custom_protocol(String::from("dioxus"), move |request| {
                         // Any content that that uses the `dioxus://` scheme will be shuttled through this handler as a "special case"
                         // For now, we only serve two pieces of content which get included as bytes into the final binary.
                         let path = request.uri().replace("dioxus://", "");
@@ -225,10 +225,10 @@ pub fn launch_with_props<P: 'static + Send>(
                         }
                     })
                     .with_file_drop_handler(move |window, evet| {
-                        if let Some(handler) = file_handler.as_ref() {
-                            return handler(window, evet);
-                        }
-                        false
+                        file_handler
+                            .as_ref()
+                            .map(|handler| handler(window, evet))
+                            .unwrap_or_default()
                     });
 
                 for (name, handler) in cfg.protocos.drain(..) {

+ 473 - 543
packages/jsinterpreter/interpreter.js

@@ -1,569 +1,499 @@
-function serialize_event(event) {
-  var _a, _b;
-  switch (event.type) {
-    case "copy":
-    case "cut":
-    case "past": {
-      return {};
-    }
-    case "compositionend":
-    case "compositionstart":
-    case "compositionupdate": {
-      var data = event.data;
-      return {
-        data: data,
-      };
-    }
-    case "keydown":
-    case "keypress":
-    case "keyup": {
-      var _c = event,
-        charCode = _c.charCode,
-        key = _c.key,
-        altKey = _c.altKey,
-        ctrlKey = _c.ctrlKey,
-        metaKey = _c.metaKey,
-        keyCode = _c.keyCode,
-        shiftKey = _c.shiftKey,
-        location_1 = _c.location,
-        repeat = _c.repeat,
-        which = _c.which;
-      return {
-        char_code: charCode,
-        key: key,
-        alt_key: altKey,
-        ctrl_key: ctrlKey,
-        meta_key: metaKey,
-        key_code: keyCode,
-        shift_key: shiftKey,
-        location: location_1,
-        repeat: repeat,
-        which: which,
-        locale: "locale",
-      };
-    }
-    case "focus":
-    case "blur": {
-      return {};
+export function main() {
+    let root = window.document.getElementById("main");
+    if (root != null) {
+        window.interpreter = new Interpreter(root);
+        window.rpc.call("initialize");
     }
-    case "change": {
-      var target = event.target;
-      var value = void 0;
-      if (target.type === "checkbox" || target.type === "radio") {
-        value = target.checked ? "true" : "false";
-      } else {
-        value =
-          (_a = target.value) !== null && _a !== void 0
-            ? _a
-            : target.textContent;
-      }
-      return {
-        value: value,
-      };
-    }
-    case "input":
-    case "invalid":
-    case "reset":
-    case "submit": {
-      var target = event.target;
-      var value =
-        (_b = target.value) !== null && _b !== void 0 ? _b : 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": {
-      var _d = event,
-        altKey = _d.altKey,
-        button = _d.button,
-        buttons = _d.buttons,
-        clientX = _d.clientX,
-        clientY = _d.clientY,
-        ctrlKey = _d.ctrlKey,
-        metaKey = _d.metaKey,
-        pageX = _d.pageX,
-        pageY = _d.pageY,
-        screenX_1 = _d.screenX,
-        screenY_1 = _d.screenY,
-        shiftKey = _d.shiftKey;
-      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_1,
-        screen_y: screenY_1,
-        shift_key: shiftKey,
-      };
+}
+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];
     }
-    case "pointerdown":
-    case "pointermove":
-    case "pointerup":
-    case "pointercancel":
-    case "gotpointercapture":
-    case "lostpointercapture":
-    case "pointerenter":
-    case "pointerleave":
-    case "pointerover":
-    case "pointerout": {
-      var _e = event,
-        altKey = _e.altKey,
-        button = _e.button,
-        buttons = _e.buttons,
-        clientX = _e.clientX,
-        clientY = _e.clientY,
-        ctrlKey = _e.ctrlKey,
-        metaKey = _e.metaKey,
-        pageX = _e.pageX,
-        pageY = _e.pageY,
-        screenX_2 = _e.screenX,
-        screenY_2 = _e.screenY,
-        shiftKey = _e.shiftKey,
-        pointerId = _e.pointerId,
-        width = _e.width,
-        height = _e.height,
-        pressure = _e.pressure,
-        tangentialPressure = _e.tangentialPressure,
-        tiltX = _e.tiltX,
-        tiltY = _e.tiltY,
-        twist = _e.twist,
-        pointerType = _e.pointerType,
-        isPrimary = _e.isPrimary;
-      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_2,
-        screen_y: screenY_2,
-        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,
-      };
+    top() {
+        return this.stack[this.stack.length - 1];
     }
-    case "select": {
-      return {};
+    pop() {
+        return this.stack.pop();
     }
-    case "touchcancel":
-    case "touchend":
-    case "touchmove":
-    case "touchstart": {
-      var _f = event,
-        altKey = _f.altKey,
-        ctrlKey = _f.ctrlKey,
-        metaKey = _f.metaKey,
-        shiftKey = _f.shiftKey;
-      return {
-        // changed_touches: event.changedTouches,
-        // target_touches: event.targetTouches,
-        // touches: event.touches,
-        alt_key: altKey,
-        ctrl_key: ctrlKey,
-        meta_key: metaKey,
-        shift_key: shiftKey,
-      };
+    PushRoot(root) {
+        const node = this.nodes[root];
+        this.stack.push(node);
     }
-    case "scroll": {
-      return {};
+    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]);
+        }
     }
-    case "wheel": {
-      var _g = event,
-        deltaX = _g.deltaX,
-        deltaY = _g.deltaY,
-        deltaZ = _g.deltaZ,
-        deltaMode = _g.deltaMode;
-      return {
-        delta_x: deltaX,
-        delta_y: deltaY,
-        delta_z: deltaZ,
-        delta_mode: deltaMode,
-      };
+    ReplaceWith(root_id, m) {
+        let root = this.nodes[root_id];
+        let els = this.stack.splice(this.stack.length - m);
+        root.replaceWith(...els);
     }
-    case "animationstart":
-    case "animationend":
-    case "animationiteration": {
-      var _h = event,
-        animationName = _h.animationName,
-        elapsedTime = _h.elapsedTime,
-        pseudoElement = _h.pseudoElement;
-      return {
-        animation_name: animationName,
-        elapsed_time: elapsedTime,
-        pseudo_element: pseudoElement,
-      };
+    InsertAfter(root, n) {
+        let old = this.nodes[root];
+        let new_nodes = this.stack.splice(this.stack.length - n);
+        old.after(...new_nodes);
     }
-    case "transitionend": {
-      var _j = event,
-        propertyName = _j.propertyName,
-        elapsedTime = _j.elapsedTime,
-        pseudoElement = _j.pseudoElement;
-      return {
-        property_name: propertyName,
-        elapsed_time: elapsedTime,
-        pseudo_element: pseudoElement,
-      };
+    InsertBefore(root, n) {
+        let old = this.nodes[root];
+        let new_nodes = this.stack.splice(this.stack.length - n);
+        old.before(...new_nodes);
     }
-    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 {};
+    Remove(root) {
+        let node = this.nodes[root];
+        if (node !== undefined) {
+            node.remove();
+        }
     }
-    case "toggle": {
-      return {};
+    CreateTextNode(text, root) {
+        // todo: make it so the types are okay
+        const node = document.createTextNode(text);
+        this.nodes[root] = node;
+        this.stack.push(node);
     }
-    default: {
-      return {};
+    CreateElement(tag, root) {
+        const el = document.createElement(tag);
+        // el.setAttribute("data-dioxus-id", `${root}`);
+        this.nodes[root] = el;
+        this.stack.push(el);
     }
-  }
-}
-var 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,
-};
-var Interpreter = /** @class */ (function () {
-  function Interpreter(root) {
-    this.root = root;
-    this.stack = [root];
-    this.listeners = {};
-    this.handlers = {};
-    this.lastNodeWasText = false;
-    this.nodes = [root];
-  }
-  Interpreter.prototype.top = function () {
-    return this.stack[this.stack.length - 1];
-  };
-  Interpreter.prototype.pop = function () {
-    return this.stack.pop();
-  };
-  Interpreter.prototype.PushRoot = function (root) {
-    var node = this.nodes[root];
-    this.stack.push(node);
-  };
-  Interpreter.prototype.AppendChildren = function (many) {
-    var root = this.stack[this.stack.length - (1 + many)];
-    var to_add = this.stack.splice(this.stack.length - many);
-    for (var i = 0; i < many; i++) {
-      root.appendChild(to_add[i]);
+    CreateElementNs(tag, root, ns) {
+        let el = document.createElementNS(ns, tag);
+        this.stack.push(el);
+        this.nodes[root] = el;
     }
-  };
-  Interpreter.prototype.ReplaceWith = function (root_id, m) {
-    var root = this.nodes[root_id];
-    var els = this.stack.splice(this.stack.length - m);
-    root.replaceWith.apply(root, els);
-  };
-  Interpreter.prototype.InsertAfter = function (root, n) {
-    var old = this.nodes[root];
-    var new_nodes = this.stack.splice(this.stack.length - n);
-    old.after.apply(old, new_nodes);
-  };
-  Interpreter.prototype.InsertBefore = function (root, n) {
-    var old = this.nodes[root];
-    var new_nodes = this.stack.splice(this.stack.length - n);
-    old.before.apply(old, new_nodes);
-  };
-  Interpreter.prototype.Remove = function (root) {
-    var node = this.nodes[root];
-    if (node !== undefined) {
-      node.remove();
+    CreatePlaceholder(root) {
+        let el = document.createElement("pre");
+        el.hidden = true;
+        this.stack.push(el);
+        this.nodes[root] = el;
     }
-  };
-  Interpreter.prototype.CreateTextNode = function (text, root) {
-    // todo: make it so the types are okay
-    var node = document.createTextNode(text);
-    this.nodes[root] = node;
-    this.stack.push(node);
-  };
-  Interpreter.prototype.CreateElement = function (tag, root) {
-    var el = document.createElement(tag);
-    el.setAttribute("dioxus-id", "".concat(root));
-    this.nodes[root] = el;
-    this.stack.push(el);
-  };
-  Interpreter.prototype.CreateElementNs = function (tag, root, ns) {
-    var el = document.createElementNS(ns, tag);
-    this.stack.push(el);
-    this.nodes[root] = el;
-  };
-  Interpreter.prototype.CreatePlaceholder = function (root) {
-    var el = document.createElement("pre");
-    el.hidden = true;
-    this.stack.push(el);
-    this.nodes[root] = el;
-  };
-  Interpreter.prototype.NewEventListener = function (
-    event_name,
-    scope,
-    root,
-    handler
-  ) {
-    // console.log('new event listener', event_name, root, scope);
-    var element = this.nodes[root];
-    element.setAttribute(
-      "dioxus-event-".concat(event_name),
-      "".concat(scope, ".").concat(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]++;
+    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]++;
+        }
     }
-  };
-  Interpreter.prototype.RemoveEventListener = function (root, event_name) {
-    var element = this.nodes[root];
-    element.removeAttribute("dioxus-event-".concat(event_name));
-    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];
+    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];
+        }
     }
-  };
-  Interpreter.prototype.SetText = function (root, text) {
-    this.nodes[root].textContent = text;
-  };
-  Interpreter.prototype.SetAttribute = function (root, field, value, ns) {
-    var name = field;
-    var 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);
-          }
-      }
+    SetText(root, text) {
+        this.nodes[root].textContent = text;
     }
-  };
-  Interpreter.prototype.RemoveAttribute = function (root, name) {
-    var node = this.nodes[root];
-    node.removeAttribute(name);
-    if (name === "value") {
-      node.value = "";
+    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);
+                    }
+            }
+        }
     }
-    if (name === "checked") {
-      node.checked = false;
+    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;
+        }
     }
-    if (name === "selected") {
-      node.selected = false;
+    handleEdits(edits) {
+        this.stack.push(this.root);
+        for (let edit of edits) {
+            this.handleEdit(edit);
+        }
     }
-  };
-  Interpreter.prototype.handleEdits = function (edits) {
-    this.stack.push(this.root);
-    for (var _i = 0, edits_1 = edits; _i < edits_1.length; _i++) {
-      var edit = edits_1[_i];
-      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;
+        }
     }
-  };
-  Interpreter.prototype.handleEdit = function (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":
-        // todo: only on desktop should we make our own handler
-        var handler = function (event) {
-          var target = event.target;
-          // console.log("event", event);
-          if (target != null) {
-            var realId = target.getAttribute("dioxus-id");
-            var shouldPreventDefault = target.getAttribute(
-              "dioxus-prevent-default"
-            );
-            var contents = serialize_event(event);
-            if (shouldPreventDefault === "on".concat(event.type)) {
-              event.preventDefault();
-            }
-
-            if (event.type == "submit") {
-              event.preventDefault();
+}
+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";
             }
-
-            if (event.type == "click") {
-              event.preventDefault();
-              if (shouldPreventDefault !== `onclick`) {
-                if (target.tagName == "A") {
-                  var href = target.getAttribute("href");
-                  if (
-                    href !== "" &&
-                    href !== null &&
-                    href !== undefined &&
-                    realId != null
-                  ) {
-                    window.rpc.call("browser_open", {
-                      mounted_dom_id: parseInt(realId),
-                      href: href,
-                    });
-                  }
-                }
-              }
+            else {
+                value = target.value ?? target.textContent;
             }
-
-            if (realId == null) {
-              return;
+            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";
             }
-            window.rpc.call("user_event", {
-              event: edit.event_name,
-              mounted_dom_id: parseInt(realId),
-              contents: contents,
-            });
-          }
-        };
-        this.NewEventListener(edit.event_name, edit.scope, 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;
+            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 {};
+        }
     }
-  };
-  return Interpreter;
-})();
-export { Interpreter };
-function main() {
-  var root = window.document.getElementById("main");
-  if (root != null) {
-    window.interpreter = new Interpreter(root);
-    window.rpc.call("initialize");
-  }
 }
-//# sourceMappingURL=interpreter.js.map
+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,
+};

+ 536 - 532
packages/jsinterpreter/interpreter.ts

@@ -1,331 +1,22 @@
-function serialize_event(event: Event) {
-  switch (event.type) {
-    case "copy":
-    case "cut":
-    case "past": {
-      return {};
-    }
-
-    case "compositionend":
-    case "compositionstart":
-    case "compositionupdate": {
-      let { data } = (event as CompositionEvent);
-      return {
-        data,
-      };
-    }
-
-    case "keydown":
-    case "keypress":
-    case "keyup": {
-      let {
-        charCode,
-        key,
-        altKey,
-        ctrlKey,
-        metaKey,
-        keyCode,
-        shiftKey,
-        location,
-        repeat,
-        which,
-      } = (event as KeyboardEvent);
-
-      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 as HTMLInputElement;
-      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 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 as MouseEvent;
-
-      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 as PointerEvent;
-      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 as TouchEvent;
-      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 as WheelEvent;
-      return {
-        delta_x: deltaX,
-        delta_y: deltaY,
-        delta_z: deltaZ,
-        delta_mode: deltaMode,
-      };
-    }
-
-    case "animationstart":
-    case "animationend":
-    case "animationiteration": {
-      const {
-        animationName,
-        elapsedTime,
-        pseudoElement,
-      } = event as AnimationEvent;
-      return {
-        animation_name: animationName,
-        elapsed_time: elapsedTime,
-        pseudo_element: pseudoElement,
-      };
-    }
-
-    case "transitionend": {
-      const {
-        propertyName,
-        elapsedTime,
-        pseudoElement,
-      } = event as TransitionEvent;
-      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 {};
-    }
+
+
+export function main() {
+  let root = window.document.getElementById("main");
+  if (root != null) {
+    window.interpreter = new Interpreter(root);
+    window.rpc.call("initialize");
+  }
+}
+
+declare global {
+  interface Window {
+    interpreter: Interpreter;
+    rpc: { call: (method: string, args?: any) => void };
   }
 }
 
-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,
-};
 
 export class Interpreter {
   root: Element;
@@ -394,240 +85,564 @@ export class Interpreter {
     }
   }
 
-  CreateTextNode(text: string, root: number) {
-    // todo: make it so the types are okay
-    const node = document.createTextNode(text) as any as Element;
-    this.nodes[root] = node;
-    this.stack.push(node);
+  CreateTextNode(text: string, root: number) {
+    // todo: make it so the types are okay
+    const node = document.createTextNode(text) as any as Element;
+    this.nodes[root] = node;
+    this.stack.push(node);
+  }
+
+  CreateElement(tag: string, root: number) {
+    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) {
+    let el = document.createElementNS(ns, tag);
+    this.stack.push(el);
+    this.nodes[root] = el;
+  }
+
+  CreatePlaceholder(root: number) {
+    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) {
+    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: number, event_name: string) {
+    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) {
+    this.nodes[root].textContent = text;
+  }
+
+  SetAttribute(root: number, field: string, value: string, ns: string | undefined) {
+    const name = field;
+    const node = this.nodes[root];
+
+    if (ns == "style") {
+
+      // @ts-ignore
+      (node as HTMLElement).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;
+          }
+          break;
+        case "checked":
+          (node as HTMLInputElement).checked = value === "true";
+          break;
+        case "selected":
+          (node as HTMLOptionElement).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: number, name: string) {
+
+    const node = this.nodes[root];
+    node.removeAttribute(name);
+
+    if (name === "value") {
+      (node as HTMLInputElement).value = "";
+    }
+
+    if (name === "checked") {
+      (node as HTMLInputElement).checked = false;
+    }
+
+    if (name === "selected") {
+      (node as HTMLOptionElement).selected = false;
+    }
+  }
+
+  handleEdits(edits: DomEdit[]) {
+    this.stack.push(this.root);
+
+    for (let edit of edits) {
+      this.handleEdit(edit);
+    }
+  }
+
+  handleEdit(edit: DomEdit) {
+    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: Event) => {
+          let target = event.target as Element | null;
+
+          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 as NewEventListener).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;
+    }
   }
+}
 
-  CreateElement(tag: string, root: number) {
-    const el = document.createElement(tag);
-    el.setAttribute("dioxus-id", `${root}`);
 
-    this.nodes[root] = el;
-    this.stack.push(el);
-  }
 
-  CreateElementNs(tag: string, root: number, ns: string) {
-    let el = document.createElementNS(ns, tag);
-    this.stack.push(el);
-    this.nodes[root] = el;
-  }
+function serialize_event(event: Event) {
+  switch (event.type) {
+    case "copy":
+    case "cut":
+    case "past": {
+      return {};
+    }
 
-  CreatePlaceholder(root: number) {
-    let el = document.createElement("pre");
-    el.hidden = true;
-    this.stack.push(el);
-    this.nodes[root] = el;
-  }
+    case "compositionend":
+    case "compositionstart":
+    case "compositionupdate": {
+      let { data } = (event as CompositionEvent);
+      return {
+        data,
+      };
+    }
 
-  NewEventListener(event_name: string, scope: number, root: number, handler: (evt: Event) => void) {
-    // console.log('new event listener', event_name, root, scope);
-    const element = this.nodes[root];
-    element.setAttribute(
-      `dioxus-event-${event_name}`,
-      `${scope}.${root}`
-    );
+    case "keydown":
+    case "keypress":
+    case "keyup": {
+      let {
+        charCode,
+        key,
+        altKey,
+        ctrlKey,
+        metaKey,
+        keyCode,
+        shiftKey,
+        location,
+        repeat,
+        which,
+      } = (event as KeyboardEvent);
 
-    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]++;
+      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",
+      };
     }
-  }
 
-  RemoveEventListener(root: number, event_name: string) {
-    const element = this.nodes[root];
-    element.removeAttribute(
-      `dioxus-event-${event_name}`
-    );
+    case "focus":
+    case "blur": {
+      return {};
+    }
 
-    this.listeners[event_name]--;
+    case "change": {
+      let target = event.target as HTMLInputElement;
+      let value;
+      if (target.type === "checkbox" || target.type === "radio") {
+        value = target.checked ? "true" : "false";
+      } else {
+        value = target.value ?? target.textContent;
+      }
 
-    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];
+      return {
+        value: value,
+      };
     }
-  }
 
+    case "input":
+    case "invalid":
+    case "reset":
+    case "submit": {
+      let target = event.target as HTMLFormElement;
+      let value = target.value ?? target.textContent;
 
-  SetText(root: number, text: string) {
-    this.nodes[root].textContent = text;
-  }
+      if (target.type == "checkbox") {
+        value = target.checked ? "true" : "false";
+      }
 
-  SetAttribute(root: number, field: string, value: string, ns: string | undefined) {
-    const name = field;
-    const node = this.nodes[root];
+      return {
+        value: value,
+      };
+    }
 
-    if (ns == "style") {
+    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 as MouseEvent;
 
-      // @ts-ignore
-      (node as HTMLElement).style[name] = value;
+      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,
+      };
+    }
 
-    } 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;
-          }
-          break;
-        case "checked":
-          (node as HTMLInputElement).checked = value === "true";
-          break;
-        case "selected":
-          (node as HTMLOptionElement).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);
-          }
-      }
+    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 as PointerEvent;
+      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,
+      };
     }
-  }
-  RemoveAttribute(root: number, name: string) {
-
-    const node = this.nodes[root];
-    node.removeAttribute(name);
 
-    if (name === "value") {
-      (node as HTMLInputElement).value = "";
+    case "select": {
+      return {};
     }
 
-    if (name === "checked") {
-      (node as HTMLInputElement).checked = false;
+    case "touchcancel":
+    case "touchend":
+    case "touchmove":
+    case "touchstart": {
+      const {
+        altKey,
+        ctrlKey,
+        metaKey,
+        shiftKey,
+      } = event as TouchEvent;
+      return {
+        // changed_touches: event.changedTouches,
+        // target_touches: event.targetTouches,
+        // touches: event.touches,
+        alt_key: altKey,
+        ctrl_key: ctrlKey,
+        meta_key: metaKey,
+        shift_key: shiftKey,
+      };
     }
 
-    if (name === "selected") {
-      (node as HTMLOptionElement).selected = false;
+    case "scroll": {
+      return {};
     }
-  }
 
-  handleEdits(edits: DomEdit[]) {
-    this.stack.push(this.root);
-
-    for (let edit of edits) {
-      this.handleEdit(edit);
+    case "wheel": {
+      const {
+        deltaX,
+        deltaY,
+        deltaZ,
+        deltaMode,
+      } = event as WheelEvent;
+      return {
+        delta_x: deltaX,
+        delta_y: deltaY,
+        delta_z: deltaZ,
+        delta_mode: deltaMode,
+      };
     }
-  }
-
-  handleEdit(edit: DomEdit) {
-    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: Event) => {
-          const target = event.target as Element | null;
-
-          if (target != null) {
-            const realId = target.getAttribute(`dioxus-id`);
-
-            const shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`);
-
-            let contents = serialize_event(event);
 
-            if (shouldPreventDefault === `on${event.type}`) {
-              event.preventDefault();
-            }
+    case "animationstart":
+    case "animationend":
+    case "animationiteration": {
+      const {
+        animationName,
+        elapsedTime,
+        pseudoElement,
+      } = event as AnimationEvent;
+      return {
+        animation_name: animationName,
+        elapsed_time: elapsedTime,
+        pseudo_element: pseudoElement,
+      };
+    }
 
-            if (event.type == "submit") {
-              event.preventDefault();
-            }
+    case "transitionend": {
+      const {
+        propertyName,
+        elapsedTime,
+        pseudoElement,
+      } = event as TransitionEvent;
+      return {
+        property_name: propertyName,
+        elapsed_time: elapsedTime,
+        pseudo_element: pseudoElement,
+      };
+    }
 
-            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
-                    });
-                  }
-                }
-              }
-            }
+    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 {};
+    }
 
-            if (realId == null) {
-              return;
-            }
+    case "toggle": {
+      return {};
+    }
 
-            window.rpc.call("user_event", {
-              event: (edit as NewEventListener).event_name,
-              mounted_dom_id: parseInt(realId),
-              contents: contents,
-            });
-          }
-        };
-        this.NewEventListener(edit.event_name, edit.scope, 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;
+    default: {
+      return {};
     }
   }
 }
 
-function main() {
-  let root = window.document.getElementById("main");
-  if (root != null) {
-    window.interpreter = new Interpreter(root);
-    window.rpc.call("initialize");
-  }
-}
+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,
+};
+
 
 
 type PushRoot = { type: "PushRoot", root: number };
@@ -663,14 +678,3 @@ type DomEdit =
   SetText |
   SetAttribute |
   RemoveAttribute;
-
-
-export { };
-declare global {
-  interface Window {
-    interpreter: Interpreter;
-    rpc: { call: (method: string, args?: any) => void };
-  }
-}
-
-

+ 3 - 3
packages/jsinterpreter/tsconfig.json

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

+ 31 - 15
packages/web/src/dom.rs

@@ -277,9 +277,7 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc<dyn Any + Send
 /// This function decodes a websys event and produces an EventTrigger
 /// With the websys implementation, we attach a unique key to the nodes
 fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<UserEvent> {
-    use anyhow::Context;
-
-    let target = event
+    let mut target = event
         .target()
         .expect("missing target")
         .dyn_into::<Element>()
@@ -287,18 +285,36 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<UserEvent> {
 
     let typ = event.type_();
 
-    let element_id = target
-        .get_attribute("dioxus-id")
-        .context("Could not find element id on event target")?
-        .parse()?;
-
-    Ok(UserEvent {
-        name: event_name_from_typ(&typ),
-        data: virtual_event_from_websys_event(event.clone()),
-        element: Some(ElementId(element_id)),
-        scope_id: None,
-        priority: dioxus_core::EventPriority::Medium,
-    })
+    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 {