awesome: arbitrary expressions excepted without braces

This commit is contained in:
Jonathan Kelley committed 2021-12-30 03:14:47 -05:00
1 parent 14961023f9
commit 4c85bcfdc8
40 files changed
+825 -767

No files matched your search

+1
View File
@@ -16,6 +16,7 @@ proc-macro = true
[dependencies]
once_cell = "1.8"
proc-macro-error = "1.0.4"
proc-macro2 = { version = "1.0.6" }
quote = "1.0"
syn = { version = "1.0.11", features = ["full", "extra-traits"] }
+1
View File
@@ -99,6 +99,7 @@ impl ToTokens for InlinePropsBody {
};
out_tokens.append_all(quote! {
#[allow(non_camel_case)]
#modifiers
#vis struct #struct_name #generics {
#(#fields),*
+3 -2
View File
@@ -177,11 +177,12 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
/// todo!()
/// }
/// ```
#[proc_macro_error::proc_macro_error]
#[proc_macro]
pub fn rsx(s: TokenStream) -> TokenStream {
match syn::parse::<rsx::CallBody>(s) {
Err(e) => e.to_compile_error().into(),
Ok(s) => s.to_token_stream().into(),
Err(err) => err.to_compile_error().into(),
Ok(stream) => stream.to_token_stream().into(),
}
}
-64
View File
@@ -1,64 +0,0 @@
//! Parse anything that has a pattern of < Ident, Bracket >
//! ========================================================
//!
//! Whenever a `name {}` pattern emerges, we need to parse it into an element, a component, or a fragment.
//! This feature must support:
//! - Namepsaced/pathed components
//! - Differentiating between built-in and custom elements
use super::*;
use proc_macro2::TokenStream as TokenStream2;
use quote::ToTokens;
use syn::{
parse::{Parse, ParseStream},
Error, Ident, Result, Token,
};
#[allow(clippy::large_enum_variant)]
pub enum AmbiguousElement {
Element(Element),
Component(Component),
}
impl Parse for AmbiguousElement {
fn parse(input: ParseStream) -> Result<Self> {
// Try to parse as an absolute path and immediately defer to the componetn
if input.peek(Token![::]) {
return input.parse::<Component>().map(AmbiguousElement::Component);
}
// If not an absolute path, then parse the ident and check if it's a valid tag
if let Ok(pat) = input.fork().parse::<syn::Path>() {
if pat.segments.len() > 1 {
return input.parse::<Component>().map(AmbiguousElement::Component);
}
}
use syn::ext::IdentExt;
if let Ok(name) = input.fork().call(Ident::parse_any) {
let name_str = name.to_string();
let first_char = name_str.chars().next().unwrap();
if first_char.is_ascii_uppercase() {
input.parse::<Component>().map(AmbiguousElement::Component)
} else {
if input.peek2(syn::token::Paren) {
input.parse::<Component>().map(AmbiguousElement::Component)
} else {
input.parse::<Element>().map(AmbiguousElement::Element)
}
}
} else {
Err(Error::new(input.span(), "Not a valid Html tag"))
}
}
}
impl ToTokens for AmbiguousElement {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
AmbiguousElement::Element(el) => el.to_tokens(tokens),
AmbiguousElement::Component(comp) => comp.to_tokens(tokens),
}
}
}
-66
View File
@@ -1,66 +0,0 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
parse::{Parse, ParseStream},
Ident, Result, Token,
};
use super::*;
pub struct CallBody {
custom_context: Option<Ident>,
roots: Vec<BodyNode>,
}
/// The custom rusty variant of parsing rsx!
impl Parse for CallBody {
fn parse(input: ParseStream) -> Result<Self> {
let custom_context = try_parse_custom_context(input)?;
let (_, roots, _) = BodyConfig::new_call_body().parse_component_body(input)?;
Ok(Self {
custom_context,
roots,
})
}
}
fn try_parse_custom_context(input: ParseStream) -> Result<Option<Ident>> {
let res = if input.peek(Ident) && input.peek2(Token![,]) {
let name = input.parse::<Ident>()?;
input.parse::<Token![,]>()?;
Some(name)
} else {
None
};
Ok(res)
}
/// Serialize the same way, regardless of flavor
impl ToTokens for CallBody {
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
let inner = if self.roots.len() == 1 {
let inner = &self.roots[0];
quote! {#inner}
} else {
let childs = &self.roots;
quote! { __cx.fragment_root([ #(#childs),* ]) }
};
match &self.custom_context {
// The `in cx` pattern allows directly rendering
Some(ident) => out_tokens.append_all(quote! {
#ident.render(LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
use dioxus_elements::{GlobalAttributes, SvgAttributes};
#inner
}))
}),
// Otherwise we just build the LazyNode wrapper
None => out_tokens.append_all(quote! {
LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
use dioxus_elements::{GlobalAttributes, SvgAttributes};
#inner
})
}),
};
}
}
+20 -95
View File
@@ -19,11 +19,10 @@ use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
ext::IdentExt,
parse::{Parse, ParseBuffer, ParseStream},
token, Error, Expr, ExprClosure, Ident, Result, Token,
token, Expr, ExprClosure, Ident, Result, Token,
};
pub struct Component {
// accept any path-like argument
name: syn::Path,
body: Vec<ComponentField>,
children: Vec<BodyNode>,
@@ -32,12 +31,8 @@ pub struct Component {
impl Parse for Component {
fn parse(stream: ParseStream) -> Result<Self> {
// let name = s.parse::<syn::ExprPath>()?;
// todo: look into somehow getting the crate/super/etc
let name = syn::Path::parse_mod_style(stream)?;
// parse the guts
let content: ParseBuffer;
// if we see a `{` then we have a block
@@ -48,13 +43,25 @@ impl Parse for Component {
syn::parenthesized!(content in stream);
}
let cfg: BodyConfig = BodyConfig {
allow_children: true,
allow_fields: true,
allow_manual_props: true,
};
let mut body = Vec::new();
let mut children = Vec::new();
let mut manual_props = None;
let (body, children, manual_props) = cfg.parse_component_body(&content)?;
while !content.is_empty() {
// if we splat into a component then we're merging properties
if content.peek(Token![..]) {
content.parse::<Token![..]>()?;
manual_props = Some(content.parse::<Expr>()?);
} else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
body.push(content.parse::<ComponentField>()?);
} else {
children.push(content.parse::<BodyNode>()?);
}
if content.peek(Token![,]) {
let _ = content.parse::<Token![,]>();
}
}
Ok(Self {
name,
@@ -65,77 +72,6 @@ impl Parse for Component {
}
}
pub struct BodyConfig {
pub allow_fields: bool,
pub allow_children: bool,
pub allow_manual_props: bool,
}
impl BodyConfig {
/// The configuration to parse the root
pub fn new_call_body() -> Self {
Self {
allow_children: true,
allow_fields: false,
allow_manual_props: false,
}
}
}
impl BodyConfig {
// todo: unify this body parsing for both elements and components
// both are style rather ad-hoc, though components are currently more configured
pub fn parse_component_body(
&self,
content: &ParseBuffer,
) -> Result<(Vec<ComponentField>, Vec<BodyNode>, Option<Expr>)> {
let mut body = Vec::new();
let mut children = Vec::new();
let mut manual_props = None;
'parsing: loop {
// [1] Break if empty
if content.is_empty() {
break 'parsing;
}
if content.peek(Token![..]) {
if !self.allow_manual_props {
return Err(Error::new(
content.span(),
"Props spread syntax is not allowed in this context. \nMake to only use the elipsis `..` in Components.",
));
}
content.parse::<Token![..]>()?;
manual_props = Some(content.parse::<Expr>()?);
} else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
if !self.allow_fields {
return Err(Error::new(
content.span(),
"Property fields is not allowed in this context. \nMake to only use fields in Components or Elements.",
));
}
body.push(content.parse::<ComponentField>()?);
} else {
if !self.allow_children {
return Err(Error::new(
content.span(),
"This item is not allowed to accept children.",
));
}
children.push(content.parse::<BodyNode>()?);
}
// consume comma if it exists
// we don't actually care if there *are* commas between attrs
if content.peek(Token![,]) {
let _ = content.parse::<Token![,]>();
}
}
Ok((body, children, manual_props))
}
}
impl ToTokens for Component {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = &self.name;
@@ -219,9 +155,7 @@ pub struct ComponentField {
enum ContentField {
ManExpr(Expr),
OnHandler(ExprClosure),
// A handler was provided in {} tokens
OnHandlerRaw(Expr),
}
@@ -229,9 +163,6 @@ impl ToTokens for ContentField {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
ContentField::ManExpr(e) => e.to_tokens(tokens),
ContentField::OnHandler(e) => tokens.append_all(quote! {
__cx.bump().alloc(#e)
}),
ContentField::OnHandlerRaw(e) => tokens.append_all(quote! {
__cx.bump().alloc(#e)
}),
@@ -246,13 +177,7 @@ impl Parse for ComponentField {
let name_str = name.to_string();
let content = if name_str.starts_with("on") {
if input.peek(token::Brace) {
let content;
syn::braced!(content in input);
ContentField::OnHandlerRaw(content.parse()?)
} else {
ContentField::OnHandler(input.parse()?)
}
ContentField::OnHandlerRaw(input.parse()?)
} else {
ContentField::ManExpr(input.parse::<Expr>()?)
};
+116 -63
View File
@@ -4,7 +4,7 @@ use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
parse::{Parse, ParseBuffer, ParseStream},
token, Expr, ExprClosure, Ident, LitStr, Result, Token,
Expr, Ident, LitStr, Result, Token,
};
// =======================================
@@ -33,45 +33,69 @@ impl Parse for Element {
let mut key = None;
let mut _el_ref = None;
// todo: more descriptive error handling
while !content.is_empty() {
// parse fields with commas
// break when we don't get this pattern anymore
// start parsing bodynodes
// "def": 456,
// abc: 123,
loop {
// Parse the raw literal fields
if content.peek(LitStr) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
let name = content.parse::<LitStr>()?;
let ident = name.clone();
content.parse::<Token![:]>()?;
if content.peek(LitStr) {
let value = content.parse::<LitStr>()?;
attributes.push(ElementAttrNamed {
el_name: el_name.clone(),
attr: ElementAttr::CustomAttrText { name, value },
});
} else {
let value = content.parse::<Expr>()?;
attributes.push(ElementAttrNamed {
el_name: el_name.clone(),
attr: ElementAttr::CustomAttrExpression { name, value },
});
}
if content.is_empty() {
break;
}
// todo: add a message saying you need to include commas between fields
if content.parse::<Token![,]>().is_err() {
proc_macro_error::emit_error!(
ident,
"This attribute is misisng a trailing comma"
)
}
continue;
}
if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
let name = content.parse::<Ident>()?;
let ident = name.clone();
let name_str = name.to_string();
content.parse::<Token![:]>()?;
if name_str.starts_with("on") {
if content.peek(token::Brace) {
let mycontent;
syn::braced!(mycontent in content);
listeners.push(ElementAttrNamed {
el_name: el_name.clone(),
attr: ElementAttr::EventTokens {
name,
tokens: mycontent.parse()?,
},
});
} else {
listeners.push(ElementAttrNamed {
el_name: el_name.clone(),
attr: ElementAttr::EventClosure {
name,
closure: content.parse()?,
},
});
};
listeners.push(ElementAttrNamed {
el_name: el_name.clone(),
attr: ElementAttr::EventTokens {
name,
tokens: content.parse()?,
},
});
} else {
match name_str.as_str() {
"key" => {
key = Some(content.parse()?);
}
"classes" => {
todo!("custom class list not supported")
}
"namespace" => {
todo!("custom namespace not supported")
}
"classes" => todo!("custom class list not supported yet"),
// "namespace" => todo!("custom namespace not supported yet"),
"node_ref" => {
_el_ref = Some(content.parse::<Expr>()?);
}
@@ -96,27 +120,56 @@ impl Parse for Element {
}
}
}
} else if content.peek(LitStr) && content.peek2(Token![:]) {
let name = content.parse::<LitStr>()?;
content.parse::<Token![:]>()?;
if content.peek(LitStr) {
let value = content.parse::<LitStr>()?;
attributes.push(ElementAttrNamed {
el_name: el_name.clone(),
attr: ElementAttr::CustomAttrText { name, value },
});
} else {
let value = content.parse::<Expr>()?;
attributes.push(ElementAttrNamed {
el_name: el_name.clone(),
attr: ElementAttr::CustomAttrExpression { name, value },
});
if content.is_empty() {
break;
}
} else {
children.push(content.parse::<BodyNode>()?);
// todo: add a message saying you need to include commas between fields
if content.parse::<Token![,]>().is_err() {
proc_macro_error::emit_error!(
ident,
"This attribute is misisng a trailing comma"
)
}
continue;
}
break;
}
while !content.is_empty() {
if (content.peek(LitStr) && content.peek2(Token![:])) && !content.peek3(Token![:]) {
let ident = content.parse::<LitStr>().unwrap();
let name = ident.value();
proc_macro_error::emit_error!(
ident, "This attribute `{}` is in the wrong place.", name;
help =
"All attribute fields must be placed above children elements.
div {
attr: \"...\", <---- attribute is above children
div { } <---- children are below attributes
}";
)
}
if (content.peek(Ident) && content.peek2(Token![:])) && !content.peek3(Token![:]) {
let ident = content.parse::<Ident>().unwrap();
let name = ident.to_string();
proc_macro_error::emit_error!(
ident, "This attribute `{}` is in the wrong place.", name;
help =
"All attribute fields must be placed above children elements.
div {
attr: \"...\", <---- attribute is above children
div { } <---- children are below attributes
}";
)
}
children.push(content.parse::<BodyNode>()?);
// consume comma if it exists
// we don't actually care if there *are* commas after elements/text
if content.peek(Token![,]) {
@@ -138,7 +191,7 @@ impl Parse for Element {
impl ToTokens for Element {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = &self.name;
let childs = &self.children;
let children = &self.children;
let listeners = &self.listeners;
let attr = &self.attributes;
@@ -153,7 +206,7 @@ impl ToTokens for Element {
dioxus_elements::#name,
[ #(#listeners),* ],
[ #(#attr),* ],
[ #(#childs),* ],
[ #(#children),* ],
#key,
)
});
@@ -173,8 +226,8 @@ enum ElementAttr {
// "attribute": true,
CustomAttrExpression { name: LitStr, value: Expr },
// onclick: move |_| {}
EventClosure { name: Ident, closure: ExprClosure },
// // onclick: move |_| {}
// EventClosure { name: Ident, closure: ExprClosure },
// onclick: {}
EventTokens { name: Ident, tokens: Expr },
@@ -189,7 +242,7 @@ impl ToTokens for ElementAttrNamed {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let ElementAttrNamed { el_name, attr } = self;
let toks = match attr {
tokens.append_all(match attr {
ElementAttr::AttrText { name, value } => {
quote! {
dioxus_elements::#el_name.#name(__cx, format_args_f!(#value))
@@ -200,26 +253,26 @@ impl ToTokens for ElementAttrNamed {
dioxus_elements::#el_name.#name(__cx, #value)
}
}
ElementAttr::CustomAttrText { name, value } => {
quote! { __cx.attr( #name, format_args_f!(#value), None, false ) }
}
ElementAttr::CustomAttrExpression { name, value } => {
quote! { __cx.attr( #name, format_args_f!(#value), None, false ) }
}
ElementAttr::EventClosure { name, closure } => {
quote! {
dioxus_elements::on::#name(__cx, #closure)
__cx.attr( #name, format_args_f!(#value), None, false )
}
}
ElementAttr::CustomAttrExpression { name, value } => {
quote! {
__cx.attr( #name, format_args_f!(#value), None, false )
}
}
// ElementAttr::EventClosure { name, closure } => {
// quote! {
// dioxus_elements::on::#name(__cx, #closure)
// }
// }
ElementAttr::EventTokens { name, tokens } => {
quote! {
dioxus_elements::on::#name(__cx, #tokens)
}
}
};
tokens.append_all(toks);
});
}
}
-63
View File
@@ -1,63 +0,0 @@
//! Parse `Fragments` into the Fragment VNode
//! ==========================================
//!
//! This parsing path emerges from [`AmbiguousElement`] which supports validation of the Fragment format.
//! We can be reasonably sure that whatever enters this parsing path is in the right format.
//! This feature must support:
//! - [x] Optional commas
//! - [ ] Children
//! - [ ] Keys
use super::AmbiguousElement;
use syn::parse::ParseBuffer;
use {
proc_macro2::TokenStream as TokenStream2,
quote::{quote, ToTokens, TokenStreamExt},
syn::{
parse::{Parse, ParseStream},
Ident, Result, Token,
},
};
pub struct Fragment {
children: Vec<AmbiguousElement>,
}
impl Parse for Fragment {
fn parse(input: ParseStream) -> Result<Self> {
input.parse::<Ident>()?;
let children = Vec::new();
// parse the guts
let content: ParseBuffer;
syn::braced!(content in input);
while !content.is_empty() {
content.parse::<AmbiguousElement>()?;
if content.peek(Token![,]) {
let _ = content.parse::<Token![,]>();
}
}
Ok(Self { children })
}
}
impl ToTokens for Fragment {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let childs = &self.children;
let children = quote! {
ChildrenList::new(__cx)
#( .add_child(#childs) )*
.finish()
};
tokens.append_all(quote! {
// #key_token,
dioxus::builder::vfragment(
__cx,
None,
#children
)
})
}
}
+74 -6
View File
@@ -11,17 +11,85 @@
//!
//! Any errors in using rsx! will likely occur when people start using it, so the first errors must be really helpful.
mod ambiguous;
mod body;
mod component;
mod element;
mod fragment;
mod node;
// Re-export the namespaces into each other
pub use ambiguous::*;
pub use body::*;
pub use component::*;
pub use element::*;
pub use fragment::*;
pub use node::*;
// imports
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
parse::{Parse, ParseStream},
Ident, Result, Token,
};
pub struct CallBody {
custom_context: Option<Ident>,
roots: Vec<BodyNode>,
}
impl Parse for CallBody {
fn parse(input: ParseStream) -> Result<Self> {
let custom_context = if input.peek(Ident) && input.peek2(Token![,]) {
let name = input.parse::<Ident>()?;
input.parse::<Token![,]>()?;
Some(name)
} else {
None
};
let mut roots = Vec::new();
while !input.is_empty() {
let node = input.parse::<BodyNode>()?;
if input.peek(Token![,]) {
let _ = input.parse::<Token![,]>();
}
roots.push(node);
}
Ok(Self {
custom_context,
roots,
})
}
}
/// Serialize the same way, regardless of flavor
impl ToTokens for CallBody {
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
let inner = if self.roots.len() == 1 {
let inner = &self.roots[0];
quote! { #inner }
} else {
let childs = &self.roots;
quote! { __cx.fragment_root([ #(#childs),* ]) }
};
match &self.custom_context {
// The `in cx` pattern allows directly rendering
Some(ident) => out_tokens.append_all(quote! {
#ident.render(LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
use dioxus_elements::{GlobalAttributes, SvgAttributes};
#inner
}))
}),
// Otherwise we just build the LazyNode wrapper
None => out_tokens.append_all(quote! {
LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
use dioxus_elements::{GlobalAttributes, SvgAttributes};
#inner
})
}),
};
}
}
+54 -39
View File
@@ -4,34 +4,67 @@ use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
parse::{Parse, ParseStream},
token, Expr, LitStr, Result,
token, Expr, LitStr, Result, Token,
};
// ==============================================
// Parse any div {} as a VElement
// ==============================================
/*
Parse
-> div {}
-> Component {}
-> component()
-> "text {with_args}"
-> (0..10).map(|f| rsx!("asd")), // <--- notice the comma - must be a complete expr
*/
pub enum BodyNode {
Element(AmbiguousElement),
Text(TextNode),
Element(Element),
Component(Component),
Text(LitStr),
RawExpr(Expr),
}
impl Parse for BodyNode {
fn parse(stream: ParseStream) -> Result<Self> {
// Supposedly this approach is discouraged due to inability to return proper errors
// TODO: Rework this to provide more informative errors
if stream.peek(token::Brace) {
let content;
syn::braced!(content in stream);
return Ok(BodyNode::RawExpr(content.parse::<Expr>()?));
}
if stream.peek(LitStr) {
return Ok(BodyNode::Text(stream.parse::<TextNode>()?));
return Ok(BodyNode::Text(stream.parse()?));
}
Ok(BodyNode::Element(stream.parse::<AmbiguousElement>()?))
// div {} -> el
// Div {} -> comp
if stream.peek(syn::Ident) && stream.peek2(token::Brace) {
if stream
.fork()
.parse::<Ident>()?
.to_string()
.chars()
.next()
.unwrap()
.is_ascii_uppercase()
{
return Ok(BodyNode::Component(stream.parse()?));
} else {
return Ok(BodyNode::Element(stream.parse::<Element>()?));
}
}
// component() -> comp
// ::component {} -> comp
// ::component () -> comp
if (stream.peek(syn::Ident) && stream.peek2(token::Paren))
|| (stream.peek(Token![::]))
|| (stream.peek(Token![:]) && stream.peek2(Token![:]))
{
return Ok(BodyNode::Component(stream.parse::<Component>()?));
}
// crate::component{} -> comp
// crate::component() -> comp
if let Ok(pat) = stream.fork().parse::<syn::Path>() {
if pat.segments.len() > 1 {
return Ok(BodyNode::Component(stream.parse::<Component>()?));
}
}
Ok(BodyNode::RawExpr(stream.parse::<Expr>()?))
}
}
@@ -39,31 +72,13 @@ impl ToTokens for BodyNode {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match &self {
BodyNode::Element(el) => el.to_tokens(tokens),
BodyNode::Text(txt) => txt.to_tokens(tokens),
BodyNode::Component(comp) => comp.to_tokens(tokens),
BodyNode::Text(txt) => tokens.append_all(quote! {
__cx.text(format_args_f!(#txt))
}),
BodyNode::RawExpr(exp) => tokens.append_all(quote! {
__cx.fragment_from_iter(#exp)
}),
}
}
}
// =======================================
// Parse just plain text
// =======================================
pub struct TextNode(LitStr);
impl Parse for TextNode {
fn parse(s: ParseStream) -> Result<Self> {
Ok(Self(s.parse()?))
}
}
impl ToTokens for TextNode {
fn to_tokens(&self, tokens: &mut TokenStream2) {
// todo: use heuristics to see if we can promote to &static str
let token_stream = &self.0.to_token_stream();
tokens.append_all(quote! {
__cx.text(format_args_f!(#token_stream))
});
}
}
+2 -2
View File
@@ -734,9 +734,9 @@ impl<'a, 'b> IntoVNode<'a> for LazyNodes<'a, 'b> {
}
}
impl IntoVNode<'_> for &'static str {
impl<'b> IntoVNode<'_> for &'b str {
fn into_vnode(self, cx: NodeFactory) -> VNode {
cx.static_text(self)
cx.text(format_args!("{}", self))
}
}
+1 -1
View File
@@ -44,7 +44,7 @@ fn lists_work() {
static App: Component = |cx| {
cx.render(rsx!(
h1 {"hello"}
{(0..6).map(|f| rsx!(span{ "{f}" }))}
(0..6).map(|f| rsx!(span{ "{f}" }))
))
};
let mut vdom = VirtualDom::new(App);
+64
View File
@@ -215,3 +215,67 @@ impl<'a, O, T: std::ops::Not<Output = O> + Copy> std::ops::Not for UseState<'a,
!*self.get()
}
}
/*
Convenience methods for UseState.
Note!
This is not comprehensive.
This is *just* meant to make common operations easier.
*/
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
impl<'a, T: Copy + Add<T, Output = T>> Add<T> for UseState<'a, T> {
type Output = T;
fn add(self, rhs: T) -> Self::Output {
self.inner.current_val.add(rhs)
}
}
impl<'a, T: Copy + Add<T, Output = T>> AddAssign<T> for UseState<'a, T> {
fn add_assign(&mut self, rhs: T) {
self.set(self.inner.current_val.add(rhs));
}
}
impl<'a, T: Copy + Sub<T, Output = T>> Sub<T> for UseState<'a, T> {
type Output = T;
fn sub(self, rhs: T) -> Self::Output {
self.inner.current_val.sub(rhs)
}
}
impl<'a, T: Copy + Sub<T, Output = T>> SubAssign<T> for UseState<'a, T> {
fn sub_assign(&mut self, rhs: T) {
self.set(self.inner.current_val.sub(rhs));
}
}
/// MUL
impl<'a, T: Copy + Mul<T, Output = T>> Mul<T> for UseState<'a, T> {
type Output = T;
fn mul(self, rhs: T) -> Self::Output {
self.inner.current_val.mul(rhs)
}
}
impl<'a, T: Copy + Mul<T, Output = T>> MulAssign<T> for UseState<'a, T> {
fn mul_assign(&mut self, rhs: T) {
self.set(self.inner.current_val.mul(rhs));
}
}
/// DIV
impl<'a, T: Copy + Div<T, Output = T>> Div<T> for UseState<'a, T> {
type Output = T;
fn div(self, rhs: T) -> Self::Output {
self.inner.current_val.div(rhs)
}
}
impl<'a, T: Copy + Div<T, Output = T>> DivAssign<T> for UseState<'a, T> {
fn div_assign(&mut self, rhs: T) {
self.set(self.inner.current_val.div(rhs));
}
}
+1 -1
View File
@@ -41,8 +41,8 @@ pub fn Link<'a, R: Routable>(cx: Scope<'a, LinkProps<'a, R>>) -> Element {
a {
href: "#",
class: format_args!("{}", cx.props.class.unwrap_or("")),
{&cx.props.children}
onclick: move |_| service.push_route(cx.props.to.clone()),
{&cx.props.children}
}
})
}
+7 -8
View File
@@ -309,19 +309,18 @@ mod tests {
static SLIGHTLY_MORE_COMPLEX: Component = |cx| {
cx.render(rsx! {
div {
title: "About W3Schools"
{(0..20).map(|f| rsx!{
div { title: "About W3Schools",
(0..20).map(|f| rsx!{
div {
title: "About W3Schools"
style: "color:blue;text-align:center"
class: "About W3Schools"
title: "About W3Schools",
style: "color:blue;text-align:center",
class: "About W3Schools",
p {
title: "About W3Schools"
title: "About W3Schools",
"Hello world!: {f}"
}
}
})}
})
}
})
};