Add proper bundling to tsc
This commit is contained in:
15 files changed
+267
-1065
No files matched your search
@@ -23,7 +23,7 @@ fn main() {
|
|||||||
fn gen_bindings(name: &str) {
|
fn gen_bindings(name: &str) {
|
||||||
let contents = read_to_string(&format!("src/{name}.ts")).unwrap();
|
let contents = read_to_string(&format!("src/{name}.ts")).unwrap();
|
||||||
let generated = read_to_string(&format!("src/gen/{name}.js")).unwrap_or_default();
|
let generated = read_to_string(&format!("src/gen/{name}.js")).unwrap_or_default();
|
||||||
let hashed = hash_file(&contents);
|
let hashed = hash_it(&contents);
|
||||||
|
|
||||||
// If the file is generated, and the hash is the same, we're good, don't do anything
|
// If the file is generated, and the hash is the same, we're good, don't do anything
|
||||||
if generated
|
if generated
|
||||||
@@ -58,7 +58,7 @@ fn gen_bindings(name: &str) {
|
|||||||
std::fs::write(&format!("src/gen/{name}.js"), generated).unwrap();
|
std::fs::write(&format!("src/gen/{name}.js"), generated).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_file(obj: &str) -> u64 {
|
fn hash_it(obj: impl Hash) -> u64 {
|
||||||
let mut hasher = DefaultHasher::new();
|
let mut hasher = DefaultHasher::new();
|
||||||
obj.hash(&mut hasher);
|
obj.hash(&mut hasher);
|
||||||
hasher.finish()
|
hasher.finish()
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
this.setAttributeInner = function (node, field, value, ns) {
|
|
||||||
const name = field;
|
|
||||||
if (ns === "style") {
|
|
||||||
// ????? why do we need to do this
|
|
||||||
if (node.style === undefined) {
|
|
||||||
node.style = {};
|
|
||||||
}
|
|
||||||
node.style[name] = value;
|
|
||||||
} else if (!!ns) {
|
|
||||||
node.setAttributeNS(ns, name, value);
|
|
||||||
} else {
|
|
||||||
switch (name) {
|
|
||||||
case "value":
|
|
||||||
if (value !== node.value) {
|
|
||||||
node.value = value;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "initial_value":
|
|
||||||
node.defaultValue = value;
|
|
||||||
break;
|
|
||||||
case "checked":
|
|
||||||
node.checked = truthy(value);
|
|
||||||
break;
|
|
||||||
case "initial_checked":
|
|
||||||
node.defaultChecked = truthy(value);
|
|
||||||
break;
|
|
||||||
case "selected":
|
|
||||||
node.selected = truthy(value);
|
|
||||||
break;
|
|
||||||
case "initial_selected":
|
|
||||||
node.defaultSelected = truthy(value);
|
|
||||||
break;
|
|
||||||
case "dangerous_inner_html":
|
|
||||||
node.innerHTML = value;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
|
|
||||||
if (!truthy(value) && bool_attrs.hasOwnProperty(name)) {
|
|
||||||
node.removeAttribute(name);
|
|
||||||
} else {
|
|
||||||
node.setAttribute(name, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool_attrs = {
|
|
||||||
allowfullscreen: true,
|
|
||||||
allowpaymentrequest: true,
|
|
||||||
async: true,
|
|
||||||
autofocus: true,
|
|
||||||
autoplay: true,
|
|
||||||
checked: true,
|
|
||||||
controls: true,
|
|
||||||
default: true,
|
|
||||||
defer: true,
|
|
||||||
disabled: true,
|
|
||||||
formnovalidate: true,
|
|
||||||
hidden: true,
|
|
||||||
ismap: true,
|
|
||||||
itemscope: true,
|
|
||||||
loop: true,
|
|
||||||
multiple: true,
|
|
||||||
muted: true,
|
|
||||||
nomodule: true,
|
|
||||||
novalidate: true,
|
|
||||||
open: true,
|
|
||||||
playsinline: true,
|
|
||||||
readonly: true,
|
|
||||||
required: true,
|
|
||||||
reversed: true,
|
|
||||||
selected: true,
|
|
||||||
truespeed: true,
|
|
||||||
webkitdirectory: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
function truthy(val) {
|
|
||||||
return val === "true" || val === true;
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
export function setAttributeInner(node, field, value, ns) {
|
|
||||||
const name = field;
|
|
||||||
if (ns === "style") {
|
|
||||||
// ????? why do we need to do this
|
|
||||||
if (node.style === undefined) {
|
|
||||||
node.style = {};
|
|
||||||
}
|
|
||||||
node.style[name] = value;
|
|
||||||
} else if (!!ns) {
|
|
||||||
node.setAttributeNS(ns, name, value);
|
|
||||||
} else {
|
|
||||||
switch (name) {
|
|
||||||
case "value":
|
|
||||||
if (value !== node.value) {
|
|
||||||
node.value = value;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "initial_value":
|
|
||||||
node.defaultValue = value;
|
|
||||||
break;
|
|
||||||
case "checked":
|
|
||||||
node.checked = truthy(value);
|
|
||||||
break;
|
|
||||||
case "initial_checked":
|
|
||||||
node.defaultChecked = truthy(value);
|
|
||||||
break;
|
|
||||||
case "selected":
|
|
||||||
node.selected = truthy(value);
|
|
||||||
break;
|
|
||||||
case "initial_selected":
|
|
||||||
node.defaultSelected = truthy(value);
|
|
||||||
break;
|
|
||||||
case "dangerous_inner_html":
|
|
||||||
node.innerHTML = value;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
|
|
||||||
if (!truthy(value) && bool_attrs.hasOwnProperty(name)) {
|
|
||||||
node.removeAttribute(name);
|
|
||||||
} else {
|
|
||||||
node.setAttribute(name, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool_attrs = {
|
|
||||||
allowfullscreen: true,
|
|
||||||
allowpaymentrequest: true,
|
|
||||||
async: true,
|
|
||||||
autofocus: true,
|
|
||||||
autoplay: true,
|
|
||||||
checked: true,
|
|
||||||
controls: true,
|
|
||||||
default: true,
|
|
||||||
defer: true,
|
|
||||||
disabled: true,
|
|
||||||
formnovalidate: true,
|
|
||||||
hidden: true,
|
|
||||||
ismap: true,
|
|
||||||
itemscope: true,
|
|
||||||
loop: true,
|
|
||||||
multiple: true,
|
|
||||||
muted: true,
|
|
||||||
nomodule: true,
|
|
||||||
novalidate: true,
|
|
||||||
open: true,
|
|
||||||
playsinline: true,
|
|
||||||
readonly: true,
|
|
||||||
required: true,
|
|
||||||
reversed: true,
|
|
||||||
selected: true,
|
|
||||||
truespeed: true,
|
|
||||||
webkitdirectory: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
function truthy(val) {
|
|
||||||
return val === "true" || val === true;
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
// DO NOT EDIT THIS FILE. HASH: 9578489549991746027
|
|
||||||
export function setAttributeInner(node, field, value, ns) {
|
|
||||||
const name = field;
|
|
||||||
if (ns === "style") {
|
|
||||||
// ????? why do we need to do this
|
|
||||||
if (node.style === undefined) {
|
|
||||||
node.style = {};
|
|
||||||
}
|
|
||||||
node.style[name] = value;
|
|
||||||
}
|
|
||||||
else if (!!ns) {
|
|
||||||
node.setAttributeNS(ns, name, value);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
switch (name) {
|
|
||||||
case "value":
|
|
||||||
if (value !== node.value) {
|
|
||||||
node.value = value;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "initial_value":
|
|
||||||
node.defaultValue = value;
|
|
||||||
break;
|
|
||||||
case "checked":
|
|
||||||
node.checked = truthy(value);
|
|
||||||
break;
|
|
||||||
case "initial_checked":
|
|
||||||
node.defaultChecked = truthy(value);
|
|
||||||
break;
|
|
||||||
case "selected":
|
|
||||||
node.selected = truthy(value);
|
|
||||||
break;
|
|
||||||
case "initial_selected":
|
|
||||||
node.defaultSelected = truthy(value);
|
|
||||||
break;
|
|
||||||
case "dangerous_inner_html":
|
|
||||||
node.innerHTML = value;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
|
|
||||||
if (!truthy(value) && bool_attrs.hasOwnProperty(name)) {
|
|
||||||
node.removeAttribute(name);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
node.setAttribute(name, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const bool_attrs = {
|
|
||||||
allowfullscreen: true,
|
|
||||||
allowpaymentrequest: true,
|
|
||||||
async: true,
|
|
||||||
autofocus: true,
|
|
||||||
autoplay: true,
|
|
||||||
checked: true,
|
|
||||||
controls: true,
|
|
||||||
default: true,
|
|
||||||
defer: true,
|
|
||||||
disabled: true,
|
|
||||||
formnovalidate: true,
|
|
||||||
hidden: true,
|
|
||||||
ismap: true,
|
|
||||||
itemscope: true,
|
|
||||||
loop: true,
|
|
||||||
multiple: true,
|
|
||||||
muted: true,
|
|
||||||
nomodule: true,
|
|
||||||
novalidate: true,
|
|
||||||
open: true,
|
|
||||||
playsinline: true,
|
|
||||||
readonly: true,
|
|
||||||
required: true,
|
|
||||||
reversed: true,
|
|
||||||
selected: true,
|
|
||||||
truespeed: true,
|
|
||||||
webkitdirectory: true,
|
|
||||||
};
|
|
||||||
function truthy(val) {
|
|
||||||
return val === "true" || val === true;
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// DO NOT EDIT THIS FILE. HASH: 3476900567878811119
|
|
||||||
Whitespace-only changes.
Whitespace-only changes.
@@ -145,609 +145,3 @@ this.handler = async function (event, name, bubbles) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function find_real_id(target) {
|
|
||||||
let realId = null;
|
|
||||||
if (target instanceof Element) {
|
|
||||||
realId = target.getAttribute(`data-dioxus-id`);
|
|
||||||
}
|
|
||||||
// walk the tree to find the real element
|
|
||||||
while (realId == null) {
|
|
||||||
// we've reached the root we don't want to send an event
|
|
||||||
if (target.parentElement === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
target = target.parentElement;
|
|
||||||
if (target instanceof Element) {
|
|
||||||
realId = target.getAttribute(`data-dioxus-id`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return realId;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ListenerMap {
|
|
||||||
constructor(root) {
|
|
||||||
// bubbling events can listen at the root element
|
|
||||||
this.global = {};
|
|
||||||
// non bubbling events listen at the element the listener was created at
|
|
||||||
this.local = {};
|
|
||||||
this.root = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
create(event_name, element, bubbles, handler) {
|
|
||||||
if (bubbles) {
|
|
||||||
if (this.global[event_name] === undefined) {
|
|
||||||
this.global[event_name] = {};
|
|
||||||
this.global[event_name].active = 1;
|
|
||||||
this.root.addEventListener(event_name, handler);
|
|
||||||
} else {
|
|
||||||
this.global[event_name].active++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const id = element.getAttribute("data-dioxus-id");
|
|
||||||
if (!this.local[id]) {
|
|
||||||
this.local[id] = {};
|
|
||||||
}
|
|
||||||
element.addEventListener(event_name, handler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
remove(element, event_name, bubbles) {
|
|
||||||
if (bubbles) {
|
|
||||||
this.global[event_name].active--;
|
|
||||||
if (this.global[event_name].active === 0) {
|
|
||||||
this.root.removeEventListener(event_name, this.global[event_name].callback);
|
|
||||||
delete this.global[event_name];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const id = element.getAttribute("data-dioxus-id");
|
|
||||||
delete this.local[id][event_name];
|
|
||||||
if (this.local[id].length === 0) {
|
|
||||||
delete this.local[id];
|
|
||||||
}
|
|
||||||
element.removeEventListener(event_name, this.global[event_name].callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
removeAllNonBubbling(element) {
|
|
||||||
const id = element.getAttribute("data-dioxus-id");
|
|
||||||
delete this.local[id];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.LoadChild = function (array) {
|
|
||||||
// iterate through each number and get that child
|
|
||||||
let node = this.stack[this.stack.length - 1];
|
|
||||||
|
|
||||||
for (let i = 0; i < array.length; i++) {
|
|
||||||
this.end = array[i];
|
|
||||||
for (node = node.firstChild; this.end > 0; this.end--) {
|
|
||||||
node = node.nextSibling;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
this.listeners = new ListenerMap();
|
|
||||||
this.nodes = [];
|
|
||||||
this.stack = [];
|
|
||||||
this.root;
|
|
||||||
this.templates = {};
|
|
||||||
this.els = null;
|
|
||||||
this.end = null;
|
|
||||||
|
|
||||||
this.AppendChildren = function (id, many) {
|
|
||||||
this.root = this.nodes[id];
|
|
||||||
this.els = this.stack.splice(this.stack.length - many);
|
|
||||||
for (let k = 0; k < many; k++) {
|
|
||||||
this.root.appendChild(this.els[k]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.initialize = function (root) {
|
|
||||||
this.nodes = [root];
|
|
||||||
this.stack = [root];
|
|
||||||
this.listeners.root = root;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.getClientRect = function (id) {
|
|
||||||
const node = this.nodes[id];
|
|
||||||
if (!node) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const rect = node.getBoundingClientRect();
|
|
||||||
return {
|
|
||||||
type: "GetClientRect",
|
|
||||||
origin: [rect.x, rect.y],
|
|
||||||
size: [rect.width, rect.height],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scrollTo = function (id, behavior) {
|
|
||||||
const node = this.nodes[id];
|
|
||||||
if (!node) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
node.scrollIntoView({
|
|
||||||
behavior: behavior,
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the focus on the element
|
|
||||||
this.setFocus = function (id, focus) {
|
|
||||||
const node = this.nodes[id];
|
|
||||||
if (!node) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (focus) {
|
|
||||||
node.focus();
|
|
||||||
} else {
|
|
||||||
node.blur();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_mouse_data(event) {
|
|
||||||
const {
|
|
||||||
altKey,
|
|
||||||
button,
|
|
||||||
buttons,
|
|
||||||
clientX,
|
|
||||||
clientY,
|
|
||||||
ctrlKey,
|
|
||||||
metaKey,
|
|
||||||
offsetX,
|
|
||||||
offsetY,
|
|
||||||
pageX,
|
|
||||||
pageY,
|
|
||||||
screenX,
|
|
||||||
screenY,
|
|
||||||
shiftKey,
|
|
||||||
} = event;
|
|
||||||
return {
|
|
||||||
alt_key: altKey,
|
|
||||||
button: button,
|
|
||||||
buttons: buttons,
|
|
||||||
client_x: clientX,
|
|
||||||
client_y: clientY,
|
|
||||||
ctrl_key: ctrlKey,
|
|
||||||
meta_key: metaKey,
|
|
||||||
offset_x: offsetX,
|
|
||||||
offset_y: offsetY,
|
|
||||||
page_x: pageX,
|
|
||||||
page_y: pageY,
|
|
||||||
screen_x: screenX,
|
|
||||||
screen_y: screenY,
|
|
||||||
shift_key: shiftKey,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function serialize_event(event) {
|
|
||||||
switch (event.type) {
|
|
||||||
case "copy":
|
|
||||||
case "cut":
|
|
||||||
case "past": {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
case "compositionend":
|
|
||||||
case "compositionstart":
|
|
||||||
case "compositionupdate": {
|
|
||||||
let { data } = event;
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "keydown":
|
|
||||||
case "keypress":
|
|
||||||
case "keyup": {
|
|
||||||
let {
|
|
||||||
charCode,
|
|
||||||
isComposing,
|
|
||||||
key,
|
|
||||||
altKey,
|
|
||||||
ctrlKey,
|
|
||||||
metaKey,
|
|
||||||
keyCode,
|
|
||||||
shiftKey,
|
|
||||||
location,
|
|
||||||
repeat,
|
|
||||||
which,
|
|
||||||
code,
|
|
||||||
} = event;
|
|
||||||
return {
|
|
||||||
char_code: charCode,
|
|
||||||
is_composing: isComposing,
|
|
||||||
key: key,
|
|
||||||
alt_key: altKey,
|
|
||||||
ctrl_key: ctrlKey,
|
|
||||||
meta_key: metaKey,
|
|
||||||
key_code: keyCode,
|
|
||||||
shift_key: shiftKey,
|
|
||||||
location: location,
|
|
||||||
repeat: repeat,
|
|
||||||
which: which,
|
|
||||||
code,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "focus":
|
|
||||||
case "blur": {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
case "change": {
|
|
||||||
let target = event.target;
|
|
||||||
let value;
|
|
||||||
if (target.type === "checkbox" || target.type === "radio") {
|
|
||||||
value = target.checked ? "true" : "false";
|
|
||||||
} else {
|
|
||||||
value = target.value ?? target.textContent;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
value: value,
|
|
||||||
values: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "input":
|
|
||||||
case "invalid":
|
|
||||||
case "reset":
|
|
||||||
case "submit": {
|
|
||||||
let target = event.target;
|
|
||||||
let value = target.value ?? target.textContent;
|
|
||||||
|
|
||||||
if (target.type === "checkbox") {
|
|
||||||
value = target.checked ? "true" : "false";
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
value: value,
|
|
||||||
values: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "drag":
|
|
||||||
case "dragend":
|
|
||||||
case "dragenter":
|
|
||||||
case "dragexit":
|
|
||||||
case "dragleave":
|
|
||||||
case "dragover":
|
|
||||||
case "dragstart":
|
|
||||||
case "drop": {
|
|
||||||
let files = null;
|
|
||||||
if (event.dataTransfer && event.dataTransfer.files) {
|
|
||||||
files = await serializeFileList(event.dataTransfer.files);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { mouse: get_mouse_data(event), files };
|
|
||||||
}
|
|
||||||
case "click":
|
|
||||||
case "contextmenu":
|
|
||||||
case "doubleclick":
|
|
||||||
case "dblclick":
|
|
||||||
case "mousedown":
|
|
||||||
case "mouseenter":
|
|
||||||
case "mouseleave":
|
|
||||||
case "mousemove":
|
|
||||||
case "mouseout":
|
|
||||||
case "mouseover":
|
|
||||||
case "mouseup": {
|
|
||||||
return get_mouse_data(event);
|
|
||||||
}
|
|
||||||
case "pointerdown":
|
|
||||||
case "pointermove":
|
|
||||||
case "pointerup":
|
|
||||||
case "pointercancel":
|
|
||||||
case "gotpointercapture":
|
|
||||||
case "lostpointercapture":
|
|
||||||
case "pointerenter":
|
|
||||||
case "pointerleave":
|
|
||||||
case "pointerover":
|
|
||||||
case "pointerout": {
|
|
||||||
const {
|
|
||||||
altKey,
|
|
||||||
button,
|
|
||||||
buttons,
|
|
||||||
clientX,
|
|
||||||
clientY,
|
|
||||||
ctrlKey,
|
|
||||||
metaKey,
|
|
||||||
pageX,
|
|
||||||
pageY,
|
|
||||||
screenX,
|
|
||||||
screenY,
|
|
||||||
shiftKey,
|
|
||||||
pointerId,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
pressure,
|
|
||||||
tangentialPressure,
|
|
||||||
tiltX,
|
|
||||||
tiltY,
|
|
||||||
twist,
|
|
||||||
pointerType,
|
|
||||||
isPrimary,
|
|
||||||
} = event;
|
|
||||||
return {
|
|
||||||
alt_key: altKey,
|
|
||||||
button: button,
|
|
||||||
buttons: buttons,
|
|
||||||
client_x: clientX,
|
|
||||||
client_y: clientY,
|
|
||||||
ctrl_key: ctrlKey,
|
|
||||||
meta_key: metaKey,
|
|
||||||
page_x: pageX,
|
|
||||||
page_y: pageY,
|
|
||||||
screen_x: screenX,
|
|
||||||
screen_y: screenY,
|
|
||||||
shift_key: shiftKey,
|
|
||||||
pointer_id: pointerId,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
pressure: pressure,
|
|
||||||
tangential_pressure: tangentialPressure,
|
|
||||||
tilt_x: tiltX,
|
|
||||||
tilt_y: tiltY,
|
|
||||||
twist: twist,
|
|
||||||
pointer_type: pointerType,
|
|
||||||
is_primary: isPrimary,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "select": {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
case "touchcancel":
|
|
||||||
case "touchend":
|
|
||||||
case "touchmove":
|
|
||||||
case "touchstart": {
|
|
||||||
const { altKey, ctrlKey, metaKey, shiftKey } = event;
|
|
||||||
return {
|
|
||||||
// changed_touches: event.changedTouches,
|
|
||||||
// target_touches: event.targetTouches,
|
|
||||||
// touches: event.touches,
|
|
||||||
alt_key: altKey,
|
|
||||||
ctrl_key: ctrlKey,
|
|
||||||
meta_key: metaKey,
|
|
||||||
shift_key: shiftKey,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "scroll": {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
case "wheel": {
|
|
||||||
const { deltaX, deltaY, deltaZ, deltaMode } = event;
|
|
||||||
return {
|
|
||||||
delta_x: deltaX,
|
|
||||||
delta_y: deltaY,
|
|
||||||
delta_z: deltaZ,
|
|
||||||
delta_mode: deltaMode,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "animationstart":
|
|
||||||
case "animationend":
|
|
||||||
case "animationiteration": {
|
|
||||||
const { animationName, elapsedTime, pseudoElement } = event;
|
|
||||||
return {
|
|
||||||
animation_name: animationName,
|
|
||||||
elapsed_time: elapsedTime,
|
|
||||||
pseudo_element: pseudoElement,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "transitionend": {
|
|
||||||
const { propertyName, elapsedTime, pseudoElement } = event;
|
|
||||||
return {
|
|
||||||
property_name: propertyName,
|
|
||||||
elapsed_time: elapsedTime,
|
|
||||||
pseudo_element: pseudoElement,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "abort":
|
|
||||||
case "canplay":
|
|
||||||
case "canplaythrough":
|
|
||||||
case "durationchange":
|
|
||||||
case "emptied":
|
|
||||||
case "encrypted":
|
|
||||||
case "ended":
|
|
||||||
case "error":
|
|
||||||
case "loadeddata":
|
|
||||||
case "loadedmetadata":
|
|
||||||
case "loadstart":
|
|
||||||
case "pause":
|
|
||||||
case "play":
|
|
||||||
case "playing":
|
|
||||||
case "progress":
|
|
||||||
case "ratechange":
|
|
||||||
case "seeked":
|
|
||||||
case "seeking":
|
|
||||||
case "stalled":
|
|
||||||
case "suspend":
|
|
||||||
case "timeupdate":
|
|
||||||
case "volumechange":
|
|
||||||
case "waiting": {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
case "toggle": {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.serializeIpcMessage = function (method, params = {}) {
|
|
||||||
return JSON.stringify({ method, params });
|
|
||||||
}
|
|
||||||
|
|
||||||
function is_element_node(node) {
|
|
||||||
return node.nodeType == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function event_bubbles(event) {
|
|
||||||
switch (event) {
|
|
||||||
case "copy":
|
|
||||||
return true;
|
|
||||||
case "cut":
|
|
||||||
return true;
|
|
||||||
case "paste":
|
|
||||||
return true;
|
|
||||||
case "compositionend":
|
|
||||||
return true;
|
|
||||||
case "compositionstart":
|
|
||||||
return true;
|
|
||||||
case "compositionupdate":
|
|
||||||
return true;
|
|
||||||
case "keydown":
|
|
||||||
return true;
|
|
||||||
case "keypress":
|
|
||||||
return true;
|
|
||||||
case "keyup":
|
|
||||||
return true;
|
|
||||||
case "focus":
|
|
||||||
return false;
|
|
||||||
case "focusout":
|
|
||||||
return true;
|
|
||||||
case "focusin":
|
|
||||||
return true;
|
|
||||||
case "blur":
|
|
||||||
return false;
|
|
||||||
case "change":
|
|
||||||
return true;
|
|
||||||
case "input":
|
|
||||||
return true;
|
|
||||||
case "invalid":
|
|
||||||
return true;
|
|
||||||
case "reset":
|
|
||||||
return true;
|
|
||||||
case "submit":
|
|
||||||
return true;
|
|
||||||
case "click":
|
|
||||||
return true;
|
|
||||||
case "contextmenu":
|
|
||||||
return true;
|
|
||||||
case "doubleclick":
|
|
||||||
return true;
|
|
||||||
case "dblclick":
|
|
||||||
return true;
|
|
||||||
case "drag":
|
|
||||||
return true;
|
|
||||||
case "dragend":
|
|
||||||
return true;
|
|
||||||
case "dragenter":
|
|
||||||
return false;
|
|
||||||
case "dragexit":
|
|
||||||
return false;
|
|
||||||
case "dragleave":
|
|
||||||
return true;
|
|
||||||
case "dragover":
|
|
||||||
return true;
|
|
||||||
case "dragstart":
|
|
||||||
return true;
|
|
||||||
case "drop":
|
|
||||||
return true;
|
|
||||||
case "mousedown":
|
|
||||||
return true;
|
|
||||||
case "mouseenter":
|
|
||||||
return false;
|
|
||||||
case "mouseleave":
|
|
||||||
return false;
|
|
||||||
case "mousemove":
|
|
||||||
return true;
|
|
||||||
case "mouseout":
|
|
||||||
return true;
|
|
||||||
case "scroll":
|
|
||||||
return false;
|
|
||||||
case "mouseover":
|
|
||||||
return true;
|
|
||||||
case "mouseup":
|
|
||||||
return true;
|
|
||||||
case "pointerdown":
|
|
||||||
return true;
|
|
||||||
case "pointermove":
|
|
||||||
return true;
|
|
||||||
case "pointerup":
|
|
||||||
return true;
|
|
||||||
case "pointercancel":
|
|
||||||
return true;
|
|
||||||
case "gotpointercapture":
|
|
||||||
return true;
|
|
||||||
case "lostpointercapture":
|
|
||||||
return true;
|
|
||||||
case "pointerenter":
|
|
||||||
return false;
|
|
||||||
case "pointerleave":
|
|
||||||
return false;
|
|
||||||
case "pointerover":
|
|
||||||
return true;
|
|
||||||
case "pointerout":
|
|
||||||
return true;
|
|
||||||
case "select":
|
|
||||||
return true;
|
|
||||||
case "touchcancel":
|
|
||||||
return true;
|
|
||||||
case "touchend":
|
|
||||||
return true;
|
|
||||||
case "touchmove":
|
|
||||||
return true;
|
|
||||||
case "touchstart":
|
|
||||||
return true;
|
|
||||||
case "wheel":
|
|
||||||
return true;
|
|
||||||
case "abort":
|
|
||||||
return false;
|
|
||||||
case "canplay":
|
|
||||||
return false;
|
|
||||||
case "canplaythrough":
|
|
||||||
return false;
|
|
||||||
case "durationchange":
|
|
||||||
return false;
|
|
||||||
case "emptied":
|
|
||||||
return false;
|
|
||||||
case "encrypted":
|
|
||||||
return true;
|
|
||||||
case "ended":
|
|
||||||
return false;
|
|
||||||
case "error":
|
|
||||||
return false;
|
|
||||||
case "loadeddata":
|
|
||||||
case "loadedmetadata":
|
|
||||||
case "loadstart":
|
|
||||||
case "load":
|
|
||||||
return false;
|
|
||||||
case "pause":
|
|
||||||
return false;
|
|
||||||
case "play":
|
|
||||||
return false;
|
|
||||||
case "playing":
|
|
||||||
return false;
|
|
||||||
case "progress":
|
|
||||||
return false;
|
|
||||||
case "ratechange":
|
|
||||||
return false;
|
|
||||||
case "seeked":
|
|
||||||
return false;
|
|
||||||
case "seeking":
|
|
||||||
return false;
|
|
||||||
case "stalled":
|
|
||||||
return false;
|
|
||||||
case "suspend":
|
|
||||||
return false;
|
|
||||||
case "timeupdate":
|
|
||||||
return false;
|
|
||||||
case "volumechange":
|
|
||||||
return false;
|
|
||||||
case "waiting":
|
|
||||||
return false;
|
|
||||||
case "animationstart":
|
|
||||||
return true;
|
|
||||||
case "animationend":
|
|
||||||
return true;
|
|
||||||
case "animationiteration":
|
|
||||||
return true;
|
|
||||||
case "transitionend":
|
|
||||||
return true;
|
|
||||||
case "toggle":
|
|
||||||
return true;
|
|
||||||
case "mounted":
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,53 @@
|
|||||||
function retrieveFormValues() {
|
// Consistently deserialize forms and form elements for use across web/desktop/mobile
|
||||||
|
|
||||||
|
type FormValues = { [key: string]: FormDataEntryValue[] };
|
||||||
|
|
||||||
|
export function retriveValues(event: Event, target: HTMLElement): FormValues {
|
||||||
|
const contents: FormValues = {};
|
||||||
|
|
||||||
|
if (target instanceof HTMLFormElement && (event.type === "submit" || event.type === "input")) {
|
||||||
|
retrieveFormValues(target, contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target instanceof HTMLSelectElement && (event.type === "input" || event.type === "change")) {
|
||||||
|
retriveInputsValues(target, contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
return contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function retrieveFormValues(form: HTMLFormElement, contents: FormValues) {
|
||||||
|
const formData = new FormData(form);
|
||||||
|
|
||||||
|
for (let name in formData.keys()) {
|
||||||
|
let element = form.elements.namedItem(name);
|
||||||
|
|
||||||
|
// todo: this is going to be a problem for select-multiple?
|
||||||
|
if (!(element instanceof HTMLInputElement)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (element.type) {
|
||||||
|
case "select-multiple":
|
||||||
|
contents[name] = formData.getAll(name);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// By default, it's just a single value
|
||||||
|
default:
|
||||||
|
contents[name] = [formData.get(name)];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function retriveInputsValues(target: HTMLSelectElement, contents: FormValues,) {
|
||||||
|
const selectData = target.options;
|
||||||
|
contents["options"] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < selectData.length; i++) {
|
||||||
|
let option = selectData[i];
|
||||||
|
if (option.selected) {
|
||||||
|
contents["options"].push(option.value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
// The root interpreter class that holds state about the mapping between DOM and VirtualDom
|
// The root interpreter class that holds state about the mapping between DOM and VirtualDom
|
||||||
// This always lives in the JS side of things, and is extended by the native and web interpreters
|
// This always lives in the JS side of things, and is extended by the native and web interpreters
|
||||||
|
|
||||||
// todo: we want to make these vars that the interpreter uses to be isolated so we can have multiple interpreters
|
|
||||||
export let m: DataView; // p, ls, d, t, op, i, e, z, metaflags;
|
|
||||||
|
|
||||||
export class Interpreter {
|
export class Interpreter {
|
||||||
// non bubbling events listen at the element the listener was created at
|
// non bubbling events listen at the element the listener was created at
|
||||||
global: {
|
global: {
|
||||||
@@ -15,15 +12,15 @@ export class Interpreter {
|
|||||||
[key: string]: EventListener
|
[key: string]: EventListener
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
root: Element;
|
root: HTMLElement;
|
||||||
handler: EventListener;
|
handler: EventListener;
|
||||||
nodes: Element[];
|
nodes: Node[];
|
||||||
stack: Element[];
|
stack: Node[];
|
||||||
templates: {
|
templates: {
|
||||||
[key: string]: Element[]
|
[key: string]: Node[]
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(root: Element, handler: EventListener) {
|
constructor(root: HTMLElement, handler: EventListener) {
|
||||||
this.root = root;
|
this.root = root;
|
||||||
this.nodes = [root];
|
this.nodes = [root];
|
||||||
this.stack = [root];
|
this.stack = [root];
|
||||||
@@ -32,7 +29,7 @@ export class Interpreter {
|
|||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
createListener(event_name: string, element: Element, bubbles: boolean) {
|
createListener(event_name: string, element: HTMLElement, bubbles: boolean) {
|
||||||
if (bubbles) {
|
if (bubbles) {
|
||||||
if (this.global[event_name] === undefined) {
|
if (this.global[event_name] === undefined) {
|
||||||
this.global[event_name] = { active: 1, callback: this.handler };
|
this.global[event_name] = { active: 1, callback: this.handler };
|
||||||
@@ -49,7 +46,7 @@ export class Interpreter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
removeListener(element: Element, event_name: string, bubbles: boolean) {
|
removeListener(element: HTMLElement, event_name: string, bubbles: boolean) {
|
||||||
if (bubbles) {
|
if (bubbles) {
|
||||||
this.removeBubblingListener(event_name);
|
this.removeBubblingListener(event_name);
|
||||||
} else {
|
} else {
|
||||||
@@ -65,7 +62,7 @@ export class Interpreter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
removeNonBubblingListener(element: Element, event_name: string) {
|
removeNonBubblingListener(element: HTMLElement, event_name: string) {
|
||||||
const id = element.getAttribute("data-dioxus-id");
|
const id = element.getAttribute("data-dioxus-id");
|
||||||
delete this.local[id][event_name];
|
delete this.local[id][event_name];
|
||||||
if (Object.keys(this.local[id]).length === 0) {
|
if (Object.keys(this.local[id]).length === 0) {
|
||||||
@@ -74,7 +71,7 @@ export class Interpreter {
|
|||||||
element.removeEventListener(event_name, this.handler);
|
element.removeEventListener(event_name, this.handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeAllNonBubblingListeners(element: Element) {
|
removeAllNonBubblingListeners(element: HTMLElement) {
|
||||||
const id = element.getAttribute("data-dioxus-id");
|
const id = element.getAttribute("data-dioxus-id");
|
||||||
delete this.local[id];
|
delete this.local[id];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,27 @@
|
|||||||
// This file provides an extended variant of the interpreter used for desktop and liveview interaction
|
// This file provides an extended variant of the interpreter used for desktop and liveview interaction
|
||||||
//
|
//
|
||||||
//
|
// This file lives on the renderer, not the host. It's basically a polyfill over functionality that the host can't
|
||||||
|
// provide since it doesn't have access to the dom.
|
||||||
|
|
||||||
|
import { retriveValues } from "./form";
|
||||||
import { Interpreter } from "./interpreter_core";
|
import { Interpreter } from "./interpreter_core";
|
||||||
|
import { SerializedEvent, serializeEvent } from "./serialize";
|
||||||
|
|
||||||
export class NativeInterpreter extends Interpreter {
|
export class NativeInterpreter extends Interpreter {
|
||||||
intercept_link_redirects: boolean;
|
intercept_link_redirects: boolean;
|
||||||
|
ipc: any;
|
||||||
|
|
||||||
constructor(root: Element) {
|
// eventually we want to remove liveview and build it into the server-side-events of fullstack
|
||||||
super(root, (event) => handler(event, this, event.type, true));
|
// however, for now we need to support it since SSE in fullstack doesn't exist yet
|
||||||
|
liveview: boolean;
|
||||||
|
|
||||||
|
constructor(root: HTMLElement) {
|
||||||
|
super(root, (event) => this.handleEvent(event, event.type, true));
|
||||||
|
this.intercept_link_redirects = true;
|
||||||
|
this.liveview = false;
|
||||||
|
|
||||||
|
// @ts-ignore - wry gives us this
|
||||||
|
this.ipc = window.ipc;
|
||||||
}
|
}
|
||||||
|
|
||||||
serializeIpcMessage(method: string, params = {}) {
|
serializeIpcMessage(method: string, params = {}) {
|
||||||
@@ -17,50 +30,38 @@ export class NativeInterpreter extends Interpreter {
|
|||||||
|
|
||||||
scrollTo(id: number, behavior: ScrollBehavior) {
|
scrollTo(id: number, behavior: ScrollBehavior) {
|
||||||
const node = this.nodes[id];
|
const node = this.nodes[id];
|
||||||
|
if (node instanceof HTMLElement) {
|
||||||
if (!(node instanceof HTMLElement)) {
|
node.scrollIntoView({ behavior });
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
node.scrollIntoView({
|
|
||||||
behavior: behavior,
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getClientRect(id: number) {
|
getClientRect(id: number) {
|
||||||
const node = this.nodes[id];
|
const node = this.nodes[id];
|
||||||
if (!node) {
|
if (node instanceof HTMLElement) {
|
||||||
return;
|
const rect = node.getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
type: "GetClientRect",
|
||||||
|
origin: [rect.x, rect.y],
|
||||||
|
size: [rect.width, rect.height],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
const rect = node.getBoundingClientRect();
|
|
||||||
return {
|
|
||||||
type: "GetClientRect",
|
|
||||||
origin: [rect.x, rect.y],
|
|
||||||
size: [rect.width, rect.height],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setFocus(id: number, focus: boolean) {
|
setFocus(id: number, focus: boolean) {
|
||||||
const node = this.nodes[id];
|
const node = this.nodes[id];
|
||||||
|
|
||||||
if (!(node instanceof HTMLElement)) {
|
if (node instanceof HTMLElement) {
|
||||||
return false;
|
if (focus) {
|
||||||
|
node.focus();
|
||||||
|
} else {
|
||||||
|
node.blur();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (focus) {
|
|
||||||
node.focus();
|
|
||||||
} else {
|
|
||||||
node.blur();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadChild(array: number[]) {
|
LoadChild(array: number[]) {
|
||||||
// iterate through each number and get that child
|
// iterate through each number and get that child
|
||||||
let node = this.stack[this.stack.length - 1] as Node;
|
let node = this.stack[this.stack.length - 1];
|
||||||
|
|
||||||
for (let i = 0; i < array.length; i++) {
|
for (let i = 0; i < array.length; i++) {
|
||||||
let end = array[i];
|
let end = array[i];
|
||||||
@@ -80,73 +81,162 @@ export class NativeInterpreter extends Interpreter {
|
|||||||
root.appendChild(els[k]);
|
root.appendChild(els[k]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
handleEvent(event: Event, name: string, bubbles: boolean) {
|
||||||
|
const target = event.target!;
|
||||||
|
const realId = targetId(target)!;
|
||||||
|
const contents = serializeEvent(event);
|
||||||
|
|
||||||
|
// Attempt to retrive the values from the form and inputs
|
||||||
|
if (target instanceof HTMLElement) {
|
||||||
|
contents.values = retriveValues(event, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the event on the virtualdom and then preventDefault if it also preventsDefault
|
||||||
|
// Some listeners
|
||||||
|
let body = {
|
||||||
|
name: name,
|
||||||
|
data: contents,
|
||||||
|
element: realId,
|
||||||
|
bubbles,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run any prevent defaults the user might've set
|
||||||
|
// This is to support the prevent_default: "onclick" attribute that dioxus has had for a while, but is not necessary
|
||||||
|
// now that we expose preventDefault to the virtualdom on desktop
|
||||||
|
// Liveview will still need to use this
|
||||||
|
this.preventDefaults(event, target);
|
||||||
|
|
||||||
|
|
||||||
|
// liveview does not have syncronous event handling, so we need to send the event to the host
|
||||||
|
if (this.liveview) {
|
||||||
|
// Okay, so the user might've requested some files to be read
|
||||||
|
if (target instanceof HTMLInputElement && (event.type === "change" || event.type === "input")) {
|
||||||
|
if (target.getAttribute("type") === "file") {
|
||||||
|
this.readFiles(target, contents, bubbles, realId, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Run the event handler on the virtualdom
|
||||||
|
// capture/prevent default of the event if the virtualdom wants to
|
||||||
|
const res = handleVirtualdomEventSync(JSON.stringify(body));
|
||||||
|
|
||||||
function handler(event: Event, interpreter: NativeInterpreter, name: string, bubbles: boolean) {
|
if (res.preventDefault) {
|
||||||
const target = event.target!;
|
event.preventDefault();
|
||||||
const realId = target_id(target)!;
|
}
|
||||||
let contents = serializeEvent(event);
|
|
||||||
|
|
||||||
if (target instanceof HTMLFormElement && (event.type === "submit" || event.type === "input")) {
|
if (res.stopPropagation) {
|
||||||
const formData = new FormData(target);
|
event.stopPropagation();
|
||||||
|
|
||||||
for (let name of formData.keys()) {
|
|
||||||
const fieldType = target.elements[name].type;
|
|
||||||
|
|
||||||
switch (fieldType) {
|
|
||||||
case "select-multiple":
|
|
||||||
contents.values[name] = formData.getAll(name);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// add cases for fieldTypes that can hold multiple values here
|
|
||||||
default:
|
|
||||||
contents.values[name] = formData.get(name);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target instanceof HTMLSelectElement && (event.type === "input" || event.type === "change")) {
|
async readFiles(target: HTMLInputElement, contents: SerializedEvent, bubbles: boolean, realId: number, name: string) {
|
||||||
const selectData = target.options;
|
let files = target.files!;
|
||||||
contents.values["options"] = [];
|
let file_contents: { [name: string]: number[] } = {};
|
||||||
for (let i = 0; i < selectData.length; i++) {
|
|
||||||
let option = selectData[i];
|
for (let i = 0; i < files.length; i++) {
|
||||||
if (option.selected) {
|
const file = files[i];
|
||||||
contents.values["options"].push(option.value.toString());
|
file_contents[file.name] = Array.from(
|
||||||
}
|
new Uint8Array(await file.arrayBuffer())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
contents.files = { files: file_contents };
|
||||||
|
|
||||||
|
const message = this.serializeIpcMessage("user_event", {
|
||||||
|
name: name,
|
||||||
|
element: realId,
|
||||||
|
data: contents,
|
||||||
|
bubbles,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ipc.postMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This should:
|
||||||
|
// - prevent form submissions from navigating
|
||||||
|
// - prevent anchor tags from navigating
|
||||||
|
// - prevent buttons from submitting forms
|
||||||
|
// - let the virtualdom attempt to prevent the event
|
||||||
|
preventDefaults(event: Event, target: EventTarget) {
|
||||||
|
let preventDefaultRequests: string | null = null;
|
||||||
|
|
||||||
|
// Some events can be triggered on text nodes, which don't have attributes
|
||||||
|
if (target instanceof Element) {
|
||||||
|
preventDefaultRequests = target.getAttribute(`dioxus-prevent-default`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preventDefaultRequests && preventDefaultRequests.includes(`on${event.type}`)) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === "submit") {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to intercept if the event is a click
|
||||||
|
if (target instanceof Element && event.type === "click") {
|
||||||
|
this.handleClickNavigate(event, target, preventDefaultRequests);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's files to read
|
handleClickNavigate(event: Event, target: Element, preventDefaultRequests: string) {
|
||||||
if (target instanceof HTMLInputElement && (event.type === "change" || event.type === "input")) {
|
// todo call prevent default if it's the right type of event
|
||||||
if (target.getAttribute("type") === "file") {
|
if (!this.intercept_link_redirects) {
|
||||||
read_files(target, contents, bubbles, realId, name);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
prevents_default(event, target);
|
// prevent buttons in forms from submitting the form
|
||||||
|
if (target.tagName === "BUTTON" && event.type == "submit") {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the target is an anchor tag, we want to intercept the click too, to prevent the browser from navigating
|
||||||
|
let a_element = target.closest("a");
|
||||||
|
if (a_element == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle the event on the virtualdom and then process whatever its output was
|
|
||||||
let body = {
|
|
||||||
name: name,
|
|
||||||
data: serializeEvent(event),
|
|
||||||
element: parseInt(realId),
|
|
||||||
bubbles,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (waitForVirtualDomPreventDefault(JSON.stringify(body))) {
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
let elementShouldPreventDefault =
|
||||||
|
preventDefaultRequests && preventDefaultRequests.includes(`onclick`);
|
||||||
|
|
||||||
|
let aElementShouldPreventDefault = a_element.getAttribute(
|
||||||
|
`dioxus-prevent-default`
|
||||||
|
);
|
||||||
|
|
||||||
|
let linkShouldPreventDefault =
|
||||||
|
aElementShouldPreventDefault &&
|
||||||
|
aElementShouldPreventDefault.includes(`onclick`);
|
||||||
|
|
||||||
|
if (!elementShouldPreventDefault && !linkShouldPreventDefault) {
|
||||||
|
const href = a_element.getAttribute("href");
|
||||||
|
if (href !== "" && href !== null && href !== undefined) {
|
||||||
|
this.ipc.postMessage(
|
||||||
|
this.serializeIpcMessage("browser_open", { href })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function waitForVirtualDomPreventDefault(contents: string): boolean {
|
type EventSyncResult = {
|
||||||
|
preventDefault: boolean;
|
||||||
|
stopPropagation: boolean;
|
||||||
|
stopImmediatePropagation: boolean;
|
||||||
|
filesRequested: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This function sends the event to the virtualdom and then waits for the virtualdom to process it
|
||||||
|
//
|
||||||
|
// However, it's not really suitable for liveview, because it's synchronous and will block the main thread
|
||||||
|
// We should definitely consider using a websocket if we want to block... or just not block on liveview
|
||||||
|
// Liveview is a little bit of a tricky beast
|
||||||
|
function handleVirtualdomEventSync(contents: string): EventSyncResult {
|
||||||
// Handle the event on the virtualdom and then process whatever its output was
|
// Handle the event on the virtualdom and then process whatever its output was
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
@@ -157,130 +247,29 @@ export function waitForVirtualDomPreventDefault(contents: string): boolean {
|
|||||||
xhr.send(contents);
|
xhr.send(contents);
|
||||||
|
|
||||||
// Deserialize the response, and then prevent the default/capture the event if the virtualdom wants to
|
// Deserialize the response, and then prevent the default/capture the event if the virtualdom wants to
|
||||||
return JSON.parse(xhr.responseText).preventDefault;
|
return JSON.parse(xhr.responseText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function targetId(target: EventTarget): number | null {
|
||||||
async function read_files(target: HTMLInputElement, contents, bubbles, realId, name) {
|
// Ensure that the target is a node, sometimes it's nota
|
||||||
let files = target.files!;
|
if (!(target instanceof Node)) {
|
||||||
let file_contents: { [name: string]: number[] } = {};
|
|
||||||
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
|
||||||
const file = files[i];
|
|
||||||
file_contents[file.name] = Array.from(
|
|
||||||
new Uint8Array(await file.arrayBuffer())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
contents.files = { files: file_contents };
|
|
||||||
|
|
||||||
if (realId === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = window.interpreter.serializeIpcMessage("user_event", {
|
|
||||||
name: name,
|
|
||||||
element: parseInt(realId),
|
|
||||||
data: contents,
|
|
||||||
bubbles,
|
|
||||||
});
|
|
||||||
|
|
||||||
window.ipc.postMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function target_id(target: EventTarget): string | null {
|
|
||||||
if (!(target instanceof Element)) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function find_real_id(target: Element): string | null {
|
let ourTarget = target;
|
||||||
let realId = target.getAttribute(`data-dioxus-id`);
|
let realId = null;
|
||||||
|
|
||||||
// walk the tree to find the real element
|
while (realId == null) {
|
||||||
while (realId == null) {
|
if (ourTarget === null) {
|
||||||
// we've reached the root we don't want to send an event
|
return null;
|
||||||
if (target.parentElement === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
target = target.parentElement;
|
|
||||||
if (target instanceof Element) {
|
|
||||||
realId = target.getAttribute(`data-dioxus-id`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return realId;
|
if (ourTarget instanceof Element) {
|
||||||
}
|
realId = ourTarget.getAttribute(`data-dioxus-id`);
|
||||||
|
|
||||||
return find_real_id(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// This should:
|
|
||||||
// - prevent form submissions from navigating
|
|
||||||
// - prevent anchor tags from navigating
|
|
||||||
// - prevent buttons from submitting forms
|
|
||||||
// - let the virtualdom attempt to prevent the event
|
|
||||||
preventDefaults(event: Event, target: EventTarget) {
|
|
||||||
let preventDefaultRequests: string | null = null;
|
|
||||||
|
|
||||||
// Some events can be triggered on text nodes, which don't have attributes
|
|
||||||
if (target instanceof Element) {
|
|
||||||
preventDefaultRequests = target.getAttribute(`dioxus-prevent-default`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preventDefaultRequests && preventDefaultRequests.includes(`on${event.type}`)) {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type === "submit") {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to intercept if the event is a click
|
|
||||||
if (target instanceof Element && event.type === "click") {
|
|
||||||
this.preventFormNavigate(event, target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preventFormNavigate(event: Event, target: Element) {
|
|
||||||
// todo call prevent default if it's the right type of event
|
|
||||||
if (!this.intercept_link_redirects) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// prevent buttons in forms from submitting the form
|
|
||||||
if (target.tagName === "BUTTON") { // && event.type == "submit"
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the target is an anchor tag, we want to intercept the click too, to prevent the browser from navigating
|
|
||||||
let a_element = target.closest("a");
|
|
||||||
if (a_element == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
let elementShouldPreventDefault =
|
|
||||||
preventDefaultRequests && preventDefaultRequests.includes(`onclick`);
|
|
||||||
|
|
||||||
let aElementShouldPreventDefault = a_element.getAttribute(
|
|
||||||
`dioxus-prevent-default`
|
|
||||||
);
|
|
||||||
|
|
||||||
let linkShouldPreventDefault =
|
|
||||||
aElementShouldPreventDefault &&
|
|
||||||
aElementShouldPreventDefault.includes(`onclick`);
|
|
||||||
|
|
||||||
if (!elementShouldPreventDefault && !linkShouldPreventDefault) {
|
|
||||||
const href = a_element.getAttribute("href");
|
|
||||||
if (href !== "" && href !== null && href !== undefined) {
|
|
||||||
window.ipc.postMessage(
|
|
||||||
window.interpreter.serializeIpcMessage("browser_open", { href })
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ourTarget = ourTarget.parentNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return parseInt(realId);
|
||||||
}
|
}
|
||||||
@@ -4,10 +4,12 @@
|
|||||||
//
|
//
|
||||||
// We're using sledgehammer directly
|
// We're using sledgehammer directly
|
||||||
|
|
||||||
import { Interpreter, m } from "./interpreter_core";
|
import { Interpreter } from "./interpreter_core";
|
||||||
|
|
||||||
export class WebInterpreter extends Interpreter {
|
export class WebInterpreter extends Interpreter {
|
||||||
constructor(root: Element, handler: EventListener) {
|
m: any;
|
||||||
|
|
||||||
|
constructor(root: HTMLElement, handler: EventListener) {
|
||||||
super(root, handler);
|
super(root, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,7 +19,7 @@ export class WebInterpreter extends Interpreter {
|
|||||||
let ptr_end = ptr + len;
|
let ptr_end = ptr + len;
|
||||||
|
|
||||||
for (; ptr < ptr_end; ptr++) {
|
for (; ptr < ptr_end; ptr++) {
|
||||||
let end = m.getUint8(ptr);
|
let end = this.m.getUint8(ptr);
|
||||||
for (node = node.firstChild; end > 0; end--) {
|
for (node = node.firstChild; end > 0; end--) {
|
||||||
node = node.nextSibling;
|
node = node.nextSibling;
|
||||||
}
|
}
|
||||||
@@ -26,7 +28,7 @@ export class WebInterpreter extends Interpreter {
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveTemplate(nodes: Element[], tmpl_id: string) {
|
saveTemplate(nodes: HTMLElement[], tmpl_id: string) {
|
||||||
this.templates[tmpl_id] = nodes;
|
this.templates[tmpl_id] = nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,7 +36,7 @@ export class WebInterpreter extends Interpreter {
|
|||||||
const hydrateNodes = document.querySelectorAll('[data-node-hydration]');
|
const hydrateNodes = document.querySelectorAll('[data-node-hydration]');
|
||||||
|
|
||||||
for (let i = 0; i < hydrateNodes.length; i++) {
|
for (let i = 0; i < hydrateNodes.length; i++) {
|
||||||
const hydrateNode = hydrateNodes[i];
|
const hydrateNode = hydrateNodes[i] as HTMLElement;
|
||||||
const hydration = hydrateNode.getAttribute('data-node-hydration');
|
const hydration = hydrateNode.getAttribute('data-node-hydration');
|
||||||
const split = hydration!.split(',');
|
const split = hydration!.split(',');
|
||||||
const id = ids[parseInt(split[0])];
|
const id = ids[parseInt(split[0])];
|
||||||
@@ -54,22 +56,27 @@ export class WebInterpreter extends Interpreter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const treeWalker = document.createTreeWalker(
|
const treeWalker = document.createTreeWalker(
|
||||||
document.body,
|
document.body,
|
||||||
NodeFilter.SHOW_COMMENT,
|
NodeFilter.SHOW_COMMENT,
|
||||||
);
|
);
|
||||||
|
|
||||||
let currentNode = treeWalker.nextNode();
|
let currentNode = treeWalker.nextNode();
|
||||||
|
|
||||||
while (currentNode) {
|
while (currentNode) {
|
||||||
const id = currentNode.textContent!;
|
const id = currentNode.textContent!;
|
||||||
const split = id.split('node-id');
|
const split = id.split('node-id');
|
||||||
|
|
||||||
if (split.length > 1) {
|
if (split.length > 1) {
|
||||||
this.nodes[ids[parseInt(split[1])]] = currentNode.nextSibling as Element;
|
this.nodes[ids[parseInt(split[1])]] = currentNode.nextSibling;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentNode = treeWalker.nextNode();
|
currentNode = treeWalker.nextNode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getNode(id: number): Element {
|
getNode(id: number): Node {
|
||||||
return this.nodes[id];
|
return this.nodes[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +84,7 @@ export class WebInterpreter extends Interpreter {
|
|||||||
const root = this.nodes[id];
|
const root = this.nodes[id];
|
||||||
const els = this.stack.splice(this.stack.length - many);
|
const els = this.stack.splice(this.stack.length - many);
|
||||||
for (let k = 0; k < many; k++) {
|
for (let k = 0; k < many; k++) {
|
||||||
this.root.appendChild(els[k]);
|
root.appendChild(els[k]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
// Handle serialization of the event data across the IPC boundarytype SerialziedEvent = {};
|
// Handle serialization of the event data across the IPC boundarytype SerialziedEvent = {};
|
||||||
|
|
||||||
type SerializedEvent = {
|
export type SerializedEvent = {
|
||||||
values?: { [key: string]: FormDataEntryValue[] } | FormDataEntryValue[];
|
values?: { [key: string]: FormDataEntryValue[] };
|
||||||
value?: string;
|
value?: string;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
function serializeEvent(event: Event): SerializedEvent {
|
export function serializeEvent(event: Event): SerializedEvent {
|
||||||
|
|
||||||
// copy, cut, paste
|
// copy, cut, paste
|
||||||
if (event instanceof KeyboardEvent) {
|
if (event instanceof KeyboardEvent) {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// A unified interface for setting attributes on a node
|
||||||
|
|
||||||
// this function should try and stay fast, if possible
|
// this function should try and stay fast, if possible
|
||||||
export function setAttributeInner(node: HTMLElement, field: string, value: string, ns: string) {
|
export function setAttributeInner(node: HTMLElement, field: string, value: string, ns: string) {
|
||||||
// we support a single namespace by default: style
|
// we support a single namespace by default: style
|
||||||
|
|||||||
@@ -3,7 +3,10 @@
|
|||||||
"module": "system",
|
"module": "system",
|
||||||
"lib": [
|
"lib": [
|
||||||
"ES2015",
|
"ES2015",
|
||||||
"DOM"
|
"DOM",
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
],
|
],
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
|
|||||||
Reference in new issue
Block a user