From 2f49a896386be771acfdbf5fe02a7ba5673eb77f Mon Sep 17 00:00:00 2001 From: ASR-ASU <153063951+ASR-ASU@users.noreply.github.com> Date: Thu, 15 Aug 2024 03:23:49 +0200 Subject: [PATCH] Add the `onresize` event handler to Element (#2479) * Add the capability to handle resize event for web target * Add the capability to handle resize event for desktop target * Return all the sizes, not just the first one * Fix conversion from platform to generic ResizedData for liveview * Update the generated interpreter js code base * Fix clippy warnings * Fix inconsistent use of block_size and inline_size * Rename `onresized` event to `onresize` * Remove the the special-casing logic from the binding logic * Propagating the resize events using CustomEvent * Fix case convention in core ts * revert changes to unified bindings * Cleanup as suggested * add a resize example * Fix desktop resize events * remove tracing from resize example * use the raw resize entry so we can downcast on web * remove unused ResizeEventDetail --------- Co-authored-by: Evan Almloff --- examples/resize.rs | 27 +++ packages/desktop/src/events.rs | 8 + packages/html/Cargo.toml | 3 + packages/html/src/events/mod.rs | 11 ++ packages/html/src/events/resize.rs | 168 +++++++++++++++++++ packages/html/src/transit.rs | 14 ++ packages/html/src/web_sys_bind/events.rs | 48 +++++- packages/interpreter/src/js/core.js | 2 +- packages/interpreter/src/js/hash.txt | 2 +- packages/interpreter/src/js/native.js | 2 +- packages/interpreter/src/ts/core.ts | 37 +++- packages/interpreter/src/ts/native.ts | 5 +- packages/interpreter/src/ts/serialize.ts | 48 ++++++ packages/interpreter/src/unified_bindings.rs | 3 +- packages/interpreter/tsconfig.json | 1 + packages/liveview/src/events.rs | 8 + packages/web/Cargo.toml | 3 +- packages/web/src/dom.rs | 7 +- packages/web/src/event.rs | 95 ++++++----- 19 files changed, 442 insertions(+), 50 deletions(-) create mode 100644 examples/resize.rs create mode 100644 packages/html/src/events/resize.rs diff --git a/examples/resize.rs b/examples/resize.rs new file mode 100644 index 000000000..eace774a0 --- /dev/null +++ b/examples/resize.rs @@ -0,0 +1,27 @@ +//! Run a callback +//! +//! Whenever an Element is finally mounted to the Dom, its data is available to be read. +//! These fields can typically only be read asynchronously, since various renderers need to release the main thread to +//! perform layout and painting. + +use dioxus::prelude::*; +use dioxus_elements::geometry::euclid::Size2D; + +fn main() { + launch(app); +} + +fn app() -> Element { + let mut dimensions = use_signal(Size2D::zero); + + rsx!( + head::Link { rel: "stylesheet", href: asset!("./examples/assets/read_size.css") } + div { + width: "50%", + height: "50%", + background_color: "red", + onresize: move |evt| dimensions.set(evt.data().get_content_box_size().unwrap()), + "This element is {dimensions():?}" + } + ) +} diff --git a/packages/desktop/src/events.rs b/packages/desktop/src/events.rs index 5e772320b..2073be5a8 100644 --- a/packages/desktop/src/events.rs +++ b/packages/desktop/src/events.rs @@ -113,6 +113,14 @@ impl HtmlEventConverter for SerializedHtmlEventConverter { .into() } + fn convert_resize_data(&self, event: &PlatformEventData) -> ResizeData { + event + .downcast::() + .cloned() + .unwrap() + .into() + } + fn convert_scroll_data(&self, event: &PlatformEventData) -> ScrollData { event .downcast::() diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 639310c62..bd95323cf 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -50,6 +50,9 @@ features = [ "PointerEvent", "FocusEvent", "CompositionEvent", + "CustomEvent", + "ResizeObserverEntry", + "ResizeObserverSize" ] [build-dependencies] diff --git a/packages/html/src/events/mod.rs b/packages/html/src/events/mod.rs index c1bc613e5..29fcf0ded 100644 --- a/packages/html/src/events/mod.rs +++ b/packages/html/src/events/mod.rs @@ -134,6 +134,8 @@ pub trait HtmlEventConverter: Send + Sync { fn convert_mouse_data(&self, event: &PlatformEventData) -> MouseData; /// Convert a general event to a pointer data event fn convert_pointer_data(&self, event: &PlatformEventData) -> PointerData; + /// Convert a general event to a resize data event + fn convert_resize_data(&self, event: &PlatformEventData) -> ResizeData; /// Convert a general event to a scroll data event fn convert_scroll_data(&self, event: &PlatformEventData) -> ScrollData; /// Convert a general event to a selection data event @@ -220,6 +222,12 @@ impl From<&PlatformEventData> for PointerData { } } +impl From<&PlatformEventData> for ResizeData { + fn from(val: &PlatformEventData) -> Self { + with_event_converter(|c| c.convert_resize_data(val)) + } +} + impl From<&PlatformEventData> for ScrollData { fn from(val: &PlatformEventData) -> Self { with_event_converter(|c| c.convert_scroll_data(val)) @@ -268,6 +276,7 @@ mod media; mod mounted; mod mouse; mod pointer; +mod resize; mod scroll; mod selection; mod toggle; @@ -287,6 +296,7 @@ pub use media::*; pub use mounted::*; pub use mouse::*; pub use pointer::*; +pub use resize::*; pub use scroll::*; pub use selection::*; pub use toggle::*; @@ -367,6 +377,7 @@ pub fn event_bubbles(evt: &str) -> bool { "playing" => false, "progress" => false, "ratechange" => false, + "resize" => false, "seeked" => false, "seeking" => false, "stalled" => false, diff --git a/packages/html/src/events/resize.rs b/packages/html/src/events/resize.rs new file mode 100644 index 000000000..e489fc948 --- /dev/null +++ b/packages/html/src/events/resize.rs @@ -0,0 +1,168 @@ +use std::fmt::{Display, Formatter}; + +pub struct ResizeData { + inner: Box, +} + +impl From for ResizeData { + fn from(e: E) -> Self { + Self { inner: Box::new(e) } + } +} + +impl ResizeData { + /// Create a new ResizeData + pub fn new(inner: impl HasResizeData + 'static) -> Self { + Self { + inner: Box::new(inner), + } + } + + /// Get the border box size of the observed element + pub fn get_border_box_size(&self) -> ResizeResult { + self.inner.get_border_box_size() + } + + /// Get the content box size of the observed element + pub fn get_content_box_size(&self) -> ResizeResult { + self.inner.get_content_box_size() + } + + /// Downcast this event to a concrete event type + pub fn downcast(&self) -> Option<&T> { + self.inner.as_any().downcast_ref::() + } +} + +impl std::fmt::Debug for ResizeData { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ResizeData") + .field("border_box_size", &self.inner.get_border_box_size()) + .field("content_box_size", &self.inner.get_content_box_size()) + .finish() + } +} + +impl PartialEq for ResizeData { + fn eq(&self, _: &Self) -> bool { + true + } +} + +#[cfg(feature = "serialize")] +/// A serialized version of ResizeData +#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)] +pub struct SerializedResizeData { + pub border_box_size: PixelsSize, + pub content_box_size: PixelsSize, +} + +#[cfg(feature = "serialize")] +impl SerializedResizeData { + /// Create a new SerializedResizeData + pub fn new(border_box_size: PixelsSize, content_box_size: PixelsSize) -> Self { + Self { + border_box_size, + content_box_size, + } + } +} + +#[cfg(feature = "serialize")] +impl From<&ResizeData> for SerializedResizeData { + fn from(data: &ResizeData) -> Self { + Self::new( + data.get_border_box_size().unwrap(), + data.get_content_box_size().unwrap(), + ) + } +} + +#[cfg(feature = "serialize")] +impl HasResizeData for SerializedResizeData { + /// Get the border box size of the observed element + fn get_border_box_size(&self) -> ResizeResult { + Ok(self.border_box_size) + } + + /// Get the content box size of the observed element + fn get_content_box_size(&self) -> ResizeResult { + Ok(self.content_box_size) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +#[cfg(feature = "serialize")] +impl serde::Serialize for ResizeData { + fn serialize(&self, serializer: S) -> Result { + SerializedResizeData::from(self).serialize(serializer) + } +} + +#[cfg(feature = "serialize")] +impl<'de> serde::Deserialize<'de> for ResizeData { + fn deserialize>(deserializer: D) -> Result { + let data = SerializedResizeData::deserialize(deserializer)?; + Ok(Self { + inner: Box::new(data), + }) + } +} + +pub trait HasResizeData: std::any::Any { + /// Get the border box size of the observed element + fn get_border_box_size(&self) -> ResizeResult { + Err(ResizeError::NotSupported) + } + /// Get the content box size of the observed element + fn get_content_box_size(&self) -> ResizeResult { + Err(ResizeError::NotSupported) + } + + /// return self as Any + fn as_any(&self) -> &dyn std::any::Any; +} + +use dioxus_core::Event; + +use crate::geometry::PixelsSize; + +pub type ResizeEvent = Event; + +impl_event! { + ResizeData; + + /// onresize + onresize +} + +/// The ResizeResult type for the ResizeData +pub type ResizeResult = Result; + +#[derive(Debug)] +/// The error type for the MountedData +#[non_exhaustive] +pub enum ResizeError { + /// The renderer does not support the requested operation + NotSupported, + /// The element was not found + OperationFailed(Box), +} + +impl Display for ResizeError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ResizeError::NotSupported => { + write!(f, "The renderer does not support the requested operation") + } + ResizeError::OperationFailed(e) => { + write!(f, "The operation failed: {}", e) + } + } + } +} + +impl std::error::Error for ResizeError {} diff --git a/packages/html/src/transit.rs b/packages/html/src/transit.rs index 32b33620c..7e28790b3 100644 --- a/packages/html/src/transit.rs +++ b/packages/html/src/transit.rs @@ -99,6 +99,9 @@ fn deserialize_raw(name: &str, data: &serde_json::Value) -> Result Touch(de(data)?), + // Resize + "resize" => Resize(de(data)?), + // Scroll "scroll" => Scroll(de(data)?), @@ -158,6 +161,7 @@ pub enum EventData { Pointer(SerializedPointerData), Selection(SerializedSelectionData), Touch(SerializedTouchData), + Resize(SerializedResizeData), Scroll(SerializedScrollData), Wheel(SerializedWheelData), Media(SerializedMediaData), @@ -197,6 +201,9 @@ impl EventData { EventData::Touch(data) => { Rc::new(PlatformEventData::new(Box::new(data))) as Rc } + EventData::Resize(data) => { + Rc::new(PlatformEventData::new(Box::new(data))) as Rc + } EventData::Scroll(data) => { Rc::new(PlatformEventData::new(Box::new(data))) as Rc } @@ -360,6 +367,13 @@ impl HtmlEventConverter for SerializedHtmlEventConverter { .unwrap() .into() } + fn convert_resize_data(&self, event: &PlatformEventData) -> ResizeData { + event + .downcast::() + .cloned() + .unwrap() + .into() + } fn convert_scroll_data(&self, event: &PlatformEventData) -> ScrollData { event diff --git a/packages/html/src/web_sys_bind/events.rs b/packages/html/src/web_sys_bind/events.rs index 7ecd778a2..58e39cc11 100644 --- a/packages/html/src/web_sys_bind/events.rs +++ b/packages/html/src/web_sys_bind/events.rs @@ -4,15 +4,17 @@ use crate::events::{ 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, Event, KeyboardEvent, MouseEvent, PointerEvent, Touch, - TouchEvent, TransitionEvent, WheelEvent, + AnimationEvent, CompositionEvent, CustomEvent, Event, KeyboardEvent, MouseEvent, PointerEvent, + Touch, TouchEvent, TransitionEvent, WheelEvent, }; macro_rules! uncheck_convert { @@ -51,6 +53,22 @@ uncheck_convert![ 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() @@ -571,6 +589,32 @@ impl crate::RenderedElementBacking for web_sys::Element { } } +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 diff --git a/packages/interpreter/src/js/core.js b/packages/interpreter/src/js/core.js index 7acedae1a..01bc2f894 100644 --- a/packages/interpreter/src/js/core.js +++ b/packages/interpreter/src/js/core.js @@ -1 +1 @@ -function setAttributeInner(node,field,value,ns){if(ns==="style"){node.style.setProperty(field,value);return}if(ns){node.setAttributeNS(ns,field,value);return}switch(field){case"value":if(node.value!==value)node.value=value;break;case"initial_value":node.defaultValue=value;break;case"checked":node.checked=truthy(value);break;case"initial_checked":node.defaultChecked=truthy(value);break;case"selected":node.selected=truthy(value);break;case"initial_selected":node.defaultSelected=truthy(value);break;case"dangerous_inner_html":node.innerHTML=value;break;default:if(!truthy(value)&&isBoolAttr(field))node.removeAttribute(field);else node.setAttribute(field,value)}}var truthy=function(val){return val==="true"||val===!0},isBoolAttr=function(field){switch(field){case"allowfullscreen":case"allowpaymentrequest":case"async":case"autofocus":case"autoplay":case"checked":case"controls":case"default":case"defer":case"disabled":case"formnovalidate":case"hidden":case"ismap":case"itemscope":case"loop":case"multiple":case"muted":case"nomodule":case"novalidate":case"open":case"playsinline":case"readonly":case"required":case"reversed":case"selected":case"truespeed":case"webkitdirectory":return!0;default:return!1}};class BaseInterpreter{global;local;root;handler;nodes;stack;templates;m;constructor(){}initialize(root,handler=null){if(this.global={},this.local={},this.root=root,this.nodes=[root],this.stack=[root],this.templates={},handler)this.handler=handler}createListener(event_name,element,bubbles){if(bubbles)if(this.global[event_name]===void 0)this.global[event_name]={active:1,callback:this.handler},this.root.addEventListener(event_name,this.handler);else this.global[event_name].active++;else{const id=element.getAttribute("data-dioxus-id");if(!this.local[id])this.local[id]={};element.addEventListener(event_name,this.handler)}}removeListener(element,event_name,bubbles){if(bubbles)this.removeBubblingListener(event_name);else this.removeNonBubblingListener(element,event_name)}removeBubblingListener(event_name){if(this.global[event_name].active--,this.global[event_name].active===0)this.root.removeEventListener(event_name,this.global[event_name].callback),delete this.global[event_name]}removeNonBubblingListener(element,event_name){const id=element.getAttribute("data-dioxus-id");if(delete this.local[id][event_name],Object.keys(this.local[id]).length===0)delete this.local[id];element.removeEventListener(event_name,this.handler)}removeAllNonBubblingListeners(element){const id=element.getAttribute("data-dioxus-id");delete this.local[id]}getNode(id){return this.nodes[id]}pushRoot(node){this.stack.push(node)}appendChildren(id,many){const root=this.nodes[id],els=this.stack.splice(this.stack.length-many);for(let k=0;k0;end--)node=node.nextSibling}return node}saveTemplate(nodes,tmpl_id){this.templates[tmpl_id]=nodes}hydrate_node(hydrateNode,ids){const split=hydrateNode.getAttribute("data-node-hydration").split(","),id=ids[parseInt(split[0])];if(this.nodes[id]=hydrateNode,split.length>1){hydrateNode.listening=split.length-1,hydrateNode.setAttribute("data-dioxus-id",id.toString());for(let j=1;j1){if(this.nodes[ids[parseInt(placeholderSplit[1])]]=currentNode,!treeWalker.nextNode())break;continue}const textNodeSplit=id.split("node-id");if(textNodeSplit.length>1){let next=currentNode.nextSibling;currentNode.remove();let commentAfterText,textNode;if(next.nodeType===Node.COMMENT_NODE){const newText=next.parentElement.insertBefore(document.createTextNode(""),next);commentAfterText=next,textNode=newText}else textNode=next,commentAfterText=textNode.nextSibling;treeWalker.currentNode=commentAfterText,this.nodes[ids[parseInt(textNodeSplit[1])]]=textNode;let exit=!treeWalker.nextNode();if(commentAfterText.remove(),exit)break;continue}}if(!treeWalker.nextNode())break}}}setAttributeInner(node,field,value,ns){setAttributeInner(node,field,value,ns)}}export{BaseInterpreter}; +function setAttributeInner(node,field,value,ns){if(ns==="style"){node.style.setProperty(field,value);return}if(ns){node.setAttributeNS(ns,field,value);return}switch(field){case"value":if(node.value!==value)node.value=value;break;case"initial_value":node.defaultValue=value;break;case"checked":node.checked=truthy(value);break;case"initial_checked":node.defaultChecked=truthy(value);break;case"selected":node.selected=truthy(value);break;case"initial_selected":node.defaultSelected=truthy(value);break;case"dangerous_inner_html":node.innerHTML=value;break;default:if(!truthy(value)&&isBoolAttr(field))node.removeAttribute(field);else node.setAttribute(field,value)}}var truthy=function(val){return val==="true"||val===!0},isBoolAttr=function(field){switch(field){case"allowfullscreen":case"allowpaymentrequest":case"async":case"autofocus":case"autoplay":case"checked":case"controls":case"default":case"defer":case"disabled":case"formnovalidate":case"hidden":case"ismap":case"itemscope":case"loop":case"multiple":case"muted":case"nomodule":case"novalidate":case"open":case"playsinline":case"readonly":case"required":case"reversed":case"selected":case"truespeed":case"webkitdirectory":return!0;default:return!1}};class BaseInterpreter{global;local;root;handler;resizeObserver;nodes;stack;templates;m;constructor(){}initialize(root,handler=null){this.global={},this.local={},this.root=root,this.nodes=[root],this.stack=[root],this.templates={},this.handler=handler}handleResizeEvent(entry){const target=entry.target;let event=new CustomEvent("resize",{bubbles:!1,detail:entry});target.dispatchEvent(event)}createObserver(element){if(!this.resizeObserver)this.resizeObserver=new ResizeObserver((entries)=>{for(let entry of entries)this.handleResizeEvent(entry)});this.resizeObserver.observe(element)}removeObserver(element){if(this.resizeObserver)this.resizeObserver.unobserve(element)}createListener(event_name,element,bubbles){if(bubbles)if(this.global[event_name]===void 0)this.global[event_name]={active:1,callback:this.handler},this.root.addEventListener(event_name,this.handler);else this.global[event_name].active++;else{const id=element.getAttribute("data-dioxus-id");if(!this.local[id])this.local[id]={};element.addEventListener(event_name,this.handler)}if(event_name=="resize")this.createObserver(element)}removeListener(element,event_name,bubbles){if(bubbles)this.removeBubblingListener(event_name);else this.removeNonBubblingListener(element,event_name)}removeBubblingListener(event_name){if(this.global[event_name].active--,this.global[event_name].active===0)this.root.removeEventListener(event_name,this.global[event_name].callback),delete this.global[event_name]}removeNonBubblingListener(element,event_name){const id=element.getAttribute("data-dioxus-id");if(delete this.local[id][event_name],Object.keys(this.local[id]).length===0)delete this.local[id];element.removeEventListener(event_name,this.handler)}removeAllNonBubblingListeners(element){const id=element.getAttribute("data-dioxus-id");delete this.local[id]}getNode(id){return this.nodes[id]}pushRoot(node){this.stack.push(node)}appendChildren(id,many){const root=this.nodes[id],els=this.stack.splice(this.stack.length-many);for(let k=0;k0;end--)node=node.nextSibling}return node}saveTemplate(nodes,tmpl_id){this.templates[tmpl_id]=nodes}hydrate_node(hydrateNode,ids){const split=hydrateNode.getAttribute("data-node-hydration").split(","),id=ids[parseInt(split[0])];if(this.nodes[id]=hydrateNode,split.length>1){hydrateNode.listening=split.length-1,hydrateNode.setAttribute("data-dioxus-id",id.toString());for(let j=1;j1){if(this.nodes[ids[parseInt(placeholderSplit[1])]]=currentNode,!treeWalker.nextNode())break;continue}const textNodeSplit=id.split("node-id");if(textNodeSplit.length>1){let next=currentNode.nextSibling;currentNode.remove();let commentAfterText,textNode;if(next.nodeType===Node.COMMENT_NODE){const newText=next.parentElement.insertBefore(document.createTextNode(""),next);commentAfterText=next,textNode=newText}else textNode=next,commentAfterText=textNode.nextSibling;treeWalker.currentNode=commentAfterText,this.nodes[ids[parseInt(textNodeSplit[1])]]=textNode;let exit=!treeWalker.nextNode();if(commentAfterText.remove(),exit)break;continue}}if(!treeWalker.nextNode())break}}}setAttributeInner(node,field,value,ns){setAttributeInner(node,field,value,ns)}}export{BaseInterpreter}; diff --git a/packages/interpreter/src/js/hash.txt b/packages/interpreter/src/js/hash.txt index 1d1db096b..2220fd98e 100644 --- a/packages/interpreter/src/js/hash.txt +++ b/packages/interpreter/src/js/hash.txt @@ -1 +1 @@ -[6449103750905854967, 12029349297046688094, 13069001215487072322, 8716623267269178440, 5336385715226370016, 14456089431355876478, 5618841090288840293, 5052021921702764563, 16478152596505612522, 5638004933879392817] \ No newline at end of file +[6449103750905854967, 4461869229701639737, 13069001215487072322, 8716623267269178440, 5336385715226370016, 14456089431355876478, 11703635120274352436, 5052021921702764563, 17534315583914394253, 5638004933879392817] \ No newline at end of file diff --git a/packages/interpreter/src/js/native.js b/packages/interpreter/src/js/native.js index 52876ec73..30d1171db 100644 --- a/packages/interpreter/src/js/native.js +++ b/packages/interpreter/src/js/native.js @@ -1 +1 @@ -function retrieveValues(event,target){let contents={values:{}},form=target.closest("form");if(form){if(event.type==="input"||event.type==="change"||event.type==="submit"||event.type==="reset"||event.type==="click")contents=retrieveFormValues(form)}return contents}function retrieveFormValues(form){const formData=new FormData(form),contents={};return formData.forEach((value,key)=>{if(contents[key])contents[key].push(value);else contents[key]=[value]}),{valid:form.checkValidity(),values:contents}}function retrieveSelectValue(target){let options=target.selectedOptions,values=[];for(let i=0;icontents={...contents,...obj};if(event instanceof WheelEvent)extend(serializeWheelEvent(event));if(event instanceof MouseEvent)extend(serializeMouseEvent(event));if(event instanceof KeyboardEvent)extend(serializeKeyboardEvent(event));if(event instanceof InputEvent)extend(serializeInputEvent(event,target));if(event instanceof PointerEvent)extend(serializePointerEvent(event));if(event instanceof AnimationEvent)extend(serializeAnimationEvent(event));if(event instanceof TransitionEvent)extend({property_name:event.propertyName,elapsed_time:event.elapsedTime,pseudo_element:event.pseudoElement});if(event instanceof CompositionEvent)extend({data:event.data});if(event instanceof DragEvent)extend(serializeDragEvent(event));if(event instanceof FocusEvent)extend({});if(event instanceof ClipboardEvent)extend({});if(typeof TouchEvent!=="undefined"&&event instanceof TouchEvent)extend(serializeTouchEvent(event));if(event.type==="submit"||event.type==="reset"||event.type==="click"||event.type==="change"||event.type==="input")extend(serializeInputEvent(event,target));if(event instanceof DragEvent);return contents}var serializeInputEvent=function(event,target){let contents={};if(target instanceof HTMLElement){let values=retrieveValues(event,target);contents.values=values.values,contents.valid=values.valid}if(event.target instanceof HTMLInputElement){let target2=event.target,value=target2.value??target2.textContent??"";if(target2.type==="checkbox")value=target2.checked?"true":"false";else if(target2.type==="radio")value=target2.value;contents.value=value}if(event.target instanceof HTMLTextAreaElement)contents.value=event.target.value;if(event.target instanceof HTMLSelectElement)contents.value=retrieveSelectValue(event.target).join(",");if(contents.value===void 0)contents.value="";return contents},serializeWheelEvent=function(event){return{delta_x:event.deltaX,delta_y:event.deltaY,delta_z:event.deltaZ,delta_mode:event.deltaMode}},serializeTouchEvent=function(event){return{alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,shift_key:event.shiftKey,changed_touches:event.changedTouches,target_touches:event.targetTouches,touches:event.touches}},serializePointerEvent=function(event){return{alt_key:event.altKey,button:event.button,buttons:event.buttons,client_x:event.clientX,client_y:event.clientY,ctrl_key:event.ctrlKey,meta_key:event.metaKey,page_x:event.pageX,page_y:event.pageY,screen_x:event.screenX,screen_y:event.screenY,shift_key:event.shiftKey,pointer_id:event.pointerId,width:event.width,height:event.height,pressure:event.pressure,tangential_pressure:event.tangentialPressure,tilt_x:event.tiltX,tilt_y:event.tiltY,twist:event.twist,pointer_type:event.pointerType,is_primary:event.isPrimary}},serializeMouseEvent=function(event){return{alt_key:event.altKey,button:event.button,buttons:event.buttons,client_x:event.clientX,client_y:event.clientY,ctrl_key:event.ctrlKey,meta_key:event.metaKey,offset_x:event.offsetX,offset_y:event.offsetY,page_x:event.pageX,page_y:event.pageY,screen_x:event.screenX,screen_y:event.screenY,shift_key:event.shiftKey}},serializeKeyboardEvent=function(event){return{char_code:event.charCode,is_composing:event.isComposing,key:event.key,alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,key_code:event.keyCode,shift_key:event.shiftKey,location:event.location,repeat:event.repeat,which:event.which,code:event.code}},serializeAnimationEvent=function(event){return{animation_name:event.animationName,elapsed_time:event.elapsedTime,pseudo_element:event.pseudoElement}},serializeDragEvent=function(event){let files=void 0;if(event.dataTransfer&&event.dataTransfer.files&&event.dataTransfer.files.length>0)files={files:{placeholder:[]}};return{mouse:{alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,shift_key:event.shiftKey,...serializeMouseEvent(event)},files}};var handleVirtualdomEventSync=function(endpoint,contents){const xhr=new XMLHttpRequest;return xhr.open("POST",endpoint,!1),xhr.setRequestHeader("Content-Type","application/json"),xhr.send(contents),JSON.parse(xhr.responseText)},getTargetId=function(target){if(!(target instanceof Node))return null;let ourTarget=target,realId=null;while(realId==null){if(ourTarget===null)return null;if(ourTarget instanceof Element)realId=ourTarget.getAttribute("data-dioxus-id");ourTarget=ourTarget.parentNode}return parseInt(realId)},JSChannel_;if(RawInterpreter!==void 0&&RawInterpreter!==null)JSChannel_=RawInterpreter;class NativeInterpreter extends JSChannel_{intercept_link_redirects;ipc;editsPath;eventsPath;kickStylesheets;queuedBytes=[];liveview;constructor(editsPath,eventsPath){super();this.editsPath=editsPath,this.eventsPath=eventsPath,this.kickStylesheets=!1}initialize(root){this.intercept_link_redirects=!0,this.liveview=!1,window.addEventListener("dragover",function(e){if(e.target instanceof Element&&e.target.tagName!="INPUT")e.preventDefault()},!1),window.addEventListener("drop",function(e){if(!(e.target instanceof Element))return;e.preventDefault()},!1),window.addEventListener("click",(event)=>{const target=event.target;if(target instanceof HTMLInputElement&&target.getAttribute("type")==="file"){let target_id=getTargetId(target);if(target_id!==null){const message=this.serializeIpcMessage("file_dialog",{event:"change&input",accept:target.getAttribute("accept"),directory:target.getAttribute("webkitdirectory")==="true",multiple:target.hasAttribute("multiple"),target:target_id,bubbles:event.bubbles});this.ipc.postMessage(message),event.preventDefault()}}}),this.ipc=window.ipc;const handler=(event)=>this.handleEvent(event,event.type,!0);super.initialize(root,handler)}serializeIpcMessage(method,params={}){return JSON.stringify({method,params})}scrollTo(id,behavior){const node=this.nodes[id];if(node instanceof HTMLElement)node.scrollIntoView({behavior})}getScrollHeight(id){const node=this.nodes[id];if(node instanceof HTMLElement)return node.scrollHeight}getScrollLeft(id){const node=this.nodes[id];if(node instanceof HTMLElement)return node.scrollLeft}getScrollTop(id){const node=this.nodes[id];if(node instanceof HTMLElement)return node.scrollTop}getScrollWidth(id){const node=this.nodes[id];if(node instanceof HTMLElement)return node.scrollWidth}getClientRect(id){const node=this.nodes[id];if(node instanceof HTMLElement){const rect=node.getBoundingClientRect();return{type:"GetClientRect",origin:[rect.x,rect.y],size:[rect.width,rect.height]}}}setFocus(id,focus){const node=this.nodes[id];if(node instanceof HTMLElement)if(focus)node.focus();else node.blur()}loadChild(array){let node=this.stack[this.stack.length-1];for(let i=0;i0;end--)node=node.nextSibling}return node}appendChildren(id,many){const root=this.nodes[id],els=this.stack.splice(this.stack.length-many);for(let k=0;k{this.flushQueuedBytes(),this.waitForRequest(headless)})}waitForRequest(headless){fetch(new Request(this.editsPath)).then((response)=>response.arrayBuffer()).then((bytes)=>{this.rafEdits(headless,bytes)})}kickAllStylesheetsOnPage(){let stylesheets=document.querySelectorAll("link[rel=stylesheet]");for(let i=0;i{sheet.href=sheet.href+"?"+Math.random()})}}async readFiles(target,contents,bubbles,realId,name){let files=target.files,file_contents={};for(let i=0;i{if(contents[key])contents[key].push(value);else contents[key]=[value]}),{valid:form.checkValidity(),values:contents}}function retrieveSelectValue(target){let options=target.selectedOptions,values=[];for(let i=0;icontents={...contents,...obj};if(event instanceof WheelEvent)extend(serializeWheelEvent(event));if(event instanceof MouseEvent)extend(serializeMouseEvent(event));if(event instanceof KeyboardEvent)extend(serializeKeyboardEvent(event));if(event instanceof InputEvent)extend(serializeInputEvent(event,target));if(event instanceof PointerEvent)extend(serializePointerEvent(event));if(event instanceof AnimationEvent)extend(serializeAnimationEvent(event));if(event instanceof TransitionEvent)extend({property_name:event.propertyName,elapsed_time:event.elapsedTime,pseudo_element:event.pseudoElement});if(event instanceof CompositionEvent)extend({data:event.data});if(event instanceof DragEvent)extend(serializeDragEvent(event));if(event instanceof FocusEvent)extend({});if(event instanceof ClipboardEvent)extend({});if(event instanceof CustomEvent){const detail=event.detail;if(detail instanceof ResizeObserverEntry)extend(serializeResizeEventDetail(detail))}if(typeof TouchEvent!=="undefined"&&event instanceof TouchEvent)extend(serializeTouchEvent(event));if(event.type==="submit"||event.type==="reset"||event.type==="click"||event.type==="change"||event.type==="input")extend(serializeInputEvent(event,target));if(event instanceof DragEvent);return contents}var toSerializableResizeObserverSize=function(size,is_inline_width){return[is_inline_width?size.inlineSize:size.blockSize,is_inline_width?size.blockSize:size.inlineSize]};function serializeResizeEventDetail(detail){let is_inline_width=!0;if(detail.target instanceof HTMLElement){if(window.getComputedStyle(detail.target).getPropertyValue("writing-mode")!=="horizontal-tb")is_inline_width=!1}return{border_box_size:detail.borderBoxSize!==void 0?toSerializableResizeObserverSize(detail.borderBoxSize[0],is_inline_width):detail.contentRect,content_box_size:detail.contentBoxSize!==void 0?toSerializableResizeObserverSize(detail.contentBoxSize[0],is_inline_width):detail.contentRect,content_rect:detail.contentRect}}var serializeInputEvent=function(event,target){let contents={};if(target instanceof HTMLElement){let values=retrieveValues(event,target);contents.values=values.values,contents.valid=values.valid}if(event.target instanceof HTMLInputElement){let target2=event.target,value=target2.value??target2.textContent??"";if(target2.type==="checkbox")value=target2.checked?"true":"false";else if(target2.type==="radio")value=target2.value;contents.value=value}if(event.target instanceof HTMLTextAreaElement)contents.value=event.target.value;if(event.target instanceof HTMLSelectElement)contents.value=retrieveSelectValue(event.target).join(",");if(contents.value===void 0)contents.value="";return contents},serializeWheelEvent=function(event){return{delta_x:event.deltaX,delta_y:event.deltaY,delta_z:event.deltaZ,delta_mode:event.deltaMode}},serializeTouchEvent=function(event){return{alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,shift_key:event.shiftKey,changed_touches:event.changedTouches,target_touches:event.targetTouches,touches:event.touches}},serializePointerEvent=function(event){return{alt_key:event.altKey,button:event.button,buttons:event.buttons,client_x:event.clientX,client_y:event.clientY,ctrl_key:event.ctrlKey,meta_key:event.metaKey,page_x:event.pageX,page_y:event.pageY,screen_x:event.screenX,screen_y:event.screenY,shift_key:event.shiftKey,pointer_id:event.pointerId,width:event.width,height:event.height,pressure:event.pressure,tangential_pressure:event.tangentialPressure,tilt_x:event.tiltX,tilt_y:event.tiltY,twist:event.twist,pointer_type:event.pointerType,is_primary:event.isPrimary}},serializeMouseEvent=function(event){return{alt_key:event.altKey,button:event.button,buttons:event.buttons,client_x:event.clientX,client_y:event.clientY,ctrl_key:event.ctrlKey,meta_key:event.metaKey,offset_x:event.offsetX,offset_y:event.offsetY,page_x:event.pageX,page_y:event.pageY,screen_x:event.screenX,screen_y:event.screenY,shift_key:event.shiftKey}},serializeKeyboardEvent=function(event){return{char_code:event.charCode,is_composing:event.isComposing,key:event.key,alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,key_code:event.keyCode,shift_key:event.shiftKey,location:event.location,repeat:event.repeat,which:event.which,code:event.code}},serializeAnimationEvent=function(event){return{animation_name:event.animationName,elapsed_time:event.elapsedTime,pseudo_element:event.pseudoElement}},serializeDragEvent=function(event){let files=void 0;if(event.dataTransfer&&event.dataTransfer.files&&event.dataTransfer.files.length>0)files={files:{placeholder:[]}};return{mouse:{alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,shift_key:event.shiftKey,...serializeMouseEvent(event)},files}};var handleVirtualdomEventSync=function(endpoint,contents){const xhr=new XMLHttpRequest;return xhr.open("POST",endpoint,!1),xhr.setRequestHeader("Content-Type","application/json"),xhr.send(contents),JSON.parse(xhr.responseText)},getTargetId=function(target){if(!(target instanceof Node))return null;let ourTarget=target,realId=null;while(realId==null){if(ourTarget===null)return null;if(ourTarget instanceof Element)realId=ourTarget.getAttribute("data-dioxus-id");ourTarget=ourTarget.parentNode}return parseInt(realId)},JSChannel_;if(RawInterpreter!==void 0&&RawInterpreter!==null)JSChannel_=RawInterpreter;class NativeInterpreter extends JSChannel_{intercept_link_redirects;ipc;editsPath;eventsPath;kickStylesheets;queuedBytes=[];liveview;constructor(editsPath,eventsPath){super();this.editsPath=editsPath,this.eventsPath=eventsPath,this.kickStylesheets=!1}initialize(root){this.intercept_link_redirects=!0,this.liveview=!1,window.addEventListener("dragover",function(e){if(e.target instanceof Element&&e.target.tagName!="INPUT")e.preventDefault()},!1),window.addEventListener("drop",function(e){if(!(e.target instanceof Element))return;e.preventDefault()},!1),window.addEventListener("click",(event)=>{const target=event.target;if(target instanceof HTMLInputElement&&target.getAttribute("type")==="file"){let target_id=getTargetId(target);if(target_id!==null){const message=this.serializeIpcMessage("file_dialog",{event:"change&input",accept:target.getAttribute("accept"),directory:target.getAttribute("webkitdirectory")==="true",multiple:target.hasAttribute("multiple"),target:target_id,bubbles:event.bubbles});this.ipc.postMessage(message),event.preventDefault()}}}),this.ipc=window.ipc;const handler=(event)=>this.handleEvent(event,event.type,!0);super.initialize(root,handler)}serializeIpcMessage(method,params={}){return JSON.stringify({method,params})}scrollTo(id,behavior){const node=this.nodes[id];if(node instanceof HTMLElement)node.scrollIntoView({behavior})}getScrollHeight(id){const node=this.nodes[id];if(node instanceof HTMLElement)return node.scrollHeight}getScrollLeft(id){const node=this.nodes[id];if(node instanceof HTMLElement)return node.scrollLeft}getScrollTop(id){const node=this.nodes[id];if(node instanceof HTMLElement)return node.scrollTop}getScrollWidth(id){const node=this.nodes[id];if(node instanceof HTMLElement)return node.scrollWidth}getClientRect(id){const node=this.nodes[id];if(node instanceof HTMLElement){const rect=node.getBoundingClientRect();return{type:"GetClientRect",origin:[rect.x,rect.y],size:[rect.width,rect.height]}}}setFocus(id,focus){const node=this.nodes[id];if(node instanceof HTMLElement)if(focus)node.focus();else node.blur()}loadChild(array){let node=this.stack[this.stack.length-1];for(let i=0;i0;end--)node=node.nextSibling}return node}appendChildren(id,many){const root=this.nodes[id],els=this.stack.splice(this.stack.length-many);for(let k=0;k{this.flushQueuedBytes(),this.waitForRequest(headless)})}waitForRequest(headless){fetch(new Request(this.editsPath)).then((response)=>response.arrayBuffer()).then((bytes)=>{this.rafEdits(headless,bytes)})}kickAllStylesheetsOnPage(){let stylesheets=document.querySelectorAll("link[rel=stylesheet]");for(let i=0;i{sheet.href=sheet.href+"?"+Math.random()})}}async readFiles(target,contents,bubbles,realId,name){let files=target.files,file_contents={};for(let i=0;i("resize", { + bubbles: false, + detail: entry, + }); + + target.dispatchEvent(event); + } + + createObserver(element: HTMLElement) { + // Lazily create the resize observer + if (!this.resizeObserver) { + this.resizeObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + this.handleResizeEvent(entry); + } + }); + } + this.resizeObserver.observe(element); + } + + removeObserver(element: HTMLElement) { + if (this.resizeObserver) { + this.resizeObserver.unobserve(element); } } @@ -59,6 +88,10 @@ export class BaseInterpreter { } element.addEventListener(event_name, this.handler); } + + if (event_name == "resize") { + this.createObserver(element); + } } removeListener(element: HTMLElement, event_name: string, bubbles: boolean) { diff --git a/packages/interpreter/src/ts/native.ts b/packages/interpreter/src/ts/native.ts index 11e40ee94..0c60041ed 100644 --- a/packages/interpreter/src/ts/native.ts +++ b/packages/interpreter/src/ts/native.ts @@ -98,6 +98,7 @@ export class NativeInterpreter extends JSChannel_ { // make sure we pass the handler to the base interpreter const handler: EventListener = (event) => this.handleEvent(event, event.type, true); + super.initialize(root, handler); } @@ -209,7 +210,7 @@ export class NativeInterpreter extends JSChannel_ { // This is to support the prevent_default: "onclick" attribute that dioxus has had for a while, but is not necessary // now that we expose preventDefault to the virtualdom on desktop // Liveview will still need to use this - this.preventDefaults(event, target); + this.preventDefaults(event); // liveview does not have synchronous event handling, so we need to send the event to the host if (this.liveview) { @@ -262,7 +263,7 @@ export class NativeInterpreter extends JSChannel_ { // - prevent anchor tags from navigating // - prevent buttons from submitting forms // - let the virtualdom attempt to prevent the event - preventDefaults(event: Event, target: EventTarget) { + preventDefaults(event: Event) { if (event.type === "submit") { event.preventDefault(); } diff --git a/packages/interpreter/src/ts/serialize.ts b/packages/interpreter/src/ts/serialize.ts index df11c4c0e..192f1fa29 100644 --- a/packages/interpreter/src/ts/serialize.ts +++ b/packages/interpreter/src/ts/serialize.ts @@ -58,6 +58,13 @@ export function serializeEvent( extend({}); } + if (event instanceof CustomEvent) { + const detail = event.detail; + if (detail instanceof ResizeObserverEntry) { + extend(serializeResizeEventDetail(detail)); + } + } + // safari is quirky and doesn't have TouchEvent if (typeof TouchEvent !== "undefined" && event instanceof TouchEvent) { extend(serializeTouchEvent(event)); @@ -96,6 +103,47 @@ export function serializeEvent( return contents; } +function toSerializableResizeObserverSize( + size: ResizeObserverSize, + is_inline_width: boolean +): Object { + return [ + is_inline_width ? size.inlineSize : size.blockSize, + is_inline_width ? size.blockSize : size.inlineSize, + ]; +} + +export function serializeResizeEventDetail( + detail: ResizeObserverEntry +): SerializedEvent { + let is_inline_width = true; + if (detail.target instanceof HTMLElement) { + let target_style = window.getComputedStyle(detail.target); + let target_writing_mode = target_style.getPropertyValue("writing-mode"); + if (target_writing_mode !== "horizontal-tb") { + is_inline_width = false; + } + } + + return { + border_box_size: + detail.borderBoxSize !== undefined + ? toSerializableResizeObserverSize( + detail.borderBoxSize[0], + is_inline_width + ) + : detail.contentRect, + content_box_size: + detail.contentBoxSize !== undefined + ? toSerializableResizeObserverSize( + detail.contentBoxSize[0], + is_inline_width + ) + : detail.contentRect, + content_rect: detail.contentRect, + }; +} + function serializeInputEvent( event: InputEvent, target: EventTarget diff --git a/packages/interpreter/src/unified_bindings.rs b/packages/interpreter/src/unified_bindings.rs index e6e99bdfb..ea95b25b1 100644 --- a/packages/interpreter/src/unified_bindings.rs +++ b/packages/interpreter/src/unified_bindings.rs @@ -77,9 +77,10 @@ mod js { fn create_placeholder(id: u32) { "{let node = document.createComment('placeholder'); this.stack.push(node); this.nodes[$id$] = node;}" } + fn new_event_listener(event_name: &str, id: u32, bubbles: u8) { r#" - let node = this.nodes[id]; + const node = this.nodes[id]; if(node.listening){node.listening += 1;}else{node.listening = 1;} node.setAttribute('data-dioxus-id', `\${id}`); this.createListener($event_name$, node, $bubbles$); diff --git a/packages/interpreter/tsconfig.json b/packages/interpreter/tsconfig.json index 11cda68e3..82dceb08e 100644 --- a/packages/interpreter/tsconfig.json +++ b/packages/interpreter/tsconfig.json @@ -11,6 +11,7 @@ "noImplicitAny": true, "removeComments": true, "preserveConstEnums": true, + "typeRoots": [".src/ts/types"] }, "exclude": [ "**/*.spec.ts" diff --git a/packages/liveview/src/events.rs b/packages/liveview/src/events.rs index b764bf7cc..9712e2035 100644 --- a/packages/liveview/src/events.rs +++ b/packages/liveview/src/events.rs @@ -99,6 +99,14 @@ impl HtmlEventConverter for SerializedHtmlEventConverter { .into() } + fn convert_resize_data(&self, event: &PlatformEventData) -> ResizeData { + event + .downcast::() + .cloned() + .unwrap() + .into() + } + fn convert_scroll_data(&self, event: &PlatformEventData) -> ScrollData { event .downcast::() diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 710d610d2..b404725cd 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -53,7 +53,8 @@ features = [ "DataTransfer", "console", "NodeList", - "CloseEvent" + "CloseEvent", + "CustomEvent", ] [features] diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 4f41aee7e..a0b5e4afe 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -13,7 +13,7 @@ use dioxus_core::{ElementId, Template}; use dioxus_interpreter_js::unified_bindings::Interpreter; use rustc_hash::FxHashMap; use wasm_bindgen::{closure::Closure, JsCast}; -use web_sys::{Document, Element, Event}; +use web_sys::{Document, Element, Event, Node}; use crate::{load_document, virtual_event_from_websys_event, Config, WebEventConverter}; @@ -146,6 +146,11 @@ fn walk_event_for_id(event: &web_sys::Event) -> Option<(ElementId, web_sys::Elem .expect("missing target") .dyn_into::() .expect("not a valid node"); + + walk_element_for_id(&target) +} + +fn walk_element_for_id(target: &Node) -> Option<(ElementId, web_sys::Element)> { let mut current_target_element = target.dyn_ref::().cloned(); loop { diff --git a/packages/web/src/event.rs b/packages/web/src/event.rs index 6b1e9d16a..24f58f0eb 100644 --- a/packages/web/src/event.rs +++ b/packages/web/src/event.rs @@ -112,6 +112,14 @@ impl HtmlEventConverter for WebEventConverter { 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, @@ -158,11 +166,11 @@ impl HtmlEventConverter for WebEventConverter { /// 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<&E>; + fn try_as_web_event(&self) -> Option; /// Downcast this event as a `web-sys` event. #[inline(always)] - fn as_web_event(&self) -> &E + fn as_web_event(&self) -> E where E: 'static, { @@ -177,127 +185,137 @@ pub trait WebEventExt { impl WebEventExt for dioxus_html::AnimationData { #[inline(always)] - fn try_as_web_event(&self) -> Option<&web_sys::AnimationEvent> { - self.downcast::() + 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<&web_sys::Event> { - self.downcast::() + 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<&web_sys::CompositionEvent> { - self.downcast::() + 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<&web_sys::MouseEvent> { - self.downcast::().map(|data| &data.raw) + 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<&web_sys::FocusEvent> { - self.downcast::() + 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<&web_sys::Event> { - self.downcast::() + 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<&WebImageEvent> { - self.downcast::() + 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<&web_sys::KeyboardEvent> { - self.downcast::() + 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<&web_sys::Event> { - self.downcast::() + fn try_as_web_event(&self) -> Option { + self.downcast::().cloned() } } impl WebEventExt for MountedData { #[inline(always)] - fn try_as_web_event(&self) -> Option<&web_sys::Element> { - self.downcast::() + 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<&web_sys::MouseEvent> { - self.downcast::() + 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<&web_sys::PointerEvent> { - self.downcast::() + fn try_as_web_event(&self) -> Option { + self.downcast::().cloned() } } impl WebEventExt for ScrollData { #[inline(always)] - fn try_as_web_event(&self) -> Option<&web_sys::Event> { - self.downcast::() + 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<&web_sys::Event> { - self.downcast::() + 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<&web_sys::Event> { - self.downcast::() + 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<&web_sys::TouchEvent> { - self.downcast::() + 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<&web_sys::TransitionEvent> { - self.downcast::() + 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<&web_sys::WheelEvent> { - self.downcast::() + 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()) } } @@ -325,6 +343,7 @@ pub(crate) fn load_document() -> Document { .expect("should have access to the Document") } +#[derive(Clone)] struct WebImageEvent { raw: Event, error: bool,