mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 16:02:33 -05:00
Compare commits
23 Commits
3013
...
two-way-da
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3782a8ad70 | ||
|
|
cb11ce0a5f | ||
|
|
37c794b283 | ||
|
|
f5ff45863d | ||
|
|
ca6f934039 | ||
|
|
699c54e16c | ||
|
|
1217ef4d8e | ||
|
|
9aba3efbe4 | ||
|
|
1b48a2f8d5 | ||
|
|
31cb766206 | ||
|
|
de28a317f0 | ||
|
|
d29433b98d | ||
|
|
f2582b6ac9 | ||
|
|
67845be161 | ||
|
|
2bf1f46b88 | ||
|
|
70ae3a0abb | ||
|
|
96e2b5cba1 | ||
|
|
ef27a198d9 | ||
|
|
47299926bb | ||
|
|
6ea942f3c5 | ||
|
|
3a079ace46 | ||
|
|
dbf654aa86 | ||
|
|
d16231aa3a |
42
Cargo.toml
42
Cargo.toml
@@ -40,36 +40,36 @@ members = [
|
||||
exclude = ["benchmarks", "examples", "projects"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.7.0-beta6"
|
||||
version = "0.7.0-gamma"
|
||||
edition = "2021"
|
||||
rust-version = "1.76"
|
||||
|
||||
[workspace.dependencies]
|
||||
throw_error = { path = "./any_error/", version = "0.2.0-beta6" }
|
||||
throw_error = { path = "./any_error/", version = "0.2.0-gamma" }
|
||||
any_spawner = { path = "./any_spawner/", version = "0.1.0" }
|
||||
const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1.0" }
|
||||
either_of = { path = "./either_of/", version = "0.1.0" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.2.0-beta6" }
|
||||
leptos = { path = "./leptos", version = "0.7.0-beta6" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.0-beta6" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.0-beta6" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.0-beta6" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.0-beta6" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.0-beta6" }
|
||||
leptos_router = { path = "./router", version = "0.7.0-beta6" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.0-beta6" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.0-beta6" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.0-beta6" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0-beta6" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.2.0-gamma" }
|
||||
leptos = { path = "./leptos", version = "0.7.0-gamma" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.0-gamma" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.0-gamma" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.0-gamma" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.0-gamma" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.0-gamma" }
|
||||
leptos_router = { path = "./router", version = "0.7.0-gamma" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.0-gamma" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.0-gamma" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.0-gamma" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0-gamma" }
|
||||
oco_ref = { path = "./oco", version = "0.2.0" }
|
||||
or_poisoned = { path = "./or_poisoned", version = "0.1.0" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.1.0-beta6" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.1.0-beta6" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.0-beta6" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.0-beta6" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.0-beta6" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.0-beta6" }
|
||||
tachys = { path = "./tachys", version = "0.1.0-beta6" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.1.0-gamma" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.1.0-gamma" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.0-gamma" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.0-gamma" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.0-gamma" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.0-gamma" }
|
||||
tachys = { path = "./tachys", version = "0.1.0-gamma" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "throw_error"
|
||||
version = "0.2.0-beta6"
|
||||
version = "0.2.0-gamma"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -319,10 +319,7 @@ pub fn Todo(todo: Todo) -> impl IntoView {
|
||||
node_ref=todo_input
|
||||
class="toggle"
|
||||
type="checkbox"
|
||||
prop:checked=move || todo.completed.get()
|
||||
on:input:target=move |ev| {
|
||||
todo.completed.set(ev.target().checked());
|
||||
}
|
||||
bind:checked=todo.completed
|
||||
/>
|
||||
|
||||
<label on:dblclick=move |_| {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "hydration_context"
|
||||
version = "0.2.0-beta6"
|
||||
version = "0.2.0-gamma"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
if (islandFn) {
|
||||
islandFn(el);
|
||||
} else {
|
||||
console.warn(`Could not find WASM function for the island ${l}.`);
|
||||
console.warn(`Could not find WASM function for the island ${id}.`);
|
||||
}
|
||||
}
|
||||
function hydrateIslands(entry, mod) {
|
||||
|
||||
@@ -174,7 +174,7 @@ pub mod prelude {
|
||||
pub use server_fn::{self, ServerFnError};
|
||||
pub use tachys::{
|
||||
self,
|
||||
reactive_graph::{node_ref::*, Suspend},
|
||||
reactive_graph::{bind::BindAttribute, node_ref::*, Suspend},
|
||||
view::template::ViewTemplate,
|
||||
};
|
||||
}
|
||||
@@ -293,6 +293,10 @@ pub mod spawn {
|
||||
pub async fn tick() {
|
||||
Executor::tick().await
|
||||
}
|
||||
|
||||
pub use reactive_graph::{
|
||||
spawn_local_scoped, spawn_local_scoped_with_cancellation,
|
||||
};
|
||||
}
|
||||
|
||||
// these reexports are used in islands
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_macro"
|
||||
version = "0.7.0-beta6"
|
||||
version = "0.7.0-gamma"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::{fragment_to_tokens, TagType};
|
||||
use crate::view::attribute_absolute;
|
||||
use crate::view::{attribute_absolute, utils::filter_prefixed_attrs};
|
||||
use proc_macro2::{Ident, TokenStream, TokenTree};
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use rstml::node::{
|
||||
@@ -105,20 +105,13 @@ pub(crate) fn component_to_tokens(
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let inputs = &binding.inputs;
|
||||
Some(quote! { #inputs })
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let items_to_clone = attrs
|
||||
.iter()
|
||||
.filter_map(|attr| {
|
||||
attr.key
|
||||
.to_string()
|
||||
.strip_prefix("clone:")
|
||||
.map(|ident| format_ident!("{ident}", span = attr.key.span()))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let items_to_clone = filter_prefixed_attrs(attrs.iter(), "clone:");
|
||||
|
||||
// include all attribute that are either
|
||||
// 1) blocks ({..attrs} or {attrs}),
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
mod component_builder;
|
||||
mod slot_helper;
|
||||
mod utils;
|
||||
|
||||
use self::{
|
||||
component_builder::component_to_tokens,
|
||||
slot_helper::{get_slot, slot_to_tokens},
|
||||
};
|
||||
use convert_case::{Case::Snake, Casing};
|
||||
use convert_case::{
|
||||
Case::{Snake, UpperCamel},
|
||||
Casing,
|
||||
};
|
||||
use leptos_hot_reload::parsing::{is_component_node, value_to_string};
|
||||
use proc_macro2::{Ident, Span, TokenStream, TokenTree};
|
||||
use proc_macro_error2::abort;
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use quote::{format_ident, quote, quote_spanned, ToTokens};
|
||||
use rstml::node::{
|
||||
CustomNode, KVAttributeValue, KeyedAttribute, Node, NodeAttribute,
|
||||
NodeBlock, NodeElement, NodeName, NodeNameFragment,
|
||||
@@ -874,6 +879,8 @@ fn attribute_to_tokens(
|
||||
directive_call_from_attribute_node(node, name)
|
||||
} else if let Some(name) = name.strip_prefix("on:") {
|
||||
event_to_tokens(name, node)
|
||||
} else if let Some(name) = name.strip_prefix("bind:") {
|
||||
two_way_binding_to_tokens(name, node)
|
||||
} else if let Some(name) = name.strip_prefix("class:") {
|
||||
let class = match &node.key {
|
||||
NodeName::Punctuated(parts) => &parts[0],
|
||||
@@ -1057,6 +1064,20 @@ pub(crate) fn attribute_absolute(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn two_way_binding_to_tokens(
|
||||
name: &str,
|
||||
node: &KeyedAttribute,
|
||||
) -> TokenStream {
|
||||
let value = attribute_value(node);
|
||||
|
||||
let ident =
|
||||
format_ident!("{}", name.to_case(UpperCamel), span = node.key.span());
|
||||
|
||||
quote! {
|
||||
.bind(::leptos::attr::#ident, #value)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn event_to_tokens(
|
||||
name: &str,
|
||||
node: &KeyedAttribute,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::{convert_to_snake_case, ident_from_tag_name};
|
||||
use crate::view::{fragment_to_tokens, TagType};
|
||||
use crate::view::{fragment_to_tokens, utils::filter_prefixed_attrs, TagType};
|
||||
use proc_macro2::{Ident, TokenStream, TokenTree};
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use quote::{quote, quote_spanned};
|
||||
use rstml::node::{CustomNode, KeyedAttribute, NodeAttribute, NodeElement};
|
||||
use std::collections::HashMap;
|
||||
use syn::spanned::Spanned;
|
||||
@@ -70,25 +70,9 @@ pub(crate) fn slot_to_tokens(
|
||||
}
|
||||
});
|
||||
|
||||
let items_to_bind = attrs
|
||||
.iter()
|
||||
.filter_map(|attr| {
|
||||
attr.key
|
||||
.to_string()
|
||||
.strip_prefix("let:")
|
||||
.map(|ident| format_ident!("{ident}", span = attr.key.span()))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let items_to_bind = filter_prefixed_attrs(attrs.iter(), "let:");
|
||||
|
||||
let items_to_clone = attrs
|
||||
.iter()
|
||||
.filter_map(|attr| {
|
||||
attr.key
|
||||
.to_string()
|
||||
.strip_prefix("clone:")
|
||||
.map(|ident| format_ident!("{ident}", span = attr.key.span()))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let items_to_clone = filter_prefixed_attrs(attrs.iter(), "clone:");
|
||||
|
||||
let dyn_attrs = attrs
|
||||
.iter()
|
||||
|
||||
19
leptos_macro/src/view/utils.rs
Normal file
19
leptos_macro/src/view/utils.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use proc_macro2::Ident;
|
||||
use quote::format_ident;
|
||||
use rstml::node::KeyedAttribute;
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
pub fn filter_prefixed_attrs<'a, A>(attrs: A, prefix: &str) -> Vec<Ident>
|
||||
where
|
||||
A: IntoIterator<Item = &'a KeyedAttribute> + Clone,
|
||||
{
|
||||
attrs
|
||||
.into_iter()
|
||||
.filter_map(|attr| {
|
||||
attr.key
|
||||
.to_string()
|
||||
.strip_prefix(prefix)
|
||||
.map(|ident| format_ident!("{ident}", span = attr.key.span()))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -105,6 +105,49 @@ impl<T, Ser> Deref for ArcResource<T, Ser> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Ser> Track for ArcResource<T, Ser>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
fn track(&self) {
|
||||
self.data.track();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Ser> ReadUntracked for ArcResource<T, Ser>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
type Value = <ArcAsyncDerived<T> as ReadUntracked>::Value;
|
||||
|
||||
#[track_caller]
|
||||
fn try_read_untracked(&self) -> Option<Self::Value> {
|
||||
#[cfg(all(feature = "hydration", debug_assertions))]
|
||||
{
|
||||
use reactive_graph::{
|
||||
computed::suspense::SuspenseContext, owner::use_context,
|
||||
};
|
||||
let suspense = use_context::<SuspenseContext>();
|
||||
if suspense.is_none() {
|
||||
let location = std::panic::Location::caller();
|
||||
reactive_graph::log_warning(format_args!(
|
||||
"At {location}, you are reading a resource in `hydrate` \
|
||||
mode outside a <Suspense/> or <Transition/>. This can \
|
||||
cause hydration mismatch errors and loses out on a \
|
||||
significant performance optimization. To fix this issue, \
|
||||
you can either: \n1. Wrap the place where you read the \
|
||||
resource in a <Suspense/> or <Transition/> component, or \
|
||||
\n2. Switch to using ArcLocalResource::new(), which will \
|
||||
wait to load the resource until the app is hydrated on \
|
||||
the client side. (This will have worse performance in \
|
||||
most cases.)",
|
||||
));
|
||||
}
|
||||
}
|
||||
self.data.try_read_untracked()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Ser> ArcResource<T, Ser>
|
||||
where
|
||||
Ser: Encoder<T> + Decoder<T>,
|
||||
@@ -564,6 +607,49 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Ser> Track for Resource<T, Ser>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
fn track(&self) {
|
||||
self.data.track();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Ser> ReadUntracked for Resource<T, Ser>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
type Value = <AsyncDerived<T> as ReadUntracked>::Value;
|
||||
|
||||
#[track_caller]
|
||||
fn try_read_untracked(&self) -> Option<Self::Value> {
|
||||
#[cfg(all(feature = "hydration", debug_assertions))]
|
||||
{
|
||||
use reactive_graph::{
|
||||
computed::suspense::SuspenseContext, owner::use_context,
|
||||
};
|
||||
let suspense = use_context::<SuspenseContext>();
|
||||
if suspense.is_none() {
|
||||
let location = std::panic::Location::caller();
|
||||
reactive_graph::log_warning(format_args!(
|
||||
"At {location}, you are reading a resource in `hydrate` \
|
||||
mode outside a <Suspense/> or <Transition/>. This can \
|
||||
cause hydration mismatch errors and loses out on a \
|
||||
significant performance optimization. To fix this issue, \
|
||||
you can either: \n1. Wrap the place where you read the \
|
||||
resource in a <Suspense/> or <Transition/> component, or \
|
||||
\n2. Switch to using LocalResource::new(), which will \
|
||||
wait to load the resource until the app is hydrated on \
|
||||
the client side. (This will have worse performance in \
|
||||
most cases.)",
|
||||
));
|
||||
}
|
||||
}
|
||||
self.data.try_read_untracked()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Resource<T, FromToStringCodec>
|
||||
where
|
||||
FromToStringCodec: Encoder<T> + Decoder<T>,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_meta"
|
||||
version = "0.7.0-beta6"
|
||||
version = "0.7.0-gamma"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "next_tuple"
|
||||
version = "0.1.0-beta6"
|
||||
version = "0.1.0-gamma"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_graph"
|
||||
version = "0.1.0-beta6"
|
||||
version = "0.1.0-gamma"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -24,7 +24,7 @@ use std::{future::Future, panic::Location, pin::Pin, sync::Arc};
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
@@ -135,7 +135,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let act = ArcAction::new(|n: &u8| {
|
||||
/// let n = n.to_owned();
|
||||
@@ -367,7 +367,7 @@ impl<I, O> ArcAction<I, O> {
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let act = ArcAction::new(|n: &u8| {
|
||||
/// let n = n.to_owned();
|
||||
@@ -395,7 +395,7 @@ impl<I, O> ArcAction<I, O> {
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let act = ArcAction::new(|n: &u8| {
|
||||
/// let n = n.to_owned();
|
||||
@@ -425,7 +425,7 @@ impl<I, O> ArcAction<I, O> {
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let act = ArcAction::new(|n: &u8| {
|
||||
/// let n = n.to_owned();
|
||||
@@ -456,7 +456,7 @@ impl<I, O> ArcAction<I, O> {
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let act = ArcAction::new(|n: &u8| {
|
||||
/// let n = n.to_owned();
|
||||
@@ -510,7 +510,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
@@ -561,7 +561,7 @@ where
|
||||
/// function, because it is stored in [Action::input] as well.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::actions::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// // if there's a single argument, just use that
|
||||
/// let action1 = Action::new(|input: &String| {
|
||||
/// let input = input.clone();
|
||||
@@ -607,7 +607,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let act = Action::new(|n: &u8| {
|
||||
/// let n = n.to_owned();
|
||||
@@ -721,7 +721,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let act = Action::new(|n: &u8| {
|
||||
/// let n = n.to_owned();
|
||||
@@ -752,7 +752,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let act = Action::new(|n: &u8| {
|
||||
/// let n = n.to_owned();
|
||||
@@ -791,7 +791,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let act = ArcAction::new(|n: &u8| {
|
||||
/// let n = n.to_owned();
|
||||
@@ -847,7 +847,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let act = Action::new(|n: &u8| {
|
||||
/// let n = n.to_owned();
|
||||
@@ -1009,7 +1009,7 @@ impl<I, O, S> Copy for Action<I, O, S> {}
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let act = create_action(|n: &u8| {
|
||||
/// let n = n.to_owned();
|
||||
|
||||
@@ -24,7 +24,7 @@ use std::{fmt::Debug, future::Future, panic::Location, pin::Pin, sync::Arc};
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
@@ -105,7 +105,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// // if there's a single argument, just use that
|
||||
/// let action1 = MultiAction::new(|input: &String| {
|
||||
@@ -151,7 +151,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
@@ -202,7 +202,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
@@ -245,7 +245,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
@@ -286,7 +286,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
@@ -336,7 +336,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
@@ -403,7 +403,7 @@ impl<I, O> ArcMultiAction<I, O> {
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// // if there's a single argument, just use that
|
||||
/// let action1 = ArcMultiAction::new(|input: &String| {
|
||||
@@ -452,7 +452,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
@@ -529,7 +529,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
@@ -579,7 +579,7 @@ impl<I, O> ArcMultiAction<I, O> {
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
@@ -609,7 +609,7 @@ impl<I, O> ArcMultiAction<I, O> {
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
|
||||
@@ -34,7 +34,7 @@ pub use selector::*;
|
||||
/// In the example below, setting an auth token will only trigger
|
||||
/// the token signal, but none of the other derived signals.
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::prelude::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # use reactive_graph::signal::RwSignal;
|
||||
/// # use reactive_graph::computed::*;
|
||||
|
||||
@@ -40,7 +40,7 @@ use std::{
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::prelude::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::computed::*;
|
||||
/// # use reactive_graph::signal::signal;
|
||||
/// # fn really_expensive_computation(value: i32) -> i32 { value };
|
||||
@@ -132,10 +132,7 @@ where
|
||||
pub fn new_with_compare(
|
||||
fun: impl Fn(Option<&T>) -> T + Send + Sync + 'static,
|
||||
changed: fn(Option<&T>, Option<&T>) -> bool,
|
||||
) -> Self
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
) -> Self {
|
||||
Self::new_owning(move |prev: Option<T>| {
|
||||
let new_value = fun(prev.as_ref());
|
||||
let changed = changed(prev.as_ref(), Some(&new_value));
|
||||
@@ -157,10 +154,7 @@ where
|
||||
)]
|
||||
pub fn new_owning(
|
||||
fun: impl Fn(Option<T>) -> (T, bool) + Send + Sync + 'static,
|
||||
) -> Self
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
) -> Self {
|
||||
let inner = Arc::new_cyclic(|weak| {
|
||||
let subscriber = AnySubscriber(
|
||||
weak.as_ptr() as usize,
|
||||
|
||||
@@ -53,10 +53,10 @@ use std::{
|
||||
/// ## Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::computed::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
///
|
||||
/// let signal1 = RwSignal::new(0);
|
||||
|
||||
@@ -29,10 +29,10 @@ use std::{future::Future, ops::DerefMut, panic::Location};
|
||||
/// ## Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::computed::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
///
|
||||
/// let signal1 = RwSignal::new(0);
|
||||
|
||||
@@ -36,7 +36,7 @@ use std::{fmt::Debug, hash::Hash, panic::Location};
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # use reactive_graph::signal::signal;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # tokio::task::LocalSet::new().run_until(async {
|
||||
/// # fn really_expensive_computation(value: i32) -> i32 { value };
|
||||
/// let (value, set_value) = signal(0);
|
||||
@@ -161,7 +161,7 @@ where
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # use reactive_graph::signal::signal;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # fn really_expensive_computation(value: i32) -> i32 { value };
|
||||
/// let (value, set_value) = signal(0);
|
||||
///
|
||||
@@ -195,10 +195,7 @@ where
|
||||
pub fn new_with_compare(
|
||||
fun: impl Fn(Option<&T>) -> T + Send + Sync + 'static,
|
||||
changed: fn(Option<&T>, Option<&T>) -> bool,
|
||||
) -> Self
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
@@ -222,10 +219,7 @@ where
|
||||
)]
|
||||
pub fn new_owning(
|
||||
fun: impl Fn(Option<T>) -> (T, bool) + Send + Sync + 'static,
|
||||
) -> Self
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
|
||||
@@ -19,13 +19,13 @@ use std::{
|
||||
///
|
||||
/// ```
|
||||
/// # use reactive_graph::computed::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # use reactive_graph::owner::StoredValue;
|
||||
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # tokio::task::LocalSet::new().run_until(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let a = RwSignal::new(0);
|
||||
/// let is_selected = Selector::new(move || a.get());
|
||||
|
||||
@@ -37,13 +37,13 @@ use std::{
|
||||
///
|
||||
/// ```
|
||||
/// # use reactive_graph::computed::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # use reactive_graph::owner::ArenaItem;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # tokio::task::LocalSet::new().run_until(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// let a = RwSignal::new(0);
|
||||
/// let b = RwSignal::new(0);
|
||||
///
|
||||
@@ -185,7 +185,7 @@ impl Effect<LocalStorage> {
|
||||
/// # use reactive_graph::signal::signal;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # tokio::task::LocalSet::new().run_until(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// #
|
||||
/// let (num, set_num) = signal(0);
|
||||
///
|
||||
@@ -217,7 +217,7 @@ impl Effect<LocalStorage> {
|
||||
/// # use reactive_graph::signal::signal;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # tokio::task::LocalSet::new().run_until(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// #
|
||||
/// let (num, set_num) = signal(0);
|
||||
/// let (cb_num, set_cb_num) = signal(0);
|
||||
@@ -256,7 +256,7 @@ impl Effect<LocalStorage> {
|
||||
/// # use reactive_graph::signal::signal;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # tokio::task::LocalSet::new().run_until(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// #
|
||||
/// let (num, set_num) = signal(0);
|
||||
///
|
||||
|
||||
@@ -104,11 +104,11 @@ impl Observer {
|
||||
///
|
||||
/// ```rust
|
||||
/// # use reactive_graph::computed::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::untrack;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// let (a, set_a) = signal(0);
|
||||
/// let (b, set_b) = signal(0);
|
||||
/// let c = Memo::new(move |_| {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
//!
|
||||
//! ```rust
|
||||
//! # any_spawner::Executor::init_futures_executor();
|
||||
//! # let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
//! use reactive_graph::{
|
||||
//! computed::ArcMemo,
|
||||
//! effect::Effect,
|
||||
@@ -87,6 +88,7 @@ pub mod traits;
|
||||
pub mod transition;
|
||||
pub mod wrappers;
|
||||
|
||||
use computed::ScopedFuture;
|
||||
pub use graph::untrack;
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
@@ -130,3 +132,36 @@ pub(crate) fn spawn(task: impl Future<Output = ()> + Send + 'static) {
|
||||
|
||||
any_spawner::Executor::spawn(task);
|
||||
}
|
||||
|
||||
/// Calls [`Executor::spawn_local`], but ensures that the task runs under the current reactive [`Owner`]
|
||||
/// and [`Observed`]. Does not cancel the task if the owner is cleaned up.
|
||||
pub fn spawn_local_scoped(task: impl Future<Output = ()> + 'static) {
|
||||
let task = ScopedFuture::new(task);
|
||||
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
let task = owner::Sandboxed::new(task);
|
||||
|
||||
any_spawner::Executor::spawn_local(task);
|
||||
}
|
||||
|
||||
/// Calls [`Executor::spawn_local`], but ensures that the task runs under the current reactive [`Owner`]
|
||||
/// and [`Observed`]. Cancels the task if the owner is cleaned up.
|
||||
pub fn spawn_local_scoped_with_cancellation(
|
||||
task: impl Future<Output = ()> + 'static,
|
||||
) {
|
||||
use crate::owner::on_cleanup;
|
||||
use futures::future::{AbortHandle, Abortable};
|
||||
|
||||
let (abort_handle, abort_registration) = AbortHandle::new_pair();
|
||||
on_cleanup(move || abort_handle.abort());
|
||||
|
||||
let task = Abortable::new(task, abort_registration);
|
||||
let task = ScopedFuture::new(task);
|
||||
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
let task = owner::Sandboxed::new(task);
|
||||
|
||||
any_spawner::Executor::spawn_local(async move {
|
||||
_ = task.await;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ mod stored_value;
|
||||
use self::arena::Arena;
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
pub use arena::sandboxed::Sandboxed;
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
use arena::ArenaMap;
|
||||
use arena::NodeId;
|
||||
pub use arena_item::*;
|
||||
pub use context::*;
|
||||
@@ -120,6 +122,12 @@ impl Owner {
|
||||
contexts: Default::default(),
|
||||
cleanups: Default::default(),
|
||||
children: Default::default(),
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
arena: parent
|
||||
.as_ref()
|
||||
.and_then(|parent| parent.upgrade())
|
||||
.map(|parent| parent.read().or_poisoned().arena.clone())
|
||||
.unwrap_or_default(),
|
||||
})),
|
||||
#[cfg(feature = "hydration")]
|
||||
shared_context,
|
||||
@@ -140,11 +148,10 @@ impl Owner {
|
||||
/// Only one `SharedContext` needs to be created per request, and will be automatically shared
|
||||
/// by any other `Owner`s created under this one.
|
||||
#[cfg(feature = "hydration")]
|
||||
#[track_caller]
|
||||
pub fn new_root(
|
||||
shared_context: Option<Arc<dyn SharedContext + Send + Sync>>,
|
||||
) -> Self {
|
||||
Arena::enter_new();
|
||||
|
||||
let this = Self {
|
||||
inner: Arc::new(RwLock::new(OwnerInner {
|
||||
parent: None,
|
||||
@@ -152,17 +159,21 @@ impl Owner {
|
||||
contexts: Default::default(),
|
||||
cleanups: Default::default(),
|
||||
children: Default::default(),
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
arena: Default::default(),
|
||||
})),
|
||||
#[cfg(feature = "hydration")]
|
||||
shared_context,
|
||||
};
|
||||
OWNER.with_borrow_mut(|owner| *owner = Some(this.clone()));
|
||||
this.set();
|
||||
this
|
||||
}
|
||||
|
||||
/// Creates a new `Owner` that is the child of the current `Owner`, if any.
|
||||
pub fn child(&self) -> Self {
|
||||
let parent = Some(Arc::downgrade(&self.inner));
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
let arena = self.inner.read().or_poisoned().arena.clone();
|
||||
let child = Self {
|
||||
inner: Arc::new(RwLock::new(OwnerInner {
|
||||
parent,
|
||||
@@ -170,6 +181,8 @@ impl Owner {
|
||||
contexts: Default::default(),
|
||||
cleanups: Default::default(),
|
||||
children: Default::default(),
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
arena,
|
||||
})),
|
||||
#[cfg(feature = "hydration")]
|
||||
shared_context: self.shared_context.clone(),
|
||||
@@ -185,6 +198,8 @@ impl Owner {
|
||||
/// Sets this as the current `Owner`.
|
||||
pub fn set(&self) {
|
||||
OWNER.with_borrow_mut(|owner| *owner = Some(self.clone()));
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
Arena::set(&self.inner.read().or_poisoned().arena);
|
||||
}
|
||||
|
||||
/// Runs the given function with this as the current `Owner`.
|
||||
@@ -194,6 +209,8 @@ impl Owner {
|
||||
mem::replace(&mut *o.borrow_mut(), Some(self.clone()))
|
||||
})
|
||||
};
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
Arena::set(&self.inner.read().or_poisoned().arena);
|
||||
let val = fun();
|
||||
OWNER.with(|o| {
|
||||
*o.borrow_mut() = prev;
|
||||
@@ -334,6 +351,8 @@ pub(crate) struct OwnerInner {
|
||||
pub contexts: FxHashMap<TypeId, Box<dyn Any + Send + Sync>>,
|
||||
pub cleanups: Vec<Box<dyn FnOnce() + Send + Sync>>,
|
||||
pub children: Vec<Weak<RwLock<OwnerInner>>>,
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
arena: Arc<RwLock<ArenaMap>>,
|
||||
}
|
||||
|
||||
impl Debug for OwnerInner {
|
||||
@@ -361,11 +380,19 @@ impl Drop for OwnerInner {
|
||||
|
||||
let nodes = mem::take(&mut self.nodes);
|
||||
if !nodes.is_empty() {
|
||||
#[cfg(not(feature = "sandboxed-arenas"))]
|
||||
Arena::with_mut(|arena| {
|
||||
for node in nodes {
|
||||
_ = arena.remove(node);
|
||||
}
|
||||
});
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
{
|
||||
let mut arena = self.arena.write().or_poisoned();
|
||||
for node in nodes {
|
||||
_ = arena.remove(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -394,11 +421,20 @@ impl Cleanup for RwLock<OwnerInner> {
|
||||
}
|
||||
|
||||
if !nodes.is_empty() {
|
||||
#[cfg(not(feature = "sandboxed-arenas"))]
|
||||
Arena::with_mut(|arena| {
|
||||
for node in nodes {
|
||||
_ = arena.remove(node);
|
||||
}
|
||||
});
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
{
|
||||
let arena = self.read().or_poisoned().arena.clone();
|
||||
let mut arena = arena.write().or_poisoned();
|
||||
for node in nodes {
|
||||
_ = arena.remove(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,42 @@
|
||||
use or_poisoned::OrPoisoned;
|
||||
use slotmap::{new_key_type, SlotMap};
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
use std::cell::RefCell;
|
||||
#[cfg(not(feature = "sandboxed-arenas"))]
|
||||
use std::sync::OnceLock;
|
||||
use std::{any::Any, hash::Hash, sync::RwLock};
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
use std::{cell::RefCell, sync::Arc};
|
||||
use std::sync::Weak;
|
||||
use std::{
|
||||
any::Any,
|
||||
hash::Hash,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
new_key_type! {
|
||||
/// Unique identifier for an item stored in the arena.
|
||||
pub struct NodeId;
|
||||
}
|
||||
|
||||
pub(crate) struct Arena;
|
||||
pub struct Arena;
|
||||
|
||||
type ArenaMap = SlotMap<NodeId, Box<dyn Any + Send + Sync>>;
|
||||
pub type ArenaMap = SlotMap<NodeId, Box<dyn Any + Send + Sync>>;
|
||||
|
||||
#[cfg(not(feature = "sandboxed-arenas"))]
|
||||
static MAP: OnceLock<RwLock<ArenaMap>> = OnceLock::new();
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
thread_local! {
|
||||
pub(crate) static MAP: RefCell<Option<Arc<RwLock<ArenaMap>>>> = RefCell::new(Some(Default::default()));
|
||||
pub(crate) static MAP: RefCell<Option<Weak<RwLock<ArenaMap>>>> = RefCell::new(Some(Default::default()));
|
||||
}
|
||||
|
||||
impl Arena {
|
||||
#[cfg(feature = "hydration")]
|
||||
#[inline(always)]
|
||||
pub fn enter_new() {
|
||||
#[allow(unused)]
|
||||
pub fn set(arena: &Arc<RwLock<ArenaMap>>) {
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
{
|
||||
use std::sync::Arc;
|
||||
let new_arena = Arc::downgrade(arena);
|
||||
MAP.with_borrow_mut(|arena| {
|
||||
*arena = Some(Arc::new(RwLock::new(
|
||||
SlotMap::with_capacity_and_key(32),
|
||||
)))
|
||||
*arena = Some(new_arena);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -48,6 +52,7 @@ impl Arena {
|
||||
MAP.with_borrow(|arena| {
|
||||
fun(&arena
|
||||
.as_ref()
|
||||
.and_then(Weak::upgrade)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"at {}, the `sandboxed-arenas` feature is active, \
|
||||
@@ -73,6 +78,7 @@ impl Arena {
|
||||
MAP.with_borrow(|arena| {
|
||||
fun(&mut arena
|
||||
.as_ref()
|
||||
.and_then(Weak::upgrade)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"at {}, the `sandboxed-arenas` feature is active, \
|
||||
@@ -94,20 +100,11 @@ pub mod sandboxed {
|
||||
use pin_project_lite::pin_project;
|
||||
use std::{
|
||||
future::Future,
|
||||
mem,
|
||||
pin::Pin,
|
||||
sync::{Arc, RwLock},
|
||||
sync::{Arc, RwLock, Weak},
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
impl Arena {
|
||||
fn set(new_arena: Arc<RwLock<ArenaMap>>) -> UnsetArenaOnDrop {
|
||||
MAP.with_borrow_mut(|arena| {
|
||||
UnsetArenaOnDrop(mem::replace(arena, Some(new_arena)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// A [`Future`] that restores its associated arena as the current arena whenever it is
|
||||
/// polled.
|
||||
@@ -117,7 +114,7 @@ pub mod sandboxed {
|
||||
/// stored values. Wrapping a `Future` in `Sandboxed` ensures that it will always use the
|
||||
/// same arena that it was created under.
|
||||
pub struct Sandboxed<T> {
|
||||
arena: Arc<RwLock<ArenaMap>>,
|
||||
arena: Option<Arc<RwLock<ArenaMap>>>,
|
||||
#[pin]
|
||||
inner: T,
|
||||
}
|
||||
@@ -127,12 +124,7 @@ pub mod sandboxed {
|
||||
/// Wraps the given [`Future`], ensuring that any [`ArenaItem`] created while it is being
|
||||
/// polled will be associated with the same arena that was active when this was called.
|
||||
pub fn new(inner: T) -> Self {
|
||||
let arena = MAP.with_borrow(|current| {
|
||||
Arc::clone(current.as_ref().expect(
|
||||
"the `sandboxed-arenas` feature is active, but no Arena \
|
||||
is active",
|
||||
))
|
||||
});
|
||||
let arena = MAP.with_borrow(|n| n.as_ref().and_then(Weak::upgrade));
|
||||
Self { arena, inner }
|
||||
}
|
||||
}
|
||||
@@ -147,11 +139,11 @@ pub mod sandboxed {
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Self::Output> {
|
||||
let unset = Arena::set(Arc::clone(&self.arena));
|
||||
if let Some(arena) = self.arena.as_ref() {
|
||||
Arena::set(arena);
|
||||
}
|
||||
let this = self.project();
|
||||
let res = this.inner.poll(cx);
|
||||
drop(unset);
|
||||
res
|
||||
this.inner.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,22 +157,11 @@ pub mod sandboxed {
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
let unset = Arena::set(Arc::clone(&self.arena));
|
||||
let this = self.project();
|
||||
let res = this.inner.poll_next(cx);
|
||||
drop(unset);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct UnsetArenaOnDrop(Option<Arc<RwLock<ArenaMap>>>);
|
||||
|
||||
impl Drop for UnsetArenaOnDrop {
|
||||
fn drop(&mut self) {
|
||||
if let Some(inner) = self.0.take() {
|
||||
MAP.with_borrow_mut(|current_map| *current_map = Some(inner));
|
||||
if let Some(arena) = self.arena.as_ref() {
|
||||
Arena::set(arena);
|
||||
}
|
||||
let this = self.project();
|
||||
this.inner.poll_next(cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +108,7 @@ impl Owner {
|
||||
/// ```rust
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::owner::*;
|
||||
/// # let owner = Owner::new(); owner.set();
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # futures::executor::block_on(async move {
|
||||
/// # any_spawner::Executor::init_futures_executor();
|
||||
@@ -140,6 +141,7 @@ impl Owner {
|
||||
/// ```rust
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::owner::*;
|
||||
/// # let owner = Owner::new(); owner.set();
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # futures::executor::block_on(async move {
|
||||
/// # any_spawner::Executor::init_futures_executor();
|
||||
@@ -187,6 +189,7 @@ pub fn provide_context<T: Send + Sync + 'static>(value: T) {
|
||||
/// ```rust
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::owner::*;
|
||||
/// # let owner = Owner::new(); owner.set();
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # futures::executor::block_on(async move {
|
||||
/// # any_spawner::Executor::init_futures_executor();
|
||||
@@ -232,6 +235,7 @@ pub fn use_context<T: Clone + 'static>() -> Option<T> {
|
||||
/// ```rust
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::owner::*;
|
||||
/// # let owner = Owner::new(); owner.set();
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # futures::executor::block_on(async move {
|
||||
/// # any_spawner::Executor::init_futures_executor();
|
||||
@@ -286,6 +290,7 @@ pub fn expect_context<T: Clone + 'static>() -> T {
|
||||
/// ```rust
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::owner::*;
|
||||
/// # let owner = Owner::new(); owner.set();
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # futures::executor::block_on(async move {
|
||||
/// # any_spawner::Executor::init_futures_executor();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::{ArenaItem, LocalStorage, Storage, SyncStorage};
|
||||
use crate::{
|
||||
signal::guards::{Plain, ReadGuard, UntrackedWriteGuard},
|
||||
traits::{DefinedAt, Dispose, IsDisposed},
|
||||
unwrap_signal,
|
||||
};
|
||||
@@ -119,7 +120,7 @@ impl<T, S: Storage<Arc<RwLock<T>>>> StoredValue<T, S> {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::owner::StoredValue;
|
||||
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::traits::Dispose;
|
||||
///
|
||||
/// // Does not implement Clone
|
||||
@@ -166,7 +167,7 @@ impl<T, S: Storage<Arc<RwLock<T>>>> StoredValue<T, S> {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::owner::StoredValue;
|
||||
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
///
|
||||
/// // Does not implement Clone
|
||||
/// struct Data {
|
||||
@@ -193,6 +194,44 @@ impl<T, S: Storage<Arc<RwLock<T>>>> StoredValue<T, S> {
|
||||
.unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
|
||||
/// Returns a read guard to the stored data, or `None` if the owner of the reactive node has been disposed.
|
||||
#[track_caller]
|
||||
pub fn try_read_value(&self) -> Option<ReadGuard<T, Plain<T>>> {
|
||||
self.value
|
||||
.try_get_value()
|
||||
.and_then(|inner| Plain::try_new(inner).map(ReadGuard::new))
|
||||
}
|
||||
|
||||
/// Returns a read guard to the stored data.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics when called after the owner of the reactive node has been disposed.
|
||||
/// See [`StoredValue::try_read_value`] for a version without panic.
|
||||
#[track_caller]
|
||||
pub fn read_value(&self) -> ReadGuard<T, Plain<T>> {
|
||||
self.try_read_value().unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
|
||||
/// Returns a write guard to the stored data, or `None` if the owner of the reactive node has been disposed.
|
||||
#[track_caller]
|
||||
pub fn try_write_value(&self) -> Option<UntrackedWriteGuard<T>> {
|
||||
self.value
|
||||
.try_get_value()
|
||||
.and_then(|inner| UntrackedWriteGuard::try_new(inner))
|
||||
}
|
||||
|
||||
/// Returns a write guard to the stored data.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics when called after the owner of the reactive node has been disposed.
|
||||
/// See [`StoredValue::try_write_value`] for a version without panic.
|
||||
#[track_caller]
|
||||
pub fn write_value(&self) -> UntrackedWriteGuard<T> {
|
||||
self.try_write_value().unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
|
||||
/// Updates the current value by applying the given closure, returning the return value of the
|
||||
/// closure, or `None` if the value has already been disposed.
|
||||
pub fn try_update_value<U>(
|
||||
@@ -212,7 +251,7 @@ impl<T, S: Storage<Arc<RwLock<T>>>> StoredValue<T, S> {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::owner::StoredValue;
|
||||
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
///
|
||||
/// #[derive(Default)] // Does not implement Clone
|
||||
/// struct Data {
|
||||
@@ -256,7 +295,7 @@ impl<T, S: Storage<Arc<RwLock<T>>>> StoredValue<T, S> {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::owner::StoredValue;
|
||||
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::traits::Dispose;
|
||||
///
|
||||
/// let data = StoredValue::new(String::default());
|
||||
@@ -314,7 +353,7 @@ impl<T, S: Storage<Arc<RwLock<T>>>> StoredValue<T, S> {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::owner::StoredValue;
|
||||
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
///
|
||||
/// let data = StoredValue::new(10);
|
||||
///
|
||||
@@ -355,7 +394,7 @@ where
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::owner::StoredValue;
|
||||
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::traits::Dispose;
|
||||
///
|
||||
/// // u8 is practically free to clone.
|
||||
@@ -395,7 +434,7 @@ where
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::owner::StoredValue;
|
||||
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
///
|
||||
/// // u8 is practically free to clone.
|
||||
/// let data: StoredValue<u8> = StoredValue::new(10);
|
||||
|
||||
@@ -36,7 +36,7 @@ pub use write::*;
|
||||
///
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// let (count, set_count) = arc_signal(0);
|
||||
///
|
||||
/// // ✅ calling the getter clones and returns the value
|
||||
@@ -82,7 +82,7 @@ pub fn arc_signal<T>(value: T) -> (ArcReadSignal<T>, ArcWriteSignal<T>) {
|
||||
/// as long as a reference to it is alive, see [`arc_signal`].
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// let (count, set_count) = signal(0);
|
||||
///
|
||||
/// // ✅ calling the getter clones and returns the value
|
||||
@@ -142,7 +142,7 @@ pub fn signal_local<T: 'static>(
|
||||
/// as long as a reference to it is alive, see [`arc_signal`].
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// let (count, set_count) = create_signal(0);
|
||||
///
|
||||
/// // ✅ calling the getter clones and returns the value
|
||||
|
||||
@@ -63,7 +63,7 @@ use std::{
|
||||
///
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// let count = ArcRwSignal::new(0);
|
||||
///
|
||||
/// // ✅ calling the getter clones and returns the value
|
||||
|
||||
@@ -49,7 +49,7 @@ use std::{
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*; use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::prelude::*; use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// let (count, set_count) = signal(0);
|
||||
///
|
||||
/// // calling .get() clones and returns the value
|
||||
|
||||
@@ -69,7 +69,7 @@ use std::{
|
||||
///
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// let count = ArcRwSignal::new(0);
|
||||
///
|
||||
/// // ✅ calling the getter clones and returns the value
|
||||
|
||||
@@ -32,7 +32,7 @@ use std::{hash::Hash, ops::DerefMut, panic::Location, sync::Arc};
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*; use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::prelude::*; use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// let (count, set_count) = signal(0);
|
||||
///
|
||||
/// // ✅ calling the setter sets the value
|
||||
|
||||
@@ -131,7 +131,7 @@ pub mod read {
|
||||
/// Wraps a derived signal, i.e., any computation that accesses one or more
|
||||
/// reactive signals.
|
||||
/// ```rust
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::wrappers::read::ArcSignal;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// let (count, set_count) = arc_signal(2);
|
||||
@@ -397,7 +397,7 @@ pub mod read {
|
||||
/// Wraps a derived signal, i.e., any computation that accesses one or more
|
||||
/// reactive signals.
|
||||
/// ```rust
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::wrappers::read::Signal;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// let (count, set_count) = signal(2);
|
||||
@@ -647,7 +647,7 @@ pub mod read {
|
||||
/// of the same type. This is especially useful for component properties.
|
||||
///
|
||||
/// ```
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::wrappers::read::MaybeSignal;
|
||||
/// # use reactive_graph::computed::Memo;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
@@ -907,7 +907,7 @@ pub mod read {
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::wrappers::read::MaybeProp;
|
||||
/// # use reactive_graph::computed::Memo;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
@@ -1282,7 +1282,7 @@ pub mod write {
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::prelude::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::wrappers::write::SignalSetter;
|
||||
/// # use reactive_graph::signal::signal;
|
||||
/// let (count, set_count) = signal(2);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use any_spawner::Executor;
|
||||
use reactive_graph::{
|
||||
computed::{ArcAsyncDerived, AsyncDerived},
|
||||
owner::Owner,
|
||||
signal::RwSignal,
|
||||
traits::{Get, Read, Set, With, WithUntracked},
|
||||
};
|
||||
@@ -9,6 +10,8 @@ use std::future::pending;
|
||||
#[tokio::test]
|
||||
async fn arc_async_derived_calculates_eagerly() {
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let value = ArcAsyncDerived::new(|| async {
|
||||
Executor::tick().await;
|
||||
@@ -21,6 +24,8 @@ async fn arc_async_derived_calculates_eagerly() {
|
||||
#[tokio::test]
|
||||
async fn arc_async_derived_tracks_signal_change() {
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let signal = RwSignal::new(10);
|
||||
let value = ArcAsyncDerived::new(move || async move {
|
||||
@@ -40,6 +45,8 @@ async fn arc_async_derived_tracks_signal_change() {
|
||||
#[tokio::test]
|
||||
async fn async_derived_calculates_eagerly() {
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let value = AsyncDerived::new(|| async {
|
||||
Executor::tick().await;
|
||||
@@ -52,6 +59,8 @@ async fn async_derived_calculates_eagerly() {
|
||||
#[tokio::test]
|
||||
async fn async_derived_tracks_signal_change() {
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let signal = RwSignal::new(10);
|
||||
let value = AsyncDerived::new(move || async move {
|
||||
@@ -71,6 +80,8 @@ async fn async_derived_tracks_signal_change() {
|
||||
#[tokio::test]
|
||||
async fn read_signal_traits_on_arc() {
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let value = ArcAsyncDerived::new(pending::<()>);
|
||||
assert_eq!(value.read(), None);
|
||||
@@ -82,6 +93,8 @@ async fn read_signal_traits_on_arc() {
|
||||
#[tokio::test]
|
||||
async fn read_signal_traits_on_arena() {
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let value = AsyncDerived::new(pending::<()>);
|
||||
println!("{:?}", value.read());
|
||||
@@ -94,6 +107,8 @@ async fn read_signal_traits_on_arena() {
|
||||
#[tokio::test]
|
||||
async fn async_derived_with_initial() {
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let signal1 = RwSignal::new(0);
|
||||
let signal2 = RwSignal::new(0);
|
||||
|
||||
@@ -3,6 +3,7 @@ pub mod imports {
|
||||
pub use any_spawner::Executor;
|
||||
pub use reactive_graph::{
|
||||
effect::{Effect, RenderEffect},
|
||||
owner::Owner,
|
||||
prelude::*,
|
||||
signal::RwSignal,
|
||||
};
|
||||
@@ -19,6 +20,8 @@ async fn render_effect_runs() {
|
||||
use imports::*;
|
||||
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
task::LocalSet::new()
|
||||
.run_until(async {
|
||||
let a = RwSignal::new(-1);
|
||||
@@ -54,6 +57,8 @@ async fn effect_runs() {
|
||||
use imports::*;
|
||||
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
task::LocalSet::new()
|
||||
.run_until(async {
|
||||
@@ -88,6 +93,8 @@ async fn dynamic_dependencies() {
|
||||
use imports::*;
|
||||
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
task::LocalSet::new()
|
||||
.run_until(async {
|
||||
@@ -156,6 +163,8 @@ async fn recursive_effect_runs_recursively() {
|
||||
use imports::*;
|
||||
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
task::LocalSet::new()
|
||||
.run_until(async {
|
||||
let s = RwSignal::new(0);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use reactive_graph::{
|
||||
computed::{ArcMemo, Memo},
|
||||
owner::Owner,
|
||||
prelude::*,
|
||||
signal::RwSignal,
|
||||
wrappers::read::Signal,
|
||||
@@ -29,6 +30,9 @@ pub mod imports {
|
||||
|
||||
#[test]
|
||||
fn memo_calculates_value() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let a = RwSignal::new(1);
|
||||
let b = RwSignal::new(2);
|
||||
let c = RwSignal::new(3);
|
||||
@@ -42,6 +46,9 @@ fn memo_calculates_value() {
|
||||
|
||||
#[test]
|
||||
fn arc_memo_readable() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let a = RwSignal::new(1);
|
||||
let b = RwSignal::new(2);
|
||||
let c = RwSignal::new(3);
|
||||
@@ -52,6 +59,9 @@ fn arc_memo_readable() {
|
||||
|
||||
#[test]
|
||||
fn memo_doesnt_repeat_calculation_per_get() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let calculations = Arc::new(RwLock::new(0));
|
||||
|
||||
let a = RwSignal::new(1);
|
||||
@@ -78,6 +88,9 @@ fn memo_doesnt_repeat_calculation_per_get() {
|
||||
|
||||
#[test]
|
||||
fn nested_memos() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let a = RwSignal::new(0); // 1
|
||||
let b = RwSignal::new(0); // 2
|
||||
let c = Memo::new(move |_| {
|
||||
@@ -111,6 +124,9 @@ fn nested_memos() {
|
||||
|
||||
#[test]
|
||||
fn memo_runs_only_when_inputs_change() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let call_count = Arc::new(RwLock::new(0));
|
||||
let a = RwSignal::new(0);
|
||||
let b = RwSignal::new(0);
|
||||
@@ -150,6 +166,9 @@ fn memo_runs_only_when_inputs_change() {
|
||||
|
||||
#[test]
|
||||
fn diamond_problem() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let name = RwSignal::new("Greg Johnston".to_string());
|
||||
let first = Memo::new(move |_| {
|
||||
println!("calculating first");
|
||||
@@ -187,9 +206,14 @@ fn diamond_problem() {
|
||||
#[cfg(feature = "effects")]
|
||||
#[tokio::test]
|
||||
async fn dynamic_dependencies() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
use imports::*;
|
||||
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let first = RwSignal::new("Greg");
|
||||
let last = RwSignal::new("Johnston");
|
||||
@@ -264,9 +288,14 @@ async fn dynamic_dependencies() {
|
||||
#[cfg(feature = "effects")]
|
||||
#[tokio::test]
|
||||
async fn render_effect_doesnt_rerun_if_memo_didnt_change() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
use imports::*;
|
||||
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
task::LocalSet::new()
|
||||
.run_until(async {
|
||||
@@ -307,9 +336,14 @@ async fn render_effect_doesnt_rerun_if_memo_didnt_change() {
|
||||
#[cfg(feature = "effects")]
|
||||
#[tokio::test]
|
||||
async fn effect_doesnt_rerun_if_memo_didnt_change() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
use imports::*;
|
||||
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
task::LocalSet::new()
|
||||
.run_until(async {
|
||||
@@ -343,9 +377,14 @@ async fn effect_doesnt_rerun_if_memo_didnt_change() {
|
||||
#[cfg(feature = "effects")]
|
||||
#[tokio::test]
|
||||
async fn effect_depending_on_signal_and_memo_doesnt_rerun_unnecessarily() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
use imports::*;
|
||||
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
task::LocalSet::new()
|
||||
.run_until(async {
|
||||
@@ -383,6 +422,9 @@ async fn effect_depending_on_signal_and_memo_doesnt_rerun_unnecessarily() {
|
||||
|
||||
#[test]
|
||||
fn unsync_derived_signal_and_memo() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let a = RwSignal::new_local(Rc::new(1));
|
||||
let b = RwSignal::new(2);
|
||||
let c = RwSignal::new(3);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use reactive_graph::{
|
||||
owner::Owner,
|
||||
signal::{arc_signal, signal, ArcRwSignal, RwSignal},
|
||||
traits::{
|
||||
Get, GetUntracked, Read, Set, Update, UpdateUntracked, With,
|
||||
@@ -54,6 +55,9 @@ fn update_arc_signal() {
|
||||
|
||||
#[test]
|
||||
fn create_rw_signal() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let a = RwSignal::new(0);
|
||||
assert_eq!(a.read(), 0);
|
||||
assert_eq!(a.get(), 0);
|
||||
@@ -63,6 +67,9 @@ fn create_rw_signal() {
|
||||
|
||||
#[test]
|
||||
fn update_rw_signal() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let a = RwSignal::new(1);
|
||||
assert_eq!(a.read(), 1);
|
||||
assert_eq!(a.get(), 1);
|
||||
@@ -76,6 +83,9 @@ fn update_rw_signal() {
|
||||
|
||||
#[test]
|
||||
fn create_signal() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let (a, _) = signal(0);
|
||||
assert_eq!(a.read(), 0);
|
||||
assert_eq!(a.get(), 0);
|
||||
@@ -86,6 +96,9 @@ fn create_signal() {
|
||||
|
||||
#[test]
|
||||
fn update_signal() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let (a, set_a) = signal(1);
|
||||
assert_eq!(a.get(), 1);
|
||||
set_a.update(|n| *n += 1);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#[cfg(feature = "effects")]
|
||||
use any_spawner::Executor;
|
||||
#[cfg(feature = "effects")]
|
||||
use reactive_graph::owner::Owner;
|
||||
#[cfg(feature = "effects")]
|
||||
use reactive_graph::{effect::Effect, prelude::*, signal::RwSignal};
|
||||
#[cfg(feature = "effects")]
|
||||
use std::sync::{Arc, RwLock};
|
||||
@@ -11,6 +13,8 @@ use tokio::task;
|
||||
#[tokio::test]
|
||||
async fn watch_runs() {
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
task::LocalSet::new()
|
||||
.run_until(async {
|
||||
@@ -71,6 +75,8 @@ async fn watch_runs() {
|
||||
#[tokio::test]
|
||||
async fn watch_runs_immediately() {
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
task::LocalSet::new()
|
||||
.run_until(async {
|
||||
@@ -118,6 +124,8 @@ async fn watch_runs_immediately() {
|
||||
#[tokio::test]
|
||||
async fn watch_ignores_callback() {
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
task::LocalSet::new()
|
||||
.run_until(async {
|
||||
@@ -174,6 +182,8 @@ async fn watch_ignores_callback() {
|
||||
#[tokio::test]
|
||||
async fn deprecated_watch_runs() {
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
task::LocalSet::new()
|
||||
.run_until(async {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_stores"
|
||||
version = "0.1.0-beta6"
|
||||
version = "0.1.0-gamma"
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_stores_macro"
|
||||
version = "0.1.0-beta6"
|
||||
version = "0.1.0-gamma"
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -9,7 +9,7 @@ proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
convert_case = "0.6"
|
||||
proc-macro-error = "1.0"
|
||||
proc-macro-error2 = "2.0"
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use convert_case::{Case, Casing};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use proc_macro_error::{abort, abort_call_site, proc_macro_error};
|
||||
use proc_macro_error2::{abort, abort_call_site, proc_macro_error};
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream, Parser},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_router"
|
||||
version = "0.7.0-beta6"
|
||||
version = "0.7.0-gamma"
|
||||
authors = ["Greg Johnston", "Ben Wishovich"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -43,7 +43,7 @@ where
|
||||
Fal: Render + 'static,
|
||||
{
|
||||
#[allow(clippy::type_complexity)]
|
||||
view: <EitherOf3<(), Fal, <Defs::Match as MatchInterface>::View> as Render>::State,
|
||||
view: <EitherOf3<(), Fal, OwnedView<<Defs::Match as MatchInterface>::View>> as Render>::State,
|
||||
id: Option<RouteMatchId>,
|
||||
owner: Owner,
|
||||
params: ArcRwSignal<ParamsMap>,
|
||||
@@ -147,7 +147,7 @@ where
|
||||
provide_context(params_memo);
|
||||
provide_context(url);
|
||||
provide_context(Matched(ArcMemo::from(matched)));
|
||||
view.choose().await
|
||||
OwnedView::new(view.choose().await)
|
||||
}
|
||||
})
|
||||
}));
|
||||
@@ -292,7 +292,7 @@ where
|
||||
provide_context(Matched(ArcMemo::from(
|
||||
new_matched,
|
||||
)));
|
||||
let view =
|
||||
let view = OwnedView::new(
|
||||
if let Some(set_is_routing) = set_is_routing {
|
||||
set_is_routing.set(true);
|
||||
let value =
|
||||
@@ -302,7 +302,8 @@ where
|
||||
value
|
||||
} else {
|
||||
view.choose().await
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// only update the route if it's still the current path
|
||||
// i.e., if we've navigated away before this has loaded, do nothing
|
||||
@@ -572,7 +573,7 @@ where
|
||||
provide_context(params_memo);
|
||||
provide_context(url);
|
||||
provide_context(Matched(ArcMemo::from(matched)));
|
||||
view.choose().await
|
||||
OwnedView::new(view.choose().await)
|
||||
}
|
||||
})
|
||||
}));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_router_macro"
|
||||
version = "0.7.0-beta6"
|
||||
version = "0.7.0-gamma"
|
||||
authors = ["Greg Johnston", "Ben Wishovich"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
@@ -13,7 +13,7 @@ edition.workspace = true
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro-error = { version = "1.0", default-features = false }
|
||||
proc-macro-error2 = { version = "2.0", default-features = false }
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use proc_macro::{TokenStream, TokenTree};
|
||||
use proc_macro2::Span;
|
||||
use proc_macro_error::abort;
|
||||
use proc_macro_error2::abort;
|
||||
use quote::{quote, ToTokens};
|
||||
|
||||
const RFC3986_UNRESERVED: [char; 4] = ['-', '.', '_', '~'];
|
||||
@@ -25,7 +25,7 @@ const RFC3986_PCHAR_OTHER: [char; 1] = ['@'];
|
||||
///
|
||||
/// assert_eq!(path, output);
|
||||
/// ```
|
||||
#[proc_macro_error::proc_macro_error]
|
||||
#[proc_macro_error2::proc_macro_error]
|
||||
#[proc_macro]
|
||||
pub fn path(tokens: TokenStream) -> TokenStream {
|
||||
let mut parser = SegmentParser::new(tokens);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tachys"
|
||||
version = "0.1.0-beta6"
|
||||
version = "0.1.0-gamma"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -8,6 +8,7 @@ pub mod custom;
|
||||
pub mod global;
|
||||
mod key;
|
||||
mod value;
|
||||
|
||||
use crate::view::{Position, ToTemplate};
|
||||
pub use key::*;
|
||||
use std::{fmt::Debug, future::Future};
|
||||
|
||||
@@ -29,6 +29,7 @@ where
|
||||
const SELF_CLOSING: bool = false;
|
||||
const ESCAPE_CHILDREN: bool = true;
|
||||
const TAG: &'static str = "";
|
||||
const NAMESPACE: Option<&'static str> = None;
|
||||
|
||||
fn tag(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
|
||||
@@ -72,6 +72,7 @@ macro_rules! html_element_inner {
|
||||
const TAG: &'static str = stringify!($tag);
|
||||
const SELF_CLOSING: bool = false;
|
||||
const ESCAPE_CHILDREN: bool = $escape;
|
||||
const NAMESPACE: Option<&'static str> = None;
|
||||
|
||||
#[inline(always)]
|
||||
fn tag(&self) -> &str {
|
||||
@@ -170,6 +171,7 @@ macro_rules! html_self_closing_elements {
|
||||
const TAG: &'static str = stringify!($tag);
|
||||
const SELF_CLOSING: bool = true;
|
||||
const ESCAPE_CHILDREN: bool = $escape;
|
||||
const NAMESPACE: Option<&'static str> = None;
|
||||
|
||||
#[inline(always)]
|
||||
fn tag(&self) -> &str {
|
||||
|
||||
@@ -138,6 +138,8 @@ pub trait ElementType: Send {
|
||||
/// like `<style>` and `<script>`, which include other languages that should not use HTML
|
||||
/// entity escaping.
|
||||
const ESCAPE_CHILDREN: bool;
|
||||
/// The element's namespace, if it is not HTML.
|
||||
const NAMESPACE: Option<&'static str>;
|
||||
|
||||
/// The element's tag.
|
||||
fn tag(&self) -> &str;
|
||||
@@ -177,7 +179,7 @@ where
|
||||
}
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
let el = Rndr::create_element(self.tag.tag());
|
||||
let el = Rndr::create_element(self.tag.tag(), E::NAMESPACE);
|
||||
|
||||
let attrs = self.attributes.build(&el);
|
||||
let children = if E::SELF_CLOSING {
|
||||
@@ -350,7 +352,7 @@ where
|
||||
let attrs = self.attributes.hydrate::<FROM_SERVER>(&el);
|
||||
|
||||
// hydrate children
|
||||
let children = if !Ch::EXISTS {
|
||||
let children = if !Ch::EXISTS || !E::ESCAPE_CHILDREN {
|
||||
None
|
||||
} else {
|
||||
position.set(Position::FirstChild);
|
||||
|
||||
@@ -101,6 +101,7 @@ macro_rules! mathml_elements {
|
||||
const TAG: &'static str = stringify!($tag);
|
||||
const SELF_CLOSING: bool = false;
|
||||
const ESCAPE_CHILDREN: bool = true;
|
||||
const NAMESPACE: Option<&'static str> = Some("http://www.w3.org/1998/Math/MathML");
|
||||
|
||||
#[inline(always)]
|
||||
fn tag(&self) -> &str {
|
||||
|
||||
515
tachys/src/reactive_graph/bind.rs
Normal file
515
tachys/src/reactive_graph/bind.rs
Normal file
@@ -0,0 +1,515 @@
|
||||
use crate::{
|
||||
dom::{event_target_checked, event_target_value},
|
||||
html::{
|
||||
attribute::{Attribute, AttributeKey, AttributeValue, NextAttribute},
|
||||
event::{change, input, on},
|
||||
property::{prop, IntoProperty},
|
||||
},
|
||||
prelude::AddAnyAttr,
|
||||
renderer::{types::Element, RemoveEventHandler},
|
||||
view::{Position, ToTemplate},
|
||||
};
|
||||
use reactive_graph::{
|
||||
signal::{ReadSignal, RwSignal, WriteSignal},
|
||||
traits::{Get, Update},
|
||||
wrappers::read::Signal,
|
||||
};
|
||||
use send_wrapper::SendWrapper;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
/// `group` attribute used for radio inputs with `bind`.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Group;
|
||||
|
||||
impl AttributeKey for Group {
|
||||
const KEY: &'static str = "group";
|
||||
}
|
||||
|
||||
/// Adds a two-way binding to the element, which adds an attribute and an event listener to the
|
||||
/// element when the element is created or hydrated.
|
||||
pub trait BindAttribute<Key, Sig, T>
|
||||
where
|
||||
Key: AttributeKey,
|
||||
Sig: IntoSplitSignal<Value = T>,
|
||||
T: FromEventTarget + AttributeValue + 'static,
|
||||
{
|
||||
/// The type of the element with the two-way binding added.
|
||||
type Output;
|
||||
|
||||
/// Adds a two-way binding to the element, which adds an attribute and an event listener to the
|
||||
/// element when the element is created or hydrated.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// ```
|
||||
/// // You can use `RwSignal`s
|
||||
/// let is_awesome = RwSignal::new(true);
|
||||
///
|
||||
/// // And you can use split signals
|
||||
/// let (text, set_text) = signal("Hello world".to_string());
|
||||
///
|
||||
/// // Use `Checked` and a `bool` signal for a checkbox
|
||||
/// checkbox_element.bind(Checked, is_awesome);
|
||||
///
|
||||
/// // Use `Group` and `String` for radio inputs
|
||||
/// radio_element.bind(Group, (text, set_text));
|
||||
///
|
||||
/// // Use `Value` and `String` for everything else
|
||||
/// input_element.bind(Value, (text, set_text));
|
||||
/// ```
|
||||
///
|
||||
/// Depending on the input different events are listened to.
|
||||
/// - `<input type="checkbox">`, `<input type="radio">` and `<select>` use the `change` event;
|
||||
/// - `<input>` with the rest of the types and `<textarea>` elements use the `input` event;
|
||||
fn bind(self, key: Key, signal: Sig) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<V, Key, Sig, T> BindAttribute<Key, Sig, T> for V
|
||||
where
|
||||
V: AddAnyAttr,
|
||||
Key: AttributeKey,
|
||||
Sig: IntoSplitSignal<Value = T>,
|
||||
T: FromEventTarget + AttributeValue + PartialEq + Sync + 'static,
|
||||
Signal<BoolOrT<T>>: IntoProperty,
|
||||
<Sig as IntoSplitSignal>::Read:
|
||||
Get<Value = T> + Send + Sync + Clone + 'static,
|
||||
<Sig as IntoSplitSignal>::Write: Send + Clone + 'static,
|
||||
Element: GetValue<T>,
|
||||
{
|
||||
type Output = <Self as AddAnyAttr>::Output<
|
||||
Bind<
|
||||
Key,
|
||||
T,
|
||||
<Sig as IntoSplitSignal>::Read,
|
||||
<Sig as IntoSplitSignal>::Write,
|
||||
>,
|
||||
>;
|
||||
|
||||
fn bind(self, key: Key, signal: Sig) -> Self::Output {
|
||||
self.add_any_attr(bind(key, signal))
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a two-way binding to the element, which adds an attribute and an event listener to the
|
||||
/// element when the element is created or hydrated.
|
||||
#[inline(always)]
|
||||
pub fn bind<Key, Sig, T>(
|
||||
key: Key,
|
||||
signal: Sig,
|
||||
) -> Bind<Key, T, <Sig as IntoSplitSignal>::Read, <Sig as IntoSplitSignal>::Write>
|
||||
where
|
||||
Key: AttributeKey,
|
||||
Sig: IntoSplitSignal<Value = T>,
|
||||
T: FromEventTarget + AttributeValue + 'static,
|
||||
<Sig as IntoSplitSignal>::Read: Get<Value = T> + Clone + 'static,
|
||||
<Sig as IntoSplitSignal>::Write: Send + Clone + 'static,
|
||||
{
|
||||
let (read_signal, write_signal) = signal.into_split_signal();
|
||||
|
||||
Bind {
|
||||
key,
|
||||
read_signal,
|
||||
write_signal,
|
||||
}
|
||||
}
|
||||
|
||||
/// Two-way binding of an attribute and an event listener
|
||||
#[derive(Debug)]
|
||||
pub struct Bind<Key, T, R, W>
|
||||
where
|
||||
Key: AttributeKey,
|
||||
T: FromEventTarget + AttributeValue + 'static,
|
||||
R: Get<Value = T> + Clone + 'static,
|
||||
W: Update<Value = T>,
|
||||
{
|
||||
key: Key,
|
||||
read_signal: R,
|
||||
write_signal: W,
|
||||
}
|
||||
|
||||
impl<Key, T, R, W> Clone for Bind<Key, T, R, W>
|
||||
where
|
||||
Key: AttributeKey,
|
||||
T: FromEventTarget + AttributeValue + 'static,
|
||||
R: Get<Value = T> + Clone + 'static,
|
||||
W: Update<Value = T> + Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
key: self.key.clone(),
|
||||
read_signal: self.read_signal.clone(),
|
||||
write_signal: self.write_signal.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Key, T, R, W> Bind<Key, T, R, W>
|
||||
where
|
||||
Key: AttributeKey,
|
||||
T: FromEventTarget + AttributeValue + PartialEq + Sync + 'static,
|
||||
R: Get<Value = T> + Clone + Send + Sync + 'static,
|
||||
W: Update<Value = T> + Clone + 'static,
|
||||
Element: ChangeEvent + GetValue<T>,
|
||||
{
|
||||
/// Attaches the event listener that updates the signal value to the element.
|
||||
pub fn attach(self, el: &Element) -> RemoveEventHandler<Element> {
|
||||
el.attach_change_event::<T, W>(Key::KEY, self.write_signal.clone())
|
||||
}
|
||||
|
||||
/// Creates the signal to update the value of the attribute. This signal is different
|
||||
/// when using a `"group"` attribute
|
||||
pub fn read_signal(&self, el: &Element) -> Signal<BoolOrT<T>> {
|
||||
let read_signal = self.read_signal.clone();
|
||||
|
||||
if Key::KEY == "group" {
|
||||
let el = SendWrapper::new(el.clone());
|
||||
|
||||
Signal::derive(move || {
|
||||
BoolOrT::Bool(el.get_value() == read_signal.get())
|
||||
})
|
||||
} else {
|
||||
Signal::derive(move || BoolOrT::T(read_signal.get()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the key of the attribute. If the key is `"group"` it returns `"checked"`, otherwise
|
||||
/// the one which was provided originally.
|
||||
pub fn key(&self) -> &'static str {
|
||||
if Key::KEY == "group" {
|
||||
"checked"
|
||||
} else {
|
||||
Key::KEY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Key, T, R, W> Attribute for Bind<Key, T, R, W>
|
||||
where
|
||||
Key: AttributeKey,
|
||||
T: FromEventTarget + AttributeValue + PartialEq + Sync + 'static,
|
||||
R: Get<Value = T> + Clone + Send + Sync + 'static,
|
||||
Signal<BoolOrT<T>>: IntoProperty,
|
||||
W: Update<Value = T> + Clone + Send + 'static,
|
||||
Element: ChangeEvent + GetValue<T>,
|
||||
{
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
type State = (
|
||||
<Signal<BoolOrT<T>> as IntoProperty>::State,
|
||||
(Element, Option<RemoveEventHandler<Element>>),
|
||||
);
|
||||
type AsyncOutput = Self;
|
||||
type Cloneable = Bind<Key, T, R, W>;
|
||||
type CloneableOwned = Bind<Key, T, R, W>;
|
||||
|
||||
fn html_len(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn to_html(
|
||||
self,
|
||||
_buf: &mut String,
|
||||
_class: &mut String,
|
||||
_style: &mut String,
|
||||
_inner_html: &mut String,
|
||||
) {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn hydrate<const FROM_SERVER: bool>(self, el: &Element) -> Self::State {
|
||||
let signal = self.read_signal(el);
|
||||
let attr_state = prop(self.key(), signal).hydrate::<FROM_SERVER>(el);
|
||||
|
||||
let cleanup = self.attach(el);
|
||||
|
||||
(attr_state, (el.clone(), Some(cleanup)))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn build(self, el: &Element) -> Self::State {
|
||||
let signal = self.read_signal(el);
|
||||
let attr_state = prop(self.key(), signal).build(el);
|
||||
|
||||
let cleanup = self.attach(el);
|
||||
|
||||
(attr_state, (el.clone(), Some(cleanup)))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
let (attr_state, (el, prev_cleanup)) = state;
|
||||
|
||||
let signal = self.read_signal(el);
|
||||
prop(self.key(), signal).rebuild(attr_state);
|
||||
|
||||
if let Some(prev) = prev_cleanup.take() {
|
||||
(prev.into_inner())(el);
|
||||
}
|
||||
*prev_cleanup = Some(self.attach(el));
|
||||
}
|
||||
|
||||
fn into_cloneable(self) -> Self::Cloneable {
|
||||
self.into_cloneable_owned()
|
||||
}
|
||||
|
||||
fn into_cloneable_owned(self) -> Self::CloneableOwned {
|
||||
self
|
||||
}
|
||||
|
||||
fn dry_resolve(&mut self) {}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Key, T, R, W> NextAttribute for Bind<Key, T, R, W>
|
||||
where
|
||||
Key: AttributeKey,
|
||||
T: FromEventTarget + AttributeValue + PartialEq + Sync + 'static,
|
||||
R: Get<Value = T> + Clone + Send + Sync + 'static,
|
||||
Signal<BoolOrT<T>>: IntoProperty,
|
||||
W: Update<Value = T> + Clone + Send + 'static,
|
||||
Element: ChangeEvent + GetValue<T>,
|
||||
{
|
||||
type Output<NewAttr: Attribute> = (Self, NewAttr);
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
new_attr: NewAttr,
|
||||
) -> Self::Output<NewAttr> {
|
||||
(self, new_attr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Key, T, R, W> ToTemplate for Bind<Key, T, R, W>
|
||||
where
|
||||
Key: AttributeKey,
|
||||
T: FromEventTarget + AttributeValue + 'static,
|
||||
R: Get<Value = T> + Clone + 'static,
|
||||
W: Update<Value = T> + Clone,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn to_template(
|
||||
_buf: &mut String,
|
||||
_class: &mut String,
|
||||
_style: &mut String,
|
||||
_inner_html: &mut String,
|
||||
_position: &mut Position,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Splits a combined signal into its read and write parts.
|
||||
///
|
||||
/// This allows you to either provide a `RwSignal` or a tuple `(ReadSignal, WriteSignal)`.
|
||||
pub trait IntoSplitSignal {
|
||||
/// The actual contained value of the signal
|
||||
type Value;
|
||||
/// The read part of the signal
|
||||
type Read: Get<Value = Self::Value>;
|
||||
/// The write part of the signal
|
||||
type Write: Update<Value = Self::Value>;
|
||||
/// Splits a combined signal into its read and write parts.
|
||||
fn into_split_signal(self) -> (Self::Read, Self::Write);
|
||||
}
|
||||
|
||||
impl<T> IntoSplitSignal for RwSignal<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
ReadSignal<T>: Get<Value = T>,
|
||||
{
|
||||
type Value = T;
|
||||
type Read = ReadSignal<T>;
|
||||
type Write = WriteSignal<T>;
|
||||
|
||||
fn into_split_signal(self) -> (ReadSignal<T>, WriteSignal<T>) {
|
||||
self.split()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, R, W> IntoSplitSignal for (R, W)
|
||||
where
|
||||
R: Get<Value = T>,
|
||||
W: Update<Value = T>,
|
||||
{
|
||||
type Value = T;
|
||||
type Read = R;
|
||||
type Write = W;
|
||||
|
||||
fn into_split_signal(self) -> (Self::Read, Self::Write) {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns self from an event target.
|
||||
pub trait FromEventTarget {
|
||||
/// Returns self from an event target.
|
||||
fn from_event_target(evt: &web_sys::Event) -> Self;
|
||||
}
|
||||
|
||||
impl FromEventTarget for bool {
|
||||
fn from_event_target(evt: &web_sys::Event) -> Self {
|
||||
event_target_checked(evt)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromEventTarget for String {
|
||||
fn from_event_target(evt: &web_sys::Event) -> Self {
|
||||
event_target_value(evt)
|
||||
}
|
||||
}
|
||||
|
||||
/// Attaches the appropriate change event listener to the element.
|
||||
/// - `<input>` with text types and `<textarea>` elements use the `input` event;
|
||||
/// - `<input type="checkbox">`, `<input type="radio">` and `<select>` use the `change` event;
|
||||
pub trait ChangeEvent {
|
||||
/// Attaches the appropriate change event listener to the element.
|
||||
fn attach_change_event<T, W>(
|
||||
&self,
|
||||
key: &str,
|
||||
write_signal: W,
|
||||
) -> RemoveEventHandler<Self>
|
||||
where
|
||||
T: FromEventTarget + AttributeValue + 'static,
|
||||
W: Update<Value = T> + 'static,
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl ChangeEvent for web_sys::Element {
|
||||
fn attach_change_event<T, W>(
|
||||
&self,
|
||||
key: &str,
|
||||
write_signal: W,
|
||||
) -> RemoveEventHandler<Self>
|
||||
where
|
||||
T: FromEventTarget + AttributeValue + 'static,
|
||||
W: Update<Value = T> + 'static,
|
||||
{
|
||||
if key == "group" {
|
||||
let handler = move |evt| {
|
||||
let checked = event_target_checked(&evt);
|
||||
if checked {
|
||||
write_signal
|
||||
.try_update(|v| *v = T::from_event_target(&evt));
|
||||
}
|
||||
};
|
||||
|
||||
on::<_, _>(change, handler).attach(self)
|
||||
} else {
|
||||
let handler = move |evt| {
|
||||
write_signal.try_update(|v| *v = T::from_event_target(&evt));
|
||||
};
|
||||
|
||||
if key == "checked" || self.tag_name() == "SELECT" {
|
||||
on::<_, _>(change, handler).attach(self)
|
||||
} else {
|
||||
on::<_, _>(input, handler).attach(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the value attribute of an element (input).
|
||||
/// Reads `value` if `T` is `String` and `checked` if `T` is `bool`.
|
||||
pub trait GetValue<T> {
|
||||
/// Get the value attribute of an element (input).
|
||||
fn get_value(&self) -> T;
|
||||
}
|
||||
|
||||
impl GetValue<String> for web_sys::Element {
|
||||
fn get_value(&self) -> String {
|
||||
self.get_attribute("value").unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl GetValue<bool> for web_sys::Element {
|
||||
fn get_value(&self) -> bool {
|
||||
self.get_attribute("checked").unwrap_or_default() == "true"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
/// Bool or a type. Needed to make the `group` attribute work. It is decided at runtime
|
||||
/// if the derived signal value is a bool or a type `T`.
|
||||
pub enum BoolOrT<T> {
|
||||
/// We have definitely a boolean value for the `group` attribute
|
||||
Bool(bool),
|
||||
/// Standard case with some type `T`
|
||||
T(T),
|
||||
}
|
||||
|
||||
impl<T> IntoProperty for BoolOrT<T>
|
||||
where
|
||||
T: IntoProperty<State = (Element, JsValue)>
|
||||
+ Into<JsValue>
|
||||
+ Clone
|
||||
+ 'static,
|
||||
{
|
||||
type State = (Element, JsValue);
|
||||
type Cloneable = Self;
|
||||
type CloneableOwned = Self;
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
el: &Element,
|
||||
key: &str,
|
||||
) -> Self::State {
|
||||
match self.clone() {
|
||||
Self::T(s) => {
|
||||
s.hydrate::<FROM_SERVER>(el, key);
|
||||
}
|
||||
Self::Bool(b) => {
|
||||
<bool as IntoProperty>::hydrate::<FROM_SERVER>(b, el, key);
|
||||
}
|
||||
};
|
||||
|
||||
(el.clone(), self.into())
|
||||
}
|
||||
|
||||
fn build(self, el: &Element, key: &str) -> Self::State {
|
||||
match self.clone() {
|
||||
Self::T(s) => {
|
||||
s.build(el, key);
|
||||
}
|
||||
Self::Bool(b) => {
|
||||
<bool as IntoProperty>::build(b, el, key);
|
||||
}
|
||||
}
|
||||
|
||||
(el.clone(), self.into())
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State, key: &str) {
|
||||
let (el, prev) = state;
|
||||
|
||||
match self {
|
||||
Self::T(s) => s.rebuild(&mut (el.clone(), prev.clone()), key),
|
||||
Self::Bool(b) => <bool as IntoProperty>::rebuild(
|
||||
b,
|
||||
&mut (el.clone(), prev.clone()),
|
||||
key,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_cloneable(self) -> Self::Cloneable {
|
||||
self
|
||||
}
|
||||
|
||||
fn into_cloneable_owned(self) -> Self::CloneableOwned {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<BoolOrT<T>> for JsValue
|
||||
where
|
||||
T: Into<JsValue>,
|
||||
{
|
||||
fn from(value: BoolOrT<T>) -> Self {
|
||||
match value {
|
||||
BoolOrT::Bool(b) => b.into(),
|
||||
BoolOrT::T(t) => t.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
use super::{ReactiveFunction, SharedReactiveFunction, Suspend};
|
||||
use crate::{html::class::IntoClass, renderer::Rndr};
|
||||
use any_spawner::Executor;
|
||||
use futures::FutureExt;
|
||||
use reactive_graph::{effect::RenderEffect, signal::guards::ReadGuard};
|
||||
use std::{
|
||||
@@ -738,7 +737,7 @@ where
|
||||
) -> Self::State {
|
||||
let el = el.to_owned();
|
||||
let state = Rc::new(RefCell::new(None));
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(&state);
|
||||
async move {
|
||||
*state.borrow_mut() =
|
||||
@@ -752,7 +751,7 @@ where
|
||||
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
|
||||
let el = el.to_owned();
|
||||
let state = Rc::new(RefCell::new(None));
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(&state);
|
||||
async move {
|
||||
*state.borrow_mut() = Some(self.inner.await.build(&el));
|
||||
@@ -763,7 +762,7 @@ where
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(state);
|
||||
async move {
|
||||
let value = self.inner.await;
|
||||
|
||||
@@ -8,7 +8,6 @@ use crate::{
|
||||
RenderHtml, ToTemplate,
|
||||
},
|
||||
};
|
||||
use any_spawner::Executor;
|
||||
use reactive_graph::effect::RenderEffect;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
@@ -17,6 +16,8 @@ use std::{
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
/// Types for two way data binding.
|
||||
pub mod bind;
|
||||
mod class;
|
||||
mod inner_html;
|
||||
/// Provides a reactive [`NodeRef`](node_ref::NodeRef) type.
|
||||
@@ -25,6 +26,7 @@ mod owned;
|
||||
mod property;
|
||||
mod style;
|
||||
mod suspense;
|
||||
|
||||
pub use owned::*;
|
||||
pub use suspense::*;
|
||||
|
||||
@@ -391,7 +393,7 @@ where
|
||||
let key = key.to_owned();
|
||||
let el = el.to_owned();
|
||||
let state = Rc::new(RefCell::new(None));
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(&state);
|
||||
async move {
|
||||
*state.borrow_mut() =
|
||||
@@ -410,7 +412,7 @@ where
|
||||
let key = key.to_owned();
|
||||
let el = el.to_owned();
|
||||
let state = Rc::new(RefCell::new(None));
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(&state);
|
||||
async move {
|
||||
*state.borrow_mut() = Some(self.inner.await.build(&el, &key));
|
||||
@@ -422,7 +424,7 @@ where
|
||||
|
||||
fn rebuild(self, key: &str, state: &mut Self::State) {
|
||||
let key = key.to_owned();
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(state);
|
||||
async move {
|
||||
let value = self.inner.await;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use super::{ReactiveFunction, SharedReactiveFunction, Suspend};
|
||||
use crate::{html::style::IntoStyle, renderer::Rndr};
|
||||
use any_spawner::Executor;
|
||||
use futures::FutureExt;
|
||||
use reactive_graph::effect::RenderEffect;
|
||||
use std::{borrow::Cow, cell::RefCell, future::Future, rc::Rc};
|
||||
@@ -463,7 +462,7 @@ where
|
||||
) -> Self::State {
|
||||
let el = el.to_owned();
|
||||
let state = Rc::new(RefCell::new(None));
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(&state);
|
||||
async move {
|
||||
*state.borrow_mut() =
|
||||
@@ -477,7 +476,7 @@ where
|
||||
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
|
||||
let el = el.to_owned();
|
||||
let state = Rc::new(RefCell::new(None));
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(&state);
|
||||
async move {
|
||||
*state.borrow_mut() = Some(self.inner.await.build(&el));
|
||||
@@ -488,7 +487,7 @@ where
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(state);
|
||||
async move {
|
||||
let value = self.inner.await;
|
||||
|
||||
@@ -8,7 +8,10 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use any_spawner::Executor;
|
||||
use futures::{select, FutureExt};
|
||||
use futures::{
|
||||
future::{AbortHandle, Abortable},
|
||||
select, FutureExt,
|
||||
};
|
||||
use or_poisoned::OrPoisoned;
|
||||
use reactive_graph::{
|
||||
computed::{
|
||||
@@ -19,7 +22,7 @@ use reactive_graph::{
|
||||
AnySource, AnySubscriber, Observer, ReactiveNode, Source, Subscriber,
|
||||
ToAnySubscriber, WithObserver,
|
||||
},
|
||||
owner::{provide_context, use_context},
|
||||
owner::{on_cleanup, provide_context, use_context},
|
||||
};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
@@ -164,15 +167,20 @@ where
|
||||
{
|
||||
type State = SuspendState<Fut::Output>;
|
||||
|
||||
// TODO cancelation if it fires multiple times
|
||||
fn build(self) -> Self::State {
|
||||
let Self { subscriber, inner } = self;
|
||||
|
||||
// create a Future that will be aborted on on_cleanup
|
||||
// this prevents trying to access signals or other resources inside the Suspend, after the
|
||||
// await, if they have already been cleaned up
|
||||
let (abort_handle, abort_registration) = AbortHandle::new_pair();
|
||||
let mut fut = Box::pin(Abortable::new(inner, abort_registration));
|
||||
on_cleanup(move || abort_handle.abort());
|
||||
|
||||
// poll the future once immediately
|
||||
// if it's already available, start in the ready state
|
||||
// otherwise, start with the fallback
|
||||
let mut fut = Box::pin(inner);
|
||||
let initial = fut.as_mut().now_or_never();
|
||||
let initial = fut.as_mut().now_or_never().and_then(Result::ok);
|
||||
let initially_pending = initial.is_none();
|
||||
let inner = Rc::new(RefCell::new(initial.build()));
|
||||
|
||||
@@ -184,7 +192,7 @@ where
|
||||
// spawning immediately means that our now_or_never poll result isn't lost
|
||||
// if it wasn't pending at first, we don't need to poll the Future again
|
||||
if initially_pending {
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(&inner);
|
||||
async move {
|
||||
let _guard = error_hook.as_ref().map(|hook| {
|
||||
@@ -193,7 +201,10 @@ where
|
||||
|
||||
let value = fut.as_mut().await;
|
||||
drop(id);
|
||||
Some(value).rebuild(&mut *state.borrow_mut());
|
||||
|
||||
if let Ok(value) = value {
|
||||
Some(value).rebuild(&mut *state.borrow_mut());
|
||||
}
|
||||
|
||||
subscriber.forward();
|
||||
}
|
||||
@@ -208,13 +219,19 @@ where
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
let Self { subscriber, inner } = self;
|
||||
|
||||
// create a Future that will be aborted on on_cleanup
|
||||
// this prevents trying to access signals or other resources inside the Suspend, after the
|
||||
// await, if they have already been cleaned up
|
||||
let (abort_handle, abort_registration) = AbortHandle::new_pair();
|
||||
let fut = Abortable::new(inner, abort_registration);
|
||||
on_cleanup(move || abort_handle.abort());
|
||||
|
||||
// get a unique ID if there's a SuspenseContext
|
||||
let fut = inner;
|
||||
let id = use_context::<SuspenseContext>().map(|sc| sc.task_id());
|
||||
let error_hook = use_context::<Arc<dyn ErrorHook>>();
|
||||
|
||||
// spawn the future, and rebuild the state when it resolves
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(&state.inner);
|
||||
async move {
|
||||
let _guard = error_hook
|
||||
@@ -223,11 +240,14 @@ where
|
||||
|
||||
let value = fut.await;
|
||||
drop(id);
|
||||
|
||||
// waiting a tick here allows Suspense to remount if necessary, which prevents some
|
||||
// edge cases in which a rebuild can't happen while unmounted because the DOM node
|
||||
// has no parent
|
||||
any_spawner::Executor::tick().await;
|
||||
Some(value).rebuild(&mut *state.borrow_mut());
|
||||
Executor::tick().await;
|
||||
if let Ok(value) = value {
|
||||
Some(value).rebuild(&mut *state.borrow_mut());
|
||||
}
|
||||
|
||||
subscriber.forward();
|
||||
}
|
||||
@@ -367,11 +387,17 @@ where
|
||||
) -> Self::State {
|
||||
let Self { subscriber, inner } = self;
|
||||
|
||||
// create a Future that will be aborted on on_cleanup
|
||||
// this prevents trying to access signals or other resources inside the Suspend, after the
|
||||
// await, if they have already been cleaned up
|
||||
let (abort_handle, abort_registration) = AbortHandle::new_pair();
|
||||
let mut fut = Box::pin(Abortable::new(inner, abort_registration));
|
||||
on_cleanup(move || abort_handle.abort());
|
||||
|
||||
// poll the future once immediately
|
||||
// if it's already available, start in the ready state
|
||||
// otherwise, start with the fallback
|
||||
let mut fut = Box::pin(inner);
|
||||
let initial = fut.as_mut().now_or_never();
|
||||
let initial = fut.as_mut().now_or_never().and_then(Result::ok);
|
||||
let initially_pending = initial.is_none();
|
||||
let inner = Rc::new(RefCell::new(
|
||||
initial.hydrate::<FROM_SERVER>(cursor, position),
|
||||
@@ -385,7 +411,7 @@ where
|
||||
// spawning immediately means that our now_or_never poll result isn't lost
|
||||
// if it wasn't pending at first, we don't need to poll the Future again
|
||||
if initially_pending {
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(&inner);
|
||||
async move {
|
||||
let _guard = error_hook.as_ref().map(|hook| {
|
||||
@@ -394,7 +420,10 @@ where
|
||||
|
||||
let value = fut.as_mut().await;
|
||||
drop(id);
|
||||
Some(value).rebuild(&mut *state.borrow_mut());
|
||||
|
||||
if let Ok(value) = value {
|
||||
Some(value).rebuild(&mut *state.borrow_mut());
|
||||
}
|
||||
|
||||
subscriber.forward();
|
||||
}
|
||||
|
||||
@@ -33,8 +33,17 @@ impl Dom {
|
||||
intern(text)
|
||||
}
|
||||
|
||||
pub fn create_element(tag: &str) -> Element {
|
||||
document().create_element(tag).unwrap()
|
||||
pub fn create_element(tag: &str, namespace: Option<&str>) -> Element {
|
||||
if let Some(namespace) = namespace {
|
||||
document()
|
||||
.create_element_ns(
|
||||
Some(Self::intern(namespace)),
|
||||
Self::intern(tag),
|
||||
)
|
||||
.unwrap()
|
||||
} else {
|
||||
document().create_element(Self::intern(tag)).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace"))]
|
||||
|
||||
@@ -69,6 +69,7 @@ macro_rules! svg_elements {
|
||||
const TAG: &'static str = stringify!($tag);
|
||||
const SELF_CLOSING: bool = false;
|
||||
const ESCAPE_CHILDREN: bool = true;
|
||||
const NAMESPACE: Option<&'static str> = Some("http://www.w3.org/2000/svg");
|
||||
|
||||
#[inline(always)]
|
||||
fn tag(&self) -> &str {
|
||||
|
||||
@@ -31,11 +31,13 @@ impl RenderHtml for () {
|
||||
self,
|
||||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
_escape: bool,
|
||||
escape: bool,
|
||||
_mark_branches: bool,
|
||||
) {
|
||||
buf.push_str("<!>");
|
||||
*position = Position::NextChild;
|
||||
if escape {
|
||||
buf.push_str("<!>");
|
||||
*position = Position::NextChild;
|
||||
}
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
|
||||
Reference in New Issue
Block a user