Merge branch 'master' into jk/form-ma

This commit is contained in:
Jonathan Kelley committed 2022-02-04 18:10:26 -05:00
commit 169f1dac11
31 files changed
+605 -1510

No files matched your search

+20
View File
@@ -0,0 +1,20 @@
FROM rust:1.58-buster
RUN apt update
RUN apt install -y \
libglib2.0-dev \
libgtk-3-dev \
libsoup2.4-dev \
libappindicator3-dev \
libwebkit2gtk-4.0-dev \
firefox-esr
# for kcov and Tarpaulin
#liblzma-dev binutils-dev libcurl4-openssl-dev libdw-dev libelf-dev
RUN cargo install cargo-make --debug
# for test coverage
#RUN cargo install cargo-tarpaulin
# clean up a bit
RUN cargo install cargo-cache && cargo cache -a
CMD ["exit"]
+8
View File
@@ -0,0 +1,8 @@
FROM dioxus-base-test-image
RUN mkdir run_test
COPY tmp /run_test
WORKDIR /run_test
RUN cargo make tests
CMD ["exit"]
+27
View File
@@ -0,0 +1,27 @@
# Why this?
This part is used to test whole package before pushing it
# How to use it?
Just run in the folder:
`bash run_local_tests.sh`. If nothing fails, then you can push your code to the repo.
or run:
`bash run_local_tests.sh --with-full-docker-cleanup`
for cleaning up images as well
# How is it composed of?
1. `Dockerfile_pre_test` will build the base image for the tests to be run into
2. `Dockerfile_test` will run the actual tests based on 1.
3. `run_local_tests.sh` to wrap this up
# Warning
The task requires some amount of CPU work and disk space (5GB per tests). Some clean up is included in the script.
# Requirements
* [docker](https://docs.docker.com/engine/install/)
* bash
* rsync
+37
View File
@@ -0,0 +1,37 @@
set -eux
echo "Test script started"
function run_script {
if [[ -d tmp ]]
then
rm -rf tmp
fi
mkdir tmp
# copy files first
rsync -a --progress ../ tmp --exclude target --exclude docker
# build base image
docker build -f Dockerfile_pre_test -t dioxus-base-test-image .
# run test
docker build -f Dockerfile_test -t dioxus-test-image .
# clean up
rm -rf tmp
if [ $# -ge 1 ]
then
echo "Got some parameter"
if [ $1 = "--with-full-docker-cleanup" ]
then
docker image rm dioxus-base-test-image
docker image rm dioxus-test-image
fi
fi
}
run_script || echo "Error occured.. cleaning a bit." && \
docker system prune -f;
docker system prune -f
echo "Script finished to execute"
+5 -2
View File
@@ -33,10 +33,13 @@ jobs:
- uses: Swatinem/rust-cache@v1 - uses: Swatinem/rust-cache@v1
- run: sudo apt-get update - run: sudo apt-get update
- run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev - run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev
- uses: davidB/rust-cargo-make@v1
- uses: browser-actions/setup-firefox@latest
- uses: jetli/wasm-pack-action@v0.3.0
- uses: actions-rs/cargo@v1 - uses: actions-rs/cargo@v1
with: with:
command: test command: make
args: --features "desktop, ssr, router" args: tests
fmt: fmt:
name: Rustfmt name: Rustfmt
+4
View File
@@ -4,4 +4,8 @@
"desktop", "desktop",
"router" "router"
], ],
"editor.formatOnSave": true,
"[toml]": {
"editor.formatOnSave": false
}
} }
+51
View File
@@ -0,0 +1,51 @@
[config]
default_to_workspace = false
min_version = "0.32.4"
[env]
CARGO_MAKE_CLIPPY_ARGS = "-- --deny=warnings"
CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
[config.modify_core_tasks]
namespace = "core"
private = true
[tasks.tests-setup]
private = true
script = [
"""
test_flags = array --headless --firefox
dioxus_test_features = set wasm_test
dioxus_test_flags = array_join ${test_flags} " "
echo "running tests with flags: ${dioxus_test_flags} and features: ${dioxus_test_features}"
set_env DIOXUS_TEST_FLAGS ${dioxus_test_flags}
set_env DIOXUS_TEST_FEATURES ${dioxus_test_features}
""",
]
script_runner = "@duckscript"
[tasks.tests]
category = "Testing"
dependencies = ["tests-setup"]
description = "Run all tests"
env = {CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*"]}
run_task = {name = ["test-flow", "test-with-browser"], fork = true}
[tasks.build]
command = "cargo"
args = ["build"]
[tasks.test-flow]
dependencies = ["test"]
private = true
[tasks.test]
dependencies = ["build"]
command = "cargo"
args = ["test", "--all-targets", "--workspace", "--exclude", "dioxus-router"]
private = true
[tasks.test-with-browser]
env = { CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS = ["**/packages/router"] }
private = true
workspace = true
-2
View File
@@ -26,9 +26,7 @@
<img src="https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg" <img src="https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg"
alt="CI status" /> alt="CI status" />
</a> </a>
</div>
<div align="center">
<!--Awesome --> <!--Awesome -->
<a href="https://github.com/dioxuslabs/awesome-dioxus"> <a href="https://github.com/dioxuslabs/awesome-dioxus">
<img src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg" alt="Awesome Page" /> <img src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg" alt="Awesome Page" />
+101
View File
@@ -0,0 +1,101 @@
// Thanks to @japsu and their project https://github.com/japsu/jatsi for the example!
use dioxus::{events::MouseEvent, prelude::*};
fn main() {
dioxus::desktop::launch(app);
}
fn app(cx: Scope) -> Element {
let (val, set_val) = use_state(&cx, || 5);
cx.render(rsx! {
div {
user_select: "none",
webkit_user_select: "none",
margin_left: "10%",
margin_right: "10%",
h1 { "Click die to generate a new value" }
div {
cursor: "pointer",
height: "80%",
width: "80%",
Die {
value: *val,
keep: true,
onclick: move |_| {
use rand::Rng;
let mut rng = rand::thread_rng();
set_val(rng.gen_range(1..6));
}
}
}
}
})
}
#[derive(Props)]
pub struct DieProps<'a> {
pub value: u64,
pub keep: bool,
pub onclick: EventHandler<'a, MouseEvent>,
}
const DOTS: [(i64, i64); 7] = [(-1, -1), (-1, -0), (-1, 1), (1, -1), (1, 0), (1, 1), (0, 0)];
const DOTS_FOR_VALUE: [[bool; 7]; 6] = [
[false, false, false, false, false, false, true],
[false, false, true, true, false, false, false],
[false, false, true, true, false, false, true],
[true, false, true, true, false, true, false],
[true, false, true, true, false, true, true],
[true, true, true, true, true, true, false],
];
const OFFSET: i64 = 600;
const DOT_RADIUS: &str = "200";
const HELD_COLOR: &str = "#aaa";
const UNHELD_COLOR: &str = "#ddd";
// A six-sided die (D6) with dots.
#[allow(non_snake_case)]
pub fn Die<'a>(cx: Scope<'a, DieProps<'a>>) -> Element {
let &DieProps { value, keep, .. } = cx.props;
let active_dots = &DOTS_FOR_VALUE[(value - 1) as usize];
let fill = if keep { HELD_COLOR } else { UNHELD_COLOR };
let dots = DOTS
.iter()
.zip(active_dots.iter())
.filter(|(_, &active)| active)
.map(|((x, y), _)| {
let dcx = x * OFFSET;
let dcy = y * OFFSET;
rsx!(circle {
cx: "{dcx}",
cy: "{dcy}",
r: "{DOT_RADIUS}",
fill: "#333"
})
});
rsx!(cx,
svg {
onclick: move |e| cx.props.onclick.call(e),
prevent_default: "onclick",
"dioxus-prevent-default": "onclick",
class: "die",
view_box: "-1000 -1000 2000 2000",
rect {
x: "-1000",
y: "-1000",
width: "2000",
height: "2000",
rx: "{DOT_RADIUS}",
fill: "{fill}",
}
dots
}
)
}
+6 -3
View File
@@ -120,15 +120,18 @@ pub fn todo_entry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
rsx!(cx, li { rsx!(cx, li {
class: "{completed} {editing}", class: "{completed} {editing}",
onclick: move |_| set_is_editing(true),
onfocusout: move |_| set_is_editing(false),
div { class: "view", div { class: "view",
input { class: "toggle", r#type: "checkbox", id: "cbg-{todo.id}", checked: "{todo.checked}", input { class: "toggle", r#type: "checkbox", id: "cbg-{todo.id}", checked: "{todo.checked}",
onchange: move |evt| { onchange: move |evt| {
cx.props.set_todos.make_mut()[&cx.props.id].checked = evt.value.parse().unwrap(); cx.props.set_todos.make_mut()[&cx.props.id].checked = evt.value.parse().unwrap();
} }
} }
label { r#for: "cbg-{todo.id}", pointer_events: "none", "{todo.contents}" } label {
r#for: "cbg-{todo.id}",
onclick: move |_| set_is_editing(true),
onfocusout: move |_| set_is_editing(false),
"{todo.contents}"
}
} }
is_editing.then(|| rsx!{ is_editing.then(|| rsx!{
input { input {
+127 -100
View File
@@ -119,11 +119,15 @@ impl<'b> DiffState<'b> {
pub fn diff_scope(&mut self, scopeid: ScopeId) { pub fn diff_scope(&mut self, scopeid: ScopeId) {
let (old, new) = (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid)); let (old, new) = (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid));
self.scope_stack.push(scopeid);
let scope = self.scopes.get_scope(scopeid).unwrap(); let scope = self.scopes.get_scope(scopeid).unwrap();
self.element_stack.push(scope.container);
self.diff_node(old, new); self.scope_stack.push(scopeid);
self.element_stack.push(scope.container);
{
self.diff_node(old, new);
}
self.element_stack.pop();
self.scope_stack.pop();
self.mutations.mark_dirty_scope(scopeid); self.mutations.mark_dirty_scope(scopeid);
} }
@@ -131,63 +135,26 @@ impl<'b> DiffState<'b> {
pub fn diff_node(&mut self, old_node: &'b VNode<'b>, new_node: &'b VNode<'b>) { pub fn diff_node(&mut self, old_node: &'b VNode<'b>, new_node: &'b VNode<'b>) {
use VNode::{Component, Element, Fragment, Placeholder, Text}; use VNode::{Component, Element, Fragment, Placeholder, Text};
match (old_node, new_node) { match (old_node, new_node) {
// Check the most common cases first
// these are *actual* elements, not wrappers around lists
(Text(old), Text(new)) => { (Text(old), Text(new)) => {
if std::ptr::eq(old, new) { self.diff_text_nodes(old, new, old_node, new_node);
return;
}
let root = old
.id
.get()
.expect("existing text nodes should have an ElementId");
if old.text != new.text {
self.mutations.set_text(new.text, root.as_u64());
}
self.scopes.update_node(new_node, root);
new.id.set(Some(root));
} }
(Placeholder(old), Placeholder(new)) => { (Placeholder(old), Placeholder(new)) => {
if std::ptr::eq(old, new) { self.diff_placeholder_nodes(old, new, old_node, new_node);
return;
}
let root = old
.id
.get()
.expect("existing placeholder nodes should have an ElementId");
self.scopes.update_node(new_node, root);
new.id.set(Some(root));
} }
(Element(old), Element(new)) => { (Element(old), Element(new)) => {
if std::ptr::eq(old, new) {
return;
}
self.diff_element_nodes(old, new, old_node, new_node); self.diff_element_nodes(old, new, old_node, new_node);
} }
// These two sets are pointers to nodes but are not actually nodes themselves
(Component(old), Component(new)) => { (Component(old), Component(new)) => {
if std::ptr::eq(old, new) {
return;
}
self.diff_component_nodes(old_node, new_node, *old, *new); self.diff_component_nodes(old_node, new_node, *old, *new);
} }
(Fragment(old), Fragment(new)) => { (Fragment(old), Fragment(new)) => {
if std::ptr::eq(old, new) {
return;
}
self.diff_fragment_nodes(old, new); self.diff_fragment_nodes(old, new);
} }
// Anything else is just a basic replace and create
( (
Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_), Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_),
Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_), Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_),
@@ -241,9 +208,7 @@ impl<'b> DiffState<'b> {
{ {
self.mutations.create_element(tag_name, *namespace, real_id); self.mutations.create_element(tag_name, *namespace, real_id);
let cur_scope_id = self let cur_scope_id = self.current_scope();
.current_scope()
.expect("diffing should always have a scope");
for listener in listeners.iter() { for listener in listeners.iter() {
listener.mounted_node.set(Some(real_id)); listener.mounted_node.set(Some(real_id));
@@ -268,28 +233,25 @@ impl<'b> DiffState<'b> {
} }
fn create_component_node(&mut self, vcomponent: &'b VComponent<'b>) -> usize { fn create_component_node(&mut self, vcomponent: &'b VComponent<'b>) -> usize {
let parent_idx = self.current_scope().unwrap(); let parent_idx = self.current_scope();
// ensure this scope doesn't already exist if we're trying to create it // the component might already exist - if it does, we need to reuse it
debug_assert!( // this makes figure out when to drop the component more complicated
vcomponent let new_idx = if let Some(idx) = vcomponent.scope.get() {
.scope assert!(self.scopes.get_scope(idx).is_some());
.get() idx
.and_then(|f| self.scopes.get_scope(f)) } else {
.is_none(), // Insert a new scope into our component list
"component scope already exists" let props: Box<dyn AnyProps + 'b> = vcomponent.props.borrow_mut().take().unwrap();
); let props: Box<dyn AnyProps + 'static> = unsafe { std::mem::transmute(props) };
self.scopes.new_with_key(
// Insert a new scope into our component list vcomponent.user_fc,
let props: Box<dyn AnyProps + 'b> = vcomponent.props.borrow_mut().take().unwrap(); props,
let props: Box<dyn AnyProps + 'static> = unsafe { std::mem::transmute(props) }; Some(parent_idx),
let new_idx = self.scopes.new_with_key( self.element_stack.last().copied().unwrap(),
vcomponent.user_fc, 0,
props, )
Some(parent_idx), };
self.element_stack.last().copied().unwrap(),
0,
);
// Actually initialize the caller's slot with the right address // Actually initialize the caller's slot with the right address
vcomponent.scope.set(Some(new_idx)); vcomponent.scope.set(Some(new_idx));
@@ -325,6 +287,53 @@ impl<'b> DiffState<'b> {
created created
} }
pub(crate) fn diff_text_nodes(
&mut self,
old: &'b VText<'b>,
new: &'b VText<'b>,
_old_node: &'b VNode<'b>,
new_node: &'b VNode<'b>,
) {
if std::ptr::eq(old, new) {
return;
}
// if the node is comming back not assigned, that means it was borrowed but removed
let root = match old.id.get() {
Some(id) => id,
None => self.scopes.reserve_node(new_node),
};
if old.text != new.text {
self.mutations.set_text(new.text, root.as_u64());
}
self.scopes.update_node(new_node, root);
new.id.set(Some(root));
}
pub(crate) fn diff_placeholder_nodes(
&mut self,
old: &'b VPlaceholder,
new: &'b VPlaceholder,
_old_node: &'b VNode<'b>,
new_node: &'b VNode<'b>,
) {
if std::ptr::eq(old, new) {
return;
}
// if the node is comming back not assigned, that means it was borrowed but removed
let root = match old.id.get() {
Some(id) => id,
None => self.scopes.reserve_node(new_node),
};
self.scopes.update_node(new_node, root);
new.id.set(Some(root));
}
fn diff_element_nodes( fn diff_element_nodes(
&mut self, &mut self,
old: &'b VElement<'b>, old: &'b VElement<'b>,
@@ -332,10 +341,15 @@ impl<'b> DiffState<'b> {
old_node: &'b VNode<'b>, old_node: &'b VNode<'b>,
new_node: &'b VNode<'b>, new_node: &'b VNode<'b>,
) { ) {
let root = old if std::ptr::eq(old, new) {
.id return;
.get() }
.expect("existing element nodes should have an ElementId");
// if the node is comming back not assigned, that means it was borrowed but removed
let root = match old.id.get() {
Some(id) => id,
None => self.scopes.reserve_node(new_node),
};
// If the element type is completely different, the element needs to be re-rendered completely // If the element type is completely different, the element needs to be re-rendered completely
// This is an optimization React makes due to how users structure their code // This is an optimization React makes due to how users structure their code
@@ -385,25 +399,25 @@ impl<'b> DiffState<'b> {
// We also need to make sure that all listeners are properly attached to the parent scope (fix_listener) // We also need to make sure that all listeners are properly attached to the parent scope (fix_listener)
// //
// TODO: take a more efficient path than this // TODO: take a more efficient path than this
if let Some(cur_scope_id) = self.current_scope() { let cur_scope_id = self.current_scope();
if old.listeners.len() == new.listeners.len() {
for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) { if old.listeners.len() == new.listeners.len() {
if old_l.event != new_l.event { for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
self.mutations if old_l.event != new_l.event {
.remove_event_listener(old_l.event, root.as_u64());
self.mutations.new_event_listener(new_l, cur_scope_id);
}
new_l.mounted_node.set(old_l.mounted_node.get());
}
} else {
for listener in old.listeners {
self.mutations self.mutations
.remove_event_listener(listener.event, root.as_u64()); .remove_event_listener(old_l.event, root.as_u64());
} self.mutations.new_event_listener(new_l, cur_scope_id);
for listener in new.listeners {
listener.mounted_node.set(Some(root));
self.mutations.new_event_listener(listener, cur_scope_id);
} }
new_l.mounted_node.set(old_l.mounted_node.get());
}
} else {
for listener in old.listeners {
self.mutations
.remove_event_listener(listener.event, root.as_u64());
}
for listener in new.listeners {
listener.mounted_node.set(Some(root));
self.mutations.new_event_listener(listener, cur_scope_id);
} }
} }
@@ -492,6 +506,10 @@ impl<'b> DiffState<'b> {
} }
fn diff_fragment_nodes(&mut self, old: &'b VFragment<'b>, new: &'b VFragment<'b>) { fn diff_fragment_nodes(&mut self, old: &'b VFragment<'b>, new: &'b VFragment<'b>) {
if std::ptr::eq(old, new) {
return;
}
// This is the case where options or direct vnodes might be used. // This is the case where options or direct vnodes might be used.
// In this case, it's faster to just skip ahead to their diff // In this case, it's faster to just skip ahead to their diff
if old.children.len() == 1 && new.children.len() == 1 { if old.children.len() == 1 && new.children.len() == 1 {
@@ -934,12 +952,18 @@ impl<'b> DiffState<'b> {
log::trace!("Replacing component x2 {:?}", old); log::trace!("Replacing component x2 {:?}", old);
// we can only remove components if they are actively being diffed let scope = self.scopes.get_scope(scope_id).unwrap();
if self.scope_stack.contains(&c.originator) { c.scope.set(None);
log::trace!("Removing component {:?}", old); let props = scope.props.take().unwrap();
c.props.borrow_mut().replace(props);
self.scopes.try_remove(scope_id).unwrap();
self.scopes.try_remove(scope_id).unwrap(); // // we can only remove components if they are actively being diffed
} // if self.scope_stack.contains(&c.originator) {
// log::trace!("Removing component {:?}", old);
// self.scopes.try_remove(scope_id).unwrap();
// }
} }
self.leave_scope(); self.leave_scope();
} }
@@ -953,6 +977,7 @@ impl<'b> DiffState<'b> {
// this check exists because our null node will be removed but does not have an ID // this check exists because our null node will be removed but does not have an ID
if let Some(id) = t.id.get() { if let Some(id) = t.id.get() {
self.scopes.collect_garbage(id); self.scopes.collect_garbage(id);
t.id.set(None);
if gen_muts { if gen_muts {
self.mutations.remove(id.as_u64()); self.mutations.remove(id.as_u64());
@@ -962,6 +987,7 @@ impl<'b> DiffState<'b> {
VNode::Placeholder(a) => { VNode::Placeholder(a) => {
let id = a.id.get().unwrap(); let id = a.id.get().unwrap();
self.scopes.collect_garbage(id); self.scopes.collect_garbage(id);
a.id.set(None);
if gen_muts { if gen_muts {
self.mutations.remove(id.as_u64()); self.mutations.remove(id.as_u64());
@@ -975,6 +1001,7 @@ impl<'b> DiffState<'b> {
} }
self.scopes.collect_garbage(id); self.scopes.collect_garbage(id);
e.id.set(None);
self.remove_nodes(e.children, false); self.remove_nodes(e.children, false);
} }
@@ -990,10 +1017,12 @@ impl<'b> DiffState<'b> {
let root = self.scopes.root_node(scope_id); let root = self.scopes.root_node(scope_id);
self.remove_nodes([root], gen_muts); self.remove_nodes([root], gen_muts);
// we can only remove this node if the originator is actively in our stackß let scope = self.scopes.get_scope(scope_id).unwrap();
if self.scope_stack.contains(&c.originator) { c.scope.set(None);
self.scopes.try_remove(scope_id).unwrap();
} let props = scope.props.take().unwrap();
c.props.borrow_mut().replace(props);
self.scopes.try_remove(scope_id).unwrap();
} }
self.leave_scope(); self.leave_scope();
} }
@@ -1026,8 +1055,8 @@ impl<'b> DiffState<'b> {
self.mutations.insert_before(first, created as u32); self.mutations.insert_before(first, created as u32);
} }
fn current_scope(&self) -> Option<ScopeId> { fn current_scope(&self) -> ScopeId {
self.scope_stack.last().copied() self.scope_stack.last().copied().expect("no current scope")
} }
fn enter_scope(&mut self, scope: ScopeId) { fn enter_scope(&mut self, scope: ScopeId) {
@@ -1097,9 +1126,7 @@ impl<'b> DiffState<'b> {
for child in el.children.iter() { for child in el.children.iter() {
num_on_stack += self.push_all_nodes(child); num_on_stack += self.push_all_nodes(child);
} }
self.mutations.push_root(el.id.get().unwrap()); self.mutations.push_root(el.id.get().unwrap());
num_on_stack + 1 num_on_stack + 1
} }
} }
-34
View File
@@ -1,34 +0,0 @@
use dioxus::prelude::*;
use dioxus_core as dioxus;
use dioxus_core_macro::*;
use dioxus_hooks::*;
use dioxus_html as dioxus_elements;
use std::time::Duration;
fn main() {
dioxus_desktop::launch(app);
}
fn app(cx: Scope) -> Element {
let (count, set_count) = use_state(&cx, || 0);
use_future(&cx, || {
let set_count = set_count.clone();
async move {
loop {
tokio::time::sleep(Duration::from_millis(1000)).await;
set_count.modify(|f| f + 1)
}
}
});
cx.render(rsx! {
div {
h1 { "High-Five counter: {count}" }
button {
onclick: move |_| set_count(0),
"Click me!"
}
}
})
}
+1 -6
View File
@@ -1047,7 +1047,7 @@ impl input {
/// - `text` /// - `text`
/// - `time` /// - `time`
/// - `url` /// - `url`
/// - `week` /// - `week`
pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
cx.attr("type", val, None, false) cx.attr("type", val, None, false)
} }
@@ -1100,11 +1100,6 @@ impl label {
cx.attr("for", val, None, false) cx.attr("for", val, None, false)
} }
} }
impl a {
pub fn prevent_default<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
cx.attr("dioxus-prevent-default", val, None, false)
}
}
builder_constructors! { builder_constructors! {
// SVG components // SVG components
+16
View File
@@ -49,6 +49,10 @@ macro_rules! aria_trait_methods {
} }
pub trait GlobalAttributes { pub trait GlobalAttributes {
/// Prevent the default action for this element.
///
/// For more information, see the MDN docs:
/// <https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault>
fn prevent_default<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { fn prevent_default<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
cx.attr("dioxus-prevent-default", val, None, false) cx.attr("dioxus-prevent-default", val, None, false)
} }
@@ -603,6 +607,11 @@ pub trait GlobalAttributes {
/// Specifies the speed curve of the transition effect. /// Specifies the speed curve of the transition effect.
transition_timing_function: "transition-timing-function", transition_timing_function: "transition-timing-function",
/// The user-select CSS property controls whether the user can select text.
/// This doesn't have any effect on content loaded as part of a browser's user interface (its chrome), except in textboxes.
user_select: "user-select",
webkit_user_select: "-webkit-user-select",
/// Sets the vertical positioning of an element relative to the current text baseline. /// Sets the vertical positioning of an element relative to the current text baseline.
vertical_align: "vertical-align", vertical_align: "vertical-align",
@@ -685,6 +694,13 @@ pub trait GlobalAttributes {
} }
pub trait SvgAttributes { pub trait SvgAttributes {
/// Prevent the default action for this element.
///
/// For more information, see the MDN docs:
/// <https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault>
fn prevent_default<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
cx.attr("dioxus-prevent-default", val, None, false)
}
aria_trait_methods! { aria_trait_methods! {
accent_height: "accent-height", accent_height: "accent-height",
accumulate: "accumulate", accumulate: "accumulate",
+8
View File
@@ -14,3 +14,11 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
wasm-bindgen = { version = "0.2.79", optional = true }
js-sys = { version = "0.3.56", optional = true }
web-sys = { version = "0.3.56", optional = true, features = ["Element", "Node"] }
[features]
default = []
web = ["wasm-bindgen", "js-sys", "web-sys"]
-16
View File
@@ -1,16 +0,0 @@
use std::process::Command;
fn main() {
println!("cargo:rerun-if-changed=interpreter.ts");
match Command::new("tsc").spawn() {
Ok(_) => println!("Was spawned :)"),
Err(e) => {
if let std::io::ErrorKind::NotFound = e.kind() {
println!("`tsc` was not found! Not going to generate new interpreter")
} else {
println!("Some strange error occurred :(");
}
}
}
}
-519
View File
@@ -1,519 +0,0 @@
export function main() {
let root = window.document.getElementById("main");
if (root != null) {
window.interpreter = new Interpreter(root);
window.rpc.call("initialize");
}
}
export class Interpreter {
root;
stack;
listeners;
handlers;
lastNodeWasText;
nodes;
constructor(root) {
this.root = root;
this.stack = [root];
this.listeners = {};
this.handlers = {};
this.lastNodeWasText = false;
this.nodes = [root];
}
top() {
return this.stack[this.stack.length - 1];
}
pop() {
return this.stack.pop();
}
PushRoot(root) {
const node = this.nodes[root];
this.stack.push(node);
}
AppendChildren(many) {
let root = this.stack[this.stack.length - (1 + many)];
let to_add = this.stack.splice(this.stack.length - many);
for (let i = 0; i < many; i++) {
root.appendChild(to_add[i]);
}
}
ReplaceWith(root_id, m) {
let root = this.nodes[root_id];
let els = this.stack.splice(this.stack.length - m);
root.replaceWith(...els);
}
InsertAfter(root, n) {
let old = this.nodes[root];
let new_nodes = this.stack.splice(this.stack.length - n);
old.after(...new_nodes);
}
InsertBefore(root, n) {
let old = this.nodes[root];
let new_nodes = this.stack.splice(this.stack.length - n);
old.before(...new_nodes);
}
Remove(root) {
let node = this.nodes[root];
if (node !== undefined) {
node.remove();
}
}
CreateTextNode(text, root) {
// todo: make it so the types are okay
const node = document.createTextNode(text);
this.nodes[root] = node;
this.stack.push(node);
}
CreateElement(tag, root) {
const el = document.createElement(tag);
// el.setAttribute("data-dioxus-id", `${root}`);
this.nodes[root] = el;
this.stack.push(el);
}
CreateElementNs(tag, root, ns) {
let el = document.createElementNS(ns, tag);
this.stack.push(el);
this.nodes[root] = el;
}
CreatePlaceholder(root) {
let el = document.createElement("pre");
el.hidden = true;
this.stack.push(el);
this.nodes[root] = el;
}
NewEventListener(event_name, root, handler) {
const element = this.nodes[root];
element.setAttribute("data-dioxus-id", `${root}`);
if (this.listeners[event_name] === undefined) {
this.listeners[event_name] = 0;
this.handlers[event_name] = handler;
this.root.addEventListener(event_name, handler);
}
else {
this.listeners[event_name]++;
}
}
RemoveEventListener(root, event_name) {
const element = this.nodes[root];
element.removeAttribute(`data-dioxus-id`);
this.listeners[event_name]--;
if (this.listeners[event_name] === 0) {
this.root.removeEventListener(event_name, this.handlers[event_name]);
delete this.listeners[event_name];
delete this.handlers[event_name];
}
}
SetText(root, text) {
this.nodes[root].textContent = text;
}
SetAttribute(root, field, value, ns) {
const name = field;
const node = this.nodes[root];
if (ns == "style") {
// @ts-ignore
node.style[name] = value;
}
else if (ns != null || ns != undefined) {
node.setAttributeNS(ns, name, value);
}
else {
switch (name) {
case "value":
if (value != node.value) {
node.value = value;
}
break;
case "checked":
node.checked = value === "true";
break;
case "selected":
node.selected = value === "true";
break;
case "dangerous_inner_html":
node.innerHTML = value;
break;
default:
// https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
if (value == "false" && bool_attrs.hasOwnProperty(name)) {
node.removeAttribute(name);
}
else {
node.setAttribute(name, value);
}
}
}
}
RemoveAttribute(root, name) {
const node = this.nodes[root];
node.removeAttribute(name);
if (name === "value") {
node.value = "";
}
if (name === "checked") {
node.checked = false;
}
if (name === "selected") {
node.selected = false;
}
}
handleEdits(edits) {
this.stack.push(this.root);
for (let edit of edits) {
this.handleEdit(edit);
}
}
handleEdit(edit) {
switch (edit.type) {
case "PushRoot":
this.PushRoot(edit.root);
break;
case "AppendChildren":
this.AppendChildren(edit.many);
break;
case "ReplaceWith":
this.ReplaceWith(edit.root, edit.m);
break;
case "InsertAfter":
this.InsertAfter(edit.root, edit.n);
break;
case "InsertBefore":
this.InsertBefore(edit.root, edit.n);
break;
case "Remove":
this.Remove(edit.root);
break;
case "CreateTextNode":
this.CreateTextNode(edit.text, edit.root);
break;
case "CreateElement":
this.CreateElement(edit.tag, edit.root);
break;
case "CreateElementNs":
this.CreateElementNs(edit.tag, edit.root, edit.ns);
break;
case "CreatePlaceholder":
this.CreatePlaceholder(edit.root);
break;
case "RemoveEventListener":
this.RemoveEventListener(edit.root, edit.event_name);
break;
case "NewEventListener":
// this handler is only provided on desktop implementations since this
// method is not used by the web implementation
let handler = (event) => {
let target = event.target;
if (target != null) {
let realId = target.getAttribute(`data-dioxus-id`);
let shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`);
if (event.type == "click") {
event.preventDefault();
if (shouldPreventDefault !== `onclick`) {
if (target.tagName == "A") {
const href = target.getAttribute("href");
if (href !== "" && href !== null && href !== undefined) {
window.rpc.call("browser_open", { href });
}
}
}
}
// walk the tree to find the real element
while (realId == null) {
// we've reached the root we don't want to send an event
if (target.parentElement === null) {
return;
}
target = target.parentElement;
realId = target.getAttribute(`data-dioxus-id`);
}
shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`);
let contents = serialize_event(event);
if (shouldPreventDefault === `on${event.type}`) {
event.preventDefault();
}
if (event.type == "submit") {
event.preventDefault();
}
if (target.tagName == "FORM") {
let formTarget = target;
for (let x = 0; x < formTarget.elements.length; x++) {
let element = formTarget.elements[x];
let name = element.getAttribute("name");
if (name != null) {
if (element.getAttribute("type") == "checkbox") {
// @ts-ignore
contents.values[name] = element.checked ? "true" : "false";
}
else {
// @ts-ignore
contents.values[name] = element.value ?? element.textContent;
}
}
}
}
if (realId == null) {
return;
}
window.rpc.call("user_event", {
event: edit.event_name,
mounted_dom_id: parseInt(realId),
contents: contents,
});
}
};
this.NewEventListener(edit.event_name, edit.root, handler);
break;
case "SetText":
this.SetText(edit.root, edit.text);
break;
case "SetAttribute":
this.SetAttribute(edit.root, edit.field, edit.value, edit.ns);
break;
case "RemoveAttribute":
this.RemoveAttribute(edit.root, edit.name);
break;
}
}
}
export function serialize_event(event) {
switch (event.type) {
case "copy":
case "cut":
case "past": {
return {};
}
case "compositionend":
case "compositionstart":
case "compositionupdate": {
let { data } = event;
return {
data,
};
}
case "keydown":
case "keypress":
case "keyup": {
let { charCode, key, altKey, ctrlKey, metaKey, keyCode, shiftKey, location, repeat, which, } = event;
return {
char_code: charCode,
key: key,
alt_key: altKey,
ctrl_key: ctrlKey,
meta_key: metaKey,
key_code: keyCode,
shift_key: shiftKey,
location: location,
repeat: repeat,
which: which,
locale: "locale",
};
}
case "focus":
case "blur": {
return {};
}
case "change": {
let target = event.target;
let value;
if (target.type === "checkbox" || target.type === "radio") {
value = target.checked ? "true" : "false";
}
else {
value = target.value ?? target.textContent;
}
return {
value: value,
};
}
case "input":
case "invalid":
case "reset":
case "submit": {
let target = event.target;
let value = target.value ?? target.textContent;
if (target.type == "checkbox") {
value = target.checked ? "true" : "false";
}
return {
value: value,
values: {}
};
}
case "click":
case "contextmenu":
case "doubleclick":
case "drag":
case "dragend":
case "dragenter":
case "dragexit":
case "dragleave":
case "dragover":
case "dragstart":
case "drop":
case "mousedown":
case "mouseenter":
case "mouseleave":
case "mousemove":
case "mouseout":
case "mouseover":
case "mouseup": {
const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, } = event;
return {
alt_key: altKey,
button: button,
buttons: buttons,
client_x: clientX,
client_y: clientY,
ctrl_key: ctrlKey,
meta_key: metaKey,
page_x: pageX,
page_y: pageY,
screen_x: screenX,
screen_y: screenY,
shift_key: shiftKey,
};
}
case "pointerdown":
case "pointermove":
case "pointerup":
case "pointercancel":
case "gotpointercapture":
case "lostpointercapture":
case "pointerenter":
case "pointerleave":
case "pointerover":
case "pointerout": {
const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, pointerId, width, height, pressure, tangentialPressure, tiltX, tiltY, twist, pointerType, isPrimary, } = event;
return {
alt_key: altKey,
button: button,
buttons: buttons,
client_x: clientX,
client_y: clientY,
ctrl_key: ctrlKey,
meta_key: metaKey,
page_x: pageX,
page_y: pageY,
screen_x: screenX,
screen_y: screenY,
shift_key: shiftKey,
pointer_id: pointerId,
width: width,
height: height,
pressure: pressure,
tangential_pressure: tangentialPressure,
tilt_x: tiltX,
tilt_y: tiltY,
twist: twist,
pointer_type: pointerType,
is_primary: isPrimary,
};
}
case "select": {
return {};
}
case "touchcancel":
case "touchend":
case "touchmove":
case "touchstart": {
const { altKey, ctrlKey, metaKey, shiftKey, } = event;
return {
// changed_touches: event.changedTouches,
// target_touches: event.targetTouches,
// touches: event.touches,
alt_key: altKey,
ctrl_key: ctrlKey,
meta_key: metaKey,
shift_key: shiftKey,
};
}
case "scroll": {
return {};
}
case "wheel": {
const { deltaX, deltaY, deltaZ, deltaMode, } = event;
return {
delta_x: deltaX,
delta_y: deltaY,
delta_z: deltaZ,
delta_mode: deltaMode,
};
}
case "animationstart":
case "animationend":
case "animationiteration": {
const { animationName, elapsedTime, pseudoElement, } = event;
return {
animation_name: animationName,
elapsed_time: elapsedTime,
pseudo_element: pseudoElement,
};
}
case "transitionend": {
const { propertyName, elapsedTime, pseudoElement, } = event;
return {
property_name: propertyName,
elapsed_time: elapsedTime,
pseudo_element: pseudoElement,
};
}
case "abort":
case "canplay":
case "canplaythrough":
case "durationchange":
case "emptied":
case "encrypted":
case "ended":
case "error":
case "loadeddata":
case "loadedmetadata":
case "loadstart":
case "pause":
case "play":
case "playing":
case "progress":
case "ratechange":
case "seeked":
case "seeking":
case "stalled":
case "suspend":
case "timeupdate":
case "volumechange":
case "waiting": {
return {};
}
case "toggle": {
return {};
}
default: {
return {};
}
}
}
const bool_attrs = {
allowfullscreen: true,
allowpaymentrequest: true,
async: true,
autofocus: true,
autoplay: true,
checked: true,
controls: true,
default: true,
defer: true,
disabled: true,
formnovalidate: true,
hidden: true,
ismap: true,
itemscope: true,
loop: true,
multiple: true,
muted: true,
nomodule: true,
novalidate: true,
open: true,
playsinline: true,
readonly: true,
required: true,
reversed: true,
selected: true,
truespeed: true,
};
@@ -1,18 +1,9 @@
use std::{fs::File, io::Write}; use js_sys::Function;
fn main() {
let src = format!(
r###"use js_sys::Function;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use web_sys::{{Element, Node}}; use web_sys::{Element, Node};
/* #[wasm_bindgen(module = "/src/interpreter.js")]
This is an autogenerated file from build.rs. extern "C" {
Do not edit this file directly.
*/
#[wasm_bindgen(inline_js = r##"{}"##)]
extern "C" {{
pub type Interpreter; pub type Interpreter;
#[wasm_bindgen(constructor)] #[wasm_bindgen(constructor)]
@@ -65,12 +56,4 @@ extern "C" {{
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn RemoveAttribute(this: &Interpreter, root: u64, field: &str); pub fn RemoveAttribute(this: &Interpreter, root: u64, field: &str);
}}
"###,
dioxus_interpreter_js::INTERPRTER_JS
);
// write the bindings to a local file
let mut file = File::create("src/bindings.rs").unwrap();
file.write_all(src.as_bytes()).unwrap();
} }
@@ -5,25 +5,14 @@ export function main() {
window.rpc.call("initialize"); window.rpc.call("initialize");
} }
} }
declare global {
interface Window {
interpreter: Interpreter;
rpc: { call: (method: string, args?: any) => void };
}
}
export class Interpreter { export class Interpreter {
root: Element; root;
stack: Element[]; stack;
listeners: { [key: string]: number }; listeners;
handlers: { [key: string]: (evt: Event) => void }; handlers;
lastNodeWasText: boolean; lastNodeWasText;
nodes: Element[]; nodes;
constructor(root) {
constructor(root: Element) {
this.root = root; this.root = root;
this.stack = [root]; this.stack = [root];
this.listeners = {}; this.listeners = {};
@@ -31,88 +20,70 @@ export class Interpreter {
this.lastNodeWasText = false; this.lastNodeWasText = false;
this.nodes = [root]; this.nodes = [root];
} }
top() { top() {
return this.stack[this.stack.length - 1]; return this.stack[this.stack.length - 1];
} }
pop() { pop() {
return this.stack.pop(); return this.stack.pop();
} }
PushRoot(root) {
PushRoot(root: number) {
const node = this.nodes[root]; const node = this.nodes[root];
this.stack.push(node); this.stack.push(node);
} }
AppendChildren(many) {
AppendChildren(many: number) {
let root = this.stack[this.stack.length - (1 + many)]; let root = this.stack[this.stack.length - (1 + many)];
let to_add = this.stack.splice(this.stack.length - many); let to_add = this.stack.splice(this.stack.length - many);
for (let i = 0; i < many; i++) { for (let i = 0; i < many; i++) {
root.appendChild(to_add[i]); root.appendChild(to_add[i]);
} }
} }
ReplaceWith(root_id, m) {
ReplaceWith(root_id: number, m: number) { let root = this.nodes[root_id];
let root = this.nodes[root_id] as Element;
let els = this.stack.splice(this.stack.length - m); let els = this.stack.splice(this.stack.length - m);
root.replaceWith(...els); root.replaceWith(...els);
} }
InsertAfter(root, n) {
InsertAfter(root: number, n: number) { let old = this.nodes[root];
let old = this.nodes[root] as Element;
let new_nodes = this.stack.splice(this.stack.length - n); let new_nodes = this.stack.splice(this.stack.length - n);
old.after(...new_nodes); old.after(...new_nodes);
} }
InsertBefore(root, n) {
InsertBefore(root: number, n: number) { let old = this.nodes[root];
let old = this.nodes[root] as Element;
let new_nodes = this.stack.splice(this.stack.length - n); let new_nodes = this.stack.splice(this.stack.length - n);
old.before(...new_nodes); old.before(...new_nodes);
} }
Remove(root) {
Remove(root: number) { let node = this.nodes[root];
let node = this.nodes[root] as Element;
if (node !== undefined) { if (node !== undefined) {
node.remove(); node.remove();
} }
} }
CreateTextNode(text, root) {
CreateTextNode(text: string, root: number) {
// todo: make it so the types are okay // todo: make it so the types are okay
const node = document.createTextNode(text) as any as Element; const node = document.createTextNode(text);
this.nodes[root] = node; this.nodes[root] = node;
this.stack.push(node); this.stack.push(node);
} }
CreateElement(tag, root) {
CreateElement(tag: string, root: number) {
const el = document.createElement(tag); const el = document.createElement(tag);
// el.setAttribute("data-dioxus-id", `${root}`); // el.setAttribute("data-dioxus-id", `${root}`);
this.nodes[root] = el; this.nodes[root] = el;
this.stack.push(el); this.stack.push(el);
} }
CreateElementNs(tag, root, ns) {
CreateElementNs(tag: string, root: number, ns: string) {
let el = document.createElementNS(ns, tag); let el = document.createElementNS(ns, tag);
this.stack.push(el); this.stack.push(el);
this.nodes[root] = el; this.nodes[root] = el;
} }
CreatePlaceholder(root) {
CreatePlaceholder(root: number) {
let el = document.createElement("pre"); let el = document.createElement("pre");
el.hidden = true; el.hidden = true;
this.stack.push(el); this.stack.push(el);
this.nodes[root] = el; this.nodes[root] = el;
} }
NewEventListener(event_name, root, handler) {
NewEventListener(event_name: string, root: number, handler: (evt: Event) => void) {
const element = this.nodes[root]; const element = this.nodes[root];
element.setAttribute("data-dioxus-id", `${root}`); element.setAttribute("data-dioxus-id", `${root}`);
if (this.listeners[event_name] === undefined) { if (this.listeners[event_name] === undefined) {
this.listeners[event_name] = 0; this.listeners[event_name] = 0;
this.handlers[event_name] = handler; this.handlers[event_name] = handler;
@@ -121,48 +92,39 @@ export class Interpreter {
this.listeners[event_name]++; this.listeners[event_name]++;
} }
} }
RemoveEventListener(root, event_name) {
RemoveEventListener(root: number, event_name: string) {
const element = this.nodes[root]; const element = this.nodes[root];
element.removeAttribute(`data-dioxus-id`); element.removeAttribute(`data-dioxus-id`);
this.listeners[event_name]--; this.listeners[event_name]--;
if (this.listeners[event_name] === 0) { if (this.listeners[event_name] === 0) {
this.root.removeEventListener(event_name, this.handlers[event_name]); this.root.removeEventListener(event_name, this.handlers[event_name]);
delete this.listeners[event_name]; delete this.listeners[event_name];
delete this.handlers[event_name]; delete this.handlers[event_name];
} }
} }
SetText(root, text) {
SetText(root: number, text: string) {
this.nodes[root].textContent = text; this.nodes[root].textContent = text;
} }
SetAttribute(root, field, value, ns) {
SetAttribute(root: number, field: string, value: string, ns: string | undefined) {
const name = field; const name = field;
const node = this.nodes[root]; const node = this.nodes[root];
if (ns == "style") { if (ns == "style") {
// @ts-ignore // @ts-ignore
(node as HTMLElement).style[name] = value; node.style[name] = value;
} else if (ns != null || ns != undefined) { } else if (ns != null || ns != undefined) {
node.setAttributeNS(ns, name, value); node.setAttributeNS(ns, name, value);
} else { } else {
switch (name) { switch (name) {
case "value": case "value":
if (value != (node as HTMLInputElement).value) { if (value != node.value) {
(node as HTMLInputElement).value = value; node.value = value;
} }
break; break;
case "checked": case "checked":
(node as HTMLInputElement).checked = value === "true"; node.checked = value === "true";
break; break;
case "selected": case "selected":
(node as HTMLOptionElement).selected = value === "true"; node.selected = value === "true";
break; break;
case "dangerous_inner_html": case "dangerous_inner_html":
node.innerHTML = value; node.innerHTML = value;
@@ -177,33 +139,26 @@ export class Interpreter {
} }
} }
} }
RemoveAttribute(root: number, name: string) { RemoveAttribute(root, name) {
const node = this.nodes[root]; const node = this.nodes[root];
node.removeAttribute(name); node.removeAttribute(name);
if (name === "value") { if (name === "value") {
(node as HTMLInputElement).value = ""; node.value = "";
} }
if (name === "checked") { if (name === "checked") {
(node as HTMLInputElement).checked = false; node.checked = false;
} }
if (name === "selected") { if (name === "selected") {
(node as HTMLOptionElement).selected = false; node.selected = false;
} }
} }
handleEdits(edits) {
handleEdits(edits: DomEdit[]) {
this.stack.push(this.root); this.stack.push(this.root);
for (let edit of edits) { for (let edit of edits) {
this.handleEdit(edit); this.handleEdit(edit);
} }
} }
handleEdit(edit) {
handleEdit(edit: DomEdit) {
switch (edit.type) { switch (edit.type) {
case "PushRoot": case "PushRoot":
this.PushRoot(edit.root); this.PushRoot(edit.root);
@@ -239,29 +194,27 @@ export class Interpreter {
this.RemoveEventListener(edit.root, edit.event_name); this.RemoveEventListener(edit.root, edit.event_name);
break; break;
case "NewEventListener": case "NewEventListener":
// this handler is only provided on desktop implementations since this // this handler is only provided on desktop implementations since this
// method is not used by the web implementation // method is not used by the web implementation
let handler = (event: Event) => { let handler = (event) => {
let target = event.target as Element | null; let target = event.target;
if (target != null) { if (target != null) {
let realId = target.getAttribute(`data-dioxus-id`); let realId = target.getAttribute(`data-dioxus-id`);
let shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); let shouldPreventDefault = target.getAttribute(
`dioxus-prevent-default`
);
if (event.type == "click") { if (event.type == "click") {
event.preventDefault(); // todo call prevent default if it's the right type of event
if (shouldPreventDefault !== `onclick`) { if (shouldPreventDefault !== `onclick`) {
if (target.tagName == "A") { if (target.tagName == "A") {
const href = target.getAttribute("href") const href = target.getAttribute("href");
if (href !== "" && href !== null && href !== undefined) { if (href !== "" && href !== null && href !== undefined) {
window.rpc.call("browser_open", { href }); window.rpc.call("browser_open", { href });
} }
} }
} }
} }
// walk the tree to find the real element // walk the tree to find the real element
while (realId == null) { while (realId == null) {
// we've reached the root we don't want to send an event // we've reached the root we don't want to send an event
@@ -273,23 +226,20 @@ export class Interpreter {
realId = target.getAttribute(`data-dioxus-id`); realId = target.getAttribute(`data-dioxus-id`);
} }
shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); shouldPreventDefault = target.getAttribute(
`dioxus-prevent-default`
);
let contents = serialize_event(event); let contents = serialize_event(event);
if (shouldPreventDefault === `on${event.type}`) { if (shouldPreventDefault === `on${event.type}`) {
event.preventDefault(); // event.preventDefault();
} }
if (event.type == "submit") { if (event.type == "submit") {
event.preventDefault(); // event.preventDefault();
} }
if (target.tagName == "FORM") { if (target.tagName == "FORM") {
let formTarget = target as HTMLFormElement; for (let x = 0; x < target.elements.length; x++) {
for (let x = 0; x < formTarget.elements.length; x++) { let element = target.elements[x];
let element = formTarget.elements[x];
let name = element.getAttribute("name"); let name = element.getAttribute("name");
if (name != null) { if (name != null) {
if (element.getAttribute("type") == "checkbox") { if (element.getAttribute("type") == "checkbox") {
@@ -297,7 +247,8 @@ export class Interpreter {
contents.values[name] = element.checked ? "true" : "false"; contents.values[name] = element.checked ? "true" : "false";
} else { } else {
// @ts-ignore // @ts-ignore
contents.values[name] = element.value ?? element.textContent; contents.values[name] =
element.value ?? element.textContent;
} }
} }
} }
@@ -306,9 +257,8 @@ export class Interpreter {
if (realId == null) { if (realId == null) {
return; return;
} }
window.rpc.call("user_event", { window.rpc.call("user_event", {
event: (edit as NewEventListener).event_name, event: edit.event_name,
mounted_dom_id: parseInt(realId), mounted_dom_id: parseInt(realId),
contents: contents, contents: contents,
}); });
@@ -329,25 +279,21 @@ export class Interpreter {
} }
} }
export function serialize_event(event) {
export function serialize_event(event: Event) {
switch (event.type) { switch (event.type) {
case "copy": case "copy":
case "cut": case "cut":
case "past": { case "past": {
return {}; return {};
} }
case "compositionend": case "compositionend":
case "compositionstart": case "compositionstart":
case "compositionupdate": { case "compositionupdate": {
let { data } = (event as CompositionEvent); let { data } = event;
return { return {
data, data,
}; };
} }
case "keydown": case "keydown":
case "keypress": case "keypress":
case "keyup": { case "keyup": {
@@ -362,8 +308,7 @@ export function serialize_event(event: Event) {
location, location,
repeat, repeat,
which, which,
} = (event as KeyboardEvent); } = event;
return { return {
char_code: charCode, char_code: charCode,
key: key, key: key,
@@ -378,45 +323,36 @@ export function serialize_event(event: Event) {
locale: "locale", locale: "locale",
}; };
} }
case "focus": case "focus":
case "blur": { case "blur": {
return {}; return {};
} }
case "change": { case "change": {
let target = event.target as HTMLInputElement; let target = event.target;
let value; let value;
if (target.type === "checkbox" || target.type === "radio") { if (target.type === "checkbox" || target.type === "radio") {
value = target.checked ? "true" : "false"; value = target.checked ? "true" : "false";
} else { } else {
value = target.value ?? target.textContent; value = target.value ?? target.textContent;
} }
return { return {
value: value, value: value,
}; };
} }
case "input": case "input":
case "invalid": case "invalid":
case "reset": case "reset":
case "submit": { case "submit": {
let target = event.target;
let target = event.target as HTMLFormElement;
let value = target.value ?? target.textContent; let value = target.value ?? target.textContent;
if (target.type == "checkbox") { if (target.type == "checkbox") {
value = target.checked ? "true" : "false"; value = target.checked ? "true" : "false";
} }
return { return {
value: value, value: value,
values: {} values: {},
}; };
} }
case "click": case "click":
case "contextmenu": case "contextmenu":
case "doubleclick": case "doubleclick":
@@ -448,8 +384,7 @@ export function serialize_event(event: Event) {
screenX, screenX,
screenY, screenY,
shiftKey, shiftKey,
} = event as MouseEvent; } = event;
return { return {
alt_key: altKey, alt_key: altKey,
button: button, button: button,
@@ -465,7 +400,6 @@ export function serialize_event(event: Event) {
shift_key: shiftKey, shift_key: shiftKey,
}; };
} }
case "pointerdown": case "pointerdown":
case "pointermove": case "pointermove":
case "pointerup": case "pointerup":
@@ -499,7 +433,7 @@ export function serialize_event(event: Event) {
twist, twist,
pointerType, pointerType,
isPrimary, isPrimary,
} = event as PointerEvent; } = event;
return { return {
alt_key: altKey, alt_key: altKey,
button: button, button: button,
@@ -525,21 +459,14 @@ export function serialize_event(event: Event) {
is_primary: isPrimary, is_primary: isPrimary,
}; };
} }
case "select": { case "select": {
return {}; return {};
} }
case "touchcancel": case "touchcancel":
case "touchend": case "touchend":
case "touchmove": case "touchmove":
case "touchstart": { case "touchstart": {
const { const { altKey, ctrlKey, metaKey, shiftKey } = event;
altKey,
ctrlKey,
metaKey,
shiftKey,
} = event as TouchEvent;
return { return {
// changed_touches: event.changedTouches, // changed_touches: event.changedTouches,
// target_touches: event.targetTouches, // target_touches: event.targetTouches,
@@ -550,18 +477,11 @@ export function serialize_event(event: Event) {
shift_key: shiftKey, shift_key: shiftKey,
}; };
} }
case "scroll": { case "scroll": {
return {}; return {};
} }
case "wheel": { case "wheel": {
const { const { deltaX, deltaY, deltaZ, deltaMode } = event;
deltaX,
deltaY,
deltaZ,
deltaMode,
} = event as WheelEvent;
return { return {
delta_x: deltaX, delta_x: deltaX,
delta_y: deltaY, delta_y: deltaY,
@@ -569,35 +489,24 @@ export function serialize_event(event: Event) {
delta_mode: deltaMode, delta_mode: deltaMode,
}; };
} }
case "animationstart": case "animationstart":
case "animationend": case "animationend":
case "animationiteration": { case "animationiteration": {
const { const { animationName, elapsedTime, pseudoElement } = event;
animationName,
elapsedTime,
pseudoElement,
} = event as AnimationEvent;
return { return {
animation_name: animationName, animation_name: animationName,
elapsed_time: elapsedTime, elapsed_time: elapsedTime,
pseudo_element: pseudoElement, pseudo_element: pseudoElement,
}; };
} }
case "transitionend": { case "transitionend": {
const { const { propertyName, elapsedTime, pseudoElement } = event;
propertyName,
elapsedTime,
pseudoElement,
} = event as TransitionEvent;
return { return {
property_name: propertyName, property_name: propertyName,
elapsed_time: elapsedTime, elapsed_time: elapsedTime,
pseudo_element: pseudoElement, pseudo_element: pseudoElement,
}; };
} }
case "abort": case "abort":
case "canplay": case "canplay":
case "canplaythrough": case "canplaythrough":
@@ -623,17 +532,14 @@ export function serialize_event(event: Event) {
case "waiting": { case "waiting": {
return {}; return {};
} }
case "toggle": { case "toggle": {
return {}; return {};
} }
default: { default: {
return {}; return {};
} }
} }
} }
const bool_attrs = { const bool_attrs = {
allowfullscreen: true, allowfullscreen: true,
allowpaymentrequest: true, allowpaymentrequest: true,
@@ -662,36 +568,3 @@ const bool_attrs = {
selected: true, selected: true,
truespeed: true, truespeed: true,
}; };
type PushRoot = { type: "PushRoot", root: number };
type AppendChildren = { type: "AppendChildren", many: number };
type ReplaceWith = { type: "ReplaceWith", root: number, m: number };
type InsertAfter = { type: "InsertAfter", root: number, n: number };
type InsertBefore = { type: "InsertBefore", root: number, n: number };
type Remove = { type: "Remove", root: number };
type CreateTextNode = { type: "CreateTextNode", text: string, root: number };
type CreateElement = { type: "CreateElement", tag: string, root: number };
type CreateElementNs = { type: "CreateElementNs", tag: string, root: number, ns: string };
type CreatePlaceholder = { type: "CreatePlaceholder", root: number };
type NewEventListener = { type: "NewEventListener", root: number, event_name: string, scope: number };
type RemoveEventListener = { type: "RemoveEventListener", event_name: string, scope: number, root: number };
type SetText = { type: "SetText", root: number, text: string };
type SetAttribute = { type: "SetAttribute", root: number, field: string, value: string, ns: string | undefined };
type RemoveAttribute = { type: "RemoveAttribute", root: number, name: string };
type DomEdit =
PushRoot |
AppendChildren |
ReplaceWith |
InsertAfter |
InsertBefore |
Remove |
CreateTextNode |
CreateElement |
CreateElementNs |
CreatePlaceholder |
NewEventListener |
RemoveEventListener |
SetText |
SetAttribute |
RemoveAttribute;
+7 -1
View File
@@ -1 +1,7 @@
pub static INTERPRTER_JS: &str = include_str!("../gen/interpreter.js"); pub static INTERPRTER_JS: &str = include_str!("./interpreter.js");
#[cfg(feature = "web")]
mod bindings;
#[cfg(feature = "web")]
pub use bindings::Interpreter;
-15
View File
@@ -1,15 +0,0 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ES2015",
"lib": [
"es2015",
"es5",
"es6",
"dom"
],
"rootDir": "src",
"strict": true,
"outDir": "gen",
}
}
+5
View File
@@ -13,3 +13,8 @@ license = "MIT/Apache-2.0"
[dependencies] [dependencies]
dioxus-desktop = { path = "../desktop", version = "^0.1.6" } dioxus-desktop = { path = "../desktop", version = "^0.1.6" }
[lib]
doctest = false
# tests suspended until package ready
test = false
+7
View File
@@ -0,0 +1,7 @@
[tasks.test]
command = "cargo"
args = [
"test",
"--no-run",
]
+9
View File
@@ -43,9 +43,18 @@ web = ["web-sys", "gloo", "js-sys", "wasm-bindgen"]
desktop = [] desktop = []
mobile = [] mobile = []
derive = [] derive = []
wasm_test = []
[dev-dependencies] [dev-dependencies]
console_error_panic_hook = "0.1.7" console_error_panic_hook = "0.1.7"
dioxus-web = { path = "../web" } dioxus-web = { path = "../web" }
log = "0.4.14" log = "0.4.14"
wasm-logger = "0.2.0" wasm-logger = "0.2.0"
wasm-bindgen-test = "0.3"
gloo-utils = "0.1.2"
[dev-dependencies.web-sys]
version = "0.3"
features = [
"Document",
]
+10
View File
@@ -0,0 +1,10 @@
[tasks.test-with-browser]
extend = "core::wasm-pack-base"
command = "wasm-pack"
args = [
"test",
"@@split(DIOXUS_TEST_FLAGS, )",
"--",
"--features",
"${DIOXUS_TEST_FEATURES}",
]
+63
View File
@@ -0,0 +1,63 @@
#![cfg(target_arch = "wasm32")]
use dioxus_core::prelude::*;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
use dioxus_router::*;
use gloo_utils::document;
use serde::{Deserialize, Serialize};
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test]
fn simple_test() {
fn main() {
console_error_panic_hook::set_once();
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
dioxus_web::launch(APP);
}
static APP: Component = |cx| {
cx.render(rsx! {
Router {
onchange: move |route| log::info!("route changed to {}", route),
Route { to: "/", Home {} }
Route { to: "blog"
Route { to: "/", BlogList {} }
Route { to: ":id", BlogPost {} }
}
}
})
};
fn Home(cx: Scope) -> Element {
cx.render(rsx! {
div {
h1 { "Home" }
}
})
}
fn BlogList(cx: Scope) -> Element {
cx.render(rsx! {
div {
}
})
}
fn BlogPost(cx: Scope) -> Element {
let id = use_route(&cx).segment::<usize>("id")?;
cx.render(rsx! {
div {
}
})
}
main();
let element = gloo_utils::document();
}
+11
View File
@@ -0,0 +1,11 @@
{
"moz:firefoxOptions": {
"binary": "/usr/bin/firefox",
"prefs": {
"media.navigator.streams.fake": true,
"media.navigator.permission.disabled": true
},
"args": []
}
}
+11 -13
View File
@@ -13,27 +13,25 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
[dependencies] [dependencies]
dioxus-core = { path = "../core", version = "^0.1.9" } dioxus-core = { path = "../core", version = "^0.1.9" }
dioxus-html = { path = "../html", version = "^0.1.6" } dioxus-html = { path = "../html", version = "^0.1.6" }
js-sys = "0.3" js-sys = "0.3.56"
wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] } wasm-bindgen = { version = "0.2.79", features = ["enable-interning"] }
lazy_static = "1.4.0" lazy_static = "1.4.0"
wasm-bindgen-futures = "0.4.20" wasm-bindgen-futures = "0.4.29"
log = { version = "0.4.14", features = ["release_max_level_off"] } log = { version = "0.4.14", features = ["release_max_level_off"] }
fxhash = "0.2.1" fxhash = "0.2.1"
wasm-logger = "0.2.0" wasm-logger = "0.2.0"
console_error_panic_hook = { version = "0.1.7", optional = true } console_error_panic_hook = { version = "0.1.7", optional = true }
wasm-bindgen-test = "0.3.21" wasm-bindgen-test = "0.3.29"
once_cell = "1.8" once_cell = "1.9.0"
async-channel = "1.6.1" async-channel = "1.6.1"
anyhow = "1.0" anyhow = "1.0.53"
gloo-timers = { version = "0.2.1", features = ["futures"] } gloo-timers = { version = "0.2.3", features = ["futures"] }
futures-util = "0.3.15" futures-util = "0.3.19"
smallstr = "0.2.0" smallstr = "0.2.0"
dioxus-interpreter-js = { path = "../interpreter", version = "^0.0.0", features = ["web"] }
[build-dependencies]
dioxus-interpreter-js = { path = "../interpreter", version = "^0.0.0" }
[dependencies.web-sys] [dependencies.web-sys]
version = "0.3.51" version = "0.3.56"
features = [ features = [
"Comment", "Comment",
"Attr", "Attr",
@@ -79,5 +77,5 @@ panic_hook = ["console_error_panic_hook"]
[dev-dependencies] [dev-dependencies]
dioxus-core-macro = { path = "../core-macro" } dioxus-core-macro = { path = "../core-macro" }
wasm-bindgen-test = "0.3.28" wasm-bindgen-test = "0.3.29"
dioxus-ssr = { path = "../ssr" } dioxus-ssr = { path = "../ssr" }
-583
View File
@@ -1,583 +0,0 @@
use js_sys::Function;
use wasm_bindgen::prelude::*;
use web_sys::{Element, Node};
/*
This is an autogenerated file from build.rs.
Do not edit this file directly.
*/
#[wasm_bindgen(inline_js = r##"export function main() {
let root = window.document.getElementById("main");
if (root != null) {
window.interpreter = new Interpreter(root);
window.rpc.call("initialize");
}
}
export class Interpreter {
root;
stack;
listeners;
handlers;
lastNodeWasText;
nodes;
constructor(root) {
this.root = root;
this.stack = [root];
this.listeners = {};
this.handlers = {};
this.lastNodeWasText = false;
this.nodes = [root];
}
top() {
return this.stack[this.stack.length - 1];
}
pop() {
return this.stack.pop();
}
PushRoot(root) {
const node = this.nodes[root];
this.stack.push(node);
}
AppendChildren(many) {
let root = this.stack[this.stack.length - (1 + many)];
let to_add = this.stack.splice(this.stack.length - many);
for (let i = 0; i < many; i++) {
root.appendChild(to_add[i]);
}
}
ReplaceWith(root_id, m) {
let root = this.nodes[root_id];
let els = this.stack.splice(this.stack.length - m);
root.replaceWith(...els);
}
InsertAfter(root, n) {
let old = this.nodes[root];
let new_nodes = this.stack.splice(this.stack.length - n);
old.after(...new_nodes);
}
InsertBefore(root, n) {
let old = this.nodes[root];
let new_nodes = this.stack.splice(this.stack.length - n);
old.before(...new_nodes);
}
Remove(root) {
let node = this.nodes[root];
if (node !== undefined) {
node.remove();
}
}
CreateTextNode(text, root) {
// todo: make it so the types are okay
const node = document.createTextNode(text);
this.nodes[root] = node;
this.stack.push(node);
}
CreateElement(tag, root) {
const el = document.createElement(tag);
// el.setAttribute("data-dioxus-id", `${root}`);
this.nodes[root] = el;
this.stack.push(el);
}
CreateElementNs(tag, root, ns) {
let el = document.createElementNS(ns, tag);
this.stack.push(el);
this.nodes[root] = el;
}
CreatePlaceholder(root) {
let el = document.createElement("pre");
el.hidden = true;
this.stack.push(el);
this.nodes[root] = el;
}
NewEventListener(event_name, root, handler) {
const element = this.nodes[root];
element.setAttribute("data-dioxus-id", `${root}`);
if (this.listeners[event_name] === undefined) {
this.listeners[event_name] = 0;
this.handlers[event_name] = handler;
this.root.addEventListener(event_name, handler);
}
else {
this.listeners[event_name]++;
}
}
RemoveEventListener(root, event_name) {
const element = this.nodes[root];
element.removeAttribute(`data-dioxus-id`);
this.listeners[event_name]--;
if (this.listeners[event_name] === 0) {
this.root.removeEventListener(event_name, this.handlers[event_name]);
delete this.listeners[event_name];
delete this.handlers[event_name];
}
}
SetText(root, text) {
this.nodes[root].textContent = text;
}
SetAttribute(root, field, value, ns) {
const name = field;
const node = this.nodes[root];
if (ns == "style") {
// @ts-ignore
node.style[name] = value;
}
else if (ns != null || ns != undefined) {
node.setAttributeNS(ns, name, value);
}
else {
switch (name) {
case "value":
if (value != node.value) {
node.value = value;
}
break;
case "checked":
node.checked = value === "true";
break;
case "selected":
node.selected = value === "true";
break;
case "dangerous_inner_html":
node.innerHTML = value;
break;
default:
// https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
if (value == "false" && bool_attrs.hasOwnProperty(name)) {
node.removeAttribute(name);
}
else {
node.setAttribute(name, value);
}
}
}
}
RemoveAttribute(root, name) {
const node = this.nodes[root];
node.removeAttribute(name);
if (name === "value") {
node.value = "";
}
if (name === "checked") {
node.checked = false;
}
if (name === "selected") {
node.selected = false;
}
}
handleEdits(edits) {
this.stack.push(this.root);
for (let edit of edits) {
this.handleEdit(edit);
}
}
handleEdit(edit) {
switch (edit.type) {
case "PushRoot":
this.PushRoot(edit.root);
break;
case "AppendChildren":
this.AppendChildren(edit.many);
break;
case "ReplaceWith":
this.ReplaceWith(edit.root, edit.m);
break;
case "InsertAfter":
this.InsertAfter(edit.root, edit.n);
break;
case "InsertBefore":
this.InsertBefore(edit.root, edit.n);
break;
case "Remove":
this.Remove(edit.root);
break;
case "CreateTextNode":
this.CreateTextNode(edit.text, edit.root);
break;
case "CreateElement":
this.CreateElement(edit.tag, edit.root);
break;
case "CreateElementNs":
this.CreateElementNs(edit.tag, edit.root, edit.ns);
break;
case "CreatePlaceholder":
this.CreatePlaceholder(edit.root);
break;
case "RemoveEventListener":
this.RemoveEventListener(edit.root, edit.event_name);
break;
case "NewEventListener":
// this handler is only provided on desktop implementations since this
// method is not used by the web implementation
let handler = (event) => {
let target = event.target;
if (target != null) {
let realId = target.getAttribute(`data-dioxus-id`);
let shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`);
if (event.type == "click") {
event.preventDefault();
if (shouldPreventDefault !== `onclick`) {
if (target.tagName == "A") {
const href = target.getAttribute("href");
if (href !== "" && href !== null && href !== undefined) {
window.rpc.call("browser_open", { href });
}
}
}
}
// walk the tree to find the real element
while (realId == null) {
// we've reached the root we don't want to send an event
if (target.parentElement === null) {
return;
}
target = target.parentElement;
realId = target.getAttribute(`data-dioxus-id`);
}
shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`);
let contents = serialize_event(event);
if (shouldPreventDefault === `on${event.type}`) {
event.preventDefault();
}
if (event.type == "submit") {
event.preventDefault();
}
if (target.tagName == "FORM") {
let formTarget = target;
for (let x = 0; x < formTarget.elements.length; x++) {
let element = formTarget.elements[x];
let name = element.getAttribute("name");
if (name != null) {
if (element.getAttribute("type") == "checkbox") {
// @ts-ignore
contents.values[name] = element.checked ? "true" : "false";
}
else {
// @ts-ignore
contents.values[name] = element.value ?? element.textContent;
}
}
}
}
if (realId == null) {
return;
}
window.rpc.call("user_event", {
event: edit.event_name,
mounted_dom_id: parseInt(realId),
contents: contents,
});
}
};
this.NewEventListener(edit.event_name, edit.root, handler);
break;
case "SetText":
this.SetText(edit.root, edit.text);
break;
case "SetAttribute":
this.SetAttribute(edit.root, edit.field, edit.value, edit.ns);
break;
case "RemoveAttribute":
this.RemoveAttribute(edit.root, edit.name);
break;
}
}
}
export function serialize_event(event) {
switch (event.type) {
case "copy":
case "cut":
case "past": {
return {};
}
case "compositionend":
case "compositionstart":
case "compositionupdate": {
let { data } = event;
return {
data,
};
}
case "keydown":
case "keypress":
case "keyup": {
let { charCode, key, altKey, ctrlKey, metaKey, keyCode, shiftKey, location, repeat, which, } = event;
return {
char_code: charCode,
key: key,
alt_key: altKey,
ctrl_key: ctrlKey,
meta_key: metaKey,
key_code: keyCode,
shift_key: shiftKey,
location: location,
repeat: repeat,
which: which,
locale: "locale",
};
}
case "focus":
case "blur": {
return {};
}
case "change": {
let target = event.target;
let value;
if (target.type === "checkbox" || target.type === "radio") {
value = target.checked ? "true" : "false";
}
else {
value = target.value ?? target.textContent;
}
return {
value: value,
};
}
case "input":
case "invalid":
case "reset":
case "submit": {
let target = event.target;
let value = target.value ?? target.textContent;
if (target.type == "checkbox") {
value = target.checked ? "true" : "false";
}
return {
value: value,
values: {}
};
}
case "click":
case "contextmenu":
case "doubleclick":
case "drag":
case "dragend":
case "dragenter":
case "dragexit":
case "dragleave":
case "dragover":
case "dragstart":
case "drop":
case "mousedown":
case "mouseenter":
case "mouseleave":
case "mousemove":
case "mouseout":
case "mouseover":
case "mouseup": {
const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, } = event;
return {
alt_key: altKey,
button: button,
buttons: buttons,
client_x: clientX,
client_y: clientY,
ctrl_key: ctrlKey,
meta_key: metaKey,
page_x: pageX,
page_y: pageY,
screen_x: screenX,
screen_y: screenY,
shift_key: shiftKey,
};
}
case "pointerdown":
case "pointermove":
case "pointerup":
case "pointercancel":
case "gotpointercapture":
case "lostpointercapture":
case "pointerenter":
case "pointerleave":
case "pointerover":
case "pointerout": {
const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, pointerId, width, height, pressure, tangentialPressure, tiltX, tiltY, twist, pointerType, isPrimary, } = event;
return {
alt_key: altKey,
button: button,
buttons: buttons,
client_x: clientX,
client_y: clientY,
ctrl_key: ctrlKey,
meta_key: metaKey,
page_x: pageX,
page_y: pageY,
screen_x: screenX,
screen_y: screenY,
shift_key: shiftKey,
pointer_id: pointerId,
width: width,
height: height,
pressure: pressure,
tangential_pressure: tangentialPressure,
tilt_x: tiltX,
tilt_y: tiltY,
twist: twist,
pointer_type: pointerType,
is_primary: isPrimary,
};
}
case "select": {
return {};
}
case "touchcancel":
case "touchend":
case "touchmove":
case "touchstart": {
const { altKey, ctrlKey, metaKey, shiftKey, } = event;
return {
// changed_touches: event.changedTouches,
// target_touches: event.targetTouches,
// touches: event.touches,
alt_key: altKey,
ctrl_key: ctrlKey,
meta_key: metaKey,
shift_key: shiftKey,
};
}
case "scroll": {
return {};
}
case "wheel": {
const { deltaX, deltaY, deltaZ, deltaMode, } = event;
return {
delta_x: deltaX,
delta_y: deltaY,
delta_z: deltaZ,
delta_mode: deltaMode,
};
}
case "animationstart":
case "animationend":
case "animationiteration": {
const { animationName, elapsedTime, pseudoElement, } = event;
return {
animation_name: animationName,
elapsed_time: elapsedTime,
pseudo_element: pseudoElement,
};
}
case "transitionend": {
const { propertyName, elapsedTime, pseudoElement, } = event;
return {
property_name: propertyName,
elapsed_time: elapsedTime,
pseudo_element: pseudoElement,
};
}
case "abort":
case "canplay":
case "canplaythrough":
case "durationchange":
case "emptied":
case "encrypted":
case "ended":
case "error":
case "loadeddata":
case "loadedmetadata":
case "loadstart":
case "pause":
case "play":
case "playing":
case "progress":
case "ratechange":
case "seeked":
case "seeking":
case "stalled":
case "suspend":
case "timeupdate":
case "volumechange":
case "waiting": {
return {};
}
case "toggle": {
return {};
}
default: {
return {};
}
}
}
const bool_attrs = {
allowfullscreen: true,
allowpaymentrequest: true,
async: true,
autofocus: true,
autoplay: true,
checked: true,
controls: true,
default: true,
defer: true,
disabled: true,
formnovalidate: true,
hidden: true,
ismap: true,
itemscope: true,
loop: true,
multiple: true,
muted: true,
nomodule: true,
novalidate: true,
open: true,
playsinline: true,
readonly: true,
required: true,
reversed: true,
selected: true,
truespeed: true,
};
"##)]
extern "C" {
pub type Interpreter;
#[wasm_bindgen(constructor)]
pub fn new(arg: Element) -> Interpreter;
#[wasm_bindgen(method)]
pub fn set_node(this: &Interpreter, id: usize, node: Node);
#[wasm_bindgen(method)]
pub fn PushRoot(this: &Interpreter, root: u64);
#[wasm_bindgen(method)]
pub fn AppendChildren(this: &Interpreter, many: u32);
#[wasm_bindgen(method)]
pub fn ReplaceWith(this: &Interpreter, root: u64, m: u32);
#[wasm_bindgen(method)]
pub fn InsertAfter(this: &Interpreter, root: u64, n: u32);
#[wasm_bindgen(method)]
pub fn InsertBefore(this: &Interpreter, root: u64, n: u32);
#[wasm_bindgen(method)]
pub fn Remove(this: &Interpreter, root: u64);
#[wasm_bindgen(method)]
pub fn CreateTextNode(this: &Interpreter, text: &str, root: u64);
#[wasm_bindgen(method)]
pub fn CreateElement(this: &Interpreter, tag: &str, root: u64);
#[wasm_bindgen(method)]
pub fn CreateElementNs(this: &Interpreter, tag: &str, root: u64, ns: &str);
#[wasm_bindgen(method)]
pub fn CreatePlaceholder(this: &Interpreter, root: u64);
#[wasm_bindgen(method)]
pub fn NewEventListener(this: &Interpreter, name: &str, root: u64, handler: &Function);
#[wasm_bindgen(method)]
pub fn RemoveEventListener(this: &Interpreter, root: u64, name: &str);
#[wasm_bindgen(method)]
pub fn SetText(this: &Interpreter, root: u64, text: &str);
#[wasm_bindgen(method)]
pub fn SetAttribute(this: &Interpreter, root: u64, field: &str, value: &str, ns: Option<&str>);
#[wasm_bindgen(method)]
pub fn RemoveAttribute(this: &Interpreter, root: u64, field: &str);
}
+1 -1
View File
@@ -7,8 +7,8 @@
//! - tests to ensure dyn_into works for various event types. //! - tests to ensure dyn_into works for various event types.
//! - Partial delegation?> //! - Partial delegation?>
use crate::bindings::Interpreter;
use dioxus_core::{DomEdit, ElementId, SchedulerMsg, UserEvent}; use dioxus_core::{DomEdit, ElementId, SchedulerMsg, UserEvent};
use dioxus_interpreter_js::Interpreter;
use js_sys::Function; use js_sys::Function;
use std::{any::Any, rc::Rc, sync::Arc}; use std::{any::Any, rc::Rc, sync::Arc};
use wasm_bindgen::{closure::Closure, JsCast}; use wasm_bindgen::{closure::Closure, JsCast};
-1
View File
@@ -61,7 +61,6 @@ pub use dioxus_core as dioxus;
use dioxus_core::prelude::Component; use dioxus_core::prelude::Component;
use futures_util::FutureExt; use futures_util::FutureExt;
pub(crate) mod bindings;
mod cache; mod cache;
mod cfg; mod cfg;
mod dom; mod dom;