mirror of
https://github.com/DioxusLabs/dioxus
synced 2025-02-17 06:08:26 +00:00
wip: overhaul event system
This commit is contained in:
parent
c44bd11fe5
commit
47d0f51e00
48 changed files with 3176 additions and 2598 deletions
|
@ -2,7 +2,8 @@
|
|||
//!
|
||||
//! There is some conversion happening when input types are checkbox/radio/select/textarea etc.
|
||||
|
||||
use dioxus::{events::FormEvent, prelude::*};
|
||||
use dioxus::events::FormEvent;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::{nodes::VNode, virtual_dom::VirtualDom};
|
||||
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||
pub struct ElementId(pub usize);
|
||||
|
||||
|
|
|
@ -87,10 +87,14 @@ impl VirtualDom {
|
|||
value,
|
||||
id,
|
||||
}),
|
||||
AttributeValue::Bool(value) => mutations.push(SetBoolAttribute {
|
||||
name: attribute.name,
|
||||
value,
|
||||
id,
|
||||
}),
|
||||
AttributeValue::Listener(_) => todo!("create listener attributes"),
|
||||
AttributeValue::Float(_) => todo!(),
|
||||
AttributeValue::Int(_) => todo!(),
|
||||
AttributeValue::Bool(_) => todo!(),
|
||||
AttributeValue::Any(_) => todo!(),
|
||||
AttributeValue::None => todo!(),
|
||||
}
|
||||
|
@ -138,11 +142,13 @@ impl VirtualDom {
|
|||
TemplateNode::DynamicText { .. } => mutations.push(CreateText {
|
||||
value: "placeholder",
|
||||
}),
|
||||
|
||||
TemplateNode::Element {
|
||||
attrs,
|
||||
children,
|
||||
namespace,
|
||||
tag,
|
||||
inner_opt,
|
||||
} => {
|
||||
let id = self.next_element(template);
|
||||
|
||||
|
@ -159,6 +165,10 @@ impl VirtualDom {
|
|||
_ => None,
|
||||
}));
|
||||
|
||||
if children.is_empty() && inner_opt {
|
||||
return;
|
||||
}
|
||||
|
||||
children
|
||||
.into_iter()
|
||||
.for_each(|child| self.create_static_node(mutations, template, child));
|
||||
|
@ -176,7 +186,7 @@ impl VirtualDom {
|
|||
idx: usize,
|
||||
) -> usize {
|
||||
match &node {
|
||||
DynamicNode::Text { id, value } => {
|
||||
DynamicNode::Text { id, value, inner } => {
|
||||
let new_id = self.next_element(template);
|
||||
id.set(new_id);
|
||||
mutations.push(HydrateText {
|
||||
|
@ -188,11 +198,17 @@ impl VirtualDom {
|
|||
}
|
||||
|
||||
DynamicNode::Component {
|
||||
props, placeholder, ..
|
||||
props,
|
||||
placeholder,
|
||||
scope: scope_slot,
|
||||
..
|
||||
} => {
|
||||
let scope = self
|
||||
.new_scope(unsafe { std::mem::transmute(props.get()) })
|
||||
.id;
|
||||
|
||||
scope_slot.set(Some(scope));
|
||||
|
||||
let return_nodes = unsafe { self.run_scope(scope).extend_lifetime_ref() };
|
||||
|
||||
match return_nodes {
|
||||
|
@ -247,7 +263,7 @@ impl VirtualDom {
|
|||
}
|
||||
}
|
||||
|
||||
DynamicNode::Fragment(children) => children
|
||||
DynamicNode::Fragment { nodes, .. } => nodes
|
||||
.iter()
|
||||
.fold(0, |acc, child| acc + self.create(mutations, child)),
|
||||
|
||||
|
|
|
@ -177,7 +177,7 @@ impl<'b> VirtualDom {
|
|||
todo!()
|
||||
},
|
||||
|
||||
(DynamicNode::Text { id: lid, value: lvalue }, DynamicNode::Text { id: rid, value: rvalue }) => {
|
||||
(DynamicNode::Text { id: lid, value: lvalue, .. }, DynamicNode::Text { id: rid, value: rvalue, .. }) => {
|
||||
rid.set(lid.get());
|
||||
if lvalue != rvalue {
|
||||
muts.push(Mutation::SetText {
|
||||
|
@ -196,7 +196,7 @@ impl<'b> VirtualDom {
|
|||
(DynamicNode::Placeholder(_), _) => todo!(),
|
||||
|
||||
|
||||
(DynamicNode::Fragment (l), DynamicNode::Fragment (r)) => {
|
||||
(DynamicNode::Fragment { nodes: lnodes, ..}, DynamicNode::Fragment { nodes: rnodes, ..}) => {
|
||||
|
||||
|
||||
// match (old, new) {
|
||||
|
|
|
@ -1,50 +1,5 @@
|
|||
use crate::{arena::ElementId, virtual_dom::VirtualDom, Attribute, AttributeValue};
|
||||
use std::cell::Cell;
|
||||
|
||||
/// User Events are events that are shuttled from the renderer into the [`VirtualDom`] through 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 {
|
||||
/// render!(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 UiEvent<T> {
|
||||
/// The priority of the event to be scheduled around ongoing work
|
||||
pub priority: EventPriority,
|
||||
|
||||
/// The optional real node associated with the trigger
|
||||
pub element: ElementId,
|
||||
|
||||
/// The event type IE "onclick" or "onmouseover"
|
||||
pub name: &'static str,
|
||||
pub bubble: Cell<bool>,
|
||||
pub event: T,
|
||||
}
|
||||
use bumpalo::boxed::Box as BumpBox;
|
||||
use std::{any::Any, cell::RefCell};
|
||||
|
||||
/// Priority of Event Triggers.
|
||||
///
|
||||
|
@ -108,6 +63,65 @@ pub fn is_path_ascendant(small: &[u8], big: &[u8]) -> bool {
|
|||
small == &big[..small.len()]
|
||||
}
|
||||
|
||||
pub type InternalHandler<'bump> = &'bump RefCell<Option<InternalListenerCallback<'bump>>>;
|
||||
type InternalListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(&'bump dyn Any) + 'bump>;
|
||||
type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>;
|
||||
|
||||
/// The callback type generated by the `rsx!` macro when an `on` field is specified for components.
|
||||
///
|
||||
/// This makes it possible to pass `move |evt| {}` style closures into components as property fields.
|
||||
///
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
///
|
||||
/// rsx!{
|
||||
/// MyComponent { onclick: move |evt| log::info!("clicked"), }
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Props)]
|
||||
/// struct MyProps<'a> {
|
||||
/// onclick: EventHandler<'a, MouseEvent>,
|
||||
/// }
|
||||
///
|
||||
/// fn MyComponent(cx: Scope<'a, MyProps<'a>>) -> Element {
|
||||
/// cx.render(rsx!{
|
||||
/// button {
|
||||
/// onclick: move |evt| cx.props.onclick.call(evt),
|
||||
/// }
|
||||
/// })
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
pub struct EventHandler<'bump, T = ()> {
|
||||
/// The (optional) callback that the user specified
|
||||
/// Uses a `RefCell` to allow for interior mutability, and FnMut closures.
|
||||
pub callback: RefCell<Option<ExternalListenerCallback<'bump, T>>>,
|
||||
}
|
||||
|
||||
impl<'a, T> Default for EventHandler<'a, T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
callback: RefCell::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventHandler<'_, T> {
|
||||
/// Call this event handler with the appropriate event type
|
||||
pub fn call(&self, event: T) {
|
||||
if let Some(callback) = self.callback.borrow_mut().as_mut() {
|
||||
callback(event);
|
||||
}
|
||||
}
|
||||
|
||||
/// Forcibly drop the internal handler callback, releasing memory
|
||||
pub fn release(&self) {
|
||||
self.callback.replace(None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_slice() {
|
||||
let left = &[1, 2, 3];
|
||||
|
|
|
@ -18,6 +18,7 @@ impl ScopeState {
|
|||
DynamicNode::Text {
|
||||
id: Cell::new(ElementId(0)),
|
||||
value: text,
|
||||
inner: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,27 +38,32 @@ impl ScopeState {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn fragment_from_iter<'a, I, F: IntoVnode<'a, I>>(
|
||||
pub fn fragment_from_iter<'a, 'c, I, J>(
|
||||
&'a self,
|
||||
it: impl IntoIterator<Item = F>,
|
||||
node_iter: impl IntoVNode<'a, I, J> + 'c,
|
||||
) -> DynamicNode {
|
||||
let mut bump_vec = bumpalo::vec![in self.bump();];
|
||||
node_iter.into_vnode(self)
|
||||
|
||||
for item in it {
|
||||
bump_vec.push(item.into_dynamic_node(self));
|
||||
}
|
||||
// let mut bump_vec = bumpalo::vec![in self.bump();];
|
||||
|
||||
match bump_vec.len() {
|
||||
0 => DynamicNode::Placeholder(Cell::new(ElementId(0))),
|
||||
_ => DynamicNode::Fragment(bump_vec.into_bump_slice()),
|
||||
}
|
||||
// for item in it {
|
||||
// bump_vec.push(item.into_vnode(self));
|
||||
// }
|
||||
|
||||
// match bump_vec.len() {
|
||||
// 0 => DynamicNode::Placeholder(Cell::new(ElementId(0))),
|
||||
// _ => DynamicNode::Fragment {
|
||||
// inner: false,
|
||||
// nodes: bump_vec.into_bump_slice(),
|
||||
// },
|
||||
// }
|
||||
}
|
||||
|
||||
/// Create a new [`Attribute`]
|
||||
pub fn attr<'a>(
|
||||
&'a self,
|
||||
name: &'static str,
|
||||
val: impl IntoAttributeValue<'a>,
|
||||
value: impl IntoAttributeValue<'a>,
|
||||
namespace: Option<&'static str>,
|
||||
volatile: bool,
|
||||
) -> Attribute<'a> {
|
||||
|
@ -65,7 +71,7 @@ impl ScopeState {
|
|||
name,
|
||||
namespace,
|
||||
volatile,
|
||||
value: val.into_value(self.bump()),
|
||||
value: value.into_value(self.bump()),
|
||||
mounted_element: Cell::new(ElementId(0)),
|
||||
}
|
||||
}
|
||||
|
@ -97,6 +103,7 @@ impl ScopeState {
|
|||
static_props: P::IS_STATIC,
|
||||
props: Cell::new(detached_dyn),
|
||||
placeholder: Cell::new(None),
|
||||
scope: Cell::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -137,39 +144,100 @@ impl<'a> RenderReturn<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait IntoVnode<'a, A = ()> {
|
||||
fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a>;
|
||||
pub trait IntoVNode<'a, A = (), J = ()> {
|
||||
fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a>;
|
||||
}
|
||||
|
||||
impl<'a, 'b> IntoVnode<'a> for LazyNodes<'a, 'b> {
|
||||
fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a> {
|
||||
self.call(cx)
|
||||
impl<'a, 'b> IntoVNode<'a> for VNode<'a> {
|
||||
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
todo!()
|
||||
// self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> IntoVnode<'a> for VNode<'a> {
|
||||
fn into_dynamic_node(self, _cx: &'a ScopeState) -> VNode<'a> {
|
||||
self
|
||||
impl<'a, 'b> IntoVNode<'a> for LazyNodes<'a, 'b> {
|
||||
fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
todo!()
|
||||
// self.call(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> IntoVnode<'a> for Option<VNode<'a>> {
|
||||
fn into_dynamic_node(self, _cx: &'a ScopeState) -> VNode<'a> {
|
||||
self.expect("please allow optional nodes in")
|
||||
impl<'b> IntoVNode<'_> for &'b str {
|
||||
fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
|
||||
// cx.text(format_args!("{}", self))
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> IntoVnode<'a> for &'a VNode<'a> {
|
||||
fn into_dynamic_node(self, _cx: &'a ScopeState) -> VNode<'a> {
|
||||
VNode {
|
||||
node_id: self.node_id.clone(),
|
||||
parent: self.parent,
|
||||
template: self.template,
|
||||
root_ids: self.root_ids,
|
||||
key: self.key,
|
||||
dynamic_nodes: self.dynamic_nodes,
|
||||
dynamic_attrs: self.dynamic_attrs,
|
||||
impl IntoVNode<'_> for String {
|
||||
fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
|
||||
// cx.text(format_args!("{}", self))
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> IntoVNode<'b> for Arguments<'_> {
|
||||
fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> {
|
||||
// cx.text(self)
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> IntoVNode<'a> for &VNode<'a> {
|
||||
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
todo!()
|
||||
// VNode {
|
||||
// node_id: self.node_id.clone(),
|
||||
// parent: self.parent,
|
||||
// template: self.template,
|
||||
// root_ids: self.root_ids,
|
||||
// key: self.key,
|
||||
// dynamic_nodes: self.dynamic_nodes,
|
||||
// dynamic_attrs: self.dynamic_attrs,
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
// Note that we're using the E as a generic but this is never crafted anyways.
|
||||
pub struct FromNodeIterator;
|
||||
impl<'a, T, I, E> IntoVNode<'a, FromNodeIterator, E> for T
|
||||
where
|
||||
T: IntoIterator<Item = I>,
|
||||
I: IntoVNode<'a, E>,
|
||||
{
|
||||
fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
let mut nodes = bumpalo::collections::Vec::new_in(cx.bump());
|
||||
|
||||
for node in self {
|
||||
nodes.push(node.into_vnode(cx));
|
||||
}
|
||||
|
||||
let children = nodes.into_bump_slice();
|
||||
|
||||
// if cfg!(debug_assertions) && children.len() > 1 && children.last().unwrap().key().is_none()
|
||||
// {
|
||||
// let bt = backtrace::Backtrace::new();
|
||||
// let bt = "no backtrace available";
|
||||
|
||||
// // todo: make the backtrace prettier or remove it altogether
|
||||
// log::error!(
|
||||
// r#"
|
||||
// Warning: Each child in an array or iterator should have a unique "key" prop.
|
||||
// Not providing a key will lead to poor performance with lists.
|
||||
// See docs.rs/dioxus for more information.
|
||||
// -------------
|
||||
// {:?}
|
||||
// "#,
|
||||
// bt
|
||||
// );
|
||||
// }
|
||||
|
||||
todo!()
|
||||
// VNode::Fragment(cx.bump.alloc(VFragment {
|
||||
// children,
|
||||
// placeholder: Default::default(),
|
||||
// key: None,
|
||||
// }))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,8 +25,9 @@ impl<'b> VirtualDom {
|
|||
todo!()
|
||||
}
|
||||
|
||||
DynamicNode::Fragment(children) => {}
|
||||
DynamicNode::Fragment { inner, nodes } => {}
|
||||
DynamicNode::Placeholder(_) => todo!(),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,7 +92,6 @@ pub use crate::innerlude::{
|
|||
Template,
|
||||
TemplateAttribute,
|
||||
TemplateNode,
|
||||
UiEvent,
|
||||
VNode,
|
||||
VirtualDom,
|
||||
};
|
||||
|
@ -102,9 +101,9 @@ pub use crate::innerlude::{
|
|||
/// This includes types like [`Scope`], [`Element`], and [`Component`].
|
||||
pub mod prelude {
|
||||
pub use crate::innerlude::{
|
||||
fc_to_builder, Element, EventPriority, Fragment, LazyNodes, NodeFactory, Properties, Scope,
|
||||
ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, UiEvent,
|
||||
VNode, VirtualDom,
|
||||
fc_to_builder, Element, EventHandler, EventPriority, Fragment, LazyNodes, NodeFactory,
|
||||
Properties, Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute,
|
||||
TemplateNode, VNode, VirtualDom,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ impl std::ops::DerefMut for Mutations<'_> {
|
|||
each subtree has its own numbering scheme
|
||||
*/
|
||||
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Mutation<'a> {
|
||||
SetAttribute {
|
||||
|
@ -43,6 +44,12 @@ pub enum Mutation<'a> {
|
|||
id: ElementId,
|
||||
},
|
||||
|
||||
SetBoolAttribute {
|
||||
name: &'a str,
|
||||
value: bool,
|
||||
id: ElementId,
|
||||
},
|
||||
|
||||
LoadTemplate {
|
||||
name: &'static str,
|
||||
index: usize,
|
||||
|
@ -86,6 +93,10 @@ pub enum Mutation<'a> {
|
|||
id: ElementId,
|
||||
},
|
||||
|
||||
SetInnerText {
|
||||
value: &'a str,
|
||||
},
|
||||
|
||||
CreateText {
|
||||
value: &'a str,
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{any_props::AnyProps, arena::ElementId, ScopeState};
|
||||
use crate::{any_props::AnyProps, arena::ElementId, ScopeId, ScopeState};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::{Cell, RefCell},
|
||||
|
@ -15,7 +15,7 @@ pub struct VNode<'a> {
|
|||
pub key: Option<&'a str>,
|
||||
|
||||
// When rendered, this template will be linked to its parent manually
|
||||
pub parent: Option<(*mut VNode<'static>, usize)>,
|
||||
pub parent: Option<*const DynamicNode<'a>>,
|
||||
|
||||
pub template: Template<'static>,
|
||||
|
||||
|
@ -98,6 +98,7 @@ pub enum TemplateNode<'a> {
|
|||
namespace: Option<&'a str>,
|
||||
attrs: &'a [TemplateAttribute<'a>],
|
||||
children: &'a [TemplateNode<'a>],
|
||||
inner_opt: bool,
|
||||
},
|
||||
Text(&'a str),
|
||||
Dynamic(usize),
|
||||
|
@ -110,12 +111,17 @@ pub enum DynamicNode<'a> {
|
|||
static_props: bool,
|
||||
props: Cell<*mut dyn AnyProps<'a>>,
|
||||
placeholder: Cell<Option<ElementId>>,
|
||||
scope: Cell<Option<ScopeId>>,
|
||||
},
|
||||
Text {
|
||||
id: Cell<ElementId>,
|
||||
value: &'a str,
|
||||
inner: bool,
|
||||
},
|
||||
Fragment {
|
||||
nodes: &'a [VNode<'a>],
|
||||
inner: bool,
|
||||
},
|
||||
Fragment(&'a [VNode<'a>]),
|
||||
Placeholder(Cell<ElementId>),
|
||||
}
|
||||
|
||||
|
@ -215,3 +221,54 @@ fn what_are_the_sizes() {
|
|||
dbg!(std::mem::size_of::<Template>());
|
||||
dbg!(std::mem::size_of::<TemplateNode>());
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
|
||||
SSR includes data-id which allows O(1) hydration
|
||||
|
||||
|
||||
we read the edit stream dn then we can just rehydare
|
||||
|
||||
|
||||
|
||||
ideas:
|
||||
- IDs for lookup
|
||||
- use edit stream to hydrate
|
||||
- write comments to dom that specify size of children
|
||||
|
||||
IDs for lookups
|
||||
- adds noise to generated html
|
||||
- doesnt work for text nodes
|
||||
- suspense could cause ordering to be weird
|
||||
|
||||
Names for lookups:
|
||||
- label each root or something with the template name
|
||||
- label each dynamic node with a path
|
||||
- noisy too
|
||||
- allows reverse lookups
|
||||
|
||||
Ideal:
|
||||
- no noise in the dom
|
||||
- fast, ideally O(1)
|
||||
- able to pick apart text nodes that get merged during SSR
|
||||
|
||||
|
||||
--> render vdom
|
||||
--> traverse vdom and real dom simultaneously
|
||||
|
||||
IE
|
||||
|
||||
div {
|
||||
div {
|
||||
div {
|
||||
"thing"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
|
|
@ -17,7 +17,7 @@ use std::{
|
|||
};
|
||||
|
||||
impl VirtualDom {
|
||||
pub fn new_scope(&mut self, props: *mut dyn AnyProps<'static>) -> &mut ScopeState {
|
||||
pub(super) fn new_scope(&mut self, props: *mut dyn AnyProps<'static>) -> &mut ScopeState {
|
||||
let parent = self.acquire_current_scope_raw();
|
||||
let container = self.acquire_current_container();
|
||||
let entry = self.scopes.vacant_entry();
|
||||
|
|
|
@ -4,18 +4,21 @@ use crate::{
|
|||
arena::ElementPath,
|
||||
diff::DirtyScope,
|
||||
factory::RenderReturn,
|
||||
innerlude::{is_path_ascendant, Mutations, Scheduler, SchedulerMsg},
|
||||
innerlude::{Mutations, Scheduler, SchedulerMsg},
|
||||
mutations::Mutation,
|
||||
nodes::{Template, TemplateId},
|
||||
scheduler::{SuspenseBoundary, SuspenseId},
|
||||
scopes::{ScopeId, ScopeState},
|
||||
Attribute, AttributeValue, Element, Scope, SuspenseContext, UiEvent,
|
||||
Attribute, AttributeValue, Element, EventPriority, Scope, SuspenseContext,
|
||||
};
|
||||
use futures_util::{pin_mut, FutureExt, StreamExt};
|
||||
use slab::Slab;
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
use std::future::Future;
|
||||
use std::rc::Rc;
|
||||
use std::{
|
||||
any::Any,
|
||||
collections::{BTreeSet, HashMap},
|
||||
};
|
||||
|
||||
/// A virtual node system that progresses user events and diffs UI trees.
|
||||
///
|
||||
|
@ -275,42 +278,68 @@ impl VirtualDom {
|
|||
!self.scheduler.leaves.borrow().is_empty()
|
||||
}
|
||||
|
||||
/// Returns None if no element could be found
|
||||
pub fn handle_event<T: 'static>(&mut self, event: &UiEvent<T>) -> Option<()> {
|
||||
let path = self.elements.get(event.element.0)?;
|
||||
/// Call a listener inside the VirtualDom with data from outside the VirtualDom.
|
||||
///
|
||||
/// This method will identify the appropriate element
|
||||
///
|
||||
///
|
||||
///
|
||||
///
|
||||
///
|
||||
///
|
||||
///
|
||||
pub fn handle_event(
|
||||
&mut self,
|
||||
name: &str,
|
||||
event: &dyn Any,
|
||||
element: ElementId,
|
||||
bubbles: bool,
|
||||
priority: EventPriority,
|
||||
) -> Option<()> {
|
||||
/*
|
||||
- click registers
|
||||
- walk upwards until first element with onclick listener
|
||||
- get template from ElementID
|
||||
- break out of wait loop
|
||||
- send event to virtualdom
|
||||
*/
|
||||
|
||||
let location = unsafe { &mut *path.template }
|
||||
let path = &self.elements[element.0];
|
||||
let template = unsafe { &*path.template };
|
||||
let dynamic = &template.dynamic_nodes[path.element];
|
||||
|
||||
let location = template
|
||||
.dynamic_attrs
|
||||
.iter()
|
||||
.position(|attr| attr.mounted_element.get() == event.element)?;
|
||||
.position(|attr| attr.mounted_element.get() == element)?;
|
||||
|
||||
let mut index = Some((path.template, location));
|
||||
// let mut index = Some((path.template, location));
|
||||
|
||||
let mut listeners = Vec::<&Attribute>::new();
|
||||
|
||||
while let Some((raw_parent, dyn_index)) = index {
|
||||
let parent = unsafe { &mut *raw_parent };
|
||||
let path = parent.template.node_paths[dyn_index];
|
||||
// while let Some((raw_parent, dyn_index)) = index {
|
||||
// let parent = unsafe { &mut *raw_parent };
|
||||
// let path = parent.template.node_paths[dyn_index];
|
||||
|
||||
listeners.extend(
|
||||
parent
|
||||
.dynamic_attrs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(idx, attr)| {
|
||||
match is_path_ascendant(parent.template.node_paths[idx], path) {
|
||||
true if attr.name == event.name => Some(attr),
|
||||
_ => None,
|
||||
}
|
||||
}),
|
||||
);
|
||||
// listeners.extend(
|
||||
// parent
|
||||
// .dynamic_attrs
|
||||
// .iter()
|
||||
// .enumerate()
|
||||
// .filter_map(|(idx, attr)| {
|
||||
// match is_path_ascendant(parent.template.node_paths[idx], path) {
|
||||
// true if attr.name == event.name => Some(attr),
|
||||
// _ => None,
|
||||
// }
|
||||
// }),
|
||||
// );
|
||||
|
||||
index = parent.parent;
|
||||
}
|
||||
// index = parent.parent;
|
||||
// }
|
||||
|
||||
for listener in listeners {
|
||||
if let AttributeValue::Listener(listener) = &listener.value {
|
||||
(listener.borrow_mut())(&event.event)
|
||||
(listener.borrow_mut())(event)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ use wry::{
|
|||
|
||||
pub(super) struct DesktopController {
|
||||
pub(super) webviews: HashMap<WindowId, WebView>,
|
||||
pub(super) sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
|
||||
pub(super) pending_edits: Arc<Mutex<Vec<String>>>,
|
||||
pub(super) quit_app_on_close: bool,
|
||||
pub(super) is_ready: Arc<AtomicBool>,
|
||||
|
@ -29,10 +28,8 @@ impl DesktopController {
|
|||
proxy: EventLoopProxy<UserWindowEvent>,
|
||||
) -> Self {
|
||||
let edit_queue = Arc::new(Mutex::new(Vec::new()));
|
||||
let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
|
||||
|
||||
let pending_edits = edit_queue.clone();
|
||||
let return_sender = sender.clone();
|
||||
let desktop_context_proxy = proxy.clone();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
|
@ -43,49 +40,54 @@ impl DesktopController {
|
|||
.unwrap();
|
||||
|
||||
runtime.block_on(async move {
|
||||
let mut dom =
|
||||
VirtualDom::new_with_props_and_scheduler(root, props, (sender, receiver));
|
||||
let mut dom = VirtualDom::new_with_props(root, props);
|
||||
|
||||
let window_context = DesktopContext::new(desktop_context_proxy);
|
||||
|
||||
dom.base_scope().provide_context(window_context);
|
||||
|
||||
todo!()
|
||||
|
||||
// // allow other proccesses to send the new rsx text to the @dioxusin ipc channel and recieve erros on the @dioxusout channel
|
||||
// #[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
// crate::hot_reload::init(&dom);
|
||||
|
||||
// let edits = dom.rebuild();
|
||||
let edits = dom.rebuild();
|
||||
let mut queue = edit_queue.lock().unwrap();
|
||||
|
||||
// edit_queue
|
||||
// .lock()
|
||||
// .unwrap()
|
||||
// .push(serde_json::to_string(&edits.edits).unwrap());
|
||||
queue.push(serde_json::to_string(&edits.template_mutations).unwrap());
|
||||
queue.push(serde_json::to_string(&edits.edits).unwrap());
|
||||
|
||||
// // Make sure the window is ready for any new updates
|
||||
// proxy.send_event(UserWindowEvent::Update).unwrap();
|
||||
// Make sure the window is ready for any new updates
|
||||
proxy.send_event(UserWindowEvent::Update).unwrap();
|
||||
|
||||
// loop {
|
||||
// dom.wait_for_work().await;
|
||||
loop {
|
||||
// todo: add the channel of the event loop in
|
||||
tokio::select! {
|
||||
_ = dom.wait_for_work() => {}
|
||||
}
|
||||
|
||||
// let muts = dom.work_with_deadline(|| false);
|
||||
let muts = dom
|
||||
.render_with_deadline(tokio::time::sleep(
|
||||
tokio::time::Duration::from_millis(100),
|
||||
))
|
||||
.await;
|
||||
|
||||
// for edit in muts {
|
||||
// edit_queue
|
||||
// .lock()
|
||||
// .unwrap()
|
||||
// .push(serde_json::to_string(&edit.edits).unwrap());
|
||||
// }
|
||||
let mut queue = edit_queue.lock().unwrap();
|
||||
|
||||
// let _ = proxy.send_event(UserWindowEvent::Update);
|
||||
// }
|
||||
for edit in muts.template_mutations {
|
||||
queue.push(serde_json::to_string(&edit).unwrap());
|
||||
}
|
||||
|
||||
for edit in muts.edits {
|
||||
queue.push(serde_json::to_string(&edit).unwrap());
|
||||
}
|
||||
|
||||
let _ = proxy.send_event(UserWindowEvent::Update);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
Self {
|
||||
pending_edits,
|
||||
sender: return_sender,
|
||||
webviews: HashMap::new(),
|
||||
is_ready: Arc::new(AtomicBool::new(false)),
|
||||
quit_app_on_close: true,
|
||||
|
|
|
@ -4,9 +4,8 @@ use std::any::Any;
|
|||
use std::sync::Arc;
|
||||
|
||||
use dioxus_core::ElementId;
|
||||
use dioxus_core::{EventPriority, UserEvent};
|
||||
use dioxus_html::event_bubbles;
|
||||
use dioxus_html::on::*;
|
||||
use dioxus_core::EventPriority;
|
||||
use dioxus_html::events::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
|
@ -41,181 +40,3 @@ struct ImEvent {
|
|||
mounted_dom_id: ElementId,
|
||||
contents: serde_json::Value,
|
||||
}
|
||||
|
||||
pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
|
||||
let ImEvent {
|
||||
event,
|
||||
mounted_dom_id,
|
||||
contents,
|
||||
} = serde_json::from_value(val).unwrap();
|
||||
|
||||
let mounted_dom_id = Some(mounted_dom_id);
|
||||
let name = event_name_from_type(&event);
|
||||
|
||||
let event = make_synthetic_event(&event, contents);
|
||||
|
||||
UserEvent {
|
||||
name,
|
||||
priority: EventPriority::Low,
|
||||
scope_id: None,
|
||||
element: mounted_dom_id,
|
||||
bubbles: event_bubbles(name),
|
||||
data: event,
|
||||
}
|
||||
}
|
||||
|
||||
fn make_synthetic_event(name: &str, val: serde_json::Value) -> Arc<dyn Any + Send + Sync> {
|
||||
match name {
|
||||
"copy" | "cut" | "paste" => {
|
||||
//
|
||||
Arc::new(ClipboardData {})
|
||||
}
|
||||
"compositionend" | "compositionstart" | "compositionupdate" => {
|
||||
Arc::new(serde_json::from_value::<CompositionData>(val).unwrap())
|
||||
}
|
||||
"keydown" | "keypress" | "keyup" => {
|
||||
let evt = serde_json::from_value::<KeyboardData>(val).unwrap();
|
||||
Arc::new(evt)
|
||||
}
|
||||
"focus" | "blur" | "focusout" | "focusin" => {
|
||||
//
|
||||
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::<FormData>(val).unwrap())
|
||||
}
|
||||
|
||||
"click" | "contextmenu" | "dblclick" | "doubleclick" | "drag" | "dragend" | "dragenter"
|
||||
| "dragexit" | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown"
|
||||
| "mouseenter" | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
|
||||
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::<PointerData>(val).unwrap())
|
||||
}
|
||||
"select" => {
|
||||
//
|
||||
Arc::new(serde_json::from_value::<SelectionData>(val).unwrap())
|
||||
}
|
||||
|
||||
"touchcancel" | "touchend" | "touchmove" | "touchstart" => {
|
||||
Arc::new(serde_json::from_value::<TouchData>(val).unwrap())
|
||||
}
|
||||
|
||||
"scroll" => Arc::new(ScrollData {}),
|
||||
|
||||
"wheel" => Arc::new(serde_json::from_value::<WheelData>(val).unwrap()),
|
||||
|
||||
"animationstart" | "animationend" | "animationiteration" => {
|
||||
Arc::new(serde_json::from_value::<AnimationData>(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(MediaData {})
|
||||
}
|
||||
|
||||
"toggle" => Arc::new(ToggleData {}),
|
||||
|
||||
_ => Arc::new(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn event_name_from_type(typ: &str) -> &'static str {
|
||||
match typ {
|
||||
"copy" => "copy",
|
||||
"cut" => "cut",
|
||||
"paste" => "paste",
|
||||
"compositionend" => "compositionend",
|
||||
"compositionstart" => "compositionstart",
|
||||
"compositionupdate" => "compositionupdate",
|
||||
"keydown" => "keydown",
|
||||
"keypress" => "keypress",
|
||||
"keyup" => "keyup",
|
||||
"focus" => "focus",
|
||||
"focusout" => "focusout",
|
||||
"focusin" => "focusin",
|
||||
"blur" => "blur",
|
||||
"change" => "change",
|
||||
"input" => "input",
|
||||
"invalid" => "invalid",
|
||||
"reset" => "reset",
|
||||
"submit" => "submit",
|
||||
"click" => "click",
|
||||
"contextmenu" => "contextmenu",
|
||||
"doubleclick" => "doubleclick",
|
||||
"dblclick" => "dblclick",
|
||||
"drag" => "drag",
|
||||
"dragend" => "dragend",
|
||||
"dragenter" => "dragenter",
|
||||
"dragexit" => "dragexit",
|
||||
"dragleave" => "dragleave",
|
||||
"dragover" => "dragover",
|
||||
"dragstart" => "dragstart",
|
||||
"drop" => "drop",
|
||||
"mousedown" => "mousedown",
|
||||
"mouseenter" => "mouseenter",
|
||||
"mouseleave" => "mouseleave",
|
||||
"mousemove" => "mousemove",
|
||||
"mouseout" => "mouseout",
|
||||
"mouseover" => "mouseover",
|
||||
"mouseup" => "mouseup",
|
||||
"pointerdown" => "pointerdown",
|
||||
"pointermove" => "pointermove",
|
||||
"pointerup" => "pointerup",
|
||||
"pointercancel" => "pointercancel",
|
||||
"gotpointercapture" => "gotpointercapture",
|
||||
"lostpointercapture" => "lostpointercapture",
|
||||
"pointerenter" => "pointerenter",
|
||||
"pointerleave" => "pointerleave",
|
||||
"pointerover" => "pointerover",
|
||||
"pointerout" => "pointerout",
|
||||
"select" => "select",
|
||||
"touchcancel" => "touchcancel",
|
||||
"touchend" => "touchend",
|
||||
"touchmove" => "touchmove",
|
||||
"touchstart" => "touchstart",
|
||||
"scroll" => "scroll",
|
||||
"wheel" => "wheel",
|
||||
"animationstart" => "animationstart",
|
||||
"animationend" => "animationend",
|
||||
"animationiteration" => "animationiteration",
|
||||
"transitionend" => "transitionend",
|
||||
"abort" => "abort",
|
||||
"canplay" => "canplay",
|
||||
"canplaythrough" => "canplaythrough",
|
||||
"durationchange" => "durationchange",
|
||||
"emptied" => "emptied",
|
||||
"encrypted" => "encrypted",
|
||||
"ended" => "ended",
|
||||
"error" => "error",
|
||||
"loadeddata" => "loadeddata",
|
||||
"loadedmetadata" => "loadedmetadata",
|
||||
"loadstart" => "loadstart",
|
||||
"pause" => "pause",
|
||||
"play" => "play",
|
||||
"playing" => "playing",
|
||||
"progress" => "progress",
|
||||
"ratechange" => "ratechange",
|
||||
"seeked" => "seeked",
|
||||
"seeking" => "seeking",
|
||||
"stalled" => "stalled",
|
||||
"suspend" => "suspend",
|
||||
"timeupdate" => "timeupdate",
|
||||
"volumechange" => "volumechange",
|
||||
"waiting" => "waiting",
|
||||
"toggle" => "toggle",
|
||||
a => {
|
||||
panic!("unsupported event type {:?}", a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use dioxus_core::{SchedulerMsg, VirtualDom};
|
||||
use dioxus_core::VirtualDom;
|
||||
use interprocess::local_socket::{LocalSocketListener, LocalSocketStream};
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::time::Duration;
|
||||
|
@ -24,8 +24,6 @@ pub(crate) fn init(dom: &VirtualDom) {
|
|||
}
|
||||
});
|
||||
|
||||
let mut channel = dom.get_scheduler_channel();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
loop {
|
||||
if let Some(conn) = &mut *latest_in_connection.lock().unwrap() {
|
||||
|
|
|
@ -17,7 +17,6 @@ pub use desktop_context::{use_eval, use_window, DesktopContext};
|
|||
pub use wry;
|
||||
pub use wry::application as tao;
|
||||
|
||||
use crate::events::trigger_from_serialized;
|
||||
pub use cfg::Config;
|
||||
use controller::DesktopController;
|
||||
use dioxus_core::*;
|
||||
|
@ -126,7 +125,8 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
|
|||
let window = builder.build(event_loop).unwrap();
|
||||
let window_id = window.id();
|
||||
|
||||
let (is_ready, sender) = (desktop.is_ready.clone(), desktop.sender.clone());
|
||||
let (is_ready, _) = (desktop.is_ready.clone(), ());
|
||||
// let (is_ready, sender) = (desktop.is_ready.clone(), desktop.sender.clone());
|
||||
|
||||
let proxy = proxy.clone();
|
||||
|
||||
|
@ -144,9 +144,10 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
|
|||
parse_ipc_message(&payload)
|
||||
.map(|message| match message.method() {
|
||||
"user_event" => {
|
||||
let event = trigger_from_serialized(message.params());
|
||||
log::trace!("User event: {:?}", event);
|
||||
sender.unbounded_send(SchedulerMsg::Event(event)).unwrap();
|
||||
println!("user event!");
|
||||
// let event = trigger_from_serialized(message.params());
|
||||
// log::trace!("User event: {:?}", event);
|
||||
// sender.unbounded_send(SchedulerMsg::Event(event)).unwrap();
|
||||
}
|
||||
"initialize" => {
|
||||
is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
|
|
|
@ -7,7 +7,7 @@ pub use dioxus_hooks as hooks;
|
|||
|
||||
pub mod events {
|
||||
#[cfg(feature = "html")]
|
||||
pub use dioxus_html::{on::*, KeyCode};
|
||||
pub use dioxus_html::{AnimationEvent, FormEvent, KeyCode, MouseEvent};
|
||||
}
|
||||
|
||||
#[cfg(feature = "html")]
|
||||
|
|
24
packages/dioxus/tests/boolattrs.rs
Normal file
24
packages/dioxus/tests/boolattrs.rs
Normal file
|
@ -0,0 +1,24 @@
|
|||
fn component(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div { hidden: false }
|
||||
})
|
||||
}
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn bool_test() {
|
||||
let mut app = VirtualDom::new(component);
|
||||
let edits = app.rebuild();
|
||||
|
||||
use dioxus_core::{ElementId, Mutation::*};
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
vec![
|
||||
LoadTemplate { name: "packages/dioxus/tests/boolattrs.rs:2:15:66", index: 0 },
|
||||
AssignId { path: &[], id: ElementId(2,) },
|
||||
SetBoolAttribute { name: "hidden", value: false, id: ElementId(2,) },
|
||||
AppendChildren { m: 1 },
|
||||
]
|
||||
)
|
||||
}
|
|
@ -18,6 +18,7 @@ wasm-bindgen = { version = "0.2.79", optional = true }
|
|||
euclid = "0.22.7"
|
||||
enumset = "1.0.11"
|
||||
keyboard-types = "0.6.2"
|
||||
async-trait = "0.1.58"
|
||||
|
||||
[dependencies.web-sys]
|
||||
optional = true
|
||||
|
|
1587
packages/html/src/events.old.rs
Normal file
1587
packages/html/src/events.old.rs
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
7
packages/html/src/events/animation.rs
Normal file
7
packages/html/src/events/animation.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AnimationEvent {
|
||||
pub animation_name: String,
|
||||
pub pseudo_element: String,
|
||||
pub elapsed_time: f32,
|
||||
}
|
0
packages/html/src/events/composition.rs
Normal file
0
packages/html/src/events/composition.rs
Normal file
0
packages/html/src/events/drag.rs
Normal file
0
packages/html/src/events/drag.rs
Normal file
0
packages/html/src/events/focus.rs
Normal file
0
packages/html/src/events/focus.rs
Normal file
44
packages/html/src/events/form.rs
Normal file
44
packages/html/src/events/form.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
/* DOMEvent: Send + SyncTarget relatedTarget */
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Clone)]
|
||||
pub struct FormEvent {
|
||||
pub value: String,
|
||||
|
||||
pub values: HashMap<String, String>,
|
||||
|
||||
#[cfg_attr(feature = "serialize", serde(skip))]
|
||||
pub files: Option<Arc<dyn FileEngine>>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
pub trait FileEngine {
|
||||
// get a list of file names
|
||||
fn files(&self) -> Vec<String>;
|
||||
|
||||
// read a file to bytes
|
||||
async fn read_file(&self, file: &str) -> Option<Vec<u8>>;
|
||||
|
||||
// read a file to string
|
||||
async fn read_file_to_string(&self, file: &str) -> Option<String>;
|
||||
}
|
||||
|
||||
impl_event! {
|
||||
FormEvent;
|
||||
|
||||
/// onchange
|
||||
onchange
|
||||
|
||||
/// oninput handler
|
||||
oninput
|
||||
|
||||
/// oninvalid
|
||||
oninvalid
|
||||
|
||||
/// onreset
|
||||
onreset
|
||||
|
||||
/// onsubmit
|
||||
onsubmit
|
||||
}
|
0
packages/html/src/events/image.rs
Normal file
0
packages/html/src/events/image.rs
Normal file
0
packages/html/src/events/keyboard.rs
Normal file
0
packages/html/src/events/keyboard.rs
Normal file
370
packages/html/src/events/keys.rs
Normal file
370
packages/html/src/events/keys.rs
Normal file
|
@ -0,0 +1,370 @@
|
|||
#[cfg_attr(
|
||||
feature = "serialize",
|
||||
derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr)
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum KeyCode {
|
||||
// That key has no keycode, = 0
|
||||
// break, = 3
|
||||
// backspace / delete, = 8
|
||||
// tab, = 9
|
||||
// clear, = 12
|
||||
// enter, = 13
|
||||
// shift, = 16
|
||||
// ctrl, = 17
|
||||
// alt, = 18
|
||||
// pause/break, = 19
|
||||
// caps lock, = 20
|
||||
// hangul, = 21
|
||||
// hanja, = 25
|
||||
// escape, = 27
|
||||
// conversion, = 28
|
||||
// non-conversion, = 29
|
||||
// spacebar, = 32
|
||||
// page up, = 33
|
||||
// page down, = 34
|
||||
// end, = 35
|
||||
// home, = 36
|
||||
// left arrow, = 37
|
||||
// up arrow, = 38
|
||||
// right arrow, = 39
|
||||
// down arrow, = 40
|
||||
// select, = 41
|
||||
// print, = 42
|
||||
// execute, = 43
|
||||
// Print Screen, = 44
|
||||
// insert, = 45
|
||||
// delete, = 46
|
||||
// help, = 47
|
||||
// 0, = 48
|
||||
// 1, = 49
|
||||
// 2, = 50
|
||||
// 3, = 51
|
||||
// 4, = 52
|
||||
// 5, = 53
|
||||
// 6, = 54
|
||||
// 7, = 55
|
||||
// 8, = 56
|
||||
// 9, = 57
|
||||
// :, = 58
|
||||
// semicolon (firefox), equals, = 59
|
||||
// <, = 60
|
||||
// equals (firefox), = 61
|
||||
// ß, = 63
|
||||
// @ (firefox), = 64
|
||||
// a, = 65
|
||||
// b, = 66
|
||||
// c, = 67
|
||||
// d, = 68
|
||||
// e, = 69
|
||||
// f, = 70
|
||||
// g, = 71
|
||||
// h, = 72
|
||||
// i, = 73
|
||||
// j, = 74
|
||||
// k, = 75
|
||||
// l, = 76
|
||||
// m, = 77
|
||||
// n, = 78
|
||||
// o, = 79
|
||||
// p, = 80
|
||||
// q, = 81
|
||||
// r, = 82
|
||||
// s, = 83
|
||||
// t, = 84
|
||||
// u, = 85
|
||||
// v, = 86
|
||||
// w, = 87
|
||||
// x, = 88
|
||||
// y, = 89
|
||||
// z, = 90
|
||||
// Windows Key / Left ⌘ / Chromebook Search key, = 91
|
||||
// right window key, = 92
|
||||
// Windows Menu / Right ⌘, = 93
|
||||
// sleep, = 95
|
||||
// numpad 0, = 96
|
||||
// numpad 1, = 97
|
||||
// numpad 2, = 98
|
||||
// numpad 3, = 99
|
||||
// numpad 4, = 100
|
||||
// numpad 5, = 101
|
||||
// numpad 6, = 102
|
||||
// numpad 7, = 103
|
||||
// numpad 8, = 104
|
||||
// numpad 9, = 105
|
||||
// multiply, = 106
|
||||
// add, = 107
|
||||
// numpad period (firefox), = 108
|
||||
// subtract, = 109
|
||||
// decimal point, = 110
|
||||
// divide, = 111
|
||||
// f1, = 112
|
||||
// f2, = 113
|
||||
// f3, = 114
|
||||
// f4, = 115
|
||||
// f5, = 116
|
||||
// f6, = 117
|
||||
// f7, = 118
|
||||
// f8, = 119
|
||||
// f9, = 120
|
||||
// f10, = 121
|
||||
// f11, = 122
|
||||
// f12, = 123
|
||||
// f13, = 124
|
||||
// f14, = 125
|
||||
// f15, = 126
|
||||
// f16, = 127
|
||||
// f17, = 128
|
||||
// f18, = 129
|
||||
// f19, = 130
|
||||
// f20, = 131
|
||||
// f21, = 132
|
||||
// f22, = 133
|
||||
// f23, = 134
|
||||
// f24, = 135
|
||||
// f25, = 136
|
||||
// f26, = 137
|
||||
// f27, = 138
|
||||
// f28, = 139
|
||||
// f29, = 140
|
||||
// f30, = 141
|
||||
// f31, = 142
|
||||
// f32, = 143
|
||||
// num lock, = 144
|
||||
// scroll lock, = 145
|
||||
// airplane mode, = 151
|
||||
// ^, = 160
|
||||
// !, = 161
|
||||
// ؛ (arabic semicolon), = 162
|
||||
// #, = 163
|
||||
// $, = 164
|
||||
// ù, = 165
|
||||
// page backward, = 166
|
||||
// page forward, = 167
|
||||
// refresh, = 168
|
||||
// closing paren (AZERTY), = 169
|
||||
// *, = 170
|
||||
// ~ + * key, = 171
|
||||
// home key, = 172
|
||||
// minus (firefox), mute/unmute, = 173
|
||||
// decrease volume level, = 174
|
||||
// increase volume level, = 175
|
||||
// next, = 176
|
||||
// previous, = 177
|
||||
// stop, = 178
|
||||
// play/pause, = 179
|
||||
// e-mail, = 180
|
||||
// mute/unmute (firefox), = 181
|
||||
// decrease volume level (firefox), = 182
|
||||
// increase volume level (firefox), = 183
|
||||
// semi-colon / ñ, = 186
|
||||
// equal sign, = 187
|
||||
// comma, = 188
|
||||
// dash, = 189
|
||||
// period, = 190
|
||||
// forward slash / ç, = 191
|
||||
// grave accent / ñ / æ / ö, = 192
|
||||
// ?, / or °, = 193
|
||||
// numpad period (chrome), = 194
|
||||
// open bracket, = 219
|
||||
// back slash, = 220
|
||||
// close bracket / å, = 221
|
||||
// single quote / ø / ä, = 222
|
||||
// `, = 223
|
||||
// left or right ⌘ key (firefox), = 224
|
||||
// altgr, = 225
|
||||
// < /git >, left back slash, = 226
|
||||
// GNOME Compose Key, = 230
|
||||
// ç, = 231
|
||||
// XF86Forward, = 233
|
||||
// XF86Back, = 234
|
||||
// non-conversion, = 235
|
||||
// alphanumeric, = 240
|
||||
// hiragana/katakana, = 242
|
||||
// half-width/full-width, = 243
|
||||
// kanji, = 244
|
||||
// unlock trackpad (Chrome/Edge), = 251
|
||||
// toggle touchpad, = 255
|
||||
NA = 0,
|
||||
Break = 3,
|
||||
Backspace = 8,
|
||||
Tab = 9,
|
||||
Clear = 12,
|
||||
Enter = 13,
|
||||
Shift = 16,
|
||||
Ctrl = 17,
|
||||
Alt = 18,
|
||||
Pause = 19,
|
||||
CapsLock = 20,
|
||||
// hangul, = 21
|
||||
// hanja, = 25
|
||||
Escape = 27,
|
||||
// conversion, = 28
|
||||
// non-conversion, = 29
|
||||
Space = 32,
|
||||
PageUp = 33,
|
||||
PageDown = 34,
|
||||
End = 35,
|
||||
Home = 36,
|
||||
LeftArrow = 37,
|
||||
UpArrow = 38,
|
||||
RightArrow = 39,
|
||||
DownArrow = 40,
|
||||
// select, = 41
|
||||
// print, = 42
|
||||
// execute, = 43
|
||||
// Print Screen, = 44
|
||||
Insert = 45,
|
||||
Delete = 46,
|
||||
// help, = 47
|
||||
Num0 = 48,
|
||||
Num1 = 49,
|
||||
Num2 = 50,
|
||||
Num3 = 51,
|
||||
Num4 = 52,
|
||||
Num5 = 53,
|
||||
Num6 = 54,
|
||||
Num7 = 55,
|
||||
Num8 = 56,
|
||||
Num9 = 57,
|
||||
// :, = 58
|
||||
// semicolon (firefox), equals, = 59
|
||||
// <, = 60
|
||||
// equals (firefox), = 61
|
||||
// ß, = 63
|
||||
// @ (firefox), = 64
|
||||
A = 65,
|
||||
B = 66,
|
||||
C = 67,
|
||||
D = 68,
|
||||
E = 69,
|
||||
F = 70,
|
||||
G = 71,
|
||||
H = 72,
|
||||
I = 73,
|
||||
J = 74,
|
||||
K = 75,
|
||||
L = 76,
|
||||
M = 77,
|
||||
N = 78,
|
||||
O = 79,
|
||||
P = 80,
|
||||
Q = 81,
|
||||
R = 82,
|
||||
S = 83,
|
||||
T = 84,
|
||||
U = 85,
|
||||
V = 86,
|
||||
W = 87,
|
||||
X = 88,
|
||||
Y = 89,
|
||||
Z = 90,
|
||||
LeftWindow = 91,
|
||||
RightWindow = 92,
|
||||
SelectKey = 93,
|
||||
Numpad0 = 96,
|
||||
Numpad1 = 97,
|
||||
Numpad2 = 98,
|
||||
Numpad3 = 99,
|
||||
Numpad4 = 100,
|
||||
Numpad5 = 101,
|
||||
Numpad6 = 102,
|
||||
Numpad7 = 103,
|
||||
Numpad8 = 104,
|
||||
Numpad9 = 105,
|
||||
Multiply = 106,
|
||||
Add = 107,
|
||||
Subtract = 109,
|
||||
DecimalPoint = 110,
|
||||
Divide = 111,
|
||||
F1 = 112,
|
||||
F2 = 113,
|
||||
F3 = 114,
|
||||
F4 = 115,
|
||||
F5 = 116,
|
||||
F6 = 117,
|
||||
F7 = 118,
|
||||
F8 = 119,
|
||||
F9 = 120,
|
||||
F10 = 121,
|
||||
F11 = 122,
|
||||
F12 = 123,
|
||||
// f13, = 124
|
||||
// f14, = 125
|
||||
// f15, = 126
|
||||
// f16, = 127
|
||||
// f17, = 128
|
||||
// f18, = 129
|
||||
// f19, = 130
|
||||
// f20, = 131
|
||||
// f21, = 132
|
||||
// f22, = 133
|
||||
// f23, = 134
|
||||
// f24, = 135
|
||||
// f25, = 136
|
||||
// f26, = 137
|
||||
// f27, = 138
|
||||
// f28, = 139
|
||||
// f29, = 140
|
||||
// f30, = 141
|
||||
// f31, = 142
|
||||
// f32, = 143
|
||||
NumLock = 144,
|
||||
ScrollLock = 145,
|
||||
// airplane mode, = 151
|
||||
// ^, = 160
|
||||
// !, = 161
|
||||
// ؛ (arabic semicolon), = 162
|
||||
// #, = 163
|
||||
// $, = 164
|
||||
// ù, = 165
|
||||
// page backward, = 166
|
||||
// page forward, = 167
|
||||
// refresh, = 168
|
||||
// closing paren (AZERTY), = 169
|
||||
// *, = 170
|
||||
// ~ + * key, = 171
|
||||
// home key, = 172
|
||||
// minus (firefox), mute/unmute, = 173
|
||||
// decrease volume level, = 174
|
||||
// increase volume level, = 175
|
||||
// next, = 176
|
||||
// previous, = 177
|
||||
// stop, = 178
|
||||
// play/pause, = 179
|
||||
// e-mail, = 180
|
||||
// mute/unmute (firefox), = 181
|
||||
// decrease volume level (firefox), = 182
|
||||
// increase volume level (firefox), = 183
|
||||
Semicolon = 186,
|
||||
EqualSign = 187,
|
||||
Comma = 188,
|
||||
Dash = 189,
|
||||
Period = 190,
|
||||
ForwardSlash = 191,
|
||||
GraveAccent = 192,
|
||||
// ?, / or °, = 193
|
||||
// numpad period (chrome), = 194
|
||||
OpenBracket = 219,
|
||||
BackSlash = 220,
|
||||
CloseBraket = 221,
|
||||
SingleQuote = 222,
|
||||
// `, = 223
|
||||
// left or right ⌘ key (firefox), = 224
|
||||
// altgr, = 225
|
||||
// < /git >, left back slash, = 226
|
||||
// GNOME Compose Key, = 230
|
||||
// ç, = 231
|
||||
// XF86Forward, = 233
|
||||
// XF86Back, = 234
|
||||
// non-conversion, = 235
|
||||
// alphanumeric, = 240
|
||||
// hiragana/katakana, = 242
|
||||
// half-width/full-width, = 243
|
||||
// kanji, = 244
|
||||
// unlock trackpad (Chrome/Edge), = 251
|
||||
// toggle touchpad, = 255
|
||||
#[cfg_attr(feature = "serialize", serde(other))]
|
||||
Unknown,
|
||||
}
|
0
packages/html/src/events/media.rs
Normal file
0
packages/html/src/events/media.rs
Normal file
302
packages/html/src/events/mouse.rs
Normal file
302
packages/html/src/events/mouse.rs
Normal file
|
@ -0,0 +1,302 @@
|
|||
use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint};
|
||||
use crate::input_data::{
|
||||
decode_mouse_button_set, encode_mouse_button_set, MouseButton, MouseButtonSet,
|
||||
};
|
||||
use dioxus_core::{Attribute, ScopeState};
|
||||
use keyboard_types::Modifiers;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
/// A synthetic event that wraps a web-style [`MouseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent)
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Clone)]
|
||||
/// Data associated with a mouse event
|
||||
///
|
||||
/// Do not use the deprecated fields; they may change or become private in the future.
|
||||
pub struct MouseEvent {
|
||||
/// True if the alt key was down when the mouse event was fired.
|
||||
#[deprecated(since = "0.3.0", note = "use modifiers() instead")]
|
||||
pub alt_key: bool,
|
||||
|
||||
/// The button number that was pressed (if applicable) when the mouse event was fired.
|
||||
#[deprecated(since = "0.3.0", note = "use trigger_button() instead")]
|
||||
pub button: i16,
|
||||
|
||||
/// Indicates which buttons are pressed on the mouse (or other input device) when a mouse event is triggered.
|
||||
///
|
||||
/// Each button that can be pressed is represented by a given number (see below). If more than one button is pressed, the button values are added together to produce a new number. For example, if the secondary (2) and auxiliary (4) buttons are pressed simultaneously, the value is 6 (i.e., 2 + 4).
|
||||
///
|
||||
/// - 1: Primary button (usually the left button)
|
||||
/// - 2: Secondary button (usually the right button)
|
||||
/// - 4: Auxiliary button (usually the mouse wheel button or middle button)
|
||||
/// - 8: 4th button (typically the "Browser Back" button)
|
||||
/// - 16 : 5th button (typically the "Browser Forward" button)
|
||||
#[deprecated(since = "0.3.0", note = "use held_buttons() instead")]
|
||||
pub buttons: u16,
|
||||
|
||||
/// The horizontal coordinate within the application's viewport at which the event occurred (as opposed to the coordinate within the page).
|
||||
///
|
||||
/// For example, clicking on the left edge of the viewport will always result in a mouse event with a clientX value of 0, regardless of whether the page is scrolled horizontally.
|
||||
#[deprecated(since = "0.3.0", note = "use client_coordinates() instead")]
|
||||
pub client_x: i32,
|
||||
|
||||
/// The vertical coordinate within the application's viewport at which the event occurred (as opposed to the coordinate within the page).
|
||||
///
|
||||
/// For example, clicking on the top edge of the viewport will always result in a mouse event with a clientY value of 0, regardless of whether the page is scrolled vertically.
|
||||
#[deprecated(since = "0.3.0", note = "use client_coordinates() instead")]
|
||||
pub client_y: i32,
|
||||
|
||||
/// True if the control key was down when the mouse event was fired.
|
||||
#[deprecated(since = "0.3.0", note = "use modifiers() instead")]
|
||||
pub ctrl_key: bool,
|
||||
|
||||
/// True if the meta key was down when the mouse event was fired.
|
||||
#[deprecated(since = "0.3.0", note = "use modifiers() instead")]
|
||||
pub meta_key: bool,
|
||||
|
||||
/// The offset in the X coordinate of the mouse pointer between that event and the padding edge of the target node.
|
||||
#[deprecated(since = "0.3.0", note = "use element_coordinates() instead")]
|
||||
pub offset_x: i32,
|
||||
|
||||
/// The offset in the Y coordinate of the mouse pointer between that event and the padding edge of the target node.
|
||||
#[deprecated(since = "0.3.0", note = "use element_coordinates() instead")]
|
||||
pub offset_y: i32,
|
||||
|
||||
/// The X (horizontal) coordinate (in pixels) of the mouse, relative to the left edge of the entire document. This includes any portion of the document not currently visible.
|
||||
///
|
||||
/// Being based on the edge of the document as it is, this property takes into account any horizontal scrolling of the page. For example, if the page is scrolled such that 200 pixels of the left side of the document are scrolled out of view, and the mouse is clicked 100 pixels inward from the left edge of the view, the value returned by pageX will be 300.
|
||||
#[deprecated(since = "0.3.0", note = "use page_coordinates() instead")]
|
||||
pub page_x: i32,
|
||||
|
||||
/// The Y (vertical) coordinate in pixels of the event relative to the whole document.
|
||||
///
|
||||
/// See `page_x`.
|
||||
#[deprecated(since = "0.3.0", note = "use page_coordinates() instead")]
|
||||
pub page_y: i32,
|
||||
|
||||
/// The X coordinate of the mouse pointer in global (screen) coordinates.
|
||||
#[deprecated(since = "0.3.0", note = "use screen_coordinates() instead")]
|
||||
pub screen_x: i32,
|
||||
|
||||
/// The Y coordinate of the mouse pointer in global (screen) coordinates.
|
||||
#[deprecated(since = "0.3.0", note = "use screen_coordinates() instead")]
|
||||
pub screen_y: i32,
|
||||
|
||||
/// True if the shift key was down when the mouse event was fired.
|
||||
#[deprecated(since = "0.3.0", note = "use modifiers() instead")]
|
||||
pub shift_key: bool,
|
||||
}
|
||||
|
||||
impl MouseEvent {
|
||||
/// Construct MouseData with the specified properties
|
||||
///
|
||||
/// Note: the current implementation truncates coordinates. In the future, when we change the internal representation, it may also support a fractional part.
|
||||
pub fn new(
|
||||
coordinates: Coordinates,
|
||||
trigger_button: Option<MouseButton>,
|
||||
held_buttons: MouseButtonSet,
|
||||
modifiers: Modifiers,
|
||||
) -> Self {
|
||||
let alt_key = modifiers.contains(Modifiers::ALT);
|
||||
let ctrl_key = modifiers.contains(Modifiers::CONTROL);
|
||||
let meta_key = modifiers.contains(Modifiers::META);
|
||||
let shift_key = modifiers.contains(Modifiers::SHIFT);
|
||||
|
||||
let [client_x, client_y]: [i32; 2] = coordinates.client().cast().into();
|
||||
let [offset_x, offset_y]: [i32; 2] = coordinates.element().cast().into();
|
||||
let [page_x, page_y]: [i32; 2] = coordinates.page().cast().into();
|
||||
let [screen_x, screen_y]: [i32; 2] = coordinates.screen().cast().into();
|
||||
|
||||
#[allow(deprecated)]
|
||||
Self {
|
||||
alt_key,
|
||||
ctrl_key,
|
||||
meta_key,
|
||||
shift_key,
|
||||
|
||||
button: trigger_button.map_or(0, |b| b.into_web_code()),
|
||||
buttons: encode_mouse_button_set(held_buttons),
|
||||
|
||||
client_x,
|
||||
client_y,
|
||||
offset_x,
|
||||
offset_y,
|
||||
page_x,
|
||||
page_y,
|
||||
screen_x,
|
||||
screen_y,
|
||||
}
|
||||
}
|
||||
|
||||
/// The event's coordinates relative to the application's viewport (as opposed to the coordinate within the page).
|
||||
///
|
||||
/// For example, clicking in the top left corner of the viewport will always result in a mouse event with client coordinates (0., 0.), regardless of whether the page is scrolled horizontally.
|
||||
pub fn client_coordinates(&self) -> ClientPoint {
|
||||
#[allow(deprecated)]
|
||||
ClientPoint::new(self.client_x.into(), self.client_y.into())
|
||||
}
|
||||
|
||||
/// The event's coordinates relative to the padding edge of the target element
|
||||
///
|
||||
/// For example, clicking in the top left corner of an element will result in element coordinates (0., 0.)
|
||||
pub fn element_coordinates(&self) -> ElementPoint {
|
||||
#[allow(deprecated)]
|
||||
ElementPoint::new(self.offset_x.into(), self.offset_y.into())
|
||||
}
|
||||
|
||||
/// The event's coordinates relative to the entire document. This includes any portion of the document not currently visible.
|
||||
///
|
||||
/// For example, if the page is scrolled 200 pixels to the right and 300 pixels down, clicking in the top left corner of the viewport would result in page coordinates (200., 300.)
|
||||
pub fn page_coordinates(&self) -> PagePoint {
|
||||
#[allow(deprecated)]
|
||||
PagePoint::new(self.page_x.into(), self.page_y.into())
|
||||
}
|
||||
|
||||
/// The event's coordinates relative to the entire screen. This takes into account the window's offset.
|
||||
pub fn screen_coordinates(&self) -> ScreenPoint {
|
||||
#[allow(deprecated)]
|
||||
ScreenPoint::new(self.screen_x.into(), self.screen_y.into())
|
||||
}
|
||||
|
||||
pub fn coordinates(&self) -> Coordinates {
|
||||
Coordinates::new(
|
||||
self.screen_coordinates(),
|
||||
self.client_coordinates(),
|
||||
self.element_coordinates(),
|
||||
self.page_coordinates(),
|
||||
)
|
||||
}
|
||||
|
||||
/// The set of modifier keys which were pressed when the event occurred
|
||||
pub fn modifiers(&self) -> Modifiers {
|
||||
let mut modifiers = Modifiers::empty();
|
||||
|
||||
#[allow(deprecated)]
|
||||
{
|
||||
if self.alt_key {
|
||||
modifiers.insert(Modifiers::ALT);
|
||||
}
|
||||
if self.ctrl_key {
|
||||
modifiers.insert(Modifiers::CONTROL);
|
||||
}
|
||||
if self.meta_key {
|
||||
modifiers.insert(Modifiers::META);
|
||||
}
|
||||
if self.shift_key {
|
||||
modifiers.insert(Modifiers::SHIFT);
|
||||
}
|
||||
}
|
||||
|
||||
modifiers
|
||||
}
|
||||
|
||||
/// The set of mouse buttons which were held when the event occurred.
|
||||
pub fn held_buttons(&self) -> MouseButtonSet {
|
||||
#[allow(deprecated)]
|
||||
decode_mouse_button_set(self.buttons)
|
||||
}
|
||||
|
||||
/// The mouse button that triggered the event
|
||||
///
|
||||
// todo the following is kind of bad; should we just return None when the trigger_button is unreliable (and frankly irrelevant)? i guess we would need the event_type here
|
||||
/// This is only guaranteed to indicate which button was pressed during events caused by pressing or releasing a button. As such, it is not reliable for events such as mouseenter, mouseleave, mouseover, mouseout, or mousemove. For example, a value of MouseButton::Primary may also indicate that no button was pressed.
|
||||
pub fn trigger_button(&self) -> Option<MouseButton> {
|
||||
#[allow(deprecated)]
|
||||
Some(MouseButton::from_web_code(self.button))
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for MouseEvent {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("MouseData")
|
||||
.field("coordinates", &self.coordinates())
|
||||
.field("modifiers", &self.modifiers())
|
||||
.field("held_buttons", &self.held_buttons())
|
||||
.field("trigger_button", &self.trigger_button())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl_event! {
|
||||
MouseEvent;
|
||||
|
||||
/// Execute a callback when a button is clicked.
|
||||
///
|
||||
/// ## Description
|
||||
///
|
||||
/// An element receives a click event when a pointing device button (such as a mouse's primary mouse button)
|
||||
/// is both pressed and released while the pointer is located inside the element.
|
||||
///
|
||||
/// - Bubbles: Yes
|
||||
/// - Cancelable: Yes
|
||||
/// - 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.
|
||||
/// `click` fires after both the `mousedown` and `mouseup` events have fired, in that order.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```rust, ignore
|
||||
/// rsx!( button { "click me", onclick: move |_| log::info!("Clicked!`") } )
|
||||
/// ```
|
||||
///
|
||||
/// ## Reference
|
||||
/// - <https://www.w3schools.com/tags/ev_onclick.asp>
|
||||
/// - <https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event>
|
||||
onclick
|
||||
|
||||
/// oncontextmenu
|
||||
oncontextmenu
|
||||
|
||||
/// ondoubleclick
|
||||
ondoubleclick
|
||||
|
||||
/// ondoubleclick
|
||||
ondblclick
|
||||
|
||||
/// ondrag
|
||||
ondrag
|
||||
|
||||
/// ondragend
|
||||
ondragend
|
||||
|
||||
/// ondragenter
|
||||
ondragenter
|
||||
|
||||
/// ondragexit
|
||||
ondragexit
|
||||
|
||||
/// ondragleave
|
||||
ondragleave
|
||||
|
||||
/// ondragover
|
||||
ondragover
|
||||
|
||||
/// ondragstart
|
||||
ondragstart
|
||||
|
||||
/// ondrop
|
||||
ondrop
|
||||
|
||||
/// onmousedown
|
||||
onmousedown
|
||||
|
||||
/// onmouseenter
|
||||
onmouseenter
|
||||
|
||||
/// onmouseleave
|
||||
onmouseleave
|
||||
|
||||
/// onmousemove
|
||||
onmousemove
|
||||
|
||||
/// onmouseout
|
||||
onmouseout
|
||||
|
||||
/// onmouseover
|
||||
///
|
||||
/// Triggered when the users's mouse hovers over an element.
|
||||
onmouseover
|
||||
|
||||
/// onmouseup
|
||||
onmouseup
|
||||
}
|
0
packages/html/src/events/pointer.rs
Normal file
0
packages/html/src/events/pointer.rs
Normal file
0
packages/html/src/events/selection.rs
Normal file
0
packages/html/src/events/selection.rs
Normal file
0
packages/html/src/events/toggle.rs
Normal file
0
packages/html/src/events/toggle.rs
Normal file
0
packages/html/src/events/touch.rs
Normal file
0
packages/html/src/events/touch.rs
Normal file
0
packages/html/src/events/transition.rs
Normal file
0
packages/html/src/events/transition.rs
Normal file
0
packages/html/src/events/wheel.rs
Normal file
0
packages/html/src/events/wheel.rs
Normal file
|
@ -14,12 +14,12 @@
|
|||
//! Currently, we don't validate for structures, but do validate attributes.
|
||||
|
||||
mod elements;
|
||||
mod events;
|
||||
pub mod events;
|
||||
pub mod geometry;
|
||||
mod global_attributes;
|
||||
pub mod input_data;
|
||||
#[cfg(feature = "wasm-bind")]
|
||||
mod web_sys_bind;
|
||||
// #[cfg(feature = "wasm-bind")]
|
||||
// mod web_sys_bind;
|
||||
|
||||
pub use elements::*;
|
||||
pub use events::*;
|
||||
|
|
|
@ -284,7 +284,7 @@ impl ToTokens for ElementAttrNamed {
|
|||
}
|
||||
ElementAttr::EventTokens { name, tokens } => {
|
||||
quote! {
|
||||
dioxus_elements::on::#name(__cx, #tokens)
|
||||
dioxus_elements::events::#name(__cx, #tokens)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -226,6 +226,7 @@ impl<'a> DynamicContext<'a> {
|
|||
out
|
||||
});
|
||||
|
||||
let opt = el.children.len() == 1;
|
||||
let children = quote! { #(#children),* };
|
||||
|
||||
quote! {
|
||||
|
@ -234,6 +235,7 @@ impl<'a> DynamicContext<'a> {
|
|||
namespace: dioxus_elements::#el_name::NAME_SPACE,
|
||||
attrs: &[ #attrs ],
|
||||
children: &[ #children ],
|
||||
inner_opt: #opt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
98
packages/ssr/src/cache.rs
Normal file
98
packages/ssr/src/cache.rs
Normal file
|
@ -0,0 +1,98 @@
|
|||
use dioxus_core::prelude::*;
|
||||
use std::fmt::Write;
|
||||
|
||||
pub struct StringCache {
|
||||
pub segments: Vec<Segment>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct StringChain {
|
||||
pub segments: Vec<Segment>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub enum Segment {
|
||||
Attr(usize),
|
||||
Node(usize),
|
||||
PreRendered(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Write for StringChain {
|
||||
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||
match self.segments.last_mut() {
|
||||
Some(Segment::PreRendered(s2)) => s2.push_str(s),
|
||||
_ => self.segments.push(Segment::PreRendered(s.to_string())),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl StringCache {
|
||||
pub fn from_template(template: &VNode) -> Result<Self, std::fmt::Error> {
|
||||
let mut chain = StringChain::default();
|
||||
|
||||
let mut cur_path = vec![];
|
||||
|
||||
for (root_idx, root) in template.template.roots.iter().enumerate() {
|
||||
Self::recurse(root, &mut cur_path, root_idx, &mut chain)?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
segments: chain.segments,
|
||||
})
|
||||
}
|
||||
|
||||
fn recurse(
|
||||
root: &TemplateNode,
|
||||
cur_path: &mut Vec<usize>,
|
||||
root_idx: usize,
|
||||
chain: &mut StringChain,
|
||||
) -> Result<(), std::fmt::Error> {
|
||||
match root {
|
||||
TemplateNode::Element {
|
||||
tag,
|
||||
attrs,
|
||||
children,
|
||||
..
|
||||
} => {
|
||||
cur_path.push(root_idx);
|
||||
write!(chain, "<{}", tag)?;
|
||||
for attr in *attrs {
|
||||
match attr {
|
||||
TemplateAttribute::Static { name, value, .. } => {
|
||||
write!(chain, " {}=\"{}\"", name, value)?;
|
||||
}
|
||||
TemplateAttribute::Dynamic(index) => {
|
||||
chain.segments.push(Segment::Attr(*index))
|
||||
}
|
||||
}
|
||||
}
|
||||
if children.len() == 0 && tag_is_self_closing(tag) {
|
||||
write!(chain, "/>")?;
|
||||
} else {
|
||||
write!(chain, ">")?;
|
||||
for child in *children {
|
||||
Self::recurse(child, cur_path, root_idx, chain)?;
|
||||
}
|
||||
write!(chain, "</{}>", tag)?;
|
||||
}
|
||||
cur_path.pop();
|
||||
}
|
||||
TemplateNode::Text(text) => write!(chain, "{}", text)?,
|
||||
TemplateNode::Dynamic(idx) | TemplateNode::DynamicText(idx) => {
|
||||
chain.segments.push(Segment::Node(*idx))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn tag_is_self_closing(tag: &str) -> bool {
|
||||
match tag {
|
||||
"area" | "base" | "br" | "col" | "embed" | "hr" | "img" | "input" | "link" | "meta"
|
||||
| "param" | "source" | "track" | "wbr" => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
38
packages/ssr/src/config.rs
Normal file
38
packages/ssr/src/config.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
#[derive(Clone, Debug, Default)]
|
||||
pub struct SsrConfig {
|
||||
/// currently not supported - control if we indent the HTML output
|
||||
indent: bool,
|
||||
|
||||
/// Control if elements are written onto a new line
|
||||
newline: bool,
|
||||
|
||||
/// Choose to write ElementIDs into elements so the page can be re-hydrated later on
|
||||
pre_render: bool,
|
||||
|
||||
// Currently not implemented
|
||||
// Don't proceed onto new components. Instead, put the name of the component.
|
||||
// TODO: components don't have names :(
|
||||
skip_components: bool,
|
||||
}
|
||||
|
||||
impl SsrConfig {
|
||||
pub fn indent(mut self, a: bool) -> Self {
|
||||
self.indent = a;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn newline(mut self, a: bool) -> Self {
|
||||
self.newline = a;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn pre_render(mut self, a: bool) -> Self {
|
||||
self.pre_render = a;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn skip_components(mut self, a: bool) -> Self {
|
||||
self.skip_components = a;
|
||||
self
|
||||
}
|
||||
}
|
1
packages/ssr/src/helpers.rs
Normal file
1
packages/ssr/src/helpers.rs
Normal file
|
@ -0,0 +1 @@
|
|||
|
|
@ -1,510 +1,9 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
|
||||
use std::fmt::{Display, Formatter, Write};
|
||||
mod cache;
|
||||
pub mod config;
|
||||
pub mod helpers;
|
||||
pub mod renderer;
|
||||
pub mod template;
|
||||
|
||||
use dioxus_core::exports::bumpalo;
|
||||
|
||||
use dioxus_core::*;
|
||||
|
||||
fn app(_cx: Scope) -> Element {
|
||||
None
|
||||
}
|
||||
|
||||
pub struct SsrRenderer {
|
||||
vdom: VirtualDom,
|
||||
cfg: SsrConfig,
|
||||
}
|
||||
|
||||
impl SsrRenderer {
|
||||
pub fn new(cfg: impl FnOnce(SsrConfig) -> SsrConfig) -> Self {
|
||||
Self {
|
||||
cfg: cfg(SsrConfig::default()),
|
||||
vdom: VirtualDom::new(app),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_lazy<'a>(&'a mut self, f: LazyNodes<'a, '_>) -> String {
|
||||
let scope = self.vdom.base_scope();
|
||||
let root = f.call(scope);
|
||||
format!(
|
||||
"{:}",
|
||||
TextRenderer {
|
||||
cfg: self.cfg.clone(),
|
||||
root: &root,
|
||||
vdom: Some(&self.vdom),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_lifetimes)]
|
||||
pub fn render_lazy<'a>(f: LazyNodes<'a, '_>) -> String {
|
||||
let vdom = VirtualDom::new(app);
|
||||
let scope: *const ScopeState = vdom.base_scope();
|
||||
|
||||
// Safety
|
||||
//
|
||||
// The lifetimes bounds on LazyNodes are really complicated - they need to support the nesting restrictions in
|
||||
// regular component usage. The <'a> lifetime is used to enforce that all calls of IntoVnode use the same allocator.
|
||||
//
|
||||
// When LazyNodes are provided, they are FnOnce, but do not come with a allocator selected to borrow from. The <'a>
|
||||
// lifetime is therefore longer than the lifetime of the allocator which doesn't exist... yet.
|
||||
//
|
||||
// Therefore, we cast our local bump allocator to the right lifetime. This is okay because our usage of the bump
|
||||
// arena is *definitely* shorter than the <'a> lifetime, and we return *owned* data - not borrowed data.
|
||||
let scope = unsafe { &*scope };
|
||||
let root = f.call(scope);
|
||||
|
||||
let vdom = Some(&vdom);
|
||||
|
||||
let ssr_renderer = TextRenderer {
|
||||
cfg: SsrConfig::default(),
|
||||
root: &root,
|
||||
vdom,
|
||||
};
|
||||
let r = ssr_renderer.to_string();
|
||||
drop(ssr_renderer);
|
||||
r
|
||||
}
|
||||
|
||||
pub fn render_vdom(dom: &VirtualDom) -> String {
|
||||
format!("{:}", TextRenderer::from_vdom(dom, SsrConfig::default()))
|
||||
}
|
||||
|
||||
pub fn pre_render_vdom(dom: &VirtualDom) -> String {
|
||||
format!(
|
||||
"{:}",
|
||||
TextRenderer::from_vdom(dom, SsrConfig::default().pre_render(true))
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render_vdom_cfg(dom: &VirtualDom, cfg: impl FnOnce(SsrConfig) -> SsrConfig) -> String {
|
||||
format!(
|
||||
"{:}",
|
||||
TextRenderer::from_vdom(dom, cfg(SsrConfig::default()))
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
|
||||
Some(format!(
|
||||
"{:}",
|
||||
TextRenderer {
|
||||
cfg: SsrConfig::default(),
|
||||
root: vdom.get_scope(scope).unwrap().root_node(),
|
||||
vdom: Some(vdom),
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
/// A configurable text renderer for the Dioxus VirtualDOM.
|
||||
///
|
||||
///
|
||||
/// ## Details
|
||||
///
|
||||
/// This uses the `Formatter` infrastructure so you can write into anything that supports `write_fmt`. We can't accept
|
||||
/// any generic writer, so you need to "Display" the text renderer. This is done through `format!` or `format_args!`
|
||||
///
|
||||
/// ## Example
|
||||
/// ```ignore
|
||||
/// static App: Component = |cx| cx.render(rsx!(div { "hello world" }));
|
||||
/// let mut vdom = VirtualDom::new(App);
|
||||
/// vdom.rebuild();
|
||||
///
|
||||
/// let renderer = TextRenderer::new(&vdom);
|
||||
/// let output = format!("{}", renderer);
|
||||
/// assert_eq!(output, "<div>hello world</div>");
|
||||
/// ```
|
||||
pub struct TextRenderer<'a, 'b, 'c> {
|
||||
vdom: Option<&'c VirtualDom>,
|
||||
root: &'b VNode<'a>,
|
||||
cfg: SsrConfig,
|
||||
}
|
||||
|
||||
impl<'a: 'c, 'c> Display for TextRenderer<'a, '_, 'c> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let mut last_node_was_text = false;
|
||||
self.html_render(self.root, f, 0, &mut last_node_was_text)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TextRenderer<'a, '_, 'a> {
|
||||
pub fn from_vdom(vdom: &'a VirtualDom, cfg: SsrConfig) -> Self {
|
||||
Self {
|
||||
cfg,
|
||||
root: vdom.base_scope().root_node(),
|
||||
vdom: Some(vdom),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a: 'c, 'c> TextRenderer<'a, '_, 'c> {
|
||||
fn html_render(
|
||||
&self,
|
||||
node: &VNode,
|
||||
f: &mut impl Write,
|
||||
il: u16,
|
||||
last_node_was_text: &mut bool,
|
||||
) -> std::fmt::Result {
|
||||
// match &node {
|
||||
// VNode::Text(text) => {
|
||||
// if *last_node_was_text {
|
||||
// write!(f, "<!--spacer-->")?;
|
||||
// }
|
||||
|
||||
// if self.cfg.indent {
|
||||
// for _ in 0..il {
|
||||
// write!(f, " ")?;
|
||||
// }
|
||||
// }
|
||||
|
||||
// *last_node_was_text = true;
|
||||
|
||||
// write!(f, "{}", text.text)?
|
||||
// }
|
||||
// VNode::Element(el) => {
|
||||
// *last_node_was_text = false;
|
||||
|
||||
// if self.cfg.indent {
|
||||
// for _ in 0..il {
|
||||
// write!(f, " ")?;
|
||||
// }
|
||||
// }
|
||||
|
||||
// write!(f, "<{}", el.tag)?;
|
||||
|
||||
// let inner_html = render_attributes(el.attributes.iter(), f)?;
|
||||
|
||||
// match self.cfg.newline {
|
||||
// true => writeln!(f, ">")?,
|
||||
// false => write!(f, ">")?,
|
||||
// }
|
||||
|
||||
// if let Some(inner_html) = inner_html {
|
||||
// write!(f, "{}", inner_html)?;
|
||||
// } else {
|
||||
// let mut last_node_was_text = false;
|
||||
// for child in el.children {
|
||||
// self.html_render(child, f, il + 1, &mut last_node_was_text)?;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if self.cfg.newline {
|
||||
// writeln!(f)?;
|
||||
// }
|
||||
// if self.cfg.indent {
|
||||
// for _ in 0..il {
|
||||
// write!(f, " ")?;
|
||||
// }
|
||||
// }
|
||||
|
||||
// write!(f, "</{}>", el.tag)?;
|
||||
// if self.cfg.newline {
|
||||
// writeln!(f)?;
|
||||
// }
|
||||
// }
|
||||
// VNode::Fragment(frag) => match frag.children.len() {
|
||||
// 0 => {
|
||||
// *last_node_was_text = false;
|
||||
// if self.cfg.indent {
|
||||
// for _ in 0..il {
|
||||
// write!(f, " ")?;
|
||||
// }
|
||||
// }
|
||||
// write!(f, "<!--placeholder-->")?;
|
||||
// }
|
||||
// _ => {
|
||||
// for child in frag.children {
|
||||
// self.html_render(child, f, il + 1, last_node_was_text)?;
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// VNode::Component(vcomp) => {
|
||||
// let idx = vcomp.scope.get().unwrap();
|
||||
|
||||
// if let (Some(vdom), false) = (self.vdom, self.cfg.skip_components) {
|
||||
// let new_node = vdom.get_scope(idx).unwrap().root_node();
|
||||
// self.html_render(new_node, f, il + 1, last_node_was_text)?;
|
||||
// } else {
|
||||
// }
|
||||
// }
|
||||
// VNode::Template(t) => {
|
||||
// if let Some(vdom) = self.vdom {
|
||||
// todo!()
|
||||
// } else {
|
||||
// panic!("Cannot render template without vdom");
|
||||
// }
|
||||
// }
|
||||
// VNode::Placeholder(_) => {
|
||||
// todo!()
|
||||
// }
|
||||
// }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// fn render_template_node(
|
||||
// &self,
|
||||
// node: &VTemplate,
|
||||
// f: &mut impl Write,
|
||||
// last_node_was_text: &mut bool,
|
||||
// il: u16,
|
||||
// ) -> std::fmt::Result {
|
||||
// match &node.node_type {
|
||||
// TemplateNodeType::Element(el) => {
|
||||
// *last_node_was_text = false;
|
||||
|
||||
// if self.cfg.indent {
|
||||
// for _ in 0..il {
|
||||
// write!(f, " ")?;
|
||||
// }
|
||||
// }
|
||||
|
||||
// write!(f, "<{}", el.tag)?;
|
||||
|
||||
// let mut inner_html = None;
|
||||
|
||||
// let mut attr_iter = el.attributes.as_ref().iter().peekable();
|
||||
|
||||
// while let Some(attr) = attr_iter.next() {
|
||||
// match attr.attribute.namespace {
|
||||
// None => {
|
||||
// if attr.attribute.name == "dangerous_inner_html" {
|
||||
// inner_html = {
|
||||
// let text = match &attr.value {
|
||||
// TemplateAttributeValue::Static(val) => {
|
||||
// val.allocate(&self.bump).as_text().unwrap()
|
||||
// }
|
||||
// TemplateAttributeValue::Dynamic(idx) => dynamic_context
|
||||
// .resolve_attribute(*idx)
|
||||
// .as_text()
|
||||
// .unwrap(),
|
||||
// };
|
||||
// Some(text)
|
||||
// }
|
||||
// } else if is_boolean_attribute(attr.attribute.name) {
|
||||
// match &attr.value {
|
||||
// TemplateAttributeValue::Static(val) => {
|
||||
// let val = val.allocate(&self.bump);
|
||||
// if val.is_truthy() {
|
||||
// write!(f, " {}=\"{}\"", attr.attribute.name, val)?
|
||||
// }
|
||||
// }
|
||||
// TemplateAttributeValue::Dynamic(idx) => {
|
||||
// let val = dynamic_context.resolve_attribute(*idx);
|
||||
// if val.is_truthy() {
|
||||
// write!(f, " {}=\"{}\"", attr.attribute.name, val)?
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// match &attr.value {
|
||||
// TemplateAttributeValue::Static(val) => {
|
||||
// let val = val.allocate(&self.bump);
|
||||
// write!(f, " {}=\"{}\"", attr.attribute.name, val)?
|
||||
// }
|
||||
// TemplateAttributeValue::Dynamic(idx) => {
|
||||
// let val = dynamic_context.resolve_attribute(*idx);
|
||||
// write!(f, " {}=\"{}\"", attr.attribute.name, val)?
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// Some(ns) => {
|
||||
// // write the opening tag
|
||||
// write!(f, " {}=\"", ns)?;
|
||||
// let mut cur_ns_el = attr;
|
||||
// loop {
|
||||
// match &attr.value {
|
||||
// TemplateAttributeValue::Static(val) => {
|
||||
// let val = val.allocate(&self.bump);
|
||||
// write!(f, "{}:{};", cur_ns_el.attribute.name, val)?;
|
||||
// }
|
||||
// TemplateAttributeValue::Dynamic(idx) => {
|
||||
// let val = dynamic_context.resolve_attribute(*idx);
|
||||
// write!(f, "{}:{};", cur_ns_el.attribute.name, val)?;
|
||||
// }
|
||||
// }
|
||||
// match attr_iter.peek() {
|
||||
// Some(next_attr)
|
||||
// if next_attr.attribute.namespace == Some(ns) =>
|
||||
// {
|
||||
// cur_ns_el = attr_iter.next().unwrap();
|
||||
// }
|
||||
// _ => break,
|
||||
// }
|
||||
// }
|
||||
// // write the closing tag
|
||||
// write!(f, "\"")?;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// match self.cfg.newline {
|
||||
// true => writeln!(f, ">")?,
|
||||
// false => write!(f, ">")?,
|
||||
// }
|
||||
|
||||
// if let Some(inner_html) = inner_html {
|
||||
// write!(f, "{}", inner_html)?;
|
||||
// } else {
|
||||
// let mut last_node_was_text = false;
|
||||
// for child in el.children.as_ref() {
|
||||
// self.render_template_node(
|
||||
// template_nodes,
|
||||
// &template_nodes.as_ref()[child.0],
|
||||
// dynamic_context,
|
||||
// f,
|
||||
// &mut last_node_was_text,
|
||||
// il + 1,
|
||||
// )?;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if self.cfg.newline {
|
||||
// writeln!(f)?;
|
||||
// }
|
||||
// if self.cfg.indent {
|
||||
// for _ in 0..il {
|
||||
// write!(f, " ")?;
|
||||
// }
|
||||
// }
|
||||
|
||||
// write!(f, "</{}>", el.tag)?;
|
||||
// if self.cfg.newline {
|
||||
// writeln!(f)?;
|
||||
// }
|
||||
// }
|
||||
// TemplateNodeType::Text(txt) => {
|
||||
// if *last_node_was_text {
|
||||
// write!(f, "<!--spacer-->")?;
|
||||
// }
|
||||
|
||||
// if self.cfg.indent {
|
||||
// for _ in 0..il {
|
||||
// write!(f, " ")?;
|
||||
// }
|
||||
// }
|
||||
|
||||
// *last_node_was_text = true;
|
||||
|
||||
// let text = dynamic_context.resolve_text(txt);
|
||||
|
||||
// write!(f, "{}", text)?
|
||||
// }
|
||||
// TemplateNodeType::DynamicNode(idx) => {
|
||||
// let node = dynamic_context.resolve_node(*idx);
|
||||
// self.html_render(node, f, il, last_node_was_text)?;
|
||||
// }
|
||||
// }
|
||||
// Ok(())
|
||||
// }
|
||||
}
|
||||
|
||||
fn render_attributes<'a, 'b: 'a>(
|
||||
attrs: impl Iterator<Item = &'a Attribute<'b>>,
|
||||
f: &mut impl Write,
|
||||
) -> Result<Option<&'b str>, std::fmt::Error> {
|
||||
let mut inner_html = None;
|
||||
let mut attr_iter = attrs.peekable();
|
||||
|
||||
// while let Some(attr) = attr_iter.next() {
|
||||
// match attr.namespace {
|
||||
// None => {
|
||||
// if attr.name == "dangerous_inner_html" {
|
||||
// inner_html = Some(attr.value.as_text().unwrap())
|
||||
// } else {
|
||||
// if is_boolean_attribute(attr.name) && !attr.value.is_truthy() {
|
||||
// continue;
|
||||
// }
|
||||
// write!(f, " {}=\"{}\"", attr.name, attr.value)?
|
||||
// }
|
||||
// }
|
||||
// Some(ns) => {
|
||||
// // write the opening tag
|
||||
// write!(f, " {}=\"", ns)?;
|
||||
// let mut cur_ns_el = attr;
|
||||
// loop {
|
||||
// write!(f, "{}:{};", cur_ns_el.name, cur_ns_el.value)?;
|
||||
// match attr_iter.peek() {
|
||||
// Some(next_attr) if next_attr.namespace == Some(ns) => {
|
||||
// cur_ns_el = attr_iter.next().unwrap();
|
||||
// }
|
||||
// _ => break,
|
||||
// }
|
||||
// }
|
||||
// // write the closing tag
|
||||
// write!(f, "\"")?;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
Ok(inner_html)
|
||||
}
|
||||
|
||||
fn is_boolean_attribute(attribute: &'static str) -> bool {
|
||||
matches!(
|
||||
attribute,
|
||||
"allowfullscreen"
|
||||
| "allowpaymentrequest"
|
||||
| "async"
|
||||
| "autofocus"
|
||||
| "autoplay"
|
||||
| "checked"
|
||||
| "controls"
|
||||
| "default"
|
||||
| "defer"
|
||||
| "disabled"
|
||||
| "formnovalidate"
|
||||
| "hidden"
|
||||
| "ismap"
|
||||
| "itemscope"
|
||||
| "loop"
|
||||
| "multiple"
|
||||
| "muted"
|
||||
| "nomodule"
|
||||
| "novalidate"
|
||||
| "open"
|
||||
| "playsinline"
|
||||
| "readonly"
|
||||
| "required"
|
||||
| "reversed"
|
||||
| "selected"
|
||||
| "truespeed"
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct SsrConfig {
|
||||
/// currently not supported - control if we indent the HTML output
|
||||
indent: bool,
|
||||
|
||||
/// Control if elements are written onto a new line
|
||||
newline: bool,
|
||||
|
||||
/// Choose to write ElementIDs into elements so the page can be re-hydrated later on
|
||||
pre_render: bool,
|
||||
|
||||
// Currently not implemented
|
||||
// Don't proceed onto new components. Instead, put the name of the component.
|
||||
// TODO: components don't have names :(
|
||||
skip_components: bool,
|
||||
}
|
||||
|
||||
impl SsrConfig {
|
||||
pub fn indent(mut self, a: bool) -> Self {
|
||||
self.indent = a;
|
||||
self
|
||||
}
|
||||
pub fn newline(mut self, a: bool) -> Self {
|
||||
self.newline = a;
|
||||
self
|
||||
}
|
||||
pub fn pre_render(mut self, a: bool) -> Self {
|
||||
self.pre_render = a;
|
||||
self
|
||||
}
|
||||
pub fn skip_components(mut self, a: bool) -> Self {
|
||||
self.skip_components = a;
|
||||
self
|
||||
}
|
||||
}
|
||||
pub use helpers::*;
|
||||
pub use template::SsrRender;
|
||||
|
|
146
packages/ssr/src/renderer.rs
Normal file
146
packages/ssr/src/renderer.rs
Normal file
|
@ -0,0 +1,146 @@
|
|||
// use dioxus_core::VirtualDom;
|
||||
|
||||
// use crate::config::SsrConfig;
|
||||
|
||||
// pub struct SsrRenderer {
|
||||
// vdom: VirtualDom,
|
||||
// cfg: SsrConfig,
|
||||
// }
|
||||
|
||||
// impl Default for SsrRenderer {
|
||||
// fn default() -> Self {
|
||||
// Self::new(SsrConfig::default())
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl SsrRenderer {
|
||||
// pub fn new(cfg: SsrConfig) -> Self {
|
||||
// Self {
|
||||
// vdom: VirtualDom::new(app),
|
||||
// cfg,
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub fn render_lazy<'a>(&'a mut self, f: LazyNodes<'a, '_>) -> String {
|
||||
// let scope = self.vdom.base_scope();
|
||||
// let root = f.call(scope);
|
||||
// format!(
|
||||
// "{:}",
|
||||
// TextRenderer {
|
||||
// cfg: self.cfg.clone(),
|
||||
// root: &root,
|
||||
// vdom: Some(&self.vdom),
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
|
||||
// fn html_render(
|
||||
// &self,
|
||||
// node: &VNode,
|
||||
// f: &mut impl Write,
|
||||
// il: u16,
|
||||
// last_node_was_text: &mut bool,
|
||||
// ) -> std::fmt::Result {
|
||||
// // match &node {
|
||||
// // VNode::Text(text) => {
|
||||
// // if *last_node_was_text {
|
||||
// // write!(f, "<!--spacer-->")?;
|
||||
// // }
|
||||
|
||||
// // if self.cfg.indent {
|
||||
// // for _ in 0..il {
|
||||
// // write!(f, " ")?;
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// // *last_node_was_text = true;
|
||||
|
||||
// // write!(f, "{}", text.text)?
|
||||
// // }
|
||||
// // VNode::Element(el) => {
|
||||
// // *last_node_was_text = false;
|
||||
|
||||
// // if self.cfg.indent {
|
||||
// // for _ in 0..il {
|
||||
// // write!(f, " ")?;
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// // write!(f, "<{}", el.tag)?;
|
||||
|
||||
// // let inner_html = render_attributes(el.attributes.iter(), f)?;
|
||||
|
||||
// // match self.cfg.newline {
|
||||
// // true => writeln!(f, ">")?,
|
||||
// // false => write!(f, ">")?,
|
||||
// // }
|
||||
|
||||
// // if let Some(inner_html) = inner_html {
|
||||
// // write!(f, "{}", inner_html)?;
|
||||
// // } else {
|
||||
// // let mut last_node_was_text = false;
|
||||
// // for child in el.children {
|
||||
// // self.html_render(child, f, il + 1, &mut last_node_was_text)?;
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// // if self.cfg.newline {
|
||||
// // writeln!(f)?;
|
||||
// // }
|
||||
// // if self.cfg.indent {
|
||||
// // for _ in 0..il {
|
||||
// // write!(f, " ")?;
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// // write!(f, "</{}>", el.tag)?;
|
||||
// // if self.cfg.newline {
|
||||
// // writeln!(f)?;
|
||||
// // }
|
||||
// // }
|
||||
// // VNode::Fragment(frag) => match frag.children.len() {
|
||||
// // 0 => {
|
||||
// // *last_node_was_text = false;
|
||||
// // if self.cfg.indent {
|
||||
// // for _ in 0..il {
|
||||
// // write!(f, " ")?;
|
||||
// // }
|
||||
// // }
|
||||
// // write!(f, "<!--placeholder-->")?;
|
||||
// // }
|
||||
// // _ => {
|
||||
// // for child in frag.children {
|
||||
// // self.html_render(child, f, il + 1, last_node_was_text)?;
|
||||
// // }
|
||||
// // }
|
||||
// // },
|
||||
// // VNode::Component(vcomp) => {
|
||||
// // let idx = vcomp.scope.get().unwrap();
|
||||
|
||||
// // if let (Some(vdom), false) = (self.vdom, self.cfg.skip_components) {
|
||||
// // let new_node = vdom.get_scope(idx).unwrap().root_node();
|
||||
// // self.html_render(new_node, f, il + 1, last_node_was_text)?;
|
||||
// // } else {
|
||||
// // }
|
||||
// // }
|
||||
// // VNode::Template(t) => {
|
||||
// // if let Some(vdom) = self.vdom {
|
||||
// // todo!()
|
||||
// // } else {
|
||||
// // panic!("Cannot render template without vdom");
|
||||
// // }
|
||||
// // }
|
||||
// // VNode::Placeholder(_) => {
|
||||
// // todo!()
|
||||
// // }
|
||||
// // }
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl<'a: 'c, 'c> Display for SsrRenderer<'a, '_, 'c> {
|
||||
// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
// let mut last_node_was_text = false;
|
||||
// self.html_render(self.root, f, 0, &mut last_node_was_text)
|
||||
// }
|
||||
// }
|
|
@ -1,101 +1,15 @@
|
|||
use dioxus_core::{prelude::*, AttributeValue};
|
||||
use std::cell::RefCell;
|
||||
use super::cache::Segment;
|
||||
use dioxus_core::{prelude::*, AttributeValue, DynamicNode};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Write;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::cache::StringCache;
|
||||
|
||||
/// A virtualdom renderer that caches the templates it has seen for faster rendering
|
||||
#[derive(Default)]
|
||||
pub struct SsrRender {
|
||||
template_cache: RefCell<HashMap<Template<'static>, Rc<StringCache>>>,
|
||||
}
|
||||
|
||||
struct StringCache {
|
||||
segments: Vec<Segment>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct StringChain {
|
||||
segments: Vec<Segment>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Segment {
|
||||
Attr(usize),
|
||||
Node(usize),
|
||||
PreRendered(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Write for StringChain {
|
||||
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||
match self.segments.last_mut() {
|
||||
Some(Segment::PreRendered(s2)) => s2.push_str(s),
|
||||
_ => self.segments.push(Segment::PreRendered(s.to_string())),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl StringCache {
|
||||
fn from_template(template: &VNode) -> Result<Self, std::fmt::Error> {
|
||||
let mut chain = StringChain::default();
|
||||
|
||||
let mut cur_path = vec![];
|
||||
|
||||
for (root_idx, root) in template.template.roots.iter().enumerate() {
|
||||
Self::recurse(root, &mut cur_path, root_idx, &mut chain)?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
segments: chain.segments,
|
||||
})
|
||||
}
|
||||
|
||||
fn recurse(
|
||||
root: &TemplateNode,
|
||||
cur_path: &mut Vec<usize>,
|
||||
root_idx: usize,
|
||||
chain: &mut StringChain,
|
||||
) -> Result<(), std::fmt::Error> {
|
||||
match root {
|
||||
TemplateNode::Element {
|
||||
tag,
|
||||
attrs,
|
||||
children,
|
||||
..
|
||||
} => {
|
||||
cur_path.push(root_idx);
|
||||
write!(chain, "<{}", tag)?;
|
||||
for attr in *attrs {
|
||||
match attr {
|
||||
TemplateAttribute::Static { name, value, .. } => {
|
||||
write!(chain, " {}=\"{}\"", name, value)?;
|
||||
}
|
||||
TemplateAttribute::Dynamic(index) => {
|
||||
chain.segments.push(Segment::Attr(*index))
|
||||
}
|
||||
}
|
||||
}
|
||||
if children.len() == 0 {
|
||||
write!(chain, "/>")?;
|
||||
} else {
|
||||
write!(chain, ">")?;
|
||||
for child in *children {
|
||||
Self::recurse(child, cur_path, root_idx, chain)?;
|
||||
}
|
||||
write!(chain, "</{}>", tag)?;
|
||||
}
|
||||
cur_path.pop();
|
||||
}
|
||||
TemplateNode::Text(text) => write!(chain, "{}", text)?,
|
||||
TemplateNode::Dynamic(idx) | TemplateNode::DynamicText(idx) => {
|
||||
chain.segments.push(Segment::Node(*idx))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
template_cache: HashMap<Template<'static>, Rc<StringCache>>,
|
||||
}
|
||||
|
||||
impl SsrRender {
|
||||
|
@ -104,15 +18,19 @@ impl SsrRender {
|
|||
let root = scope.root_node();
|
||||
|
||||
let mut out = String::new();
|
||||
self.render_template(&mut out, root).unwrap();
|
||||
self.render_template(&mut out, dom, root).unwrap();
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
fn render_template(&self, buf: &mut String, template: &VNode) -> std::fmt::Result {
|
||||
fn render_template(
|
||||
&mut self,
|
||||
buf: &mut String,
|
||||
dom: &VirtualDom,
|
||||
template: &VNode,
|
||||
) -> std::fmt::Result {
|
||||
let entry = self
|
||||
.template_cache
|
||||
.borrow_mut()
|
||||
.entry(template.template)
|
||||
.or_insert_with(|| Rc::new(StringCache::from_template(template).unwrap()))
|
||||
.clone();
|
||||
|
@ -123,29 +41,40 @@ impl SsrRender {
|
|||
let attr = &template.dynamic_attrs[*idx];
|
||||
match attr.value {
|
||||
AttributeValue::Text(value) => write!(buf, " {}=\"{}\"", attr.name, value)?,
|
||||
AttributeValue::Bool(value) => write!(buf, " {}={}", attr.name, value)?,
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
Segment::Node(idx) => match &template.dynamic_nodes[*idx] {
|
||||
DynamicNode::Text { value, .. } => {
|
||||
// todo: escape the text
|
||||
write!(buf, "{}", value)?
|
||||
}
|
||||
DynamicNode::Fragment(children) => {
|
||||
for child in *children {
|
||||
self.render_template(buf, child)?;
|
||||
DynamicNode::Text { value, inner, .. } => {
|
||||
// in SSR, we are concerned that we can't hunt down the right text node since they might get merged
|
||||
if !*inner {
|
||||
write!(buf, "<!--#-->")?;
|
||||
}
|
||||
|
||||
// todo: escape the text
|
||||
write!(buf, "{}", value)?;
|
||||
|
||||
if !*inner {
|
||||
write!(buf, "<!--/#-->")?;
|
||||
}
|
||||
//
|
||||
}
|
||||
DynamicNode::Component { .. } => {
|
||||
//
|
||||
DynamicNode::Fragment { nodes, .. } => {
|
||||
for child in *nodes {
|
||||
self.render_template(buf, dom, child)?;
|
||||
}
|
||||
}
|
||||
DynamicNode::Placeholder(el) => {
|
||||
//
|
||||
DynamicNode::Component { scope, .. } => {
|
||||
let id = scope.get().unwrap();
|
||||
let scope = dom.get_scope(id).unwrap();
|
||||
self.render_template(buf, dom, scope.root_node())?;
|
||||
}
|
||||
DynamicNode::Placeholder(_el) => {
|
||||
write!(buf, "<!--placeholder-->")?;
|
||||
}
|
||||
},
|
||||
|
||||
Segment::PreRendered(text) => buf.push_str(&text),
|
||||
Segment::PreRendered(contents) => buf.push_str(contents),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,89 +92,39 @@ fn to_string_works() {
|
|||
|
||||
render! {
|
||||
div { class: "asdasdasd", class: "asdasdasd", id: "id-{dynamic}",
|
||||
"Hello world 1 -->"
|
||||
"{dynamic}"
|
||||
"<-- Hello world 2"
|
||||
"Hello world 1 -->" "{dynamic}" "<-- Hello world 2"
|
||||
div { "nest 1" }
|
||||
div {}
|
||||
div { "nest 2" }
|
||||
"{dyn2}"
|
||||
|
||||
(0..5).map(|i| rsx! { div { "finalize {i}" } })
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut dom = VirtualDom::new(app);
|
||||
|
||||
let mut mutations = Vec::new();
|
||||
dom.rebuild(&mut mutations);
|
||||
|
||||
let cache = StringCache::from_template(&dom.base_scope().root_node()).unwrap();
|
||||
dbg!(cache.segments);
|
||||
|
||||
let mut renderer = SsrRender::default();
|
||||
dbg!(renderer.render_vdom(&dom));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn children_processes_properly() {
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let d = 123;
|
||||
|
||||
render! {
|
||||
div {
|
||||
ChildWithChildren {
|
||||
p { "{d}" "hii" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline_props]
|
||||
fn ChildWithChildren<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
|
||||
render! {
|
||||
h1 { children }
|
||||
}
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
dom.rebuild();
|
||||
|
||||
let mut mutations = vec![];
|
||||
dom.rebuild(&mut mutations);
|
||||
dbg!(mutations);
|
||||
use Segment::*;
|
||||
|
||||
let mut mutations = vec![];
|
||||
dom.rebuild(&mut mutations);
|
||||
dbg!(mutations);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn async_children() {
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let d = 123;
|
||||
|
||||
render! {
|
||||
div {
|
||||
"Hello world"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn async_child(cx: Scope<'_>) -> Element {
|
||||
let d = 123;
|
||||
|
||||
let user_name = use_fetch("https://jsonplaceholder.typicode.com/users/1").await;
|
||||
|
||||
render! { p { "{d}" "hii" } }
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
|
||||
let mut mutations = vec![];
|
||||
dom.rebuild(&mut mutations);
|
||||
dbg!(mutations);
|
||||
assert_eq!(
|
||||
StringCache::from_template(&dom.base_scope().root_node())
|
||||
.unwrap()
|
||||
.segments,
|
||||
vec![
|
||||
PreRendered("<div class=\"asdasdasd\" class=\"asdasdasd\"".into(),),
|
||||
Attr(0,),
|
||||
PreRendered(">Hello world 1 -->".into(),),
|
||||
Node(0,),
|
||||
PreRendered("<-- Hello world 2<div>nest 1</div><div></div><div>nest 2</div>".into(),),
|
||||
Node(1,),
|
||||
Node(2,),
|
||||
PreRendered("</div>".into(),),
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
SsrRender::default().render_vdom(&dom),
|
||||
"<div class=\"asdasdasd\" class=\"asdasdasd\" id=\"id-123\">Hello world 1 --><!--#-->123<!--/#--><-- Hello world 2<div>nest 1</div><div></div><div>nest 2</div><!--#--></diiiiiiiiv><!--/#--><div><!--#-->finalize 0<!--/#--></div><div><!--#-->finalize 1<!--/#--></div><div><!--#-->finalize 2<!--/#--></div><div><!--#-->finalize 3<!--/#--></div><div><!--#-->finalize 4<!--/#--></div></div>"
|
||||
);
|
||||
}
|
||||
|
|
84
packages/ssr/tests/simple.rs
Normal file
84
packages/ssr/tests/simple.rs
Normal file
|
@ -0,0 +1,84 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn simple() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
render! { div { "hello!" } }
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
dom.rebuild();
|
||||
|
||||
assert_eq!(
|
||||
dioxus_ssr::SsrRender::default().render_vdom(&dom),
|
||||
"<div>hello!</div>"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lists() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
render! {
|
||||
ul {
|
||||
(0..5).map(|i| rsx! {
|
||||
li { "item {i}" }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
dom.rebuild();
|
||||
|
||||
assert_eq!(
|
||||
dioxus_ssr::SsrRender::default().render_vdom(&dom),
|
||||
"<ul><li>item 0</li><li>item 1</li><li>item 2</li><li>item 3</li><li>item 4</li></ul>"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dynamic() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
let dynamic = 123;
|
||||
|
||||
render! {
|
||||
div { "Hello world 1 -->" "{dynamic}" "<-- Hello world 2" }
|
||||
}
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
dom.rebuild();
|
||||
|
||||
assert_eq!(
|
||||
dioxus_ssr::SsrRender::default().render_vdom(&dom),
|
||||
"<div>Hello world 1 -->123<-- Hello world 2</div>"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn components() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
render! {
|
||||
div {
|
||||
(0..5).map(|name| rsx! {
|
||||
my_component { name: name }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline_props]
|
||||
fn my_component(cx: Scope, name: i32) -> Element {
|
||||
render! {
|
||||
div { "component {name}" }
|
||||
}
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
dom.rebuild();
|
||||
|
||||
assert_eq!(
|
||||
dioxus_ssr::SsrRender::default().render_vdom(&dom),
|
||||
"<div><div>component 0</div><div>component 1</div><div>component 2</div><div>component 3</div><div>component 4</div></div>"
|
||||
);
|
||||
}
|
Loading…
Add table
Reference in a new issue