Merge branch 'many_optional_attributes' into pr/atty303/1349
This commit is contained in:
commit
9e167dfdb7
16 files changed
+553
-319
No files matched your search
@@ -53,6 +53,7 @@ fn App(cx: Scope) -> Element {
|
|||||||
let formatting = "formatting!";
|
let formatting = "formatting!";
|
||||||
let formatting_tuple = ("a", "b");
|
let formatting_tuple = ("a", "b");
|
||||||
let lazy_fmt = format_args!("lazily formatted text");
|
let lazy_fmt = format_args!("lazily formatted text");
|
||||||
|
let asd = 123;
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
// Elements
|
// Elements
|
||||||
@@ -80,6 +81,10 @@ fn App(cx: Scope) -> Element {
|
|||||||
// pass simple rust expressions in
|
// pass simple rust expressions in
|
||||||
class: lazy_fmt,
|
class: lazy_fmt,
|
||||||
id: format_args!("attributes can be passed lazily with std::fmt::Arguments"),
|
id: format_args!("attributes can be passed lazily with std::fmt::Arguments"),
|
||||||
|
class: "asd",
|
||||||
|
class: "{asd}",
|
||||||
|
// if statements can be used to conditionally render attributes
|
||||||
|
class: if formatting.contains("form") { "{asd}" },
|
||||||
div {
|
div {
|
||||||
class: {
|
class: {
|
||||||
const WORD: &str = "expressions";
|
const WORD: &str = "expressions";
|
||||||
|
|||||||
@@ -14,9 +14,13 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn app(cx: Scope) -> Element {
|
pub fn app(cx: Scope) -> Element {
|
||||||
|
let grey_background = true;
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
div {
|
div {
|
||||||
header { class: "text-gray-400 bg-gray-900 body-font",
|
header {
|
||||||
|
class: "text-gray-400 body-font",
|
||||||
|
// you can use optional attributes to optionally apply a tailwind class
|
||||||
|
class: if grey_background { "bg-gray-900" },
|
||||||
div { class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center",
|
div { class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center",
|
||||||
a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0",
|
a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0",
|
||||||
StacksIcon {}
|
StacksIcon {}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ impl Writer<'_> {
|
|||||||
children,
|
children,
|
||||||
brace,
|
brace,
|
||||||
extra_attributes,
|
extra_attributes,
|
||||||
|
..
|
||||||
} = el;
|
} = el;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -210,12 +211,34 @@ impl Writer<'_> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_attribute(&mut self, attr: &ElementAttrNamed) -> Result {
|
fn write_attribute_name(&mut self, attr: &ElementAttrName) -> Result {
|
||||||
match &attr.attr {
|
match attr {
|
||||||
ElementAttr::AttrText { name, value } => {
|
ElementAttrName::BuiltIn(name) => {
|
||||||
write!(self.out, "{name}: {value}", value = ifmt_to_string(value))?;
|
write!(self.out, "{}", name)?;
|
||||||
}
|
}
|
||||||
ElementAttr::AttrExpression { name, value } => {
|
ElementAttrName::Custom(name) => {
|
||||||
|
write!(self.out, "{}", name.to_token_stream())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_attribute_value(&mut self, value: &ElementAttrValue) -> Result {
|
||||||
|
match value {
|
||||||
|
ElementAttrValue::AttrOptionalExpr { condition, value } => {
|
||||||
|
write!(
|
||||||
|
self.out,
|
||||||
|
"if {condition} {{ ",
|
||||||
|
condition = prettyplease::unparse_expr(condition),
|
||||||
|
)?;
|
||||||
|
self.write_attribute_value(value)?;
|
||||||
|
write!(self.out, " }}")?;
|
||||||
|
}
|
||||||
|
ElementAttrValue::AttrLiteral(value) => {
|
||||||
|
write!(self.out, "{value}", value = ifmt_to_string(value))?;
|
||||||
|
}
|
||||||
|
ElementAttrValue::AttrExpr(value) => {
|
||||||
let out = prettyplease::unparse_expr(value);
|
let out = prettyplease::unparse_expr(value);
|
||||||
let mut lines = out.split('\n').peekable();
|
let mut lines = out.split('\n').peekable();
|
||||||
let first = lines.next().unwrap();
|
let first = lines.next().unwrap();
|
||||||
@@ -223,9 +246,9 @@ impl Writer<'_> {
|
|||||||
// a one-liner for whatever reason
|
// a one-liner for whatever reason
|
||||||
// Does not need a new line
|
// Does not need a new line
|
||||||
if lines.peek().is_none() {
|
if lines.peek().is_none() {
|
||||||
write!(self.out, "{name}: {first}")?;
|
write!(self.out, "{first}")?;
|
||||||
} else {
|
} else {
|
||||||
writeln!(self.out, "{name}: {first}")?;
|
writeln!(self.out, "{first}")?;
|
||||||
|
|
||||||
while let Some(line) = lines.next() {
|
while let Some(line) = lines.next() {
|
||||||
self.out.indented_tab()?;
|
self.out.indented_tab()?;
|
||||||
@@ -238,22 +261,7 @@ impl Writer<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ElementAttrValue::EventTokens(tokens) => {
|
||||||
ElementAttr::CustomAttrText { name, value } => {
|
|
||||||
write!(
|
|
||||||
self.out,
|
|
||||||
"{name}: {value}",
|
|
||||||
name = name.to_token_stream(),
|
|
||||||
value = ifmt_to_string(value)
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
ElementAttr::CustomAttrExpression { name, value } => {
|
|
||||||
let out = prettyplease::unparse_expr(value);
|
|
||||||
write!(self.out, "{}: {}", name.to_token_stream(), out)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
ElementAttr::EventTokens { name, tokens } => {
|
|
||||||
let out = self.retrieve_formatted_expr(tokens).to_string();
|
let out = self.retrieve_formatted_expr(tokens).to_string();
|
||||||
|
|
||||||
let mut lines = out.split('\n').peekable();
|
let mut lines = out.split('\n').peekable();
|
||||||
@@ -262,9 +270,9 @@ impl Writer<'_> {
|
|||||||
// a one-liner for whatever reason
|
// a one-liner for whatever reason
|
||||||
// Does not need a new line
|
// Does not need a new line
|
||||||
if lines.peek().is_none() {
|
if lines.peek().is_none() {
|
||||||
write!(self.out, "{name}: {first}")?;
|
write!(self.out, "{first}")?;
|
||||||
} else {
|
} else {
|
||||||
writeln!(self.out, "{name}: {first}")?;
|
writeln!(self.out, "{first}")?;
|
||||||
|
|
||||||
while let Some(line) = lines.next() {
|
while let Some(line) = lines.next() {
|
||||||
self.out.indented_tab()?;
|
self.out.indented_tab()?;
|
||||||
@@ -282,6 +290,14 @@ impl Writer<'_> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_attribute(&mut self, attr: &ElementAttrNamed) -> Result {
|
||||||
|
self.write_attribute_name(&attr.attr.name)?;
|
||||||
|
write!(self.out, ": ")?;
|
||||||
|
self.write_attribute_value(&attr.attr.value)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// make sure the comments are actually relevant to this element.
|
// make sure the comments are actually relevant to this element.
|
||||||
// test by making sure this element is the primary element on this line
|
// test by making sure this element is the primary element on this line
|
||||||
pub fn current_span_is_primary(&self, location: Span) -> bool {
|
pub fn current_span_is_primary(&self, location: Span) -> bool {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use dioxus_rsx::{BodyNode, ElementAttr, ElementAttrNamed, ForLoop};
|
use dioxus_rsx::{BodyNode, ElementAttrNamed, ElementAttrValue, ForLoop};
|
||||||
use proc_macro2::{LineColumn, Span};
|
use proc_macro2::{LineColumn, Span};
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
use std::{
|
use std::{
|
||||||
@@ -132,6 +132,39 @@ impl<'a> Writer<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn attr_value_len(&mut self, value: &ElementAttrValue) -> usize {
|
||||||
|
match value {
|
||||||
|
ElementAttrValue::AttrOptionalExpr { condition, value } => {
|
||||||
|
let condition_len = self.retrieve_formatted_expr(condition).len();
|
||||||
|
let value_len = self.attr_value_len(value);
|
||||||
|
|
||||||
|
condition_len + value_len + 6
|
||||||
|
}
|
||||||
|
ElementAttrValue::AttrLiteral(lit) => ifmt_to_string(lit).len(),
|
||||||
|
ElementAttrValue::AttrExpr(expr) => expr.span().line_length(),
|
||||||
|
ElementAttrValue::EventTokens(tokens) => {
|
||||||
|
let location = Location::new(tokens.span().start());
|
||||||
|
|
||||||
|
let len = if let std::collections::hash_map::Entry::Vacant(e) =
|
||||||
|
self.cached_formats.entry(location)
|
||||||
|
{
|
||||||
|
let formatted = prettyplease::unparse_expr(tokens);
|
||||||
|
let len = if formatted.contains('\n') {
|
||||||
|
10000
|
||||||
|
} else {
|
||||||
|
formatted.len()
|
||||||
|
};
|
||||||
|
e.insert(formatted);
|
||||||
|
len
|
||||||
|
} else {
|
||||||
|
self.cached_formats[&location].len()
|
||||||
|
};
|
||||||
|
|
||||||
|
len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn is_short_attrs(&mut self, attributes: &[ElementAttrNamed]) -> usize {
|
pub(crate) fn is_short_attrs(&mut self, attributes: &[ElementAttrNamed]) -> usize {
|
||||||
let mut total = 0;
|
let mut total = 0;
|
||||||
|
|
||||||
@@ -146,40 +179,17 @@ impl<'a> Writer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
total += match &attr.attr {
|
total += match &attr.attr.name {
|
||||||
ElementAttr::AttrText { value, name } => {
|
dioxus_rsx::ElementAttrName::BuiltIn(name) => {
|
||||||
ifmt_to_string(value).len() + name.span().line_length() + 6
|
let name = name.to_string();
|
||||||
}
|
name.len()
|
||||||
ElementAttr::AttrExpression { name, value } => {
|
|
||||||
value.span().line_length() + name.span().line_length() + 6
|
|
||||||
}
|
|
||||||
ElementAttr::CustomAttrText { value, name } => {
|
|
||||||
ifmt_to_string(value).len() + name.to_token_stream().to_string().len() + 6
|
|
||||||
}
|
|
||||||
ElementAttr::CustomAttrExpression { name, value } => {
|
|
||||||
name.to_token_stream().to_string().len() + value.span().line_length() + 6
|
|
||||||
}
|
|
||||||
ElementAttr::EventTokens { tokens, name } => {
|
|
||||||
let location = Location::new(tokens.span().start());
|
|
||||||
|
|
||||||
let len = if let std::collections::hash_map::Entry::Vacant(e) =
|
|
||||||
self.cached_formats.entry(location)
|
|
||||||
{
|
|
||||||
let formatted = prettyplease::unparse_expr(tokens);
|
|
||||||
let len = if formatted.contains('\n') {
|
|
||||||
10000
|
|
||||||
} else {
|
|
||||||
formatted.len()
|
|
||||||
};
|
|
||||||
e.insert(formatted);
|
|
||||||
len
|
|
||||||
} else {
|
|
||||||
self.cached_formats[&location].len()
|
|
||||||
};
|
|
||||||
|
|
||||||
len + name.span().line_length() + 6
|
|
||||||
}
|
}
|
||||||
|
dioxus_rsx::ElementAttrName::Custom(name) => name.value().len() + 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
total += self.attr_value_len(&attr.attr.value);
|
||||||
|
|
||||||
|
total += 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
total
|
total
|
||||||
@@ -218,7 +228,7 @@ impl<'a> Writer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait SpanLength {
|
pub(crate) trait SpanLength {
|
||||||
fn line_length(&self) -> usize;
|
fn line_length(&self) -> usize;
|
||||||
}
|
}
|
||||||
impl SpanLength for Span {
|
impl SpanLength for Span {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ rsx! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// No children, minimal props
|
// No children, minimal props
|
||||||
img { class: "mb-6 mx-auto h-24", src: "artemis-assets/images/friends.png", alt: "" }
|
img { class: "mb-6 mx-auto h-24", src: "artemis-assets/images/friends.png" }
|
||||||
|
|
||||||
// One level compression
|
// One level compression
|
||||||
div {
|
div {
|
||||||
|
|||||||
@@ -851,6 +851,12 @@ impl<'a> IntoAttributeValue<'a> for &'a str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoAttributeValue<'a> for String {
|
||||||
|
fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
|
||||||
|
AttributeValue::Text(bump.alloc(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> IntoAttributeValue<'a> for f64 {
|
impl<'a> IntoAttributeValue<'a> for f64 {
|
||||||
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||||
AttributeValue::Float(self)
|
AttributeValue::Float(self)
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element {
|
|||||||
div { key: "12345",
|
div { key: "12345",
|
||||||
class: "asd",
|
class: "asd",
|
||||||
class: "{asd}",
|
class: "{asd}",
|
||||||
|
class: if true { "{asd}" },
|
||||||
|
class: if false { "{asd}" },
|
||||||
onclick: move |_| {},
|
onclick: move |_| {},
|
||||||
div { "{var}" }
|
div { "{var}" }
|
||||||
div {
|
div {
|
||||||
@@ -24,6 +26,7 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dual_stream() {
|
fn dual_stream() {
|
||||||
let mut dom = VirtualDom::new(basic_syntax_is_a_template);
|
let mut dom = VirtualDom::new(basic_syntax_is_a_template);
|
||||||
@@ -36,7 +39,7 @@ fn dual_stream() {
|
|||||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||||
SetAttribute {
|
SetAttribute {
|
||||||
name: "class",
|
name: "class",
|
||||||
value: (&*bump.alloc("123".into_value(&bump))).into(),
|
value: (&*bump.alloc("asd 123 123".into_value(&bump))).into(),
|
||||||
id: ElementId(1),
|
id: ElementId(1),
|
||||||
ns: None,
|
ns: None,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,9 +36,11 @@ pub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {
|
|||||||
|
|
||||||
ElementAttrNamed {
|
ElementAttrNamed {
|
||||||
el_name: el_name.clone(),
|
el_name: el_name.clone(),
|
||||||
attr: ElementAttr::AttrText {
|
attr: ElementAttr {
|
||||||
value: ifmt_from_text(value.as_deref().unwrap_or("false")),
|
value: dioxus_rsx::ElementAttrValue::AttrLiteral(ifmt_from_text(
|
||||||
name: ident,
|
value.as_deref().unwrap_or("false"),
|
||||||
|
)),
|
||||||
|
name: dioxus_rsx::ElementAttrName::BuiltIn(ident),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -48,9 +50,12 @@ pub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {
|
|||||||
if !class.is_empty() {
|
if !class.is_empty() {
|
||||||
attributes.push(ElementAttrNamed {
|
attributes.push(ElementAttrNamed {
|
||||||
el_name: el_name.clone(),
|
el_name: el_name.clone(),
|
||||||
attr: ElementAttr::AttrText {
|
attr: ElementAttr {
|
||||||
name: Ident::new("class", Span::call_site()),
|
name: dioxus_rsx::ElementAttrName::BuiltIn(Ident::new(
|
||||||
value: ifmt_from_text(&class),
|
"class",
|
||||||
|
Span::call_site(),
|
||||||
|
)),
|
||||||
|
value: dioxus_rsx::ElementAttrValue::AttrLiteral(ifmt_from_text(&class)),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -58,9 +63,12 @@ pub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {
|
|||||||
if let Some(id) = &el.id {
|
if let Some(id) = &el.id {
|
||||||
attributes.push(ElementAttrNamed {
|
attributes.push(ElementAttrNamed {
|
||||||
el_name: el_name.clone(),
|
el_name: el_name.clone(),
|
||||||
attr: ElementAttr::AttrText {
|
attr: ElementAttr {
|
||||||
name: Ident::new("id", Span::call_site()),
|
name: dioxus_rsx::ElementAttrName::BuiltIn(Ident::new(
|
||||||
value: ifmt_from_text(id),
|
"id",
|
||||||
|
Span::call_site(),
|
||||||
|
)),
|
||||||
|
value: dioxus_rsx::ElementAttrValue::AttrLiteral(ifmt_from_text(id)),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -71,6 +79,7 @@ pub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {
|
|||||||
name: el_name,
|
name: el_name,
|
||||||
children,
|
children,
|
||||||
attributes,
|
attributes,
|
||||||
|
merged_attributes: Default::default(),
|
||||||
key: None,
|
key: None,
|
||||||
brace: Default::default(),
|
brace: Default::default(),
|
||||||
extra_attributes: None,
|
extra_attributes: None,
|
||||||
|
|||||||
@@ -22,5 +22,7 @@ internment = { version = "0.7.0", optional = true }
|
|||||||
krates = { version = "0.12.6", optional = true }
|
krates = { version = "0.12.6", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
default = ["html"]
|
||||||
hot_reload = ["krates", "internment"]
|
hot_reload = ["krates", "internment"]
|
||||||
serde = ["dep:serde"]
|
serde = ["dep:serde"]
|
||||||
|
html = []
|
||||||
@@ -0,0 +1,309 @@
|
|||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||||
|
use quote::{quote, ToTokens, TokenStreamExt};
|
||||||
|
use syn::{parse_quote, Expr, ExprIf, Ident, LitStr};
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||||
|
pub struct ElementAttrNamed {
|
||||||
|
pub el_name: ElementName,
|
||||||
|
pub attr: ElementAttr,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
if let Some(separator) = self.attr.name.multi_attribute_separator() {
|
||||||
|
return Some(ElementAttrNamed {
|
||||||
|
el_name: self.el_name.clone(),
|
||||||
|
attr: ElementAttr {
|
||||||
|
name: self.attr.name.clone(),
|
||||||
|
value: self.attr.value.combine(separator, &other.attr.value),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for ElementAttrNamed {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
|
let ElementAttrNamed { el_name, attr } = self;
|
||||||
|
|
||||||
|
let ns = |name: &ElementAttrName| match (el_name, name) {
|
||||||
|
(ElementName::Ident(i), ElementAttrName::BuiltIn(_)) => {
|
||||||
|
quote! { dioxus_elements::#i::#name.1 }
|
||||||
|
}
|
||||||
|
_ => quote! { None },
|
||||||
|
};
|
||||||
|
let volitile = |name: &ElementAttrName| match (el_name, name) {
|
||||||
|
(ElementName::Ident(i), ElementAttrName::BuiltIn(_)) => {
|
||||||
|
quote! { dioxus_elements::#i::#name.2 }
|
||||||
|
}
|
||||||
|
_ => quote! { false },
|
||||||
|
};
|
||||||
|
let attribute = |name: &ElementAttrName| match name {
|
||||||
|
ElementAttrName::BuiltIn(name) => match el_name {
|
||||||
|
ElementName::Ident(_) => quote! { #el_name::#name.0 },
|
||||||
|
ElementName::Custom(_) => {
|
||||||
|
let as_string = name.to_string();
|
||||||
|
quote!(#as_string)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ElementAttrName::Custom(s) => quote! { #s },
|
||||||
|
};
|
||||||
|
|
||||||
|
let attribute = {
|
||||||
|
match &attr.value {
|
||||||
|
ElementAttrValue::AttrLiteral(_)
|
||||||
|
| ElementAttrValue::AttrExpr(_)
|
||||||
|
| ElementAttrValue::AttrOptionalExpr { .. } => {
|
||||||
|
let name = &self.attr.name;
|
||||||
|
let ns = ns(name);
|
||||||
|
let volitile = volitile(name);
|
||||||
|
let attribute = attribute(name);
|
||||||
|
let value = &self.attr.value;
|
||||||
|
let value = quote! { #value };
|
||||||
|
quote! {
|
||||||
|
__cx.attr(
|
||||||
|
#attribute,
|
||||||
|
#value,
|
||||||
|
#ns,
|
||||||
|
#volitile
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ElementAttrValue::EventTokens(tokens) => match &self.attr.name {
|
||||||
|
ElementAttrName::BuiltIn(name) => {
|
||||||
|
quote! {
|
||||||
|
dioxus_elements::events::#name(__cx, #tokens)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ElementAttrName::Custom(_) => todo!(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tokens.append_all(attribute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||||
|
pub struct ElementAttr {
|
||||||
|
pub name: ElementAttrName,
|
||||||
|
pub value: ElementAttrValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||||
|
pub enum ElementAttrValue {
|
||||||
|
/// attribute: "value"
|
||||||
|
AttrLiteral(IfmtInput),
|
||||||
|
/// attribute: if bool { "value" }
|
||||||
|
AttrOptionalExpr {
|
||||||
|
condition: Expr,
|
||||||
|
value: Box<ElementAttrValue>,
|
||||||
|
},
|
||||||
|
/// attribute: true
|
||||||
|
AttrExpr(Expr),
|
||||||
|
/// onclick: move |_| {}
|
||||||
|
EventTokens(Expr),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for ElementAttrValue {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
Ok(if input.peek(Token![if]) {
|
||||||
|
let if_expr = input.parse::<ExprIf>()?;
|
||||||
|
if is_if_chain_terminated(&if_expr) {
|
||||||
|
ElementAttrValue::AttrExpr(Expr::If(if_expr))
|
||||||
|
} else {
|
||||||
|
ElementAttrValue::AttrOptionalExpr {
|
||||||
|
condition: *if_expr.cond,
|
||||||
|
value: {
|
||||||
|
let stmts = if_expr.then_branch.stmts;
|
||||||
|
Box::new(syn::parse2(quote! {
|
||||||
|
#(#stmts)*
|
||||||
|
})?)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if input.peek(LitStr) {
|
||||||
|
let value = input.parse()?;
|
||||||
|
ElementAttrValue::AttrLiteral(value)
|
||||||
|
} else {
|
||||||
|
let value = input.parse::<Expr>()?;
|
||||||
|
ElementAttrValue::AttrExpr(value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for ElementAttrValue {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
|
match self {
|
||||||
|
ElementAttrValue::AttrLiteral(lit) => tokens.append_all(quote! { #lit }),
|
||||||
|
ElementAttrValue::AttrOptionalExpr { condition, value } => {
|
||||||
|
tokens.append_all(quote! { if #condition { Some(#value) } else { None } })
|
||||||
|
}
|
||||||
|
ElementAttrValue::AttrExpr(expr) => tokens.append_all(quote! { #expr }),
|
||||||
|
ElementAttrValue::EventTokens(expr) => tokens.append_all(quote! { #expr }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ElementAttrValue {
|
||||||
|
fn to_str_expr(&self) -> Option<TokenStream2> {
|
||||||
|
match self {
|
||||||
|
ElementAttrValue::AttrLiteral(lit) => Some(quote!(#lit.to_string())),
|
||||||
|
ElementAttrValue::AttrOptionalExpr { value, .. } => value.to_str_expr(),
|
||||||
|
ElementAttrValue::AttrExpr(expr) => Some(quote!(#expr.to_string())),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn combine(&self, separator: &str, other: &Self) -> Self {
|
||||||
|
match (self, other) {
|
||||||
|
(Self::AttrLiteral(lit1), Self::AttrLiteral(lit2)) => {
|
||||||
|
let fmt = lit1.clone().join(lit2.clone(), separator);
|
||||||
|
Self::AttrLiteral(fmt)
|
||||||
|
}
|
||||||
|
(Self::AttrLiteral(expr1), Self::AttrExpr(expr2)) => {
|
||||||
|
let mut ifmt = expr1.clone();
|
||||||
|
ifmt.push_str(separator);
|
||||||
|
ifmt.push_expr(expr2.clone());
|
||||||
|
Self::AttrLiteral(ifmt)
|
||||||
|
}
|
||||||
|
(Self::AttrExpr(expr1), Self::AttrLiteral(expr2)) => {
|
||||||
|
let mut ifmt = expr2.clone();
|
||||||
|
ifmt.push_str(separator);
|
||||||
|
ifmt.push_expr(expr1.clone());
|
||||||
|
Self::AttrLiteral(ifmt)
|
||||||
|
}
|
||||||
|
(Self::AttrExpr(expr1), Self::AttrExpr(expr2)) => {
|
||||||
|
let mut ifmt = IfmtInput::default();
|
||||||
|
ifmt.push_expr(expr1.clone());
|
||||||
|
ifmt.push_str(separator);
|
||||||
|
ifmt.push_expr(expr2.clone());
|
||||||
|
Self::AttrLiteral(ifmt)
|
||||||
|
}
|
||||||
|
(
|
||||||
|
Self::AttrOptionalExpr {
|
||||||
|
condition: condition1,
|
||||||
|
value: value1,
|
||||||
|
},
|
||||||
|
Self::AttrOptionalExpr {
|
||||||
|
condition: condition2,
|
||||||
|
value: value2,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
let first_as_string = value1.to_str_expr();
|
||||||
|
let second_as_string = value2.to_str_expr();
|
||||||
|
Self::AttrExpr(parse_quote! {
|
||||||
|
{
|
||||||
|
let mut __combined = String::new();
|
||||||
|
if #condition1 {
|
||||||
|
__combined.push_str(&#first_as_string);
|
||||||
|
}
|
||||||
|
if #condition2 {
|
||||||
|
if __combined.len() > 0 {
|
||||||
|
__combined.push_str(&#separator);
|
||||||
|
}
|
||||||
|
__combined.push_str(&#second_as_string);
|
||||||
|
}
|
||||||
|
__combined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
(Self::AttrOptionalExpr { condition, value }, other) => {
|
||||||
|
let first_as_string = value.to_str_expr();
|
||||||
|
let second_as_string = other.to_str_expr();
|
||||||
|
Self::AttrExpr(parse_quote! {
|
||||||
|
{
|
||||||
|
let mut __combined = #second_as_string;
|
||||||
|
if #condition {
|
||||||
|
__combined.push_str(&#separator);
|
||||||
|
__combined.push_str(&#first_as_string);
|
||||||
|
}
|
||||||
|
__combined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
(other, Self::AttrOptionalExpr { condition, value }) => {
|
||||||
|
let first_as_string = other.to_str_expr();
|
||||||
|
let second_as_string = value.to_str_expr();
|
||||||
|
Self::AttrExpr(parse_quote! {
|
||||||
|
{
|
||||||
|
let mut __combined = #first_as_string;
|
||||||
|
if #condition {
|
||||||
|
__combined.push_str(&#separator);
|
||||||
|
__combined.push_str(&#second_as_string);
|
||||||
|
}
|
||||||
|
__combined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||||
|
pub enum ElementAttrName {
|
||||||
|
BuiltIn(Ident),
|
||||||
|
Custom(LitStr),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ElementAttrName {
|
||||||
|
fn multi_attribute_separator(&self) -> Option<&'static str> {
|
||||||
|
match self {
|
||||||
|
ElementAttrName::BuiltIn(i) => match i.to_string().as_str() {
|
||||||
|
"class" => Some(" "),
|
||||||
|
"style" => Some(";"),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
ElementAttrName::Custom(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(&self) -> Span {
|
||||||
|
match self {
|
||||||
|
ElementAttrName::BuiltIn(i) => i.span(),
|
||||||
|
ElementAttrName::Custom(s) => s.span(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for ElementAttrName {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
|
match self {
|
||||||
|
ElementAttrName::BuiltIn(i) => tokens.append_all(quote! { #i }),
|
||||||
|
ElementAttrName::Custom(s) => tokens.append_all(quote! { #s }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ElementAttrName {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ElementAttrName::BuiltIn(i) => write!(f, "{}", i),
|
||||||
|
ElementAttrName::Custom(s) => write!(f, "{}", s.value()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ElementAttr {
|
||||||
|
pub fn start(&self) -> Span {
|
||||||
|
self.name.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_expr(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
ElementAttr {
|
||||||
|
value: ElementAttrValue::AttrExpr(_) | ElementAttrValue::EventTokens(_),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
-170
@@ -8,7 +8,7 @@ use syn::{
|
|||||||
parse::{Parse, ParseBuffer, ParseStream},
|
parse::{Parse, ParseBuffer, ParseStream},
|
||||||
punctuated::Punctuated,
|
punctuated::Punctuated,
|
||||||
spanned::Spanned,
|
spanned::Spanned,
|
||||||
Error, Expr, Ident, LitStr, Result, Token,
|
Ident, LitStr, Result, Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
// =======================================
|
// =======================================
|
||||||
@@ -19,6 +19,7 @@ pub struct Element {
|
|||||||
pub name: ElementName,
|
pub name: ElementName,
|
||||||
pub key: Option<IfmtInput>,
|
pub key: Option<IfmtInput>,
|
||||||
pub attributes: Vec<ElementAttrNamed>,
|
pub attributes: Vec<ElementAttrNamed>,
|
||||||
|
pub merged_attributes: Vec<ElementAttrNamed>,
|
||||||
pub children: Vec<BodyNode>,
|
pub children: Vec<BodyNode>,
|
||||||
pub brace: syn::token::Brace,
|
pub brace: syn::token::Brace,
|
||||||
pub extra_attributes: Option<Expr>,
|
pub extra_attributes: Option<Expr>,
|
||||||
@@ -35,8 +36,6 @@ impl Parse for Element {
|
|||||||
let mut attributes: Vec<ElementAttrNamed> = vec![];
|
let mut attributes: Vec<ElementAttrNamed> = vec![];
|
||||||
let mut children: Vec<BodyNode> = vec![];
|
let mut children: Vec<BodyNode> = vec![];
|
||||||
let mut key = None;
|
let mut key = None;
|
||||||
let mut _el_ref = None;
|
|
||||||
let mut extra_attributes = None;
|
|
||||||
|
|
||||||
// parse fields with commas
|
// parse fields with commas
|
||||||
// break when we don't get this pattern anymore
|
// break when we don't get this pattern anymore
|
||||||
@@ -56,19 +55,14 @@ impl Parse for Element {
|
|||||||
|
|
||||||
content.parse::<Token![:]>()?;
|
content.parse::<Token![:]>()?;
|
||||||
|
|
||||||
if content.peek(LitStr) {
|
let value = content.parse::<ElementAttrValue>()?;
|
||||||
let value = content.parse()?;
|
attributes.push(ElementAttrNamed {
|
||||||
attributes.push(ElementAttrNamed {
|
el_name: el_name.clone(),
|
||||||
el_name: el_name.clone(),
|
attr: ElementAttr {
|
||||||
attr: ElementAttr::CustomAttrText { name, value },
|
name: ElementAttrName::Custom(name),
|
||||||
});
|
value,
|
||||||
} else {
|
},
|
||||||
let value = content.parse::<Expr>()?;
|
});
|
||||||
attributes.push(ElementAttrNamed {
|
|
||||||
el_name: el_name.clone(),
|
|
||||||
attr: ElementAttr::CustomAttrExpression { name, value },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if content.is_empty() {
|
if content.is_empty() {
|
||||||
break;
|
break;
|
||||||
@@ -93,9 +87,9 @@ impl Parse for Element {
|
|||||||
if name_str.starts_with("on") {
|
if name_str.starts_with("on") {
|
||||||
attributes.push(ElementAttrNamed {
|
attributes.push(ElementAttrNamed {
|
||||||
el_name: el_name.clone(),
|
el_name: el_name.clone(),
|
||||||
attr: ElementAttr::EventTokens {
|
attr: ElementAttr {
|
||||||
name,
|
name: ElementAttrName::BuiltIn(name),
|
||||||
tokens: content.parse()?,
|
value: ElementAttrValue::EventTokens(content.parse()?),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -103,29 +97,15 @@ impl Parse for Element {
|
|||||||
"key" => {
|
"key" => {
|
||||||
key = Some(content.parse()?);
|
key = Some(content.parse()?);
|
||||||
}
|
}
|
||||||
"classes" => todo!("custom class list not supported yet"),
|
|
||||||
// "namespace" => todo!("custom namespace not supported yet"),
|
|
||||||
"node_ref" => {
|
|
||||||
_el_ref = Some(content.parse::<Expr>()?);
|
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
if content.peek(LitStr) {
|
let value = content.parse::<ElementAttrValue>()?;
|
||||||
attributes.push(ElementAttrNamed {
|
attributes.push(ElementAttrNamed {
|
||||||
el_name: el_name.clone(),
|
el_name: el_name.clone(),
|
||||||
attr: ElementAttr::AttrText {
|
attr: ElementAttr {
|
||||||
name,
|
name: ElementAttrName::BuiltIn(name),
|
||||||
value: content.parse()?,
|
value,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
attributes.push(ElementAttrNamed {
|
|
||||||
el_name: el_name.clone(),
|
|
||||||
attr: ElementAttr::AttrExpression {
|
|
||||||
name,
|
|
||||||
value: content.parse()?,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,6 +124,23 @@ impl Parse for Element {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
for attr in &attributes {
|
||||||
|
if let Some(old_attr_index) = merged_attributes
|
||||||
|
.iter()
|
||||||
|
.position(|a| a.attr.name == attr.attr.name)
|
||||||
|
{
|
||||||
|
let old_attr = &mut merged_attributes[old_attr_index];
|
||||||
|
if let Some(combined) = old_attr.try_combine(attr) {
|
||||||
|
*old_attr = combined;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
merged_attributes.push(attr.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while !content.is_empty() {
|
while !content.is_empty() {
|
||||||
if (content.peek(LitStr) && content.peek2(Token![:])) && !content.peek3(Token![:]) {
|
if (content.peek(LitStr) && content.peek2(Token![:])) && !content.peek3(Token![:]) {
|
||||||
attr_after_element!(content.span());
|
attr_after_element!(content.span());
|
||||||
@@ -165,6 +162,7 @@ impl Parse for Element {
|
|||||||
key,
|
key,
|
||||||
name: el_name,
|
name: el_name,
|
||||||
attributes,
|
attributes,
|
||||||
|
merged_attributes,
|
||||||
children,
|
children,
|
||||||
brace,
|
brace,
|
||||||
extra_attributes,
|
extra_attributes,
|
||||||
@@ -183,14 +181,14 @@ impl ToTokens for Element {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let listeners = self
|
let listeners = self
|
||||||
.attributes
|
.merged_attributes
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|f| matches!(f.attr, ElementAttr::EventTokens { .. }));
|
.filter(|f| matches!(f.attr.value, ElementAttrValue::EventTokens { .. }));
|
||||||
|
|
||||||
let attr = self
|
let attr = self
|
||||||
.attributes
|
.merged_attributes
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|f| !matches!(f.attr, ElementAttr::EventTokens { .. }));
|
.filter(|f| !matches!(f.attr.value, ElementAttrValue::EventTokens { .. }));
|
||||||
|
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
__cx.element(
|
__cx.element(
|
||||||
@@ -272,128 +270,3 @@ impl ToTokens for ElementName {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
|
||||||
pub enum ElementAttr {
|
|
||||||
/// `attribute: "value"`
|
|
||||||
AttrText { name: Ident, value: IfmtInput },
|
|
||||||
|
|
||||||
/// `attribute: true`
|
|
||||||
AttrExpression { name: Ident, value: Expr },
|
|
||||||
|
|
||||||
/// `"attribute": "value"`
|
|
||||||
CustomAttrText { name: LitStr, value: IfmtInput },
|
|
||||||
|
|
||||||
/// `"attribute": true`
|
|
||||||
CustomAttrExpression { name: LitStr, value: Expr },
|
|
||||||
|
|
||||||
// /// onclick: move |_| {}
|
|
||||||
// EventClosure { name: Ident, closure: ExprClosure },
|
|
||||||
/// onclick: {}
|
|
||||||
EventTokens { name: Ident, tokens: Expr },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ElementAttr {
|
|
||||||
pub fn start(&self) -> Span {
|
|
||||||
match self {
|
|
||||||
ElementAttr::AttrText { name, .. } => name.span(),
|
|
||||||
ElementAttr::AttrExpression { name, .. } => name.span(),
|
|
||||||
ElementAttr::CustomAttrText { name, .. } => name.span(),
|
|
||||||
ElementAttr::CustomAttrExpression { name, .. } => name.span(),
|
|
||||||
ElementAttr::EventTokens { name, .. } => name.span(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_expr(&self) -> bool {
|
|
||||||
matches!(
|
|
||||||
self,
|
|
||||||
ElementAttr::AttrExpression { .. }
|
|
||||||
| ElementAttr::CustomAttrExpression { .. }
|
|
||||||
| ElementAttr::EventTokens { .. }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
|
||||||
pub struct ElementAttrNamed {
|
|
||||||
pub el_name: ElementName,
|
|
||||||
pub attr: ElementAttr,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToTokens for ElementAttrNamed {
|
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
|
||||||
let ElementAttrNamed { el_name, attr } = self;
|
|
||||||
|
|
||||||
let ns = |name| match el_name {
|
|
||||||
ElementName::Ident(i) => quote! { dioxus_elements::#i::#name.1 },
|
|
||||||
ElementName::Custom(_) => quote! { None },
|
|
||||||
};
|
|
||||||
let volitile = |name| match el_name {
|
|
||||||
ElementName::Ident(_) => quote! { #el_name::#name.2 },
|
|
||||||
ElementName::Custom(_) => quote! { false },
|
|
||||||
};
|
|
||||||
let attribute = |name: &Ident| match el_name {
|
|
||||||
ElementName::Ident(_) => quote! { #el_name::#name.0 },
|
|
||||||
ElementName::Custom(_) => {
|
|
||||||
let as_string = name.to_string();
|
|
||||||
quote!(#as_string)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let attribute = match attr {
|
|
||||||
ElementAttr::AttrText { name, value } => {
|
|
||||||
let ns = ns(name);
|
|
||||||
let volitile = volitile(name);
|
|
||||||
let attribute = attribute(name);
|
|
||||||
quote! {
|
|
||||||
__cx.attr(
|
|
||||||
#attribute,
|
|
||||||
#value,
|
|
||||||
#ns,
|
|
||||||
#volitile
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ElementAttr::AttrExpression { name, value } => {
|
|
||||||
let ns = ns(name);
|
|
||||||
let volitile = volitile(name);
|
|
||||||
let attribute = attribute(name);
|
|
||||||
quote! {
|
|
||||||
__cx.attr(
|
|
||||||
#attribute,
|
|
||||||
#value,
|
|
||||||
#ns,
|
|
||||||
#volitile
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ElementAttr::CustomAttrText { name, value } => {
|
|
||||||
quote! {
|
|
||||||
__cx.attr(
|
|
||||||
#name,
|
|
||||||
#value,
|
|
||||||
None,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ElementAttr::CustomAttrExpression { name, value } => {
|
|
||||||
quote! {
|
|
||||||
__cx.attr(
|
|
||||||
#name,
|
|
||||||
#value,
|
|
||||||
None,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ElementAttr::EventTokens { name, tokens } => {
|
|
||||||
quote! {
|
|
||||||
dioxus_elements::events::#name(__cx, #tokens)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
tokens.append_all(attribute);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
macro_rules! missing_trailing_comma {
|
macro_rules! missing_trailing_comma {
|
||||||
($span:expr) => {
|
($span:expr) => {
|
||||||
return Err(Error::new($span, "missing trailing comma"));
|
return Err(syn::Error::new($span, "missing trailing comma"));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! attr_after_element {
|
macro_rules! attr_after_element {
|
||||||
($span:expr) => {
|
($span:expr) => {
|
||||||
return Err(Error::new($span, "expected element\n = help move the attribute above all the children and text elements"));
|
return Err(syn::Error::new($span, "expected element\n = help move the attribute above all the children and text elements"));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ pub fn format_args_f_impl(input: IfmtInput) -> Result<TokenStream> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)] // dumb compiler does not see the struct being used...
|
#[allow(dead_code)] // dumb compiler does not see the struct being used...
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
#[derive(Debug, PartialEq, Eq, Clone, Hash, Default)]
|
||||||
pub struct IfmtInput {
|
pub struct IfmtInput {
|
||||||
pub source: Option<LitStr>,
|
pub source: Option<LitStr>,
|
||||||
pub segments: Vec<Segment>,
|
pub segments: Vec<Segment>,
|
||||||
@@ -27,8 +27,29 @@ impl IfmtInput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn join(mut self, other: Self, separator: &str) -> Self {
|
||||||
|
if !self.segments.is_empty() {
|
||||||
|
self.segments.push(Segment::Literal(separator.to_string()));
|
||||||
|
}
|
||||||
|
self.segments.extend(other.segments);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_expr(&mut self, expr: Expr) {
|
||||||
|
self.segments.push(Segment::Formatted(FormattedSegment {
|
||||||
|
format_args: String::new(),
|
||||||
|
segment: FormattedSegmentType::Expr(Box::new(expr)),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_str(&mut self, s: &str) {
|
||||||
|
self.segments.push(Segment::Literal(s.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_static(&self) -> bool {
|
pub fn is_static(&self) -> bool {
|
||||||
matches!(self.segments.as_slice(), &[Segment::Literal(_)] | &[])
|
self.segments
|
||||||
|
.iter()
|
||||||
|
.all(|seg| matches!(seg, Segment::Literal(_)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+47
-71
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod errors;
|
mod errors;
|
||||||
|
mod attribute;
|
||||||
mod component;
|
mod component;
|
||||||
mod element;
|
mod element;
|
||||||
#[cfg(feature = "hot_reload")]
|
#[cfg(feature = "hot_reload")]
|
||||||
@@ -23,6 +24,7 @@ mod node;
|
|||||||
use std::{fmt::Debug, hash::Hash};
|
use std::{fmt::Debug, hash::Hash};
|
||||||
|
|
||||||
// Re-export the namespaces into each other
|
// Re-export the namespaces into each other
|
||||||
|
pub use attribute::*;
|
||||||
pub use component::*;
|
pub use component::*;
|
||||||
#[cfg(feature = "hot_reload")]
|
#[cfg(feature = "hot_reload")]
|
||||||
use dioxus_core::{Template, TemplateAttribute, TemplateNode};
|
use dioxus_core::{Template, TemplateAttribute, TemplateNode};
|
||||||
@@ -306,17 +308,10 @@ impl DynamicMapping {
|
|||||||
fn add_node(&mut self, node: BodyNode) {
|
fn add_node(&mut self, node: BodyNode) {
|
||||||
match node {
|
match node {
|
||||||
BodyNode::Element(el) => {
|
BodyNode::Element(el) => {
|
||||||
for attr in el.attributes {
|
for attr in el.merged_attributes {
|
||||||
match &attr.attr {
|
match &attr.attr.value {
|
||||||
ElementAttr::CustomAttrText { value, .. }
|
ElementAttrValue::AttrLiteral(input) if input.is_static() => {}
|
||||||
| ElementAttr::AttrText { value, .. }
|
_ => {
|
||||||
if value.is_static() => {}
|
|
||||||
|
|
||||||
ElementAttr::AttrExpression { .. }
|
|
||||||
| ElementAttr::AttrText { .. }
|
|
||||||
| ElementAttr::CustomAttrText { .. }
|
|
||||||
| ElementAttr::CustomAttrExpression { .. }
|
|
||||||
| ElementAttr::EventTokens { .. } => {
|
|
||||||
self.insert_attribute(attr.attr);
|
self.insert_attribute(attr.attr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -364,10 +359,11 @@ impl<'a> DynamicContext<'a> {
|
|||||||
let element_name_rust = el.name.to_string();
|
let element_name_rust = el.name.to_string();
|
||||||
|
|
||||||
let mut static_attrs = Vec::new();
|
let mut static_attrs = Vec::new();
|
||||||
for attr in &el.attributes {
|
for attr in &el.merged_attributes {
|
||||||
match &attr.attr {
|
match &attr.attr.value {
|
||||||
ElementAttr::AttrText { name, value } if value.is_static() => {
|
ElementAttrValue::AttrLiteral(value) if value.is_static() => {
|
||||||
let value = value.source.as_ref().unwrap();
|
let value = value.source.as_ref().unwrap();
|
||||||
|
let name = &attr.attr.name;
|
||||||
let attribute_name_rust = name.to_string();
|
let attribute_name_rust = name.to_string();
|
||||||
let (name, namespace) =
|
let (name, namespace) =
|
||||||
Ctx::map_attribute(&element_name_rust, &attribute_name_rust)
|
Ctx::map_attribute(&element_name_rust, &attribute_name_rust)
|
||||||
@@ -379,20 +375,7 @@ impl<'a> DynamicContext<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
ElementAttr::CustomAttrText { name, value } if value.is_static() => {
|
_ => {
|
||||||
let value = value.source.as_ref().unwrap();
|
|
||||||
static_attrs.push(TemplateAttribute::Static {
|
|
||||||
name: intern(name.value().as_str()),
|
|
||||||
namespace: None,
|
|
||||||
value: intern(value.value().as_str()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
ElementAttr::AttrExpression { .. }
|
|
||||||
| ElementAttr::AttrText { .. }
|
|
||||||
| ElementAttr::CustomAttrText { .. }
|
|
||||||
| ElementAttr::CustomAttrExpression { .. }
|
|
||||||
| ElementAttr::EventTokens { .. } => {
|
|
||||||
let idx = match mapping {
|
let idx = match mapping {
|
||||||
Some(mapping) => mapping.get_attribute_idx(&attr.attr)?,
|
Some(mapping) => mapping.get_attribute_idx(&attr.attr)?,
|
||||||
None => self.dynamic_attributes.len(),
|
None => self.dynamic_attributes.len(),
|
||||||
@@ -464,54 +447,47 @@ impl<'a> DynamicContext<'a> {
|
|||||||
ElementName::Ident(i) => quote! { dioxus_elements::#i::#name },
|
ElementName::Ident(i) => quote! { dioxus_elements::#i::#name },
|
||||||
ElementName::Custom(_) => quote! { None },
|
ElementName::Custom(_) => quote! { None },
|
||||||
};
|
};
|
||||||
let static_attrs = el.attributes.iter().map(|attr| match &attr.attr {
|
let static_attrs = el
|
||||||
ElementAttr::AttrText { name, value } if value.is_static() => {
|
.merged_attributes
|
||||||
let value = value.to_static().unwrap();
|
.iter()
|
||||||
let ns = ns(quote!(#name.1));
|
.map(|attr| match &attr.attr.value {
|
||||||
let name = match el_name {
|
ElementAttrValue::AttrLiteral(value) if value.is_static() => {
|
||||||
ElementName::Ident(_) => quote! { #el_name::#name.0 },
|
let value = value.to_static().unwrap();
|
||||||
ElementName::Custom(_) => {
|
let ns = {
|
||||||
let as_string = name.to_string();
|
match &attr.attr.name {
|
||||||
quote! { #as_string }
|
ElementAttrName::BuiltIn(name) => ns(quote!(#name.1)),
|
||||||
}
|
ElementAttrName::Custom(_) => quote!(None),
|
||||||
};
|
}
|
||||||
quote! {
|
};
|
||||||
::dioxus::core::TemplateAttribute::Static {
|
let name = &attr.attr.name;
|
||||||
name: #name,
|
let name = match (el_name, name) {
|
||||||
namespace: #ns,
|
(ElementName::Ident(_), ElementAttrName::BuiltIn(_)) => {
|
||||||
value: #value,
|
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
|
// todo: we don't diff these so we never apply the volatile flag
|
||||||
// volatile: dioxus_elements::#el_name::#name.2,
|
// volatile: dioxus_elements::#el_name::#name.2,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ElementAttr::CustomAttrText { name, value } if value.is_static() => {
|
_ => {
|
||||||
let value = value.to_static().unwrap();
|
let ct = self.dynamic_attributes.len();
|
||||||
quote! {
|
self.dynamic_attributes.push(attr);
|
||||||
::dioxus::core::TemplateAttribute::Static {
|
self.attr_paths.push(self.current_path.clone());
|
||||||
name: #name,
|
quote! { ::dioxus::core::TemplateAttribute::Dynamic { id: #ct } }
|
||||||
namespace: None,
|
|
||||||
value: #value,
|
|
||||||
|
|
||||||
// todo: we don't diff these so we never apply the volatile flag
|
|
||||||
// volatile: dioxus_elements::#el_name::#name.2,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
ElementAttr::AttrExpression { .. }
|
|
||||||
| ElementAttr::AttrText { .. }
|
|
||||||
| ElementAttr::CustomAttrText { .. }
|
|
||||||
| ElementAttr::CustomAttrExpression { .. }
|
|
||||||
| ElementAttr::EventTokens { .. } => {
|
|
||||||
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),*};
|
let attrs = quote! { #(#static_attrs),*};
|
||||||
|
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ impl Parse for ForLoop {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_if_chain_terminated(chain: &ExprIf) -> bool {
|
pub(crate) fn is_if_chain_terminated(chain: &ExprIf) -> bool {
|
||||||
let mut current = chain;
|
let mut current = chain;
|
||||||
loop {
|
loop {
|
||||||
if let Some((_, else_block)) = ¤t.else_branch {
|
if let Some((_, else_block)) = ¤t.else_branch {
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ fn to_string_works() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
item.1.segments,
|
item.1.segments,
|
||||||
vec![
|
vec![
|
||||||
PreRendered("<div class=\"asdasdasd\" class=\"asdasdasd\"".into(),),
|
PreRendered("<div class=\"asdasdasd asdasdasd\"".into(),),
|
||||||
Attr(0,),
|
Attr(0,),
|
||||||
StyleMarker {
|
StyleMarker {
|
||||||
inside_style_tag: false,
|
inside_style_tag: false,
|
||||||
@@ -238,7 +238,7 @@ fn to_string_works() {
|
|||||||
|
|
||||||
use Segment::*;
|
use Segment::*;
|
||||||
|
|
||||||
assert_eq!(out, "<div class=\"asdasdasd\" class=\"asdasdasd\" id=\"id-123\">Hello world 1 -->123<-- Hello world 2<div>nest 1</div><div></div><div>nest 2</div></diiiiiiiiv><div>finalize 0</div><div>finalize 1</div><div>finalize 2</div><div>finalize 3</div><div>finalize 4</div></div>");
|
assert_eq!(out, "<div class=\"asdasdasd asdasdasd\" id=\"id-123\">Hello world 1 -->123<-- Hello world 2<div>nest 1</div><div></div><div>nest 2</div></diiiiiiiiv><div>finalize 0</div><div>finalize 1</div><div>finalize 2</div><div>finalize 3</div><div>finalize 4</div></div>");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) const BOOL_ATTRS: &[&str] = &[
|
pub(crate) const BOOL_ATTRS: &[&str] = &[
|
||||||
|
|||||||
Reference in new issue
Block a user