瀏覽代碼

Add proper bundling to tsc

Jonathan Kelley 1 年之前
父節點
當前提交
4b64894fc5

+ 2 - 2
packages/interpreter/build.rs

@@ -23,7 +23,7 @@ fn main() {
 fn gen_bindings(name: &str) {
     let contents = read_to_string(&format!("src/{name}.ts")).unwrap();
     let generated = read_to_string(&format!("src/gen/{name}.js")).unwrap_or_default();
-    let hashed = hash_file(&contents);
+    let hashed = hash_it(&contents);
 
     // If the file is generated, and the hash is the same, we're good, don't do anything
     if generated
@@ -58,7 +58,7 @@ fn gen_bindings(name: &str) {
     std::fs::write(&format!("src/gen/{name}.js"), generated).unwrap();
 }
 
-fn hash_file(obj: &str) -> u64 {
+fn hash_it(obj: impl Hash) -> u64 {
     let mut hasher = DefaultHasher::new();
     obj.hash(&mut hasher);
     hasher.finish()

+ 0 - 79
packages/interpreter/src/common.js

@@ -1,79 +0,0 @@
-this.setAttributeInner = function (node, field, value, ns) {
-  const name = field;
-  if (ns === "style") {
-    // ????? why do we need to do this
-    if (node.style === undefined) {
-      node.style = {};
-    }
-    node.style[name] = value;
-  } else if (!!ns) {
-    node.setAttributeNS(ns, name, value);
-  } else {
-    switch (name) {
-      case "value":
-        if (value !== node.value) {
-          node.value = value;
-        }
-        break;
-      case "initial_value":
-        node.defaultValue = value;
-        break;
-      case "checked":
-        node.checked = truthy(value);
-        break;
-      case "initial_checked":
-        node.defaultChecked = truthy(value);
-        break;
-      case "selected":
-        node.selected = truthy(value);
-        break;
-      case "initial_selected":
-        node.defaultSelected = truthy(value);
-        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 (!truthy(value) && bool_attrs.hasOwnProperty(name)) {
-          node.removeAttribute(name);
-        } else {
-          node.setAttribute(name, value);
-        }
-    }
-  }
-}
-
-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,
-  webkitdirectory: true,
-};
-
-function truthy(val) {
-  return val === "true" || val === true;
-}

+ 0 - 79
packages/interpreter/src/common_exported.js

@@ -1,79 +0,0 @@
-export function setAttributeInner(node, field, value, ns) {
-  const name = field;
-  if (ns === "style") {
-    // ????? why do we need to do this
-    if (node.style === undefined) {
-      node.style = {};
-    }
-    node.style[name] = value;
-  } else if (!!ns) {
-    node.setAttributeNS(ns, name, value);
-  } else {
-    switch (name) {
-      case "value":
-        if (value !== node.value) {
-          node.value = value;
-        }
-        break;
-      case "initial_value":
-        node.defaultValue = value;
-        break;
-      case "checked":
-        node.checked = truthy(value);
-        break;
-      case "initial_checked":
-        node.defaultChecked = truthy(value);
-        break;
-      case "selected":
-        node.selected = truthy(value);
-        break;
-      case "initial_selected":
-        node.defaultSelected = truthy(value);
-        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 (!truthy(value) && bool_attrs.hasOwnProperty(name)) {
-          node.removeAttribute(name);
-        } else {
-          node.setAttribute(name, value);
-        }
-    }
-  }
-}
-
-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,
-  webkitdirectory: true,
-};
-
-function truthy(val) {
-  return val === "true" || val === true;
-}

+ 0 - 81
packages/interpreter/src/gen/common.js

@@ -1,81 +0,0 @@
-// DO NOT EDIT THIS FILE. HASH: 9578489549991746027
-export function setAttributeInner(node, field, value, ns) {
-    const name = field;
-    if (ns === "style") {
-        // ????? why do we need to do this
-        if (node.style === undefined) {
-            node.style = {};
-        }
-        node.style[name] = value;
-    }
-    else if (!!ns) {
-        node.setAttributeNS(ns, name, value);
-    }
-    else {
-        switch (name) {
-            case "value":
-                if (value !== node.value) {
-                    node.value = value;
-                }
-                break;
-            case "initial_value":
-                node.defaultValue = value;
-                break;
-            case "checked":
-                node.checked = truthy(value);
-                break;
-            case "initial_checked":
-                node.defaultChecked = truthy(value);
-                break;
-            case "selected":
-                node.selected = truthy(value);
-                break;
-            case "initial_selected":
-                node.defaultSelected = truthy(value);
-                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 (!truthy(value) && bool_attrs.hasOwnProperty(name)) {
-                    node.removeAttribute(name);
-                }
-                else {
-                    node.setAttribute(name, value);
-                }
-        }
-    }
-}
-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,
-    webkitdirectory: true,
-};
-function truthy(val) {
-    return val === "true" || val === true;
-}

+ 0 - 1
packages/interpreter/src/gen/form.js

@@ -1 +0,0 @@
-// DO NOT EDIT THIS FILE. HASH: 3476900567878811119

+ 0 - 0
packages/interpreter/src/gen/interpreter.js


+ 0 - 0
packages/interpreter/src/gen/sledgehammer.js


+ 0 - 606
packages/interpreter/src/interpreter.js

@@ -145,609 +145,3 @@ this.handler = async function (event, name, bubbles) {
     );
   }
 }
-
-function find_real_id(target) {
-  let realId = null;
-  if (target instanceof Element) {
-    realId = target.getAttribute(`data-dioxus-id`);
-  }
-  // walk the tree to find the real element
-  while (realId == null) {
-    // we've reached the root we don't want to send an event
-    if (target.parentElement === null) {
-      return;
-    }
-
-    target = target.parentElement;
-    if (target instanceof Element) {
-      realId = target.getAttribute(`data-dioxus-id`);
-    }
-  }
-  return realId;
-}
-
-class ListenerMap {
-  constructor(root) {
-    // bubbling events can listen at the root element
-    this.global = {};
-    // non bubbling events listen at the element the listener was created at
-    this.local = {};
-    this.root = null;
-  }
-
-  create(event_name, element, bubbles, handler) {
-    if (bubbles) {
-      if (this.global[event_name] === undefined) {
-        this.global[event_name] = {};
-        this.global[event_name].active = 1;
-        this.root.addEventListener(event_name, handler);
-      } else {
-        this.global[event_name].active++;
-      }
-    }
-    else {
-      const id = element.getAttribute("data-dioxus-id");
-      if (!this.local[id]) {
-        this.local[id] = {};
-      }
-      element.addEventListener(event_name, handler);
-    }
-  }
-
-  remove(element, event_name, bubbles) {
-    if (bubbles) {
-      this.global[event_name].active--;
-      if (this.global[event_name].active === 0) {
-        this.root.removeEventListener(event_name, this.global[event_name].callback);
-        delete this.global[event_name];
-      }
-    }
-    else {
-      const id = element.getAttribute("data-dioxus-id");
-      delete this.local[id][event_name];
-      if (this.local[id].length === 0) {
-        delete this.local[id];
-      }
-      element.removeEventListener(event_name, this.global[event_name].callback);
-    }
-  }
-
-  removeAllNonBubbling(element) {
-    const id = element.getAttribute("data-dioxus-id");
-    delete this.local[id];
-  }
-}
-this.LoadChild = function (array) {
-  // iterate through each number and get that child
-  let node = this.stack[this.stack.length - 1];
-
-  for (let i = 0; i < array.length; i++) {
-    this.end = array[i];
-    for (node = node.firstChild; this.end > 0; this.end--) {
-      node = node.nextSibling;
-    }
-  }
-  return node;
-}
-this.listeners = new ListenerMap();
-this.nodes = [];
-this.stack = [];
-this.root;
-this.templates = {};
-this.els = null;
-this.end = null;
-
-this.AppendChildren = function (id, many) {
-  this.root = this.nodes[id];
-  this.els = this.stack.splice(this.stack.length - many);
-  for (let k = 0; k < many; k++) {
-    this.root.appendChild(this.els[k]);
-  }
-}
-
-this.initialize = function (root) {
-  this.nodes = [root];
-  this.stack = [root];
-  this.listeners.root = root;
-}
-
-this.getClientRect = function (id) {
-  const node = this.nodes[id];
-  if (!node) {
-    return;
-  }
-  const rect = node.getBoundingClientRect();
-  return {
-    type: "GetClientRect",
-    origin: [rect.x, rect.y],
-    size: [rect.width, rect.height],
-  };
-}
-
-this.scrollTo = function (id, behavior) {
-  const node = this.nodes[id];
-  if (!node) {
-    return false;
-  }
-  node.scrollIntoView({
-    behavior: behavior,
-  });
-  return true;
-}
-
-/// Set the focus on the element
-this.setFocus = function (id, focus) {
-  const node = this.nodes[id];
-  if (!node) {
-    return false;
-  }
-  if (focus) {
-    node.focus();
-  } else {
-    node.blur();
-  }
-  return true;
-}
-
-function get_mouse_data(event) {
-  const {
-    altKey,
-    button,
-    buttons,
-    clientX,
-    clientY,
-    ctrlKey,
-    metaKey,
-    offsetX,
-    offsetY,
-    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,
-    offset_x: offsetX,
-    offset_y: offsetY,
-    page_x: pageX,
-    page_y: pageY,
-    screen_x: screenX,
-    screen_y: screenY,
-    shift_key: shiftKey,
-  };
-}
-
-async 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,
-        isComposing,
-        key,
-        altKey,
-        ctrlKey,
-        metaKey,
-        keyCode,
-        shiftKey,
-        location,
-        repeat,
-        which,
-        code,
-      } = event;
-      return {
-        char_code: charCode,
-        is_composing: isComposing,
-        key: key,
-        alt_key: altKey,
-        ctrl_key: ctrlKey,
-        meta_key: metaKey,
-        key_code: keyCode,
-        shift_key: shiftKey,
-        location: location,
-        repeat: repeat,
-        which: which,
-        code,
-      };
-    }
-    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,
-        values: {},
-      };
-    }
-    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 "drag":
-    case "dragend":
-    case "dragenter":
-    case "dragexit":
-    case "dragleave":
-    case "dragover":
-    case "dragstart":
-    case "drop": {
-      let files = null;
-      if (event.dataTransfer && event.dataTransfer.files) {
-        files = await serializeFileList(event.dataTransfer.files);
-      }
-
-      return { mouse: get_mouse_data(event), files };
-    }
-    case "click":
-    case "contextmenu":
-    case "doubleclick":
-    case "dblclick":
-    case "mousedown":
-    case "mouseenter":
-    case "mouseleave":
-    case "mousemove":
-    case "mouseout":
-    case "mouseover":
-    case "mouseup": {
-      return get_mouse_data(event);
-    }
-    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 {};
-    }
-  }
-}
-this.serializeIpcMessage = function (method, params = {}) {
-  return JSON.stringify({ method, params });
-}
-
-function is_element_node(node) {
-  return node.nodeType == 1;
-}
-
-function event_bubbles(event) {
-  switch (event) {
-    case "copy":
-      return true;
-    case "cut":
-      return true;
-    case "paste":
-      return true;
-    case "compositionend":
-      return true;
-    case "compositionstart":
-      return true;
-    case "compositionupdate":
-      return true;
-    case "keydown":
-      return true;
-    case "keypress":
-      return true;
-    case "keyup":
-      return true;
-    case "focus":
-      return false;
-    case "focusout":
-      return true;
-    case "focusin":
-      return true;
-    case "blur":
-      return false;
-    case "change":
-      return true;
-    case "input":
-      return true;
-    case "invalid":
-      return true;
-    case "reset":
-      return true;
-    case "submit":
-      return true;
-    case "click":
-      return true;
-    case "contextmenu":
-      return true;
-    case "doubleclick":
-      return true;
-    case "dblclick":
-      return true;
-    case "drag":
-      return true;
-    case "dragend":
-      return true;
-    case "dragenter":
-      return false;
-    case "dragexit":
-      return false;
-    case "dragleave":
-      return true;
-    case "dragover":
-      return true;
-    case "dragstart":
-      return true;
-    case "drop":
-      return true;
-    case "mousedown":
-      return true;
-    case "mouseenter":
-      return false;
-    case "mouseleave":
-      return false;
-    case "mousemove":
-      return true;
-    case "mouseout":
-      return true;
-    case "scroll":
-      return false;
-    case "mouseover":
-      return true;
-    case "mouseup":
-      return true;
-    case "pointerdown":
-      return true;
-    case "pointermove":
-      return true;
-    case "pointerup":
-      return true;
-    case "pointercancel":
-      return true;
-    case "gotpointercapture":
-      return true;
-    case "lostpointercapture":
-      return true;
-    case "pointerenter":
-      return false;
-    case "pointerleave":
-      return false;
-    case "pointerover":
-      return true;
-    case "pointerout":
-      return true;
-    case "select":
-      return true;
-    case "touchcancel":
-      return true;
-    case "touchend":
-      return true;
-    case "touchmove":
-      return true;
-    case "touchstart":
-      return true;
-    case "wheel":
-      return true;
-    case "abort":
-      return false;
-    case "canplay":
-      return false;
-    case "canplaythrough":
-      return false;
-    case "durationchange":
-      return false;
-    case "emptied":
-      return false;
-    case "encrypted":
-      return true;
-    case "ended":
-      return false;
-    case "error":
-      return false;
-    case "loadeddata":
-    case "loadedmetadata":
-    case "loadstart":
-    case "load":
-      return false;
-    case "pause":
-      return false;
-    case "play":
-      return false;
-    case "playing":
-      return false;
-    case "progress":
-      return false;
-    case "ratechange":
-      return false;
-    case "seeked":
-      return false;
-    case "seeking":
-      return false;
-    case "stalled":
-      return false;
-    case "suspend":
-      return false;
-    case "timeupdate":
-      return false;
-    case "volumechange":
-      return false;
-    case "waiting":
-      return false;
-    case "animationstart":
-      return true;
-    case "animationend":
-      return true;
-    case "animationiteration":
-      return true;
-    case "transitionend":
-      return true;
-    case "toggle":
-      return true;
-    case "mounted":
-      return false;
-  }
-
-  return true;
-}

+ 51 - 1
packages/interpreter/src/ts/form.ts

@@ -1,3 +1,53 @@
-function retrieveFormValues() {
+// Consistently deserialize forms and form elements for use across web/desktop/mobile
 
+type FormValues = { [key: string]: FormDataEntryValue[] };
+
+export function retriveValues(event: Event, target: HTMLElement): FormValues {
+  const contents: FormValues = {};
+
+  if (target instanceof HTMLFormElement && (event.type === "submit" || event.type === "input")) {
+    retrieveFormValues(target, contents);
+  }
+
+  if (target instanceof HTMLSelectElement && (event.type === "input" || event.type === "change")) {
+    retriveInputsValues(target, contents);
+  }
+
+  return contents;
+}
+
+export function retrieveFormValues(form: HTMLFormElement, contents: FormValues) {
+  const formData = new FormData(form);
+
+  for (let name in formData.keys()) {
+    let element = form.elements.namedItem(name);
+
+    // todo: this is going to be a problem for select-multiple?
+    if (!(element instanceof HTMLInputElement)) {
+      continue;
+    }
+
+    switch (element.type) {
+      case "select-multiple":
+        contents[name] = formData.getAll(name);
+        break;
+
+      // By default, it's just a single value
+      default:
+        contents[name] = [formData.get(name)];
+        break;
+    }
+  }
+}
+
+export function retriveInputsValues(target: HTMLSelectElement, contents: FormValues,) {
+  const selectData = target.options;
+  contents["options"] = [];
+
+  for (let i = 0; i < selectData.length; i++) {
+    let option = selectData[i];
+    if (option.selected) {
+      contents["options"].push(option.value.toString());
+    }
+  }
 }

+ 9 - 12
packages/interpreter/src/ts/interpreter_core.ts

@@ -1,9 +1,6 @@
 // The root interpreter class that holds state about the mapping between DOM and VirtualDom
 // This always lives in the JS side of things, and is extended by the native and web interpreters
 
-// todo: we want to make these vars that the interpreter uses to be isolated so we can have multiple interpreters
-export let m: DataView; // p, ls, d, t, op, i, e, z, metaflags;
-
 export class Interpreter {
   // non bubbling events listen at the element the listener was created at
   global: {
@@ -15,15 +12,15 @@ export class Interpreter {
       [key: string]: EventListener
     }
   };
-  root: Element;
+  root: HTMLElement;
   handler: EventListener;
-  nodes: Element[];
-  stack: Element[];
+  nodes: Node[];
+  stack: Node[];
   templates: {
-    [key: string]: Element[]
+    [key: string]: Node[]
   };
 
-  constructor(root: Element, handler: EventListener) {
+  constructor(root: HTMLElement, handler: EventListener) {
     this.root = root;
     this.nodes = [root];
     this.stack = [root];
@@ -32,7 +29,7 @@ export class Interpreter {
     this.handler = handler;
   }
 
-  createListener(event_name: string, element: Element, bubbles: boolean) {
+  createListener(event_name: string, element: HTMLElement, bubbles: boolean) {
     if (bubbles) {
       if (this.global[event_name] === undefined) {
         this.global[event_name] = { active: 1, callback: this.handler };
@@ -49,7 +46,7 @@ export class Interpreter {
     }
   }
 
-  removeListener(element: Element, event_name: string, bubbles: boolean) {
+  removeListener(element: HTMLElement, event_name: string, bubbles: boolean) {
     if (bubbles) {
       this.removeBubblingListener(event_name);
     } else {
@@ -65,7 +62,7 @@ export class Interpreter {
     }
   }
 
-  removeNonBubblingListener(element: Element, event_name: string) {
+  removeNonBubblingListener(element: HTMLElement, event_name: string) {
     const id = element.getAttribute("data-dioxus-id");
     delete this.local[id][event_name];
     if (Object.keys(this.local[id]).length === 0) {
@@ -74,7 +71,7 @@ export class Interpreter {
     element.removeEventListener(event_name, this.handler);
   }
 
-  removeAllNonBubblingListeners(element: Element) {
+  removeAllNonBubblingListeners(element: HTMLElement) {
     const id = element.getAttribute("data-dioxus-id");
     delete this.local[id];
   }

+ 173 - 184
packages/interpreter/src/ts/interpreter_native.ts

@@ -1,14 +1,27 @@
 // This file provides an extended variant of the interpreter used for desktop and liveview interaction
 //
-//
+// This file lives on the renderer, not the host. It's basically a polyfill over functionality that the host can't
+// provide since it doesn't have access to the dom.
 
+import { retriveValues } from "./form";
 import { Interpreter } from "./interpreter_core";
+import { SerializedEvent, serializeEvent } from "./serialize";
 
 export class NativeInterpreter extends Interpreter {
   intercept_link_redirects: boolean;
+  ipc: any;
+
+  // eventually we want to remove liveview and build it into the server-side-events of fullstack
+  // however, for now we need to support it since SSE in fullstack doesn't exist yet
+  liveview: boolean;
+
+  constructor(root: HTMLElement) {
+    super(root, (event) => this.handleEvent(event, event.type, true));
+    this.intercept_link_redirects = true;
+    this.liveview = false;
 
-  constructor(root: Element) {
-    super(root, (event) => handler(event, this, event.type, true));
+    // @ts-ignore - wry gives us this
+    this.ipc = window.ipc;
   }
 
   serializeIpcMessage(method: string, params = {}) {
@@ -17,50 +30,38 @@ export class NativeInterpreter extends Interpreter {
 
   scrollTo(id: number, behavior: ScrollBehavior) {
     const node = this.nodes[id];
-
-    if (!(node instanceof HTMLElement)) {
-      return false;
+    if (node instanceof HTMLElement) {
+      node.scrollIntoView({ behavior });
     }
-
-    node.scrollIntoView({
-      behavior: behavior,
-    });
-
-    return true;
   }
 
   getClientRect(id: number) {
     const node = this.nodes[id];
-    if (!node) {
-      return;
+    if (node instanceof HTMLElement) {
+      const rect = node.getBoundingClientRect();
+      return {
+        type: "GetClientRect",
+        origin: [rect.x, rect.y],
+        size: [rect.width, rect.height],
+      };
     }
-    const rect = node.getBoundingClientRect();
-    return {
-      type: "GetClientRect",
-      origin: [rect.x, rect.y],
-      size: [rect.width, rect.height],
-    };
   }
 
   setFocus(id: number, focus: boolean) {
     const node = this.nodes[id];
 
-    if (!(node instanceof HTMLElement)) {
-      return false;
-    }
-
-    if (focus) {
-      node.focus();
-    } else {
-      node.blur();
+    if (node instanceof HTMLElement) {
+      if (focus) {
+        node.focus();
+      } else {
+        node.blur();
+      }
     }
-
-    return true;
   }
 
   LoadChild(array: number[]) {
     // iterate through each number and get that child
-    let node = this.stack[this.stack.length - 1] as Node;
+    let node = this.stack[this.stack.length - 1];
 
     for (let i = 0; i < array.length; i++) {
       let end = array[i];
@@ -80,207 +81,195 @@ export class NativeInterpreter extends Interpreter {
       root.appendChild(els[k]);
     }
   }
-}
 
+  handleEvent(event: Event, name: string, bubbles: boolean) {
+    const target = event.target!;
+    const realId = targetId(target)!;
+    const contents = serializeEvent(event);
 
+    // Attempt to retrive the values from the form and inputs
+    if (target instanceof HTMLElement) {
+      contents.values = retriveValues(event, target);
+    }
 
+    // Handle the event on the virtualdom and then preventDefault if it also preventsDefault
+    // Some listeners
+    let body = {
+      name: name,
+      data: contents,
+      element: realId,
+      bubbles,
+    };
 
-function handler(event: Event, interpreter: NativeInterpreter, name: string, bubbles: boolean) {
-  const target = event.target!;
-  const realId = target_id(target)!;
-  let contents = serializeEvent(event);
+    // Run any prevent defaults the user might've set
+    // This is to support the prevent_default: "onclick" attribute that dioxus has had for a while, but is not necessary
+    // now that we expose preventDefault to the virtualdom on desktop
+    // Liveview will still need to use this
+    this.preventDefaults(event, target);
 
-  if (target instanceof HTMLFormElement && (event.type === "submit" || event.type === "input")) {
-    const formData = new FormData(target);
 
-    for (let name of formData.keys()) {
-      const fieldType = target.elements[name].type;
+    // liveview does not have syncronous event handling, so we need to send the event to the host
+    if (this.liveview) {
+      // Okay, so the user might've requested some files to be read
+      if (target instanceof HTMLInputElement && (event.type === "change" || event.type === "input")) {
+        if (target.getAttribute("type") === "file") {
+          this.readFiles(target, contents, bubbles, realId, name);
+        }
+      }
+    } else {
 
-      switch (fieldType) {
-        case "select-multiple":
-          contents.values[name] = formData.getAll(name);
-          break;
+      // Run the event handler on the virtualdom
+      // capture/prevent default of the event if the virtualdom wants to
+      const res = handleVirtualdomEventSync(JSON.stringify(body));
 
-        // add cases for fieldTypes that can hold multiple values here
-        default:
-          contents.values[name] = formData.get(name);
-          break;
+      if (res.preventDefault) {
+        event.preventDefault();
       }
-    }
-  }
 
-  if (target instanceof HTMLSelectElement && (event.type === "input" || event.type === "change")) {
-    const selectData = target.options;
-    contents.values["options"] = [];
-    for (let i = 0; i < selectData.length; i++) {
-      let option = selectData[i];
-      if (option.selected) {
-        contents.values["options"].push(option.value.toString());
+      if (res.stopPropagation) {
+        event.stopPropagation();
       }
     }
   }
 
-  // If there's files to read
-  if (target instanceof HTMLInputElement && (event.type === "change" || event.type === "input")) {
-    if (target.getAttribute("type") === "file") {
-      read_files(target, contents, bubbles, realId, name);
-      return;
+  async readFiles(target: HTMLInputElement, contents: SerializedEvent, bubbles: boolean, realId: number, name: string) {
+    let files = target.files!;
+    let file_contents: { [name: string]: number[] } = {};
+
+    for (let i = 0; i < files.length; i++) {
+      const file = files[i];
+      file_contents[file.name] = Array.from(
+        new Uint8Array(await file.arrayBuffer())
+      );
     }
-  }
 
-  prevents_default(event, target);
+    contents.files = { files: file_contents };
 
+    const message = this.serializeIpcMessage("user_event", {
+      name: name,
+      element: realId,
+      data: contents,
+      bubbles,
+    });
 
-  // Handle the event on the virtualdom and then process whatever its output was
-  let body = {
-    name: name,
-    data: serializeEvent(event),
-    element: parseInt(realId),
-    bubbles,
-  };
-
-  if (waitForVirtualDomPreventDefault(JSON.stringify(body))) {
-    event.preventDefault();
+    this.ipc.postMessage(message);
   }
-}
 
-export function waitForVirtualDomPreventDefault(contents: string): boolean {
 
+  // This should:
+  // - prevent form submissions from navigating
+  // - prevent anchor tags from navigating
+  // - prevent buttons from submitting forms
+  // - let the virtualdom attempt to prevent the event
+  preventDefaults(event: Event, target: EventTarget) {
+    let preventDefaultRequests: string | null = null;
 
-  // Handle the event on the virtualdom and then process whatever its output was
-  const xhr = new XMLHttpRequest();
-
-  // Serialize the event and send it to the custom protocol in the Rust side of things
-  xhr.timeout = 1000;
-  xhr.open("GET", "/handle/event.please", false);
-  xhr.setRequestHeader("Content-Type", "application/json");
-  xhr.send(contents);
-
-  // Deserialize the response, and then prevent the default/capture the event if the virtualdom wants to
-  return JSON.parse(xhr.responseText).preventDefault;
-}
+    // Some events can be triggered on text nodes, which don't have attributes
+    if (target instanceof Element) {
+      preventDefaultRequests = target.getAttribute(`dioxus-prevent-default`);
+    }
 
+    if (preventDefaultRequests && preventDefaultRequests.includes(`on${event.type}`)) {
+      event.preventDefault();
+    }
 
-async function read_files(target: HTMLInputElement, contents, bubbles, realId, name) {
-  let files = target.files!;
-  let file_contents: { [name: string]: number[] } = {};
+    if (event.type === "submit") {
+      event.preventDefault();
+    }
 
-  for (let i = 0; i < files.length; i++) {
-    const file = files[i];
-    file_contents[file.name] = Array.from(
-      new Uint8Array(await file.arrayBuffer())
-    );
+    // Attempt to intercept if the event is a click
+    if (target instanceof Element && event.type === "click") {
+      this.handleClickNavigate(event, target, preventDefaultRequests);
+    }
   }
 
-  contents.files = { files: file_contents };
+  handleClickNavigate(event: Event, target: Element, preventDefaultRequests: string) {
+    // todo call prevent default if it's the right type of event
+    if (!this.intercept_link_redirects) {
+      return;
+    }
 
-  if (realId === null) {
-    return;
-  }
+    // prevent buttons in forms from submitting the form
+    if (target.tagName === "BUTTON" && event.type == "submit") {
+      event.preventDefault();
+    }
 
-  const message = window.interpreter.serializeIpcMessage("user_event", {
-    name: name,
-    element: parseInt(realId),
-    data: contents,
-    bubbles,
-  });
+    // If the target is an anchor tag, we want to intercept the click too, to prevent the browser from navigating
+    let a_element = target.closest("a");
+    if (a_element == null) {
+      return;
+    }
 
-  window.ipc.postMessage(message);
-}
+    event.preventDefault();
 
+    let elementShouldPreventDefault =
+      preventDefaultRequests && preventDefaultRequests.includes(`onclick`);
 
-export function target_id(target: EventTarget): string | null {
-  if (!(target instanceof Element)) {
-    return null;
-  }
+    let aElementShouldPreventDefault = a_element.getAttribute(
+      `dioxus-prevent-default`
+    );
 
-  function find_real_id(target: Element): string | null {
-    let realId = target.getAttribute(`data-dioxus-id`);
+    let linkShouldPreventDefault =
+      aElementShouldPreventDefault &&
+      aElementShouldPreventDefault.includes(`onclick`);
 
-    // walk the tree to find the real element
-    while (realId == null) {
-      // we've reached the root we don't want to send an event
-      if (target.parentElement === null) {
-        return null;
-      }
-
-      target = target.parentElement;
-      if (target instanceof Element) {
-        realId = target.getAttribute(`data-dioxus-id`);
+    if (!elementShouldPreventDefault && !linkShouldPreventDefault) {
+      const href = a_element.getAttribute("href");
+      if (href !== "" && href !== null && href !== undefined) {
+        this.ipc.postMessage(
+          this.serializeIpcMessage("browser_open", { href })
+        );
       }
     }
-
-    return realId;
   }
-
-  return find_real_id(target);
 }
 
+type EventSyncResult = {
+  preventDefault: boolean;
+  stopPropagation: boolean;
+  stopImmediatePropagation: boolean;
+  filesRequested: boolean;
+};
 
+// This function sends the event to the virtualdom and then waits for the virtualdom to process it
+//
+// However, it's not really suitable for liveview, because it's synchronous and will block the main thread
+// We should definitely consider using a websocket if we want to block... or just not block on liveview
+// Liveview is a little bit of a tricky beast
+function handleVirtualdomEventSync(contents: string): EventSyncResult {
+  // Handle the event on the virtualdom and then process whatever its output was
+  const xhr = new XMLHttpRequest();
 
-// This should:
-// - prevent form submissions from navigating
-// - prevent anchor tags from navigating
-// - prevent buttons from submitting forms
-// - let the virtualdom attempt to prevent the event
-preventDefaults(event: Event, target: EventTarget) {
-  let preventDefaultRequests: string | null = null;
-
-  // Some events can be triggered on text nodes, which don't have attributes
-  if (target instanceof Element) {
-    preventDefaultRequests = target.getAttribute(`dioxus-prevent-default`);
-  }
-
-  if (preventDefaultRequests && preventDefaultRequests.includes(`on${event.type}`)) {
-    event.preventDefault();
-  }
-
-  if (event.type === "submit") {
-    event.preventDefault();
-  }
+  // Serialize the event and send it to the custom protocol in the Rust side of things
+  xhr.timeout = 1000;
+  xhr.open("GET", "/handle/event.please", false);
+  xhr.setRequestHeader("Content-Type", "application/json");
+  xhr.send(contents);
 
-  // Attempt to intercept if the event is a click
-  if (target instanceof Element && event.type === "click") {
-    this.preventFormNavigate(event, target);
-  }
+  // Deserialize the response, and then prevent the default/capture the event if the virtualdom wants to
+  return JSON.parse(xhr.responseText);
 }
 
-preventFormNavigate(event: Event, target: Element) {
-  // todo call prevent default if it's the right type of event
-  if (!this.intercept_link_redirects) {
-    return;
-  }
-
-  // prevent buttons in forms from submitting the form
-  if (target.tagName === "BUTTON") { //  && event.type == "submit"
-    event.preventDefault();
-  }
-
-  // If the target is an anchor tag, we want to intercept the click too, to prevent the browser from navigating
-  let a_element = target.closest("a");
-  if (a_element == null) {
-    return;
+function targetId(target: EventTarget): number | null {
+  // Ensure that the target is a node, sometimes it's nota
+  if (!(target instanceof Node)) {
+    return null;
   }
 
-  event.preventDefault();
-
-  let elementShouldPreventDefault =
-    preventDefaultRequests && preventDefaultRequests.includes(`onclick`);
+  let ourTarget = target;
+  let realId = null;
 
-  let aElementShouldPreventDefault = a_element.getAttribute(
-    `dioxus-prevent-default`
-  );
-
-  let linkShouldPreventDefault =
-    aElementShouldPreventDefault &&
-    aElementShouldPreventDefault.includes(`onclick`);
+  while (realId == null) {
+    if (ourTarget === null) {
+      return null;
+    }
 
-  if (!elementShouldPreventDefault && !linkShouldPreventDefault) {
-    const href = a_element.getAttribute("href");
-    if (href !== "" && href !== null && href !== undefined) {
-      window.ipc.postMessage(
-        window.interpreter.serializeIpcMessage("browser_open", { href })
-      );
+    if (ourTarget instanceof Element) {
+      realId = ourTarget.getAttribute(`data-dioxus-id`);
     }
+
+    ourTarget = ourTarget.parentNode;
   }
+
+  return parseInt(realId);
 }

+ 15 - 8
packages/interpreter/src/ts/interpreter_web.ts

@@ -4,10 +4,12 @@
 //
 // We're using sledgehammer directly
 
-import { Interpreter, m } from "./interpreter_core";
+import { Interpreter } from "./interpreter_core";
 
 export class WebInterpreter extends Interpreter {
-  constructor(root: Element, handler: EventListener) {
+  m: any;
+
+  constructor(root: HTMLElement, handler: EventListener) {
     super(root, handler);
   }
 
@@ -17,7 +19,7 @@ export class WebInterpreter extends Interpreter {
     let ptr_end = ptr + len;
 
     for (; ptr < ptr_end; ptr++) {
-      let end = m.getUint8(ptr);
+      let end = this.m.getUint8(ptr);
       for (node = node.firstChild; end > 0; end--) {
         node = node.nextSibling;
       }
@@ -26,7 +28,7 @@ export class WebInterpreter extends Interpreter {
     return node;
   }
 
-  saveTemplate(nodes: Element[], tmpl_id: string) {
+  saveTemplate(nodes: HTMLElement[], tmpl_id: string) {
     this.templates[tmpl_id] = nodes;
   }
 
@@ -34,7 +36,7 @@ export class WebInterpreter extends Interpreter {
     const hydrateNodes = document.querySelectorAll('[data-node-hydration]');
 
     for (let i = 0; i < hydrateNodes.length; i++) {
-      const hydrateNode = hydrateNodes[i];
+      const hydrateNode = hydrateNodes[i] as HTMLElement;
       const hydration = hydrateNode.getAttribute('data-node-hydration');
       const split = hydration!.split(',');
       const id = ids[parseInt(split[0])];
@@ -54,22 +56,27 @@ export class WebInterpreter extends Interpreter {
         }
       }
     }
+
     const treeWalker = document.createTreeWalker(
       document.body,
       NodeFilter.SHOW_COMMENT,
     );
+
     let currentNode = treeWalker.nextNode();
+
     while (currentNode) {
       const id = currentNode.textContent!;
       const split = id.split('node-id');
+
       if (split.length > 1) {
-        this.nodes[ids[parseInt(split[1])]] = currentNode.nextSibling as Element;
+        this.nodes[ids[parseInt(split[1])]] = currentNode.nextSibling;
       }
+
       currentNode = treeWalker.nextNode();
     }
   }
 
-  getNode(id: number): Element {
+  getNode(id: number): Node {
     return this.nodes[id];
   }
 
@@ -77,7 +84,7 @@ export class WebInterpreter extends Interpreter {
     const root = this.nodes[id];
     const els = this.stack.splice(this.stack.length - many);
     for (let k = 0; k < many; k++) {
-      this.root.appendChild(els[k]);
+      root.appendChild(els[k]);
     }
   }
 }

+ 3 - 3
packages/interpreter/src/ts/serialize.ts

@@ -1,12 +1,12 @@
 // Handle serialization of the event data across the IPC boundarytype SerialziedEvent = {};
 
-type SerializedEvent = {
-  values?: { [key: string]: FormDataEntryValue[] } | FormDataEntryValue[];
+export type SerializedEvent = {
+  values?: { [key: string]: FormDataEntryValue[] };
   value?: string;
   [key: string]: any;
 };
 
-function serializeEvent(event: Event): SerializedEvent {
+export function serializeEvent(event: Event): SerializedEvent {
 
   // copy, cut, paste
   if (event instanceof KeyboardEvent) {

+ 2 - 0
packages/interpreter/src/ts/set_attribute.ts

@@ -1,3 +1,5 @@
+// A unified interface for setting attributes on a node
+
 // this function should try and stay fast, if possible
 export function setAttributeInner(node: HTMLElement, field: string, value: string, ns: string) {
   // we support a single namespace by default: style

+ 4 - 1
packages/interpreter/tsconfig.json

@@ -3,7 +3,10 @@
         "module": "system",
         "lib": [
             "ES2015",
-            "DOM"
+            "DOM",
+            "dom",
+            "dom.iterable",
+            "esnext"
         ],
         "noImplicitAny": true,
         "removeComments": true,