Merge pull request #121 from DioxusLabs/jk/unify
feat: unify web and desktop interpreter with the same typescript codebase
This commit is contained in:
17 files changed
+2166
-962
No files matched your search
@@ -12,7 +12,9 @@
|
|||||||
<div id="main">
|
<div id="main">
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
<script type="text/javascript" src="index.js">
|
|
||||||
</script>
|
<script>var exports = {};</script>
|
||||||
|
<script type="text/javascript" src="index.js" type="module"></script>
|
||||||
|
<script>main()</script>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,468 +0,0 @@
|
|||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
function serialize_event(event) {
|
|
||||||
switch (event.type) {
|
|
||||||
case "copy":
|
|
||||||
case "cut":
|
|
||||||
case "past":
|
|
||||||
return {};
|
|
||||||
|
|
||||||
case "compositionend":
|
|
||||||
case "compositionstart":
|
|
||||||
case "compositionupdate":
|
|
||||||
return {
|
|
||||||
data: event.data,
|
|
||||||
};
|
|
||||||
|
|
||||||
case "keydown":
|
|
||||||
case "keypress":
|
|
||||||
case "keyup":
|
|
||||||
return {
|
|
||||||
char_code: event.charCode,
|
|
||||||
key: event.key,
|
|
||||||
alt_key: event.altKey,
|
|
||||||
ctrl_key: event.ctrlKey,
|
|
||||||
meta_key: event.metaKey,
|
|
||||||
key_code: event.keyCode,
|
|
||||||
shift_key: event.shiftKey,
|
|
||||||
locale: "locale",
|
|
||||||
location: event.location,
|
|
||||||
repeat: event.repeat,
|
|
||||||
which: event.which,
|
|
||||||
// locale: event.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":
|
|
||||||
return {
|
|
||||||
alt_key: event.altKey,
|
|
||||||
button: event.button,
|
|
||||||
buttons: event.buttons,
|
|
||||||
client_x: event.clientX,
|
|
||||||
client_y: event.clientY,
|
|
||||||
ctrl_key: event.ctrlKey,
|
|
||||||
meta_key: event.metaKey,
|
|
||||||
page_x: event.pageX,
|
|
||||||
page_y: event.pageY,
|
|
||||||
screen_x: event.screenX,
|
|
||||||
screen_y: event.screenY,
|
|
||||||
shift_key: event.shiftKey,
|
|
||||||
};
|
|
||||||
|
|
||||||
case "pointerdown":
|
|
||||||
case "pointermove":
|
|
||||||
case "pointerup":
|
|
||||||
case "pointercancel":
|
|
||||||
case "gotpointercapture":
|
|
||||||
case "lostpointercapture":
|
|
||||||
case "pointerenter":
|
|
||||||
case "pointerleave":
|
|
||||||
case "pointerover":
|
|
||||||
case "pointerout":
|
|
||||||
return {
|
|
||||||
alt_key: event.altKey,
|
|
||||||
button: event.button,
|
|
||||||
buttons: event.buttons,
|
|
||||||
client_x: event.clientX,
|
|
||||||
client_y: event.clientY,
|
|
||||||
ctrl_key: event.ctrlKey,
|
|
||||||
meta_key: event.metaKey,
|
|
||||||
page_x: event.pageX,
|
|
||||||
page_y: event.pageY,
|
|
||||||
screen_x: event.screenX,
|
|
||||||
screen_y: event.screenY,
|
|
||||||
shift_key: event.shiftKey,
|
|
||||||
pointer_id: event.pointerId,
|
|
||||||
width: event.width,
|
|
||||||
height: event.height,
|
|
||||||
pressure: event.pressure,
|
|
||||||
tangential_pressure: event.tangentialPressure,
|
|
||||||
tilt_x: event.tiltX,
|
|
||||||
tilt_y: event.tiltY,
|
|
||||||
twist: event.twist,
|
|
||||||
pointer_type: event.pointerType,
|
|
||||||
is_primary: event.isPrimary,
|
|
||||||
};
|
|
||||||
|
|
||||||
case "select":
|
|
||||||
return {};
|
|
||||||
|
|
||||||
case "touchcancel":
|
|
||||||
case "touchend":
|
|
||||||
case "touchmove":
|
|
||||||
case "touchstart":
|
|
||||||
return {
|
|
||||||
alt_key: event.altKey,
|
|
||||||
ctrl_key: event.ctrlKey,
|
|
||||||
meta_key: event.metaKey,
|
|
||||||
shift_key: event.shiftKey,
|
|
||||||
|
|
||||||
// changed_touches: event.changedTouches,
|
|
||||||
// target_touches: event.targetTouches,
|
|
||||||
// touches: event.touches,
|
|
||||||
};
|
|
||||||
|
|
||||||
case "scroll":
|
|
||||||
return {};
|
|
||||||
|
|
||||||
case "wheel":
|
|
||||||
return {
|
|
||||||
delta_x: event.deltaX,
|
|
||||||
delta_y: event.deltaY,
|
|
||||||
delta_z: event.deltaZ,
|
|
||||||
delta_mode: event.deltaMode,
|
|
||||||
};
|
|
||||||
|
|
||||||
case "animationstart":
|
|
||||||
case "animationend":
|
|
||||||
case "animationiteration":
|
|
||||||
return {
|
|
||||||
animation_name: event.animationName,
|
|
||||||
elapsed_time: event.elapsedTime,
|
|
||||||
pseudo_element: event.pseudoElement,
|
|
||||||
};
|
|
||||||
|
|
||||||
case "transitionend":
|
|
||||||
return {
|
|
||||||
property_name: event.propertyName,
|
|
||||||
elapsed_time: event.elapsedTime,
|
|
||||||
pseudo_element: event.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 {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Interpreter {
|
|
||||||
constructor(root) {
|
|
||||||
this.root = root;
|
|
||||||
this.stack = [root];
|
|
||||||
this.listeners = {
|
|
||||||
onclick: {},
|
|
||||||
};
|
|
||||||
this.lastNodeWasText = false;
|
|
||||||
this.nodes = [root];
|
|
||||||
}
|
|
||||||
|
|
||||||
top() {
|
|
||||||
return this.stack[this.stack.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
pop() {
|
|
||||||
return this.stack.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
PushRoot(edit) {
|
|
||||||
const id = edit.root;
|
|
||||||
const node = this.nodes[id];
|
|
||||||
this.stack.push(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
AppendChildren(edit) {
|
|
||||||
let root = this.stack[this.stack.length - (1 + edit.many)];
|
|
||||||
|
|
||||||
let to_add = this.stack.splice(this.stack.length - edit.many);
|
|
||||||
|
|
||||||
for (let i = 0; i < edit.many; i++) {
|
|
||||||
root.appendChild(to_add[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ReplaceWith(edit) {
|
|
||||||
let root = this.nodes[edit.root];
|
|
||||||
let els = this.stack.splice(this.stack.length - edit.m);
|
|
||||||
|
|
||||||
root.replaceWith(...els);
|
|
||||||
}
|
|
||||||
|
|
||||||
InsertAfter(edit) {
|
|
||||||
let old = this.nodes[edit.root];
|
|
||||||
let new_nodes = this.stack.splice(this.stack.length - edit.n);
|
|
||||||
old.after(...new_nodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
InsertBefore(edit) {
|
|
||||||
let old = this.nodes[edit.root];
|
|
||||||
let new_nodes = this.stack.splice(this.stack.length - edit.n);
|
|
||||||
old.before(...new_nodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
Remove(edit) {
|
|
||||||
let node = this.nodes[edit.root];
|
|
||||||
if (node !== undefined) {
|
|
||||||
node.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateTextNode(edit) {
|
|
||||||
const node = document.createTextNode(edit.text);
|
|
||||||
this.nodes[edit.root] = node;
|
|
||||||
this.stack.push(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateElement(edit) {
|
|
||||||
const tagName = edit.tag;
|
|
||||||
const el = document.createElement(tagName);
|
|
||||||
this.nodes[edit.root] = el;
|
|
||||||
el.setAttribute("dioxus-id", edit.root);
|
|
||||||
this.stack.push(el);
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateElementNs(edit) {
|
|
||||||
let el = document.createElementNS(edit.ns, edit.tag);
|
|
||||||
this.stack.push(el);
|
|
||||||
this.nodes[edit.root] = el;
|
|
||||||
}
|
|
||||||
|
|
||||||
CreatePlaceholder(edit) {
|
|
||||||
let el = document.createElement("pre");
|
|
||||||
el.hidden = true;
|
|
||||||
this.stack.push(el);
|
|
||||||
this.nodes[edit.root] = el;
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoveEventListener(edit) {}
|
|
||||||
|
|
||||||
NewEventListener(edit) {
|
|
||||||
const event_name = edit.event_name;
|
|
||||||
const mounted_node_id = edit.root;
|
|
||||||
const scope = edit.scope;
|
|
||||||
|
|
||||||
const element = this.nodes[edit.root];
|
|
||||||
element.setAttribute(
|
|
||||||
`dioxus-event-${event_name}`,
|
|
||||||
`${scope}.${mounted_node_id}`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.listeners[event_name] === undefined) {
|
|
||||||
this.listeners[event_name] = true;
|
|
||||||
|
|
||||||
this.root.addEventListener(event_name, (event) => {
|
|
||||||
|
|
||||||
const target = event.target;
|
|
||||||
const real_id = target.getAttribute(`dioxus-id`);
|
|
||||||
|
|
||||||
const should_prevent_default = target.getAttribute(
|
|
||||||
`dioxus-prevent-default`
|
|
||||||
);
|
|
||||||
|
|
||||||
let contents = serialize_event(event);
|
|
||||||
|
|
||||||
if (should_prevent_default === `on${event.type}`) {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type == "submit") {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type == "click") {
|
|
||||||
event.preventDefault();
|
|
||||||
if (should_prevent_default !== `onclick`) {
|
|
||||||
if(element.tagName == "A") {
|
|
||||||
const href = event.target.getAttribute("href")
|
|
||||||
if (href !== "" && href !== null && href !== undefined) {
|
|
||||||
rpc.call("browser_open", {
|
|
||||||
mounted_dom_id: parseInt(real_id),
|
|
||||||
href: event.target.getAttribute("href")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (real_id == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
rpc.call("user_event", {
|
|
||||||
event: event_name,
|
|
||||||
mounted_dom_id: parseInt(real_id),
|
|
||||||
contents: contents,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SetText(edit) {
|
|
||||||
this.nodes[edit.root].textContent = edit.text;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetAttribute(edit) {
|
|
||||||
|
|
||||||
// console.log("setting attr", edit);
|
|
||||||
|
|
||||||
const name = edit.field;
|
|
||||||
const value = edit.value;
|
|
||||||
const ns = edit.ns;
|
|
||||||
const node = this.nodes[edit.root];
|
|
||||||
|
|
||||||
if (ns == "style") {
|
|
||||||
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(edit) {
|
|
||||||
const name = edit.field;
|
|
||||||
const node = this.nodes[edit.root];
|
|
||||||
node.removeAttribute(name);
|
|
||||||
|
|
||||||
if (name === "value") {
|
|
||||||
node.value = null;
|
|
||||||
}
|
|
||||||
if (name === "checked") {
|
|
||||||
node.checked = false;
|
|
||||||
}
|
|
||||||
if (name === "selected") {
|
|
||||||
node.selected = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleEdits(edits) {
|
|
||||||
this.stack.push(this.root);
|
|
||||||
|
|
||||||
for (let x = 0; x < edits.length; x++) {
|
|
||||||
let edit = edits[x];
|
|
||||||
let f = this[edit.type];
|
|
||||||
f.call(this, edit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function main() {
|
|
||||||
let root = window.document.getElementById("main");
|
|
||||||
window.interpreter = new Interpreter(root);
|
|
||||||
rpc.call("initialize");
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
@@ -217,7 +217,7 @@ pub fn launch_with_props<P: 'static + Send>(
|
|||||||
} else if path.trim_end_matches('/') == "index.html/index.js" {
|
} else if path.trim_end_matches('/') == "index.html/index.js" {
|
||||||
wry::http::ResponseBuilder::new()
|
wry::http::ResponseBuilder::new()
|
||||||
.mimetype("text/javascript")
|
.mimetype("text/javascript")
|
||||||
.body(include_bytes!("./index.js").to_vec())
|
.body(include_bytes!("../../jsinterpreter/interpreter.js").to_vec())
|
||||||
} else {
|
} else {
|
||||||
wry::http::ResponseBuilder::new()
|
wry::http::ResponseBuilder::new()
|
||||||
.status(wry::http::status::StatusCode::NOT_FOUND)
|
.status(wry::http::status::StatusCode::NOT_FOUND)
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# JS Interpreter
|
||||||
|
|
||||||
|
After diffing old and new trees, the Dioxus VirtualDom produces patches that are used to modify the existing Dom. We can send these patches anywhere - including targets without WASM support.
|
||||||
|
|
||||||
|
In renderers with support for JavaScript, we use the interpreter from this repository - written in TypeScript - to patch the Dom. This lets us circumvent any overhead on the Rust <-> Dom boundary and keep consistency in our interpreter implementation in web/webview targets.
|
||||||
|
|
||||||
|
|
||||||
|
For now - both Dioxus Web and Dioxus Desktop (webview) use the same interpreter code with tweaks.
|
||||||
@@ -0,0 +1,477 @@
|
|||||||
|
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 {};
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "select": {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "scroll": {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "transitionend": {
|
||||||
|
var _j = event, propertyName = _j.propertyName, elapsedTime = _j.elapsedTime, pseudoElement = _j.pseudoElement;
|
||||||
|
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 {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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]++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Interpreter.prototype.RemoveAttribute = function (root, name) {
|
||||||
|
var node = this.nodes[root];
|
||||||
|
node.removeAttribute(name);
|
||||||
|
if (name === "value") {
|
||||||
|
node.value = "";
|
||||||
|
}
|
||||||
|
if (name === "checked") {
|
||||||
|
node.checked = false;
|
||||||
|
}
|
||||||
|
if (name === "selected") {
|
||||||
|
node.selected = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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 real_id = target.getAttribute("dioxus-id");
|
||||||
|
var should_prevent_default = target.getAttribute("dioxus-prevent-default");
|
||||||
|
var contents = serialize_event(event);
|
||||||
|
if (should_prevent_default === "on".concat(event.type)) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
if (real_id == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.rpc.call("user_event", {
|
||||||
|
event: edit.event_name,
|
||||||
|
mounted_dom_id: parseInt(real_id),
|
||||||
|
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 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
|
||||||
@@ -0,0 +1,676 @@
|
|||||||
|
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 {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
stack: Element[];
|
||||||
|
listeners: { [key: string]: number };
|
||||||
|
handlers: { [key: string]: (evt: Event) => void };
|
||||||
|
lastNodeWasText: boolean;
|
||||||
|
nodes: Element[];
|
||||||
|
|
||||||
|
|
||||||
|
constructor(root: Element) {
|
||||||
|
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: number) {
|
||||||
|
const node = this.nodes[root];
|
||||||
|
this.stack.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
AppendChildren(many: number) {
|
||||||
|
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;
|
||||||
|
let els = this.stack.splice(this.stack.length - m);
|
||||||
|
|
||||||
|
root.replaceWith(...els);
|
||||||
|
}
|
||||||
|
|
||||||
|
InsertAfter(root: number, n: number) {
|
||||||
|
let old = this.nodes[root] as Element;
|
||||||
|
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;
|
||||||
|
let new_nodes = this.stack.splice(this.stack.length - n);
|
||||||
|
old.before(...new_nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
Remove(root: number) {
|
||||||
|
let node = this.nodes[root] as Element;
|
||||||
|
if (node !== undefined) {
|
||||||
|
node.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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("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, 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}`
|
||||||
|
);
|
||||||
|
|
||||||
|
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(
|
||||||
|
`dioxus-event-${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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
let root = window.document.getElementById("main");
|
||||||
|
if (root != null) {
|
||||||
|
window.interpreter = new Interpreter(root);
|
||||||
|
window.rpc.call("initialize");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
export { };
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
interpreter: Interpreter;
|
||||||
|
rpc: { call: (method: string, args?: any) => void };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES5",
|
||||||
|
"module": "ESNext",
|
||||||
|
"lib": [
|
||||||
|
"es2015",
|
||||||
|
"es5",
|
||||||
|
"es6",
|
||||||
|
"dom"
|
||||||
|
],
|
||||||
|
"strict": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,8 +11,8 @@ documentation = "https://dioxuslabs.com"
|
|||||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dioxus-core = { path = "../core", version ="^0.1.7"}
|
dioxus-core = { path = "../core", version = "^0.1.7" }
|
||||||
dioxus-html = { path = "../html", version ="^0.1.4"}
|
dioxus-html = { path = "../html", version = "^0.1.4" }
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] }
|
wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
use js_sys::Function;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
use web_sys::{Element, Node};
|
||||||
|
|
||||||
|
#[wasm_bindgen(module = "/../jsinterpreter/interpreter.js")]
|
||||||
|
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);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
pub static BUILTIN_INTERNED_STRINGS: &[&'static str] = &[
|
pub static BUILTIN_INTERNED_STRINGS: &[&str] = &[
|
||||||
// Important tags to dioxus
|
// Important tags to dioxus
|
||||||
"dioxus-id",
|
"dioxus-id",
|
||||||
"dioxus",
|
"dioxus",
|
||||||
|
|||||||
@@ -3,10 +3,9 @@
|
|||||||
/// This struct helps configure the specifics of hydration and render destination for WebSys.
|
/// This struct helps configure the specifics of hydration and render destination for WebSys.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
|
///
|
||||||
/// ```rust, ignore
|
/// ```rust, ignore
|
||||||
/// fn main() {
|
/// dioxus::web::launch(App, |cfg| cfg.hydrate(true).root_name("myroot"))
|
||||||
/// dioxus::web::launch(App, |cfg| cfg.hydrate(true).root_name("myroot"))
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
pub struct WebConfig {
|
pub struct WebConfig {
|
||||||
pub(crate) hydrate: bool,
|
pub(crate) hydrate: bool,
|
||||||
|
|||||||
+55
-444
@@ -7,56 +7,51 @@
|
|||||||
//! - tests to ensure dyn_into works for various event types.
|
//! - tests to ensure dyn_into works for various event types.
|
||||||
//! - Partial delegation?>
|
//! - Partial delegation?>
|
||||||
|
|
||||||
use dioxus_core::{DomEdit, ElementId, SchedulerMsg, ScopeId, UserEvent};
|
use crate::bindings::Interpreter;
|
||||||
use fxhash::FxHashMap;
|
use dioxus_core::{DomEdit, ElementId, SchedulerMsg, UserEvent};
|
||||||
use std::{any::Any, fmt::Debug, rc::Rc, sync::Arc};
|
use js_sys::Function;
|
||||||
|
use std::{any::Any, rc::Rc, sync::Arc};
|
||||||
use wasm_bindgen::{closure::Closure, JsCast};
|
use wasm_bindgen::{closure::Closure, JsCast};
|
||||||
use web_sys::{
|
use web_sys::{Document, Element, Event, HtmlElement};
|
||||||
CssStyleDeclaration, Document, Element, Event, HtmlElement, HtmlInputElement,
|
|
||||||
HtmlOptionElement, HtmlTextAreaElement, Node,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{nodeslab::NodeSlab, WebConfig};
|
use crate::WebConfig;
|
||||||
|
|
||||||
pub struct WebsysDom {
|
pub struct WebsysDom {
|
||||||
stack: Stack,
|
pub interpreter: Interpreter,
|
||||||
|
|
||||||
/// A map from ElementID (index) to Node
|
|
||||||
pub(crate) nodes: NodeSlab,
|
|
||||||
|
|
||||||
document: Document,
|
|
||||||
|
|
||||||
pub(crate) root: Element,
|
pub(crate) root: Element,
|
||||||
|
|
||||||
sender_callback: Rc<dyn Fn(SchedulerMsg)>,
|
pub handler: Closure<dyn FnMut(&Event)>,
|
||||||
|
|
||||||
// map of listener types to number of those listeners
|
|
||||||
// This is roughly a delegater
|
|
||||||
// TODO: check how infero delegates its events - some are more performant
|
|
||||||
listeners: FxHashMap<&'static str, ListenerEntry>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListenerEntry = (usize, Closure<dyn FnMut(&Event)>);
|
|
||||||
|
|
||||||
impl WebsysDom {
|
impl WebsysDom {
|
||||||
pub fn new(cfg: WebConfig, sender_callback: Rc<dyn Fn(SchedulerMsg)>) -> Self {
|
pub fn new(cfg: WebConfig, sender_callback: Rc<dyn Fn(SchedulerMsg)>) -> Self {
|
||||||
let document = load_document();
|
// eventually, we just want to let the interpreter do all the work of decoding events into our event type
|
||||||
|
let callback: Box<dyn FnMut(&Event)> = Box::new(move |event: &web_sys::Event| {
|
||||||
|
if let Ok(synthetic_event) = decode_trigger(event) {
|
||||||
|
// Try to prevent default if the attribute is set
|
||||||
|
if let Some(target) = event.target() {
|
||||||
|
if let Some(node) = target.dyn_ref::<HtmlElement>() {
|
||||||
|
if let Some(name) = node.get_attribute("dioxus-prevent-default") {
|
||||||
|
if name == synthetic_event.name
|
||||||
|
|| name.trim_start_matches("on") == synthetic_event.name
|
||||||
|
{
|
||||||
|
log::trace!("Preventing default");
|
||||||
|
event.prevent_default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let nodes = NodeSlab::new(2000);
|
sender_callback.as_ref()(SchedulerMsg::Event(synthetic_event))
|
||||||
let listeners = FxHashMap::default();
|
}
|
||||||
|
});
|
||||||
let mut stack = Stack::with_capacity(10);
|
|
||||||
|
|
||||||
let root = load_document().get_element_by_id(&cfg.rootname).unwrap();
|
let root = load_document().get_element_by_id(&cfg.rootname).unwrap();
|
||||||
let root_node = root.clone().dyn_into::<Node>().unwrap();
|
|
||||||
stack.push(root_node);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
stack,
|
interpreter: Interpreter::new(root.clone()),
|
||||||
nodes,
|
handler: Closure::wrap(callback),
|
||||||
listeners,
|
|
||||||
document,
|
|
||||||
sender_callback,
|
|
||||||
root,
|
root,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,434 +59,49 @@ impl WebsysDom {
|
|||||||
pub fn apply_edits(&mut self, mut edits: Vec<DomEdit>) {
|
pub fn apply_edits(&mut self, mut edits: Vec<DomEdit>) {
|
||||||
for edit in edits.drain(..) {
|
for edit in edits.drain(..) {
|
||||||
match edit {
|
match edit {
|
||||||
DomEdit::PushRoot { root } => self.push(root),
|
DomEdit::PushRoot { root } => self.interpreter.PushRoot(root),
|
||||||
DomEdit::AppendChildren { many } => self.append_children(many),
|
DomEdit::AppendChildren { many } => self.interpreter.AppendChildren(many),
|
||||||
DomEdit::ReplaceWith { m, root } => self.replace_with(m, root),
|
DomEdit::ReplaceWith { root, m } => self.interpreter.ReplaceWith(root, m),
|
||||||
DomEdit::Remove { root } => self.remove(root),
|
DomEdit::InsertAfter { root, n } => self.interpreter.InsertAfter(root, n),
|
||||||
DomEdit::CreateTextNode { text, root: id } => self.create_text_node(text, id),
|
DomEdit::InsertBefore { root, n } => self.interpreter.InsertBefore(root, n),
|
||||||
DomEdit::CreateElement { tag, root: id } => self.create_element(tag, None, id),
|
DomEdit::Remove { root } => self.interpreter.Remove(root),
|
||||||
DomEdit::CreateElementNs { tag, root: id, ns } => {
|
DomEdit::CreateTextNode { text, root } => {
|
||||||
self.create_element(tag, Some(ns), id)
|
self.interpreter.CreateTextNode(text, root)
|
||||||
}
|
}
|
||||||
DomEdit::CreatePlaceholder { root: id } => self.create_placeholder(id),
|
DomEdit::CreateElement { tag, root } => self.interpreter.CreateElement(tag, root),
|
||||||
|
DomEdit::CreateElementNs { tag, root, ns } => {
|
||||||
|
self.interpreter.CreateElementNs(tag, root, ns)
|
||||||
|
}
|
||||||
|
DomEdit::CreatePlaceholder { root } => self.interpreter.CreatePlaceholder(root),
|
||||||
DomEdit::NewEventListener {
|
DomEdit::NewEventListener {
|
||||||
event_name, root, ..
|
event_name, root, ..
|
||||||
} => self.new_event_listener(event_name, root),
|
} => {
|
||||||
|
let handler: &Function = self.handler.as_ref().unchecked_ref();
|
||||||
DomEdit::RemoveEventListener { event, root } => {
|
self.interpreter.NewEventListener(event_name, root, handler);
|
||||||
self.remove_event_listener(event, root)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DomEdit::SetText { text, root } => self.set_text(text, root),
|
DomEdit::RemoveEventListener { root, event } => {
|
||||||
|
self.interpreter.RemoveEventListener(root, event)
|
||||||
|
}
|
||||||
|
DomEdit::SetText { root, text } => self.interpreter.SetText(root, text),
|
||||||
DomEdit::SetAttribute {
|
DomEdit::SetAttribute {
|
||||||
|
root,
|
||||||
field,
|
field,
|
||||||
value,
|
value,
|
||||||
ns,
|
ns,
|
||||||
root,
|
} => self.interpreter.SetAttribute(root, field, value, ns),
|
||||||
} => self.set_attribute(field, value, ns, root),
|
DomEdit::RemoveAttribute { root, name } => {
|
||||||
DomEdit::RemoveAttribute { name, root } => self.remove_attribute(name, root),
|
self.interpreter.RemoveAttribute(root, name)
|
||||||
|
|
||||||
DomEdit::InsertAfter { n, root } => self.insert_after(n, root),
|
|
||||||
DomEdit::InsertBefore { n, root } => self.insert_before(n, root),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn push(&mut self, root: u64) {
|
|
||||||
let key = root as usize;
|
|
||||||
let domnode = &self.nodes[key];
|
|
||||||
|
|
||||||
let real_node: Node = match domnode {
|
|
||||||
Some(n) => n.clone(),
|
|
||||||
None => todo!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.stack.push(real_node);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn append_children(&mut self, many: u32) {
|
|
||||||
let root: Node = self
|
|
||||||
.stack
|
|
||||||
.list
|
|
||||||
.get(self.stack.list.len() - (1 + many as usize))
|
|
||||||
.unwrap()
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
// We need to make sure to add comments between text nodes
|
|
||||||
// We ensure that the text siblings are patched by preventing the browser from merging
|
|
||||||
// neighboring text nodes. Originally inspired by some of React's work from 2016.
|
|
||||||
// -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
|
|
||||||
// -> https://github.com/facebook/react/pull/5753
|
|
||||||
/*
|
|
||||||
todo: we need to track this for replacing/insert after/etc
|
|
||||||
*/
|
|
||||||
let mut last_node_was_text = false;
|
|
||||||
|
|
||||||
for child in self
|
|
||||||
.stack
|
|
||||||
.list
|
|
||||||
.drain((self.stack.list.len() - many as usize)..)
|
|
||||||
{
|
|
||||||
if child.dyn_ref::<web_sys::Text>().is_some() {
|
|
||||||
if last_node_was_text {
|
|
||||||
let comment_node = self
|
|
||||||
.document
|
|
||||||
.create_comment("dioxus")
|
|
||||||
.dyn_into::<Node>()
|
|
||||||
.unwrap();
|
|
||||||
root.append_child(&comment_node).unwrap();
|
|
||||||
}
|
|
||||||
last_node_was_text = true;
|
|
||||||
} else {
|
|
||||||
last_node_was_text = false;
|
|
||||||
}
|
|
||||||
root.append_child(&child).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn replace_with(&mut self, m: u32, root: u64) {
|
|
||||||
let old = self.nodes[root as usize].as_ref().unwrap();
|
|
||||||
|
|
||||||
let arr: js_sys::Array = self
|
|
||||||
.stack
|
|
||||||
.list
|
|
||||||
.drain((self.stack.list.len() - m as usize)..)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if let Some(el) = old.dyn_ref::<Element>() {
|
|
||||||
el.replace_with_with_node(&arr).unwrap();
|
|
||||||
} else if let Some(el) = old.dyn_ref::<web_sys::CharacterData>() {
|
|
||||||
el.replace_with_with_node(&arr).unwrap();
|
|
||||||
} else if let Some(el) = old.dyn_ref::<web_sys::DocumentType>() {
|
|
||||||
el.replace_with_with_node(&arr).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove(&mut self, root: u64) {
|
|
||||||
let node = self.nodes[root as usize].as_ref().unwrap();
|
|
||||||
if let Some(element) = node.dyn_ref::<Element>() {
|
|
||||||
element.remove();
|
|
||||||
} else if let Some(parent) = node.parent_node() {
|
|
||||||
parent.remove_child(node).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_placeholder(&mut self, id: u64) {
|
|
||||||
self.create_element("pre", None, id);
|
|
||||||
self.set_attribute("hidden", "", None, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_text_node(&mut self, text: &str, id: u64) {
|
|
||||||
let textnode = self
|
|
||||||
.document
|
|
||||||
.create_text_node(text)
|
|
||||||
.dyn_into::<Node>()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
self.stack.push(textnode.clone());
|
|
||||||
|
|
||||||
self.nodes[(id as usize)] = Some(textnode);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_element(&mut self, tag: &str, ns: Option<&'static str>, id: u64) {
|
|
||||||
let tag = wasm_bindgen::intern(tag);
|
|
||||||
|
|
||||||
let el = match ns {
|
|
||||||
Some(ns) => self
|
|
||||||
.document
|
|
||||||
.create_element_ns(Some(ns), tag)
|
|
||||||
.unwrap()
|
|
||||||
.dyn_into::<Node>()
|
|
||||||
.unwrap(),
|
|
||||||
None => self
|
|
||||||
.document
|
|
||||||
.create_element(tag)
|
|
||||||
.unwrap()
|
|
||||||
.dyn_into::<Node>()
|
|
||||||
.unwrap(),
|
|
||||||
};
|
|
||||||
|
|
||||||
use smallstr::SmallString;
|
|
||||||
use std::fmt::Write;
|
|
||||||
|
|
||||||
let mut s: SmallString<[u8; 8]> = smallstr::SmallString::new();
|
|
||||||
write!(s, "{}", id).unwrap();
|
|
||||||
|
|
||||||
let el2 = el.dyn_ref::<Element>().unwrap();
|
|
||||||
el2.set_attribute("dioxus-id", s.as_str()).unwrap();
|
|
||||||
|
|
||||||
self.stack.push(el.clone());
|
|
||||||
self.nodes[(id as usize)] = Some(el);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_event_listener(&mut self, event: &'static str, node: u64) {
|
|
||||||
let event = wasm_bindgen::intern(event);
|
|
||||||
|
|
||||||
// attach the correct attributes to the element
|
|
||||||
// these will be used by accessing the event's target
|
|
||||||
// This ensures we only ever have one handler attached to the root, but decide
|
|
||||||
// dynamically when we want to call a listener.
|
|
||||||
|
|
||||||
let el = self.nodes[(node as usize)].as_ref().unwrap();
|
|
||||||
|
|
||||||
let el = el.dyn_ref::<Element>().unwrap();
|
|
||||||
|
|
||||||
el.set_attribute("dioxus-event", event).unwrap();
|
|
||||||
|
|
||||||
// Register the callback to decode
|
|
||||||
|
|
||||||
if let Some(entry) = self.listeners.get_mut(event) {
|
|
||||||
entry.0 += 1;
|
|
||||||
} else {
|
|
||||||
let trigger = self.sender_callback.clone();
|
|
||||||
|
|
||||||
let c: Box<dyn FnMut(&Event)> = Box::new(move |event: &web_sys::Event| {
|
|
||||||
// "Result" cannot be received from JS
|
|
||||||
// Instead, we just build and immediately execute a closure that returns result
|
|
||||||
match decode_trigger(event) {
|
|
||||||
Ok(synthetic_event) => {
|
|
||||||
let target = event.target().unwrap();
|
|
||||||
if let Some(node) = target.dyn_ref::<HtmlElement>() {
|
|
||||||
if let Some(name) = node.get_attribute("dioxus-prevent-default") {
|
|
||||||
if name == synthetic_event.name
|
|
||||||
|| name.trim_start_matches("on") == synthetic_event.name
|
|
||||||
{
|
|
||||||
log::trace!("Preventing default");
|
|
||||||
event.prevent_default();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trigger.as_ref()(SchedulerMsg::Event(synthetic_event))
|
|
||||||
}
|
|
||||||
Err(e) => log::error!("Error decoding Dioxus event attribute. {:#?}", e),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
let handler = Closure::wrap(c);
|
|
||||||
|
|
||||||
self.root
|
|
||||||
.add_event_listener_with_callback(event, (&handler).as_ref().unchecked_ref())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Increment the listeners
|
|
||||||
self.listeners.insert(event, (1, handler));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_event_listener(&mut self, _event: &str, _root: u64) {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_text(&mut self, text: &str, root: u64) {
|
|
||||||
let el = self.nodes[root as usize].as_ref().unwrap();
|
|
||||||
el.set_text_content(Some(text))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_attribute(&mut self, name: &str, value: &str, ns: Option<&str>, root: u64) {
|
|
||||||
let node = self.nodes[root as usize].as_ref().unwrap();
|
|
||||||
if ns == Some("style") {
|
|
||||||
if let Some(el) = node.dyn_ref::<Element>() {
|
|
||||||
let el = el.dyn_ref::<HtmlElement>().unwrap();
|
|
||||||
let style_dc: CssStyleDeclaration = el.style();
|
|
||||||
style_dc.set_property(name, value).unwrap();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let fallback = || {
|
|
||||||
let el = node.dyn_ref::<Element>().unwrap();
|
|
||||||
el.set_attribute(name, value).unwrap()
|
|
||||||
};
|
|
||||||
match name {
|
|
||||||
"dangerous_inner_html" => {
|
|
||||||
if let Some(el) = node.dyn_ref::<Element>() {
|
|
||||||
el.set_inner_html(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"value" => {
|
|
||||||
if let Some(input) = node.dyn_ref::<HtmlInputElement>() {
|
|
||||||
/*
|
|
||||||
if the attribute being set is the same as the value of the input, then don't bother setting it.
|
|
||||||
This is used in controlled components to keep the cursor in the right spot.
|
|
||||||
|
|
||||||
this logic should be moved into the virtualdom since we have the notion of "volatile"
|
|
||||||
*/
|
|
||||||
if input.value() != value {
|
|
||||||
input.set_value(value);
|
|
||||||
}
|
|
||||||
} else if let Some(node) = node.dyn_ref::<HtmlTextAreaElement>() {
|
|
||||||
if name == "value" {
|
|
||||||
node.set_value(value);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fallback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"checked" => {
|
|
||||||
if let Some(input) = node.dyn_ref::<HtmlInputElement>() {
|
|
||||||
match value {
|
|
||||||
"true" => input.set_checked(true),
|
|
||||||
"false" => input.set_checked(false),
|
|
||||||
_ => fallback(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fallback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"selected" => {
|
|
||||||
if let Some(node) = node.dyn_ref::<HtmlOptionElement>() {
|
|
||||||
node.set_selected(true);
|
|
||||||
} else {
|
|
||||||
fallback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
|
|
||||||
if value == "false" {
|
|
||||||
if let Some(el) = node.dyn_ref::<Element>() {
|
|
||||||
match name {
|
|
||||||
"allowfullscreen"
|
|
||||||
| "allowpaymentrequest"
|
|
||||||
| "async"
|
|
||||||
| "autofocus"
|
|
||||||
| "autoplay"
|
|
||||||
| "checked"
|
|
||||||
| "controls"
|
|
||||||
| "default"
|
|
||||||
| "defer"
|
|
||||||
| "disabled"
|
|
||||||
| "formnovalidate"
|
|
||||||
| "hidden"
|
|
||||||
| "ismap"
|
|
||||||
| "itemscope"
|
|
||||||
| "loop"
|
|
||||||
| "multiple"
|
|
||||||
| "muted"
|
|
||||||
| "nomodule"
|
|
||||||
| "novalidate"
|
|
||||||
| "open"
|
|
||||||
| "playsinline"
|
|
||||||
| "readonly"
|
|
||||||
| "required"
|
|
||||||
| "reversed"
|
|
||||||
| "selected"
|
|
||||||
| "truespeed" => {
|
|
||||||
let _ = el.remove_attribute(name);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let _ = el.set_attribute(name, value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fallback();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_attribute(&mut self, name: &str, root: u64) {
|
|
||||||
let node = self.nodes[root as usize].as_ref().unwrap();
|
|
||||||
if let Some(node) = node.dyn_ref::<web_sys::Element>() {
|
|
||||||
node.remove_attribute(name).unwrap();
|
|
||||||
}
|
|
||||||
if let Some(node) = node.dyn_ref::<HtmlInputElement>() {
|
|
||||||
// Some attributes are "volatile" and don't work through `removeAttribute`.
|
|
||||||
if name == "value" {
|
|
||||||
node.set_value("");
|
|
||||||
}
|
|
||||||
if name == "checked" {
|
|
||||||
node.set_checked(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(node) = node.dyn_ref::<HtmlOptionElement>() {
|
|
||||||
if name == "selected" {
|
|
||||||
node.set_selected(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert_after(&mut self, n: u32, root: u64) {
|
|
||||||
let old = self.nodes[root as usize].as_ref().unwrap();
|
|
||||||
|
|
||||||
let arr: js_sys::Array = self
|
|
||||||
.stack
|
|
||||||
.list
|
|
||||||
.drain((self.stack.list.len() - n as usize)..)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if let Some(el) = old.dyn_ref::<Element>() {
|
|
||||||
el.after_with_node(&arr).unwrap();
|
|
||||||
} else if let Some(el) = old.dyn_ref::<web_sys::CharacterData>() {
|
|
||||||
el.after_with_node(&arr).unwrap();
|
|
||||||
} else if let Some(el) = old.dyn_ref::<web_sys::DocumentType>() {
|
|
||||||
el.after_with_node(&arr).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert_before(&mut self, n: u32, root: u64) {
|
|
||||||
let anchor = self.nodes[root as usize].as_ref().unwrap();
|
|
||||||
|
|
||||||
if n == 1 {
|
|
||||||
let before = self.stack.pop();
|
|
||||||
|
|
||||||
anchor
|
|
||||||
.parent_node()
|
|
||||||
.unwrap()
|
|
||||||
.insert_before(&before, Some(anchor))
|
|
||||||
.unwrap();
|
|
||||||
} else {
|
|
||||||
let arr: js_sys::Array = self
|
|
||||||
.stack
|
|
||||||
.list
|
|
||||||
.drain((self.stack.list.len() - n as usize)..)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if let Some(el) = anchor.dyn_ref::<Element>() {
|
|
||||||
el.before_with_node(&arr).unwrap();
|
|
||||||
} else if let Some(el) = anchor.dyn_ref::<web_sys::CharacterData>() {
|
|
||||||
el.before_with_node(&arr).unwrap();
|
|
||||||
} else if let Some(el) = anchor.dyn_ref::<web_sys::DocumentType>() {
|
|
||||||
el.before_with_node(&arr).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
struct Stack {
|
|
||||||
list: Vec<Node>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Stack {
|
|
||||||
#[inline]
|
|
||||||
fn with_capacity(cap: usize) -> Self {
|
|
||||||
Stack {
|
|
||||||
list: Vec::with_capacity(cap),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn push(&mut self, node: Node) {
|
|
||||||
self.list.push(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn pop(&mut self) -> Node {
|
|
||||||
self.list.pop().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn top(&self) -> &Node {
|
|
||||||
match self.list.last() {
|
|
||||||
Some(a) => a,
|
|
||||||
None => panic!("Called 'top' of an empty stack, make sure to push the root first"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DioxusWebsysEvent(web_sys::Event);
|
pub struct DioxusWebsysEvent(web_sys::Event);
|
||||||
|
|
||||||
// safety: currently the web is not multithreaded and our VirtualDom exists on the same thread
|
// safety: currently the web is not multithreaded and our VirtualDom exists on the same thread
|
||||||
|
#[allow(clippy::non_send_fields_in_send_ty)]
|
||||||
unsafe impl Send for DioxusWebsysEvent {}
|
unsafe impl Send for DioxusWebsysEvent {}
|
||||||
unsafe impl Sync for DioxusWebsysEvent {}
|
unsafe impl Sync for DioxusWebsysEvent {}
|
||||||
|
|
||||||
@@ -628,6 +238,7 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc<dyn Any + Send
|
|||||||
shift_key: evt.shift_key(),
|
shift_key: evt.shift_key(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
"scroll" => Arc::new(()),
|
"scroll" => Arc::new(()),
|
||||||
"wheel" => {
|
"wheel" => {
|
||||||
let evt: &web_sys::WheelEvent = event.dyn_ref().unwrap();
|
let evt: &web_sys::WheelEvent = event.dyn_ref().unwrap();
|
||||||
|
|||||||
@@ -61,10 +61,10 @@ pub use dioxus_core as dioxus;
|
|||||||
use dioxus_core::prelude::Component;
|
use dioxus_core::prelude::Component;
|
||||||
use futures_util::FutureExt;
|
use futures_util::FutureExt;
|
||||||
|
|
||||||
|
pub(crate) mod bindings;
|
||||||
mod cache;
|
mod cache;
|
||||||
mod cfg;
|
mod cfg;
|
||||||
mod dom;
|
mod dom;
|
||||||
mod nodeslab;
|
|
||||||
mod rehydrate;
|
mod rehydrate;
|
||||||
mod ric_raf;
|
mod ric_raf;
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
//! This module provides a mirror of the VirtualDOM Element Slab using a Vector.
|
|
||||||
|
|
||||||
use std::ops::{Index, IndexMut};
|
|
||||||
use web_sys::Node;
|
|
||||||
|
|
||||||
pub(crate) struct NodeSlab {
|
|
||||||
nodes: Vec<Option<Node>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NodeSlab {
|
|
||||||
pub fn new(capacity: usize) -> NodeSlab {
|
|
||||||
let nodes = Vec::with_capacity(capacity);
|
|
||||||
NodeSlab { nodes }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Index<usize> for NodeSlab {
|
|
||||||
type Output = Option<Node>;
|
|
||||||
fn index(&self, index: usize) -> &Self::Output {
|
|
||||||
&self.nodes[index]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IndexMut<usize> for NodeSlab {
|
|
||||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
|
||||||
if index >= self.nodes.capacity() * 3 {
|
|
||||||
panic!("Trying to mutate an element way too far out of bounds");
|
|
||||||
}
|
|
||||||
|
|
||||||
if index + 1 > self.nodes.len() {
|
|
||||||
self.nodes.resize_with(index + 1, || None);
|
|
||||||
}
|
|
||||||
&mut self.nodes[index]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,857 @@
|
|||||||
|
//! Implementation of a renderer for Dioxus on the web.
|
||||||
|
//!
|
||||||
|
//! Oustanding todos:
|
||||||
|
//! - Removing event listeners (delegation)
|
||||||
|
//! - Passive event listeners
|
||||||
|
//! - no-op event listener patch for safari
|
||||||
|
//! - tests to ensure dyn_into works for various event types.
|
||||||
|
//! - Partial delegation?>
|
||||||
|
|
||||||
|
use dioxus_core::{DomEdit, ElementId, SchedulerMsg, ScopeId, UserEvent};
|
||||||
|
use fxhash::FxHashMap;
|
||||||
|
use std::{any::Any, fmt::Debug, rc::Rc, sync::Arc};
|
||||||
|
use wasm_bindgen::{closure::Closure, JsCast};
|
||||||
|
use web_sys::{
|
||||||
|
CssStyleDeclaration, Document, Element, Event, HtmlElement, HtmlInputElement,
|
||||||
|
HtmlOptionElement, HtmlTextAreaElement, Node,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{nodeslab::NodeSlab, WebConfig};
|
||||||
|
|
||||||
|
pub struct WebsysDom {
|
||||||
|
stack: Stack,
|
||||||
|
|
||||||
|
/// A map from ElementID (index) to Node
|
||||||
|
pub(crate) nodes: NodeSlab,
|
||||||
|
|
||||||
|
document: Document,
|
||||||
|
|
||||||
|
pub(crate) root: Element,
|
||||||
|
|
||||||
|
sender_callback: Rc<dyn Fn(SchedulerMsg)>,
|
||||||
|
|
||||||
|
// map of listener types to number of those listeners
|
||||||
|
// This is roughly a delegater
|
||||||
|
// TODO: check how infero delegates its events - some are more performant
|
||||||
|
listeners: FxHashMap<&'static str, ListenerEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListenerEntry = (usize, Closure<dyn FnMut(&Event)>);
|
||||||
|
|
||||||
|
impl WebsysDom {
|
||||||
|
pub fn new(cfg: WebConfig, sender_callback: Rc<dyn Fn(SchedulerMsg)>) -> Self {
|
||||||
|
let document = load_document();
|
||||||
|
|
||||||
|
let nodes = NodeSlab::new(2000);
|
||||||
|
let listeners = FxHashMap::default();
|
||||||
|
|
||||||
|
let mut stack = Stack::with_capacity(10);
|
||||||
|
|
||||||
|
let root = load_document().get_element_by_id(&cfg.rootname).unwrap();
|
||||||
|
let root_node = root.clone().dyn_into::<Node>().unwrap();
|
||||||
|
stack.push(root_node);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
stack,
|
||||||
|
nodes,
|
||||||
|
listeners,
|
||||||
|
document,
|
||||||
|
sender_callback,
|
||||||
|
root,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_edits(&mut self, mut edits: Vec<DomEdit>) {
|
||||||
|
for edit in edits.drain(..) {
|
||||||
|
match edit {
|
||||||
|
DomEdit::PushRoot { root } => self.push(root),
|
||||||
|
DomEdit::AppendChildren { many } => self.append_children(many),
|
||||||
|
DomEdit::ReplaceWith { m, root } => self.replace_with(m, root),
|
||||||
|
DomEdit::Remove { root } => self.remove(root),
|
||||||
|
DomEdit::CreateTextNode { text, root: id } => self.create_text_node(text, id),
|
||||||
|
DomEdit::CreateElement { tag, root: id } => self.create_element(tag, None, id),
|
||||||
|
DomEdit::CreateElementNs { tag, root: id, ns } => {
|
||||||
|
self.create_element(tag, Some(ns), id)
|
||||||
|
}
|
||||||
|
DomEdit::CreatePlaceholder { root: id } => self.create_placeholder(id),
|
||||||
|
DomEdit::NewEventListener {
|
||||||
|
event_name,
|
||||||
|
scope,
|
||||||
|
root: mounted_node_id,
|
||||||
|
} => self.new_event_listener(event_name, scope, mounted_node_id),
|
||||||
|
|
||||||
|
DomEdit::RemoveEventListener { event, root } => {
|
||||||
|
self.remove_event_listener(event, root)
|
||||||
|
}
|
||||||
|
|
||||||
|
DomEdit::SetText { text, root } => self.set_text(text, root),
|
||||||
|
DomEdit::SetAttribute {
|
||||||
|
field,
|
||||||
|
value,
|
||||||
|
ns,
|
||||||
|
root,
|
||||||
|
} => self.set_attribute(field, value, ns, root),
|
||||||
|
DomEdit::RemoveAttribute { name, root } => self.remove_attribute(name, root),
|
||||||
|
|
||||||
|
DomEdit::InsertAfter { n, root } => self.insert_after(n, root),
|
||||||
|
DomEdit::InsertBefore { n, root } => self.insert_before(n, root),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn push(&mut self, root: u64) {
|
||||||
|
let key = root as usize;
|
||||||
|
let domnode = &self.nodes[key];
|
||||||
|
|
||||||
|
let real_node: Node = match domnode {
|
||||||
|
Some(n) => n.clone(),
|
||||||
|
None => todo!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.stack.push(real_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_children(&mut self, many: u32) {
|
||||||
|
let root: Node = self
|
||||||
|
.stack
|
||||||
|
.list
|
||||||
|
.get(self.stack.list.len() - (1 + many as usize))
|
||||||
|
.unwrap()
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
// We need to make sure to add comments between text nodes
|
||||||
|
// We ensure that the text siblings are patched by preventing the browser from merging
|
||||||
|
// neighboring text nodes. Originally inspired by some of React's work from 2016.
|
||||||
|
// -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
|
||||||
|
// -> https://github.com/facebook/react/pull/5753
|
||||||
|
/*
|
||||||
|
todo: we need to track this for replacing/insert after/etc
|
||||||
|
*/
|
||||||
|
let mut last_node_was_text = false;
|
||||||
|
|
||||||
|
for child in self
|
||||||
|
.stack
|
||||||
|
.list
|
||||||
|
.drain((self.stack.list.len() - many as usize)..)
|
||||||
|
{
|
||||||
|
if child.dyn_ref::<web_sys::Text>().is_some() {
|
||||||
|
if last_node_was_text {
|
||||||
|
let comment_node = self
|
||||||
|
.document
|
||||||
|
.create_comment("dioxus")
|
||||||
|
.dyn_into::<Node>()
|
||||||
|
.unwrap();
|
||||||
|
root.append_child(&comment_node).unwrap();
|
||||||
|
}
|
||||||
|
last_node_was_text = true;
|
||||||
|
} else {
|
||||||
|
last_node_was_text = false;
|
||||||
|
}
|
||||||
|
root.append_child(&child).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_with(&mut self, m: u32, root: u64) {
|
||||||
|
let old = self.nodes[root as usize].as_ref().unwrap();
|
||||||
|
|
||||||
|
let arr: js_sys::Array = self
|
||||||
|
.stack
|
||||||
|
.list
|
||||||
|
.drain((self.stack.list.len() - m as usize)..)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if let Some(el) = old.dyn_ref::<Element>() {
|
||||||
|
el.replace_with_with_node(&arr).unwrap();
|
||||||
|
} else if let Some(el) = old.dyn_ref::<web_sys::CharacterData>() {
|
||||||
|
el.replace_with_with_node(&arr).unwrap();
|
||||||
|
} else if let Some(el) = old.dyn_ref::<web_sys::DocumentType>() {
|
||||||
|
el.replace_with_with_node(&arr).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&mut self, root: u64) {
|
||||||
|
let node = self.nodes[root as usize].as_ref().unwrap();
|
||||||
|
if let Some(element) = node.dyn_ref::<Element>() {
|
||||||
|
element.remove();
|
||||||
|
} else {
|
||||||
|
if let Some(parent) = node.parent_node() {
|
||||||
|
parent.remove_child(&node).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_placeholder(&mut self, id: u64) {
|
||||||
|
self.create_element("pre", None, id);
|
||||||
|
self.set_attribute("hidden", "", None, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_text_node(&mut self, text: &str, id: u64) {
|
||||||
|
let textnode = self
|
||||||
|
.document
|
||||||
|
.create_text_node(text)
|
||||||
|
.dyn_into::<Node>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
self.stack.push(textnode.clone());
|
||||||
|
|
||||||
|
self.nodes[(id as usize)] = Some(textnode);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_element(&mut self, tag: &str, ns: Option<&'static str>, id: u64) {
|
||||||
|
let tag = wasm_bindgen::intern(tag);
|
||||||
|
|
||||||
|
let el = match ns {
|
||||||
|
Some(ns) => self
|
||||||
|
.document
|
||||||
|
.create_element_ns(Some(ns), tag)
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<Node>()
|
||||||
|
.unwrap(),
|
||||||
|
None => self
|
||||||
|
.document
|
||||||
|
.create_element(tag)
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<Node>()
|
||||||
|
.unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
use smallstr::SmallString;
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
let mut s: SmallString<[u8; 8]> = smallstr::SmallString::new();
|
||||||
|
write!(s, "{}", id).unwrap();
|
||||||
|
|
||||||
|
let el2 = el.dyn_ref::<Element>().unwrap();
|
||||||
|
el2.set_attribute("dioxus-id", s.as_str()).unwrap();
|
||||||
|
|
||||||
|
self.stack.push(el.clone());
|
||||||
|
self.nodes[(id as usize)] = Some(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_event_listener(&mut self, event: &'static str, _scope: ScopeId, _real_id: u64) {
|
||||||
|
let event = wasm_bindgen::intern(event);
|
||||||
|
|
||||||
|
// attach the correct attributes to the element
|
||||||
|
// these will be used by accessing the event's target
|
||||||
|
// This ensures we only ever have one handler attached to the root, but decide
|
||||||
|
// dynamically when we want to call a listener.
|
||||||
|
|
||||||
|
let el = self.stack.top();
|
||||||
|
|
||||||
|
let el = el.dyn_ref::<Element>().unwrap();
|
||||||
|
|
||||||
|
el.set_attribute("dioxus-event", event).unwrap();
|
||||||
|
|
||||||
|
// Register the callback to decode
|
||||||
|
|
||||||
|
if let Some(entry) = self.listeners.get_mut(event) {
|
||||||
|
entry.0 += 1;
|
||||||
|
} else {
|
||||||
|
let trigger = self.sender_callback.clone();
|
||||||
|
|
||||||
|
let c: Box<dyn FnMut(&Event)> = Box::new(move |event: &web_sys::Event| {
|
||||||
|
// "Result" cannot be received from JS
|
||||||
|
// Instead, we just build and immediately execute a closure that returns result
|
||||||
|
match decode_trigger(event) {
|
||||||
|
Ok(synthetic_event) => {
|
||||||
|
let target = event.target().unwrap();
|
||||||
|
if let Some(node) = target.dyn_ref::<HtmlElement>() {
|
||||||
|
if let Some(name) = node.get_attribute("dioxus-prevent-default") {
|
||||||
|
if name == synthetic_event.name
|
||||||
|
|| name.trim_start_matches("on") == synthetic_event.name
|
||||||
|
{
|
||||||
|
log::trace!("Preventing default");
|
||||||
|
event.prevent_default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trigger.as_ref()(SchedulerMsg::Event(synthetic_event))
|
||||||
|
}
|
||||||
|
Err(e) => log::error!("Error decoding Dioxus event attribute. {:#?}", e),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
let handler = Closure::wrap(c);
|
||||||
|
|
||||||
|
self.root
|
||||||
|
.add_event_listener_with_callback(event, (&handler).as_ref().unchecked_ref())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Increment the listeners
|
||||||
|
self.listeners.insert(event.into(), (1, handler));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_event_listener(&mut self, _event: &str, _root: u64) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_text(&mut self, text: &str, root: u64) {
|
||||||
|
let el = self.nodes[root as usize].as_ref().unwrap();
|
||||||
|
el.set_text_content(Some(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_attribute(&mut self, name: &str, value: &str, ns: Option<&str>, root: u64) {
|
||||||
|
let node = self.nodes[root as usize].as_ref().unwrap();
|
||||||
|
if ns == Some("style") {
|
||||||
|
if let Some(el) = node.dyn_ref::<Element>() {
|
||||||
|
let el = el.dyn_ref::<HtmlElement>().unwrap();
|
||||||
|
let style_dc: CssStyleDeclaration = el.style();
|
||||||
|
style_dc.set_property(name, value).unwrap();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let fallback = || {
|
||||||
|
let el = node.dyn_ref::<Element>().unwrap();
|
||||||
|
el.set_attribute(name, value).unwrap()
|
||||||
|
};
|
||||||
|
match name {
|
||||||
|
"dangerous_inner_html" => {
|
||||||
|
if let Some(el) = node.dyn_ref::<Element>() {
|
||||||
|
el.set_inner_html(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"value" => {
|
||||||
|
if let Some(input) = node.dyn_ref::<HtmlInputElement>() {
|
||||||
|
/*
|
||||||
|
if the attribute being set is the same as the value of the input, then don't bother setting it.
|
||||||
|
This is used in controlled components to keep the cursor in the right spot.
|
||||||
|
|
||||||
|
this logic should be moved into the virtualdom since we have the notion of "volatile"
|
||||||
|
*/
|
||||||
|
if input.value() != value {
|
||||||
|
input.set_value(value);
|
||||||
|
}
|
||||||
|
} else if let Some(node) = node.dyn_ref::<HtmlTextAreaElement>() {
|
||||||
|
if name == "value" {
|
||||||
|
node.set_value(value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"checked" => {
|
||||||
|
if let Some(input) = node.dyn_ref::<HtmlInputElement>() {
|
||||||
|
match value {
|
||||||
|
"true" => input.set_checked(true),
|
||||||
|
"false" => input.set_checked(false),
|
||||||
|
_ => fallback(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"selected" => {
|
||||||
|
if let Some(node) = node.dyn_ref::<HtmlOptionElement>() {
|
||||||
|
node.set_selected(true);
|
||||||
|
} else {
|
||||||
|
fallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
|
||||||
|
if value == "false" {
|
||||||
|
if let Some(el) = node.dyn_ref::<Element>() {
|
||||||
|
match name {
|
||||||
|
"allowfullscreen"
|
||||||
|
| "allowpaymentrequest"
|
||||||
|
| "async"
|
||||||
|
| "autofocus"
|
||||||
|
| "autoplay"
|
||||||
|
| "checked"
|
||||||
|
| "controls"
|
||||||
|
| "default"
|
||||||
|
| "defer"
|
||||||
|
| "disabled"
|
||||||
|
| "formnovalidate"
|
||||||
|
| "hidden"
|
||||||
|
| "ismap"
|
||||||
|
| "itemscope"
|
||||||
|
| "loop"
|
||||||
|
| "multiple"
|
||||||
|
| "muted"
|
||||||
|
| "nomodule"
|
||||||
|
| "novalidate"
|
||||||
|
| "open"
|
||||||
|
| "playsinline"
|
||||||
|
| "readonly"
|
||||||
|
| "required"
|
||||||
|
| "reversed"
|
||||||
|
| "selected"
|
||||||
|
| "truespeed" => {
|
||||||
|
let _ = el.remove_attribute(name);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let _ = el.set_attribute(name, value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_attribute(&mut self, name: &str, root: u64) {
|
||||||
|
let node = self.nodes[root as usize].as_ref().unwrap();
|
||||||
|
if let Some(node) = node.dyn_ref::<web_sys::Element>() {
|
||||||
|
node.remove_attribute(name).unwrap();
|
||||||
|
}
|
||||||
|
if let Some(node) = node.dyn_ref::<HtmlInputElement>() {
|
||||||
|
// Some attributes are "volatile" and don't work through `removeAttribute`.
|
||||||
|
if name == "value" {
|
||||||
|
node.set_value("");
|
||||||
|
}
|
||||||
|
if name == "checked" {
|
||||||
|
node.set_checked(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(node) = node.dyn_ref::<HtmlOptionElement>() {
|
||||||
|
if name == "selected" {
|
||||||
|
node.set_selected(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_after(&mut self, n: u32, root: u64) {
|
||||||
|
let old = self.nodes[root as usize].as_ref().unwrap();
|
||||||
|
|
||||||
|
let arr: js_sys::Array = self
|
||||||
|
.stack
|
||||||
|
.list
|
||||||
|
.drain((self.stack.list.len() - n as usize)..)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if let Some(el) = old.dyn_ref::<Element>() {
|
||||||
|
el.after_with_node(&arr).unwrap();
|
||||||
|
} else if let Some(el) = old.dyn_ref::<web_sys::CharacterData>() {
|
||||||
|
el.after_with_node(&arr).unwrap();
|
||||||
|
} else if let Some(el) = old.dyn_ref::<web_sys::DocumentType>() {
|
||||||
|
el.after_with_node(&arr).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_before(&mut self, n: u32, root: u64) {
|
||||||
|
let anchor = self.nodes[root as usize].as_ref().unwrap();
|
||||||
|
|
||||||
|
if n == 1 {
|
||||||
|
let before = self.stack.pop();
|
||||||
|
|
||||||
|
anchor
|
||||||
|
.parent_node()
|
||||||
|
.unwrap()
|
||||||
|
.insert_before(&before, Some(&anchor))
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
let arr: js_sys::Array = self
|
||||||
|
.stack
|
||||||
|
.list
|
||||||
|
.drain((self.stack.list.len() - n as usize)..)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if let Some(el) = anchor.dyn_ref::<Element>() {
|
||||||
|
el.before_with_node(&arr).unwrap();
|
||||||
|
} else if let Some(el) = anchor.dyn_ref::<web_sys::CharacterData>() {
|
||||||
|
el.before_with_node(&arr).unwrap();
|
||||||
|
} else if let Some(el) = anchor.dyn_ref::<web_sys::DocumentType>() {
|
||||||
|
el.before_with_node(&arr).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct Stack {
|
||||||
|
list: Vec<Node>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stack {
|
||||||
|
#[inline]
|
||||||
|
fn with_capacity(cap: usize) -> Self {
|
||||||
|
Stack {
|
||||||
|
list: Vec::with_capacity(cap),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn push(&mut self, node: Node) {
|
||||||
|
self.list.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn pop(&mut self) -> Node {
|
||||||
|
self.list.pop().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn top(&self) -> &Node {
|
||||||
|
match self.list.last() {
|
||||||
|
Some(a) => a,
|
||||||
|
None => panic!("Called 'top' of an empty stack, make sure to push the root first"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DioxusWebsysEvent(web_sys::Event);
|
||||||
|
|
||||||
|
// safety: currently the web is not multithreaded and our VirtualDom exists on the same thread
|
||||||
|
unsafe impl Send for DioxusWebsysEvent {}
|
||||||
|
unsafe impl Sync for DioxusWebsysEvent {}
|
||||||
|
|
||||||
|
// todo: some of these events are being casted to the wrong event type.
|
||||||
|
// We need tests that simulate clicks/etc and make sure every event type works.
|
||||||
|
fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc<dyn Any + Send + Sync> {
|
||||||
|
use dioxus_html::on::*;
|
||||||
|
use dioxus_html::KeyCode;
|
||||||
|
|
||||||
|
match event.type_().as_str() {
|
||||||
|
"copy" | "cut" | "paste" => Arc::new(ClipboardData {}),
|
||||||
|
"compositionend" | "compositionstart" | "compositionupdate" => {
|
||||||
|
let evt: &web_sys::CompositionEvent = event.dyn_ref().unwrap();
|
||||||
|
Arc::new(CompositionData {
|
||||||
|
data: evt.data().unwrap_or_default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
"keydown" | "keypress" | "keyup" => {
|
||||||
|
let evt: &web_sys::KeyboardEvent = event.dyn_ref().unwrap();
|
||||||
|
Arc::new(KeyboardData {
|
||||||
|
alt_key: evt.alt_key(),
|
||||||
|
char_code: evt.char_code(),
|
||||||
|
key: evt.key(),
|
||||||
|
key_code: KeyCode::from_raw_code(evt.key_code() as u8),
|
||||||
|
ctrl_key: evt.ctrl_key(),
|
||||||
|
locale: "not implemented".to_string(),
|
||||||
|
location: evt.location() as usize,
|
||||||
|
meta_key: evt.meta_key(),
|
||||||
|
repeat: evt.repeat(),
|
||||||
|
shift_key: evt.shift_key(),
|
||||||
|
which: evt.which() as usize,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
"focus" | "blur" => Arc::new(FocusData {}),
|
||||||
|
|
||||||
|
// todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy
|
||||||
|
// don't have a good solution with the serialized event problem
|
||||||
|
"change" | "input" | "invalid" | "reset" | "submit" => {
|
||||||
|
let evt: &web_sys::Event = event.dyn_ref().unwrap();
|
||||||
|
|
||||||
|
let target: web_sys::EventTarget = evt.target().unwrap();
|
||||||
|
let value: String = (&target)
|
||||||
|
.dyn_ref()
|
||||||
|
.map(|input: &web_sys::HtmlInputElement| {
|
||||||
|
// todo: special case more input types
|
||||||
|
match input.type_().as_str() {
|
||||||
|
"checkbox" => {
|
||||||
|
match input.checked() {
|
||||||
|
true => "true".to_string(),
|
||||||
|
false => "false".to_string(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
input.value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
target
|
||||||
|
.dyn_ref()
|
||||||
|
.map(|input: &web_sys::HtmlTextAreaElement| input.value())
|
||||||
|
})
|
||||||
|
// select elements are NOT input events - because - why woudn't they be??
|
||||||
|
.or_else(|| {
|
||||||
|
target
|
||||||
|
.dyn_ref()
|
||||||
|
.map(|input: &web_sys::HtmlSelectElement| input.value())
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
target
|
||||||
|
.dyn_ref::<web_sys::HtmlElement>()
|
||||||
|
.unwrap()
|
||||||
|
.text_content()
|
||||||
|
})
|
||||||
|
.expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
|
||||||
|
|
||||||
|
Arc::new(FormData { value })
|
||||||
|
}
|
||||||
|
"click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
|
||||||
|
| "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
|
||||||
|
| "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
|
||||||
|
let evt: &web_sys::MouseEvent = event.dyn_ref().unwrap();
|
||||||
|
Arc::new(MouseData {
|
||||||
|
alt_key: evt.alt_key(),
|
||||||
|
button: evt.button(),
|
||||||
|
buttons: evt.buttons(),
|
||||||
|
client_x: evt.client_x(),
|
||||||
|
client_y: evt.client_y(),
|
||||||
|
ctrl_key: evt.ctrl_key(),
|
||||||
|
meta_key: evt.meta_key(),
|
||||||
|
screen_x: evt.screen_x(),
|
||||||
|
screen_y: evt.screen_y(),
|
||||||
|
shift_key: evt.shift_key(),
|
||||||
|
page_x: evt.page_x(),
|
||||||
|
page_y: evt.page_y(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
"pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
|
||||||
|
| "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
|
||||||
|
let evt: &web_sys::PointerEvent = event.dyn_ref().unwrap();
|
||||||
|
Arc::new(PointerData {
|
||||||
|
alt_key: evt.alt_key(),
|
||||||
|
button: evt.button(),
|
||||||
|
buttons: evt.buttons(),
|
||||||
|
client_x: evt.client_x(),
|
||||||
|
client_y: evt.client_y(),
|
||||||
|
ctrl_key: evt.ctrl_key(),
|
||||||
|
meta_key: evt.meta_key(),
|
||||||
|
page_x: evt.page_x(),
|
||||||
|
page_y: evt.page_y(),
|
||||||
|
screen_x: evt.screen_x(),
|
||||||
|
screen_y: evt.screen_y(),
|
||||||
|
shift_key: evt.shift_key(),
|
||||||
|
pointer_id: evt.pointer_id(),
|
||||||
|
width: evt.width(),
|
||||||
|
height: evt.height(),
|
||||||
|
pressure: evt.pressure(),
|
||||||
|
tangential_pressure: evt.tangential_pressure(),
|
||||||
|
tilt_x: evt.tilt_x(),
|
||||||
|
tilt_y: evt.tilt_y(),
|
||||||
|
twist: evt.twist(),
|
||||||
|
pointer_type: evt.pointer_type(),
|
||||||
|
is_primary: evt.is_primary(),
|
||||||
|
// get_modifier_state: evt.get_modifier_state(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
"select" => Arc::new(SelectionData {}),
|
||||||
|
"touchcancel" | "touchend" | "touchmove" | "touchstart" => {
|
||||||
|
let evt: &web_sys::TouchEvent = event.dyn_ref().unwrap();
|
||||||
|
Arc::new(TouchData {
|
||||||
|
alt_key: evt.alt_key(),
|
||||||
|
ctrl_key: evt.ctrl_key(),
|
||||||
|
meta_key: evt.meta_key(),
|
||||||
|
shift_key: evt.shift_key(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
"scroll" => Arc::new(()),
|
||||||
|
"wheel" => {
|
||||||
|
let evt: &web_sys::WheelEvent = event.dyn_ref().unwrap();
|
||||||
|
Arc::new(WheelData {
|
||||||
|
delta_x: evt.delta_x(),
|
||||||
|
delta_y: evt.delta_y(),
|
||||||
|
delta_z: evt.delta_z(),
|
||||||
|
delta_mode: evt.delta_mode(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
"animationstart" | "animationend" | "animationiteration" => {
|
||||||
|
let evt: &web_sys::AnimationEvent = event.dyn_ref().unwrap();
|
||||||
|
Arc::new(AnimationData {
|
||||||
|
elapsed_time: evt.elapsed_time(),
|
||||||
|
animation_name: evt.animation_name(),
|
||||||
|
pseudo_element: evt.pseudo_element(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
"transitionend" => {
|
||||||
|
let evt: &web_sys::TransitionEvent = event.dyn_ref().unwrap();
|
||||||
|
Arc::new(TransitionData {
|
||||||
|
elapsed_time: evt.elapsed_time(),
|
||||||
|
property_name: evt.property_name(),
|
||||||
|
pseudo_element: evt.pseudo_element(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
"abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
|
||||||
|
| "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
|
||||||
|
| "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
|
||||||
|
| "timeupdate" | "volumechange" | "waiting" => Arc::new(MediaData {}),
|
||||||
|
"toggle" => Arc::new(ToggleData {}),
|
||||||
|
_ => Arc::new(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
.target()
|
||||||
|
.expect("missing target")
|
||||||
|
.dyn_into::<Element>()
|
||||||
|
.expect("not a valid element");
|
||||||
|
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn load_document() -> Document {
|
||||||
|
web_sys::window()
|
||||||
|
.expect("should have access to the Window")
|
||||||
|
.document()
|
||||||
|
.expect("should have access to the Document")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event_name_from_typ(typ: &str) -> &'static str {
|
||||||
|
match typ {
|
||||||
|
"copy" => "copy",
|
||||||
|
"cut" => "cut",
|
||||||
|
"paste" => "paste",
|
||||||
|
"compositionend" => "compositionend",
|
||||||
|
"compositionstart" => "compositionstart",
|
||||||
|
"compositionupdate" => "compositionupdate",
|
||||||
|
"keydown" => "keydown",
|
||||||
|
"keypress" => "keypress",
|
||||||
|
"keyup" => "keyup",
|
||||||
|
"focus" => "focus",
|
||||||
|
"blur" => "blur",
|
||||||
|
"change" => "change",
|
||||||
|
"input" => "input",
|
||||||
|
"invalid" => "invalid",
|
||||||
|
"reset" => "reset",
|
||||||
|
"submit" => "submit",
|
||||||
|
"click" => "click",
|
||||||
|
"contextmenu" => "contextmenu",
|
||||||
|
"doubleclick" => "doubleclick",
|
||||||
|
"drag" => "drag",
|
||||||
|
"dragend" => "dragend",
|
||||||
|
"dragenter" => "dragenter",
|
||||||
|
"dragexit" => "dragexit",
|
||||||
|
"dragleave" => "dragleave",
|
||||||
|
"dragover" => "dragover",
|
||||||
|
"dragstart" => "dragstart",
|
||||||
|
"drop" => "drop",
|
||||||
|
"mousedown" => "mousedown",
|
||||||
|
"mouseenter" => "mouseenter",
|
||||||
|
"mouseleave" => "mouseleave",
|
||||||
|
"mousemove" => "mousemove",
|
||||||
|
"mouseout" => "mouseout",
|
||||||
|
"mouseover" => "mouseover",
|
||||||
|
"mouseup" => "mouseup",
|
||||||
|
"pointerdown" => "pointerdown",
|
||||||
|
"pointermove" => "pointermove",
|
||||||
|
"pointerup" => "pointerup",
|
||||||
|
"pointercancel" => "pointercancel",
|
||||||
|
"gotpointercapture" => "gotpointercapture",
|
||||||
|
"lostpointercapture" => "lostpointercapture",
|
||||||
|
"pointerenter" => "pointerenter",
|
||||||
|
"pointerleave" => "pointerleave",
|
||||||
|
"pointerover" => "pointerover",
|
||||||
|
"pointerout" => "pointerout",
|
||||||
|
"select" => "select",
|
||||||
|
"touchcancel" => "touchcancel",
|
||||||
|
"touchend" => "touchend",
|
||||||
|
"touchmove" => "touchmove",
|
||||||
|
"touchstart" => "touchstart",
|
||||||
|
"scroll" => "scroll",
|
||||||
|
"wheel" => "wheel",
|
||||||
|
"animationstart" => "animationstart",
|
||||||
|
"animationend" => "animationend",
|
||||||
|
"animationiteration" => "animationiteration",
|
||||||
|
"transitionend" => "transitionend",
|
||||||
|
"abort" => "abort",
|
||||||
|
"canplay" => "canplay",
|
||||||
|
"canplaythrough" => "canplaythrough",
|
||||||
|
"durationchange" => "durationchange",
|
||||||
|
"emptied" => "emptied",
|
||||||
|
"encrypted" => "encrypted",
|
||||||
|
"ended" => "ended",
|
||||||
|
"error" => "error",
|
||||||
|
"loadeddata" => "loadeddata",
|
||||||
|
"loadedmetadata" => "loadedmetadata",
|
||||||
|
"loadstart" => "loadstart",
|
||||||
|
"pause" => "pause",
|
||||||
|
"play" => "play",
|
||||||
|
"playing" => "playing",
|
||||||
|
"progress" => "progress",
|
||||||
|
"ratechange" => "ratechange",
|
||||||
|
"seeked" => "seeked",
|
||||||
|
"seeking" => "seeking",
|
||||||
|
"stalled" => "stalled",
|
||||||
|
"suspend" => "suspend",
|
||||||
|
"timeupdate" => "timeupdate",
|
||||||
|
"volumechange" => "volumechange",
|
||||||
|
"waiting" => "waiting",
|
||||||
|
"toggle" => "toggle",
|
||||||
|
_ => {
|
||||||
|
panic!("unsupported event type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//! This module provides a mirror of the VirtualDOM Element Slab using a Vector.
|
||||||
|
|
||||||
|
use std::ops::{Index, IndexMut};
|
||||||
|
use web_sys::Node;
|
||||||
|
|
||||||
|
pub(crate) struct NodeSlab {
|
||||||
|
nodes: Vec<Option<Node>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NodeSlab {
|
||||||
|
pub fn new(capacity: usize) -> NodeSlab {
|
||||||
|
let nodes = Vec::with_capacity(capacity);
|
||||||
|
NodeSlab { nodes }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Index<usize> for NodeSlab {
|
||||||
|
type Output = Option<Node>;
|
||||||
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
|
&self.nodes[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndexMut<usize> for NodeSlab {
|
||||||
|
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||||
|
if index >= self.nodes.capacity() * 3 {
|
||||||
|
panic!("Trying to mutate an element way too far out of bounds");
|
||||||
|
}
|
||||||
|
|
||||||
|
if index + 1 > self.nodes.len() {
|
||||||
|
self.nodes.resize_with(index + 1, || None);
|
||||||
|
}
|
||||||
|
&mut self.nodes[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct Stack {
|
||||||
|
list: Vec<Node>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stack {
|
||||||
|
#[inline]
|
||||||
|
fn with_capacity(cap: usize) -> Self {
|
||||||
|
Stack {
|
||||||
|
list: Vec::with_capacity(cap),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn push(&mut self, node: Node) {
|
||||||
|
self.list.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn pop(&mut self) -> Node {
|
||||||
|
self.list.pop().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn top(&self) -> &Node {
|
||||||
|
match self.list.last() {
|
||||||
|
Some(a) => a,
|
||||||
|
None => panic!("Called 'top' of an empty stack, make sure to push the root first"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -80,7 +80,8 @@ impl WebsysDom {
|
|||||||
|
|
||||||
*last_node_was_text = true;
|
*last_node_was_text = true;
|
||||||
|
|
||||||
self.nodes[node_id.0] = Some(node);
|
self.interpreter.set_node(node_id.0, node);
|
||||||
|
// self.nodes[node_id.0] = Some(node);
|
||||||
|
|
||||||
*cur_place += 1;
|
*cur_place += 1;
|
||||||
}
|
}
|
||||||
@@ -105,7 +106,8 @@ impl WebsysDom {
|
|||||||
.set_attribute("dioxus-id", s.as_str())
|
.set_attribute("dioxus-id", s.as_str())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
self.nodes[node_id.0] = Some(node.clone());
|
self.interpreter.set_node(node_id.0, node.clone());
|
||||||
|
// self.nodes[node_id.0] = Some(node.clone());
|
||||||
|
|
||||||
*cur_place += 1;
|
*cur_place += 1;
|
||||||
|
|
||||||
@@ -120,9 +122,10 @@ impl WebsysDom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for listener in vel.listeners {
|
for listener in vel.listeners {
|
||||||
self.new_event_listener(
|
self.interpreter.NewEventListener(
|
||||||
listener.event,
|
listener.event,
|
||||||
listener.mounted_node.get().unwrap().as_u64(),
|
listener.mounted_node.get().unwrap().as_u64(),
|
||||||
|
self.handler.as_ref().unchecked_ref(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +145,9 @@ impl WebsysDom {
|
|||||||
let cur_place = place.last_mut().unwrap();
|
let cur_place = place.last_mut().unwrap();
|
||||||
let node = nodes.last().unwrap().child_nodes().get(*cur_place).unwrap();
|
let node = nodes.last().unwrap().child_nodes().get(*cur_place).unwrap();
|
||||||
|
|
||||||
self.nodes[node_id.0] = Some(node);
|
self.interpreter.set_node(node_id.0, node);
|
||||||
|
|
||||||
|
// self.nodes[node_id.0] = Some(node);
|
||||||
|
|
||||||
*cur_place += 1;
|
*cur_place += 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,8 +68,7 @@ impl RafLoop {
|
|||||||
let ric_fn = self.ric_closure.as_ref().dyn_ref::<Function>().unwrap();
|
let ric_fn = self.ric_closure.as_ref().dyn_ref::<Function>().unwrap();
|
||||||
let _cb_id: u32 = self.window.request_idle_callback(ric_fn).unwrap();
|
let _cb_id: u32 = self.window.request_idle_callback(ric_fn).unwrap();
|
||||||
let deadline = self.ric_receiver.recv().await.unwrap();
|
let deadline = self.ric_receiver.recv().await.unwrap();
|
||||||
let deadline = TimeoutFuture::new(deadline);
|
TimeoutFuture::new(deadline)
|
||||||
deadline
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn wait_for_raf(&self) {
|
pub async fn wait_for_raf(&self) {
|
||||||
|
|||||||
Reference in new issue
Block a user