diff --git a/examples/todomvc.rs b/examples/todomvc.rs index e5f7d1dfaa..937d6992c1 100644 --- a/examples/todomvc.rs +++ b/examples/todomvc.rs @@ -120,15 +120,18 @@ pub fn todo_entry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element { rsx!(cx, li { class: "{completed} {editing}", - onclick: move |_| set_is_editing(true), - onfocusout: move |_| set_is_editing(false), div { class: "view", input { class: "toggle", r#type: "checkbox", id: "cbg-{todo.id}", checked: "{todo.checked}", onchange: move |evt| { cx.props.set_todos.make_mut()[&cx.props.id].checked = evt.value.parse().unwrap(); } } - label { r#for: "cbg-{todo.id}", pointer_events: "none", "{todo.contents}" } + label { + r#for: "cbg-{todo.id}", + onclick: move |_| set_is_editing(true), + onfocusout: move |_| set_is_editing(false), + "{todo.contents}" + } } is_editing.then(|| rsx!{ input { diff --git a/packages/interpreter/Cargo.toml b/packages/interpreter/Cargo.toml index fe150e1f9b..1e0f4bb510 100644 --- a/packages/interpreter/Cargo.toml +++ b/packages/interpreter/Cargo.toml @@ -14,3 +14,11 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +wasm-bindgen = { version = "0.2.79", optional = true } +js-sys = { version = "0.3.56", optional = true } +web-sys = { version = "0.3.56", optional = true, features = ["Element", "Node"] } + + +[features] +default = [] +web = ["wasm-bindgen", "js-sys", "web-sys"] diff --git a/packages/interpreter/build.rs b/packages/interpreter/build.rs deleted file mode 100644 index 610e3c3c35..0000000000 --- a/packages/interpreter/build.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std::process::Command; - -fn main() { - println!("cargo:rerun-if-changed=interpreter.ts"); - - match Command::new("tsc").spawn() { - Ok(_) => println!("Was spawned :)"), - Err(e) => { - if let std::io::ErrorKind::NotFound = e.kind() { - println!("`tsc` was not found! Not going to generate new interpreter") - } else { - println!("Some strange error occurred :("); - } - } - } -} diff --git a/packages/interpreter/gen/interpreter.js b/packages/interpreter/gen/interpreter.js deleted file mode 100644 index 0d89efa85a..0000000000 --- a/packages/interpreter/gen/interpreter.js +++ /dev/null @@ -1,497 +0,0 @@ -export function main() { - let root = window.document.getElementById("main"); - if (root != null) { - window.interpreter = new Interpreter(root); - window.rpc.call("initialize"); - } -} -export class Interpreter { - root; - stack; - listeners; - handlers; - lastNodeWasText; - nodes; - constructor(root) { - this.root = root; - this.stack = [root]; - this.listeners = {}; - this.handlers = {}; - this.lastNodeWasText = false; - this.nodes = [root]; - } - top() { - return this.stack[this.stack.length - 1]; - } - pop() { - return this.stack.pop(); - } - PushRoot(root) { - const node = this.nodes[root]; - this.stack.push(node); - } - AppendChildren(many) { - let root = this.stack[this.stack.length - (1 + many)]; - let to_add = this.stack.splice(this.stack.length - many); - for (let i = 0; i < many; i++) { - root.appendChild(to_add[i]); - } - } - ReplaceWith(root_id, m) { - let root = this.nodes[root_id]; - let els = this.stack.splice(this.stack.length - m); - root.replaceWith(...els); - } - InsertAfter(root, n) { - let old = this.nodes[root]; - let new_nodes = this.stack.splice(this.stack.length - n); - old.after(...new_nodes); - } - InsertBefore(root, n) { - let old = this.nodes[root]; - let new_nodes = this.stack.splice(this.stack.length - n); - old.before(...new_nodes); - } - Remove(root) { - let node = this.nodes[root]; - if (node !== undefined) { - node.remove(); - } - } - CreateTextNode(text, root) { - // todo: make it so the types are okay - const node = document.createTextNode(text); - this.nodes[root] = node; - this.stack.push(node); - } - CreateElement(tag, root) { - const el = document.createElement(tag); - // el.setAttribute("data-dioxus-id", `${root}`); - this.nodes[root] = el; - this.stack.push(el); - } - CreateElementNs(tag, root, ns) { - let el = document.createElementNS(ns, tag); - this.stack.push(el); - this.nodes[root] = el; - } - CreatePlaceholder(root) { - let el = document.createElement("pre"); - el.hidden = true; - this.stack.push(el); - this.nodes[root] = el; - } - NewEventListener(event_name, root, handler) { - const element = this.nodes[root]; - element.setAttribute("data-dioxus-id", `${root}`); - if (this.listeners[event_name] === undefined) { - this.listeners[event_name] = 0; - this.handlers[event_name] = handler; - this.root.addEventListener(event_name, handler); - } - else { - this.listeners[event_name]++; - } - } - RemoveEventListener(root, event_name) { - const element = this.nodes[root]; - element.removeAttribute(`data-dioxus-id`); - this.listeners[event_name]--; - if (this.listeners[event_name] === 0) { - this.root.removeEventListener(event_name, this.handlers[event_name]); - delete this.listeners[event_name]; - delete this.handlers[event_name]; - } - } - SetText(root, text) { - this.nodes[root].textContent = text; - } - SetAttribute(root, field, value, ns) { - const name = field; - const node = this.nodes[root]; - if (ns == "style") { - // @ts-ignore - node.style[name] = value; - } - else if (ns != null || ns != undefined) { - node.setAttributeNS(ns, name, value); - } - else { - switch (name) { - case "value": - if (value != node.value) { - node.value = value; - } - break; - case "checked": - node.checked = value === "true"; - break; - case "selected": - node.selected = value === "true"; - break; - case "dangerous_inner_html": - node.innerHTML = value; - break; - default: - // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364 - if (value == "false" && bool_attrs.hasOwnProperty(name)) { - node.removeAttribute(name); - } - else { - node.setAttribute(name, value); - } - } - } - } - RemoveAttribute(root, name) { - const node = this.nodes[root]; - node.removeAttribute(name); - if (name === "value") { - node.value = ""; - } - if (name === "checked") { - node.checked = false; - } - if (name === "selected") { - node.selected = false; - } - } - handleEdits(edits) { - this.stack.push(this.root); - for (let edit of edits) { - this.handleEdit(edit); - } - } - handleEdit(edit) { - switch (edit.type) { - case "PushRoot": - this.PushRoot(edit.root); - break; - case "AppendChildren": - this.AppendChildren(edit.many); - break; - case "ReplaceWith": - this.ReplaceWith(edit.root, edit.m); - break; - case "InsertAfter": - this.InsertAfter(edit.root, edit.n); - break; - case "InsertBefore": - this.InsertBefore(edit.root, edit.n); - break; - case "Remove": - this.Remove(edit.root); - break; - case "CreateTextNode": - this.CreateTextNode(edit.text, edit.root); - break; - case "CreateElement": - this.CreateElement(edit.tag, edit.root); - break; - case "CreateElementNs": - this.CreateElementNs(edit.tag, edit.root, edit.ns); - break; - case "CreatePlaceholder": - this.CreatePlaceholder(edit.root); - break; - case "RemoveEventListener": - this.RemoveEventListener(edit.root, edit.event_name); - break; - case "NewEventListener": - // this handler is only provided on desktop implementations since this - // method is not used by the web implementation - let handler = (event) => { - let target = event.target; - if (target != null) { - let realId = target.getAttribute(`data-dioxus-id`); - let shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); - if (event.type == "click") { - event.preventDefault(); - if (shouldPreventDefault !== `onclick`) { - if (target.tagName == "A") { - const href = target.getAttribute("href"); - if (href !== "" && href !== null && href !== undefined) { - window.rpc.call("browser_open", { href }); - } - } - } - } - // walk the tree to find the real element - while (realId == null && target.parentElement != null) { - target = target.parentElement; - realId = target.getAttribute(`data-dioxus-id`); - } - shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); - let contents = serialize_event(event); - if (shouldPreventDefault === `on${event.type}`) { - event.preventDefault(); - } - if (event.type == "submit") { - event.preventDefault(); - } - if (realId == null) { - return; - } - window.rpc.call("user_event", { - event: edit.event_name, - mounted_dom_id: parseInt(realId), - contents: contents, - }); - } - }; - this.NewEventListener(edit.event_name, edit.root, handler); - break; - case "SetText": - this.SetText(edit.root, edit.text); - break; - case "SetAttribute": - this.SetAttribute(edit.root, edit.field, edit.value, edit.ns); - break; - case "RemoveAttribute": - this.RemoveAttribute(edit.root, edit.name); - break; - } - } -} -function serialize_event(event) { - switch (event.type) { - case "copy": - case "cut": - case "past": { - return {}; - } - case "compositionend": - case "compositionstart": - case "compositionupdate": { - let { data } = event; - return { - data, - }; - } - case "keydown": - case "keypress": - case "keyup": { - let { charCode, key, altKey, ctrlKey, metaKey, keyCode, shiftKey, location, repeat, which, } = event; - return { - char_code: charCode, - key: key, - alt_key: altKey, - ctrl_key: ctrlKey, - meta_key: metaKey, - key_code: keyCode, - shift_key: shiftKey, - location: location, - repeat: repeat, - which: which, - locale: "locale", - }; - } - case "focus": - case "blur": { - return {}; - } - case "change": { - let target = event.target; - let value; - if (target.type === "checkbox" || target.type === "radio") { - value = target.checked ? "true" : "false"; - } - else { - value = target.value ?? target.textContent; - } - return { - value: value, - }; - } - case "input": - case "invalid": - case "reset": - case "submit": { - let target = event.target; - let value = target.value ?? target.textContent; - if (target.type == "checkbox") { - value = target.checked ? "true" : "false"; - } - return { - value: value, - }; - } - case "click": - case "contextmenu": - case "doubleclick": - case "drag": - case "dragend": - case "dragenter": - case "dragexit": - case "dragleave": - case "dragover": - case "dragstart": - case "drop": - case "mousedown": - case "mouseenter": - case "mouseleave": - case "mousemove": - case "mouseout": - case "mouseover": - case "mouseup": { - const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, } = event; - return { - alt_key: altKey, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey, - meta_key: metaKey, - page_x: pageX, - page_y: pageY, - screen_x: screenX, - screen_y: screenY, - shift_key: shiftKey, - }; - } - case "pointerdown": - case "pointermove": - case "pointerup": - case "pointercancel": - case "gotpointercapture": - case "lostpointercapture": - case "pointerenter": - case "pointerleave": - case "pointerover": - case "pointerout": { - const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, pointerId, width, height, pressure, tangentialPressure, tiltX, tiltY, twist, pointerType, isPrimary, } = event; - return { - alt_key: altKey, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey, - meta_key: metaKey, - page_x: pageX, - page_y: pageY, - screen_x: screenX, - screen_y: screenY, - shift_key: shiftKey, - pointer_id: pointerId, - width: width, - height: height, - pressure: pressure, - tangential_pressure: tangentialPressure, - tilt_x: tiltX, - tilt_y: tiltY, - twist: twist, - pointer_type: pointerType, - is_primary: isPrimary, - }; - } - case "select": { - return {}; - } - case "touchcancel": - case "touchend": - case "touchmove": - case "touchstart": { - const { altKey, ctrlKey, metaKey, shiftKey, } = event; - return { - // changed_touches: event.changedTouches, - // target_touches: event.targetTouches, - // touches: event.touches, - alt_key: altKey, - ctrl_key: ctrlKey, - meta_key: metaKey, - shift_key: shiftKey, - }; - } - case "scroll": { - return {}; - } - case "wheel": { - const { deltaX, deltaY, deltaZ, deltaMode, } = event; - return { - delta_x: deltaX, - delta_y: deltaY, - delta_z: deltaZ, - delta_mode: deltaMode, - }; - } - case "animationstart": - case "animationend": - case "animationiteration": { - const { animationName, elapsedTime, pseudoElement, } = event; - return { - animation_name: animationName, - elapsed_time: elapsedTime, - pseudo_element: pseudoElement, - }; - } - case "transitionend": { - const { propertyName, elapsedTime, pseudoElement, } = event; - return { - property_name: propertyName, - elapsed_time: elapsedTime, - pseudo_element: pseudoElement, - }; - } - case "abort": - case "canplay": - case "canplaythrough": - case "durationchange": - case "emptied": - case "encrypted": - case "ended": - case "error": - case "loadeddata": - case "loadedmetadata": - case "loadstart": - case "pause": - case "play": - case "playing": - case "progress": - case "ratechange": - case "seeked": - case "seeking": - case "stalled": - case "suspend": - case "timeupdate": - case "volumechange": - case "waiting": { - return {}; - } - case "toggle": { - return {}; - } - default: { - return {}; - } - } -} -const bool_attrs = { - allowfullscreen: true, - allowpaymentrequest: true, - async: true, - autofocus: true, - autoplay: true, - checked: true, - controls: true, - default: true, - defer: true, - disabled: true, - formnovalidate: true, - hidden: true, - ismap: true, - itemscope: true, - loop: true, - multiple: true, - muted: true, - nomodule: true, - novalidate: true, - open: true, - playsinline: true, - readonly: true, - required: true, - reversed: true, - selected: true, - truespeed: true, -}; diff --git a/packages/web/build.rs b/packages/interpreter/src/bindings.rs similarity index 77% rename from packages/web/build.rs rename to packages/interpreter/src/bindings.rs index 3c9baa7c8e..fa96e32308 100644 --- a/packages/web/build.rs +++ b/packages/interpreter/src/bindings.rs @@ -1,18 +1,9 @@ -use std::{fs::File, io::Write}; - -fn main() { - let src = format!( - r###"use js_sys::Function; +use js_sys::Function; use wasm_bindgen::prelude::*; -use web_sys::{{Element, Node}}; - -/* -This is an autogenerated file from build.rs. -Do not edit this file directly. -*/ +use web_sys::{Element, Node}; -#[wasm_bindgen(inline_js = r##"{}"##)] -extern "C" {{ +#[wasm_bindgen(module = "/src/interpreter.js")] +extern "C" { pub type Interpreter; #[wasm_bindgen(constructor)] @@ -65,12 +56,4 @@ extern "C" {{ #[wasm_bindgen(method)] pub fn RemoveAttribute(this: &Interpreter, root: u64, field: &str); -}} -"###, - dioxus_interpreter_js::INTERPRTER_JS - ); - - // write the bindings to a local file - let mut file = File::create("src/bindings.rs").unwrap(); - file.write_all(src.as_bytes()).unwrap(); } diff --git a/packages/interpreter/src/interpreter.ts b/packages/interpreter/src/interpreter.js similarity index 72% rename from packages/interpreter/src/interpreter.ts rename to packages/interpreter/src/interpreter.js index f150410f47..dbcb73a96e 100644 --- a/packages/interpreter/src/interpreter.ts +++ b/packages/interpreter/src/interpreter.js @@ -5,25 +5,14 @@ export function main() { window.rpc.call("initialize"); } } - -declare global { - interface Window { - interpreter: Interpreter; - rpc: { call: (method: string, args?: any) => void }; - } -} - - export class Interpreter { - root: Element; - stack: Element[]; - listeners: { [key: string]: number }; - handlers: { [key: string]: (evt: Event) => void }; - lastNodeWasText: boolean; - nodes: Element[]; - - - constructor(root: Element) { + root; + stack; + listeners; + handlers; + lastNodeWasText; + nodes; + constructor(root) { this.root = root; this.stack = [root]; this.listeners = {}; @@ -31,88 +20,70 @@ export class Interpreter { this.lastNodeWasText = false; this.nodes = [root]; } - top() { return this.stack[this.stack.length - 1]; } - pop() { return this.stack.pop(); } - - PushRoot(root: number) { + PushRoot(root) { const node = this.nodes[root]; this.stack.push(node); } - - AppendChildren(many: number) { + AppendChildren(many) { let root = this.stack[this.stack.length - (1 + many)]; - let to_add = this.stack.splice(this.stack.length - many); - for (let i = 0; i < many; i++) { root.appendChild(to_add[i]); } } - - ReplaceWith(root_id: number, m: number) { - let root = this.nodes[root_id] as Element; + ReplaceWith(root_id, m) { + let root = this.nodes[root_id]; let els = this.stack.splice(this.stack.length - m); - root.replaceWith(...els); } - - InsertAfter(root: number, n: number) { - let old = this.nodes[root] as Element; + InsertAfter(root, n) { + let old = this.nodes[root]; let new_nodes = this.stack.splice(this.stack.length - n); old.after(...new_nodes); } - - InsertBefore(root: number, n: number) { - let old = this.nodes[root] as Element; + InsertBefore(root, n) { + let old = this.nodes[root]; let new_nodes = this.stack.splice(this.stack.length - n); old.before(...new_nodes); } - - Remove(root: number) { - let node = this.nodes[root] as Element; + Remove(root) { + let node = this.nodes[root]; if (node !== undefined) { node.remove(); } } - - CreateTextNode(text: string, root: number) { + CreateTextNode(text, root) { // todo: make it so the types are okay - const node = document.createTextNode(text) as any as Element; + const node = document.createTextNode(text); this.nodes[root] = node; this.stack.push(node); } - - CreateElement(tag: string, root: number) { + CreateElement(tag, root) { const el = document.createElement(tag); // el.setAttribute("data-dioxus-id", `${root}`); - this.nodes[root] = el; this.stack.push(el); } - - CreateElementNs(tag: string, root: number, ns: string) { + CreateElementNs(tag, root, ns) { let el = document.createElementNS(ns, tag); this.stack.push(el); this.nodes[root] = el; } - - CreatePlaceholder(root: number) { + CreatePlaceholder(root) { let el = document.createElement("pre"); el.hidden = true; this.stack.push(el); this.nodes[root] = el; } - - NewEventListener(event_name: string, root: number, handler: (evt: Event) => void) { + NewEventListener(event_name, root, handler) { const element = this.nodes[root]; element.setAttribute("data-dioxus-id", `${root}`); - if (this.listeners[event_name] === undefined) { this.listeners[event_name] = 0; this.handlers[event_name] = handler; @@ -121,48 +92,39 @@ export class Interpreter { this.listeners[event_name]++; } } - - RemoveEventListener(root: number, event_name: string) { + RemoveEventListener(root, event_name) { const element = this.nodes[root]; element.removeAttribute(`data-dioxus-id`); - this.listeners[event_name]--; - if (this.listeners[event_name] === 0) { this.root.removeEventListener(event_name, this.handlers[event_name]); delete this.listeners[event_name]; delete this.handlers[event_name]; } } - - - SetText(root: number, text: string) { + SetText(root, text) { this.nodes[root].textContent = text; } - - SetAttribute(root: number, field: string, value: string, ns: string | undefined) { + SetAttribute(root, field, value, ns) { const name = field; const node = this.nodes[root]; - if (ns == "style") { - // @ts-ignore - (node as HTMLElement).style[name] = value; - + node.style[name] = value; } else if (ns != null || ns != undefined) { node.setAttributeNS(ns, name, value); } else { switch (name) { case "value": - if (value != (node as HTMLInputElement).value) { - (node as HTMLInputElement).value = value; + if (value != node.value) { + node.value = value; } break; case "checked": - (node as HTMLInputElement).checked = value === "true"; + node.checked = value === "true"; break; case "selected": - (node as HTMLOptionElement).selected = value === "true"; + node.selected = value === "true"; break; case "dangerous_inner_html": node.innerHTML = value; @@ -177,33 +139,26 @@ export class Interpreter { } } } - RemoveAttribute(root: number, name: string) { - + RemoveAttribute(root, name) { const node = this.nodes[root]; node.removeAttribute(name); - if (name === "value") { - (node as HTMLInputElement).value = ""; + node.value = ""; } - if (name === "checked") { - (node as HTMLInputElement).checked = false; + node.checked = false; } - if (name === "selected") { - (node as HTMLOptionElement).selected = false; + node.selected = false; } } - - handleEdits(edits: DomEdit[]) { + handleEdits(edits) { this.stack.push(this.root); - for (let edit of edits) { this.handleEdit(edit); } } - - handleEdit(edit: DomEdit) { + handleEdit(edit) { switch (edit.type) { case "PushRoot": this.PushRoot(edit.root); @@ -239,54 +194,48 @@ export class Interpreter { this.RemoveEventListener(edit.root, edit.event_name); break; case "NewEventListener": - - // this handler is only provided on desktop implementations since this // method is not used by the web implementation - let handler = (event: Event) => { - let target = event.target as Element | null; - + let handler = (event) => { + let target = event.target; if (target != null) { let realId = target.getAttribute(`data-dioxus-id`); - let shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); + let shouldPreventDefault = target.getAttribute( + `dioxus-prevent-default` + ); if (event.type == "click") { - event.preventDefault(); + // todo call prevent default if it's the right type of event if (shouldPreventDefault !== `onclick`) { if (target.tagName == "A") { - const href = target.getAttribute("href") + const href = target.getAttribute("href"); if (href !== "" && href !== null && href !== undefined) { window.rpc.call("browser_open", { href }); } } } } - // walk the tree to find the real element while (realId == null && target.parentElement != null) { target = target.parentElement; realId = target.getAttribute(`data-dioxus-id`); } - shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); - + shouldPreventDefault = target.getAttribute( + `dioxus-prevent-default` + ); let contents = serialize_event(event); - if (shouldPreventDefault === `on${event.type}`) { - event.preventDefault(); + // event.preventDefault(); } - if (event.type == "submit") { - event.preventDefault(); + // event.preventDefault(); } - - if (realId == null) { return; } - window.rpc.call("user_event", { - event: (edit as NewEventListener).event_name, + event: edit.event_name, mounted_dom_id: parseInt(realId), contents: contents, }); @@ -306,26 +255,21 @@ export class Interpreter { } } } - - - -function serialize_event(event: Event) { +function serialize_event(event) { switch (event.type) { case "copy": case "cut": case "past": { return {}; } - case "compositionend": case "compositionstart": case "compositionupdate": { - let { data } = (event as CompositionEvent); + let { data } = event; return { data, }; } - case "keydown": case "keypress": case "keyup": { @@ -340,8 +284,7 @@ function serialize_event(event: Event) { location, repeat, which, - } = (event as KeyboardEvent); - + } = event; return { char_code: charCode, key: key, @@ -356,42 +299,35 @@ function serialize_event(event: Event) { locale: "locale", }; } - case "focus": case "blur": { return {}; } - case "change": { - let target = event.target as HTMLInputElement; + let target = event.target; let value; if (target.type === "checkbox" || target.type === "radio") { value = target.checked ? "true" : "false"; } else { value = target.value ?? target.textContent; } - return { value: value, }; } - case "input": case "invalid": case "reset": case "submit": { - let target = event.target as HTMLFormElement; + let target = event.target; let value = target.value ?? target.textContent; - if (target.type == "checkbox") { value = target.checked ? "true" : "false"; } - return { value: value, }; } - case "click": case "contextmenu": case "doubleclick": @@ -423,8 +359,7 @@ function serialize_event(event: Event) { screenX, screenY, shiftKey, - } = event as MouseEvent; - + } = event; return { alt_key: altKey, button: button, @@ -440,7 +375,6 @@ function serialize_event(event: Event) { shift_key: shiftKey, }; } - case "pointerdown": case "pointermove": case "pointerup": @@ -474,7 +408,7 @@ function serialize_event(event: Event) { twist, pointerType, isPrimary, - } = event as PointerEvent; + } = event; return { alt_key: altKey, button: button, @@ -500,21 +434,14 @@ function serialize_event(event: Event) { is_primary: isPrimary, }; } - case "select": { return {}; } - case "touchcancel": case "touchend": case "touchmove": case "touchstart": { - const { - altKey, - ctrlKey, - metaKey, - shiftKey, - } = event as TouchEvent; + const { altKey, ctrlKey, metaKey, shiftKey } = event; return { // changed_touches: event.changedTouches, // target_touches: event.targetTouches, @@ -525,18 +452,11 @@ function serialize_event(event: Event) { shift_key: shiftKey, }; } - case "scroll": { return {}; } - case "wheel": { - const { - deltaX, - deltaY, - deltaZ, - deltaMode, - } = event as WheelEvent; + const { deltaX, deltaY, deltaZ, deltaMode } = event; return { delta_x: deltaX, delta_y: deltaY, @@ -544,35 +464,24 @@ function serialize_event(event: Event) { delta_mode: deltaMode, }; } - case "animationstart": case "animationend": case "animationiteration": { - const { - animationName, - elapsedTime, - pseudoElement, - } = event as AnimationEvent; + const { animationName, elapsedTime, pseudoElement } = event; return { animation_name: animationName, elapsed_time: elapsedTime, pseudo_element: pseudoElement, }; } - case "transitionend": { - const { - propertyName, - elapsedTime, - pseudoElement, - } = event as TransitionEvent; + const { propertyName, elapsedTime, pseudoElement } = event; return { property_name: propertyName, elapsed_time: elapsedTime, pseudo_element: pseudoElement, }; } - case "abort": case "canplay": case "canplaythrough": @@ -598,17 +507,14 @@ function serialize_event(event: Event) { case "waiting": { return {}; } - case "toggle": { return {}; } - default: { return {}; } } } - const bool_attrs = { allowfullscreen: true, allowpaymentrequest: true, @@ -637,39 +543,3 @@ const bool_attrs = { selected: true, truespeed: true, }; - - - -type PushRoot = { type: "PushRoot", root: number }; -type AppendChildren = { type: "AppendChildren", many: number }; -type ReplaceWith = { type: "ReplaceWith", root: number, m: number }; -type InsertAfter = { type: "InsertAfter", root: number, n: number }; -type InsertBefore = { type: "InsertBefore", root: number, n: number }; -type Remove = { type: "Remove", root: number }; -type CreateTextNode = { type: "CreateTextNode", text: string, root: number }; -type CreateElement = { type: "CreateElement", tag: string, root: number }; -type CreateElementNs = { type: "CreateElementNs", tag: string, root: number, ns: string }; -type CreatePlaceholder = { type: "CreatePlaceholder", root: number }; -type NewEventListener = { type: "NewEventListener", root: number, event_name: string, scope: number }; -type RemoveEventListener = { type: "RemoveEventListener", event_name: string, scope: number, root: number }; -type SetText = { type: "SetText", root: number, text: string }; -type SetAttribute = { type: "SetAttribute", root: number, field: string, value: string, ns: string | undefined }; -type RemoveAttribute = { type: "RemoveAttribute", root: number, name: string }; - - -type DomEdit = - PushRoot | - AppendChildren | - ReplaceWith | - InsertAfter | - InsertBefore | - Remove | - CreateTextNode | - CreateElement | - CreateElementNs | - CreatePlaceholder | - NewEventListener | - RemoveEventListener | - SetText | - SetAttribute | - RemoveAttribute; diff --git a/packages/interpreter/src/lib.rs b/packages/interpreter/src/lib.rs index 7d680ae254..ebca3cbf4a 100644 --- a/packages/interpreter/src/lib.rs +++ b/packages/interpreter/src/lib.rs @@ -1 +1,7 @@ -pub static INTERPRTER_JS: &str = include_str!("../gen/interpreter.js"); +pub static INTERPRTER_JS: &str = include_str!("./interpreter.js"); + +#[cfg(feature = "web")] +mod bindings; + +#[cfg(feature = "web")] +pub use bindings::Interpreter; diff --git a/packages/interpreter/tsconfig.json b/packages/interpreter/tsconfig.json deleted file mode 100644 index 1c1ed1ef9f..0000000000 --- a/packages/interpreter/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNext", - "module": "ES2015", - "lib": [ - "es2015", - "es5", - "es6", - "dom" - ], - "rootDir": "src", - "strict": true, - "outDir": "gen", - } -} diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 9ac508a8cf..9145fed8b7 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -13,27 +13,25 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] dioxus-core = { path = "../core", version = "^0.1.9" } dioxus-html = { path = "../html", version = "^0.1.6" } -js-sys = "0.3" -wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] } +js-sys = "0.3.56" +wasm-bindgen = { version = "0.2.79", features = ["enable-interning"] } lazy_static = "1.4.0" -wasm-bindgen-futures = "0.4.20" +wasm-bindgen-futures = "0.4.29" log = { version = "0.4.14", features = ["release_max_level_off"] } fxhash = "0.2.1" wasm-logger = "0.2.0" console_error_panic_hook = { version = "0.1.7", optional = true } -wasm-bindgen-test = "0.3.21" -once_cell = "1.8" +wasm-bindgen-test = "0.3.29" +once_cell = "1.9.0" async-channel = "1.6.1" -anyhow = "1.0" -gloo-timers = { version = "0.2.1", features = ["futures"] } -futures-util = "0.3.15" +anyhow = "1.0.53" +gloo-timers = { version = "0.2.3", features = ["futures"] } +futures-util = "0.3.19" smallstr = "0.2.0" - -[build-dependencies] -dioxus-interpreter-js = { path = "../interpreter", version = "^0.0.0" } +dioxus-interpreter-js = { path = "../interpreter", version = "^0.0.0", features = ["web"] } [dependencies.web-sys] -version = "0.3.51" +version = "0.3.56" features = [ "Comment", "Attr", @@ -78,5 +76,5 @@ panic_hook = ["console_error_panic_hook"] [dev-dependencies] dioxus-core-macro = { path = "../core-macro" } -wasm-bindgen-test = "0.3.28" +wasm-bindgen-test = "0.3.29" dioxus-ssr = { path = "../ssr" } diff --git a/packages/web/src/bindings.rs b/packages/web/src/bindings.rs deleted file mode 100644 index 4834146afa..0000000000 --- a/packages/web/src/bindings.rs +++ /dev/null @@ -1,561 +0,0 @@ -use js_sys::Function; -use wasm_bindgen::prelude::*; -use web_sys::{Element, Node}; - -/* -This is an autogenerated file from build.rs. -Do not edit this file directly. -*/ - -#[wasm_bindgen(inline_js = r##"export function main() { - let root = window.document.getElementById("main"); - if (root != null) { - window.interpreter = new Interpreter(root); - window.rpc.call("initialize"); - } -} -export class Interpreter { - root; - stack; - listeners; - handlers; - lastNodeWasText; - nodes; - constructor(root) { - this.root = root; - this.stack = [root]; - this.listeners = {}; - this.handlers = {}; - this.lastNodeWasText = false; - this.nodes = [root]; - } - top() { - return this.stack[this.stack.length - 1]; - } - pop() { - return this.stack.pop(); - } - PushRoot(root) { - const node = this.nodes[root]; - this.stack.push(node); - } - AppendChildren(many) { - let root = this.stack[this.stack.length - (1 + many)]; - let to_add = this.stack.splice(this.stack.length - many); - for (let i = 0; i < many; i++) { - root.appendChild(to_add[i]); - } - } - ReplaceWith(root_id, m) { - let root = this.nodes[root_id]; - let els = this.stack.splice(this.stack.length - m); - root.replaceWith(...els); - } - InsertAfter(root, n) { - let old = this.nodes[root]; - let new_nodes = this.stack.splice(this.stack.length - n); - old.after(...new_nodes); - } - InsertBefore(root, n) { - let old = this.nodes[root]; - let new_nodes = this.stack.splice(this.stack.length - n); - old.before(...new_nodes); - } - Remove(root) { - let node = this.nodes[root]; - if (node !== undefined) { - node.remove(); - } - } - CreateTextNode(text, root) { - // todo: make it so the types are okay - const node = document.createTextNode(text); - this.nodes[root] = node; - this.stack.push(node); - } - CreateElement(tag, root) { - const el = document.createElement(tag); - // el.setAttribute("data-dioxus-id", `${root}`); - this.nodes[root] = el; - this.stack.push(el); - } - CreateElementNs(tag, root, ns) { - let el = document.createElementNS(ns, tag); - this.stack.push(el); - this.nodes[root] = el; - } - CreatePlaceholder(root) { - let el = document.createElement("pre"); - el.hidden = true; - this.stack.push(el); - this.nodes[root] = el; - } - NewEventListener(event_name, root, handler) { - const element = this.nodes[root]; - element.setAttribute("data-dioxus-id", `${root}`); - if (this.listeners[event_name] === undefined) { - this.listeners[event_name] = 0; - this.handlers[event_name] = handler; - this.root.addEventListener(event_name, handler); - } - else { - this.listeners[event_name]++; - } - } - RemoveEventListener(root, event_name) { - const element = this.nodes[root]; - element.removeAttribute(`data-dioxus-id`); - this.listeners[event_name]--; - if (this.listeners[event_name] === 0) { - this.root.removeEventListener(event_name, this.handlers[event_name]); - delete this.listeners[event_name]; - delete this.handlers[event_name]; - } - } - SetText(root, text) { - this.nodes[root].textContent = text; - } - SetAttribute(root, field, value, ns) { - const name = field; - const node = this.nodes[root]; - if (ns == "style") { - // @ts-ignore - node.style[name] = value; - } - else if (ns != null || ns != undefined) { - node.setAttributeNS(ns, name, value); - } - else { - switch (name) { - case "value": - if (value != node.value) { - node.value = value; - } - break; - case "checked": - node.checked = value === "true"; - break; - case "selected": - node.selected = value === "true"; - break; - case "dangerous_inner_html": - node.innerHTML = value; - break; - default: - // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364 - if (value == "false" && bool_attrs.hasOwnProperty(name)) { - node.removeAttribute(name); - } - else { - node.setAttribute(name, value); - } - } - } - } - RemoveAttribute(root, name) { - const node = this.nodes[root]; - node.removeAttribute(name); - if (name === "value") { - node.value = ""; - } - if (name === "checked") { - node.checked = false; - } - if (name === "selected") { - node.selected = false; - } - } - handleEdits(edits) { - this.stack.push(this.root); - for (let edit of edits) { - this.handleEdit(edit); - } - } - handleEdit(edit) { - switch (edit.type) { - case "PushRoot": - this.PushRoot(edit.root); - break; - case "AppendChildren": - this.AppendChildren(edit.many); - break; - case "ReplaceWith": - this.ReplaceWith(edit.root, edit.m); - break; - case "InsertAfter": - this.InsertAfter(edit.root, edit.n); - break; - case "InsertBefore": - this.InsertBefore(edit.root, edit.n); - break; - case "Remove": - this.Remove(edit.root); - break; - case "CreateTextNode": - this.CreateTextNode(edit.text, edit.root); - break; - case "CreateElement": - this.CreateElement(edit.tag, edit.root); - break; - case "CreateElementNs": - this.CreateElementNs(edit.tag, edit.root, edit.ns); - break; - case "CreatePlaceholder": - this.CreatePlaceholder(edit.root); - break; - case "RemoveEventListener": - this.RemoveEventListener(edit.root, edit.event_name); - break; - case "NewEventListener": - // this handler is only provided on desktop implementations since this - // method is not used by the web implementation - let handler = (event) => { - let target = event.target; - if (target != null) { - let realId = target.getAttribute(`data-dioxus-id`); - let shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); - if (event.type == "click") { - event.preventDefault(); - if (shouldPreventDefault !== `onclick`) { - if (target.tagName == "A") { - const href = target.getAttribute("href"); - if (href !== "" && href !== null && href !== undefined) { - window.rpc.call("browser_open", { href }); - } - } - } - } - // walk the tree to find the real element - while (realId == null && target.parentElement != null) { - target = target.parentElement; - realId = target.getAttribute(`data-dioxus-id`); - } - shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); - let contents = serialize_event(event); - if (shouldPreventDefault === `on${event.type}`) { - event.preventDefault(); - } - if (event.type == "submit") { - event.preventDefault(); - } - if (realId == null) { - return; - } - window.rpc.call("user_event", { - event: edit.event_name, - mounted_dom_id: parseInt(realId), - contents: contents, - }); - } - }; - this.NewEventListener(edit.event_name, edit.root, handler); - break; - case "SetText": - this.SetText(edit.root, edit.text); - break; - case "SetAttribute": - this.SetAttribute(edit.root, edit.field, edit.value, edit.ns); - break; - case "RemoveAttribute": - this.RemoveAttribute(edit.root, edit.name); - break; - } - } -} -function serialize_event(event) { - switch (event.type) { - case "copy": - case "cut": - case "past": { - return {}; - } - case "compositionend": - case "compositionstart": - case "compositionupdate": { - let { data } = event; - return { - data, - }; - } - case "keydown": - case "keypress": - case "keyup": { - let { charCode, key, altKey, ctrlKey, metaKey, keyCode, shiftKey, location, repeat, which, } = event; - return { - char_code: charCode, - key: key, - alt_key: altKey, - ctrl_key: ctrlKey, - meta_key: metaKey, - key_code: keyCode, - shift_key: shiftKey, - location: location, - repeat: repeat, - which: which, - locale: "locale", - }; - } - case "focus": - case "blur": { - return {}; - } - case "change": { - let target = event.target; - let value; - if (target.type === "checkbox" || target.type === "radio") { - value = target.checked ? "true" : "false"; - } - else { - value = target.value ?? target.textContent; - } - return { - value: value, - }; - } - case "input": - case "invalid": - case "reset": - case "submit": { - let target = event.target; - let value = target.value ?? target.textContent; - if (target.type == "checkbox") { - value = target.checked ? "true" : "false"; - } - return { - value: value, - }; - } - case "click": - case "contextmenu": - case "doubleclick": - case "drag": - case "dragend": - case "dragenter": - case "dragexit": - case "dragleave": - case "dragover": - case "dragstart": - case "drop": - case "mousedown": - case "mouseenter": - case "mouseleave": - case "mousemove": - case "mouseout": - case "mouseover": - case "mouseup": { - const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, } = event; - return { - alt_key: altKey, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey, - meta_key: metaKey, - page_x: pageX, - page_y: pageY, - screen_x: screenX, - screen_y: screenY, - shift_key: shiftKey, - }; - } - case "pointerdown": - case "pointermove": - case "pointerup": - case "pointercancel": - case "gotpointercapture": - case "lostpointercapture": - case "pointerenter": - case "pointerleave": - case "pointerover": - case "pointerout": { - const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, pointerId, width, height, pressure, tangentialPressure, tiltX, tiltY, twist, pointerType, isPrimary, } = event; - return { - alt_key: altKey, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey, - meta_key: metaKey, - page_x: pageX, - page_y: pageY, - screen_x: screenX, - screen_y: screenY, - shift_key: shiftKey, - pointer_id: pointerId, - width: width, - height: height, - pressure: pressure, - tangential_pressure: tangentialPressure, - tilt_x: tiltX, - tilt_y: tiltY, - twist: twist, - pointer_type: pointerType, - is_primary: isPrimary, - }; - } - case "select": { - return {}; - } - case "touchcancel": - case "touchend": - case "touchmove": - case "touchstart": { - const { altKey, ctrlKey, metaKey, shiftKey, } = event; - return { - // changed_touches: event.changedTouches, - // target_touches: event.targetTouches, - // touches: event.touches, - alt_key: altKey, - ctrl_key: ctrlKey, - meta_key: metaKey, - shift_key: shiftKey, - }; - } - case "scroll": { - return {}; - } - case "wheel": { - const { deltaX, deltaY, deltaZ, deltaMode, } = event; - return { - delta_x: deltaX, - delta_y: deltaY, - delta_z: deltaZ, - delta_mode: deltaMode, - }; - } - case "animationstart": - case "animationend": - case "animationiteration": { - const { animationName, elapsedTime, pseudoElement, } = event; - return { - animation_name: animationName, - elapsed_time: elapsedTime, - pseudo_element: pseudoElement, - }; - } - case "transitionend": { - const { propertyName, elapsedTime, pseudoElement, } = event; - return { - property_name: propertyName, - elapsed_time: elapsedTime, - pseudo_element: pseudoElement, - }; - } - case "abort": - case "canplay": - case "canplaythrough": - case "durationchange": - case "emptied": - case "encrypted": - case "ended": - case "error": - case "loadeddata": - case "loadedmetadata": - case "loadstart": - case "pause": - case "play": - case "playing": - case "progress": - case "ratechange": - case "seeked": - case "seeking": - case "stalled": - case "suspend": - case "timeupdate": - case "volumechange": - case "waiting": { - return {}; - } - case "toggle": { - return {}; - } - default: { - return {}; - } - } -} -const bool_attrs = { - allowfullscreen: true, - allowpaymentrequest: true, - async: true, - autofocus: true, - autoplay: true, - checked: true, - controls: true, - default: true, - defer: true, - disabled: true, - formnovalidate: true, - hidden: true, - ismap: true, - itemscope: true, - loop: true, - multiple: true, - muted: true, - nomodule: true, - novalidate: true, - open: true, - playsinline: true, - readonly: true, - required: true, - reversed: true, - selected: true, - truespeed: true, -}; -"##)] -extern "C" { - pub type Interpreter; - - #[wasm_bindgen(constructor)] - pub fn new(arg: Element) -> Interpreter; - - #[wasm_bindgen(method)] - pub fn set_node(this: &Interpreter, id: usize, node: Node); - - #[wasm_bindgen(method)] - pub fn PushRoot(this: &Interpreter, root: u64); - - #[wasm_bindgen(method)] - pub fn AppendChildren(this: &Interpreter, many: u32); - - #[wasm_bindgen(method)] - pub fn ReplaceWith(this: &Interpreter, root: u64, m: u32); - - #[wasm_bindgen(method)] - pub fn InsertAfter(this: &Interpreter, root: u64, n: u32); - - #[wasm_bindgen(method)] - pub fn InsertBefore(this: &Interpreter, root: u64, n: u32); - - #[wasm_bindgen(method)] - pub fn Remove(this: &Interpreter, root: u64); - - #[wasm_bindgen(method)] - pub fn CreateTextNode(this: &Interpreter, text: &str, root: u64); - - #[wasm_bindgen(method)] - pub fn CreateElement(this: &Interpreter, tag: &str, root: u64); - - #[wasm_bindgen(method)] - pub fn CreateElementNs(this: &Interpreter, tag: &str, root: u64, ns: &str); - - #[wasm_bindgen(method)] - pub fn CreatePlaceholder(this: &Interpreter, root: u64); - - #[wasm_bindgen(method)] - pub fn NewEventListener(this: &Interpreter, name: &str, root: u64, handler: &Function); - - #[wasm_bindgen(method)] - pub fn RemoveEventListener(this: &Interpreter, root: u64, name: &str); - - #[wasm_bindgen(method)] - pub fn SetText(this: &Interpreter, root: u64, text: &str); - - #[wasm_bindgen(method)] - pub fn SetAttribute(this: &Interpreter, root: u64, field: &str, value: &str, ns: Option<&str>); - - #[wasm_bindgen(method)] - pub fn RemoveAttribute(this: &Interpreter, root: u64, field: &str); -} diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 733e50e4bf..6ca689d289 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -7,8 +7,8 @@ //! - tests to ensure dyn_into works for various event types. //! - Partial delegation?> -use crate::bindings::Interpreter; use dioxus_core::{DomEdit, ElementId, SchedulerMsg, UserEvent}; +use dioxus_interpreter_js::Interpreter; use js_sys::Function; use std::{any::Any, rc::Rc, sync::Arc}; use wasm_bindgen::{closure::Closure, JsCast}; diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index f4266a7c8a..cef1bc14cf 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -61,7 +61,6 @@ pub use dioxus_core as dioxus; use dioxus_core::prelude::Component; use futures_util::FutureExt; -pub(crate) mod bindings; mod cache; mod cfg; mod dom;