diff --git a/packages/sycamore-reactive/src/effect.rs b/packages/sycamore-reactive/src/effect.rs index 4fa41fde0..9b32e975c 100644 --- a/packages/sycamore-reactive/src/effect.rs +++ b/packages/sycamore-reactive/src/effect.rs @@ -155,7 +155,7 @@ impl Drop for ReactiveScope { /// However, there can be multiple weak references to the same [`ReactiveScope`]. As such, it is /// impossible to obtain a [`ReactiveScope`] from a [`ReactiveScopeWeak`] because that would allow /// creating multiple [`ReactiveScope`]s. -#[derive(Default)] +#[derive(Default, Clone)] pub struct ReactiveScopeWeak(pub(crate) Weak>); impl ReactiveScopeWeak { diff --git a/packages/sycamore-reactive/src/iter.rs b/packages/sycamore-reactive/src/iter.rs index 7a3b3e88a..7d7dc976a 100644 --- a/packages/sycamore-reactive/src/iter.rs +++ b/packages/sycamore-reactive/src/iter.rs @@ -32,6 +32,8 @@ where K: Eq + Hash, U: Clone + 'static, { + let parent_scope = current_scope(); + // Previous state used for diffing. let mut items = Rc::new(Vec::new()); let mapped = Rc::new(RefCell::new(Vec::new())); @@ -47,7 +49,7 @@ where } else if items.is_empty() { // Fast path for new create. for new_item in new_items.iter() { - let new_scope = create_root(|| { + let new_scope = create_child_scope_in(parent_scope.as_ref(), || { mapped.borrow_mut().push(map_fn(new_item)); }); scopes.push(Some(Rc::new(new_scope))); @@ -141,7 +143,7 @@ where } else { // Create new value. let mut new_mapped = None; - let new_scope = create_root(|| { + let new_scope = create_child_scope_in(parent_scope.as_ref(), || { new_mapped = Some(map_fn(&new_items[j])); }); @@ -191,6 +193,8 @@ where T: PartialEq + Clone, U: Clone + 'static, { + let parent_scope = current_scope(); + // Previous state used for diffing. let mut items = Rc::new(Vec::new()); let mapped = Rc::new(RefCell::new(Vec::new())); @@ -216,12 +220,12 @@ where let item = items.get(i); if item.is_none() { - let new_scope = create_root(|| { + let new_scope = create_child_scope_in(parent_scope.as_ref(), || { mapped.borrow_mut().push(map_fn(new_item)); }); scopes.push(new_scope); } else if item != Some(new_item) { - let new_scope = create_root(|| { + let new_scope = create_child_scope_in(parent_scope.as_ref(),|| { mapped.borrow_mut()[i] = map_fn(new_item); }); scopes[i] = new_scope; diff --git a/packages/sycamore-reactive/src/lib.rs b/packages/sycamore-reactive/src/lib.rs index 9b0c72c94..c5bfcc97f 100644 --- a/packages/sycamore-reactive/src/lib.rs +++ b/packages/sycamore-reactive/src/lib.rs @@ -15,6 +15,44 @@ pub use signal::*; use wasm_bindgen::prelude::*; +/// Creates a new reactive root / scope. Generally, you won't need this method as it is called +/// automatically in `render`. +/// +/// # Example +/// ``` +/// use sycamore_reactive::*; +/// +/// let trigger = Signal::new(()); +/// let counter = Signal::new(0); +/// +/// let scope = create_scope(cloned!((trigger, counter) => move || { +/// create_effect(move || { +/// trigger.get(); // subscribe to trigger +/// counter.set(*counter.get_untracked() + 1); +/// }); +/// })); +/// +/// assert_eq!(*counter.get(), 1); +/// +/// trigger.set(()); +/// assert_eq!(*counter.get(), 2); +/// +/// drop(scope); +/// trigger.set(()); +/// assert_eq!(*counter.get(), 2); // should not be updated because scope was dropped +/// ``` +#[must_use = "create_scope returns the reactive scope of the effects created inside this scope"] +pub fn create_scope<'a>(callback: impl FnOnce() + 'a) -> ReactiveScope { + _create_child_scope_in(None, Box::new(callback)) +} + +pub fn create_child_scope_in<'a>( + parent: Option<&ReactiveScopeWeak>, + callback: impl FnOnce() + 'a, +) -> ReactiveScope { + _create_child_scope_in(parent, Box::new(callback)) +} + /// Creates a new reactive root / scope. Generally, you won't need this method as it is called /// automatically in `render`. /// @@ -41,18 +79,26 @@ use wasm_bindgen::prelude::*; /// trigger.set(()); /// assert_eq!(*counter.get(), 2); // should not be updated because scope was dropped /// ``` +/// TODO: deprecate this method in favor of [`create_scope`]. #[must_use = "create_root returns the reactive scope of the effects created inside this scope"] pub fn create_root<'a>(callback: impl FnOnce() + 'a) -> ReactiveScope { - _create_root(Box::new(callback)) + _create_child_scope_in(None, Box::new(callback)) } /// Internal implementation: use dynamic dispatch to reduce code bloat. -fn _create_root<'a>(callback: Box) -> ReactiveScope { +fn _create_child_scope_in<'a>( + parent: Option<&ReactiveScopeWeak>, + callback: Box, +) -> ReactiveScope { SCOPES.with(|scopes| { // Push new empty scope on the stack. let scope = ReactiveScope::new(); - if let Some(parent) = scopes.borrow().last() { + // If `parent` was specified, use it as the parent of the new scope. Else use the parent of + // the scope this function is called in. + if let Some(parent) = parent { + scope.0.borrow_mut().parent = parent.clone(); + } else if let Some(parent) = scopes.borrow().last() { scope.0.borrow_mut().parent = parent.downgrade(); } scopes.borrow_mut().push(scope); diff --git a/packages/sycamore/src/builder/agnostic/mod.rs b/packages/sycamore/src/builder/agnostic/mod.rs index 6f1b542fe..0190d6fd9 100644 --- a/packages/sycamore/src/builder/agnostic/mod.rs +++ b/packages/sycamore/src/builder/agnostic/mod.rs @@ -21,9 +21,14 @@ pub mod prelude { /// Create [`NodeBuilder`] to create UI elements. /// /// # Example -/// ```no_run -/// node("div").build(); -/// node("a").build(); +/// ``` +/// # use sycamore::prelude::*; +/// # fn _test() -> Template { +/// node("div").build() +/// # } +/// # fn _test2() -> Template { +/// node("a").build() +/// # } /// ``` pub fn node(tag: &'static str) -> NodeBuilder where @@ -39,14 +44,15 @@ where /// # Example /// ``` /// use sycamore::prelude::*; -/// +/// # use sycamore::builder::html::*; /// #[component(MyComponent)] /// fn my_component() -> Template { /// h1().text("I am a component").build() /// } /// +/// // Elsewhere in another component. /// # fn view() -> Template { -/// component::<_, MyComponent<_>>(()) +/// component::<_, MyComponent<_>>(()) /// # } /// ``` pub fn component(props: C::Props) -> Template @@ -62,7 +68,7 @@ where /// # Example /// ``` /// # use sycamore::prelude::*; -/// +/// # use sycamore::builder::html::*; /// # fn _test() -> Template { /// fragment([ /// div().build(), @@ -78,7 +84,7 @@ where } /// The main type powering the builder API. -#[cfg_attr(debug_assertions, derive(Debug))] +#[derive(Debug)] pub struct NodeBuilder where G: GenericNode, @@ -90,12 +96,12 @@ impl NodeBuilder where G: GenericNode, { - /// Add a child [`Template`] + /// Add a child [`Template`]. /// /// # Example /// ``` /// # use sycamore::prelude::*; - /// + /// # use sycamore::builder::html::*; /// # fn _test() -> Template { /// div() /// .child(h1().text("I am a child").build()) @@ -113,11 +119,11 @@ where /// # Example /// ``` /// # use sycamore::prelude::*; - /// + /// # use sycamore::builder::html::*; /// # fn _test() -> Template { /// let visible = Signal::new(true); /// - /// div() + /// div() /// .dyn_child( /// move || { /// if *visible.get() { @@ -139,7 +145,7 @@ where /// # Example /// ``` /// # use sycamore::prelude::*; - /// + /// # use sycamore::builder::html::*; /// # fn _test() -> Template { /// h1().text("I am text").build() /// } @@ -155,7 +161,7 @@ where /// # Example /// ``` /// # use sycamore::prelude::*; - /// + /// # use sycamore::builder::html::*; /// # fn _test() -> Template { /// let required = Signal::new(false); /// @@ -185,7 +191,7 @@ where /// # Example /// ``` /// use sycamore::prelude::*; - /// + /// # use sycamore::builder::html::*; /// #[component(MyComponent)] /// fn my_component() -> Template { /// h1().text("My component").build() @@ -204,12 +210,12 @@ where self } - /// Convinience function for adding an `id` to a node. + /// Convenience function for adding an `id` to a node. /// /// # Example /// ``` /// # use sycamore::prelude::*; - /// + /// # use sycamore::builder::html::*; /// # fn _test() -> Template { /// button().id("my-button").build() /// # } @@ -223,7 +229,7 @@ where /// # Example /// ``` /// # use sycamore::prelude::*; - /// + /// # use sycamore::builder::html::*; /// # fn _test() -> Template { /// button().attr("type", "submit").build() /// # } @@ -243,7 +249,7 @@ where /// # Example /// ``` /// # use sycamore::prelude::*; - /// + /// # use sycamore::builder::html::*; /// # fn _test() -> Template { /// input().bool_attr("required", true).build() /// # } @@ -269,7 +275,7 @@ where /// # Example /// ``` /// # use sycamore::prelude::*; - /// + /// # use sycamore::builder::html::*; /// # fn _test() -> Template { /// let input_type = Signal::new(Some("text")); /// @@ -305,7 +311,7 @@ where /// # Example /// ``` /// # use sycamore::prelude::*; - /// + /// # use sycamore::builder::html::*; /// # fn _test() -> Template { /// let required = Signal::new(true); /// @@ -339,7 +345,7 @@ where /// # Example /// ``` /// # use sycamore::prelude::*; - /// + /// # use sycamore::builder::html::*; /// # fn _test() -> Template { /// input().prop("value", "I am the value set.").build() /// # } @@ -362,7 +368,7 @@ where /// # Example /// ``` /// # use sycamore::prelude::*; - /// + /// # use sycamore::builder::html::*; /// # fn _test() -> Template { /// let checked = Signal::new(Some(false)); /// @@ -399,7 +405,7 @@ where /// # Example /// ``` /// # use sycamore::prelude::*; - /// + /// # use sycamore::builder::html::*; /// # fn _test() -> Template { /// button().class("bg-green-500").text("My Button").build() /// # } @@ -418,7 +424,7 @@ where /// # Example /// ``` /// # use sycamore::prelude::*; - /// + /// # use sycamore::builder::html::*; /// # fn _test() -> Template { /// let checked_class = Signal::new(false); /// @@ -459,23 +465,12 @@ where self } - // Need the ability to get attributes so one can filter out the - // applied attribute to add/remove it. - #[allow(dead_code)] - #[doc(hidden)] - fn dyn_style( - &self, - _style: (impl ToString, impl ToString), - _apply: StateHandle, - ) -> &Self { - todo!("Implement set_dyn_style"); - } - /// Adds an event listener to the node. /// /// # Example /// ``` /// # use sycamore::prelude::*; + /// # use sycamore::builder::html::*; /// # fn _test() -> Template { /// button() /// .text("My Button") @@ -503,7 +498,7 @@ where /// # Example /// ``` /// # use sycamore::prelude::*; - /// + /// # use sycamore::builder::html::*; /// # fn _test() -> Template { /// let value = Signal::new(String::new()); /// @@ -541,7 +536,7 @@ where /// # Example /// ``` /// # use sycamore::prelude::*; - /// + /// # use sycamore::builder::html::*; /// # fn _test() -> Template { /// let checked = Signal::new(false); /// @@ -581,7 +576,7 @@ where /// # Example /// ``` /// # use sycamore::prelude::*; - /// + /// # use sycamore::builder::html::*; /// # fn _test() -> Template { /// let node_ref = NodeRef::new(); /// @@ -604,7 +599,7 @@ where /// # Example /// ``` /// # use sycamore::prelude::*; - /// + /// # use sycamore::builder::html::*; /// # fn _test() -> Template { /// input() /// /* builder stuff */ diff --git a/website/sitemap_index.xml b/website/sitemap_index.xml index 5068cec67..922eca2e7 100644 --- a/website/sitemap_index.xml +++ b/website/sitemap_index.xml @@ -7,14 +7,14 @@ https://sycamore-rs.netlify.app/news/announcing-v0.6.0yearly0.8 https://sycamore-rs.netlify.app/docs/advanced/advanced_reactivityweekly0.5 https://sycamore-rs.netlify.app/docs/advanced/contextsweekly0.5 +https://sycamore-rs.netlify.app/docs/advanced/cssweekly0.5 https://sycamore-rs.netlify.app/docs/advanced/js_interopweekly0.5 https://sycamore-rs.netlify.app/docs/advanced/noderefweekly0.5 https://sycamore-rs.netlify.app/docs/advanced/optimize_wasm_sizeweekly0.5 +https://sycamore-rs.netlify.app/docs/advanced/routingweekly0.5 https://sycamore-rs.netlify.app/docs/advanced/ssrweekly0.5 https://sycamore-rs.netlify.app/docs/advanced/testingweekly0.5 https://sycamore-rs.netlify.app/docs/advanced/tweenedweekly0.5 -https://sycamore-rs.netlify.app/docs/advanced/cssweekly0.5 -https://sycamore-rs.netlify.app/docs/advanced/routingweekly0.5 https://sycamore-rs.netlify.app/docs/basics/componentsweekly0.5 https://sycamore-rs.netlify.app/docs/basics/control_flowweekly0.5 https://sycamore-rs.netlify.app/docs/basics/data_bindingweekly0.5 @@ -54,10 +54,10 @@ https://sycamore-rs.netlify.app/docs/v0.6/advanced/js_interopyearly0.3 https://sycamore-rs.netlify.app/docs/v0.6/advanced/noderefyearly0.3 https://sycamore-rs.netlify.app/docs/v0.6/advanced/optimize_wasm_sizeyearly0.3 +https://sycamore-rs.netlify.app/docs/v0.6/advanced/routingyearly0.3 https://sycamore-rs.netlify.app/docs/v0.6/advanced/ssryearly0.3 https://sycamore-rs.netlify.app/docs/v0.6/advanced/testingyearly0.3 https://sycamore-rs.netlify.app/docs/v0.6/advanced/tweenedyearly0.3 -https://sycamore-rs.netlify.app/docs/v0.6/advanced/routingyearly0.3 https://sycamore-rs.netlify.app/docs/v0.6/basics/componentsyearly0.3 https://sycamore-rs.netlify.app/docs/v0.6/basics/control_flowyearly0.3 https://sycamore-rs.netlify.app/docs/v0.6/basics/data_bindingyearly0.3 @@ -74,10 +74,10 @@ https://sycamore-rs.netlify.app/examples/contextmonthly0.5 https://sycamore-rs.netlify.app/examples/countermonthly0.5 https://sycamore-rs.netlify.app/examples/hellomonthly0.5 +https://sycamore-rs.netlify.app/examples/hello-buildermonthly0.5 https://sycamore-rs.netlify.app/examples/higher-order-componentsmonthly0.5 https://sycamore-rs.netlify.app/examples/iterationmonthly0.5 https://sycamore-rs.netlify.app/examples/ssrmonthly0.5 https://sycamore-rs.netlify.app/examples/todomvcmonthly0.5 https://sycamore-rs.netlify.app/examples/tweenedmonthly0.5 -https://sycamore-rs.netlify.app/examples/hello-buildermonthly0.5