fix custom attribute value support

This commit is contained in:
Evan Almloff committed 2022-12-09 16:18:37 -06:00
1 parent 937eb1f0f0
commit 8a1c96a68c
28 files changed
+369 -276

No files matched your search

+14 -23
View File
@@ -105,27 +105,10 @@ impl<'b> VirtualDom {
attribute.mounted_element.set(id);
// Safety: we promise not to re-alias this text later on after committing it to the mutation
let unbounded_name = unsafe { std::mem::transmute(attribute.name) };
let unbounded_name: &str =
unsafe { std::mem::transmute(attribute.name) };
match &attribute.value {
AttributeValue::Text(value) => {
// Safety: we promise not to re-alias this text later on after committing it to the mutation
let unbounded_value = unsafe { std::mem::transmute(*value) };
self.mutations.push(SetAttribute {
name: unbounded_name,
value: unbounded_value,
ns: attribute.namespace,
id,
})
}
AttributeValue::Bool(value) => {
self.mutations.push(SetBoolAttribute {
name: unbounded_name,
value: *value,
id,
})
}
AttributeValue::Listener(_) => {
self.mutations.push(NewEventListener {
// all listeners start with "on"
@@ -134,10 +117,18 @@ impl<'b> VirtualDom {
id,
})
}
AttributeValue::Float(_) => todo!(),
AttributeValue::Int(_) => todo!(),
AttributeValue::Any(_) => todo!(),
AttributeValue::None => todo!(),
_ => {
// Safety: we promise not to re-alias this text later on after committing it to the mutation
let unbounded_value =
unsafe { std::mem::transmute(attribute.value.clone()) };
self.mutations.push(SetAttribute {
name: unbounded_name,
value: unbounded_value,
ns: attribute.namespace,
id,
})
}
}
// Only push the dynamic attributes forward if they match the current path (same element)
+8 -14
View File
@@ -77,20 +77,14 @@ impl<'b> VirtualDom {
if left_attr.value != right_attr.value || left_attr.volatile {
// todo: add more types of attribute values
match right_attr.value {
AttributeValue::Text(text) => {
let name = unsafe { std::mem::transmute(left_attr.name) };
let value = unsafe { std::mem::transmute(text) };
self.mutations.push(Mutation::SetAttribute {
id: left_attr.mounted_element.get(),
ns: right_attr.namespace,
name,
value,
});
}
// todo: more types of attribute values
_ => todo!("other attribute types"),
}
let name = unsafe { std::mem::transmute(left_attr.name) };
let value = unsafe { std::mem::transmute(right_attr.value.clone()) };
self.mutations.push(Mutation::SetAttribute {
id: left_attr.mounted_element.get(),
ns: right_attr.namespace,
name,
value,
});
}
}
+7 -7
View File
@@ -70,10 +70,10 @@ pub(crate) mod innerlude {
}
pub use crate::innerlude::{
fc_to_builder, Attribute, AttributeValue, Component, DynamicNode, Element, ElementId, Event,
Fragment, IntoDynNode, LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope,
ScopeId, ScopeState, Scoped, SuspenseContext, TaskId, Template, TemplateAttribute,
TemplateNode, VComponent, VNode, VText, VirtualDom,
fc_to_builder, AnyValueBox, Attribute, AttributeValue, Component, DynamicNode, Element,
ElementId, Event, Fragment, IntoAttributeValue, IntoDynNode, LazyNodes, Mutation, Mutations,
Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, SuspenseContext, TaskId,
Template, TemplateAttribute, TemplateNode, VComponent, VNode, VText, VirtualDom,
};
/// The purpose of this module is to alleviate imports of many common types
@@ -81,9 +81,9 @@ pub use crate::innerlude::{
/// This includes types like [`Scope`], [`Element`], and [`Component`].
pub mod prelude {
pub use crate::innerlude::{
fc_to_builder, Element, Event, EventHandler, Fragment, LazyNodes, Properties, Scope,
ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, VNode,
VirtualDom,
fc_to_builder, Element, Event, EventHandler, Fragment, IntoAttributeValue, LazyNodes,
Properties, Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute,
TemplateNode, VNode, VirtualDom,
};
}
+5 -16
View File
@@ -1,6 +1,6 @@
use fxhash::FxHashSet;
use crate::{arena::ElementId, ScopeId, Template};
use crate::{arena::ElementId, AttributeValue, ScopeId, Template};
/// A container for all the relevant steps to modify the Real DOM
///
@@ -48,7 +48,7 @@ impl<'a> Mutations<'a> {
/// Push a new mutation into the dom_edits list
pub(crate) fn push(&mut self, mutation: Mutation<'static>) {
self.edits.push(mutation)
unsafe { self.edits.push(std::mem::transmute(mutation)) }
}
}
@@ -61,7 +61,7 @@ impl<'a> Mutations<'a> {
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type")
)]
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq)]
pub enum Mutation<'a> {
/// Add these m children to the target element
AppendChildren {
@@ -193,8 +193,9 @@ pub enum Mutation<'a> {
SetAttribute {
/// The name of the attribute to set.
name: &'a str,
/// The value of the attribute.
value: &'a str,
value: AttributeValue<'a>,
/// The ID of the node to set the attribute of.
id: ElementId,
@@ -204,18 +205,6 @@ pub enum Mutation<'a> {
ns: Option<&'a str>,
},
/// Set the value of a node's attribute.
SetBoolAttribute {
/// The name of the attribute to set.
name: &'a str,
/// The value of the attribute.
value: bool,
/// The ID of the node to set the attribute of.
id: ElementId,
},
/// Set the textcontent of a node.
SetText {
/// The textcontent of the node
+71 -6
View File
@@ -8,6 +8,7 @@ use std::{
cell::{Cell, RefCell},
fmt::Arguments,
future::Future,
rc::Rc,
};
pub type TemplateId = &'static str;
@@ -88,7 +89,7 @@ impl<'a> VNode<'a> {
pub(crate) fn clear_listeners(&self) {
for attr in self.dynamic_attrs {
if let AttributeValue::Listener(l) = &attr.value {
l.borrow_mut().take();
l.0.borrow_mut().take();
}
}
}
@@ -317,6 +318,9 @@ pub struct Attribute<'a> {
///
/// These are built-in to be faster during the diffing process. To use a custom value, use the [`AttributeValue::Any`]
/// variant.
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", serde(untagged))]
#[derive(Clone)]
pub enum AttributeValue<'a> {
/// Text attribute
Text(&'a str),
@@ -331,16 +335,67 @@ pub enum AttributeValue<'a> {
Bool(bool),
/// A listener, like "onclick"
Listener(RefCell<Option<ListenerCb<'a>>>),
Listener(ListenerCb<'a>),
/// An arbitrary value that implements PartialEq and is static
Any(BumpBox<'a, dyn AnyValue>),
Any(AnyValueBox),
/// A "none" value, resulting in the removal of an attribute from the dom
None,
}
type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event<dyn Any>) + 'a>;
pub type ListenerCbInner<'a> = RefCell<Option<BumpBox<'a, dyn FnMut(Event<dyn Any>) + 'a>>>;
pub struct ListenerCb<'a>(pub ListenerCbInner<'a>);
impl Clone for ListenerCb<'_> {
fn clone(&self) -> Self {
panic!("ListenerCb cannot be cloned")
}
}
#[cfg(feature = "serialize")]
impl<'a> serde::Serialize for ListenerCb<'a> {
fn serialize<S>(&self, _: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
panic!("ListenerCb cannot be serialized")
}
}
#[cfg(feature = "serialize")]
impl<'de, 'a> serde::Deserialize<'de> for ListenerCb<'a> {
fn deserialize<D>(_: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
panic!("ListenerCb cannot be deserialized")
}
}
/// A boxed value that implements PartialEq and Any
#[derive(Clone)]
pub struct AnyValueBox(pub Rc<dyn AnyValue>);
#[cfg(feature = "serialize")]
impl serde::Serialize for AnyValueBox {
fn serialize<S>(&self, _: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
panic!("AnyValueBox cannot be serialized")
}
}
#[cfg(feature = "serialize")]
impl<'de> serde::Deserialize<'de> for AnyValueBox {
fn deserialize<D>(_: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
panic!("AnyValueBox cannot be deserialized")
}
}
impl<'a> std::fmt::Debug for AttributeValue<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -364,8 +419,8 @@ impl<'a> PartialEq for AttributeValue<'a> {
(Self::Int(l0), Self::Int(r0)) => l0 == r0,
(Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
(Self::Listener(_), Self::Listener(_)) => true,
(Self::Any(l0), Self::Any(r0)) => l0.any_cmp(r0.as_ref()),
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
(Self::Any(l0), Self::Any(r0)) => l0.0.any_cmp(r0.0.as_ref()),
_ => false,
}
}
}
@@ -559,21 +614,25 @@ impl<'a> IntoAttributeValue<'a> for &'a str {
AttributeValue::Text(self)
}
}
impl<'a> IntoAttributeValue<'a> for f64 {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Float(self)
}
}
impl<'a> IntoAttributeValue<'a> for i64 {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Int(self)
}
}
impl<'a> IntoAttributeValue<'a> for bool {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Bool(self)
}
}
impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
use bumpalo::core_alloc::fmt::Write;
@@ -582,3 +641,9 @@ impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
AttributeValue::Text(str_buf.into_bump_str())
}
}
impl<'a> IntoAttributeValue<'a> for AnyValueBox {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Any(self)
}
}
+2 -2
View File
@@ -4,7 +4,7 @@ use crate::{
arena::ElementId,
bump_frame::BumpFrame,
innerlude::{DynamicNode, EventHandler, VComponent, VText},
innerlude::{Scheduler, SchedulerMsg},
innerlude::{ListenerCb, Scheduler, SchedulerMsg},
lazynodes::LazyNodes,
nodes::{ComponentReturn, IntoAttributeValue, IntoDynNode, RenderReturn},
Attribute, AttributeValue, Element, Event, Properties, TaskId,
@@ -483,7 +483,7 @@ impl<'src> ScopeState {
}))
};
AttributeValue::Listener(RefCell::new(Some(boxed)))
AttributeValue::Listener(ListenerCb(RefCell::new(Some(boxed))))
}
/// Store a value between renders. The foundational hook for all other hooks.
+5 -5
View File
@@ -386,7 +386,7 @@ impl VirtualDom {
// We check the bubble state between each call to see if the event has been stopped from bubbling
for listener in listeners.drain(..).rev() {
if let AttributeValue::Listener(listener) = listener {
if let Some(cb) = listener.borrow_mut().as_deref_mut() {
if let Some(cb) = listener.0.borrow_mut().as_deref_mut() {
cb(uievent.clone());
}
@@ -493,7 +493,7 @@ impl VirtualDom {
RenderReturn::Async(_) => unreachable!("Root scope cannot be an async component"),
}
self.finalize()
unsafe { std::mem::transmute(self.finalize()) }
}
/// Render whatever the VirtualDom has ready as fast as possible without requiring an executor to progress
@@ -591,7 +591,7 @@ impl VirtualDom {
// If there's no pending suspense, then we have no reason to wait for anything
if self.scheduler.leaves.borrow().is_empty() {
return self.finalize();
return unsafe { std::mem::transmute(self.finalize()) };
}
// Poll the suspense leaves in the meantime
@@ -605,13 +605,13 @@ impl VirtualDom {
if let Either::Left((_, _)) = select(&mut deadline, pinned).await {
// release the borrowed
drop(work);
return self.finalize();
return unsafe { std::mem::transmute(self.finalize()) };
}
}
}
/// Swap the current mutations with a new
fn finalize(&mut self) -> Mutations {
fn finalize(&mut self) -> Mutations<'static> {
// todo: make this a routine
let mut out = Mutations::default();
std::mem::swap(&mut self.mutations, &mut out);