mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-23 04:33:06 +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/router",
|
||||
"packages/html",
|
||||
"packages/html-internal-macro",
|
||||
"packages/hooks",
|
||||
"packages/web",
|
||||
"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-macro = { path = "packages/router-macro", version = "0.4.1" }
|
||||
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-web = { path = "packages/web", version = "0.4.0" }
|
||||
dioxus-ssr = { path = "packages/ssr", version = "0.4.0" }
|
||||
|
|
|
@ -49,6 +49,7 @@ impl Writer<'_> {
|
|||
attributes,
|
||||
children,
|
||||
brace,
|
||||
extra_attributes,
|
||||
} = el;
|
||||
|
||||
/*
|
||||
|
|
|
@ -73,8 +73,8 @@ pub(crate) mod innerlude {
|
|||
}
|
||||
|
||||
pub use crate::innerlude::{
|
||||
fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeValue, BorrowedAttributeValue,
|
||||
CapturedError, Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode,
|
||||
fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeBox, AttributeValue, BorrowedAttributeValue,
|
||||
CapturedError, Component, DynamicNode, Element, ElementId, Event, Fragment, HasAttributesBox, IntoDynNode,
|
||||
LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped,
|
||||
TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText,
|
||||
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]
|
||||
dioxus-core = { workspace = true }
|
||||
dioxus-rsx = { workspace = true, features = ["hot_reload"], optional = true }
|
||||
dioxus-html-internal-macro = { workspace = true }
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
serde_repr = { version = "0.1", optional = true }
|
||||
wasm-bindgen = { workspace = true, optional = true }
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
#![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")]
|
||||
use crate::{map_global_attributes, map_svg_attributes};
|
||||
use crate::{GlobalAttributes, SvgAttributes};
|
||||
#[cfg(feature = "hot-reload-context")]
|
||||
use dioxus_rsx::HotReloadingContext;
|
||||
|
||||
pub type AttributeDiscription = (&'static str, Option<&'static str>, bool);
|
||||
|
||||
|
@ -106,6 +112,8 @@ macro_rules! impl_element {
|
|||
}
|
||||
|
||||
impl GlobalAttributes for $name {}
|
||||
|
||||
impl_extension_attributes![ELEMENT $name { $($fil,)* }];
|
||||
};
|
||||
|
||||
(
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
#![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;
|
||||
|
||||
#[cfg(feature = "hot-reload-context")]
|
||||
|
@ -62,6 +67,8 @@ macro_rules! trait_methods {
|
|||
)*
|
||||
None
|
||||
}
|
||||
|
||||
impl_extension_attributes![GLOBAL $trait { $($name,)* }];
|
||||
};
|
||||
|
||||
// Rename the incoming ident and apply a custom namespace
|
||||
|
|
|
@ -73,6 +73,7 @@ pub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {
|
|||
attributes,
|
||||
key: None,
|
||||
brace: Default::default(),
|
||||
extra_attributes: None,
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ pub struct Element {
|
|||
pub attributes: Vec<ElementAttrNamed>,
|
||||
pub children: Vec<BodyNode>,
|
||||
pub brace: syn::token::Brace,
|
||||
pub extra_attributes: Option<Expr>,
|
||||
}
|
||||
|
||||
impl Parse for Element {
|
||||
|
@ -35,6 +36,7 @@ impl Parse for Element {
|
|||
let mut children: Vec<BodyNode> = vec![];
|
||||
let mut key = None;
|
||||
let mut _el_ref = None;
|
||||
let mut extra_attributes = None;
|
||||
|
||||
// parse fields with commas
|
||||
// break when we don't get this pattern anymore
|
||||
|
@ -42,6 +44,11 @@ impl Parse for Element {
|
|||
// "def": 456,
|
||||
// abc: 123,
|
||||
loop {
|
||||
if content.peek(Token![...]) {
|
||||
content.parse::<Token![...]>()?;
|
||||
extra_attributes = Some(content.parse::<Expr>()?);
|
||||
}
|
||||
|
||||
// Parse the raw literal fields
|
||||
if content.peek(LitStr) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
|
||||
let name = content.parse::<LitStr>()?;
|
||||
|
@ -160,6 +167,7 @@ impl Parse for Element {
|
|||
attributes,
|
||||
children,
|
||||
brace,
|
||||
extra_attributes
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue