mirror of
https://github.com/DioxusLabs/dioxus
synced 2025-02-17 06:08:26 +00:00
feat: props spread
This commit is contained in:
parent
1ab5a03aef
commit
d573f5dfd5
14 changed files with 301 additions and 4 deletions
|
@ -9,6 +9,7 @@ members = [
|
||||||
"packages/extension",
|
"packages/extension",
|
||||||
"packages/router",
|
"packages/router",
|
||||||
"packages/html",
|
"packages/html",
|
||||||
|
"packages/html-internal-macro",
|
||||||
"packages/hooks",
|
"packages/hooks",
|
||||||
"packages/web",
|
"packages/web",
|
||||||
"packages/ssr",
|
"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 = { path = "packages/router", version = "0.4.1" }
|
||||||
dioxus-router-macro = { path = "packages/router-macro", version = "0.4.1" }
|
dioxus-router-macro = { path = "packages/router-macro", version = "0.4.1" }
|
||||||
dioxus-html = { path = "packages/html", version = "0.4.0" }
|
dioxus-html = { path = "packages/html", 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-hooks = { path = "packages/hooks", version = "0.4.0" }
|
||||||
dioxus-web = { path = "packages/web", version = "0.4.0" }
|
dioxus-web = { path = "packages/web", version = "0.4.0" }
|
||||||
dioxus-ssr = { path = "packages/ssr", version = "0.4.0" }
|
dioxus-ssr = { path = "packages/ssr", version = "0.4.0" }
|
||||||
|
|
|
@ -49,6 +49,7 @@ impl Writer<'_> {
|
||||||
attributes,
|
attributes,
|
||||||
children,
|
children,
|
||||||
brace,
|
brace,
|
||||||
|
extra_attributes,
|
||||||
} = el;
|
} = el;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -73,8 +73,8 @@ pub(crate) mod innerlude {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use crate::innerlude::{
|
pub use crate::innerlude::{
|
||||||
fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeValue, BorrowedAttributeValue,
|
fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeBox, AttributeValue, BorrowedAttributeValue,
|
||||||
CapturedError, Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode,
|
CapturedError, Component, DynamicNode, Element, ElementId, Event, Fragment, HasAttributesBox, IntoDynNode,
|
||||||
LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped,
|
LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped,
|
||||||
TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText,
|
TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText,
|
||||||
VirtualDom,
|
VirtualDom,
|
||||||
|
|
|
@ -832,3 +832,34 @@ impl<'a, T: IntoAttributeValue<'a>> IntoAttributeValue<'a> for Option<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct AttributeBox<'a> {
|
||||||
|
/// The name of the attribute.
|
||||||
|
pub name: &'a str,
|
||||||
|
|
||||||
|
/// The value of the attribute
|
||||||
|
pub value: Box<dyn IntoAttributeValue<'a> + 'static>,
|
||||||
|
|
||||||
|
/// The namespace of the attribute.
|
||||||
|
///
|
||||||
|
/// Doesn’t exist in the html spec. Used in Dioxus to denote “style” tags and other attribute groups.
|
||||||
|
pub namespace: Option<&'static str>,
|
||||||
|
|
||||||
|
/// An indication of we should always try and set the attribute. Used in controlled components to ensure changes are propagated
|
||||||
|
pub volatile: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> AttributeBox<'a> {
|
||||||
|
pub fn new(name: &'a str, value: impl IntoAttributeValue<'a> + 'static, namespace: Option<&'static str>, volatile: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
value: Box::new(value),
|
||||||
|
namespace,
|
||||||
|
volatile,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait HasAttributesBox<'a, T> {
|
||||||
|
fn push_attribute(self, attr: AttributeBox<'a>) -> T;
|
||||||
|
}
|
||||||
|
|
121
packages/dioxus/tests/props_spread.rs
Normal file
121
packages/dioxus/tests/props_spread.rs
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
use dioxus::core_macro::render;
|
||||||
|
use dioxus::prelude::rsx;
|
||||||
|
use dioxus_core::{AttributeBox, Element, HasAttributesBox, Scope};
|
||||||
|
use dioxus_html::{ExtendedAudioMarker, ExtendedGlobalAttributesMarker};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn props_spread() {
|
||||||
|
pub struct FooProps<'a> {
|
||||||
|
pub open: Option<&'a str>,
|
||||||
|
attributes: Vec<AttributeBox<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----
|
||||||
|
impl<'a> FooProps<'a> {
|
||||||
|
#[doc = "\nCreate a builder for building `FooProps`.\nOn the builder, call `.open(...)`(optional) to set the values of the fields.\nFinally, call `.build()` to create the instance of `FooProps`.\n "]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn builder() -> FooPropsBuilder<'a, ((), ), > {
|
||||||
|
FooPropsBuilder { fields: ((), ), attributes: Vec::new(), _phantom: core::default::Default::default() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[must_use]
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[allow(dead_code, non_camel_case_types, non_snake_case)]
|
||||||
|
pub struct FooPropsBuilder<'a, TypedBuilderFields, > {
|
||||||
|
fields: TypedBuilderFields,
|
||||||
|
attributes: Vec<AttributeBox<'a>>,
|
||||||
|
_phantom: ( core::marker::PhantomData<&'a ()> ),
|
||||||
|
}
|
||||||
|
//impl<'a, TypedBuilderFields, > Clone for FooPropsBuilder<'a, TypedBuilderFields, > where TypedBuilderFields: Clone { fn clone(&self) -> Self { Self { fields: self.fields.clone(), attributes: self.attributes, _phantom: Default::default() } } }
|
||||||
|
impl<'a> dioxus::prelude::Properties for FooProps<'a> {
|
||||||
|
type Builder = FooPropsBuilder<'a, ((), ), >;
|
||||||
|
const IS_STATIC: bool = false;
|
||||||
|
fn builder() -> Self::Builder { FooProps::builder() }
|
||||||
|
unsafe fn memoize(&self, other: &Self) -> bool { false }
|
||||||
|
}
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[allow(dead_code, non_camel_case_types, non_snake_case)]
|
||||||
|
pub trait FooPropsBuilder_Optional<T> { fn into_value<F: FnOnce() -> T>(self, default: F) -> T; }
|
||||||
|
impl<T> FooPropsBuilder_Optional<T> for () { fn into_value<F: FnOnce() -> T>(self, default: F) -> T { default() } }
|
||||||
|
impl<T> FooPropsBuilder_Optional<T> for (T, ) { fn into_value<F: FnOnce() -> T>(self, _: F) -> T { self.0 } }
|
||||||
|
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
||||||
|
impl<'a> FooPropsBuilder<'a, ((), )> {
|
||||||
|
pub fn open(self, open: &'a str) -> FooPropsBuilder<'a, ((Option<&'a str>,
|
||||||
|
// pub attributes: Vec<Attribute<'a>>,
|
||||||
|
), )> {
|
||||||
|
let open = (Some(open), );
|
||||||
|
let (_, ) = self.fields;
|
||||||
|
FooPropsBuilder { fields: (open, ), attributes: self.attributes, _phantom: self._phantom }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[allow(dead_code, non_camel_case_types, non_snake_case)]
|
||||||
|
pub enum FooPropsBuilder_Error_Repeated_field_open {}
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
||||||
|
impl<'a> FooPropsBuilder<'a, ((Option<&'a str>,
|
||||||
|
// pub attributes: Vec<Attribute<'a>>,
|
||||||
|
), )> {
|
||||||
|
#[deprecated(note = "Repeated field open")]
|
||||||
|
pub fn open(self, _: FooPropsBuilder_Error_Repeated_field_open) -> FooPropsBuilder<'a, ((Option<&'a str>,
|
||||||
|
// pub attributes: Vec<Attribute<'a>>,
|
||||||
|
), )> { self }
|
||||||
|
}
|
||||||
|
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
||||||
|
impl<'a, __open: FooPropsBuilder_Optional<Option<&'a str>>> FooPropsBuilder<'a, (__open, ), > {
|
||||||
|
pub fn build(self) -> FooProps<'a> {
|
||||||
|
let (open, ) = self.fields;
|
||||||
|
let open = FooPropsBuilder_Optional::into_value(open, || Default::default());
|
||||||
|
FooProps { open, attributes: self.attributes }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// -----
|
||||||
|
|
||||||
|
impl<'a, A> HasAttributesBox<'a, FooPropsBuilder<'a, (A, )>> for FooPropsBuilder<'a, (A, )> {
|
||||||
|
fn push_attribute(self, attr: AttributeBox<'a>) -> FooPropsBuilder<'a, (A, )> {
|
||||||
|
let mut attrs = Vec::from(self.attributes);
|
||||||
|
attrs.push(attr);
|
||||||
|
FooPropsBuilder { fields: self.fields, attributes: attrs, _phantom: self._phantom }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<A,> ExtendedGlobalAttributesMarker for FooPropsBuilder<'_, (A,)> {}
|
||||||
|
impl<A,> ExtendedAudioMarker for FooPropsBuilder<'_, (A,)> {}
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_html::AudioExtension;
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn Foo<'a>(cx: Scope<'a, FooProps<'a>>) -> Element<'a> {
|
||||||
|
let muted = false;
|
||||||
|
let attributes = cx.props.attributes;
|
||||||
|
render! {
|
||||||
|
// rsx! {
|
||||||
|
// audio {
|
||||||
|
// muted: muted,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
::dioxus::core::LazyNodes::new(move |__cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode {
|
||||||
|
static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template { name: concat!(file!(), ":", line!(), ":", column!(), ":", "" ), roots: &[::dioxus::core::TemplateNode::Element { tag: dioxus_elements::audio::TAG_NAME, namespace: dioxus_elements::audio::NAME_SPACE, attrs: &[::dioxus::core::TemplateAttribute::Dynamic { id: 0usize }], children: &[] }], node_paths: &[], attr_paths: &[&[0u8]] };
|
||||||
|
let mut attrs = vec![__cx.attr(dioxus_elements::audio::muted.0, muted, dioxus_elements::audio::muted.1, dioxus_elements::audio::muted.2)];
|
||||||
|
for attr in attributes {
|
||||||
|
attrs.push(__cx.attr(attr.name, attr.value.into_value(__cx.bump()), attr.namespace, attr.volatile));
|
||||||
|
};
|
||||||
|
::dioxus::core::VNode {
|
||||||
|
parent: None,
|
||||||
|
key: None,
|
||||||
|
template: std::cell::Cell::new(TEMPLATE),
|
||||||
|
root_ids: Default::default(),
|
||||||
|
dynamic_nodes: __cx.bump().alloc([]),
|
||||||
|
dynamic_attrs: __cx.bump().alloc(attrs),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
Foo {
|
||||||
|
autoplay: true,
|
||||||
|
controls: true,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
28
packages/html-internal-macro/Cargo.toml
Normal file
28
packages/html-internal-macro/Cargo.toml
Normal file
|
@ -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"] }
|
83
packages/html-internal-macro/src/lib.rs
Normal file
83
packages/html-internal-macro/src/lib.rs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
|
||||||
|
use convert_case::{Case, Casing};
|
||||||
|
use quote::{quote, TokenStreamExt, ToTokens};
|
||||||
|
use syn::{braced, Ident, parse_macro_input, Token};
|
||||||
|
use syn::__private::TokenStream2;
|
||||||
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
|
||||||
|
#[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.to_string() == "ELEMENT",
|
||||||
|
name,
|
||||||
|
attrs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for ImplExtensionAttributes {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
|
let name = &self.name;
|
||||||
|
let camel_name = name.to_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());
|
||||||
|
let marker_name = Ident::new(format!("Extended{}Marker", &camel_name).as_str(), name.span());
|
||||||
|
|
||||||
|
if !self.is_element {
|
||||||
|
tokens.append_all(quote! {
|
||||||
|
trait #impl_name {}
|
||||||
|
impl #name for #impl_name {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let defs = self.attrs.iter().map(|ident| {
|
||||||
|
quote! {
|
||||||
|
fn #ident(self, value: impl IntoAttributeValue<'a> + 'static) -> Self;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let impls = self.attrs.iter().map(|ident| {
|
||||||
|
let d = if self.is_element {
|
||||||
|
quote! { #name::#ident }
|
||||||
|
} else {
|
||||||
|
quote! { #impl_name::#ident }
|
||||||
|
};
|
||||||
|
quote! {
|
||||||
|
fn #ident(self, value: impl IntoAttributeValue<'a> + 'static) -> Self {
|
||||||
|
let d = #d;
|
||||||
|
self.push_attribute(AttributeBox::new(d.0, value, d.1, d.2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tokens.append_all(quote! {
|
||||||
|
pub trait #marker_name {}
|
||||||
|
|
||||||
|
pub trait #extension_name<'a> {
|
||||||
|
#(#defs)*
|
||||||
|
}
|
||||||
|
impl<'a, T> #extension_name<'a> for T where T: HasAttributesBox<'a, T> + #marker_name {
|
||||||
|
#(#impls)*
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
1
packages/html-internal-macro/tests/01-simple.rs
Normal file
1
packages/html-internal-macro/tests/01-simple.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
fn main() {}
|
5
packages/html-internal-macro/tests/progress.rs
Normal file
5
packages/html-internal-macro/tests/progress.rs
Normal file
|
@ -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]
|
[dependencies]
|
||||||
dioxus-core = { workspace = true }
|
dioxus-core = { workspace = true }
|
||||||
dioxus-rsx = { workspace = true, features = ["hot_reload"], optional = true }
|
dioxus-rsx = { workspace = true, features = ["hot_reload"], optional = true }
|
||||||
|
dioxus-html-internal-macro = { workspace = true }
|
||||||
serde = { version = "1", features = ["derive"], optional = true }
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
serde_repr = { version = "0.1", optional = true }
|
serde_repr = { version = "0.1", optional = true }
|
||||||
wasm-bindgen = { workspace = true, optional = true }
|
wasm-bindgen = { workspace = true, optional = true }
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
#![allow(non_upper_case_globals)]
|
#![allow(non_upper_case_globals)]
|
||||||
|
|
||||||
|
use dioxus_core::AttributeBox;
|
||||||
|
use dioxus_core::HasAttributesBox;
|
||||||
|
use dioxus_core::prelude::IntoAttributeValue;
|
||||||
|
use dioxus_html_internal_macro::impl_extension_attributes;
|
||||||
|
#[cfg(feature = "hot-reload-context")]
|
||||||
|
use dioxus_rsx::HotReloadingContext;
|
||||||
|
|
||||||
#[cfg(feature = "hot-reload-context")]
|
#[cfg(feature = "hot-reload-context")]
|
||||||
use crate::{map_global_attributes, map_svg_attributes};
|
use crate::{map_global_attributes, map_svg_attributes};
|
||||||
use crate::{GlobalAttributes, SvgAttributes};
|
use crate::{GlobalAttributes, SvgAttributes};
|
||||||
#[cfg(feature = "hot-reload-context")]
|
|
||||||
use dioxus_rsx::HotReloadingContext;
|
|
||||||
|
|
||||||
pub type AttributeDiscription = (&'static str, Option<&'static str>, bool);
|
pub type AttributeDiscription = (&'static str, Option<&'static str>, bool);
|
||||||
|
|
||||||
|
@ -106,6 +112,8 @@ macro_rules! impl_element {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlobalAttributes for $name {}
|
impl GlobalAttributes for $name {}
|
||||||
|
|
||||||
|
impl_extension_attributes![ELEMENT $name { $($fil,)* }];
|
||||||
};
|
};
|
||||||
|
|
||||||
(
|
(
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
#![allow(non_upper_case_globals)]
|
#![allow(non_upper_case_globals)]
|
||||||
|
|
||||||
|
use dioxus_core::HasAttributesBox;
|
||||||
|
use dioxus_core::AttributeBox;
|
||||||
|
use dioxus_core::prelude::IntoAttributeValue;
|
||||||
|
use dioxus_html_internal_macro::impl_extension_attributes;
|
||||||
|
|
||||||
use crate::AttributeDiscription;
|
use crate::AttributeDiscription;
|
||||||
|
|
||||||
#[cfg(feature = "hot-reload-context")]
|
#[cfg(feature = "hot-reload-context")]
|
||||||
|
@ -62,6 +67,8 @@ macro_rules! trait_methods {
|
||||||
)*
|
)*
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_extension_attributes![GLOBAL $trait { $($name,)* }];
|
||||||
};
|
};
|
||||||
|
|
||||||
// Rename the incoming ident and apply a custom namespace
|
// Rename the incoming ident and apply a custom namespace
|
||||||
|
|
|
@ -73,6 +73,7 @@ pub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {
|
||||||
attributes,
|
attributes,
|
||||||
key: None,
|
key: None,
|
||||||
brace: Default::default(),
|
brace: Default::default(),
|
||||||
|
extra_attributes: None,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ pub struct Element {
|
||||||
pub attributes: Vec<ElementAttrNamed>,
|
pub 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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for Element {
|
impl Parse for Element {
|
||||||
|
@ -35,6 +36,7 @@ impl Parse for Element {
|
||||||
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 _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
|
||||||
|
@ -42,6 +44,11 @@ impl Parse for Element {
|
||||||
// "def": 456,
|
// "def": 456,
|
||||||
// abc: 123,
|
// abc: 123,
|
||||||
loop {
|
loop {
|
||||||
|
if content.peek(Token![...]) {
|
||||||
|
content.parse::<Token![...]>()?;
|
||||||
|
extra_attributes = Some(content.parse::<Expr>()?);
|
||||||
|
}
|
||||||
|
|
||||||
// Parse the raw literal fields
|
// Parse the raw literal fields
|
||||||
if content.peek(LitStr) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
|
if content.peek(LitStr) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
|
||||||
let name = content.parse::<LitStr>()?;
|
let name = content.parse::<LitStr>()?;
|
||||||
|
@ -160,6 +167,7 @@ impl Parse for Element {
|
||||||
attributes,
|
attributes,
|
||||||
children,
|
children,
|
||||||
brace,
|
brace,
|
||||||
|
extra_attributes
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue