fix navigating when files are dropped
This commit is contained in:
23 files changed
+1242
-1026
No files matched your search
@@ -1 +1 @@
|
||||
13493771420133770074
|
||||
4429706825984325407
|
||||
File diff suppressed because it is too large.
Load diff
+129
-158
@@ -1,159 +1,130 @@
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
if (typeof b !== "function" && b !== null)
|
||||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
System.register("interpreter_core", [], function (exports_1, context_1) {
|
||||
"use strict";
|
||||
var Interpreter;
|
||||
var __moduleName = context_1 && context_1.id;
|
||||
return {
|
||||
setters: [],
|
||||
execute: function () {
|
||||
Interpreter = (function () {
|
||||
function Interpreter(root, handler) {
|
||||
this.root = root;
|
||||
this.nodes = [root];
|
||||
this.stack = [root];
|
||||
this.global = {};
|
||||
this.local = {};
|
||||
this.handler = handler;
|
||||
}
|
||||
Interpreter.prototype.createListener = function (event_name, element, bubbles) {
|
||||
if (bubbles) {
|
||||
if (this.global[event_name] === undefined) {
|
||||
this.global[event_name] = { active: 1, callback: this.handler };
|
||||
this.root.addEventListener(event_name, this.handler);
|
||||
}
|
||||
else {
|
||||
this.global[event_name].active++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
var id = element.getAttribute("data-dioxus-id");
|
||||
if (!this.local[id]) {
|
||||
this.local[id] = {};
|
||||
}
|
||||
element.addEventListener(event_name, this.handler);
|
||||
}
|
||||
};
|
||||
Interpreter.prototype.removeListener = function (element, event_name, bubbles) {
|
||||
if (bubbles) {
|
||||
this.removeBubblingListener(event_name);
|
||||
}
|
||||
else {
|
||||
this.removeNonBubblingListener(element, event_name);
|
||||
}
|
||||
};
|
||||
Interpreter.prototype.removeBubblingListener = function (event_name) {
|
||||
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];
|
||||
}
|
||||
};
|
||||
Interpreter.prototype.removeNonBubblingListener = function (element, event_name) {
|
||||
var id = element.getAttribute("data-dioxus-id");
|
||||
delete this.local[id][event_name];
|
||||
if (Object.keys(this.local[id]).length === 0) {
|
||||
delete this.local[id];
|
||||
}
|
||||
element.removeEventListener(event_name, this.handler);
|
||||
};
|
||||
Interpreter.prototype.removeAllNonBubblingListeners = function (element) {
|
||||
var id = element.getAttribute("data-dioxus-id");
|
||||
delete this.local[id];
|
||||
};
|
||||
Interpreter.prototype.getNode = function (id) {
|
||||
return this.nodes[id];
|
||||
};
|
||||
Interpreter.prototype.appendChildren = function (id, many) {
|
||||
var root = this.nodes[id];
|
||||
var els = this.stack.splice(this.stack.length - many);
|
||||
for (var k = 0; k < many; k++) {
|
||||
root.appendChild(els[k]);
|
||||
}
|
||||
};
|
||||
return Interpreter;
|
||||
}());
|
||||
exports_1("Interpreter", Interpreter);
|
||||
// src/ts/interpreter_core.ts
|
||||
class Interpreter {
|
||||
global;
|
||||
local;
|
||||
root;
|
||||
handler;
|
||||
nodes;
|
||||
stack;
|
||||
templates;
|
||||
constructor(root, handler) {
|
||||
this.handler = handler;
|
||||
this.initialize(root);
|
||||
}
|
||||
initialize(root) {
|
||||
this.global = {};
|
||||
this.local = {};
|
||||
this.root = root;
|
||||
this.nodes = [root];
|
||||
this.stack = [root];
|
||||
this.templates = {};
|
||||
}
|
||||
createListener(event_name, element, bubbles) {
|
||||
if (bubbles) {
|
||||
if (this.global[event_name] === undefined) {
|
||||
this.global[event_name] = { active: 1, callback: this.handler };
|
||||
this.root.addEventListener(event_name, this.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, this.handler);
|
||||
}
|
||||
}
|
||||
removeListener(element, event_name, bubbles) {
|
||||
if (bubbles) {
|
||||
this.removeBubblingListener(event_name);
|
||||
} else {
|
||||
this.removeNonBubblingListener(element, event_name);
|
||||
}
|
||||
}
|
||||
removeBubblingListener(event_name) {
|
||||
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];
|
||||
}
|
||||
}
|
||||
removeNonBubblingListener(element, event_name) {
|
||||
const id = element.getAttribute("data-dioxus-id");
|
||||
delete this.local[id][event_name];
|
||||
if (Object.keys(this.local[id]).length === 0) {
|
||||
delete this.local[id];
|
||||
}
|
||||
element.removeEventListener(event_name, this.handler);
|
||||
}
|
||||
removeAllNonBubblingListeners(element) {
|
||||
const id = element.getAttribute("data-dioxus-id");
|
||||
delete this.local[id];
|
||||
}
|
||||
getNode(id) {
|
||||
return this.nodes[id];
|
||||
}
|
||||
appendChildren(id, many) {
|
||||
const root = this.nodes[id];
|
||||
const els = this.stack.splice(this.stack.length - many);
|
||||
for (let k = 0;k < many; k++) {
|
||||
root.appendChild(els[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// src/ts/interpreter_web.ts
|
||||
class PlatformInterpreter extends Interpreter {
|
||||
m;
|
||||
constructor(root, handler) {
|
||||
super(root, handler);
|
||||
}
|
||||
LoadChild(ptr, len) {
|
||||
let node = this.stack[this.stack.length - 1];
|
||||
let ptr_end = ptr + len;
|
||||
for (;ptr < ptr_end; ptr++) {
|
||||
let end = this.m.getUint8(ptr);
|
||||
for (node = node.firstChild;end > 0; end--) {
|
||||
node = node.nextSibling;
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
saveTemplate(nodes, tmpl_id) {
|
||||
this.templates[tmpl_id] = nodes;
|
||||
}
|
||||
hydrateRoot(ids) {
|
||||
const hydrateNodes = document.querySelectorAll("[data-node-hydration]");
|
||||
for (let i = 0;i < hydrateNodes.length; i++) {
|
||||
const hydrateNode = hydrateNodes[i];
|
||||
const hydration = hydrateNode.getAttribute("data-node-hydration");
|
||||
const split = hydration.split(",");
|
||||
const id = ids[parseInt(split[0])];
|
||||
this.nodes[id] = hydrateNode;
|
||||
if (split.length > 1) {
|
||||
hydrateNode.listening = split.length - 1;
|
||||
hydrateNode.setAttribute("data-dioxus-id", id.toString());
|
||||
for (let j = 1;j < split.length; j++) {
|
||||
const listener = split[j];
|
||||
const split2 = listener.split(":");
|
||||
const event_name = split2[0];
|
||||
const bubbles = split2[1] === "1";
|
||||
this.createListener(event_name, hydrateNode, bubbles);
|
||||
}
|
||||
};
|
||||
});
|
||||
System.register("interpreter_web", ["interpreter_core"], function (exports_2, context_2) {
|
||||
"use strict";
|
||||
var interpreter_core_1, WebInterpreter;
|
||||
var __moduleName = context_2 && context_2.id;
|
||||
return {
|
||||
setters: [
|
||||
function (interpreter_core_1_1) {
|
||||
interpreter_core_1 = interpreter_core_1_1;
|
||||
}
|
||||
],
|
||||
execute: function () {
|
||||
WebInterpreter = (function (_super) {
|
||||
__extends(WebInterpreter, _super);
|
||||
function WebInterpreter(root, handler) {
|
||||
return _super.call(this, root, handler) || this;
|
||||
}
|
||||
WebInterpreter.prototype.LoadChild = function (ptr, len) {
|
||||
var node = this.stack[this.stack.length - 1];
|
||||
var ptr_end = ptr + len;
|
||||
for (; ptr < ptr_end; ptr++) {
|
||||
var end = this.m.getUint8(ptr);
|
||||
for (node = node.firstChild; end > 0; end--) {
|
||||
node = node.nextSibling;
|
||||
}
|
||||
}
|
||||
return node;
|
||||
};
|
||||
WebInterpreter.prototype.saveTemplate = function (nodes, tmpl_id) {
|
||||
this.templates[tmpl_id] = nodes;
|
||||
};
|
||||
WebInterpreter.prototype.hydrateRoot = function (ids) {
|
||||
var hydrateNodes = document.querySelectorAll('[data-node-hydration]');
|
||||
for (var i = 0; i < hydrateNodes.length; i++) {
|
||||
var hydrateNode = hydrateNodes[i];
|
||||
var hydration = hydrateNode.getAttribute('data-node-hydration');
|
||||
var split = hydration.split(',');
|
||||
var id = ids[parseInt(split[0])];
|
||||
this.nodes[id] = hydrateNode;
|
||||
if (split.length > 1) {
|
||||
hydrateNode.listening = split.length - 1;
|
||||
hydrateNode.setAttribute('data-dioxus-id', id.toString());
|
||||
for (var j = 1; j < split.length; j++) {
|
||||
var listener = split[j];
|
||||
var split2 = listener.split(':');
|
||||
var event_name = split2[0];
|
||||
var bubbles = split2[1] === '1';
|
||||
this.createListener(event_name, hydrateNode, bubbles);
|
||||
}
|
||||
}
|
||||
}
|
||||
var treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT);
|
||||
var currentNode = treeWalker.nextNode();
|
||||
while (currentNode) {
|
||||
var id = currentNode.textContent;
|
||||
var split = id.split('node-id');
|
||||
if (split.length > 1) {
|
||||
this.nodes[ids[parseInt(split[1])]] = currentNode.nextSibling;
|
||||
}
|
||||
currentNode = treeWalker.nextNode();
|
||||
}
|
||||
};
|
||||
return WebInterpreter;
|
||||
}(interpreter_core_1.Interpreter));
|
||||
exports_2("WebInterpreter", WebInterpreter);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
const treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT);
|
||||
let currentNode = treeWalker.nextNode();
|
||||
while (currentNode) {
|
||||
const id = currentNode.textContent;
|
||||
const split = id.split("node-id");
|
||||
if (split.length > 1) {
|
||||
this.nodes[ids[parseInt(split[1])]] = currentNode.nextSibling;
|
||||
}
|
||||
currentNode = treeWalker.nextNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
export {
|
||||
PlatformInterpreter
|
||||
};
|
||||
@@ -24,6 +24,7 @@ pub mod minimal_bindings {
|
||||
/// Set the attribute of the node
|
||||
pub fn setAttributeInner(node: JsValue, name: &str, value: JsValue, ns: Option<&str>);
|
||||
|
||||
/// Roll up all the values from the node into a JS object that we can deserialize
|
||||
pub fn collectFormValues(node: JsValue) -> JsValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +1,58 @@
|
||||
// Consistently deserialize forms and form elements for use across web/desktop/mobile
|
||||
|
||||
type FormValues = { [key: string]: FormDataEntryValue[] };
|
||||
type FormValues = {
|
||||
valid?: boolean;
|
||||
values: { [key: string]: FormDataEntryValue };
|
||||
}
|
||||
|
||||
export function retriveValues(event: Event, target: HTMLElement): FormValues {
|
||||
const contents: FormValues = {};
|
||||
let contents: FormValues = {
|
||||
values: {}
|
||||
};
|
||||
|
||||
if (target instanceof HTMLFormElement && (event.type === "submit" || event.type === "input")) {
|
||||
retrieveFormValues(target, contents);
|
||||
}
|
||||
// If there's a form...
|
||||
let form = target.closest("form");
|
||||
|
||||
if (target instanceof HTMLSelectElement && (event.type === "input" || event.type === "change")) {
|
||||
retriveInputsValues(target, contents);
|
||||
// If the target is an input, and the event is input or change, we want to get the value without going through the form
|
||||
if (form) {
|
||||
if (
|
||||
event.type === "input"
|
||||
|| event.type === "change"
|
||||
|| event.type === "submit"
|
||||
|| event.type === "reset"
|
||||
|| event.type === "click"
|
||||
) {
|
||||
contents = retrieveFormValues(form);
|
||||
}
|
||||
}
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
export function retrieveFormValues(form: HTMLFormElement, contents: FormValues) {
|
||||
// todo: maybe encode spaces or something?
|
||||
// We encode select multiple as a comma separated list which breaks... when there's commas in the values
|
||||
export function retrieveFormValues(form: HTMLFormElement): 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;
|
||||
const contents: { [key: string]: FormDataEntryValue } = {};
|
||||
formData.forEach((value, key) => {
|
||||
if (contents[key]) {
|
||||
contents[key] += "," + value;
|
||||
} else {
|
||||
contents[key] = value;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
return {
|
||||
valid: form.checkValidity(),
|
||||
values: contents
|
||||
};
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
export function retriveSelectValue(target: HTMLSelectElement): string[] {
|
||||
// there might be multiple...
|
||||
let options = target.selectedOptions;
|
||||
let values = [];
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
values.push(options[i].value);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
// 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
|
||||
|
||||
export type NodeId = number;
|
||||
|
||||
export class Interpreter {
|
||||
// non bubbling events listen at the element the listener was created at
|
||||
global: {
|
||||
@@ -22,12 +24,18 @@ export class Interpreter {
|
||||
};
|
||||
|
||||
constructor(root: HTMLElement, handler: EventListener) {
|
||||
this.root = root;
|
||||
this.nodes = [root];
|
||||
this.stack = [root];
|
||||
this.handler = handler;
|
||||
this.initialize(root);
|
||||
}
|
||||
|
||||
initialize(root: HTMLElement) {
|
||||
this.global = {};
|
||||
this.local = {};
|
||||
this.handler = handler;
|
||||
this.root = root;
|
||||
|
||||
this.nodes = [root];
|
||||
this.stack = [root];
|
||||
this.templates = {};
|
||||
}
|
||||
|
||||
createListener(event_name: string, element: HTMLElement, bubbles: boolean) {
|
||||
@@ -77,11 +85,11 @@ export class Interpreter {
|
||||
delete this.local[id];
|
||||
}
|
||||
|
||||
getNode(id: number): Node {
|
||||
getNode(id: NodeId): Node {
|
||||
return this.nodes[id];
|
||||
}
|
||||
|
||||
appendChildren(id: number, many: number) {
|
||||
appendChildren(id: NodeId, many: number) {
|
||||
const root = this.nodes[id];
|
||||
const els = this.stack.splice(this.stack.length - many);
|
||||
for (let k = 0; k < many; k++) {
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
// 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, NodeId } from "./interpreter_core";
|
||||
import { SerializedEvent, serializeEvent } from "./serialize";
|
||||
import { setAttributeInner } from "./set_attribute";
|
||||
|
||||
|
||||
export class NativeInterpreter extends Interpreter {
|
||||
export class PlatformInterpreter extends Interpreter {
|
||||
intercept_link_redirects: boolean;
|
||||
ipc: any;
|
||||
|
||||
@@ -21,6 +20,23 @@ export class NativeInterpreter extends Interpreter {
|
||||
this.intercept_link_redirects = true;
|
||||
this.liveview = false;
|
||||
|
||||
// attach an event listener on the body that prevents file drops from navigating
|
||||
// this is because the browser will try to navigate to the file if it's dropped on the window
|
||||
window.addEventListener("dragover", function (e) {
|
||||
// check which element is our target
|
||||
if (e.target instanceof Element && e.target.tagName != "INPUT") {
|
||||
e.preventDefault();
|
||||
}
|
||||
}, false);
|
||||
|
||||
window.addEventListener("drop", function (e) {
|
||||
// check which element is our target
|
||||
if (e.target instanceof Element && e.target.tagName != "INPUT") {
|
||||
e.preventDefault();
|
||||
}
|
||||
}, false);
|
||||
|
||||
|
||||
// @ts-ignore - wry gives us this
|
||||
this.ipc = window.ipc;
|
||||
}
|
||||
@@ -29,14 +45,18 @@ export class NativeInterpreter extends Interpreter {
|
||||
return JSON.stringify({ method, params });
|
||||
}
|
||||
|
||||
scrollTo(id: number, behavior: ScrollBehavior) {
|
||||
setAttributeInner(node: HTMLElement, field: string, value: string, ns: string) {
|
||||
setAttributeInner(node, field, value, ns);
|
||||
}
|
||||
|
||||
scrollTo(id: NodeId, behavior: ScrollBehavior) {
|
||||
const node = this.nodes[id];
|
||||
if (node instanceof HTMLElement) {
|
||||
node.scrollIntoView({ behavior });
|
||||
}
|
||||
}
|
||||
|
||||
getClientRect(id: number) {
|
||||
getClientRect(id: NodeId): { type: string; origin: number[]; size: number[]; } | undefined {
|
||||
const node = this.nodes[id];
|
||||
if (node instanceof HTMLElement) {
|
||||
const rect = node.getBoundingClientRect();
|
||||
@@ -48,7 +68,7 @@ export class NativeInterpreter extends Interpreter {
|
||||
}
|
||||
}
|
||||
|
||||
setFocus(id: number, focus: boolean) {
|
||||
setFocus(id: NodeId, focus: boolean) {
|
||||
const node = this.nodes[id];
|
||||
|
||||
if (node instanceof HTMLElement) {
|
||||
@@ -74,7 +94,7 @@ export class NativeInterpreter extends Interpreter {
|
||||
return node;
|
||||
}
|
||||
|
||||
AppendChildren(id: number, many: number) {
|
||||
AppendChildren(id: NodeId, many: number) {
|
||||
const root = this.nodes[id];
|
||||
const els = this.stack.splice(this.stack.length - many);
|
||||
|
||||
@@ -86,12 +106,7 @@ export class NativeInterpreter extends Interpreter {
|
||||
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);
|
||||
}
|
||||
const contents = serializeEvent(event, target);
|
||||
|
||||
// Handle the event on the virtualdom and then preventDefault if it also preventsDefault
|
||||
// Some listeners
|
||||
@@ -119,21 +134,26 @@ export class NativeInterpreter extends Interpreter {
|
||||
}
|
||||
} 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));
|
||||
const message = this.serializeIpcMessage("user_event", body);
|
||||
this.ipc.postMessage(message);
|
||||
|
||||
if (res.preventDefault) {
|
||||
event.preventDefault();
|
||||
}
|
||||
// // Run the event handler on the virtualdom
|
||||
// // capture/prevent default of the event if the virtualdom wants to
|
||||
// const res = handleVirtualdomEventSync(JSON.stringify(body));
|
||||
|
||||
if (res.stopPropagation) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
// if (res.preventDefault) {
|
||||
// event.preventDefault();
|
||||
// }
|
||||
|
||||
// if (res.stopPropagation) {
|
||||
// event.stopPropagation();
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
async readFiles(target: HTMLInputElement, contents: SerializedEvent, bubbles: boolean, realId: number, name: string) {
|
||||
// A liveview only function
|
||||
// Desktop will intercept the event before it hits this
|
||||
async readFiles(target: HTMLInputElement, contents: SerializedEvent, bubbles: boolean, realId: NodeId, name: string) {
|
||||
let files = target.files!;
|
||||
let file_contents: { [name: string]: number[] } = {};
|
||||
|
||||
@@ -251,7 +271,7 @@ function handleVirtualdomEventSync(contents: string): EventSyncResult {
|
||||
return JSON.parse(xhr.responseText);
|
||||
}
|
||||
|
||||
function targetId(target: EventTarget): number | null {
|
||||
function targetId(target: EventTarget): NodeId | null {
|
||||
// Ensure that the target is a node, sometimes it's nota
|
||||
if (!(target instanceof Node)) {
|
||||
return null;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { Interpreter } from "./interpreter_core";
|
||||
|
||||
export class WebInterpreter extends Interpreter {
|
||||
export class PlatformInterpreter extends Interpreter {
|
||||
m: any;
|
||||
|
||||
constructor(root: HTMLElement, handler: EventListener) {
|
||||
|
||||
@@ -1,159 +1,217 @@
|
||||
// Handle serialization of the event data across the IPC boundarytype SerialziedEvent = {};
|
||||
|
||||
import { retriveSelectValue, retriveValues } from "./form";
|
||||
|
||||
export type AppTouchEvent = TouchEvent;
|
||||
|
||||
export type SerializedEvent = {
|
||||
values?: { [key: string]: FormDataEntryValue[] };
|
||||
values?: { [key: string]: FormDataEntryValue };
|
||||
value?: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export function serializeEvent(event: Event): SerializedEvent {
|
||||
if (event instanceof InputEvent) {
|
||||
if (event.target instanceof HTMLInputElement) {
|
||||
let target = event.target;
|
||||
let value = target.value ?? target.textContent;
|
||||
if (target.type === "checkbox") {
|
||||
value = target.checked ? "true" : "false";
|
||||
}
|
||||
return {
|
||||
value: value,
|
||||
values: {},
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
if (event instanceof KeyboardEvent) {
|
||||
return {
|
||||
char_code: event.charCode,
|
||||
is_composing: event.isComposing,
|
||||
key: event.key,
|
||||
alt_key: event.altKey,
|
||||
ctrl_key: event.ctrlKey,
|
||||
meta_key: event.metaKey,
|
||||
key_code: event.keyCode,
|
||||
shift_key: event.shiftKey,
|
||||
location: event.location,
|
||||
repeat: event.repeat,
|
||||
which: event.which,
|
||||
code: event.code,
|
||||
};
|
||||
}
|
||||
|
||||
if (event instanceof MouseEvent) {
|
||||
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,
|
||||
offset_x: event.offsetX,
|
||||
offset_y: event.offsetY,
|
||||
page_x: event.pageX,
|
||||
page_y: event.pageY,
|
||||
screen_x: event.screenX,
|
||||
screen_y: event.screenY,
|
||||
shift_key: event.shiftKey,
|
||||
};
|
||||
}
|
||||
|
||||
if (event instanceof PointerEvent) {
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (event instanceof TouchEvent) {
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
if (event instanceof WheelEvent) {
|
||||
return {
|
||||
delta_x: event.deltaX,
|
||||
delta_y: event.deltaY,
|
||||
delta_z: event.deltaZ,
|
||||
delta_mode: event.deltaMode,
|
||||
};
|
||||
}
|
||||
|
||||
if (event instanceof AnimationEvent) {
|
||||
return {
|
||||
animation_name: event.animationName,
|
||||
elapsed_time: event.elapsedTime,
|
||||
pseudo_element: event.pseudoElement,
|
||||
};
|
||||
}
|
||||
|
||||
if (event instanceof TransitionEvent) {
|
||||
return {
|
||||
property_name: event.propertyName,
|
||||
elapsed_time: event.elapsedTime,
|
||||
pseudo_element: event.pseudoElement,
|
||||
};
|
||||
}
|
||||
|
||||
if (event instanceof ClipboardEvent) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (event instanceof CompositionEvent) {
|
||||
return {
|
||||
data: event.data,
|
||||
};
|
||||
export function serializeEvent(event: Event, target: EventTarget): SerializedEvent {
|
||||
let contents = {};
|
||||
|
||||
// merge the object into the contents
|
||||
let extend = (obj: any) => (contents = { ...contents, ...obj });
|
||||
|
||||
if (event instanceof WheelEvent) { extend(serializeWheelEvent(event)) };
|
||||
if (event instanceof MouseEvent) { extend(serializeMouseEvent(event)) }
|
||||
if (event instanceof KeyboardEvent) { extend(serializeKeyboardEvent(event)) }
|
||||
|
||||
if (event instanceof InputEvent) { extend(serializeInputEvent(event, target)) }
|
||||
if (event instanceof PointerEvent) { extend(serializePointerEvent(event)) }
|
||||
if (event instanceof AnimationEvent) { extend(serializeAnimationEvent(event)) }
|
||||
if (event instanceof TransitionEvent) { extend({ property_name: event.propertyName, elapsed_time: event.elapsedTime, pseudo_element: event.pseudoElement, }) }
|
||||
if (event instanceof CompositionEvent) { extend({ data: event.data, }) }
|
||||
if (event instanceof DragEvent) { extend(serializeDragEvent(event)) }
|
||||
if (event instanceof FocusEvent) { extend({}) }
|
||||
if (event instanceof ClipboardEvent) { extend({}) }
|
||||
|
||||
// safari is quirky and doesn't have TouchEvent
|
||||
if (typeof TouchEvent !== 'undefined' && event instanceof TouchEvent) { extend(serializeTouchEvent(event)); }
|
||||
|
||||
if (event.type === "submit" || event.type === "reset" || event.type === "click" || event.type === "change" || event.type === "input") {
|
||||
extend(serializeInputEvent(event as InputEvent, target));
|
||||
}
|
||||
|
||||
// If there's any files, we need to serialize them
|
||||
if (event instanceof DragEvent) {
|
||||
// let files = [];
|
||||
// if (event.dataTransfer && event.dataTransfer.files) {
|
||||
// files = ["a", "b", "c"];
|
||||
// // files = await serializeFileList(event.dataTransfer.files);
|
||||
// }
|
||||
// return { mouse: get_mouse_data(event), files };
|
||||
return {
|
||||
mouse: {
|
||||
alt_key: event.altKey,
|
||||
ctrl_key: event.ctrlKey,
|
||||
meta_key: event.metaKey,
|
||||
shift_key: event.shiftKey,
|
||||
},
|
||||
files: [],
|
||||
};
|
||||
// let files: { [key: string]: Uint8Array } = {};
|
||||
// if (event.dataTransfer && event.dataTransfer.files) {
|
||||
// files["a"] = new Uint8Array(0);
|
||||
// // files = {
|
||||
// // entries: Array.from(event.dataTransfer.files).map((file) => {
|
||||
// // return {
|
||||
// // name: file.name,
|
||||
// // type: file.type,
|
||||
// // size: file.size,
|
||||
// // last_modified: file.lastModified,
|
||||
// // };
|
||||
// // }
|
||||
// // };
|
||||
// // files = await serializeFileList(event.dataTransfer.files);
|
||||
// }
|
||||
// extend({ files: files });
|
||||
}
|
||||
|
||||
if (event instanceof FocusEvent) {
|
||||
return {};
|
||||
return contents;
|
||||
}
|
||||
|
||||
function serializeInputEvent(event: InputEvent, target: EventTarget): SerializedEvent {
|
||||
let contents: SerializedEvent = {};
|
||||
|
||||
// Attempt to retrieve the values from the form
|
||||
if (target instanceof HTMLElement) {
|
||||
let values = retriveValues(event, target);
|
||||
contents.values = values.values;
|
||||
contents.valid = values.valid;
|
||||
}
|
||||
|
||||
if (event.target instanceof HTMLInputElement) {
|
||||
let target = event.target;
|
||||
let value = target.value ?? target.textContent ?? "";
|
||||
|
||||
if (target.type === "checkbox") {
|
||||
value = target.checked ? "true" : "false";
|
||||
} else if (target.type === "radio") {
|
||||
value = target.value;
|
||||
}
|
||||
|
||||
contents.value = value;
|
||||
}
|
||||
|
||||
if (event.target instanceof HTMLTextAreaElement) {
|
||||
contents.value = event.target.value;
|
||||
}
|
||||
|
||||
if (event.target instanceof HTMLSelectElement) {
|
||||
contents.value = retriveSelectValue(event.target).join(",");
|
||||
}
|
||||
|
||||
// Ensure the serializer isn't quirky
|
||||
if (contents.value === undefined) {
|
||||
contents.value = "";
|
||||
}
|
||||
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function serializeWheelEvent(event: WheelEvent): SerializedEvent {
|
||||
return {
|
||||
delta_x: event.deltaX,
|
||||
delta_y: event.deltaY,
|
||||
delta_z: event.deltaZ,
|
||||
delta_mode: event.deltaMode,
|
||||
};
|
||||
}
|
||||
|
||||
function serializeTouchEvent(event: TouchEvent): SerializedEvent {
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
function serializePointerEvent(event: PointerEvent): SerializedEvent {
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
function serializeMouseEvent(event: MouseEvent): SerializedEvent {
|
||||
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,
|
||||
offset_x: event.offsetX,
|
||||
offset_y: event.offsetY,
|
||||
page_x: event.pageX,
|
||||
page_y: event.pageY,
|
||||
screen_x: event.screenX,
|
||||
screen_y: event.screenY,
|
||||
shift_key: event.shiftKey,
|
||||
};
|
||||
}
|
||||
|
||||
function serializeKeyboardEvent(event: KeyboardEvent): SerializedEvent {
|
||||
return {
|
||||
char_code: event.charCode,
|
||||
is_composing: event.isComposing,
|
||||
key: event.key,
|
||||
alt_key: event.altKey,
|
||||
ctrl_key: event.ctrlKey,
|
||||
meta_key: event.metaKey,
|
||||
key_code: event.keyCode,
|
||||
shift_key: event.shiftKey,
|
||||
location: event.location,
|
||||
repeat: event.repeat,
|
||||
which: event.which,
|
||||
code: event.code,
|
||||
};
|
||||
}
|
||||
|
||||
function serializeAnimationEvent(event: AnimationEvent): SerializedEvent {
|
||||
return {
|
||||
animation_name: event.animationName,
|
||||
elapsed_time: event.elapsedTime,
|
||||
pseudo_element: event.pseudoElement,
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
function serializeDragEvent(event: DragEvent): SerializedEvent {
|
||||
// let files = [];
|
||||
// if (event.dataTransfer && event.dataTransfer.files) {
|
||||
// files = ["a", "b", "c"];
|
||||
// // files = await serializeFileList(event.dataTransfer.files);
|
||||
// }
|
||||
// return { mouse: get_mouse_data(event), files };
|
||||
return {
|
||||
mouse: {
|
||||
alt_key: event.altKey,
|
||||
ctrl_key: event.ctrlKey,
|
||||
meta_key: event.metaKey,
|
||||
shift_key: event.shiftKey,
|
||||
...serializeMouseEvent(event),
|
||||
},
|
||||
files: {
|
||||
files: {
|
||||
"a": [1, 2, 3],
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -17,37 +17,36 @@ export function setAttributeInner(node: HTMLElement, field: string, value: strin
|
||||
// A few attributes are need to be set with either boolean values or require some sort of translation
|
||||
switch (field) {
|
||||
case "value":
|
||||
if (value !== node.getAttribute("value")) {
|
||||
node.setAttribute(field, value);
|
||||
// @ts-ignore
|
||||
if (node.value !== value) {
|
||||
// @ts-ignore
|
||||
node.value = value;
|
||||
}
|
||||
break;
|
||||
|
||||
case "initial_value":
|
||||
node.setAttribute("defaultValue", value);
|
||||
// @ts-ignore
|
||||
node.defaultValue = value;
|
||||
break;
|
||||
|
||||
case "checked":
|
||||
if (node instanceof HTMLInputElement) {
|
||||
node.checked = truthy(value);
|
||||
}
|
||||
// @ts-ignore
|
||||
node.checked = truthy(value);
|
||||
break;
|
||||
|
||||
case "initial_checked":
|
||||
if (node instanceof HTMLInputElement) {
|
||||
node.defaultChecked = truthy(value);
|
||||
}
|
||||
// @ts-ignore
|
||||
node.defaultChecked = truthy(value);
|
||||
break;
|
||||
|
||||
case "selected":
|
||||
if (node instanceof HTMLOptionElement) {
|
||||
node.selected = truthy(value);
|
||||
}
|
||||
// @ts-ignore
|
||||
node.selected = truthy(value);
|
||||
break;
|
||||
|
||||
case "initial_selected":
|
||||
if (node instanceof HTMLOptionElement) {
|
||||
node.defaultSelected = truthy(value);
|
||||
}
|
||||
// @ts-ignore
|
||||
node.defaultSelected = truthy(value);
|
||||
break;
|
||||
|
||||
case "dangerous_inner_html":
|
||||
|
||||
@@ -6,6 +6,10 @@ use wasm_bindgen::prelude::wasm_bindgen;
|
||||
|
||||
use sledgehammer_bindgen::bindgen;
|
||||
|
||||
pub fn native_js() -> String {
|
||||
format!("{}\n{}", include_str!("./js/native.js"), GENERATED_JS,)
|
||||
}
|
||||
|
||||
pub const SLEDGEHAMMER_JS: &str = GENERATED_JS;
|
||||
|
||||
/// Extensions to the interpreter that are specific to the web platform.
|
||||
@@ -55,7 +59,7 @@ mod js {
|
||||
"{this.stack.pop();}"
|
||||
}
|
||||
fn replace_with(id: u32, n: u16) {
|
||||
"{const root = this.nodes[$id$]; this.els = this.stack.splice(this.stack.length-$n$); if (root.listening) { this.listeners.removeAllNonBubbling(root); } root.replaceWith(...this.els);}"
|
||||
"{const root = this.nodes[$id$]; this.els = this.stack.splice(this.stack.length-$n$); if (root.listening) { this.removeAllNonBubblingListeners(root); } root.replaceWith(...this.els);}"
|
||||
}
|
||||
fn insert_after(id: u32, n: u16) {
|
||||
"{this.nodes[$id$].after(...this.stack.splice(this.stack.length-$n$));}"
|
||||
@@ -64,7 +68,7 @@ mod js {
|
||||
"{this.nodes[$id$].before(...this.stack.splice(this.stack.length-$n$));}"
|
||||
}
|
||||
fn remove(id: u32) {
|
||||
"{let node = this.nodes[$id$]; if (node !== undefined) { if (node.listening) { this.listeners.removeAllNonBubbling(node); } node.remove(); }}"
|
||||
"{let node = this.nodes[$id$]; if (node !== undefined) { if (node.listening) { this.removeAllNonBubblingListeners(node); } node.remove(); }}"
|
||||
}
|
||||
fn create_raw_text(text: &str) {
|
||||
"{this.stack.push(document.createTextNode($text$));}"
|
||||
@@ -76,10 +80,10 @@ mod js {
|
||||
"{let node = document.createElement('pre'); node.hidden = true; this.stack.push(node); this.nodes[$id$] = node;}"
|
||||
}
|
||||
fn new_event_listener(event_name: &str<u8, evt>, id: u32, bubbles: u8) {
|
||||
r#"let node = this.nodes[id]; if(node.listening){node.listening += 1;}else{node.listening = 1;} node.setAttribute('data-dioxus-id', `\${id}`); this.listeners.create($event_name$, node, $bubbles$);"#
|
||||
r#"let node = this.nodes[id]; if(node.listening){node.listening += 1;}else{node.listening = 1;} node.setAttribute('data-dioxus-id', `\${id}`); this.createListener($event_name$, node, $bubbles$);"#
|
||||
}
|
||||
fn remove_event_listener(event_name: &str<u8, evt>, id: u32, bubbles: u8) {
|
||||
"{let node = this.nodes[$id$]; node.listening -= 1; node.removeAttribute('data-dioxus-id'); this.listeners.remove(node, $event_name$, $bubbles$);}"
|
||||
"{let node = this.nodes[$id$]; node.listening -= 1; node.removeAttribute('data-dioxus-id'); this.removeListener(node, $event_name$, $bubbles$);}"
|
||||
}
|
||||
fn set_text(id: u32, text: &str) {
|
||||
"{this.nodes[$id$].textContent = $text$;}"
|
||||
@@ -173,13 +177,13 @@ mod js {
|
||||
fn foreign_event_listener(event: &str<u8, evt>, id: u32, bubbles: u8) {
|
||||
r#"
|
||||
bubbles = bubbles == 1;
|
||||
let node = this.nodes[id];
|
||||
if(node.listening){
|
||||
node.listening += 1;
|
||||
let this_node = this.nodes[id];
|
||||
if(this_node.listening){
|
||||
this_node.listening += 1;
|
||||
} else {
|
||||
node.listening = 1;
|
||||
this_node.listening = 1;
|
||||
}
|
||||
node.setAttribute('data-dioxus-id', `\${id}`);
|
||||
this_node.setAttribute('data-dioxus-id', `\${id}`);
|
||||
const event_name = $event$;
|
||||
|
||||
// if this is a mounted listener, we send the event immediately
|
||||
@@ -193,7 +197,7 @@ mod js {
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this.listeners.create(event_name, node, bubbles, (event) => {
|
||||
this.createListener(event_name, this_node, bubbles, (event) => {
|
||||
this.handler(event, event_name, bubbles);
|
||||
});
|
||||
}"#
|
||||
|
||||
@@ -181,7 +181,8 @@ impl WriteMutations for MutationState {
|
||||
}
|
||||
|
||||
fn create_event_listener(&mut self, name: &'static str, id: dioxus_core::ElementId) {
|
||||
// note that we use the foreign event listener here
|
||||
// note that we use the foreign event listener here instead of the native one
|
||||
// the native method assumes we have direct access to the dom, which we don't.
|
||||
self.channel
|
||||
.foreign_event_listener(name, id.0 as u32, event_bubbles(name) as u8);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user