diff --git a/.vscode/spellright.dict b/.vscode/spellright.dict index ee02bfa75..28bfecf22 100644 --- a/.vscode/spellright.dict +++ b/.vscode/spellright.dict @@ -58,3 +58,4 @@ textarea noderefs wasm 7ns +attr diff --git a/README.md b/README.md index 8bb575380..2a6eea9f3 100644 --- a/README.md +++ b/README.md @@ -111,11 +111,13 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an | Null components | ✅ | ✅ | allow returning no components | | No-div components | ✅ | ✅ | components that render components | | Fragments | ✅ | ✅ | rsx! can return multiple elements without a root | -| Manual Props | 👀 | ✅ | Manually pass in props | -| NodeRef | 👀 | ✅ | gain direct access to nodes | +| Manual Props | ✅ | ✅ | Manually pass in props with spread syntax | | Controlled Inputs | ✅ | ✅ | stateful wrappers around inputs | -| CSS/Inline Styles | 🛠 | ✅ | syntax for inline/conditional styles | | 1st class global state | 🛠 | ✅ | redux/recoil/mobx on top of context | +| NodeRef | 🛠 | ✅ | gain direct access to nodes [1] | +| CSS/Inline Styles | 🛠 | ✅ | syntax for inline styles/attribute groups | + +[1] Currently blocked until we figure out a cross-platform way of exposing an imperative Node API ### Phase 2: Advanced Toolkits diff --git a/examples/rsx_usage.rs b/examples/rsx_usage.rs index da3d77eba..6fdfb336f 100644 --- a/examples/rsx_usage.rs +++ b/examples/rsx_usage.rs @@ -162,11 +162,10 @@ static Example: FC<()> = |cx| { // Can take optional properties Taller { a: "asd" } - // Can pass in props directly + // Can pass in props directly as an expression {{ - todo!("this neesd to be implemented"); let props = TallerProps {a: "hello"}; - rsx!(Taller {a: "a"}) + rsx!(Taller { ..props }) }} // Can take children diff --git a/notes/Parity.md b/notes/Parity.md index c838aedbb..76b1cf992 100644 --- a/notes/Parity.md +++ b/notes/Parity.md @@ -1,52 +1,31 @@ # Parity with React -Sorted by priority - -| Feature | Dioxus | React | Notes | -| ---------------------- | ------ | ----- | ------------------------------------------------ | -| ----- Phase 1 ----- | ----- | ----- | ----- | -| Conditional Rendering | ✅ | ✅ | if/then to hide/show component | -| Map, Iterator | ✅ | ✅ | map/filter/reduce rsx! | -| Keyed Components | ✅ | ✅ | advanced diffing with keys | -| Web | ✅ | ✅ | renderer for web browser | -| Desktop (webview) | ✅ | ✅ | renderer for desktop | -| Context | ✅ | ✅ | share state through the tree | -| Hook | ✅ | ✅ | memory cells in components | -| SSR | ✅ | ✅ | render directly to string | -| Runs natively | ✅ | 👀 | runs as a portable binary w/o a runtime (Node) | -| Component Children | ✅ | ✅ | cx.children() as a list of nodes | -| Null components | ✅ | ✅ | allow returning no components | -| No-div components | ✅ | ✅ | components that render components | -| Fragments | ✅ | ✅ | rsx! can return multiple elements without a root | -| Manual Props | 👀 | ✅ | Manually pass in props | -| NodeRef | 👀 | ✅ | gain direct access to nodes | -| Controlled Inputs | ✅ | ✅ | stateful wrappers around inputs | -| CSS/Inline Styles | 🛠 | ✅ | syntax for inline/conditional styles | -| 1st class global state | 🛠 | ✅ | redux/recoil/mobx on top of context | -| ----- Phase 2 ----- | ----- | ----- | ----- | -| 1st class router | 👀 | ✅ | Hook built on top of history | -| Assets | 👀 | ✅ | include css/svg/img url statically | -| Integrated classnames | 🛠 | 👀 | built-in `classnames` | -| Suspense | 👀 | 👀 | schedule future render from future/promise | -| Transition | 👀 | 👀 | High-level control over suspense | -| Animation | 👀 | ✅ | Spring-style animations | -| Mobile | 👀 | ✅ | Render with cacao | -| Desktop (native) | 👀 | ✅ | Render with native desktop | -| 3D Renderer | 👀 | ✅ | react-three-fiber | -| ----- Phase 3 ----- | ----- | ----- | ----- | -| Portal | 👀 | ✅ | cast elements through tree | -| Error/Panic boundary | 👀 | ✅ | catch panics and display custom BSOD | -| Code-splitting | 👀 | ✅ | Make bundle smaller/lazy | -| LiveView | 👀 | 👀 | Example for SSR + WASM apps | +Parity has moved to the homepage ## Required services: --- -Gloo is covering a lot of these. We want to build hooks around these, and provide examples on how to use them. +Gloo is covering a lot of these. We want to build hooks around these and provide examples on how to use them. https://github.com/rustwasm/gloo -If the gloo service doesn't exist, then we need to contribute to the project +For example, resize observer would function like this: + +```rust +static Example: FC<()> = |cx| { + let observer = use_resize_observer(); + + cx.render(rsx!( + div { ref: observer.node_ref + "Size, x: {observer.x} y: {observer.y}" + } + )) +} +``` + +However, resize observing is _not_ cross-platform, so this hook (internally) needs to abstract over the rendering platform. + +For other services, we shell out to gloo. If the gloo service doesn't exist, then we need to contribute to the project to make sure it exists. | Service | Hook examples | Current Projects | | ---------------------------- | ------------- | ---------------- | diff --git a/packages/core-macro/src/rsx/component.rs b/packages/core-macro/src/rsx/component.rs index 3d10aede0..e9b42996b 100644 --- a/packages/core-macro/src/rsx/component.rs +++ b/packages/core-macro/src/rsx/component.rs @@ -27,21 +27,23 @@ pub struct Component { name: syn::Path, body: Vec, children: Vec, + manual_props: Option, } impl Parse for Component { - fn parse(s: ParseStream) -> Result { + fn parse(stream: ParseStream) -> Result { // let name = s.parse::()?; // todo: look into somehow getting the crate/super/etc - let name = syn::Path::parse_mod_style(s)?; + let name = syn::Path::parse_mod_style(stream)?; // parse the guts let content: ParseBuffer; - syn::braced!(content in s); + syn::braced!(content in stream); let mut body: Vec = Vec::new(); let mut children: Vec = Vec::new(); + let mut manual_props = None; 'parsing: loop { // [1] Break if empty @@ -49,15 +51,10 @@ impl Parse for Component { break 'parsing; } - if content.peek(token::Brace) && content.peek2(Token![...]) { - let inner: ParseBuffer; - syn::braced!(inner in content); - if inner.peek(Token![...]) { - todo!("Inline props not yet supported"); - } - } - - if content.peek(Ident) && content.peek2(Token![:]) { + if content.peek(Token![..]) { + content.parse::()?; + manual_props = Some(content.parse::()?); + } else if content.peek(Ident) && content.peek2(Token![:]) { body.push(content.parse::()?); } else { children.push(content.parse::()?); @@ -74,6 +71,7 @@ impl Parse for Component { name, body, children, + manual_props, }) } } @@ -82,8 +80,13 @@ impl ToTokens for Component { fn to_tokens(&self, tokens: &mut TokenStream2) { let name = &self.name; - let mut builder = quote! { - fc_to_builder(#name) + let using_manual_override = self.manual_props.is_some(); + + let mut builder = { + match &self.manual_props { + Some(manual_props) => quote! { #manual_props }, + None => quote! { fc_to_builder(#name) }, + } }; let mut has_key = None; @@ -92,13 +95,18 @@ impl ToTokens for Component { if field.name.to_string() == "key" { has_key = Some(field); } else { - builder.append_all(quote! {#field}); + match using_manual_override { + true => panic!("Currently we don't support manual props and prop fields. Choose either manual props or prop fields"), + false => builder.append_all(quote! {#field}), + } } } - builder.append_all(quote! { - .build() - }); + if !using_manual_override { + builder.append_all(quote! { + .build() + }); + } let key_token = match has_key { Some(field) => {