소스 검색

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 <evanalmloff@gmail.com>
ASR-ASU 10 달 전
부모
커밋
2f49a89638

+ 27 - 0
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():?}"
+        }
+    )
+}

+ 8 - 0
packages/desktop/src/events.rs

@@ -113,6 +113,14 @@ impl HtmlEventConverter for SerializedHtmlEventConverter {
             .into()
     }
 
+    fn convert_resize_data(&self, event: &PlatformEventData) -> ResizeData {
+        event
+            .downcast::<SerializedResizeData>()
+            .cloned()
+            .unwrap()
+            .into()
+    }
+
     fn convert_scroll_data(&self, event: &PlatformEventData) -> ScrollData {
         event
             .downcast::<SerializedScrollData>()

+ 3 - 0
packages/html/Cargo.toml

@@ -50,6 +50,9 @@ features = [
     "PointerEvent",
     "FocusEvent",
     "CompositionEvent",
+    "CustomEvent",
+    "ResizeObserverEntry",
+    "ResizeObserverSize"
 ]
 
 [build-dependencies]

+ 11 - 0
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,

+ 168 - 0
packages/html/src/events/resize.rs

@@ -0,0 +1,168 @@
+use std::fmt::{Display, Formatter};
+
+pub struct ResizeData {
+    inner: Box<dyn HasResizeData>,
+}
+
+impl<E: HasResizeData> From<E> 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<PixelsSize> {
+        self.inner.get_border_box_size()
+    }
+
+    /// Get the content box size of the observed element
+    pub fn get_content_box_size(&self) -> ResizeResult<PixelsSize> {
+        self.inner.get_content_box_size()
+    }
+
+    /// Downcast this event to a concrete event type
+    pub fn downcast<T: 'static>(&self) -> Option<&T> {
+        self.inner.as_any().downcast_ref::<T>()
+    }
+}
+
+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<PixelsSize> {
+        Ok(self.border_box_size)
+    }
+
+    /// Get the content box size of the observed element
+    fn get_content_box_size(&self) -> ResizeResult<PixelsSize> {
+        Ok(self.content_box_size)
+    }
+
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+}
+
+#[cfg(feature = "serialize")]
+impl serde::Serialize for ResizeData {
+    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        SerializedResizeData::from(self).serialize(serializer)
+    }
+}
+
+#[cfg(feature = "serialize")]
+impl<'de> serde::Deserialize<'de> for ResizeData {
+    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+        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<PixelsSize> {
+        Err(ResizeError::NotSupported)
+    }
+    /// Get the content box size of the observed element
+    fn get_content_box_size(&self) -> ResizeResult<PixelsSize> {
+        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<ResizeData>;
+
+impl_event! {
+    ResizeData;
+
+    /// onresize
+    onresize
+}
+
+/// The ResizeResult type for the ResizeData
+pub type ResizeResult<T> = Result<T, ResizeError>;
+
+#[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<dyn std::error::Error>),
+}
+
+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 {}

+ 14 - 0
packages/html/src/transit.rs

@@ -99,6 +99,9 @@ fn deserialize_raw(name: &str, data: &serde_json::Value) -> Result<EventData, se
         // Touch
         "touchcancel" | "touchend" | "touchmove" | "touchstart" => 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<dyn Any>
             }
+            EventData::Resize(data) => {
+                Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>
+            }
             EventData::Scroll(data) => {
                 Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>
             }
@@ -360,6 +367,13 @@ impl HtmlEventConverter for SerializedHtmlEventConverter {
             .unwrap()
             .into()
     }
+    fn convert_resize_data(&self, event: &PlatformEventData) -> ResizeData {
+        event
+            .downcast::<SerializedResizeData>()
+            .cloned()
+            .unwrap()
+            .into()
+    }
 
     fn convert_scroll_data(&self, event: &PlatformEventData) -> ScrollData {
         event

+ 46 - 2
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<Event> for ResizeData {
+    #[inline]
+    fn from(e: Event) -> Self {
+        <ResizeData as From<&Event>>::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::<ResizeObserverEntry>())
+    }
+}
+
 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<PixelsSize> {
+    let first = resize_observer_output.get(0);
+    let size = first.unchecked_into::<web_sys::ResizeObserverSize>();
+
+    // 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<PixelsSize> {
+        extract_first_size(self.border_box_size())
+    }
+
+    fn get_content_box_size(&self) -> ResizeResult<PixelsSize> {
+        extract_first_size(self.content_box_size())
+    }
+}
+
 impl HasScrollData for Event {
     fn as_any(&self) -> &dyn std::any::Any {
         self

+ 1 - 1
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;k<many;k++)root.appendChild(els[k])}loadChild(ptr,len){let node=this.stack[this.stack.length-1],ptr_end=ptr+len;for(;ptr<ptr_end;ptr++){let end=this.m.getUint8(ptr);for(node=node.firstChild;end>0;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;j<split.length;j++){const split2=split[j].split(":"),event_name=split2[0],bubbles=split2[1]==="1";this.createListener(event_name,hydrateNode,bubbles)}}}hydrate(ids,underNodes){for(let i=0;i<underNodes.length;i++){const under=underNodes[i];if(under instanceof HTMLElement){if(under.getAttribute("data-node-hydration"))this.hydrate_node(under,ids);const hydrateNodes=under.querySelectorAll("[data-node-hydration]");for(let i2=0;i2<hydrateNodes.length;i2++)this.hydrate_node(hydrateNodes[i2],ids)}const treeWalker=document.createTreeWalker(under,NodeFilter.SHOW_COMMENT);while(treeWalker.currentNode){const currentNode=treeWalker.currentNode;if(currentNode.nodeType===Node.COMMENT_NODE){const id=currentNode.textContent,placeholderSplit=id.split("placeholder");if(placeholderSplit.length>1){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;k<many;k++)root.appendChild(els[k])}loadChild(ptr,len){let node=this.stack[this.stack.length-1],ptr_end=ptr+len;for(;ptr<ptr_end;ptr++){let end=this.m.getUint8(ptr);for(node=node.firstChild;end>0;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;j<split.length;j++){const split2=split[j].split(":"),event_name=split2[0],bubbles=split2[1]==="1";this.createListener(event_name,hydrateNode,bubbles)}}}hydrate(ids,underNodes){for(let i=0;i<underNodes.length;i++){const under=underNodes[i];if(under instanceof HTMLElement){if(under.getAttribute("data-node-hydration"))this.hydrate_node(under,ids);const hydrateNodes=under.querySelectorAll("[data-node-hydration]");for(let i2=0;i2<hydrateNodes.length;i2++)this.hydrate_node(hydrateNodes[i2],ids)}const treeWalker=document.createTreeWalker(under,NodeFilter.SHOW_COMMENT);while(treeWalker.currentNode){const currentNode=treeWalker.currentNode;if(currentNode.nodeType===Node.COMMENT_NODE){const id=currentNode.textContent,placeholderSplit=id.split("placeholder");if(placeholderSplit.length>1){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};

+ 1 - 1
packages/interpreter/src/js/hash.txt

@@ -1 +1 @@
-[6449103750905854967, 12029349297046688094, 13069001215487072322, 8716623267269178440, 5336385715226370016, 14456089431355876478, 5618841090288840293, 5052021921702764563, 16478152596505612522, 5638004933879392817]
+[6449103750905854967, 4461869229701639737, 13069001215487072322, 8716623267269178440, 5336385715226370016, 14456089431355876478, 11703635120274352436, 5052021921702764563, 17534315583914394253, 5638004933879392817]

+ 1 - 1
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;i<options.length;i++)values.push(options[i].value);return values}function serializeEvent(event,target){let contents={},extend=(obj)=>contents={...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;i<array.length;i++){let end=array[i];for(node=node.firstChild;end>0;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<many;k++)root.appendChild(els[k])}handleEvent(event,name,bubbles){const target=event.target,realId=getTargetId(target),contents=serializeEvent(event,target);let body={name,data:contents,element:realId,bubbles};if(this.preventDefaults(event,target),this.liveview){if(target instanceof HTMLInputElement&&(event.type==="change"||event.type==="input")){if(target.getAttribute("type")==="file"){this.readFiles(target,contents,bubbles,realId,name);return}}}const response=this.sendSerializedEvent(body);if(response){if(response.preventDefault)event.preventDefault();else if(target instanceof Element&&event.type==="click")this.handleClickNavigate(event,target);if(response.stopPropagation)event.stopPropagation()}}sendSerializedEvent(body){if(this.liveview){const message=this.serializeIpcMessage("user_event",body);this.ipc.postMessage(message)}else return handleVirtualdomEventSync(this.eventsPath,JSON.stringify(body))}preventDefaults(event,target){if(event.type==="submit")event.preventDefault()}handleClickNavigate(event,target){if(!this.intercept_link_redirects)return;if(target.tagName==="BUTTON"&&event.type=="submit")event.preventDefault();let a_element=target.closest("a");if(a_element==null)return;event.preventDefault();const href=a_element.getAttribute("href");if(href!==""&&href!==null&&href!==void 0)this.ipc.postMessage(this.serializeIpcMessage("browser_open",{href}))}enqueueBytes(bytes){this.queuedBytes.push(bytes)}flushQueuedBytes(){const byteArray=this.queuedBytes;this.queuedBytes=[];for(let bytes of byteArray)this.run_from_bytes(bytes)}rafEdits(headless,bytes){if(headless)this.run_from_bytes(bytes),this.waitForRequest(headless);else this.enqueueBytes(bytes),requestAnimationFrame(()=>{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<stylesheets.length;i++){let sheet=stylesheets[i];fetch(sheet.href,{cache:"reload"}).then(()=>{sheet.href=sheet.href+"?"+Math.random()})}}async readFiles(target,contents,bubbles,realId,name){let files=target.files,file_contents={};for(let i=0;i<files.length;i++){const file=files[i];file_contents[file.name]=Array.from(new Uint8Array(await file.arrayBuffer()))}contents.files={files:file_contents};const message=this.sendSerializedEvent({name,element:realId,data:contents,bubbles});this.ipc.postMessage(message)}}export{NativeInterpreter};
+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;i<options.length;i++)values.push(options[i].value);return values}function serializeEvent(event,target){let contents={},extend=(obj)=>contents={...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;i<array.length;i++){let end=array[i];for(node=node.firstChild;end>0;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<many;k++)root.appendChild(els[k])}handleEvent(event,name,bubbles){const target=event.target,realId=getTargetId(target),contents=serializeEvent(event,target);let body={name,data:contents,element:realId,bubbles};if(this.preventDefaults(event),this.liveview){if(target instanceof HTMLInputElement&&(event.type==="change"||event.type==="input")){if(target.getAttribute("type")==="file"){this.readFiles(target,contents,bubbles,realId,name);return}}}const response=this.sendSerializedEvent(body);if(response){if(response.preventDefault)event.preventDefault();else if(target instanceof Element&&event.type==="click")this.handleClickNavigate(event,target);if(response.stopPropagation)event.stopPropagation()}}sendSerializedEvent(body){if(this.liveview){const message=this.serializeIpcMessage("user_event",body);this.ipc.postMessage(message)}else return handleVirtualdomEventSync(this.eventsPath,JSON.stringify(body))}preventDefaults(event){if(event.type==="submit")event.preventDefault()}handleClickNavigate(event,target){if(!this.intercept_link_redirects)return;if(target.tagName==="BUTTON"&&event.type=="submit")event.preventDefault();let a_element=target.closest("a");if(a_element==null)return;event.preventDefault();const href=a_element.getAttribute("href");if(href!==""&&href!==null&&href!==void 0)this.ipc.postMessage(this.serializeIpcMessage("browser_open",{href}))}enqueueBytes(bytes){this.queuedBytes.push(bytes)}flushQueuedBytes(){const byteArray=this.queuedBytes;this.queuedBytes=[];for(let bytes of byteArray)this.run_from_bytes(bytes)}rafEdits(headless,bytes){if(headless)this.run_from_bytes(bytes),this.waitForRequest(headless);else this.enqueueBytes(bytes),requestAnimationFrame(()=>{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<stylesheets.length;i++){let sheet=stylesheets[i];fetch(sheet.href,{cache:"reload"}).then(()=>{sheet.href=sheet.href+"?"+Math.random()})}}async readFiles(target,contents,bubbles,realId,name){let files=target.files,file_contents={};for(let i=0;i<files.length;i++){const file=files[i];file_contents[file.name]=Array.from(new Uint8Array(await file.arrayBuffer()))}contents.files={files:file_contents};const message=this.sendSerializedEvent({name,element:realId,data:contents,bubbles});this.ipc.postMessage(message)}}export{NativeInterpreter};

+ 35 - 2
packages/interpreter/src/ts/core.ts

@@ -19,6 +19,8 @@ export class BaseInterpreter {
 
   root: HTMLElement;
   handler: EventListener;
+  resizeObserver: ResizeObserver;
+
   nodes: Node[];
   stack: Node[];
   templates: {
@@ -39,8 +41,35 @@ export class BaseInterpreter {
     this.stack = [root];
     this.templates = {};
 
-    if (handler) {
-      this.handler = handler;
+    this.handler = handler;
+  }
+
+  handleResizeEvent(entry: ResizeObserverEntry) {
+    const target = entry.target;
+
+    let event = new CustomEvent<ResizeObserverEntry>("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) {

+ 3 - 2
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();
     }

+ 48 - 0
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

+ 2 - 1
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<u8, evt>, 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$);

+ 1 - 0
packages/interpreter/tsconfig.json

@@ -11,6 +11,7 @@
         "noImplicitAny": true,
         "removeComments": true,
         "preserveConstEnums": true,
+        "typeRoots": [".src/ts/types"]
     },
     "exclude": [
         "**/*.spec.ts"

+ 8 - 0
packages/liveview/src/events.rs

@@ -99,6 +99,14 @@ impl HtmlEventConverter for SerializedHtmlEventConverter {
             .into()
     }
 
+    fn convert_resize_data(&self, event: &PlatformEventData) -> ResizeData {
+        event
+            .downcast::<SerializedResizeData>()
+            .cloned()
+            .unwrap()
+            .into()
+    }
+
     fn convert_scroll_data(&self, event: &PlatformEventData) -> ScrollData {
         event
             .downcast::<SerializedScrollData>()

+ 2 - 1
packages/web/Cargo.toml

@@ -53,7 +53,8 @@ features = [
     "DataTransfer",
     "console",
     "NodeList",
-    "CloseEvent"
+    "CloseEvent",
+    "CustomEvent",
 ]
 
 [features]

+ 6 - 1
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::<web_sys::Node>()
         .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::<web_sys::Element>().cloned();
 
     loop {

+ 57 - 38
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<E> {
     /// 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<E>;
 
     /// 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<E> {
 
 impl WebEventExt<web_sys::AnimationEvent> for dioxus_html::AnimationData {
     #[inline(always)]
-    fn try_as_web_event(&self) -> Option<&web_sys::AnimationEvent> {
-        self.downcast::<web_sys::AnimationEvent>()
+    fn try_as_web_event(&self) -> Option<web_sys::AnimationEvent> {
+        self.downcast::<web_sys::AnimationEvent>().cloned()
     }
 }
 
 impl WebEventExt<web_sys::Event> for dioxus_html::ClipboardData {
     #[inline(always)]
-    fn try_as_web_event(&self) -> Option<&web_sys::Event> {
-        self.downcast::<web_sys::Event>()
+    fn try_as_web_event(&self) -> Option<web_sys::Event> {
+        self.downcast::<web_sys::Event>().cloned()
     }
 }
 
 impl WebEventExt<web_sys::CompositionEvent> for dioxus_html::CompositionData {
     #[inline(always)]
-    fn try_as_web_event(&self) -> Option<&web_sys::CompositionEvent> {
-        self.downcast::<web_sys::CompositionEvent>()
+    fn try_as_web_event(&self) -> Option<web_sys::CompositionEvent> {
+        self.downcast::<web_sys::CompositionEvent>().cloned()
     }
 }
 
 impl WebEventExt<web_sys::MouseEvent> for dioxus_html::DragData {
     #[inline(always)]
-    fn try_as_web_event(&self) -> Option<&web_sys::MouseEvent> {
-        self.downcast::<WebDragData>().map(|data| &data.raw)
+    fn try_as_web_event(&self) -> Option<web_sys::MouseEvent> {
+        self.downcast::<WebDragData>()
+            .map(|data| &data.raw)
+            .cloned()
     }
 }
 
 impl WebEventExt<web_sys::FocusEvent> for dioxus_html::FocusData {
     #[inline(always)]
-    fn try_as_web_event(&self) -> Option<&web_sys::FocusEvent> {
-        self.downcast::<web_sys::FocusEvent>()
+    fn try_as_web_event(&self) -> Option<web_sys::FocusEvent> {
+        self.downcast::<web_sys::FocusEvent>().cloned()
     }
 }
 
 impl WebEventExt<web_sys::Event> for dioxus_html::FormData {
     #[inline(always)]
-    fn try_as_web_event(&self) -> Option<&web_sys::Event> {
-        self.downcast::<web_sys::Event>()
+    fn try_as_web_event(&self) -> Option<web_sys::Event> {
+        self.downcast::<web_sys::Event>().cloned()
     }
 }
 
 impl WebEventExt<WebImageEvent> for dioxus_html::ImageData {
     #[inline(always)]
-    fn try_as_web_event(&self) -> Option<&WebImageEvent> {
-        self.downcast::<WebImageEvent>()
+    fn try_as_web_event(&self) -> Option<WebImageEvent> {
+        self.downcast::<WebImageEvent>().cloned()
     }
 }
 
 impl WebEventExt<web_sys::KeyboardEvent> for dioxus_html::KeyboardData {
     #[inline(always)]
-    fn try_as_web_event(&self) -> Option<&web_sys::KeyboardEvent> {
-        self.downcast::<web_sys::KeyboardEvent>()
+    fn try_as_web_event(&self) -> Option<web_sys::KeyboardEvent> {
+        self.downcast::<web_sys::KeyboardEvent>().cloned()
     }
 }
 
 impl WebEventExt<web_sys::Event> for dioxus_html::MediaData {
     #[inline(always)]
-    fn try_as_web_event(&self) -> Option<&web_sys::Event> {
-        self.downcast::<web_sys::Event>()
+    fn try_as_web_event(&self) -> Option<web_sys::Event> {
+        self.downcast::<web_sys::Event>().cloned()
     }
 }
 
 impl WebEventExt<web_sys::Element> for MountedData {
     #[inline(always)]
-    fn try_as_web_event(&self) -> Option<&web_sys::Element> {
-        self.downcast::<web_sys::Element>()
+    fn try_as_web_event(&self) -> Option<web_sys::Element> {
+        self.downcast::<web_sys::Element>().cloned()
     }
 }
 
 impl WebEventExt<web_sys::MouseEvent> for dioxus_html::MouseData {
     #[inline(always)]
-    fn try_as_web_event(&self) -> Option<&web_sys::MouseEvent> {
-        self.downcast::<web_sys::MouseEvent>()
+    fn try_as_web_event(&self) -> Option<web_sys::MouseEvent> {
+        self.downcast::<web_sys::MouseEvent>().cloned()
     }
 }
 
 impl WebEventExt<web_sys::PointerEvent> for dioxus_html::PointerData {
     #[inline(always)]
-    fn try_as_web_event(&self) -> Option<&web_sys::PointerEvent> {
-        self.downcast::<web_sys::PointerEvent>()
+    fn try_as_web_event(&self) -> Option<web_sys::PointerEvent> {
+        self.downcast::<web_sys::PointerEvent>().cloned()
     }
 }
 
 impl WebEventExt<web_sys::Event> for ScrollData {
     #[inline(always)]
-    fn try_as_web_event(&self) -> Option<&web_sys::Event> {
-        self.downcast::<web_sys::Event>()
+    fn try_as_web_event(&self) -> Option<web_sys::Event> {
+        self.downcast::<web_sys::Event>().cloned()
     }
 }
 
 impl WebEventExt<web_sys::Event> for dioxus_html::SelectionData {
     #[inline(always)]
-    fn try_as_web_event(&self) -> Option<&web_sys::Event> {
-        self.downcast::<web_sys::Event>()
+    fn try_as_web_event(&self) -> Option<web_sys::Event> {
+        self.downcast::<web_sys::Event>().cloned()
     }
 }
 
 impl WebEventExt<web_sys::Event> for dioxus_html::ToggleData {
     #[inline(always)]
-    fn try_as_web_event(&self) -> Option<&web_sys::Event> {
-        self.downcast::<web_sys::Event>()
+    fn try_as_web_event(&self) -> Option<web_sys::Event> {
+        self.downcast::<web_sys::Event>().cloned()
     }
 }
 
 impl WebEventExt<web_sys::TouchEvent> for dioxus_html::TouchData {
     #[inline(always)]
-    fn try_as_web_event(&self) -> Option<&web_sys::TouchEvent> {
-        self.downcast::<web_sys::TouchEvent>()
+    fn try_as_web_event(&self) -> Option<web_sys::TouchEvent> {
+        self.downcast::<web_sys::TouchEvent>().cloned()
     }
 }
 
 impl WebEventExt<web_sys::TransitionEvent> for dioxus_html::TransitionData {
     #[inline(always)]
-    fn try_as_web_event(&self) -> Option<&web_sys::TransitionEvent> {
-        self.downcast::<web_sys::TransitionEvent>()
+    fn try_as_web_event(&self) -> Option<web_sys::TransitionEvent> {
+        self.downcast::<web_sys::TransitionEvent>().cloned()
     }
 }
 
 impl WebEventExt<web_sys::WheelEvent> for dioxus_html::WheelData {
     #[inline(always)]
-    fn try_as_web_event(&self) -> Option<&web_sys::WheelEvent> {
-        self.downcast::<web_sys::WheelEvent>()
+    fn try_as_web_event(&self) -> Option<web_sys::WheelEvent> {
+        self.downcast::<web_sys::WheelEvent>().cloned()
+    }
+}
+
+impl WebEventExt<web_sys::ResizeObserverEntry> for dioxus_html::ResizeData {
+    #[inline(always)]
+    fn try_as_web_event(&self) -> Option<web_sys::ResizeObserverEntry> {
+        self.downcast::<web_sys::CustomEvent>()
+            .and_then(|e| e.detail().dyn_into::<web_sys::ResizeObserverEntry>().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,