diff --git a/packages/html/src/events/pointer.rs b/packages/html/src/events/pointer.rs
index 5540dbe62..ec351d3dc 100644
--- a/packages/html/src/events/pointer.rs
+++ b/packages/html/src/events/pointer.rs
@@ -10,6 +10,18 @@ pub struct PointerData {
inner: Box,
}
+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(&self) -> Option<&T> {
+ self.inner.as_any().downcast_ref::()
+ }
+}
+
impl From for PointerData {
fn from(e: E) -> Self {
Self { inner: Box::new(e) }
diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs
index fb0dfef46..9799b50be 100644
--- a/packages/web/src/dom.rs
+++ b/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,
max_template_id: u32,
pub(crate) interpreter: Channel,
+ #[cfg(feature = "mounted")]
event_channel: mpsc::UnboundedSender,
}
@@ -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::() {
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::()
- .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::()
- .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 = std::collections::HashMap::new();
-
- // 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::() {
- let item_values =
- item_values.iter().filter_map(|v| v.as_string()).collect();
-
- values.insert(name, item_values);
- }
- }
- }
- }
- }
-
- values
- }
-
- fn files(&self) -> Option> {
- #[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
- })
- })
- });
-
- 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()
diff --git a/packages/web/src/event.rs b/packages/web/src/event.rs
new file mode 100644
index 000000000..8a4a2c204
--- /dev/null
+++ b/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::()
+ .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 {
+ /// Get the event as a web-sys event.
+ fn web_event(&self) -> &E;
+}
+
+impl WebEventExt for dioxus_html::AnimationData {
+ fn web_event(&self) -> &web_sys::AnimationEvent {
+ self.downcast::()
+ .expect("event should be a WebAnimationEvent")
+ }
+}
+
+impl WebEventExt for dioxus_html::ClipboardData {
+ fn web_event(&self) -> &web_sys::Event {
+ &self
+ .downcast::()
+ .expect("event should be a web_sys::Event")
+ }
+}
+
+impl WebEventExt for dioxus_html::CompositionData {
+ fn web_event(&self) -> &web_sys::CompositionEvent {
+ self.downcast::()
+ .expect("event should be a WebCompositionEvent")
+ }
+}
+
+impl WebEventExt for dioxus_html::DragData {
+ fn web_event(&self) -> &web_sys::MouseEvent {
+ self.downcast::()
+ .expect("event should be a WebMouseEvent")
+ }
+}
+
+impl WebEventExt for dioxus_html::FocusData {
+ fn web_event(&self) -> &web_sys::FocusEvent {
+ self.downcast::()
+ .expect("event should be a WebFocusEvent")
+ }
+}
+
+impl WebEventExt for dioxus_html::FormData {
+ fn web_event(&self) -> &web_sys::Event {
+ self.downcast::()
+ .expect("event should be a WebFormData")
+ }
+}
+
+impl WebEventExt for dioxus_html::ImageData {
+ fn web_event(&self) -> &WebImageEvent {
+ self.downcast::()
+ .expect("event should be a WebImageEvent")
+ }
+}
+
+impl WebEventExt for dioxus_html::KeyboardData {
+ fn web_event(&self) -> &web_sys::KeyboardEvent {
+ self.downcast::()
+ .expect("event should be a WebKeyboardEvent")
+ }
+}
+
+impl WebEventExt for dioxus_html::MediaData {
+ fn web_event(&self) -> &web_sys::Event {
+ self.downcast::()
+ .expect("event should be a WebMediaEvent")
+ }
+}
+
+impl WebEventExt for MountedData {
+ fn web_event(&self) -> &MountedData {
+ self.downcast::()
+ .expect("event should be a WebMountedEvent")
+ }
+}
+
+impl WebEventExt for dioxus_html::MouseData {
+ fn web_event(&self) -> &web_sys::MouseEvent {
+ self.downcast::()
+ .expect("event should be a WebMouseEvent")
+ }
+}
+
+impl WebEventExt for dioxus_html::PointerData {
+ fn web_event(&self) -> &web_sys::PointerEvent {
+ self.downcast::()
+ .expect("event should be a WebPointerEvent")
+ }
+}
+
+impl WebEventExt for ScrollData {
+ fn web_event(&self) -> &web_sys::Event {
+ self.downcast::()
+ .expect("event should be a WebScrollEvent")
+ }
+}
+
+impl WebEventExt for dioxus_html::SelectionData {
+ fn web_event(&self) -> &web_sys::Event {
+ self.downcast::()
+ .expect("event should be a WebSelectionEvent")
+ }
+}
+
+impl WebEventExt for dioxus_html::ToggleData {
+ fn web_event(&self) -> &web_sys::Event {
+ self.downcast::()
+ .expect("event should be a WebToggleEvent")
+ }
+}
+
+impl WebEventExt for dioxus_html::TouchData {
+ fn web_event(&self) -> &web_sys::TouchEvent {
+ self.downcast::()
+ .expect("event should be a WebTouchEvent")
+ }
+}
+
+impl WebEventExt for dioxus_html::TransitionData {
+ fn web_event(&self) -> &web_sys::TransitionEvent {
+ self.downcast::()
+ .expect("event should be a WebTransitionEvent")
+ }
+}
+
+impl WebEventExt for dioxus_html::WheelData {
+ fn web_event(&self) -> &web_sys::WheelEvent {
+ self.downcast::()
+ .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::()
+ .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 = std::collections::HashMap::new();
+
+ // 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::() {
+ let item_values =
+ item_values.iter().filter_map(|v| v.as_string()).collect();
+
+ values.insert(name, item_values);
+ }
+ }
+ }
+ }
+ }
+
+ values
+ }
+
+ fn files(&self) -> Option> {
+ #[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
+ })
+ })
+ });
+
+ 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;
+}
diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs
index 016d805e3..002bdb916 100644
--- a/packages/web/src/lib.rs
+++ b/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))]