Merge pull request #895 from Demonthos/inline-custom-elements

Allow raw elements if they include a dash
This commit is contained in:
Jon Kelley 2023-05-29 15:05:19 +02:00 committed by GitHub
commit 7e96475951
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 152 additions and 18 deletions

13
examples/web_component.rs Normal file
View File

@ -0,0 +1,13 @@
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 convert_case::{Case, Casing};
use dioxus_rsx::{ use dioxus_rsx::{
BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed, IfmtInput, BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed, ElementName, IfmtInput,
}; };
pub use html_parser::{Dom, Node}; pub use html_parser::{Dom, Node};
use proc_macro2::{Ident, Span}; 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::Text(text) => Some(BodyNode::Text(ifmt_from_text(text))),
Node::Element(el) => { Node::Element(el) => {
let el_name = el.name.to_case(Case::Snake); 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 let mut attributes: Vec<_> = el
.attributes .attributes

View File

@ -1,9 +1,13 @@
use std::fmt::{Display, Formatter};
use super::*; use super::*;
use proc_macro2::{Span, TokenStream as TokenStream2}; use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, ToTokens, TokenStreamExt}; use quote::{quote, ToTokens, TokenStreamExt};
use syn::{ use syn::{
parse::{Parse, ParseBuffer, ParseStream}, parse::{Parse, ParseBuffer, ParseStream},
punctuated::Punctuated,
spanned::Spanned,
Error, Expr, Ident, LitStr, Result, Token, Error, Expr, Ident, LitStr, Result, Token,
}; };
@ -12,7 +16,7 @@ use syn::{
// ======================================= // =======================================
#[derive(PartialEq, Eq, Clone, Debug, Hash)] #[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub struct Element { pub struct Element {
pub name: Ident, pub name: ElementName,
pub key: Option<IfmtInput>, pub key: Option<IfmtInput>,
pub attributes: Vec<ElementAttrNamed>, pub attributes: Vec<ElementAttrNamed>,
pub children: Vec<BodyNode>, pub children: Vec<BodyNode>,
@ -22,7 +26,7 @@ pub struct Element {
impl Parse for Element { impl Parse for Element {
fn parse(stream: ParseStream) -> Result<Self> { fn parse(stream: ParseStream) -> Result<Self> {
let el_name = Ident::parse(stream)?; let el_name = ElementName::parse(stream)?;
// parse the guts // parse the guts
let content: ParseBuffer; let content: ParseBuffer;
@ -181,7 +185,7 @@ impl ToTokens for Element {
tokens.append_all(quote! { tokens.append_all(quote! {
__cx.element( __cx.element(
dioxus_elements::#name, #name,
__cx.bump().alloc([ #(#listeners),* ]), __cx.bump().alloc([ #(#listeners),* ]),
__cx.bump().alloc([ #(#attr),* ]), __cx.bump().alloc([ #(#attr),* ]),
__cx.bump().alloc([ #(#children),* ]), __cx.bump().alloc([ #(#children),* ]),
@ -191,6 +195,75 @@ impl ToTokens for Element {
} }
} }
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub enum ElementName {
Ident(Ident),
Custom(LitStr),
}
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 ElementName {
pub fn span(&self) -> Span {
match self {
ElementName::Ident(i) => i.span(),
ElementName::Custom(s) => s.span(),
}
}
}
impl PartialEq<&str> for ElementName {
fn eq(&self, other: &&str) -> bool {
match self {
ElementName::Ident(i) => i == *other,
ElementName::Custom(s) => s.value() == *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.value()),
}
}
}
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 span = raw.span();
let tag = raw
.into_iter()
.map(|ident| ident.to_string())
.collect::<Vec<_>>()
.join("-");
let tag = LitStr::new(&tag, span);
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)] #[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub enum ElementAttr { pub enum ElementAttr {
/// `attribute: "value"` /// `attribute: "value"`
@ -234,7 +307,7 @@ impl ElementAttr {
#[derive(PartialEq, Eq, Clone, Debug, Hash)] #[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub struct ElementAttrNamed { pub struct ElementAttrNamed {
pub el_name: Ident, pub el_name: ElementName,
pub attr: ElementAttr, pub attr: ElementAttr,
} }
@ -242,24 +315,46 @@ impl ToTokens for ElementAttrNamed {
fn to_tokens(&self, tokens: &mut TokenStream2) { fn to_tokens(&self, tokens: &mut TokenStream2) {
let ElementAttrNamed { el_name, attr } = self; 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 } => { ElementAttr::AttrText { name, value } => {
let ns = ns(name);
let volitile = volitile(name);
let attribute = attribute(name);
quote! { quote! {
__cx.attr( __cx.attr(
dioxus_elements::#el_name::#name.0, #attribute,
#value, #value,
dioxus_elements::#el_name::#name.1, #ns,
dioxus_elements::#el_name::#name.2 #volitile
) )
} }
} }
ElementAttr::AttrExpression { name, value } => { ElementAttr::AttrExpression { name, value } => {
let ns = ns(name);
let volitile = volitile(name);
let attribute = attribute(name);
quote! { quote! {
__cx.attr( __cx.attr(
dioxus_elements::#el_name::#name.0, #attribute,
#value, #value,
dioxus_elements::#el_name::#name.1, #ns,
dioxus_elements::#el_name::#name.2 #volitile
) )
} }
} }
@ -288,7 +383,9 @@ impl ToTokens for ElementAttrNamed {
dioxus_elements::events::#name(__cx, #tokens) dioxus_elements::events::#name(__cx, #tokens)
} }
} }
}); };
tokens.append_all(attribute);
} }
} }

View File

@ -426,13 +426,25 @@ impl<'a> DynamicContext<'a> {
match root { match root {
BodyNode::Element(el) => { BodyNode::Element(el) => {
let el_name = &el.name; 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 { let static_attrs = el.attributes.iter().map(|attr| match &attr.attr {
ElementAttr::AttrText { name, value } if value.is_static() => { ElementAttr::AttrText { name, value } if value.is_static() => {
let value = value.to_static().unwrap(); 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! { quote! {
::dioxus::core::TemplateAttribute::Static { ::dioxus::core::TemplateAttribute::Static {
name: dioxus_elements::#el_name::#name.0, name: #name,
namespace: dioxus_elements::#el_name::#name.1, namespace: #ns,
value: #value, 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
@ -479,10 +491,13 @@ impl<'a> DynamicContext<'a> {
let _opt = el.children.len() == 1; let _opt = el.children.len() == 1;
let children = quote! { #(#children),* }; let children = quote! { #(#children),* };
let ns = ns(quote!(NAME_SPACE));
let el_name = el_name.tag_name();
quote! { quote! {
::dioxus::core::TemplateNode::Element { ::dioxus::core::TemplateNode::Element {
tag: dioxus_elements::#el_name::TAG_NAME, tag: #el_name,
namespace: dioxus_elements::#el_name::NAME_SPACE, namespace: #ns,
attrs: &[ #attrs ], attrs: &[ #attrs ],
children: &[ #children ], children: &[ #children ],
} }

View File

@ -50,7 +50,16 @@ impl Parse for BodyNode {
return Ok(BodyNode::Text(stream.parse()?)); 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(); let body_stream = stream.fork();
if let Ok(ElementName::Custom(name)) = body_stream.parse::<ElementName>() {
if name.value().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>() { if let Ok(path) = body_stream.parse::<syn::Path>() {
// this is an Element if path match of: // this is an Element if path match of:
// - one ident // - one ident