Compare commits

..

3 Commits
2086 ... 3024

Author SHA1 Message Date
Greg Johnston
565a901c08 undo rename so I don't have to break all the rest 2024-09-26 16:56:48 -04:00
Greg Johnston
f626ac26e0 change: rename leptos::spawn to leptos::task and reexport scoped spawners 2024-09-26 16:25:59 -04:00
Greg Johnston
fb3159d093 fix: properly scope Suspend rebuilds within ownership tree (closes #3024) 2024-09-26 16:14:36 -04:00
95 changed files with 417 additions and 1110 deletions

View File

@@ -40,36 +40,36 @@ members = [
exclude = ["benchmarks", "examples", "projects"]
[workspace.package]
version = "0.7.0-gamma"
version = "0.7.0-beta6"
edition = "2021"
rust-version = "1.76"
[workspace.dependencies]
throw_error = { path = "./any_error/", version = "0.2.0-gamma" }
throw_error = { path = "./any_error/", version = "0.2.0-beta6" }
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-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" }
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" }
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-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" }
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" }
[profile.release]
codegen-units = 1

View File

@@ -1,6 +1,6 @@
[package]
name = "throw_error"
version = "0.2.0-gamma"
version = "0.2.0-beta6"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -1,7 +1,7 @@
use counter::*;
use leptos::mount::mount_to;
use leptos::prelude::*;
use leptos::task::tick;
use leptos::spawn::tick;
use wasm_bindgen::JsCast;
use wasm_bindgen_test::*;

View File

@@ -1,4 +1,4 @@
use leptos::prelude::*;
use leptos::{prelude::*, reactive_graph::actions::Action};
use leptos_router::{
components::{FlatRoutes, Route, Router, A},
StaticSegment,

View File

@@ -1,5 +1,5 @@
use counter_without_macros::counter;
use leptos::{prelude::*, task::tick};
use leptos::{prelude::*, spawn::tick};
use pretty_assertions::assert_eq;
use wasm_bindgen::JsCast;
use wasm_bindgen_test::*;

View File

@@ -4,7 +4,7 @@ use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
use counters::Counters;
use leptos::prelude::*;
use leptos::task::tick;
use leptos::spawn::tick;
use web_sys::HtmlElement;
#[wasm_bindgen_test]

View File

@@ -1,5 +1,5 @@
use directives::App;
use leptos::{prelude::*, task::tick};
use leptos::{prelude::*, spawn::tick};
use wasm_bindgen::JsCast;
use wasm_bindgen_test::*;
use web_sys::HtmlElement;

View File

@@ -17,7 +17,7 @@ leptos = { path = "../../leptos", features = [
leptos_router = { path = "../../router" }
server_fn = { path = "../../server_fn", features = ["serde-lite"] }
leptos_axum = { path = "../../integrations/axum", features = [
"dont-use-islands-router",
"islands-router",
], optional = true }
log = "0.4.22"
serde = { version = "1.0", features = ["derive"] }

View File

@@ -1,14 +1,19 @@
# Work in Progress
# Leptos Todo App Sqlite with Axum
This example is something I wrote on a long layover in the Orlando airport in July. (It was really hot!)
This example creates a basic todo app with an Axum backend that uses Leptos' server functions to call sqlx from the client and seamlessly run it on the server.
It is the culmination of a couple years of thinking and working toward being able to do this, which you can see
described pretty well in the pinned roadmap issue (#1830) and its discussion of different modes of client-side
routing when you use islands.
## Getting Started
This uses *only* server rendering, with no actual islands, but still maintains client-side state across page navigations.
It does this by building on the fact that we now have a statically-typed view tree to do pretty smart updates with
new HTML from the client, with extremely minimal diffing.
See the [Examples README](../README.md) for setup and run instructions.
The demo itself works, but the feature that supports it is incomplete. A couple people have accidentally
used it and broken their applications in ways they don't understand, so I've renamed the feature to `dont-use-islands-router`.
## E2E Testing
See the [E2E README](./e2e/README.md) for more information about the testing strategy.
## Rendering
See the [SSR Notes](../SSR_NOTES.md) for more information about Server Side Rendering.
## Quick Start
Run `cargo leptos watch` to run this example.

View File

@@ -1,5 +1,5 @@
use js_framework_benchmark_leptos::*;
use leptos::{prelude::*, task::tick};
use leptos::{prelude::*, spawn::tick};
use wasm_bindgen::JsCast;
use wasm_bindgen_test::*;

View File

@@ -3,7 +3,7 @@ use wasm_bindgen::JsCast;
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
use leptos::task::tick;
use leptos::spawn::tick;
use leptos::{leptos_dom::helpers::document, mount::mount_to};
use web_sys::HtmlButtonElement;

View File

@@ -1,6 +1,6 @@
use futures::StreamExt;
use http::Method;
use leptos::{html::Input, prelude::*, task::spawn_local};
use leptos::{html::Input, prelude::*, spawn::spawn_local};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use server_fn::{
client::{browser::BrowserClient, Client},

View File

@@ -319,7 +319,10 @@ pub fn Todo(todo: Todo) -> impl IntoView {
node_ref=todo_input
class="toggle"
type="checkbox"
bind:checked=todo.completed
prop:checked=move || todo.completed.get()
on:input:target=move |ev| {
todo.completed.set(ev.target().checked());
}
/>
<label on:dblclick=move |_| {

View File

@@ -1,6 +1,6 @@
[package]
name = "hydration_context"
version = "0.2.0-gamma"
version = "0.2.0-beta6"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -33,7 +33,7 @@ once_cell = "1"
rustdoc-args = ["--generate-link-to-definition"]
[features]
dont-use-islands-router = []
islands-router = []
tracing = ["dep:tracing"]
[package.metadata.cargo-all-features]

View File

@@ -24,7 +24,7 @@ use leptos::{
config::LeptosOptions,
context::{provide_context, use_context},
prelude::expect_context,
reactive::{computed::ScopedFuture, owner::Owner},
reactive_graph::{computed::ScopedFuture, owner::Owner},
IntoView,
};
use leptos_integration_utils::{
@@ -749,7 +749,7 @@ where
IV: IntoView + 'static,
{
Box::pin(async move {
let app = if cfg!(feature = "dont-use-islands-router") {
let app = if cfg!(feature = "islands-router") {
app.to_html_stream_in_order_branching()
} else {
app.to_html_stream_in_order()

View File

@@ -39,7 +39,7 @@ tokio = { version = "1.39", features = ["net", "rt-multi-thread"] }
[features]
wasm = []
default = ["tokio/fs", "tokio/sync", "tower-http/fs", "tower/util"]
dont-use-islands-router = []
islands-router = []
tracing = ["dep:tracing"]
[package.metadata.docs.rs]

View File

@@ -53,7 +53,7 @@ use leptos::{
config::LeptosOptions,
context::{provide_context, use_context},
prelude::*,
reactive::{computed::ScopedFuture, owner::Owner},
reactive_graph::{computed::ScopedFuture, owner::Owner},
IntoView,
};
use leptos_integration_utils::{
@@ -784,7 +784,7 @@ where
_ = replace_blocks; // TODO
handle_response(additional_context, app_fn, |app, chunks| {
Box::pin(async move {
let app = if cfg!(feature = "dont-use-islands-router") {
let app = if cfg!(feature = "islands-router") {
app.to_html_stream_out_of_order_branching()
} else {
app.to_html_stream_out_of_order()
@@ -849,7 +849,7 @@ where
IV: IntoView + 'static,
{
handle_response(additional_context, app_fn, |app, chunks| {
let app = if cfg!(feature = "dont-use-islands-router") {
let app = if cfg!(feature = "islands-router") {
app.to_html_stream_in_order_branching()
} else {
app.to_html_stream_in_order()
@@ -1069,7 +1069,7 @@ where
{
handle_response(additional_context, app_fn, |app, chunks| {
Box::pin(async move {
let app = if cfg!(feature = "dont-use-islands-router") {
let app = if cfg!(feature = "islands-router") {
app.to_html_stream_in_order_branching()
} else {
app.to_html_stream_in_order()
@@ -1146,7 +1146,7 @@ where
IV: IntoView + 'static,
{
Box::pin(async move {
let app = if cfg!(feature = "dont-use-islands-router") {
let app = if cfg!(feature = "islands-router") {
app.to_html_stream_in_order_branching()
} else {
app.to_html_stream_in_order()

View File

@@ -2,7 +2,7 @@ use futures::{stream::once, Stream, StreamExt};
use hydration_context::{SharedContext, SsrSharedContext};
use leptos::{
nonce::use_nonce,
reactive::owner::{Owner, Sandboxed},
reactive_graph::owner::{Owner, Sandboxed},
IntoView,
};
use leptos_config::LeptosOptions;

View File

@@ -168,12 +168,13 @@ pub mod prelude {
pub use leptos_server::*;
pub use oco_ref::*;
pub use reactive_graph::{
actions::*, computed::*, effect::*, graph::untrack, owner::*,
signal::*, wrappers::read::*,
actions::*, computed::*, effect::*, owner::*, signal::*, untrack,
wrappers::read::*,
};
pub use server_fn::{self, ServerFnError};
pub use tachys::{
reactive_graph::{bind::BindAttribute, node_ref::*, Suspend},
self,
reactive_graph::{node_ref::*, Suspend},
view::template::ViewTemplate,
};
}
@@ -229,7 +230,6 @@ mod suspense_component;
pub mod text_prop;
mod transition;
pub use leptos_macro::*;
#[doc(inline)]
pub use server_fn;
#[doc(hidden)]
pub use typed_builder;
@@ -237,22 +237,16 @@ pub use typed_builder;
pub use typed_builder_macro;
mod into_view;
pub use into_view::IntoView;
#[doc(inline)]
pub use leptos_dom;
mod provider;
#[doc(inline)]
pub use tachys;
/// Tools to mount an application to the DOM, or to hydrate it from server-rendered HTML.
pub mod mount;
#[doc(inline)]
pub use leptos_config as config;
#[doc(inline)]
pub use oco_ref as oco;
mod from_form_data;
#[doc(inline)]
pub use either_of as either;
#[doc(inline)]
pub use reactive_graph as reactive;
pub use reactive_graph;
/// Provide and access data along the reactive graph, sharing data without directly passing arguments.
pub mod context {
@@ -260,22 +254,17 @@ pub mod context {
pub use reactive_graph::owner::{provide_context, use_context};
}
#[doc(inline)]
pub use leptos_server as server;
/// HTML attribute types.
#[doc(inline)]
pub use tachys::html::attribute as attr;
/// HTML element types.
#[doc(inline)]
pub use tachys::html::element as html;
/// HTML event types.
#[doc(no_inline)]
pub use tachys::html::event as ev;
/// MathML element types.
#[doc(inline)]
pub use tachys::mathml as math;
/// SVG element types.
#[doc(inline)]
pub use tachys::svg;
/// Utilities for simple isomorphic logging to the console or terminal.
@@ -283,7 +272,7 @@ pub mod logging {
pub use leptos_dom::{debug_warn, error, log, warn};
}
pub mod task {
pub mod spawn {
pub use any_spawner::Executor;
use std::future::Future;
@@ -301,7 +290,6 @@ pub mod task {
Executor::spawn_local(fut)
}
/// Waits until the next "tick" of the current async executor.
pub async fn tick() {
Executor::tick().await
}

View File

@@ -1,7 +1,7 @@
use crate::{children::TypedChildrenFn, mount, IntoView};
use leptos_dom::helpers::document;
use leptos_macro::component;
use reactive_graph::{effect::Effect, graph::untrack, owner::Owner};
use reactive_graph::{effect::Effect, owner::Owner, untrack};
use std::sync::Arc;
/// Renders components somewhere else in the DOM.

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_macro"
version = "0.7.0-gamma"
version = "0.7.0-beta6"
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"

View File

@@ -255,7 +255,7 @@ impl ToTokens for Model {
let body_name = unmodified_fn_name_from_fn_name(&body_name);
let body_expr = if is_island {
quote! {
::leptos::reactive::owner::Owner::with_hydration(move || {
::leptos::reactive_graph::owner::Owner::with_hydration(move || {
#body_name(#prop_names)
})
}
@@ -266,7 +266,7 @@ impl ToTokens for Model {
};
let component = quote! {
::leptos::prelude::untrack(
::leptos::reactive_graph::untrack(
move || {
#tracing_guard_expr
#tracing_props_expr
@@ -280,7 +280,7 @@ impl ToTokens for Model {
let hydrate_fn_name = hydrate_fn_name.as_ref().unwrap();
quote! {
{
if ::leptos::reactive::owner::Owner::current_shared_context()
if ::leptos::reactive_graph::owner::Owner::current_shared_context()
.map(|sc| sc.get_is_hydrating())
.unwrap_or(false) {
::leptos::either::Either::Left(
@@ -316,9 +316,9 @@ impl ToTokens for Model {
quote! {
use leptos::tachys::view::any_view::IntoAny;
let children = Box::new(|| {
let sc = ::leptos::reactive::owner::Owner::current_shared_context().unwrap();
let sc = ::leptos::reactive_graph::owner::Owner::current_shared_context().unwrap();
let prev = sc.get_is_hydrating();
let value = ::leptos::reactive::owner::Owner::with_no_hydration(||
let value = ::leptos::reactive_graph::owner::Owner::with_no_hydration(||
::leptos::tachys::html::islands::IslandChildren::new(children()).into_any()
);
sc.set_is_hydrating(prev);

View File

@@ -40,9 +40,9 @@ impl ToTokens for MemoMacroInput {
let path = &self.path;
tokens.extend(quote! {
::leptos::reactive::computed::Memo::new(
::leptos::reactive_graph::computed::Memo::new(
move |_| {
use ::leptos::reactive::traits::With;
use ::leptos::reactive_graph::traits::With;
#root.with(|st: _| st.#path.clone())
}
)

View File

@@ -40,7 +40,7 @@ impl ToTokens for SliceMacroInput {
let path = &self.path;
tokens.extend(quote! {
::leptos::reactive::computed::create_slice(
::leptos::reactive_graph::computed::create_slice(
#root,
|st: &_| st.#path.clone(),
|st: &mut _, n| st.#path = n

View File

@@ -1,5 +1,5 @@
use super::{fragment_to_tokens, TagType};
use crate::view::{attribute_absolute, utils::filter_prefixed_attrs};
use crate::view::attribute_absolute;
use proc_macro2::{Ident, TokenStream, TokenTree};
use quote::{format_ident, quote, quote_spanned};
use rstml::node::{
@@ -105,13 +105,20 @@ pub(crate) fn component_to_tokens(
return None;
}
};
let inputs = &binding.inputs;
Some(quote! { #inputs })
})
.collect::<Vec<_>>();
let items_to_clone = filter_prefixed_attrs(attrs.iter(), "clone:");
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<_>>();
// include all attribute that are either
// 1) blocks ({..attrs} or {attrs}),

View File

@@ -1,19 +1,14 @@
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, UpperCamel},
Casing,
};
use convert_case::{Case::Snake, 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::{format_ident, quote, quote_spanned, ToTokens};
use quote::{quote, quote_spanned, ToTokens};
use rstml::node::{
CustomNode, KVAttributeValue, KeyedAttribute, Node, NodeAttribute,
NodeBlock, NodeElement, NodeName, NodeNameFragment,
@@ -307,12 +302,10 @@ fn inert_element_to_tokens(
match current {
Node::RawText(raw) => {
let text = raw.to_string_best();
let text = html_escape::encode_text(&text);
html.push_str(&text);
}
Node::Text(text) => {
let text = text.value_string();
let text = html_escape::encode_text(&text);
html.push_str(&text);
}
Node::Element(node) => {
@@ -339,13 +332,11 @@ fn inert_element_to_tokens(
)) = &value.value
{
if let Lit::Str(txt) = &lit.lit {
let value = txt.value();
let value = html_escape::encode_double_quoted_attribute(&value);
if attr_name == "class" {
html.push_class(&value);
html.push_class(&txt.value());
} else {
html.push_str("=\"");
html.push_str(&value);
html.push_str(&txt.value());
html.push('"');
}
}
@@ -883,8 +874,6 @@ 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],
@@ -1068,20 +1057,6 @@ 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,

View File

@@ -1,7 +1,7 @@
use super::{convert_to_snake_case, ident_from_tag_name};
use crate::view::{fragment_to_tokens, utils::filter_prefixed_attrs, TagType};
use crate::view::{fragment_to_tokens, TagType};
use proc_macro2::{Ident, TokenStream, TokenTree};
use quote::{quote, quote_spanned};
use quote::{format_ident, quote, quote_spanned};
use rstml::node::{CustomNode, KeyedAttribute, NodeAttribute, NodeElement};
use std::collections::HashMap;
use syn::spanned::Spanned;
@@ -70,9 +70,25 @@ pub(crate) fn slot_to_tokens(
}
});
let items_to_bind = filter_prefixed_attrs(attrs.iter(), "let:");
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_clone = filter_prefixed_attrs(attrs.iter(), "clone:");
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 dyn_attrs = attrs
.iter()

View File

@@ -1,19 +0,0 @@
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()
}

View File

@@ -105,49 +105,6 @@ 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>,
@@ -607,49 +564,6 @@ 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>,

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_meta"
version = "0.7.0-gamma"
version = "0.7.0-beta6"
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"

View File

@@ -2,7 +2,7 @@ use crate::ServerMetaContext;
use leptos::{
attr::NextAttribute,
component, html,
reactive::owner::use_context,
reactive_graph::owner::use_context,
tachys::{
dom::document,
html::attribute::Attribute,

View File

@@ -2,7 +2,7 @@ use crate::ServerMetaContext;
use leptos::{
attr::NextAttribute,
component, html,
reactive::owner::use_context,
reactive_graph::owner::use_context,
tachys::{
dom::document,
html::attribute::Attribute,

View File

@@ -52,7 +52,7 @@ use leptos::{
attr::NextAttribute,
component,
logging::debug_warn,
reactive::owner::{provide_context, use_context},
reactive_graph::owner::{provide_context, use_context},
tachys::{
dom::document,
html::{

View File

@@ -3,7 +3,7 @@ use leptos::{
attr::Attribute,
component,
oco::Oco,
reactive::{
reactive_graph::{
effect::RenderEffect,
owner::{use_context, Owner},
},

View File

@@ -1,6 +1,6 @@
[package]
name = "next_tuple"
version = "0.1.0-gamma"
version = "0.1.0-beta6"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -30,21 +30,23 @@ sqlx = { version = "0.8.0", features = [
], optional = true }
thiserror = "1.0"
wasm-bindgen = "0.2.0"
axum_session_auth = { version = "0.14.0", features = [], optional = true }
axum_session = { version = "0.14.0", features = [], optional = true }
axum_session_sqlx = { version = "0.3.0", features = [ "sqlite", "tls-rustls"], optional = true }
axum_session_auth = { version = "0.14.0", features = [
"sqlite-rustls",
], optional = true }
axum_session = { version = "0.14.0", features = [
"sqlite-rustls",
], optional = true }
bcrypt = { version = "0.15.0", optional = true }
async-trait = { version = "0.1.0", optional = true }
[features]
default = ["ssr"]
hydrate = ["leptos/hydrate"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = [
"dep:axum",
"dep:tower",
"dep:tower-http",
"dep:tokio",
"dep:axum_session_sqlx",
"dep:axum_session_auth",
"dep:axum_session",
"dep:async-trait",

View File

@@ -28,8 +28,9 @@ impl Default for User {
#[cfg(feature = "ssr")]
pub mod ssr {
pub use super::{User, UserPasshash};
pub use axum_session_auth::{Authentication, HasPermission};
use axum_session_sqlx::SessionSqlitePool;
pub use axum_session_auth::{
Authentication, HasPermission, SessionSqlitePool,
};
pub use sqlx::SqlitePool;
pub use std::collections::HashSet;
pub type AuthSession = axum_session_auth::AuthSession<

View File

@@ -8,10 +8,10 @@ use leptos_axum::ResponseOptions;
#[component]
pub fn ErrorTemplate(
#[prop(optional)] outside_errors: Option<Errors>,
#[prop(optional)] errors: Option<ArcRwSignal<Errors>>,
#[prop(optional)] errors: Option<RwSignal<Errors>>,
) -> impl IntoView {
let errors = match outside_errors {
Some(e) => ArcRwSignal::new(e),
Some(e) => RwSignal::new(e),
None => match errors {
Some(e) => e,
None => panic!("No Errors found and we expected errors!"),

View File

@@ -0,0 +1,50 @@
use crate::{error_template::ErrorTemplate, errors::TodoAppError};
use axum::{
body::Body,
extract::State,
http::{Request, Response, StatusCode, Uri},
response::{IntoResponse, Response as AxumResponse},
};
use leptos::{view, Errors, LeptosOptions};
use tower::ServiceExt;
use tower_http::services::ServeDir;
pub async fn file_and_error_handler(
uri: Uri,
State(options): State<LeptosOptions>,
req: Request<Body>,
) -> AxumResponse {
let root = options.site_root.clone();
let res = get_static_file(uri.clone(), &root).await.unwrap();
if res.status() == StatusCode::OK {
res.into_response()
} else {
let mut errors = Errors::default();
errors.insert_with_default_key(TodoAppError::NotFound);
let handler = leptos_axum::render_app_to_stream(
options.to_owned(),
move || view! {<ErrorTemplate outside_errors=errors.clone()/>},
);
handler(req).await.into_response()
}
}
async fn get_static_file(
uri: Uri,
root: &str,
) -> Result<Response<Body>, (StatusCode, String)> {
let req = Request::builder()
.uri(uri.clone())
.body(Body::empty())
.unwrap();
// `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot`
// This path is relative to the cargo root
match ServeDir::new(root).oneshot(req).await {
Ok(res) => Ok(res.into_response()),
Err(err) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {err}"),
)),
}
}

View File

@@ -2,6 +2,8 @@ pub mod auth;
pub mod error_template;
pub mod errors;
#[cfg(feature = "ssr")]
pub mod fallback;
#[cfg(feature = "ssr")]
pub mod state;
pub mod todo;
@@ -12,5 +14,5 @@ pub fn hydrate() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
leptos::mount::hydrate_body(TodoApp);
leptos::mount_to_body(TodoApp);
}

View File

@@ -7,16 +7,14 @@ use axum::{
Router,
};
use axum_session::{SessionConfig, SessionLayer, SessionStore};
use axum_session_auth::{AuthConfig, AuthSessionLayer};
use axum_session_sqlx::SessionSqlitePool;
use leptos::{
config::get_configuration, logging::log, prelude::provide_context,
};
use axum_session_auth::{AuthConfig, AuthSessionLayer, SessionSqlitePool};
use leptos::{get_configuration, logging::log, provide_context};
use leptos_axum::{
generate_route_list, handle_server_fns_with_context, LeptosRoutes,
};
use session_auth_axum::{
auth::{ssr::AuthSession, User},
fallback::file_and_error_handler,
state::AppState,
todo::*,
};
@@ -42,19 +40,19 @@ async fn server_fn_handler(
async fn leptos_routes_handler(
auth_session: AuthSession,
state: State<AppState>,
State(app_state): State<AppState>,
req: Request<AxumBody>,
) -> Response {
let State(app_state) = state.clone();
let handler = leptos_axum::render_route_with_context(
app_state.leptos_options.clone(),
app_state.routes.clone(),
move || {
provide_context(auth_session.clone());
provide_context(app_state.pool.clone());
},
move || shell(app_state.leptos_options.clone()),
TodoApp,
);
handler(state, req).await.into_response()
handler(req).await.into_response()
}
#[tokio::main]
@@ -113,7 +111,7 @@ async fn main() {
get(server_fn_handler).post(server_fn_handler),
)
.leptos_routes_with_handler(routes, get(leptos_routes_handler))
.fallback(leptos_axum::file_and_error_handler::<AppState, _>(shell))
.fallback(file_and_error_handler)
.layer(
AuthSessionLayer::<User, i64, SessionSqlitePool, SqlitePool>::new(
Some(pool.clone()),

View File

@@ -1,6 +1,6 @@
use axum::extract::FromRef;
use leptos::prelude::LeptosOptions;
use leptos_axum::AxumRouteListing;
use leptos::LeptosOptions;
use leptos_router::RouteListing;
use sqlx::SqlitePool;
/// This takes advantage of Axum's SubStates feature by deriving FromRef. This is the only way to have more than one
@@ -9,5 +9,5 @@ use sqlx::SqlitePool;
pub struct AppState {
pub leptos_options: LeptosOptions,
pub pool: SqlitePool,
pub routes: Vec<AxumRouteListing>,
pub routes: Vec<RouteListing>,
}

View File

@@ -1,7 +1,7 @@
use crate::{auth::*, error_template::ErrorTemplate};
use leptos::prelude::*;
use leptos_meta::*;
use leptos_router::{components::*, *};
use leptos_router::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
@@ -109,33 +109,13 @@ pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
.map(|_| ())?)
}
pub fn shell(options: LeptosOptions) -> impl IntoView {
view! {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<AutoReload options=options.clone() />
<HydrationScripts options/>
<link rel="stylesheet" id="leptos" href="/pkg/session_auth_axum.css"/>
<link rel="shortcut icon" type="image/ico" href="/favicon.ico"/>
<MetaTags/>
</head>
<body>
<TodoApp/>
</body>
</html>
}
}
#[component]
pub fn TodoApp() -> impl IntoView {
let login = ServerAction::<Login>::new();
let logout = ServerAction::<Logout>::new();
let signup = ServerAction::<Signup>::new();
let login = create_server_action::<Login>();
let logout = create_server_action::<Logout>();
let signup = create_server_action::<Signup>();
let user = Resource::new(
let user = create_resource(
move || {
(
login.version().get(),
@@ -148,6 +128,8 @@ pub fn TodoApp() -> impl IntoView {
provide_meta_context();
view! {
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
<Stylesheet id="leptos" href="/pkg/session_auth_axum.css"/>
<Router>
<header>
<A href="/">
@@ -167,7 +149,7 @@ pub fn TodoApp() -> impl IntoView {
", "
<span>{format!("Login error: {}", e)}</span>
}
.into_any()
.into_view()
}
Ok(None) => {
view! {
@@ -177,7 +159,7 @@ pub fn TodoApp() -> impl IntoView {
", "
<span>"Logged out."</span>
}
.into_any()
.into_view()
}
Ok(Some(user)) => {
view! {
@@ -187,7 +169,7 @@ pub fn TodoApp() -> impl IntoView {
{format!("Logged in as: {} ({})", user.username, user.id)}
</span>
}
.into_any()
.into_view()
}
})
}}
@@ -196,15 +178,13 @@ pub fn TodoApp() -> impl IntoView {
</header>
<hr/>
<main>
<FlatRoutes fallback=|| "Not found.">
<Routes>
// Route
<Route path=path!("") view=Todos/>
<Route path=path!("signup") view=move || view! { <Signup action=signup/> }/>
<Route path=path!("login") view=move || view! { <Login action=login/> }/>
<ProtectedRoute
path=path!("settings")
condition=move || user.get().map(|r| r.ok().flatten().is_some())
redirect_path=|| "/"
<Route path="" view=Todos/>
<Route path="signup" view=move || view! { <Signup action=signup/> }/>
<Route path="login" view=move || view! { <Login action=login/> }/>
<Route
path="settings"
view=move || {
view! {
<h1>"Settings"</h1>
@@ -213,7 +193,7 @@ pub fn TodoApp() -> impl IntoView {
}
/>
</FlatRoutes>
</Routes>
</main>
</Router>
}
@@ -221,12 +201,12 @@ pub fn TodoApp() -> impl IntoView {
#[component]
pub fn Todos() -> impl IntoView {
let add_todo = ServerMultiAction::<AddTodo>::new();
let delete_todo = ServerAction::<DeleteTodo>::new();
let add_todo = create_server_multi_action::<AddTodo>();
let delete_todo = create_server_action::<DeleteTodo>();
let submissions = add_todo.submissions();
// list of todos is loaded from the server in reaction to changes
let todos = Resource::new(
let todos = create_resource(
move || (add_todo.version().get(), delete_todo.version().get()),
move |_| get_todos(),
);
@@ -251,11 +231,11 @@ pub fn Todos() -> impl IntoView {
view! {
<pre class="error">"Server Error: " {e.to_string()}</pre>
}
.into_any()
.into_view()
}
Ok(todos) => {
if todos.is_empty() {
view! { <p>"No tasks were found."</p> }.into_any()
view! { <p>"No tasks were found."</p> }.into_view()
} else {
todos
.into_iter()
@@ -272,11 +252,10 @@ pub fn Todos() -> impl IntoView {
}
})
.collect_view()
.into_any()
}
}
})
.unwrap_or(().into_any())
.unwrap_or_default()
}
};
let pending_todos = move || {
@@ -287,7 +266,7 @@ pub fn Todos() -> impl IntoView {
.map(|submission| {
view! {
<li class="pending">
{move || submission.input().get().map(|data| data.title)}
{move || submission.input.get().map(|data| data.title)}
</li>
}
})
@@ -303,7 +282,9 @@ pub fn Todos() -> impl IntoView {
}
#[component]
pub fn Login(action: ServerAction<Login>) -> impl IntoView {
pub fn Login(
action: Action<Login, Result<(), ServerFnError>>,
) -> impl IntoView {
view! {
<ActionForm action=action>
<h1>"Log In"</h1>
@@ -336,7 +317,9 @@ pub fn Login(action: ServerAction<Login>) -> impl IntoView {
}
#[component]
pub fn Signup(action: ServerAction<Signup>) -> impl IntoView {
pub fn Signup(
action: Action<Signup, Result<(), ServerFnError>>,
) -> impl IntoView {
view! {
<ActionForm action=action>
<h1>"Sign Up"</h1>
@@ -379,7 +362,9 @@ pub fn Signup(action: ServerAction<Signup>) -> impl IntoView {
}
#[component]
pub fn Logout(action: ServerAction<Logout>) -> impl IntoView {
pub fn Logout(
action: Action<Logout, Result<(), ServerFnError>>,
) -> impl IntoView {
view! {
<div id="loginbox">
<ActionForm action=action>

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_graph"
version = "0.1.0-gamma"
version = "0.1.0-beta6"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::actions::*;
/// // 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// let act = create_action(|n: &u8| {
/// let n = n.to_owned();

View File

@@ -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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// async fn send_new_todo_to_api(task: String) -> usize {
/// // do something...

View File

@@ -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::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::effect::Effect;
/// # use reactive_graph::signal::RwSignal;
/// # use reactive_graph::computed::*;

View File

@@ -40,7 +40,7 @@ use std::{
///
/// ## Examples
/// ```
/// # use reactive_graph::prelude::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::computed::*;
/// # use reactive_graph::signal::signal;
/// # fn really_expensive_computation(value: i32) -> i32 { value };
@@ -132,7 +132,10 @@ where
pub fn new_with_compare(
fun: impl Fn(Option<&T>) -> T + Send + Sync + 'static,
changed: fn(Option<&T>, Option<&T>) -> bool,
) -> Self {
) -> Self
where
T: PartialEq,
{
Self::new_owning(move |prev: Option<T>| {
let new_value = fun(prev.as_ref());
let changed = changed(prev.as_ref(), Some(&new_value));
@@ -154,7 +157,10 @@ where
)]
pub fn new_owning(
fun: impl Fn(Option<T>) -> (T, bool) + Send + Sync + 'static,
) -> Self {
) -> Self
where
T: PartialEq,
{
let inner = Arc::new_cyclic(|weak| {
let subscriber = AnySubscriber(
weak.as_ptr() as usize,

View File

@@ -19,7 +19,7 @@ use crate::{
},
traits::{
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
Write,
Writeable,
},
transition::AsyncTransition,
};
@@ -53,10 +53,10 @@ use std::{
/// ## Examples
/// ```rust
/// # use reactive_graph::computed::*;
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::signal::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
///
/// let signal1 = RwSignal::new(0);
@@ -600,7 +600,7 @@ impl<T: 'static> Notify for ArcAsyncDerived<T> {
}
}
impl<T: 'static> Write for ArcAsyncDerived<T> {
impl<T: 'static> Writeable for ArcAsyncDerived<T> {
type Value = Option<T>;
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {

View File

@@ -8,7 +8,7 @@ use crate::{
signal::guards::{AsyncPlain, ReadGuard, WriteGuard},
traits::{
DefinedAt, Dispose, IsDisposed, Notify, ReadUntracked,
UntrackableGuard, Write,
UntrackableGuard, Writeable,
},
unwrap_signal,
};
@@ -29,10 +29,10 @@ use std::{future::Future, ops::DerefMut, panic::Location};
/// ## Examples
/// ```rust
/// # use reactive_graph::computed::*;
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::signal::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
///
/// let signal1 = RwSignal::new(0);
@@ -300,7 +300,7 @@ where
}
}
impl<T, S> Write for AsyncDerived<T, S>
impl<T, S> Writeable for AsyncDerived<T, S>
where
T: 'static,
S: Storage<ArcAsyncDerived<T>>,

View File

@@ -58,7 +58,7 @@ impl<Fut: Future> Future for ScopedFuture<Fut> {
pub mod suspense {
use crate::{
signal::ArcRwSignal,
traits::{Update, Write},
traits::{Update, Writeable},
};
use futures::channel::oneshot::Sender;
use or_poisoned::OrPoisoned;

View File

@@ -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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # fn really_expensive_computation(value: i32) -> i32 { value };
/// let (value, set_value) = signal(0);
///
@@ -195,7 +195,10 @@ where
pub fn new_with_compare(
fun: impl Fn(Option<&T>) -> T + Send + Sync + 'static,
changed: fn(Option<&T>, Option<&T>) -> bool,
) -> Self {
) -> Self
where
T: PartialEq,
{
Self {
#[cfg(debug_assertions)]
defined_at: Location::caller(),
@@ -219,7 +222,10 @@ where
)]
pub fn new_owning(
fun: impl Fn(Option<T>) -> (T, bool) + Send + Sync + 'static,
) -> Self {
) -> Self
where
T: PartialEq,
{
Self {
#[cfg(debug_assertions)]
defined_at: Location::caller(),

View File

@@ -19,13 +19,13 @@ use std::{
///
/// ```
/// # use reactive_graph::computed::*;
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::signal::*;
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::effect::Effect;
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::owner::StoredValue;
/// # tokio_test::block_on(async move {
/// # tokio::task::LocalSet::new().run_until(async move {
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// let a = RwSignal::new(0);
/// let is_selected = Selector::new(move || a.get());

View File

@@ -37,13 +37,13 @@ use std::{
///
/// ```
/// # use reactive_graph::computed::*;
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::signal::*;
/// # 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// #
/// 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// #
/// 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(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// #
/// let (num, set_num) = signal(0);
///

View File

@@ -104,11 +104,11 @@ impl Observer {
///
/// ```rust
/// # use reactive_graph::computed::*;
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::signal::*;
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::graph::untrack;
/// # use reactive_graph::untrack;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # any_spawner::Executor::init_tokio();
/// let (a, set_a) = signal(0);
/// let (b, set_b) = signal(0);
/// let c = Memo::new(move |_| {

View File

@@ -14,7 +14,6 @@
//!
//! ```rust
//! # any_spawner::Executor::init_futures_executor();
//! # let owner = reactive_graph::owner::Owner::new(); owner.set();
//! use reactive_graph::{
//! computed::ArcMemo,
//! effect::Effect,
@@ -89,6 +88,7 @@ pub mod transition;
pub mod wrappers;
use computed::ScopedFuture;
pub use graph::untrack;
#[cfg(feature = "nightly")]
mod nightly;

View File

@@ -20,8 +20,6 @@ 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::*;
@@ -122,12 +120,6 @@ 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,
@@ -148,10 +140,11 @@ 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,
@@ -159,21 +152,17 @@ impl Owner {
contexts: Default::default(),
cleanups: Default::default(),
children: Default::default(),
#[cfg(feature = "sandboxed-arenas")]
arena: Default::default(),
})),
#[cfg(feature = "hydration")]
shared_context,
};
this.set();
OWNER.with_borrow_mut(|owner| *owner = Some(this.clone()));
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,
@@ -181,8 +170,6 @@ 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(),
@@ -198,8 +185,6 @@ 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`.
@@ -209,8 +194,6 @@ 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;
@@ -351,8 +334,6 @@ 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 {
@@ -380,19 +361,11 @@ 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);
}
}
}
}
}
@@ -421,20 +394,11 @@ 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);
}
}
}
}
}

View File

@@ -1,42 +1,38 @@
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::sync::Weak;
use std::{
any::Any,
hash::Hash,
sync::{Arc, RwLock},
};
use std::{cell::RefCell, sync::Arc};
new_key_type! {
/// Unique identifier for an item stored in the arena.
pub struct NodeId;
}
pub struct Arena;
pub(crate) struct Arena;
pub type ArenaMap = SlotMap<NodeId, Box<dyn Any + Send + Sync>>;
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<Weak<RwLock<ArenaMap>>>> = RefCell::new(Some(Default::default()));
pub(crate) static MAP: RefCell<Option<Arc<RwLock<ArenaMap>>>> = RefCell::new(Some(Default::default()));
}
impl Arena {
#[cfg(feature = "hydration")]
#[inline(always)]
#[allow(unused)]
pub fn set(arena: &Arc<RwLock<ArenaMap>>) {
pub fn enter_new() {
#[cfg(feature = "sandboxed-arenas")]
{
let new_arena = Arc::downgrade(arena);
use std::sync::Arc;
MAP.with_borrow_mut(|arena| {
*arena = Some(new_arena);
*arena = Some(Arc::new(RwLock::new(
SlotMap::with_capacity_and_key(32),
)))
})
}
}
@@ -52,7 +48,6 @@ 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, \
@@ -78,7 +73,6 @@ 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, \
@@ -100,11 +94,20 @@ pub mod sandboxed {
use pin_project_lite::pin_project;
use std::{
future::Future,
mem,
pin::Pin,
sync::{Arc, RwLock, Weak},
sync::{Arc, RwLock},
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.
@@ -114,7 +117,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: Option<Arc<RwLock<ArenaMap>>>,
arena: Arc<RwLock<ArenaMap>>,
#[pin]
inner: T,
}
@@ -124,7 +127,12 @@ 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(|n| n.as_ref().and_then(Weak::upgrade));
let arena = MAP.with_borrow(|current| {
Arc::clone(current.as_ref().expect(
"the `sandboxed-arenas` feature is active, but no Arena \
is active",
))
});
Self { arena, inner }
}
}
@@ -139,11 +147,11 @@ pub mod sandboxed {
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Self::Output> {
if let Some(arena) = self.arena.as_ref() {
Arena::set(arena);
}
let unset = Arena::set(Arc::clone(&self.arena));
let this = self.project();
this.inner.poll(cx)
let res = this.inner.poll(cx);
drop(unset);
res
}
}
@@ -157,11 +165,22 @@ pub mod sandboxed {
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
if let Some(arena) = self.arena.as_ref() {
Arena::set(arena);
}
let unset = Arena::set(Arc::clone(&self.arena));
let this = self.project();
this.inner.poll_next(cx)
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));
}
}
}
}

View File

@@ -108,7 +108,6 @@ 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();
@@ -141,7 +140,6 @@ 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();
@@ -189,7 +187,6 @@ 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();
@@ -235,7 +232,6 @@ 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();
@@ -290,7 +286,6 @@ 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();

View File

@@ -120,7 +120,7 @@ impl<T, S: Storage<Arc<RwLock<T>>>> StoredValue<T, S> {
///
/// # Examples
/// ```rust
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::owner::StoredValue;
/// # use reactive_graph::traits::Dispose;
///
/// // Does not implement Clone
@@ -167,7 +167,7 @@ impl<T, S: Storage<Arc<RwLock<T>>>> StoredValue<T, S> {
///
/// # Examples
/// ```rust
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::owner::StoredValue;
///
/// // Does not implement Clone
/// struct Data {
@@ -251,7 +251,7 @@ impl<T, S: Storage<Arc<RwLock<T>>>> StoredValue<T, S> {
///
/// # Examples
/// ```rust
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::owner::StoredValue;
///
/// #[derive(Default)] // Does not implement Clone
/// struct Data {
@@ -295,7 +295,7 @@ impl<T, S: Storage<Arc<RwLock<T>>>> StoredValue<T, S> {
///
/// # Examples
/// ```rust
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::owner::StoredValue;
/// # use reactive_graph::traits::Dispose;
///
/// let data = StoredValue::new(String::default());
@@ -353,7 +353,7 @@ impl<T, S: Storage<Arc<RwLock<T>>>> StoredValue<T, S> {
///
/// # Examples
/// ```rust
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::owner::StoredValue;
///
/// let data = StoredValue::new(10);
///
@@ -394,7 +394,7 @@ where
///
/// # Examples
/// ```rust
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::owner::StoredValue;
/// # use reactive_graph::traits::Dispose;
///
/// // u8 is practically free to clone.
@@ -434,7 +434,7 @@ where
///
/// # Examples
/// ```rust
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::owner::StoredValue;
///
/// // u8 is practically free to clone.
/// let data: StoredValue<u8> = StoredValue::new(10);

View File

@@ -36,7 +36,7 @@ pub use write::*;
///
/// ```
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::signal::*;
/// 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::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::signal::*;
/// 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::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::signal::*;
/// let (count, set_count) = create_signal(0);
///
/// // ✅ calling the getter clones and returns the value

View File

@@ -6,7 +6,7 @@ use super::{
use crate::{
graph::{ReactiveNode, SubscriberSet},
prelude::{IsDisposed, Notify},
traits::{DefinedAt, ReadUntracked, UntrackableGuard, Write},
traits::{DefinedAt, ReadUntracked, UntrackableGuard, Writeable},
};
use core::fmt::{Debug, Formatter, Result};
use std::{
@@ -50,7 +50,7 @@ use std::{
/// - [`.set()`](crate::traits::Set) sets the signal to a new value.
/// - [`.update()`](crate::traits::Update) updates the value of the signal by
/// applying a closure that takes a mutable reference.
/// - [`.write()`](crate::traits::Write) returns a guard through which the signal
/// - [`.write()`](crate::traits::Writeable) returns a guard through which the signal
/// can be mutated, and which notifies subscribers when it is dropped.
///
/// > Each of these has a related `_untracked()` method, which updates the signal
@@ -63,7 +63,7 @@ use std::{
///
/// ```
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::signal::*;
/// let count = ArcRwSignal::new(0);
///
/// // ✅ calling the getter clones and returns the value
@@ -253,7 +253,7 @@ impl<T> Notify for ArcRwSignal<T> {
}
}
impl<T: 'static> Write for ArcRwSignal<T> {
impl<T: 'static> Writeable for ArcRwSignal<T> {
type Value = T;
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {

View File

@@ -2,7 +2,7 @@ use super::guards::{UntrackedWriteGuard, WriteGuard};
use crate::{
graph::{ReactiveNode, SubscriberSet},
prelude::{IsDisposed, Notify},
traits::{DefinedAt, UntrackableGuard, Write},
traits::{DefinedAt, UntrackableGuard, Writeable},
};
use core::fmt::{Debug, Formatter, Result};
use std::{
@@ -23,7 +23,7 @@ use std::{
/// - [`.set()`](crate::traits::Set) sets the signal to a new value.
/// - [`.update()`](crate::traits::Update) updates the value of the signal by
/// applying a closure that takes a mutable reference.
/// - [`.write()`](crate::traits::Write) returns a guard through which the signal
/// - [`.write()`](crate::traits::Writeable) returns a guard through which the signal
/// can be mutated, and which notifies subscribers when it is dropped.
///
/// > Each of these has a related `_untracked()` method, which updates the signal
@@ -122,7 +122,7 @@ impl<T> Notify for ArcWriteSignal<T> {
}
}
impl<T: 'static> Write for ArcWriteSignal<T> {
impl<T: 'static> Writeable for ArcWriteSignal<T> {
type Value = T;
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {

View File

@@ -49,7 +49,7 @@ use std::{
///
/// ## Examples
/// ```
/// # use reactive_graph::prelude::*; use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::prelude::*; use reactive_graph::signal::*;
/// let (count, set_count) = signal(0);
///
/// // calling .get() clones and returns the value

View File

@@ -9,7 +9,7 @@ use crate::{
signal::guards::{UntrackedWriteGuard, WriteGuard},
traits::{
DefinedAt, Dispose, IsDisposed, Notify, ReadUntracked,
UntrackableGuard, Write,
UntrackableGuard, Writeable,
},
unwrap_signal,
};
@@ -57,7 +57,7 @@ use std::{
/// - [`.set()`](crate::traits::Set) sets the signal to a new value.
/// - [`.update()`](crate::traits::Update) updates the value of the signal by
/// applying a closure that takes a mutable reference.
/// - [`.write()`](crate::traits::Write) returns a guard through which the signal
/// - [`.write()`](crate::traits::Writeable) returns a guard through which the signal
/// can be mutated, and which notifies subscribers when it is dropped.
///
/// > Each of these has a related `_untracked()` method, which updates the signal
@@ -69,7 +69,7 @@ use std::{
///
/// ```
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::signal::*;
/// let count = ArcRwSignal::new(0);
///
/// // ✅ calling the getter clones and returns the value
@@ -349,7 +349,7 @@ where
}
}
impl<T, S> Write for RwSignal<T, S>
impl<T, S> Writeable for RwSignal<T, S>
where
T: 'static,
S: Storage<ArcRwSignal<T>>,

View File

@@ -1,7 +1,9 @@
use super::{guards::WriteGuard, ArcWriteSignal};
use crate::{
owner::{ArenaItem, Storage, SyncStorage},
traits::{DefinedAt, Dispose, IsDisposed, Notify, UntrackableGuard, Write},
traits::{
DefinedAt, Dispose, IsDisposed, Notify, UntrackableGuard, Writeable,
},
};
use core::fmt::Debug;
use guardian::ArcRwLockWriteGuardian;
@@ -20,7 +22,7 @@ use std::{hash::Hash, ops::DerefMut, panic::Location, sync::Arc};
/// - [`.set()`](crate::traits::Set) sets the signal to a new value.
/// - [`.update()`](crate::traits::Update) updates the value of the signal by
/// applying a closure that takes a mutable reference.
/// - [`.write()`](crate::traits::Write) returns a guard through which the signal
/// - [`.write()`](crate::traits::Writeable) returns a guard through which the signal
/// can be mutated, and which notifies subscribers when it is dropped.
///
/// > Each of these has a related `_untracked()` method, which updates the signal
@@ -30,7 +32,7 @@ use std::{hash::Hash, ops::DerefMut, panic::Location, sync::Arc};
///
/// ## Examples
/// ```
/// # use reactive_graph::prelude::*; use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::prelude::*; use reactive_graph::signal::*;
/// let (count, set_count) = signal(0);
///
/// // ✅ calling the setter sets the value
@@ -126,7 +128,7 @@ where
}
}
impl<T, S> Write for WriteSignal<T, S>
impl<T, S> Writeable for WriteSignal<T, S>
where
T: 'static,
S: Storage<ArcWriteSignal<T>>,

View File

@@ -18,7 +18,7 @@
//! | [`Track`] | — | Tracks changes to this value, adding it as a source of the current reactive observer. |
//! | [`Trigger`] | — | Notifies subscribers that this value has changed. |
//! | [`ReadUntracked`] | Guard | Gives immutable access to the value of this signal. |
//! | [`Write`] | Guard | Gives mutable access to the value of this signal.
//! | [`Writeable`] | Guard | Gives mutable access to the value of this signal.
//!
//! ## Derived Traits
//!
@@ -33,7 +33,7 @@
//! ### Update
//! | Trait | Mode | Composition | Description
//! |---------------------|---------------|-----------------------------------|------------
//! | [`UpdateUntracked`] | `fn(&mut T)` | [`Write`] | Applies closure to the current value to update it, but doesn't notify subscribers.
//! | [`UpdateUntracked`] | `fn(&mut T)` | [`Writeable`] | Applies closure to the current value to update it, but doesn't notify subscribers.
//! | [`Update`] | `fn(&mut T)` | [`UpdateUntracked`] + [`Trigger`] | Applies closure to the current value to update it, and notifies subscribers.
//! | [`Set`] | `T` | [`Update`] | Sets the value to a new value, and notifies subscribers.
//!
@@ -61,7 +61,6 @@ use std::{
panic::Location,
};
#[doc(hidden)]
/// Provides a sensible panic message for accessing disposed signals.
#[macro_export]
macro_rules! unwrap_signal {
@@ -214,7 +213,7 @@ pub trait UntrackableGuard: DerefMut {
/// Gives mutable access to a signal's value through a guard type. When the guard is dropped, the
/// signal's subscribers will be notified.
pub trait Write: Sized + DefinedAt + Notify {
pub trait Writeable: Sized + DefinedAt + Notify {
/// The type of the signal's value.
type Value: Sized + 'static;
@@ -422,9 +421,9 @@ pub trait UpdateUntracked: DefinedAt {
impl<T> UpdateUntracked for T
where
T: Write,
T: Writeable,
{
type Value = <Self as Write>::Value;
type Value = <Self as Writeable>::Value;
#[track_caller]
fn try_update_untracked<U>(
@@ -479,9 +478,9 @@ pub trait Update {
impl<T> Update for T
where
T: Write,
T: Writeable,
{
type Value = <Self as Write>::Value;
type Value = <Self as Writeable>::Value;
#[track_caller]
fn try_maybe_update<U>(

View File

@@ -4,11 +4,10 @@
pub mod read {
use crate::{
computed::{ArcMemo, Memo},
graph::untrack,
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
signal::{ArcReadSignal, ArcRwSignal, ReadSignal, RwSignal},
traits::{DefinedAt, Dispose, Get, With, WithUntracked},
unwrap_signal,
untrack, unwrap_signal,
};
use send_wrapper::SendWrapper;
use std::{panic::Location, sync::Arc};
@@ -132,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::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::signal::*;
/// # use reactive_graph::wrappers::read::ArcSignal;
/// # use reactive_graph::prelude::*;
/// let (count, set_count) = arc_signal(2);
@@ -398,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::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::signal::*;
/// # use reactive_graph::wrappers::read::Signal;
/// # use reactive_graph::prelude::*;
/// let (count, set_count) = signal(2);
@@ -648,7 +647,7 @@ pub mod read {
/// of the same type. This is especially useful for component properties.
///
/// ```
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::signal::*;
/// # use reactive_graph::wrappers::read::MaybeSignal;
/// # use reactive_graph::computed::Memo;
/// # use reactive_graph::prelude::*;
@@ -908,7 +907,7 @@ pub mod read {
///
/// ## Examples
/// ```rust
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::signal::*;
/// # use reactive_graph::wrappers::read::MaybeProp;
/// # use reactive_graph::computed::Memo;
/// # use reactive_graph::prelude::*;
@@ -1283,7 +1282,7 @@ pub mod write {
///
/// ## Examples
/// ```rust
/// # use reactive_graph::prelude::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::wrappers::write::SignalSetter;
/// # use reactive_graph::signal::signal;
/// let (count, set_count) = signal(2);

View File

@@ -1,7 +1,6 @@
use any_spawner::Executor;
use reactive_graph::{
computed::{ArcAsyncDerived, AsyncDerived},
owner::Owner,
signal::RwSignal,
traits::{Get, Read, Set, With, WithUntracked},
};
@@ -10,8 +9,6 @@ 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;
@@ -24,8 +21,6 @@ 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 {
@@ -45,8 +40,6 @@ 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;
@@ -59,8 +52,6 @@ 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 {
@@ -80,8 +71,6 @@ 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);
@@ -93,8 +82,6 @@ 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());
@@ -107,8 +94,6 @@ 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);

View File

@@ -3,7 +3,6 @@ pub mod imports {
pub use any_spawner::Executor;
pub use reactive_graph::{
effect::{Effect, RenderEffect},
owner::Owner,
prelude::*,
signal::RwSignal,
};
@@ -20,8 +19,6 @@ 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);
@@ -57,8 +54,6 @@ async fn effect_runs() {
use imports::*;
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
task::LocalSet::new()
.run_until(async {
@@ -93,8 +88,6 @@ async fn dynamic_dependencies() {
use imports::*;
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
task::LocalSet::new()
.run_until(async {
@@ -163,8 +156,6 @@ 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);

View File

@@ -1,6 +1,5 @@
use reactive_graph::{
computed::{ArcMemo, Memo},
owner::Owner,
prelude::*,
signal::RwSignal,
wrappers::read::Signal,
@@ -30,9 +29,6 @@ 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);
@@ -46,9 +42,6 @@ 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);
@@ -59,9 +52,6 @@ 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);
@@ -88,9 +78,6 @@ 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 |_| {
@@ -124,9 +111,6 @@ 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);
@@ -166,9 +150,6 @@ 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");
@@ -206,14 +187,9 @@ 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");
@@ -288,14 +264,9 @@ 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 {
@@ -336,14 +307,9 @@ 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 {
@@ -377,14 +343,9 @@ 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 {
@@ -422,9 +383,6 @@ 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);

View File

@@ -1,9 +1,8 @@
use reactive_graph::{
owner::Owner,
signal::{arc_signal, signal, ArcRwSignal, RwSignal},
traits::{
Get, GetUntracked, Read, Set, Update, UpdateUntracked, With,
WithUntracked, Write,
WithUntracked, Writeable,
},
};
@@ -55,9 +54,6 @@ 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);
@@ -67,9 +63,6 @@ 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);
@@ -83,9 +76,6 @@ 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);
@@ -96,9 +86,6 @@ 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);

View File

@@ -1,8 +1,6 @@
#[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};
@@ -13,8 +11,6 @@ use tokio::task;
#[tokio::test]
async fn watch_runs() {
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
task::LocalSet::new()
.run_until(async {
@@ -75,8 +71,6 @@ 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 {
@@ -124,8 +118,6 @@ 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 {
@@ -182,8 +174,6 @@ 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 {

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_stores"
version = "0.1.0-gamma"
version = "0.1.0-beta6"
rust-version.workspace = true
edition.workspace = true

View File

@@ -10,7 +10,7 @@ use reactive_graph::{
},
traits::{
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
Write,
Writeable,
},
};
use std::{
@@ -171,7 +171,7 @@ where
}
}
impl<Inner, Prev> Write for AtIndex<Inner, Prev>
impl<Inner, Prev> Writeable for AtIndex<Inner, Prev>
where
Inner: StoreField<Value = Prev>,
Prev: IndexMut<usize> + 'static,

View File

@@ -10,7 +10,7 @@ use reactive_graph::{
},
traits::{
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
Write,
Writeable,
},
};
use std::{
@@ -312,7 +312,7 @@ where
}
}
impl<Inner, Prev, K, T> Write for KeyedSubfield<Inner, Prev, K, T>
impl<Inner, Prev, K, T> Writeable for KeyedSubfield<Inner, Prev, K, T>
where
Self: Clone,
for<'a> &'a T: IntoIterator,
@@ -574,7 +574,7 @@ where
}
}
impl<Inner, Prev, K, T> Write for AtKeyed<Inner, Prev, K, T>
impl<Inner, Prev, K, T> Writeable for AtKeyed<Inner, Prev, K, T>
where
K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
KeyedSubfield<Inner, Prev, K, T>: Clone,

View File

@@ -7,7 +7,7 @@ use reactive_graph::{
},
traits::{
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
Write,
Writeable,
},
};
use rustc_hash::FxHashMap;
@@ -254,7 +254,7 @@ where
}
}
impl<T> Write for ArcStore<T>
impl<T> Writeable for ArcStore<T>
where
T: 'static,
{
@@ -379,7 +379,7 @@ where
}
}
impl<T, S> Write for Store<T, S>
impl<T, S> Writeable for Store<T, S>
where
T: 'static,
S: Storage<ArcStore<T>>,
@@ -428,7 +428,7 @@ mod tests {
use crate::{self as reactive_stores, Patch, Store, StoreFieldIterator};
use reactive_graph::{
effect::Effect,
traits::{Read, ReadUntracked, Set, Update, Write},
traits::{Read, ReadUntracked, Set, Update, Writeable},
};
use reactive_stores_macro::{Patch, Store};
use std::sync::{

View File

@@ -49,7 +49,7 @@ mod tests {
use crate::{self as reactive_stores, Store};
use reactive_graph::{
effect::Effect,
traits::{Get, Read, ReadUntracked, Set, Write},
traits::{Get, Read, ReadUntracked, Set, Writeable},
};
use reactive_stores_macro::Store;
use std::sync::{

View File

@@ -11,7 +11,7 @@ use reactive_graph::{
},
traits::{
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
Write,
Writeable,
},
unwrap_signal,
};
@@ -251,7 +251,7 @@ where
}
}
impl<T, S> Write for Then<T, S>
impl<T, S> Writeable for Then<T, S>
where
T: 'static,
S: StoreField,

View File

@@ -10,7 +10,7 @@ use reactive_graph::{
},
traits::{
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
Write,
Writeable,
},
};
use std::{iter, marker::PhantomData, ops::DerefMut, panic::Location};
@@ -168,7 +168,7 @@ where
}
}
impl<Inner, Prev, T> Write for Subfield<Inner, Prev, T>
impl<Inner, Prev, T> Writeable for Subfield<Inner, Prev, T>
where
T: 'static,
Inner: StoreField<Value = Prev>,

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_stores_macro"
version = "0.1.0-gamma"
version = "0.1.0-beta6"
rust-version.workspace = true
edition.workspace = true
@@ -9,7 +9,7 @@ proc-macro = true
[dependencies]
convert_case = "0.6"
proc-macro-error2 = "2.0"
proc-macro-error = "1.0"
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "2.0", features = ["full"] }

View File

@@ -1,6 +1,6 @@
use convert_case::{Case, Casing};
use proc_macro2::{Span, TokenStream};
use proc_macro_error2::{abort, abort_call_site, proc_macro_error};
use proc_macro_error::{abort, abort_call_site, proc_macro_error};
use quote::{quote, ToTokens};
use syn::{
parse::{Parse, ParseStream, Parser},

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_router"
version = "0.7.0-gamma"
version = "0.7.0-beta6"
authors = ["Greg Johnston", "Ben Wishovich"]
license = "MIT"
readme = "../README.md"

View File

@@ -4,7 +4,7 @@ use crate::{
location::{BrowserUrl, LocationProvider},
NavigateOptions,
};
use leptos::{ev, html::form, prelude::*, task::spawn_local};
use leptos::{ev, html::form, prelude::*, spawn::spawn_local};
use std::{error::Error, sync::Arc};
use wasm_bindgen::{JsCast, UnwrapThrowExt};
use web_sys::{FormData, RequestRedirect, Response};

View File

@@ -1,5 +1,5 @@
use crate::{components::RouterContext, hooks::use_resolved_path};
use leptos::{children::Children, oco::Oco, prelude::*};
use leptos::{children::Children, oco::Oco, prelude::*, *};
use reactive_graph::{computed::ArcMemo, owner::use_context};
use std::{borrow::Cow, rc::Rc};

View File

@@ -1,6 +1,6 @@
use crate::{hooks::RawParamsMap, params::ParamsMap, PathSegment};
use futures::{channel::oneshot, stream, Stream, StreamExt};
use leptos::task::spawn;
use leptos::spawn::spawn;
use reactive_graph::{owner::Owner, traits::GetUntracked};
use std::{
fmt::{Debug, Display},

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_router_macro"
version = "0.7.0-gamma"
version = "0.7.0-beta6"
authors = ["Greg Johnston", "Ben Wishovich"]
license = "MIT"
readme = "../README.md"
@@ -13,7 +13,7 @@ edition.workspace = true
proc-macro = true
[dependencies]
proc-macro-error2 = { version = "2.0", default-features = false }
proc-macro-error = { version = "1.0", default-features = false }
proc-macro2 = "1.0"
quote = "1.0"

View File

@@ -1,6 +1,6 @@
use proc_macro::{TokenStream, TokenTree};
use proc_macro2::Span;
use proc_macro_error2::abort;
use proc_macro_error::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_error2::proc_macro_error]
#[proc_macro_error::proc_macro_error]
#[proc_macro]
pub fn path(tokens: TokenStream) -> TokenStream {
let mut parser = SegmentParser::new(tokens);

View File

@@ -1,6 +1,6 @@
[package]
name = "tachys"
version = "0.1.0-gamma"
version = "0.1.0-beta6"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -8,7 +8,6 @@ pub mod custom;
pub mod global;
mod key;
mod value;
use crate::view::{Position, ToTemplate};
pub use key::*;
use std::{fmt::Debug, future::Future};

View File

@@ -352,7 +352,7 @@ where
let attrs = self.attributes.hydrate::<FROM_SERVER>(&el);
// hydrate children
let children = if !Ch::EXISTS || !E::ESCAPE_CHILDREN {
let children = if !Ch::EXISTS {
None
} else {
position.set(Position::FirstChild);

View File

@@ -1,515 +0,0 @@
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:
///
/// ```ignore
/// // 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(),
}
}
}

View File

@@ -16,8 +16,6 @@ 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.
@@ -26,7 +24,6 @@ mod owned;
mod property;
mod style;
mod suspense;
pub use owned::*;
pub use suspense::*;

View File

@@ -31,13 +31,11 @@ impl RenderHtml for () {
self,
buf: &mut String,
position: &mut Position,
escape: bool,
_escape: bool,
_mark_branches: bool,
) {
if escape {
buf.push_str("<!>");
*position = Position::NextChild;
}
buf.push_str("<!>");
*position = Position::NextChild;
}
fn hydrate<const FROM_SERVER: bool>(