35 files changed
+1035
-265
No files matched your search
@@ -9,6 +9,7 @@ members = [
|
||||
"packages/extension",
|
||||
"packages/router",
|
||||
"packages/html",
|
||||
"packages/html-internal-macro",
|
||||
"packages/hooks",
|
||||
"packages/web",
|
||||
"packages/ssr",
|
||||
@@ -60,6 +61,7 @@ dioxus-core-macro = { path = "packages/core-macro", version = "0.4.0" }
|
||||
dioxus-router = { path = "packages/router", version = "0.4.1" }
|
||||
dioxus-router-macro = { path = "packages/router-macro", version = "0.4.1" }
|
||||
dioxus-html = { path = "packages/html", default-features = false, version = "0.4.0" }
|
||||
dioxus-html-internal-macro = { path = "packages/html-internal-macro", version = "0.4.0" }
|
||||
dioxus-hooks = { path = "packages/hooks", version = "0.4.0" }
|
||||
dioxus-web = { path = "packages/web", version = "0.4.0" }
|
||||
dioxus-ssr = { path = "packages/ssr", version = "0.4.0" }
|
||||
|
||||
@@ -139,7 +139,6 @@ Missing Features
|
||||
Missing examples
|
||||
- Shared state
|
||||
- Root-less element groups
|
||||
- Spread props
|
||||
- Custom elements
|
||||
- Component Children: Pass children into child components
|
||||
- Render To string: Render a mounted virtualdom to a string
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let _ = dom.rebuild();
|
||||
let html = dioxus_ssr::render(&dom);
|
||||
|
||||
println!("{}", html);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
render! {
|
||||
Component {
|
||||
width: "10px",
|
||||
extra_data: "hello{1}",
|
||||
extra_data2: "hello{2}",
|
||||
height: "10px",
|
||||
left: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Component<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
|
||||
render! {
|
||||
audio { ..cx.props.attributes, "1: {cx.props.extra_data}\n2: {cx.props.extra_data2}" }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Props)]
|
||||
struct Props<'a> {
|
||||
#[props(extends = GlobalAttributes)]
|
||||
attributes: Vec<Attribute<'a>>,
|
||||
extra_data: &'a str,
|
||||
extra_data2: &'a str,
|
||||
}
|
||||
@@ -166,7 +166,7 @@ impl Writer<'_> {
|
||||
|
||||
fn write_attributes(
|
||||
&mut self,
|
||||
attributes: &[ElementAttrNamed],
|
||||
attributes: &[AttributeType],
|
||||
key: &Option<IfmtInput>,
|
||||
sameline: bool,
|
||||
) -> Result {
|
||||
@@ -188,7 +188,7 @@ impl Writer<'_> {
|
||||
while let Some(attr) = attr_iter.next() {
|
||||
self.out.indent_level += 1;
|
||||
if !sameline {
|
||||
self.write_comments(attr.attr.start())?;
|
||||
self.write_comments(attr.start())?;
|
||||
}
|
||||
self.out.indent_level -= 1;
|
||||
|
||||
@@ -289,7 +289,14 @@ impl Writer<'_> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_attribute(&mut self, attr: &ElementAttrNamed) -> Result {
|
||||
fn write_attribute(&mut self, attr: &AttributeType) -> Result {
|
||||
match attr {
|
||||
AttributeType::Named(attr) => self.write_named_attribute(attr),
|
||||
AttributeType::Spread(attr) => self.write_spread_attribute(attr),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_named_attribute(&mut self, attr: &ElementAttrNamed) -> Result {
|
||||
self.write_attribute_name(&attr.attr.name)?;
|
||||
write!(self.out, ": ")?;
|
||||
self.write_attribute_value(&attr.attr.value)?;
|
||||
@@ -297,6 +304,13 @@ impl Writer<'_> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_spread_attribute(&mut self, attr: &Expr) -> Result {
|
||||
write!(self.out, "..")?;
|
||||
write!(self.out, "{}", prettyplease::unparse_expr(attr))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// make sure the comments are actually relevant to this element.
|
||||
// test by making sure this element is the primary element on this line
|
||||
pub fn current_span_is_primary(&self, location: Span) -> bool {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use dioxus_rsx::{BodyNode, ElementAttrNamed, ElementAttrValue, ForLoop};
|
||||
use dioxus_rsx::{AttributeType, BodyNode, ElementAttrValue, ForLoop};
|
||||
use proc_macro2::{LineColumn, Span};
|
||||
use quote::ToTokens;
|
||||
use std::{
|
||||
@@ -165,12 +165,12 @@ impl<'a> Writer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_short_attrs(&mut self, attributes: &[ElementAttrNamed]) -> usize {
|
||||
pub(crate) fn is_short_attrs(&mut self, attributes: &[AttributeType]) -> usize {
|
||||
let mut total = 0;
|
||||
|
||||
for attr in attributes {
|
||||
if self.current_span_is_primary(attr.attr.start()) {
|
||||
'line: for line in self.src[..attr.attr.start().start().line - 1].iter().rev() {
|
||||
if self.current_span_is_primary(attr.start()) {
|
||||
'line: for line in self.src[..attr.start().start().line - 1].iter().rev() {
|
||||
match (line.trim().starts_with("//"), line.is_empty()) {
|
||||
(true, _) => return 100000,
|
||||
(_, true) => continue 'line,
|
||||
@@ -179,16 +179,24 @@ impl<'a> Writer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
total += match &attr.attr.name {
|
||||
dioxus_rsx::ElementAttrName::BuiltIn(name) => {
|
||||
let name = name.to_string();
|
||||
name.len()
|
||||
match attr {
|
||||
AttributeType::Named(attr) => {
|
||||
let name_len = match &attr.attr.name {
|
||||
dioxus_rsx::ElementAttrName::BuiltIn(name) => {
|
||||
let name = name.to_string();
|
||||
name.len()
|
||||
}
|
||||
dioxus_rsx::ElementAttrName::Custom(name) => name.value().len() + 2,
|
||||
};
|
||||
total += name_len;
|
||||
total += self.attr_value_len(&attr.attr.value);
|
||||
}
|
||||
AttributeType::Spread(expr) => {
|
||||
let expr_len = self.retrieve_formatted_expr(expr).len();
|
||||
total += expr_len + 3;
|
||||
}
|
||||
dioxus_rsx::ElementAttrName::Custom(name) => name.value().len() + 2,
|
||||
};
|
||||
|
||||
total += self.attr_value_len(&attr.attr.value);
|
||||
|
||||
total += 6;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,10 +15,11 @@ proc-macro = true
|
||||
[dependencies]
|
||||
proc-macro2 = { version = "1.0" }
|
||||
quote = "1.0"
|
||||
syn = { version = "2.0", features = ["full", "extra-traits"] }
|
||||
syn = { version = "2.0", features = ["full", "extra-traits", "visit"] }
|
||||
dioxus-rsx = { workspace = true }
|
||||
dioxus-core = { workspace = true }
|
||||
constcat = "0.3.0"
|
||||
convert_case = "^0.6.0"
|
||||
prettyplease = "0.2.15"
|
||||
|
||||
# testing
|
||||
|
||||
@@ -24,6 +24,10 @@ pub fn impl_my_derive(ast: &syn::DeriveInput) -> Result<TokenStream, Error> {
|
||||
.included_fields()
|
||||
.map(|f| struct_info.field_impl(f))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let extends = struct_info
|
||||
.extend_fields()
|
||||
.map(|f| struct_info.extends_impl(f))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let fields = quote!(#(#fields)*).into_iter();
|
||||
let required_fields = struct_info
|
||||
.included_fields()
|
||||
@@ -36,6 +40,7 @@ pub fn impl_my_derive(ast: &syn::DeriveInput) -> Result<TokenStream, Error> {
|
||||
#builder_creation
|
||||
#conversion_helper
|
||||
#( #fields )*
|
||||
#( #extends )*
|
||||
#( #required_fields )*
|
||||
#build_method
|
||||
}
|
||||
@@ -167,8 +172,8 @@ mod field_info {
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::Expr;
|
||||
use syn::{parse::Error, punctuated::Punctuated};
|
||||
use syn::{Expr, Path};
|
||||
|
||||
use super::util::{
|
||||
expr_to_single_string, ident_to_type, path_to_single_string, strip_raw_ident_prefix,
|
||||
@@ -199,6 +204,13 @@ mod field_info {
|
||||
);
|
||||
}
|
||||
|
||||
// extended field is automatically empty
|
||||
if !builder_attr.extends.is_empty() {
|
||||
builder_attr.default = Some(
|
||||
syn::parse(quote!(::core::default::Default::default()).into()).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
// auto detect optional
|
||||
let strip_option_auto = builder_attr.strip_option
|
||||
|| !builder_attr.ignore_option
|
||||
@@ -253,6 +265,7 @@ mod field_info {
|
||||
pub auto_into: bool,
|
||||
pub strip_option: bool,
|
||||
pub ignore_option: bool,
|
||||
pub extends: Vec<Path>,
|
||||
}
|
||||
|
||||
impl FieldBuilderAttr {
|
||||
@@ -305,6 +318,17 @@ mod field_info {
|
||||
let name = expr_to_single_string(&assign.left)
|
||||
.ok_or_else(|| Error::new_spanned(&assign.left, "Expected identifier"))?;
|
||||
match name.as_str() {
|
||||
"extends" => {
|
||||
if let syn::Expr::Path(path) = *assign.right {
|
||||
self.extends.push(path.path);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new_spanned(
|
||||
assign.right,
|
||||
"Expected simple identifier",
|
||||
))
|
||||
}
|
||||
}
|
||||
"default" => {
|
||||
self.default = Some(*assign.right);
|
||||
Ok(())
|
||||
@@ -359,6 +383,11 @@ mod field_info {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
"extend" => {
|
||||
self.extends.push(path.path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
_ => {
|
||||
macro_rules! handle_fields {
|
||||
( $( $flag:expr, $field:ident, $already:expr; )* ) => {
|
||||
@@ -462,11 +491,14 @@ fn type_from_inside_option(ty: &syn::Type, check_option_name: bool) -> Option<&s
|
||||
}
|
||||
|
||||
mod struct_info {
|
||||
use convert_case::{Case, Casing};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::parse::Error;
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::Expr;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::visit::Visit;
|
||||
use syn::{parse_quote, Expr, Ident};
|
||||
|
||||
use super::field_info::{FieldBuilderAttr, FieldInfo};
|
||||
use super::util::{
|
||||
@@ -489,7 +521,46 @@ mod struct_info {
|
||||
|
||||
impl<'a> StructInfo<'a> {
|
||||
pub fn included_fields(&self) -> impl Iterator<Item = &FieldInfo<'a>> {
|
||||
self.fields.iter().filter(|f| !f.builder_attr.skip)
|
||||
self.fields
|
||||
.iter()
|
||||
.filter(|f| !f.builder_attr.skip && f.builder_attr.extends.is_empty())
|
||||
}
|
||||
|
||||
pub fn extend_fields(&self) -> impl Iterator<Item = &FieldInfo<'a>> {
|
||||
self.fields
|
||||
.iter()
|
||||
.filter(|f| !f.builder_attr.extends.is_empty())
|
||||
}
|
||||
|
||||
fn extend_lifetime(&self) -> syn::Result<Option<syn::Lifetime>> {
|
||||
let first_extend = self.extend_fields().next();
|
||||
|
||||
match first_extend {
|
||||
Some(f) => {
|
||||
struct VisitFirstLifetime(Option<syn::Lifetime>);
|
||||
|
||||
impl Visit<'_> for VisitFirstLifetime {
|
||||
fn visit_lifetime(&mut self, lifetime: &'_ syn::Lifetime) {
|
||||
if self.0.is_none() {
|
||||
self.0 = Some(lifetime.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let name = f.name;
|
||||
let mut visitor = VisitFirstLifetime(None);
|
||||
|
||||
visitor.visit_type(f.ty);
|
||||
|
||||
visitor.0.ok_or_else(|| {
|
||||
syn::Error::new_spanned(
|
||||
name,
|
||||
"Unable to find lifetime for extended field. Please specify it manually",
|
||||
)
|
||||
}).map(Some)
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
@@ -536,7 +607,17 @@ mod struct_info {
|
||||
// Therefore, we will generate code that shortcircuits the "comparison" in memoization
|
||||
let are_there_generics = !self.generics.params.is_empty();
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
|
||||
let extend_lifetime = self.extend_lifetime()?;
|
||||
|
||||
let generics = self.generics.clone();
|
||||
let (_, ty_generics, where_clause) = generics.split_for_impl();
|
||||
let impl_generics = self.modify_generics(|g| {
|
||||
if extend_lifetime.is_none() {
|
||||
g.params.insert(0, parse_quote!('__bump));
|
||||
}
|
||||
});
|
||||
let (impl_generics, _, _) = impl_generics.split_for_impl();
|
||||
let (_, b_initial_generics, _) = self.generics.split_for_impl();
|
||||
let all_fields_param = syn::GenericParam::Type(
|
||||
syn::Ident::new("TypedBuilderFields", proc_macro2::Span::call_site()).into(),
|
||||
);
|
||||
@@ -544,7 +625,7 @@ mod struct_info {
|
||||
g.params.insert(0, all_fields_param.clone());
|
||||
});
|
||||
let empties_tuple = type_tuple(self.included_fields().map(|_| empty_type()));
|
||||
let generics_with_empty = modify_types_generics_hack(&ty_generics, |args| {
|
||||
let generics_with_empty = modify_types_generics_hack(&b_initial_generics, |args| {
|
||||
args.insert(0, syn::GenericArgument::Type(empties_tuple.clone().into()));
|
||||
});
|
||||
let phantom_generics = self.generics.params.iter().filter_map(|param| match param {
|
||||
@@ -603,8 +684,7 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||
quote!(#[doc(hidden)])
|
||||
};
|
||||
|
||||
let (b_generics_impl, b_generics_ty, b_generics_where_extras_predicates) =
|
||||
b_generics.split_for_impl();
|
||||
let (_, _, b_generics_where_extras_predicates) = b_generics.split_for_impl();
|
||||
let mut b_generics_where: syn::WhereClause = syn::parse2(quote! {
|
||||
where TypedBuilderFields: Clone
|
||||
})?;
|
||||
@@ -624,12 +704,39 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||
false => quote! { true },
|
||||
};
|
||||
|
||||
let extend_fields = self.extend_fields().map(|f| {
|
||||
let name = f.name;
|
||||
let ty = f.ty;
|
||||
quote!(#name: #ty)
|
||||
});
|
||||
let extend_fields_value = self.extend_fields().map(|f| {
|
||||
let name = f.name;
|
||||
quote!(#name: Vec::new())
|
||||
});
|
||||
let has_extend_fields = self.extend_fields().next().is_some();
|
||||
let take_bump = if has_extend_fields {
|
||||
quote!(bump: _cx.bump(),)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
let bump_field = if has_extend_fields {
|
||||
quote!(bump: & #extend_lifetime ::dioxus::core::exports::bumpalo::Bump,)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
let extend_lifetime = extend_lifetime.unwrap_or(syn::Lifetime::new(
|
||||
"'__bump",
|
||||
proc_macro2::Span::call_site(),
|
||||
));
|
||||
|
||||
Ok(quote! {
|
||||
impl #impl_generics #name #ty_generics #where_clause {
|
||||
#[doc = #builder_method_doc]
|
||||
#[allow(dead_code, clippy::type_complexity)]
|
||||
#vis fn builder() -> #builder_name #generics_with_empty {
|
||||
#vis fn builder(_cx: & #extend_lifetime ::dioxus::prelude::ScopeState) -> #builder_name #generics_with_empty {
|
||||
#builder_name {
|
||||
#(#extend_fields_value,)*
|
||||
#take_bump
|
||||
fields: #empties_tuple,
|
||||
_phantom: ::core::default::Default::default(),
|
||||
}
|
||||
@@ -640,26 +747,19 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||
#builder_type_doc
|
||||
#[allow(dead_code, non_camel_case_types, non_snake_case)]
|
||||
#vis struct #builder_name #b_generics {
|
||||
#(#extend_fields,)*
|
||||
#bump_field
|
||||
fields: #all_fields_param,
|
||||
_phantom: (#( #phantom_generics ),*),
|
||||
}
|
||||
|
||||
impl #b_generics_impl Clone for #builder_name #b_generics_ty #b_generics_where {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
fields: self.fields.clone(),
|
||||
_phantom: ::core::default::Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics ::dioxus::prelude::Properties for #name #ty_generics
|
||||
impl #impl_generics ::dioxus::prelude::Properties<#extend_lifetime> for #name #ty_generics
|
||||
#b_generics_where_extras_predicates
|
||||
{
|
||||
type Builder = #builder_name #generics_with_empty;
|
||||
const IS_STATIC: bool = #is_static;
|
||||
fn builder() -> Self::Builder {
|
||||
#name::builder()
|
||||
fn builder(_cx: &#extend_lifetime ::dioxus::prelude::ScopeState) -> Self::Builder {
|
||||
#name::builder(_cx)
|
||||
}
|
||||
unsafe fn memoize(&self, other: &Self) -> bool {
|
||||
#can_memoize
|
||||
@@ -694,11 +794,143 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||
})
|
||||
}
|
||||
|
||||
pub fn extends_impl(&self, field: &FieldInfo) -> Result<TokenStream, Error> {
|
||||
let StructInfo {
|
||||
ref builder_name, ..
|
||||
} = *self;
|
||||
|
||||
let field_name = field.name;
|
||||
|
||||
let descructuring = self.included_fields().map(|f| {
|
||||
if f.ordinal == field.ordinal {
|
||||
quote!(_)
|
||||
} else {
|
||||
let name = f.name;
|
||||
quote!(#name)
|
||||
}
|
||||
});
|
||||
let reconstructing = self.included_fields().map(|f| f.name);
|
||||
|
||||
// Add the bump lifetime to the generics
|
||||
let mut ty_generics: Vec<syn::GenericArgument> = self
|
||||
.generics
|
||||
.params
|
||||
.iter()
|
||||
.map(|generic_param| match generic_param {
|
||||
syn::GenericParam::Type(type_param) => {
|
||||
let ident = type_param.ident.clone();
|
||||
syn::parse(quote!(#ident).into()).unwrap()
|
||||
}
|
||||
syn::GenericParam::Lifetime(lifetime_def) => {
|
||||
syn::GenericArgument::Lifetime(lifetime_def.lifetime.clone())
|
||||
}
|
||||
syn::GenericParam::Const(const_param) => {
|
||||
let ident = const_param.ident.clone();
|
||||
syn::parse(quote!(#ident).into()).unwrap()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let mut target_generics_tuple = empty_type_tuple();
|
||||
let mut ty_generics_tuple = empty_type_tuple();
|
||||
let generics = self.modify_generics(|g| {
|
||||
let index_after_lifetime_in_generics = g
|
||||
.params
|
||||
.iter()
|
||||
.filter(|arg| matches!(arg, syn::GenericParam::Lifetime(_)))
|
||||
.count();
|
||||
for f in self.included_fields() {
|
||||
if f.ordinal == field.ordinal {
|
||||
ty_generics_tuple.elems.push_value(empty_type());
|
||||
target_generics_tuple
|
||||
.elems
|
||||
.push_value(f.tuplized_type_ty_param());
|
||||
} else {
|
||||
g.params
|
||||
.insert(index_after_lifetime_in_generics, f.generic_ty_param());
|
||||
let generic_argument: syn::Type = f.type_ident();
|
||||
ty_generics_tuple.elems.push_value(generic_argument.clone());
|
||||
target_generics_tuple.elems.push_value(generic_argument);
|
||||
}
|
||||
ty_generics_tuple.elems.push_punct(Default::default());
|
||||
target_generics_tuple.elems.push_punct(Default::default());
|
||||
}
|
||||
});
|
||||
let mut target_generics = ty_generics.clone();
|
||||
let index_after_lifetime_in_generics = target_generics
|
||||
.iter()
|
||||
.filter(|arg| matches!(arg, syn::GenericArgument::Lifetime(_)))
|
||||
.count();
|
||||
target_generics.insert(
|
||||
index_after_lifetime_in_generics,
|
||||
syn::GenericArgument::Type(target_generics_tuple.into()),
|
||||
);
|
||||
ty_generics.insert(
|
||||
index_after_lifetime_in_generics,
|
||||
syn::GenericArgument::Type(ty_generics_tuple.into()),
|
||||
);
|
||||
let (impl_generics, _, where_clause) = generics.split_for_impl();
|
||||
|
||||
let forward_extended_fields = self.extend_fields().map(|f| {
|
||||
let name = f.name;
|
||||
quote!(#name: self.#name)
|
||||
});
|
||||
|
||||
let extend_lifetime = self.extend_lifetime()?.ok_or(Error::new_spanned(
|
||||
field_name,
|
||||
"Unable to find lifetime for extended field. Please specify it manually",
|
||||
))?;
|
||||
|
||||
let extends_impl = field.builder_attr.extends.iter().map(|path| {
|
||||
let name_str = path_to_single_string(path).unwrap();
|
||||
let camel_name = name_str.to_case(Case::UpperCamel);
|
||||
let marker_name = Ident::new(
|
||||
format!("{}Extension", &camel_name).as_str(),
|
||||
path.span(),
|
||||
);
|
||||
quote! {
|
||||
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
||||
impl #impl_generics dioxus_elements::extensions::#marker_name < #extend_lifetime > for #builder_name < #( #ty_generics ),* > #where_clause {}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(quote! {
|
||||
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
||||
impl #impl_generics ::dioxus::prelude::HasAttributes<#extend_lifetime> for #builder_name < #( #ty_generics ),* > #where_clause {
|
||||
fn push_attribute(
|
||||
mut self,
|
||||
name: &#extend_lifetime str,
|
||||
ns: Option<&'static str>,
|
||||
attr: impl ::dioxus::prelude::IntoAttributeValue<#extend_lifetime>,
|
||||
volatile: bool
|
||||
) -> Self {
|
||||
let ( #(#descructuring,)* ) = self.fields;
|
||||
self.#field_name.push(
|
||||
::dioxus::core::Attribute::new(
|
||||
name,
|
||||
{
|
||||
use ::dioxus::prelude::IntoAttributeValue;
|
||||
attr.into_value(self.bump)
|
||||
},
|
||||
ns,
|
||||
volatile,
|
||||
)
|
||||
);
|
||||
#builder_name {
|
||||
#(#forward_extended_fields,)*
|
||||
bump: self.bump,
|
||||
fields: ( #(#reconstructing,)* ),
|
||||
_phantom: self._phantom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#(#extends_impl)*
|
||||
})
|
||||
}
|
||||
|
||||
pub fn field_impl(&self, field: &FieldInfo) -> Result<TokenStream, Error> {
|
||||
let FieldInfo {
|
||||
name: field_name,
|
||||
ty: field_type,
|
||||
..
|
||||
name: field_name, ..
|
||||
} = field;
|
||||
if *field_name == "key" {
|
||||
return Err(Error::new_spanned(field_name, "Naming a prop `key` is not allowed because the name can conflict with the built in key attribute. See https://dioxuslabs.com/learn/0.4/reference/dynamic_rendering#rendering-lists for more information about keys"));
|
||||
@@ -717,6 +949,12 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||
});
|
||||
let reconstructing = self.included_fields().map(|f| f.name);
|
||||
|
||||
let FieldInfo {
|
||||
name: field_name,
|
||||
ty: field_type,
|
||||
..
|
||||
} = field;
|
||||
// Add the bump lifetime to the generics
|
||||
let mut ty_generics: Vec<syn::GenericArgument> = self
|
||||
.generics
|
||||
.params
|
||||
@@ -800,6 +1038,16 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||
);
|
||||
let repeated_fields_error_message = format!("Repeated field {field_name}");
|
||||
|
||||
let forward_extended_fields = self.extend_fields().map(|f| {
|
||||
let name = f.name;
|
||||
quote!(#name: self.#name)
|
||||
});
|
||||
let forward_bump = if self.extend_fields().next().is_some() {
|
||||
quote!(bump: self.bump,)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
||||
impl #impl_generics #builder_name < #( #ty_generics ),* > #where_clause {
|
||||
@@ -809,6 +1057,8 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||
let #field_name = (#arg_expr,);
|
||||
let ( #(#descructuring,)* ) = self.fields;
|
||||
#builder_name {
|
||||
#(#forward_extended_fields,)*
|
||||
#forward_bump
|
||||
fields: ( #(#reconstructing,)* ),
|
||||
_phantom: self._phantom,
|
||||
}
|
||||
@@ -842,6 +1092,7 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||
name: ref field_name,
|
||||
..
|
||||
} = field;
|
||||
// Add a bump lifetime to the generics
|
||||
let mut builder_generics: Vec<syn::GenericArgument> = self
|
||||
.generics
|
||||
.params
|
||||
@@ -1009,7 +1260,9 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||
// reordering based on that, but for now this much simpler thing is a reasonable approach.
|
||||
let assignments = self.fields.iter().map(|field| {
|
||||
let name = &field.name;
|
||||
if let Some(ref default) = field.builder_attr.default {
|
||||
if !field.builder_attr.extends.is_empty() {
|
||||
quote!(let #name = self.#name;)
|
||||
} else if let Some(ref default) = field.builder_attr.default {
|
||||
if field.builder_attr.skip {
|
||||
quote!(let #name = #default;)
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::any_props::AnyProps;
|
||||
use crate::innerlude::{
|
||||
BorrowedAttributeValue, ElementPath, ElementRef, VComponent, VPlaceholder, VText,
|
||||
AttributeType, BorrowedAttributeValue, ElementPath, ElementRef, MountedAttribute, VComponent,
|
||||
VPlaceholder, VText,
|
||||
};
|
||||
use crate::mutations::Mutation;
|
||||
use crate::mutations::Mutation::*;
|
||||
@@ -314,7 +315,7 @@ impl<'b> VirtualDom {
|
||||
let id = self.assign_static_node_as_dynamic(path, root);
|
||||
|
||||
loop {
|
||||
self.write_attribute(node, attr_id, &node.dynamic_attrs[attr_id], id);
|
||||
self.write_attribute_type(node, &node.dynamic_attrs[attr_id], attr_id, id);
|
||||
|
||||
// Only push the dynamic attributes forward if they match the current path (same element)
|
||||
match attrs.next_if(|(_, p)| *p == path) {
|
||||
@@ -325,25 +326,41 @@ impl<'b> VirtualDom {
|
||||
}
|
||||
}
|
||||
|
||||
fn write_attribute(
|
||||
fn write_attribute_type(
|
||||
&mut self,
|
||||
template: &'b VNode<'b>,
|
||||
vnode: &'b VNode<'b>,
|
||||
attribute: &'b MountedAttribute<'b>,
|
||||
idx: usize,
|
||||
attribute: &'b crate::Attribute<'b>,
|
||||
id: ElementId,
|
||||
) {
|
||||
// Make sure we set the attribute's associated id
|
||||
attribute.mounted_element.set(id);
|
||||
match &attribute.ty {
|
||||
AttributeType::Single(attribute) => self.write_attribute(vnode, attribute, idx, id),
|
||||
AttributeType::Many(attribute) => {
|
||||
for attribute in *attribute {
|
||||
self.write_attribute(vnode, attribute, idx, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn write_attribute(
|
||||
&mut self,
|
||||
vnode: &'b VNode<'b>,
|
||||
attribute: &'b crate::Attribute<'b>,
|
||||
idx: usize,
|
||||
id: ElementId,
|
||||
) {
|
||||
// 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) };
|
||||
|
||||
match &attribute.value {
|
||||
AttributeValue::Listener(_) => {
|
||||
let path = &template.template.get().attr_paths[idx];
|
||||
let path = &vnode.template.get().attr_paths[idx];
|
||||
let element_ref = ElementRef {
|
||||
path: ElementPath { path },
|
||||
template: template.stable_id().unwrap(),
|
||||
template: vnode.stable_id().unwrap(),
|
||||
scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)),
|
||||
};
|
||||
self.elements[id.0] = Some(element_ref);
|
||||
|
||||
+79
-10
@@ -2,8 +2,8 @@ use crate::{
|
||||
any_props::AnyProps,
|
||||
arena::ElementId,
|
||||
innerlude::{
|
||||
BorrowedAttributeValue, DirtyScope, ElementPath, ElementRef, VComponent, VPlaceholder,
|
||||
VText,
|
||||
AttributeType, BorrowedAttributeValue, DirtyScope, ElementPath, ElementRef, VComponent,
|
||||
VPlaceholder, VText,
|
||||
},
|
||||
mutations::Mutation,
|
||||
nodes::RenderReturn,
|
||||
@@ -126,14 +126,54 @@ impl<'b> VirtualDom {
|
||||
.dynamic_attrs
|
||||
.iter()
|
||||
.zip(right_template.dynamic_attrs.iter())
|
||||
.for_each(|(left_attr, right_attr)| {
|
||||
.enumerate()
|
||||
.for_each(|(idx, (left_attr, right_attr))| {
|
||||
// Move over the ID from the old to the new
|
||||
let mounted_element = left_attr.mounted_element.get();
|
||||
right_attr.mounted_element.set(mounted_element);
|
||||
let mounted_id = left_attr.mounted_element.get();
|
||||
right_attr.mounted_element.set(mounted_id);
|
||||
|
||||
// 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);
|
||||
match (&left_attr.ty, &right_attr.ty) {
|
||||
(AttributeType::Single(left), AttributeType::Single(right)) => {
|
||||
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_template,
|
||||
right,
|
||||
idx,
|
||||
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_template, right, idx, mounted_id)
|
||||
}
|
||||
(None, None) => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => unreachable!("The macro should never generate this case"),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -164,6 +204,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(
|
||||
&mut self,
|
||||
left_node: &'b DynamicNode<'b>,
|
||||
@@ -184,12 +236,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 value: BorrowedAttributeValue<'b> = (&right_attr.value).into();
|
||||
let value = unsafe { std::mem::transmute(value) };
|
||||
self.mutations.push(Mutation::SetAttribute {
|
||||
id: left_attr.mounted_element.get(),
|
||||
id,
|
||||
ns: right_attr.namespace,
|
||||
name,
|
||||
value,
|
||||
|
||||
@@ -334,10 +334,10 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'a> Properties for ErrorBoundaryProps<'a> {
|
||||
impl<'a> Properties<'a> for ErrorBoundaryProps<'a> {
|
||||
type Builder = ErrorBoundaryPropsBuilder<'a, ((), ())>;
|
||||
const IS_STATIC: bool = false;
|
||||
fn builder() -> Self::Builder {
|
||||
fn builder(_: &'a ScopeState) -> Self::Builder {
|
||||
ErrorBoundaryProps::builder()
|
||||
}
|
||||
unsafe fn memoize(&self, _: &Self) -> bool {
|
||||
|
||||
@@ -92,10 +92,10 @@ impl<'a, const A: bool> FragmentBuilder<'a, A> {
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
impl<'a> Properties for FragmentProps<'a> {
|
||||
impl<'a> Properties<'_> for FragmentProps<'a> {
|
||||
type Builder = FragmentBuilder<'a, false>;
|
||||
const IS_STATIC: bool = false;
|
||||
fn builder() -> Self::Builder {
|
||||
fn builder(_cx: &ScopeState) -> Self::Builder {
|
||||
FragmentBuilder(None)
|
||||
}
|
||||
unsafe fn memoize(&self, _other: &Self) -> bool {
|
||||
|
||||
@@ -76,11 +76,11 @@ pub(crate) mod innerlude {
|
||||
}
|
||||
|
||||
pub use crate::innerlude::{
|
||||
fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeValue, BorrowedAttributeValue,
|
||||
CapturedError, Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode,
|
||||
LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped,
|
||||
TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText,
|
||||
VirtualDom,
|
||||
fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeType, AttributeValue,
|
||||
BorrowedAttributeValue, CapturedError, Component, DynamicNode, Element, ElementId, Event,
|
||||
Fragment, HasAttributes, IntoDynNode, LazyNodes, MountedAttribute, Mutation, Mutations,
|
||||
Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, TaskId, Template,
|
||||
TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText, VirtualDom,
|
||||
};
|
||||
|
||||
/// The purpose of this module is to alleviate imports of many common types
|
||||
@@ -91,10 +91,10 @@ pub mod prelude {
|
||||
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,
|
||||
remove_future, schedule_update_any, spawn, spawn_forever, suspend, use_error_boundary,
|
||||
AnyValue, Component, Element, ErrorBoundary, Event, EventHandler, Fragment,
|
||||
IntoAttributeValue, IntoDynNode, LazyNodes, Properties, Runtime, RuntimeGuard, Scope,
|
||||
ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, Throw,
|
||||
VNode, VirtualDom,
|
||||
AnyValue, Attribute, AttributeType, Component, Element, ErrorBoundary, Event, EventHandler,
|
||||
Fragment, HasAttributes, IntoAttributeValue, IntoDynNode, LazyNodes, MountedAttribute,
|
||||
Properties, Runtime, RuntimeGuard, Scope, ScopeId, ScopeState, Scoped, TaskId, Template,
|
||||
TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ pub struct VNode<'a> {
|
||||
pub dynamic_nodes: &'a [DynamicNode<'a>],
|
||||
|
||||
/// The dynamic parts of the template
|
||||
pub dynamic_attrs: &'a [Attribute<'a>],
|
||||
pub dynamic_attrs: &'a [MountedAttribute<'a>],
|
||||
}
|
||||
|
||||
impl<'a> VNode<'a> {
|
||||
@@ -92,7 +92,7 @@ impl<'a> VNode<'a> {
|
||||
template: Template<'static>,
|
||||
root_ids: bumpalo::collections::Vec<'a, ElementId>,
|
||||
dynamic_nodes: &'a [DynamicNode<'a>],
|
||||
dynamic_attrs: &'a [Attribute<'a>],
|
||||
dynamic_attrs: &'a [MountedAttribute<'a>],
|
||||
) -> Self {
|
||||
Self {
|
||||
key,
|
||||
@@ -445,6 +445,51 @@ pub enum TemplateAttribute<'a> {
|
||||
},
|
||||
}
|
||||
|
||||
/// An attribute with information about its position in the DOM and the element it was mounted to
|
||||
#[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> From<&'a Vec<Attribute<'a>>> for MountedAttribute<'a> {
|
||||
fn from(attr: &'a Vec<Attribute<'a>>) -> Self {
|
||||
attr.as_slice().into()
|
||||
}
|
||||
}
|
||||
|
||||
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"`
|
||||
#[derive(Debug)]
|
||||
pub struct Attribute<'a> {
|
||||
@@ -461,9 +506,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
|
||||
pub volatile: bool,
|
||||
|
||||
/// The element in the DOM that this attribute belongs to
|
||||
pub(crate) mounted_element: Cell<ElementId>,
|
||||
}
|
||||
|
||||
impl<'a> Attribute<'a> {
|
||||
@@ -479,13 +521,40 @@ impl<'a> Attribute<'a> {
|
||||
value,
|
||||
namespace,
|
||||
volatile,
|
||||
mounted_element: Cell::new(ElementId::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of an attribute
|
||||
#[derive(Debug)]
|
||||
pub enum AttributeType<'a> {
|
||||
/// A single attribute
|
||||
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
|
||||
pub fn mounted_element(&self) -> ElementId {
|
||||
self.mounted_element.get()
|
||||
/// Try to call the given function on each attribute
|
||||
pub fn try_for_each<'b, F, E>(&'b self, mut f: F) -> Result<(), E>
|
||||
where
|
||||
F: FnMut(&'b Attribute<'a>) -> Result<(), E>,
|
||||
{
|
||||
match self {
|
||||
Self::Single(attr) => f(attr),
|
||||
Self::Many(attrs) => attrs.iter().try_for_each(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -870,3 +939,15 @@ impl<'a, T: IntoAttributeValue<'a>> IntoAttributeValue<'a> for Option<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for anything that has a dynamic list of attributes
|
||||
pub trait HasAttributes<'a> {
|
||||
/// Push an attribute onto the list of attributes
|
||||
fn push_attribute(
|
||||
self,
|
||||
name: &'a str,
|
||||
ns: Option<&'static str>,
|
||||
attr: impl IntoAttributeValue<'a>,
|
||||
volatile: bool,
|
||||
) -> Self;
|
||||
}
|
||||
@@ -32,7 +32,7 @@ use crate::innerlude::*;
|
||||
/// data: &'a str
|
||||
/// }
|
||||
/// ```
|
||||
pub trait Properties: Sized {
|
||||
pub trait Properties<'a>: Sized {
|
||||
/// The type of the builder for this component.
|
||||
/// Used to create "in-progress" versions of the props.
|
||||
type Builder;
|
||||
@@ -41,7 +41,7 @@ pub trait Properties: Sized {
|
||||
const IS_STATIC: bool;
|
||||
|
||||
/// Create a builder for this component.
|
||||
fn builder() -> Self::Builder;
|
||||
fn builder(cx: &'a ScopeState) -> Self::Builder;
|
||||
|
||||
/// Memoization can only happen if the props are valid for the 'static lifetime
|
||||
///
|
||||
@@ -51,10 +51,10 @@ pub trait Properties: Sized {
|
||||
unsafe fn memoize(&self, other: &Self) -> bool;
|
||||
}
|
||||
|
||||
impl Properties for () {
|
||||
impl Properties<'_> for () {
|
||||
type Builder = EmptyBuilder;
|
||||
const IS_STATIC: bool = true;
|
||||
fn builder() -> Self::Builder {
|
||||
fn builder(_cx: &ScopeState) -> Self::Builder {
|
||||
EmptyBuilder {}
|
||||
}
|
||||
unsafe fn memoize(&self, _other: &Self) -> bool {
|
||||
@@ -70,8 +70,11 @@ impl EmptyBuilder {
|
||||
|
||||
/// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern
|
||||
/// to initialize a component's props.
|
||||
pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element<'a>) -> T::Builder {
|
||||
T::builder()
|
||||
pub fn fc_to_builder<'a, T: Properties<'a> + 'a>(
|
||||
cx: &'a ScopeState,
|
||||
_: fn(Scope<'a, T>) -> Element<'a>,
|
||||
) -> T::Builder {
|
||||
T::builder(cx)
|
||||
}
|
||||
|
||||
#[cfg(not(miri))]
|
||||
|
||||
+26
-21
@@ -7,7 +7,8 @@ use crate::{
|
||||
nodes::{IntoAttributeValue, IntoDynNode, RenderReturn},
|
||||
runtime::Runtime,
|
||||
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 std::{
|
||||
@@ -350,20 +351,22 @@ impl<'src> ScopeState {
|
||||
|
||||
let mut listeners = self.attributes_to_drop_before_render.borrow_mut();
|
||||
for attr in element.dynamic_attrs {
|
||||
match attr.value {
|
||||
// We need to drop listeners before the next render because they may borrow data from the borrowed props which will be dropped
|
||||
AttributeValue::Listener(_) => {
|
||||
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
|
||||
listeners.push(unbounded);
|
||||
}
|
||||
// We need to drop any values manually to make sure that their drop implementation is called before the next render
|
||||
AttributeValue::Any(_) => {
|
||||
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
|
||||
self.previous_frame().add_attribute_to_drop(unbounded);
|
||||
}
|
||||
attr.ty.for_each(|attr| {
|
||||
match attr.value {
|
||||
// We need to drop listeners before the next render because they may borrow data from the borrowed props which will be dropped
|
||||
AttributeValue::Listener(_) => {
|
||||
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
|
||||
listeners.push(unbounded);
|
||||
}
|
||||
// We need to drop any values manually to make sure that their drop implementation is called before the next render
|
||||
AttributeValue::Any(_) => {
|
||||
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
|
||||
self.previous_frame().add_attribute_to_drop(unbounded);
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let mut props = self.borrowed_props.borrow_mut();
|
||||
@@ -419,13 +422,15 @@ impl<'src> ScopeState {
|
||||
value: impl IntoAttributeValue<'src>,
|
||||
namespace: Option<&'static str>,
|
||||
volatile: bool,
|
||||
) -> Attribute<'src> {
|
||||
Attribute {
|
||||
name,
|
||||
namespace,
|
||||
volatile,
|
||||
) -> MountedAttribute<'src> {
|
||||
MountedAttribute {
|
||||
ty: AttributeType::Single(Attribute {
|
||||
name,
|
||||
namespace,
|
||||
volatile,
|
||||
value: value.into_value(self.bump()),
|
||||
}),
|
||||
mounted_element: Default::default(),
|
||||
value: value.into_value(self.bump()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,7 +456,7 @@ impl<'src> ScopeState {
|
||||
) -> DynamicNode<'src>
|
||||
where
|
||||
// The properties must be valid until the next bump frame
|
||||
P: Properties + 'src,
|
||||
P: Properties<'src> + 'src,
|
||||
// The current bump allocator frame must outlive the child's borrowed props
|
||||
'src: 'child,
|
||||
{
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
use crate::{
|
||||
any_props::VProps,
|
||||
arena::ElementId,
|
||||
innerlude::{DirtyScope, ElementRef, ErrorBoundary, Mutations, Scheduler, SchedulerMsg},
|
||||
arena::{ElementId, ElementRef},
|
||||
innerlude::{DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg},
|
||||
mutations::Mutation,
|
||||
nodes::RenderReturn,
|
||||
nodes::{Template, TemplateId},
|
||||
@@ -371,7 +371,6 @@ impl VirtualDom {
|
||||
.get(parent_path.template.0)
|
||||
.cloned()
|
||||
.map(|el| (*parent_path, el));
|
||||
let mut listeners = vec![];
|
||||
|
||||
// We will clone this later. The data itself is wrapped in RC to be used in callbacks if required
|
||||
let uievent = Event {
|
||||
@@ -383,6 +382,8 @@ impl VirtualDom {
|
||||
if bubbles {
|
||||
// Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent.
|
||||
while let Some((path, el_ref)) = parent_node {
|
||||
let mut listeners = vec![];
|
||||
|
||||
// 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();
|
||||
@@ -392,10 +393,14 @@ impl VirtualDom {
|
||||
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);
|
||||
if target_path.is_decendant(&this_path) {
|
||||
attr.ty.for_each(|attribute| {
|
||||
if attribute.name.trim_start_matches("on") == name {
|
||||
if let AttributeValue::Listener(listener) = &attribute.value {
|
||||
listeners.push(listener);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 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
|
||||
@@ -408,20 +413,18 @@ impl VirtualDom {
|
||||
|
||||
// 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
|
||||
for listener in listeners.drain(..).rev() {
|
||||
if let AttributeValue::Listener(listener) = listener {
|
||||
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);
|
||||
for listener in listeners.into_iter().rev() {
|
||||
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;
|
||||
}
|
||||
if !uievent.propagates.get() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,18 +448,26 @@ impl VirtualDom {
|
||||
|
||||
// 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.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 target_path == this_path {
|
||||
let mut should_stop = false;
|
||||
attr.ty.for_each(|attribute| {
|
||||
if attribute.name.trim_start_matches("on") == name {
|
||||
if let AttributeValue::Listener(listener) = &attribute.value {
|
||||
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);
|
||||
|
||||
break;
|
||||
should_stop = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
if should_stop {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![cfg(not(miri))]
|
||||
|
||||
use dioxus::prelude::Props;
|
||||
use dioxus_core::*;
|
||||
use dioxus_core::{MountedAttribute, *};
|
||||
use std::{cfg, collections::HashSet};
|
||||
|
||||
fn random_ns() -> Option<&'static str> {
|
||||
@@ -206,7 +206,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 {
|
||||
0 => AttributeValue::Text(Box::leak(
|
||||
format!("{}", rand::random::<usize>()).into_boxed_str(),
|
||||
@@ -218,7 +218,7 @@ fn create_random_dynamic_attr(cx: &ScopeState) -> Attribute {
|
||||
5 => AttributeValue::None,
|
||||
6 => {
|
||||
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!(),
|
||||
};
|
||||
@@ -228,6 +228,7 @@ fn create_random_dynamic_attr(cx: &ScopeState) -> Attribute {
|
||||
random_ns(),
|
||||
rand::random(),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
static mut TEMPLATE_COUNT: usize = 0;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "dioxus-html-internal-macro"
|
||||
version = { workspace = true }
|
||||
edition = "2021"
|
||||
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react", "liveview"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "HTML function macros for Dioxus"
|
||||
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.66"
|
||||
syn = { version = "2", features = ["full"] }
|
||||
quote = "^1.0.26"
|
||||
convert_case = "^0.6.0"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[[test]]
|
||||
name = "tests"
|
||||
path = "tests/progress.rs"
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = { version = "1.0.82", features = ["diff"] }
|
||||
@@ -0,0 +1,76 @@
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
use convert_case::{Case, Casing};
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::__private::TokenStream2;
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::{braced, parse_macro_input, Ident, Token};
|
||||
|
||||
#[proc_macro]
|
||||
pub fn impl_extension_attributes(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as ImplExtensionAttributes);
|
||||
input.to_token_stream().into()
|
||||
}
|
||||
|
||||
struct ImplExtensionAttributes {
|
||||
is_element: bool,
|
||||
name: Ident,
|
||||
attrs: Punctuated<Ident, Token![,]>,
|
||||
}
|
||||
|
||||
impl Parse for ImplExtensionAttributes {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let content;
|
||||
|
||||
let element: Ident = input.parse()?;
|
||||
let name = input.parse()?;
|
||||
braced!(content in input);
|
||||
let attrs = content.parse_terminated(Ident::parse, Token![,])?;
|
||||
|
||||
Ok(ImplExtensionAttributes {
|
||||
is_element: element == "ELEMENT",
|
||||
name,
|
||||
attrs,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for ImplExtensionAttributes {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let name = &self.name;
|
||||
let name_string = name.to_string();
|
||||
let camel_name = name_string
|
||||
.strip_prefix("r#")
|
||||
.unwrap_or(&name_string)
|
||||
.to_case(Case::UpperCamel);
|
||||
let impl_name = Ident::new(format!("{}Impl", &camel_name).as_str(), name.span());
|
||||
let extension_name = Ident::new(format!("{}Extension", &camel_name).as_str(), name.span());
|
||||
|
||||
if !self.is_element {
|
||||
tokens.append_all(quote! {
|
||||
struct #impl_name;
|
||||
impl #name for #impl_name {}
|
||||
});
|
||||
}
|
||||
|
||||
let impls = self.attrs.iter().map(|ident| {
|
||||
let d = if self.is_element {
|
||||
quote! { #name::#ident }
|
||||
} else {
|
||||
quote! { <#impl_name as #name>::#ident }
|
||||
};
|
||||
quote! {
|
||||
fn #ident(self, value: impl IntoAttributeValue<'a>) -> Self {
|
||||
let d = #d;
|
||||
self.push_attribute(d.0, d.1, value, d.2)
|
||||
}
|
||||
}
|
||||
});
|
||||
tokens.append_all(quote! {
|
||||
pub trait #extension_name<'a>: HasAttributes<'a> + Sized {
|
||||
#(#impls)*
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
#[test]
|
||||
fn tests() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.pass("tests/01-simple.rs");
|
||||
}
|
||||
@@ -12,6 +12,7 @@ keywords = ["dom", "ui", "gui", "react"]
|
||||
[dependencies]
|
||||
dioxus-core = { workspace = true }
|
||||
dioxus-rsx = { workspace = true, features = ["hot_reload"], optional = true }
|
||||
dioxus-html-internal-macro = { workspace = true }
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
serde_repr = { version = "0.1", optional = true }
|
||||
wasm-bindgen = { workspace = true, optional = true }
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
#![allow(non_upper_case_globals)]
|
||||
|
||||
use dioxus_core::prelude::IntoAttributeValue;
|
||||
use dioxus_core::HasAttributes;
|
||||
use dioxus_html_internal_macro::impl_extension_attributes;
|
||||
#[cfg(feature = "hot-reload-context")]
|
||||
use dioxus_rsx::HotReloadingContext;
|
||||
|
||||
#[cfg(feature = "hot-reload-context")]
|
||||
use crate::{map_global_attributes, map_svg_attributes};
|
||||
use crate::{GlobalAttributes, SvgAttributes};
|
||||
#[cfg(feature = "hot-reload-context")]
|
||||
use dioxus_rsx::HotReloadingContext;
|
||||
|
||||
pub type AttributeDiscription = (&'static str, Option<&'static str>, bool);
|
||||
|
||||
@@ -378,6 +383,13 @@ macro_rules! builder_constructors {
|
||||
}
|
||||
);
|
||||
)*
|
||||
|
||||
pub(crate) mod extensions {
|
||||
use super::*;
|
||||
$(
|
||||
impl_extension_attributes![ELEMENT $name { $($fil,)* }];
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ macro_rules! impl_event {
|
||||
$(
|
||||
$( #[$attr] )*
|
||||
#[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(
|
||||
impl_event!(@name $name $($js_name)?),
|
||||
_cx.listener(move |e: ::dioxus_core::Event<crate::PlatformEventData>| {
|
||||
@@ -20,7 +20,7 @@ macro_rules! impl_event {
|
||||
}),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
).into()
|
||||
}
|
||||
)*
|
||||
};
|
||||
|
||||
@@ -78,7 +78,7 @@ impl_event! {
|
||||
#[deprecated(since = "0.5.0", note = "use ondoubleclick instead")]
|
||||
ondblclick
|
||||
|
||||
ondoubleclick: "ondblclick"
|
||||
// ondoubleclick: "ondblclick"
|
||||
|
||||
/// onmousedown
|
||||
onmousedown
|
||||
@@ -104,6 +104,22 @@ impl_event! {
|
||||
onmouseup
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn ondoubleclick<'a, E: crate::EventReturn<T>, T>(
|
||||
_cx: &'a ::dioxus_core::ScopeState,
|
||||
mut _f: impl FnMut(::dioxus_core::Event<MouseData>) -> E + 'a,
|
||||
) -> ::dioxus_core::MountedAttribute<'a> {
|
||||
::dioxus_core::Attribute::new(
|
||||
"ondblclick",
|
||||
_cx.listener(move |e: ::dioxus_core::Event<MouseData>| {
|
||||
_f(e).spawn(_cx);
|
||||
}),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
impl MouseData {
|
||||
/// Create a new instance of MouseData
|
||||
pub fn new(inner: impl HasMouseData + 'static) -> Self {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#![allow(non_upper_case_globals)]
|
||||
|
||||
use dioxus_core::prelude::IntoAttributeValue;
|
||||
use dioxus_core::HasAttributes;
|
||||
use dioxus_html_internal_macro::impl_extension_attributes;
|
||||
|
||||
use crate::AttributeDiscription;
|
||||
|
||||
#[cfg(feature = "hot-reload-context")]
|
||||
@@ -106,6 +110,8 @@ macro_rules! trait_methods {
|
||||
)*
|
||||
None
|
||||
}
|
||||
|
||||
impl_extension_attributes![GLOBAL $trait { $($name,)* }];
|
||||
};
|
||||
|
||||
// Rename the incoming ident and apply a custom namespace
|
||||
|
||||
@@ -46,10 +46,17 @@ pub use render_template::*;
|
||||
#[cfg(feature = "eval")]
|
||||
pub mod eval;
|
||||
|
||||
pub mod extensions {
|
||||
pub use crate::elements::extensions::*;
|
||||
pub use crate::global_attributes::{GlobalAttributesExtension, SvgAttributesExtension};
|
||||
}
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::elements::extensions::*;
|
||||
#[cfg(feature = "eval")]
|
||||
pub use crate::eval::*;
|
||||
pub use crate::events::*;
|
||||
pub use crate::global_attributes::{GlobalAttributesExtension, SvgAttributesExtension};
|
||||
pub use crate::point_interaction::*;
|
||||
pub use keyboard_types::{self, Code, Key, Location, Modifiers};
|
||||
}
|
||||
@@ -204,6 +204,20 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_random_dynamic_mounted_attr(cx: &ScopeState) -> MountedAttribute {
|
||||
match rand::random::<u8>() % 2 {
|
||||
0 => MountedAttribute::from(
|
||||
&*cx.bump().alloc(
|
||||
(0..(rand::random::<u8>() % 3) as usize)
|
||||
.map(|_| create_random_dynamic_attr(cx))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
),
|
||||
1 => MountedAttribute::from(create_random_dynamic_attr(cx)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_random_dynamic_attr(cx: &ScopeState) -> Attribute {
|
||||
let value = match rand::random::<u8>() % 6 {
|
||||
0 => AttributeValue::Text(Box::leak(
|
||||
@@ -214,7 +228,6 @@ fn create_random_dynamic_attr(cx: &ScopeState) -> Attribute {
|
||||
3 => AttributeValue::Bool(rand::random()),
|
||||
4 => cx.any_value(rand::random::<usize>()),
|
||||
5 => AttributeValue::None,
|
||||
// Listener(RefCell<Option<ListenerCb<'a>>>),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
Attribute::new(
|
||||
@@ -271,11 +284,13 @@ fn create_random_element(cx: Scope<DepthProps>) -> Element {
|
||||
.collect();
|
||||
cx.bump().alloc(dynamic_nodes)
|
||||
},
|
||||
cx.bump().alloc(
|
||||
(0..template.attr_paths.len())
|
||||
.map(|_| create_random_dynamic_attr(cx))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
cx.bump()
|
||||
.alloc(
|
||||
(0..template.attr_paths.len())
|
||||
.map(|_| create_random_dynamic_mounted_attr(cx))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.as_slice(),
|
||||
);
|
||||
Some(node)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
use convert_case::{Case, Casing};
|
||||
use dioxus_html::{map_html_attribute_to_rsx, map_html_element_to_rsx};
|
||||
use dioxus_rsx::{
|
||||
BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed, ElementName, IfmtInput,
|
||||
AttributeType, BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed,
|
||||
ElementName, IfmtInput,
|
||||
};
|
||||
pub use html_parser::{Dom, Node};
|
||||
use proc_macro2::{Ident, Span};
|
||||
@@ -63,16 +64,16 @@ pub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {
|
||||
}
|
||||
};
|
||||
|
||||
ElementAttrNamed {
|
||||
AttributeType::Named(ElementAttrNamed {
|
||||
el_name: el_name.clone(),
|
||||
attr,
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
let class = el.classes.join(" ");
|
||||
if !class.is_empty() {
|
||||
attributes.push(ElementAttrNamed {
|
||||
attributes.push(AttributeType::Named(ElementAttrNamed {
|
||||
el_name: el_name.clone(),
|
||||
attr: ElementAttr {
|
||||
name: dioxus_rsx::ElementAttrName::BuiltIn(Ident::new(
|
||||
@@ -81,11 +82,11 @@ pub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {
|
||||
)),
|
||||
value: dioxus_rsx::ElementAttrValue::AttrLiteral(ifmt_from_text(&class)),
|
||||
},
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
if let Some(id) = &el.id {
|
||||
attributes.push(ElementAttrNamed {
|
||||
attributes.push(AttributeType::Named(ElementAttrNamed {
|
||||
el_name: el_name.clone(),
|
||||
attr: ElementAttr {
|
||||
name: dioxus_rsx::ElementAttrName::BuiltIn(Ident::new(
|
||||
@@ -94,7 +95,7 @@ pub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {
|
||||
)),
|
||||
value: dioxus_rsx::ElementAttrValue::AttrLiteral(ifmt_from_text(id)),
|
||||
},
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
let children = el.children.iter().filter_map(rsx_node_from_html).collect();
|
||||
|
||||
@@ -4,14 +4,59 @@ use super::*;
|
||||
|
||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::{parse_quote, Expr, ExprIf, Ident, LitStr};
|
||||
use syn::{parse_quote, spanned::Spanned, Expr, ExprIf, Ident, LitStr};
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||
pub enum AttributeType {
|
||||
Named(ElementAttrNamed),
|
||||
Spread(Expr),
|
||||
}
|
||||
|
||||
impl AttributeType {
|
||||
pub fn start(&self) -> Span {
|
||||
match self {
|
||||
AttributeType::Named(n) => n.attr.start(),
|
||||
AttributeType::Spread(e) => e.span(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_combine(&self, other: &Self) -> Option<Self> {
|
||||
match (self, other) {
|
||||
(Self::Named(a), Self::Named(b)) => a.try_combine(b).map(Self::Named),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for AttributeType {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
match self {
|
||||
AttributeType::Named(n) => tokens.append_all(quote! { #n }),
|
||||
AttributeType::Spread(e) => tokens.append_all(quote! { (&#e).into() }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ElementAttrNamed {
|
||||
pub el_name: ElementName,
|
||||
pub attr: ElementAttr,
|
||||
}
|
||||
|
||||
impl Hash for ElementAttrNamed {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.attr.name.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ElementAttrNamed {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.attr == other.attr
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ElementAttrNamed {}
|
||||
|
||||
impl ElementAttrNamed {
|
||||
pub(crate) fn try_combine(&self, other: &Self) -> Option<Self> {
|
||||
if self.el_name == other.el_name && self.attr.name == other.attr.name {
|
||||
|
||||
@@ -151,8 +151,8 @@ impl ToTokens for Component {
|
||||
}
|
||||
None => {
|
||||
let mut toks = match prop_gen_args {
|
||||
Some(gen_args) => quote! { fc_to_builder(#name #gen_args) },
|
||||
None => quote! { fc_to_builder(#name) },
|
||||
Some(gen_args) => quote! { fc_to_builder(__cx, #name #gen_args) },
|
||||
None => quote! { fc_to_builder(__cx, #name) },
|
||||
};
|
||||
for field in &self.fields {
|
||||
match field.name.to_string().as_str() {
|
||||
|
||||
+69
-24
@@ -8,7 +8,7 @@ use syn::{
|
||||
parse::{Parse, ParseBuffer, ParseStream},
|
||||
punctuated::Punctuated,
|
||||
spanned::Spanned,
|
||||
Ident, LitStr, Result, Token,
|
||||
Expr, Ident, LitStr, Result, Token,
|
||||
};
|
||||
|
||||
// =======================================
|
||||
@@ -18,8 +18,8 @@ use syn::{
|
||||
pub struct Element {
|
||||
pub name: ElementName,
|
||||
pub key: Option<IfmtInput>,
|
||||
pub attributes: Vec<ElementAttrNamed>,
|
||||
pub merged_attributes: Vec<ElementAttrNamed>,
|
||||
pub attributes: Vec<AttributeType>,
|
||||
pub merged_attributes: Vec<AttributeType>,
|
||||
pub children: Vec<BodyNode>,
|
||||
pub brace: syn::token::Brace,
|
||||
}
|
||||
@@ -32,7 +32,7 @@ impl Parse for Element {
|
||||
let content: ParseBuffer;
|
||||
let brace = syn::braced!(content in stream);
|
||||
|
||||
let mut attributes: Vec<ElementAttrNamed> = vec![];
|
||||
let mut attributes: Vec<AttributeType> = vec![];
|
||||
let mut children: Vec<BodyNode> = vec![];
|
||||
let mut key = None;
|
||||
|
||||
@@ -42,6 +42,22 @@ impl Parse for Element {
|
||||
// "def": 456,
|
||||
// abc: 123,
|
||||
loop {
|
||||
if content.peek(Token![..]) {
|
||||
content.parse::<Token![..]>()?;
|
||||
let expr = content.parse::<Expr>()?;
|
||||
let span = expr.span();
|
||||
attributes.push(attribute::AttributeType::Spread(expr));
|
||||
|
||||
if content.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
if content.parse::<Token![,]>().is_err() {
|
||||
missing_trailing_comma!(span);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse the raw literal fields
|
||||
if content.peek(LitStr) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
|
||||
let name = content.parse::<LitStr>()?;
|
||||
@@ -50,13 +66,13 @@ impl Parse for Element {
|
||||
content.parse::<Token![:]>()?;
|
||||
|
||||
let value = content.parse::<ElementAttrValue>()?;
|
||||
attributes.push(ElementAttrNamed {
|
||||
attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
|
||||
el_name: el_name.clone(),
|
||||
attr: ElementAttr {
|
||||
name: ElementAttrName::Custom(name),
|
||||
value,
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
if content.is_empty() {
|
||||
break;
|
||||
@@ -79,13 +95,13 @@ impl Parse for Element {
|
||||
let span = content.span();
|
||||
|
||||
if name_str.starts_with("on") {
|
||||
attributes.push(ElementAttrNamed {
|
||||
attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
|
||||
el_name: el_name.clone(),
|
||||
attr: ElementAttr {
|
||||
name: ElementAttrName::BuiltIn(name),
|
||||
value: ElementAttrValue::EventTokens(content.parse()?),
|
||||
},
|
||||
});
|
||||
}));
|
||||
} else {
|
||||
match name_str.as_str() {
|
||||
"key" => {
|
||||
@@ -93,13 +109,13 @@ impl Parse for Element {
|
||||
}
|
||||
_ => {
|
||||
let value = content.parse::<ElementAttrValue>()?;
|
||||
attributes.push(ElementAttrNamed {
|
||||
attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
|
||||
el_name: el_name.clone(),
|
||||
attr: ElementAttr {
|
||||
name: ElementAttrName::BuiltIn(name),
|
||||
value,
|
||||
},
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,7 +124,6 @@ impl Parse for Element {
|
||||
break;
|
||||
}
|
||||
|
||||
// todo: add a message saying you need to include commas between fields
|
||||
if content.parse::<Token![,]>().is_err() {
|
||||
missing_trailing_comma!(span);
|
||||
}
|
||||
@@ -120,12 +135,26 @@ impl Parse for Element {
|
||||
|
||||
// Deduplicate any attributes that can be combined
|
||||
// For example, if there are two `class` attributes, combine them into one
|
||||
let mut merged_attributes: Vec<ElementAttrNamed> = Vec::new();
|
||||
let mut merged_attributes: Vec<AttributeType> = Vec::new();
|
||||
for attr in &attributes {
|
||||
if let Some(old_attr_index) = merged_attributes
|
||||
.iter()
|
||||
.position(|a| a.attr.name == attr.attr.name)
|
||||
{
|
||||
if let Some(old_attr_index) = merged_attributes.iter().position(|a| {
|
||||
matches!((a, attr), (
|
||||
AttributeType::Named(ElementAttrNamed {
|
||||
attr: ElementAttr {
|
||||
name: ElementAttrName::BuiltIn(old_name),
|
||||
..
|
||||
},
|
||||
..
|
||||
}),
|
||||
AttributeType::Named(ElementAttrNamed {
|
||||
attr: ElementAttr {
|
||||
name: ElementAttrName::BuiltIn(new_name),
|
||||
..
|
||||
},
|
||||
..
|
||||
}),
|
||||
) if old_name == new_name)
|
||||
}) {
|
||||
let old_attr = &mut merged_attributes[old_attr_index];
|
||||
if let Some(combined) = old_attr.try_combine(attr) {
|
||||
*old_attr = combined;
|
||||
@@ -173,15 +202,31 @@ impl ToTokens for Element {
|
||||
None => quote! { None },
|
||||
};
|
||||
|
||||
let listeners = self
|
||||
.merged_attributes
|
||||
.iter()
|
||||
.filter(|f| matches!(f.attr.value, ElementAttrValue::EventTokens { .. }));
|
||||
let listeners = self.merged_attributes.iter().filter(|f| {
|
||||
matches!(
|
||||
f,
|
||||
AttributeType::Named(ElementAttrNamed {
|
||||
attr: ElementAttr {
|
||||
value: ElementAttrValue::EventTokens { .. },
|
||||
..
|
||||
},
|
||||
..
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
let attr = self
|
||||
.merged_attributes
|
||||
.iter()
|
||||
.filter(|f| !matches!(f.attr.value, ElementAttrValue::EventTokens { .. }));
|
||||
let attr = self.merged_attributes.iter().filter(|f| {
|
||||
!matches!(
|
||||
f,
|
||||
AttributeType::Named(ElementAttrNamed {
|
||||
attr: ElementAttr {
|
||||
value: ElementAttrValue::EventTokens { .. },
|
||||
..
|
||||
},
|
||||
..
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
tokens.append_all(quote! {
|
||||
__cx.element(
|
||||
|
||||
+64
-48
@@ -268,7 +268,7 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
|
||||
#[cfg(feature = "hot_reload")]
|
||||
#[derive(Default, Debug)]
|
||||
struct DynamicMapping {
|
||||
attribute_to_idx: std::collections::HashMap<ElementAttr, Vec<usize>>,
|
||||
attribute_to_idx: std::collections::HashMap<AttributeType, Vec<usize>>,
|
||||
last_attribute_idx: usize,
|
||||
node_to_idx: std::collections::HashMap<BodyNode, Vec<usize>>,
|
||||
last_element_idx: usize,
|
||||
@@ -284,7 +284,7 @@ impl DynamicMapping {
|
||||
new
|
||||
}
|
||||
|
||||
fn get_attribute_idx(&mut self, attr: &ElementAttr) -> Option<usize> {
|
||||
fn get_attribute_idx(&mut self, attr: &AttributeType) -> Option<usize> {
|
||||
self.attribute_to_idx
|
||||
.get_mut(attr)
|
||||
.and_then(|idxs| idxs.pop())
|
||||
@@ -294,7 +294,7 @@ impl DynamicMapping {
|
||||
self.node_to_idx.get_mut(node).and_then(|idxs| idxs.pop())
|
||||
}
|
||||
|
||||
fn insert_attribute(&mut self, attr: ElementAttr) -> usize {
|
||||
fn insert_attribute(&mut self, attr: AttributeType) -> usize {
|
||||
let idx = self.last_attribute_idx;
|
||||
self.last_attribute_idx += 1;
|
||||
|
||||
@@ -316,10 +316,17 @@ impl DynamicMapping {
|
||||
match node {
|
||||
BodyNode::Element(el) => {
|
||||
for attr in el.merged_attributes {
|
||||
match &attr.attr.value {
|
||||
ElementAttrValue::AttrLiteral(input) if input.is_static() => {}
|
||||
match &attr {
|
||||
AttributeType::Named(ElementAttrNamed {
|
||||
attr:
|
||||
ElementAttr {
|
||||
value: ElementAttrValue::AttrLiteral(input),
|
||||
..
|
||||
},
|
||||
..
|
||||
}) if input.is_static() => {}
|
||||
_ => {
|
||||
self.insert_attribute(attr.attr);
|
||||
self.insert_attribute(attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -347,7 +354,7 @@ impl DynamicMapping {
|
||||
#[derive(Default, Debug)]
|
||||
pub struct DynamicContext<'a> {
|
||||
dynamic_nodes: Vec<&'a BodyNode>,
|
||||
dynamic_attributes: Vec<&'a ElementAttrNamed>,
|
||||
dynamic_attributes: Vec<&'a AttributeType>,
|
||||
current_path: Vec<u8>,
|
||||
|
||||
node_paths: Vec<Vec<u8>>,
|
||||
@@ -367,10 +374,16 @@ impl<'a> DynamicContext<'a> {
|
||||
|
||||
let mut static_attrs = Vec::new();
|
||||
for attr in &el.merged_attributes {
|
||||
match &attr.attr.value {
|
||||
ElementAttrValue::AttrLiteral(value) if value.is_static() => {
|
||||
match &attr {
|
||||
AttributeType::Named(ElementAttrNamed {
|
||||
attr:
|
||||
ElementAttr {
|
||||
value: ElementAttrValue::AttrLiteral(value),
|
||||
name,
|
||||
},
|
||||
..
|
||||
}) if value.is_static() => {
|
||||
let value = value.source.as_ref().unwrap();
|
||||
let name = &attr.attr.name;
|
||||
let attribute_name_rust = name.to_string();
|
||||
let (name, namespace) =
|
||||
Ctx::map_attribute(&element_name_rust, &attribute_name_rust)
|
||||
@@ -384,7 +397,7 @@ impl<'a> DynamicContext<'a> {
|
||||
|
||||
_ => {
|
||||
let idx = match mapping {
|
||||
Some(mapping) => mapping.get_attribute_idx(&attr.attr)?,
|
||||
Some(mapping) => mapping.get_attribute_idx(attr)?,
|
||||
None => self.dynamic_attributes.len(),
|
||||
};
|
||||
self.dynamic_attributes.push(attr);
|
||||
@@ -454,47 +467,50 @@ impl<'a> DynamicContext<'a> {
|
||||
ElementName::Ident(i) => quote! { dioxus_elements::#i::#name },
|
||||
ElementName::Custom(_) => quote! { None },
|
||||
};
|
||||
let static_attrs = el
|
||||
.merged_attributes
|
||||
.iter()
|
||||
.map(|attr| match &attr.attr.value {
|
||||
ElementAttrValue::AttrLiteral(value) if value.is_static() => {
|
||||
let value = value.to_static().unwrap();
|
||||
let ns = {
|
||||
match &attr.attr.name {
|
||||
ElementAttrName::BuiltIn(name) => ns(quote!(#name.1)),
|
||||
ElementAttrName::Custom(_) => quote!(None),
|
||||
}
|
||||
};
|
||||
let name = &attr.attr.name;
|
||||
let name = match (el_name, name) {
|
||||
(ElementName::Ident(_), ElementAttrName::BuiltIn(_)) => {
|
||||
quote! { #el_name::#name.0 }
|
||||
}
|
||||
_ => {
|
||||
let as_string = name.to_string();
|
||||
quote! { #as_string }
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
::dioxus::core::TemplateAttribute::Static {
|
||||
name: #name,
|
||||
namespace: #ns,
|
||||
value: #value,
|
||||
let static_attrs = el.merged_attributes.iter().map(|attr| match attr {
|
||||
AttributeType::Named(ElementAttrNamed {
|
||||
attr:
|
||||
ElementAttr {
|
||||
value: ElementAttrValue::AttrLiteral(value),
|
||||
name,
|
||||
},
|
||||
..
|
||||
}) if value.is_static() => {
|
||||
let value = value.to_static().unwrap();
|
||||
let ns = {
|
||||
match name {
|
||||
ElementAttrName::BuiltIn(name) => ns(quote!(#name.1)),
|
||||
ElementAttrName::Custom(_) => quote!(None),
|
||||
}
|
||||
};
|
||||
let name = match (el_name, name) {
|
||||
(ElementName::Ident(_), ElementAttrName::BuiltIn(_)) => {
|
||||
quote! { #el_name::#name.0 }
|
||||
}
|
||||
_ => {
|
||||
let as_string = name.to_string();
|
||||
quote! { #as_string }
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
::dioxus::core::TemplateAttribute::Static {
|
||||
name: #name,
|
||||
namespace: #ns,
|
||||
value: #value,
|
||||
|
||||
// todo: we don't diff these so we never apply the volatile flag
|
||||
// volatile: dioxus_elements::#el_name::#name.2,
|
||||
}
|
||||
// todo: we don't diff these so we never apply the volatile flag
|
||||
// volatile: dioxus_elements::#el_name::#name.2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
let ct = self.dynamic_attributes.len();
|
||||
self.dynamic_attributes.push(attr);
|
||||
self.attr_paths.push(self.current_path.clone());
|
||||
quote! { ::dioxus::core::TemplateAttribute::Dynamic { id: #ct } }
|
||||
}
|
||||
});
|
||||
_ => {
|
||||
let ct = self.dynamic_attributes.len();
|
||||
self.dynamic_attributes.push(attr);
|
||||
self.attr_paths.push(self.current_path.clone());
|
||||
quote! { ::dioxus::core::TemplateAttribute::Dynamic { id: #ct } }
|
||||
}
|
||||
});
|
||||
|
||||
let attrs = quote! { #(#static_attrs),*};
|
||||
|
||||
|
||||
@@ -91,27 +91,29 @@ impl Renderer {
|
||||
match segment {
|
||||
Segment::Attr(idx) => {
|
||||
let attr = &template.dynamic_attrs[*idx];
|
||||
if attr.name == "dangerous_inner_html" {
|
||||
inner_html = Some(attr);
|
||||
} else if attr.namespace == Some("style") {
|
||||
accumulated_dynamic_styles.push(attr);
|
||||
} else if BOOL_ATTRS.contains(&attr.name) {
|
||||
if truthy(&attr.value) {
|
||||
write!(buf, " {}=", attr.name)?;
|
||||
write_value(buf, &attr.value)?;
|
||||
attr.attribute_type().try_for_each(|attr| {
|
||||
if attr.name == "dangerous_inner_html" {
|
||||
inner_html = Some(attr);
|
||||
} else if attr.namespace == Some("style") {
|
||||
accumulated_dynamic_styles.push(attr);
|
||||
} else if BOOL_ATTRS.contains(&attr.name) {
|
||||
if truthy(&attr.value) {
|
||||
write_attribute(buf, attr)?;
|
||||
}
|
||||
} else {
|
||||
write_attribute(buf, attr)?;
|
||||
}
|
||||
} else {
|
||||
write_attribute(buf, attr)?;
|
||||
}
|
||||
|
||||
if self.pre_render {
|
||||
if let AttributeValue::Listener(_) = &attr.value {
|
||||
// The onmounted event doesn't need a DOM listener
|
||||
if attr.name != "onmounted" {
|
||||
accumulated_listeners.push(attr.name);
|
||||
if self.pre_render {
|
||||
if let AttributeValue::Listener(_) = &attr.value {
|
||||
// The onmounted event doesn't need a DOM listener
|
||||
if attr.name != "onmounted" {
|
||||
accumulated_listeners.push(attr.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
Segment::Node(idx) => match &template.dynamic_nodes[*idx] {
|
||||
DynamicNode::Component(node) => {
|
||||
@@ -424,16 +426,6 @@ pub(crate) fn write_attribute(buf: &mut impl Write, attr: &Attribute) -> std::fm
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn write_value(buf: &mut impl Write, value: &AttributeValue) -> std::fmt::Result {
|
||||
match value {
|
||||
AttributeValue::Text(value) => write!(buf, "\"{}\"", value),
|
||||
AttributeValue::Bool(value) => write!(buf, "{}", value),
|
||||
AttributeValue::Int(value) => write!(buf, "{}", value),
|
||||
AttributeValue::Float(value) => write!(buf, "{}", value),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn write_value_unquoted(
|
||||
buf: &mut impl Write,
|
||||
value: &AttributeValue,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::dom::WebsysDom;
|
||||
use dioxus_core::AttributeValue;
|
||||
use dioxus_core::{DynamicNode, ElementId, ScopeState, TemplateNode, VNode, VirtualDom};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -81,12 +82,15 @@ impl WebsysDom {
|
||||
if let dioxus_core::TemplateAttribute::Dynamic { id } = attr {
|
||||
let attribute = &vnode.dynamic_attrs[*id];
|
||||
let id = attribute.mounted_element();
|
||||
mounted_id = Some(id);
|
||||
if let dioxus_core::AttributeValue::Listener(_) = attribute.value {
|
||||
if attribute.name == "onmounted" {
|
||||
to_mount.push(id);
|
||||
attribute.attribute_type().for_each(|attribute| {
|
||||
let value = &attribute.value;
|
||||
mounted_id = Some(id);
|
||||
if let AttributeValue::Listener(_) = value {
|
||||
if attribute.name == "onmounted" {
|
||||
to_mount.push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if let Some(id) = mounted_id {
|
||||
|
||||
Reference in new issue
Block a user