浏览代码

Remove native and web features from dioxus-html (#3006)

* remove native and web features from dioxus-html

* Fix downcasting web events

* Fix desktop with tokio disabled
Evan Almloff 8 月之前
父节点
当前提交
cda8bb24f1
共有 54 个文件被更改,包括 1767 次插入1511 次删除
  1. 3 3
      Cargo.lock
  2. 4 1
      packages/desktop/Cargo.toml
  3. 9 0
      packages/desktop/build.rs
  4. 2 2
      packages/desktop/src/app.rs
  5. 3 0
      packages/desktop/src/document.rs
  6. 81 1
      packages/desktop/src/file_upload.rs
  7. 1 0
      packages/desktop/src/js/hash.txt
  8. 0 0
      packages/desktop/src/js/native_eval.js
  9. 11 9
      packages/desktop/src/launch.rs
  10. 1 1
      packages/desktop/src/protocol.rs
  11. 5 1
      packages/desktop/src/ts/native_eval.ts
  12. 9 6
      packages/desktop/src/webview.rs
  13. 18 0
      packages/desktop/tsconfig.json
  14. 2 41
      packages/html/Cargo.toml
  15. 0 2
      packages/html/build.rs
  16. 0 50
      packages/html/src/document/bindings.rs
  17. 0 2
      packages/html/src/document/mod.rs
  18. 7 1
      packages/html/src/events/mounted.rs
  19. 1 1
      packages/html/src/js/hash.txt
  20. 0 6
      packages/html/src/lib.rs
  21. 0 3
      packages/html/src/native_bind/mod.rs
  22. 0 56
      packages/html/src/native_bind/native_file_engine.rs
  23. 0 38
      packages/html/src/ts/eval.ts
  24. 0 675
      packages/html/src/web_sys_bind/events.rs
  25. 0 5
      packages/html/src/web_sys_bind/mod.rs
  26. 42 9
      packages/web/Cargo.toml
  27. 7 0
      packages/web/build.rs
  28. 45 3
      packages/web/src/document.rs
  29. 0 591
      packages/web/src/event.rs
  30. 32 0
      packages/web/src/events/animation.rs
  31. 26 0
      packages/web/src/events/clipboard.rs
  32. 24 0
      packages/web/src/events/composition.rs
  33. 108 0
      packages/web/src/events/drag.rs
  34. 24 0
      packages/web/src/events/file.rs
  35. 19 0
      packages/web/src/events/focus.rs
  36. 160 0
      packages/web/src/events/form.rs
  37. 67 0
      packages/web/src/events/keyboard.rs
  38. 37 0
      packages/web/src/events/load.rs
  39. 19 0
      packages/web/src/events/media.rs
  40. 278 0
      packages/web/src/events/mod.rs
  41. 126 0
      packages/web/src/events/mounted.rs
  42. 87 0
      packages/web/src/events/mouse.rs
  43. 119 0
      packages/web/src/events/pointer.rs
  44. 57 0
      packages/web/src/events/resize.rs
  45. 19 0
      packages/web/src/events/selection.rs
  46. 19 0
      packages/web/src/events/toggle.rs
  47. 111 0
      packages/web/src/events/touch.rs
  48. 22 0
      packages/web/src/events/transition.rs
  49. 110 0
      packages/web/src/events/wheel.rs
  50. 1 1
      packages/web/src/file_engine.rs
  51. 1 1
      packages/web/src/js/eval.js
  52. 1 0
      packages/web/src/js/hash.txt
  53. 6 2
      packages/web/src/lib.rs
  54. 43 0
      packages/web/src/ts/eval.ts

+ 3 - 3
Cargo.lock

@@ -2592,6 +2592,7 @@ dependencies = [
  "global-hotkey",
  "http-range",
  "infer 0.11.0",
+ "lazy-js-bundle",
  "muda",
  "objc",
  "objc_id",
@@ -2763,9 +2764,6 @@ dependencies = [
  "serde_repr",
  "tokio",
  "tracing",
- "wasm-bindgen",
- "wasm-bindgen-futures",
- "web-sys",
 ]
 
 [[package]]
@@ -3069,6 +3067,7 @@ dependencies = [
 name = "dioxus-web"
 version = "0.6.0-alpha.2"
 dependencies = [
+ "async-trait",
  "ciborium",
  "console_error_panic_hook",
  "dioxus",
@@ -3086,6 +3085,7 @@ dependencies = [
  "gloo-dialogs",
  "gloo-timers",
  "js-sys",
+ "lazy-js-bundle",
  "rustc-hash 1.1.0",
  "serde",
  "serde-wasm-bindgen",

+ 4 - 1
packages/desktop/Cargo.toml

@@ -13,8 +13,8 @@ keywords = ["dom", "ui", "gui", "react"]
 dioxus-core = { workspace = true, features = ["serialize"] }
 dioxus-html = { workspace = true, features = [
     "serialize",
-    "native-bind",
     "mounted",
+    "file_engine",
     "document",
 ] }
 dioxus-signals = { workspace = true, optional = true }
@@ -88,6 +88,9 @@ cocoa = "0.25"
 core-foundation = "0.9.3"
 objc = "0.2.7"
 
+[build-dependencies]
+lazy-js-bundle = { workspace = true }
+
 [features]
 default = ["tokio_runtime", "wry/objc-exception", "devtools"]
 tokio_runtime = ["dep:tokio"]

+ 9 - 0
packages/desktop/build.rs

@@ -36,8 +36,17 @@ fn check_gnu() {
     }
 }
 
+fn compile_ts() {
+    // If any TS files change, re-run the build script
+    lazy_js_bundle::LazyTypeScriptBindings::new()
+        .with_watching("./src/ts")
+        .with_binding("./src/ts/native_eval.ts", "./src/js/native_eval.js")
+        .run();
+}
+
 fn main() {
     check_gnu();
+    compile_ts();
 }
 
 const EXAMPLES_TOML: &str = r#"

+ 2 - 2
packages/desktop/src/app.rs

@@ -1,14 +1,14 @@
 use crate::{
     config::{Config, WindowCloseBehaviour},
     event_handlers::WindowEventHandlers,
-    file_upload::{DesktopFileUploadForm, FileDialogRequest},
+    file_upload::{DesktopFileUploadForm, FileDialogRequest, NativeFileEngine},
     ipc::{IpcMessage, UserWindowEvent},
     query::QueryResult,
     shortcut::ShortcutRegistry,
     webview::WebviewInstance,
 };
 use dioxus_core::{ElementId, VirtualDom};
-use dioxus_html::{native_bind::NativeFileEngine, PlatformEventData};
+use dioxus_html::PlatformEventData;
 use std::{
     any::Any,
     cell::{Cell, RefCell},

+ 3 - 0
packages/desktop/src/document.rs

@@ -3,6 +3,9 @@ use generational_box::{AnyStorage, GenerationalBox, UnsyncStorage};
 
 use crate::{query::Query, DesktopContext};
 
+/// Code for the Dioxus channel used to communicate between the dioxus and javascript code
+pub const NATIVE_EVAL_JS: &str = include_str!("./js/native_eval.js");
+
 /// Represents the desktop-target's provider of evaluators.
 pub struct DesktopDocument {
     pub(crate) desktop_ctx: DesktopContext,

+ 81 - 1
packages/desktop/src/file_upload.rs

@@ -1,9 +1,13 @@
 #![allow(unused)]
 
+use std::any::Any;
+
+#[cfg(feature = "tokio_runtime")]
+use tokio::{fs::File, io::AsyncReadExt};
+
 use dioxus_html::{
     geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint},
     input_data::{MouseButton, MouseButtonSet},
-    native_bind::NativeFileEngine,
     point_interaction::{
         InteractionElementOffset, InteractionLocation, ModifiersInteraction, PointerInteraction,
     },
@@ -237,3 +241,79 @@ impl PointerInteraction for DesktopFileDragEvent {
         self.mouse.trigger_button()
     }
 }
+
+pub struct NativeFileEngine {
+    files: Vec<PathBuf>,
+}
+
+impl NativeFileEngine {
+    pub fn new(files: Vec<PathBuf>) -> Self {
+        Self { files }
+    }
+}
+
+#[async_trait::async_trait(?Send)]
+impl FileEngine for NativeFileEngine {
+    fn files(&self) -> Vec<String> {
+        self.files
+            .iter()
+            .filter_map(|f| Some(f.to_str()?.to_string()))
+            .collect()
+    }
+
+    async fn file_size(&self, file: &str) -> Option<u64> {
+        #[cfg(feature = "tokio_runtime")]
+        {
+            let file = File::open(file).await.ok()?;
+            Some(file.metadata().await.ok()?.len())
+        }
+        #[cfg(not(feature = "tokio_runtime"))]
+        {
+            None
+        }
+    }
+
+    async fn read_file(&self, file: &str) -> Option<Vec<u8>> {
+        #[cfg(feature = "tokio_runtime")]
+        {
+            let mut file = File::open(file).await.ok()?;
+
+            let mut contents = Vec::new();
+            file.read_to_end(&mut contents).await.ok()?;
+
+            Some(contents)
+        }
+        #[cfg(not(feature = "tokio_runtime"))]
+        {
+            None
+        }
+    }
+
+    async fn read_file_to_string(&self, file: &str) -> Option<String> {
+        #[cfg(feature = "tokio_runtime")]
+        {
+            let mut file = File::open(file).await.ok()?;
+
+            let mut contents = String::new();
+            file.read_to_string(&mut contents).await.ok()?;
+
+            Some(contents)
+        }
+        #[cfg(not(feature = "tokio_runtime"))]
+        {
+            None
+        }
+    }
+
+    async fn get_native_file(&self, file: &str) -> Option<Box<dyn Any>> {
+        #[cfg(feature = "tokio_runtime")]
+        {
+            let file = File::open(file).await.ok()?;
+            Some(Box::new(file))
+        }
+        #[cfg(not(feature = "tokio_runtime"))]
+        {
+            None
+        }
+    }
+}

+ 1 - 0
packages/desktop/src/js/hash.txt

@@ -0,0 +1 @@
+[11927251734412729446]

+ 0 - 0
packages/html/src/js/native_eval.js → packages/desktop/src/js/native_eval.js


+ 11 - 9
packages/desktop/src/launch.rs

@@ -63,18 +63,20 @@ pub fn launch_virtual_dom_blocking(virtual_dom: VirtualDom, desktop_config: Conf
 /// Launches the WebView and runs the event loop, with configuration and root props.
 pub fn launch_virtual_dom(virtual_dom: VirtualDom, desktop_config: Config) -> ! {
     #[cfg(feature = "tokio_runtime")]
-    tokio::runtime::Builder::new_multi_thread()
-        .enable_all()
-        .build()
-        .unwrap()
-        .block_on(tokio::task::unconstrained(async move {
-            launch_virtual_dom_blocking(virtual_dom, desktop_config)
-        }));
+    {
+        tokio::runtime::Builder::new_multi_thread()
+            .enable_all()
+            .build()
+            .unwrap()
+            .block_on(tokio::task::unconstrained(async move {
+                launch_virtual_dom_blocking(virtual_dom, desktop_config)
+            }));
+
+        unreachable!("The desktop launch function will never exit")
+    }
 
     #[cfg(not(feature = "tokio_runtime"))]
     launch_virtual_dom_blocking(virtual_dom, desktop_config);
-
-    unreachable!("The desktop launch function will never exit")
 }
 
 /// Launches the WebView and runs the event loop, with configuration and root props.

+ 1 - 1
packages/desktop/src/protocol.rs

@@ -1,5 +1,5 @@
+use crate::document::NATIVE_EVAL_JS;
 use crate::{assets::*, webview::WebviewEdits};
-use dioxus_html::document::NATIVE_EVAL_JS;
 use dioxus_interpreter_js::unified_bindings::SLEDGEHAMMER_JS;
 use dioxus_interpreter_js::NATIVE_JS;
 use serde::Deserialize;

+ 5 - 1
packages/html/src/ts/native_eval.ts → packages/desktop/src/ts/native_eval.ts

@@ -1,4 +1,8 @@
-import { Channel, DioxusChannel, WeakDioxusChannel } from "./eval";
+import {
+  Channel,
+  DioxusChannel,
+  WeakDioxusChannel,
+} from "../../../html/src/ts/eval";
 
 // In dioxus desktop, eval needs to use the window object to store global state because we evaluate separate snippets of javascript in the browser
 declare global {

+ 9 - 6
packages/desktop/src/webview.rs

@@ -3,15 +3,18 @@ use crate::element::DesktopElement;
 use crate::file_upload::DesktopFileDragEvent;
 use crate::menubar::DioxusMenu;
 use crate::{
-    app::SharedContext, assets::AssetHandlerRegistry, edits::WryQueue,
-    file_upload::NativeFileHover, ipc::UserWindowEvent, protocol, waker::tao_waker, Config,
-    DesktopContext, DesktopService,
+    app::SharedContext,
+    assets::AssetHandlerRegistry,
+    edits::WryQueue,
+    file_upload::{NativeFileEngine, NativeFileHover},
+    ipc::UserWindowEvent,
+    protocol,
+    waker::tao_waker,
+    Config, DesktopContext, DesktopService,
 };
 use dioxus_core::{Runtime, ScopeId, VirtualDom};
 use dioxus_hooks::to_owned;
-use dioxus_html::{
-    native_bind::NativeFileEngine, prelude::Document, HasFileData, HtmlEvent, PlatformEventData,
-};
+use dioxus_html::{prelude::Document, HasFileData, HtmlEvent, PlatformEventData};
 use futures_util::{pin_mut, FutureExt};
 use std::cell::OnceCell;
 use std::sync::Arc;

+ 18 - 0
packages/desktop/tsconfig.json

@@ -0,0 +1,18 @@
+{
+    "compilerOptions": {
+        "module": "CommonJS",
+        "lib": [
+            "ES2015",
+            "DOM",
+            "dom",
+            "dom.iterable",
+            "ESNext"
+        ],
+        "noImplicitAny": true,
+        "removeComments": true,
+        "preserveConstEnums": true,
+    },
+    "exclude": [
+        "**/*.spec.ts"
+    ]
+}

+ 2 - 41
packages/html/Cargo.toml

@@ -19,8 +19,6 @@ dioxus-hooks = { workspace = true }
 generational-box = { workspace = true }
 serde = { version = "1", features = ["derive"], optional = true }
 serde_repr = { version = "0.1", optional = true }
-wasm-bindgen = { workspace = true, optional = true }
-wasm-bindgen-futures = { workspace = true, optional = true }
 js-sys = { version = "0.3.56", optional = true }
 euclid = "0.22.7"
 enumset = "1.1.2"
@@ -32,30 +30,6 @@ serde_json = { version = "1", optional = true }
 tracing.workspace = true
 rustversion = "1.0.17"
 
-[dependencies.web-sys]
-optional = true
-version = "0.3.56"
-features = [
-    "Touch",
-    "TouchList",
-    "TouchEvent",
-    "MouseEvent",
-    "DragEvent",
-    "InputEvent",
-    "HtmlInputElement",
-    "ClipboardEvent",
-    "KeyboardEvent",
-    "WheelEvent",
-    "AnimationEvent",
-    "TransitionEvent",
-    "PointerEvent",
-    "FocusEvent",
-    "CompositionEvent",
-    "CustomEvent",
-    "ResizeObserverEntry",
-    "ResizeObserverSize"
-]
-
 [build-dependencies]
 lazy-js-bundle = { workspace = true }
 
@@ -76,30 +50,17 @@ serialize = [
     "keyboard-types/serde",
     "dioxus-core/serialize"
 ]
-mounted = [
-    "web-sys?/Element",
-    "web-sys?/DomRect",
-    "web-sys?/ScrollIntoViewOptions",
-    "web-sys?/ScrollLogicalPosition",
-    "web-sys?/ScrollBehavior",
-    "web-sys?/HtmlElement",
-]
+mounted = []
 document = [
     "dep:serde",
     "dep:serde_json"
 ]
 file_engine = [
     "dep:async-trait",
-    "dep:js-sys",
-    "web-sys?/File",
-    "web-sys?/FileList",
-    "web-sys?/FileReader"
 ]
-wasm-bind = ["dep:web-sys", "dep:wasm-bindgen", "dep:wasm-bindgen-futures"]
-native-bind = ["dep:tokio", "file_engine"]
 hot-reload-context = ["dep:dioxus-rsx"]
 html-to-rsx = []
 
 [package.metadata.docs.rs]
 cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
-feature = ["html-to-rsx", "hot-reload-context", "html-to-rsx", "native-bind", "wasm-bind"]
+feature = ["html-to-rsx", "hot-reload-context", "html-to-rsx"]

+ 0 - 2
packages/html/build.rs

@@ -2,8 +2,6 @@ fn main() {
     // If any TS files change, re-run the build script
     lazy_js_bundle::LazyTypeScriptBindings::new()
         .with_watching("./src/ts")
-        .with_binding("./src/ts/eval.ts", "./src/js/eval.js")
-        .with_binding("./src/ts/native_eval.ts", "./src/js/native_eval.js")
         .with_binding("./src/ts/head.ts", "./src/js/head.js")
         .run();
 }

+ 0 - 50
packages/html/src/document/bindings.rs

@@ -1,50 +0,0 @@
-/// Code for the Dioxus channel used to communicate between the dioxus and javascript code
-#[cfg(feature = "native-bind")]
-pub const NATIVE_EVAL_JS: &str = include_str!("../js/native_eval.js");
-
-#[cfg(feature = "wasm-bind")]
-#[wasm_bindgen::prelude::wasm_bindgen]
-pub struct JSOwner {
-    _owner: Box<dyn std::any::Any>,
-}
-
-#[cfg(feature = "wasm-bind")]
-impl JSOwner {
-    pub fn new(owner: impl std::any::Any) -> Self {
-        Self {
-            _owner: Box::new(owner),
-        }
-    }
-}
-
-#[cfg(feature = "wasm-bind")]
-#[wasm_bindgen::prelude::wasm_bindgen(module = "/src/js/eval.js")]
-extern "C" {
-    pub type WebDioxusChannel;
-
-    #[wasm_bindgen(constructor)]
-    pub fn new(owner: JSOwner) -> WebDioxusChannel;
-
-    #[wasm_bindgen(method, js_name = "rustSend")]
-    pub fn rust_send(this: &WebDioxusChannel, value: wasm_bindgen::JsValue);
-
-    #[wasm_bindgen(method, js_name = "rustRecv")]
-    pub async fn rust_recv(this: &WebDioxusChannel) -> wasm_bindgen::JsValue;
-
-    #[wasm_bindgen(method)]
-    pub fn send(this: &WebDioxusChannel, value: wasm_bindgen::JsValue);
-
-    #[wasm_bindgen(method)]
-    pub async fn recv(this: &WebDioxusChannel) -> wasm_bindgen::JsValue;
-
-    #[wasm_bindgen(method)]
-    pub fn weak(this: &WebDioxusChannel) -> WeakDioxusChannel;
-
-    pub type WeakDioxusChannel;
-
-    #[wasm_bindgen(method, js_name = "rustSend")]
-    pub fn rust_send(this: &WeakDioxusChannel, value: wasm_bindgen::JsValue);
-
-    #[wasm_bindgen(method, js_name = "rustRecv")]
-    pub async fn rust_recv(this: &WeakDioxusChannel) -> wasm_bindgen::JsValue;
-}

+ 0 - 2
packages/html/src/document/mod.rs

@@ -7,9 +7,7 @@ use std::{
 
 use generational_box::{AnyStorage, GenerationalBox, UnsyncStorage};
 
-mod bindings;
 #[allow(unused)]
-pub use bindings::*;
 mod eval;
 pub use eval::*;
 

+ 7 - 1
packages/html/src/events/mounted.rs

@@ -1,7 +1,7 @@
 //! Handles querying data from the renderer
 
 use std::{
-    fmt::{Display, Formatter},
+    fmt::{Debug, Display, Formatter},
     future::Future,
     pin::Pin,
 };
@@ -70,6 +70,12 @@ pub struct MountedData {
     inner: Box<dyn RenderedElementBacking>,
 }
 
+impl Debug for MountedData {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("MountedData").finish()
+    }
+}
+
 impl<E: RenderedElementBacking> From<E> for MountedData {
     fn from(e: E) -> Self {
         Self { inner: Box::new(e) }

+ 1 - 1
packages/html/src/js/hash.txt

@@ -1 +1 @@
-[10372071913661173523, 8375185156499858125, 4813754958077120784]
+[206827801705263822, 8375185156499858125]

+ 0 - 6
packages/html/src/lib.rs

@@ -27,14 +27,8 @@ pub use file_data::*;
 mod attribute_groups;
 pub mod geometry;
 pub mod input_data;
-#[cfg(feature = "native-bind")]
-pub mod native_bind;
 pub mod point_interaction;
 mod render_template;
-#[cfg(feature = "wasm-bind")]
-mod web_sys_bind;
-#[cfg(feature = "wasm-bind")]
-pub use web_sys_bind::*;
 
 #[cfg(feature = "serialize")]
 mod transit;

+ 0 - 3
packages/html/src/native_bind/mod.rs

@@ -1,3 +0,0 @@
-mod native_file_engine;
-
-pub use native_file_engine::*;

+ 0 - 56
packages/html/src/native_bind/native_file_engine.rs

@@ -1,56 +0,0 @@
-use std::any::Any;
-use std::path::PathBuf;
-
-use tokio::fs::File;
-use tokio::io::AsyncReadExt;
-
-use crate::file_data::FileEngine;
-
-pub struct NativeFileEngine {
-    files: Vec<PathBuf>,
-}
-
-impl NativeFileEngine {
-    pub fn new(files: Vec<PathBuf>) -> Self {
-        Self { files }
-    }
-}
-
-#[cfg(feature = "file_engine")]
-#[async_trait::async_trait(?Send)]
-impl FileEngine for NativeFileEngine {
-    fn files(&self) -> Vec<String> {
-        self.files
-            .iter()
-            .filter_map(|f| Some(f.to_str()?.to_string()))
-            .collect()
-    }
-
-    async fn file_size(&self, file: &str) -> Option<u64> {
-        let file = File::open(file).await.ok()?;
-        Some(file.metadata().await.ok()?.len())
-    }
-
-    async fn read_file(&self, file: &str) -> Option<Vec<u8>> {
-        let mut file = File::open(file).await.ok()?;
-
-        let mut contents = Vec::new();
-        file.read_to_end(&mut contents).await.ok()?;
-
-        Some(contents)
-    }
-
-    async fn read_file_to_string(&self, file: &str) -> Option<String> {
-        let mut file = File::open(file).await.ok()?;
-
-        let mut contents = String::new();
-        file.read_to_string(&mut contents).await.ok()?;
-
-        Some(contents)
-    }
-
-    async fn get_native_file(&self, file: &str) -> Option<Box<dyn Any>> {
-        let file = File::open(file).await.ok()?;
-        Some(Box::new(file))
-    }
-}

+ 0 - 38
packages/html/src/ts/eval.ts

@@ -68,41 +68,3 @@ export abstract class DioxusChannel {
   // Receive data sent from javascript in rust
   abstract rustRecv(): Promise<any>;
 }
-
-export class WebDioxusChannel extends DioxusChannel {
-  js_to_rust: Channel;
-  rust_to_js: Channel;
-  owner: any;
-
-  constructor(owner: any) {
-    super();
-    this.owner = owner;
-    this.js_to_rust = new Channel();
-    this.rust_to_js = new Channel();
-  }
-
-  // Return a weak reference to this channel
-  weak(): WeakDioxusChannel {
-    return new WeakDioxusChannel(this);
-  }
-
-  // Receive message from Rust
-  async recv() {
-    return await this.rust_to_js.recv();
-  }
-
-  // Send message to rust.
-  send(data: any) {
-    this.js_to_rust.send(data);
-  }
-
-  // Send data from rust to javascript
-  rustSend(data: any) {
-    this.rust_to_js.send(data);
-  }
-
-  // Receive data sent from javascript in rust
-  async rustRecv(): Promise<any> {
-    return await this.js_to_rust.recv();
-  }
-}

+ 0 - 675
packages/html/src/web_sys_bind/events.rs

@@ -1,675 +0,0 @@
-use crate::events::HasKeyboardData;
-use crate::events::{
-    AnimationData, CompositionData, KeyboardData, MouseData, PointerData, TouchData,
-    TransitionData, WheelData,
-};
-use crate::file_data::HasFileData;
-use crate::geometry::PixelsSize;
-use crate::geometry::{ClientPoint, ElementPoint, PagePoint, ScreenPoint};
-use crate::input_data::{decode_key_location, decode_mouse_button_set, MouseButton};
-use crate::prelude::*;
-use keyboard_types::{Code, Key, Modifiers};
-use std::str::FromStr;
-use wasm_bindgen::JsCast;
-use web_sys::{js_sys, ResizeObserverEntry};
-use web_sys::{
-    AnimationEvent, CompositionEvent, CustomEvent, Event, KeyboardEvent, MouseEvent, PointerEvent,
-    Touch, TouchEvent, TransitionEvent, WheelEvent,
-};
-
-macro_rules! uncheck_convert {
-    ($t:ty, $d:ty) => {
-        impl From<Event> for $d {
-            #[inline]
-            fn from(e: Event) -> Self {
-                let e: $t = e.unchecked_into();
-                Self::from(e)
-            }
-        }
-
-        impl From<&Event> for $d {
-            #[inline]
-            fn from(e: &Event) -> Self {
-                let e: &$t = e.unchecked_ref();
-                Self::from(e.clone())
-            }
-        }
-    };
-    ($($t:ty => $d:ty),+ $(,)?) => {
-        $(uncheck_convert!($t, $d);)+
-    };
-}
-
-uncheck_convert![
-    web_sys::CompositionEvent => CompositionData,
-    web_sys::KeyboardEvent    => KeyboardData,
-    web_sys::MouseEvent       => MouseData,
-    web_sys::TouchEvent       => TouchData,
-    web_sys::PointerEvent     => PointerData,
-    web_sys::WheelEvent       => WheelData,
-    web_sys::AnimationEvent   => AnimationData,
-    web_sys::TransitionEvent  => TransitionData,
-    web_sys::MouseEvent       => DragData,
-    web_sys::FocusEvent       => FocusData,
-];
-
-impl From<Event> for ResizeData {
-    #[inline]
-    fn from(e: Event) -> Self {
-        <ResizeData as From<&Event>>::from(&e)
-    }
-}
-
-impl From<&Event> for ResizeData {
-    #[inline]
-    fn from(e: &Event) -> Self {
-        let e: &CustomEvent = e.unchecked_ref();
-        let value = e.detail();
-        Self::from(value.unchecked_into::<ResizeObserverEntry>())
-    }
-}
-
-impl HasCompositionData for CompositionEvent {
-    fn data(&self) -> std::string::String {
-        self.data().unwrap_or_default()
-    }
-
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-}
-
-impl HasKeyboardData for KeyboardEvent {
-    fn key(&self) -> Key {
-        Key::from_str(self.key().as_str()).unwrap_or(Key::Unidentified)
-    }
-
-    fn code(&self) -> Code {
-        Code::from_str(self.code().as_str()).unwrap_or(Code::Unidentified)
-    }
-
-    fn location(&self) -> keyboard_types::Location {
-        decode_key_location(self.location() as usize)
-    }
-
-    fn is_auto_repeating(&self) -> bool {
-        self.repeat()
-    }
-
-    fn is_composing(&self) -> bool {
-        self.is_composing()
-    }
-
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-}
-
-impl ModifiersInteraction for KeyboardEvent {
-    fn modifiers(&self) -> Modifiers {
-        let mut modifiers = Modifiers::empty();
-
-        if self.alt_key() {
-            modifiers.insert(Modifiers::ALT);
-        }
-        if self.ctrl_key() {
-            modifiers.insert(Modifiers::CONTROL);
-        }
-        if self.meta_key() {
-            modifiers.insert(Modifiers::META);
-        }
-        if self.shift_key() {
-            modifiers.insert(Modifiers::SHIFT);
-        }
-
-        modifiers
-    }
-}
-
-impl InteractionLocation for MouseEvent {
-    fn client_coordinates(&self) -> ClientPoint {
-        ClientPoint::new(self.client_x().into(), self.client_y().into())
-    }
-
-    fn page_coordinates(&self) -> PagePoint {
-        PagePoint::new(self.page_x().into(), self.page_y().into())
-    }
-
-    fn screen_coordinates(&self) -> ScreenPoint {
-        ScreenPoint::new(self.screen_x().into(), self.screen_y().into())
-    }
-}
-
-impl InteractionElementOffset for MouseEvent {
-    fn element_coordinates(&self) -> ElementPoint {
-        ElementPoint::new(self.offset_x().into(), self.offset_y().into())
-    }
-}
-
-impl ModifiersInteraction for MouseEvent {
-    fn modifiers(&self) -> Modifiers {
-        let mut modifiers = Modifiers::empty();
-
-        if self.alt_key() {
-            modifiers.insert(Modifiers::ALT);
-        }
-        if self.ctrl_key() {
-            modifiers.insert(Modifiers::CONTROL);
-        }
-        if self.meta_key() {
-            modifiers.insert(Modifiers::META);
-        }
-        if self.shift_key() {
-            modifiers.insert(Modifiers::SHIFT);
-        }
-
-        modifiers
-    }
-}
-
-impl PointerInteraction for MouseEvent {
-    fn held_buttons(&self) -> crate::input_data::MouseButtonSet {
-        decode_mouse_button_set(self.buttons())
-    }
-
-    fn trigger_button(&self) -> Option<MouseButton> {
-        Some(MouseButton::from_web_code(self.button()))
-    }
-}
-
-impl HasMouseData for MouseEvent {
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-}
-
-impl HasFileData for MouseEvent {}
-
-impl HasDragData for MouseEvent {
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-}
-
-impl ModifiersInteraction for TouchEvent {
-    fn modifiers(&self) -> Modifiers {
-        let mut modifiers = Modifiers::empty();
-
-        if self.alt_key() {
-            modifiers.insert(Modifiers::ALT);
-        }
-        if self.ctrl_key() {
-            modifiers.insert(Modifiers::CONTROL);
-        }
-        if self.meta_key() {
-            modifiers.insert(Modifiers::META);
-        }
-        if self.shift_key() {
-            modifiers.insert(Modifiers::SHIFT);
-        }
-
-        modifiers
-    }
-}
-
-impl crate::events::HasTouchData for TouchEvent {
-    fn touches(&self) -> Vec<TouchPoint> {
-        let touches = TouchEvent::touches(self);
-        let mut result = Vec::with_capacity(touches.length() as usize);
-        for i in 0..touches.length() {
-            let touch = touches.get(i).unwrap();
-            result.push(TouchPoint::new(touch));
-        }
-        result
-    }
-
-    fn touches_changed(&self) -> Vec<TouchPoint> {
-        let touches = self.changed_touches();
-        let mut result = Vec::with_capacity(touches.length() as usize);
-        for i in 0..touches.length() {
-            let touch = touches.get(i).unwrap();
-            result.push(TouchPoint::new(touch));
-        }
-        result
-    }
-
-    fn target_touches(&self) -> Vec<TouchPoint> {
-        let touches = self.target_touches();
-        let mut result = Vec::with_capacity(touches.length() as usize);
-        for i in 0..touches.length() {
-            let touch = touches.get(i).unwrap();
-            result.push(TouchPoint::new(touch));
-        }
-        result
-    }
-
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-}
-
-impl HasTouchPointData for Touch {
-    fn identifier(&self) -> i32 {
-        self.identifier()
-    }
-
-    fn radius(&self) -> ScreenPoint {
-        ScreenPoint::new(self.radius_x().into(), self.radius_y().into())
-    }
-
-    fn rotation(&self) -> f64 {
-        self.rotation_angle() as f64
-    }
-
-    fn force(&self) -> f64 {
-        self.force() as f64
-    }
-
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-}
-
-impl InteractionLocation for Touch {
-    fn client_coordinates(&self) -> ClientPoint {
-        ClientPoint::new(self.client_x().into(), self.client_y().into())
-    }
-
-    fn screen_coordinates(&self) -> ScreenPoint {
-        ScreenPoint::new(self.screen_x().into(), self.screen_y().into())
-    }
-
-    fn page_coordinates(&self) -> PagePoint {
-        PagePoint::new(self.page_x().into(), self.page_y().into())
-    }
-}
-
-impl HasPointerData for PointerEvent {
-    fn pointer_id(&self) -> i32 {
-        self.pointer_id()
-    }
-
-    fn width(&self) -> i32 {
-        self.width()
-    }
-
-    fn height(&self) -> i32 {
-        self.height()
-    }
-
-    fn pressure(&self) -> f32 {
-        self.pressure()
-    }
-
-    fn tangential_pressure(&self) -> f32 {
-        self.tangential_pressure()
-    }
-
-    fn tilt_x(&self) -> i32 {
-        self.tilt_x()
-    }
-
-    fn tilt_y(&self) -> i32 {
-        self.tilt_y()
-    }
-
-    fn twist(&self) -> i32 {
-        self.twist()
-    }
-
-    fn pointer_type(&self) -> String {
-        self.pointer_type()
-    }
-
-    fn is_primary(&self) -> bool {
-        self.is_primary()
-    }
-
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-}
-
-impl InteractionLocation for PointerEvent {
-    fn client_coordinates(&self) -> ClientPoint {
-        ClientPoint::new(self.client_x().into(), self.client_y().into())
-    }
-
-    fn screen_coordinates(&self) -> ScreenPoint {
-        ScreenPoint::new(self.screen_x().into(), self.screen_y().into())
-    }
-
-    fn page_coordinates(&self) -> PagePoint {
-        PagePoint::new(self.page_x().into(), self.page_y().into())
-    }
-}
-
-impl InteractionElementOffset for PointerEvent {
-    fn element_coordinates(&self) -> ElementPoint {
-        ElementPoint::new(self.offset_x().into(), self.offset_y().into())
-    }
-}
-
-impl ModifiersInteraction for PointerEvent {
-    fn modifiers(&self) -> Modifiers {
-        let mut modifiers = Modifiers::empty();
-
-        if self.alt_key() {
-            modifiers.insert(Modifiers::ALT);
-        }
-        if self.ctrl_key() {
-            modifiers.insert(Modifiers::CONTROL);
-        }
-        if self.meta_key() {
-            modifiers.insert(Modifiers::META);
-        }
-        if self.shift_key() {
-            modifiers.insert(Modifiers::SHIFT);
-        }
-
-        modifiers
-    }
-}
-
-impl PointerInteraction for PointerEvent {
-    fn held_buttons(&self) -> crate::input_data::MouseButtonSet {
-        decode_mouse_button_set(self.buttons())
-    }
-
-    fn trigger_button(&self) -> Option<MouseButton> {
-        Some(MouseButton::from_web_code(self.button()))
-    }
-}
-
-impl HasWheelData for WheelEvent {
-    fn delta(&self) -> crate::geometry::WheelDelta {
-        crate::geometry::WheelDelta::from_web_attributes(
-            self.delta_mode(),
-            self.delta_x(),
-            self.delta_y(),
-            self.delta_z(),
-        )
-    }
-
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-}
-
-impl HasMouseData for WheelEvent {
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-}
-
-impl InteractionLocation for WheelEvent {
-    fn client_coordinates(&self) -> ClientPoint {
-        ClientPoint::new(self.client_x().into(), self.client_y().into())
-    }
-
-    fn screen_coordinates(&self) -> ScreenPoint {
-        ScreenPoint::new(self.screen_x().into(), self.screen_y().into())
-    }
-
-    fn page_coordinates(&self) -> PagePoint {
-        PagePoint::new(self.page_x().into(), self.page_y().into())
-    }
-}
-
-impl InteractionElementOffset for WheelEvent {
-    fn element_coordinates(&self) -> ElementPoint {
-        ElementPoint::new(self.offset_x().into(), self.offset_y().into())
-    }
-}
-
-impl ModifiersInteraction for WheelEvent {
-    fn modifiers(&self) -> Modifiers {
-        let mut modifiers = Modifiers::empty();
-
-        if self.alt_key() {
-            modifiers.insert(Modifiers::ALT);
-        }
-        if self.ctrl_key() {
-            modifiers.insert(Modifiers::CONTROL);
-        }
-        if self.meta_key() {
-            modifiers.insert(Modifiers::META);
-        }
-        if self.shift_key() {
-            modifiers.insert(Modifiers::SHIFT);
-        }
-
-        modifiers
-    }
-}
-
-impl PointerInteraction for WheelEvent {
-    fn held_buttons(&self) -> crate::input_data::MouseButtonSet {
-        decode_mouse_button_set(self.buttons())
-    }
-
-    fn trigger_button(&self) -> Option<MouseButton> {
-        Some(MouseButton::from_web_code(self.button()))
-    }
-}
-
-impl HasAnimationData for AnimationEvent {
-    fn animation_name(&self) -> String {
-        self.animation_name()
-    }
-
-    fn pseudo_element(&self) -> String {
-        self.pseudo_element()
-    }
-
-    fn elapsed_time(&self) -> f32 {
-        self.elapsed_time()
-    }
-
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-}
-
-impl HasTransitionData for TransitionEvent {
-    fn elapsed_time(&self) -> f32 {
-        self.elapsed_time()
-    }
-
-    fn property_name(&self) -> String {
-        self.property_name()
-    }
-
-    fn pseudo_element(&self) -> String {
-        self.pseudo_element()
-    }
-
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-}
-
-#[cfg(feature = "mounted")]
-impl From<&web_sys::Element> for MountedData {
-    fn from(e: &web_sys::Element) -> Self {
-        MountedData::new(e.clone())
-    }
-}
-
-#[cfg(feature = "mounted")]
-impl crate::RenderedElementBacking for web_sys::Element {
-    fn get_scroll_offset(
-        &self,
-    ) -> std::pin::Pin<
-        Box<
-            dyn std::future::Future<Output = crate::MountedResult<crate::geometry::PixelsVector2D>>,
-        >,
-    > {
-        let left = self.scroll_left();
-        let top = self.scroll_top();
-        let result = Ok(crate::geometry::PixelsVector2D::new(
-            left as f64,
-            top as f64,
-        ));
-        Box::pin(async { result })
-    }
-
-    fn get_scroll_size(
-        &self,
-    ) -> std::pin::Pin<
-        Box<dyn std::future::Future<Output = crate::MountedResult<crate::geometry::PixelsSize>>>,
-    > {
-        let width = self.scroll_width();
-        let height = self.scroll_height();
-        let result = Ok(crate::geometry::PixelsSize::new(
-            width as f64,
-            height as f64,
-        ));
-        Box::pin(async { result })
-    }
-
-    fn get_client_rect(
-        &self,
-    ) -> std::pin::Pin<
-        Box<dyn std::future::Future<Output = crate::MountedResult<crate::geometry::PixelsRect>>>,
-    > {
-        let rect = self.get_bounding_client_rect();
-        let result = Ok(crate::geometry::PixelsRect::new(
-            euclid::Point2D::new(rect.left(), rect.top()),
-            euclid::Size2D::new(rect.width(), rect.height()),
-        ));
-        Box::pin(async { result })
-    }
-
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-
-    fn scroll_to(
-        &self,
-        behavior: crate::ScrollBehavior,
-    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::MountedResult<()>>>> {
-        let options = web_sys::ScrollIntoViewOptions::new();
-        match behavior {
-            crate::ScrollBehavior::Instant => {
-                options.set_behavior(web_sys::ScrollBehavior::Instant);
-            }
-            crate::ScrollBehavior::Smooth => {
-                options.set_behavior(web_sys::ScrollBehavior::Smooth);
-            }
-        }
-        self.scroll_into_view_with_scroll_into_view_options(&options);
-
-        Box::pin(async { Ok(()) })
-    }
-
-    fn set_focus(
-        &self,
-        focus: bool,
-    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::MountedResult<()>>>> {
-        #[derive(Debug)]
-        struct FocusError(wasm_bindgen::JsValue);
-
-        impl std::fmt::Display for FocusError {
-            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-                write!(f, "failed to focus element {:?}", self.0)
-            }
-        }
-
-        impl std::error::Error for FocusError {}
-
-        let result = self
-            .dyn_ref::<web_sys::HtmlElement>()
-            .ok_or_else(|| crate::MountedError::OperationFailed(Box::new(FocusError(self.into()))))
-            .and_then(|e| {
-                (if focus { e.focus() } else { e.blur() })
-                    .map_err(|err| crate::MountedError::OperationFailed(Box::new(FocusError(err))))
-            });
-        Box::pin(async { result })
-    }
-}
-
-fn extract_first_size(resize_observer_output: js_sys::Array) -> ResizeResult<PixelsSize> {
-    let first = resize_observer_output.get(0);
-    let size = first.unchecked_into::<web_sys::ResizeObserverSize>();
-
-    // inline_size matches the width of the element if its writing-mode is horizontal, the height otherwise
-    let inline_size = size.inline_size();
-    // block_size matches the height of the element if its writing-mode is horizontal, the width otherwise
-    let block_size = size.block_size();
-
-    Ok(PixelsSize::new(inline_size, block_size))
-}
-
-impl HasResizeData for ResizeObserverEntry {
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-
-    fn get_border_box_size(&self) -> ResizeResult<PixelsSize> {
-        extract_first_size(self.border_box_size())
-    }
-
-    fn get_content_box_size(&self) -> ResizeResult<PixelsSize> {
-        extract_first_size(self.content_box_size())
-    }
-}
-
-impl HasScrollData for Event {
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-}
-
-impl HasClipboardData for Event {
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-}
-
-impl From<&Event> for ClipboardData {
-    fn from(e: &Event) -> Self {
-        ClipboardData::new(e.clone())
-    }
-}
-
-impl HasFocusData for web_sys::FocusEvent {
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-}
-
-impl HasToggleData for web_sys::Event {
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-}
-
-impl HasSelectionData for web_sys::Event {
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-}
-
-impl HasMediaData for web_sys::Event {
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-}
-
-impl HasFileData for web_sys::Event {
-    #[cfg(feature = "file_engine")]
-    fn files(&self) -> Option<std::sync::Arc<dyn crate::file_data::FileEngine>> {
-        let files = self
-            .dyn_ref()
-            .and_then(|input: &web_sys::HtmlInputElement| {
-                input.files().and_then(|files| {
-                    #[allow(clippy::arc_with_non_send_sync)]
-                    crate::web_sys_bind::file_engine::WebFileEngine::new(files)
-                        .map(|f| std::sync::Arc::new(f) as std::sync::Arc<dyn crate::FileEngine>)
-                })
-            });
-
-        files
-    }
-}

+ 0 - 5
packages/html/src/web_sys_bind/mod.rs

@@ -1,5 +0,0 @@
-mod events;
-#[cfg(feature = "file_engine")]
-mod file_engine;
-#[cfg(feature = "file_engine")]
-pub use file_engine::*;

+ 42 - 9
packages/web/Cargo.toml

@@ -12,7 +12,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 [dependencies]
 dioxus-core = { workspace = true }
 dioxus-core-types = { workspace = true }
-dioxus-html = { workspace = true, features = ["wasm-bind"] }
+dioxus-html = { workspace = true }
 dioxus-devtools = { workspace = true }
 dioxus-signals = { workspace = true }
 dioxus-interpreter-js = { workspace = true, features = [
@@ -38,33 +38,66 @@ serde = { version = "1.0", optional = true }
 serde-wasm-bindgen = { version = "0.5.0", optional = true }
 
 ciborium = { workspace = true, optional = true }
+async-trait = { version = "0.1.58", optional = true }
 
 [dependencies.web-sys]
 version = "0.3.56"
 features = [
+    "AnimationEvent",
+    "ClipboardEvent",
+    "CloseEvent",
+    "Comment",
+    "CompositionEvent",
+    "console",
+    "CustomEvent",
+    "DataTransfer",
     "Document",
+    "DragEvent",
+    "FocusEvent",
     "HtmlElement",
+    "HtmlFormElement",
     "HtmlInputElement",
     "HtmlSelectElement",
     "HtmlTextAreaElement",
-    "HtmlFormElement",
+    "InputEvent",
+    "KeyboardEvent",
+    "MouseEvent",
+    "NodeList",
+    "PointerEvent",
+    "ResizeObserverEntry",
+    "ResizeObserverSize",
     "Text",
-    "Comment",
+    "Touch",
+    "TouchEvent",
+    "TouchList",
+    "TransitionEvent",
+    "WheelEvent",
     "Window",
-    "DataTransfer",
-    "console",
-    "NodeList",
-    "CloseEvent",
-    "CustomEvent",
 ]
 
+[build-dependencies]
+lazy-js-bundle = { workspace = true }
+
 [features]
 default = ["panic_hook", "mounted", "file_engine", "devtools", "document"]
 panic_hook = ["dep:console_error_panic_hook"]
 hydrate = ["web-sys/Comment", "ciborium", "dep:serde"]
-mounted = ["web-sys/Element", "dioxus-html/mounted"]
+mounted = [
+    "web-sys/Element",
+    "dioxus-html/mounted",
+    "web-sys/Element",
+    "web-sys/DomRect",
+    "web-sys/ScrollIntoViewOptions",
+    "web-sys/ScrollLogicalPosition",
+    "web-sys/ScrollBehavior",
+    "web-sys/HtmlElement",
+]
 file_engine = [
     "dioxus-html/file_engine",
+    "dep:async-trait",
+    "web-sys/File",
+    "web-sys/FileList",
+    "web-sys/FileReader"
 ]
 devtools = ["web-sys/MessageEvent", "web-sys/WebSocket", "web-sys/Location", "dep:serde_json", "dep:serde", "dioxus-core/serialize"]
 document = ["dioxus-html/document", "dep:serde-wasm-bindgen", "dep:serde_json", "dep:serde"]

+ 7 - 0
packages/web/build.rs

@@ -0,0 +1,7 @@
+fn main() {
+    // If any TS files change, re-run the build script
+    lazy_js_bundle::LazyTypeScriptBindings::new()
+        .with_watching("./src/ts")
+        .with_binding("./src/ts/eval.ts", "./src/js/eval.js")
+        .run();
+}

+ 45 - 3
packages/web/src/document.rs

@@ -1,7 +1,5 @@
 use dioxus_core::ScopeId;
-use dioxus_html::document::{
-    Document, EvalError, Evaluator, JSOwner, WeakDioxusChannel, WebDioxusChannel,
-};
+use dioxus_html::document::{Document, EvalError, Evaluator};
 use generational_box::{AnyStorage, GenerationalBox, UnsyncStorage};
 use js_sys::Function;
 use serde::Serialize;
@@ -11,6 +9,50 @@ use std::pin::Pin;
 use std::{rc::Rc, str::FromStr};
 use wasm_bindgen::prelude::*;
 
+#[wasm_bindgen::prelude::wasm_bindgen]
+pub struct JSOwner {
+    _owner: Box<dyn std::any::Any>,
+}
+
+impl JSOwner {
+    pub fn new(owner: impl std::any::Any) -> Self {
+        Self {
+            _owner: Box::new(owner),
+        }
+    }
+}
+
+#[wasm_bindgen::prelude::wasm_bindgen(module = "/src/js/eval.js")]
+extern "C" {
+    pub type WebDioxusChannel;
+
+    #[wasm_bindgen(constructor)]
+    pub fn new(owner: JSOwner) -> WebDioxusChannel;
+
+    #[wasm_bindgen(method, js_name = "rustSend")]
+    pub fn rust_send(this: &WebDioxusChannel, value: wasm_bindgen::JsValue);
+
+    #[wasm_bindgen(method, js_name = "rustRecv")]
+    pub async fn rust_recv(this: &WebDioxusChannel) -> wasm_bindgen::JsValue;
+
+    #[wasm_bindgen(method)]
+    pub fn send(this: &WebDioxusChannel, value: wasm_bindgen::JsValue);
+
+    #[wasm_bindgen(method)]
+    pub async fn recv(this: &WebDioxusChannel) -> wasm_bindgen::JsValue;
+
+    #[wasm_bindgen(method)]
+    pub fn weak(this: &WebDioxusChannel) -> WeakDioxusChannel;
+
+    pub type WeakDioxusChannel;
+
+    #[wasm_bindgen(method, js_name = "rustSend")]
+    pub fn rust_send(this: &WeakDioxusChannel, value: wasm_bindgen::JsValue);
+
+    #[wasm_bindgen(method, js_name = "rustRecv")]
+    pub async fn rust_recv(this: &WeakDioxusChannel) -> wasm_bindgen::JsValue;
+}
+
 /// Provides the WebEvalProvider through [`ScopeId::provide_context`].
 pub fn init_document() {
     let provider: Rc<dyn Document> = Rc::new(WebDocument);

+ 0 - 591
packages/web/src/event.rs

@@ -1,591 +0,0 @@
-use std::{any::Any, collections::HashMap};
-
-use dioxus_html::{
-    point_interaction::{
-        InteractionElementOffset, InteractionLocation, ModifiersInteraction, PointerInteraction,
-    },
-    DragData, FormData, FormValue, HasDragData, HasFileData, HasFormData, HasImageData,
-    HasMouseData, HtmlEventConverter, ImageData, MountedData, PlatformEventData, ScrollData,
-};
-use js_sys::Array;
-use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue};
-use web_sys::{Document, Element, Event, MouseEvent};
-
-pub(crate) struct WebEventConverter;
-
-#[inline(always)]
-fn downcast_event(event: &dioxus_html::PlatformEventData) -> &GenericWebSysEvent {
-    event
-        .downcast::<GenericWebSysEvent>()
-        .expect("event should be a GenericWebSysEvent")
-}
-
-impl HtmlEventConverter for WebEventConverter {
-    #[inline(always)]
-    fn convert_animation_data(
-        &self,
-        event: &dioxus_html::PlatformEventData,
-    ) -> dioxus_html::AnimationData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    #[inline(always)]
-    fn convert_clipboard_data(
-        &self,
-        event: &dioxus_html::PlatformEventData,
-    ) -> dioxus_html::ClipboardData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    #[inline(always)]
-    fn convert_composition_data(
-        &self,
-        event: &dioxus_html::PlatformEventData,
-    ) -> dioxus_html::CompositionData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    #[inline(always)]
-    fn convert_drag_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::DragData {
-        let event = downcast_event(event);
-        DragData::new(WebDragData::new(event.raw.clone().unchecked_into()))
-    }
-
-    #[inline(always)]
-    fn convert_focus_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::FocusData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    #[inline(always)]
-    fn convert_form_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::FormData {
-        let event = downcast_event(event);
-        FormData::new(WebFormData::new(event.element.clone(), event.raw.clone()))
-    }
-
-    #[inline(always)]
-    fn convert_image_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::ImageData {
-        let event = downcast_event(event);
-        let error = event.raw.type_() == "error";
-        ImageData::new(WebImageEvent::new(event.raw.clone(), error))
-    }
-
-    #[inline(always)]
-    fn convert_keyboard_data(
-        &self,
-        event: &dioxus_html::PlatformEventData,
-    ) -> dioxus_html::KeyboardData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    #[inline(always)]
-    fn convert_media_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::MediaData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    #[allow(unused_variables)]
-    #[inline(always)]
-    fn convert_mounted_data(&self, event: &dioxus_html::PlatformEventData) -> MountedData {
-        #[cfg(feature = "mounted")]
-        {
-            MountedData::from(
-                event
-                    .downcast::<web_sys::Element>()
-                    .expect("event should be a web_sys::Element"),
-            )
-        }
-        #[cfg(not(feature = "mounted"))]
-        {
-            panic!("mounted events are not supported without the mounted feature on the dioxus-web crate enabled")
-        }
-    }
-
-    #[inline(always)]
-    fn convert_mouse_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::MouseData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    #[inline(always)]
-    fn convert_pointer_data(
-        &self,
-        event: &dioxus_html::PlatformEventData,
-    ) -> dioxus_html::PointerData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    #[inline(always)]
-    fn convert_resize_data(
-        &self,
-        event: &dioxus_html::PlatformEventData,
-    ) -> dioxus_html::ResizeData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    #[inline(always)]
-    fn convert_scroll_data(
-        &self,
-        event: &dioxus_html::PlatformEventData,
-    ) -> dioxus_html::ScrollData {
-        ScrollData::from(downcast_event(event).raw.clone())
-    }
-
-    #[inline(always)]
-    fn convert_selection_data(
-        &self,
-        event: &dioxus_html::PlatformEventData,
-    ) -> dioxus_html::SelectionData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    #[inline(always)]
-    fn convert_toggle_data(
-        &self,
-        event: &dioxus_html::PlatformEventData,
-    ) -> dioxus_html::ToggleData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    #[inline(always)]
-    fn convert_touch_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::TouchData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    #[inline(always)]
-    fn convert_transition_data(
-        &self,
-        event: &dioxus_html::PlatformEventData,
-    ) -> dioxus_html::TransitionData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    #[inline(always)]
-    fn convert_wheel_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::WheelData {
-        downcast_event(event).raw.clone().into()
-    }
-}
-
-/// A extension trait for web-sys events that provides a way to get the event as a web-sys event.
-pub trait WebEventExt<E> {
-    /// Try to downcast this event as a `web-sys` event.
-    fn try_as_web_event(&self) -> Option<E>;
-
-    /// Downcast this event as a `web-sys` event.
-    #[inline(always)]
-    fn as_web_event(&self) -> E
-    where
-        E: 'static,
-    {
-        self.try_as_web_event().unwrap_or_else(|| {
-            panic!(
-                "Error downcasting to `web-sys`, event should be a {}.",
-                std::any::type_name::<E>()
-            )
-        })
-    }
-}
-
-impl WebEventExt<web_sys::AnimationEvent> for dioxus_html::AnimationData {
-    #[inline(always)]
-    fn try_as_web_event(&self) -> Option<web_sys::AnimationEvent> {
-        self.downcast::<web_sys::AnimationEvent>().cloned()
-    }
-}
-
-impl WebEventExt<web_sys::Event> for dioxus_html::ClipboardData {
-    #[inline(always)]
-    fn try_as_web_event(&self) -> Option<web_sys::Event> {
-        self.downcast::<web_sys::Event>().cloned()
-    }
-}
-
-impl WebEventExt<web_sys::CompositionEvent> for dioxus_html::CompositionData {
-    #[inline(always)]
-    fn try_as_web_event(&self) -> Option<web_sys::CompositionEvent> {
-        self.downcast::<web_sys::CompositionEvent>().cloned()
-    }
-}
-
-impl WebEventExt<web_sys::MouseEvent> for dioxus_html::DragData {
-    #[inline(always)]
-    fn try_as_web_event(&self) -> Option<web_sys::MouseEvent> {
-        self.downcast::<WebDragData>()
-            .map(|data| &data.raw)
-            .cloned()
-    }
-}
-
-impl WebEventExt<web_sys::FocusEvent> for dioxus_html::FocusData {
-    #[inline(always)]
-    fn try_as_web_event(&self) -> Option<web_sys::FocusEvent> {
-        self.downcast::<web_sys::FocusEvent>().cloned()
-    }
-}
-
-impl WebEventExt<web_sys::Event> for dioxus_html::FormData {
-    #[inline(always)]
-    fn try_as_web_event(&self) -> Option<web_sys::Event> {
-        self.downcast::<web_sys::Event>().cloned()
-    }
-}
-
-impl WebEventExt<WebImageEvent> for dioxus_html::ImageData {
-    #[inline(always)]
-    fn try_as_web_event(&self) -> Option<WebImageEvent> {
-        self.downcast::<WebImageEvent>().cloned()
-    }
-}
-
-impl WebEventExt<web_sys::KeyboardEvent> for dioxus_html::KeyboardData {
-    #[inline(always)]
-    fn try_as_web_event(&self) -> Option<web_sys::KeyboardEvent> {
-        self.downcast::<web_sys::KeyboardEvent>().cloned()
-    }
-}
-
-impl WebEventExt<web_sys::Event> for dioxus_html::MediaData {
-    #[inline(always)]
-    fn try_as_web_event(&self) -> Option<web_sys::Event> {
-        self.downcast::<web_sys::Event>().cloned()
-    }
-}
-
-impl WebEventExt<web_sys::Element> for MountedData {
-    #[inline(always)]
-    fn try_as_web_event(&self) -> Option<web_sys::Element> {
-        self.downcast::<web_sys::Element>().cloned()
-    }
-}
-
-impl WebEventExt<web_sys::MouseEvent> for dioxus_html::MouseData {
-    #[inline(always)]
-    fn try_as_web_event(&self) -> Option<web_sys::MouseEvent> {
-        self.downcast::<web_sys::MouseEvent>().cloned()
-    }
-}
-
-impl WebEventExt<web_sys::PointerEvent> for dioxus_html::PointerData {
-    #[inline(always)]
-    fn try_as_web_event(&self) -> Option<web_sys::PointerEvent> {
-        self.downcast::<web_sys::PointerEvent>().cloned()
-    }
-}
-
-impl WebEventExt<web_sys::Event> for ScrollData {
-    #[inline(always)]
-    fn try_as_web_event(&self) -> Option<web_sys::Event> {
-        self.downcast::<web_sys::Event>().cloned()
-    }
-}
-
-impl WebEventExt<web_sys::Event> for dioxus_html::SelectionData {
-    #[inline(always)]
-    fn try_as_web_event(&self) -> Option<web_sys::Event> {
-        self.downcast::<web_sys::Event>().cloned()
-    }
-}
-
-impl WebEventExt<web_sys::Event> for dioxus_html::ToggleData {
-    #[inline(always)]
-    fn try_as_web_event(&self) -> Option<web_sys::Event> {
-        self.downcast::<web_sys::Event>().cloned()
-    }
-}
-
-impl WebEventExt<web_sys::TouchEvent> for dioxus_html::TouchData {
-    #[inline(always)]
-    fn try_as_web_event(&self) -> Option<web_sys::TouchEvent> {
-        self.downcast::<web_sys::TouchEvent>().cloned()
-    }
-}
-
-impl WebEventExt<web_sys::TransitionEvent> for dioxus_html::TransitionData {
-    #[inline(always)]
-    fn try_as_web_event(&self) -> Option<web_sys::TransitionEvent> {
-        self.downcast::<web_sys::TransitionEvent>().cloned()
-    }
-}
-
-impl WebEventExt<web_sys::WheelEvent> for dioxus_html::WheelData {
-    #[inline(always)]
-    fn try_as_web_event(&self) -> Option<web_sys::WheelEvent> {
-        self.downcast::<web_sys::WheelEvent>().cloned()
-    }
-}
-
-impl WebEventExt<web_sys::ResizeObserverEntry> for dioxus_html::ResizeData {
-    #[inline(always)]
-    fn try_as_web_event(&self) -> Option<web_sys::ResizeObserverEntry> {
-        self.downcast::<web_sys::CustomEvent>()
-            .and_then(|e| e.detail().dyn_into::<web_sys::ResizeObserverEntry>().ok())
-    }
-}
-
-struct GenericWebSysEvent {
-    raw: Event,
-    element: Element,
-}
-
-// todo: some of these events are being casted to the wrong event type.
-// We need tests that simulate clicks/etc and make sure every event type works.
-pub(crate) fn virtual_event_from_websys_event(
-    event: web_sys::Event,
-    target: Element,
-) -> PlatformEventData {
-    PlatformEventData::new(Box::new(GenericWebSysEvent {
-        raw: event,
-        element: target,
-    }))
-}
-
-pub(crate) fn load_document() -> Document {
-    web_sys::window()
-        .expect("should have access to the Window")
-        .document()
-        .expect("should have access to the Document")
-}
-
-#[derive(Clone)]
-struct WebImageEvent {
-    raw: Event,
-    error: bool,
-}
-
-impl WebImageEvent {
-    fn new(raw: Event, error: bool) -> Self {
-        Self { raw, error }
-    }
-}
-
-impl HasImageData for WebImageEvent {
-    fn load_error(&self) -> bool {
-        self.error
-    }
-
-    fn as_any(&self) -> &dyn Any {
-        &self.raw as &dyn Any
-    }
-}
-
-struct WebFormData {
-    element: Element,
-    raw: Event,
-}
-
-impl WebFormData {
-    fn new(element: Element, raw: Event) -> Self {
-        Self { element, raw }
-    }
-}
-
-impl HasFormData for WebFormData {
-    fn value(&self) -> String {
-        let target = &self.element;
-        target
-        .dyn_ref()
-        .map(|input: &web_sys::HtmlInputElement| {
-            // todo: special case more input types
-            match input.type_().as_str() {
-                "checkbox" => {
-                    match input.checked() {
-                        true => "true".to_string(),
-                        false => "false".to_string(),
-                    }
-                },
-                _ => {
-                    input.value()
-                }
-            }
-        })
-        .or_else(|| {
-            target
-                .dyn_ref()
-                .map(|input: &web_sys::HtmlTextAreaElement| input.value())
-        })
-        // select elements are NOT input events - because - why woudn't they be??
-        .or_else(|| {
-            target
-                .dyn_ref()
-                .map(|input: &web_sys::HtmlSelectElement| input.value())
-        })
-        .or_else(|| {
-            target
-                .dyn_ref::<web_sys::HtmlElement>()
-                .unwrap()
-                .text_content()
-        })
-        .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener")
-    }
-
-    fn values(&self) -> HashMap<String, FormValue> {
-        let mut values = HashMap::new();
-
-        fn insert_value(map: &mut HashMap<String, FormValue>, key: String, new_value: String) {
-            map.entry(key.clone()).or_default().0.push(new_value);
-        }
-
-        // try to fill in form values
-        if let Some(form) = self.element.dyn_ref::<web_sys::HtmlFormElement>() {
-            let form_data = get_form_data(form);
-            for value in form_data.entries().into_iter().flatten() {
-                if let Ok(array) = value.dyn_into::<Array>() {
-                    if let Some(name) = array.get(0).as_string() {
-                        if let Ok(item_values) = array.get(1).dyn_into::<Array>() {
-                            item_values
-                                .iter()
-                                .filter_map(|v| v.as_string())
-                                .for_each(|v| insert_value(&mut values, name.clone(), v));
-                        } else if let Ok(item_value) = array.get(1).dyn_into::<JsValue>() {
-                            insert_value(&mut values, name, item_value.as_string().unwrap());
-                        }
-                    }
-                }
-            }
-        } else if let Some(select) = self.element.dyn_ref::<web_sys::HtmlSelectElement>() {
-            // try to fill in select element values
-            let options = get_select_data(select);
-            values.insert("options".to_string(), FormValue(options));
-        }
-
-        values
-    }
-
-    fn as_any(&self) -> &dyn Any {
-        &self.raw as &dyn Any
-    }
-}
-
-impl HasFileData for WebFormData {
-    #[cfg(feature = "file_engine")]
-    fn files(&self) -> Option<std::sync::Arc<dyn dioxus_html::FileEngine>> {
-        let files = self
-            .element
-            .dyn_ref()
-            .and_then(|input: &web_sys::HtmlInputElement| {
-                input.files().and_then(|files| {
-                    #[allow(clippy::arc_with_non_send_sync)]
-                    dioxus_html::WebFileEngine::new(files).map(|f| {
-                        std::sync::Arc::new(f) as std::sync::Arc<dyn dioxus_html::FileEngine>
-                    })
-                })
-            });
-
-        files
-    }
-}
-
-struct WebDragData {
-    raw: MouseEvent,
-}
-
-impl WebDragData {
-    fn new(raw: MouseEvent) -> Self {
-        Self { raw }
-    }
-}
-
-impl HasDragData for WebDragData {
-    fn as_any(&self) -> &dyn std::any::Any {
-        &self.raw as &dyn std::any::Any
-    }
-}
-
-impl HasMouseData for WebDragData {
-    fn as_any(&self) -> &dyn std::any::Any {
-        &self.raw as &dyn std::any::Any
-    }
-}
-
-impl PointerInteraction for WebDragData {
-    fn trigger_button(&self) -> Option<dioxus_html::input_data::MouseButton> {
-        self.raw.trigger_button()
-    }
-
-    fn held_buttons(&self) -> dioxus_html::input_data::MouseButtonSet {
-        self.raw.held_buttons()
-    }
-}
-
-impl ModifiersInteraction for WebDragData {
-    fn modifiers(&self) -> dioxus_html::prelude::Modifiers {
-        self.raw.modifiers()
-    }
-}
-
-impl InteractionElementOffset for WebDragData {
-    fn coordinates(&self) -> dioxus_html::geometry::Coordinates {
-        self.raw.coordinates()
-    }
-
-    fn element_coordinates(&self) -> dioxus_html::geometry::ElementPoint {
-        self.raw.element_coordinates()
-    }
-}
-
-impl InteractionLocation for WebDragData {
-    fn client_coordinates(&self) -> dioxus_html::geometry::ClientPoint {
-        self.raw.client_coordinates()
-    }
-
-    fn screen_coordinates(&self) -> dioxus_html::geometry::ScreenPoint {
-        self.raw.screen_coordinates()
-    }
-
-    fn page_coordinates(&self) -> dioxus_html::geometry::PagePoint {
-        self.raw.page_coordinates()
-    }
-}
-
-impl HasFileData for WebDragData {
-    #[cfg(feature = "file_engine")]
-    fn files(&self) -> Option<std::sync::Arc<dyn dioxus_html::FileEngine>> {
-        let files = self
-            .raw
-            .dyn_ref::<web_sys::DragEvent>()
-            .and_then(|drag_event| {
-                drag_event.data_transfer().and_then(|dt| {
-                    dt.files().and_then(|files| {
-                        #[allow(clippy::arc_with_non_send_sync)]
-                        dioxus_html::WebFileEngine::new(files).map(|f| {
-                            std::sync::Arc::new(f) as std::sync::Arc<dyn dioxus_html::FileEngine>
-                        })
-                    })
-                })
-            });
-
-        files
-    }
-}
-
-// web-sys does not expose the keys api for form data, so we need to manually bind to it
-#[wasm_bindgen(inline_js = r#"
-export function get_form_data(form) {
-    let values = new Map();
-    const formData = new FormData(form);
-
-    for (let name of formData.keys()) {
-        values.set(name, formData.getAll(name));
-    }
-
-    return values;
-}
-"#)]
-extern "C" {
-    fn get_form_data(form: &web_sys::HtmlFormElement) -> js_sys::Map;
-}
-
-// web-sys does not expose the keys api for select data, so we need to manually bind to it
-#[wasm_bindgen(inline_js = r#"
-export function get_select_data(select) {
-    let values = [];
-    for (let i = 0; i < select.options.length; i++) {
-      let option = select.options[i];
-      if (option.selected) {
-        values.push(option.value.toString());
-      }
-    }
-
-    return values;
-}
-"#)]
-extern "C" {
-    fn get_select_data(select: &web_sys::HtmlSelectElement) -> Vec<String>;
-}

+ 32 - 0
packages/web/src/events/animation.rs

@@ -0,0 +1,32 @@
+use dioxus_html::HasAnimationData;
+use web_sys::AnimationEvent;
+
+use super::{Synthetic, WebEventExt};
+
+impl HasAnimationData for Synthetic<AnimationEvent> {
+    fn animation_name(&self) -> String {
+        self.event.animation_name()
+    }
+
+    fn pseudo_element(&self) -> String {
+        self.event.pseudo_element()
+    }
+
+    fn elapsed_time(&self) -> f32 {
+        self.event.elapsed_time()
+    }
+
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+}
+
+impl WebEventExt for dioxus_html::AnimationData {
+    type WebEvent = web_sys::AnimationEvent;
+
+    #[inline(always)]
+    fn try_as_web_event(&self) -> Option<web_sys::AnimationEvent> {
+        self.downcast::<Synthetic<web_sys::AnimationEvent>>()
+            .map(|e| e.event.clone())
+    }
+}

+ 26 - 0
packages/web/src/events/clipboard.rs

@@ -0,0 +1,26 @@
+use dioxus_html::HasClipboardData;
+use web_sys::Event;
+
+use super::{Synthetic, WebEventExt};
+
+impl From<&Event> for Synthetic<Event> {
+    fn from(e: &Event) -> Self {
+        Synthetic::new(e.clone())
+    }
+}
+
+impl HasClipboardData for Synthetic<Event> {
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+}
+
+impl WebEventExt for dioxus_html::ClipboardData {
+    type WebEvent = web_sys::Event;
+
+    #[inline(always)]
+    fn try_as_web_event(&self) -> Option<Self::WebEvent> {
+        self.downcast::<Synthetic<web_sys::Event>>()
+            .map(|e| e.event.clone())
+    }
+}

+ 24 - 0
packages/web/src/events/composition.rs

@@ -0,0 +1,24 @@
+use dioxus_html::HasCompositionData;
+use web_sys::CompositionEvent;
+
+use super::{Synthetic, WebEventExt};
+
+impl HasCompositionData for Synthetic<CompositionEvent> {
+    fn data(&self) -> std::string::String {
+        self.event.data().unwrap_or_default()
+    }
+
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+}
+
+impl WebEventExt for dioxus_html::CompositionData {
+    type WebEvent = web_sys::CompositionEvent;
+
+    #[inline(always)]
+    fn try_as_web_event(&self) -> Option<Self::WebEvent> {
+        self.downcast::<Synthetic<web_sys::CompositionEvent>>()
+            .map(|e| e.event.clone())
+    }
+}

+ 108 - 0
packages/web/src/events/drag.rs

@@ -0,0 +1,108 @@
+use dioxus_html::{
+    prelude::{
+        InteractionElementOffset, InteractionLocation, ModifiersInteraction, PointerInteraction,
+    },
+    HasDragData, HasFileData, HasMouseData,
+};
+use web_sys::MouseEvent;
+
+use super::{Synthetic, WebEventExt};
+
+pub(crate) struct WebDragData {
+    raw: Synthetic<MouseEvent>,
+}
+
+impl WebDragData {
+    pub fn new(raw: MouseEvent) -> Self {
+        Self {
+            raw: Synthetic::new(raw),
+        }
+    }
+}
+
+impl HasDragData for WebDragData {
+    fn as_any(&self) -> &dyn std::any::Any {
+        &self.raw as &dyn std::any::Any
+    }
+}
+
+impl HasMouseData for WebDragData {
+    fn as_any(&self) -> &dyn std::any::Any {
+        &self.raw as &dyn std::any::Any
+    }
+}
+
+impl PointerInteraction for WebDragData {
+    fn trigger_button(&self) -> Option<dioxus_html::input_data::MouseButton> {
+        self.raw.trigger_button()
+    }
+
+    fn held_buttons(&self) -> dioxus_html::input_data::MouseButtonSet {
+        self.raw.held_buttons()
+    }
+}
+
+impl ModifiersInteraction for WebDragData {
+    fn modifiers(&self) -> dioxus_html::prelude::Modifiers {
+        self.raw.modifiers()
+    }
+}
+
+impl InteractionElementOffset for WebDragData {
+    fn coordinates(&self) -> dioxus_html::geometry::Coordinates {
+        self.raw.coordinates()
+    }
+
+    fn element_coordinates(&self) -> dioxus_html::geometry::ElementPoint {
+        self.raw.element_coordinates()
+    }
+}
+
+impl InteractionLocation for WebDragData {
+    fn client_coordinates(&self) -> dioxus_html::geometry::ClientPoint {
+        self.raw.client_coordinates()
+    }
+
+    fn screen_coordinates(&self) -> dioxus_html::geometry::ScreenPoint {
+        self.raw.screen_coordinates()
+    }
+
+    fn page_coordinates(&self) -> dioxus_html::geometry::PagePoint {
+        self.raw.page_coordinates()
+    }
+}
+
+impl HasFileData for WebDragData {
+    #[cfg(feature = "file_engine")]
+    fn files(&self) -> Option<std::sync::Arc<dyn dioxus_html::FileEngine>> {
+        use wasm_bindgen::JsCast;
+
+        let files = self
+            .raw
+            .event
+            .dyn_ref::<web_sys::DragEvent>()
+            .and_then(|drag_event| {
+                drag_event.data_transfer().and_then(|dt| {
+                    dt.files().and_then(|files| {
+                        #[allow(clippy::arc_with_non_send_sync)]
+                        crate::file_engine::WebFileEngine::new(files).map(|f| {
+                            std::sync::Arc::new(f) as std::sync::Arc<dyn dioxus_html::FileEngine>
+                        })
+                    })
+                })
+            });
+
+        files
+    }
+}
+
+impl WebEventExt for dioxus_html::DragData {
+    type WebEvent = web_sys::MouseEvent;
+
+    #[inline(always)]
+    fn try_as_web_event(&self) -> Option<web_sys::MouseEvent> {
+        self.downcast::<WebDragData>()
+            .map(|data| &data.raw.event)
+            .cloned()
+    }
+}

+ 24 - 0
packages/web/src/events/file.rs

@@ -0,0 +1,24 @@
+use dioxus_html::HasFileData;
+
+use super::Synthetic;
+
+impl HasFileData for Synthetic<web_sys::Event> {
+    #[cfg(feature = "file_engine")]
+    fn files(&self) -> Option<std::sync::Arc<dyn dioxus_html::FileEngine>> {
+        use wasm_bindgen::JsCast;
+
+        let files = self
+            .event
+            .dyn_ref()
+            .and_then(|input: &web_sys::HtmlInputElement| {
+                input.files().and_then(|files| {
+                    #[allow(clippy::arc_with_non_send_sync)]
+                    crate::file_engine::WebFileEngine::new(files).map(|f| {
+                        std::sync::Arc::new(f) as std::sync::Arc<dyn dioxus_html::FileEngine>
+                    })
+                })
+            });
+
+        files
+    }
+}

+ 19 - 0
packages/web/src/events/focus.rs

@@ -0,0 +1,19 @@
+use dioxus_html::HasFocusData;
+
+use super::{Synthetic, WebEventExt};
+
+impl HasFocusData for Synthetic<web_sys::FocusEvent> {
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+}
+
+impl WebEventExt for dioxus_html::FocusData {
+    type WebEvent = web_sys::FocusEvent;
+
+    #[inline(always)]
+    fn try_as_web_event(&self) -> Option<Self::WebEvent> {
+        self.downcast::<Synthetic<web_sys::FocusEvent>>()
+            .map(|e| e.event.clone())
+    }
+}

+ 160 - 0
packages/web/src/events/form.rs

@@ -0,0 +1,160 @@
+use std::{any::Any, collections::HashMap};
+
+use dioxus_html::{FormValue, HasFileData, HasFormData};
+use js_sys::Array;
+use wasm_bindgen::JsValue;
+use wasm_bindgen::{prelude::wasm_bindgen, JsCast};
+use web_sys::{Element, Event};
+
+use super::WebEventExt;
+
+pub(crate) struct WebFormData {
+    element: Element,
+    raw: Event,
+}
+
+impl WebEventExt for dioxus_html::FormData {
+    type WebEvent = Event;
+
+    #[inline(always)]
+    fn try_as_web_event(&self) -> Option<Self::WebEvent> {
+        self.downcast::<WebFormData>().map(|e| e.raw.clone())
+    }
+}
+
+impl WebFormData {
+    pub fn new(element: Element, raw: Event) -> Self {
+        Self { element, raw }
+    }
+}
+
+impl HasFormData for WebFormData {
+    fn value(&self) -> String {
+        let target = &self.element;
+        target
+        .dyn_ref()
+        .map(|input: &web_sys::HtmlInputElement| {
+            // todo: special case more input types
+            match input.type_().as_str() {
+                "checkbox" => {
+                    match input.checked() {
+                        true => "true".to_string(),
+                        false => "false".to_string(),
+                    }
+                },
+                _ => {
+                    input.value()
+                }
+            }
+        })
+        .or_else(|| {
+            target
+                .dyn_ref()
+                .map(|input: &web_sys::HtmlTextAreaElement| input.value())
+        })
+        // select elements are NOT input events - because - why woudn't they be??
+        .or_else(|| {
+            target
+                .dyn_ref()
+                .map(|input: &web_sys::HtmlSelectElement| input.value())
+        })
+        .or_else(|| {
+            target
+                .dyn_ref::<web_sys::HtmlElement>()
+                .unwrap()
+                .text_content()
+        })
+        .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener")
+    }
+
+    fn values(&self) -> HashMap<String, FormValue> {
+        let mut values = HashMap::new();
+
+        fn insert_value(map: &mut HashMap<String, FormValue>, key: String, new_value: String) {
+            map.entry(key.clone()).or_default().0.push(new_value);
+        }
+
+        // try to fill in form values
+        if let Some(form) = self.element.dyn_ref::<web_sys::HtmlFormElement>() {
+            let form_data = get_form_data(form);
+            for value in form_data.entries().into_iter().flatten() {
+                if let Ok(array) = value.dyn_into::<Array>() {
+                    if let Some(name) = array.get(0).as_string() {
+                        if let Ok(item_values) = array.get(1).dyn_into::<Array>() {
+                            item_values
+                                .iter()
+                                .filter_map(|v| v.as_string())
+                                .for_each(|v| insert_value(&mut values, name.clone(), v));
+                        } else if let Ok(item_value) = array.get(1).dyn_into::<JsValue>() {
+                            insert_value(&mut values, name, item_value.as_string().unwrap());
+                        }
+                    }
+                }
+            }
+        } else if let Some(select) = self.element.dyn_ref::<web_sys::HtmlSelectElement>() {
+            // try to fill in select element values
+            let options = get_select_data(select);
+            values.insert("options".to_string(), FormValue(options));
+        }
+
+        values
+    }
+
+    fn as_any(&self) -> &dyn Any {
+        &self.raw as &dyn Any
+    }
+}
+
+impl HasFileData for WebFormData {
+    #[cfg(feature = "file_engine")]
+    fn files(&self) -> Option<std::sync::Arc<dyn dioxus_html::FileEngine>> {
+        let files = self
+            .element
+            .dyn_ref()
+            .and_then(|input: &web_sys::HtmlInputElement| {
+                input.files().and_then(|files| {
+                    #[allow(clippy::arc_with_non_send_sync)]
+                    crate::file_engine::WebFileEngine::new(files).map(|f| {
+                        std::sync::Arc::new(f) as std::sync::Arc<dyn dioxus_html::FileEngine>
+                    })
+                })
+            });
+
+        files
+    }
+}
+
+// web-sys does not expose the keys api for select data, so we need to manually bind to it
+#[wasm_bindgen(inline_js = r#"
+export function get_select_data(select) {
+    let values = [];
+    for (let i = 0; i < select.options.length; i++) {
+      let option = select.options[i];
+      if (option.selected) {
+        values.push(option.value.toString());
+      }
+    }
+
+    return values;
+}
+"#)]
+extern "C" {
+    fn get_select_data(select: &web_sys::HtmlSelectElement) -> Vec<String>;
+}
+
+// web-sys does not expose the keys api for form data, so we need to manually bind to it
+#[wasm_bindgen(inline_js = r#"
+export function get_form_data(form) {
+    let values = new Map();
+    const formData = new FormData(form);
+
+    for (let name of formData.keys()) {
+        values.set(name, formData.getAll(name));
+    }
+
+    return values;
+}
+"#)]
+extern "C" {
+    fn get_form_data(form: &web_sys::HtmlFormElement) -> js_sys::Map;
+}

+ 67 - 0
packages/web/src/events/keyboard.rs

@@ -0,0 +1,67 @@
+use std::str::FromStr;
+
+use dioxus_html::{
+    input_data::decode_key_location,
+    prelude::{Code, Key, Location, Modifiers, ModifiersInteraction},
+    HasKeyboardData,
+};
+use web_sys::KeyboardEvent;
+
+use super::{Synthetic, WebEventExt};
+
+impl HasKeyboardData for Synthetic<KeyboardEvent> {
+    fn key(&self) -> Key {
+        Key::from_str(self.event.key().as_str()).unwrap_or(Key::Unidentified)
+    }
+
+    fn code(&self) -> Code {
+        Code::from_str(self.event.code().as_str()).unwrap_or(Code::Unidentified)
+    }
+
+    fn location(&self) -> Location {
+        decode_key_location(self.event.location() as usize)
+    }
+
+    fn is_auto_repeating(&self) -> bool {
+        self.event.repeat()
+    }
+
+    fn is_composing(&self) -> bool {
+        self.event.is_composing()
+    }
+
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+}
+
+impl ModifiersInteraction for Synthetic<KeyboardEvent> {
+    fn modifiers(&self) -> Modifiers {
+        let mut modifiers = Modifiers::empty();
+
+        if self.event.alt_key() {
+            modifiers.insert(Modifiers::ALT);
+        }
+        if self.event.ctrl_key() {
+            modifiers.insert(Modifiers::CONTROL);
+        }
+        if self.event.meta_key() {
+            modifiers.insert(Modifiers::META);
+        }
+        if self.event.shift_key() {
+            modifiers.insert(Modifiers::SHIFT);
+        }
+
+        modifiers
+    }
+}
+
+impl WebEventExt for dioxus_html::KeyboardData {
+    type WebEvent = web_sys::KeyboardEvent;
+
+    #[inline(always)]
+    fn try_as_web_event(&self) -> Option<web_sys::KeyboardEvent> {
+        self.downcast::<Synthetic<web_sys::KeyboardEvent>>()
+            .map(|e| e.event.clone())
+    }
+}

+ 37 - 0
packages/web/src/events/load.rs

@@ -0,0 +1,37 @@
+use std::any::Any;
+
+use dioxus_html::HasImageData;
+use web_sys::Event;
+
+use super::WebEventExt;
+
+#[derive(Clone)]
+pub(crate) struct WebImageEvent {
+    raw: Event,
+    error: bool,
+}
+
+impl WebImageEvent {
+    pub fn new(raw: Event, error: bool) -> Self {
+        Self { raw, error }
+    }
+}
+
+impl HasImageData for WebImageEvent {
+    fn load_error(&self) -> bool {
+        self.error
+    }
+
+    fn as_any(&self) -> &dyn Any {
+        &self.raw as &dyn Any
+    }
+}
+
+impl WebEventExt for dioxus_html::ImageData {
+    type WebEvent = Event;
+
+    #[inline(always)]
+    fn try_as_web_event(&self) -> Option<Event> {
+        self.downcast::<WebImageEvent>().map(|e| e.raw.clone())
+    }
+}

+ 19 - 0
packages/web/src/events/media.rs

@@ -0,0 +1,19 @@
+use dioxus_html::HasMediaData;
+
+use super::{Synthetic, WebEventExt};
+
+impl HasMediaData for Synthetic<web_sys::Event> {
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+}
+
+impl WebEventExt for dioxus_html::MediaData {
+    type WebEvent = web_sys::Event;
+
+    #[inline(always)]
+    fn try_as_web_event(&self) -> Option<Self::WebEvent> {
+        self.downcast::<Synthetic<web_sys::Event>>()
+            .map(|e| e.event.clone())
+    }
+}

+ 278 - 0
packages/web/src/events/mod.rs

@@ -0,0 +1,278 @@
+use dioxus_html::{
+    DragData, FormData, HtmlEventConverter, ImageData, MountedData, PlatformEventData,
+};
+use drag::WebDragData;
+use form::WebFormData;
+use load::WebImageEvent;
+use wasm_bindgen::JsCast;
+use web_sys::{Document, Element, Event};
+
+mod animation;
+mod clipboard;
+mod composition;
+mod drag;
+mod file;
+mod focus;
+mod form;
+mod keyboard;
+mod load;
+mod media;
+#[cfg(feature = "mounted")]
+mod mounted;
+mod mouse;
+mod pointer;
+mod resize;
+mod selection;
+mod toggle;
+mod touch;
+mod transition;
+mod wheel;
+
+/// A wrapper for the websys event that allows us to give it the impls from dioxus-html
+pub(crate) struct Synthetic<T: 'static> {
+    /// The inner web sys event that the synthetic event wraps
+    pub event: T,
+}
+
+impl<T: 'static> Synthetic<T> {
+    /// Create a new synthetic event from a web sys event
+    pub fn new(event: T) -> Self {
+        Self { event }
+    }
+}
+
+pub(crate) struct WebEventConverter;
+
+#[inline(always)]
+fn downcast_event(event: &dioxus_html::PlatformEventData) -> &GenericWebSysEvent {
+    event
+        .downcast::<GenericWebSysEvent>()
+        .expect("event should be a GenericWebSysEvent")
+}
+
+impl HtmlEventConverter for WebEventConverter {
+    #[inline(always)]
+    fn convert_animation_data(
+        &self,
+        event: &dioxus_html::PlatformEventData,
+    ) -> dioxus_html::AnimationData {
+        Synthetic::<web_sys::AnimationEvent>::from(downcast_event(event).raw.clone()).into()
+    }
+
+    #[inline(always)]
+    fn convert_clipboard_data(
+        &self,
+        event: &dioxus_html::PlatformEventData,
+    ) -> dioxus_html::ClipboardData {
+        Synthetic::new(downcast_event(event).raw.clone()).into()
+    }
+
+    #[inline(always)]
+    fn convert_composition_data(
+        &self,
+        event: &dioxus_html::PlatformEventData,
+    ) -> dioxus_html::CompositionData {
+        Synthetic::<web_sys::CompositionEvent>::from(downcast_event(event).raw.clone()).into()
+    }
+
+    #[inline(always)]
+    fn convert_drag_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::DragData {
+        let event = downcast_event(event);
+        DragData::new(WebDragData::new(event.raw.clone().unchecked_into()))
+    }
+
+    #[inline(always)]
+    fn convert_focus_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::FocusData {
+        Synthetic::<web_sys::FocusEvent>::from(downcast_event(event).raw.clone()).into()
+    }
+
+    #[inline(always)]
+    fn convert_form_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::FormData {
+        let event = downcast_event(event);
+        FormData::new(WebFormData::new(event.element.clone(), event.raw.clone()))
+    }
+
+    #[inline(always)]
+    fn convert_image_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::ImageData {
+        let event = downcast_event(event);
+        let error = event.raw.type_() == "error";
+        ImageData::new(WebImageEvent::new(event.raw.clone(), error))
+    }
+
+    #[inline(always)]
+    fn convert_keyboard_data(
+        &self,
+        event: &dioxus_html::PlatformEventData,
+    ) -> dioxus_html::KeyboardData {
+        Synthetic::<web_sys::KeyboardEvent>::from(downcast_event(event).raw.clone()).into()
+    }
+
+    #[inline(always)]
+    fn convert_media_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::MediaData {
+        Synthetic::new(downcast_event(event).raw.clone()).into()
+    }
+
+    #[allow(unused_variables)]
+    #[inline(always)]
+    fn convert_mounted_data(&self, event: &dioxus_html::PlatformEventData) -> MountedData {
+        #[cfg(feature = "mounted")]
+        {
+            Synthetic::new(
+                event
+                    .downcast::<web_sys::Element>()
+                    .expect("event should be a web_sys::Element")
+                    .clone(),
+            )
+            .into()
+        }
+        #[cfg(not(feature = "mounted"))]
+        {
+            panic!("mounted events are not supported without the mounted feature on the dioxus-web crate enabled")
+        }
+    }
+
+    #[inline(always)]
+    fn convert_mouse_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::MouseData {
+        Synthetic::<web_sys::MouseEvent>::from(downcast_event(event).raw.clone()).into()
+    }
+
+    #[inline(always)]
+    fn convert_pointer_data(
+        &self,
+        event: &dioxus_html::PlatformEventData,
+    ) -> dioxus_html::PointerData {
+        Synthetic::<web_sys::PointerEvent>::from(downcast_event(event).raw.clone()).into()
+    }
+
+    #[inline(always)]
+    fn convert_resize_data(
+        &self,
+        event: &dioxus_html::PlatformEventData,
+    ) -> dioxus_html::ResizeData {
+        Synthetic::<web_sys::ResizeObserverEntry>::from(downcast_event(event).raw.clone()).into()
+    }
+
+    #[inline(always)]
+    fn convert_scroll_data(
+        &self,
+        event: &dioxus_html::PlatformEventData,
+    ) -> dioxus_html::ScrollData {
+        Synthetic::new(downcast_event(event).raw.clone()).into()
+    }
+
+    #[inline(always)]
+    fn convert_selection_data(
+        &self,
+        event: &dioxus_html::PlatformEventData,
+    ) -> dioxus_html::SelectionData {
+        Synthetic::new(downcast_event(event).raw.clone()).into()
+    }
+
+    #[inline(always)]
+    fn convert_toggle_data(
+        &self,
+        event: &dioxus_html::PlatformEventData,
+    ) -> dioxus_html::ToggleData {
+        Synthetic::new(downcast_event(event).raw.clone()).into()
+    }
+
+    #[inline(always)]
+    fn convert_touch_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::TouchData {
+        Synthetic::<web_sys::TouchEvent>::from(downcast_event(event).raw.clone()).into()
+    }
+
+    #[inline(always)]
+    fn convert_transition_data(
+        &self,
+        event: &dioxus_html::PlatformEventData,
+    ) -> dioxus_html::TransitionData {
+        Synthetic::<web_sys::TransitionEvent>::from(downcast_event(event).raw.clone()).into()
+    }
+
+    #[inline(always)]
+    fn convert_wheel_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::WheelData {
+        Synthetic::<web_sys::WheelEvent>::from(downcast_event(event).raw.clone()).into()
+    }
+}
+
+/// A extension trait for web-sys events that provides a way to get the event as a web-sys event.
+pub trait WebEventExt {
+    /// The web specific event type
+    type WebEvent;
+
+    /// Try to downcast this event as a `web-sys` event.
+    fn try_as_web_event(&self) -> Option<Self::WebEvent>;
+
+    /// Downcast this event as a `web-sys` event.
+    #[inline(always)]
+    fn as_web_event(&self) -> Self::WebEvent
+    where
+        Self::WebEvent: 'static,
+    {
+        self.try_as_web_event().unwrap_or_else(|| {
+            panic!(
+                "Error downcasting to `web-sys`, event should be a {}.",
+                std::any::type_name::<Self::WebEvent>()
+            )
+        })
+    }
+}
+
+struct GenericWebSysEvent {
+    raw: Event,
+    element: Element,
+}
+
+// todo: some of these events are being casted to the wrong event type.
+// We need tests that simulate clicks/etc and make sure every event type works.
+pub(crate) fn virtual_event_from_websys_event(
+    event: web_sys::Event,
+    target: Element,
+) -> PlatformEventData {
+    PlatformEventData::new(Box::new(GenericWebSysEvent {
+        raw: event,
+        element: target,
+    }))
+}
+
+pub(crate) fn load_document() -> Document {
+    web_sys::window()
+        .expect("should have access to the Window")
+        .document()
+        .expect("should have access to the Document")
+}
+
+macro_rules! uncheck_convert {
+    ($t:ty) => {
+        impl From<Event> for Synthetic<$t> {
+            #[inline]
+            fn from(e: Event) -> Self {
+                let e: $t = e.unchecked_into();
+                Self::new(e)
+            }
+        }
+
+        impl From<&Event> for Synthetic<$t> {
+            #[inline]
+            fn from(e: &Event) -> Self {
+                let e: &$t = e.unchecked_ref();
+                Self::new(e.clone())
+            }
+        }
+    };
+    ($($t:ty),+ $(,)?) => {
+        $(uncheck_convert!($t);)+
+    };
+}
+
+uncheck_convert![
+    web_sys::CompositionEvent,
+    web_sys::KeyboardEvent,
+    web_sys::TouchEvent,
+    web_sys::PointerEvent,
+    web_sys::WheelEvent,
+    web_sys::AnimationEvent,
+    web_sys::TransitionEvent,
+    web_sys::MouseEvent,
+    web_sys::FocusEvent,
+];

+ 126 - 0
packages/web/src/events/mounted.rs

@@ -0,0 +1,126 @@
+use dioxus_html::{
+    geometry::euclid::{Point2D, Size2D},
+    MountedData,
+};
+use wasm_bindgen::JsCast;
+
+use super::{Synthetic, WebEventExt};
+
+impl dioxus_html::RenderedElementBacking for Synthetic<web_sys::Element> {
+    fn get_scroll_offset(
+        &self,
+    ) -> std::pin::Pin<
+        Box<
+            dyn std::future::Future<
+                Output = dioxus_html::MountedResult<dioxus_html::geometry::PixelsVector2D>,
+            >,
+        >,
+    > {
+        let left = self.event.scroll_left();
+        let top = self.event.scroll_top();
+        let result = Ok(dioxus_html::geometry::PixelsVector2D::new(
+            left as f64,
+            top as f64,
+        ));
+        Box::pin(async { result })
+    }
+
+    fn get_scroll_size(
+        &self,
+    ) -> std::pin::Pin<
+        Box<
+            dyn std::future::Future<
+                Output = dioxus_html::MountedResult<dioxus_html::geometry::PixelsSize>,
+            >,
+        >,
+    > {
+        let width = self.event.scroll_width();
+        let height = self.event.scroll_height();
+        let result = Ok(dioxus_html::geometry::PixelsSize::new(
+            width as f64,
+            height as f64,
+        ));
+        Box::pin(async { result })
+    }
+
+    fn get_client_rect(
+        &self,
+    ) -> std::pin::Pin<
+        Box<
+            dyn std::future::Future<
+                Output = dioxus_html::MountedResult<dioxus_html::geometry::PixelsRect>,
+            >,
+        >,
+    > {
+        let rect = self.event.get_bounding_client_rect();
+        let result = Ok(dioxus_html::geometry::PixelsRect::new(
+            Point2D::new(rect.left(), rect.top()),
+            Size2D::new(rect.width(), rect.height()),
+        ));
+        Box::pin(async { result })
+    }
+
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+
+    fn scroll_to(
+        &self,
+        behavior: dioxus_html::ScrollBehavior,
+    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = dioxus_html::MountedResult<()>>>> {
+        let options = web_sys::ScrollIntoViewOptions::new();
+        match behavior {
+            dioxus_html::ScrollBehavior::Instant => {
+                options.set_behavior(web_sys::ScrollBehavior::Instant);
+            }
+            dioxus_html::ScrollBehavior::Smooth => {
+                options.set_behavior(web_sys::ScrollBehavior::Smooth);
+            }
+        }
+        self.event
+            .scroll_into_view_with_scroll_into_view_options(&options);
+
+        Box::pin(async { Ok(()) })
+    }
+
+    fn set_focus(
+        &self,
+        focus: bool,
+    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = dioxus_html::MountedResult<()>>>> {
+        #[derive(Debug)]
+        struct FocusError(wasm_bindgen::JsValue);
+
+        impl std::fmt::Display for FocusError {
+            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                write!(f, "failed to focus element {:?}", self.0)
+            }
+        }
+
+        impl std::error::Error for FocusError {}
+
+        let result = self
+            .event
+            .dyn_ref::<web_sys::HtmlElement>()
+            .ok_or_else(|| {
+                dioxus_html::MountedError::OperationFailed(Box::new(FocusError(
+                    self.event.clone().into(),
+                )))
+            })
+            .and_then(|e| {
+                (if focus { e.focus() } else { e.blur() }).map_err(|err| {
+                    dioxus_html::MountedError::OperationFailed(Box::new(FocusError(err)))
+                })
+            });
+        Box::pin(async { result })
+    }
+}
+
+impl WebEventExt for MountedData {
+    type WebEvent = web_sys::Element;
+
+    #[inline(always)]
+    fn try_as_web_event(&self) -> Option<web_sys::Element> {
+        self.downcast::<Synthetic<web_sys::Element>>()
+            .map(|e| e.event.clone())
+    }
+}

+ 87 - 0
packages/web/src/events/mouse.rs

@@ -0,0 +1,87 @@
+use dioxus_html::{
+    geometry::{ClientPoint, ElementPoint, PagePoint, ScreenPoint},
+    input_data::{decode_mouse_button_set, MouseButton},
+    prelude::{
+        InteractionElementOffset, InteractionLocation, Modifiers, ModifiersInteraction,
+        PointerInteraction,
+    },
+    HasDragData, HasFileData, HasMouseData,
+};
+use web_sys::MouseEvent;
+
+use super::{Synthetic, WebEventExt};
+
+impl InteractionLocation for Synthetic<MouseEvent> {
+    fn client_coordinates(&self) -> ClientPoint {
+        ClientPoint::new(self.event.client_x().into(), self.event.client_y().into())
+    }
+
+    fn page_coordinates(&self) -> PagePoint {
+        PagePoint::new(self.event.page_x().into(), self.event.page_y().into())
+    }
+
+    fn screen_coordinates(&self) -> ScreenPoint {
+        ScreenPoint::new(self.event.screen_x().into(), self.event.screen_y().into())
+    }
+}
+
+impl InteractionElementOffset for Synthetic<MouseEvent> {
+    fn element_coordinates(&self) -> ElementPoint {
+        ElementPoint::new(self.event.offset_x().into(), self.event.offset_y().into())
+    }
+}
+
+impl ModifiersInteraction for Synthetic<MouseEvent> {
+    fn modifiers(&self) -> Modifiers {
+        let mut modifiers = Modifiers::empty();
+
+        if self.event.alt_key() {
+            modifiers.insert(Modifiers::ALT);
+        }
+        if self.event.ctrl_key() {
+            modifiers.insert(Modifiers::CONTROL);
+        }
+        if self.event.meta_key() {
+            modifiers.insert(Modifiers::META);
+        }
+        if self.event.shift_key() {
+            modifiers.insert(Modifiers::SHIFT);
+        }
+
+        modifiers
+    }
+}
+
+impl PointerInteraction for Synthetic<MouseEvent> {
+    fn held_buttons(&self) -> dioxus_html::input_data::MouseButtonSet {
+        decode_mouse_button_set(self.event.buttons())
+    }
+
+    fn trigger_button(&self) -> Option<MouseButton> {
+        Some(MouseButton::from_web_code(self.event.button()))
+    }
+}
+
+impl HasMouseData for Synthetic<MouseEvent> {
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+}
+
+impl HasFileData for Synthetic<MouseEvent> {}
+
+impl HasDragData for Synthetic<MouseEvent> {
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+}
+
+impl WebEventExt for dioxus_html::MouseData {
+    type WebEvent = web_sys::MouseEvent;
+
+    #[inline(always)]
+    fn try_as_web_event(&self) -> Option<web_sys::MouseEvent> {
+        self.downcast::<Synthetic<web_sys::MouseEvent>>()
+            .map(|e| e.event.clone())
+    }
+}

+ 119 - 0
packages/web/src/events/pointer.rs

@@ -0,0 +1,119 @@
+use dioxus_html::{
+    geometry::{ClientPoint, ElementPoint, PagePoint, ScreenPoint},
+    input_data::{decode_mouse_button_set, MouseButton},
+    prelude::{
+        InteractionElementOffset, InteractionLocation, Modifiers, ModifiersInteraction,
+        PointerInteraction,
+    },
+    HasPointerData,
+};
+use web_sys::PointerEvent;
+
+use super::{Synthetic, WebEventExt};
+
+impl HasPointerData for Synthetic<PointerEvent> {
+    fn pointer_id(&self) -> i32 {
+        self.event.pointer_id()
+    }
+
+    fn width(&self) -> i32 {
+        self.event.width()
+    }
+
+    fn height(&self) -> i32 {
+        self.event.height()
+    }
+
+    fn pressure(&self) -> f32 {
+        self.event.pressure()
+    }
+
+    fn tangential_pressure(&self) -> f32 {
+        self.event.tangential_pressure()
+    }
+
+    fn tilt_x(&self) -> i32 {
+        self.event.tilt_x()
+    }
+
+    fn tilt_y(&self) -> i32 {
+        self.event.tilt_y()
+    }
+
+    fn twist(&self) -> i32 {
+        self.event.twist()
+    }
+
+    fn pointer_type(&self) -> String {
+        self.event.pointer_type()
+    }
+
+    fn is_primary(&self) -> bool {
+        self.event.is_primary()
+    }
+
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+}
+
+impl InteractionLocation for Synthetic<PointerEvent> {
+    fn client_coordinates(&self) -> ClientPoint {
+        ClientPoint::new(self.event.client_x().into(), self.event.client_y().into())
+    }
+
+    fn screen_coordinates(&self) -> ScreenPoint {
+        ScreenPoint::new(self.event.screen_x().into(), self.event.screen_y().into())
+    }
+
+    fn page_coordinates(&self) -> PagePoint {
+        PagePoint::new(self.event.page_x().into(), self.event.page_y().into())
+    }
+}
+
+impl InteractionElementOffset for Synthetic<PointerEvent> {
+    fn element_coordinates(&self) -> ElementPoint {
+        ElementPoint::new(self.event.offset_x().into(), self.event.offset_y().into())
+    }
+}
+
+impl ModifiersInteraction for Synthetic<PointerEvent> {
+    fn modifiers(&self) -> Modifiers {
+        let mut modifiers = Modifiers::empty();
+
+        if self.event.alt_key() {
+            modifiers.insert(Modifiers::ALT);
+        }
+        if self.event.ctrl_key() {
+            modifiers.insert(Modifiers::CONTROL);
+        }
+        if self.event.meta_key() {
+            modifiers.insert(Modifiers::META);
+        }
+        if self.event.shift_key() {
+            modifiers.insert(Modifiers::SHIFT);
+        }
+
+        modifiers
+    }
+}
+
+impl PointerInteraction for Synthetic<PointerEvent> {
+    fn held_buttons(&self) -> dioxus_html::input_data::MouseButtonSet {
+        decode_mouse_button_set(self.event.buttons())
+    }
+
+    fn trigger_button(&self) -> Option<MouseButton> {
+        Some(MouseButton::from_web_code(self.event.button()))
+    }
+}
+
+impl WebEventExt for dioxus_html::PointerData {
+    type WebEvent = web_sys::PointerEvent;
+
+    #[inline(always)]
+    fn try_as_web_event(&self) -> Option<web_sys::PointerEvent> {
+        self.downcast::<Synthetic<web_sys::PointerEvent>>()
+            .map(|e| e.event.clone())
+    }
+}

+ 57 - 0
packages/web/src/events/resize.rs

@@ -0,0 +1,57 @@
+use dioxus_html::{geometry::PixelsSize, HasResizeData, ResizeResult};
+use wasm_bindgen::JsCast;
+use web_sys::{CustomEvent, Event, ResizeObserverEntry};
+
+use super::{Synthetic, WebEventExt};
+
+impl From<Event> for Synthetic<ResizeObserverEntry> {
+    #[inline]
+    fn from(e: Event) -> Self {
+        <Synthetic<ResizeObserverEntry> as From<&Event>>::from(&e)
+    }
+}
+
+impl From<&Event> for Synthetic<ResizeObserverEntry> {
+    #[inline]
+    fn from(e: &Event) -> Self {
+        let e: &CustomEvent = e.unchecked_ref();
+        let value = e.detail();
+        Self::new(value.unchecked_into::<ResizeObserverEntry>())
+    }
+}
+
+impl HasResizeData for Synthetic<ResizeObserverEntry> {
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+
+    fn get_border_box_size(&self) -> ResizeResult<PixelsSize> {
+        extract_first_size(self.event.border_box_size())
+    }
+
+    fn get_content_box_size(&self) -> ResizeResult<PixelsSize> {
+        extract_first_size(self.event.content_box_size())
+    }
+}
+
+impl WebEventExt for dioxus_html::ResizeData {
+    type WebEvent = web_sys::ResizeObserverEntry;
+
+    #[inline(always)]
+    fn try_as_web_event(&self) -> Option<web_sys::ResizeObserverEntry> {
+        self.downcast::<Synthetic<web_sys::ResizeObserverEntry>>()
+            .map(|e| e.event.clone())
+    }
+}
+
+fn extract_first_size(resize_observer_output: js_sys::Array) -> ResizeResult<PixelsSize> {
+    let first = resize_observer_output.get(0);
+    let size = first.unchecked_into::<web_sys::ResizeObserverSize>();
+
+    // inline_size matches the width of the element if its writing-mode is horizontal, the height otherwise
+    let inline_size = size.inline_size();
+    // block_size matches the height of the element if its writing-mode is horizontal, the width otherwise
+    let block_size = size.block_size();
+
+    Ok(PixelsSize::new(inline_size, block_size))
+}

+ 19 - 0
packages/web/src/events/selection.rs

@@ -0,0 +1,19 @@
+use dioxus_html::HasSelectionData;
+
+use super::{Synthetic, WebEventExt};
+
+impl HasSelectionData for Synthetic<web_sys::Event> {
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+}
+
+impl WebEventExt for dioxus_html::SelectionData {
+    type WebEvent = web_sys::Event;
+
+    #[inline(always)]
+    fn try_as_web_event(&self) -> Option<Self::WebEvent> {
+        self.downcast::<Synthetic<web_sys::Event>>()
+            .map(|e| e.event.clone())
+    }
+}

+ 19 - 0
packages/web/src/events/toggle.rs

@@ -0,0 +1,19 @@
+use dioxus_html::HasToggleData;
+
+use super::{Synthetic, WebEventExt};
+
+impl HasToggleData for Synthetic<web_sys::Event> {
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+}
+
+impl WebEventExt for dioxus_html::ToggleData {
+    type WebEvent = web_sys::Event;
+
+    #[inline(always)]
+    fn try_as_web_event(&self) -> Option<Self::WebEvent> {
+        self.downcast::<Synthetic<web_sys::Event>>()
+            .map(|e| e.event.clone())
+    }
+}

+ 111 - 0
packages/web/src/events/touch.rs

@@ -0,0 +1,111 @@
+use dioxus_html::{
+    geometry::{ClientPoint, PagePoint, ScreenPoint},
+    prelude::{InteractionLocation, Modifiers, ModifiersInteraction},
+    HasTouchPointData, TouchPoint,
+};
+use web_sys::{Touch, TouchEvent};
+
+use super::{Synthetic, WebEventExt};
+
+impl ModifiersInteraction for Synthetic<TouchEvent> {
+    fn modifiers(&self) -> Modifiers {
+        let mut modifiers = Modifiers::empty();
+
+        if self.event.alt_key() {
+            modifiers.insert(Modifiers::ALT);
+        }
+        if self.event.ctrl_key() {
+            modifiers.insert(Modifiers::CONTROL);
+        }
+        if self.event.meta_key() {
+            modifiers.insert(Modifiers::META);
+        }
+        if self.event.shift_key() {
+            modifiers.insert(Modifiers::SHIFT);
+        }
+
+        modifiers
+    }
+}
+
+impl dioxus_html::events::HasTouchData for Synthetic<TouchEvent> {
+    fn touches(&self) -> Vec<TouchPoint> {
+        let touches = TouchEvent::touches(&self.event);
+        let mut result = Vec::with_capacity(touches.length() as usize);
+        for i in 0..touches.length() {
+            let touch = touches.get(i).unwrap();
+            result.push(TouchPoint::new(Synthetic::new(touch)));
+        }
+        result
+    }
+
+    fn touches_changed(&self) -> Vec<TouchPoint> {
+        let touches = self.event.changed_touches();
+        let mut result = Vec::with_capacity(touches.length() as usize);
+        for i in 0..touches.length() {
+            let touch = touches.get(i).unwrap();
+            result.push(TouchPoint::new(Synthetic::new(touch)));
+        }
+        result
+    }
+
+    fn target_touches(&self) -> Vec<TouchPoint> {
+        let touches = self.event.target_touches();
+        let mut result = Vec::with_capacity(touches.length() as usize);
+        for i in 0..touches.length() {
+            let touch = touches.get(i).unwrap();
+            result.push(TouchPoint::new(Synthetic::new(touch)));
+        }
+        result
+    }
+
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+}
+
+impl HasTouchPointData for Synthetic<Touch> {
+    fn identifier(&self) -> i32 {
+        self.event.identifier()
+    }
+
+    fn radius(&self) -> ScreenPoint {
+        ScreenPoint::new(self.event.radius_x().into(), self.event.radius_y().into())
+    }
+
+    fn rotation(&self) -> f64 {
+        self.event.rotation_angle() as f64
+    }
+
+    fn force(&self) -> f64 {
+        self.event.force() as f64
+    }
+
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+}
+
+impl InteractionLocation for Synthetic<Touch> {
+    fn client_coordinates(&self) -> ClientPoint {
+        ClientPoint::new(self.event.client_x().into(), self.event.client_y().into())
+    }
+
+    fn screen_coordinates(&self) -> ScreenPoint {
+        ScreenPoint::new(self.event.screen_x().into(), self.event.screen_y().into())
+    }
+
+    fn page_coordinates(&self) -> PagePoint {
+        PagePoint::new(self.event.page_x().into(), self.event.page_y().into())
+    }
+}
+
+impl WebEventExt for dioxus_html::TouchData {
+    type WebEvent = web_sys::TouchEvent;
+
+    #[inline(always)]
+    fn try_as_web_event(&self) -> Option<web_sys::TouchEvent> {
+        self.downcast::<Synthetic<web_sys::TouchEvent>>()
+            .map(|e| e.event.clone())
+    }
+}

+ 22 - 0
packages/web/src/events/transition.rs

@@ -0,0 +1,22 @@
+use dioxus_html::HasTransitionData;
+use web_sys::TransitionEvent;
+
+use super::Synthetic;
+
+impl HasTransitionData for Synthetic<TransitionEvent> {
+    fn elapsed_time(&self) -> f32 {
+        self.event.elapsed_time()
+    }
+
+    fn property_name(&self) -> String {
+        self.event.property_name()
+    }
+
+    fn pseudo_element(&self) -> String {
+        self.event.pseudo_element()
+    }
+
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+}

+ 110 - 0
packages/web/src/events/wheel.rs

@@ -0,0 +1,110 @@
+use dioxus_html::{
+    geometry::{ClientPoint, ElementPoint, PagePoint, ScreenPoint},
+    input_data::{decode_mouse_button_set, MouseButton},
+    prelude::{
+        InteractionElementOffset, InteractionLocation, Modifiers, ModifiersInteraction,
+        PointerInteraction,
+    },
+    HasMouseData, HasScrollData, HasWheelData,
+};
+use web_sys::{Event, WheelEvent};
+
+use super::{Synthetic, WebEventExt};
+
+impl HasWheelData for Synthetic<WheelEvent> {
+    fn delta(&self) -> dioxus_html::geometry::WheelDelta {
+        dioxus_html::geometry::WheelDelta::from_web_attributes(
+            self.event.delta_mode(),
+            self.event.delta_x(),
+            self.event.delta_y(),
+            self.event.delta_z(),
+        )
+    }
+
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+}
+
+impl HasMouseData for Synthetic<WheelEvent> {
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+}
+
+impl InteractionLocation for Synthetic<WheelEvent> {
+    fn client_coordinates(&self) -> ClientPoint {
+        ClientPoint::new(self.event.client_x().into(), self.event.client_y().into())
+    }
+
+    fn screen_coordinates(&self) -> ScreenPoint {
+        ScreenPoint::new(self.event.screen_x().into(), self.event.screen_y().into())
+    }
+
+    fn page_coordinates(&self) -> PagePoint {
+        PagePoint::new(self.event.page_x().into(), self.event.page_y().into())
+    }
+}
+
+impl InteractionElementOffset for Synthetic<WheelEvent> {
+    fn element_coordinates(&self) -> ElementPoint {
+        ElementPoint::new(self.event.offset_x().into(), self.event.offset_y().into())
+    }
+}
+
+impl ModifiersInteraction for Synthetic<WheelEvent> {
+    fn modifiers(&self) -> Modifiers {
+        let mut modifiers = Modifiers::empty();
+
+        if self.event.alt_key() {
+            modifiers.insert(Modifiers::ALT);
+        }
+        if self.event.ctrl_key() {
+            modifiers.insert(Modifiers::CONTROL);
+        }
+        if self.event.meta_key() {
+            modifiers.insert(Modifiers::META);
+        }
+        if self.event.shift_key() {
+            modifiers.insert(Modifiers::SHIFT);
+        }
+
+        modifiers
+    }
+}
+
+impl PointerInteraction for Synthetic<WheelEvent> {
+    fn held_buttons(&self) -> dioxus_html::input_data::MouseButtonSet {
+        decode_mouse_button_set(self.event.buttons())
+    }
+
+    fn trigger_button(&self) -> Option<MouseButton> {
+        Some(MouseButton::from_web_code(self.event.button()))
+    }
+}
+
+impl HasScrollData for Synthetic<Event> {
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+}
+
+impl WebEventExt for dioxus_html::ScrollData {
+    type WebEvent = web_sys::Event;
+
+    #[inline(always)]
+    fn try_as_web_event(&self) -> Option<Self::WebEvent> {
+        self.downcast::<Synthetic<web_sys::Event>>()
+            .map(|e| e.event.clone())
+    }
+}
+
+impl WebEventExt for dioxus_html::WheelData {
+    type WebEvent = web_sys::WheelEvent;
+
+    #[inline(always)]
+    fn try_as_web_event(&self) -> Option<Self::WebEvent> {
+        self.downcast::<Synthetic<web_sys::WheelEvent>>()
+            .map(|e| e.event.clone())
+    }
+}

+ 1 - 1
packages/html/src/web_sys_bind/file_engine.rs → packages/web/src/file_engine.rs

@@ -1,6 +1,6 @@
 use std::any::Any;
 
-use crate::FileEngine;
+use dioxus_html::FileEngine;
 use futures_channel::oneshot;
 use js_sys::Uint8Array;
 use wasm_bindgen::{prelude::Closure, JsCast};

+ 1 - 1
packages/html/src/js/eval.js → packages/web/src/js/eval.js

@@ -1 +1 @@
-class Channel{pending;waiting;constructor(){this.pending=[],this.waiting=[]}send(data){if(this.waiting.length>0){this.waiting.shift()(data);return}this.pending.push(data)}async recv(){return new Promise((resolve,_reject)=>{if(this.pending.length>0){resolve(this.pending.shift());return}this.waiting.push(resolve)})}}class WeakDioxusChannel{inner;constructor(channel){this.inner=new WeakRef(channel)}rustSend(data){let channel=this.inner.deref();if(channel)channel.rustSend(data)}async rustRecv(){let channel=this.inner.deref();if(channel)return await channel.rustRecv()}}class DioxusChannel{weak(){return new WeakDioxusChannel(this)}}class WebDioxusChannel extends DioxusChannel{js_to_rust;rust_to_js;owner;constructor(owner){super();this.owner=owner,this.js_to_rust=new Channel,this.rust_to_js=new Channel}weak(){return new WeakDioxusChannel(this)}async recv(){return await this.rust_to_js.recv()}send(data){this.js_to_rust.send(data)}rustSend(data){this.rust_to_js.send(data)}async rustRecv(){return await this.js_to_rust.recv()}}export{WebDioxusChannel,WeakDioxusChannel,DioxusChannel,Channel};
+class Channel{pending;waiting;constructor(){this.pending=[],this.waiting=[]}send(data){if(this.waiting.length>0){this.waiting.shift()(data);return}this.pending.push(data)}async recv(){return new Promise((resolve,_reject)=>{if(this.pending.length>0){resolve(this.pending.shift());return}this.waiting.push(resolve)})}}class WeakDioxusChannel{inner;constructor(channel){this.inner=new WeakRef(channel)}rustSend(data){let channel=this.inner.deref();if(channel)channel.rustSend(data)}async rustRecv(){let channel=this.inner.deref();if(channel)return await channel.rustRecv()}}class DioxusChannel{weak(){return new WeakDioxusChannel(this)}}class WebDioxusChannel extends DioxusChannel{js_to_rust;rust_to_js;owner;constructor(owner){super();this.owner=owner,this.js_to_rust=new Channel,this.rust_to_js=new Channel}weak(){return new WeakDioxusChannel(this)}async recv(){return await this.rust_to_js.recv()}send(data){this.js_to_rust.send(data)}rustSend(data){this.rust_to_js.send(data)}async rustRecv(){return await this.js_to_rust.recv()}}export{WebDioxusChannel};

+ 1 - 0
packages/web/src/js/hash.txt

@@ -0,0 +1 @@
+[3479327739946104450]

+ 6 - 2
packages/web/src/lib.rs

@@ -29,15 +29,19 @@ use futures_util::{pin_mut, select, FutureExt, StreamExt};
 mod cfg;
 mod dom;
 
-mod event;
+mod events;
 pub mod launch;
 mod mutations;
-pub use event::*;
+pub use events::*;
 
 #[cfg(feature = "document")]
 mod document;
+#[cfg(feature = "file_engine")]
+mod file_engine;
 #[cfg(feature = "document")]
 pub use document::WebDocument;
+#[cfg(feature = "file_engine")]
+pub use file_engine::*;
 
 #[cfg(all(feature = "devtools", debug_assertions))]
 mod devtools;

+ 43 - 0
packages/web/src/ts/eval.ts

@@ -0,0 +1,43 @@
+import {
+  DioxusChannel,
+  Channel,
+  WeakDioxusChannel,
+} from "../../../html/src/ts/eval";
+
+export class WebDioxusChannel extends DioxusChannel {
+  js_to_rust: Channel;
+  rust_to_js: Channel;
+  owner: any;
+
+  constructor(owner: any) {
+    super();
+    this.owner = owner;
+    this.js_to_rust = new Channel();
+    this.rust_to_js = new Channel();
+  }
+
+  // Return a weak reference to this channel
+  weak(): WeakDioxusChannel {
+    return new WeakDioxusChannel(this);
+  }
+
+  // Receive message from Rust
+  async recv() {
+    return await this.rust_to_js.recv();
+  }
+
+  // Send message to rust.
+  send(data: any) {
+    this.js_to_rust.send(data);
+  }
+
+  // Send data from rust to javascript
+  rustSend(data: any) {
+    this.rust_to_js.send(data);
+  }
+
+  // Receive data sent from javascript in rust
+  async rustRecv(): Promise<any> {
+    return await this.js_to_rust.recv();
+  }
+}