diff --git a/README.md b/README.md index 85bb34443..408c533fd 100644 --- a/README.md +++ b/README.md @@ -107,29 +107,29 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an | Feature | Dioxus | React | Notes for Dioxus | | ----------------------- | ------ | ----- | ----------------------------------------------------- | | Conditional Rendering | ✅ | ✅ | if/then to hide/show component | -| Map, Iterator | ✅ | ✅ | map/filter/reduce rsx! | +| Map, Iterator | ✅ | ✅ | map/filter/reduce to produce rsx! | | Keyed Components | ✅ | ✅ | advanced diffing with keys | | Web | ✅ | ✅ | renderer for web browser | | Desktop (webview) | ✅ | ✅ | renderer for desktop | -| Context | ✅ | ✅ | share state through the tree | +| Shared State (Context) | ✅ | ✅ | share state through the tree | | Hook | ✅ | ✅ | memory cells in components | | SSR | ✅ | ✅ | render directly to string | | 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 | +| Headless components | ✅ | ✅ | components that don't return real elements | +| Fragments | ✅ | ✅ | multiple elements without a real root | | Manual Props | ✅ | ✅ | Manually pass in props with spread syntax | | Controlled Inputs | ✅ | ✅ | stateful wrappers around inputs | -| CSS/Inline Styles | ✅ | ✅ | syntax for inline styles/attribute groups[2] | -| NodeRef | 🛠 | ✅ | gain direct access to nodes [1] | -| 1st class global state | 🛠 | ✅ | redux/recoil/mobx on top of context | +| CSS/Inline Styles | ✅ | ✅ | syntax for inline styles/attribute groups | +| Custom elements | ✅ | ✅ | Define new element primitives | +| Compile-time correct | ✅ | ✅ | Throw errors on invalid template layouts | +| 1st class global state | ✅ | ❓ | redux/recoil/mobx on top of context | | Suspense | 🛠 | ✅ | schedule future render from future/promise | | Cooperative Scheduling | 🛠 | ✅ | Prioritize important events over non-important events | | Fine-grained reactivity | 🛠 | ❓ | Skip diffing for fine-grain updates | | Runs natively | ✅ | ❓ | runs as a portable binary w/o a runtime (Node) | +| NodeRef | 🛠 | ✅ | gain direct access to nodes [1] | - [1] Currently blocked until we figure out a cross-platform way of exposing an imperative Node API. -- [2] Would like to solve this in a more general way. Something like attribute groups that's not styling-specific. ### Phase 2: Advanced Toolkits diff --git a/examples/readme.rs b/examples/readme.rs index d71c3ae93..7b3679d44 100644 --- a/examples/readme.rs +++ b/examples/readme.rs @@ -2,8 +2,7 @@ //! //! The example from the README.md -use dioxus::{events::on::MouseEvent, prelude::*}; -use dioxus_html_namespace::{button, div, h1}; +use dioxus::prelude::*; fn main() { dioxus::web::launch(Example) @@ -12,11 +11,27 @@ fn main() { fn Example(cx: Context<()>) -> VNode { let name = use_state(&cx, || "..?"); - let handler = move |e: MouseEvent| e.cl; - cx.render(rsx! { h1 { "Hello, {name}" } - button { "?", onclick: move |event| name.set("world!")} + button { "?", onclick: move |_| name.set("world!")} button { "?", onclick: move |_| name.set("Dioxus 🎉")} }) } + +static Example2: FC<()> = |cx| { + let (g, set_g) = use_state_classic(&cx, || 0); + let v = (0..10).map(|f| { + dioxus::prelude::LazyNodes::new(move |__cx: &NodeFactory| { + __cx.element(dioxus_elements::li) + .listeners([dioxus::events::on::onclick(__cx, move |_| set_g(10))]) + .finish() + }) + }); + cx.render(dioxus::prelude::LazyNodes::new( + move |__cx: &NodeFactory| { + __cx.element(dioxus_elements::div) + .children([__cx.fragment_from_iter(v)]) + .finish() + }, + )) +}; diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs index 6b75ec360..bdb11af11 100644 --- a/packages/core-macro/src/lib.rs +++ b/packages/core-macro/src/lib.rs @@ -20,23 +20,6 @@ pub fn html(s: TokenStream) -> TokenStream { } } -/// The html! macro makes it easy for developers to write jsx-style markup in their components. -/// ``` -/// rsx! { -/// div { -/// class: "some special class" -/// h1 { "Children too" } -/// } -/// } -/// ``` -#[proc_macro] -pub fn rsx(s: TokenStream) -> TokenStream { - match syn::parse::(s) { - Err(e) => e.to_compile_error().into(), - Ok(s) => s.to_token_stream().into(), - } -} - /// The html! macro makes it easy for developers to write jsx-style markup in their components. /// We aim to keep functional parity with html templates. #[proc_macro] @@ -97,3 +80,165 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token Err(error) => error.to_compile_error().into(), } } + +/// The html! macro makes it easy for developers to write jsx-style markup in their components. +/// +/// ## Complete Reference Guide: +/// ``` +/// const Example: FC<()> = |cx| { +/// let formatting = "formatting!"; +/// let formatting_tuple = ("a", "b"); +/// let lazy_fmt = format_args!("lazily formatted text"); +/// cx.render(rsx! { +/// div { +/// // Elements +/// div {} +/// h1 {"Some text"} +/// h1 {"Some text with {formatting}"} +/// h1 {"Formatting basic expressions {formatting_tuple.0} and {formatting_tuple.1}"} +/// h2 { +/// "Multiple" +/// "Text" +/// "Blocks" +/// "Use comments as separators in html" +/// } +/// div { +/// h1 {"multiple"} +/// h2 {"nested"} +/// h3 {"elements"} +/// } +/// div { +/// class: "my special div" +/// h1 {"Headers and attributes!"} +/// } +/// div { +/// // pass simple rust expressions in +/// class: lazy_fmt, +/// id: format_args!("attributes can be passed lazily with std::fmt::Arguments"), +/// div { +/// class: { +/// const WORD: &str = "expressions"; +/// format_args!("Arguments can be passed in through curly braces for complex {}", WORD) +/// } +/// } +/// } +/// +/// // Expressions can be used in element position too: +/// {rsx!(p { "More templating!" })} +/// {html!(

"Even HTML templating!!"

)} +/// +/// // Iterators +/// {(0..10).map(|i| rsx!(li { "{i}" }))} +/// {{ +/// let data = std::collections::HashMap::<&'static str, &'static str>::new(); +/// // Iterators *should* have keys when you can provide them. +/// // Keys make your app run faster. Make sure your keys are stable, unique, and predictable. +/// // Using an "ID" associated with your data is a good idea. +/// data.into_iter().map(|(k, v)| rsx!(li { key: "{k}" "{v}" })) +/// }} +/// +/// // Matching +/// // Matching will throw a Rust error about "no two closures are the same type" +/// // To fix this, call "render" method or use the "in" syntax to produce VNodes. +/// // There's nothing we can do about it, sorry :/ (unless you want *really* unhygenic macros) +/// {match true { +/// true => rsx!(in cx, h1 {"Top text"}), +/// false => cx.render(rsx!( h1 {"Bottom text"})) +/// }} +/// +/// // Conditional rendering +/// // Dioxus conditional rendering is based around None/Some. We have no special syntax for conditionals. +/// // You can convert a bool condition to rsx! with .then and .or +/// {true.then(|| rsx!(div {}))} +/// +/// // True conditions need to be rendered (same reasons as matching) +/// {if true { +/// rsx!(in cx, h1 {"Top text"}) +/// } else { +/// rsx!(in cx, h1 {"Bottom text"}) +/// }} +/// +/// // returning "None" is a bit noisy... but rare in practice +/// {None as Option<()>} +/// +/// // Use the Dioxus type-alias for less noise +/// {NONE_ELEMENT} +/// +/// // can also just use empty fragments +/// Fragment {} +/// +/// // Fragments let you insert groups of nodes without a parent. +/// // This lets you make components that insert elements as siblings without a container. +/// div {"A"} +/// Fragment { +/// div {"B"} +/// div {"C"} +/// Fragment { +/// "D" +/// Fragment { +/// "heavily nested fragments is an antipattern" +/// "they cause Dioxus to do unnecessary work" +/// "don't use them carelessly if you can help it" +/// } +/// } +/// } +/// +/// // Components +/// // Can accept any paths +/// // Notice how you still get syntax highlighting and IDE support :) +/// Baller {} +/// baller::Baller { } +/// crate::baller::Baller {} +/// +/// // Can take properties +/// Taller { a: "asd" } +/// +/// // Can take optional properties +/// Taller { a: "asd" } +/// +/// // Can pass in props directly as an expression +/// {{ +/// let props = TallerProps {a: "hello"}; +/// rsx!(Taller { ..props }) +/// }} +/// +/// // Spreading can also be overridden manually +/// Taller { +/// ..TallerProps { a: "ballin!" } +/// a: "not ballin!" +/// } +/// +/// // Can take children too! +/// Taller { a: "asd", div {"hello world!"} } +/// } +/// }) +/// }; +/// +/// mod baller { +/// use super::*; +/// pub struct BallerProps {} +/// +/// /// This component totally balls +/// pub fn Baller(cx: Context<()>) -> VNode { +/// todo!() +/// } +/// } +/// +/// #[derive(Debug, PartialEq, Props)] +/// pub struct TallerProps { +/// a: &'static str, +/// } +/// +/// /// This component is taller than most :) +/// pub fn Taller(cx: Context) -> VNode { +/// let b = true; +/// todo!() +/// } +/// ``` +#[proc_macro] +pub fn rsx(s: TokenStream) -> TokenStream { + match syn::parse::(s) { + Err(e) => e.to_compile_error().into(), + Ok(s) => s.to_token_stream().into(), + } +} diff --git a/packages/core-macro/src/rsx/element.rs b/packages/core-macro/src/rsx/element.rs index f1ea3b242..10c2401d7 100644 --- a/packages/core-macro/src/rsx/element.rs +++ b/packages/core-macro/src/rsx/element.rs @@ -25,7 +25,7 @@ impl ToTokens for Element { // let name = &self.name.to_string(); tokens.append_all(quote! { - __cx.element(#name) + __cx.element(dioxus_elements::#name) }); // Add attributes diff --git a/packages/core-macro/src/rsx/mod.rs b/packages/core-macro/src/rsx/mod.rs index 44fce223b..2611d8a26 100644 --- a/packages/core-macro/src/rsx/mod.rs +++ b/packages/core-macro/src/rsx/mod.rs @@ -80,7 +80,7 @@ impl ToTokens for RsxRender { quote! {#inner} } else { let childs = &self.roots; - quote! { __cx.fragment_from_iter(&[ #(#childs),* ]) } + quote! { __cx.fragment_from_iter([ #(#childs),* ]) } }; match &self.custom_context { diff --git a/packages/core/examples/alternative.rs b/packages/core/examples/alternative.rs index 2d227dc6d..298686e5d 100644 --- a/packages/core/examples/alternative.rs +++ b/packages/core/examples/alternative.rs @@ -1,27 +1,29 @@ fn main() {} +use dioxus_core as dioxus; use dioxus_core::prelude::*; +mod dioxus_elements { + use super::*; + pub struct div; + impl DioxusElement for div { + const TAG_NAME: &'static str = "str"; + const NAME_SPACE: Option<&'static str> = None; + } +} static Example: FC<()> = |cx| { - cx.render(dioxus_core::prelude::LazyNodes::new(move |cx| { + let list = (0..10).map(|f| { + // + LazyNodes::new(move |f: NodeFactory| todo!()) + }); + cx.render(LazyNodes::new(move |cx| { let bump = cx.bump(); - dioxus_core::builder::ElementBuilder::new(cx, "h1") - .children([dioxus_core::builder::text3(bump, format_args!("hello"))]) - .finish() - })) -}; - -struct Props { - text: String, -} -static Example2: FC = |cx| { - cx.render(dioxus_core::prelude::LazyNodes::new(move |__cx| { - let bump = __cx.bump(); - dioxus_core::builder::ElementBuilder::new(__cx, "h1") - .children([dioxus_core::builder::text3( - bump, - format_args!("{}", cx.props.text), - )]) + dioxus_core::builder::ElementBuilder::new(&cx, "h1") + .children([ + cx.text(format_args!("hello")), + cx.text(format_args!("hello")), + cx.fragment_from_iter(list), + ]) .finish() })) }; diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index 27975eda0..26341516e 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -49,10 +49,23 @@ pub fn fc_to_builder(_: FC) -> T::Builder { /// /// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase. /// Try to avoid nesting fragments if you can. Infinitely nested Fragments *will* cause diffing to crash. -#[allow(non_upper_case_globals)] -pub const Fragment: FC<()> = |cx| { - use crate::prelude::*; - cx.render(LazyNodes::new(move |c| { - crate::nodebuilder::vfragment(c, None, cx.children()) +use crate::prelude::*; +pub fn Fragment<'a>(cx: Context<'a, ()>) -> VNode<'a> { + let childs: &'a [VNode<'a>] = cx.children(); + cx.render(LazyNodes::new({ + move |f| { + // + f.fragment_from_iter(childs) + } })) -}; +} +// #[allow(non_upper_case_globals)] +// pub const Fragment: FC<()> = |cx| { +// use crate::prelude::*; +// let childs = cx.children(); +// cx.render(LazyNodes::new(move |c| { +// c.fragment_from_iter(childs) +// // c.text(format_args!("")) +// // crate::nodebuilder::vfragment(c, None, cx.children()) +// })) +// }; diff --git a/packages/core/src/debug_renderer.rs b/packages/core/src/debug_renderer.rs index 9ae811afa..b565f68ba 100644 --- a/packages/core/src/debug_renderer.rs +++ b/packages/core/src/debug_renderer.rs @@ -49,18 +49,18 @@ impl DebugRenderer { // If you have a list or "nth" child, you do need to list those children, but you don't need to // fill in their children/attrs/etc // Does not handle children or lifecycles and will always fail the test if they show up in the rhs - pub fn compare<'a, F>(&self, other: LazyNodes<'a, F>) -> Result<()> + pub fn compare(&self, other: LazyNodes) -> Result<()> where - F: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a, + F: for<'b, 'c> FnOnce(&'b NodeFactory<'c>) -> VNode<'c>, { Ok(()) } // Do a full compare - everything must match // Ignores listeners and children components - pub fn compare_full<'a, F>(&self, other: LazyNodes<'a, F>) -> Result<()> + pub fn compare_full(&self, other: LazyNodes) -> Result<()> where - F: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a, + F: for<'b, 'c> FnOnce(&'b NodeFactory<'c>) -> VNode<'c>, { Ok(()) } @@ -69,9 +69,9 @@ impl DebugRenderer { Ok(()) } - pub fn render_nodes<'a, F>(&self, other: LazyNodes<'a, F>) -> Result<()> + pub fn render_nodes(&self, other: LazyNodes) -> Result<()> where - F: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a, + F: for<'b, 'c> FnOnce(&'b NodeFactory<'c>) -> VNode<'c>, { Ok(()) } diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index b219b822a..34a2fd4d9 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -124,14 +124,12 @@ pub mod on { macro_rules! event_directory { ( $( - $( - #[$attr:meta] - )* + $( #[$attr:meta] )* $eventdata:ident($wrapper:ident): [ $( - #[$method_attr:meta] + $( #[$method_attr:meta] )* + $name:ident )* - $( $name:ident )* ]; )* ) => { $( @@ -149,8 +147,8 @@ pub mod on { } } - $(#[$method_attr])* $( + $(#[$method_attr])* pub fn $name<'a>( c: &'_ NodeFactory<'a>, callback: impl Fn($wrapper) + 'a, @@ -181,35 +179,51 @@ pub mod on { // // event_directory! { - - - - ClipboardEventInner(ClipboardEvent): [ /// Called when "copy" oncopy + /// oncut oncut + /// onpaste onpaste ]; + CompositionEventInner(CompositionEvent): [ + /// oncompositionend oncompositionend + /// oncompositionstart oncompositionstart + /// oncompositionupdate oncompositionupdate ]; + KeyboardEventInner(KeyboardEvent): [ + /// onkeydown onkeydown + /// onkeypress onkeypress + /// onkeyup onkeyup ]; + FocusEventInner(FocusEvent): [ + /// onfocus onfocus + /// onblur onblur ]; + + FormEventInner(FormEvent): [ + /// onchange onchange + /// oninput oninput + /// oninvalid oninvalid + /// onreset onreset + /// onsubmit onsubmit ]; @@ -237,63 +251,202 @@ pub mod on { /// ``` /// /// ## Event Handlers - /// - click - /// - contextmenu - /// - doubleclick - /// - drag - /// - dragend - /// - dragenter - /// - dragexit - /// - dragleave - /// - dragover - /// - dragstart - /// - drop - /// - mousedown - /// - mouseenter - /// - mouseleave - /// - mousemove - /// - mouseout - /// - mouseover - /// - mouseup + /// - [`onclick`] + /// - [`oncontextmenu`] + /// - [`ondoubleclick`] + /// - [`ondrag`] + /// - [`ondragend`] + /// - [`ondragenter`] + /// - [`ondragexit`] + /// - [`ondragleave`] + /// - [`ondragover`] + /// - [`ondragstart`] + /// - [`ondrop`] + /// - [`onmousedown`] + /// - [`onmouseenter`] + /// - [`onmouseleave`] + /// - [`onmousemove`] + /// - [`onmouseout`] + /// - [`onmouseover`] + /// - [`onmouseup`] MouseEventInner(MouseEvent): [ - /// Onclick! + /// Execute a callback when a button is clicked. + /// + /// ## Description + /// + /// An element receives a click event when a pointing device button (such as a mouse's primary mouse button) + /// is both pressed and released while the pointer is located inside the element. + /// + /// - Bubbles: Yes + /// - Cancelable: Yes + /// - Interface: [`MouseEvent`] + /// + /// If the button is pressed on one element and the pointer is moved outside the element before the button + /// is released, the event is fired on the most specific ancestor element that contained both elements. + /// `click` fires after both the `mousedown` and `mouseup` events have fired, in that order. + /// + /// ## Example + /// ``` + /// rsx!( button { "click me", onclick: move |_| log::info!("Clicked!`") } ) + /// ``` + /// + /// ## Reference + /// - https://www.w3schools.com/tags/ev_onclick.asp + /// - https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event + /// onclick + /// oncontextmenu oncontextmenu + /// ondoubleclick ondoubleclick + /// ondrag ondrag + /// ondragend ondragend + /// ondragenter ondragenter + /// ondragexit ondragexit + /// ondragleave ondragleave + /// ondragover ondragover + /// ondragstart ondragstart + /// ondrop ondrop + /// onmousedown onmousedown + /// onmouseenter onmouseenter + /// onmouseleave onmouseleave + /// onmousemove onmousemove + /// onmouseout onmouseout + /// onmouseover onmouseover + /// onmouseup onmouseup ]; PointerEventInner(PointerEvent): [ - pointerdown pointermove pointerup pointercancel gotpointercapture - lostpointercapture pointerenter pointerleave pointerover pointerout + /// pointerdown + onpointerdown + /// pointermove + onpointermove + /// pointerup + onpointerup + /// pointercancel + onpointercancel + /// gotpointercapture + ongotpointercapture + /// lostpointercapture + onlostpointercapture + /// pointerenter + onpointerenter + /// pointerleave + onpointerleave + /// pointerover + onpointerover + /// pointerout + onpointerout ]; - SelectionEventInner(SelectionEvent): [select]; - TouchEventInner(TouchEvent): [touchcancel touchend touchmove touchstart]; - UIEventInner(UIEvent): [scroll]; - WheelEventInner(WheelEvent): [wheel]; + + SelectionEventInner(SelectionEvent): [ + /// onselect + onselect + ]; + + TouchEventInner(TouchEvent): [ + /// ontouchcancel + ontouchcancel + /// ontouchend + ontouchend + /// ontouchmove + ontouchmove + /// ontouchstart + ontouchstart + ]; + + UIEventInner(UIEvent): [ + /// + scroll + ]; + + WheelEventInner(WheelEvent): [ + /// + wheel + ]; + MediaEventInner(MediaEvent): [ - abort canplay canplaythrough durationchange emptied encrypted - ended error loadeddata loadedmetadata loadstart pause play - playing progress ratechange seeked seeking stalled suspend - timeupdate volumechange waiting + ///abort + onabort + ///canplay + oncanplay + ///canplaythrough + oncanplaythrough + ///durationchange + ondurationchange + ///emptied + onemptied + ///encrypted + onencrypted + ///ended + onended + ///error + onerror + ///loadeddata + onloadeddata + ///loadedmetadata + onloadedmetadata + ///loadstart + onloadstart + ///pause + onpause + ///play + onplay + ///playing + onplaying + ///progress + onprogress + ///ratechange + onratechange + ///seeked + onseeked + ///seeking + onseeking + ///stalled + onstalled + ///suspend + onsuspend + ///timeupdate + ontimeupdate + ///volumechange + onvolumechange + ///waiting + onwaiting + ]; + + AnimationEventInner(AnimationEvent): [ + /// onanimationstart + onanimationstart + /// onanimationend + onanimationend + /// onanimationiteration + onanimationiteration + ]; + + TransitionEventInner(TransitionEvent): [ + /// + ontransitionend + ]; + + ToggleEventInner(ToggleEvent): [ + /// + ontoggle ]; - AnimationEventInner(AnimationEvent): [animationstart animationend animationiteration]; - TransitionEventInner(TransitionEvent): [transitionend]; - ToggleEventInner(ToggleEvent): [toggle]; } pub trait GenericEventInner { @@ -404,7 +557,6 @@ pub mod on { fn alt_key(&self) -> bool; fn button(&self) -> i16; fn buttons(&self) -> u16; - /// Get the X coordinate of the mouse relative to the window fn client_x(&self) -> i32; fn client_y(&self) -> i32; @@ -709,26 +861,3 @@ pub mod on { } } } - -// mod tests { - -// use std::rc::Rc; - -// use crate as dioxus; -// use crate::events::on::MouseEvent; -// use crate::prelude::*; - -// fn autocomplete() { -// // let v = move |evt| { -// // let r = evt.alt_key(); -// // }; - -// let g = rsx! { -// button { -// onclick: move |evt| { -// let r = evt.alt_key(); -// } -// } -// }; -// } -// } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 150ba68e5..933117881 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -16,7 +16,7 @@ pub mod component; pub mod styles; pub mod util; // Logic for extending FC -pub mod debug_renderer; +// pub mod debug_renderer; pub mod diff; pub mod error; // Error type we expose to the renderers @@ -62,7 +62,6 @@ pub mod prelude { pub use crate::nodebuilder::LazyNodes; - pub use crate::nodebuilder::ChildrenList; pub use crate::nodebuilder::{DioxusElement, NodeFactory}; // pub use nodes::iterables::IterableNodes; /// This type alias is an internal way of abstracting over the static functions that represent components. diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index bc3f8b140..cef325d7d 100644 --- a/packages/core/src/nodebuilder.rs +++ b/packages/core/src/nodebuilder.rs @@ -6,6 +6,7 @@ use std::{ cell::{Cell, RefCell}, fmt::Arguments, intrinsics::transmute, + marker::PhantomData, u128, }; @@ -511,8 +512,9 @@ where pub fn iter_child(mut self, nodes: impl IntoIterator>) -> Self { let len_before = self.children.len(); for item in nodes { - let child = item.into_vnode(&self.cx); - self.children.push(child); + todo!() + // let child = item.into_vnode(&self.cx); + // self.children.push(child); } if cfg!(debug_assertions) { if self.children.len() > len_before + 1 { @@ -541,52 +543,53 @@ impl<'a> IntoIterator for VNode<'a> { } } impl<'a> IntoVNode<'a> for VNode<'a> { - fn into_vnode(self, cx: &NodeFactory<'a>) -> VNode<'a> { + fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { self } } impl<'a> IntoVNode<'a> for &VNode<'a> { - fn into_vnode(self, cx: &NodeFactory<'a>) -> VNode<'a> { + fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> +// where + // 'a: 'c, + { + todo!() // cloning is cheap since vnodes are just references into bump arenas - self.clone() + // self.clone() } } pub trait IntoVNode<'a> { - fn into_vnode(self, cx: &NodeFactory<'a>) -> VNode<'a>; + fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>; } -pub trait VNodeBuilder<'a, G>: IntoIterator -where - G: IntoVNode<'a>, -{ -} +// pub trait VNodeBuilder<'a, G>: IntoIterator +// where +// G: IntoVNode<'a>, +// { +// } -impl<'a, F> VNodeBuilder<'a, LazyNodes<'a, F>> for LazyNodes<'a, F> where - F: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a -{ -} +// impl<'a, F> VNodeBuilder<'a, LazyNodes> for LazyNodes where F: FnOnce(NodeFactory) -> VNode {} // Wrap the the node-builder closure in a concrete type. // --- // This is a bit of a hack to implement the IntoVNode trait for closure types. pub struct LazyNodes<'a, G> where - G: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a, + G: FnOnce(NodeFactory<'a>) -> VNode<'a>, { inner: G, - _p: std::marker::PhantomData<&'a ()>, + _p: PhantomData<&'a ()>, } impl<'a, G> LazyNodes<'a, G> where - G: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a, + G: FnOnce(NodeFactory<'a>) -> VNode<'a>, { pub fn new(f: G) -> Self { Self { inner: f, - _p: std::default::Default::default(), + _p: PhantomData {}, } } } @@ -598,9 +601,9 @@ where // rsx! { {nodes } } impl<'a, G> IntoVNode<'a> for LazyNodes<'a, G> where - G: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a, + G: FnOnce(NodeFactory<'a>) -> VNode<'a>, { - fn into_vnode(self, cx: &NodeFactory<'a>) -> VNode<'a> { + fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { (self.inner)(cx) } } @@ -608,7 +611,7 @@ where // Required because anything that enters brackets in the rsx! macro needs to implement IntoIterator impl<'a, G> IntoIterator for LazyNodes<'a, G> where - G: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a, + G: FnOnce(NodeFactory<'a>) -> VNode<'a>, { type Item = Self; type IntoIter = std::iter::Once; @@ -617,23 +620,23 @@ where } } -impl<'a> IntoVNode<'a> for () { - fn into_vnode(self, cx: &NodeFactory<'a>) -> VNode<'a> { - todo!(); - VNode::Suspended { - real: Cell::new(RealDomNode::empty()), - } - } -} +// impl IntoVNode<'_> for () { +// fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> { +// todo!(); +// VNode::Suspended { +// real: Cell::new(RealDomNode::empty()), +// } +// } +// } -impl<'a> IntoVNode<'a> for Option<()> { - fn into_vnode(self, cx: &NodeFactory<'a>) -> VNode<'a> { - todo!(); - VNode::Suspended { - real: Cell::new(RealDomNode::empty()), - } - } -} +// impl IntoVNode<'_> for Option<()> { +// fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> { +// todo!(); +// VNode::Suspended { +// real: Cell::new(RealDomNode::empty()), +// } +// } +// } /// Construct a text VNode. /// @@ -702,7 +705,7 @@ pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> { } pub fn virtual_child<'a, T: Properties + 'a>( - cx: &NodeFactory<'a>, + cx: NodeFactory<'a>, f: FC, props: T, key: Option<&'a str>, // key: NodeKey<'a>, @@ -713,54 +716,28 @@ pub fn virtual_child<'a, T: Properties + 'a>( // todo!() VNode::Component( cx.bump() - .alloc(crate::nodes::VComponent::new(cx, f, props, key, children)), + .alloc(crate::nodes::VComponent::new(&cx, f, props, key, children)), // cx.bump() // .alloc(crate::nodes::VComponent::new(f, props, key)), ) } pub fn vfragment<'a>( - cx: &NodeFactory<'a>, + cx: NodeFactory<'a>, key: Option<&'a str>, // key: NodeKey<'a>, children: &'a [VNode<'a>], ) -> VNode<'a> { VNode::Fragment(cx.bump().alloc(VFragment::new(key, children))) } -pub struct ChildrenList<'a, 'b> { - cx: &'b NodeFactory<'a>, - children: bumpalo::collections::Vec<'a, VNode<'a>>, -} - -impl<'a, 'b> ChildrenList<'a, 'b> { - pub fn new(cx: &'b NodeFactory<'a>) -> Self { - Self { - cx, - children: bumpalo::collections::Vec::new_in(cx.bump()), - } - } - - pub fn add_child(mut self, nodes: impl IntoIterator>) -> Self { - for item in nodes { - let child = item.into_vnode(&self.cx); - self.children.push(child); - } - self - } - - pub fn finish(self) -> &'a [VNode<'a>] { - self.children.into_bump_slice() - } -} - /// This struct provides an ergonomic API to quickly build VNodes. /// /// NodeFactory is used to build VNodes in the component's memory space. /// This struct adds metadata to the final VNode about listeners, attributes, and children -#[derive(Clone)] +#[derive(Copy, Clone)] pub struct NodeFactory<'a> { pub scope_ref: &'a Scope, - pub listener_id: Cell, + pub listener_id: &'a Cell, } impl<'a> NodeFactory<'a> { @@ -791,7 +768,7 @@ impl<'a> NodeFactory<'a> { pub fn attr( &self, name: &'static str, - val: Arguments<'a>, + val: Arguments, namespace: Option<&'static str>, ) -> Attribute<'a> { let value = raw_text(self.bump(), val); @@ -802,18 +779,6 @@ impl<'a> NodeFactory<'a> { } } - pub fn child_list(&self) -> ChildrenList { - ChildrenList::new(&self) - } - - pub fn fragment_builder<'b>( - &'b self, - key: Option<&'a str>, - builder: impl FnOnce(ChildrenList<'a, 'b>) -> &'a [VNode<'a>], - ) -> VNode<'a> { - self.fragment(builder(ChildrenList::new(&self)), key) - } - pub fn fragment(&self, children: &'a [VNode<'a>], key: Option<&'a str>) -> VNode<'a> { VNode::Fragment(self.bump().alloc(VFragment { children, @@ -822,12 +787,12 @@ impl<'a> NodeFactory<'a> { } pub fn fragment_from_iter( - &self, + self, node_iter: impl IntoIterator>, ) -> VNode<'a> { let mut nodes = bumpalo::collections::Vec::new_in(self.bump()); for node in node_iter.into_iter() { - nodes.push(node.into_vnode(&self)); + nodes.push(node.into_vnode(self)); } VNode::Fragment( self.bump() diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 8cc846323..15093097d 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -393,6 +393,8 @@ pub struct Scope { // could also use ourborous hooks: RefCell>, + pub(crate) listener_idx: Cell, + // Unsafety: // - is self-refenrential and therefore needs to point into the bump // Stores references into the listeners attached to the vnodes @@ -463,6 +465,7 @@ impl Scope { height, event_channel, arena_link, + listener_idx: Default::default(), frames: ActiveFrame::new(), hooks: Default::default(), shared_contexts: Default::default(), @@ -506,13 +509,9 @@ impl Scope { // Remove all the outdated listeners self.listeners.borrow_mut().clear(); - // self.listeners - // .try_borrow_mut() - // .ok() - // .ok_or(Error::FatalInternal("Borrowing listener failed"))? - // .drain(..); self.hookidx.set(0); + self.listener_idx.set(0); let caller = self .caller @@ -522,12 +521,8 @@ impl Scope { // Cast the caller ptr from static to one with our own reference let c2: &OpaqueComponent = caller.as_ref(); let c3: &OpaqueComponent = unsafe { std::mem::transmute(c2) }; - // let c2: &OpaqueComponent<'static> = caller.as_ref(); - // let c3: &OpaqueComponent<'sel> = unsafe { std::mem::transmute(c2) }; - let unsafe_head = unsafe { self.own_vnodes(c3) }; - - self.frames.cur_frame_mut().head_node = unsafe_head; + self.frames.cur_frame_mut().head_node = unsafe { self.own_vnodes(c3) }; Ok(()) } @@ -685,13 +680,15 @@ pub trait Scoped<'src>: Sized { /// cx.render(lazy_tree) /// } ///``` - fn render<'a, F: for<'b> FnOnce(&'b NodeFactory<'src>) -> VNode<'src> + 'src + 'a>( + fn render) -> VNode<'src>>( self, lazy_nodes: LazyNodes<'src, F>, ) -> VNode<'src> { - lazy_nodes.into_vnode(&NodeFactory { - scope_ref: self.get_scope(), - listener_id: 0.into(), + let scope_ref = self.get_scope(); + let listener_id = &scope_ref.listener_idx; + lazy_nodes.into_vnode(NodeFactory { + scope_ref, + listener_id, }) } @@ -890,14 +887,14 @@ Any function prefixed with "use" should not be called conditionally. #[derive(Clone)] pub struct SuspendedContext {} -impl SuspendedContext { - pub fn render<'a, 'src, F: for<'b> FnOnce(&'b NodeFactory<'src>) -> VNode<'src> + 'src + 'a>( - self, - lazy_nodes: LazyNodes<'src, F>, - ) -> VNode<'src> { - todo!() - } -} +// impl SuspendedContext { +// pub fn render<'a, F: for<'b, 'src> FnOnce(&'b NodeFactory<'src>) -> VNode<'src>>( +// &self, +// lazy_nodes: LazyNodes, +// ) -> VNode { +// todo!() +// } +// } // ================================================================================== // Supporting structs for the above abstractions diff --git a/packages/html-namespace/src/lib.rs b/packages/html-namespace/src/lib.rs index beeb4c8b6..ee86e679a 100644 --- a/packages/html-namespace/src/lib.rs +++ b/packages/html-namespace/src/lib.rs @@ -26,7 +26,18 @@ macro_rules! builder_constructors { ( $( $(#[$attr:meta])* $name:ident <> $namespace:tt; - )* ) => {}; + )* ) => { + $( + #[allow(non_camel_case_types)] + $(#[$attr])* + pub struct $name; + + impl DioxusElement for $name { + const TAG_NAME: &'static str = stringify!($name); + const NAME_SPACE: Option<&'static str> = Some(stringify!($namespace)); + } + )* + }; } // Organized in the same order as @@ -554,6 +565,7 @@ builder_constructors! { /// [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot) /// element. slot; + /// Build a /// [`