wip: use the new structure
This commit is contained in:
1 parent
a85b8c4b6b
commit
a05047d01e
12 files changed
+884
-1241
No files matched your search
@@ -1,20 +1,7 @@
|
||||
use crate::hooklist::HookList;
|
||||
use crate::{arena::SharedArena, innerlude::*};
|
||||
use appendlist::AppendList;
|
||||
use crate::innerlude::*;
|
||||
use bumpalo::Bump;
|
||||
use slotmap::DefaultKey;
|
||||
use slotmap::SlotMap;
|
||||
use std::marker::PhantomData;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::{Cell, RefCell},
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
fmt::Debug,
|
||||
future::Future,
|
||||
ops::Deref,
|
||||
pin::Pin,
|
||||
rc::{Rc, Weak},
|
||||
};
|
||||
|
||||
use std::cell::RefCell;
|
||||
pub struct ActiveFrame {
|
||||
// We use a "generation" for users of contents in the bump frames to ensure their data isn't broken
|
||||
pub generation: RefCell<usize>,
|
||||
@@ -33,11 +20,11 @@ impl ActiveFrame {
|
||||
Self::from_frames(
|
||||
BumpFrame {
|
||||
bump: Bump::new(),
|
||||
head_node: VNode::static_text(""),
|
||||
head_node: NodeFactory::static_text(""),
|
||||
},
|
||||
BumpFrame {
|
||||
bump: Bump::new(),
|
||||
head_node: VNode::static_text(""),
|
||||
head_node: NodeFactory::static_text(""),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -270,17 +270,8 @@ Any function prefixed with "use" should not be called conditionally.
|
||||
) -> VNode<'src> {
|
||||
use futures_util::FutureExt;
|
||||
match fut.now_or_never() {
|
||||
Some(out) => {
|
||||
let suspended_cx = SuspendedContext {};
|
||||
let nodes = callback(suspended_cx, out);
|
||||
return nodes;
|
||||
}
|
||||
None => {
|
||||
// we need to register this task
|
||||
VNode::Suspended {
|
||||
real: Cell::new(RealDomNode::empty()),
|
||||
}
|
||||
}
|
||||
Some(out) => callback(SuspendedContext {}, out),
|
||||
None => NodeFactory::suspended(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+64
-62
@@ -158,50 +158,46 @@ where
|
||||
//
|
||||
// each function call assumes the stack is fresh (empty).
|
||||
pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {
|
||||
match (old_node, new_node) {
|
||||
match (&old_node.kind, &new_node.kind) {
|
||||
// Handle the "sane" cases first.
|
||||
// The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment".
|
||||
// So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable.
|
||||
(VNode::Text(old), VNode::Text(new)) => {
|
||||
if old.is_static {
|
||||
log::debug!("encountered static text node: {:?}", old.text);
|
||||
}
|
||||
|
||||
(VNodeKind::Text(old), VNodeKind::Text(new)) => {
|
||||
let root = old_node.dom_id.get();
|
||||
if old.text != new.text {
|
||||
self.dom.push(old.dom_id.get());
|
||||
self.dom.push(root);
|
||||
log::debug!("Text has changed {}, {}", old.text, new.text);
|
||||
self.dom.set_text(new.text);
|
||||
self.dom.pop();
|
||||
}
|
||||
|
||||
new.dom_id.set(old.dom_id.get());
|
||||
new_node.dom_id.set(root);
|
||||
}
|
||||
|
||||
(VNode::Element(old), VNode::Element(new)) => {
|
||||
(VNodeKind::Element(old), VNodeKind::Element(new)) => {
|
||||
// If the element type is completely different, the element needs to be re-rendered completely
|
||||
// This is an optimization React makes due to how users structure their code
|
||||
//
|
||||
// In Dioxus, this is less likely to occur unless through a fragment
|
||||
let root = old_node.dom_id.get();
|
||||
if new.tag_name != old.tag_name || new.namespace != old.namespace {
|
||||
self.dom.push(old.dom_id.get());
|
||||
self.dom.push(root);
|
||||
let meta = self.create(new_node);
|
||||
self.dom.replace_with(meta.added_to_stack);
|
||||
self.dom.pop();
|
||||
return;
|
||||
}
|
||||
|
||||
let oldid = old.dom_id.get();
|
||||
new.dom_id.set(oldid);
|
||||
new_node.dom_id.set(root);
|
||||
|
||||
// push it just in case
|
||||
self.dom.push(oldid);
|
||||
self.dom.push(root);
|
||||
self.diff_listeners(old.listeners, new.listeners);
|
||||
self.diff_attr(old.attributes, new.attributes, new.namespace);
|
||||
self.diff_children(old.children, new.children);
|
||||
self.dom.pop();
|
||||
}
|
||||
|
||||
(VNode::Component(old), VNode::Component(new)) => {
|
||||
(VNodeKind::Component(old), VNodeKind::Component(new)) => {
|
||||
log::warn!("diffing components? {:#?}", new.user_fc);
|
||||
if old.user_fc == new.user_fc {
|
||||
// Make sure we're dealing with the same component (by function pointer)
|
||||
@@ -257,7 +253,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
(VNode::Fragment(old), VNode::Fragment(new)) => {
|
||||
(VNodeKind::Fragment(old), VNodeKind::Fragment(new)) => {
|
||||
// This is the case where options or direct vnodes might be used.
|
||||
// In this case, it's faster to just skip ahead to their diff
|
||||
if old.children.len() == 1 && new.children.len() == 1 {
|
||||
@@ -278,15 +274,19 @@ where
|
||||
// We also walk the "real node" list to make sure all latent roots are claened up
|
||||
// This covers the case any time a fragment or component shows up with pretty much anything else
|
||||
(
|
||||
VNode::Component(_) | VNode::Fragment(_) | VNode::Text(_) | VNode::Element(_),
|
||||
VNode::Component(_) | VNode::Fragment(_) | VNode::Text(_) | VNode::Element(_),
|
||||
VNodeKind::Component(_)
|
||||
| VNodeKind::Fragment(_)
|
||||
| VNodeKind::Text(_)
|
||||
| VNodeKind::Element(_),
|
||||
VNodeKind::Component(_)
|
||||
| VNodeKind::Fragment(_)
|
||||
| VNodeKind::Text(_)
|
||||
| VNodeKind::Element(_),
|
||||
) => {
|
||||
// Choose the node to use as the placeholder for replacewith
|
||||
let back_node = match old_node {
|
||||
let back_node = match old_node.kind {
|
||||
// We special case these two types to avoid allocating the small-vecs
|
||||
VNode::Element(_) | VNode::Text(_) => old_node
|
||||
.get_mounted_id(&self.components)
|
||||
.expect("Element and text always have a real node"),
|
||||
VNodeKind::Element(_) | VNodeKind::Text(_) => old_node.dom_id.get(),
|
||||
|
||||
_ => {
|
||||
let mut old_iter = RealChildIterator::new(old_node, &self.components);
|
||||
@@ -314,8 +314,8 @@ where
|
||||
}
|
||||
|
||||
// TODO
|
||||
(VNode::Suspended { .. }, _) => todo!(),
|
||||
(_, VNode::Suspended { .. }) => todo!(),
|
||||
(VNodeKind::Suspended { .. }, _) => todo!(),
|
||||
(_, VNodeKind::Suspended { .. }) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -351,13 +351,14 @@ where
|
||||
// [... node]
|
||||
fn create(&mut self, node: &'bump VNode<'bump>) -> CreateMeta {
|
||||
log::warn!("Creating node! ... {:#?}", node);
|
||||
match node {
|
||||
VNode::Text(text) => {
|
||||
match &node.kind {
|
||||
VNodeKind::Text(text) => {
|
||||
let real_id = self.dom.create_text_node(text.text);
|
||||
text.dom_id.set(real_id);
|
||||
CreateMeta::new(text.is_static, 1)
|
||||
todo!()
|
||||
// text.dom_id.set(real_id);
|
||||
// CreateMeta::new(text.is_static, 1)
|
||||
}
|
||||
VNode::Element(el) => {
|
||||
VNodeKind::Element(el) => {
|
||||
// we have the potential to completely eliminate working on this node in the future(!)
|
||||
//
|
||||
// This can only be done if all of the elements properties (attrs, children, listeners, etc) are static
|
||||
@@ -366,14 +367,14 @@ where
|
||||
let mut is_static: bool = true;
|
||||
|
||||
let VElement {
|
||||
key,
|
||||
tag_name,
|
||||
listeners,
|
||||
attributes,
|
||||
children,
|
||||
namespace,
|
||||
dom_id,
|
||||
is_static: el_is_static,
|
||||
static_attrs,
|
||||
static_children,
|
||||
static_listeners,
|
||||
} = el;
|
||||
|
||||
let real_id = if let Some(namespace) = namespace {
|
||||
@@ -381,7 +382,7 @@ where
|
||||
} else {
|
||||
self.dom.create_element(tag_name, None)
|
||||
};
|
||||
dom_id.set(real_id);
|
||||
// dom_id.set(real_id);
|
||||
|
||||
listeners.iter().enumerate().for_each(|(idx, listener)| {
|
||||
listener.mounted_node.set(real_id);
|
||||
@@ -408,7 +409,7 @@ where
|
||||
//
|
||||
// TODO move over
|
||||
// if children.len() == 1 {
|
||||
// if let VNode::Text(text) = &children[0] {
|
||||
// if let VNodeKind::Text(text) = &children[0] {
|
||||
// self.dom.set_text(text.text);
|
||||
// return;
|
||||
// }
|
||||
@@ -428,11 +429,11 @@ where
|
||||
log::debug!("created a dynamic node {:#?}", node);
|
||||
}
|
||||
|
||||
el_is_static.set(is_static);
|
||||
// el_is_static.set(is_static);
|
||||
CreateMeta::new(is_static, 1)
|
||||
}
|
||||
|
||||
VNode::Component(vcomponent) => {
|
||||
VNodeKind::Component(vcomponent) => {
|
||||
log::debug!("Mounting a new component");
|
||||
let caller = vcomponent.caller.clone();
|
||||
|
||||
@@ -493,7 +494,7 @@ where
|
||||
// Fragments are the only nodes that can contain dynamic content (IE through curlies or iterators).
|
||||
// We can never ignore their contents, so the prescence of a fragment indicates that we need always diff them.
|
||||
// Fragments will just put all their nodes onto the stack after creation
|
||||
VNode::Fragment(frag) => {
|
||||
VNodeKind::Fragment(frag) => {
|
||||
let mut nodes_added = 0;
|
||||
for child in frag.children.iter().rev() {
|
||||
// different types of nodes will generate different amounts on the stack
|
||||
@@ -508,9 +509,10 @@ where
|
||||
CreateMeta::new(false, nodes_added)
|
||||
}
|
||||
|
||||
VNode::Suspended { real } => {
|
||||
let id = self.dom.create_placeholder();
|
||||
real.set(id);
|
||||
VNodeKind::Suspended => {
|
||||
todo!();
|
||||
// let id = self.dom.create_placeholder();
|
||||
// real.set(id);
|
||||
CreateMeta::new(false, 1)
|
||||
}
|
||||
}
|
||||
@@ -620,7 +622,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
||||
// The "fast" path is when the list of attributes name is identical and in the same order
|
||||
// With the Rsx and Html macros, this will almost always be the case
|
||||
'outer: for new_attr in new {
|
||||
if new_attr.is_volatile() {
|
||||
if new_attr.is_volatile {
|
||||
// self.dom.commit_traversal();
|
||||
self.dom
|
||||
.set_attribute(new_attr.name, new_attr.value, namespace);
|
||||
@@ -675,13 +677,13 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
||||
|
||||
if new.len() == 1 {
|
||||
match (&old.first(), &new[0]) {
|
||||
(Some(VNode::Text(old_vtext)), VNode::Text(new_vtext))
|
||||
if old_vtext.text == new_vtext.text =>
|
||||
{
|
||||
// Don't take this fast path...
|
||||
}
|
||||
// (Some(VNodeKind::Text(old_vtext)), VNodeKind::Text(new_vtext))
|
||||
// if old_vtext.text == new_vtext.text =>
|
||||
// {
|
||||
// // Don't take this fast path...
|
||||
// }
|
||||
|
||||
// (_, VNode::Text(text)) => {
|
||||
// (_, VNodeKind::Text(text)) => {
|
||||
// // self.dom.commit_traversal();
|
||||
// log::debug!("using optimized text set");
|
||||
// self.dom.set_text(text.text);
|
||||
@@ -701,15 +703,15 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_is_keyed = new[0].key().is_some();
|
||||
let old_is_keyed = old[0].key().is_some();
|
||||
let new_is_keyed = new[0].key.is_some();
|
||||
let old_is_keyed = old[0].key.is_some();
|
||||
|
||||
debug_assert!(
|
||||
new.iter().all(|n| n.key().is_some() == new_is_keyed),
|
||||
new.iter().all(|n| n.key.is_some() == new_is_keyed),
|
||||
"all siblings must be keyed or all siblings must be non-keyed"
|
||||
);
|
||||
debug_assert!(
|
||||
old.iter().all(|o| o.key().is_some() == old_is_keyed),
|
||||
old.iter().all(|o| o.key.is_some() == old_is_keyed),
|
||||
"all siblings must be keyed or all siblings must be non-keyed"
|
||||
);
|
||||
|
||||
@@ -754,7 +756,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
||||
let mut assert_unique_keys = |children: &'bump [VNode<'bump>]| {
|
||||
keys.clear();
|
||||
for child in children {
|
||||
let key = child.key();
|
||||
let key = child.key;
|
||||
debug_assert!(
|
||||
key.is_some(),
|
||||
"if any sibling is keyed, all siblings must be keyed"
|
||||
@@ -797,7 +799,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
||||
.iter()
|
||||
.rev()
|
||||
.zip(new[shared_prefix_count..].iter().rev())
|
||||
.take_while(|&(old, new)| old.key() == new.key())
|
||||
.take_while(|&(old, new)| old.key == new.key)
|
||||
.count();
|
||||
|
||||
let old_shared_suffix_start = old.len() - shared_suffix_count;
|
||||
@@ -1259,7 +1261,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
||||
pub fn remove_self_and_next_siblings(&self, old: &[VNode<'bump>]) {
|
||||
// debug_assert!(self.dom.traversal_is_committed());
|
||||
for child in old {
|
||||
if let VNode::Component(vcomp) = child {
|
||||
if let VNodeKind::Component(vcomp) = child.kind {
|
||||
// dom
|
||||
// .create_text_node("placeholder for vcomponent");
|
||||
|
||||
@@ -1328,23 +1330,23 @@ impl<'a> Iterator for RealChildIterator<'a> {
|
||||
|
||||
while returned_node.is_none() {
|
||||
if let Some((count, node)) = self.stack.last_mut() {
|
||||
match node {
|
||||
match &node.kind {
|
||||
// We can only exit our looping when we get "real" nodes
|
||||
// This includes fragments and components when they're empty (have a single root)
|
||||
VNode::Element(_) | VNode::Text(_) => {
|
||||
VNodeKind::Element(_) | VNodeKind::Text(_) => {
|
||||
// We've recursed INTO an element/text
|
||||
// We need to recurse *out* of it and move forward to the next
|
||||
should_pop = true;
|
||||
returned_node = node.get_mounted_id(&self.scopes);
|
||||
returned_node = Some(node.dom_id.get());
|
||||
}
|
||||
|
||||
// If we get a fragment we push the next child
|
||||
VNode::Fragment(frag) => {
|
||||
VNodeKind::Fragment(frag) => {
|
||||
let subcount = *count as usize;
|
||||
|
||||
if frag.children.len() == 0 {
|
||||
should_pop = true;
|
||||
returned_node = node.get_mounted_id(&self.scopes);
|
||||
returned_node = Some(node.dom_id.get());
|
||||
}
|
||||
|
||||
if subcount >= frag.children.len() {
|
||||
@@ -1355,11 +1357,11 @@ impl<'a> Iterator for RealChildIterator<'a> {
|
||||
}
|
||||
|
||||
// Immediately abort suspended nodes - can't do anything with them yet
|
||||
// VNode::Suspended => should_pop = true,
|
||||
VNode::Suspended { real } => todo!(),
|
||||
// VNodeKind::Suspended => should_pop = true,
|
||||
VNodeKind::Suspended => todo!(),
|
||||
|
||||
// For components, we load their root and push them onto the stack
|
||||
VNode::Component(sc) => {
|
||||
VNodeKind::Component(sc) => {
|
||||
let scope = self.scopes.try_get(sc.ass_scope.get().unwrap()).unwrap();
|
||||
|
||||
// Simply swap the current node on the stack with the root of the component
|
||||
|
||||
@@ -125,7 +125,6 @@ pub mod on {
|
||||
use std::{fmt::Debug, ops::Deref, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
innerlude::ElementBuilder,
|
||||
innerlude::NodeFactory,
|
||||
innerlude::{Attribute, Listener, RealDomNode, VNode},
|
||||
};
|
||||
|
||||
@@ -19,11 +19,9 @@ pub mod diff;
|
||||
pub mod error;
|
||||
pub mod events;
|
||||
pub mod hooklist;
|
||||
pub mod nodebuilder;
|
||||
pub mod nodes;
|
||||
pub mod scope;
|
||||
pub mod signals;
|
||||
pub mod styles;
|
||||
pub mod tasks;
|
||||
pub mod util;
|
||||
pub mod virtual_dom;
|
||||
@@ -36,7 +34,6 @@ pub(crate) mod innerlude {
|
||||
pub use crate::diff::*;
|
||||
pub use crate::error::*;
|
||||
pub use crate::events::*;
|
||||
pub use crate::nodebuilder::*;
|
||||
pub use crate::nodes::*;
|
||||
pub use crate::scope::*;
|
||||
pub use crate::serialize::*;
|
||||
@@ -46,8 +43,6 @@ pub(crate) mod innerlude {
|
||||
|
||||
pub type FC<P> = fn(Context<P>) -> VNode;
|
||||
|
||||
// Re-export the FC macro
|
||||
pub use crate::nodebuilder as builder;
|
||||
pub use dioxus_core_macro::{html, rsx};
|
||||
}
|
||||
|
||||
@@ -61,8 +56,8 @@ pub use crate::{
|
||||
pub mod prelude {
|
||||
pub use crate::component::{fc_to_builder, Fragment, Properties};
|
||||
pub use crate::context::Context;
|
||||
pub use crate::innerlude::DioxusElement;
|
||||
pub use crate::innerlude::{LazyNodes, NodeFactory, FC};
|
||||
pub use crate::nodebuilder::DioxusElement;
|
||||
pub use crate::nodes::VNode;
|
||||
pub use crate::VirtualDom;
|
||||
pub use dioxus_core_macro::{format_args_f, html, rsx, Props};
|
||||
|
||||
@@ -1,749 +1 @@
|
||||
//! Helpers for building virtual DOM VNodes.
|
||||
|
||||
use std::{
|
||||
any::Any,
|
||||
borrow::BorrowMut,
|
||||
cell::{Cell, RefCell},
|
||||
fmt::Arguments,
|
||||
intrinsics::transmute,
|
||||
marker::PhantomData,
|
||||
u128,
|
||||
};
|
||||
|
||||
use bumpalo::Bump;
|
||||
|
||||
use crate::{
|
||||
events::VirtualEvent,
|
||||
innerlude::{Properties, RealDomNode, Scope, VComponent, VFragment, VText, FC},
|
||||
nodes::{Attribute, Listener, NodeKey, VNode},
|
||||
};
|
||||
|
||||
/// A virtual DOM element builder.
|
||||
///
|
||||
/// Typically constructed with element-specific constructors, eg the `div`
|
||||
/// function for building `<div>` elements or the `button` function for building
|
||||
/// `<button>` elements.
|
||||
#[derive(Debug)]
|
||||
pub struct ElementBuilder<'a, 'b, Listeners, Attributes, Children>
|
||||
where
|
||||
Listeners: 'a + AsRef<[Listener<'a>]>,
|
||||
Attributes: 'a + AsRef<[Attribute<'a>]>,
|
||||
Children: 'a + AsRef<[VNode<'a>]>,
|
||||
{
|
||||
cx: &'b NodeFactory<'a>,
|
||||
key: NodeKey<'a>,
|
||||
tag_name: &'static str,
|
||||
listeners: Listeners,
|
||||
attributes: Attributes,
|
||||
children: Children,
|
||||
namespace: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl<'a, 'b>
|
||||
ElementBuilder<
|
||||
'a,
|
||||
'b,
|
||||
bumpalo::collections::Vec<'a, Listener<'a>>,
|
||||
bumpalo::collections::Vec<'a, Attribute<'a>>,
|
||||
bumpalo::collections::Vec<'a, VNode<'a>>,
|
||||
>
|
||||
{
|
||||
/// Create a new `ElementBuilder` for an element with the given tag name.
|
||||
///
|
||||
/// In general, only use this constructor if the tag is dynamic (i.e. you
|
||||
/// might build a `<div>` or you might build a `<span>` and you don't know
|
||||
/// until runtime). Prefer using the tag-specific constructors instead:
|
||||
/// `div(bump)` or `span(bump)`, etc.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use dioxus::{builder::*, bumpalo::Bump};
|
||||
///
|
||||
/// let b = Bump::new();
|
||||
///
|
||||
/// let tag_name = if flip_coin() {
|
||||
/// "div"
|
||||
/// } else {
|
||||
/// "span"
|
||||
/// };
|
||||
///
|
||||
/// let my_element_builder = ElementBuilder::new(&b, tag_name);
|
||||
/// # fn flip_coin() -> bool { true }
|
||||
/// ```
|
||||
pub fn new(cx: &'b NodeFactory<'a>, tag_name: &'static str) -> Self {
|
||||
let bump = cx.bump();
|
||||
ElementBuilder {
|
||||
cx,
|
||||
key: NodeKey::NONE,
|
||||
tag_name,
|
||||
listeners: bumpalo::collections::Vec::new_in(bump),
|
||||
attributes: bumpalo::collections::Vec::new_in(bump),
|
||||
children: bumpalo::collections::Vec::new_in(bump),
|
||||
namespace: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, Listeners, Attributes, Children>
|
||||
ElementBuilder<'a, 'b, Listeners, Attributes, Children>
|
||||
where
|
||||
Listeners: 'a + AsRef<[Listener<'a>]>,
|
||||
Attributes: 'a + AsRef<[Attribute<'a>]>,
|
||||
Children: 'a + AsRef<[VNode<'a>]>,
|
||||
{
|
||||
/// Set the listeners for this element.
|
||||
///
|
||||
/// You can use this method to customize the backing storage for listeners,
|
||||
/// for example to use a fixed-size array instead of the default
|
||||
/// dynamically-sized `bumpalo::collections::Vec`.
|
||||
///
|
||||
/// Any listeners already added to the builder will be overridden.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use dioxus::{builder::*, bumpalo::Bump};
|
||||
///
|
||||
/// let b = Bump::new();
|
||||
///
|
||||
/// // Create a `<div>` with a fixed-size array of two listeners.
|
||||
/// let my_div = div(&b)
|
||||
/// .listeners([
|
||||
/// on(&b, "click", |root, vdom, event| {
|
||||
/// // ...
|
||||
/// }),
|
||||
/// on(&b, "dblclick", |root, vdom, event| {
|
||||
/// // ...
|
||||
/// }),
|
||||
/// ])
|
||||
/// .finish();
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn listeners<L>(self, listeners: L) -> ElementBuilder<'a, 'b, L, Attributes, Children>
|
||||
where
|
||||
L: 'a + AsRef<[Listener<'a>]>,
|
||||
{
|
||||
ElementBuilder {
|
||||
cx: self.cx,
|
||||
key: self.key,
|
||||
tag_name: self.tag_name,
|
||||
listeners,
|
||||
attributes: self.attributes,
|
||||
children: self.children,
|
||||
namespace: self.namespace,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the attributes for this element.
|
||||
///
|
||||
/// You can use this method to customize the backing storage for attributes,
|
||||
/// for example to use a fixed-size array instead of the default
|
||||
/// dynamically-sized `bumpalo::collections::Vec`.
|
||||
///
|
||||
/// Any attributes already added to the builder will be overridden.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use dioxus::{builder::*, bumpalo::Bump, Attribute};
|
||||
///
|
||||
/// let b = Bump::new();
|
||||
///
|
||||
/// // Create a `<div>` with a fixed-size array of two attributes.
|
||||
/// let my_div = div(&b)
|
||||
/// .attributes([
|
||||
/// attr("id", "my-div"),
|
||||
/// attr("class", "notification"),
|
||||
/// ])
|
||||
/// .finish();
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn attributes<A>(self, attributes: A) -> ElementBuilder<'a, 'b, Listeners, A, Children>
|
||||
where
|
||||
A: 'a + AsRef<[Attribute<'a>]>,
|
||||
{
|
||||
ElementBuilder {
|
||||
cx: self.cx,
|
||||
key: self.key,
|
||||
tag_name: self.tag_name,
|
||||
listeners: self.listeners,
|
||||
attributes,
|
||||
children: self.children,
|
||||
namespace: self.namespace,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the children for this element.
|
||||
///
|
||||
/// You can use this method to customize the backing storage for children,
|
||||
/// for example to use a fixed-size array instead of the default
|
||||
/// dynamically-sized `bumpalo::collections::Vec`.
|
||||
///
|
||||
/// Any children already added to the builder will be overridden.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use dioxus::{builder::*, bumpalo::Bump};
|
||||
///
|
||||
/// let b = Bump::new();
|
||||
///
|
||||
/// // Create a `<div>` with a fixed-size array of two `<span>` children.
|
||||
/// let my_div = div(&b)
|
||||
/// .children([
|
||||
/// span(&b).finish(),
|
||||
/// span(&b).finish(),
|
||||
/// ])
|
||||
/// .finish();
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn children<C>(self, children: C) -> ElementBuilder<'a, 'b, Listeners, Attributes, C>
|
||||
where
|
||||
C: 'a + AsRef<[VNode<'a>]>,
|
||||
{
|
||||
ElementBuilder {
|
||||
cx: self.cx,
|
||||
key: self.key,
|
||||
tag_name: self.tag_name,
|
||||
listeners: self.listeners,
|
||||
attributes: self.attributes,
|
||||
children,
|
||||
namespace: self.namespace,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the namespace for this element.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use dioxus::{builder::*, bumpalo::Bump};
|
||||
///
|
||||
/// let b = Bump::new();
|
||||
///
|
||||
/// // Create a `<td>` tag with an xhtml namespace
|
||||
/// let my_td = td(&b)
|
||||
/// .namespace(Some("http://www.w3.org/1999/xhtml"))
|
||||
/// .finish();
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn namespace(self, namespace: Option<&'static str>) -> Self {
|
||||
ElementBuilder {
|
||||
cx: self.cx,
|
||||
key: self.key,
|
||||
tag_name: self.tag_name,
|
||||
listeners: self.listeners,
|
||||
attributes: self.attributes,
|
||||
children: self.children,
|
||||
namespace,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set this element's key.
|
||||
///
|
||||
/// When diffing sets of siblings, if an old sibling and new sibling share a
|
||||
/// key, then they will always reuse the same physical DOM VNode. This is
|
||||
/// important when using CSS animations, web components, third party JS, or
|
||||
/// anything else that makes the diffing implementation observable.
|
||||
///
|
||||
/// Do not use keys if such a scenario does not apply. Keyed diffing is
|
||||
/// generally more expensive than not, since it is putting greater
|
||||
/// constraints on the diffing algorithm.
|
||||
///
|
||||
/// # Invariants You Must Uphold
|
||||
///
|
||||
/// The key may not be `u32::MAX`, which is a reserved key value.
|
||||
///
|
||||
/// Keys must be unique among siblings.
|
||||
///
|
||||
/// All sibling VNodes must be keyed, or they must all not be keyed. You may
|
||||
/// not mix keyed and unkeyed siblings.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use dioxus::{builder::*, bumpalo::Bump};
|
||||
///
|
||||
/// let b = Bump::new();
|
||||
///
|
||||
/// let my_li = li(&b)
|
||||
/// .key(1337)
|
||||
/// .finish();
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn key(mut self, key: &'a str) -> Self {
|
||||
self.key = NodeKey(Some(key));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn key2(mut self, args: Arguments) -> Self {
|
||||
let key = match args.as_str() {
|
||||
Some(static_str) => static_str,
|
||||
None => {
|
||||
use bumpalo::core_alloc::fmt::Write;
|
||||
let mut s = bumpalo::collections::String::new_in(self.cx.bump());
|
||||
s.write_fmt(args).unwrap();
|
||||
s.into_bump_str()
|
||||
}
|
||||
};
|
||||
self.key = NodeKey(Some(key));
|
||||
self
|
||||
}
|
||||
|
||||
/// Create the virtual DOM VNode described by this builder.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use dioxus::{builder::*, bumpalo::Bump, VNode};
|
||||
///
|
||||
/// let b = Bump::new();
|
||||
///
|
||||
/// // Start with a builder...
|
||||
/// let builder: ElementBuilder<_, _, _> = div(&b);
|
||||
///
|
||||
/// // ...and finish it to create a virtual DOM VNode!
|
||||
/// let my_div: VNode = builder.finish();
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn finish(mut self) -> VNode<'a> {
|
||||
let bump = self.cx.bump();
|
||||
|
||||
let children: &'a Children = bump.alloc(self.children);
|
||||
let children: &'a [VNode<'a>] = children.as_ref();
|
||||
|
||||
let listeners: &'a Listeners = bump.alloc(self.listeners);
|
||||
let listeners: &'a [Listener<'a>] = listeners.as_ref();
|
||||
|
||||
for listener in listeners {
|
||||
// bump the context id forward
|
||||
let id = self.cx.listener_id.get();
|
||||
self.cx.listener_id.set(id + 1);
|
||||
|
||||
// Add this listener to the context list
|
||||
// This casts the listener to a self-referential pointer
|
||||
// This is okay because the bump arena is stable
|
||||
|
||||
// TODO: maybe not add it into CTX immediately
|
||||
let r = unsafe { std::mem::transmute::<&Listener<'a>, &Listener<'static>>(listener) };
|
||||
self.cx.scope_ref.listeners.borrow_mut().push((
|
||||
r.mounted_node as *const _ as *mut _,
|
||||
r.callback as *const _ as *mut _,
|
||||
));
|
||||
}
|
||||
|
||||
let attributes: &'a Attributes = bump.alloc(self.attributes);
|
||||
let attributes: &'a [Attribute<'a>] = attributes.as_ref();
|
||||
|
||||
VNode::element(
|
||||
bump,
|
||||
self.key,
|
||||
self.tag_name,
|
||||
listeners,
|
||||
attributes,
|
||||
children,
|
||||
self.namespace,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, Attributes, Children>
|
||||
ElementBuilder<'a, 'b, bumpalo::collections::Vec<'a, Listener<'a>>, Attributes, Children>
|
||||
where
|
||||
Attributes: 'a + AsRef<[Attribute<'a>]>,
|
||||
Children: 'a + AsRef<[VNode<'a>]>,
|
||||
{
|
||||
// / Add a new event listener to this element.
|
||||
// /
|
||||
// / The `event` string specifies which event will be listened for. The
|
||||
// / `callback` function is the function that will be invoked if the
|
||||
// / specified event occurs.
|
||||
// /
|
||||
// / # Example
|
||||
// /
|
||||
// / ```no_run
|
||||
// / use dioxus::{builder::*, bumpalo::Bump};
|
||||
// /
|
||||
// / let b = Bump::new();
|
||||
// /
|
||||
// / // A button that does something when clicked!
|
||||
// / let my_button = button(&b)
|
||||
// / .on("click", |event| {
|
||||
// / // ...
|
||||
// / })
|
||||
// / .finish();
|
||||
// / ```
|
||||
// pub fn on(self, event: &'static str, callback: impl FnMut(VirtualEvent) + 'a) -> Self {
|
||||
// let bump = &self.cx.bump();
|
||||
// let listener = Listener {
|
||||
// event,
|
||||
// callback: bump.alloc(callback),
|
||||
// scope: self.cx.scope_ref.arena_idx,
|
||||
// mounted_node: bump.alloc(Cell::new(RealDomNode::empty())),
|
||||
// };
|
||||
// self.add_listener(listener)
|
||||
// }
|
||||
|
||||
// pub fn add_listener(mut self, listener: Listener<'a>) -> Self {
|
||||
// self.listeners.push(listener);
|
||||
|
||||
// // bump the context id forward
|
||||
// let id = self.cx.listener_id.get();
|
||||
// self.cx.listener_id.set(id + 1);
|
||||
|
||||
// // Add this listener to the context list
|
||||
// // This casts the listener to a self-referential pointer
|
||||
// // This is okay because the bump arena is stable
|
||||
// self.listeners.last().map(|g| {
|
||||
// let r = unsafe { std::mem::transmute::<&Listener<'a>, &Listener<'static>>(g) };
|
||||
// self.cx.scope_ref.listeners.borrow_mut().push((
|
||||
// r.mounted_node as *const _ as *mut _,
|
||||
// r.callback as *const _ as *mut _,
|
||||
// ));
|
||||
// });
|
||||
|
||||
// self
|
||||
// }
|
||||
}
|
||||
|
||||
impl<'a, 'b, Listeners, Children>
|
||||
ElementBuilder<'a, 'b, Listeners, bumpalo::collections::Vec<'a, Attribute<'a>>, Children>
|
||||
where
|
||||
Listeners: 'a + AsRef<[Listener<'a>]>,
|
||||
Children: 'a + AsRef<[VNode<'a>]>,
|
||||
{
|
||||
/// Add a new attribute to this element.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use dioxus::{builder::*, bumpalo::Bump};
|
||||
///
|
||||
/// let b = Bump::new();
|
||||
///
|
||||
/// // Create the `<div id="my-div"/>` element.
|
||||
/// let my_div = div(&b).attr("id", "my-div").finish();
|
||||
/// ```
|
||||
pub fn attr(mut self, name: &'static str, args: std::fmt::Arguments) -> Self {
|
||||
let (value, is_static) = raw_text(self.cx.bump(), args);
|
||||
|
||||
self.attributes.push(Attribute {
|
||||
name,
|
||||
value,
|
||||
is_static,
|
||||
namespace: None,
|
||||
});
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, Listeners, Attributes>
|
||||
ElementBuilder<'a, 'b, Listeners, Attributes, bumpalo::collections::Vec<'a, VNode<'a>>>
|
||||
where
|
||||
Listeners: 'a + AsRef<[Listener<'a>]>,
|
||||
Attributes: 'a + AsRef<[Attribute<'a>]>,
|
||||
{
|
||||
/// Add a new child to this element.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use dioxus::{builder::*, bumpalo::Bump};
|
||||
/// use js_sys::Math;
|
||||
///
|
||||
/// let b = Bump::new();
|
||||
///
|
||||
/// // Create `<p><span></span></p>`.
|
||||
/// let my_div = p(&b)
|
||||
/// .child(span(&b).finish())
|
||||
/// .finish();
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn child(mut self, child: VNode<'a>) -> Self {
|
||||
self.children.push(child);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add multiple children to this element from an iterator.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use dioxus::{builder::*, bumpalo::Bump};
|
||||
///
|
||||
/// let b = Bump::new();
|
||||
///
|
||||
/// let my_div = p(&b)
|
||||
/// .iter_child((0..10).map(|f| span(&b).finish())
|
||||
/// .finish();
|
||||
/// ```
|
||||
pub fn iter_child(mut self, nodes: impl IntoIterator<Item = impl IntoVNode<'a>>) -> Self {
|
||||
todo!();
|
||||
let len_before = self.children.len();
|
||||
for item in nodes {
|
||||
todo!()
|
||||
// let child = item.into_vnode(&self.cx);
|
||||
// self.children.push(child);
|
||||
}
|
||||
if cfg!(debug_assertions) {
|
||||
if self.children.len() > len_before + 1 {
|
||||
if self.children.last().unwrap().key().is_none() {
|
||||
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.
|
||||
---
|
||||
To help you identify where this error is coming from, we've generated a backtrace.
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for VNode<'a> {
|
||||
type Item = VNode<'a>;
|
||||
type IntoIter = std::iter::Once<Self::Item>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
std::iter::once(self)
|
||||
}
|
||||
}
|
||||
impl<'a> IntoVNode<'a> for VNode<'a> {
|
||||
fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoVNode<'a> for &VNode<'a> {
|
||||
fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoVNode<'a> {
|
||||
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>;
|
||||
}
|
||||
|
||||
// Wrap the the node-builder closure in a concrete type.
|
||||
// ---
|
||||
// This is a bit of a hack to implement the IntoVNode trait for closure types.
|
||||
pub struct LazyNodes<'a, G>
|
||||
where
|
||||
G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||
{
|
||||
inner: G,
|
||||
_p: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a, G> LazyNodes<'a, G>
|
||||
where
|
||||
G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||
{
|
||||
pub fn new(f: G) -> Self {
|
||||
Self {
|
||||
inner: f,
|
||||
_p: PhantomData {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cover the cases where nodes are used by macro.
|
||||
// Likely used directly.
|
||||
// ---
|
||||
// let nodes = rsx!{ ... };
|
||||
// rsx! { {nodes } }
|
||||
impl<'a, G> IntoVNode<'a> for LazyNodes<'a, G>
|
||||
where
|
||||
G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||
{
|
||||
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
|
||||
(self.inner)(cx)
|
||||
}
|
||||
}
|
||||
|
||||
// Required because anything that enters brackets in the rsx! macro needs to implement IntoIterator
|
||||
impl<'a, G> IntoIterator for LazyNodes<'a, G>
|
||||
where
|
||||
G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||
{
|
||||
type Item = Self;
|
||||
type IntoIter = std::iter::Once<Self::Item>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
std::iter::once(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoVNode<'_> for () {
|
||||
fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> {
|
||||
cx.fragment_from_iter(None as Option<VNode>)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoVNode<'_> for Option<()> {
|
||||
fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> {
|
||||
cx.fragment_from_iter(None as Option<VNode>)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn raw_text<'a>(bump: &'a bumpalo::Bump, args: std::fmt::Arguments) -> (&'a str, bool) {
|
||||
match args.as_str() {
|
||||
Some(static_str) => (static_str, true),
|
||||
None => {
|
||||
use bumpalo::core_alloc::fmt::Write;
|
||||
let mut s = bumpalo::collections::String::new_in(bump);
|
||||
s.write_fmt(args).unwrap();
|
||||
(s.into_bump_str(), false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn virtual_child<'a, T: Properties + 'a>(
|
||||
cx: NodeFactory<'a>,
|
||||
f: FC<T>,
|
||||
props: T,
|
||||
key: Option<&'a str>, // key: NodeKey<'a>,
|
||||
children: &'a [VNode<'a>],
|
||||
) -> VNode<'a> {
|
||||
// currently concerned about if props have a custom drop implementation
|
||||
// might override it with the props macro
|
||||
// todo!()
|
||||
VNode::Component(
|
||||
cx.bump()
|
||||
.alloc(crate::nodes::VComponent::new(&cx, f, props, key, children)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn vfragment<'a>(
|
||||
cx: NodeFactory<'a>,
|
||||
key: Option<&'a str>, // key: NodeKey<'a>,
|
||||
children: &'a [VNode<'a>],
|
||||
) -> VNode<'a> {
|
||||
VNode::Fragment(cx.bump().alloc(VFragment::new(key, children)))
|
||||
}
|
||||
|
||||
/// This struct provides an ergonomic API to quickly build VNodes.
|
||||
///
|
||||
/// NodeFactory is used to build VNodes in the component's memory space.
|
||||
/// This struct adds metadata to the final VNode about listeners, attributes, and children
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct NodeFactory<'a> {
|
||||
pub scope_ref: &'a Scope,
|
||||
pub listener_id: &'a Cell<usize>,
|
||||
}
|
||||
|
||||
impl<'a> NodeFactory<'a> {
|
||||
#[inline]
|
||||
pub fn bump(&self) -> &'a bumpalo::Bump {
|
||||
&self.scope_ref.cur_frame().bump
|
||||
}
|
||||
|
||||
/// Create some text that's allocated along with the other vnodes
|
||||
pub fn text(&self, args: Arguments) -> VNode<'a> {
|
||||
VNode::text(self.bump(), args)
|
||||
}
|
||||
|
||||
/// Create an element builder
|
||||
pub fn raw_element<'b>(
|
||||
&'b self,
|
||||
tag: &'static str,
|
||||
) -> ElementBuilder<
|
||||
'a,
|
||||
'b,
|
||||
bumpalo::collections::Vec<'a, Listener<'a>>,
|
||||
bumpalo::collections::Vec<'a, Attribute<'a>>,
|
||||
bumpalo::collections::Vec<'a, VNode<'a>>,
|
||||
> {
|
||||
ElementBuilder::new(self, tag)
|
||||
}
|
||||
|
||||
/// Create an element builder
|
||||
pub fn element<'b>(
|
||||
&'b self,
|
||||
tag: impl DioxusElement,
|
||||
) -> ElementBuilder<
|
||||
'a,
|
||||
'b,
|
||||
bumpalo::collections::Vec<'a, Listener<'a>>,
|
||||
bumpalo::collections::Vec<'a, Attribute<'a>>,
|
||||
bumpalo::collections::Vec<'a, VNode<'a>>,
|
||||
> {
|
||||
ElementBuilder::new(self, tag.tag_name())
|
||||
}
|
||||
|
||||
pub fn attr(
|
||||
&self,
|
||||
name: &'static str,
|
||||
val: Arguments,
|
||||
namespace: Option<&'static str>,
|
||||
) -> Attribute<'a> {
|
||||
let (value, is_static) = raw_text(self.bump(), val);
|
||||
Attribute {
|
||||
name,
|
||||
value,
|
||||
is_static,
|
||||
namespace,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fragment(&self, children: &'a [VNode<'a>], key: Option<&'a str>) -> VNode<'a> {
|
||||
VNode::Fragment(self.bump().alloc(VFragment {
|
||||
children,
|
||||
key: NodeKey::new_opt(key),
|
||||
void_root: Cell::new(None),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn virtual_child<T, C>(
|
||||
&self,
|
||||
f: FC<T>,
|
||||
props: T,
|
||||
key: Option<&'a str>, // key: NodeKey<'a>,
|
||||
children: C,
|
||||
) -> VNode<'a>
|
||||
where
|
||||
C: 'a + AsRef<[VNode<'a>]>,
|
||||
T: Properties + 'a,
|
||||
{
|
||||
let children: &'a C = self.bump().alloc(children);
|
||||
VNode::Component(self.bump().alloc(crate::nodes::VComponent::new(
|
||||
self,
|
||||
f,
|
||||
props,
|
||||
key,
|
||||
children.as_ref(),
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn fragment_from_iter(
|
||||
self,
|
||||
node_iter: impl IntoIterator<Item = impl IntoVNode<'a>>,
|
||||
) -> VNode<'a> {
|
||||
let mut nodes = bumpalo::collections::Vec::new_in(self.bump());
|
||||
for node in node_iter.into_iter() {
|
||||
nodes.push(node.into_vnode(self));
|
||||
}
|
||||
VNode::Fragment(
|
||||
self.bump()
|
||||
.alloc(VFragment::new(None, nodes.into_bump_slice())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
use std::fmt::Debug;
|
||||
impl Debug for NodeFactory<'_> {
|
||||
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DioxusElement {
|
||||
const TAG_NAME: &'static str;
|
||||
const NAME_SPACE: Option<&'static str>;
|
||||
fn tag_name(&self) -> &'static str {
|
||||
Self::TAG_NAME
|
||||
}
|
||||
}
|
||||
+318
-392
@@ -4,225 +4,66 @@
|
||||
//! These VNodes should be *very* cheap and *very* fast to construct - building a full tree should be insanely quick.
|
||||
|
||||
use crate::{
|
||||
arena::SharedArena,
|
||||
events::VirtualEvent,
|
||||
innerlude::{Context, Properties, RealDom, RealDomNode, Scope, ScopeIdx, FC},
|
||||
nodebuilder::NodeFactory,
|
||||
};
|
||||
use appendlist::AppendList;
|
||||
use bumpalo::Bump;
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
fmt::{Arguments, Debug, Formatter},
|
||||
marker::PhantomData,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
pub struct VNode<'src> {
|
||||
pub kind: VNodeKind<'src>,
|
||||
pub dom_id: Cell<RealDomNode>,
|
||||
pub key: Option<&'src str>,
|
||||
}
|
||||
|
||||
/// Tools for the base unit of the virtual dom - the VNode
|
||||
/// VNodes are intended to be quickly-allocated, lightweight enum values.
|
||||
///
|
||||
/// Components will be generating a lot of these very quickly, so we want to
|
||||
/// limit the amount of heap allocations / overly large enum sizes.
|
||||
pub enum VNode<'src> {
|
||||
/// An element node (node type `ELEMENT_NODE`).
|
||||
Element(&'src VElement<'src>),
|
||||
|
||||
/// A text node (node type `TEXT_NODE`).
|
||||
pub enum VNodeKind<'src> {
|
||||
Text(VText<'src>),
|
||||
|
||||
/// A fragment is a list of elements that might have a dynamic order.
|
||||
/// Normally, children will have a fixed order. However, Fragments allow a dynamic order and must be diffed differently.
|
||||
///
|
||||
/// Fragments don't have a single mount into the dom, so their position is characterized by the head and tail nodes.
|
||||
///
|
||||
/// Fragments may have children and keys
|
||||
Fragment(&'src VFragment<'src>),
|
||||
|
||||
/// A "suspended component"
|
||||
/// This is a masqeurade over an underlying future that needs to complete
|
||||
/// When the future is completed, the VNode will then trigger a render and the `real` field gets populated
|
||||
Suspended { real: Cell<RealDomNode> },
|
||||
|
||||
/// A User-defined componen node (node type COMPONENT_NODE)
|
||||
Element(&'src VElement<'src>),
|
||||
Fragment(VFragment<'src>),
|
||||
Component(&'src VComponent<'src>),
|
||||
Suspended,
|
||||
}
|
||||
|
||||
// it's okay to clone because vnodes are just references to places into the bump
|
||||
impl<'a> Clone for VNode<'a> {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
VNode::Element(element) => VNode::Element(element),
|
||||
VNode::Text(old) => VNode::Text(old.clone()),
|
||||
VNode::Fragment(fragment) => VNode::Fragment(fragment),
|
||||
VNode::Component(component) => VNode::Component(component),
|
||||
VNode::Suspended { real } => VNode::Suspended { real: real.clone() },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'old, 'new> VNode<'old> {
|
||||
// performs a somewhat costly clone of this vnode into another bump
|
||||
// this is used when you want to drag nodes from an old frame into a new frame
|
||||
// There is no way to safely drag listeners over (no way to clone a closure)
|
||||
//
|
||||
// This method will only be called if a component was once a real node and then becomes suspended
|
||||
fn deep_clone_to_new_bump(&self, new: &'new Bump) -> VNode<'new> {
|
||||
match self {
|
||||
VNode::Element(el) => {
|
||||
let new_el: VElement<'new> = VElement {
|
||||
key: NodeKey::NONE,
|
||||
// key: el.key.clone(),
|
||||
tag_name: el.tag_name,
|
||||
// wipe listeners on deep clone, there's no way to know what other bump material they might be referencing (nodes, etc)
|
||||
listeners: &[],
|
||||
attributes: {
|
||||
let attr_vec = bumpalo::collections::Vec::new_in(new);
|
||||
attr_vec.into_bump_slice()
|
||||
},
|
||||
children: {
|
||||
let attr_vec = bumpalo::collections::Vec::new_in(new);
|
||||
attr_vec.into_bump_slice()
|
||||
},
|
||||
namespace: el.namespace.clone(),
|
||||
dom_id: el.dom_id.clone(),
|
||||
is_static: el.is_static.clone(),
|
||||
};
|
||||
|
||||
VNode::Element(new.alloc_with(move || new_el))
|
||||
}
|
||||
VNode::Text(_) => todo!(),
|
||||
VNode::Fragment(_) => todo!(),
|
||||
VNode::Suspended { real } => todo!(),
|
||||
VNode::Component(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> VNode<'a> {
|
||||
/// Low-level constructor for making a new `Node` of type element with given
|
||||
/// parts.
|
||||
///
|
||||
/// This is primarily intended for JSX and templating proc-macros to compile
|
||||
/// down into. If you are building nodes by-hand, prefer using the
|
||||
/// `dodrio::builder::*` APIs.
|
||||
#[inline]
|
||||
pub fn element(
|
||||
bump: &'a Bump,
|
||||
key: NodeKey<'a>,
|
||||
tag_name: &'static str,
|
||||
listeners: &'a [Listener<'a>],
|
||||
attributes: &'a [Attribute<'a>],
|
||||
children: &'a [VNode<'a>],
|
||||
namespace: Option<&'static str>,
|
||||
) -> VNode<'a> {
|
||||
let element = bump.alloc_with(|| VElement {
|
||||
key,
|
||||
tag_name,
|
||||
listeners,
|
||||
attributes,
|
||||
children,
|
||||
namespace,
|
||||
dom_id: Cell::new(RealDomNode::empty()),
|
||||
is_static: Cell::new(false),
|
||||
});
|
||||
VNode::Element(element)
|
||||
}
|
||||
|
||||
pub fn static_text(text: &'static str) -> VNode {
|
||||
VNode::Text(VText {
|
||||
text,
|
||||
is_static: true,
|
||||
dom_id: Cell::new(RealDomNode::empty()),
|
||||
})
|
||||
}
|
||||
/// Construct a new text node with the given text.
|
||||
pub fn text(bump: &'a Bump, args: Arguments) -> VNode<'a> {
|
||||
match args.as_str() {
|
||||
Some(text) => VNode::static_text(text),
|
||||
None => {
|
||||
use bumpalo::core_alloc::fmt::Write;
|
||||
let mut s = bumpalo::collections::String::new_in(bump);
|
||||
s.write_fmt(args).unwrap();
|
||||
VNode::Text(VText {
|
||||
text: s.into_bump_str(),
|
||||
is_static: false,
|
||||
dom_id: Cell::new(RealDomNode::empty()),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn key(&self) -> NodeKey {
|
||||
match &self {
|
||||
VNode::Text { .. } => NodeKey::NONE,
|
||||
VNode::Element(e) => e.key,
|
||||
VNode::Fragment(frag) => frag.key,
|
||||
VNode::Component(c) => c.key,
|
||||
|
||||
// todo suspend should be allowed to have keys
|
||||
VNode::Suspended { .. } => NodeKey::NONE,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_child(&self, id: u32) -> Option<&'a VNode<'a>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn is_real(&self) -> bool {
|
||||
match self {
|
||||
VNode::Element(_) => true,
|
||||
VNode::Text(_) => true,
|
||||
VNode::Fragment(_) => false,
|
||||
VNode::Suspended { .. } => false,
|
||||
VNode::Component(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mounted_id(&self, components: &SharedArena) -> Option<RealDomNode> {
|
||||
match self {
|
||||
VNode::Element(el) => Some(el.dom_id.get()),
|
||||
VNode::Text(te) => Some(te.dom_id.get()),
|
||||
VNode::Fragment(frag) => frag.void_root.get(),
|
||||
VNode::Suspended { .. } => todo!(),
|
||||
VNode::Component(el) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for VNode<'_> {
|
||||
fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
match self {
|
||||
VNode::Element(el) => write!(s, "element, {}", el.tag_name),
|
||||
VNode::Text(t) => write!(s, "text, {}", t.text),
|
||||
VNode::Fragment(_) => write!(s, "fragment"),
|
||||
VNode::Suspended { .. } => write!(s, "suspended"),
|
||||
VNode::Component(_) => write!(s, "component"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VText<'src> {
|
||||
pub text: &'src str,
|
||||
pub is_static: bool,
|
||||
pub dom_id: Cell<RealDomNode>,
|
||||
}
|
||||
|
||||
// ========================================================
|
||||
// VElement (div, h1, etc), attrs, keys, listener handle
|
||||
// ========================================================
|
||||
pub struct VFragment<'src> {
|
||||
pub children: &'src [VNode<'src>],
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub trait DioxusElement {
|
||||
const TAG_NAME: &'static str;
|
||||
const NAME_SPACE: Option<&'static str>;
|
||||
fn tag_name(&self) -> &'static str {
|
||||
Self::TAG_NAME
|
||||
}
|
||||
}
|
||||
pub struct VElement<'a> {
|
||||
/// Elements have a tag name, zero or more attributes, and zero or more
|
||||
pub key: NodeKey<'a>,
|
||||
// tag is always static
|
||||
pub tag_name: &'static str,
|
||||
|
||||
pub static_listeners: bool,
|
||||
pub listeners: &'a [Listener<'a>],
|
||||
|
||||
pub static_attrs: bool,
|
||||
pub attributes: &'a [Attribute<'a>],
|
||||
|
||||
pub static_children: bool,
|
||||
pub children: &'a [VNode<'a>],
|
||||
|
||||
// namespace is always static
|
||||
pub namespace: Option<&'static str>,
|
||||
pub dom_id: Cell<RealDomNode>,
|
||||
pub is_static: Cell<bool>,
|
||||
}
|
||||
|
||||
/// An attribute on a DOM node, such as `id="my-thing"` or
|
||||
@@ -232,175 +73,141 @@ pub struct Attribute<'a> {
|
||||
pub name: &'static str,
|
||||
pub value: &'a str,
|
||||
pub is_static: bool,
|
||||
|
||||
/// If an attribute is "namespaced", then it belongs to a group
|
||||
/// The most common namespace is the "style" namespace
|
||||
// pub is_dynamic: bool,
|
||||
pub is_volatile: bool,
|
||||
// Doesn't exist in the html spec, mostly used to denote "style" tags - could be for any type of group
|
||||
pub namespace: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl<'a> Attribute<'a> {
|
||||
/// Get this attribute's name, such as `"id"` in `<div id="my-thing" />`.
|
||||
#[inline]
|
||||
pub fn name(&self) -> &'a str {
|
||||
self.name
|
||||
}
|
||||
|
||||
/// The attribute value, such as `"my-thing"` in `<div id="my-thing" />`.
|
||||
#[inline]
|
||||
pub fn value(&self) -> &'a str {
|
||||
self.value
|
||||
}
|
||||
|
||||
/// Certain attributes are considered "volatile" and can change via user
|
||||
/// input that we can't see when diffing against the old virtual DOM. For
|
||||
/// these attributes, we want to always re-set the attribute on the physical
|
||||
/// DOM node, even if the old and new virtual DOM nodes have the same value.
|
||||
#[inline]
|
||||
pub(crate) fn is_volatile(&self) -> bool {
|
||||
match self.name {
|
||||
"value" | "checked" | "selected" => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ListenerHandle {
|
||||
pub event: &'static str,
|
||||
pub scope: ScopeIdx,
|
||||
pub id: usize,
|
||||
}
|
||||
|
||||
/// An event listener.
|
||||
/// IE onclick, onkeydown, etc
|
||||
pub struct Listener<'bump> {
|
||||
/// The type of event to listen for.
|
||||
pub(crate) event: &'static str,
|
||||
|
||||
/// Which scope?
|
||||
/// This might not actually be relevant
|
||||
pub scope: ScopeIdx,
|
||||
|
||||
pub mounted_node: &'bump Cell<RealDomNode>,
|
||||
|
||||
/// The callback to invoke when the event happens.
|
||||
pub(crate) callback: &'bump dyn FnMut(VirtualEvent),
|
||||
}
|
||||
|
||||
/// The key for keyed children.
|
||||
///
|
||||
/// Keys must be unique among siblings.
|
||||
///
|
||||
/// If any sibling is keyed, then they all must be keyed.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct NodeKey<'a>(pub(crate) Option<&'a str>);
|
||||
|
||||
impl<'a> Default for NodeKey<'a> {
|
||||
fn default() -> NodeKey<'a> {
|
||||
NodeKey::NONE
|
||||
}
|
||||
}
|
||||
impl<'a> NodeKey<'a> {
|
||||
/// The default, lack of a key.
|
||||
pub const NONE: NodeKey<'a> = NodeKey(None);
|
||||
|
||||
/// Is this key `NodeKey::NONE`?
|
||||
#[inline]
|
||||
pub fn is_none(&self) -> bool {
|
||||
*self == Self::NONE
|
||||
}
|
||||
|
||||
/// Is this key not `NodeKey::NONE`?
|
||||
#[inline]
|
||||
pub fn is_some(&self) -> bool {
|
||||
!self.is_none()
|
||||
}
|
||||
|
||||
/// Create a new `NodeKey`.
|
||||
///
|
||||
/// `key` must not be `u32::MAX`.
|
||||
#[inline]
|
||||
pub fn new(key: &'a str) -> Self {
|
||||
NodeKey(Some(key))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_opt(key: Option<&'a str>) -> Self {
|
||||
NodeKey(key)
|
||||
}
|
||||
}
|
||||
|
||||
// ==============================
|
||||
// Custom components
|
||||
// ==============================
|
||||
|
||||
/// Virtual Components for custom user-defined components
|
||||
/// Only supports the functional syntax
|
||||
pub type StableScopeAddres = Option<u32>;
|
||||
pub type VCompAssociatedScope = Option<ScopeIdx>;
|
||||
|
||||
pub struct VComponent<'src> {
|
||||
pub key: NodeKey<'src>,
|
||||
|
||||
// pub void_root: Cell<Option<RealDomNode>>,
|
||||
pub ass_scope: Cell<VCompAssociatedScope>,
|
||||
|
||||
// todo: swap the RC out with
|
||||
pub ass_scope: Cell<Option<ScopeIdx>>,
|
||||
pub caller: Rc<dyn Fn(&Scope) -> VNode>,
|
||||
|
||||
pub children: &'src [VNode<'src>],
|
||||
|
||||
pub comparator: Option<&'src dyn Fn(&VComponent) -> bool>,
|
||||
|
||||
pub is_static: bool,
|
||||
|
||||
// a pointer into the bump arena (given by the 'src lifetime)
|
||||
// raw_props: Box<dyn Any>,
|
||||
raw_props: *const (),
|
||||
|
||||
// a pointer to the raw fn typ
|
||||
pub user_fc: *const (),
|
||||
pub is_static: bool,
|
||||
}
|
||||
|
||||
impl<'a> VComponent<'a> {
|
||||
/// When the rsx! macro is called, it will check if the CanMemo flag is set to true (from the Props impl)
|
||||
/// If it is set to true, then this method will be called which implements automatic memoization.
|
||||
///
|
||||
/// If the CanMemo is `false`, then the macro will call the backup method which always defaults to "false"
|
||||
pub fn new<P: Properties + 'a>(
|
||||
cx: &NodeFactory<'a>,
|
||||
/// This struct provides an ergonomic API to quickly build VNodes.
|
||||
///
|
||||
/// NodeFactory is used to build VNodes in the component's memory space.
|
||||
/// This struct adds metadata to the final VNode about listeners, attributes, and children
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct NodeFactory<'a> {
|
||||
pub scope_ref: &'a Scope,
|
||||
pub listener_id: &'a Cell<usize>,
|
||||
}
|
||||
|
||||
impl<'a> NodeFactory<'a> {
|
||||
#[inline]
|
||||
pub fn bump(&self) -> &'a bumpalo::Bump {
|
||||
&self.scope_ref.cur_frame().bump
|
||||
}
|
||||
|
||||
pub fn static_text(text: &'static str) -> VNode {
|
||||
VNode {
|
||||
dom_id: RealDomNode::empty_cell(),
|
||||
key: None,
|
||||
kind: VNodeKind::Text(VText {
|
||||
text,
|
||||
is_static: true,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn raw_text(&self, args: Arguments) -> (&'a str, bool) {
|
||||
match args.as_str() {
|
||||
Some(static_str) => (static_str, true),
|
||||
None => {
|
||||
use bumpalo::core_alloc::fmt::Write;
|
||||
let mut s = bumpalo::collections::String::new_in(self.bump());
|
||||
s.write_fmt(args).unwrap();
|
||||
(s.into_bump_str(), false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create some text that's allocated along with the other vnodes
|
||||
pub fn text(&self, args: Arguments) -> VNode<'a> {
|
||||
let (text, is_static) = self.raw_text(args);
|
||||
VNode {
|
||||
dom_id: RealDomNode::empty_cell(),
|
||||
key: None,
|
||||
kind: VNodeKind::Text(VText { text, is_static }),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn const_el(&self) {}
|
||||
|
||||
pub fn raw_element(
|
||||
&self,
|
||||
tag: &'static str,
|
||||
listeners: &[Listener],
|
||||
attributes: &[Attribute],
|
||||
children: &'a [VNode<'a>],
|
||||
) {
|
||||
}
|
||||
|
||||
pub fn element() {}
|
||||
|
||||
pub fn suspended() -> VNode<'static> {
|
||||
VNode {
|
||||
dom_id: RealDomNode::empty_cell(),
|
||||
key: None,
|
||||
kind: VNodeKind::Suspended,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attr(
|
||||
&self,
|
||||
name: &'static str,
|
||||
val: Arguments,
|
||||
namespace: Option<&'static str>,
|
||||
is_volatile: bool,
|
||||
) -> Attribute<'a> {
|
||||
let (value, is_static) = self.raw_text(val);
|
||||
Attribute {
|
||||
name,
|
||||
value,
|
||||
is_static,
|
||||
namespace,
|
||||
is_volatile,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn virtual_child<P, C>(
|
||||
&self,
|
||||
component: FC<P>,
|
||||
props: P,
|
||||
key: Option<&'a str>,
|
||||
key: Option<&'a str>, // key: NodeKey<'a>,
|
||||
children: &'a [VNode<'a>],
|
||||
) -> Self {
|
||||
let bump = cx.bump();
|
||||
let user_fc = component as *const ();
|
||||
|
||||
let props = bump.alloc(props);
|
||||
) -> VNode<'a>
|
||||
where
|
||||
P: Properties + 'a,
|
||||
{
|
||||
// We don't want the fat part of the fat pointer
|
||||
// This function does static dispatch so we don't need any VTable stuff
|
||||
let props = self.bump().alloc(props);
|
||||
let raw_props = props as *const P as *const ();
|
||||
|
||||
let comparator: Option<&dyn Fn(&VComponent) -> bool> = Some(bump.alloc_with(|| {
|
||||
let user_fc = component as *const ();
|
||||
|
||||
let comparator: Option<&dyn Fn(&VComponent) -> bool> = Some(self.bump().alloc_with(|| {
|
||||
move |other: &VComponent| {
|
||||
// Safety:
|
||||
// ------
|
||||
//
|
||||
// Invariants:
|
||||
// - Component function pointers are the same
|
||||
// - Generic properties on the same function pointer are the same
|
||||
// - Lifetime of P borrows from its parent
|
||||
// - The parent scope still exists when method is called
|
||||
// - Casting from T to *const () is portable
|
||||
// - Casting raw props to P can only happen when P is static
|
||||
//
|
||||
// Explanation:
|
||||
// We are guaranteed that the props will be of the same type because
|
||||
// there is no way to create a VComponent other than this `new` method.
|
||||
//
|
||||
// Therefore, if the render functions are identical (by address), then so will be
|
||||
// props type paramter (because it is the same render function). Therefore, we can be
|
||||
// sure that it is safe to interperet the previous props raw pointer as the same props
|
||||
// type. From there, we can call the props' "memoize" method to see if we can
|
||||
// avoid re-rendering the component.
|
||||
if user_fc == other.user_fc {
|
||||
let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
|
||||
let props_memoized = unsafe { props.memoize(&real_other) };
|
||||
@@ -414,81 +221,200 @@ impl<'a> VComponent<'a> {
|
||||
}
|
||||
}));
|
||||
|
||||
let key = match key {
|
||||
Some(key) => NodeKey::new(key),
|
||||
None => NodeKey(None),
|
||||
};
|
||||
|
||||
let caller = create_component_caller(component, raw_props);
|
||||
|
||||
// If the component does not have children, has no props (we can't memoize props), and has no no key, then we don't
|
||||
// need to bother diffing it in the future
|
||||
//
|
||||
// This is more of an optimization to prevent unnecessary descending through the tree during diffing, rather than
|
||||
// actually speeding up the diff process itself
|
||||
let is_static = children.len() == 0 && P::IS_STATIC && key.is_none();
|
||||
|
||||
Self {
|
||||
user_fc,
|
||||
comparator,
|
||||
raw_props,
|
||||
children,
|
||||
ass_scope: Cell::new(None),
|
||||
VNode {
|
||||
key,
|
||||
caller,
|
||||
is_static,
|
||||
// void_root: Cell::new(None),
|
||||
dom_id: Cell::new(RealDomNode::empty()),
|
||||
kind: VNodeKind::Component(self.bump().alloc_with(|| VComponent {
|
||||
user_fc,
|
||||
comparator,
|
||||
raw_props,
|
||||
children,
|
||||
caller: NodeFactory::create_component_caller(component, raw_props),
|
||||
is_static: children.len() == 0 && P::IS_STATIC && key.is_none(),
|
||||
ass_scope: Cell::new(None),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_component_caller<'g, P: 'g>(
|
||||
component: FC<P>,
|
||||
raw_props: *const (),
|
||||
) -> Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r>> {
|
||||
type Captured<'a> = Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r> + 'a>;
|
||||
let caller: Captured = Rc::new(move |scp: &Scope| -> VNode {
|
||||
// cast back into the right lifetime
|
||||
let safe_props: &'_ P = unsafe { &*(raw_props as *const P) };
|
||||
let tasks = RefCell::new(Vec::new());
|
||||
let cx: Context<P> = Context {
|
||||
props: safe_props,
|
||||
scope: scp,
|
||||
tasks: &tasks,
|
||||
};
|
||||
|
||||
let res = component(cx);
|
||||
|
||||
// submit any async tasks to the scope
|
||||
for task in tasks.borrow_mut().drain(..) {
|
||||
scp.submit_task(task);
|
||||
}
|
||||
|
||||
let g2 = unsafe { std::mem::transmute(res) };
|
||||
|
||||
g2
|
||||
});
|
||||
unsafe { std::mem::transmute::<_, Captured<'static>>(caller) }
|
||||
}
|
||||
|
||||
pub fn fragment_from_iter(
|
||||
self,
|
||||
node_iter: impl IntoIterator<Item = impl IntoVNode<'a>>,
|
||||
) -> VNode<'a> {
|
||||
let mut nodes = bumpalo::collections::Vec::new_in(self.bump());
|
||||
// TODO throw an error if there are nodes without keys
|
||||
for node in node_iter.into_iter() {
|
||||
nodes.push(node.into_vnode(self));
|
||||
}
|
||||
VNode {
|
||||
dom_id: RealDomNode::empty_cell(),
|
||||
key: None,
|
||||
kind: VNodeKind::Fragment(VFragment {
|
||||
children: nodes.into_bump_slice(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Captured<'a> = Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r> + 'a>;
|
||||
|
||||
pub fn create_component_caller<'a, P: 'a>(
|
||||
user_component: FC<P>,
|
||||
raw_props: *const (),
|
||||
) -> Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r>> {
|
||||
let g: Captured = Rc::new(move |scp: &Scope| -> VNode {
|
||||
// cast back into the right lifetime
|
||||
let safe_props: &'_ P = unsafe { &*(raw_props as *const P) };
|
||||
let tasks = RefCell::new(Vec::new());
|
||||
let cx: Context<P> = Context {
|
||||
props: safe_props,
|
||||
scope: scp,
|
||||
tasks: &tasks,
|
||||
};
|
||||
|
||||
let g = user_component(cx);
|
||||
|
||||
for task in tasks.borrow_mut().drain(..) {
|
||||
scp.submit_task(task);
|
||||
}
|
||||
|
||||
let g2 = unsafe { std::mem::transmute(g) };
|
||||
|
||||
g2
|
||||
});
|
||||
let r: Captured<'static> = unsafe { std::mem::transmute(g) };
|
||||
r
|
||||
impl<'a> IntoIterator for VNode<'a> {
|
||||
type Item = VNode<'a>;
|
||||
type IntoIter = std::iter::Once<Self::Item>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
std::iter::once(self)
|
||||
}
|
||||
}
|
||||
impl<'a> IntoVNode<'a> for VNode<'a> {
|
||||
fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VFragment<'src> {
|
||||
pub key: NodeKey<'src>,
|
||||
pub children: &'src [VNode<'src>],
|
||||
pub void_root: Cell<Option<RealDomNode>>,
|
||||
impl<'a> IntoVNode<'a> for &VNode<'a> {
|
||||
fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> VFragment<'a> {
|
||||
pub fn new(key: Option<&'a str>, children: &'a [VNode<'a>]) -> Self {
|
||||
let key = match key {
|
||||
Some(key) => NodeKey::new(key),
|
||||
None => NodeKey(None),
|
||||
};
|
||||
pub trait IntoVNode<'a> {
|
||||
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>;
|
||||
}
|
||||
|
||||
// Wrap the the node-builder closure in a concrete type.
|
||||
// ---
|
||||
// This is a bit of a hack to implement the IntoVNode trait for closure types.
|
||||
pub struct LazyNodes<'a, G>
|
||||
where
|
||||
G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||
{
|
||||
inner: G,
|
||||
_p: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a, G> LazyNodes<'a, G>
|
||||
where
|
||||
G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||
{
|
||||
pub fn new(f: G) -> Self {
|
||||
Self {
|
||||
key,
|
||||
children,
|
||||
void_root: Cell::new(None),
|
||||
inner: f,
|
||||
_p: PhantomData {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cover the cases where nodes are used by macro.
|
||||
// Likely used directly.
|
||||
// ---
|
||||
// let nodes = rsx!{ ... };
|
||||
// rsx! { {nodes } }
|
||||
impl<'a, G> IntoVNode<'a> for LazyNodes<'a, G>
|
||||
where
|
||||
G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||
{
|
||||
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
|
||||
(self.inner)(cx)
|
||||
}
|
||||
}
|
||||
|
||||
// Required because anything that enters brackets in the rsx! macro needs to implement IntoIterator
|
||||
impl<'a, G> IntoIterator for LazyNodes<'a, G>
|
||||
where
|
||||
G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||
{
|
||||
type Item = Self;
|
||||
type IntoIter = std::iter::Once<Self::Item>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
std::iter::once(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoVNode<'_> for () {
|
||||
fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> {
|
||||
cx.fragment_from_iter(None as Option<VNode>)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoVNode<'_> for Option<()> {
|
||||
fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> {
|
||||
cx.fragment_from_iter(None as Option<VNode>)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for NodeFactory<'_> {
|
||||
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// it's okay to clone because vnodes are just references to places into the bump
|
||||
impl<'a> Clone for VNode<'a> {
|
||||
fn clone(&self) -> Self {
|
||||
let kind = match &self.kind {
|
||||
VNodeKind::Element(element) => VNodeKind::Element(element),
|
||||
VNodeKind::Text(old) => VNodeKind::Text(VText {
|
||||
text: old.text,
|
||||
is_static: old.is_static,
|
||||
}),
|
||||
VNodeKind::Fragment(fragment) => VNodeKind::Fragment(VFragment {
|
||||
children: fragment.children,
|
||||
}),
|
||||
VNodeKind::Component(component) => VNodeKind::Component(component),
|
||||
VNodeKind::Suspended => VNodeKind::Suspended,
|
||||
};
|
||||
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for VNode<'_> {
|
||||
fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
match &self.kind {
|
||||
VNodeKind::Element(el) => write!(s, "element, {}", el.tag_name),
|
||||
VNodeKind::Text(t) => write!(s, "text, {}", t.text),
|
||||
VNodeKind::Fragment(_) => write!(s, "fragment"),
|
||||
VNodeKind::Suspended { .. } => write!(s, "suspended"),
|
||||
VNodeKind::Component(_) => write!(s, "component"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {}
|
||||
|
||||
#[test]
|
||||
fn sizing() {
|
||||
dbg!(std::mem::size_of::<VElement>());
|
||||
dbg!(std::mem::align_of::<VElement>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
/// Typically constructed with element-specific constructors, eg the `div`
|
||||
/// function for building `<div>` elements or the `button` function for building
|
||||
/// `<button>` elements.
|
||||
#[derive(Debug)]
|
||||
pub struct ElementBuilder<'a, 'b, Listeners, Attributes, Children>
|
||||
where
|
||||
Listeners: 'a + AsRef<[Listener<'a>]>,
|
||||
Attributes: 'a + AsRef<[Attribute<'a>]>,
|
||||
Children: 'a + AsRef<[VNode<'a>]>,
|
||||
{
|
||||
cx: &'b NodeFactory<'a>,
|
||||
key: NodeKey<'a>,
|
||||
tag_name: &'static str,
|
||||
listeners: Listeners,
|
||||
attributes: Attributes,
|
||||
children: Children,
|
||||
namespace: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl<'a, 'b>
|
||||
ElementBuilder<
|
||||
'a,
|
||||
'b,
|
||||
bumpalo::collections::Vec<'a, Listener<'a>>,
|
||||
bumpalo::collections::Vec<'a, Attribute<'a>>,
|
||||
bumpalo::collections::Vec<'a, VNode<'a>>,
|
||||
>
|
||||
{
|
||||
/// Create a new `ElementBuilder` for an element with the given tag name.
|
||||
///
|
||||
/// In general, only use this constructor if the tag is dynamic (i.e. you
|
||||
/// might build a `<div>` or you might build a `<span>` and you don't know
|
||||
/// until runtime). Prefer using the tag-specific constructors instead:
|
||||
/// `div(bump)` or `span(bump)`, etc.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use dioxus::{builder::*, bumpalo::Bump};
|
||||
///
|
||||
/// let b = Bump::new();
|
||||
///
|
||||
/// let tag_name = if flip_coin() {
|
||||
/// "div"
|
||||
/// } else {
|
||||
/// "span"
|
||||
/// };
|
||||
///
|
||||
/// let my_element_builder = ElementBuilder::new(&b, tag_name);
|
||||
/// # fn flip_coin() -> bool { true }
|
||||
/// ```
|
||||
pub fn new(cx: &'b NodeFactory<'a>, tag_name: &'static str) -> Self {
|
||||
let bump = cx.bump();
|
||||
ElementBuilder {
|
||||
cx,
|
||||
key: NodeKey::NONE,
|
||||
tag_name,
|
||||
listeners: bumpalo::collections::Vec::new_in(bump),
|
||||
attributes: bumpalo::collections::Vec::new_in(bump),
|
||||
children: bumpalo::collections::Vec::new_in(bump),
|
||||
namespace: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, Listeners, Attributes, Children>
|
||||
ElementBuilder<'a, 'b, Listeners, Attributes, Children>
|
||||
where
|
||||
Listeners: 'a + AsRef<[Listener<'a>]>,
|
||||
Attributes: 'a + AsRef<[Attribute<'a>]>,
|
||||
Children: 'a + AsRef<[VNode<'a>]>,
|
||||
{
|
||||
/// Set the listeners for this element.
|
||||
///
|
||||
/// You can use this method to customize the backing storage for listeners,
|
||||
/// for example to use a fixed-size array instead of the default
|
||||
/// dynamically-sized `bumpalo::collections::Vec`.
|
||||
///
|
||||
/// Any listeners already added to the builder will be overridden.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use dioxus::{builder::*, bumpalo::Bump};
|
||||
///
|
||||
/// let b = Bump::new();
|
||||
///
|
||||
/// // Create a `<div>` with a fixed-size array of two listeners.
|
||||
/// let my_div = div(&b)
|
||||
/// .listeners([
|
||||
/// on(&b, "click", |root, vdom, event| {
|
||||
/// // ...
|
||||
/// }),
|
||||
/// on(&b, "dblclick", |root, vdom, event| {
|
||||
/// // ...
|
||||
/// }),
|
||||
/// ])
|
||||
/// .finish();
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn listeners<L>(self, listeners: L) -> ElementBuilder<'a, 'b, L, Attributes, Children>
|
||||
where
|
||||
L: 'a + AsRef<[Listener<'a>]>,
|
||||
{
|
||||
ElementBuilder {
|
||||
cx: self.cx,
|
||||
key: self.key,
|
||||
tag_name: self.tag_name,
|
||||
listeners,
|
||||
attributes: self.attributes,
|
||||
children: self.children,
|
||||
namespace: self.namespace,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the attributes for this element.
|
||||
///
|
||||
/// You can use this method to customize the backing storage for attributes,
|
||||
/// for example to use a fixed-size array instead of the default
|
||||
/// dynamically-sized `bumpalo::collections::Vec`.
|
||||
///
|
||||
/// Any attributes already added to the builder will be overridden.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use dioxus::{builder::*, bumpalo::Bump, Attribute};
|
||||
///
|
||||
/// let b = Bump::new();
|
||||
///
|
||||
/// // Create a `<div>` with a fixed-size array of two attributes.
|
||||
/// let my_div = div(&b)
|
||||
/// .attributes([
|
||||
/// attr("id", "my-div"),
|
||||
/// attr("class", "notification"),
|
||||
/// ])
|
||||
/// .finish();
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn attributes<A>(self, attributes: A) -> ElementBuilder<'a, 'b, Listeners, A, Children>
|
||||
where
|
||||
A: 'a + AsRef<[Attribute<'a>]>,
|
||||
{
|
||||
ElementBuilder {
|
||||
cx: self.cx,
|
||||
key: self.key,
|
||||
tag_name: self.tag_name,
|
||||
listeners: self.listeners,
|
||||
attributes,
|
||||
children: self.children,
|
||||
namespace: self.namespace,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the children for this element.
|
||||
///
|
||||
/// You can use this method to customize the backing storage for children,
|
||||
/// for example to use a fixed-size array instead of the default
|
||||
/// dynamically-sized `bumpalo::collections::Vec`.
|
||||
///
|
||||
/// Any children already added to the builder will be overridden.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use dioxus::{builder::*, bumpalo::Bump};
|
||||
///
|
||||
/// let b = Bump::new();
|
||||
///
|
||||
/// // Create a `<div>` with a fixed-size array of two `<span>` children.
|
||||
/// let my_div = div(&b)
|
||||
/// .children([
|
||||
/// span(&b).finish(),
|
||||
/// span(&b).finish(),
|
||||
/// ])
|
||||
/// .finish();
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn children<C>(self, children: C) -> ElementBuilder<'a, 'b, Listeners, Attributes, C>
|
||||
where
|
||||
C: 'a + AsRef<[VNode<'a>]>,
|
||||
{
|
||||
ElementBuilder {
|
||||
cx: self.cx,
|
||||
key: self.key,
|
||||
tag_name: self.tag_name,
|
||||
listeners: self.listeners,
|
||||
attributes: self.attributes,
|
||||
children,
|
||||
namespace: self.namespace,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the namespace for this element.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use dioxus::{builder::*, bumpalo::Bump};
|
||||
///
|
||||
/// let b = Bump::new();
|
||||
///
|
||||
/// // Create a `<td>` tag with an xhtml namespace
|
||||
/// let my_td = td(&b)
|
||||
/// .namespace(Some("http://www.w3.org/1999/xhtml"))
|
||||
/// .finish();
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn namespace(self, namespace: Option<&'static str>) -> Self {
|
||||
ElementBuilder {
|
||||
cx: self.cx,
|
||||
key: self.key,
|
||||
tag_name: self.tag_name,
|
||||
listeners: self.listeners,
|
||||
attributes: self.attributes,
|
||||
children: self.children,
|
||||
namespace,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set this element's key.
|
||||
///
|
||||
/// When diffing sets of siblings, if an old sibling and new sibling share a
|
||||
/// key, then they will always reuse the same physical DOM VNode. This is
|
||||
/// important when using CSS animations, web components, third party JS, or
|
||||
/// anything else that makes the diffing implementation observable.
|
||||
///
|
||||
/// Do not use keys if such a scenario does not apply. Keyed diffing is
|
||||
/// generally more expensive than not, since it is putting greater
|
||||
/// constraints on the diffing algorithm.
|
||||
///
|
||||
/// # Invariants You Must Uphold
|
||||
///
|
||||
/// The key may not be `u32::MAX`, which is a reserved key value.
|
||||
///
|
||||
/// Keys must be unique among siblings.
|
||||
///
|
||||
/// All sibling VNodes must be keyed, or they must all not be keyed. You may
|
||||
/// not mix keyed and unkeyed siblings.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use dioxus::{builder::*, bumpalo::Bump};
|
||||
///
|
||||
/// let b = Bump::new();
|
||||
///
|
||||
/// let my_li = li(&b)
|
||||
/// .key(1337)
|
||||
/// .finish();
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn key(mut self, key: &'a str) -> Self {
|
||||
self.key = NodeKey(Some(key));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn key2(mut self, args: Arguments) -> Self {
|
||||
let key = match args.as_str() {
|
||||
Some(static_str) => static_str,
|
||||
None => {
|
||||
use bumpalo::core_alloc::fmt::Write;
|
||||
let mut s = bumpalo::collections::String::new_in(self.cx.bump());
|
||||
s.write_fmt(args).unwrap();
|
||||
s.into_bump_str()
|
||||
}
|
||||
};
|
||||
self.key = NodeKey(Some(key));
|
||||
self
|
||||
}
|
||||
|
||||
/// Create the virtual DOM VNode described by this builder.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use dioxus::{builder::*, bumpalo::Bump, VNode};
|
||||
///
|
||||
/// let b = Bump::new();
|
||||
///
|
||||
/// // Start with a builder...
|
||||
/// let builder: ElementBuilder<_, _, _> = div(&b);
|
||||
///
|
||||
/// // ...and finish it to create a virtual DOM VNode!
|
||||
/// let my_div: VNode = builder.finish();
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn finish(mut self) -> VNode<'a> {
|
||||
let bump = self.cx.bump();
|
||||
|
||||
let children: &'a Children = bump.alloc(self.children);
|
||||
let children: &'a [VNode<'a>] = children.as_ref();
|
||||
|
||||
let listeners: &'a Listeners = bump.alloc(self.listeners);
|
||||
let listeners: &'a [Listener<'a>] = listeners.as_ref();
|
||||
|
||||
for listener in listeners {
|
||||
// bump the context id forward
|
||||
let id = self.cx.listener_id.get();
|
||||
self.cx.listener_id.set(id + 1);
|
||||
|
||||
// Add this listener to the context list
|
||||
// This casts the listener to a self-referential pointer
|
||||
// This is okay because the bump arena is stable
|
||||
|
||||
// TODO: maybe not add it into CTX immediately
|
||||
let r = unsafe { std::mem::transmute::<&Listener<'a>, &Listener<'static>>(listener) };
|
||||
self.cx.scope_ref.listeners.borrow_mut().push((
|
||||
r.mounted_node as *const _ as *mut _,
|
||||
r.callback as *const _ as *mut _,
|
||||
));
|
||||
}
|
||||
|
||||
let attributes: &'a Attributes = bump.alloc(self.attributes);
|
||||
let attributes: &'a [Attribute<'a>] = attributes.as_ref();
|
||||
|
||||
VNode::element(
|
||||
bump,
|
||||
self.key,
|
||||
self.tag_name,
|
||||
listeners,
|
||||
attributes,
|
||||
children,
|
||||
self.namespace,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, Attributes, Children>
|
||||
ElementBuilder<'a, 'b, bumpalo::collections::Vec<'a, Listener<'a>>, Attributes, Children>
|
||||
where
|
||||
Attributes: 'a + AsRef<[Attribute<'a>]>,
|
||||
Children: 'a + AsRef<[VNode<'a>]>,
|
||||
{
|
||||
// / Add a new event listener to this element.
|
||||
// /
|
||||
// / The `event` string specifies which event will be listened for. The
|
||||
// / `callback` function is the function that will be invoked if the
|
||||
// / specified event occurs.
|
||||
// /
|
||||
// / # Example
|
||||
// /
|
||||
// / ```no_run
|
||||
// / use dioxus::{builder::*, bumpalo::Bump};
|
||||
// /
|
||||
// / let b = Bump::new();
|
||||
// /
|
||||
// / // A button that does something when clicked!
|
||||
// / let my_button = button(&b)
|
||||
// / .on("click", |event| {
|
||||
// / // ...
|
||||
// / })
|
||||
// / .finish();
|
||||
// / ```
|
||||
// pub fn on(self, event: &'static str, callback: impl FnMut(VirtualEvent) + 'a) -> Self {
|
||||
// let bump = &self.cx.bump();
|
||||
// let listener = Listener {
|
||||
// event,
|
||||
// callback: bump.alloc(callback),
|
||||
// scope: self.cx.scope_ref.arena_idx,
|
||||
// mounted_node: bump.alloc(Cell::new(RealDomNode::empty())),
|
||||
// };
|
||||
// self.add_listener(listener)
|
||||
// }
|
||||
|
||||
// pub fn add_listener(mut self, listener: Listener<'a>) -> Self {
|
||||
// self.listeners.push(listener);
|
||||
|
||||
// // bump the context id forward
|
||||
// let id = self.cx.listener_id.get();
|
||||
// self.cx.listener_id.set(id + 1);
|
||||
|
||||
// // Add this listener to the context list
|
||||
// // This casts the listener to a self-referential pointer
|
||||
// // This is okay because the bump arena is stable
|
||||
// self.listeners.last().map(|g| {
|
||||
// let r = unsafe { std::mem::transmute::<&Listener<'a>, &Listener<'static>>(g) };
|
||||
// self.cx.scope_ref.listeners.borrow_mut().push((
|
||||
// r.mounted_node as *const _ as *mut _,
|
||||
// r.callback as *const _ as *mut _,
|
||||
// ));
|
||||
// });
|
||||
|
||||
// self
|
||||
// }
|
||||
}
|
||||
|
||||
impl<'a, 'b, Listeners, Children>
|
||||
ElementBuilder<'a, 'b, Listeners, bumpalo::collections::Vec<'a, Attribute<'a>>, Children>
|
||||
where
|
||||
Listeners: 'a + AsRef<[Listener<'a>]>,
|
||||
Children: 'a + AsRef<[VNode<'a>]>,
|
||||
{
|
||||
/// Add a new attribute to this element.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use dioxus::{builder::*, bumpalo::Bump};
|
||||
///
|
||||
/// let b = Bump::new();
|
||||
///
|
||||
/// // Create the `<div id="my-div"/>` element.
|
||||
/// let my_div = div(&b).attr("id", "my-div").finish();
|
||||
/// ```
|
||||
pub fn attr(mut self, name: &'static str, args: std::fmt::Arguments) -> Self {
|
||||
let (value, is_static) = raw_text(self.cx.bump(), args);
|
||||
|
||||
self.attributes.push(Attribute {
|
||||
name,
|
||||
value,
|
||||
is_static,
|
||||
namespace: None,
|
||||
});
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, Listeners, Attributes>
|
||||
ElementBuilder<'a, 'b, Listeners, Attributes, bumpalo::collections::Vec<'a, VNode<'a>>>
|
||||
where
|
||||
Listeners: 'a + AsRef<[Listener<'a>]>,
|
||||
Attributes: 'a + AsRef<[Attribute<'a>]>,
|
||||
{
|
||||
/// Add a new child to this element.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use dioxus::{builder::*, bumpalo::Bump};
|
||||
/// use js_sys::Math;
|
||||
///
|
||||
/// let b = Bump::new();
|
||||
///
|
||||
/// // Create `<p><span></span></p>`.
|
||||
/// let my_div = p(&b)
|
||||
/// .child(span(&b).finish())
|
||||
/// .finish();
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn child(mut self, child: VNode<'a>) -> Self {
|
||||
self.children.push(child);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add multiple children to this element from an iterator.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use dioxus::{builder::*, bumpalo::Bump};
|
||||
///
|
||||
/// let b = Bump::new();
|
||||
///
|
||||
/// let my_div = p(&b)
|
||||
/// .iter_child((0..10).map(|f| span(&b).finish())
|
||||
/// .finish();
|
||||
/// ```
|
||||
pub fn iter_child(mut self, nodes: impl IntoIterator<Item = impl IntoVNode<'a>>) -> Self {
|
||||
todo!();
|
||||
let len_before = self.children.len();
|
||||
for item in nodes {
|
||||
todo!()
|
||||
// let child = item.into_vnode(&self.cx);
|
||||
// self.children.push(child);
|
||||
}
|
||||
if cfg!(debug_assertions) {
|
||||
if self.children.len() > len_before + 1 {
|
||||
if self.children.last().unwrap().key().is_none() {
|
||||
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.
|
||||
---
|
||||
To help you identify where this error is coming from, we've generated a backtrace.
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -212,18 +212,22 @@ impl Scope {
|
||||
(self.task_submitter)(DTask::new(task, self.arena_idx));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
||||
self.frames.current_head_node()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
||||
self.frames.prev_head_node()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn cur_frame(&self) -> &BumpFrame {
|
||||
self.frames.cur_frame()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn root<'a>(&'a self) -> &'a VNode<'a> {
|
||||
&self.frames.cur_frame().head_node
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
cell::{RefCell, RefMut},
|
||||
cell::{Cell, RefCell, RefMut},
|
||||
rc::Rc,
|
||||
vec::Drain,
|
||||
};
|
||||
@@ -65,6 +65,9 @@ impl RealDomNode {
|
||||
pub const fn empty() -> Self {
|
||||
Self(u64::MIN)
|
||||
}
|
||||
pub const fn empty_cell() -> Cell<Self> {
|
||||
Cell::new(Self::empty())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DebugDom {
|
||||
|
||||
@@ -146,7 +146,7 @@ impl VirtualDom {
|
||||
.with(|arena| {
|
||||
arena.insert_with_key(move |myidx| {
|
||||
let event_channel = _event_queue.new_channel(0, myidx);
|
||||
let caller = crate::nodes::create_component_caller(root, props_ptr as *const _);
|
||||
let caller = NodeFactory::create_component_caller(root, props_ptr as *const _);
|
||||
Scope::new(caller, myidx, None, 0, event_channel, link, &[], submitter)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
//!
|
||||
//!
|
||||
|
||||
use crate::{innerlude::Attribute, nodebuilder::NodeFactory};
|
||||
use crate::innerlude::{Attribute, NodeFactory};
|
||||
|
||||
pub struct StyleBuilder;
|
||||
pub trait AsAttr<'a> {
|
||||
Reference in new issue
Block a user