add extension to downcast web events
This commit is contained in:
1 parent
8ba1c05a93
commit
097eb2099c
4 files changed
+442
-288
No files matched your search
@@ -10,6 +10,18 @@ pub struct PointerData {
|
|||||||
inner: Box<dyn HasPointerData>,
|
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 {
|
impl<E: HasPointerData> From<E> for PointerData {
|
||||||
fn from(e: E) -> Self {
|
fn from(e: E) -> Self {
|
||||||
Self { inner: Box::new(e) }
|
Self { inner: Box::new(e) }
|
||||||
|
|||||||
+8
-288
@@ -9,19 +9,14 @@
|
|||||||
use dioxus_core::{
|
use dioxus_core::{
|
||||||
BorrowedAttributeValue, ElementId, Mutation, Template, TemplateAttribute, TemplateNode,
|
BorrowedAttributeValue, ElementId, Mutation, Template, TemplateAttribute, TemplateNode,
|
||||||
};
|
};
|
||||||
use dioxus_html::{
|
use dioxus_html::{event_bubbles, PlatformEventData};
|
||||||
event_bubbles, FileEngine, FormData, HasFormData, HasImageData, HtmlEventConverter, ImageData,
|
use dioxus_interpreter_js::{minimal_bindings, save_template, Channel};
|
||||||
MountedData, PlatformEventData, ScrollData,
|
|
||||||
};
|
|
||||||
use dioxus_interpreter_js::{get_node, minimal_bindings, save_template, Channel};
|
|
||||||
use futures_channel::mpsc;
|
use futures_channel::mpsc;
|
||||||
use js_sys::Array;
|
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use std::{any::Any, collections::HashMap};
|
use wasm_bindgen::{closure::Closure, JsCast, JsValue};
|
||||||
use wasm_bindgen::{closure::Closure, prelude::wasm_bindgen, JsCast, JsValue};
|
|
||||||
use web_sys::{Document, Element, Event};
|
use web_sys::{Document, Element, Event};
|
||||||
|
|
||||||
use crate::Config;
|
use crate::{load_document, virtual_event_from_websys_event, Config, WebEventConverter};
|
||||||
|
|
||||||
pub struct WebsysDom {
|
pub struct WebsysDom {
|
||||||
document: Document,
|
document: Document,
|
||||||
@@ -30,6 +25,7 @@ pub struct WebsysDom {
|
|||||||
templates: FxHashMap<String, u32>,
|
templates: FxHashMap<String, u32>,
|
||||||
max_template_id: u32,
|
max_template_id: u32,
|
||||||
pub(crate) interpreter: Channel,
|
pub(crate) interpreter: Channel,
|
||||||
|
#[cfg(feature = "mounted")]
|
||||||
event_channel: mpsc::UnboundedSender<UiEvent>,
|
event_channel: mpsc::UnboundedSender<UiEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +100,7 @@ impl WebsysDom {
|
|||||||
interpreter,
|
interpreter,
|
||||||
templates: FxHashMap::default(),
|
templates: FxHashMap::default(),
|
||||||
max_template_id: 0,
|
max_template_id: 0,
|
||||||
|
#[cfg(feature = "mounted")]
|
||||||
event_channel,
|
event_channel,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -253,10 +250,10 @@ impl WebsysDom {
|
|||||||
|
|
||||||
#[cfg(feature = "mounted")]
|
#[cfg(feature = "mounted")]
|
||||||
for id in to_mount {
|
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>() {
|
if let Some(element) = node.dyn_ref::<Element>() {
|
||||||
log::info!("mounted event fired: {}", id.0);
|
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 data = PlatformEventData::new(Box::new(data));
|
||||||
let _ = self.event_channel.unbounded_send(UiEvent {
|
let _ = self.event_channel.unbounded_send(UiEvent {
|
||||||
name: "mounted".to_string(),
|
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)> {
|
fn walk_event_for_id(event: &web_sys::Event) -> Option<(ElementId, web_sys::Element)> {
|
||||||
let target = event
|
let target = event
|
||||||
.target()
|
.target()
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -69,6 +69,8 @@ mod cfg;
|
|||||||
mod dom;
|
mod dom;
|
||||||
#[cfg(feature = "eval")]
|
#[cfg(feature = "eval")]
|
||||||
mod eval;
|
mod eval;
|
||||||
|
mod event;
|
||||||
|
pub use event::*;
|
||||||
#[cfg(feature = "file_engine")]
|
#[cfg(feature = "file_engine")]
|
||||||
mod file_engine;
|
mod file_engine;
|
||||||
#[cfg(all(feature = "hot_reload", debug_assertions))]
|
#[cfg(all(feature = "hot_reload", debug_assertions))]
|
||||||
|
|||||||
Reference in new issue
Block a user