Browse Source

add extension to downcast web events

Evan Almloff 1 year ago
parent
commit
097eb2099c
4 changed files with 442 additions and 288 deletions
  1. 12 0
      packages/html/src/events/pointer.rs
  2. 8 288
      packages/web/src/dom.rs
  3. 420 0
      packages/web/src/event.rs
  4. 2 0
      packages/web/src/lib.rs

+ 12 - 0
packages/html/src/events/pointer.rs

@@ -10,6 +10,18 @@ pub struct PointerData {
     inner: Box<dyn HasPointerData>,
 }
 
+impl PointerData {
+    /// Create a new PointerData
+    pub fn new(data: impl HasPointerData + 'static) -> Self {
+        Self::from(data)
+    }
+
+    /// Downcast this event to a concrete event type
+    pub fn downcast<T: 'static>(&self) -> Option<&T> {
+        self.inner.as_any().downcast_ref::<T>()
+    }
+}
+
 impl<E: HasPointerData> From<E> for PointerData {
     fn from(e: E) -> Self {
         Self { inner: Box::new(e) }

+ 8 - 288
packages/web/src/dom.rs

@@ -9,19 +9,14 @@
 use dioxus_core::{
     BorrowedAttributeValue, ElementId, Mutation, Template, TemplateAttribute, TemplateNode,
 };
-use dioxus_html::{
-    event_bubbles, FileEngine, FormData, HasFormData, HasImageData, HtmlEventConverter, ImageData,
-    MountedData, PlatformEventData, ScrollData,
-};
-use dioxus_interpreter_js::{get_node, minimal_bindings, save_template, Channel};
+use dioxus_html::{event_bubbles, PlatformEventData};
+use dioxus_interpreter_js::{minimal_bindings, save_template, Channel};
 use futures_channel::mpsc;
-use js_sys::Array;
 use rustc_hash::FxHashMap;
-use std::{any::Any, collections::HashMap};
-use wasm_bindgen::{closure::Closure, prelude::wasm_bindgen, JsCast, JsValue};
+use wasm_bindgen::{closure::Closure, JsCast, JsValue};
 use web_sys::{Document, Element, Event};
 
-use crate::Config;
+use crate::{load_document, virtual_event_from_websys_event, Config, WebEventConverter};
 
 pub struct WebsysDom {
     document: Document,
@@ -30,6 +25,7 @@ pub struct WebsysDom {
     templates: FxHashMap<String, u32>,
     max_template_id: u32,
     pub(crate) interpreter: Channel,
+    #[cfg(feature = "mounted")]
     event_channel: mpsc::UnboundedSender<UiEvent>,
 }
 
@@ -104,6 +100,7 @@ impl WebsysDom {
             interpreter,
             templates: FxHashMap::default(),
             max_template_id: 0,
+            #[cfg(feature = "mounted")]
             event_channel,
         }
     }
@@ -253,10 +250,10 @@ impl WebsysDom {
 
         #[cfg(feature = "mounted")]
         for id in to_mount {
-            let node = get_node(id.0 as u32);
+            let node = dioxus_interpreter_js::get_node(id.0 as u32);
             if let Some(element) = node.dyn_ref::<Element>() {
                 log::info!("mounted event fired: {}", id.0);
-                let data: MountedData = element.into();
+                let data: dioxus_html::MountedData = element.into();
                 let data = PlatformEventData::new(Box::new(data));
                 let _ = self.event_channel.unbounded_send(UiEvent {
                     name: "mounted".to_string(),
@@ -269,283 +266,6 @@ impl WebsysDom {
     }
 }
 
-struct WebEventConverter;
-
-fn downcast_event(event: &dioxus_html::PlatformEventData) -> &GenericWebSysEvent {
-    event
-        .downcast::<GenericWebSysEvent>()
-        .expect("event should be a GenericWebSysEvent")
-}
-
-impl HtmlEventConverter for WebEventConverter {
-    fn convert_animation_data(
-        &self,
-        event: &dioxus_html::PlatformEventData,
-    ) -> dioxus_html::AnimationData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    fn convert_clipboard_data(
-        &self,
-        event: &dioxus_html::PlatformEventData,
-    ) -> dioxus_html::ClipboardData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    fn convert_composition_data(
-        &self,
-        event: &dioxus_html::PlatformEventData,
-    ) -> dioxus_html::CompositionData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    fn convert_drag_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::DragData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    fn convert_focus_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::FocusData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    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()))
-    }
-
-    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))
-    }
-
-    fn convert_keyboard_data(
-        &self,
-        event: &dioxus_html::PlatformEventData,
-    ) -> dioxus_html::KeyboardData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    fn convert_media_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::MediaData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    fn convert_mounted_data(&self, event: &dioxus_html::PlatformEventData) -> MountedData {
-        #[cfg(feature = "mounted")]
-        {
-            MountedData::from(downcast_event(event).element.clone())
-        }
-        #[cfg(not(feature = "mounted"))]
-        {
-            panic!("mounted events are not supported without the mounted feature on the dioxus-web crate enabled")
-        }
-    }
-
-    fn convert_mouse_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::MouseData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    fn convert_pointer_data(
-        &self,
-        event: &dioxus_html::PlatformEventData,
-    ) -> dioxus_html::PointerData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    fn convert_scroll_data(
-        &self,
-        event: &dioxus_html::PlatformEventData,
-    ) -> dioxus_html::ScrollData {
-        ScrollData::from(downcast_event(event).raw.clone())
-    }
-
-    fn convert_selection_data(
-        &self,
-        event: &dioxus_html::PlatformEventData,
-    ) -> dioxus_html::SelectionData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    fn convert_toggle_data(
-        &self,
-        event: &dioxus_html::PlatformEventData,
-    ) -> dioxus_html::ToggleData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    fn convert_touch_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::TouchData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    fn convert_transition_data(
-        &self,
-        event: &dioxus_html::PlatformEventData,
-    ) -> dioxus_html::TransitionData {
-        downcast_event(event).raw.clone().into()
-    }
-
-    fn convert_wheel_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::WheelData {
-        downcast_event(event).raw.clone().into()
-    }
-}
-
-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 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")
-}
-
-struct WebImageEvent {
-    raw: Event,
-    error: bool,
-}
-
-impl WebImageEvent {
-    fn new(raw: Event, error: bool) -> Self {
-        Self { raw, error }
-    }
-}
-
-impl HasImageData for WebImageEvent {
-    fn load_error(&self) -> bool {
-        self.error
-    }
-
-    fn as_any(&self) -> &dyn Any {
-        &self.raw as &dyn Any
-    }
-}
-
-struct WebFormData {
-    element: Element,
-    raw: Event,
-}
-
-impl WebFormData {
-    fn new(element: Element, raw: Event) -> Self {
-        Self { element, raw }
-    }
-}
-
-impl HasFormData for WebFormData {
-    fn value(&self) -> String {
-        let target = &self.element;
-        target
-        .dyn_ref()
-        .map(|input: &web_sys::HtmlInputElement| {
-            // todo: special case more input types
-            match input.type_().as_str() {
-                "checkbox" => {
-                    match input.checked() {
-                        true => "true".to_string(),
-                        false => "false".to_string(),
-                    }
-                },
-                _ => {
-                    input.value()
-                }
-            }
-        })
-        .or_else(|| {
-            target
-                .dyn_ref()
-                .map(|input: &web_sys::HtmlTextAreaElement| input.value())
-        })
-        // select elements are NOT input events - because - why woudn't they be??
-        .or_else(|| {
-            target
-                .dyn_ref()
-                .map(|input: &web_sys::HtmlSelectElement| input.value())
-        })
-        .or_else(|| {
-            target
-                .dyn_ref::<web_sys::HtmlElement>()
-                .unwrap()
-                .text_content()
-        })
-        .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener")
-    }
-
-    fn values(&self) -> HashMap<String, Vec<String>> {
-        let mut values = std::collections::HashMap::new();
-
-        // try to fill in form values
-        if let Some(form) = self.element.dyn_ref::<web_sys::HtmlFormElement>() {
-            let form_data = get_form_data(form);
-            for value in form_data.entries().into_iter().flatten() {
-                if let Ok(array) = value.dyn_into::<Array>() {
-                    if let Some(name) = array.get(0).as_string() {
-                        if let Ok(item_values) = array.get(1).dyn_into::<Array>() {
-                            let item_values =
-                                item_values.iter().filter_map(|v| v.as_string()).collect();
-
-                            values.insert(name, item_values);
-                        }
-                    }
-                }
-            }
-        }
-
-        values
-    }
-
-    fn files(&self) -> Option<std::sync::Arc<dyn FileEngine>> {
-        #[cfg(not(feature = "file_engine"))]
-        let files = None;
-        #[cfg(feature = "file_engine")]
-        let files = self
-            .element
-            .dyn_ref()
-            .and_then(|input: &web_sys::HtmlInputElement| {
-                input.files().and_then(|files| {
-                    crate::file_engine::WebFileEngine::new(files).map(|f| {
-                        std::sync::Arc::new(f) as std::sync::Arc<dyn dioxus_html::FileEngine>
-                    })
-                })
-            });
-
-        files
-    }
-
-    fn as_any(&self) -> &dyn Any {
-        &self.raw as &dyn Any
-    }
-}
-
-// 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;
-}
-
 fn walk_event_for_id(event: &web_sys::Event) -> Option<(ElementId, web_sys::Element)> {
     let target = event
         .target()

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

@@ -0,0 +1,420 @@
+use std::{any::Any, collections::HashMap};
+
+use dioxus_html::{
+    FileEngine, FormData, HasFormData, HasImageData, HtmlEventConverter, ImageData, MountedData,
+    PlatformEventData, ScrollData,
+};
+use js_sys::Array;
+use wasm_bindgen::{prelude::wasm_bindgen, JsCast};
+use web_sys::{Document, Element, Event};
+
+pub(crate) struct WebEventConverter;
+
+fn downcast_event(event: &dioxus_html::PlatformEventData) -> &GenericWebSysEvent {
+    event
+        .downcast::<GenericWebSysEvent>()
+        .expect("event should be a GenericWebSysEvent")
+}
+
+impl HtmlEventConverter for WebEventConverter {
+    fn convert_animation_data(
+        &self,
+        event: &dioxus_html::PlatformEventData,
+    ) -> dioxus_html::AnimationData {
+        downcast_event(event).raw.clone().into()
+    }
+
+    fn convert_clipboard_data(
+        &self,
+        event: &dioxus_html::PlatformEventData,
+    ) -> dioxus_html::ClipboardData {
+        downcast_event(event).raw.clone().into()
+    }
+
+    fn convert_composition_data(
+        &self,
+        event: &dioxus_html::PlatformEventData,
+    ) -> dioxus_html::CompositionData {
+        downcast_event(event).raw.clone().into()
+    }
+
+    fn convert_drag_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::DragData {
+        downcast_event(event).raw.clone().into()
+    }
+
+    fn convert_focus_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::FocusData {
+        downcast_event(event).raw.clone().into()
+    }
+
+    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()))
+    }
+
+    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))
+    }
+
+    fn convert_keyboard_data(
+        &self,
+        event: &dioxus_html::PlatformEventData,
+    ) -> dioxus_html::KeyboardData {
+        downcast_event(event).raw.clone().into()
+    }
+
+    fn convert_media_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::MediaData {
+        downcast_event(event).raw.clone().into()
+    }
+
+    #[allow(unused_variables)]
+    fn convert_mounted_data(&self, event: &dioxus_html::PlatformEventData) -> MountedData {
+        #[cfg(feature = "mounted")]
+        {
+            MountedData::from(downcast_event(event).element.clone())
+        }
+        #[cfg(not(feature = "mounted"))]
+        {
+            panic!("mounted events are not supported without the mounted feature on the dioxus-web crate enabled")
+        }
+    }
+
+    fn convert_mouse_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::MouseData {
+        downcast_event(event).raw.clone().into()
+    }
+
+    fn convert_pointer_data(
+        &self,
+        event: &dioxus_html::PlatformEventData,
+    ) -> dioxus_html::PointerData {
+        downcast_event(event).raw.clone().into()
+    }
+
+    fn convert_scroll_data(
+        &self,
+        event: &dioxus_html::PlatformEventData,
+    ) -> dioxus_html::ScrollData {
+        ScrollData::from(downcast_event(event).raw.clone())
+    }
+
+    fn convert_selection_data(
+        &self,
+        event: &dioxus_html::PlatformEventData,
+    ) -> dioxus_html::SelectionData {
+        downcast_event(event).raw.clone().into()
+    }
+
+    fn convert_toggle_data(
+        &self,
+        event: &dioxus_html::PlatformEventData,
+    ) -> dioxus_html::ToggleData {
+        downcast_event(event).raw.clone().into()
+    }
+
+    fn convert_touch_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::TouchData {
+        downcast_event(event).raw.clone().into()
+    }
+
+    fn convert_transition_data(
+        &self,
+        event: &dioxus_html::PlatformEventData,
+    ) -> dioxus_html::TransitionData {
+        downcast_event(event).raw.clone().into()
+    }
+
+    fn convert_wheel_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::WheelData {
+        downcast_event(event).raw.clone().into()
+    }
+}
+
+/// A extension trait for web-sys events that provides a way to get the event as a web-sys event.
+pub trait WebEventExt<E> {
+    /// Get the event as a web-sys event.
+    fn web_event(&self) -> &E;
+}
+
+impl WebEventExt<web_sys::AnimationEvent> for dioxus_html::AnimationData {
+    fn web_event(&self) -> &web_sys::AnimationEvent {
+        self.downcast::<web_sys::AnimationEvent>()
+            .expect("event should be a WebAnimationEvent")
+    }
+}
+
+impl WebEventExt<web_sys::Event> for dioxus_html::ClipboardData {
+    fn web_event(&self) -> &web_sys::Event {
+        &self
+            .downcast::<web_sys::Event>()
+            .expect("event should be a web_sys::Event")
+    }
+}
+
+impl WebEventExt<web_sys::CompositionEvent> for dioxus_html::CompositionData {
+    fn web_event(&self) -> &web_sys::CompositionEvent {
+        self.downcast::<web_sys::CompositionEvent>()
+            .expect("event should be a WebCompositionEvent")
+    }
+}
+
+impl WebEventExt<web_sys::MouseEvent> for dioxus_html::DragData {
+    fn web_event(&self) -> &web_sys::MouseEvent {
+        self.downcast::<web_sys::MouseEvent>()
+            .expect("event should be a WebMouseEvent")
+    }
+}
+
+impl WebEventExt<web_sys::FocusEvent> for dioxus_html::FocusData {
+    fn web_event(&self) -> &web_sys::FocusEvent {
+        self.downcast::<web_sys::FocusEvent>()
+            .expect("event should be a WebFocusEvent")
+    }
+}
+
+impl WebEventExt<web_sys::Event> for dioxus_html::FormData {
+    fn web_event(&self) -> &web_sys::Event {
+        self.downcast::<web_sys::Event>()
+            .expect("event should be a WebFormData")
+    }
+}
+
+impl WebEventExt<WebImageEvent> for dioxus_html::ImageData {
+    fn web_event(&self) -> &WebImageEvent {
+        self.downcast::<WebImageEvent>()
+            .expect("event should be a WebImageEvent")
+    }
+}
+
+impl WebEventExt<web_sys::KeyboardEvent> for dioxus_html::KeyboardData {
+    fn web_event(&self) -> &web_sys::KeyboardEvent {
+        self.downcast::<web_sys::KeyboardEvent>()
+            .expect("event should be a WebKeyboardEvent")
+    }
+}
+
+impl WebEventExt<web_sys::Event> for dioxus_html::MediaData {
+    fn web_event(&self) -> &web_sys::Event {
+        self.downcast::<web_sys::Event>()
+            .expect("event should be a WebMediaEvent")
+    }
+}
+
+impl WebEventExt<MountedData> for MountedData {
+    fn web_event(&self) -> &MountedData {
+        self.downcast::<MountedData>()
+            .expect("event should be a WebMountedEvent")
+    }
+}
+
+impl WebEventExt<web_sys::MouseEvent> for dioxus_html::MouseData {
+    fn web_event(&self) -> &web_sys::MouseEvent {
+        self.downcast::<web_sys::MouseEvent>()
+            .expect("event should be a WebMouseEvent")
+    }
+}
+
+impl WebEventExt<web_sys::PointerEvent> for dioxus_html::PointerData {
+    fn web_event(&self) -> &web_sys::PointerEvent {
+        self.downcast::<web_sys::PointerEvent>()
+            .expect("event should be a WebPointerEvent")
+    }
+}
+
+impl WebEventExt<web_sys::Event> for ScrollData {
+    fn web_event(&self) -> &web_sys::Event {
+        self.downcast::<web_sys::Event>()
+            .expect("event should be a WebScrollEvent")
+    }
+}
+
+impl WebEventExt<web_sys::Event> for dioxus_html::SelectionData {
+    fn web_event(&self) -> &web_sys::Event {
+        self.downcast::<web_sys::Event>()
+            .expect("event should be a WebSelectionEvent")
+    }
+}
+
+impl WebEventExt<web_sys::Event> for dioxus_html::ToggleData {
+    fn web_event(&self) -> &web_sys::Event {
+        self.downcast::<web_sys::Event>()
+            .expect("event should be a WebToggleEvent")
+    }
+}
+
+impl WebEventExt<web_sys::TouchEvent> for dioxus_html::TouchData {
+    fn web_event(&self) -> &web_sys::TouchEvent {
+        self.downcast::<web_sys::TouchEvent>()
+            .expect("event should be a WebTouchEvent")
+    }
+}
+
+impl WebEventExt<web_sys::TransitionEvent> for dioxus_html::TransitionData {
+    fn web_event(&self) -> &web_sys::TransitionEvent {
+        self.downcast::<web_sys::TransitionEvent>()
+            .expect("event should be a WebTransitionEvent")
+    }
+}
+
+impl WebEventExt<web_sys::WheelEvent> for dioxus_html::WheelData {
+    fn web_event(&self) -> &web_sys::WheelEvent {
+        self.downcast::<web_sys::WheelEvent>()
+            .expect("event should be a WebWheelEvent")
+    }
+}
+
+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")
+}
+
+struct WebImageEvent {
+    raw: Event,
+    error: bool,
+}
+
+impl WebImageEvent {
+    fn new(raw: Event, error: bool) -> Self {
+        Self { raw, error }
+    }
+}
+
+impl HasImageData for WebImageEvent {
+    fn load_error(&self) -> bool {
+        self.error
+    }
+
+    fn as_any(&self) -> &dyn Any {
+        &self.raw as &dyn Any
+    }
+}
+
+struct WebFormData {
+    element: Element,
+    raw: Event,
+}
+
+impl WebFormData {
+    fn new(element: Element, raw: Event) -> Self {
+        Self { element, raw }
+    }
+}
+
+impl HasFormData for WebFormData {
+    fn value(&self) -> String {
+        let target = &self.element;
+        target
+        .dyn_ref()
+        .map(|input: &web_sys::HtmlInputElement| {
+            // todo: special case more input types
+            match input.type_().as_str() {
+                "checkbox" => {
+                    match input.checked() {
+                        true => "true".to_string(),
+                        false => "false".to_string(),
+                    }
+                },
+                _ => {
+                    input.value()
+                }
+            }
+        })
+        .or_else(|| {
+            target
+                .dyn_ref()
+                .map(|input: &web_sys::HtmlTextAreaElement| input.value())
+        })
+        // select elements are NOT input events - because - why woudn't they be??
+        .or_else(|| {
+            target
+                .dyn_ref()
+                .map(|input: &web_sys::HtmlSelectElement| input.value())
+        })
+        .or_else(|| {
+            target
+                .dyn_ref::<web_sys::HtmlElement>()
+                .unwrap()
+                .text_content()
+        })
+        .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener")
+    }
+
+    fn values(&self) -> HashMap<String, Vec<String>> {
+        let mut values = std::collections::HashMap::new();
+
+        // try to fill in form values
+        if let Some(form) = self.element.dyn_ref::<web_sys::HtmlFormElement>() {
+            let form_data = get_form_data(form);
+            for value in form_data.entries().into_iter().flatten() {
+                if let Ok(array) = value.dyn_into::<Array>() {
+                    if let Some(name) = array.get(0).as_string() {
+                        if let Ok(item_values) = array.get(1).dyn_into::<Array>() {
+                            let item_values =
+                                item_values.iter().filter_map(|v| v.as_string()).collect();
+
+                            values.insert(name, item_values);
+                        }
+                    }
+                }
+            }
+        }
+
+        values
+    }
+
+    fn files(&self) -> Option<std::sync::Arc<dyn FileEngine>> {
+        #[cfg(not(feature = "file_engine"))]
+        let files = None;
+        #[cfg(feature = "file_engine")]
+        let files = self
+            .element
+            .dyn_ref()
+            .and_then(|input: &web_sys::HtmlInputElement| {
+                input.files().and_then(|files| {
+                    crate::file_engine::WebFileEngine::new(files).map(|f| {
+                        std::sync::Arc::new(f) as std::sync::Arc<dyn dioxus_html::FileEngine>
+                    })
+                })
+            });
+
+        files
+    }
+
+    fn as_any(&self) -> &dyn Any {
+        &self.raw as &dyn Any
+    }
+}
+
+// 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;
+}

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

@@ -69,6 +69,8 @@ mod cfg;
 mod dom;
 #[cfg(feature = "eval")]
 mod eval;
+mod event;
+pub use event::*;
 #[cfg(feature = "file_engine")]
 mod file_engine;
 #[cfg(all(feature = "hot_reload", debug_assertions))]