Kaynağa Gözat

feat: plug in bubbling

Jonathan Kelley 3 yıl önce
ebeveyn
işleme
d84fc05

+ 159 - 0
packages/core/src/events.rs

@@ -0,0 +1,159 @@
+//! Internal and external event system
+//!
+//!
+//! This is all kinda WIP, but the bones are there.
+
+use crate::{ElementId, ScopeId};
+use std::{any::Any, cell::Cell, rc::Rc, sync::Arc};
+
+pub(crate) struct BubbleState {
+    pub canceled: Cell<bool>,
+}
+
+impl BubbleState {
+    pub fn new() -> Self {
+        Self {
+            canceled: Cell::new(false),
+        }
+    }
+}
+
+/// User Events are events that are shuttled from the renderer into the VirtualDom trhough the scheduler channel.
+///
+/// These events will be passed to the appropriate Element given by `mounted_dom_id` and then bubbled up through the tree
+/// where each listener is checked and fired if the event name matches.
+///
+/// It is the expectation that the event name matches the corresponding event listener, otherwise Dioxus will panic in
+/// attempting to downcast the event data.
+///
+/// Because Event Data is sent across threads, it must be `Send + Sync`. We are hoping to lift the `Sync` restriction but
+/// `Send` will not be lifted. The entire `UserEvent` must also be `Send + Sync` due to its use in the scheduler channel.
+///
+/// # Example
+/// ```rust, ignore
+/// fn App(cx: Scope) -> Element {
+///     rsx!(cx, div {
+///         onclick: move |_| println!("Clicked!")
+///     })
+/// }
+///
+/// let mut dom = VirtualDom::new(App);
+/// let mut scheduler = dom.get_scheduler_channel();
+/// scheduler.unbounded_send(SchedulerMsg::UiEvent(
+///     UserEvent {
+///         scope_id: None,
+///         priority: EventPriority::Medium,
+///         name: "click",
+///         element: Some(ElementId(0)),
+///         data: Arc::new(ClickEvent { .. })
+///     }
+/// )).unwrap();
+/// ```
+#[derive(Debug)]
+pub struct UserEvent {
+    /// The originator of the event trigger if available
+    pub scope_id: Option<ScopeId>,
+
+    /// The priority of the event to be scheduled around ongoing work
+    pub priority: EventPriority,
+
+    /// The optional real node associated with the trigger
+    pub element: Option<ElementId>,
+
+    /// The event type IE "onclick" or "onmouseover"
+    pub name: &'static str,
+
+    /// The event data to be passed onto the event handler
+    pub data: Arc<dyn Any + Send + Sync>,
+}
+
+/// Priority of Event Triggers.
+///
+/// Internally, Dioxus will abort work that's taking too long if new, more important work arrives. Unlike React, Dioxus
+/// won't be afraid to pause work or flush changes to the RealDOM. This is called "cooperative scheduling". Some Renderers
+/// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
+///
+/// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
+///
+/// React has a 5-tier priority system. However, they break things into "Continuous" and "Discrete" priority. For now,
+/// we keep it simple, and just use a 3-tier priority system.
+///
+/// - NoPriority = 0
+/// - LowPriority = 1
+/// - NormalPriority = 2
+/// - UserBlocking = 3
+/// - HighPriority = 4
+/// - ImmediatePriority = 5
+///
+/// We still have a concept of discrete vs continuous though - discrete events won't be batched, but continuous events will.
+/// This means that multiple "scroll" events will be processed in a single frame, but multiple "click" events will be
+/// flushed before proceeding. Multiple discrete events is highly unlikely, though.
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
+pub enum EventPriority {
+    /// Work that must be completed during the EventHandler phase.
+    ///
+    /// Currently this is reserved for controlled inputs.
+    Immediate = 3,
+
+    /// "High Priority" work will not interrupt other high priority work, but will interrupt medium and low priority work.
+    ///
+    /// This is typically reserved for things like user interaction.
+    ///
+    /// React calls these "discrete" events, but with an extra category of "user-blocking" (Immediate).
+    High = 2,
+
+    /// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important
+    /// than "High Priority" events and will take precedence over low priority events.
+    ///
+    /// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
+    ///
+    /// React calls these "continuous" events (e.g. mouse move, mouse wheel, touch move, etc).
+    Medium = 1,
+
+    /// "Low Priority" work will always be preempted unless the work is significantly delayed, in which case it will be
+    /// advanced to the front of the work queue until completed.
+    ///
+    /// The primary user of Low Priority work is the asynchronous work system (Suspense).
+    ///
+    /// This is considered "idle" work or "background" work.
+    Low = 0,
+}
+
+pub struct AnyEvent {
+    pub(crate) bubble_state: Rc<BubbleState>,
+    pub(crate) data: Arc<dyn Any + Send + Sync>,
+}
+
+impl AnyEvent {
+    pub fn downcast<T: Send + Sync + 'static>(self) -> Option<UiEvent<T>> {
+        let AnyEvent { data, bubble_state } = self;
+
+        if let Ok(data) = data.downcast::<T>() {
+            Some(UiEvent { bubble_state, data })
+        } else {
+            None
+        }
+    }
+}
+
+pub struct UiEvent<T> {
+    pub data: Arc<T>,
+
+    #[allow(unused)]
+    bubble_state: Rc<BubbleState>,
+}
+
+impl<T> std::ops::Deref for UiEvent<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        self.data.as_ref()
+    }
+}
+
+impl<T> UiEvent<T> {
+    /// Prevent this event from bubbling up the tree.
+    pub fn cancel_bubble(&self) {
+        self.bubble_state.canceled.set(true);
+    }
+}

+ 12 - 10
packages/core/src/lib.rs

@@ -2,18 +2,24 @@
 #![doc = include_str!("../README.md")]
 
 pub(crate) mod diff;
+pub(crate) mod events;
 pub(crate) mod lazynodes;
 pub(crate) mod mutations;
 pub(crate) mod nodes;
+pub(crate) mod properties;
 pub(crate) mod scopes;
+pub(crate) mod util;
 pub(crate) mod virtual_dom;
 
 pub(crate) mod innerlude {
     pub(crate) use crate::diff::*;
+    pub use crate::events::*;
     pub use crate::lazynodes::*;
     pub use crate::mutations::*;
     pub use crate::nodes::*;
+    pub use crate::properties::*;
     pub use crate::scopes::*;
+    pub use crate::util::*;
     pub use crate::virtual_dom::*;
 
     /// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].
@@ -65,21 +71,17 @@ pub(crate) mod innerlude {
 }
 
 pub use crate::innerlude::{
-    Attribute, Component, DioxusElement, DomEdit, Element, ElementId, ElementIdIterator, Event,
+    AnyEvent, Attribute, Component, DioxusElement, DomEdit, Element, ElementId, ElementIdIterator,
     EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, Mutations, NodeFactory,
-    Properties, SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, UserEvent, VComponent, VElement,
-    VFragment, VNode, VPlaceholder, VText, VirtualDom,
+    Properties, SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, UiEvent, UserEvent, VComponent,
+    VElement, VFragment, VNode, VPlaceholder, VText, VirtualDom,
 };
 
 pub mod prelude {
-    pub use crate::innerlude::Scope;
     pub use crate::innerlude::{
-        Attributes, Component, DioxusElement, Element, EventHandler, LazyNodes, NodeFactory,
-        ScopeState,
+        fc_to_builder, Attributes, Component, DioxusElement, Element, EventHandler, Fragment,
+        LazyNodes, NodeFactory, Properties, Scope, ScopeState, VNode, VirtualDom,
     };
-    pub use crate::nodes::VNode;
-    pub use crate::virtual_dom::{fc_to_builder, Fragment, Properties};
-    pub use crate::VirtualDom;
 }
 
 pub mod exports {
@@ -93,7 +95,7 @@ pub mod exports {
 pub(crate) mod unsafe_utils {
     use crate::VNode;
 
-    pub unsafe fn extend_vnode<'a, 'b>(node: &'a VNode<'a>) -> &'b VNode<'b> {
+    pub(crate) unsafe fn extend_vnode<'a, 'b>(node: &'a VNode<'a>) -> &'b VNode<'b> {
         std::mem::transmute(node)
     }
 }

+ 3 - 5
packages/core/src/nodes.rs

@@ -6,14 +6,12 @@
 use crate::{
     innerlude::{Element, Properties, Scope, ScopeId, ScopeState},
     lazynodes::LazyNodes,
-    Component,
+    AnyEvent, Component,
 };
 use bumpalo::{boxed::Box as BumpBox, Bump};
 use std::{
-    any::Any,
     cell::{Cell, RefCell},
     fmt::{Arguments, Debug, Formatter},
-    sync::Arc,
 };
 
 /// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM.
@@ -330,7 +328,7 @@ pub struct EventHandler<'bump> {
 }
 
 impl EventHandler<'_> {
-    pub fn call(&self, event: Arc<dyn Any + Send + Sync>) {
+    pub fn call(&self, event: AnyEvent) {
         if let Some(callback) = self.callback.borrow_mut().as_mut() {
             callback(event);
         }
@@ -340,7 +338,7 @@ impl EventHandler<'_> {
     }
 }
 
-type ListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(Arc<dyn Any + Send + Sync>) + 'bump>;
+type ListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(AnyEvent) + 'bump>;
 
 impl Copy for EventHandler<'_> {}
 impl Clone for EventHandler<'_> {

+ 164 - 0
packages/core/src/properties.rs

@@ -0,0 +1,164 @@
+use crate::innerlude::*;
+
+pub struct FragmentProps<'a>(Element<'a>);
+pub struct FragmentBuilder<'a, const BUILT: bool>(Element<'a>);
+impl<'a> FragmentBuilder<'a, false> {
+    pub fn children(self, children: Element<'a>) -> FragmentBuilder<'a, true> {
+        FragmentBuilder(children)
+    }
+}
+impl<'a, const A: bool> FragmentBuilder<'a, A> {
+    pub fn build(self) -> FragmentProps<'a> {
+        FragmentProps(self.0)
+    }
+}
+
+/// Access the children elements passed into the component
+///
+/// This enables patterns where a component is passed children from its parent.
+///
+/// ## Details
+///
+/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions
+/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure
+/// on the props that takes Context.
+///
+/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other
+/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's
+/// props are valid for the static lifetime.
+///
+/// ## Example
+///
+/// ```rust, ignore
+/// fn App(cx: Scope) -> Element {
+///     cx.render(rsx!{
+///         CustomCard {
+///             h1 {}2
+///             p {}
+///         }
+///     })
+/// }
+///
+/// #[derive(PartialEq, Props)]
+/// struct CardProps {
+///     children: Element
+/// }
+///
+/// fn CustomCard(cx: Scope<CardProps>) -> Element {
+///     cx.render(rsx!{
+///         div {
+///             h1 {"Title card"}
+///             {cx.props.children}
+///         }
+///     })
+/// }
+/// ```
+impl<'a> Properties for FragmentProps<'a> {
+    type Builder = FragmentBuilder<'a, false>;
+    const IS_STATIC: bool = false;
+    fn builder() -> Self::Builder {
+        FragmentBuilder(None)
+    }
+    unsafe fn memoize(&self, _other: &Self) -> bool {
+        false
+    }
+}
+
+/// Create inline fragments using Component syntax.
+///
+/// ## Details
+///
+/// Fragments capture a series of children without rendering extra nodes.
+///
+/// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and
+/// a key is needed to identify each item.
+///
+/// ## Example
+///
+/// ```rust, ignore
+/// rsx!{
+///     Fragment { key: "abc" }
+/// }
+/// ```
+///
+/// ## Usage
+///
+/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
+/// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments.
+///
+/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
+///
+/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
+#[allow(non_upper_case_globals, non_snake_case)]
+pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
+    let i = cx.props.0.as_ref().map(|f| f.decouple());
+    cx.render(LazyNodes::new(|f| f.fragment_from_iter(i)))
+}
+
+/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
+/// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the
+/// derive macro to implement the `Properties` trait automatically as guarantee that your memoization strategy is safe.
+///
+/// If your props are 'static, then Dioxus will require that they also be PartialEq for the derived memoize strategy. However,
+/// if your props borrow data, then the memoization strategy will simply default to "false" and the PartialEq will be ignored.
+/// This tends to be useful when props borrow something that simply cannot be compared (IE a reference to a closure);
+///
+/// By default, the memoization strategy is very conservative, but can be tuned to be more aggressive manually. However,
+/// this is only safe if the props are 'static - otherwise you might borrow references after-free.
+///
+/// We strongly suggest that any changes to memoization be done at the "PartialEq" level for 'static props. Additionally,
+/// we advise the use of smart pointers in cases where memoization is important.
+///
+/// ## Example
+///
+/// For props that are 'static:
+/// ```rust, ignore
+/// #[derive(Props, PartialEq)]
+/// struct MyProps {
+///     data: String
+/// }
+/// ```
+///
+/// For props that borrow:
+///
+/// ```rust, ignore
+/// #[derive(Props)]
+/// struct MyProps<'a >{
+///     data: &'a str
+/// }
+/// ```
+pub trait Properties: Sized {
+    type Builder;
+    const IS_STATIC: bool;
+    fn builder() -> Self::Builder;
+
+    /// Memoization can only happen if the props are valid for the 'static lifetime
+    ///
+    /// # Safety
+    /// The user must know if their props are static, but if they make a mistake, UB happens
+    /// Therefore it's unsafe to memoize.
+    unsafe fn memoize(&self, other: &Self) -> bool;
+}
+
+impl Properties for () {
+    type Builder = EmptyBuilder;
+    const IS_STATIC: bool = true;
+    fn builder() -> Self::Builder {
+        EmptyBuilder {}
+    }
+    unsafe fn memoize(&self, _other: &Self) -> bool {
+        true
+    }
+}
+// We allow components to use the () generic parameter if they have no props. This impl enables the "build" method
+// that the macros use to anonymously complete prop construction.
+pub struct EmptyBuilder;
+impl EmptyBuilder {
+    pub fn build(self) {}
+}
+
+/// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern
+/// to initialize a component's props.
+pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder {
+    T::builder()
+}

+ 13 - 4
packages/core/src/scopes.rs

@@ -278,19 +278,29 @@ impl ScopeArena {
         let nodes = self.nodes.borrow();
         let mut cur_el = Some(element);
 
+        let state = Rc::new(BubbleState::new());
+
         while let Some(id) = cur_el.take() {
             if let Some(el) = nodes.get(id.0) {
                 let real_el = unsafe { &**el };
                 if let VNode::Element(real_el) = real_el {
                     for listener in real_el.listeners.borrow().iter() {
                         if listener.event == event.name {
+                            if state.canceled.get() {
+                                // stop bubbling if canceled
+                                break;
+                            }
+
                             let mut cb = listener.callback.callback.borrow_mut();
                             if let Some(cb) = cb.as_mut() {
                                 // todo: arcs are pretty heavy to clone
                                 // we really want to convert arc to rc
                                 // unfortunately, the SchedulerMsg must be send/sync to be sent across threads
                                 // we could convert arc to rc internally or something
-                                (cb)(event.data.clone());
+                                (cb)(AnyEvent {
+                                    bubble_state: state.clone(),
+                                    data: event.data.clone(),
+                                });
                             }
                         }
                     }
@@ -702,10 +712,9 @@ impl ScopeState {
     /// }
     ///```
     pub fn render<'src>(&'src self, rsx: LazyNodes<'src, '_>) -> Option<VNode<'src>> {
-        let fac = NodeFactory {
+        Some(rsx.call(NodeFactory {
             bump: &self.wip_frame().bump,
-        };
-        Some(rsx.call(fac))
+        }))
     }
 
     /// Store a value between renders

+ 78 - 0
packages/core/src/util.rs

@@ -0,0 +1,78 @@
+use crate::innerlude::*;
+
+pub struct ElementIdIterator<'a> {
+    vdom: &'a VirtualDom,
+
+    // Heuristcally we should never bleed into 5 completely nested fragments/components
+    // Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
+    stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 5]>,
+}
+
+impl<'a> ElementIdIterator<'a> {
+    pub fn new(vdom: &'a VirtualDom, node: &'a VNode<'a>) -> Self {
+        Self {
+            vdom,
+            stack: smallvec::smallvec![(0, node)],
+        }
+    }
+}
+
+impl<'a> Iterator for ElementIdIterator<'a> {
+    type Item = &'a VNode<'a>;
+
+    fn next(&mut self) -> Option<&'a VNode<'a>> {
+        let mut should_pop = false;
+        let mut returned_node = None;
+        let mut should_push = None;
+
+        while returned_node.is_none() {
+            if let Some((count, node)) = self.stack.last_mut() {
+                match node {
+                    // We can only exit our looping when we get "real" nodes
+                    VNode::Element(_) | VNode::Text(_) | VNode::Placeholder(_) => {
+                        // We've recursed INTO an element/text
+                        // We need to recurse *out* of it and move forward to the next
+                        // println!("Found element! Returning it!");
+                        should_pop = true;
+                        returned_node = Some(&**node);
+                    }
+
+                    // If we get a fragment we push the next child
+                    VNode::Fragment(frag) => {
+                        let _count = *count as usize;
+                        if _count >= frag.children.len() {
+                            should_pop = true;
+                        } else {
+                            should_push = Some(&frag.children[_count]);
+                        }
+                    }
+
+                    // For components, we load their root and push them onto the stack
+                    VNode::Component(sc) => {
+                        let scope = self.vdom.get_scope(sc.scope.get().unwrap()).unwrap();
+                        // Simply swap the current node on the stack with the root of the component
+                        *node = scope.root_node();
+                    }
+                }
+            } else {
+                // If there's no more items on the stack, we're done!
+                return None;
+            }
+
+            if should_pop {
+                self.stack.pop();
+                if let Some((id, _)) = self.stack.last_mut() {
+                    *id += 1;
+                }
+                should_pop = false;
+            }
+
+            if let Some(push) = should_push {
+                self.stack.push((0, push));
+                should_push = None;
+            }
+        }
+
+        returned_node
+    }
+}

+ 44 - 411
packages/core/src/virtual_dom.rs

@@ -4,10 +4,10 @@
 
 use crate::innerlude::*;
 use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
-use futures_util::{Future, StreamExt};
+use futures_util::{future::poll_fn, StreamExt};
 use fxhash::FxHashSet;
 use indexmap::IndexSet;
-use std::{any::Any, collections::VecDeque, iter::FromIterator, pin::Pin, sync::Arc, task::Poll};
+use std::{collections::VecDeque, iter::FromIterator, task::Poll};
 
 /// A virtual node s ystem that progresses user events and diffs UI trees.
 ///
@@ -113,6 +113,18 @@ pub struct VirtualDom {
     ),
 }
 
+#[derive(Debug)]
+pub enum SchedulerMsg {
+    // events from the host
+    Event(UserEvent),
+
+    // setstate
+    Immediate(ScopeId),
+
+    // an async task pushed from an event handler (or just spawned)
+    NewTask(ScopeId),
+}
+
 // Methods to create the VirtualDom
 impl VirtualDom {
     /// Create a new VirtualDom with a component that does not have special props.
@@ -260,6 +272,7 @@ impl VirtualDom {
         self.channel.0.clone()
     }
 
+    /// Try to get an element from its ElementId
     pub fn get_element(&self, id: ElementId) -> Option<&VNode> {
         self.scopes.get_element(id)
     }
@@ -318,7 +331,35 @@ impl VirtualDom {
                 if self.scopes.tasks.has_tasks() {
                     use futures_util::future::{select, Either};
 
-                    match select(PollTasks(&mut self.scopes), self.channel.1.next()).await {
+                    let scopes = &mut self.scopes;
+                    let task_poll = poll_fn(|cx| {
+                        //
+                        let mut any_pending = false;
+
+                        let mut tasks = scopes.tasks.tasks.borrow_mut();
+                        let mut to_remove = vec![];
+
+                        // this would be better served by retain
+                        for (id, task) in tasks.iter_mut() {
+                            if task.as_mut().poll(cx).is_ready() {
+                                to_remove.push(*id);
+                            } else {
+                                any_pending = true;
+                            }
+                        }
+
+                        for id in to_remove {
+                            tasks.remove(&id);
+                        }
+
+                        // Resolve the future if any singular task is ready
+                        match any_pending {
+                            true => Poll::Pending,
+                            false => Poll::Ready(()),
+                        }
+                    });
+
+                    match select(task_poll, self.channel.1.next()).await {
                         Either::Left((_, _)) => {}
                         Either::Right((msg, _)) => self.pending_messages.push_front(msg.unwrap()),
                     }
@@ -699,411 +740,3 @@ impl Drop for VirtualDom {
         }
     }
 }
-
-struct PollTasks<'a>(&'a mut ScopeArena);
-
-impl<'a> Future for PollTasks<'a> {
-    type Output = ();
-
-    fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
-        let mut any_pending = false;
-
-        let mut tasks = self.0.tasks.tasks.borrow_mut();
-        let mut to_remove = vec![];
-
-        // this would be better served by retain
-        for (id, task) in tasks.iter_mut() {
-            if task.as_mut().poll(cx).is_ready() {
-                to_remove.push(*id);
-            } else {
-                any_pending = true;
-            }
-        }
-
-        for id in to_remove {
-            tasks.remove(&id);
-        }
-
-        // Resolve the future if any singular task is ready
-        match any_pending {
-            true => Poll::Pending,
-            false => Poll::Ready(()),
-        }
-    }
-}
-
-#[derive(Debug)]
-pub enum SchedulerMsg {
-    // events from the host
-    Event(UserEvent),
-
-    // setstate
-    Immediate(ScopeId),
-
-    // an async task pushed from an event handler (or just spawned)
-    NewTask(ScopeId),
-}
-
-/// User Events are events that are shuttled from the renderer into the VirtualDom trhough the scheduler channel.
-///
-/// These events will be passed to the appropriate Element given by `mounted_dom_id` and then bubbled up through the tree
-/// where each listener is checked and fired if the event name matches.
-///
-/// It is the expectation that the event name matches the corresponding event listener, otherwise Dioxus will panic in
-/// attempting to downcast the event data.
-///
-/// Because Event Data is sent across threads, it must be `Send + Sync`. We are hoping to lift the `Sync` restriction but
-/// `Send` will not be lifted. The entire `UserEvent` must also be `Send + Sync` due to its use in the scheduler channel.
-///
-/// # Example
-/// ```rust, ignore
-/// fn App(cx: Scope) -> Element {
-///     rsx!(cx, div {
-///         onclick: move |_| println!("Clicked!")
-///     })
-/// }
-///
-/// let mut dom = VirtualDom::new(App);
-/// let mut scheduler = dom.get_scheduler_channel();
-/// scheduler.unbounded_send(SchedulerMsg::UiEvent(
-///     UserEvent {
-///         scope_id: None,
-///         priority: EventPriority::Medium,
-///         name: "click",
-///         element: Some(ElementId(0)),
-///         data: Arc::new(ClickEvent { .. })
-///     }
-/// )).unwrap();
-/// ```
-#[derive(Debug)]
-pub struct UserEvent {
-    /// The originator of the event trigger if available
-    pub scope_id: Option<ScopeId>,
-
-    /// The priority of the event to be scheduled around ongoing work
-    pub priority: EventPriority,
-
-    /// The optional real node associated with the trigger
-    pub element: Option<ElementId>,
-
-    /// The event type IE "onclick" or "onmouseover"
-    pub name: &'static str,
-
-    /// The event data to be passed onto the event handler
-    pub data: Arc<dyn Any + Send + Sync>,
-}
-
-/// Priority of Event Triggers.
-///
-/// Internally, Dioxus will abort work that's taking too long if new, more important work arrives. Unlike React, Dioxus
-/// won't be afraid to pause work or flush changes to the RealDOM. This is called "cooperative scheduling". Some Renderers
-/// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
-///
-/// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
-///
-/// React has a 5-tier priority system. However, they break things into "Continuous" and "Discrete" priority. For now,
-/// we keep it simple, and just use a 3-tier priority system.
-///
-/// - NoPriority = 0
-/// - LowPriority = 1
-/// - NormalPriority = 2
-/// - UserBlocking = 3
-/// - HighPriority = 4
-/// - ImmediatePriority = 5
-///
-/// We still have a concept of discrete vs continuous though - discrete events won't be batched, but continuous events will.
-/// This means that multiple "scroll" events will be processed in a single frame, but multiple "click" events will be
-/// flushed before proceeding. Multiple discrete events is highly unlikely, though.
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
-pub enum EventPriority {
-    /// Work that must be completed during the EventHandler phase.
-    ///
-    /// Currently this is reserved for controlled inputs.
-    Immediate = 3,
-
-    /// "High Priority" work will not interrupt other high priority work, but will interrupt medium and low priority work.
-    ///
-    /// This is typically reserved for things like user interaction.
-    ///
-    /// React calls these "discrete" events, but with an extra category of "user-blocking" (Immediate).
-    High = 2,
-
-    /// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important
-    /// than "High Priority" events and will take precedence over low priority events.
-    ///
-    /// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
-    ///
-    /// React calls these "continuous" events (e.g. mouse move, mouse wheel, touch move, etc).
-    Medium = 1,
-
-    /// "Low Priority" work will always be preempted unless the work is significantly delayed, in which case it will be
-    /// advanced to the front of the work queue until completed.
-    ///
-    /// The primary user of Low Priority work is the asynchronous work system (Suspense).
-    ///
-    /// This is considered "idle" work or "background" work.
-    Low = 0,
-}
-
-pub struct FragmentProps<'a>(Element<'a>);
-pub struct FragmentBuilder<'a, const BUILT: bool>(Element<'a>);
-impl<'a> FragmentBuilder<'a, false> {
-    pub fn children(self, children: Element<'a>) -> FragmentBuilder<'a, true> {
-        FragmentBuilder(children)
-    }
-}
-impl<'a, const A: bool> FragmentBuilder<'a, A> {
-    pub fn build(self) -> FragmentProps<'a> {
-        FragmentProps(self.0)
-    }
-}
-
-/// Access the children elements passed into the component
-///
-/// This enables patterns where a component is passed children from its parent.
-///
-/// ## Details
-///
-/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions
-/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure
-/// on the props that takes Context.
-///
-/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other
-/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's
-/// props are valid for the static lifetime.
-///
-/// ## Example
-///
-/// ```rust, ignore
-/// fn App(cx: Scope) -> Element {
-///     cx.render(rsx!{
-///         CustomCard {
-///             h1 {}2
-///             p {}
-///         }
-///     })
-/// }
-///
-/// #[derive(PartialEq, Props)]
-/// struct CardProps {
-///     children: Element
-/// }
-///
-/// fn CustomCard(cx: Scope<CardProps>) -> Element {
-///     cx.render(rsx!{
-///         div {
-///             h1 {"Title card"}
-///             {cx.props.children}
-///         }
-///     })
-/// }
-/// ```
-impl<'a> Properties for FragmentProps<'a> {
-    type Builder = FragmentBuilder<'a, false>;
-    const IS_STATIC: bool = false;
-    fn builder() -> Self::Builder {
-        FragmentBuilder(None)
-    }
-    unsafe fn memoize(&self, _other: &Self) -> bool {
-        false
-    }
-}
-
-/// Create inline fragments using Component syntax.
-///
-/// ## Details
-///
-/// Fragments capture a series of children without rendering extra nodes.
-///
-/// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and
-/// a key is needed to identify each item.
-///
-/// ## Example
-///
-/// ```rust, ignore
-/// rsx!{
-///     Fragment { key: "abc" }
-/// }
-/// ```
-///
-/// ## Usage
-///
-/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
-/// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments.
-///
-/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
-///
-/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
-#[allow(non_upper_case_globals, non_snake_case)]
-pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
-    let i = cx.props.0.as_ref().map(|f| f.decouple());
-    cx.render(LazyNodes::new(|f| f.fragment_from_iter(i)))
-}
-
-/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
-/// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the
-/// derive macro to implement the `Properties` trait automatically as guarantee that your memoization strategy is safe.
-///
-/// If your props are 'static, then Dioxus will require that they also be PartialEq for the derived memoize strategy. However,
-/// if your props borrow data, then the memoization strategy will simply default to "false" and the PartialEq will be ignored.
-/// This tends to be useful when props borrow something that simply cannot be compared (IE a reference to a closure);
-///
-/// By default, the memoization strategy is very conservative, but can be tuned to be more aggressive manually. However,
-/// this is only safe if the props are 'static - otherwise you might borrow references after-free.
-///
-/// We strongly suggest that any changes to memoization be done at the "PartialEq" level for 'static props. Additionally,
-/// we advise the use of smart pointers in cases where memoization is important.
-///
-/// ## Example
-///
-/// For props that are 'static:
-/// ```rust, ignore
-/// #[derive(Props, PartialEq)]
-/// struct MyProps {
-///     data: String
-/// }
-/// ```
-///
-/// For props that borrow:
-///
-/// ```rust, ignore
-/// #[derive(Props)]
-/// struct MyProps<'a >{
-///     data: &'a str
-/// }
-/// ```
-pub trait Properties: Sized {
-    type Builder;
-    const IS_STATIC: bool;
-    fn builder() -> Self::Builder;
-
-    /// Memoization can only happen if the props are valid for the 'static lifetime
-    ///
-    /// # Safety
-    /// The user must know if their props are static, but if they make a mistake, UB happens
-    /// Therefore it's unsafe to memoize.
-    unsafe fn memoize(&self, other: &Self) -> bool;
-}
-
-impl Properties for () {
-    type Builder = EmptyBuilder;
-    const IS_STATIC: bool = true;
-    fn builder() -> Self::Builder {
-        EmptyBuilder {}
-    }
-    unsafe fn memoize(&self, _other: &Self) -> bool {
-        true
-    }
-}
-// We allow components to use the () generic parameter if they have no props. This impl enables the "build" method
-// that the macros use to anonymously complete prop construction.
-pub struct EmptyBuilder;
-impl EmptyBuilder {
-    pub fn build(self) {}
-}
-
-/// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern
-/// to initialize a component's props.
-pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder {
-    T::builder()
-}
-
-pub struct Event<T> {
-    data: Arc<T>,
-    _cancel: std::rc::Rc<std::cell::Cell<bool>>,
-}
-
-impl<T> std::ops::Deref for Event<T> {
-    type Target = T;
-
-    fn deref(&self) -> &Self::Target {
-        self.data.as_ref()
-    }
-}
-
-impl<T> Event<T> {
-    pub fn cancel(&self) {}
-    pub fn timestamp(&self) {}
-    pub fn triggered_element(&self) -> Option<Element> {
-        None
-    }
-}
-
-pub struct ElementIdIterator<'a> {
-    vdom: &'a VirtualDom,
-
-    // Heuristcally we should never bleed into 5 completely nested fragments/components
-    // Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
-    stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 5]>,
-}
-
-impl<'a> ElementIdIterator<'a> {
-    pub fn new(vdom: &'a VirtualDom, node: &'a VNode<'a>) -> Self {
-        Self {
-            vdom,
-            stack: smallvec::smallvec![(0, node)],
-        }
-    }
-}
-
-impl<'a> Iterator for ElementIdIterator<'a> {
-    type Item = &'a VNode<'a>;
-
-    fn next(&mut self) -> Option<&'a VNode<'a>> {
-        let mut should_pop = false;
-        let mut returned_node = None;
-        let mut should_push = None;
-
-        while returned_node.is_none() {
-            if let Some((count, node)) = self.stack.last_mut() {
-                match node {
-                    // We can only exit our looping when we get "real" nodes
-                    VNode::Element(_) | VNode::Text(_) | VNode::Placeholder(_) => {
-                        // We've recursed INTO an element/text
-                        // We need to recurse *out* of it and move forward to the next
-                        // println!("Found element! Returning it!");
-                        should_pop = true;
-                        returned_node = Some(&**node);
-                    }
-
-                    // If we get a fragment we push the next child
-                    VNode::Fragment(frag) => {
-                        let _count = *count as usize;
-                        if _count >= frag.children.len() {
-                            should_pop = true;
-                        } else {
-                            should_push = Some(&frag.children[_count]);
-                        }
-                    }
-
-                    // For components, we load their root and push them onto the stack
-                    VNode::Component(sc) => {
-                        // todo!();
-                        let scope = self.vdom.get_scope(sc.scope.get().unwrap()).unwrap();
-
-                        // Simply swap the current node on the stack with the root of the component
-                        *node = scope.root_node();
-                    }
-                }
-            } else {
-                // If there's no more items on the stack, we're done!
-                return None;
-            }
-
-            if should_pop {
-                self.stack.pop();
-                if let Some((id, _)) = self.stack.last_mut() {
-                    *id += 1;
-                }
-                should_pop = false;
-            }
-
-            if let Some(push) = should_push {
-                self.stack.push((0, push));
-                should_push = None;
-            }
-        }
-
-        returned_node
-    }
-}

+ 14 - 14
packages/desktop/src/events.rs

@@ -43,63 +43,63 @@ fn make_synthetic_event(name: &str, val: serde_json::Value) -> Arc<dyn Any + Sen
     match name {
         "copy" | "cut" | "paste" => {
             //
-            Arc::new(ClipboardEvent {})
+            Arc::new(ClipboardData {})
         }
         "compositionend" | "compositionstart" | "compositionupdate" => {
-            Arc::new(serde_json::from_value::<CompositionEvent>(val).unwrap())
+            Arc::new(serde_json::from_value::<CompositionData>(val).unwrap())
         }
         "keydown" | "keypress" | "keyup" => {
-            let evt = serde_json::from_value::<KeyboardEvent>(val).unwrap();
+            let evt = serde_json::from_value::<KeyboardData>(val).unwrap();
             Arc::new(evt)
         }
         "focus" | "blur" => {
             //
-            Arc::new(FocusEvent {})
+            Arc::new(FocusData {})
         }
 
         // todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy
         // don't have a good solution with the serialized event problem
         "change" | "input" | "invalid" | "reset" | "submit" => {
-            Arc::new(serde_json::from_value::<FormEvent>(val).unwrap())
+            Arc::new(serde_json::from_value::<FormData>(val).unwrap())
         }
 
         "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
         | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
         | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
-            Arc::new(serde_json::from_value::<MouseEvent>(val).unwrap())
+            Arc::new(serde_json::from_value::<MouseData>(val).unwrap())
         }
         "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
         | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
-            Arc::new(serde_json::from_value::<PointerEvent>(val).unwrap())
+            Arc::new(serde_json::from_value::<PointerData>(val).unwrap())
         }
         "select" => {
             //
-            Arc::new(serde_json::from_value::<SelectionEvent>(val).unwrap())
+            Arc::new(serde_json::from_value::<SelectionData>(val).unwrap())
         }
 
         "touchcancel" | "touchend" | "touchmove" | "touchstart" => {
-            Arc::new(serde_json::from_value::<TouchEvent>(val).unwrap())
+            Arc::new(serde_json::from_value::<TouchData>(val).unwrap())
         }
 
         "scroll" => Arc::new(()),
 
-        "wheel" => Arc::new(serde_json::from_value::<WheelEvent>(val).unwrap()),
+        "wheel" => Arc::new(serde_json::from_value::<WheelData>(val).unwrap()),
 
         "animationstart" | "animationend" | "animationiteration" => {
-            Arc::new(serde_json::from_value::<AnimationEvent>(val).unwrap())
+            Arc::new(serde_json::from_value::<AnimationData>(val).unwrap())
         }
 
-        "transitionend" => Arc::new(serde_json::from_value::<TransitionEvent>(val).unwrap()),
+        "transitionend" => Arc::new(serde_json::from_value::<TransitionData>(val).unwrap()),
 
         "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
         | "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
         | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
         | "timeupdate" | "volumechange" | "waiting" => {
             //
-            Arc::new(MediaEvent {})
+            Arc::new(MediaData {})
         }
 
-        "toggle" => Arc::new(ToggleEvent {}),
+        "toggle" => Arc::new(ToggleData {}),
 
         _ => Arc::new(()),
     }

+ 42 - 42
packages/hooks/src/lib.rs

@@ -16,45 +16,45 @@ pub use usefuture::*;
 mod usesuspense;
 pub use usesuspense::*;
 
-#[macro_export]
-macro_rules! to_owned {
-    ($($es:ident),+) => {$(
-        #[allow(unused_mut)]
-        let mut $es = $es.to_owned();
-    )*}
-}
-
-/// Calls `for_async` on the series of paramters.
-///
-/// If the type is Clone, then it will be cloned. However, if the type is not `clone`
-/// then it must have a `for_async` method for Rust to lower down into.
-///
-/// See: how use_state implements `for_async` but *not* through the trait.
-#[macro_export]
-macro_rules! for_async {
-    ($($es:ident),+) => {$(
-        #[allow(unused_mut)]
-        let mut $es = $es.for_async();
-    )*}
-}
-
-/// This is a marker trait that uses decoherence.
-///
-/// It is *not* meant for hooks to actually implement, but rather defer to their
-/// underlying implementation if they *don't* implement the trait.
-///
-///
-pub trait AsyncHook {
-    type Output;
-    fn for_async(self) -> Self::Output;
-}
-
-impl<T> AsyncHook for T
-where
-    T: ToOwned<Owned = T>,
-{
-    type Output = T;
-    fn for_async(self) -> Self::Output {
-        self
-    }
-}
+// #[macro_export]
+// macro_rules! to_owned {
+//     ($($es:ident),+) => {$(
+//         #[allow(unused_mut)]
+//         let mut $es = $es.to_owned();
+//     )*}
+// }
+
+// /// Calls `for_async` on the series of paramters.
+// ///
+// /// If the type is Clone, then it will be cloned. However, if the type is not `clone`
+// /// then it must have a `for_async` method for Rust to lower down into.
+// ///
+// /// See: how use_state implements `for_async` but *not* through the trait.
+// #[macro_export]
+// macro_rules! for_async {
+//     ($($es:ident),+) => {$(
+//         #[allow(unused_mut)]
+//         let mut $es = $es.for_async();
+//     )*}
+// }
+
+// /// This is a marker trait that uses decoherence.
+// ///
+// /// It is *not* meant for hooks to actually implement, but rather defer to their
+// /// underlying implementation if they *don't* implement the trait.
+// ///
+// ///
+// pub trait AsyncHook {
+//     type Output;
+//     fn for_async(self) -> Self::Output;
+// }
+
+// impl<T> AsyncHook for T
+// where
+//     T: ToOwned<Owned = T>,
+// {
+//     type Output = T;
+//     fn for_async(self) -> Self::Output {
+//         self
+//     }
+// }

+ 53 - 39
packages/html/src/events.rs

@@ -1,16 +1,13 @@
 use bumpalo::boxed::Box as BumpBox;
 use dioxus_core::exports::bumpalo;
 use dioxus_core::*;
-use std::any::Any;
 
 pub mod on {
     use super::*;
-    use std::sync::Arc;
     macro_rules! event_directory {
         ( $(
             $( #[$attr:meta] )*
-            $wrapper:ident: [
-            // $eventdata:ident($wrapper:ident): [
+            $wrapper:ident($data:ident): [
                 $(
                     $( #[$method_attr:meta] )*
                     $name:ident
@@ -24,19 +21,21 @@ pub mod on {
                         factory: NodeFactory<'a>,
                         mut callback: F,
                     ) -> Listener<'a>
-                        where F: FnMut(Arc<$wrapper>) + 'a
+                        where F: FnMut($wrapper) + 'a
                     {
                         let bump = &factory.bump();
 
+
+                        use dioxus_core::{AnyEvent};
                         // we can't allocate unsized in bumpalo's box, so we need to craft the box manually
                         // safety: this is essentially the same as calling Box::new() but manually
                         // The box is attached to the lifetime of the bumpalo allocator
-                        let cb: &mut dyn FnMut(Arc<dyn Any + Send + Sync>) = bump.alloc(move |evt: Arc<dyn Any + Send + Sync>| {
-                            let event = evt.downcast::<$wrapper>().unwrap();
+                        let cb: &mut dyn FnMut(AnyEvent) = bump.alloc(move |evt: AnyEvent| {
+                            let event = evt.downcast::<$data>().unwrap();
                             callback(event)
                         });
 
-                        let callback: BumpBox<dyn FnMut(Arc<dyn Any + Send + Sync>) + 'a> = unsafe { BumpBox::from_raw(cb) };
+                        let callback: BumpBox<dyn FnMut(AnyEvent) + 'a> = unsafe { BumpBox::from_raw(cb) };
 
                         // ie oncopy
                         let event_name = stringify!($name);
@@ -58,7 +57,7 @@ pub mod on {
     // The Dioxus Synthetic event system
     // todo: move these into the html event system. dioxus accepts *any* event, so having these here doesn't make sense.
     event_directory! {
-        ClipboardEvent: [
+        ClipboardEvent(ClipboardData): [
             /// Called when "copy"
             oncopy
 
@@ -69,7 +68,7 @@ pub mod on {
             onpaste
         ];
 
-        CompositionEvent: [
+        CompositionEvent(CompositionData): [
             /// oncompositionend
             oncompositionend
 
@@ -80,7 +79,7 @@ pub mod on {
             oncompositionupdate
         ];
 
-        KeyboardEvent: [
+        KeyboardEvent(KeyboardData): [
             /// onkeydown
             onkeydown
 
@@ -91,7 +90,7 @@ pub mod on {
             onkeyup
         ];
 
-        FocusEvent: [
+        FocusEvent(FocusData): [
             /// onfocus
             onfocus
 
@@ -99,7 +98,7 @@ pub mod on {
             onblur
         ];
 
-        FormEvent: [
+        FormEvent(FormData): [
             /// onchange
             onchange
 
@@ -157,7 +156,7 @@ pub mod on {
         /// - [`onmouseout`]
         /// - [`onmouseover`]
         /// - [`onmouseup`]
-        MouseEvent: [
+        MouseEvent(MouseData): [
             /// Execute a callback when a button is clicked.
             ///
             /// ## Description
@@ -167,7 +166,7 @@ pub mod on {
             ///
             /// - Bubbles: Yes
             /// - Cancelable: Yes
-            /// - Interface: [`MouseEvent`]
+            /// - Interface(InteData): [`MouseEvent`]
             ///
             /// If the button is pressed on one element and the pointer is moved outside the element before the button
             /// is released, the event is fired on the most specific ancestor element that contained both elements.
@@ -240,7 +239,7 @@ pub mod on {
             onmouseup
         ];
 
-        PointerEvent: [
+        PointerEvent(PointerData): [
             /// pointerdown
             onpointerdown
 
@@ -272,12 +271,12 @@ pub mod on {
             onpointerout
         ];
 
-        SelectionEvent: [
+        SelectionEvent(SelectionData): [
             /// onselect
             onselect
         ];
 
-        TouchEvent: [
+        TouchEvent(TouchData): [
             /// ontouchcancel
             ontouchcancel
 
@@ -291,12 +290,12 @@ pub mod on {
             ontouchstart
         ];
 
-        WheelEvent: [
+        WheelEvent(WheelData): [
             ///
             onwheel
         ];
 
-        MediaEvent: [
+        MediaEvent(MediaData): [
             ///abort
             onabort
 
@@ -367,7 +366,7 @@ pub mod on {
             onwaiting
         ];
 
-        AnimationEvent: [
+        AnimationEvent(AnimationData): [
             /// onanimationstart
             onanimationstart
 
@@ -378,32 +377,35 @@ pub mod on {
             onanimationiteration
         ];
 
-        TransitionEvent: [
+        TransitionEvent(TransitionData): [
             ///
             ontransitionend
         ];
 
-        ToggleEvent: [
+        ToggleEvent(ToggleData): [
             ///
             ontoggle
         ];
     }
 
+    pub type ClipboardEvent = UiEvent<ClipboardData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
     #[derive(Debug)]
-    pub struct ClipboardEvent(
+    pub struct ClipboardData {
         // DOMDataTransfer clipboardData
-    );
+    }
 
+    pub type CompositionEvent = UiEvent<CompositionData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
     #[derive(Debug)]
-    pub struct CompositionEvent {
+    pub struct CompositionData {
         pub data: String,
     }
 
+    pub type KeyboardEvent = UiEvent<KeyboardData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
     #[derive(Debug)]
-    pub struct KeyboardEvent {
+    pub struct KeyboardData {
         pub char_code: u32,
 
         /// Identify which "key" was entered.
@@ -469,20 +471,23 @@ pub mod on {
         // get_modifier_state: bool,
     }
 
+    pub type FocusEvent = UiEvent<FocusData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
     #[derive(Debug)]
-    pub struct FocusEvent {/* DOMEventInner:  Send + SyncTarget relatedTarget */}
+    pub struct FocusData {/* DOMEventInner:  Send + SyncTarget relatedTarget */}
 
+    pub type FormEvent = UiEvent<FormData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
     #[derive(Debug)]
-    pub struct FormEvent {
+    pub struct FormData {
         pub value: String,
         /* DOMEvent:  Send + SyncTarget relatedTarget */
     }
 
+    pub type MouseEvent = UiEvent<MouseData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
     #[derive(Debug)]
-    pub struct MouseEvent {
+    pub struct MouseData {
         pub alt_key: bool,
         pub button: i16,
         pub buttons: u16,
@@ -498,9 +503,10 @@ pub mod on {
         // fn get_modifier_state(&self, key_code: &str) -> bool;
     }
 
+    pub type PointerEvent = UiEvent<PointerData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
     #[derive(Debug)]
-    pub struct PointerEvent {
+    pub struct PointerData {
         // Mouse only
         pub alt_key: bool,
         pub button: i16,
@@ -527,13 +533,15 @@ pub mod on {
         // pub get_modifier_state: bool,
     }
 
+    pub type SelectionEvent = UiEvent<SelectionData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
     #[derive(Debug)]
-    pub struct SelectionEvent {}
+    pub struct SelectionData {}
 
+    pub type TouchEvent = UiEvent<TouchData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
     #[derive(Debug)]
-    pub struct TouchEvent {
+    pub struct TouchData {
         pub alt_key: bool,
         pub ctrl_key: bool,
         pub meta_key: bool,
@@ -544,44 +552,50 @@ pub mod on {
         // touches: DOMTouchList,
     }
 
+    pub type WheelEvent = UiEvent<WheelData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
     #[derive(Debug)]
-    pub struct WheelEvent {
+    pub struct WheelData {
         pub delta_mode: u32,
         pub delta_x: f64,
         pub delta_y: f64,
         pub delta_z: f64,
     }
 
+    pub type MediaEvent = UiEvent<MediaData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
     #[derive(Debug)]
-    pub struct MediaEvent {}
+    pub struct MediaData {}
 
+    pub type ImageEvent = UiEvent<ImageData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
     #[derive(Debug)]
-    pub struct ImageEvent {
+    pub struct ImageData {
         pub load_error: bool,
     }
 
+    pub type AnimationEvent = UiEvent<AnimationData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
     #[derive(Debug)]
-    pub struct AnimationEvent {
+    pub struct AnimationData {
         pub animation_name: String,
         pub pseudo_element: String,
         pub elapsed_time: f32,
     }
 
+    pub type TransitionEvent = UiEvent<TransitionData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
     #[derive(Debug)]
-    pub struct TransitionEvent {
+    pub struct TransitionData {
         pub property_name: String,
         pub pseudo_element: String,
         pub elapsed_time: f32,
     }
 
+    pub type ToggleEvent = UiEvent<ToggleData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
     #[derive(Debug)]
-    pub struct ToggleEvent {}
+    pub struct ToggleData {}
 }
 
 #[cfg_attr(