partially restore spreading attributes
This commit is contained in:
1 parent
2470a46511
commit
36e9eb1160
19 files changed
+252
-194
No files matched your search
+103
-54
@@ -7,7 +7,6 @@ use crate::{
|
||||
arena::ElementId,
|
||||
innerlude::{ElementPath, ElementRef, VComponent, VNodeMount, VText},
|
||||
nodes::DynamicNode,
|
||||
nodes::RenderReturn,
|
||||
scopes::ScopeId,
|
||||
TemplateNode,
|
||||
TemplateNode::*,
|
||||
@@ -61,16 +60,7 @@ impl VNode {
|
||||
|
||||
// If the templates are the same, we can diff the attributes and children
|
||||
// Start with the attributes
|
||||
self.dynamic_attrs
|
||||
.iter()
|
||||
.zip(new.dynamic_attrs.iter())
|
||||
.enumerate()
|
||||
.for_each(|(idx, (old_attr, new_attr))| {
|
||||
// If the attributes are different (or volatile), we need to update them
|
||||
if old_attr.value != new_attr.value || new_attr.volatile {
|
||||
self.update_attribute(mount, idx, new_attr, to);
|
||||
}
|
||||
});
|
||||
self.diff_attributes(new, dom, to);
|
||||
|
||||
// Now diff the dynamic nodes
|
||||
self.dynamic_nodes
|
||||
@@ -317,17 +307,108 @@ impl VNode {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn update_attribute(
|
||||
pub(super) fn diff_attributes(
|
||||
&self,
|
||||
mount: &VNodeMount,
|
||||
idx: usize,
|
||||
new_attr: &Attribute,
|
||||
new: &VNode,
|
||||
dom: &mut VirtualDom,
|
||||
to: &mut impl WriteMutations,
|
||||
) {
|
||||
let name = &new_attr.name;
|
||||
let value = &new_attr.value;
|
||||
let id = mount.mounted_attributes[idx];
|
||||
to.set_attribute(name, new_attr.namespace, value, id);
|
||||
let mount_id = self.mount.get();
|
||||
for (idx, (old_attrs, new_attrs)) in self
|
||||
.dynamic_attrs
|
||||
.iter()
|
||||
.zip(new.dynamic_attrs.iter())
|
||||
.enumerate()
|
||||
{
|
||||
let mut old_attributes_iter = old_attrs.iter().peekable();
|
||||
let mut new_attributes_iter = new_attrs.iter().peekable();
|
||||
let attribute_id = dom.mounts[mount_id.0].mounted_attributes[idx];
|
||||
let path = self.template.get().attr_paths[idx];
|
||||
|
||||
loop {
|
||||
match (old_attributes_iter.peek(), new_attributes_iter.peek()) {
|
||||
(Some(old_attribute), Some(new_attribute)) => {
|
||||
// check which name is greater
|
||||
match old_attribute.name.cmp(new_attribute.name) {
|
||||
// The two attributes are the same, so diff them
|
||||
std::cmp::Ordering::Equal => {
|
||||
let old = old_attributes_iter.next().unwrap();
|
||||
let new = new_attributes_iter.next().unwrap();
|
||||
if old.value != new.value {
|
||||
self.write_attribute(
|
||||
path,
|
||||
new,
|
||||
attribute_id,
|
||||
mount_id,
|
||||
dom,
|
||||
to,
|
||||
);
|
||||
}
|
||||
}
|
||||
// In a sorted list, if the old attribute name is first, then the new attribute is missing
|
||||
std::cmp::Ordering::Less => {
|
||||
let old = old_attributes_iter.next().unwrap();
|
||||
self.remove_attribute(old, attribute_id, to)
|
||||
}
|
||||
// In a sorted list, if the new attribute name is first, then the old attribute is missing
|
||||
std::cmp::Ordering::Greater => {
|
||||
let new = new_attributes_iter.next().unwrap();
|
||||
self.write_attribute(path, new, attribute_id, mount_id, dom, to);
|
||||
}
|
||||
}
|
||||
}
|
||||
(Some(_), None) => {
|
||||
let left = old_attributes_iter.next().unwrap();
|
||||
self.remove_attribute(left, attribute_id, to)
|
||||
}
|
||||
(None, Some(_)) => {
|
||||
let right = new_attributes_iter.next().unwrap();
|
||||
self.write_attribute(path, right, attribute_id, mount_id, dom, to)
|
||||
}
|
||||
(None, None) => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_attribute(&self, attribute: &Attribute, id: ElementId, to: &mut impl WriteMutations) {
|
||||
match &attribute.value {
|
||||
AttributeValue::Listener(_) => {
|
||||
to.remove_event_listener(&attribute.name[2..], id);
|
||||
}
|
||||
_ => {
|
||||
to.set_attribute(
|
||||
attribute.name,
|
||||
attribute.namespace,
|
||||
&AttributeValue::None,
|
||||
id,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_attribute(
|
||||
&self,
|
||||
path: &'static [u8],
|
||||
attribute: &Attribute,
|
||||
id: ElementId,
|
||||
mount: MountId,
|
||||
dom: &mut VirtualDom,
|
||||
to: &mut impl WriteMutations,
|
||||
) {
|
||||
match &attribute.value {
|
||||
AttributeValue::Listener(_) => {
|
||||
let element_ref = ElementRef {
|
||||
path: ElementPath { path },
|
||||
mount,
|
||||
};
|
||||
dom.elements[id.0] = Some(element_ref);
|
||||
to.create_event_listener(&attribute.name[2..], id);
|
||||
}
|
||||
_ => {
|
||||
to.set_attribute(attribute.name, attribute.namespace, &attribute.value, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Lightly diff the two templates, checking only their roots.
|
||||
@@ -650,7 +731,9 @@ impl VNode {
|
||||
let id = self.assign_static_node_as_dynamic(path, root, dom, to);
|
||||
|
||||
loop {
|
||||
self.write_attribute(mount, attr_id, id, dom, to);
|
||||
for attr in &*self.dynamic_attrs[attr_id] {
|
||||
self.write_attribute(path, attr, id, mount, dom, to);
|
||||
}
|
||||
|
||||
// Only push the dynamic attributes forward if they match the current path (same element)
|
||||
match attrs.next_if(|(_, p)| *p == path) {
|
||||
@@ -661,40 +744,6 @@ impl VNode {
|
||||
}
|
||||
}
|
||||
|
||||
fn write_attribute(
|
||||
&self,
|
||||
mount: MountId,
|
||||
idx: usize,
|
||||
id: ElementId,
|
||||
dom: &mut VirtualDom,
|
||||
to: &mut impl WriteMutations,
|
||||
) {
|
||||
// Make sure we set the attribute's associated id
|
||||
dom.mounts[mount.0].mounted_attributes[idx] = id;
|
||||
|
||||
let attribute = &self.dynamic_attrs[idx];
|
||||
|
||||
match &attribute.value {
|
||||
AttributeValue::Listener(_) => {
|
||||
// If this is a listener, we need to create an element reference for it so that when we receive an event, we can find the element
|
||||
let path = &self.template.get().attr_paths[idx];
|
||||
|
||||
// The mount information should always be in the VDOM at this point
|
||||
debug_assert!(dom.mounts.get(mount.0).is_some());
|
||||
|
||||
let element_ref = ElementRef {
|
||||
path: ElementPath { path },
|
||||
mount,
|
||||
};
|
||||
dom.elements[id.0] = Some(element_ref);
|
||||
to.create_event_listener(&attribute.name[2..], id);
|
||||
}
|
||||
_ => {
|
||||
to.set_attribute(attribute.name, attribute.namespace, &attribute.value, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_template_root(
|
||||
&self,
|
||||
mount: MountId,
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::{
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
backtrace::Backtrace,
|
||||
cell::{Cell, RefCell},
|
||||
cell::RefCell,
|
||||
error::Error,
|
||||
fmt::{Debug, Display},
|
||||
rc::Rc,
|
||||
|
||||
@@ -74,9 +74,9 @@ pub(crate) mod innerlude {
|
||||
pub use crate::innerlude::{
|
||||
fc_to_builder, generation, once, schedule_update, schedule_update_any, vdom_is_rendering,
|
||||
AnyValue, Attribute, AttributeValue, CapturedError, Component, DynamicNode, Element, ElementId,
|
||||
Event, Fragment, IntoDynNode, Mutation, Mutations, NoOpMutations, Properties, RenderReturn,
|
||||
ScopeId, Task, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VNodeInner,
|
||||
VPlaceholder, VText, VirtualDom, WriteMutations,
|
||||
Event, Fragment, HasAttributes, IntoDynNode, Mutation, Mutations, NoOpMutations, Properties,
|
||||
RenderReturn, ScopeId, Task, Template, TemplateAttribute, TemplateNode, VComponent, VNode,
|
||||
VNodeInner, VPlaceholder, VText, VirtualDom, WriteMutations,
|
||||
};
|
||||
|
||||
/// The purpose of this module is to alleviate imports of many common types
|
||||
@@ -87,9 +87,9 @@ pub mod prelude {
|
||||
consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, generation,
|
||||
has_context, needs_update, once, parent_scope, provide_context, provide_root_context,
|
||||
push_future, remove_future, schedule_update, schedule_update_any, spawn, spawn_forever,
|
||||
suspend, use_error_boundary, use_hook, AnyValue, Component, Element, ErrorBoundary, Event,
|
||||
EventHandler, Fragment, IntoAttributeValue, IntoDynNode, Properties, Runtime, RuntimeGuard,
|
||||
ScopeId, Task, Template, TemplateAttribute, TemplateNode, Throw, VNode, VNodeInner,
|
||||
VirtualDom,
|
||||
suspend, use_error_boundary, use_hook, AnyValue, Attribute, Component, Element,
|
||||
ErrorBoundary, Event, EventHandler, Fragment, HasAttributes, IntoAttributeValue,
|
||||
IntoDynNode, Properties, Runtime, RuntimeGuard, ScopeId, Task, Template, TemplateAttribute,
|
||||
TemplateNode, Throw, VNode, VNodeInner, VirtualDom,
|
||||
};
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use crate::{
|
||||
};
|
||||
use crate::{arena::ElementId, Element, Event};
|
||||
use crate::{Properties, VirtualDom};
|
||||
use core::panic;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
use std::vec;
|
||||
@@ -94,11 +95,34 @@ pub struct VNodeInner {
|
||||
/// The static nodes and static descriptor of the template
|
||||
pub template: Cell<Template>,
|
||||
|
||||
/// The dynamic parts of the template
|
||||
/// The dynamic nodes in the template
|
||||
pub dynamic_nodes: Box<[DynamicNode]>,
|
||||
|
||||
/// The dynamic parts of the template
|
||||
pub dynamic_attrs: Box<[Attribute]>,
|
||||
/// The dynamic attribute slots in the template
|
||||
///
|
||||
/// This is a list of positions in the template where dynamic attributes can be inserted.
|
||||
///
|
||||
/// The inner list *must* be in the format [static named attributes, remaining dynamically named attributes].
|
||||
///
|
||||
/// For example:
|
||||
/// ```rust
|
||||
/// div {
|
||||
/// class: "{class}",
|
||||
/// ..attrs,
|
||||
/// p {
|
||||
/// color: "{color}",
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Would be represented as:
|
||||
/// ```rust
|
||||
/// [
|
||||
/// [class, every attribute in attrs sorted by name], // Slot 0 in the template
|
||||
/// [color], // Slot 1 in the template
|
||||
/// ]
|
||||
/// ```
|
||||
pub dynamic_attrs: Box<[Box<[Attribute]>]>,
|
||||
}
|
||||
|
||||
/// A reference to a template along with any context needed to hydrate it
|
||||
@@ -186,7 +210,7 @@ impl VNode {
|
||||
key: Option<String>,
|
||||
template: Template,
|
||||
dynamic_nodes: Box<[DynamicNode]>,
|
||||
dynamic_attrs: Box<[Attribute]>,
|
||||
dynamic_attrs: Box<[Box<[Attribute]>]>,
|
||||
) -> Self {
|
||||
Self {
|
||||
vnode: Rc::new(VNodeInner {
|
||||
@@ -572,7 +596,7 @@ pub enum TemplateAttribute {
|
||||
}
|
||||
|
||||
/// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Attribute {
|
||||
/// The name of the attribute.
|
||||
pub name: &'static str,
|
||||
@@ -687,6 +711,19 @@ impl PartialEq for AttributeValue {
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for AttributeValue {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Self::Text(arg0) => Self::Text(arg0.clone()),
|
||||
Self::Float(arg0) => Self::Float(*arg0),
|
||||
Self::Int(arg0) => Self::Int(*arg0),
|
||||
Self::Bool(arg0) => Self::Bool(*arg0),
|
||||
Self::Listener(_) | Self::Any(_) => panic!("Cannot clone listener or any value"),
|
||||
Self::None => Self::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait AnyValue: 'static {
|
||||
fn any_cmp(&self, other: &dyn AnyValue) -> bool;
|
||||
@@ -880,11 +917,11 @@ impl<T: IntoAttributeValue> IntoAttributeValue for Option<T> {
|
||||
}
|
||||
|
||||
/// A trait for anything that has a dynamic list of attributes
|
||||
pub trait HasAttributes<'a> {
|
||||
pub trait HasAttributes {
|
||||
/// Push an attribute onto the list of attributes
|
||||
fn push_attribute(
|
||||
self,
|
||||
name: &'a str,
|
||||
name: &'static str,
|
||||
ns: Option<&'static str>,
|
||||
attr: impl IntoAttributeValue,
|
||||
volatile: bool,
|
||||
|
||||
@@ -391,20 +391,22 @@ impl VirtualDom {
|
||||
let node_template = el_ref.template.get();
|
||||
let target_path = path.path;
|
||||
|
||||
for (idx, attr) in el_ref.dynamic_attrs.iter().enumerate() {
|
||||
for (idx, attrs) in el_ref.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
|
||||
if attr.name.trim_start_matches("on") == name
|
||||
&& target_path.is_decendant(&this_path)
|
||||
{
|
||||
listeners.push(&attr.value);
|
||||
for attr in attrs.iter() {
|
||||
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
|
||||
if attr.name.trim_start_matches("on") == name
|
||||
&& target_path.is_decendant(&this_path)
|
||||
{
|
||||
listeners.push(&attr.value);
|
||||
|
||||
// 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
|
||||
// documented, or be rejected from the rsx! macro outright
|
||||
if target_path == this_path {
|
||||
break;
|
||||
// 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
|
||||
// documented, or be rejected from the rsx! macro outright
|
||||
if target_path == this_path {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -436,15 +438,17 @@ impl VirtualDom {
|
||||
for (idx, attr) in el_ref.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 {
|
||||
self.runtime.rendering.set(false);
|
||||
listener.call(uievent.clone());
|
||||
self.runtime.rendering.set(true);
|
||||
for attr in attr.iter() {
|
||||
// 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 {
|
||||
self.runtime.rendering.set(false);
|
||||
listener.call(uievent.clone());
|
||||
self.runtime.rendering.set(true);
|
||||
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user