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 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,13 @@
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,
spanned::Spanned,
Error, Expr, Ident, LitStr, Result, Token,
};
@ -12,7 +16,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 +26,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 +185,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 +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)]
pub enum ElementAttr {
/// `attribute: "value"`
@ -234,7 +307,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 +315,46 @@ 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 +383,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,16 @@ 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>() {
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>() {
// this is an Element if path match of:
// - one ident