From cda8bb24f103c5de91f2859b40c884d8db73023c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 10 Oct 2024 15:35:59 -0500 Subject: [PATCH] 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 --- Cargo.lock | 6 +- packages/desktop/Cargo.toml | 5 +- packages/desktop/build.rs | 9 + packages/desktop/src/app.rs | 4 +- packages/desktop/src/document.rs | 3 + packages/desktop/src/file_upload.rs | 82 ++- packages/desktop/src/js/hash.txt | 1 + .../{html => desktop}/src/js/native_eval.js | 0 packages/desktop/src/launch.rs | 20 +- packages/desktop/src/protocol.rs | 2 +- .../{html => desktop}/src/ts/native_eval.ts | 6 +- packages/desktop/src/webview.rs | 15 +- packages/desktop/tsconfig.json | 18 + packages/html/Cargo.toml | 43 +- packages/html/build.rs | 2 - packages/html/src/document/bindings.rs | 50 -- packages/html/src/document/mod.rs | 2 - packages/html/src/events/mounted.rs | 8 +- packages/html/src/js/hash.txt | 2 +- packages/html/src/lib.rs | 6 - packages/html/src/native_bind/mod.rs | 3 - .../src/native_bind/native_file_engine.rs | 56 -- packages/html/src/ts/eval.ts | 38 - packages/html/src/web_sys_bind/events.rs | 675 ------------------ packages/html/src/web_sys_bind/mod.rs | 5 - packages/web/Cargo.toml | 53 +- packages/web/build.rs | 7 + packages/web/src/document.rs | 48 +- packages/web/src/event.rs | 591 --------------- packages/web/src/events/animation.rs | 32 + packages/web/src/events/clipboard.rs | 26 + packages/web/src/events/composition.rs | 24 + packages/web/src/events/drag.rs | 108 +++ packages/web/src/events/file.rs | 24 + packages/web/src/events/focus.rs | 19 + packages/web/src/events/form.rs | 160 +++++ packages/web/src/events/keyboard.rs | 67 ++ packages/web/src/events/load.rs | 37 + packages/web/src/events/media.rs | 19 + packages/web/src/events/mod.rs | 278 ++++++++ packages/web/src/events/mounted.rs | 126 ++++ packages/web/src/events/mouse.rs | 87 +++ packages/web/src/events/pointer.rs | 119 +++ packages/web/src/events/resize.rs | 57 ++ packages/web/src/events/selection.rs | 19 + packages/web/src/events/toggle.rs | 19 + packages/web/src/events/touch.rs | 111 +++ packages/web/src/events/transition.rs | 22 + packages/web/src/events/wheel.rs | 110 +++ .../web_sys_bind => web/src}/file_engine.rs | 2 +- packages/{html => web}/src/js/eval.js | 2 +- packages/web/src/js/hash.txt | 1 + packages/web/src/lib.rs | 8 +- packages/web/src/ts/eval.ts | 43 ++ 54 files changed, 1768 insertions(+), 1512 deletions(-) create mode 100644 packages/desktop/src/js/hash.txt rename packages/{html => desktop}/src/js/native_eval.js (100%) rename packages/{html => desktop}/src/ts/native_eval.ts (96%) create mode 100644 packages/desktop/tsconfig.json delete mode 100644 packages/html/src/document/bindings.rs delete mode 100644 packages/html/src/native_bind/mod.rs delete mode 100644 packages/html/src/native_bind/native_file_engine.rs delete mode 100644 packages/html/src/web_sys_bind/events.rs delete mode 100644 packages/html/src/web_sys_bind/mod.rs create mode 100644 packages/web/build.rs delete mode 100644 packages/web/src/event.rs create mode 100644 packages/web/src/events/animation.rs create mode 100644 packages/web/src/events/clipboard.rs create mode 100644 packages/web/src/events/composition.rs create mode 100644 packages/web/src/events/drag.rs create mode 100644 packages/web/src/events/file.rs create mode 100644 packages/web/src/events/focus.rs create mode 100644 packages/web/src/events/form.rs create mode 100644 packages/web/src/events/keyboard.rs create mode 100644 packages/web/src/events/load.rs create mode 100644 packages/web/src/events/media.rs create mode 100644 packages/web/src/events/mod.rs create mode 100644 packages/web/src/events/mounted.rs create mode 100644 packages/web/src/events/mouse.rs create mode 100644 packages/web/src/events/pointer.rs create mode 100644 packages/web/src/events/resize.rs create mode 100644 packages/web/src/events/selection.rs create mode 100644 packages/web/src/events/toggle.rs create mode 100644 packages/web/src/events/touch.rs create mode 100644 packages/web/src/events/transition.rs create mode 100644 packages/web/src/events/wheel.rs rename packages/{html/src/web_sys_bind => web/src}/file_engine.rs (99%) rename packages/{html => web}/src/js/eval.js (93%) create mode 100644 packages/web/src/js/hash.txt create mode 100644 packages/web/src/ts/eval.ts diff --git a/Cargo.lock b/Cargo.lock index 634ee98ec..94aac6462 100644 --- a/Cargo.lock +++ b/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", diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 4bccbe18d..08f281caf 100644 --- a/packages/desktop/Cargo.toml +++ b/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"] diff --git a/packages/desktop/build.rs b/packages/desktop/build.rs index c8b5f592b..b1705cb50 100644 --- a/packages/desktop/build.rs +++ b/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#" diff --git a/packages/desktop/src/app.rs b/packages/desktop/src/app.rs index c35936cf9..830891cda 100644 --- a/packages/desktop/src/app.rs +++ b/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}, diff --git a/packages/desktop/src/document.rs b/packages/desktop/src/document.rs index 707b9d832..26c724ff4 100644 --- a/packages/desktop/src/document.rs +++ b/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, diff --git a/packages/desktop/src/file_upload.rs b/packages/desktop/src/file_upload.rs index dba431ef4..5448586d3 100644 --- a/packages/desktop/src/file_upload.rs +++ b/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, +} + +impl NativeFileEngine { + pub fn new(files: Vec) -> Self { + Self { files } + } +} + +#[async_trait::async_trait(?Send)] +impl FileEngine for NativeFileEngine { + fn files(&self) -> Vec { + self.files + .iter() + .filter_map(|f| Some(f.to_str()?.to_string())) + .collect() + } + + async fn file_size(&self, file: &str) -> Option { + #[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> { + #[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 { + #[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> { + #[cfg(feature = "tokio_runtime")] + { + let file = File::open(file).await.ok()?; + Some(Box::new(file)) + } + #[cfg(not(feature = "tokio_runtime"))] + { + None + } + } +} diff --git a/packages/desktop/src/js/hash.txt b/packages/desktop/src/js/hash.txt new file mode 100644 index 000000000..6287f751b --- /dev/null +++ b/packages/desktop/src/js/hash.txt @@ -0,0 +1 @@ +[11927251734412729446] \ No newline at end of file diff --git a/packages/html/src/js/native_eval.js b/packages/desktop/src/js/native_eval.js similarity index 100% rename from packages/html/src/js/native_eval.js rename to packages/desktop/src/js/native_eval.js diff --git a/packages/desktop/src/launch.rs b/packages/desktop/src/launch.rs index 0bef0fc11..1a66d169b 100644 --- a/packages/desktop/src/launch.rs +++ b/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. diff --git a/packages/desktop/src/protocol.rs b/packages/desktop/src/protocol.rs index 295a00e55..2c159ef8a 100644 --- a/packages/desktop/src/protocol.rs +++ b/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; diff --git a/packages/html/src/ts/native_eval.ts b/packages/desktop/src/ts/native_eval.ts similarity index 96% rename from packages/html/src/ts/native_eval.ts rename to packages/desktop/src/ts/native_eval.ts index b4152074b..10c4d6aab 100644 --- a/packages/html/src/ts/native_eval.ts +++ b/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 { diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs index 7be37b654..21aa8adbe 100644 --- a/packages/desktop/src/webview.rs +++ b/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; diff --git a/packages/desktop/tsconfig.json b/packages/desktop/tsconfig.json new file mode 100644 index 000000000..11cda68e3 --- /dev/null +++ b/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" + ] +} diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 9bcdff30a..a12360254 100644 --- a/packages/html/Cargo.toml +++ b/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"] diff --git a/packages/html/build.rs b/packages/html/build.rs index 88da265a4..8d5edfa58 100644 --- a/packages/html/build.rs +++ b/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(); } diff --git a/packages/html/src/document/bindings.rs b/packages/html/src/document/bindings.rs deleted file mode 100644 index ad8ef1606..000000000 --- a/packages/html/src/document/bindings.rs +++ /dev/null @@ -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, -} - -#[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; -} diff --git a/packages/html/src/document/mod.rs b/packages/html/src/document/mod.rs index a81b1c8c3..8131657fb 100644 --- a/packages/html/src/document/mod.rs +++ b/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::*; diff --git a/packages/html/src/events/mounted.rs b/packages/html/src/events/mounted.rs index cccf53c7a..ed021b6ae 100644 --- a/packages/html/src/events/mounted.rs +++ b/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, } +impl Debug for MountedData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MountedData").finish() + } +} + impl From for MountedData { fn from(e: E) -> Self { Self { inner: Box::new(e) } diff --git a/packages/html/src/js/hash.txt b/packages/html/src/js/hash.txt index 051bb006a..faaeb89f2 100644 --- a/packages/html/src/js/hash.txt +++ b/packages/html/src/js/hash.txt @@ -1 +1 @@ -[10372071913661173523, 8375185156499858125, 4813754958077120784] \ No newline at end of file +[206827801705263822, 8375185156499858125] \ No newline at end of file diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index 3f1646912..0067a9068 100644 --- a/packages/html/src/lib.rs +++ b/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; diff --git a/packages/html/src/native_bind/mod.rs b/packages/html/src/native_bind/mod.rs deleted file mode 100644 index 4d84a4ab3..000000000 --- a/packages/html/src/native_bind/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod native_file_engine; - -pub use native_file_engine::*; diff --git a/packages/html/src/native_bind/native_file_engine.rs b/packages/html/src/native_bind/native_file_engine.rs deleted file mode 100644 index 98893ced6..000000000 --- a/packages/html/src/native_bind/native_file_engine.rs +++ /dev/null @@ -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, -} - -impl NativeFileEngine { - pub fn new(files: Vec) -> Self { - Self { files } - } -} - -#[cfg(feature = "file_engine")] -#[async_trait::async_trait(?Send)] -impl FileEngine for NativeFileEngine { - fn files(&self) -> Vec { - self.files - .iter() - .filter_map(|f| Some(f.to_str()?.to_string())) - .collect() - } - - async fn file_size(&self, file: &str) -> Option { - let file = File::open(file).await.ok()?; - Some(file.metadata().await.ok()?.len()) - } - - async fn read_file(&self, file: &str) -> Option> { - 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 { - 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> { - let file = File::open(file).await.ok()?; - Some(Box::new(file)) - } -} diff --git a/packages/html/src/ts/eval.ts b/packages/html/src/ts/eval.ts index 44ce99d67..4440fe9df 100644 --- a/packages/html/src/ts/eval.ts +++ b/packages/html/src/ts/eval.ts @@ -68,41 +68,3 @@ export abstract class DioxusChannel { // Receive data sent from javascript in rust abstract rustRecv(): Promise; } - -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 { - return await this.js_to_rust.recv(); - } -} diff --git a/packages/html/src/web_sys_bind/events.rs b/packages/html/src/web_sys_bind/events.rs deleted file mode 100644 index 2be499fdd..000000000 --- a/packages/html/src/web_sys_bind/events.rs +++ /dev/null @@ -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 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 for ResizeData { - #[inline] - fn from(e: Event) -> Self { - >::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::()) - } -} - -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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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>, - >, - > { - 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>>, - > { - 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>>, - > { - 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>>> { - 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>>> { - #[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::() - .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 { - let first = resize_observer_output.get(0); - let size = first.unchecked_into::(); - - // 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 { - extract_first_size(self.border_box_size()) - } - - fn get_content_box_size(&self) -> ResizeResult { - 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> { - 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) - }) - }); - - files - } -} diff --git a/packages/html/src/web_sys_bind/mod.rs b/packages/html/src/web_sys_bind/mod.rs deleted file mode 100644 index 4ef737d43..000000000 --- a/packages/html/src/web_sys_bind/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod events; -#[cfg(feature = "file_engine")] -mod file_engine; -#[cfg(feature = "file_engine")] -pub use file_engine::*; diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index c1abecc35..f79b2692a 100644 --- a/packages/web/Cargo.toml +++ b/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", - "Text", - "Comment", - "Window", - "DataTransfer", - "console", + "InputEvent", + "KeyboardEvent", + "MouseEvent", "NodeList", - "CloseEvent", - "CustomEvent", + "PointerEvent", + "ResizeObserverEntry", + "ResizeObserverSize", + "Text", + "Touch", + "TouchEvent", + "TouchList", + "TransitionEvent", + "WheelEvent", + "Window", ] +[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"] diff --git a/packages/web/build.rs b/packages/web/build.rs new file mode 100644 index 000000000..860c56875 --- /dev/null +++ b/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(); +} diff --git a/packages/web/src/document.rs b/packages/web/src/document.rs index ce8828a5e..62127c7c7 100644 --- a/packages/web/src/document.rs +++ b/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, +} + +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 = Rc::new(WebDocument); diff --git a/packages/web/src/event.rs b/packages/web/src/event.rs deleted file mode 100644 index 24f58f0eb..000000000 --- a/packages/web/src/event.rs +++ /dev/null @@ -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::() - .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::() - .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 { - /// Try to downcast this event as a `web-sys` event. - fn try_as_web_event(&self) -> Option; - - /// 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::() - ) - }) - } -} - -impl WebEventExt for dioxus_html::AnimationData { - #[inline(always)] - fn try_as_web_event(&self) -> Option { - self.downcast::().cloned() - } -} - -impl WebEventExt for dioxus_html::ClipboardData { - #[inline(always)] - fn try_as_web_event(&self) -> Option { - self.downcast::().cloned() - } -} - -impl WebEventExt for dioxus_html::CompositionData { - #[inline(always)] - fn try_as_web_event(&self) -> Option { - self.downcast::().cloned() - } -} - -impl WebEventExt for dioxus_html::DragData { - #[inline(always)] - fn try_as_web_event(&self) -> Option { - self.downcast::() - .map(|data| &data.raw) - .cloned() - } -} - -impl WebEventExt for dioxus_html::FocusData { - #[inline(always)] - fn try_as_web_event(&self) -> Option { - self.downcast::().cloned() - } -} - -impl WebEventExt for dioxus_html::FormData { - #[inline(always)] - fn try_as_web_event(&self) -> Option { - self.downcast::().cloned() - } -} - -impl WebEventExt for dioxus_html::ImageData { - #[inline(always)] - fn try_as_web_event(&self) -> Option { - self.downcast::().cloned() - } -} - -impl WebEventExt for dioxus_html::KeyboardData { - #[inline(always)] - fn try_as_web_event(&self) -> Option { - self.downcast::().cloned() - } -} - -impl WebEventExt for dioxus_html::MediaData { - #[inline(always)] - fn try_as_web_event(&self) -> Option { - self.downcast::().cloned() - } -} - -impl WebEventExt for MountedData { - #[inline(always)] - fn try_as_web_event(&self) -> Option { - self.downcast::().cloned() - } -} - -impl WebEventExt for dioxus_html::MouseData { - #[inline(always)] - fn try_as_web_event(&self) -> Option { - self.downcast::().cloned() - } -} - -impl WebEventExt for dioxus_html::PointerData { - #[inline(always)] - fn try_as_web_event(&self) -> Option { - self.downcast::().cloned() - } -} - -impl WebEventExt for ScrollData { - #[inline(always)] - fn try_as_web_event(&self) -> Option { - self.downcast::().cloned() - } -} - -impl WebEventExt for dioxus_html::SelectionData { - #[inline(always)] - fn try_as_web_event(&self) -> Option { - self.downcast::().cloned() - } -} - -impl WebEventExt for dioxus_html::ToggleData { - #[inline(always)] - fn try_as_web_event(&self) -> Option { - self.downcast::().cloned() - } -} - -impl WebEventExt for dioxus_html::TouchData { - #[inline(always)] - fn try_as_web_event(&self) -> Option { - self.downcast::().cloned() - } -} - -impl WebEventExt for dioxus_html::TransitionData { - #[inline(always)] - fn try_as_web_event(&self) -> Option { - self.downcast::().cloned() - } -} - -impl WebEventExt for dioxus_html::WheelData { - #[inline(always)] - fn try_as_web_event(&self) -> Option { - self.downcast::().cloned() - } -} - -impl WebEventExt for dioxus_html::ResizeData { - #[inline(always)] - fn try_as_web_event(&self) -> Option { - self.downcast::() - .and_then(|e| e.detail().dyn_into::().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::() - .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 { - let mut values = HashMap::new(); - - fn insert_value(map: &mut HashMap, 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::() { - let form_data = get_form_data(form); - for value in form_data.entries().into_iter().flatten() { - if let Ok(array) = value.dyn_into::() { - if let Some(name) = array.get(0).as_string() { - if let Ok(item_values) = array.get(1).dyn_into::() { - 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::() { - insert_value(&mut values, name, item_value.as_string().unwrap()); - } - } - } - } - } else if let Some(select) = self.element.dyn_ref::() { - // 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> { - 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 - }) - }) - }); - - 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 { - 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> { - let files = self - .raw - .dyn_ref::() - .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 - }) - }) - }) - }); - - 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; -} diff --git a/packages/web/src/events/animation.rs b/packages/web/src/events/animation.rs new file mode 100644 index 000000000..a1ed2c71c --- /dev/null +++ b/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 { + 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 { + self.downcast::>() + .map(|e| e.event.clone()) + } +} diff --git a/packages/web/src/events/clipboard.rs b/packages/web/src/events/clipboard.rs new file mode 100644 index 000000000..9eeb7791a --- /dev/null +++ b/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 { + fn from(e: &Event) -> Self { + Synthetic::new(e.clone()) + } +} + +impl HasClipboardData for Synthetic { + 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.downcast::>() + .map(|e| e.event.clone()) + } +} diff --git a/packages/web/src/events/composition.rs b/packages/web/src/events/composition.rs new file mode 100644 index 000000000..221caf9ae --- /dev/null +++ b/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 { + 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.downcast::>() + .map(|e| e.event.clone()) + } +} diff --git a/packages/web/src/events/drag.rs b/packages/web/src/events/drag.rs new file mode 100644 index 000000000..6379dbd29 --- /dev/null +++ b/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, +} + +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 { + 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> { + use wasm_bindgen::JsCast; + + let files = self + .raw + .event + .dyn_ref::() + .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 + }) + }) + }) + }); + + files + } +} + +impl WebEventExt for dioxus_html::DragData { + type WebEvent = web_sys::MouseEvent; + + #[inline(always)] + fn try_as_web_event(&self) -> Option { + self.downcast::() + .map(|data| &data.raw.event) + .cloned() + } +} diff --git a/packages/web/src/events/file.rs b/packages/web/src/events/file.rs new file mode 100644 index 000000000..981a5288b --- /dev/null +++ b/packages/web/src/events/file.rs @@ -0,0 +1,24 @@ +use dioxus_html::HasFileData; + +use super::Synthetic; + +impl HasFileData for Synthetic { + #[cfg(feature = "file_engine")] + fn files(&self) -> Option> { + 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 + }) + }) + }); + + files + } +} diff --git a/packages/web/src/events/focus.rs b/packages/web/src/events/focus.rs new file mode 100644 index 000000000..befaadfab --- /dev/null +++ b/packages/web/src/events/focus.rs @@ -0,0 +1,19 @@ +use dioxus_html::HasFocusData; + +use super::{Synthetic, WebEventExt}; + +impl HasFocusData for Synthetic { + 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.downcast::>() + .map(|e| e.event.clone()) + } +} diff --git a/packages/web/src/events/form.rs b/packages/web/src/events/form.rs new file mode 100644 index 000000000..d92330f64 --- /dev/null +++ b/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.downcast::().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::() + .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 { + let mut values = HashMap::new(); + + fn insert_value(map: &mut HashMap, 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::() { + let form_data = get_form_data(form); + for value in form_data.entries().into_iter().flatten() { + if let Ok(array) = value.dyn_into::() { + if let Some(name) = array.get(0).as_string() { + if let Ok(item_values) = array.get(1).dyn_into::() { + 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::() { + insert_value(&mut values, name, item_value.as_string().unwrap()); + } + } + } + } + } else if let Some(select) = self.element.dyn_ref::() { + // 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> { + 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 + }) + }) + }); + + 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; +} + +// 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; +} diff --git a/packages/web/src/events/keyboard.rs b/packages/web/src/events/keyboard.rs new file mode 100644 index 000000000..03a8bbded --- /dev/null +++ b/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 { + 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 { + 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 { + self.downcast::>() + .map(|e| e.event.clone()) + } +} diff --git a/packages/web/src/events/load.rs b/packages/web/src/events/load.rs new file mode 100644 index 000000000..a79794145 --- /dev/null +++ b/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 { + self.downcast::().map(|e| e.raw.clone()) + } +} diff --git a/packages/web/src/events/media.rs b/packages/web/src/events/media.rs new file mode 100644 index 000000000..bfd3d33a3 --- /dev/null +++ b/packages/web/src/events/media.rs @@ -0,0 +1,19 @@ +use dioxus_html::HasMediaData; + +use super::{Synthetic, WebEventExt}; + +impl HasMediaData for Synthetic { + 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.downcast::>() + .map(|e| e.event.clone()) + } +} diff --git a/packages/web/src/events/mod.rs b/packages/web/src/events/mod.rs new file mode 100644 index 000000000..98d3e3bbe --- /dev/null +++ b/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 { + /// The inner web sys event that the synthetic event wraps + pub event: T, +} + +impl Synthetic { + /// 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::() + .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::::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::::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::::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::::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::() + .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::::from(downcast_event(event).raw.clone()).into() + } + + #[inline(always)] + fn convert_pointer_data( + &self, + event: &dioxus_html::PlatformEventData, + ) -> dioxus_html::PointerData { + Synthetic::::from(downcast_event(event).raw.clone()).into() + } + + #[inline(always)] + fn convert_resize_data( + &self, + event: &dioxus_html::PlatformEventData, + ) -> dioxus_html::ResizeData { + Synthetic::::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::::from(downcast_event(event).raw.clone()).into() + } + + #[inline(always)] + fn convert_transition_data( + &self, + event: &dioxus_html::PlatformEventData, + ) -> dioxus_html::TransitionData { + Synthetic::::from(downcast_event(event).raw.clone()).into() + } + + #[inline(always)] + fn convert_wheel_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::WheelData { + Synthetic::::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; + + /// 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::() + ) + }) + } +} + +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 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, +]; diff --git a/packages/web/src/events/mounted.rs b/packages/web/src/events/mounted.rs new file mode 100644 index 000000000..bf0dafc1a --- /dev/null +++ b/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 { + fn get_scroll_offset( + &self, + ) -> std::pin::Pin< + Box< + dyn std::future::Future< + Output = dioxus_html::MountedResult, + >, + >, + > { + 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, + >, + >, + > { + 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, + >, + >, + > { + 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>>> { + 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>>> { + #[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::() + .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 { + self.downcast::>() + .map(|e| e.event.clone()) + } +} diff --git a/packages/web/src/events/mouse.rs b/packages/web/src/events/mouse.rs new file mode 100644 index 000000000..22b181174 --- /dev/null +++ b/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 { + 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 { + fn element_coordinates(&self) -> ElementPoint { + ElementPoint::new(self.event.offset_x().into(), self.event.offset_y().into()) + } +} + +impl ModifiersInteraction for Synthetic { + 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 { + fn held_buttons(&self) -> dioxus_html::input_data::MouseButtonSet { + decode_mouse_button_set(self.event.buttons()) + } + + fn trigger_button(&self) -> Option { + Some(MouseButton::from_web_code(self.event.button())) + } +} + +impl HasMouseData for Synthetic { + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl HasFileData for Synthetic {} + +impl HasDragData for Synthetic { + 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 { + self.downcast::>() + .map(|e| e.event.clone()) + } +} diff --git a/packages/web/src/events/pointer.rs b/packages/web/src/events/pointer.rs new file mode 100644 index 000000000..abc9d7d85 --- /dev/null +++ b/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 { + 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 { + 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 { + fn element_coordinates(&self) -> ElementPoint { + ElementPoint::new(self.event.offset_x().into(), self.event.offset_y().into()) + } +} + +impl ModifiersInteraction for Synthetic { + 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 { + fn held_buttons(&self) -> dioxus_html::input_data::MouseButtonSet { + decode_mouse_button_set(self.event.buttons()) + } + + fn trigger_button(&self) -> Option { + 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 { + self.downcast::>() + .map(|e| e.event.clone()) + } +} diff --git a/packages/web/src/events/resize.rs b/packages/web/src/events/resize.rs new file mode 100644 index 000000000..028abe69d --- /dev/null +++ b/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 for Synthetic { + #[inline] + fn from(e: Event) -> Self { + as From<&Event>>::from(&e) + } +} + +impl From<&Event> for Synthetic { + #[inline] + fn from(e: &Event) -> Self { + let e: &CustomEvent = e.unchecked_ref(); + let value = e.detail(); + Self::new(value.unchecked_into::()) + } +} + +impl HasResizeData for Synthetic { + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn get_border_box_size(&self) -> ResizeResult { + extract_first_size(self.event.border_box_size()) + } + + fn get_content_box_size(&self) -> ResizeResult { + 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 { + self.downcast::>() + .map(|e| e.event.clone()) + } +} + +fn extract_first_size(resize_observer_output: js_sys::Array) -> ResizeResult { + let first = resize_observer_output.get(0); + let size = first.unchecked_into::(); + + // 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)) +} diff --git a/packages/web/src/events/selection.rs b/packages/web/src/events/selection.rs new file mode 100644 index 000000000..d68a6ec96 --- /dev/null +++ b/packages/web/src/events/selection.rs @@ -0,0 +1,19 @@ +use dioxus_html::HasSelectionData; + +use super::{Synthetic, WebEventExt}; + +impl HasSelectionData for Synthetic { + 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.downcast::>() + .map(|e| e.event.clone()) + } +} diff --git a/packages/web/src/events/toggle.rs b/packages/web/src/events/toggle.rs new file mode 100644 index 000000000..67d2f76b3 --- /dev/null +++ b/packages/web/src/events/toggle.rs @@ -0,0 +1,19 @@ +use dioxus_html::HasToggleData; + +use super::{Synthetic, WebEventExt}; + +impl HasToggleData for Synthetic { + 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.downcast::>() + .map(|e| e.event.clone()) + } +} diff --git a/packages/web/src/events/touch.rs b/packages/web/src/events/touch.rs new file mode 100644 index 000000000..4a108e4f7 --- /dev/null +++ b/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 { + 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 { + fn touches(&self) -> Vec { + 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 { + 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 { + 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 { + 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 { + 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 { + self.downcast::>() + .map(|e| e.event.clone()) + } +} diff --git a/packages/web/src/events/transition.rs b/packages/web/src/events/transition.rs new file mode 100644 index 000000000..ee0638cbc --- /dev/null +++ b/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 { + 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 + } +} diff --git a/packages/web/src/events/wheel.rs b/packages/web/src/events/wheel.rs new file mode 100644 index 000000000..c32889cd8 --- /dev/null +++ b/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 { + 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 { + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl InteractionLocation for Synthetic { + 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 { + fn element_coordinates(&self) -> ElementPoint { + ElementPoint::new(self.event.offset_x().into(), self.event.offset_y().into()) + } +} + +impl ModifiersInteraction for Synthetic { + 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 { + fn held_buttons(&self) -> dioxus_html::input_data::MouseButtonSet { + decode_mouse_button_set(self.event.buttons()) + } + + fn trigger_button(&self) -> Option { + Some(MouseButton::from_web_code(self.event.button())) + } +} + +impl HasScrollData for Synthetic { + 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.downcast::>() + .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.downcast::>() + .map(|e| e.event.clone()) + } +} diff --git a/packages/html/src/web_sys_bind/file_engine.rs b/packages/web/src/file_engine.rs similarity index 99% rename from packages/html/src/web_sys_bind/file_engine.rs rename to packages/web/src/file_engine.rs index ce138c96b..f0a0ff698 100644 --- a/packages/html/src/web_sys_bind/file_engine.rs +++ b/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}; diff --git a/packages/html/src/js/eval.js b/packages/web/src/js/eval.js similarity index 93% rename from packages/html/src/js/eval.js rename to packages/web/src/js/eval.js index 216f11c9d..111158c3e 100644 --- a/packages/html/src/js/eval.js +++ b/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}; diff --git a/packages/web/src/js/hash.txt b/packages/web/src/js/hash.txt new file mode 100644 index 000000000..f397e27fa --- /dev/null +++ b/packages/web/src/js/hash.txt @@ -0,0 +1 @@ +[3479327739946104450] \ No newline at end of file diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 4600ddb6c..8e772cef7 100644 --- a/packages/web/src/lib.rs +++ b/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; diff --git a/packages/web/src/ts/eval.ts b/packages/web/src/ts/eval.ts new file mode 100644 index 000000000..3c87d0d25 --- /dev/null +++ b/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 { + return await this.js_to_rust.recv(); + } +}