Merge pull request #1393 from ealmloff/fix-event-bubbling
Fix event multi node event bubbling
This commit is contained in:
13 files changed
+542
-313
No files matched your search
+62
-58
@@ -13,62 +13,50 @@ use crate::{
|
|||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||||
pub struct ElementId(pub usize);
|
pub struct ElementId(pub usize);
|
||||||
|
|
||||||
pub(crate) struct ElementRef {
|
/// An Element that can be bubbled to's unique identifier.
|
||||||
|
///
|
||||||
|
/// `BubbleId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
|
||||||
|
/// unmounted, then the `BubbleId` will be reused for a new component.
|
||||||
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||||
|
pub struct VNodeId(pub usize);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct ElementRef {
|
||||||
// the pathway of the real element inside the template
|
// the pathway of the real element inside the template
|
||||||
pub path: ElementPath,
|
pub(crate) path: ElementPath,
|
||||||
|
|
||||||
// The actual template
|
// The actual template
|
||||||
pub template: Option<NonNull<VNode<'static>>>,
|
pub(crate) template: VNodeId,
|
||||||
|
|
||||||
// The scope the element belongs to
|
// The scope the element belongs to
|
||||||
pub scope: ScopeId,
|
pub(crate) scope: ScopeId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum ElementPath {
|
pub struct ElementPath {
|
||||||
Deep(&'static [u8]),
|
pub(crate) path: &'static [u8],
|
||||||
Root(usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ElementRef {
|
|
||||||
pub(crate) fn none() -> Self {
|
|
||||||
Self {
|
|
||||||
template: None,
|
|
||||||
path: ElementPath::Root(0),
|
|
||||||
scope: ScopeId::ROOT,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VirtualDom {
|
impl VirtualDom {
|
||||||
pub(crate) fn next_element(&mut self, template: &VNode, path: &'static [u8]) -> ElementId {
|
pub(crate) fn next_element(&mut self) -> ElementId {
|
||||||
self.next_reference(template, ElementPath::Deep(path))
|
ElementId(self.elements.insert(None))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn next_root(&mut self, template: &VNode, path: usize) -> ElementId {
|
pub(crate) fn next_vnode_ref(&mut self, vnode: &VNode) -> VNodeId {
|
||||||
self.next_reference(template, ElementPath::Root(path))
|
let new_id = VNodeId(self.element_refs.insert(Some(unsafe {
|
||||||
}
|
std::mem::transmute::<NonNull<VNode>, _>(vnode.into())
|
||||||
|
})));
|
||||||
|
|
||||||
pub(crate) fn next_null(&mut self) -> ElementId {
|
// Set this id to be dropped when the scope is rerun
|
||||||
let entry = self.elements.vacant_entry();
|
if let Some(scope) = self.runtime.current_scope_id() {
|
||||||
let id = entry.key();
|
self.scopes[scope.0]
|
||||||
|
.element_refs_to_drop
|
||||||
|
.borrow_mut()
|
||||||
|
.push(new_id);
|
||||||
|
}
|
||||||
|
|
||||||
entry.insert(ElementRef::none());
|
new_id
|
||||||
ElementId(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_reference(&mut self, template: &VNode, path: ElementPath) -> ElementId {
|
|
||||||
let entry = self.elements.vacant_entry();
|
|
||||||
let id = entry.key();
|
|
||||||
let scope = self.runtime.current_scope_id().unwrap_or(ScopeId::ROOT);
|
|
||||||
|
|
||||||
entry.insert(ElementRef {
|
|
||||||
// We know this is non-null because it comes from a reference
|
|
||||||
template: Some(unsafe { NonNull::new_unchecked(template as *const _ as *mut _) }),
|
|
||||||
path,
|
|
||||||
scope,
|
|
||||||
});
|
|
||||||
ElementId(id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn reclaim(&mut self, el: ElementId) {
|
pub(crate) fn reclaim(&mut self, el: ElementId) {
|
||||||
@@ -76,7 +64,7 @@ impl VirtualDom {
|
|||||||
.unwrap_or_else(|| panic!("cannot reclaim {:?}", el));
|
.unwrap_or_else(|| panic!("cannot reclaim {:?}", el));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn try_reclaim(&mut self, el: ElementId) -> Option<ElementRef> {
|
pub(crate) fn try_reclaim(&mut self, el: ElementId) -> Option<()> {
|
||||||
if el.0 == 0 {
|
if el.0 == 0 {
|
||||||
panic!(
|
panic!(
|
||||||
"Cannot reclaim the root element - {:#?}",
|
"Cannot reclaim the root element - {:#?}",
|
||||||
@@ -84,12 +72,12 @@ impl VirtualDom {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.elements.try_remove(el.0)
|
self.elements.try_remove(el.0).map(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn update_template(&mut self, el: ElementId, node: &VNode) {
|
pub(crate) fn set_template(&mut self, id: VNodeId, vnode: &VNode) {
|
||||||
let node: *const VNode = node as *const _;
|
self.element_refs[id.0] =
|
||||||
self.elements[el.0].template = unsafe { std::mem::transmute(node) };
|
Some(unsafe { std::mem::transmute::<NonNull<VNode>, _>(vnode.into()) });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop a scope and all its children
|
// Drop a scope and all its children
|
||||||
@@ -101,6 +89,15 @@ impl VirtualDom {
|
|||||||
id,
|
id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Remove all VNode ids from the scope
|
||||||
|
for id in self.scopes[id.0]
|
||||||
|
.element_refs_to_drop
|
||||||
|
.borrow_mut()
|
||||||
|
.drain(..)
|
||||||
|
{
|
||||||
|
self.element_refs.try_remove(id.0);
|
||||||
|
}
|
||||||
|
|
||||||
self.ensure_drop_safety(id);
|
self.ensure_drop_safety(id);
|
||||||
|
|
||||||
if recursive {
|
if recursive {
|
||||||
@@ -145,14 +142,25 @@ impl VirtualDom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Descend through the tree, removing any borrowed props and listeners
|
/// Descend through the tree, removing any borrowed props and listeners
|
||||||
pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) {
|
pub(crate) fn ensure_drop_safety(&mut self, scope_id: ScopeId) {
|
||||||
let scope = &self.scopes[scope_id.0];
|
let scope = &self.scopes[scope_id.0];
|
||||||
|
|
||||||
|
{
|
||||||
|
// Drop all element refs that could be invalidated when the component was rerun
|
||||||
|
let mut element_refs = self.scopes[scope_id.0].element_refs_to_drop.borrow_mut();
|
||||||
|
let element_refs_slab = &mut self.element_refs;
|
||||||
|
for element_ref in element_refs.drain(..) {
|
||||||
|
if let Some(element_ref) = element_refs_slab.get_mut(element_ref.0) {
|
||||||
|
*element_ref = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
|
// make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
|
||||||
// run the hooks (which hold an &mut Reference)
|
// run the hooks (which hold an &mut Reference)
|
||||||
// recursively call ensure_drop_safety on all children
|
// recursively call ensure_drop_safety on all children
|
||||||
let mut props = scope.borrowed_props.borrow_mut();
|
let props = { scope.borrowed_props.borrow_mut().clone() };
|
||||||
props.drain(..).for_each(|comp| {
|
for comp in props {
|
||||||
let comp = unsafe { &*comp };
|
let comp = unsafe { &*comp };
|
||||||
match comp.scope.get() {
|
match comp.scope.get() {
|
||||||
Some(child) if child != scope_id => self.ensure_drop_safety(child),
|
Some(child) if child != scope_id => self.ensure_drop_safety(child),
|
||||||
@@ -161,7 +169,9 @@ impl VirtualDom {
|
|||||||
if let Ok(mut props) = comp.props.try_borrow_mut() {
|
if let Ok(mut props) = comp.props.try_borrow_mut() {
|
||||||
*props = None;
|
*props = None;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
let scope = &self.scopes[scope_id.0];
|
||||||
|
scope.borrowed_props.borrow_mut().clear();
|
||||||
|
|
||||||
// Now that all the references are gone, we can safely drop our own references in our listeners.
|
// Now that all the references are gone, we can safely drop our own references in our listeners.
|
||||||
let mut listeners = scope.attributes_to_drop_before_render.borrow_mut();
|
let mut listeners = scope.attributes_to_drop_before_render.borrow_mut();
|
||||||
@@ -176,18 +186,12 @@ impl VirtualDom {
|
|||||||
|
|
||||||
impl ElementPath {
|
impl ElementPath {
|
||||||
pub(crate) fn is_decendant(&self, small: &&[u8]) -> bool {
|
pub(crate) fn is_decendant(&self, small: &&[u8]) -> bool {
|
||||||
match *self {
|
small.len() <= self.path.len() && *small == &self.path[..small.len()]
|
||||||
ElementPath::Deep(big) => small.len() <= big.len() && *small == &big[..small.len()],
|
|
||||||
ElementPath::Root(r) => small.len() == 1 && small[0] == r as u8,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq<&[u8]> for ElementPath {
|
impl PartialEq<&[u8]> for ElementPath {
|
||||||
fn eq(&self, other: &&[u8]) -> bool {
|
fn eq(&self, other: &&[u8]) -> bool {
|
||||||
match *self {
|
self.path.eq(*other)
|
||||||
ElementPath::Deep(deep) => deep.eq(*other),
|
|
||||||
ElementPath::Root(r) => other.len() == 1 && other[0] == r as u8,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+78
-44
@@ -1,5 +1,7 @@
|
|||||||
use crate::any_props::AnyProps;
|
use crate::any_props::AnyProps;
|
||||||
use crate::innerlude::{BorrowedAttributeValue, VComponent, VPlaceholder, VText};
|
use crate::innerlude::{
|
||||||
|
BorrowedAttributeValue, ElementPath, ElementRef, VComponent, VPlaceholder, VText,
|
||||||
|
};
|
||||||
use crate::mutations::Mutation;
|
use crate::mutations::Mutation;
|
||||||
use crate::mutations::Mutation::*;
|
use crate::mutations::Mutation::*;
|
||||||
use crate::nodes::VNode;
|
use crate::nodes::VNode;
|
||||||
@@ -94,6 +96,9 @@ impl<'b> VirtualDom {
|
|||||||
nodes_mut.resize(len, ElementId::default());
|
nodes_mut.resize(len, ElementId::default());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Set this node id
|
||||||
|
node.stable_id.set(Some(self.next_vnode_ref(node)));
|
||||||
|
|
||||||
// The best renderers will have templates prehydrated and registered
|
// The best renderers will have templates prehydrated and registered
|
||||||
// Just in case, let's create the template using instructions anyways
|
// Just in case, let's create the template using instructions anyways
|
||||||
self.register_template(node.template.get());
|
self.register_template(node.template.get());
|
||||||
@@ -181,15 +186,30 @@ impl<'b> VirtualDom {
|
|||||||
use DynamicNode::*;
|
use DynamicNode::*;
|
||||||
match &template.dynamic_nodes[idx] {
|
match &template.dynamic_nodes[idx] {
|
||||||
node @ Component { .. } | node @ Fragment(_) => {
|
node @ Component { .. } | node @ Fragment(_) => {
|
||||||
self.create_dynamic_node(template, node, idx)
|
let template_ref = ElementRef {
|
||||||
|
path: ElementPath {
|
||||||
|
path: template.template.get().node_paths[idx],
|
||||||
|
},
|
||||||
|
template: template.stable_id().unwrap(),
|
||||||
|
scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)),
|
||||||
|
};
|
||||||
|
self.create_dynamic_node(template_ref, node)
|
||||||
}
|
}
|
||||||
Placeholder(VPlaceholder { id }) => {
|
Placeholder(VPlaceholder { id, parent }) => {
|
||||||
let id = self.set_slot(template, id, idx);
|
let template_ref = ElementRef {
|
||||||
|
path: ElementPath {
|
||||||
|
path: template.template.get().node_paths[idx],
|
||||||
|
},
|
||||||
|
template: template.stable_id().unwrap(),
|
||||||
|
scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)),
|
||||||
|
};
|
||||||
|
parent.set(Some(template_ref));
|
||||||
|
let id = self.set_slot(id);
|
||||||
self.mutations.push(CreatePlaceholder { id });
|
self.mutations.push(CreatePlaceholder { id });
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
Text(VText { id, value }) => {
|
Text(VText { id, value }) => {
|
||||||
let id = self.set_slot(template, id, idx);
|
let id = self.set_slot(id);
|
||||||
self.create_static_text(value, id);
|
self.create_static_text(value, id);
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
@@ -265,7 +285,14 @@ impl<'b> VirtualDom {
|
|||||||
.map(|sorted_index| dynamic_nodes[sorted_index].0);
|
.map(|sorted_index| dynamic_nodes[sorted_index].0);
|
||||||
|
|
||||||
for idx in reversed_iter {
|
for idx in reversed_iter {
|
||||||
let m = self.create_dynamic_node(template, &template.dynamic_nodes[idx], idx);
|
let boundary_ref = ElementRef {
|
||||||
|
path: ElementPath {
|
||||||
|
path: template.template.get().node_paths[idx],
|
||||||
|
},
|
||||||
|
template: template.stable_id().unwrap(),
|
||||||
|
scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)),
|
||||||
|
};
|
||||||
|
let m = self.create_dynamic_node(boundary_ref, &template.dynamic_nodes[idx]);
|
||||||
if m > 0 {
|
if m > 0 {
|
||||||
// The path is one shorter because the top node is the root
|
// The path is one shorter because the top node is the root
|
||||||
let path = &template.template.get().node_paths[idx][1..];
|
let path = &template.template.get().node_paths[idx][1..];
|
||||||
@@ -279,15 +306,15 @@ impl<'b> VirtualDom {
|
|||||||
attrs: &mut Peekable<impl Iterator<Item = (usize, &'static [u8])>>,
|
attrs: &mut Peekable<impl Iterator<Item = (usize, &'static [u8])>>,
|
||||||
root_idx: u8,
|
root_idx: u8,
|
||||||
root: ElementId,
|
root: ElementId,
|
||||||
node: &VNode,
|
node: &'b VNode<'b>,
|
||||||
) {
|
) {
|
||||||
while let Some((mut attr_id, path)) =
|
while let Some((mut attr_id, path)) =
|
||||||
attrs.next_if(|(_, p)| p.first().copied() == Some(root_idx))
|
attrs.next_if(|(_, p)| p.first().copied() == Some(root_idx))
|
||||||
{
|
{
|
||||||
let id = self.assign_static_node_as_dynamic(path, root, node, attr_id);
|
let id = self.assign_static_node_as_dynamic(path, root);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
self.write_attribute(&node.dynamic_attrs[attr_id], id);
|
self.write_attribute(node, attr_id, &node.dynamic_attrs[attr_id], id);
|
||||||
|
|
||||||
// Only push the dynamic attributes forward if they match the current path (same element)
|
// Only push the dynamic attributes forward if they match the current path (same element)
|
||||||
match attrs.next_if(|(_, p)| *p == path) {
|
match attrs.next_if(|(_, p)| *p == path) {
|
||||||
@@ -298,7 +325,13 @@ impl<'b> VirtualDom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_attribute(&mut self, attribute: &'b crate::Attribute<'b>, id: ElementId) {
|
fn write_attribute(
|
||||||
|
&mut self,
|
||||||
|
template: &'b VNode<'b>,
|
||||||
|
idx: usize,
|
||||||
|
attribute: &'b crate::Attribute<'b>,
|
||||||
|
id: ElementId,
|
||||||
|
) {
|
||||||
// Make sure we set the attribute's associated id
|
// Make sure we set the attribute's associated id
|
||||||
attribute.mounted_element.set(id);
|
attribute.mounted_element.set(id);
|
||||||
|
|
||||||
@@ -307,6 +340,13 @@ impl<'b> VirtualDom {
|
|||||||
|
|
||||||
match &attribute.value {
|
match &attribute.value {
|
||||||
AttributeValue::Listener(_) => {
|
AttributeValue::Listener(_) => {
|
||||||
|
let path = &template.template.get().attr_paths[idx];
|
||||||
|
let element_ref = ElementRef {
|
||||||
|
path: ElementPath { path },
|
||||||
|
template: template.stable_id().unwrap(),
|
||||||
|
scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)),
|
||||||
|
};
|
||||||
|
self.elements[id.0] = Some(element_ref);
|
||||||
self.mutations.push(NewEventListener {
|
self.mutations.push(NewEventListener {
|
||||||
// all listeners start with "on"
|
// all listeners start with "on"
|
||||||
name: &unbounded_name[2..],
|
name: &unbounded_name[2..],
|
||||||
@@ -330,7 +370,7 @@ impl<'b> VirtualDom {
|
|||||||
|
|
||||||
fn load_template_root(&mut self, template: &VNode, root_idx: usize) -> ElementId {
|
fn load_template_root(&mut self, template: &VNode, root_idx: usize) -> ElementId {
|
||||||
// Get an ID for this root since it's a real root
|
// Get an ID for this root since it's a real root
|
||||||
let this_id = self.next_root(template, root_idx);
|
let this_id = self.next_element();
|
||||||
template.root_ids.borrow_mut()[root_idx] = this_id;
|
template.root_ids.borrow_mut()[root_idx] = this_id;
|
||||||
|
|
||||||
self.mutations.push(LoadTemplate {
|
self.mutations.push(LoadTemplate {
|
||||||
@@ -353,8 +393,6 @@ impl<'b> VirtualDom {
|
|||||||
&mut self,
|
&mut self,
|
||||||
path: &'static [u8],
|
path: &'static [u8],
|
||||||
this_id: ElementId,
|
this_id: ElementId,
|
||||||
template: &VNode,
|
|
||||||
attr_id: usize,
|
|
||||||
) -> ElementId {
|
) -> ElementId {
|
||||||
if path.len() == 1 {
|
if path.len() == 1 {
|
||||||
return this_id;
|
return this_id;
|
||||||
@@ -362,7 +400,7 @@ impl<'b> VirtualDom {
|
|||||||
|
|
||||||
// if attribute is on a root node, then we've already created the element
|
// if attribute is on a root node, then we've already created the element
|
||||||
// Else, it's deep in the template and we should create a new id for it
|
// Else, it's deep in the template and we should create a new id for it
|
||||||
let id = self.next_element(template, template.template.get().attr_paths[attr_id]);
|
let id = self.next_element();
|
||||||
|
|
||||||
self.mutations.push(Mutation::AssignId {
|
self.mutations.push(Mutation::AssignId {
|
||||||
path: &path[1..],
|
path: &path[1..],
|
||||||
@@ -440,27 +478,21 @@ impl<'b> VirtualDom {
|
|||||||
|
|
||||||
pub(crate) fn create_dynamic_node(
|
pub(crate) fn create_dynamic_node(
|
||||||
&mut self,
|
&mut self,
|
||||||
template: &'b VNode<'b>,
|
parent: ElementRef,
|
||||||
node: &'b DynamicNode<'b>,
|
node: &'b DynamicNode<'b>,
|
||||||
idx: usize,
|
|
||||||
) -> usize {
|
) -> usize {
|
||||||
use DynamicNode::*;
|
use DynamicNode::*;
|
||||||
match node {
|
match node {
|
||||||
Text(text) => self.create_dynamic_text(template, text, idx),
|
Text(text) => self.create_dynamic_text(parent, text),
|
||||||
Placeholder(place) => self.create_placeholder(place, template, idx),
|
Placeholder(place) => self.create_placeholder(place, parent),
|
||||||
Component(component) => self.create_component_node(template, component),
|
Component(component) => self.create_component_node(Some(parent), component),
|
||||||
Fragment(frag) => frag.iter().map(|child| self.create(child)).sum(),
|
Fragment(frag) => self.create_children(*frag, Some(parent)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_dynamic_text(
|
fn create_dynamic_text(&mut self, parent: ElementRef, text: &'b VText<'b>) -> usize {
|
||||||
&mut self,
|
|
||||||
template: &'b VNode<'b>,
|
|
||||||
text: &'b VText<'b>,
|
|
||||||
idx: usize,
|
|
||||||
) -> usize {
|
|
||||||
// Allocate a dynamic element reference for this text node
|
// Allocate a dynamic element reference for this text node
|
||||||
let new_id = self.next_element(template, template.template.get().node_paths[idx]);
|
let new_id = self.next_element();
|
||||||
|
|
||||||
// Make sure the text node is assigned to the correct element
|
// Make sure the text node is assigned to the correct element
|
||||||
text.id.set(Some(new_id));
|
text.id.set(Some(new_id));
|
||||||
@@ -471,7 +503,7 @@ impl<'b> VirtualDom {
|
|||||||
// Add the mutation to the list
|
// Add the mutation to the list
|
||||||
self.mutations.push(HydrateText {
|
self.mutations.push(HydrateText {
|
||||||
id: new_id,
|
id: new_id,
|
||||||
path: &template.template.get().node_paths[idx][1..],
|
path: &parent.path.path[1..],
|
||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -482,18 +514,20 @@ impl<'b> VirtualDom {
|
|||||||
pub(crate) fn create_placeholder(
|
pub(crate) fn create_placeholder(
|
||||||
&mut self,
|
&mut self,
|
||||||
placeholder: &VPlaceholder,
|
placeholder: &VPlaceholder,
|
||||||
template: &'b VNode<'b>,
|
parent: ElementRef,
|
||||||
idx: usize,
|
|
||||||
) -> usize {
|
) -> usize {
|
||||||
// Allocate a dynamic element reference for this text node
|
// Allocate a dynamic element reference for this text node
|
||||||
let id = self.next_element(template, template.template.get().node_paths[idx]);
|
let id = self.next_element();
|
||||||
|
|
||||||
// Make sure the text node is assigned to the correct element
|
// Make sure the text node is assigned to the correct element
|
||||||
placeholder.id.set(Some(id));
|
placeholder.id.set(Some(id));
|
||||||
|
|
||||||
|
// Assign the placeholder's parent
|
||||||
|
placeholder.parent.set(Some(parent));
|
||||||
|
|
||||||
// Assign the ID to the existing node in the template
|
// Assign the ID to the existing node in the template
|
||||||
self.mutations.push(AssignId {
|
self.mutations.push(AssignId {
|
||||||
path: &template.template.get().node_paths[idx][1..],
|
path: &parent.path.path[1..],
|
||||||
id,
|
id,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -503,7 +537,7 @@ impl<'b> VirtualDom {
|
|||||||
|
|
||||||
pub(super) fn create_component_node(
|
pub(super) fn create_component_node(
|
||||||
&mut self,
|
&mut self,
|
||||||
template: &'b VNode<'b>,
|
parent: Option<ElementRef>,
|
||||||
component: &'b VComponent<'b>,
|
component: &'b VComponent<'b>,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
use RenderReturn::*;
|
use RenderReturn::*;
|
||||||
@@ -515,8 +549,11 @@ impl<'b> VirtualDom {
|
|||||||
|
|
||||||
match unsafe { self.run_scope(scope).extend_lifetime_ref() } {
|
match unsafe { self.run_scope(scope).extend_lifetime_ref() } {
|
||||||
// Create the component's root element
|
// Create the component's root element
|
||||||
Ready(t) => self.create_scope(scope, t),
|
Ready(t) => {
|
||||||
Aborted(t) => self.mount_aborted(template, t),
|
self.assign_boundary_ref(parent, t);
|
||||||
|
self.create_scope(scope, t)
|
||||||
|
}
|
||||||
|
Aborted(t) => self.mount_aborted(t, parent),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,20 +569,17 @@ impl<'b> VirtualDom {
|
|||||||
.unwrap_or_else(|| component.scope.get().unwrap())
|
.unwrap_or_else(|| component.scope.get().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mount_aborted(&mut self, parent: &'b VNode<'b>, placeholder: &VPlaceholder) -> usize {
|
fn mount_aborted(&mut self, placeholder: &VPlaceholder, parent: Option<ElementRef>) -> usize {
|
||||||
let id = self.next_element(parent, &[]);
|
let id = self.next_element();
|
||||||
self.mutations.push(Mutation::CreatePlaceholder { id });
|
self.mutations.push(Mutation::CreatePlaceholder { id });
|
||||||
placeholder.id.set(Some(id));
|
placeholder.id.set(Some(id));
|
||||||
|
placeholder.parent.set(parent);
|
||||||
|
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_slot(
|
fn set_slot(&mut self, slot: &'b Cell<Option<ElementId>>) -> ElementId {
|
||||||
&mut self,
|
let id = self.next_element();
|
||||||
template: &'b VNode<'b>,
|
|
||||||
slot: &'b Cell<Option<ElementId>>,
|
|
||||||
id: usize,
|
|
||||||
) -> ElementId {
|
|
||||||
let id = self.next_element(template, template.template.get().node_paths[id]);
|
|
||||||
slot.set(Some(id));
|
slot.set(Some(id));
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|||||||
+149
-65
@@ -1,7 +1,10 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
any_props::AnyProps,
|
any_props::AnyProps,
|
||||||
arena::ElementId,
|
arena::ElementId,
|
||||||
innerlude::{BorrowedAttributeValue, DirtyScope, VComponent, VPlaceholder, VText},
|
innerlude::{
|
||||||
|
BorrowedAttributeValue, DirtyScope, ElementPath, ElementRef, VComponent, VPlaceholder,
|
||||||
|
VText,
|
||||||
|
},
|
||||||
mutations::Mutation,
|
mutations::Mutation,
|
||||||
nodes::RenderReturn,
|
nodes::RenderReturn,
|
||||||
nodes::{DynamicNode, VNode},
|
nodes::{DynamicNode, VNode},
|
||||||
@@ -39,19 +42,27 @@ impl<'b> VirtualDom {
|
|||||||
(Ready(l), Aborted(p)) => self.diff_ok_to_err(l, p),
|
(Ready(l), Aborted(p)) => self.diff_ok_to_err(l, p),
|
||||||
|
|
||||||
// Just move over the placeholder
|
// Just move over the placeholder
|
||||||
(Aborted(l), Aborted(r)) => r.id.set(l.id.get()),
|
(Aborted(l), Aborted(r)) => {
|
||||||
|
r.id.set(l.id.get());
|
||||||
|
r.parent.set(l.parent.get())
|
||||||
|
}
|
||||||
|
|
||||||
// Placeholder becomes something
|
// Placeholder becomes something
|
||||||
// We should also clear the error now
|
// We should also clear the error now
|
||||||
(Aborted(l), Ready(r)) => self.replace_placeholder(l, [r]),
|
(Aborted(l), Ready(r)) => self.replace_placeholder(
|
||||||
|
l,
|
||||||
|
[r],
|
||||||
|
l.parent.get().expect("root node should not be none"),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
self.runtime.scope_stack.borrow_mut().pop();
|
self.runtime.scope_stack.borrow_mut().pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diff_ok_to_err(&mut self, l: &'b VNode<'b>, p: &'b VPlaceholder) {
|
fn diff_ok_to_err(&mut self, l: &'b VNode<'b>, p: &'b VPlaceholder) {
|
||||||
let id = self.next_null();
|
let id = self.next_element();
|
||||||
p.id.set(Some(id));
|
p.id.set(Some(id));
|
||||||
|
p.parent.set(l.parent.get());
|
||||||
self.mutations.push(Mutation::CreatePlaceholder { id });
|
self.mutations.push(Mutation::CreatePlaceholder { id });
|
||||||
|
|
||||||
let pre_edits = self.mutations.edits.len();
|
let pre_edits = self.mutations.edits.len();
|
||||||
@@ -81,12 +92,24 @@ impl<'b> VirtualDom {
|
|||||||
if let Some(&template) = map.get(&byte_index) {
|
if let Some(&template) = map.get(&byte_index) {
|
||||||
right_template.template.set(template);
|
right_template.template.set(template);
|
||||||
if template != left_template.template.get() {
|
if template != left_template.template.get() {
|
||||||
return self.replace(left_template, [right_template]);
|
let parent = left_template.parent.take();
|
||||||
|
return self.replace(left_template, [right_template], parent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Copy over the parent
|
||||||
|
{
|
||||||
|
right_template.parent.set(left_template.parent.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the bubble id pointer
|
||||||
|
right_template.stable_id.set(left_template.stable_id.get());
|
||||||
|
if let Some(bubble_id) = right_template.stable_id.get() {
|
||||||
|
self.set_template(bubble_id, right_template);
|
||||||
|
}
|
||||||
|
|
||||||
// If the templates are the same, we don't need to do anything, nor do we want to
|
// If the templates are the same, we don't need to do anything, nor do we want to
|
||||||
if templates_are_the_same(left_template, right_template) {
|
if templates_are_the_same(left_template, right_template) {
|
||||||
return;
|
return;
|
||||||
@@ -105,12 +128,8 @@ impl<'b> VirtualDom {
|
|||||||
.zip(right_template.dynamic_attrs.iter())
|
.zip(right_template.dynamic_attrs.iter())
|
||||||
.for_each(|(left_attr, right_attr)| {
|
.for_each(|(left_attr, right_attr)| {
|
||||||
// Move over the ID from the old to the new
|
// Move over the ID from the old to the new
|
||||||
right_attr
|
let mounted_element = left_attr.mounted_element.get();
|
||||||
.mounted_element
|
right_attr.mounted_element.set(mounted_element);
|
||||||
.set(left_attr.mounted_element.get());
|
|
||||||
|
|
||||||
// We want to make sure anything that gets pulled is valid
|
|
||||||
self.update_template(left_attr.mounted_element.get(), right_template);
|
|
||||||
|
|
||||||
// If the attributes are different (or volatile), we need to update them
|
// If the attributes are different (or volatile), we need to update them
|
||||||
if left_attr.value != right_attr.value || left_attr.volatile {
|
if left_attr.value != right_attr.value || left_attr.volatile {
|
||||||
@@ -123,8 +142,16 @@ impl<'b> VirtualDom {
|
|||||||
.dynamic_nodes
|
.dynamic_nodes
|
||||||
.iter()
|
.iter()
|
||||||
.zip(right_template.dynamic_nodes.iter())
|
.zip(right_template.dynamic_nodes.iter())
|
||||||
.for_each(|(left_node, right_node)| {
|
.enumerate()
|
||||||
self.diff_dynamic_node(left_node, right_node, right_template);
|
.for_each(|(dyn_node_idx, (left_node, right_node))| {
|
||||||
|
let current_ref = ElementRef {
|
||||||
|
template: right_template.stable_id().unwrap(),
|
||||||
|
path: ElementPath {
|
||||||
|
path: left_template.template.get().node_paths[dyn_node_idx],
|
||||||
|
},
|
||||||
|
scope: self.runtime.scope_stack.borrow().last().copied().unwrap(),
|
||||||
|
};
|
||||||
|
self.diff_dynamic_node(left_node, right_node, current_ref);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Make sure the roots get transferred over while we're here
|
// Make sure the roots get transferred over while we're here
|
||||||
@@ -135,30 +162,24 @@ impl<'b> VirtualDom {
|
|||||||
right.push(element);
|
right.push(element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let root_ids = right_template.root_ids.borrow();
|
|
||||||
|
|
||||||
// Update the node refs
|
|
||||||
for i in 0..root_ids.len() {
|
|
||||||
if let Some(root_id) = root_ids.get(i) {
|
|
||||||
self.update_template(*root_id, right_template);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diff_dynamic_node(
|
fn diff_dynamic_node(
|
||||||
&mut self,
|
&mut self,
|
||||||
left_node: &'b DynamicNode<'b>,
|
left_node: &'b DynamicNode<'b>,
|
||||||
right_node: &'b DynamicNode<'b>,
|
right_node: &'b DynamicNode<'b>,
|
||||||
node: &'b VNode<'b>,
|
parent: ElementRef,
|
||||||
) {
|
) {
|
||||||
match (left_node, right_node) {
|
match (left_node, right_node) {
|
||||||
(Text(left), Text(right)) => self.diff_vtext(left, right, node),
|
(Text(left), Text(right)) => self.diff_vtext(left, right),
|
||||||
(Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right),
|
(Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right, parent),
|
||||||
(Placeholder(left), Placeholder(right)) => right.id.set(left.id.get()),
|
(Placeholder(left), Placeholder(right)) => {
|
||||||
(Component(left), Component(right)) => self.diff_vcomponent(left, right, node),
|
right.id.set(left.id.get());
|
||||||
(Placeholder(left), Fragment(right)) => self.replace_placeholder(left, *right),
|
right.parent.set(left.parent.get());
|
||||||
(Fragment(left), Placeholder(right)) => self.node_to_placeholder(left, right),
|
},
|
||||||
|
(Component(left), Component(right)) => self.diff_vcomponent(left, right, Some(parent)),
|
||||||
|
(Placeholder(left), Fragment(right)) => self.replace_placeholder(left, *right, parent),
|
||||||
|
(Fragment(left), Placeholder(right)) => self.node_to_placeholder(left, right, parent),
|
||||||
_ => todo!("This is an usual custom case for dynamic nodes. We don't know how to handle it yet."),
|
_ => todo!("This is an usual custom case for dynamic nodes. We don't know how to handle it yet."),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -179,7 +200,7 @@ impl<'b> VirtualDom {
|
|||||||
&mut self,
|
&mut self,
|
||||||
left: &'b VComponent<'b>,
|
left: &'b VComponent<'b>,
|
||||||
right: &'b VComponent<'b>,
|
right: &'b VComponent<'b>,
|
||||||
right_template: &'b VNode<'b>,
|
parent: Option<ElementRef>,
|
||||||
) {
|
) {
|
||||||
if std::ptr::eq(left, right) {
|
if std::ptr::eq(left, right) {
|
||||||
return;
|
return;
|
||||||
@@ -187,7 +208,7 @@ impl<'b> VirtualDom {
|
|||||||
|
|
||||||
// Replace components that have different render fns
|
// Replace components that have different render fns
|
||||||
if left.render_fn != right.render_fn {
|
if left.render_fn != right.render_fn {
|
||||||
return self.replace_vcomponent(right_template, right, left);
|
return self.replace_vcomponent(right, left, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the new vcomponent has the right scopeid associated to it
|
// Make sure the new vcomponent has the right scopeid associated to it
|
||||||
@@ -228,11 +249,11 @@ impl<'b> VirtualDom {
|
|||||||
|
|
||||||
fn replace_vcomponent(
|
fn replace_vcomponent(
|
||||||
&mut self,
|
&mut self,
|
||||||
right_template: &'b VNode<'b>,
|
|
||||||
right: &'b VComponent<'b>,
|
right: &'b VComponent<'b>,
|
||||||
left: &'b VComponent<'b>,
|
left: &'b VComponent<'b>,
|
||||||
|
parent: Option<ElementRef>,
|
||||||
) {
|
) {
|
||||||
let m = self.create_component_node(right_template, right);
|
let m = self.create_component_node(parent, right);
|
||||||
|
|
||||||
let pre_edits = self.mutations.edits.len();
|
let pre_edits = self.mutations.edits.len();
|
||||||
|
|
||||||
@@ -287,11 +308,12 @@ impl<'b> VirtualDom {
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
fn light_diff_templates(&mut self, left: &'b VNode<'b>, right: &'b VNode<'b>) {
|
fn light_diff_templates(&mut self, left: &'b VNode<'b>, right: &'b VNode<'b>) {
|
||||||
|
let parent = left.parent.take();
|
||||||
match matching_components(left, right) {
|
match matching_components(left, right) {
|
||||||
None => self.replace(left, [right]),
|
None => self.replace(left, [right], parent),
|
||||||
Some(components) => components
|
Some(components) => components
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.for_each(|(l, r)| self.diff_vcomponent(l, r, right)),
|
.for_each(|(l, r)| self.diff_vcomponent(l, r, parent)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,11 +321,8 @@ impl<'b> VirtualDom {
|
|||||||
///
|
///
|
||||||
/// This just moves the ID of the old node over to the new node, and then sets the text of the new node if it's
|
/// This just moves the ID of the old node over to the new node, and then sets the text of the new node if it's
|
||||||
/// different.
|
/// different.
|
||||||
fn diff_vtext(&mut self, left: &'b VText<'b>, right: &'b VText<'b>, node: &'b VNode<'b>) {
|
fn diff_vtext(&mut self, left: &'b VText<'b>, right: &'b VText<'b>) {
|
||||||
let id = left
|
let id = left.id.get().unwrap_or_else(|| self.next_element());
|
||||||
.id
|
|
||||||
.get()
|
|
||||||
.unwrap_or_else(|| self.next_element(node, &[0]));
|
|
||||||
|
|
||||||
right.id.set(Some(id));
|
right.id.set(Some(id));
|
||||||
if left.value != right.value {
|
if left.value != right.value {
|
||||||
@@ -312,7 +331,12 @@ impl<'b> VirtualDom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diff_non_empty_fragment(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
|
fn diff_non_empty_fragment(
|
||||||
|
&mut self,
|
||||||
|
old: &'b [VNode<'b>],
|
||||||
|
new: &'b [VNode<'b>],
|
||||||
|
parent: ElementRef,
|
||||||
|
) {
|
||||||
let new_is_keyed = new[0].key.is_some();
|
let new_is_keyed = new[0].key.is_some();
|
||||||
let old_is_keyed = old[0].key.is_some();
|
let old_is_keyed = old[0].key.is_some();
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
@@ -325,9 +349,9 @@ impl<'b> VirtualDom {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if new_is_keyed && old_is_keyed {
|
if new_is_keyed && old_is_keyed {
|
||||||
self.diff_keyed_children(old, new);
|
self.diff_keyed_children(old, new, parent);
|
||||||
} else {
|
} else {
|
||||||
self.diff_non_keyed_children(old, new);
|
self.diff_non_keyed_children(old, new, parent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,7 +363,12 @@ impl<'b> VirtualDom {
|
|||||||
// [... parent]
|
// [... parent]
|
||||||
//
|
//
|
||||||
// the change list stack is in the same state when this function returns.
|
// the change list stack is in the same state when this function returns.
|
||||||
fn diff_non_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
|
fn diff_non_keyed_children(
|
||||||
|
&mut self,
|
||||||
|
old: &'b [VNode<'b>],
|
||||||
|
new: &'b [VNode<'b>],
|
||||||
|
parent: ElementRef,
|
||||||
|
) {
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
// Handled these cases in `diff_children` before calling this function.
|
// Handled these cases in `diff_children` before calling this function.
|
||||||
@@ -348,7 +377,9 @@ impl<'b> VirtualDom {
|
|||||||
|
|
||||||
match old.len().cmp(&new.len()) {
|
match old.len().cmp(&new.len()) {
|
||||||
Ordering::Greater => self.remove_nodes(&old[new.len()..]),
|
Ordering::Greater => self.remove_nodes(&old[new.len()..]),
|
||||||
Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()),
|
Ordering::Less => {
|
||||||
|
self.create_and_insert_after(&new[old.len()..], old.last().unwrap(), parent)
|
||||||
|
}
|
||||||
Ordering::Equal => {}
|
Ordering::Equal => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,7 +404,12 @@ impl<'b> VirtualDom {
|
|||||||
// https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
|
// https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
|
||||||
//
|
//
|
||||||
// The stack is empty upon entry.
|
// The stack is empty upon entry.
|
||||||
fn diff_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
|
fn diff_keyed_children(
|
||||||
|
&mut self,
|
||||||
|
old: &'b [VNode<'b>],
|
||||||
|
new: &'b [VNode<'b>],
|
||||||
|
parent: ElementRef,
|
||||||
|
) {
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
let mut keys = rustc_hash::FxHashSet::default();
|
let mut keys = rustc_hash::FxHashSet::default();
|
||||||
let mut assert_unique_keys = |children: &'b [VNode<'b>]| {
|
let mut assert_unique_keys = |children: &'b [VNode<'b>]| {
|
||||||
@@ -401,7 +437,7 @@ impl<'b> VirtualDom {
|
|||||||
//
|
//
|
||||||
// `shared_prefix_count` is the count of how many nodes at the start of
|
// `shared_prefix_count` is the count of how many nodes at the start of
|
||||||
// `new` and `old` share the same keys.
|
// `new` and `old` share the same keys.
|
||||||
let (left_offset, right_offset) = match self.diff_keyed_ends(old, new) {
|
let (left_offset, right_offset) = match self.diff_keyed_ends(old, new, parent) {
|
||||||
Some(count) => count,
|
Some(count) => count,
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
@@ -427,18 +463,18 @@ impl<'b> VirtualDom {
|
|||||||
if left_offset == 0 {
|
if left_offset == 0 {
|
||||||
// insert at the beginning of the old list
|
// insert at the beginning of the old list
|
||||||
let foothold = &old[old.len() - right_offset];
|
let foothold = &old[old.len() - right_offset];
|
||||||
self.create_and_insert_before(new_middle, foothold);
|
self.create_and_insert_before(new_middle, foothold, parent);
|
||||||
} else if right_offset == 0 {
|
} else if right_offset == 0 {
|
||||||
// insert at the end the old list
|
// insert at the end the old list
|
||||||
let foothold = old.last().unwrap();
|
let foothold = old.last().unwrap();
|
||||||
self.create_and_insert_after(new_middle, foothold);
|
self.create_and_insert_after(new_middle, foothold, parent);
|
||||||
} else {
|
} else {
|
||||||
// inserting in the middle
|
// inserting in the middle
|
||||||
let foothold = &old[left_offset - 1];
|
let foothold = &old[left_offset - 1];
|
||||||
self.create_and_insert_after(new_middle, foothold);
|
self.create_and_insert_after(new_middle, foothold, parent);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.diff_keyed_middle(old_middle, new_middle);
|
self.diff_keyed_middle(old_middle, new_middle, parent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,6 +487,7 @@ impl<'b> VirtualDom {
|
|||||||
&mut self,
|
&mut self,
|
||||||
old: &'b [VNode<'b>],
|
old: &'b [VNode<'b>],
|
||||||
new: &'b [VNode<'b>],
|
new: &'b [VNode<'b>],
|
||||||
|
parent: ElementRef,
|
||||||
) -> Option<(usize, usize)> {
|
) -> Option<(usize, usize)> {
|
||||||
let mut left_offset = 0;
|
let mut left_offset = 0;
|
||||||
|
|
||||||
@@ -466,7 +503,7 @@ impl<'b> VirtualDom {
|
|||||||
// If that was all of the old children, then create and append the remaining
|
// If that was all of the old children, then create and append the remaining
|
||||||
// new children and we're finished.
|
// new children and we're finished.
|
||||||
if left_offset == old.len() {
|
if left_offset == old.len() {
|
||||||
self.create_and_insert_after(&new[left_offset..], old.last().unwrap());
|
self.create_and_insert_after(&new[left_offset..], old.last().unwrap(), parent);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,7 +542,12 @@ impl<'b> VirtualDom {
|
|||||||
//
|
//
|
||||||
// Upon exit from this function, it will be restored to that same self.
|
// Upon exit from this function, it will be restored to that same self.
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
fn diff_keyed_middle(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
|
fn diff_keyed_middle(
|
||||||
|
&mut self,
|
||||||
|
old: &'b [VNode<'b>],
|
||||||
|
new: &'b [VNode<'b>],
|
||||||
|
parent: ElementRef,
|
||||||
|
) {
|
||||||
/*
|
/*
|
||||||
1. Map the old keys into a numerical ordering based on indices.
|
1. Map the old keys into a numerical ordering based on indices.
|
||||||
2. Create a map of old key to its index
|
2. Create a map of old key to its index
|
||||||
@@ -562,7 +604,7 @@ impl<'b> VirtualDom {
|
|||||||
if shared_keys.is_empty() {
|
if shared_keys.is_empty() {
|
||||||
if old.first().is_some() {
|
if old.first().is_some() {
|
||||||
self.remove_nodes(&old[1..]);
|
self.remove_nodes(&old[1..]);
|
||||||
self.replace(&old[0], new);
|
self.replace(&old[0], new, Some(parent));
|
||||||
} else {
|
} else {
|
||||||
// I think this is wrong - why are we appending?
|
// I think this is wrong - why are we appending?
|
||||||
// only valid of the if there are no trailing elements
|
// only valid of the if there are no trailing elements
|
||||||
@@ -739,20 +781,38 @@ impl<'b> VirtualDom {
|
|||||||
.sum()
|
.sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_children(&mut self, nodes: impl IntoIterator<Item = &'b VNode<'b>>) -> usize {
|
pub(crate) fn create_children(
|
||||||
|
&mut self,
|
||||||
|
nodes: impl IntoIterator<Item = &'b VNode<'b>>,
|
||||||
|
parent: Option<ElementRef>,
|
||||||
|
) -> usize {
|
||||||
nodes
|
nodes
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.fold(0, |acc, child| acc + self.create(child))
|
.map(|child| {
|
||||||
|
self.assign_boundary_ref(parent, child);
|
||||||
|
self.create(child)
|
||||||
|
})
|
||||||
|
.sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_and_insert_before(&mut self, new: &'b [VNode<'b>], before: &'b VNode<'b>) {
|
fn create_and_insert_before(
|
||||||
let m = self.create_children(new);
|
&mut self,
|
||||||
|
new: &'b [VNode<'b>],
|
||||||
|
before: &'b VNode<'b>,
|
||||||
|
parent: ElementRef,
|
||||||
|
) {
|
||||||
|
let m = self.create_children(new, Some(parent));
|
||||||
let id = self.find_first_element(before);
|
let id = self.find_first_element(before);
|
||||||
self.mutations.push(Mutation::InsertBefore { id, m })
|
self.mutations.push(Mutation::InsertBefore { id, m })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_and_insert_after(&mut self, new: &'b [VNode<'b>], after: &'b VNode<'b>) {
|
fn create_and_insert_after(
|
||||||
let m = self.create_children(new);
|
&mut self,
|
||||||
|
new: &'b [VNode<'b>],
|
||||||
|
after: &'b VNode<'b>,
|
||||||
|
parent: ElementRef,
|
||||||
|
) {
|
||||||
|
let m = self.create_children(new, Some(parent));
|
||||||
let id = self.find_last_element(after);
|
let id = self.find_last_element(after);
|
||||||
self.mutations.push(Mutation::InsertAfter { id, m })
|
self.mutations.push(Mutation::InsertAfter { id, m })
|
||||||
}
|
}
|
||||||
@@ -762,15 +822,21 @@ impl<'b> VirtualDom {
|
|||||||
&mut self,
|
&mut self,
|
||||||
l: &'b VPlaceholder,
|
l: &'b VPlaceholder,
|
||||||
r: impl IntoIterator<Item = &'b VNode<'b>>,
|
r: impl IntoIterator<Item = &'b VNode<'b>>,
|
||||||
|
parent: ElementRef,
|
||||||
) {
|
) {
|
||||||
let m = self.create_children(r);
|
let m = self.create_children(r, Some(parent));
|
||||||
let id = l.id.get().unwrap();
|
let id = l.id.get().unwrap();
|
||||||
self.mutations.push(Mutation::ReplaceWith { id, m });
|
self.mutations.push(Mutation::ReplaceWith { id, m });
|
||||||
self.reclaim(id);
|
self.reclaim(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace(&mut self, left: &'b VNode<'b>, right: impl IntoIterator<Item = &'b VNode<'b>>) {
|
fn replace(
|
||||||
let m = self.create_children(right);
|
&mut self,
|
||||||
|
left: &'b VNode<'b>,
|
||||||
|
right: impl IntoIterator<Item = &'b VNode<'b>>,
|
||||||
|
parent: Option<ElementRef>,
|
||||||
|
) {
|
||||||
|
let m = self.create_children(right, parent);
|
||||||
|
|
||||||
let pre_edits = self.mutations.edits.len();
|
let pre_edits = self.mutations.edits.len();
|
||||||
|
|
||||||
@@ -789,11 +855,12 @@ impl<'b> VirtualDom {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_to_placeholder(&mut self, l: &'b [VNode<'b>], r: &'b VPlaceholder) {
|
fn node_to_placeholder(&mut self, l: &'b [VNode<'b>], r: &'b VPlaceholder, parent: ElementRef) {
|
||||||
// Create the placeholder first, ensuring we get a dedicated ID for the placeholder
|
// Create the placeholder first, ensuring we get a dedicated ID for the placeholder
|
||||||
let placeholder = self.next_element(&l[0], &[]);
|
let placeholder = self.next_element();
|
||||||
|
|
||||||
r.id.set(Some(placeholder));
|
r.id.set(Some(placeholder));
|
||||||
|
r.parent.set(Some(parent));
|
||||||
|
|
||||||
self.mutations
|
self.mutations
|
||||||
.push(Mutation::CreatePlaceholder { id: placeholder });
|
.push(Mutation::CreatePlaceholder { id: placeholder });
|
||||||
@@ -831,6 +898,16 @@ impl<'b> VirtualDom {
|
|||||||
// Clean up the roots, assuming we need to generate mutations for these
|
// Clean up the roots, assuming we need to generate mutations for these
|
||||||
// This is done last in order to preserve Node ID reclaim order (reclaim in reverse order of claim)
|
// This is done last in order to preserve Node ID reclaim order (reclaim in reverse order of claim)
|
||||||
self.reclaim_roots(node, gen_muts);
|
self.reclaim_roots(node, gen_muts);
|
||||||
|
|
||||||
|
// Clean up the vnode id
|
||||||
|
self.reclaim_vnode_id(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reclaim_vnode_id(&mut self, node: &'b VNode<'b>) {
|
||||||
|
// Clean up the vnode id
|
||||||
|
if let Some(id) = node.stable_id() {
|
||||||
|
self.element_refs.remove(id.0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reclaim_roots(&mut self, node: &VNode, gen_muts: bool) {
|
fn reclaim_roots(&mut self, node: &VNode, gen_muts: bool) {
|
||||||
@@ -989,6 +1066,13 @@ impl<'b> VirtualDom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn assign_boundary_ref(&mut self, parent: Option<ElementRef>, child: &'b VNode<'b>) {
|
||||||
|
if let Some(parent) = parent {
|
||||||
|
// assign the parent of the child
|
||||||
|
child.parent.set(Some(parent));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Are the templates the same?
|
/// Are the templates the same?
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
|
|||||||
let children = cx.props.0.as_ref()?;
|
let children = cx.props.0.as_ref()?;
|
||||||
Some(VNode {
|
Some(VNode {
|
||||||
key: children.key,
|
key: children.key,
|
||||||
parent: children.parent,
|
parent: children.parent.clone(),
|
||||||
|
stable_id: children.stable_id.clone(),
|
||||||
template: children.template.clone(),
|
template: children.template.clone(),
|
||||||
root_ids: children.root_ids.clone(),
|
root_ids: children.root_ids.clone(),
|
||||||
dynamic_nodes: children.dynamic_nodes,
|
dynamic_nodes: children.dynamic_nodes,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::innerlude::{ElementRef, VNodeId};
|
||||||
use crate::{
|
use crate::{
|
||||||
any_props::AnyProps, arena::ElementId, Element, Event, LazyNodes, ScopeId, ScopeState,
|
any_props::AnyProps, arena::ElementId, Element, Event, LazyNodes, ScopeId, ScopeState,
|
||||||
};
|
};
|
||||||
@@ -47,7 +48,10 @@ pub struct VNode<'a> {
|
|||||||
pub key: Option<&'a str>,
|
pub key: Option<&'a str>,
|
||||||
|
|
||||||
/// When rendered, this template will be linked to its parent manually
|
/// When rendered, this template will be linked to its parent manually
|
||||||
pub parent: Option<ElementId>,
|
pub(crate) parent: Cell<Option<ElementRef>>,
|
||||||
|
|
||||||
|
/// The bubble id assigned to the child that we need to update and drop when diffing happens
|
||||||
|
pub(crate) stable_id: Cell<Option<VNodeId>>,
|
||||||
|
|
||||||
/// The static nodes and static descriptor of the template
|
/// The static nodes and static descriptor of the template
|
||||||
pub template: Cell<Template<'static>>,
|
pub template: Cell<Template<'static>>,
|
||||||
@@ -68,7 +72,8 @@ impl<'a> VNode<'a> {
|
|||||||
pub fn empty(cx: &'a ScopeState) -> Element<'a> {
|
pub fn empty(cx: &'a ScopeState) -> Element<'a> {
|
||||||
Some(VNode {
|
Some(VNode {
|
||||||
key: None,
|
key: None,
|
||||||
parent: None,
|
parent: Default::default(),
|
||||||
|
stable_id: Default::default(),
|
||||||
root_ids: RefCell::new(bumpalo::collections::Vec::new_in(cx.bump())),
|
root_ids: RefCell::new(bumpalo::collections::Vec::new_in(cx.bump())),
|
||||||
dynamic_nodes: &[],
|
dynamic_nodes: &[],
|
||||||
dynamic_attrs: &[],
|
dynamic_attrs: &[],
|
||||||
@@ -81,6 +86,30 @@ impl<'a> VNode<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new VNode
|
||||||
|
pub fn new(
|
||||||
|
key: Option<&'a str>,
|
||||||
|
template: Template<'static>,
|
||||||
|
root_ids: bumpalo::collections::Vec<'a, ElementId>,
|
||||||
|
dynamic_nodes: &'a [DynamicNode<'a>],
|
||||||
|
dynamic_attrs: &'a [Attribute<'a>],
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
key,
|
||||||
|
parent: Cell::new(None),
|
||||||
|
stable_id: Cell::new(None),
|
||||||
|
template: Cell::new(template),
|
||||||
|
root_ids: RefCell::new(root_ids),
|
||||||
|
dynamic_nodes,
|
||||||
|
dynamic_attrs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the stable id of this node used for bubbling events
|
||||||
|
pub(crate) fn stable_id(&self) -> Option<VNodeId> {
|
||||||
|
self.stable_id.get()
|
||||||
|
}
|
||||||
|
|
||||||
/// Load a dynamic root at the given index
|
/// Load a dynamic root at the given index
|
||||||
///
|
///
|
||||||
/// Returns [`None`] if the root is actually a static node (Element/Text)
|
/// Returns [`None`] if the root is actually a static node (Element/Text)
|
||||||
@@ -319,7 +348,7 @@ pub struct VComponent<'a> {
|
|||||||
|
|
||||||
/// The function pointer of the component, known at compile time
|
/// The function pointer of the component, known at compile time
|
||||||
///
|
///
|
||||||
/// It is possible that components get folded at comppile time, so these shouldn't be really used as a key
|
/// It is possible that components get folded at compile time, so these shouldn't be really used as a key
|
||||||
pub(crate) render_fn: *const (),
|
pub(crate) render_fn: *const (),
|
||||||
|
|
||||||
pub(crate) props: RefCell<Option<Box<dyn AnyProps<'a> + 'a>>>,
|
pub(crate) props: RefCell<Option<Box<dyn AnyProps<'a> + 'a>>>,
|
||||||
@@ -372,6 +401,8 @@ impl<'a> VText<'a> {
|
|||||||
pub struct VPlaceholder {
|
pub struct VPlaceholder {
|
||||||
/// The ID of this node in the real DOM
|
/// The ID of this node in the real DOM
|
||||||
pub(crate) id: Cell<Option<ElementId>>,
|
pub(crate) id: Cell<Option<ElementId>>,
|
||||||
|
/// The parent of this node
|
||||||
|
pub(crate) parent: Cell<Option<ElementRef>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VPlaceholder {
|
impl VPlaceholder {
|
||||||
@@ -722,7 +753,8 @@ impl<'b> IntoDynNode<'b> for Arguments<'_> {
|
|||||||
impl<'a> IntoDynNode<'a> for &'a VNode<'a> {
|
impl<'a> IntoDynNode<'a> for &'a VNode<'a> {
|
||||||
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
|
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||||
DynamicNode::Fragment(_cx.bump().alloc([VNode {
|
DynamicNode::Fragment(_cx.bump().alloc([VNode {
|
||||||
parent: self.parent,
|
parent: self.parent.clone(),
|
||||||
|
stable_id: self.stable_id.clone(),
|
||||||
template: self.template.clone(),
|
template: self.template.clone(),
|
||||||
root_ids: self.root_ids.clone(),
|
root_ids: self.root_ids.clone(),
|
||||||
key: self.key,
|
key: self.key,
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ impl VirtualDom {
|
|||||||
|
|
||||||
borrowed_props: Default::default(),
|
borrowed_props: Default::default(),
|
||||||
attributes_to_drop_before_render: Default::default(),
|
attributes_to_drop_before_render: Default::default(),
|
||||||
|
element_refs_to_drop: Default::default(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let context =
|
let context =
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::{
|
|||||||
any_props::VProps,
|
any_props::VProps,
|
||||||
bump_frame::BumpFrame,
|
bump_frame::BumpFrame,
|
||||||
innerlude::ErrorBoundary,
|
innerlude::ErrorBoundary,
|
||||||
innerlude::{DynamicNode, EventHandler, VComponent, VText},
|
innerlude::{DynamicNode, EventHandler, VComponent, VNodeId, VText},
|
||||||
lazynodes::LazyNodes,
|
lazynodes::LazyNodes,
|
||||||
nodes::{IntoAttributeValue, IntoDynNode, RenderReturn},
|
nodes::{IntoAttributeValue, IntoDynNode, RenderReturn},
|
||||||
runtime::Runtime,
|
runtime::Runtime,
|
||||||
@@ -94,6 +94,7 @@ pub struct ScopeState {
|
|||||||
pub(crate) hook_idx: Cell<usize>,
|
pub(crate) hook_idx: Cell<usize>,
|
||||||
|
|
||||||
pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
|
pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
|
||||||
|
pub(crate) element_refs_to_drop: RefCell<Vec<VNodeId>>,
|
||||||
pub(crate) attributes_to_drop_before_render: RefCell<Vec<*const Attribute<'static>>>,
|
pub(crate) attributes_to_drop_before_render: RefCell<Vec<*const Attribute<'static>>>,
|
||||||
|
|
||||||
pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
|
pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
|
||||||
@@ -466,7 +467,7 @@ impl<'src> ScopeState {
|
|||||||
render_fn: component as *const (),
|
render_fn: component as *const (),
|
||||||
static_props: P::IS_STATIC,
|
static_props: P::IS_STATIC,
|
||||||
props: RefCell::new(Some(extended)),
|
props: RefCell::new(Some(extended)),
|
||||||
scope: Cell::new(None),
|
scope: Default::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,19 +4,19 @@
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
any_props::VProps,
|
any_props::VProps,
|
||||||
arena::{ElementId, ElementRef},
|
arena::ElementId,
|
||||||
innerlude::{DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg},
|
innerlude::{DirtyScope, ElementRef, ErrorBoundary, Mutations, Scheduler, SchedulerMsg},
|
||||||
mutations::Mutation,
|
mutations::Mutation,
|
||||||
nodes::RenderReturn,
|
nodes::RenderReturn,
|
||||||
nodes::{Template, TemplateId},
|
nodes::{Template, TemplateId},
|
||||||
runtime::{Runtime, RuntimeGuard},
|
runtime::{Runtime, RuntimeGuard},
|
||||||
scopes::{ScopeId, ScopeState},
|
scopes::{ScopeId, ScopeState},
|
||||||
AttributeValue, Element, Event, Scope,
|
AttributeValue, Element, Event, Scope, VNode,
|
||||||
};
|
};
|
||||||
use futures_util::{pin_mut, StreamExt};
|
use futures_util::{pin_mut, StreamExt};
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use slab::Slab;
|
use slab::Slab;
|
||||||
use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, rc::Rc};
|
use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, ptr::NonNull, rc::Rc};
|
||||||
|
|
||||||
/// A virtual node system that progresses user events and diffs UI trees.
|
/// A virtual node system that progresses user events and diffs UI trees.
|
||||||
///
|
///
|
||||||
@@ -186,7 +186,10 @@ pub struct VirtualDom {
|
|||||||
pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>,
|
pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>,
|
||||||
|
|
||||||
// Every element is actually a dual reference - one to the template and the other to the dynamic node in that template
|
// Every element is actually a dual reference - one to the template and the other to the dynamic node in that template
|
||||||
pub(crate) elements: Slab<ElementRef>,
|
pub(crate) element_refs: Slab<Option<NonNull<VNode<'static>>>>,
|
||||||
|
|
||||||
|
// The element ids that are used in the renderer
|
||||||
|
pub(crate) elements: Slab<Option<ElementRef>>,
|
||||||
|
|
||||||
pub(crate) mutations: Mutations<'static>,
|
pub(crate) mutations: Mutations<'static>,
|
||||||
|
|
||||||
@@ -263,6 +266,7 @@ impl VirtualDom {
|
|||||||
dirty_scopes: Default::default(),
|
dirty_scopes: Default::default(),
|
||||||
templates: Default::default(),
|
templates: Default::default(),
|
||||||
elements: Default::default(),
|
elements: Default::default(),
|
||||||
|
element_refs: Default::default(),
|
||||||
mutations: Mutations::default(),
|
mutations: Mutations::default(),
|
||||||
suspended_scopes: Default::default(),
|
suspended_scopes: Default::default(),
|
||||||
};
|
};
|
||||||
@@ -276,7 +280,7 @@ impl VirtualDom {
|
|||||||
root.provide_context(Rc::new(ErrorBoundary::new(ScopeId::ROOT)));
|
root.provide_context(Rc::new(ErrorBoundary::new(ScopeId::ROOT)));
|
||||||
|
|
||||||
// the root element is always given element ID 0 since it's the container for the entire tree
|
// the root element is always given element ID 0 since it's the container for the entire tree
|
||||||
dom.elements.insert(ElementRef::none());
|
dom.elements.insert(None);
|
||||||
|
|
||||||
dom
|
dom
|
||||||
}
|
}
|
||||||
@@ -314,9 +318,9 @@ impl VirtualDom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call a listener inside the VirtualDom with data from outside the VirtualDom.
|
/// Call a listener inside the VirtualDom with data from outside the VirtualDom. **The ElementId passed in must be the id of an dynamic element, not a static node or a text node.**
|
||||||
///
|
///
|
||||||
/// This method will identify the appropriate element. The data must match up with the listener delcared. Note that
|
/// This method will identify the appropriate element. The data must match up with the listener declared. Note that
|
||||||
/// this method does not give any indication as to the success of the listener call. If the listener is not found,
|
/// this method does not give any indication as to the success of the listener call. If the listener is not found,
|
||||||
/// nothing will happen.
|
/// nothing will happen.
|
||||||
///
|
///
|
||||||
@@ -353,7 +357,15 @@ impl VirtualDom {
|
|||||||
| | | <-- no, broke early
|
| | | <-- no, broke early
|
||||||
| <-- no, broke early
|
| <-- no, broke early
|
||||||
*/
|
*/
|
||||||
let mut parent_path = self.elements.get(element.0);
|
let parent_path = match self.elements.get(element.0) {
|
||||||
|
Some(Some(el)) => el,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
let mut parent_node = self
|
||||||
|
.element_refs
|
||||||
|
.get(parent_path.template.0)
|
||||||
|
.cloned()
|
||||||
|
.map(|el| (*parent_path, el));
|
||||||
let mut listeners = vec![];
|
let mut listeners = vec![];
|
||||||
|
|
||||||
// We will clone this later. The data itself is wrapped in RC to be used in callbacks if required
|
// We will clone this later. The data itself is wrapped in RC to be used in callbacks if required
|
||||||
@@ -365,36 +377,72 @@ impl VirtualDom {
|
|||||||
// If the event bubbles, we traverse through the tree until we find the target element.
|
// If the event bubbles, we traverse through the tree until we find the target element.
|
||||||
if bubbles {
|
if bubbles {
|
||||||
// Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent.
|
// Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent.
|
||||||
while let Some(el_ref) = parent_path {
|
while let Some((path, el_ref)) = parent_node {
|
||||||
// safety: we maintain references of all vnodes in the element slab
|
// safety: we maintain references of all vnodes in the element slab
|
||||||
if let Some(template) = el_ref.template {
|
let template = unsafe { el_ref.unwrap().as_ref() };
|
||||||
let template = unsafe { template.as_ref() };
|
let node_template = template.template.get();
|
||||||
let node_template = template.template.get();
|
let target_path = path.path;
|
||||||
let target_path = el_ref.path;
|
|
||||||
|
|
||||||
for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
|
for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
|
||||||
let this_path = node_template.attr_paths[idx];
|
let this_path = node_template.attr_paths[idx];
|
||||||
|
|
||||||
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
|
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
|
||||||
if attr.name.trim_start_matches("on") == name
|
if attr.name.trim_start_matches("on") == name
|
||||||
&& target_path.is_decendant(&this_path)
|
&& target_path.is_decendant(&this_path)
|
||||||
{
|
{
|
||||||
listeners.push(&attr.value);
|
listeners.push(&attr.value);
|
||||||
|
|
||||||
// Break if this is the exact target element.
|
// Break if this is the exact target element.
|
||||||
// This means we won't call two listeners with the same name on the same element. This should be
|
// This means we won't call two listeners with the same name on the same element. This should be
|
||||||
// documented, or be rejected from the rsx! macro outright
|
// documented, or be rejected from the rsx! macro outright
|
||||||
if target_path == this_path {
|
if target_path == this_path {
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Now that we've accumulated all the parent attributes for the target element, call them in reverse order
|
// Now that we've accumulated all the parent attributes for the target element, call them in reverse order
|
||||||
// We check the bubble state between each call to see if the event has been stopped from bubbling
|
// We check the bubble state between each call to see if the event has been stopped from bubbling
|
||||||
for listener in listeners.drain(..).rev() {
|
for listener in listeners.drain(..).rev() {
|
||||||
if let AttributeValue::Listener(listener) = listener {
|
if let AttributeValue::Listener(listener) = listener {
|
||||||
let origin = el_ref.scope;
|
let origin = path.scope;
|
||||||
|
self.runtime.scope_stack.borrow_mut().push(origin);
|
||||||
|
self.runtime.rendering.set(false);
|
||||||
|
if let Some(cb) = listener.borrow_mut().as_deref_mut() {
|
||||||
|
cb(uievent.clone());
|
||||||
|
}
|
||||||
|
self.runtime.scope_stack.borrow_mut().pop();
|
||||||
|
self.runtime.rendering.set(true);
|
||||||
|
|
||||||
|
if !uievent.propagates.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parent_node = template.parent.get().and_then(|element_ref| {
|
||||||
|
self.element_refs
|
||||||
|
.get(element_ref.template.0)
|
||||||
|
.cloned()
|
||||||
|
.map(|el| (element_ref, el))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise, we just call the listener on the target element
|
||||||
|
if let Some((path, el_ref)) = parent_node {
|
||||||
|
// safety: we maintain references of all vnodes in the element slab
|
||||||
|
let template = unsafe { el_ref.unwrap().as_ref() };
|
||||||
|
let node_template = template.template.get();
|
||||||
|
let target_path = path.path;
|
||||||
|
|
||||||
|
for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
|
||||||
|
let this_path = node_template.attr_paths[idx];
|
||||||
|
|
||||||
|
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
|
||||||
|
// Only call the listener if this is the exact target element.
|
||||||
|
if attr.name.trim_start_matches("on") == name && target_path == this_path {
|
||||||
|
if let AttributeValue::Listener(listener) = &attr.value {
|
||||||
|
let origin = path.scope;
|
||||||
self.runtime.scope_stack.borrow_mut().push(origin);
|
self.runtime.scope_stack.borrow_mut().push(origin);
|
||||||
self.runtime.rendering.set(false);
|
self.runtime.rendering.set(false);
|
||||||
if let Some(cb) = listener.borrow_mut().as_deref_mut() {
|
if let Some(cb) = listener.borrow_mut().as_deref_mut() {
|
||||||
@@ -403,44 +451,7 @@ impl VirtualDom {
|
|||||||
self.runtime.scope_stack.borrow_mut().pop();
|
self.runtime.scope_stack.borrow_mut().pop();
|
||||||
self.runtime.rendering.set(true);
|
self.runtime.rendering.set(true);
|
||||||
|
|
||||||
if !uievent.propagates.get() {
|
break;
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parent_path = template.parent.and_then(|id| self.elements.get(id.0));
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Otherwise, we just call the listener on the target element
|
|
||||||
if let Some(el_ref) = parent_path {
|
|
||||||
// safety: we maintain references of all vnodes in the element slab
|
|
||||||
if let Some(template) = el_ref.template {
|
|
||||||
let template = unsafe { template.as_ref() };
|
|
||||||
let node_template = template.template.get();
|
|
||||||
let target_path = el_ref.path;
|
|
||||||
|
|
||||||
for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
|
|
||||||
let this_path = node_template.attr_paths[idx];
|
|
||||||
|
|
||||||
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
|
|
||||||
// Only call the listener if this is the exact target element.
|
|
||||||
if attr.name.trim_start_matches("on") == name && target_path == this_path {
|
|
||||||
if let AttributeValue::Listener(listener) = &attr.value {
|
|
||||||
let origin = el_ref.scope;
|
|
||||||
self.runtime.scope_stack.borrow_mut().push(origin);
|
|
||||||
self.runtime.rendering.set(false);
|
|
||||||
if let Some(cb) = listener.borrow_mut().as_deref_mut() {
|
|
||||||
cb(uievent.clone());
|
|
||||||
}
|
|
||||||
self.runtime.scope_stack.borrow_mut().pop();
|
|
||||||
self.runtime.rendering.set(true);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -563,7 +574,7 @@ impl VirtualDom {
|
|||||||
// If an error occurs, we should try to render the default error component and context where the error occured
|
// If an error occurs, we should try to render the default error component and context where the error occured
|
||||||
RenderReturn::Aborted(placeholder) => {
|
RenderReturn::Aborted(placeholder) => {
|
||||||
tracing::debug!("Ran into suspended or aborted scope during rebuild");
|
tracing::debug!("Ran into suspended or aborted scope during rebuild");
|
||||||
let id = self.next_null();
|
let id = self.next_element();
|
||||||
placeholder.id.set(Some(id));
|
placeholder.id.set(Some(id));
|
||||||
self.mutations.push(Mutation::CreatePlaceholder { id });
|
self.mutations.push(Mutation::CreatePlaceholder { id });
|
||||||
}
|
}
|
||||||
@@ -595,15 +606,12 @@ impl VirtualDom {
|
|||||||
/// The mutations will be thrown out, so it's best to use this method for things like SSR that have async content
|
/// The mutations will be thrown out, so it's best to use this method for things like SSR that have async content
|
||||||
pub async fn wait_for_suspense(&mut self) {
|
pub async fn wait_for_suspense(&mut self) {
|
||||||
loop {
|
loop {
|
||||||
// println!("waiting for suspense {:?}", self.suspended_scopes);
|
|
||||||
if self.suspended_scopes.is_empty() {
|
if self.suspended_scopes.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// println!("waiting for suspense");
|
|
||||||
self.wait_for_work().await;
|
self.wait_for_work().await;
|
||||||
|
|
||||||
// println!("Rendered immediately");
|
|
||||||
_ = self.render_immediate();
|
_ = self.render_immediate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_core::ElementId;
|
||||||
|
use std::{rc::Rc, sync::Mutex};
|
||||||
|
|
||||||
|
static CLICKS: Mutex<usize> = Mutex::new(0);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn events_propagate() {
|
||||||
|
let mut dom = VirtualDom::new(app);
|
||||||
|
_ = dom.rebuild();
|
||||||
|
|
||||||
|
// Top-level click is registered
|
||||||
|
dom.handle_event("click", Rc::new(MouseData::default()), ElementId(1), true);
|
||||||
|
assert_eq!(*CLICKS.lock().unwrap(), 1);
|
||||||
|
|
||||||
|
// break reference....
|
||||||
|
for _ in 0..5 {
|
||||||
|
dom.mark_dirty(ScopeId(0));
|
||||||
|
_ = dom.render_immediate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lower click is registered
|
||||||
|
dom.handle_event("click", Rc::new(MouseData::default()), ElementId(2), true);
|
||||||
|
assert_eq!(*CLICKS.lock().unwrap(), 3);
|
||||||
|
|
||||||
|
// break reference....
|
||||||
|
for _ in 0..5 {
|
||||||
|
dom.mark_dirty(ScopeId(0));
|
||||||
|
_ = dom.render_immediate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop propagation occurs
|
||||||
|
dom.handle_event("click", Rc::new(MouseData::default()), ElementId(2), true);
|
||||||
|
assert_eq!(*CLICKS.lock().unwrap(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
div {
|
||||||
|
onclick: move |_| {
|
||||||
|
println!("top clicked");
|
||||||
|
*CLICKS.lock().unwrap() += 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
vec![
|
||||||
|
render! {
|
||||||
|
problematic_child {}
|
||||||
|
}
|
||||||
|
].into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn problematic_child(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
button {
|
||||||
|
onclick: move |evt| {
|
||||||
|
println!("bottom clicked");
|
||||||
|
let mut clicks = CLICKS.lock().unwrap();
|
||||||
|
|
||||||
|
if *clicks == 3 {
|
||||||
|
evt.stop_propagation();
|
||||||
|
} else {
|
||||||
|
*clicks += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use dioxus::prelude::Props;
|
use dioxus::prelude::Props;
|
||||||
use dioxus_core::*;
|
use dioxus_core::*;
|
||||||
use std::{cell::Cell, collections::HashSet};
|
use std::{cfg, collections::HashSet};
|
||||||
|
|
||||||
fn random_ns() -> Option<&'static str> {
|
fn random_ns() -> Option<&'static str> {
|
||||||
let namespace = rand::random::<u8>() % 2;
|
let namespace = rand::random::<u8>() % 2;
|
||||||
@@ -170,22 +170,23 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode {
|
|||||||
let range = if depth > 5 { 1 } else { 4 };
|
let range = if depth > 5 { 1 } else { 4 };
|
||||||
match rand::random::<u8>() % range {
|
match rand::random::<u8>() % range {
|
||||||
0 => DynamicNode::Placeholder(Default::default()),
|
0 => DynamicNode::Placeholder(Default::default()),
|
||||||
1 => cx.make_node((0..(rand::random::<u8>() % 5)).map(|_| VNode {
|
1 => cx.make_node((0..(rand::random::<u8>() % 5)).map(|_| {
|
||||||
key: None,
|
VNode::new(
|
||||||
parent: Default::default(),
|
None,
|
||||||
template: Cell::new(Template {
|
Template {
|
||||||
name: concat!(file!(), ":", line!(), ":", column!(), ":0"),
|
name: concat!(file!(), ":", line!(), ":", column!(), ":0"),
|
||||||
roots: &[TemplateNode::Dynamic { id: 0 }],
|
roots: &[TemplateNode::Dynamic { id: 0 }],
|
||||||
node_paths: &[&[0]],
|
node_paths: &[&[0]],
|
||||||
attr_paths: &[],
|
attr_paths: &[],
|
||||||
}),
|
},
|
||||||
root_ids: bumpalo::collections::Vec::new_in(cx.bump()).into(),
|
bumpalo::collections::Vec::new_in(cx.bump()),
|
||||||
dynamic_nodes: cx.bump().alloc([cx.component(
|
cx.bump().alloc([cx.component(
|
||||||
create_random_element,
|
create_random_element,
|
||||||
DepthProps { depth, root: false },
|
DepthProps { depth, root: false },
|
||||||
"create_random_element",
|
"create_random_element",
|
||||||
)]),
|
)]),
|
||||||
dynamic_attrs: &[],
|
&[],
|
||||||
|
)
|
||||||
})),
|
})),
|
||||||
2 => cx.component(
|
2 => cx.component(
|
||||||
create_random_element,
|
create_random_element,
|
||||||
@@ -271,13 +272,11 @@ fn create_random_element(cx: Scope<DepthProps>) -> Element {
|
|||||||
)
|
)
|
||||||
.into_boxed_str(),
|
.into_boxed_str(),
|
||||||
));
|
));
|
||||||
// println!("{template:#?}");
|
let node = VNode::new(
|
||||||
let node = VNode {
|
None,
|
||||||
key: None,
|
template,
|
||||||
parent: None,
|
bumpalo::collections::Vec::new_in(cx.bump()),
|
||||||
template: Cell::new(template),
|
{
|
||||||
root_ids: bumpalo::collections::Vec::new_in(cx.bump()).into(),
|
|
||||||
dynamic_nodes: {
|
|
||||||
let dynamic_nodes: Vec<_> = dynamic_node_types
|
let dynamic_nodes: Vec<_> = dynamic_node_types
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ty| match ty {
|
.map(|ty| match ty {
|
||||||
@@ -291,12 +290,12 @@ fn create_random_element(cx: Scope<DepthProps>) -> Element {
|
|||||||
.collect();
|
.collect();
|
||||||
cx.bump().alloc(dynamic_nodes)
|
cx.bump().alloc(dynamic_nodes)
|
||||||
},
|
},
|
||||||
dynamic_attrs: cx.bump().alloc(
|
cx.bump().alloc(
|
||||||
(0..template.attr_paths.len())
|
(0..template.attr_paths.len())
|
||||||
.map(|_| create_random_dynamic_attr(cx))
|
.map(|_| create_random_dynamic_attr(cx))
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
),
|
),
|
||||||
};
|
);
|
||||||
Some(node)
|
Some(node)
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
@@ -306,10 +305,10 @@ fn create_random_element(cx: Scope<DepthProps>) -> Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// test for panics when creating random nodes and templates
|
// test for panics when creating random nodes and templates
|
||||||
#[cfg(not(miri))]
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create() {
|
fn create() {
|
||||||
for _ in 0..1000 {
|
let repeat_count = if cfg!(miri) { 100 } else { 1000 };
|
||||||
|
for _ in 0..repeat_count {
|
||||||
let mut vdom =
|
let mut vdom =
|
||||||
VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0, root: true });
|
VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0, root: true });
|
||||||
let _ = vdom.rebuild();
|
let _ = vdom.rebuild();
|
||||||
@@ -318,10 +317,10 @@ fn create() {
|
|||||||
|
|
||||||
// test for panics when diffing random nodes
|
// test for panics when diffing random nodes
|
||||||
// This test will change the template every render which is not very realistic, but it helps stress the system
|
// This test will change the template every render which is not very realistic, but it helps stress the system
|
||||||
#[cfg(not(miri))]
|
|
||||||
#[test]
|
#[test]
|
||||||
fn diff() {
|
fn diff() {
|
||||||
for _ in 0..100000 {
|
let repeat_count = if cfg!(miri) { 100 } else { 1000 };
|
||||||
|
for _ in 0..repeat_count {
|
||||||
let mut vdom =
|
let mut vdom =
|
||||||
VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0, root: true });
|
VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0, root: true });
|
||||||
let _ = vdom.rebuild();
|
let _ = vdom.rebuild();
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ use serde::{de::DeserializeOwned, Serialize};
|
|||||||
/// let state1 = use_state(cx, || server_cached(|| {
|
/// let state1 = use_state(cx, || server_cached(|| {
|
||||||
/// 1234
|
/// 1234
|
||||||
/// }));
|
/// }));
|
||||||
///
|
|
||||||
/// todo!()
|
/// todo!()
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ use dioxus_core::*;
|
|||||||
use dioxus_native_core::prelude::*;
|
use dioxus_native_core::prelude::*;
|
||||||
use dioxus_native_core_macro::partial_derive_state;
|
use dioxus_native_core_macro::partial_derive_state;
|
||||||
use shipyard::Component;
|
use shipyard::Component;
|
||||||
use std::cell::Cell;
|
|
||||||
|
|
||||||
fn random_ns() -> Option<&'static str> {
|
fn random_ns() -> Option<&'static str> {
|
||||||
let namespace = rand::random::<u8>() % 2;
|
let namespace = rand::random::<u8>() % 2;
|
||||||
@@ -178,22 +177,23 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode {
|
|||||||
let range = if depth > 3 { 1 } else { 3 };
|
let range = if depth > 3 { 1 } else { 3 };
|
||||||
match rand::random::<u8>() % range {
|
match rand::random::<u8>() % range {
|
||||||
0 => DynamicNode::Placeholder(Default::default()),
|
0 => DynamicNode::Placeholder(Default::default()),
|
||||||
1 => cx.make_node((0..(rand::random::<u8>() % 5)).map(|_| VNode {
|
1 => cx.make_node((0..(rand::random::<u8>() % 5)).map(|_| {
|
||||||
key: None,
|
VNode::new(
|
||||||
parent: Default::default(),
|
None,
|
||||||
template: Cell::new(Template {
|
Template {
|
||||||
name: concat!(file!(), ":", line!(), ":", column!(), ":0"),
|
name: concat!(file!(), ":", line!(), ":", column!(), ":0"),
|
||||||
roots: &[TemplateNode::Dynamic { id: 0 }],
|
roots: &[TemplateNode::Dynamic { id: 0 }],
|
||||||
node_paths: &[&[0]],
|
node_paths: &[&[0]],
|
||||||
attr_paths: &[],
|
attr_paths: &[],
|
||||||
}),
|
},
|
||||||
root_ids: dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()).into(),
|
dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()),
|
||||||
dynamic_nodes: cx.bump().alloc([cx.component(
|
cx.bump().alloc([cx.component(
|
||||||
create_random_element,
|
create_random_element,
|
||||||
DepthProps { depth, root: false },
|
DepthProps { depth, root: false },
|
||||||
"create_random_element",
|
"create_random_element",
|
||||||
)]),
|
)]),
|
||||||
dynamic_attrs: &[],
|
&[],
|
||||||
|
)
|
||||||
})),
|
})),
|
||||||
2 => cx.component(
|
2 => cx.component(
|
||||||
create_random_element,
|
create_random_element,
|
||||||
@@ -253,13 +253,11 @@ fn create_random_element(cx: Scope<DepthProps>) -> Element {
|
|||||||
.into_boxed_str(),
|
.into_boxed_str(),
|
||||||
));
|
));
|
||||||
println!("{template:#?}");
|
println!("{template:#?}");
|
||||||
let node = VNode {
|
let node = VNode::new(
|
||||||
key: None,
|
None,
|
||||||
parent: None,
|
template,
|
||||||
template: Cell::new(template),
|
dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()),
|
||||||
root_ids: dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump())
|
{
|
||||||
.into(),
|
|
||||||
dynamic_nodes: {
|
|
||||||
let dynamic_nodes: Vec<_> = dynamic_node_types
|
let dynamic_nodes: Vec<_> = dynamic_node_types
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ty| match ty {
|
.map(|ty| match ty {
|
||||||
@@ -273,12 +271,12 @@ fn create_random_element(cx: Scope<DepthProps>) -> Element {
|
|||||||
.collect();
|
.collect();
|
||||||
cx.bump().alloc(dynamic_nodes)
|
cx.bump().alloc(dynamic_nodes)
|
||||||
},
|
},
|
||||||
dynamic_attrs: cx.bump().alloc(
|
cx.bump().alloc(
|
||||||
(0..template.attr_paths.len())
|
(0..template.attr_paths.len())
|
||||||
.map(|_| create_random_dynamic_attr(cx))
|
.map(|_| create_random_dynamic_attr(cx))
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
),
|
),
|
||||||
};
|
);
|
||||||
Some(node)
|
Some(node)
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|||||||
@@ -252,14 +252,13 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
|
|||||||
node_paths: &[ #(#node_paths),* ],
|
node_paths: &[ #(#node_paths),* ],
|
||||||
attr_paths: &[ #(#attr_paths),* ],
|
attr_paths: &[ #(#attr_paths),* ],
|
||||||
};
|
};
|
||||||
::dioxus::core::VNode {
|
::dioxus::core::VNode::new(
|
||||||
parent: None,
|
#key_tokens,
|
||||||
key: #key_tokens,
|
TEMPLATE,
|
||||||
template: std::cell::Cell::new(TEMPLATE),
|
dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in(#root_count, __cx.bump()),
|
||||||
root_ids: dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in(#root_count, __cx.bump()).into(),
|
__cx.bump().alloc([ #( #node_printer ),* ]),
|
||||||
dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]),
|
__cx.bump().alloc([ #( #dyn_attr_printer ),* ]),
|
||||||
dynamic_attrs: __cx.bump().alloc([ #( #dyn_attr_printer ),* ]),
|
)
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user