allow raw elements if they include a dash

This commit is contained in:
Evan Almloff 2023-03-19 20:37:40 -05:00
parent 46cc07e048
commit 209a55da69
5 changed files with 141 additions and 18 deletions

15
examples/web_component.rs Normal file
View file

@ -0,0 +1,15 @@
use dioxus::prelude::*;
fn main() {
dioxus_desktop::launch(app);
}
fn app(cx: Scope) -> Element {
cx.render(rsx! {
web-component {
"my-prop": "5%",
}
})
}

View file

@ -1,6 +1,6 @@
use convert_case::{Case, Casing};
use dioxus_rsx::{
BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed, IfmtInput,
BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed, ElementName, IfmtInput,
};
pub use html_parser::{Dom, Node};
use proc_macro2::{Ident, Span};
@ -21,7 +21,7 @@ pub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {
Node::Text(text) => Some(BodyNode::Text(ifmt_from_text(text))),
Node::Element(el) => {
let el_name = el.name.to_case(Case::Snake);
let el_name = Ident::new(el_name.as_str(), Span::call_site());
let el_name = ElementName::Ident(Ident::new(el_name.as_str(), Span::call_site()));
let mut attributes: Vec<_> = el
.attributes

View file

@ -1,9 +1,12 @@
use std::fmt::{Display, Formatter};
use super::*;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
parse::{Parse, ParseBuffer, ParseStream},
punctuated::Punctuated,
Error, Expr, Ident, LitStr, Result, Token,
};
@ -12,7 +15,7 @@ use syn::{
// =======================================
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub struct Element {
pub name: Ident,
pub name: ElementName,
pub key: Option<IfmtInput>,
pub attributes: Vec<ElementAttrNamed>,
pub children: Vec<BodyNode>,
@ -22,7 +25,7 @@ pub struct Element {
impl Parse for Element {
fn parse(stream: ParseStream) -> Result<Self> {
let el_name = Ident::parse(stream)?;
let el_name = ElementName::parse(stream)?;
// parse the guts
let content: ParseBuffer;
@ -181,7 +184,7 @@ impl ToTokens for Element {
tokens.append_all(quote! {
__cx.element(
dioxus_elements::#name,
#name,
__cx.bump().alloc([ #(#listeners),* ]),
__cx.bump().alloc([ #(#attr),* ]),
__cx.bump().alloc([ #(#children),* ]),
@ -191,6 +194,60 @@ impl ToTokens for Element {
}
}
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub enum ElementName {
Ident(Ident),
Custom(String),
}
impl ElementName {
pub(crate) fn tag_name(&self) -> TokenStream2 {
match self {
ElementName::Ident(i) => quote! { dioxus_elements::#i::TAG_NAME },
ElementName::Custom(s) => quote! { #s },
}
}
}
impl PartialEq<&str> for ElementName {
fn eq(&self, other: &&str) -> bool {
match self {
ElementName::Ident(i) => i == *other,
ElementName::Custom(s) => s == *other,
}
}
}
impl Display for ElementName {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ElementName::Ident(i) => write!(f, "{}", i),
ElementName::Custom(s) => write!(f, "{}", s),
}
}
}
impl Parse for ElementName {
fn parse(stream: ParseStream) -> Result<Self> {
let raw = Punctuated::<Ident, Token![-]>::parse_separated_nonempty(stream)?;
if raw.len() == 1 {
Ok(ElementName::Ident(raw.into_iter().next().unwrap()))
} else {
let tag = raw.into_iter().map(|ident| ident.to_string()).collect::<Vec<_>>().join("-");
Ok(ElementName::Custom(tag))
}
}
}
impl ToTokens for ElementName {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
ElementName::Ident(i) => tokens.append_all(quote! { dioxus_elements::#i }),
ElementName::Custom(s) => tokens.append_all(quote! { #s }),
}
}
}
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub enum ElementAttr {
/// `attribute: "value"`
@ -234,7 +291,7 @@ impl ElementAttr {
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub struct ElementAttrNamed {
pub el_name: Ident,
pub el_name: ElementName,
pub attr: ElementAttr,
}
@ -242,24 +299,48 @@ impl ToTokens for ElementAttrNamed {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let ElementAttrNamed { el_name, attr } = self;
tokens.append_all(match attr {
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(
dioxus_elements::#el_name::#name.0,
#attribute,
#value,
dioxus_elements::#el_name::#name.1,
dioxus_elements::#el_name::#name.2
#ns,
#volitile
)
}
}
ElementAttr::AttrExpression { name, value } => {
let ns = ns(name);
let volitile = volitile(name);
let attribute=attribute(name);
quote! {
__cx.attr(
dioxus_elements::#el_name::#name.0,
#attribute,
#value,
dioxus_elements::#el_name::#name.1,
dioxus_elements::#el_name::#name.2
#ns,
#volitile
)
}
}
@ -288,7 +369,9 @@ impl ToTokens for ElementAttrNamed {
dioxus_elements::events::#name(__cx, #tokens)
}
}
});
};
tokens.append_all(attribute);
}
}

View file

@ -426,13 +426,25 @@ impl<'a> DynamicContext<'a> {
match root {
BodyNode::Element(el) => {
let el_name = &el.name;
let ns = |name| match el_name {
ElementName::Ident(i) => quote! { dioxus_elements::#i::#name },
ElementName::Custom(_) => quote! { None },
};
let static_attrs = el.attributes.iter().map(|attr| match &attr.attr {
ElementAttr::AttrText { name, value } if value.is_static() => {
let value = value.to_static().unwrap();
let ns = ns(quote!(#name.1));
let name=match el_name {
ElementName::Ident(_) => quote! { #el_name::#name.0 },
ElementName::Custom(_) => {
let as_string=name.to_string();
quote! { #as_string }
},
};
quote! {
::dioxus::core::TemplateAttribute::Static {
name: dioxus_elements::#el_name::#name.0,
namespace: dioxus_elements::#el_name::#name.1,
name: #name,
namespace: #ns,
value: #value,
// todo: we don't diff these so we never apply the volatile flag
@ -479,10 +491,13 @@ impl<'a> DynamicContext<'a> {
let _opt = el.children.len() == 1;
let children = quote! { #(#children),* };
let ns = ns(quote!(NAME_SPACE));
let el_name = el_name.tag_name();
quote! {
::dioxus::core::TemplateNode::Element {
tag: dioxus_elements::#el_name::TAG_NAME,
namespace: dioxus_elements::#el_name::NAME_SPACE,
tag: #el_name,
namespace: #ns,
attrs: &[ #attrs ],
children: &[ #children ],
}

View file

@ -50,7 +50,17 @@ impl Parse for BodyNode {
return Ok(BodyNode::Text(stream.parse()?));
}
// if this is a dash-separated path, it's a web component (custom element)
let body_stream = stream.fork();
if let Ok(ElementName::Custom(name)) = body_stream.parse::<ElementName>() {
println!("name: {}", name);
if name.contains('-') && body_stream.peek(token::Brace) {
return Ok(BodyNode::Element(stream.parse::<Element>()?));
}
}
let body_stream = stream.fork();
if let Ok(path) = body_stream.parse::<syn::Path>() {
// this is an Element if path match of:
// - one ident