allow many attributes to be attached to one element

This commit is contained in:
Evan Almloff 2023-09-20 16:02:04 -05:00
parent dc446b5e5b
commit fc8c25280a
12 changed files with 289 additions and 96 deletions

View file

@ -1,5 +1,7 @@
use crate::any_props::AnyProps; use crate::any_props::AnyProps;
use crate::innerlude::{BorrowedAttributeValue, VComponent, VPlaceholder, VText}; use crate::innerlude::{
AttributeType, BorrowedAttributeValue, MountedAttribute, 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;
@ -283,7 +285,7 @@ impl<'b> VirtualDom {
let id = self.assign_static_node_as_dynamic(path, root, node, attr_id); let id = self.assign_static_node_as_dynamic(path, root, node, attr_id);
loop { loop {
self.write_attribute(&node.dynamic_attrs[attr_id], id); self.write_attribute_type(&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) {
@ -294,10 +296,20 @@ impl<'b> VirtualDom {
} }
} }
fn write_attribute(&mut self, attribute: &'b crate::Attribute<'b>, id: ElementId) { fn write_attribute_type(&mut self, attribute: &'b MountedAttribute<'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);
match &attribute.ty {
AttributeType::Single(attribute) => self.write_attribute(attribute, id),
AttributeType::Many(attribute) => {
for attribute in *attribute {
self.write_attribute(attribute, id);
}
}
}
}
pub(crate) fn write_attribute(&mut self, attribute: &'b crate::Attribute<'b>, id: ElementId) {
// Safety: we promise not to re-alias this text later on after committing it to the mutation // Safety: we promise not to re-alias this text later on after committing it to the mutation
let unbounded_name: &str = unsafe { std::mem::transmute(attribute.name) }; let unbounded_name: &str = unsafe { std::mem::transmute(attribute.name) };

View file

@ -1,7 +1,9 @@
use crate::{ use crate::{
any_props::AnyProps, any_props::AnyProps,
arena::ElementId, arena::ElementId,
innerlude::{BorrowedAttributeValue, DirtyScope, VComponent, VPlaceholder, VText}, innerlude::{
AttributeType, BorrowedAttributeValue, DirtyScope, VComponent, VPlaceholder, VText,
},
mutations::Mutation, mutations::Mutation,
nodes::RenderReturn, nodes::RenderReturn,
nodes::{DynamicNode, VNode}, nodes::{DynamicNode, VNode},
@ -103,16 +105,51 @@ 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_id = left_attr.mounted_element.get();
.mounted_element right_attr.mounted_element.set(mounted_id);
.set(left_attr.mounted_element.get());
// We want to make sure anything that gets pulled is valid // We want to make sure anything that gets pulled is valid
self.update_template(left_attr.mounted_element.get(), right_template); self.update_template(left_attr.mounted_element.get(), right_template);
// If the attributes are different (or volatile), we need to update them match (&left_attr.ty, &right_attr.ty) {
if left_attr.value != right_attr.value || left_attr.volatile { (AttributeType::Single(left), AttributeType::Single(right)) => {
self.update_attribute(right_attr, left_attr); self.diff_attribute(left, right, mounted_id)
}
(AttributeType::Many(left), AttributeType::Many(right)) => {
let mut left_iter = left.iter().peekable();
let mut right_iter = right.iter().peekable();
loop {
match (left_iter.peek(), right_iter.peek()) {
(Some(left), Some(right)) => {
// check which name is greater
match left.name.cmp(right.name) {
std::cmp::Ordering::Less => self.remove_attribute(
left.name,
left.namespace,
mounted_id,
),
std::cmp::Ordering::Greater => {
self.write_attribute(right, mounted_id)
}
std::cmp::Ordering::Equal => {
self.diff_attribute(left, right, mounted_id)
}
}
}
(Some(_), None) => {
let left = left_iter.next().unwrap();
self.remove_attribute(left.name, left.namespace, mounted_id)
}
(None, Some(_)) => {
let right = right_iter.next().unwrap();
self.write_attribute(right, mounted_id)
}
(None, None) => break,
}
}
}
_ => unreachable!("The macro should never generate this case"),
} }
}); });
@ -138,6 +175,18 @@ impl<'b> VirtualDom {
} }
} }
fn diff_attribute(
&mut self,
left_attr: &'b Attribute<'b>,
right_attr: &'b Attribute<'b>,
id: ElementId,
) {
// If the attributes are different (or volatile), we need to update them
if left_attr.value != right_attr.value || left_attr.volatile {
self.update_attribute(right_attr, left_attr, id);
}
}
fn diff_dynamic_node( fn diff_dynamic_node(
&mut self, &mut self,
left_node: &'b DynamicNode<'b>, left_node: &'b DynamicNode<'b>,
@ -155,12 +204,29 @@ impl<'b> VirtualDom {
}; };
} }
fn update_attribute(&mut self, right_attr: &'b Attribute<'b>, left_attr: &'b Attribute) { fn remove_attribute(&mut self, name: &'b str, ns: Option<&'static str>, id: ElementId) {
let name = unsafe { std::mem::transmute(name) };
let value: BorrowedAttributeValue<'b> = BorrowedAttributeValue::None;
let value = unsafe { std::mem::transmute(value) };
self.mutations.push(Mutation::SetAttribute {
id,
ns,
name,
value,
});
}
fn update_attribute(
&mut self,
right_attr: &'b Attribute<'b>,
left_attr: &'b Attribute<'b>,
id: ElementId,
) {
let name = unsafe { std::mem::transmute(left_attr.name) }; let name = unsafe { std::mem::transmute(left_attr.name) };
let value: BorrowedAttributeValue<'b> = (&right_attr.value).into(); let value: BorrowedAttributeValue<'b> = (&right_attr.value).into();
let value = unsafe { std::mem::transmute(value) }; let value = unsafe { std::mem::transmute(value) };
self.mutations.push(Mutation::SetAttribute { self.mutations.push(Mutation::SetAttribute {
id: left_attr.mounted_element.get(), id,
ns: right_attr.namespace, ns: right_attr.namespace,
name, name,
value, value,

View file

@ -73,11 +73,11 @@ pub(crate) mod innerlude {
} }
pub use crate::innerlude::{ pub use crate::innerlude::{
fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeValue, BorrowedAttributeValue, fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeType, AttributeValue,
CapturedError, Component, DynamicNode, Element, ElementId, Event, Fragment, HasAttributesBox, BorrowedAttributeValue, CapturedError, Component, DynamicNode, Element, ElementId, Event,
IntoDynNode, LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope, ScopeId, Fragment, HasAttributesBox, IntoDynNode, LazyNodes, MountedAttribute, Mutation, Mutations,
ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, TaskId, Template,
VPlaceholder, VText, VirtualDom, TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText, VirtualDom,
}; };
/// The purpose of this module is to alleviate imports of many common types /// The purpose of this module is to alleviate imports of many common types
@ -88,9 +88,9 @@ pub mod prelude {
consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, has_context, consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, has_context,
provide_context, provide_context_to_scope, provide_root_context, push_future, provide_context, provide_context_to_scope, provide_root_context, push_future,
remove_future, schedule_update_any, spawn, spawn_forever, suspend, throw, AnyValue, remove_future, schedule_update_any, spawn, spawn_forever, suspend, throw, AnyValue,
Component, Element, Event, EventHandler, Fragment, IntoAttributeValue, LazyNodes, AttributeType, Component, Element, Event, EventHandler, Fragment, IntoAttributeValue,
Properties, Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, LazyNodes, MountedAttribute, Properties, Scope, ScopeId, ScopeState, Scoped, TaskId,
TemplateNode, Throw, VNode, VirtualDom, Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom,
}; };
} }

View file

@ -60,7 +60,7 @@ pub struct VNode<'a> {
pub dynamic_nodes: &'a [DynamicNode<'a>], pub dynamic_nodes: &'a [DynamicNode<'a>],
/// The dynamic parts of the template /// The dynamic parts of the template
pub dynamic_attrs: &'a [Attribute<'a>], pub dynamic_attrs: &'a [MountedAttribute<'a>],
} }
impl<'a> VNode<'a> { impl<'a> VNode<'a> {
@ -414,6 +414,44 @@ pub enum TemplateAttribute<'a> {
}, },
} }
#[derive(Debug)]
pub struct MountedAttribute<'a> {
pub(crate) ty: AttributeType<'a>,
/// The element in the DOM that this attribute belongs to
pub(crate) mounted_element: Cell<ElementId>,
}
impl<'a> From<Attribute<'a>> for MountedAttribute<'a> {
fn from(attr: Attribute<'a>) -> Self {
Self {
ty: AttributeType::Single(attr),
mounted_element: Default::default(),
}
}
}
impl<'a> From<&'a [Attribute<'a>]> for MountedAttribute<'a> {
fn from(attr: &'a [Attribute<'a>]) -> Self {
Self {
ty: AttributeType::Many(attr),
mounted_element: Default::default(),
}
}
}
impl<'a> MountedAttribute<'a> {
/// Get the type of this attribute
pub fn attribute_type(&self) -> &AttributeType<'a> {
&self.ty
}
/// Get the element that this attribute is mounted to
pub fn mounted_element(&self) -> ElementId {
self.mounted_element.get()
}
}
/// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"` /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`
#[derive(Debug)] #[derive(Debug)]
pub struct Attribute<'a> { pub struct Attribute<'a> {
@ -430,9 +468,6 @@ pub struct Attribute<'a> {
/// An indication of we should always try and set the attribute. Used in controlled components to ensure changes are propagated /// An indication of we should always try and set the attribute. Used in controlled components to ensure changes are propagated
pub volatile: bool, pub volatile: bool,
/// The element in the DOM that this attribute belongs to
pub(crate) mounted_element: Cell<ElementId>,
} }
impl<'a> Attribute<'a> { impl<'a> Attribute<'a> {
@ -448,13 +483,38 @@ impl<'a> Attribute<'a> {
value, value,
namespace, namespace,
volatile, volatile,
mounted_element: Cell::new(ElementId::default()), }
}
}
#[derive(Debug)]
pub enum AttributeType<'a> {
Single(Attribute<'a>),
/// Many different attributes sorted by name
Many(&'a [Attribute<'a>]),
}
impl<'a> AttributeType<'a> {
/// Call the given function on each attribute
pub fn for_each<'b, F>(&'b self, mut f: F)
where
F: FnMut(&'b Attribute<'a>),
{
match self {
Self::Single(attr) => f(attr),
Self::Many(attrs) => attrs.iter().for_each(f),
} }
} }
/// Get the element that this attribute is mounted to /// Try to call the given function on each attribute
pub fn mounted_element(&self) -> ElementId { pub fn try_for_each<'b, F, E>(&'b self, mut f: F) -> Result<(), E>
self.mounted_element.get() where
F: FnMut(&'b Attribute<'a>) -> Result<(), E>,
{
match self {
Self::Single(attr) => f(attr),
Self::Many(attrs) => attrs.iter().try_for_each(f),
}
} }
} }

View file

@ -8,7 +8,8 @@ use crate::{
nodes::{IntoAttributeValue, IntoDynNode, RenderReturn}, nodes::{IntoAttributeValue, IntoDynNode, RenderReturn},
runtime::Runtime, runtime::Runtime,
scope_context::ScopeContext, scope_context::ScopeContext,
AnyValue, Attribute, AttributeValue, Element, Event, Properties, TaskId, AnyValue, Attribute, AttributeType, AttributeValue, Element, Event, MountedAttribute,
Properties, TaskId,
}; };
use bumpalo::{boxed::Box as BumpBox, Bump}; use bumpalo::{boxed::Box as BumpBox, Bump};
use std::{ use std::{
@ -335,14 +336,10 @@ impl<'src> ScopeState {
let mut listeners = self.attributes_to_drop.borrow_mut(); let mut listeners = self.attributes_to_drop.borrow_mut();
for attr in element.dynamic_attrs { for attr in element.dynamic_attrs {
match attr.value { attr.ty.for_each(|attr| {
AttributeValue::Any(_) | AttributeValue::Listener(_) => { let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) }; listeners.push(unbounded);
listeners.push(unbounded); })
}
_ => (),
}
} }
let mut props = self.borrowed_props.borrow_mut(); let mut props = self.borrowed_props.borrow_mut();
@ -393,13 +390,15 @@ impl<'src> ScopeState {
value: impl IntoAttributeValue<'src>, value: impl IntoAttributeValue<'src>,
namespace: Option<&'static str>, namespace: Option<&'static str>,
volatile: bool, volatile: bool,
) -> Attribute<'src> { ) -> MountedAttribute<'src> {
Attribute { MountedAttribute {
name, ty: AttributeType::Single(Attribute {
namespace, name,
volatile, namespace,
volatile,
value: value.into_value(self.bump()),
}),
mounted_element: Default::default(), mounted_element: Default::default(),
value: value.into_value(self.bump()),
} }
} }

View file

@ -5,13 +5,13 @@
use crate::{ use crate::{
any_props::VProps, any_props::VProps,
arena::{ElementId, ElementRef}, arena::{ElementId, ElementRef},
innerlude::{DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg}, innerlude::{AttributeType, DirtyScope, 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, Attribute, AttributeValue, Element, Event, Scope,
}; };
use futures_util::{pin_mut, StreamExt}; use futures_util::{pin_mut, StreamExt};
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
@ -371,12 +371,30 @@ impl VirtualDom {
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 fn add_listener<'a>(
if attr.name.trim_start_matches("on") == name attribute: &'a Attribute<'a>,
&& target_path.is_decendant(&this_path) event_name: &str,
{ listeners: &mut Vec<&'a AttributeValue<'a>>,
listeners.push(&attr.value); ) {
if attribute.name.trim_start_matches("on") == event_name {
listeners.push(&attribute.value);
}
listeners.push(&attribute.value);
}
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
if target_path.is_decendant(&this_path) {
match &attr.ty {
AttributeType::Single(attribute) => {
add_listener(attribute, name, &mut listeners);
}
AttributeType::Many(attributes) => {
for attribute in *attributes {
add_listener(attribute, name, &mut listeners);
}
}
}
// 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
@ -422,20 +440,57 @@ impl VirtualDom {
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 fn call_listener(
// Only call the listener if this is the exact target element. attribute: &Attribute,
if attr.name.trim_start_matches("on") == name && target_path == this_path { event_name: &str,
if let AttributeValue::Listener(listener) = &attr.value { uievent: &Event<dyn Any>,
let origin = el_ref.scope; runtime: &Runtime,
self.runtime.scope_stack.borrow_mut().push(origin); origin: ScopeId,
self.runtime.rendering.set(false); ) -> bool {
if let Some(cb) = listener.borrow_mut().as_deref_mut() { // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
cb(uievent.clone()); // Only call the listener if this is the exact target element.
} if attribute.name.trim_start_matches("on") == event_name {
self.runtime.scope_stack.borrow_mut().pop(); if let AttributeValue::Listener(listener) = &attribute.value {
self.runtime.rendering.set(true); runtime.scope_stack.borrow_mut().push(origin);
runtime.rendering.set(false);
if let Some(cb) = listener.borrow_mut().as_deref_mut() {
cb(uievent.clone());
}
runtime.scope_stack.borrow_mut().pop();
runtime.rendering.set(true);
break; return true;
}
}
false
}
if target_path == this_path {
match &attr.ty {
AttributeType::Single(attribute) => {
if call_listener(
attribute,
name,
&uievent,
&self.runtime,
el_ref.scope,
) {
return;
}
}
AttributeType::Many(attributes) => {
for attribute in *attributes {
if call_listener(
attribute,
name,
&uievent,
&self.runtime,
el_ref.scope,
) {
return;
}
}
}
} }
} }
} }

View file

@ -1,7 +1,7 @@
#![cfg(not(miri))] #![cfg(not(miri))]
use dioxus::prelude::Props; use dioxus::prelude::Props;
use dioxus_core::*; use dioxus_core::{MountedAttribute, *};
use std::{cell::Cell, collections::HashSet}; use std::{cell::Cell, collections::HashSet};
fn random_ns() -> Option<&'static str> { fn random_ns() -> Option<&'static str> {
@ -205,7 +205,7 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode {
} }
} }
fn create_random_dynamic_attr(cx: &ScopeState) -> Attribute { fn create_random_dynamic_attr(cx: &ScopeState) -> MountedAttribute {
let value = match rand::random::<u8>() % 7 { let value = match rand::random::<u8>() % 7 {
0 => AttributeValue::Text(Box::leak( 0 => AttributeValue::Text(Box::leak(
format!("{}", rand::random::<usize>()).into_boxed_str(), format!("{}", rand::random::<usize>()).into_boxed_str(),
@ -217,7 +217,7 @@ fn create_random_dynamic_attr(cx: &ScopeState) -> Attribute {
5 => AttributeValue::None, 5 => AttributeValue::None,
6 => { 6 => {
let value = cx.listener(|e: Event<String>| println!("{:?}", e)); let value = cx.listener(|e: Event<String>| println!("{:?}", e));
return Attribute::new("ondata", value, None, false); return Attribute::new("ondata", value, None, false).into();
} }
_ => unreachable!(), _ => unreachable!(),
}; };
@ -227,6 +227,7 @@ fn create_random_dynamic_attr(cx: &ScopeState) -> Attribute {
random_ns(), random_ns(),
rand::random(), rand::random(),
) )
.into()
} }
static mut TEMPLATE_COUNT: usize = 0; static mut TEMPLATE_COUNT: usize = 0;

View file

@ -164,7 +164,7 @@ fn props_spread() {
static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template { name: concat!(file!(), ":", line!(), ":", column!(), ":", "" ), roots: &[::dioxus::core::TemplateNode::Element { tag: dioxus_elements::audio::TAG_NAME, namespace: dioxus_elements::audio::NAME_SPACE, attrs: &[::dioxus::core::TemplateAttribute::Dynamic { id: 0usize }], children: &[] }], node_paths: &[], attr_paths: &[&[0u8]] }; static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template { name: concat!(file!(), ":", line!(), ":", column!(), ":", "" ), roots: &[::dioxus::core::TemplateNode::Element { tag: dioxus_elements::audio::TAG_NAME, namespace: dioxus_elements::audio::NAME_SPACE, attrs: &[::dioxus::core::TemplateAttribute::Dynamic { id: 0usize }], children: &[] }], node_paths: &[], attr_paths: &[&[0u8]] };
let mut attrs = vec![__cx.attr(dioxus_elements::audio::muted.0, muted, dioxus_elements::audio::muted.1, dioxus_elements::audio::muted.2)]; let mut attrs = vec![__cx.attr(dioxus_elements::audio::muted.0, muted, dioxus_elements::audio::muted.1, dioxus_elements::audio::muted.2)];
for attr in attributes { for attr in attributes {
attrs.push(__cx.attr(attr.name, attr.value.into_value(__cx.bump()), attr.namespace, attr.volatile)); attrs.push(attr);
} }
::dioxus::core::VNode { ::dioxus::core::VNode {
parent: None, parent: None,

View file

@ -1 +1 @@
fn main() {} fn main() {}

View file

@ -9,7 +9,7 @@ macro_rules! impl_event {
$( $(
$( #[$attr] )* $( #[$attr] )*
#[inline] #[inline]
pub fn $name<'a, E: crate::EventReturn<T>, T>(_cx: &'a ::dioxus_core::ScopeState, mut _f: impl FnMut(::dioxus_core::Event<$data>) -> E + 'a) -> ::dioxus_core::Attribute<'a> { pub fn $name<'a, E: crate::EventReturn<T>, T>(_cx: &'a ::dioxus_core::ScopeState, mut _f: impl FnMut(::dioxus_core::Event<$data>) -> E + 'a) -> ::dioxus_core::MountedAttribute<'a> {
::dioxus_core::Attribute::new( ::dioxus_core::Attribute::new(
stringify!($name), stringify!($name),
_cx.listener(move |e: ::dioxus_core::Event<$data>| { _cx.listener(move |e: ::dioxus_core::Event<$data>| {
@ -17,7 +17,7 @@ macro_rules! impl_event {
}), }),
None, None,
false, false,
) ).into()
} }
)* )*
}; };

View file

@ -167,7 +167,7 @@ impl Parse for Element {
attributes, attributes,
children, children,
brace, brace,
extra_attributes extra_attributes,
}) })
} }
} }
@ -397,11 +397,3 @@ impl ToTokens for ElementAttrNamed {
tokens.append_all(attribute); tokens.append_all(attribute);
} }
} }
// ::dioxus::core::Attribute {
// name: stringify!(#name),
// namespace: None,
// volatile: false,
// mounted_node: Default::default(),
// value: ::dioxus::core::AttributeValue::Text(#value),
// }

View file

@ -79,23 +79,31 @@ impl Renderer {
match segment { match segment {
Segment::Attr(idx) => { Segment::Attr(idx) => {
let attr = &template.dynamic_attrs[*idx]; let attr = &template.dynamic_attrs[*idx];
if attr.name == "dangerous_inner_html" { attr.attribute_type().try_for_each(|attr| {
inner_html = Some(attr); if attr.name == "dangerous_inner_html" {
} else if attr.namespace == Some("style") { inner_html = Some(attr);
accumulated_dynamic_styles.push(attr); } else if attr.namespace == Some("style") {
} else { accumulated_dynamic_styles.push(attr);
match attr.value { } else {
AttributeValue::Text(value) => { match attr.value {
write!(buf, " {}=\"{}\"", attr.name, value)? AttributeValue::Text(value) => {
} write!(buf, " {}=\"{}\"", attr.name, value)?
AttributeValue::Bool(value) => write!(buf, " {}={}", attr.name, value)?, }
AttributeValue::Int(value) => write!(buf, " {}={}", attr.name, value)?, AttributeValue::Bool(value) => {
AttributeValue::Float(value) => { write!(buf, " {}={}", attr.name, value)?
write!(buf, " {}={}", attr.name, value)? }
} AttributeValue::Int(value) => {
_ => {} write!(buf, " {}={}", attr.name, value)?
}; }
} AttributeValue::Float(value) => {
write!(buf, " {}={}", attr.name, value)?
}
_ => {}
};
}
Ok(())
})?;
} }
Segment::Node(idx) => match &template.dynamic_nodes[*idx] { Segment::Node(idx) => match &template.dynamic_nodes[*idx] {
DynamicNode::Component(node) => { DynamicNode::Component(node) => {