mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 15:44:42 -05:00
Compare commits
107 Commits
docs-clean
...
v0.7.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdd5671fe1 | ||
|
|
293149eeb2 | ||
|
|
a9ce608433 | ||
|
|
62196ff638 | ||
|
|
db9cabb365 | ||
|
|
5293afddb7 | ||
|
|
eeb01e2d52 | ||
|
|
7d9cd43c0e | ||
|
|
723685c370 | ||
|
|
6d19e1a921 | ||
|
|
165911b2e6 | ||
|
|
575186349c | ||
|
|
10db2f83eb | ||
|
|
357f3be393 | ||
|
|
a1f5d6f79f | ||
|
|
34ecd2d541 | ||
|
|
df38b075d8 | ||
|
|
46233e3097 | ||
|
|
46f6d9ae26 | ||
|
|
8264d478e4 | ||
|
|
9c54da304e | ||
|
|
1c002a2df8 | ||
|
|
6e4dac91bf | ||
|
|
6493b0b359 | ||
|
|
d58042b7ba | ||
|
|
7a5b27ad20 | ||
|
|
78d0dbdfa2 | ||
|
|
1c30a4c131 | ||
|
|
b91429d4f0 | ||
|
|
6663fddf76 | ||
|
|
28af33d511 | ||
|
|
5a3413d3b3 | ||
|
|
702d2e247b | ||
|
|
71f698f322 | ||
|
|
8d4776bf5f | ||
|
|
65798e430f | ||
|
|
d46d1a4fab | ||
|
|
f9533ab75b | ||
|
|
d08f8822c0 | ||
|
|
7357839efb | ||
|
|
1b8ad58114 | ||
|
|
ef72f1ce96 | ||
|
|
6a5bfe9a5d | ||
|
|
1661fe2412 | ||
|
|
2324853155 | ||
|
|
28a3859365 | ||
|
|
6b50179189 | ||
|
|
0bb825f6bd | ||
|
|
a6aa111122 | ||
|
|
753cfe8e44 | ||
|
|
bc9c3add87 | ||
|
|
d0bb45dbc4 | ||
|
|
2a4b80cf22 | ||
|
|
881734b43a | ||
|
|
68fdc8d9c4 | ||
|
|
03ed805137 | ||
|
|
5d4603e988 | ||
|
|
e65969cfcd | ||
|
|
e1e90b8595 | ||
|
|
71a136c7af | ||
|
|
49366be2a5 | ||
|
|
6f5bdcc675 | ||
|
|
726a99441f | ||
|
|
775bea46d9 | ||
|
|
2aa9827a1f | ||
|
|
be3c1811fc | ||
|
|
8df4f3c71d | ||
|
|
3028a9acd0 | ||
|
|
4b167fb809 | ||
|
|
46ae8ef9b2 | ||
|
|
21f26e7a6b | ||
|
|
204648d388 | ||
|
|
a04bca55a2 | ||
|
|
1fec678174 | ||
|
|
4dbb8d1a42 | ||
|
|
fc2c52eb04 | ||
|
|
1ae35ce5b3 | ||
|
|
8edb11f324 | ||
|
|
5a01a7f2ed | ||
|
|
f252460d02 | ||
|
|
6331b488e4 | ||
|
|
fcba8b3b17 | ||
|
|
d665dd4b89 | ||
|
|
be740b38ee | ||
|
|
d2803c938c | ||
|
|
f29224415a | ||
|
|
292772c4d6 | ||
|
|
5947aa299e | ||
|
|
6098836cf7 | ||
|
|
2dfa61ff6a | ||
|
|
4f39b0b0ef | ||
|
|
980595f1f0 | ||
|
|
14eb707e82 | ||
|
|
3de0414ed5 | ||
|
|
75cae91661 | ||
|
|
8b258b0d26 | ||
|
|
84bdd6b568 | ||
|
|
809023a2ad | ||
|
|
1477ae2cfb | ||
|
|
26995f8efd | ||
|
|
3c174b26a5 | ||
|
|
0c7e77800a | ||
|
|
fee2421047 | ||
|
|
89f26f6e9b | ||
|
|
e76b22bec8 | ||
|
|
cff277b3db | ||
|
|
0258ac6df4 |
12
.github/dependabot.yml
vendored
12
.github/dependabot.yml
vendored
@@ -1,12 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: "cargo"
|
||||
directories:
|
||||
- "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 10
|
||||
5
.github/workflows/autofix.yml
vendored
5
.github/workflows/autofix.yml
vendored
@@ -13,6 +13,7 @@ concurrency:
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUST_BACKTRACE: 1
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
jobs:
|
||||
autofix:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -21,6 +22,10 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with: {toolchain: nightly, components: "rustfmt, clippy", target: "wasm32-unknown-unknown", rustflags: ""}
|
||||
- name: Install Glib
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libglib2.0-dev
|
||||
- name: Install jq
|
||||
run: sudo apt-get install jq
|
||||
- run: |
|
||||
|
||||
8
.github/workflows/ci-semver.yml
vendored
8
.github/workflows/ci-semver.yml
vendored
@@ -8,15 +8,21 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
jobs:
|
||||
get-leptos-changed:
|
||||
uses: ./.github/workflows/get-leptos-changed.yml
|
||||
test:
|
||||
needs: [get-leptos-changed]
|
||||
if: github.event.pull_request.labels[0].name == 'semver' # needs.get-leptos-changed.outputs.leptos_changed == 'true' && github.event.pull_request.labels[0].name != 'breaking'
|
||||
if: needs.get-leptos-changed.outputs.leptos_changed == 'true' && github.event.pull_request.labels[0].name != 'breaking'
|
||||
name: Run semver check (nightly-2024-08-01)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Glib
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libglib2.0-dev
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Semver Checks
|
||||
|
||||
5
.github/workflows/run-cargo-make-task.yml
vendored
5
.github/workflows/run-cargo-make-task.yml
vendored
@@ -14,6 +14,7 @@ on:
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
jobs:
|
||||
test:
|
||||
name: Run ${{ inputs.cargo_make_task }} (${{ inputs.toolchain }})
|
||||
@@ -33,6 +34,10 @@ jobs:
|
||||
echo "Disk space after cleanup:"
|
||||
df -h
|
||||
# Setup environment
|
||||
- name: Install Glib
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libglib2.0-dev
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
|
||||
651
Cargo.lock
generated
651
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
50
Cargo.toml
50
Cargo.toml
@@ -40,36 +40,36 @@ members = [
|
||||
exclude = ["benchmarks", "examples", "projects"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.7.0-rc1"
|
||||
version = "0.7.4"
|
||||
edition = "2021"
|
||||
rust-version = "1.76"
|
||||
|
||||
[workspace.dependencies]
|
||||
throw_error = { path = "./any_error/", version = "0.2.0-rc1" }
|
||||
any_spawner = { path = "./any_spawner/", version = "0.1.0" }
|
||||
throw_error = { path = "./any_error/", version = "0.2.0" }
|
||||
any_spawner = { path = "./any_spawner/", version = "0.2.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-rc1" }
|
||||
leptos = { path = "./leptos", version = "0.7.0-rc1" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.0-rc1" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.0-rc1" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.0-rc1" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.0-rc1" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.0-rc1" }
|
||||
leptos_router = { path = "./router", version = "0.7.0-rc1" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.0-rc1" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.0-rc1" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.0-rc1" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0-rc1" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.2.0" }
|
||||
leptos = { path = "./leptos", version = "0.7.4" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.4" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.4" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.4" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.4" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.4" }
|
||||
leptos_router = { path = "./router", version = "0.7.4" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.4" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.4" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.4" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0" }
|
||||
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-rc1" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.1.0-rc1" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.0-rc1" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.0-rc1" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.0-rc1" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.0-rc1" }
|
||||
tachys = { path = "./tachys", version = "0.1.0-rc1" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.1.0" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.1.0" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.0" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.4" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.4" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.4" }
|
||||
tachys = { path = "./tachys", version = "0.1.0" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
@@ -78,3 +78,9 @@ opt-level = 'z'
|
||||
|
||||
[workspace.metadata.cargo-all-features]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
|
||||
[workspace.lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = [
|
||||
'cfg(leptos_debuginfo)',
|
||||
'cfg(erase_components)',
|
||||
] }
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
|
||||
You can find a list of useful libraries and example projects at [`awesome-leptos`](https://github.com/leptos-rs/awesome-leptos).
|
||||
|
||||
# The `main` branch is currently undergoing major changes in preparation for the [0.7](https://github.com/leptos-rs/leptos/milestone/4) release. For a stable version, please use the [v0.6.13 tag](https://github.com/leptos-rs/leptos/tree/v0.6.13)
|
||||
|
||||
# Leptos
|
||||
|
||||
```rust
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "throw_error"
|
||||
version = "0.2.0-rc1"
|
||||
version = "0.2.0"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "any_spawner"
|
||||
version = "0.1.1"
|
||||
version = "0.2.1"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
@@ -16,8 +16,8 @@ thiserror = "2.0"
|
||||
tokio = { version = "1.41", optional = true, default-features = false, features = [
|
||||
"rt",
|
||||
] }
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
wasm-bindgen-futures = { version = "0.4.45", optional = true }
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
wasm-bindgen-futures = { version = "0.4.47", optional = true }
|
||||
|
||||
[features]
|
||||
async-executor = ["dep:async-executor"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "either_of"
|
||||
version = "0.1.0"
|
||||
version = "0.1.3"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -134,7 +134,7 @@ tuples!(EitherOf14 + EitherOf14Future + EitherOf14FutureProj => A, B, C, D, E, F
|
||||
tuples!(EitherOf15 + EitherOf15Future + EitherOf15FutureProj => A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
|
||||
tuples!(EitherOf16 + EitherOf16Future + EitherOf16FutureProj => A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
|
||||
|
||||
/// Matches over the first expression and returns an either ([`Either`], [`EitherOf3`], ... [`EitherOf6`])
|
||||
/// Matches over the first expression and returns an either ([`Either`], [`EitherOf3`], ... [`EitherOf8`])
|
||||
/// composed of the values returned by the match arms.
|
||||
///
|
||||
/// The pattern syntax is exactly the same as found in a match arm.
|
||||
@@ -197,6 +197,29 @@ macro_rules! either {
|
||||
$e_pattern => $crate::EitherOf6::E($e_expression),
|
||||
$f_pattern => $crate::EitherOf6::F($f_expression),
|
||||
}
|
||||
};
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr, $e_pattern:pat => $e_expression:expr, $f_pattern:pat => $f_expression:expr, $g_pattern:pat => $g_expression:expr,) => {
|
||||
match $match {
|
||||
$a_pattern => $crate::EitherOf7::A($a_expression),
|
||||
$b_pattern => $crate::EitherOf7::B($b_expression),
|
||||
$c_pattern => $crate::EitherOf7::C($c_expression),
|
||||
$d_pattern => $crate::EitherOf7::D($d_expression),
|
||||
$e_pattern => $crate::EitherOf7::E($e_expression),
|
||||
$f_pattern => $crate::EitherOf7::F($f_expression),
|
||||
$g_pattern => $crate::EitherOf7::G($g_expression),
|
||||
}
|
||||
};
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr, $e_pattern:pat => $e_expression:expr, $f_pattern:pat => $f_expression:expr, $g_pattern:pat => $g_expression:expr, $h_pattern:pat => $h_expression:expr,) => {
|
||||
match $match {
|
||||
$a_pattern => $crate::EitherOf8::A($a_expression),
|
||||
$b_pattern => $crate::EitherOf8::B($b_expression),
|
||||
$c_pattern => $crate::EitherOf8::C($c_expression),
|
||||
$d_pattern => $crate::EitherOf8::D($d_expression),
|
||||
$e_pattern => $crate::EitherOf8::E($e_expression),
|
||||
$f_pattern => $crate::EitherOf8::F($f_expression),
|
||||
$g_pattern => $crate::EitherOf8::G($g_expression),
|
||||
$h_pattern => $crate::EitherOf8::H($h_expression),
|
||||
}
|
||||
}; // if you need more eithers feel free to open a PR ;-)
|
||||
}
|
||||
|
||||
@@ -233,4 +256,23 @@ fn either_macro() {
|
||||
16 => 24u8,
|
||||
_ => 12,
|
||||
);
|
||||
let _: EitherOf7<&str, f64, char, f32, u8, i8, i32> = either!(12,
|
||||
12 => "12",
|
||||
13 => 0.0,
|
||||
14 => ' ',
|
||||
15 => 0.0f32,
|
||||
16 => 24u8,
|
||||
17 => 2i8,
|
||||
_ => 12,
|
||||
);
|
||||
let _: EitherOf8<&str, f64, char, f32, u8, i8, u32, i32> = either!(12,
|
||||
12 => "12",
|
||||
13 => 0.0,
|
||||
14 => ' ',
|
||||
15 => 0.0f32,
|
||||
16 => 24u8,
|
||||
17 => 2i8,
|
||||
18 => 42u32,
|
||||
_ => 12,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,25 +4,6 @@ use leptos::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use server_fn::ServerFnError;
|
||||
|
||||
pub fn shell(leptos_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=leptos_options.clone() />
|
||||
<HydrationScripts options=leptos_options.clone()/>
|
||||
<link rel="stylesheet" id="leptos" href="/pkg/todo_app_sqlite_csr.css"/>
|
||||
<link rel="shortcut icon" type="image/ico" href="/favicon.ico"/>
|
||||
</head>
|
||||
<body>
|
||||
<TodoApp/>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
|
||||
pub struct Todo {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "hydration_context"
|
||||
version = "0.2.0-rc1"
|
||||
version = "0.2.1"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
@@ -14,8 +14,8 @@ throw_error = { workspace = true }
|
||||
or_poisoned = { workspace = true }
|
||||
futures = "0.3.31"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
wasm-bindgen = { version = "0.2.95", optional = true }
|
||||
js-sys = { version = "0.3.72", optional = true }
|
||||
wasm-bindgen = { version = "0.2.97", optional = true }
|
||||
js-sys = { version = "0.3.74", optional = true }
|
||||
once_cell = "1.20"
|
||||
pin-project-lite = "0.2.15"
|
||||
|
||||
@@ -25,3 +25,6 @@ browser = ["dep:wasm-bindgen", "dep:js-sys"]
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
// #[wasm_bindgen(thread_local)] is deprecated in wasm-bindgen 0.2.96
|
||||
// but the replacement is also only shipped in that version
|
||||
// as a result, we'll just allow deprecated for now
|
||||
#![allow(deprecated)]
|
||||
|
||||
use super::{SerializedDataId, SharedContext};
|
||||
use crate::{PinnedFuture, PinnedStream};
|
||||
use core::fmt::Debug;
|
||||
|
||||
@@ -83,15 +83,14 @@ pub trait SharedContext: Debug {
|
||||
|
||||
/// Reads the current value of some data from the shared context, if it has been
|
||||
/// sent from the server. This returns the serialized data as a `String` that should
|
||||
/// be deserialized using [`Serializable::de`].
|
||||
/// be deserialized.
|
||||
///
|
||||
/// On the server and in client-side rendered implementations, this should
|
||||
/// always return [`None`].
|
||||
fn read_data(&self, id: &SerializedDataId) -> Option<String>;
|
||||
|
||||
/// Returns a [`Future`] that resolves with a `String` that should
|
||||
/// be deserialized using [`Serializable::de`] once the given piece of server
|
||||
/// data has resolved.
|
||||
/// be deserialized once the given piece of server data has resolved.
|
||||
///
|
||||
/// On the server and in client-side rendered implementations, this should
|
||||
/// return a [`Future`] that is immediately ready with [`None`].
|
||||
@@ -148,8 +147,8 @@ pub trait SharedContext: Debug {
|
||||
|
||||
/// Adds a `Future` to the set of “blocking resources” that should prevent the server’s
|
||||
/// response stream from beginning until all are resolved. The `Future` returned by
|
||||
/// [`blocking_resources`](Self::blocking_resources) will not resolve until every `Future`
|
||||
/// added by this method has resolved.
|
||||
/// blocking resources will not resolve until every `Future` added by this method
|
||||
/// has resolved.
|
||||
///
|
||||
/// In browser implementations, this should be a no-op.
|
||||
fn defer_stream(&self, wait_for: PinnedFuture<()>);
|
||||
|
||||
@@ -18,7 +18,7 @@ hydration_context = { workspace = true }
|
||||
leptos = { workspace = true, features = ["nonce", "ssr"] }
|
||||
leptos_integration_utils = { workspace = true }
|
||||
leptos_macro = { workspace = true, features = ["actix"] }
|
||||
leptos_meta = { workspace = true }
|
||||
leptos_meta = { workspace = true, features = ["nonce"] }
|
||||
leptos_router = { workspace = true, features = ["ssr"] }
|
||||
server_fn = { workspace = true, features = ["actix"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
@@ -82,7 +82,7 @@ impl ResponseParts {
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper for an Actix [`HttpRequest`](actix_web::HttpRequest) that allows it to be used in an
|
||||
/// A wrapper for an Actix [`HttpRequest`] that allows it to be used in an
|
||||
/// `Send`/`Sync` setting like Leptos's Context API.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Request(SendWrapper<HttpRequest>);
|
||||
@@ -375,8 +375,8 @@ pub fn handle_server_fns_with_context(
|
||||
.take(),
|
||||
);
|
||||
|
||||
// it it accepts text/html (i.e., is a plain form post) and doesn't already have a
|
||||
// Location set, then redirect to to Referer
|
||||
// if it accepts text/html (i.e., is a plain form post) and doesn't already have a
|
||||
// Location set, then redirect to the Referer
|
||||
if accepts_html {
|
||||
if let Some(referrer) = referrer {
|
||||
let has_location =
|
||||
@@ -390,7 +390,20 @@ pub fn handle_server_fns_with_context(
|
||||
}
|
||||
}
|
||||
|
||||
// apply status code and headers if used changed them
|
||||
// the Location header may have been set to Referer, so any redirection by the
|
||||
// user must overwrite it
|
||||
{
|
||||
let mut res_options = res_options.0.write();
|
||||
let headers = res.0.headers_mut();
|
||||
|
||||
for location in
|
||||
res_options.headers.remove(header::LOCATION)
|
||||
{
|
||||
headers.insert(header::LOCATION, location);
|
||||
}
|
||||
}
|
||||
|
||||
// apply status code and headers if user changed them
|
||||
res.extend_response(&res_options);
|
||||
res.0
|
||||
})
|
||||
@@ -419,12 +432,6 @@ pub fn handle_server_fns_with_context(
|
||||
/// will include fallback content for any `<Suspense/>` nodes, and be immediately interactive,
|
||||
/// but requires some client-side JavaScript.
|
||||
///
|
||||
/// The provides a [MetaContext] and a [RouterIntegrationContext] to app’s context before
|
||||
/// rendering it, and includes any meta tags injected using [leptos_meta].
|
||||
///
|
||||
/// The HTML stream is rendered using [render_to_stream](leptos::ssr::render_to_stream), and
|
||||
/// includes everything described in the documentation for that function.
|
||||
///
|
||||
/// This can then be set up at an appropriate route in your application:
|
||||
/// ```
|
||||
/// use actix_web::{App, HttpServer};
|
||||
@@ -465,7 +472,6 @@ pub fn handle_server_fns_with_context(
|
||||
/// - [ResponseOptions]
|
||||
/// - [Request]
|
||||
/// - [MetaContext](leptos_meta::MetaContext)
|
||||
/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -485,13 +491,6 @@ where
|
||||
/// This stream will pause at each `<Suspense/>` node and wait for it to resolve before
|
||||
/// sending down its HTML. The app will become interactive once it has fully loaded.
|
||||
///
|
||||
/// The provides a [MetaContext] and a [RouterIntegrationContext] to app’s context before
|
||||
/// rendering it, and includes any meta tags injected using [leptos_meta].
|
||||
///
|
||||
/// The HTML stream is rendered using
|
||||
/// [render_to_stream_in_order](leptos::ssr::render_to_stream_in_order),
|
||||
/// and includes everything described in the documentation for that function.
|
||||
///
|
||||
/// This can then be set up at an appropriate route in your application:
|
||||
/// ```
|
||||
/// use actix_web::{App, HttpServer};
|
||||
@@ -534,7 +533,6 @@ where
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [ResponseOptions]
|
||||
/// - [Request]
|
||||
/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -551,13 +549,7 @@ where
|
||||
|
||||
/// Returns an Actix [struct@Route](actix_web::Route) that listens for a `GET` request and tries
|
||||
/// to route it using [leptos_router], asynchronously rendering an HTML page after all
|
||||
/// `async` [Resource](leptos::Resource)s have loaded.
|
||||
///
|
||||
/// The provides a [MetaContext] and a [RouterIntegrationContext] to the app’s context before
|
||||
/// rendering it, and includes any meta tags injected using [leptos_meta].
|
||||
///
|
||||
/// The HTML stream is rendered using [render_to_string_async](leptos::ssr::render_to_string_async), and
|
||||
/// includes everything described in the documentation for that function.
|
||||
/// `async` resources have loaded.
|
||||
///
|
||||
/// This can then be set up at an appropriate route in your application:
|
||||
/// ```
|
||||
@@ -690,7 +682,6 @@ where
|
||||
/// - [ResponseOptions]
|
||||
/// - [Request]
|
||||
/// - [MetaContext](leptos_meta::MetaContext)
|
||||
/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -713,7 +704,7 @@ where
|
||||
|
||||
/// Returns an Actix [struct@Route](actix_web::Route) that listens for a `GET` request and tries
|
||||
/// to route it using [leptos_router], asynchronously serving the page once all `async`
|
||||
/// [Resource](leptos::Resource)s have loaded.
|
||||
/// resources have loaded.
|
||||
///
|
||||
/// This function allows you to provide additional information to Leptos for your route.
|
||||
/// It could be used to pass in Path Info, Connection Info, or anything your heart desires.
|
||||
|
||||
@@ -11,7 +11,7 @@ edition.workspace = true
|
||||
[dependencies]
|
||||
any_spawner = { workspace = true, features = ["tokio"] }
|
||||
hydration_context = { workspace = true }
|
||||
axum = { version = "0.7.8", default-features = false, features = [
|
||||
axum = { version = "0.7.9", default-features = false, features = [
|
||||
"matched-path",
|
||||
] }
|
||||
dashmap = "6"
|
||||
@@ -19,18 +19,18 @@ futures = "0.3.31"
|
||||
leptos = { workspace = true, features = ["nonce", "ssr"] }
|
||||
server_fn = { workspace = true, features = ["axum-no-default"] }
|
||||
leptos_macro = { workspace = true, features = ["axum"] }
|
||||
leptos_meta = { workspace = true, features = ["ssr"] }
|
||||
leptos_meta = { workspace = true, features = ["ssr", "nonce"] }
|
||||
leptos_router = { workspace = true, features = ["ssr"] }
|
||||
leptos_integration_utils = { workspace = true }
|
||||
once_cell = "1"
|
||||
parking_lot = "0.12.3"
|
||||
tokio = { version = "1.41", default-features = false }
|
||||
tower = { version = "0.5.1", features = ["util"] }
|
||||
tower-http = "0.6.1"
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
tower-http = "0.6.2"
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
axum = "0.7.8"
|
||||
axum = "0.7.9"
|
||||
tokio = { version = "1.41", features = ["net", "rt-multi-thread"] }
|
||||
|
||||
[features]
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
//! - `default`: supports running in a typical native Tokio/Axum environment
|
||||
//! - `wasm`: with `default-features = false`, supports running in a JS Fetch-based
|
||||
//! environment
|
||||
//! - `islands`: activates Leptos [islands mode](https://leptos-rs.github.io/leptos/islands.html)
|
||||
//!
|
||||
//! ### Important Note
|
||||
//! Prior to 0.5, using `default-features = false` on `leptos_axum` simply did nothing. Now, it actively
|
||||
@@ -197,9 +196,9 @@ impl ExtendResponse for AxumResponse {
|
||||
/// 2. A server function that is called from WASM running in the client (e.g., a dispatched action
|
||||
/// or a spawned `Future`).
|
||||
/// 3. A `<form>` submitted to the server function endpoint using default browser APIs (often due
|
||||
/// to using [`ActionForm`](leptos::form::ActionForm) without JS/WASM present.)
|
||||
/// to using [`ActionForm`] without JS/WASM present.)
|
||||
///
|
||||
/// Using it with a non-blocking [`Resource`](leptos::server::Resource) will not work if you are using streaming rendering,
|
||||
/// Using it with a non-blocking [`Resource`] will not work if you are using streaming rendering,
|
||||
/// as the response's headers will already have been sent by the time the server function calls `redirect()`.
|
||||
///
|
||||
/// ### Implementation
|
||||
@@ -399,8 +398,8 @@ async fn handle_server_fns_inner(
|
||||
// actually run the server fn
|
||||
let mut res = AxumResponse(service.run(req).await);
|
||||
|
||||
// it it accepts text/html (i.e., is a plain form post) and doesn't already have a
|
||||
// Location set, then redirect to to Referer
|
||||
// if it accepts text/html (i.e., is a plain form post) and doesn't already have a
|
||||
// Location set, then redirect to the Referer
|
||||
if accepts_html {
|
||||
if let Some(referrer) = referrer {
|
||||
let has_location =
|
||||
@@ -412,7 +411,7 @@ async fn handle_server_fns_inner(
|
||||
}
|
||||
}
|
||||
|
||||
// apply status code and headers if used changed them
|
||||
// apply status code and headers if user changed them
|
||||
res.extend_response(&res_options);
|
||||
Ok(res.0)
|
||||
})
|
||||
@@ -442,12 +441,6 @@ pub type PinnedHtmlStream =
|
||||
/// Returns an Axum [Handler](axum::handler::Handler) that listens for a `GET` request and tries
|
||||
/// to route it using [leptos_router], serving an HTML stream of your application.
|
||||
///
|
||||
/// The provides a [MetaContext] and a [RouterIntegrationContext] to app’s context before
|
||||
/// rendering it, and includes any meta tags injected using [leptos_meta].
|
||||
///
|
||||
/// The HTML stream is rendered using [render_to_stream](leptos::ssr::render_to_stream), and
|
||||
/// includes everything described in the documentation for that function.
|
||||
///
|
||||
/// This can then be set up at an appropriate route in your application:
|
||||
/// ```
|
||||
/// use axum::{handler::Handler, Router};
|
||||
@@ -485,8 +478,7 @@ pub type PinnedHtmlStream =
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [`Parts`]
|
||||
/// - [`ResponseOptions`]
|
||||
/// - [`ServerMetaContext`](leptos_meta::ServerMetaContext)
|
||||
/// - [`RouterIntegrationContext`](leptos_router::RouterIntegrationContext)
|
||||
/// - [`ServerMetaContext`]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -537,12 +529,6 @@ where
|
||||
/// This stream will pause at each `<Suspense/>` node and wait for it to resolve before
|
||||
/// sending down its HTML. The app will become interactive once it has fully loaded.
|
||||
///
|
||||
/// The provides a [MetaContext] and a [RouterIntegrationContext] to app’s context before
|
||||
/// rendering it, and includes any meta tags injected using [leptos_meta].
|
||||
///
|
||||
/// The HTML stream is rendered using [render_to_stream_in_order], and includes everything described in
|
||||
/// the documentation for that function.
|
||||
///
|
||||
/// This can then be set up at an appropriate route in your application:
|
||||
/// ```
|
||||
/// use axum::{handler::Handler, Router};
|
||||
@@ -580,8 +566,7 @@ where
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [`Parts`]
|
||||
/// - [`ResponseOptions`]
|
||||
/// - [`ServerMetaContext`](leptos_meta::ServerMetaContext)
|
||||
/// - [`RouterIntegrationContext`](leptos_router::RouterIntegrationContext)
|
||||
/// - [`ServerMetaContext`]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -634,8 +619,7 @@ where
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [`Parts`]
|
||||
/// - [`ResponseOptions`]
|
||||
/// - [`ServerMetaContext`](leptos_meta::ServerMetaContext)
|
||||
/// - [`RouterIntegrationContext`](leptos_router::RouterIntegrationContext)
|
||||
/// - [`ServerMetaContext`]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -766,8 +750,7 @@ where
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [`Parts`]
|
||||
/// - [`ResponseOptions`]
|
||||
/// - [`ServerMetaContext`](leptos_meta::ServerMetaContext)
|
||||
/// - [`RouterIntegrationContext`](leptos_router::RouterIntegrationContext)
|
||||
/// - [`ServerMetaContext`]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -834,8 +817,7 @@ where
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [`Parts`]
|
||||
/// - [`ResponseOptions`]
|
||||
/// - [`ServerMetaContext`](leptos_meta::ServerMetaContext)
|
||||
/// - [`RouterIntegrationContext`](leptos_router::RouterIntegrationContext)
|
||||
/// - [`ServerMetaContext`]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -952,13 +934,7 @@ fn provide_contexts(
|
||||
|
||||
/// Returns an Axum [Handler](axum::handler::Handler) that listens for a `GET` request and tries
|
||||
/// to route it using [leptos_router], asynchronously rendering an HTML page after all
|
||||
/// `async` [Resource](leptos::Resource)s have loaded.
|
||||
///
|
||||
/// The provides a [MetaContext] and a [RouterIntegrationContext] to app’s context before
|
||||
/// rendering it, and includes any meta tags injected using [leptos_meta].
|
||||
///
|
||||
/// The HTML stream is rendered using [render_to_string_async], and includes everything described in
|
||||
/// the documentation for that function.
|
||||
/// `async` resources have loaded.
|
||||
///
|
||||
/// This can then be set up at an appropriate route in your application:
|
||||
/// ```
|
||||
@@ -998,8 +974,7 @@ fn provide_contexts(
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [`Parts`]
|
||||
/// - [`ResponseOptions`]
|
||||
/// - [`ServerMetaContext`](leptos_meta::ServerMetaContext)
|
||||
/// - [`RouterIntegrationContext`](leptos_router::RouterIntegrationContext)
|
||||
/// - [`ServerMetaContext`]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -1020,7 +995,7 @@ where
|
||||
|
||||
/// Returns an Axum [Handler](axum::handler::Handler) that listens for a `GET` request and tries
|
||||
/// to route it using [leptos_router], asynchronously rendering an HTML page after all
|
||||
/// `async` [Resource](leptos::Resource)s have loaded.
|
||||
/// `async` resources have loaded.
|
||||
///
|
||||
/// This version allows us to pass Axum State/Extension/Extractor or other infro from Axum or network
|
||||
/// layers above Leptos itself. To use it, you'll need to write your own handler function that provides
|
||||
@@ -1053,8 +1028,7 @@ where
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [`Parts`]
|
||||
/// - [`ResponseOptions`]
|
||||
/// - [`ServerMetaContext`](leptos_meta::ServerMetaContext)
|
||||
/// - [`RouterIntegrationContext`](leptos_router::RouterIntegrationContext)
|
||||
/// - [`ServerMetaContext`]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -1088,7 +1062,7 @@ where
|
||||
|
||||
/// Returns an Axum [Handler](axum::handler::Handler) that listens for a `GET` request and tries
|
||||
/// to route it using [leptos_router], asynchronously rendering an HTML page after all
|
||||
/// `async` [Resource](leptos::Resource)s have loaded.
|
||||
/// `async` resources have loaded.
|
||||
///
|
||||
/// This version allows us to pass Axum State/Extension/Extractor or other infro from Axum or network
|
||||
/// layers above Leptos itself. To use it, you'll need to write your own handler function that provides
|
||||
@@ -1121,8 +1095,7 @@ where
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [`Parts`]
|
||||
/// - [`ResponseOptions`]
|
||||
/// - [`ServerMetaContext`](leptos_meta::ServerMetaContext)
|
||||
/// - [`RouterIntegrationContext`](leptos_router::RouterIntegrationContext)
|
||||
/// - [`ServerMetaContext`]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -2020,12 +1993,12 @@ pub fn file_and_error_handler<S, IV>(
|
||||
+ 'static
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
S: Send + 'static,
|
||||
S: Send + Sync + Clone + 'static,
|
||||
LeptosOptions: FromRef<S>,
|
||||
{
|
||||
move |uri: Uri, State(options): State<S>, req: Request<Body>| {
|
||||
move |uri: Uri, State(state): State<S>, req: Request<Body>| {
|
||||
Box::pin(async move {
|
||||
let options = LeptosOptions::from_ref(&options);
|
||||
let options = LeptosOptions::from_ref(&state);
|
||||
let res = get_static_file(uri, &options.site_root, req.headers());
|
||||
let res = res.await.unwrap();
|
||||
|
||||
@@ -2033,7 +2006,9 @@ where
|
||||
res.into_response()
|
||||
} else {
|
||||
let mut res = handle_response_inner(
|
||||
|| {},
|
||||
move || {
|
||||
provide_context(state.clone());
|
||||
},
|
||||
move || shell(options),
|
||||
req,
|
||||
|app, chunks| {
|
||||
|
||||
@@ -40,15 +40,28 @@ pub trait ExtendResponse: Sized {
|
||||
let (owner, stream) =
|
||||
build_response(app_fn, additional_context, stream_builder);
|
||||
|
||||
let sc = owner.shared_context().unwrap();
|
||||
|
||||
let stream = stream.await.ready_chunks(32).map(|n| n.join(""));
|
||||
|
||||
let sc = owner.shared_context().unwrap();
|
||||
while let Some(pending) = sc.await_deferred() {
|
||||
pending.await;
|
||||
}
|
||||
|
||||
let mut stream =
|
||||
Box::pin(meta_context.inject_meta_context(stream).await);
|
||||
let mut stream = Box::pin(
|
||||
meta_context.inject_meta_context(stream).await.then({
|
||||
let sc = Arc::clone(&sc);
|
||||
move |chunk| {
|
||||
let sc = Arc::clone(&sc);
|
||||
async move {
|
||||
while let Some(pending) = sc.await_deferred() {
|
||||
pending.await;
|
||||
}
|
||||
chunk
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
// wait for the first chunk of the stream, then set the status and headers
|
||||
let first_chunk = stream.next().await.unwrap_or_default();
|
||||
|
||||
@@ -11,7 +11,10 @@ edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
throw_error = { workspace = true }
|
||||
any_spawner = { workspace = true, features = ["wasm-bindgen", "futures-executor"] }
|
||||
any_spawner = { workspace = true, features = [
|
||||
"wasm-bindgen",
|
||||
"futures-executor",
|
||||
] }
|
||||
base64 = { version = "0.22.1", optional = true }
|
||||
cfg-if = "1.0"
|
||||
hydration_context = { workspace = true }
|
||||
@@ -28,9 +31,13 @@ paste = "1.0"
|
||||
rand = { version = "0.8.5", optional = true }
|
||||
reactive_graph = { workspace = true, features = ["serde"] }
|
||||
rustc-hash = "2.0"
|
||||
tachys = { workspace = true, features = ["reactive_graph", "reactive_stores", "oco"] }
|
||||
tachys = { workspace = true, features = [
|
||||
"reactive_graph",
|
||||
"reactive_stores",
|
||||
"oco",
|
||||
] }
|
||||
thiserror = "2.0"
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
typed-builder = "0.20.0"
|
||||
typed-builder-macro = "0.20.0"
|
||||
serde = "1.0"
|
||||
@@ -45,25 +52,27 @@ web-sys = { version = "0.3.72", features = [
|
||||
"ShadowRootInit",
|
||||
"ShadowRootMode",
|
||||
] }
|
||||
wasm-bindgen = "0.2.95"
|
||||
wasm-bindgen = "0.2.97"
|
||||
serde_qs = "0.13.0"
|
||||
slotmap = "1.0"
|
||||
futures = "0.3.31"
|
||||
send_wrapper = "0.6.0"
|
||||
getrandom = { version = "0.2", features = ["js"], optional = true }
|
||||
|
||||
[features]
|
||||
hydration = [
|
||||
"reactive_graph/hydration",
|
||||
"leptos_server/hydration",
|
||||
"hydration_context/browser",
|
||||
"leptos_dom/hydration"
|
||||
"leptos_dom/hydration",
|
||||
]
|
||||
csr = ["leptos_macro/csr", "reactive_graph/effects"]
|
||||
csr = ["leptos_macro/csr", "reactive_graph/effects", "dep:getrandom"]
|
||||
hydrate = [
|
||||
"leptos_macro/hydrate",
|
||||
"hydration",
|
||||
"tachys/hydrate",
|
||||
"reactive_graph/effects",
|
||||
"dep:getrandom",
|
||||
]
|
||||
default-tls = ["server_fn/default-tls"]
|
||||
rustls = ["server_fn/rustls"]
|
||||
@@ -75,7 +84,10 @@ ssr = [
|
||||
"tachys/ssr",
|
||||
]
|
||||
nightly = ["leptos_macro/nightly", "reactive_graph/nightly", "tachys/nightly"]
|
||||
rkyv = ["server_fn/rkyv"]
|
||||
rkyv = [
|
||||
"server_fn/rkyv",
|
||||
"leptos_server/rkyv"
|
||||
]
|
||||
tracing = [
|
||||
"dep:tracing",
|
||||
"reactive_graph/tracing",
|
||||
@@ -89,7 +101,7 @@ spin = ["leptos-spin-macro"]
|
||||
islands = ["leptos_macro/islands", "dep:serde_json"]
|
||||
trace-component-props = [
|
||||
"leptos_macro/trace-component-props",
|
||||
"leptos_dom/trace-component-props"
|
||||
"leptos_dom/trace-component-props",
|
||||
]
|
||||
delegation = ["tachys/delegation"]
|
||||
|
||||
@@ -101,23 +113,56 @@ denylist = [
|
||||
"rustls",
|
||||
"default-tls",
|
||||
"wasm-bindgen",
|
||||
"rkyv", # was causing clippy issues on nightly
|
||||
"rkyv", # was causing clippy issues on nightly
|
||||
"trace-component-props",
|
||||
"spin",
|
||||
"islands",
|
||||
]
|
||||
skip_feature_sets = [
|
||||
["csr", "ssr"],
|
||||
["csr", "hydrate"],
|
||||
["ssr", "hydrate"],
|
||||
["serde", "serde-lite"],
|
||||
["serde-lite", "miniserde"],
|
||||
["serde", "miniserde"],
|
||||
["serde", "rkyv"],
|
||||
["miniserde", "rkyv"],
|
||||
["serde-lite", "rkyv"],
|
||||
["default-tls", "rustls"],
|
||||
[
|
||||
"csr",
|
||||
"ssr",
|
||||
],
|
||||
[
|
||||
"csr",
|
||||
"hydrate",
|
||||
],
|
||||
[
|
||||
"ssr",
|
||||
"hydrate",
|
||||
],
|
||||
[
|
||||
"serde",
|
||||
"serde-lite",
|
||||
],
|
||||
[
|
||||
"serde-lite",
|
||||
"miniserde",
|
||||
],
|
||||
[
|
||||
"serde",
|
||||
"miniserde",
|
||||
],
|
||||
[
|
||||
"serde",
|
||||
"rkyv",
|
||||
],
|
||||
[
|
||||
"miniserde",
|
||||
"rkyv",
|
||||
],
|
||||
[
|
||||
"serde-lite",
|
||||
"rkyv",
|
||||
],
|
||||
[
|
||||
"default-tls",
|
||||
"rustls",
|
||||
],
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
|
||||
|
||||
150
leptos/src/attribute_interceptor.rs
Normal file
150
leptos/src/attribute_interceptor.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
use crate::attr::{
|
||||
any_attribute::{AnyAttribute, IntoAnyAttribute},
|
||||
Attribute, NextAttribute,
|
||||
};
|
||||
use leptos::prelude::*;
|
||||
|
||||
/// Function stored to build/rebuild the wrapped children when attributes are added.
|
||||
type ChildBuilder<T> = dyn Fn(AnyAttribute) -> T + Send + Sync + 'static;
|
||||
|
||||
/// Intercepts attributes passed to your component, allowing passing them to any element.
|
||||
///
|
||||
/// By default, Leptos passes any attributes passed to your component (e.g. `<MyComponent
|
||||
/// attr:class="some-class"/>`) to the top-level element in the view returned by your component.
|
||||
/// [`AttributeInterceptor`] allows you to intercept this behavior and pass it onto any element in
|
||||
/// your component instead.
|
||||
///
|
||||
/// Must be the top level element in your component's view.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// Any attributes passed to MyComponent will be passed to the #inner element.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::prelude::*;
|
||||
/// use leptos::attribute_interceptor::AttributeInterceptor;
|
||||
///
|
||||
/// #[component]
|
||||
/// pub fn MyComponent() -> impl IntoView {
|
||||
/// view! {
|
||||
/// <AttributeInterceptor let:attrs>
|
||||
/// <div id="wrapper">
|
||||
/// <div id="inner" {..attrs} />
|
||||
/// </div>
|
||||
/// </AttributeInterceptor>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[component(transparent)]
|
||||
pub fn AttributeInterceptor<Chil, T>(
|
||||
/// The elements that will be rendered, with the attributes this component received as a
|
||||
/// parameter.
|
||||
children: Chil,
|
||||
) -> impl IntoView
|
||||
where
|
||||
Chil: Fn(AnyAttribute) -> T + Send + Sync + 'static,
|
||||
T: IntoView,
|
||||
{
|
||||
AttributeInterceptorInner::new(children)
|
||||
}
|
||||
|
||||
/// Wrapper to intercept attributes passed to a component so you can apply them to a different
|
||||
/// element.
|
||||
struct AttributeInterceptorInner<T: IntoView, A> {
|
||||
children_builder: Box<ChildBuilder<T>>,
|
||||
children: T,
|
||||
attributes: A,
|
||||
}
|
||||
|
||||
impl<T: IntoView> AttributeInterceptorInner<T, ()> {
|
||||
/// Use this as the returned view from your component to collect the attributes that are passed
|
||||
/// to your component so you can manually handle them.
|
||||
pub fn new<F>(children: F) -> Self
|
||||
where
|
||||
F: Fn(AnyAttribute) -> T + Send + Sync + 'static,
|
||||
{
|
||||
let children_builder = Box::new(children);
|
||||
let children = children_builder(().into_any_attr());
|
||||
|
||||
Self {
|
||||
children_builder,
|
||||
children,
|
||||
attributes: (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoView, A: Attribute> Render for AttributeInterceptorInner<T, A> {
|
||||
type State = <T as Render>::State;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
self.children.build()
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
self.children.rebuild(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoView, A> AddAnyAttr for AttributeInterceptorInner<T, A>
|
||||
where
|
||||
A: Attribute,
|
||||
{
|
||||
type Output<SomeNewAttr: leptos::attr::Attribute> =
|
||||
AttributeInterceptorInner<T, <<A as NextAttribute>::Output<SomeNewAttr> as Attribute>::CloneableOwned>;
|
||||
|
||||
fn add_any_attr<NewAttr: leptos::attr::Attribute>(
|
||||
self,
|
||||
attr: NewAttr,
|
||||
) -> Self::Output<NewAttr>
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml,
|
||||
{
|
||||
let attributes =
|
||||
self.attributes.add_any_attr(attr).into_cloneable_owned();
|
||||
|
||||
let children =
|
||||
(self.children_builder)(attributes.clone().into_any_attr());
|
||||
|
||||
AttributeInterceptorInner {
|
||||
children_builder: self.children_builder,
|
||||
children,
|
||||
attributes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoView, A: Attribute> RenderHtml for AttributeInterceptorInner<T, A> {
|
||||
type AsyncOutput = T::AsyncOutput;
|
||||
|
||||
const MIN_LENGTH: usize = T::MIN_LENGTH;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
self.children.dry_resolve()
|
||||
}
|
||||
|
||||
fn resolve(
|
||||
self,
|
||||
) -> impl std::future::Future<Output = Self::AsyncOutput> + Send {
|
||||
self.children.resolve()
|
||||
}
|
||||
|
||||
fn to_html_with_buf(
|
||||
self,
|
||||
buf: &mut String,
|
||||
position: &mut leptos::tachys::view::Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
self.children
|
||||
.to_html_with_buf(buf, position, escape, mark_branches)
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &leptos::tachys::hydration::Cursor,
|
||||
position: &leptos::tachys::view::PositionState,
|
||||
) -> Self::State {
|
||||
self.children.hydrate::<FROM_SERVER>(cursor, position)
|
||||
}
|
||||
}
|
||||
@@ -31,13 +31,13 @@
|
||||
//! *Notes*:
|
||||
//! - The `render_number` prop can receive any type that implements `Fn(i32) -> String`.
|
||||
//! - Callbacks are most useful when you want optional generic props.
|
||||
//! - All callbacks implement the [`Callable`] trait, and can be invoked with `my_callback.run(input)`.
|
||||
//! - All callbacks implement the [`Callable`](leptos::callback::Callable) trait, and can be invoked with `my_callback.run(input)`.
|
||||
//! - The callback types implement [`Copy`], so they can easily be moved into and out of other closures, just like signals.
|
||||
//!
|
||||
//! # Types
|
||||
//! This modules implements 2 callback types:
|
||||
//! - [`Callback`]
|
||||
//! - [`UnsyncCallback`]
|
||||
//! - [`Callback`](leptos::callback::Callback)
|
||||
//! - [`UnsyncCallback`](leptos::callback::UnsyncCallback)
|
||||
//!
|
||||
//! Use `SyncCallback` if the function is not `Sync` and `Send`.
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ type BoxedChildrenFn = Box<dyn Fn() -> AnyView + Send>;
|
||||
/// )
|
||||
/// }
|
||||
pub trait ToChildren<F> {
|
||||
/// Convert the provided type to (generally a closure) to Self (generally a "children" type,
|
||||
/// Convert the provided type (generally a closure) to Self (generally a "children" type,
|
||||
/// e.g., [Children]). See the implementations to see exactly which input types are supported
|
||||
/// and which "children" type they are converted to.
|
||||
fn to_children(f: F) -> Self;
|
||||
@@ -246,7 +246,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A typed equivalent to [`ChildrenMut`], which takes a generic but preserves type information to
|
||||
/// A typed equivalent to [`ChildrenFnMut`], which takes a generic but preserves type information to
|
||||
/// allow the compiler to optimize the view more effectively.
|
||||
pub struct TypedChildrenMut<T>(Box<dyn FnMut() -> View<T> + Send>);
|
||||
|
||||
@@ -285,6 +285,13 @@ impl<T> Debug for TypedChildrenFn<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for TypedChildrenFn<T> {
|
||||
// Manual implementation to avoid the `T: Clone` bound.
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TypedChildrenFn<T> {
|
||||
/// Extracts the inner `children` function.
|
||||
pub fn into_inner(self) -> Arc<dyn Fn() -> View<T> + Send + Sync> {
|
||||
|
||||
@@ -44,6 +44,67 @@ use tachys::{reactive_graph::OwnedView, view::keyed::keyed};
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// For convenience, you can also choose to write template code directly in the `<For>`
|
||||
/// component, using the `let` syntax:
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::prelude::*;
|
||||
///
|
||||
/// # #[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
/// # struct Counter {
|
||||
/// # id: usize,
|
||||
/// # count: RwSignal<i32>
|
||||
/// # }
|
||||
/// #
|
||||
/// # #[component]
|
||||
/// # fn Counters() -> impl IntoView {
|
||||
/// # let (counters, set_counters) = create_signal::<Vec<Counter>>(vec![]);
|
||||
/// #
|
||||
/// view! {
|
||||
/// <div>
|
||||
/// <For
|
||||
/// each=move || counters.get()
|
||||
/// key=|counter| counter.id
|
||||
/// let(counter)
|
||||
/// >
|
||||
/// <button>"Value: " {move || counter.count.get()}</button>
|
||||
/// </For>
|
||||
/// </div>
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// The `let` syntax also supports destructuring the pattern of your data.
|
||||
/// `let((one, two))` in the case of tuples, and `let(Struct { field_one, field_two })`
|
||||
/// in the case of structs.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::prelude::*;
|
||||
///
|
||||
/// # #[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
/// # struct Counter {
|
||||
/// # id: usize,
|
||||
/// # count: RwSignal<i32>
|
||||
/// # }
|
||||
/// #
|
||||
/// # #[component]
|
||||
/// # fn Counters() -> impl IntoView {
|
||||
/// # let (counters, set_counters) = create_signal::<Vec<Counter>>(vec![]);
|
||||
/// #
|
||||
/// view! {
|
||||
/// <div>
|
||||
/// <For
|
||||
/// each=move || counters.get()
|
||||
/// key=|counter| counter.id
|
||||
/// let(Counter { id, count })
|
||||
/// >
|
||||
/// <button>"Value: " {move || count.get()}</button>
|
||||
/// </For>
|
||||
/// </div>
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||
#[component]
|
||||
pub fn For<IF, I, T, EF, N, KF, K>(
|
||||
|
||||
@@ -72,9 +72,7 @@ use web_sys::{
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||
#[component]
|
||||
pub fn ActionForm<ServFn>(
|
||||
/// The action from which to build the form. This should include a URL, which can be generated
|
||||
/// by default using [`create_server_action`](leptos_server::create_server_action) or added
|
||||
/// manually using [`using_server_fn`](leptos_server::Action::using_server_fn).
|
||||
/// The action from which to build the form.
|
||||
action: ServerAction<ServFn>,
|
||||
/// A [`NodeRef`] in which the `<form>` element should be stored.
|
||||
#[prop(optional)]
|
||||
@@ -149,9 +147,7 @@ where
|
||||
/// progressively enhanced to use client-side routing.
|
||||
#[component]
|
||||
pub fn MultiActionForm<ServFn>(
|
||||
/// The action from which to build the form. This should include a URL, which can be generated
|
||||
/// by default using [create_server_action](leptos_server::create_server_action) or added
|
||||
/// manually using [leptos_server::Action::using_server_fn].
|
||||
/// The action from which to build the form.
|
||||
action: ServerMultiAction<ServFn>,
|
||||
/// A [`NodeRef`] in which the `<form>` element should be stored.
|
||||
#[prop(optional)]
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
((root, pkg_path, output_name, wasm_output_name) => {
|
||||
let MOST_RECENT_CHILDREN_CB;
|
||||
|
||||
function idle(c) {
|
||||
if ("requestIdleCallback" in window) {
|
||||
window.requestIdleCallback(c);
|
||||
@@ -6,55 +8,49 @@
|
||||
c();
|
||||
}
|
||||
}
|
||||
function islandTree(rootNode) {
|
||||
const tree = [];
|
||||
|
||||
function traverse(node, parent) {
|
||||
function hydrateIslands(rootNode, mod) {
|
||||
function traverse(node) {
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
if(node.tagName.toLowerCase() === 'leptos-island') {
|
||||
const tag = node.tagName.toLowerCase();
|
||||
if(tag === 'leptos-island') {
|
||||
const children = [];
|
||||
const id = node.dataset.component || null;
|
||||
const data = { id, node, children };
|
||||
|
||||
hydrateIsland(node, id, mod);
|
||||
|
||||
for(const child of node.children) {
|
||||
traverse(child, children);
|
||||
}
|
||||
|
||||
(parent || tree).push(data);
|
||||
} else {
|
||||
if(tag === 'leptos-children') {
|
||||
MOST_RECENT_CHILDREN_CB = node.$$on_hydrate;
|
||||
}
|
||||
for(const child of node.children) {
|
||||
traverse(child, parent);
|
||||
traverse(child);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
traverse(rootNode, null);
|
||||
|
||||
return { el: null, id: null, children: tree };
|
||||
traverse(rootNode);
|
||||
}
|
||||
function hydrateIsland(el, id, mod) {
|
||||
const islandFn = mod[id];
|
||||
if (islandFn) {
|
||||
if (MOST_RECENT_CHILDREN_CB) {
|
||||
MOST_RECENT_CHILDREN_CB();
|
||||
}
|
||||
islandFn(el);
|
||||
} else {
|
||||
console.warn(`Could not find WASM function for the island ${id}.`);
|
||||
}
|
||||
}
|
||||
function hydrateIslands(entry, mod) {
|
||||
if(entry.node) {
|
||||
hydrateIsland(entry.node, entry.id, mod);
|
||||
}
|
||||
for (const island of entry.children) {
|
||||
hydrateIslands(island, mod);
|
||||
}
|
||||
}
|
||||
idle(() => {
|
||||
import(`${root}/${pkg_path}/${output_name}.js`)
|
||||
.then(mod => {
|
||||
mod.default(`${root}/${pkg_path}/${wasm_output_name}.wasm`).then(() => {
|
||||
mod.hydrate();
|
||||
hydrateIslands(islandTree(document.body, null), mod);
|
||||
hydrateIslands(document.body, mod);
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
//! server actions, forms, and server-sent events (SSE).
|
||||
//! - **[`todomvc`]** shows the basics of building an isomorphic web app. Both the server and the client import the same app code.
|
||||
//! The server renders the app directly to an HTML string, and the client hydrates that HTML to make it interactive.
|
||||
//! You might also want to see how we use [`Effect::new`](leptos::prelude::Effect::new) to
|
||||
//! You might also want to see how we use [`Effect::new`](leptos::prelude::Effect) to
|
||||
//! [serialize JSON to `localStorage`](https://github.com/leptos-rs/leptos/blob/20af4928b2fffe017408d3f4e7330db22cf68277/examples/todomvc/src/lib.rs#L191-L209)
|
||||
//! and [reactively call DOM methods](https://github.com/leptos-rs/leptos/blob/16f084a71268ac325fbc4a5e50c260df185eadb6/examples/todomvc/src/lib.rs#L292-L296)
|
||||
//! on [references to elements](https://github.com/leptos-rs/leptos/blob/20af4928b2fffe017408d3f4e7330db22cf68277/examples/todomvc/src/lib.rs#L228).
|
||||
@@ -78,7 +78,7 @@
|
||||
//! + `async` interop: [`Resource`](leptos::prelude::Resource) for loading data using `async` functions
|
||||
//! and [`Action`](leptos::prelude::Action) to mutate data or imperatively call `async` functions.
|
||||
//! + reactions: [`Effect`](leptos::prelude::Effect) and [`RenderEffect`](leptos::prelude::RenderEffect).
|
||||
//! - **Templating/Views**: the [`view`] macro and [`IntoView`](leptos::IntoView) trait.
|
||||
//! - **Templating/Views**: the [`view`] macro and [`IntoView`] trait.
|
||||
//! - **Routing**: the [`leptos_router`](https://docs.rs/leptos_router/latest/leptos_router/) crate
|
||||
//! - **Server Functions**: the [`server`](macro@leptos::prelude::server) macro and [`ServerAction`](leptos::prelude::ServerAction).
|
||||
//!
|
||||
@@ -192,6 +192,9 @@ pub mod callback;
|
||||
/// Types that can be passed as the `children` prop of a component.
|
||||
pub mod children;
|
||||
|
||||
/// Wrapper for intercepting component attributes.
|
||||
pub mod attribute_interceptor;
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Traits used to implement component constructors.
|
||||
pub mod component;
|
||||
@@ -290,7 +293,7 @@ pub mod logging {
|
||||
|
||||
/// Utilities for working with asynchronous tasks.
|
||||
pub mod task {
|
||||
pub use any_spawner::Executor;
|
||||
pub use any_spawner::{self, CustomExecutor, Executor};
|
||||
use std::future::Future;
|
||||
|
||||
/// Spawns a thread-safe [`Future`].
|
||||
|
||||
@@ -51,6 +51,13 @@ use tachys::html::attribute::AttributeValue;
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Nonce(pub(crate) Arc<str>);
|
||||
|
||||
impl Nonce {
|
||||
/// Returns a reference to the inner reference-counted string slice representing the nonce.
|
||||
pub fn as_inner(&self) -> &Arc<str> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Nonce {
|
||||
type Target = str;
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ use reactive_graph::{
|
||||
traits::{Dispose, Get, Read, Track, With},
|
||||
};
|
||||
use slotmap::{DefaultKey, SlotMap};
|
||||
use std::sync::Arc;
|
||||
use tachys::{
|
||||
either::Either,
|
||||
html::attribute::Attribute,
|
||||
@@ -132,6 +133,18 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
fn nonce_or_not() -> Option<Arc<str>> {
|
||||
#[cfg(feature = "nonce")]
|
||||
{
|
||||
use crate::nonce::Nonce;
|
||||
use_context::<Nonce>().map(|n| n.0)
|
||||
}
|
||||
#[cfg(not(feature = "nonce"))]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SuspenseBoundary<const TRANSITION: bool, Fal, Chil> {
|
||||
pub id: SerializedDataId,
|
||||
pub none_pending: ArcMemo<bool>,
|
||||
@@ -379,7 +392,12 @@ where
|
||||
&mut fallback_position,
|
||||
mark_branches,
|
||||
);
|
||||
buf.push_async_out_of_order(fut, position, mark_branches);
|
||||
buf.push_async_out_of_order_with_nonce(
|
||||
fut,
|
||||
position,
|
||||
mark_branches,
|
||||
nonce_or_not(),
|
||||
);
|
||||
} else {
|
||||
buf.push_async({
|
||||
let mut position = *position;
|
||||
|
||||
@@ -85,41 +85,44 @@ pub fn Transition<Chil>(
|
||||
where
|
||||
Chil: IntoView + Send + 'static,
|
||||
{
|
||||
let (starts_local, id) = {
|
||||
Owner::current_shared_context()
|
||||
.map(|sc| {
|
||||
let id = sc.next_id();
|
||||
(sc.get_incomplete_chunk(&id), id)
|
||||
})
|
||||
.unwrap_or_else(|| (false, Default::default()))
|
||||
};
|
||||
let fallback = fallback.run();
|
||||
let children = children.into_inner()();
|
||||
let tasks = ArcRwSignal::new(SlotMap::<DefaultKey, ()>::new());
|
||||
provide_context(SuspenseContext {
|
||||
tasks: tasks.clone(),
|
||||
});
|
||||
let none_pending = ArcMemo::new(move |prev: Option<&bool>| {
|
||||
tasks.track();
|
||||
if prev.is_none() && starts_local {
|
||||
false
|
||||
} else {
|
||||
tasks.with(SlotMap::is_empty)
|
||||
}
|
||||
});
|
||||
if let Some(set_pending) = set_pending {
|
||||
Effect::new_isomorphic({
|
||||
let none_pending = none_pending.clone();
|
||||
move |_| {
|
||||
set_pending.set(!none_pending.get());
|
||||
let owner = Owner::new();
|
||||
owner.with(|| {
|
||||
let (starts_local, id) = {
|
||||
Owner::current_shared_context()
|
||||
.map(|sc| {
|
||||
let id = sc.next_id();
|
||||
(sc.get_incomplete_chunk(&id), id)
|
||||
})
|
||||
.unwrap_or_else(|| (false, Default::default()))
|
||||
};
|
||||
let fallback = fallback.run();
|
||||
let children = children.into_inner()();
|
||||
let tasks = ArcRwSignal::new(SlotMap::<DefaultKey, ()>::new());
|
||||
provide_context(SuspenseContext {
|
||||
tasks: tasks.clone(),
|
||||
});
|
||||
let none_pending = ArcMemo::new(move |prev: Option<&bool>| {
|
||||
tasks.track();
|
||||
if prev.is_none() && starts_local {
|
||||
false
|
||||
} else {
|
||||
tasks.with(SlotMap::is_empty)
|
||||
}
|
||||
});
|
||||
}
|
||||
if let Some(set_pending) = set_pending {
|
||||
Effect::new_isomorphic({
|
||||
let none_pending = none_pending.clone();
|
||||
move |_| {
|
||||
set_pending.set(!none_pending.get());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
OwnedView::new(SuspenseBoundary::<true, _, _> {
|
||||
id,
|
||||
none_pending,
|
||||
fallback,
|
||||
children,
|
||||
OwnedView::new(SuspenseBoundary::<true, _, _> {
|
||||
id,
|
||||
none_pending,
|
||||
fallback,
|
||||
children,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1535,7 +1535,7 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.1"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
||||
dependencies = [
|
||||
@@ -1851,7 +1851,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote-use"
|
||||
version = "0.7.1"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58e9a38ef862d7fec635661503289062bc5b3035e61859a8de3d3f81823accd2"
|
||||
dependencies = [
|
||||
@@ -1953,7 +1953,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ron"
|
||||
version = "0.7.1"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a"
|
||||
dependencies = [
|
||||
@@ -2104,7 +2104,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||
dependencies = [
|
||||
|
||||
@@ -26,3 +26,6 @@ temp-env = { version = "0.3.6", features = ["async_closure"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
|
||||
@@ -12,10 +12,10 @@ edition.workspace = true
|
||||
tachys = { workspace = true }
|
||||
reactive_graph = { workspace = true }
|
||||
or_poisoned = { workspace = true }
|
||||
js-sys = "0.3.72"
|
||||
js-sys = "0.3.74"
|
||||
send_wrapper = "0.6.0"
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
wasm-bindgen = "0.2.95"
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
wasm-bindgen = "0.2.97"
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
serde = { version = "1.0", optional = true }
|
||||
|
||||
@@ -37,3 +37,6 @@ rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["tracing"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
|
||||
@@ -131,6 +131,10 @@ impl AnimationFrameRequestHandle {
|
||||
|
||||
/// Runs the given function between the next repaint using
|
||||
/// [`Window.requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame).
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(feature = "tracing", instrument(level = "trace", skip_all))]
|
||||
#[inline(always)]
|
||||
pub fn request_animation_frame(cb: impl FnOnce() + 'static) {
|
||||
@@ -159,6 +163,10 @@ fn closure_once(cb: impl FnOnce() + 'static) -> JsValue {
|
||||
/// Runs the given function between the next repaint using
|
||||
/// [`Window.requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame),
|
||||
/// returning a cancelable handle.
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(feature = "tracing", instrument(level = "trace", skip_all))]
|
||||
#[inline(always)]
|
||||
pub fn request_animation_frame_with_handle(
|
||||
@@ -197,6 +205,10 @@ impl IdleCallbackHandle {
|
||||
|
||||
/// Queues the given function during an idle period using
|
||||
/// [`Window.requestIdleCallback`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestIdleCallback).
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(feature = "tracing", instrument(level = "trace", skip_all))]
|
||||
#[inline(always)]
|
||||
pub fn request_idle_callback(cb: impl Fn() + 'static) {
|
||||
@@ -206,6 +218,10 @@ pub fn request_idle_callback(cb: impl Fn() + 'static) {
|
||||
/// Queues the given function during an idle period using
|
||||
/// [`Window.requestIdleCallback`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestIdleCallback),
|
||||
/// returning a cancelable handle.
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(feature = "tracing", instrument(level = "trace", skip_all))]
|
||||
#[inline(always)]
|
||||
pub fn request_idle_callback_with_handle(
|
||||
@@ -239,6 +255,8 @@ pub fn request_idle_callback_with_handle(
|
||||
/// to perform final cleanup or other just-before-rendering tasks.
|
||||
///
|
||||
/// [MDN queueMicrotask](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask)
|
||||
///
|
||||
/// <div class="warning">The task is called outside of the ownership tree, this means that if you want to access for example the context you need to reestablish the owner.</div>
|
||||
pub fn queue_microtask(task: impl FnOnce() + 'static) {
|
||||
use js_sys::{Function, Reflect};
|
||||
|
||||
@@ -265,6 +283,10 @@ impl TimeoutHandle {
|
||||
|
||||
/// Executes the given function after the given duration of time has passed.
|
||||
/// [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout).
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
instrument(level = "trace", skip_all, fields(duration = ?duration))
|
||||
@@ -275,6 +297,10 @@ pub fn set_timeout(cb: impl FnOnce() + 'static, duration: Duration) {
|
||||
|
||||
/// Executes the given function after the given duration of time has passed, returning a cancelable handle.
|
||||
/// [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout).
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
instrument(level = "trace", skip_all, fields(duration = ?duration))
|
||||
@@ -331,6 +357,10 @@ pub fn set_timeout_with_handle(
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
pub fn debounce<T: 'static>(
|
||||
delay: Duration,
|
||||
mut cb: impl FnMut(T) + 'static,
|
||||
@@ -398,6 +428,10 @@ impl IntervalHandle {
|
||||
|
||||
/// Repeatedly calls the given function, with a delay of the given duration between calls.
|
||||
/// See [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval).
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
instrument(level = "trace", skip_all, fields(duration = ?duration))
|
||||
@@ -409,6 +443,10 @@ pub fn set_interval(cb: impl Fn() + 'static, duration: Duration) {
|
||||
/// Repeatedly calls the given function, with a delay of the given duration between calls,
|
||||
/// returning a cancelable handle.
|
||||
/// See [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval).
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
instrument(level = "trace", skip_all, fields(duration = ?duration))
|
||||
@@ -451,6 +489,10 @@ pub fn set_interval_with_handle(
|
||||
|
||||
/// Adds an event listener to the `Window`, typed as a generic `Event`,
|
||||
/// returning a cancelable handle.
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
instrument(level = "trace", skip_all, fields(event_name = %event_name))
|
||||
@@ -519,6 +561,10 @@ pub fn window_event_listener_untyped(
|
||||
/// on_cleanup(move || handle.remove());
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
pub fn window_event_listener<E: EventDescriptor + 'static>(
|
||||
event: E,
|
||||
cb: impl Fn(E::EventType) + 'static,
|
||||
|
||||
@@ -29,14 +29,9 @@ macro_rules! error {
|
||||
macro_rules! debug_warn {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
if cfg!(debug_assertions) {
|
||||
$crate::warn!($($x)*)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
($($x)*)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_macro"
|
||||
version = "0.7.0-rc1"
|
||||
version = "0.7.4"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
@@ -13,7 +13,7 @@ edition.workspace = true
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
attribute-derive = { version = "0.10.2", features = ["syn-full"] }
|
||||
attribute-derive = { version = "0.10.3", features = ["syn-full"] }
|
||||
cfg-if = "1.0"
|
||||
html-escape = "0.2.13"
|
||||
itertools = "0.13.0"
|
||||
@@ -27,7 +27,7 @@ leptos_hot_reload = { workspace = true }
|
||||
server_fn_macro = { workspace = true }
|
||||
convert_case = "0.6.0"
|
||||
uuid = { version = "1.11", features = ["v4"] }
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
log = "0.4.22"
|
||||
@@ -45,6 +45,7 @@ ssr = ["server_fn_macro/ssr", "leptos/ssr"]
|
||||
nightly = ["server_fn_macro/nightly"]
|
||||
tracing = ["dep:tracing"]
|
||||
islands = []
|
||||
trace-components = []
|
||||
trace-component-props = []
|
||||
actix = ["server_fn_macro/actix"]
|
||||
axum = ["server_fn_macro/axum"]
|
||||
@@ -83,4 +84,7 @@ skip_feature_sets = [
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(erase_components)'] }
|
||||
unexpected_cfgs = { level = "warn", check-cfg = [
|
||||
'cfg(leptos_debuginfo)',
|
||||
'cfg(erase_components)',
|
||||
] }
|
||||
|
||||
@@ -201,13 +201,35 @@ impl ToTokens for Model {
|
||||
) = {
|
||||
#[cfg(feature = "tracing")]
|
||||
{
|
||||
/* TODO for 0.8: fix this
|
||||
*
|
||||
* The problem is that cargo now warns about an expected "tracing" cfg if
|
||||
* you don't have a "tracing" feature in your actual crate
|
||||
*
|
||||
* However, until https://github.com/tokio-rs/tracing/pull/1819 is merged
|
||||
* (?), you can't provide an alternate path for `tracing` (for example,
|
||||
* ::leptos::tracing), which means that if you're going to use the macro
|
||||
* you *must* have `tracing` in your Cargo.toml.
|
||||
*
|
||||
* Including the feature-check here causes cargo warnings on
|
||||
* previously-working projects.
|
||||
*
|
||||
* Removing the feature-check here breaks any project that uses leptos with
|
||||
* the tracing feature turned on, but without a tracing dependency in its
|
||||
* Cargo.toml.
|
||||
* /
|
||||
*/
|
||||
let instrument = cfg!(feature = "trace-components").then(|| quote! {
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
::leptos::tracing::instrument(level = "info", name = #trace_name, skip_all)
|
||||
)]
|
||||
});
|
||||
|
||||
(
|
||||
quote! {
|
||||
#[allow(clippy::let_with_type_underscore)]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
::leptos::tracing::instrument(level = "info", name = #trace_name, skip_all)
|
||||
)]
|
||||
#instrument
|
||||
},
|
||||
quote! {
|
||||
let __span = ::leptos::tracing::Span::current();
|
||||
@@ -260,8 +282,12 @@ 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 || {
|
||||
#body_name(#prop_names)
|
||||
::leptos::reactive::owner::Owner::new().with(|| {
|
||||
::leptos::reactive::owner::Owner::with_hydration(move || {
|
||||
::leptos::tachys::reactive_graph::OwnedView::new({
|
||||
#body_name(#prop_names)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
} else {
|
||||
@@ -301,8 +327,8 @@ impl ToTokens for Model {
|
||||
let hydrate_fn_name = hydrate_fn_name.as_ref().unwrap();
|
||||
quote! {
|
||||
{
|
||||
if ::leptos::reactive::owner::Owner::current_shared_context()
|
||||
.map(|sc| sc.get_is_hydrating())
|
||||
if ::leptos::context::use_context::<::leptos::reactive::owner::IsHydrating>()
|
||||
.map(|h| h.0)
|
||||
.unwrap_or(false) {
|
||||
::leptos::either::Either::Left(
|
||||
#component
|
||||
@@ -339,9 +365,23 @@ impl ToTokens for Model {
|
||||
let children = Box::new(|| {
|
||||
let sc = ::leptos::reactive::owner::Owner::current_shared_context().unwrap();
|
||||
let prev = sc.get_is_hydrating();
|
||||
let value = ::leptos::reactive::owner::Owner::with_no_hydration(||
|
||||
::leptos::tachys::html::islands::IslandChildren::new(children()).into_any()
|
||||
);
|
||||
let owner = ::leptos::reactive::owner::Owner::new();
|
||||
let value = owner.clone().with(|| {
|
||||
::leptos::reactive::owner::Owner::with_no_hydration(move || {
|
||||
::leptos::tachys::reactive_graph::OwnedView::new({
|
||||
::leptos::tachys::html::islands::IslandChildren::new_with_on_hydrate(
|
||||
children(),
|
||||
{
|
||||
let owner = owner.clone();
|
||||
move || {
|
||||
owner.set()
|
||||
}
|
||||
}
|
||||
|
||||
)
|
||||
}).into_any()
|
||||
})
|
||||
});
|
||||
sc.set_is_hydrating(prev);
|
||||
value
|
||||
});
|
||||
@@ -424,20 +464,21 @@ impl ToTokens for Model {
|
||||
};
|
||||
let children = if is_island_with_children {
|
||||
quote! {
|
||||
.children({Box::new(|| {
|
||||
.children({
|
||||
let owner = leptos::reactive::owner::Owner::current();
|
||||
Box::new(move || {
|
||||
use leptos::tachys::view::any_view::IntoAny;
|
||||
::leptos::tachys::html::islands::IslandChildren::new(
|
||||
// TODO owner restoration for context
|
||||
()
|
||||
::leptos::tachys::html::islands::IslandChildren::new_with_on_hydrate(
|
||||
(),
|
||||
{
|
||||
let owner = owner.clone();
|
||||
move || {
|
||||
if let Some(owner) = &owner {
|
||||
owner.set()
|
||||
}
|
||||
}
|
||||
}
|
||||
).into_any()})})
|
||||
//.children(children)
|
||||
/*.children(Box::new(|| {
|
||||
use leptos::tachys::view::any_view::IntoAny;
|
||||
::leptos::tachys::html::islands::IslandChildren::new(
|
||||
// TODO owner restoration for context
|
||||
()
|
||||
).into_any()
|
||||
}))*/
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
@@ -655,14 +696,44 @@ impl Prop {
|
||||
abort!(e.span(), e.to_string());
|
||||
});
|
||||
|
||||
let name = if let Pat::Ident(i) = *typed.pat {
|
||||
i
|
||||
} else {
|
||||
abort!(
|
||||
typed.pat,
|
||||
"only `prop: bool` style types are allowed within the \
|
||||
`#[component]` macro"
|
||||
);
|
||||
let name = match *typed.pat {
|
||||
Pat::Ident(i) => {
|
||||
if let Some(name) = &prop_opts.name {
|
||||
PatIdent {
|
||||
attrs: vec![],
|
||||
by_ref: None,
|
||||
mutability: None,
|
||||
ident: Ident::new(name, i.span()),
|
||||
subpat: None,
|
||||
}
|
||||
} else {
|
||||
i
|
||||
}
|
||||
}
|
||||
Pat::Struct(_) | Pat::Tuple(_) | Pat::TupleStruct(_) => {
|
||||
if let Some(name) = &prop_opts.name {
|
||||
PatIdent {
|
||||
attrs: vec![],
|
||||
by_ref: None,
|
||||
mutability: None,
|
||||
ident: Ident::new(name, typed.pat.span()),
|
||||
subpat: None,
|
||||
}
|
||||
} else {
|
||||
abort!(
|
||||
typed.pat,
|
||||
"destructured props must be given a name e.g. \
|
||||
#[prop(name = \"data\")]"
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
abort!(
|
||||
typed.pat,
|
||||
"only `prop: bool` style types are allowed within the \
|
||||
`#[component]` macro"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Self {
|
||||
@@ -865,6 +936,7 @@ struct PropOpt {
|
||||
default: Option<syn::Expr>,
|
||||
into: bool,
|
||||
attrs: bool,
|
||||
name: Option<String>,
|
||||
}
|
||||
|
||||
struct TypedBuilderOpts {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Macros for use with the [`leptos`] framework.
|
||||
//! Macros for use with the Leptos framework.
|
||||
|
||||
#![cfg_attr(feature = "nightly", feature(proc_macro_span))]
|
||||
#![forbid(unsafe_code)]
|
||||
@@ -272,8 +272,8 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
||||
view_macro_impl(tokens, false)
|
||||
}
|
||||
|
||||
/// The `template` macro behaves like [`view`], except that it wraps the entire tree in a
|
||||
/// [`ViewTemplate`](leptos::prelude::ViewTemplate). This optimizes creation speed by rendering
|
||||
/// The `template` macro behaves like [`view`](view!), except that it wraps the entire tree in a
|
||||
/// [`ViewTemplate`](https://docs.rs/leptos/0.7.0-gamma3/leptos/prelude/struct.ViewTemplate.html). This optimizes creation speed by rendering
|
||||
/// most of the view into a `<template>` tag with HTML rendered at compile time, then hydrating it.
|
||||
/// In exchange, there is a small binary size overhead.
|
||||
#[proc_macro_error2::proc_macro_error]
|
||||
@@ -366,7 +366,7 @@ fn normalized_call_site(site: proc_macro::Span) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
/// This behaves like the [`view`] macro, but loads the view from an external file instead of
|
||||
/// This behaves like the [`view`](view!) macro, but loads the view from an external file instead of
|
||||
/// parsing it inline.
|
||||
///
|
||||
/// This is designed to allow editing views in a separate file, if this improves a user's workflow.
|
||||
@@ -639,7 +639,7 @@ pub fn island(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
abort!(
|
||||
transparent,
|
||||
"only `transparent` is supported";
|
||||
help = "try `#[component(transparent)]` or `#[component]`"
|
||||
help = "try `#[island(transparent)]` or `#[island]`"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -108,9 +108,12 @@ pub(crate) fn component_to_tokens(
|
||||
let KeyedAttributeValue::Binding(binding) = &attr.possible_value
|
||||
else {
|
||||
if let Some(ident) = attr.key.to_string().strip_prefix("let:") {
|
||||
let ident1 =
|
||||
format_ident!("{ident}", span = attr.key.span());
|
||||
return Some(quote! { #ident1 });
|
||||
let span = match &attr.key {
|
||||
NodeName::Punctuated(path) => path[1].span(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let ident1 = format_ident!("{ident}", span = span);
|
||||
return Some(quote_spanned! { span => #ident1 });
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -652,6 +652,18 @@ pub(crate) fn element_to_tokens(
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let NodeAttribute::Attribute(a) = a {
|
||||
if let Some(Tuple(_)) = a.value() {
|
||||
return Ordering::Greater;
|
||||
}
|
||||
}
|
||||
if let NodeAttribute::Attribute(b) = b {
|
||||
if let Some(Tuple(_)) = b.value() {
|
||||
return Ordering::Less;
|
||||
}
|
||||
}
|
||||
|
||||
match (key_a.as_deref(), key_b.as_deref()) {
|
||||
(Some("class"), Some("class")) | (Some("style"), Some("style")) => {
|
||||
Ordering::Equal
|
||||
@@ -755,7 +767,7 @@ pub(crate) fn element_to_tokens(
|
||||
let name = node.name().to_string();
|
||||
// link custom ident to name span for IDE docs
|
||||
let custom = Ident::new("custom", name.span());
|
||||
quote! { ::leptos::tachys::html::element::#custom(#name) }
|
||||
quote_spanned! { node.name().span() => ::leptos::tachys::html::element::#custom(#name) }
|
||||
} else if is_svg_element(&tag) {
|
||||
parent_type = TagType::Svg;
|
||||
let name = if tag == "use" || tag == "use_" {
|
||||
@@ -763,33 +775,33 @@ pub(crate) fn element_to_tokens(
|
||||
} else {
|
||||
name.to_token_stream()
|
||||
};
|
||||
quote! { ::leptos::tachys::svg::#name() }
|
||||
quote_spanned! { node.name().span() => ::leptos::tachys::svg::#name() }
|
||||
} else if is_math_ml_element(&tag) {
|
||||
parent_type = TagType::Math;
|
||||
quote! { ::leptos::tachys::mathml::#name() }
|
||||
quote_spanned! { node.name().span() => ::leptos::tachys::mathml::#name() }
|
||||
} else if is_ambiguous_element(&tag) {
|
||||
match parent_type {
|
||||
TagType::Unknown => {
|
||||
// We decided this warning was too aggressive, but I'll leave it here in case we want it later
|
||||
/* proc_macro_error2::emit_warning!(name.span(), "The view macro is assuming this is an HTML element, \
|
||||
but it is ambiguous; if it is an SVG or MathML element, prefix with svg:: or math::"); */
|
||||
quote! {
|
||||
quote_spanned! { node.name().span() =>
|
||||
::leptos::tachys::html::element::#name()
|
||||
}
|
||||
}
|
||||
TagType::Html => {
|
||||
quote! { ::leptos::tachys::html::element::#name() }
|
||||
quote_spanned! { node.name().span() => ::leptos::tachys::html::element::#name() }
|
||||
}
|
||||
TagType::Svg => {
|
||||
quote! { ::leptos::tachys::svg::#name() }
|
||||
quote_spanned! { node.name().span() => ::leptos::tachys::svg::#name() }
|
||||
}
|
||||
TagType::Math => {
|
||||
quote! { ::leptos::tachys::math::#name() }
|
||||
quote_spanned! { node.name().span() => ::leptos::tachys::math::#name() }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
parent_type = TagType::Html;
|
||||
quote! { ::leptos::tachys::html::element::#name() }
|
||||
quote_spanned! { name.span() => ::leptos::tachys::html::element::#name() }
|
||||
};
|
||||
|
||||
/* TODO restore this
|
||||
@@ -1639,7 +1651,7 @@ pub(crate) fn ident_from_tag_name(tag_name: &NodeName) -> Ident {
|
||||
.path
|
||||
.segments
|
||||
.iter()
|
||||
.last()
|
||||
.next_back()
|
||||
.map(|segment| segment.ident.clone())
|
||||
.expect("element needs to have a name"),
|
||||
NodeName::Block(_) => {
|
||||
@@ -1710,7 +1722,7 @@ fn tuple_name(name: &str, node: &KeyedAttribute) -> TupleName {
|
||||
TupleName::None
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum TupleName {
|
||||
None,
|
||||
Str(String),
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
use core::num::NonZeroUsize;
|
||||
use leptos::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct UserInfo {
|
||||
user_id: String,
|
||||
email: String,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct Admin(bool);
|
||||
|
||||
#[component]
|
||||
fn Component(
|
||||
#[prop(optional)] optional: bool,
|
||||
@@ -10,6 +19,10 @@ fn Component(
|
||||
#[prop(default = NonZeroUsize::new(10).unwrap())] default: NonZeroUsize,
|
||||
#[prop(into)] into: String,
|
||||
impl_trait: impl Fn() -> i32 + 'static,
|
||||
#[prop(name = "data")] UserInfo { email, user_id }: UserInfo,
|
||||
#[prop(name = "tuple")] (name, id): (String, i32),
|
||||
#[prop(name = "tuple_struct")] Admin(is_admin): Admin,
|
||||
#[prop(name = "outside_name")] inside_name: i32,
|
||||
) -> impl IntoView {
|
||||
_ = optional;
|
||||
_ = optional_into;
|
||||
@@ -18,6 +31,12 @@ fn Component(
|
||||
_ = default;
|
||||
_ = into;
|
||||
_ = impl_trait;
|
||||
_ = email;
|
||||
_ = user_id;
|
||||
_ = id;
|
||||
_ = name;
|
||||
_ = is_admin;
|
||||
_ = inside_name;
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -26,6 +45,13 @@ fn component() {
|
||||
.into("")
|
||||
.strip_option(9)
|
||||
.impl_trait(|| 42)
|
||||
.data(UserInfo {
|
||||
email: "em@il".into(),
|
||||
user_id: "1".into(),
|
||||
})
|
||||
.tuple(("Joe".into(), 12))
|
||||
.tuple_struct(Admin(true))
|
||||
.outside_name(1)
|
||||
.build();
|
||||
assert!(!cp.optional);
|
||||
assert_eq!(cp.optional_into, None);
|
||||
@@ -34,6 +60,16 @@ fn component() {
|
||||
assert_eq!(cp.default, NonZeroUsize::new(10).unwrap());
|
||||
assert_eq!(cp.into, "");
|
||||
assert_eq!((cp.impl_trait)(), 42);
|
||||
assert_eq!(
|
||||
cp.data,
|
||||
UserInfo {
|
||||
email: "em@il".into(),
|
||||
user_id: "1".into(),
|
||||
}
|
||||
);
|
||||
assert_eq!(cp.tuple, ("Joe".into(), 12));
|
||||
assert_eq!(cp.tuple_struct, Admin(true));
|
||||
assert_eq!(cp.outside_name, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -45,12 +81,26 @@ fn component_nostrip() {
|
||||
strip_option=9
|
||||
into=""
|
||||
impl_trait=|| 42
|
||||
data=UserInfo {
|
||||
email: "em@il".into(),
|
||||
user_id: "1".into(),
|
||||
}
|
||||
tuple=("Joe".into(), 12)
|
||||
tuple_struct=Admin(true)
|
||||
outside_name=1
|
||||
/>
|
||||
<Component
|
||||
nostrip:optional_into=Some("foo")
|
||||
strip_option=9
|
||||
into=""
|
||||
impl_trait=|| 42
|
||||
data=UserInfo {
|
||||
email: "em@il".into(),
|
||||
user_id: "1".into(),
|
||||
}
|
||||
tuple=("Joe".into(), 12)
|
||||
tuple_struct=Admin(true)
|
||||
outside_name=1
|
||||
/>
|
||||
};
|
||||
}
|
||||
|
||||
@@ -44,4 +44,10 @@ fn default_with_invalid_value(
|
||||
_ = default;
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn destructure_without_name((default, value): (bool, i32)) -> impl IntoView {
|
||||
_ = default;
|
||||
_ = value;
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default`, `into` and `attrs`
|
||||
error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default`, `into`, `attrs` and `name`
|
||||
--> tests/ui/component.rs:10:31
|
||||
|
|
||||
10 | fn unknown_prop_option(#[prop(hello)] test: bool) -> impl IntoView {
|
||||
@@ -41,3 +41,9 @@ error: unexpected end of input, expected one of: identifier, `::`, `<`, `_`, lit
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the attribute macro `component` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: destructured props must be given a name e.g. #[prop(name = "data")]
|
||||
--> tests/ui/component.rs:48:29
|
||||
|
|
||||
48 | fn destructure_without_name((default, value): (bool, i32)) -> impl IntoView {
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default`, `into` and `attrs`
|
||||
error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default`, `into`, `attrs` and `name`
|
||||
--> tests/ui/component_absolute.rs:5:31
|
||||
|
|
||||
5 | fn unknown_prop_option(#[prop(hello)] test: bool) -> impl ::leptos::IntoView {
|
||||
|
||||
@@ -15,7 +15,7 @@ codee = { version = "0.2.0", features = ["json_serde"] }
|
||||
hydration_context = { workspace = true }
|
||||
reactive_graph = { workspace = true, features = ["hydration"] }
|
||||
server_fn = { workspace = true }
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
futures = "0.3.31"
|
||||
|
||||
any_spawner = { workspace = true }
|
||||
@@ -25,8 +25,8 @@ send_wrapper = "0.6"
|
||||
|
||||
# serialization formats
|
||||
serde = { version = "1.0" }
|
||||
js-sys = { version = "0.3.72", optional = true }
|
||||
wasm-bindgen = { version = "0.2.95", optional = true }
|
||||
js-sys = { version = "0.3.74", optional = true }
|
||||
wasm-bindgen = { version = "0.2.97", optional = true }
|
||||
serde_json = { version = "1.0" }
|
||||
|
||||
[features]
|
||||
@@ -44,3 +44,6 @@ denylist = ["tracing"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
|
||||
@@ -43,7 +43,7 @@ where
|
||||
S::Output: 'static,
|
||||
{
|
||||
inner: ArcAction<S, Result<S::Output, ServerFnError<S::Error>>>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ where
|
||||
inner: ArcAction::new_with_value(err, |input: &S| {
|
||||
S::run_on_client(input.clone())
|
||||
}),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,7 @@ where
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
}
|
||||
}
|
||||
@@ -114,11 +114,11 @@ where
|
||||
S::Output: 'static,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -132,7 +132,7 @@ where
|
||||
S::Output: 'static,
|
||||
{
|
||||
inner: Action<S, Result<S::Output, ServerFnError<S::Error>>>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ where
|
||||
inner: Action::new_with_value(err, |input: &S| {
|
||||
S::run_on_client(input.clone())
|
||||
}),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -217,11 +217,11 @@ where
|
||||
S::Output: 'static,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Utilities for communicating between the server and the client with [`leptos`].
|
||||
//! Utilities for communicating between the server and the client with Leptos.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
@@ -8,8 +8,11 @@ use reactive_graph::{
|
||||
ToAnySource, ToAnySubscriber,
|
||||
},
|
||||
owner::use_context,
|
||||
signal::guards::{AsyncPlain, ReadGuard},
|
||||
traits::{DefinedAt, IsDisposed, ReadUntracked},
|
||||
signal::{
|
||||
guards::{AsyncPlain, ReadGuard},
|
||||
ArcRwSignal, RwSignal,
|
||||
},
|
||||
traits::{DefinedAt, IsDisposed, ReadUntracked, Track, Update, Write},
|
||||
};
|
||||
use send_wrapper::SendWrapper;
|
||||
use std::{
|
||||
@@ -20,7 +23,8 @@ use std::{
|
||||
/// A reference-counted resource that only loads its data locally on the client.
|
||||
pub struct ArcLocalResource<T> {
|
||||
data: ArcAsyncDerived<SendWrapper<T>>,
|
||||
#[cfg(debug_assertions)]
|
||||
refetch: ArcRwSignal<usize>,
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -28,7 +32,8 @@ impl<T> Clone for ArcLocalResource<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
data: self.data.clone(),
|
||||
#[cfg(debug_assertions)]
|
||||
refetch: self.refetch.clone(),
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
}
|
||||
}
|
||||
@@ -65,15 +70,27 @@ impl<T> ArcLocalResource<T> {
|
||||
}
|
||||
};
|
||||
let fetcher = SendWrapper::new(fetcher);
|
||||
Self {
|
||||
data: ArcAsyncDerived::new(move || {
|
||||
let refetch = ArcRwSignal::new(0);
|
||||
let data = {
|
||||
let refetch = refetch.clone();
|
||||
ArcAsyncDerived::new(move || {
|
||||
refetch.track();
|
||||
let fut = fetcher();
|
||||
SendWrapper::new(async move { SendWrapper::new(fut.await) })
|
||||
}),
|
||||
#[cfg(debug_assertions)]
|
||||
})
|
||||
};
|
||||
Self {
|
||||
data,
|
||||
refetch,
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Re-runs the async function.
|
||||
pub fn refetch(&self) {
|
||||
*self.refetch.write() += 1;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoFuture for ArcLocalResource<T>
|
||||
@@ -104,11 +121,11 @@ where
|
||||
|
||||
impl<T> DefinedAt for ArcLocalResource<T> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -200,7 +217,8 @@ impl<T> Subscriber for ArcLocalResource<T> {
|
||||
/// A resource that only loads its data locally on the client.
|
||||
pub struct LocalResource<T> {
|
||||
data: AsyncDerived<SendWrapper<T>>,
|
||||
#[cfg(debug_assertions)]
|
||||
refetch: RwSignal<usize>,
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -242,6 +260,7 @@ impl<T> LocalResource<T> {
|
||||
}
|
||||
}
|
||||
};
|
||||
let refetch = RwSignal::new(0);
|
||||
|
||||
Self {
|
||||
data: if cfg!(feature = "ssr") {
|
||||
@@ -249,14 +268,21 @@ impl<T> LocalResource<T> {
|
||||
} else {
|
||||
let fetcher = SendWrapper::new(fetcher);
|
||||
AsyncDerived::new(move || {
|
||||
refetch.track();
|
||||
let fut = fetcher();
|
||||
SendWrapper::new(async move { SendWrapper::new(fut.await) })
|
||||
})
|
||||
},
|
||||
#[cfg(debug_assertions)]
|
||||
refetch,
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Re-runs the async function.
|
||||
pub fn refetch(&self) {
|
||||
self.refetch.try_update(|n| *n += 1);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoFuture for LocalResource<T>
|
||||
@@ -287,11 +313,11 @@ where
|
||||
|
||||
impl<T> DefinedAt for LocalResource<T> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -398,7 +424,8 @@ impl<T: 'static> From<ArcLocalResource<T>> for LocalResource<T> {
|
||||
fn from(arc: ArcLocalResource<T>) -> Self {
|
||||
Self {
|
||||
data: arc.data.into(),
|
||||
#[cfg(debug_assertions)]
|
||||
refetch: arc.refetch.into(),
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: arc.defined_at,
|
||||
}
|
||||
}
|
||||
@@ -408,7 +435,8 @@ impl<T: 'static> From<LocalResource<T>> for ArcLocalResource<T> {
|
||||
fn from(local: LocalResource<T>) -> Self {
|
||||
Self {
|
||||
data: local.data.into(),
|
||||
#[cfg(debug_assertions)]
|
||||
refetch: local.refetch.into(),
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: local.defined_at,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ where
|
||||
S::Output: 'static,
|
||||
{
|
||||
inner: ArcMultiAction<S, Result<S::Output, ServerFnError<S::Error>>>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ where
|
||||
inner: ArcMultiAction::new(|input: &S| {
|
||||
S::run_on_client(input.clone())
|
||||
}),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ where
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
}
|
||||
}
|
||||
@@ -78,11 +78,11 @@ where
|
||||
S::Output: 'static,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -96,7 +96,7 @@ where
|
||||
S::Output: 'static,
|
||||
{
|
||||
inner: MultiAction<S, Result<S::Output, ServerFnError<S::Error>>>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ where
|
||||
inner: MultiAction::new(|input: &S| {
|
||||
S::run_on_client(input.clone())
|
||||
}),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -176,11 +176,11 @@ where
|
||||
S::Output: 'static,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ pub struct ArcOnceResource<T, Ser = JsonSerdeCodec> {
|
||||
suspenses: Arc<RwLock<Vec<SuspenseContext>>>,
|
||||
loading: Arc<AtomicBool>,
|
||||
ser: PhantomData<fn() -> Ser>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ impl<T, Ser> Clone for ArcOnceResource<T, Ser> {
|
||||
suspenses: self.suspenses.clone(),
|
||||
loading: self.loading.clone(),
|
||||
ser: self.ser,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
}
|
||||
}
|
||||
@@ -140,7 +140,7 @@ where
|
||||
wakers,
|
||||
suspenses,
|
||||
ser: PhantomData,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
};
|
||||
|
||||
@@ -183,11 +183,11 @@ impl<T, Ser> ArcOnceResource<T, Ser> {
|
||||
|
||||
impl<T, Ser> DefinedAt for ArcOnceResource<T, Ser> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
@@ -253,7 +253,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Future`] that is ready when an [`ArcAsyncDerived`] is finished loading or reloading,
|
||||
/// A [`Future`] that is ready when an
|
||||
/// [`ArcAsyncDerived`](reactive_graph::computed::ArcAsyncDerived) is finished loading or reloading,
|
||||
/// and contains its value. `.await`ing this clones the value `T`.
|
||||
pub struct OnceResourceFuture<T> {
|
||||
source: AnySource,
|
||||
@@ -271,7 +272,7 @@ where
|
||||
|
||||
#[track_caller]
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
let _guard = SpecialNonReactiveZone::enter();
|
||||
let waker = cx.waker();
|
||||
self.source.track();
|
||||
@@ -490,7 +491,7 @@ where
|
||||
#[derive(Debug)]
|
||||
pub struct OnceResource<T, Ser = JsonSerdeCodec> {
|
||||
inner: ArenaItem<ArcOnceResource<T, Ser>>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -523,13 +524,13 @@ where
|
||||
fut: impl Future<Output = T> + Send + 'static,
|
||||
blocking: bool,
|
||||
) -> Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
let defined_at = Location::caller();
|
||||
Self {
|
||||
inner: ArenaItem::new(ArcOnceResource::new_with_options(
|
||||
fut, blocking,
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at,
|
||||
}
|
||||
}
|
||||
@@ -550,11 +551,11 @@ where
|
||||
|
||||
impl<T, Ser> DefinedAt for OnceResource<T, Ser> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ pub struct ArcResource<T, Ser = JsonSerdeCodec> {
|
||||
ser: PhantomData<Ser>,
|
||||
refetch: ArcRwSignal<usize>,
|
||||
data: ArcAsyncDerived<T>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ impl<T, Ser> Debug for ArcResource<T, Ser> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut d = f.debug_struct("ArcResource");
|
||||
d.field("ser", &self.ser).field("data", &self.data);
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
d.field("defined_at", self.defined_at);
|
||||
d.finish_non_exhaustive()
|
||||
}
|
||||
@@ -98,7 +98,7 @@ where
|
||||
ser: PhantomData,
|
||||
data: arc_resource.data.into(),
|
||||
refetch: arc_resource.refetch.into(),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -114,7 +114,7 @@ where
|
||||
ser: PhantomData,
|
||||
data: resource.data.into(),
|
||||
refetch: resource.refetch.into(),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -122,11 +122,11 @@ where
|
||||
|
||||
impl<T, Ser> DefinedAt for ArcResource<T, Ser> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -139,7 +139,7 @@ impl<T, Ser> Clone for ArcResource<T, Ser> {
|
||||
ser: self.ser,
|
||||
refetch: self.refetch.clone(),
|
||||
data: self.data.clone(),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
}
|
||||
}
|
||||
@@ -300,7 +300,7 @@ where
|
||||
ser: PhantomData,
|
||||
data,
|
||||
refetch,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -781,7 +781,7 @@ where
|
||||
ser: PhantomData<Ser>,
|
||||
data: AsyncDerived<T>,
|
||||
refetch: RwSignal<usize>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -792,7 +792,7 @@ where
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut d = f.debug_struct("ArcResource");
|
||||
d.field("ser", &self.ser).field("data", &self.data);
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
d.field("defined_at", self.defined_at);
|
||||
d.finish_non_exhaustive()
|
||||
}
|
||||
@@ -803,11 +803,11 @@ where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -1270,7 +1270,7 @@ where
|
||||
ser: PhantomData,
|
||||
data: data.into(),
|
||||
refetch: refetch.into(),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_meta"
|
||||
version = "0.7.0-rc1"
|
||||
version = "0.7.4"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
@@ -14,8 +14,8 @@ once_cell = "1.20"
|
||||
or_poisoned = { workspace = true }
|
||||
indexmap = "2.6"
|
||||
send_wrapper = "0.6.0"
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
wasm-bindgen = "0.2.95"
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
wasm-bindgen = "0.2.97"
|
||||
futures = "0.3.31"
|
||||
|
||||
[dependencies.web-sys]
|
||||
@@ -26,9 +26,13 @@ features = ["HtmlLinkElement", "HtmlMetaElement", "HtmlTitleElement"]
|
||||
default = []
|
||||
ssr = []
|
||||
tracing = ["dep:tracing"]
|
||||
nonce = ["leptos/nonce"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["tracing"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
//! using the [`Leptos`](https://github.com/leptos-rs/leptos) web framework.
|
||||
//!
|
||||
//! Document metadata is updated automatically when running in the browser. For server-side
|
||||
//! rendering, after the component tree is rendered to HTML, [`MetaContext::dehydrate`] can generate
|
||||
//! HTML that should be injected into the `<head>` of the HTML document being rendered.
|
||||
//! rendering, after the component tree is rendered to HTML, [`ServerMetaContextOutput::inject_meta_context`] will inject meta tags into a stream of HTML inside the `<head>`.
|
||||
//!
|
||||
//! ```
|
||||
//! use leptos::prelude::*;
|
||||
@@ -38,20 +37,17 @@
|
||||
//! }
|
||||
//! ```
|
||||
//! # Feature Flags
|
||||
//! - `csr` Client-side rendering: Generate DOM nodes in the browser
|
||||
//! - `ssr` Server-side rendering: Generate an HTML string (typically on the server)
|
||||
//! - `hydrate` Hydration: use this to add interactivity to an SSRed Leptos app
|
||||
//! - `stable` By default, Leptos requires `nightly` Rust, which is what allows the ergonomics
|
||||
//! of calling signals as functions. Enable this feature to support `stable` Rust.
|
||||
//! - `tracing` Adds integration with the `tracing` crate.
|
||||
//!
|
||||
//! **Important Note:** You must enable one of `csr`, `hydrate`, or `ssr` to tell Leptos
|
||||
//! which mode your app is operating in.
|
||||
//! **Important Note:** If you’re using server-side rendering, you should enable `ssr`.
|
||||
|
||||
use futures::{Stream, StreamExt};
|
||||
use leptos::{
|
||||
attr::NextAttribute,
|
||||
component,
|
||||
logging::debug_warn,
|
||||
oco::Oco,
|
||||
reactive::owner::{provide_context, use_context},
|
||||
tachys::{
|
||||
dom::document,
|
||||
@@ -571,3 +567,25 @@ impl RenderHtml for MetaTagsView {
|
||||
) -> Self::State {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait OrDefaultNonce {
|
||||
fn or_default_nonce(self) -> Option<Oco<'static, str>>;
|
||||
}
|
||||
|
||||
impl OrDefaultNonce for Option<Oco<'static, str>> {
|
||||
fn or_default_nonce(self) -> Option<Oco<'static, str>> {
|
||||
#[cfg(feature = "nonce")]
|
||||
{
|
||||
use leptos::nonce::use_nonce;
|
||||
|
||||
match self {
|
||||
Some(nonce) => Some(nonce),
|
||||
None => use_nonce().map(|n| Arc::clone(n.as_inner()).into()),
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "nonce"))]
|
||||
{
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::register;
|
||||
use crate::{register, OrDefaultNonce};
|
||||
use leptos::{
|
||||
component, oco::Oco, prelude::*, tachys::html::element::script, IntoView,
|
||||
};
|
||||
@@ -74,7 +74,7 @@ pub fn Script(
|
||||
.fetchpriority(fetchpriority)
|
||||
.integrity(integrity)
|
||||
.nomodule(nomodule)
|
||||
.nonce(nonce)
|
||||
.nonce(nonce.or_default_nonce())
|
||||
.referrerpolicy(referrerpolicy)
|
||||
.src(src)
|
||||
.r#type(type_)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::register;
|
||||
use crate::{register, OrDefaultNonce};
|
||||
use leptos::{
|
||||
component, oco::Oco, prelude::*, tachys::html::element::style, IntoView,
|
||||
};
|
||||
@@ -48,7 +48,7 @@ pub fn Style(
|
||||
style()
|
||||
.id(id)
|
||||
.media(media)
|
||||
.nonce(nonce)
|
||||
.nonce(nonce.or_default_nonce())
|
||||
.title(title)
|
||||
.blocking(blocking)
|
||||
.child(children.map(|c| c())),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "next_tuple"
|
||||
version = "0.1.0-rc1"
|
||||
version = "0.1.0"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_graph"
|
||||
version = "0.1.0-rc1"
|
||||
version = "0.1.4"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
@@ -19,13 +19,13 @@ rustc-hash = "2.0"
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
slotmap = "1.0"
|
||||
thiserror = "2.0"
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
guardian = "1.2"
|
||||
async-lock = "3.4.0"
|
||||
send_wrapper = { version = "0.6.0", features = ["futures"] }
|
||||
|
||||
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
|
||||
web-sys = "0.3.72"
|
||||
web-sys = { version = "0.3.72", features = ["console"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.41", features = ["rt-multi-thread", "macros"] }
|
||||
@@ -47,3 +47,6 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["tracing"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
|
||||
|
||||
@@ -101,7 +101,7 @@ pub struct ArcAction<I, O> {
|
||||
action_fn: Arc<
|
||||
dyn Fn(&I) -> Pin<Box<dyn Future<Output = O> + Send>> + Send + Sync,
|
||||
>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ impl<I, O> Clone for ArcAction<I, O> {
|
||||
version: self.version.clone(),
|
||||
dispatched: self.dispatched.clone(),
|
||||
action_fn: self.action_fn.clone(),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
}
|
||||
}
|
||||
@@ -198,7 +198,7 @@ where
|
||||
version: Default::default(),
|
||||
dispatched: Default::default(),
|
||||
action_fn: Arc::new(move |input| Box::pin(action_fn(input))),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -370,7 +370,7 @@ where
|
||||
action_fn: Arc::new(move |input| {
|
||||
Box::pin(SendWrapper::new(action_fn(input)))
|
||||
}),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -502,11 +502,11 @@ where
|
||||
O: 'static,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -592,7 +592,7 @@ where
|
||||
/// ```
|
||||
pub struct Action<I, O, S = SyncStorage> {
|
||||
inner: ArenaItem<ArcAction<I, O>, S>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -656,7 +656,7 @@ where
|
||||
{
|
||||
Self {
|
||||
inner: ArenaItem::new(ArcAction::new(action_fn)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -681,7 +681,7 @@ where
|
||||
{
|
||||
Self {
|
||||
inner: ArenaItem::new(ArcAction::new_with_value(value, action_fn)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -715,11 +715,11 @@ where
|
||||
pub fn new_local<F, Fu>(action_fn: F) -> Self
|
||||
where
|
||||
F: Fn(&I) -> Fu + 'static,
|
||||
Fu: Future<Output = O> + Send + 'static,
|
||||
Fu: Future<Output = O> + 'static,
|
||||
{
|
||||
Self {
|
||||
inner: ArenaItem::new_local(ArcAction::new_unsync(action_fn)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -737,7 +737,7 @@ where
|
||||
inner: ArenaItem::new_local(ArcAction::new_unsync_with_value(
|
||||
value, action_fn,
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -979,7 +979,7 @@ where
|
||||
inner: ArenaItem::new_with_storage(ArcAction::new_unsync(
|
||||
action_fn,
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -998,7 +998,7 @@ where
|
||||
inner: ArenaItem::new_with_storage(
|
||||
ArcAction::new_unsync_with_value(value, action_fn),
|
||||
),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -1006,11 +1006,11 @@ where
|
||||
|
||||
impl<I, O, S> DefinedAt for Action<I, O, S> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ use std::{fmt::Debug, future::Future, panic::Location, pin::Pin, sync::Arc};
|
||||
/// ```
|
||||
pub struct MultiAction<I, O, S = SyncStorage> {
|
||||
inner: ArenaItem<ArcMultiAction<I, O>, S>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -62,11 +62,11 @@ where
|
||||
O: 'static,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -130,7 +130,7 @@ where
|
||||
{
|
||||
Self {
|
||||
inner: ArenaItem::new_with_storage(ArcMultiAction::new(action_fn)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ pub(crate) use inner::MemoInner;
|
||||
pub use memo::*;
|
||||
pub use selector::*;
|
||||
|
||||
/// Derives a reactive slice of an [`RwSignal`](crate::signal::RwSignal).
|
||||
/// Derives a reactive slice of an [`RwSignal`].
|
||||
///
|
||||
/// Slices have the same guarantees as [`Memo`s](crate::computed::Memo):
|
||||
/// they only emit their value when it has actually been changed.
|
||||
|
||||
@@ -93,7 +93,7 @@ pub struct ArcMemo<T, S = SyncStorage>
|
||||
where
|
||||
S: Storage<T>,
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: Arc<RwLock<MemoInner<T, S>>>,
|
||||
}
|
||||
@@ -164,7 +164,7 @@ where
|
||||
RwLock::new(MemoInner::new(Arc::new(fun), subscriber))
|
||||
});
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner,
|
||||
}
|
||||
@@ -177,7 +177,7 @@ where
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
inner: Arc::clone(&self.inner),
|
||||
}
|
||||
@@ -190,11 +190,11 @@ where
|
||||
{
|
||||
#[inline(always)]
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -272,7 +272,7 @@ where
|
||||
AnySource(
|
||||
Arc::as_ptr(&self.inner) as usize,
|
||||
Arc::downgrade(&self.inner) as Weak<dyn Source + Send + Sync>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
self.defined_at,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ use std::{
|
||||
/// - [`IntoFuture`](std::future::Future) allows you to create a [`Future`] that resolves
|
||||
/// when this resource is done loading.
|
||||
pub struct ArcAsyncDerived<T> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
// the current state of this signal
|
||||
pub(crate) value: Arc<AsyncRwLock<Option<T>>>,
|
||||
@@ -117,6 +117,7 @@ pub struct ArcAsyncDerived<T> {
|
||||
pub(crate) loading: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) trait BlockingLock<T> {
|
||||
fn blocking_read_arc(self: &Arc<Self>)
|
||||
-> async_lock::RwLockReadGuardArc<T>;
|
||||
@@ -183,7 +184,7 @@ impl<T> BlockingLock<T> for AsyncRwLock<T> {
|
||||
impl<T> Clone for ArcAsyncDerived<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
value: Arc::clone(&self.value),
|
||||
wakers: Arc::clone(&self.wakers),
|
||||
@@ -196,7 +197,7 @@ impl<T> Clone for ArcAsyncDerived<T> {
|
||||
impl<T> Debug for ArcAsyncDerived<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut f = f.debug_struct("ArcAsyncDerived");
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
f.field("defined_at", &self.defined_at);
|
||||
f.finish_non_exhaustive()
|
||||
}
|
||||
@@ -205,11 +206,11 @@ impl<T> Debug for ArcAsyncDerived<T> {
|
||||
impl<T> DefinedAt for ArcAsyncDerived<T> {
|
||||
#[inline(always)]
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -240,7 +241,7 @@ macro_rules! spawn_derived {
|
||||
let wakers = Arc::new(RwLock::new(Vec::new()));
|
||||
|
||||
let this = ArcAsyncDerived {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
value: Arc::clone(&value),
|
||||
wakers,
|
||||
@@ -271,7 +272,10 @@ macro_rules! spawn_derived {
|
||||
// so that the correct value is set synchronously
|
||||
let initial = initial_fut.as_mut().now_or_never();
|
||||
match initial {
|
||||
None => (false, Some(initial_fut)),
|
||||
None => {
|
||||
inner.write().or_poisoned().notifier.notify();
|
||||
(false, Some(initial_fut))
|
||||
}
|
||||
Some(orig_value) => {
|
||||
let mut guard = this.inner.write().or_poisoned();
|
||||
|
||||
@@ -295,10 +299,6 @@ macro_rules! spawn_derived {
|
||||
if was_ready {
|
||||
first_run.take();
|
||||
}
|
||||
// begin loading eagerly but asynchronously, if not already loaded
|
||||
if !was_ready {
|
||||
any_subscriber.mark_dirty();
|
||||
}
|
||||
|
||||
if let Some(source) = $source {
|
||||
any_subscriber.with_observer(|| source.track());
|
||||
@@ -311,6 +311,18 @@ macro_rules! spawn_derived {
|
||||
let wakers = Arc::downgrade(&this.wakers);
|
||||
let loading = Arc::downgrade(&this.loading);
|
||||
let fut = async move {
|
||||
// if the AsyncDerived has *already* been marked dirty (i.e., one of its
|
||||
// sources has changed after creation), we should throw out the Future
|
||||
// we already created, because its values might be stale
|
||||
let already_dirty = inner.upgrade()
|
||||
.as_ref()
|
||||
.and_then(|inner| inner.read().ok())
|
||||
.map(|inner| inner.state == AsyncDerivedState::Dirty)
|
||||
.unwrap_or(false);
|
||||
if already_dirty {
|
||||
initial_fut.take();
|
||||
}
|
||||
|
||||
while rx.next().await.is_some() {
|
||||
let update_if_necessary = if $should_track {
|
||||
any_subscriber
|
||||
@@ -412,7 +424,10 @@ impl<T: 'static> ArcAsyncDerived<T> {
|
||||
) {
|
||||
loading.store(false, Ordering::Relaxed);
|
||||
|
||||
inner.write().or_poisoned().state = AsyncDerivedState::Notifying;
|
||||
let prev_state = mem::replace(
|
||||
&mut inner.write().or_poisoned().state,
|
||||
AsyncDerivedState::Notifying,
|
||||
);
|
||||
|
||||
if let Some(ready_tx) = ready_tx {
|
||||
// if it's an Err, that just means the Receiver was dropped
|
||||
@@ -432,7 +447,10 @@ impl<T: 'static> ArcAsyncDerived<T> {
|
||||
waker.wake();
|
||||
}
|
||||
|
||||
inner.write().or_poisoned().state = AsyncDerivedState::Clean;
|
||||
// if this was marked dirty before notifications began, this means it
|
||||
// had been notified while loading; marking it clean will cause it not to
|
||||
// run on the next tick of the async loop, so here it should be left dirty
|
||||
inner.write().or_poisoned().state = prev_state;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -583,19 +601,17 @@ impl<T: 'static> ReadUntracked for ArcAsyncDerived<T> {
|
||||
|
||||
fn try_read_untracked(&self) -> Option<Self::Value> {
|
||||
if let Some(suspense_context) = use_context::<SuspenseContext>() {
|
||||
if self.value.blocking_read().is_none() {
|
||||
let handle = suspense_context.task_id();
|
||||
let ready = SpecialNonReactiveFuture::new(self.ready());
|
||||
crate::spawn(async move {
|
||||
ready.await;
|
||||
drop(handle);
|
||||
});
|
||||
self.inner
|
||||
.write()
|
||||
.or_poisoned()
|
||||
.suspenses
|
||||
.push(suspense_context);
|
||||
}
|
||||
let handle = suspense_context.task_id();
|
||||
let ready = SpecialNonReactiveFuture::new(self.ready());
|
||||
crate::spawn(async move {
|
||||
ready.await;
|
||||
drop(handle);
|
||||
});
|
||||
self.inner
|
||||
.write()
|
||||
.or_poisoned()
|
||||
.suspenses
|
||||
.push(suspense_context);
|
||||
}
|
||||
AsyncPlain::try_new(&self.value).map(ReadGuard::new)
|
||||
}
|
||||
@@ -633,7 +649,7 @@ impl<T: 'static> ToAnySource for ArcAsyncDerived<T> {
|
||||
AnySource(
|
||||
Arc::as_ptr(&self.inner) as usize,
|
||||
Arc::downgrade(&self.inner) as Weak<dyn Source + Send + Sync>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
self.defined_at,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ use std::{future::Future, ops::DerefMut, panic::Location};
|
||||
/// - [`IntoFuture`](std::future::Future) allows you to create a [`Future`] that resolves
|
||||
/// when this resource is done loading.
|
||||
pub struct AsyncDerived<T, S = SyncStorage> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
pub(crate) inner: ArenaItem<ArcAsyncDerived<T>, S>,
|
||||
}
|
||||
@@ -99,10 +99,10 @@ where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
fn from(value: ArcAsyncDerived<T>) -> Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
let defined_at = value.defined_at;
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at,
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
@@ -127,10 +127,10 @@ where
|
||||
T: 'static,
|
||||
{
|
||||
fn from_local(value: ArcAsyncDerived<T>) -> Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
let defined_at = value.defined_at;
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at,
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
@@ -152,7 +152,7 @@ where
|
||||
Fut: Future<Output = T> + Send + 'static,
|
||||
{
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(ArcAsyncDerived::new(fun)),
|
||||
}
|
||||
@@ -170,7 +170,7 @@ where
|
||||
Fut: Future<Output = T> + Send + 'static,
|
||||
{
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(
|
||||
ArcAsyncDerived::new_with_initial(initial_value, fun),
|
||||
@@ -187,7 +187,7 @@ impl<T> AsyncDerived<SendWrapper<T>> {
|
||||
Fut: Future<Output = T> + 'static,
|
||||
{
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(ArcAsyncDerived::new_mock(fun)),
|
||||
}
|
||||
@@ -209,7 +209,7 @@ where
|
||||
Fut: Future<Output = T> + 'static,
|
||||
{
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(ArcAsyncDerived::new_unsync(
|
||||
fun,
|
||||
@@ -230,7 +230,7 @@ where
|
||||
Fut: Future<Output = T> + 'static,
|
||||
{
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(
|
||||
ArcAsyncDerived::new_unsync_with_initial(initial_value, fun),
|
||||
@@ -278,11 +278,11 @@ where
|
||||
impl<T, S> DefinedAt for AsyncDerived<T, S> {
|
||||
#[inline(always)]
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
@@ -67,7 +67,9 @@ where
|
||||
fn mark_check(&self) {
|
||||
{
|
||||
let mut lock = self.write().or_poisoned();
|
||||
lock.state = ReactiveNodeState::Check;
|
||||
if lock.state != ReactiveNodeState::Dirty {
|
||||
lock.state = ReactiveNodeState::Check;
|
||||
}
|
||||
}
|
||||
for sub in (&self.read().or_poisoned().subscribers).into_iter() {
|
||||
sub.mark_check();
|
||||
|
||||
@@ -100,7 +100,7 @@ pub struct Memo<T, S = SyncStorage>
|
||||
where
|
||||
S: Storage<T>,
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: ArenaItem<ArcMemo<T, S>, S>,
|
||||
}
|
||||
@@ -121,7 +121,7 @@ where
|
||||
#[track_caller]
|
||||
fn from(value: ArcMemo<T, SyncStorage>) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
@@ -135,7 +135,7 @@ where
|
||||
#[track_caller]
|
||||
fn from_local(value: ArcMemo<T, LocalStorage>) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
@@ -175,7 +175,7 @@ where
|
||||
T: PartialEq,
|
||||
{
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(ArcMemo::new(fun)),
|
||||
}
|
||||
@@ -197,7 +197,7 @@ where
|
||||
changed: fn(Option<&T>, Option<&T>) -> bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(ArcMemo::new_with_compare(
|
||||
fun, changed,
|
||||
@@ -207,7 +207,7 @@ where
|
||||
|
||||
/// Creates a new memo by passing a function that computes the value.
|
||||
///
|
||||
/// Unlike [`ArcMemo::new`](), this receives ownership of the previous value. As a result, it
|
||||
/// Unlike [`Memo::new`](), this receives ownership of the previous value. As a result, it
|
||||
/// must return both the new value and a `bool` that is `true` if the value has changed.
|
||||
///
|
||||
/// This is lazy: the function will not be called until the memo's value is read for the first
|
||||
@@ -221,7 +221,7 @@ where
|
||||
fun: impl Fn(Option<T>) -> (T, bool) + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(ArcMemo::new_owning(fun)),
|
||||
}
|
||||
@@ -276,11 +276,11 @@ where
|
||||
S: Storage<T>,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ impl<'a> IntoIterator for &'a SourceSet {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
#[derive(Default, Clone)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct SubscriberSet(Vec<AnySubscriber>);
|
||||
|
||||
impl SubscriberSet {
|
||||
|
||||
@@ -26,16 +26,17 @@ pub trait Source: ReactiveNode {
|
||||
pub struct AnySource(
|
||||
pub(crate) usize,
|
||||
pub(crate) Weak<dyn Source + Send + Sync>,
|
||||
#[cfg(debug_assertions)] pub(crate) &'static Location<'static>,
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
pub(crate) &'static Location<'static>,
|
||||
);
|
||||
|
||||
impl DefinedAt for AnySource {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.2)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ pub fn log_warning(text: Arguments) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls [`Executor::spawn`], but ensures that the task also runs in the current arena, if
|
||||
/// Calls [`Executor::spawn`](any_spawner::Executor), but ensures that the task also runs in the current arena, if
|
||||
/// multithreaded arena sandboxing is enabled.
|
||||
pub fn spawn(task: impl Future<Output = ()> + Send + 'static) {
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
@@ -133,8 +133,9 @@ pub fn spawn(task: impl Future<Output = ()> + Send + 'static) {
|
||||
any_spawner::Executor::spawn(task);
|
||||
}
|
||||
|
||||
/// Calls [`Executor::spawn_local`], but ensures that the task runs under the current reactive [`Owner`]
|
||||
/// and [`Observed`]. Does not cancel the task if the owner is cleaned up.
|
||||
/// Calls [`Executor::spawn_local`](any_spawner::Executor), but ensures that the task runs under the current reactive [`Owner`](crate::owner::Owner) and observer.
|
||||
///
|
||||
/// Does not cancel the task if the owner is cleaned up.
|
||||
pub fn spawn_local_scoped(task: impl Future<Output = ()> + 'static) {
|
||||
let task = ScopedFuture::new(task);
|
||||
|
||||
@@ -144,8 +145,9 @@ pub fn spawn_local_scoped(task: impl Future<Output = ()> + 'static) {
|
||||
any_spawner::Executor::spawn_local(task);
|
||||
}
|
||||
|
||||
/// Calls [`Executor::spawn_local`], but ensures that the task runs under the current reactive [`Owner`]
|
||||
/// and [`Observed`]. Cancels the task if the owner is cleaned up.
|
||||
/// Calls [`Executor::spawn_local`](any_spawner::Executor), but ensures that the task runs under the current reactive [`Owner`](crate::owner::Owner) and observer.
|
||||
///
|
||||
/// Cancels the task if the owner is cleaned up.
|
||||
pub fn spawn_local_scoped_with_cancellation(
|
||||
task: impl Future<Output = ()> + 'static,
|
||||
) {
|
||||
|
||||
@@ -292,6 +292,8 @@ impl Owner {
|
||||
#[cfg(feature = "hydration")]
|
||||
pub fn with_hydration<T>(fun: impl FnOnce() -> T + 'static) -> T {
|
||||
fn inner<T>(fun: Box<dyn FnOnce() -> T>) -> T {
|
||||
provide_context(IsHydrating(true));
|
||||
|
||||
let sc = OWNER.with_borrow(|o| {
|
||||
o.as_ref()
|
||||
.and_then(|current| current.shared_context.clone())
|
||||
@@ -315,6 +317,8 @@ impl Owner {
|
||||
#[cfg(feature = "hydration")]
|
||||
pub fn with_no_hydration<T>(fun: impl FnOnce() -> T + 'static) -> T {
|
||||
fn inner<T>(fun: Box<dyn FnOnce() -> T>) -> T {
|
||||
provide_context(IsHydrating(false));
|
||||
|
||||
let sc = OWNER.with_borrow(|o| {
|
||||
o.as_ref()
|
||||
.and_then(|current| current.shared_context.clone())
|
||||
@@ -335,6 +339,10 @@ impl Owner {
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct IsHydrating(pub bool);
|
||||
|
||||
/// Registers a function to be run the next time the current owner is cleaned up.
|
||||
///
|
||||
/// Because the ownership model is associated with reactive nodes, each "decision point" in an
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
signal::guards::{Plain, ReadGuard, UntrackedWriteGuard},
|
||||
traits::{DefinedAt, IsDisposed, ReadValue, WriteValue},
|
||||
traits::{DefinedAt, IntoInner, IsDisposed, ReadValue, WriteValue},
|
||||
};
|
||||
use std::{
|
||||
fmt::{Debug, Formatter},
|
||||
@@ -19,7 +19,7 @@ use std::{
|
||||
/// accessing it does not cause effects to subscribe, and
|
||||
/// updating it does not notify anything else.
|
||||
pub struct ArcStoredValue<T> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
value: Arc<RwLock<T>>,
|
||||
}
|
||||
@@ -27,7 +27,7 @@ pub struct ArcStoredValue<T> {
|
||||
impl<T> Clone for ArcStoredValue<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
value: Arc::clone(&self.value),
|
||||
}
|
||||
@@ -47,7 +47,7 @@ impl<T: Default> Default for ArcStoredValue<T> {
|
||||
#[track_caller]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
value: Arc::new(RwLock::new(T::default())),
|
||||
}
|
||||
@@ -70,11 +70,11 @@ impl<T> Hash for ArcStoredValue<T> {
|
||||
|
||||
impl<T> DefinedAt for ArcStoredValue<T> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -90,7 +90,7 @@ impl<T> ArcStoredValue<T> {
|
||||
#[track_caller]
|
||||
pub fn new(value: T) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
value: Arc::new(RwLock::new(value)),
|
||||
}
|
||||
@@ -124,3 +124,12 @@ impl<T> IsDisposed for ArcStoredValue<T> {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoInner for ArcStoredValue<T> {
|
||||
type Value = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_inner(self) -> Option<Self::Value> {
|
||||
Some(Arc::into_inner(self.value)?.into_inner().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,8 +121,11 @@ pub mod sandboxed {
|
||||
}
|
||||
|
||||
impl<T> Sandboxed<T> {
|
||||
/// 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.
|
||||
/// Wraps the given [`Future`], ensuring that any [`ArenaItem`][item] created while it is
|
||||
/// being polled will be associated with the same arena that was active when this was
|
||||
/// called.
|
||||
///
|
||||
/// [item]:[crate::owner::ArenaItem]
|
||||
pub fn new(inner: T) -> Self {
|
||||
let arena = MAP.with_borrow(|n| n.as_ref().and_then(Weak::upgrade));
|
||||
Self { arena, inner }
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::{
|
||||
arena::{Arena, NodeId},
|
||||
LocalStorage, Storage, SyncStorage, OWNER,
|
||||
};
|
||||
use crate::traits::{Dispose, IsDisposed};
|
||||
use crate::traits::{Dispose, IntoInner, IsDisposed};
|
||||
use send_wrapper::SendWrapper;
|
||||
use std::{any::Any, hash::Hash, marker::PhantomData};
|
||||
|
||||
@@ -134,3 +134,12 @@ impl<T, S> Dispose for ArenaItem<T, S> {
|
||||
Arena::with_mut(|arena| arena.remove(self.node));
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S: Storage<T>> IntoInner for ArenaItem<T, S> {
|
||||
type Value = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_inner(self) -> Option<Self::Value> {
|
||||
S::take(self.node)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,35 +15,14 @@ impl Owner {
|
||||
}
|
||||
|
||||
fn use_context<T: Clone + 'static>(&self) -> Option<T> {
|
||||
let ty = TypeId::of::<T>();
|
||||
let inner = self.inner.read().or_poisoned();
|
||||
let mut parent = inner.parent.as_ref().and_then(|p| p.upgrade());
|
||||
let contexts = &self.inner.read().or_poisoned().contexts;
|
||||
if let Some(context) = contexts.get(&ty) {
|
||||
context.downcast_ref::<T>().cloned()
|
||||
} else {
|
||||
while let Some(ref this_parent) = parent.clone() {
|
||||
let this_parent = this_parent.read().or_poisoned();
|
||||
let contexts = &this_parent.contexts;
|
||||
let value = contexts.get(&ty);
|
||||
let downcast = value
|
||||
.and_then(|context| context.downcast_ref::<T>().cloned());
|
||||
if let Some(value) = downcast {
|
||||
return Some(value);
|
||||
} else {
|
||||
parent =
|
||||
this_parent.parent.as_ref().and_then(|p| p.upgrade());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
self.with_context(Clone::clone)
|
||||
}
|
||||
|
||||
fn take_context<T: 'static>(&self) -> Option<T> {
|
||||
let ty = TypeId::of::<T>();
|
||||
let inner = self.inner.read().or_poisoned();
|
||||
let mut inner = self.inner.write().or_poisoned();
|
||||
let mut parent = inner.parent.as_ref().and_then(|p| p.upgrade());
|
||||
let contexts = &mut self.inner.write().or_poisoned().contexts;
|
||||
let contexts = &mut inner.contexts;
|
||||
if let Some(context) = contexts.remove(&ty) {
|
||||
context.downcast::<T>().ok().map(|n| *n)
|
||||
} else {
|
||||
@@ -64,6 +43,64 @@ impl Owner {
|
||||
}
|
||||
}
|
||||
|
||||
fn with_context<T: 'static, R>(
|
||||
&self,
|
||||
cb: impl FnOnce(&T) -> R,
|
||||
) -> Option<R> {
|
||||
let ty = TypeId::of::<T>();
|
||||
let inner = self.inner.read().or_poisoned();
|
||||
let mut parent = inner.parent.as_ref().and_then(|p| p.upgrade());
|
||||
let contexts = &inner.contexts;
|
||||
let reference = if let Some(context) = contexts.get(&ty) {
|
||||
context.downcast_ref::<T>()
|
||||
} else {
|
||||
while let Some(ref this_parent) = parent.clone() {
|
||||
let this_parent = this_parent.read().or_poisoned();
|
||||
let contexts = &this_parent.contexts;
|
||||
let value = contexts.get(&ty);
|
||||
let downcast =
|
||||
value.and_then(|context| context.downcast_ref::<T>());
|
||||
if let Some(value) = downcast {
|
||||
return Some(cb(value));
|
||||
} else {
|
||||
parent =
|
||||
this_parent.parent.as_ref().and_then(|p| p.upgrade());
|
||||
}
|
||||
}
|
||||
None
|
||||
};
|
||||
reference.map(cb)
|
||||
}
|
||||
|
||||
fn update_context<T: 'static, R>(
|
||||
&self,
|
||||
cb: impl FnOnce(&mut T) -> R,
|
||||
) -> Option<R> {
|
||||
let ty = TypeId::of::<T>();
|
||||
let mut inner = self.inner.write().or_poisoned();
|
||||
let mut parent = inner.parent.as_ref().and_then(|p| p.upgrade());
|
||||
let contexts = &mut inner.contexts;
|
||||
let reference = if let Some(context) = contexts.get_mut(&ty) {
|
||||
context.downcast_mut::<T>()
|
||||
} else {
|
||||
while let Some(ref this_parent) = parent.clone() {
|
||||
let mut this_parent = this_parent.write().or_poisoned();
|
||||
let contexts = &mut this_parent.contexts;
|
||||
let value = contexts.get_mut(&ty);
|
||||
let downcast =
|
||||
value.and_then(|context| context.downcast_mut::<T>());
|
||||
if let Some(value) = downcast {
|
||||
return Some(cb(value));
|
||||
} else {
|
||||
parent =
|
||||
this_parent.parent.as_ref().and_then(|p| p.upgrade());
|
||||
}
|
||||
}
|
||||
None
|
||||
};
|
||||
reference.map(cb)
|
||||
}
|
||||
|
||||
/// Searches for items stored in context in either direction, either among parents or among
|
||||
/// descendants.
|
||||
pub fn use_context_bidirectional<T: Clone + 'static>(&self) -> Option<T> {
|
||||
@@ -319,3 +356,86 @@ pub fn expect_context<T: Clone + 'static>() -> T {
|
||||
pub fn take_context<T: 'static>() -> Option<T> {
|
||||
Owner::current().and_then(|owner| owner.take_context())
|
||||
}
|
||||
|
||||
/// Access a reference to a context value of type `T` in the reactive system.
|
||||
///
|
||||
/// This traverses the reactive ownership graph, beginning from the current reactive
|
||||
/// [`Owner`] and iterating through its parents, if any. When the value is found,
|
||||
/// the function that you pass is applied to an immutable reference to it.
|
||||
///
|
||||
/// The context value should have been provided elsewhere using
|
||||
/// [`provide_context`](provide_context).
|
||||
///
|
||||
/// This is useful for passing values down to components or functions lower in a
|
||||
/// hierarchy without needs to “prop drill” by passing them through each layer as
|
||||
/// arguments to a function or properties of a component.
|
||||
///
|
||||
/// Context works similarly to variable scope: a context that is provided higher in
|
||||
/// the reactive graph can be used lower down, but a context that is provided lower
|
||||
/// in the tree cannot be used higher up.
|
||||
///
|
||||
/// ```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();
|
||||
/// Effect::new(move |_| {
|
||||
/// provide_context(String::from("foo"));
|
||||
///
|
||||
/// Effect::new(move |_| {
|
||||
/// let value = with_context::<String, _>(|val| val.to_string())
|
||||
/// .expect("could not find String in context");
|
||||
/// assert_eq!(value, "foo");
|
||||
/// });
|
||||
/// });
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn with_context<T: 'static, R>(cb: impl FnOnce(&T) -> R) -> Option<R> {
|
||||
Owner::current().and_then(|owner| owner.with_context(cb))
|
||||
}
|
||||
|
||||
/// Update a context value of type `T` in the reactive system.
|
||||
///
|
||||
/// This traverses the reactive ownership graph, beginning from the current reactive
|
||||
/// [`Owner`] and iterating through its parents, if any. When the value is found,
|
||||
/// the function that you pass is applied to a mutable reference to it.
|
||||
///
|
||||
/// The context value should have been provided elsewhere using
|
||||
/// [`provide_context`](provide_context).
|
||||
///
|
||||
/// This is useful for passing values down to components or functions lower in a
|
||||
/// hierarchy without needs to “prop drill” by passing them through each layer as
|
||||
/// arguments to a function or properties of a component.
|
||||
///
|
||||
/// Context works similarly to variable scope: a context that is provided higher in
|
||||
/// the reactive graph can be used lower down, but a context that is provided lower
|
||||
/// in the tree cannot be used higher up.
|
||||
///
|
||||
/// ```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();
|
||||
/// Effect::new(move |_| {
|
||||
/// provide_context(String::from("foo"));
|
||||
///
|
||||
/// Effect::new(move |_| {
|
||||
/// let value = update_context::<String, _>(|val| {
|
||||
/// std::mem::replace(val, "bar".to_string())
|
||||
/// })
|
||||
/// .expect("could not find String in context");
|
||||
/// assert_eq!(value, "foo");
|
||||
/// assert_eq!(expect_context::<String>(), "bar");
|
||||
/// });
|
||||
/// });
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn update_context<T: 'static, R>(
|
||||
cb: impl FnOnce(&mut T) -> R,
|
||||
) -> Option<R> {
|
||||
Owner::current().and_then(|owner| owner.update_context(cb))
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ impl<T> StorageAccess<T> for SendWrapper<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A way of storing a [`ArenaItem`], either as itself or with a wrapper to make it threadsafe.
|
||||
/// A way of storing an [`ArenaItem`](super::arena_item::ArenaItem), either as itself or with a wrapper to make it threadsafe.
|
||||
///
|
||||
/// This exists because all items stored in the arena must be `Send + Sync`, but in single-threaded
|
||||
/// environments you might want or need to use thread-unsafe types.
|
||||
@@ -54,6 +54,10 @@ pub trait Storage<T>: Send + Sync + 'static {
|
||||
|
||||
/// Sets a new value for the stored value. If it has been disposed, returns `Some(T)`.
|
||||
fn try_set(node: NodeId, value: T) -> Option<T>;
|
||||
|
||||
/// Takes an item from the arena if it exists and can be accessed from this thread.
|
||||
/// If it cannot be casted, it will still be removed from the arena.
|
||||
fn take(node: NodeId) -> Option<T>;
|
||||
}
|
||||
|
||||
/// A form of [`Storage`] that stores the type as itself, with no wrapper.
|
||||
@@ -100,6 +104,16 @@ where
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn take(node: NodeId) -> Option<T> {
|
||||
Arena::with_mut(|arena| {
|
||||
let m = arena.remove(node)?;
|
||||
match m.downcast::<T>() {
|
||||
Ok(inner) => Some(*inner),
|
||||
Err(_) => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A form of [`Storage`] that stores the type with a wrapper that makes it `Send + Sync`, but only
|
||||
@@ -148,4 +162,14 @@ where
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn take(node: NodeId) -> Option<T> {
|
||||
Arena::with_mut(|arena| {
|
||||
let m = arena.remove(node)?;
|
||||
match m.downcast::<SendWrapper<T>>() {
|
||||
Ok(inner) => Some(inner.take()),
|
||||
Err(_) => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ use super::{
|
||||
};
|
||||
use crate::{
|
||||
signal::guards::{Plain, ReadGuard, UntrackedWriteGuard},
|
||||
traits::{DefinedAt, Dispose, IsDisposed, ReadValue, WriteValue},
|
||||
traits::{
|
||||
DefinedAt, Dispose, IntoInner, IsDisposed, ReadValue, WriteValue,
|
||||
},
|
||||
unwrap_signal,
|
||||
};
|
||||
use std::{
|
||||
@@ -22,7 +24,7 @@ use std::{
|
||||
/// updating it does not notify anything else.
|
||||
pub struct StoredValue<T, S = SyncStorage> {
|
||||
value: ArenaItem<ArcStoredValue<T>, S>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -62,11 +64,11 @@ impl<T, S> Hash for StoredValue<T, S> {
|
||||
|
||||
impl<T, S> DefinedAt for StoredValue<T, S> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -83,7 +85,7 @@ where
|
||||
pub fn new_with_storage(value: T) -> Self {
|
||||
Self {
|
||||
value: ArenaItem::new_with_storage(ArcStoredValue::new(value)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -162,6 +164,19 @@ impl<T, S> Dispose for StoredValue<T, S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> IntoInner for StoredValue<T, S>
|
||||
where
|
||||
T: 'static,
|
||||
S: Storage<ArcStoredValue<T>>,
|
||||
{
|
||||
type Value = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_inner(self) -> Option<Self::Value> {
|
||||
self.value.into_inner()?.into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<ArcStoredValue<T>> for StoredValue<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
@@ -169,7 +184,7 @@ where
|
||||
#[track_caller]
|
||||
fn from(value: ArcStoredValue<T>) -> Self {
|
||||
StoredValue {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
value: ArenaItem::new(value),
|
||||
}
|
||||
|
||||
@@ -113,7 +113,8 @@ pub fn arc_signal<T>(value: T) -> (ArcReadSignal<T>, ArcWriteSignal<T>) {
|
||||
pub fn signal<T: Send + Sync + 'static>(
|
||||
value: T,
|
||||
) -> (ReadSignal<T>, WriteSignal<T>) {
|
||||
RwSignal::new(value).split()
|
||||
let (r, w) = arc_signal(value);
|
||||
(r.into(), w.into())
|
||||
}
|
||||
|
||||
/// Creates an arena-allocated signal.
|
||||
|
||||
@@ -4,7 +4,7 @@ use super::{
|
||||
};
|
||||
use crate::{
|
||||
graph::SubscriberSet,
|
||||
traits::{DefinedAt, IsDisposed, ReadUntracked},
|
||||
traits::{DefinedAt, IntoInner, IsDisposed, ReadUntracked},
|
||||
};
|
||||
use core::fmt::{Debug, Formatter, Result};
|
||||
use std::{
|
||||
@@ -54,7 +54,7 @@ use std::{
|
||||
/// assert_eq!(count.read(), 0);
|
||||
/// ```
|
||||
pub struct ArcReadSignal<T> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
pub(crate) value: Arc<RwLock<T>>,
|
||||
pub(crate) inner: Arc<RwLock<SubscriberSet>>,
|
||||
@@ -64,7 +64,7 @@ impl<T> Clone for ArcReadSignal<T> {
|
||||
#[track_caller]
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
value: Arc::clone(&self.value),
|
||||
inner: Arc::clone(&self.inner),
|
||||
@@ -85,7 +85,7 @@ impl<T: Default> Default for ArcReadSignal<T> {
|
||||
#[track_caller]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
value: Arc::new(RwLock::new(T::default())),
|
||||
inner: Arc::new(RwLock::new(SubscriberSet::new())),
|
||||
@@ -110,11 +110,11 @@ impl<T> Hash for ArcReadSignal<T> {
|
||||
impl<T> DefinedAt for ArcReadSignal<T> {
|
||||
#[inline(always)]
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -128,6 +128,15 @@ impl<T> IsDisposed for ArcReadSignal<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoInner for ArcReadSignal<T> {
|
||||
type Value = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_inner(self) -> Option<Self::Value> {
|
||||
Some(Arc::into_inner(self.value)?.into_inner().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsSubscriberSet for ArcReadSignal<T> {
|
||||
type Output = Arc<RwLock<SubscriberSet>>;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use super::{
|
||||
use crate::{
|
||||
graph::{ReactiveNode, SubscriberSet},
|
||||
prelude::{IsDisposed, Notify},
|
||||
traits::{DefinedAt, ReadUntracked, UntrackableGuard, Write},
|
||||
traits::{DefinedAt, IntoInner, ReadUntracked, UntrackableGuard, Write},
|
||||
};
|
||||
use core::fmt::{Debug, Formatter, Result};
|
||||
use std::{
|
||||
@@ -94,7 +94,7 @@ use std::{
|
||||
/// assert_eq!(double_count(), 2);
|
||||
/// ```
|
||||
pub struct ArcRwSignal<T> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
pub(crate) value: Arc<RwLock<T>>,
|
||||
pub(crate) inner: Arc<RwLock<SubscriberSet>>,
|
||||
@@ -104,7 +104,7 @@ impl<T> Clone for ArcRwSignal<T> {
|
||||
#[track_caller]
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
value: Arc::clone(&self.value),
|
||||
inner: Arc::clone(&self.inner),
|
||||
@@ -154,7 +154,7 @@ impl<T> ArcRwSignal<T> {
|
||||
#[track_caller]
|
||||
pub fn new(value: T) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
value: Arc::new(RwLock::new(value)),
|
||||
inner: Arc::new(RwLock::new(SubscriberSet::new())),
|
||||
@@ -165,7 +165,7 @@ impl<T> ArcRwSignal<T> {
|
||||
#[track_caller]
|
||||
pub fn read_only(&self) -> ArcReadSignal<T> {
|
||||
ArcReadSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
value: Arc::clone(&self.value),
|
||||
inner: Arc::clone(&self.inner),
|
||||
@@ -176,7 +176,7 @@ impl<T> ArcRwSignal<T> {
|
||||
#[track_caller]
|
||||
pub fn write_only(&self) -> ArcWriteSignal<T> {
|
||||
ArcWriteSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
value: Arc::clone(&self.value),
|
||||
inner: Arc::clone(&self.inner),
|
||||
@@ -198,7 +198,7 @@ impl<T> ArcRwSignal<T> {
|
||||
) -> Option<Self> {
|
||||
if Arc::ptr_eq(&read.inner, &write.inner) {
|
||||
Some(Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
value: read.value,
|
||||
inner: read.inner,
|
||||
@@ -212,11 +212,11 @@ impl<T> ArcRwSignal<T> {
|
||||
impl<T> DefinedAt for ArcRwSignal<T> {
|
||||
#[inline(always)]
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -230,6 +230,15 @@ impl<T> IsDisposed for ArcRwSignal<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoInner for ArcRwSignal<T> {
|
||||
type Value = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_inner(self) -> Option<Self::Value> {
|
||||
Some(Arc::into_inner(self.value)?.into_inner().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsSubscriberSet for ArcRwSignal<T> {
|
||||
type Output = Arc<RwLock<SubscriberSet>>;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ use std::{
|
||||
///
|
||||
/// This can be useful for when using external data not stored in signals, for example.
|
||||
pub struct ArcTrigger {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
pub(crate) inner: Arc<RwLock<SubscriberSet>>,
|
||||
}
|
||||
@@ -23,7 +23,7 @@ impl ArcTrigger {
|
||||
#[track_caller]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: Default::default(),
|
||||
}
|
||||
@@ -40,7 +40,7 @@ impl Clone for ArcTrigger {
|
||||
#[track_caller]
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
inner: Arc::clone(&self.inner),
|
||||
}
|
||||
@@ -72,11 +72,11 @@ impl AsSubscriberSet for ArcTrigger {
|
||||
impl DefinedAt for ArcTrigger {
|
||||
#[inline(always)]
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::guards::{UntrackedWriteGuard, WriteGuard};
|
||||
use crate::{
|
||||
graph::{ReactiveNode, SubscriberSet},
|
||||
prelude::{IsDisposed, Notify},
|
||||
traits::{DefinedAt, UntrackableGuard, Write},
|
||||
traits::{DefinedAt, IntoInner, UntrackableGuard, Write},
|
||||
};
|
||||
use core::fmt::{Debug, Formatter, Result};
|
||||
use std::{
|
||||
@@ -54,7 +54,7 @@ use std::{
|
||||
/// assert_eq!(count.get(), 3);
|
||||
/// ```
|
||||
pub struct ArcWriteSignal<T> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
pub(crate) value: Arc<RwLock<T>>,
|
||||
pub(crate) inner: Arc<RwLock<SubscriberSet>>,
|
||||
@@ -64,7 +64,7 @@ impl<T> Clone for ArcWriteSignal<T> {
|
||||
#[track_caller]
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
value: Arc::clone(&self.value),
|
||||
inner: Arc::clone(&self.inner),
|
||||
@@ -98,11 +98,11 @@ impl<T> Hash for ArcWriteSignal<T> {
|
||||
impl<T> DefinedAt for ArcWriteSignal<T> {
|
||||
#[inline(always)]
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -116,6 +116,15 @@ impl<T> IsDisposed for ArcWriteSignal<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoInner for ArcWriteSignal<T> {
|
||||
type Value = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_inner(self) -> Option<Self::Value> {
|
||||
Some(Arc::into_inner(self.value)?.into_inner().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Notify for ArcWriteSignal<T> {
|
||||
fn notify(&self) {
|
||||
self.inner.mark_dirty();
|
||||
|
||||
@@ -104,7 +104,7 @@ impl<T: 'static> Debug for Plain<T> {
|
||||
impl<T: 'static> Plain<T> {
|
||||
/// Takes a reference-counted read guard on the given lock.
|
||||
pub fn try_new(inner: Arc<RwLock<T>>) -> Option<Self> {
|
||||
ArcRwLockReadGuardian::take(inner)
|
||||
ArcRwLockReadGuardian::try_take(inner)?
|
||||
.ok()
|
||||
.map(|guard| Plain { guard })
|
||||
}
|
||||
@@ -331,7 +331,7 @@ pub struct UntrackedWriteGuard<T: 'static>(ArcRwLockWriteGuardian<T>);
|
||||
impl<T: 'static> UntrackedWriteGuard<T> {
|
||||
/// Creates a write guard from the given lock.
|
||||
pub fn try_new(inner: Arc<RwLock<T>>) -> Option<Self> {
|
||||
ArcRwLockWriteGuardian::take(inner)
|
||||
ArcRwLockWriteGuardian::try_take(inner)?
|
||||
.ok()
|
||||
.map(UntrackedWriteGuard)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use super::{
|
||||
use crate::{
|
||||
graph::SubscriberSet,
|
||||
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
|
||||
traits::{DefinedAt, Dispose, IsDisposed, ReadUntracked},
|
||||
traits::{DefinedAt, Dispose, IntoInner, IsDisposed, ReadUntracked},
|
||||
unwrap_signal,
|
||||
};
|
||||
use core::fmt::Debug;
|
||||
@@ -58,7 +58,7 @@ use std::{
|
||||
/// assert_eq!(count.read(), 0);
|
||||
/// ```
|
||||
pub struct ReadSignal<T, S = SyncStorage> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
pub(crate) inner: ArenaItem<ArcReadSignal<T>, S>,
|
||||
}
|
||||
@@ -105,11 +105,11 @@ impl<T, S> Hash for ReadSignal<T, S> {
|
||||
|
||||
impl<T, S> DefinedAt for ReadSignal<T, S> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -122,6 +122,18 @@ impl<T, S> IsDisposed for ReadSignal<T, S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> IntoInner for ReadSignal<T, S>
|
||||
where
|
||||
S: Storage<ArcReadSignal<T>>,
|
||||
{
|
||||
type Value = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_inner(self) -> Option<Self::Value> {
|
||||
self.inner.into_inner()?.into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> AsSubscriberSet for ReadSignal<T, S>
|
||||
where
|
||||
S: Storage<ArcReadSignal<T>>,
|
||||
@@ -156,7 +168,7 @@ where
|
||||
#[track_caller]
|
||||
fn from(value: ArcReadSignal<T>) -> Self {
|
||||
ReadSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
@@ -170,7 +182,7 @@ where
|
||||
#[track_caller]
|
||||
fn from_local(value: ArcReadSignal<T>) -> Self {
|
||||
ReadSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
|
||||
signal::guards::{UntrackedWriteGuard, WriteGuard},
|
||||
traits::{
|
||||
DefinedAt, Dispose, IsDisposed, Notify, ReadUntracked,
|
||||
DefinedAt, Dispose, IntoInner, IsDisposed, Notify, ReadUntracked,
|
||||
UntrackableGuard, Write,
|
||||
},
|
||||
unwrap_signal,
|
||||
@@ -100,7 +100,7 @@ use std::{
|
||||
/// assert_eq!(double_count(), 2);
|
||||
/// ```
|
||||
pub struct RwSignal<T, S = SyncStorage> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: ArenaItem<ArcRwSignal<T>, S>,
|
||||
}
|
||||
@@ -139,7 +139,7 @@ where
|
||||
#[track_caller]
|
||||
pub fn new_with_storage(value: T) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(ArcRwSignal::new(value)),
|
||||
}
|
||||
@@ -172,7 +172,7 @@ where
|
||||
#[track_caller]
|
||||
pub fn read_only(&self) -> ReadSignal<T, S> {
|
||||
ReadSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(
|
||||
self.inner
|
||||
@@ -194,7 +194,7 @@ where
|
||||
#[track_caller]
|
||||
pub fn write_only(&self) -> WriteSignal<T, S> {
|
||||
WriteSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(
|
||||
self.inner
|
||||
@@ -231,10 +231,10 @@ where
|
||||
(Some(read), Some(write)) => {
|
||||
if Arc::ptr_eq(&read.inner, &write.inner) {
|
||||
Some(Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(ArcRwSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
value: Arc::clone(&read.value),
|
||||
inner: Arc::clone(&read.inner),
|
||||
@@ -296,11 +296,11 @@ impl<T, S> Hash for RwSignal<T, S> {
|
||||
|
||||
impl<T, S> DefinedAt for RwSignal<T, S> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -313,6 +313,18 @@ impl<T: 'static, S> IsDisposed for RwSignal<T, S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> IntoInner for RwSignal<T, S>
|
||||
where
|
||||
S: Storage<ArcRwSignal<T>>,
|
||||
{
|
||||
type Value = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_inner(self) -> Option<Self::Value> {
|
||||
self.inner.into_inner()?.into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> AsSubscriberSet for RwSignal<T, S>
|
||||
where
|
||||
S: Storage<ArcRwSignal<T>>,
|
||||
@@ -378,7 +390,7 @@ where
|
||||
#[track_caller]
|
||||
fn from(value: ArcRwSignal<T>) -> Self {
|
||||
RwSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
@@ -402,7 +414,7 @@ where
|
||||
#[track_caller]
|
||||
fn from_local(value: ArcRwSignal<T>) -> Self {
|
||||
RwSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ where
|
||||
AnySource(
|
||||
Arc::as_ptr(subs) as usize,
|
||||
Arc::downgrade(subs) as Weak<dyn Source + Send + Sync>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
self.defined_at().expect("no DefinedAt in debug mode"),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -18,7 +18,7 @@ use std::{
|
||||
/// [`Owner`](crate::owner::Owner) cleans up. For a reference-counted trigger that lives
|
||||
/// as long as a reference to it is alive, see [`ArcTrigger`].
|
||||
pub struct Trigger {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
pub(crate) inner: ArenaItem<ArcTrigger>,
|
||||
}
|
||||
@@ -28,7 +28,7 @@ impl Trigger {
|
||||
#[track_caller]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new(ArcTrigger::new()),
|
||||
}
|
||||
@@ -83,11 +83,11 @@ impl AsSubscriberSet for Trigger {
|
||||
impl DefinedAt for Trigger {
|
||||
#[inline(always)]
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use super::{guards::WriteGuard, ArcWriteSignal};
|
||||
use crate::{
|
||||
owner::{ArenaItem, Storage, SyncStorage},
|
||||
traits::{DefinedAt, Dispose, IsDisposed, Notify, UntrackableGuard, Write},
|
||||
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
|
||||
traits::{
|
||||
DefinedAt, Dispose, IntoInner, IsDisposed, Notify, UntrackableGuard,
|
||||
Write,
|
||||
},
|
||||
};
|
||||
use core::fmt::Debug;
|
||||
use guardian::ArcRwLockWriteGuardian;
|
||||
@@ -50,7 +53,7 @@ use std::{hash::Hash, ops::DerefMut, panic::Location, sync::Arc};
|
||||
/// assert_eq!(count.get(), 3);
|
||||
/// ```
|
||||
pub struct WriteSignal<T, S = SyncStorage> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
pub(crate) inner: ArenaItem<ArcWriteSignal<T>, S>,
|
||||
}
|
||||
@@ -97,23 +100,63 @@ impl<T, S> Hash for WriteSignal<T, S> {
|
||||
|
||||
impl<T, S> DefinedAt for WriteSignal<T, S> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<ArcWriteSignal<T>> for WriteSignal<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(value: ArcWriteSignal<T>) -> Self {
|
||||
WriteSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FromLocal<ArcWriteSignal<T>> for WriteSignal<T, LocalStorage>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from_local(value: ArcWriteSignal<T>) -> Self {
|
||||
WriteSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> IsDisposed for WriteSignal<T, S> {
|
||||
fn is_disposed(&self) -> bool {
|
||||
self.inner.is_disposed()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> IntoInner for WriteSignal<T, S>
|
||||
where
|
||||
S: Storage<ArcWriteSignal<T>>,
|
||||
{
|
||||
type Value = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_inner(self) -> Option<Self::Value> {
|
||||
self.inner.into_inner()?.into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> Notify for WriteSignal<T, S>
|
||||
where
|
||||
T: 'static,
|
||||
|
||||
@@ -220,7 +220,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper trait to implement flatten() on Option<&Option<T>>.
|
||||
/// Helper trait to implement flatten() on `Option<&Option<T>>`.
|
||||
pub trait FlattenOptionRefOption {
|
||||
/// The type of the value contained in the double option.
|
||||
type Value;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
//! | Trait | Mode | Description |
|
||||
//! |-------------------|-------|---------------------------------------------------------------------------------------|
|
||||
//! | [`Track`] | — | Tracks changes to this value, adding it as a source of the current reactive observer. |
|
||||
//! | [`Trigger`] | — | Notifies subscribers that this value has changed. |
|
||||
//! | [`Notify`] | — | 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.
|
||||
//!
|
||||
@@ -34,7 +34,7 @@
|
||||
//! | Trait | Mode | Composition | Description
|
||||
//! |---------------------|---------------|-----------------------------------|------------
|
||||
//! | [`UpdateUntracked`] | `fn(&mut T)` | [`Write`] | 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.
|
||||
//! | [`Update`] | `fn(&mut T)` | [`UpdateUntracked`] + [`Notify`] | 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.
|
||||
//!
|
||||
//! ## Using the Traits
|
||||
@@ -67,10 +67,10 @@ use std::{
|
||||
#[macro_export]
|
||||
macro_rules! unwrap_signal {
|
||||
($signal:ident) => {{
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
let location = std::panic::Location::caller();
|
||||
|| {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
panic!(
|
||||
"{}",
|
||||
@@ -80,7 +80,7 @@ macro_rules! unwrap_signal {
|
||||
)
|
||||
);
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
panic!(
|
||||
"Tried to access a reactive value that has already been \
|
||||
@@ -103,6 +103,7 @@ pub trait Dispose {
|
||||
/// Allows tracking the value of some reactive data.
|
||||
pub trait Track {
|
||||
/// Subscribes to this signal in the current reactive scope without doing anything with its value.
|
||||
#[track_caller]
|
||||
fn track(&self);
|
||||
}
|
||||
|
||||
@@ -404,6 +405,7 @@ where
|
||||
/// Notifies subscribers of a change in this signal.
|
||||
pub trait Notify {
|
||||
/// Notifies subscribers of a change in this signal.
|
||||
#[track_caller]
|
||||
fn notify(&self);
|
||||
}
|
||||
|
||||
@@ -630,6 +632,18 @@ pub trait IsDisposed {
|
||||
fn is_disposed(&self) -> bool;
|
||||
}
|
||||
|
||||
/// Turns a signal back into a raw value.
|
||||
pub trait IntoInner {
|
||||
/// The type of the value contained in the signal.
|
||||
type Value;
|
||||
|
||||
/// Returns the inner value if this is the only reference to to the signal.
|
||||
/// Otherwise, returns `None` and drops this reference.
|
||||
/// # Panics
|
||||
/// Panics if the inner lock is poisoned.
|
||||
fn into_inner(self) -> Option<Self::Value>;
|
||||
}
|
||||
|
||||
/// Describes where the signal was defined. This is used for diagnostic warnings and is purely a
|
||||
/// debug-mode tool.
|
||||
pub trait DefinedAt {
|
||||
|
||||
@@ -93,17 +93,35 @@ pub mod read {
|
||||
}
|
||||
|
||||
/// A wrapper for any kind of reference-counted reactive signal:
|
||||
/// an [`ArcReadSignal`], [`ArcMemo`], [`ArcRwSignal`],
|
||||
/// or derived signal closure.
|
||||
/// an [`ArcReadSignal`], [`ArcMemo`], [`ArcRwSignal`], or derived signal closure,
|
||||
/// or a plain value of the same type
|
||||
///
|
||||
/// This allows you to create APIs that take any kind of `ArcSignal<T>` as an argument,
|
||||
/// rather than adding a generic `F: Fn() -> T`. Values can be access with the same
|
||||
/// function call, `with()`, and `get()` APIs as other signals.
|
||||
/// This allows you to create APIs that take `T` or any reactive value that returns `T`
|
||||
/// as an argument, rather than adding a generic `F: Fn() -> T`.
|
||||
///
|
||||
/// Values can be accessed with the same function call, `read()`, `with()`, and `get()`
|
||||
/// APIs as other signals.
|
||||
///
|
||||
/// ## Important Notes about Derived Signals
|
||||
///
|
||||
/// `Signal::derive()` is simply a way to box and type-erase a “derived signal,” which
|
||||
/// is a plain closure that accesses one or more signals. It does *not* cache the value
|
||||
/// of that computation. Accessing the value of a `Signal<_>` that is created using `Signal::derive()`
|
||||
/// will run the closure again every time you call `.read()`, `.with()`, or `.get()`.
|
||||
///
|
||||
/// If you want the closure to run the minimal number of times necessary to update its state,
|
||||
/// and then to cache its value, you should use a [`Memo`] (and convert it into a `Signal<_>`)
|
||||
/// rather than using `Signal::derive()`.
|
||||
///
|
||||
/// Note that for many computations, it is nevertheless less expensive to use a derived signal
|
||||
/// than to create a separate memo and to cache the value: creating a new reactive node and
|
||||
/// taking the lock on that cached value whenever you access the signal is *more* expensive than
|
||||
/// simply re-running the calculation in many cases.
|
||||
pub struct ArcSignal<T: 'static, S = SyncStorage>
|
||||
where
|
||||
S: Storage<T>,
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: SignalTypes<T, S>,
|
||||
}
|
||||
@@ -114,7 +132,7 @@ pub mod read {
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
@@ -128,7 +146,7 @@ pub mod read {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
let mut s = f.debug_struct("ArcSignal");
|
||||
s.field("inner", &self.inner);
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
s.field("defined_at", &self.defined_at);
|
||||
s.finish()
|
||||
}
|
||||
@@ -184,7 +202,7 @@ pub mod read {
|
||||
|
||||
Self {
|
||||
inner: SignalTypes::DerivedSignal(Arc::new(derived_signal)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -194,7 +212,7 @@ pub mod read {
|
||||
pub fn stored(value: T) -> Self {
|
||||
Self {
|
||||
inner: SignalTypes::Stored(ArcStoredValue::new(value)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -214,7 +232,7 @@ pub mod read {
|
||||
fn from(value: ArcReadSignal<T>) -> Self {
|
||||
Self {
|
||||
inner: SignalTypes::ReadSignal(value),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -225,7 +243,7 @@ pub mod read {
|
||||
fn from(value: ArcRwSignal<T>) -> Self {
|
||||
Self {
|
||||
inner: SignalTypes::ReadSignal(value.read_only()),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -239,7 +257,7 @@ pub mod read {
|
||||
fn from(value: ArcMemo<T, S>) -> Self {
|
||||
Self {
|
||||
inner: SignalTypes::Memo(value),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -250,11 +268,11 @@ pub mod read {
|
||||
S: Storage<T>,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -330,16 +348,35 @@ pub mod read {
|
||||
}
|
||||
|
||||
/// A wrapper for any kind of arena-allocated reactive signal:
|
||||
/// an [`ReadSignal`], [`Memo`], [`RwSignal`], or derived signal closure.
|
||||
/// a [`ReadSignal`], [`Memo`], [`RwSignal`], or derived signal closure,
|
||||
/// or a plain value of the same type
|
||||
///
|
||||
/// This allows you to create APIs that take any kind of `Signal<T>` as an argument,
|
||||
/// rather than adding a generic `F: Fn() -> T`. Values can be access with the same
|
||||
/// function call, `with()`, and `get()` APIs as other signals.
|
||||
/// This allows you to create APIs that take `T` or any reactive value that returns `T`
|
||||
/// as an argument, rather than adding a generic `F: Fn() -> T`.
|
||||
///
|
||||
/// Values can be accessed with the same function call, `read()`, `with()`, and `get()`
|
||||
/// APIs as other signals.
|
||||
///
|
||||
/// ## Important Notes about Derived Signals
|
||||
///
|
||||
/// `Signal::derive()` is simply a way to box and type-erase a “derived signal,” which
|
||||
/// is a plain closure that accesses one or more signals. It does *not* cache the value
|
||||
/// of that computation. Accessing the value of a `Signal<_>` that is created using `Signal::derive()`
|
||||
/// will run the closure again every time you call `.read()`, `.with()`, or `.get()`.
|
||||
///
|
||||
/// If you want the closure to run the minimal number of times necessary to update its state,
|
||||
/// and then to cache its value, you should use a [`Memo`] (and convert it into a `Signal<_>`)
|
||||
/// rather than using `Signal::derive()`.
|
||||
///
|
||||
/// Note that for many computations, it is nevertheless less expensive to use a derived signal
|
||||
/// than to create a separate memo and to cache the value: creating a new reactive node and
|
||||
/// taking the lock on that cached value whenever you access the signal is *more* expensive than
|
||||
/// simply re-running the calculation in many cases.
|
||||
pub struct Signal<T, S = SyncStorage>
|
||||
where
|
||||
S: Storage<T>,
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: ArenaItem<SignalTypes<T, S>, S>,
|
||||
}
|
||||
@@ -371,7 +408,7 @@ pub mod read {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
let mut s = f.debug_struct("Signal");
|
||||
s.field("inner", &self.inner);
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
s.field("defined_at", &self.defined_at);
|
||||
s.finish()
|
||||
}
|
||||
@@ -393,11 +430,11 @@ pub mod read {
|
||||
S: Storage<T>,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -530,7 +567,7 @@ pub mod read {
|
||||
inner: ArenaItem::new_with_storage(SignalTypes::DerivedSignal(
|
||||
Arc::new(derived_signal),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -542,7 +579,7 @@ pub mod read {
|
||||
inner: ArenaItem::new_with_storage(SignalTypes::Stored(
|
||||
ArcStoredValue::new(value),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -569,7 +606,7 @@ pub mod read {
|
||||
inner: ArenaItem::new_local(SignalTypes::DerivedSignal(
|
||||
Arc::new(derived_signal),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -582,7 +619,7 @@ pub mod read {
|
||||
inner: ArenaItem::new_local(SignalTypes::Stored(
|
||||
ArcStoredValue::new(value),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -640,7 +677,7 @@ pub mod read {
|
||||
#[track_caller]
|
||||
fn from(value: ArcSignal<T, SyncStorage>) -> Self {
|
||||
Signal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new(value.inner),
|
||||
}
|
||||
@@ -654,7 +691,7 @@ pub mod read {
|
||||
#[track_caller]
|
||||
fn from_local(value: ArcSignal<T, LocalStorage>) -> Self {
|
||||
Signal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_local(value.inner),
|
||||
}
|
||||
@@ -668,7 +705,7 @@ pub mod read {
|
||||
#[track_caller]
|
||||
fn from(value: Signal<T, S>) -> Self {
|
||||
ArcSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: value
|
||||
.inner
|
||||
@@ -686,7 +723,7 @@ pub mod read {
|
||||
fn from(value: ReadSignal<T>) -> Self {
|
||||
Self {
|
||||
inner: ArenaItem::new(SignalTypes::ReadSignal(value.into())),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -702,7 +739,7 @@ pub mod read {
|
||||
inner: ArenaItem::new_local(SignalTypes::ReadSignal(
|
||||
value.into(),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -716,7 +753,7 @@ pub mod read {
|
||||
fn from(value: ArcReadSignal<T>) -> Self {
|
||||
Self {
|
||||
inner: ArenaItem::new(SignalTypes::ReadSignal(value)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -730,7 +767,7 @@ pub mod read {
|
||||
fn from(value: ArcReadSignal<T>) -> Self {
|
||||
Self {
|
||||
inner: ArenaItem::new_local(SignalTypes::ReadSignal(value)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -746,7 +783,7 @@ pub mod read {
|
||||
inner: ArenaItem::new(SignalTypes::ReadSignal(
|
||||
value.read_only().into(),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -762,7 +799,7 @@ pub mod read {
|
||||
inner: ArenaItem::new_local(SignalTypes::ReadSignal(
|
||||
value.read_only().into(),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -778,7 +815,7 @@ pub mod read {
|
||||
inner: ArenaItem::new(SignalTypes::ReadSignal(
|
||||
value.read_only(),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -794,7 +831,7 @@ pub mod read {
|
||||
inner: ArenaItem::new_local(SignalTypes::ReadSignal(
|
||||
value.read_only(),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -808,7 +845,7 @@ pub mod read {
|
||||
fn from(value: Memo<T>) -> Self {
|
||||
Self {
|
||||
inner: ArenaItem::new(SignalTypes::Memo(value.into())),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -822,7 +859,7 @@ pub mod read {
|
||||
fn from(value: Memo<T, LocalStorage>) -> Self {
|
||||
Self {
|
||||
inner: ArenaItem::new_local(SignalTypes::Memo(value.into())),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -836,7 +873,7 @@ pub mod read {
|
||||
fn from(value: ArcMemo<T>) -> Self {
|
||||
Self {
|
||||
inner: ArenaItem::new(SignalTypes::Memo(value)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -850,7 +887,7 @@ pub mod read {
|
||||
fn from(value: ArcMemo<T, LocalStorage>) -> Self {
|
||||
Self {
|
||||
inner: ArenaItem::new_local(SignalTypes::Memo(value)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -1831,7 +1868,7 @@ pub mod write {
|
||||
T: 'static,
|
||||
{
|
||||
inner: SignalSetterTypes<T, S>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static std::panic::Location<'static>,
|
||||
}
|
||||
|
||||
@@ -1846,7 +1883,7 @@ pub mod write {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inner: SignalSetterTypes::Default,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -1899,7 +1936,7 @@ pub mod write {
|
||||
inner: SignalSetterTypes::Mapped(ArenaItem::new_with_storage(
|
||||
Box::new(mapped_setter),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -1910,7 +1947,7 @@ pub mod write {
|
||||
fn from(value: WriteSignal<T, S>) -> Self {
|
||||
Self {
|
||||
inner: SignalSetterTypes::Write(value),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -1925,7 +1962,7 @@ pub mod write {
|
||||
fn from(value: RwSignal<T, S>) -> Self {
|
||||
Self {
|
||||
inner: SignalSetterTypes::Write(value.write_only()),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,3 +443,43 @@ fn unsync_derived_signal_and_memo() {
|
||||
assert_eq!(f.with(|n| *n), 6);
|
||||
assert_eq!(f.get_untracked(), 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memo_updates_even_if_not_read_until_later() {
|
||||
#![allow(clippy::bool_assert_comparison)]
|
||||
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
// regression test for https://github.com/leptos-rs/leptos/issues/3339
|
||||
|
||||
let input = RwSignal::new(0);
|
||||
let first_memo = Memo::new(move |_| input.get() == 1);
|
||||
let second_memo = Memo::new(move |_| first_memo.get());
|
||||
|
||||
assert_eq!(input.get(), 0);
|
||||
assert_eq!(first_memo.get(), false);
|
||||
|
||||
println!("update to 1");
|
||||
input.set(1);
|
||||
assert_eq!(input.get(), 1);
|
||||
println!("read memo 1");
|
||||
assert_eq!(first_memo.get(), true);
|
||||
println!("read memo 2");
|
||||
assert_eq!(second_memo.get(), true);
|
||||
|
||||
// this time, we don't read the memo
|
||||
println!("\nupdate to 2");
|
||||
input.set(2);
|
||||
assert_eq!(input.get(), 2);
|
||||
println!("read memo 1");
|
||||
assert_eq!(first_memo.get(), false);
|
||||
|
||||
println!("\nupdate to 3");
|
||||
input.set(3);
|
||||
assert_eq!(input.get(), 3);
|
||||
println!("read memo 1");
|
||||
assert_eq!(first_memo.get(), false);
|
||||
println!("read memo 2");
|
||||
assert_eq!(second_memo.get(), false);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ use reactive_graph::{
|
||||
owner::Owner,
|
||||
signal::{arc_signal, signal, ArcRwSignal, RwSignal},
|
||||
traits::{
|
||||
Get, GetUntracked, Read, Set, Update, UpdateUntracked, With,
|
||||
WithUntracked, Write,
|
||||
Dispose, Get, GetUntracked, IntoInner, Read, Set, Update,
|
||||
UpdateUntracked, With, WithUntracked, Write,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -108,3 +108,35 @@ fn update_signal() {
|
||||
set_a.set(4);
|
||||
assert_eq!(a.get(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_inner_signal() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let rw_signal = RwSignal::new(1);
|
||||
assert_eq!(rw_signal.get(), 1);
|
||||
assert_eq!(rw_signal.into_inner(), Some(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_inner_arc_signal() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let (a, b) = arc_signal(2);
|
||||
assert_eq!(a.get(), 2);
|
||||
std::mem::drop(b);
|
||||
assert_eq!(a.into_inner(), Some(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_inner_non_arc_signal() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let (a, b) = signal(2);
|
||||
assert_eq!(a.get(), 2);
|
||||
b.dispose();
|
||||
assert_eq!(a.into_inner(), Some(2));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_stores"
|
||||
version = "0.1.0-rc1"
|
||||
version = "0.1.3"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
@@ -23,3 +23,6 @@ tokio = { version = "1.41", features = ["rt-multi-thread", "macros"] }
|
||||
tokio-test = { version = "0.4.4" }
|
||||
any_spawner = { workspace = true, features = ["futures-executor", "tokio"] }
|
||||
reactive_graph = { workspace = true, features = ["effects"] }
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
|
||||
|
||||
@@ -7,6 +7,7 @@ use reactive_graph::{
|
||||
owner::Storage,
|
||||
traits::{
|
||||
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
|
||||
Write,
|
||||
},
|
||||
};
|
||||
use std::{
|
||||
@@ -25,13 +26,14 @@ pub struct ArcField<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
path: StorePath,
|
||||
trigger: StoreFieldTrigger,
|
||||
get_trigger: Arc<dyn Fn(StorePath) -> StoreFieldTrigger + Send + Sync>,
|
||||
read: Arc<dyn Fn() -> Option<StoreFieldReader<T>> + Send + Sync>,
|
||||
write: Arc<dyn Fn() -> Option<StoreFieldWriter<T>> + Send + Sync>,
|
||||
pub(crate) write:
|
||||
Arc<dyn Fn() -> Option<StoreFieldWriter<T>> + Send + Sync>,
|
||||
keys: Arc<dyn Fn() -> Option<KeyMap> + Send + Sync>,
|
||||
track_field: Arc<dyn Fn() + Send + Sync>,
|
||||
}
|
||||
@@ -114,7 +116,7 @@ where
|
||||
#[track_caller]
|
||||
fn from(value: Store<T, S>) -> Self {
|
||||
ArcField {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
path: value.path().into_iter().collect(),
|
||||
trigger: value.get_trigger(value.path().into_iter().collect()),
|
||||
@@ -134,7 +136,7 @@ where
|
||||
#[track_caller]
|
||||
fn from(value: ArcStore<T>) -> Self {
|
||||
ArcField {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
path: value.path().into_iter().collect(),
|
||||
trigger: value.get_trigger(value.path().into_iter().collect()),
|
||||
@@ -172,7 +174,7 @@ where
|
||||
#[track_caller]
|
||||
fn from(value: Subfield<Inner, Prev, T>) -> Self {
|
||||
ArcField {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
path: value.path().into_iter().collect(),
|
||||
trigger: value.get_trigger(value.path().into_iter().collect()),
|
||||
@@ -210,7 +212,7 @@ where
|
||||
#[track_caller]
|
||||
fn from(value: AtIndex<Inner, Prev>) -> Self {
|
||||
ArcField {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
path: value.path().into_iter().collect(),
|
||||
trigger: value.get_trigger(value.path().into_iter().collect()),
|
||||
@@ -252,7 +254,7 @@ where
|
||||
#[track_caller]
|
||||
fn from(value: AtKeyed<Inner, Prev, K, T>) -> Self {
|
||||
ArcField {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
path: value.path().into_iter().collect(),
|
||||
trigger: value.get_trigger(value.path().into_iter().collect()),
|
||||
@@ -283,7 +285,7 @@ where
|
||||
impl<T> Clone for ArcField<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
path: self.path.clone(),
|
||||
trigger: self.trigger.clone(),
|
||||
@@ -298,11 +300,11 @@ impl<T> Clone for ArcField<T> {
|
||||
|
||||
impl<T> DefinedAt for ArcField<T> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -329,6 +331,22 @@ impl<T> ReadUntracked for ArcField<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Write for ArcField<T> {
|
||||
type Value = T;
|
||||
|
||||
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
|
||||
(self.write)()
|
||||
}
|
||||
|
||||
fn try_write_untracked(
|
||||
&self,
|
||||
) -> Option<impl DerefMut<Target = Self::Value>> {
|
||||
let mut guard = (self.write)()?;
|
||||
guard.untrack();
|
||||
Some(guard)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IsDisposed for ArcField<T> {
|
||||
fn is_disposed(&self) -> bool {
|
||||
false
|
||||
|
||||
@@ -6,10 +6,18 @@ use crate::{
|
||||
};
|
||||
use reactive_graph::{
|
||||
owner::{ArenaItem, Storage, SyncStorage},
|
||||
traits::{DefinedAt, IsDisposed, Notify, ReadUntracked, Track},
|
||||
traits::{
|
||||
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
|
||||
Write,
|
||||
},
|
||||
unwrap_signal,
|
||||
};
|
||||
use std::{fmt::Debug, hash::Hash, ops::IndexMut, panic::Location};
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
hash::Hash,
|
||||
ops::{DerefMut, IndexMut},
|
||||
panic::Location,
|
||||
};
|
||||
|
||||
/// Wraps access to a single field of type `T`.
|
||||
///
|
||||
@@ -19,7 +27,7 @@ pub struct Field<T, S = SyncStorage>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: ArenaItem<ArcField<T>, S>,
|
||||
}
|
||||
@@ -67,7 +75,7 @@ where
|
||||
#[track_caller]
|
||||
fn from(value: Store<T, S>) -> Self {
|
||||
Field {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(value.into()),
|
||||
}
|
||||
@@ -82,7 +90,7 @@ where
|
||||
#[track_caller]
|
||||
fn from(value: ArcStore<T>) -> Self {
|
||||
Field {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(value.into()),
|
||||
}
|
||||
@@ -100,7 +108,7 @@ where
|
||||
#[track_caller]
|
||||
fn from(value: Subfield<Inner, Prev, T>) -> Self {
|
||||
Field {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(value.into()),
|
||||
}
|
||||
@@ -118,7 +126,7 @@ where
|
||||
#[track_caller]
|
||||
fn from(value: AtIndex<Inner, Prev>) -> Self {
|
||||
Field {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(value.into()),
|
||||
}
|
||||
@@ -141,7 +149,7 @@ where
|
||||
#[track_caller]
|
||||
fn from(value: AtKeyed<Inner, Prev, K, T>) -> Self {
|
||||
Field {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(value.into()),
|
||||
}
|
||||
@@ -158,11 +166,11 @@ impl<T, S> Copy for Field<T, S> {}
|
||||
|
||||
impl<T, S> DefinedAt for Field<T, S> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -204,6 +212,24 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Write for Field<T> {
|
||||
type Value = T;
|
||||
|
||||
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
|
||||
self.inner.try_get_value().and_then(|inner| (inner.write)())
|
||||
}
|
||||
|
||||
fn try_write_untracked(
|
||||
&self,
|
||||
) -> Option<impl DerefMut<Target = Self::Value>> {
|
||||
self.inner.try_get_value().and_then(|inner| {
|
||||
let mut guard = (inner.write)()?;
|
||||
guard.untrack();
|
||||
Some(guard)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> IsDisposed for Field<T, S> {
|
||||
fn is_disposed(&self) -> bool {
|
||||
self.inner.is_disposed()
|
||||
|
||||
@@ -23,7 +23,7 @@ use std::{
|
||||
/// Provides access to the data at some index in another collection.
|
||||
#[derive(Debug)]
|
||||
pub struct AtIndex<Inner, Prev> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: Inner,
|
||||
index: usize,
|
||||
@@ -36,7 +36,7 @@ where
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
inner: self.inner.clone(),
|
||||
index: self.index,
|
||||
@@ -52,7 +52,7 @@ impl<Inner, Prev> AtIndex<Inner, Prev> {
|
||||
#[track_caller]
|
||||
pub fn new(inner: Inner, index: usize) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner,
|
||||
index,
|
||||
@@ -115,11 +115,11 @@ where
|
||||
Inner: StoreField<Value = Prev>,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ pub struct KeyedSubfield<Inner, Prev, K, T>
|
||||
where
|
||||
for<'a> &'a T: IntoIterator,
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
path_segment: StorePathSegment,
|
||||
inner: Inner,
|
||||
@@ -44,7 +44,7 @@ where
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
path_segment: self.path_segment,
|
||||
inner: self.inner.clone(),
|
||||
@@ -76,7 +76,7 @@ where
|
||||
write: fn(&mut Prev) -> &mut T,
|
||||
) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner,
|
||||
path_segment,
|
||||
@@ -254,11 +254,11 @@ where
|
||||
Inner: StoreField<Value = Prev>,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -356,7 +356,7 @@ pub struct AtKeyed<Inner, Prev, K, T>
|
||||
where
|
||||
for<'a> &'a T: IntoIterator,
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: KeyedSubfield<Inner, Prev, K, T>,
|
||||
key: K,
|
||||
@@ -370,7 +370,7 @@ where
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
inner: self.inner.clone(),
|
||||
key: self.key.clone(),
|
||||
@@ -394,7 +394,7 @@ where
|
||||
#[track_caller]
|
||||
pub fn new(inner: KeyedSubfield<Inner, Prev, K, T>, key: K) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner,
|
||||
key,
|
||||
@@ -450,10 +450,7 @@ where
|
||||
let inner = self.inner.reader()?;
|
||||
|
||||
let inner_path = self.inner.path().into_iter().collect();
|
||||
let keys = self
|
||||
.inner
|
||||
.keys()
|
||||
.expect("using keys on a store with no keys");
|
||||
let keys = self.inner.keys()?;
|
||||
let index = keys
|
||||
.with_field_keys(
|
||||
inner_path,
|
||||
@@ -461,8 +458,7 @@ where
|
||||
|| self.inner.latest_keys(),
|
||||
)
|
||||
.flatten()
|
||||
.map(|(_, idx)| idx)
|
||||
.expect("reading from a keyed field that has not yet been created");
|
||||
.map(|(_, idx)| idx)?;
|
||||
|
||||
Some(MappedMutArc::new(
|
||||
inner,
|
||||
@@ -511,11 +507,11 @@ where
|
||||
for<'a> &'a T: IntoIterator,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -623,10 +619,15 @@ where
|
||||
.keys()
|
||||
.expect("updating keys on a store with no keys");
|
||||
|
||||
// generating the latest keys out here means that if we have
|
||||
// nested keyed fields, the second field will not try to take a
|
||||
// read-lock on the key map to get the field while the first field
|
||||
// is still holding the write-lock in the closure below
|
||||
let latest = self.latest_keys();
|
||||
keys.with_field_keys(
|
||||
inner_path,
|
||||
|keys| {
|
||||
keys.update(self.latest_keys());
|
||||
keys.update(latest);
|
||||
},
|
||||
|| self.latest_keys(),
|
||||
);
|
||||
@@ -649,8 +650,8 @@ where
|
||||
#[track_caller]
|
||||
fn into_iter(self) -> StoreFieldKeyedIter<Inner, Prev, K, T> {
|
||||
// reactively track changes to this field
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.this.track();
|
||||
self.update_keys();
|
||||
self.track_field();
|
||||
|
||||
// get the current length of the field by accessing slice
|
||||
let reader = self
|
||||
|
||||
@@ -114,8 +114,8 @@ use reactive_graph::{
|
||||
ArcTrigger,
|
||||
},
|
||||
traits::{
|
||||
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
|
||||
Write,
|
||||
DefinedAt, Dispose, IsDisposed, Notify, ReadUntracked, Track,
|
||||
UntrackableGuard, Write,
|
||||
},
|
||||
};
|
||||
pub use reactive_stores_macro::{Patch, Store};
|
||||
@@ -315,7 +315,7 @@ impl KeyMap {
|
||||
/// This adds a getter method for each field to `Store<T>`, which allow accessing reactive versions
|
||||
/// of each individual field of the struct.
|
||||
pub struct ArcStore<T> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
pub(crate) value: Arc<RwLock<T>>,
|
||||
signals: Arc<RwLock<TriggerMap>>,
|
||||
@@ -326,7 +326,7 @@ impl<T> ArcStore<T> {
|
||||
/// Creates a new store from the initial value.
|
||||
pub fn new(value: T) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
value: Arc::new(RwLock::new(value)),
|
||||
signals: Default::default(),
|
||||
@@ -335,10 +335,16 @@ impl<T> ArcStore<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default> Default for ArcStore<T> {
|
||||
fn default() -> Self {
|
||||
Self::new(T::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for ArcStore<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut f = f.debug_struct("ArcStore");
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
let f = f.field("defined_at", &self.defined_at);
|
||||
f.field("value", &self.value)
|
||||
.field("signals", &self.signals)
|
||||
@@ -349,7 +355,7 @@ impl<T: Debug> Debug for ArcStore<T> {
|
||||
impl<T> Clone for ArcStore<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
value: Arc::clone(&self.value),
|
||||
signals: Arc::clone(&self.signals),
|
||||
@@ -360,11 +366,11 @@ impl<T> Clone for ArcStore<T> {
|
||||
|
||||
impl<T> DefinedAt for ArcStore<T> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -433,7 +439,7 @@ impl<T: 'static> Notify for ArcStore<T> {
|
||||
/// This follows the same ownership rules as arena-allocated types like
|
||||
/// [`RwSignal`](reactive_graph::signal::RwSignal).
|
||||
pub struct Store<T, S = SyncStorage> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: ArenaItem<ArcStore<T>, S>,
|
||||
}
|
||||
@@ -445,7 +451,7 @@ where
|
||||
/// Creates a new store with the initial value.
|
||||
pub fn new(value: T) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(ArcStore::new(value)),
|
||||
}
|
||||
@@ -461,20 +467,38 @@ where
|
||||
/// This pins the value to the current thread. Accessing it from any other thread will panic.
|
||||
pub fn new_local(value: T) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(ArcStore::new(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Store<T>
|
||||
where
|
||||
T: Default + Send + Sync + 'static,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::new(T::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Store<T, LocalStorage>
|
||||
where
|
||||
T: Default + 'static,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::new_local(T::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug, S> Debug for Store<T, S>
|
||||
where
|
||||
S: Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut f = f.debug_struct("Store");
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
let f = f.field("defined_at", &self.defined_at);
|
||||
f.field("inner", &self.inner).finish()
|
||||
}
|
||||
@@ -490,11 +514,11 @@ impl<T, S> Copy for Store<T, S> {}
|
||||
|
||||
impl<T, S> DefinedAt for Store<T, S> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -511,6 +535,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> Dispose for Store<T, S>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
fn dispose(self) {
|
||||
self.inner.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> ReadUntracked for Store<T, S>
|
||||
where
|
||||
T: 'static,
|
||||
@@ -569,6 +602,20 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> From<ArcStore<T>> for Store<T, S>
|
||||
where
|
||||
T: 'static,
|
||||
S: Storage<ArcStore<T>>,
|
||||
{
|
||||
fn from(value: ArcStore<T>) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: value.defined_at,
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{self as reactive_stores, Patch, Store, StoreFieldIterator};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{StoreField, Subfield};
|
||||
use reactive_graph::traits::Read;
|
||||
use reactive_graph::traits::{Read, ReadUntracked};
|
||||
use std::ops::Deref;
|
||||
|
||||
/// Extends optional store fields, with the ability to unwrap or map over them.
|
||||
@@ -23,12 +23,23 @@ where
|
||||
self,
|
||||
map_fn: impl FnOnce(Subfield<Self, Option<Self::Output>, Self::Output>) -> U,
|
||||
) -> Option<U>;
|
||||
|
||||
/// Unreactively maps over the field.
|
||||
///
|
||||
/// This returns `None` if the subfield is currently `None`,
|
||||
/// and a new store subfield with the inner value if it is `Some`. This is an unreactive variant of
|
||||
/// `[OptionStoreExt::map]`, and will not cause the reactive context to re-run if the field changes.
|
||||
fn map_untracked<U>(
|
||||
self,
|
||||
map_fn: impl FnOnce(Subfield<Self, Option<Self::Output>, Self::Output>) -> U,
|
||||
) -> Option<U>;
|
||||
}
|
||||
|
||||
impl<T, S> OptionStoreExt for S
|
||||
where
|
||||
S: StoreField<Value = Option<T>> + Read,
|
||||
S: StoreField<Value = Option<T>> + Read + ReadUntracked,
|
||||
<S as Read>::Value: Deref<Target = Option<T>>,
|
||||
<S as ReadUntracked>::Value: Deref<Target = Option<T>>,
|
||||
{
|
||||
type Output = T;
|
||||
|
||||
@@ -51,6 +62,17 @@ where
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn map_untracked<U>(
|
||||
self,
|
||||
map_fn: impl FnOnce(Subfield<S, Option<T>, T>) -> U,
|
||||
) -> Option<U> {
|
||||
if self.read_untracked().is_some() {
|
||||
Some(map_fn(self.unwrap()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -78,6 +78,7 @@ patch_primitives! {
|
||||
Arc<str>,
|
||||
Rc<str>,
|
||||
Cow<'_, str>,
|
||||
usize,
|
||||
u8,
|
||||
u16,
|
||||
u32,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user