Compare commits

...

77 Commits
3200 ... v0.7.2

Author SHA1 Message Date
Greg Johnston
28af33d511 v0.7.2 2024-12-21 14:09:01 -05:00
Greg Johnston
5a3413d3b3 fix: correct hydration position for static text nodes in nightly (closes #3395) (#3396) 2024-12-21 14:02:38 -05:00
Saber Haj Rabiee
702d2e247b fix(ci): add missing glib for semver checks (#3393) 2024-12-21 09:07:07 -05:00
Greg Johnston
71f698f322 fix: correct span for let: syntax (closes #3387) (#3391) 2024-12-20 15:35:48 -05:00
Michael Scofield
8d4776bf5f feat: add a From<ArcStore<T>> for Store<T, S> (#3389) 2024-12-20 13:02:48 -05:00
Oliver
65798e430f docs: showcase let syntax in for_loop (closes #3059) (#3383)
* docs: showcase let syntax in for_loop

* fix: doctests on for_loop

---------

Co-authored-by: Oliver Nordh <oliver.nordh@proton.me>
2024-12-20 13:00:46 -05:00
Greg Johnston
d46d1a4fab Merge pull request #3376 from sabify/fix-ci
fix(ci): missing glib in ci
2024-12-20 12:59:45 -05:00
Alyx Mote
f9533ab75b Update elements.rs (#3379)
fix: add missing `popovertarget` and `popovertargetaction` attributes for `<button>`
2024-12-17 19:26:04 -05:00
Saber Haj Rabiee
d08f8822c0 fix(ci): add missing glib for autofix ci 2024-12-16 23:35:05 -08:00
Saber Haj Rabiee
7357839efb fix(ci): missing glib in ci 2024-12-16 23:34:33 -08:00
Greg Johnston
1b8ad58114 chore: update lockfile 2024-12-16 20:57:56 -05:00
Greg Johnston
ef72f1ce96 v0.7.1 2024-12-16 20:52:00 -05:00
Gary Coady
6a5bfe9a5d feat: impl IntoSplitSignal for Field (closes #3362) (#3364) 2024-12-16 20:48:13 -05:00
stefnotch
1661fe2412 feat: add signal.into_inner() (#3343) 2024-12-16 20:47:25 -05:00
Greg Johnston
2324853155 chore: reenable cargo-semver-checks on PRs (#3375) 2024-12-16 19:48:52 -05:00
Greg Johnston
28a3859365 fix: rebuilding of InertElement (closes #3368) (#3370) 2024-12-16 19:23:15 -05:00
Greg Johnston
6b50179189 docs: clarify Signal::derive() behavior (#3351) 2024-12-16 09:22:30 -05:00
Paul Hansen
0bb825f6bd feat: AttributeInterceptor component to allow passing attributes to other elements (#3340) 2024-12-16 09:18:10 -05:00
Greg Johnston
a6aa111122 fix: only mark memos Check if they aren't already Dirty (closes #3339) (#3356) 2024-12-16 09:10:20 -05:00
Marcus Whybrow
753cfe8e44 chore: implement PatchField for usize (#3346) 2024-12-16 09:09:50 -05:00
zakstucke
bc9c3add87 feat: accessing context by reference (with_context and update_context) (#3279) 2024-12-16 09:09:22 -05:00
Michael Scofield
d0bb45dbc4 chore: update leptos_meta feature docs (#3350) 2024-12-16 08:33:24 -05:00
zakstucke
2a4b80cf22 feat: opt-in locations in release mode with --cfg locations (#3281) 2024-12-16 08:32:57 -05:00
redforks
881734b43a chore: impl Clone trait for TypedChildrenFn (#3349) 2024-12-16 08:32:17 -05:00
bicarlsen
68fdc8d9c4 docs: typo fix in ToChildren::to_children docs. (#3352) 2024-12-14 10:58:49 -05:00
sutantodadang
03ed805137 implemented actix multipart (#3361) 2024-12-14 10:58:29 -05:00
Greg Johnston
5d4603e988 fix: don't try to hydrate inside <noscript> (closes #3360) (#3363) 2024-12-14 10:57:51 -05:00
Greg Johnston
e65969cfcd fix: nested keyed fields in stores (closes #3338) (#3344) 2024-12-11 20:03:41 -05:00
Greg Johnston
e1e90b8595 Merge pull request #3341 from FreezyLemon/fix-some-doc-typos
Fix a doc typo and one broken intra-doc link
2024-12-10 16:27:30 -05:00
bicarlsen
71a136c7af feat: increase number of branch arms for either! macro (#3337) 2024-12-10 16:26:41 -05:00
Greg Johnston
49366be2a5 feat: add scroll prop to <A/> component to control scrolling behavior (closes #2666) (#3333) 2024-12-10 16:26:11 -05:00
FreezyLemon
6f5bdcc675 Fix rustdoc link to ArenaItem on Sandboxed::new 2024-12-09 23:01:27 +01:00
FreezyLemon
726a99441f Fix typo in Signal/ArcSignal 2024-12-09 22:49:07 +01:00
Greg Johnston
775bea46d9 fix: correctly mount/unmount hydrated static text nodes in nightly mode (closes #3334) (#3336) 2024-12-08 12:39:16 -05:00
Greg Johnston
2aa9827a1f fix: correctly support !Send Actix APIs in server functions (#3326)
* fix: correctly support `!Send` Actix APIs in server functions

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2024-12-07 15:41:32 -05:00
Greg Johnston
be3c1811fc fix: correctly swap Vec<_> in the DOM (closes #3321) (#3324)
* fix: correctly swap `Vec<_>` in the DOM (closes #3321)

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2024-12-05 21:08:05 -05:00
Greg Johnston
8df4f3c71d publish 0.1.1 to include either! macro 2024-12-04 18:53:18 -05:00
dependabot[bot]
3028a9acd0 chore(deps): bump rustls in the cargo group across 1 directory (#3289)
Bumps the cargo group with 1 update in the / directory: [rustls](https://github.com/rustls/rustls).


Updates `rustls` from 0.23.16 to 0.23.18
- [Release notes](https://github.com/rustls/rustls/releases)
- [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustls/rustls/compare/v/0.23.16...v/0.23.18)

---
updated-dependencies:
- dependency-name: rustls
  dependency-type: indirect
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-03 15:21:55 -08:00
dependabot[bot]
4b167fb809 chore(deps): bump hyper from 1.5.0 to 1.5.1 (#3265)
Bumps [hyper](https://github.com/hyperium/hyper) from 1.5.0 to 1.5.1.
- [Release notes](https://github.com/hyperium/hyper/releases)
- [Changelog](https://github.com/hyperium/hyper/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/hyper/compare/v1.5.0...v1.5.1)

---
updated-dependencies:
- dependency-name: hyper
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-03 15:21:42 -08:00
dependabot[bot]
46ae8ef9b2 chore(deps): bump rkyv from 0.8.8 to 0.8.9 (#3290)
Bumps [rkyv](https://github.com/rkyv/rkyv) from 0.8.8 to 0.8.9.
- [Release notes](https://github.com/rkyv/rkyv/releases)
- [Commits](https://github.com/rkyv/rkyv/commits)

---
updated-dependencies:
- dependency-name: rkyv
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-03 15:21:26 -08:00
benwis
21f26e7a6b Remove dependa 2024-12-03 09:00:34 -08:00
dependabot[bot]
204648d388 chore(deps): bump wasm-bindgen from 0.2.95 to 0.2.97 (#3317)
Bumps [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) from 0.2.95 to 0.2.97.
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/compare/0.2.95...0.2.97)

---
updated-dependencies:
- dependency-name: wasm-bindgen
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ben Wishovich <benwis@users.noreply.github.com>
2024-12-03 08:54:32 -08:00
dependabot[bot]
a04bca55a2 chore(deps): bump tracing from 0.1.40 to 0.1.41 (#3294)
Bumps [tracing](https://github.com/tokio-rs/tracing) from 0.1.40 to 0.1.41.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.40...tracing-0.1.41)

---
updated-dependencies:
- dependency-name: tracing
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ben Wishovich <benwis@users.noreply.github.com>
2024-12-03 08:50:46 -08:00
dependabot[bot]
1fec678174 chore(deps): bump url from 2.5.3 to 2.5.4 (#3291)
Bumps [url](https://github.com/servo/rust-url) from 2.5.3 to 2.5.4.
- [Release notes](https://github.com/servo/rust-url/releases)
- [Commits](https://github.com/servo/rust-url/compare/v2.5.3...v2.5.4)

---
updated-dependencies:
- dependency-name: url
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-03 08:49:04 -08:00
dependabot[bot]
4dbb8d1a42 chore(deps): bump bytes from 1.8.0 to 1.9.0 (#3297)
Bumps [bytes](https://github.com/tokio-rs/bytes) from 1.8.0 to 1.9.0.
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.8.0...v1.9.0)

---
updated-dependencies:
- dependency-name: bytes
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-03 08:48:33 -08:00
dependabot[bot]
fc2c52eb04 chore(deps): bump js-sys from 0.3.72 to 0.3.74 (#3306)
Bumps [js-sys](https://github.com/rustwasm/wasm-bindgen) from 0.3.72 to 0.3.74.
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

---
updated-dependencies:
- dependency-name: js-sys
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-03 08:48:03 -08:00
dependabot[bot]
1ae35ce5b3 chore(deps): bump wasm-bindgen-futures from 0.4.45 to 0.4.47 (#3318)
Bumps [wasm-bindgen-futures](https://github.com/rustwasm/wasm-bindgen) from 0.4.45 to 0.4.47.
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

---
updated-dependencies:
- dependency-name: wasm-bindgen-futures
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-03 08:47:49 -08:00
dependabot[bot]
8edb11f324 chore(deps): bump postcard from 1.0.10 to 1.1.1 (#3319)
Bumps [postcard](https://github.com/jamesmunns/postcard) from 1.0.10 to 1.1.1.
- [Release notes](https://github.com/jamesmunns/postcard/releases)
- [Changelog](https://github.com/jamesmunns/postcard/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jamesmunns/postcard/compare/v1.0.10...postcard/v1.1.1)

---
updated-dependencies:
- dependency-name: postcard
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-03 08:47:36 -08:00
mahdi739
5a01a7f2ed chore: fix typo (#3320) 2024-12-03 08:47:25 -08:00
Greg Johnston
f252460d02 fix: wait for blocking resources before sending subsequent chunks (close #3280) (#3282) 2024-12-02 16:44:36 -08:00
rjmac
6331b488e4 Remove the Send requirement on a local Action's future (fixes #3309) (#3310)
Co-authored-by: Robert Macomber <robertm@mox>
2024-12-02 09:19:42 -08:00
Gabriel Lopes Veiga
fcba8b3b17 fix: prevent multiple location headers on redirect (#3298) (#3311) 2024-12-01 18:54:35 -08:00
Greg Johnston
d665dd4b89 v0.7.0 2024-11-30 12:09:41 -05:00
Greg Johnston
be740b38ee feat: add NodeRef::on_load() and writeable NodeRef (#3305) 2024-11-30 11:55:13 -05:00
Rakshith Ravi
d2803c938c chore: remove pre-release message from README (#3075) 2024-11-30 11:48:50 -05:00
Greg Johnston
f29224415a fix: sort tuple-syntax class and style in addition to colon-syntax (closes #3296) (#3303) 2024-11-29 19:28:11 -05:00
Greg Johnston
292772c4d6 fix: allow a deprecated wasm-bindgen struct 2024-11-29 15:50:57 -05:00
benwis
5947aa299e Release rc3 2024-11-28 11:28:20 -08:00
Greg Johnston
6098836cf7 docs: improve line location of hydration error message when using view macro (#3293) 2024-11-27 14:36:31 -05:00
Niklas Eicker
2dfa61ff6a docs: fix help message for island macro (#3287) 2024-11-24 14:45:10 -05:00
Darwin Boersma
4f39b0b0ef Version any_spawner alongside other crates, reexport CustomExecutor (#3284)
* rc2 version any_spawner

Signed-off-by: Darwin Boersma <darwin@sadlark.com>

* reexport full any_spawner crate

Signed-off-by: Darwin Boersma <darwin@sadlark.com>

---------

Signed-off-by: Darwin Boersma <darwin@sadlark.com>
2024-11-23 14:49:43 -08:00
dependabot[bot]
980595f1f0 chore(deps): bump proc-macro2 from 1.0.91 to 1.0.92 (#3276)
Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.91 to 1.0.92.
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.91...1.0.92)

---
updated-dependencies:
- dependency-name: proc-macro2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-23 08:29:52 -05:00
Greg Johnston
14eb707e82 rc2 2024-11-22 15:27:00 -05:00
Greg Johnston
3de0414ed5 docs: adds more helpful text for hydration errors (closes #3267) (#3275) 2024-11-22 15:10:37 -05:00
Greg Johnston
75cae91661 chore: expose some internals of routing to make wrappers easier (#3232) 2024-11-22 15:10:23 -05:00
Greg Johnston
8b258b0d26 fix: notify Suspense on subsequent reloads (closes #3277) (#3278) 2024-11-22 14:55:24 -05:00
dependabot[bot]
84bdd6b568 chore(deps): bump serde_json from 1.0.132 to 1.0.133 (#3261)
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.132 to 1.0.133.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.132...v1.0.133)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-21 19:24:47 -05:00
dependabot[bot]
809023a2ad chore(deps): bump attribute-derive from 0.10.2 to 0.10.3 (#3260)
Bumps [attribute-derive](https://github.com/ModProg/attribute-derive) from 0.10.2 to 0.10.3.
- [Release notes](https://github.com/ModProg/attribute-derive/releases)
- [Changelog](https://github.com/ModProg/attribute-derive/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ModProg/attribute-derive/commits)

---
updated-dependencies:
- dependency-name: attribute-derive
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-21 19:24:39 -05:00
dependabot[bot]
1477ae2cfb chore(deps): bump axum from 0.7.8 to 0.7.9 (#3259)
Bumps [axum](https://github.com/tokio-rs/axum) from 0.7.8 to 0.7.9.
- [Release notes](https://github.com/tokio-rs/axum/releases)
- [Changelog](https://github.com/tokio-rs/axum/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/axum/compare/axum-v0.7.8...axum-v0.7.9)

---
updated-dependencies:
- dependency-name: axum
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-21 19:24:31 -05:00
dependabot[bot]
26995f8efd chore(deps): bump tower-http from 0.6.1 to 0.6.2 (#3266)
Bumps [tower-http](https://github.com/tower-rs/tower-http) from 0.6.1 to 0.6.2.
- [Release notes](https://github.com/tower-rs/tower-http/releases)
- [Commits](https://github.com/tower-rs/tower-http/compare/tower-http-0.6.1...tower-http-0.6.2)

---
updated-dependencies:
- dependency-name: tower-http
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-21 19:24:23 -05:00
dependabot[bot]
3c174b26a5 chore(deps): bump syn from 2.0.87 to 2.0.89 (#3272)
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.87 to 2.0.89.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.87...2.0.89)

---
updated-dependencies:
- dependency-name: syn
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-21 19:24:13 -05:00
dependabot[bot]
0c7e77800a chore(deps): bump proc-macro2 from 1.0.89 to 1.0.91 (#3273)
Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.89 to 1.0.91.
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.89...1.0.91)

---
updated-dependencies:
- dependency-name: proc-macro2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-21 19:24:04 -05:00
Greg Johnston
fee2421047 chore: fix broken links, etc. in docs (#3269) 2024-11-21 19:23:55 -05:00
Ari Seyhun
89f26f6e9b feat: add fallback prop to ProtectedRoute and ProtectedParentRoute (#3264) 2024-11-19 22:32:45 -08:00
Silver
e76b22bec8 Remove unused shell() function definition (#3254) 2024-11-19 12:17:45 -08:00
Greg Johnston
cff277b3db fix: add Write implementatations for Field<T> and ArcField<T> (closes #3257) (#3262) 2024-11-19 10:04:40 -05:00
Ari Seyhun
0258ac6df4 feat(reactive_stores): add map_untracked to OptionStoreExt (#3245) 2024-11-18 09:41:04 -05:00
121 changed files with 2099 additions and 992 deletions

View File

@@ -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

View File

@@ -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: |

View File

@@ -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

View File

@@ -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

650
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -40,36 +40,36 @@ members = [
exclude = ["benchmarks", "examples", "projects"]
[workspace.package]
version = "0.7.0-rc1"
version = "0.7.2"
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.2" }
leptos_config = { path = "./leptos_config", version = "0.7.2" }
leptos_dom = { path = "./leptos_dom", version = "0.7.2" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.2" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.2" }
leptos_macro = { path = "./leptos_macro", version = "0.7.2" }
leptos_router = { path = "./router", version = "0.7.2" }
leptos_router_macro = { path = "./router_macro", version = "0.7.2" }
leptos_server = { path = "./leptos_server", version = "0.7.2" }
leptos_meta = { path = "./meta", version = "0.7.2" }
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.2" }
server_fn_macro = { path = "./server_fn_macro", version = "0.7.2" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.2" }
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)',
] }

View File

@@ -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

View File

@@ -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"

View File

@@ -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"]

View File

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

View File

@@ -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,
);
}

View File

@@ -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 {

View File

@@ -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)'] }

View File

@@ -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;

View File

@@ -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 servers
/// 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<()>);

View File

@@ -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 apps 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 apps 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 apps 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.

View File

@@ -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"
@@ -26,11 +26,11 @@ 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]

View File

@@ -197,9 +197,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 +399,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 +412,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 +442,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 apps 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 +479,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 +530,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 apps 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 +567,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 +620,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 +751,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 +818,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 +935,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 apps 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 +975,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 +996,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 +1029,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 +1063,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 +1096,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)

View File

@@ -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();

View File

@@ -30,7 +30,7 @@ reactive_graph = { workspace = true, features = ["serde"] }
rustc-hash = "2.0"
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,7 +45,7 @@ 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"
@@ -121,3 +121,6 @@ skip_feature_sets = [
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }

View 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]
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)
}
}

View File

@@ -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`.

View File

@@ -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> {

View File

@@ -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>(

View File

@@ -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)]

View File

@@ -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`].

View File

@@ -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 = [

View File

@@ -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)'] }

View File

@@ -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)'] }

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_macro"
version = "0.7.0-rc1"
version = "0.7.2"
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"
@@ -83,4 +83,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)',
] }

View File

@@ -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]`"
);
}

View File

@@ -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;
}

View File

@@ -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
@@ -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),

View File

@@ -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)'] }

View File

@@ -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
}

View File

@@ -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)]

View File

@@ -20,7 +20,7 @@ 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)]
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: &'static Location<'static>,
}
@@ -28,7 +28,7 @@ impl<T> Clone for ArcLocalResource<T> {
fn clone(&self) -> Self {
Self {
data: self.data.clone(),
#[cfg(debug_assertions)]
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: self.defined_at,
}
}
@@ -70,7 +70,7 @@ impl<T> ArcLocalResource<T> {
let fut = fetcher();
SendWrapper::new(async move { SendWrapper::new(fut.await) })
}),
#[cfg(debug_assertions)]
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: Location::caller(),
}
}
@@ -104,11 +104,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 +200,7 @@ 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)]
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: &'static Location<'static>,
}
@@ -253,7 +253,7 @@ impl<T> LocalResource<T> {
SendWrapper::new(async move { SendWrapper::new(fut.await) })
})
},
#[cfg(debug_assertions)]
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: Location::caller(),
}
}
@@ -287,11 +287,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 +398,7 @@ impl<T: 'static> From<ArcLocalResource<T>> for LocalResource<T> {
fn from(arc: ArcLocalResource<T>) -> Self {
Self {
data: arc.data.into(),
#[cfg(debug_assertions)]
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: arc.defined_at,
}
}
@@ -408,7 +408,7 @@ impl<T: 'static> From<LocalResource<T>> for ArcLocalResource<T> {
fn from(local: LocalResource<T>) -> Self {
Self {
data: local.data.into(),
#[cfg(debug_assertions)]
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: local.defined_at,
}
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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(),
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_meta"
version = "0.7.0-rc1"
version = "0.7.2"
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]
@@ -32,3 +32,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)'] }

View File

@@ -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,14 +37,10 @@
//! }
//! ```
//! # 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 youre using server-side rendering, you should enable `ssr`.
use futures::{Stream, StreamExt};
use leptos::{

View File

@@ -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"

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_graph"
version = "0.1.0-rc1"
version = "0.1.1"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"
@@ -19,7 +19,7 @@ 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"] }
@@ -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)'] }

View File

@@ -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
}

View File

@@ -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(),
}
}

View File

@@ -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.

View File

@@ -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,
)
}

View File

@@ -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,
@@ -583,19 +584,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 +632,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,
)
}

View File

@@ -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
}

View File

@@ -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();

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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,
) {

View File

@@ -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())
}
}

View File

@@ -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 }

View File

@@ -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)
}
}

View File

@@ -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))
}

View File

@@ -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,
}
})
}
}

View File

@@ -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),
}

View File

@@ -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.

View File

@@ -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>>;

View File

@@ -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>>;

View File

@@ -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
}

View File

@@ -2,7 +2,7 @@ use super::guards::{UntrackedWriteGuard, WriteGuard};
use crate::{
graph::{ReactiveNode, SubscriberSet},
prelude::{IsDisposed, Notify},
traits::{DefinedAt, UntrackableGuard, Write},
traits::{DefinedAt, 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();

View File

@@ -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),
}

View File

@@ -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),
}

View File

@@ -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"),
)
})

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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;

View File

@@ -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 \
@@ -630,6 +630,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 {

View File

@@ -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(),
}
}

View File

@@ -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);
}

View File

@@ -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));
}

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_stores"
version = "0.1.0-rc1"
version = "0.1.2"
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)'] }

View File

@@ -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

View File

@@ -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()

View File

@@ -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
}

View File

@@ -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,
@@ -511,11 +511,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 +623,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(),
);

View File

@@ -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(),
@@ -338,7 +338,7 @@ impl<T> ArcStore<T> {
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 +349,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 +360,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 +433,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 +445,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,7 +461,7 @@ 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)),
}
@@ -474,7 +474,7 @@ where
{
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 +490,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
}
@@ -569,6 +569,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};

View File

@@ -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)]

View File

@@ -78,6 +78,7 @@ patch_primitives! {
Arc<str>,
Rc<str>,
Cow<'_, str>,
usize,
u8,
u16,
u32,

View File

@@ -18,7 +18,7 @@ use std::{iter, marker::PhantomData, ops::DerefMut, panic::Location};
/// Accesses a single field of a reactive structure.
#[derive(Debug)]
pub struct Subfield<Inner, Prev, T> {
#[cfg(debug_assertions)]
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: &'static Location<'static>,
path_segment: StorePathSegment,
inner: Inner,
@@ -33,7 +33,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(),
@@ -56,7 +56,7 @@ impl<Inner, Prev, T> Subfield<Inner, Prev, T> {
write: fn(&mut Prev) -> &mut T,
) -> Self {
Self {
#[cfg(debug_assertions)]
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: Location::caller(),
inner,
path_segment,
@@ -119,11 +119,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
}

View File

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

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_router"
version = "0.7.0-rc1"
version = "0.7.2"
authors = ["Greg Johnston", "Ben Wishovich"]
license = "MIT"
readme = "../README.md"
@@ -19,9 +19,9 @@ reactive_graph = { workspace = true }
tachys = { workspace = true, features = ["reactive_graph"] }
futures = "0.3.31"
url = "2.5"
js-sys = { version = "0.3.72" }
wasm-bindgen = { version = "0.2.95" }
tracing = { version = "0.1.40", optional = true }
js-sys = { version = "0.3.74" }
wasm-bindgen = { version = "0.2.97" }
tracing = { version = "0.1.41", optional = true }
once_cell = "1.20"
send_wrapper = "0.6.0"
thiserror = "2.0"

View File

@@ -13,8 +13,8 @@ use crate::{
resolve_path::resolve_path,
ChooseView, MatchNestedRoutes, NestedRoute, RouteDefs, SsrMode,
};
use either_of::Either;
use leptos::prelude::*;
use either_of::EitherOf3;
use leptos::{children, prelude::*};
use reactive_graph::{
owner::{provide_context, use_context, Owner},
signal::ArcRwSignal,
@@ -64,8 +64,8 @@ pub fn Router<Chil>(
//#[prop(optional)]
//trailing_slash: TrailingSlash,
/// The `<Router/>` should usually wrap your whole page. It can contain
/// any elements, and should include a [`Routes`](crate::Routes) component somewhere
/// to define and display [`Route`](crate::Route)s.
/// any elements, and should include a [`Routes`] component somewhere
/// to define and display [`Route`]s.
children: TypedChildren<Chil>,
) -> impl IntoView
where
@@ -328,9 +328,10 @@ where
/// and the element it should display.
#[component(transparent)]
pub fn Route<Segments, View>(
/// The path fragment that this route should match. This can be created using the [`path`]
/// macro, or path segments ([`StaticSegment`], [`ParamSegment`], [`WildcardSegment`], and
/// [`OptionalParamSegment`]).
/// The path fragment that this route should match. This can be created using the
/// [`path`](crate::path) macro, or path segments ([`StaticSegment`](crate::StaticSegment),
/// [`ParamSegment`](crate::ParamSegment), [`WildcardSegment`](crate::WildcardSegment), and
/// [`OptionalParamSegment`](crate::OptionalParamSegment)).
path: Segments,
/// The view for this route.
view: View,
@@ -349,9 +350,10 @@ where
/// and the element it should display.
#[component(transparent)]
pub fn ParentRoute<Segments, View, Children>(
/// The path fragment that this route should match. This can be created using the [`path`]
/// macro, or path segments ([`StaticSegment`], [`ParamSegment`], [`WildcardSegment`], and
/// [`OptionalParamSegment`]).
/// The path fragment that this route should match. This can be created using the
/// [`path`](crate::path) macro, or path segments ([`StaticSegment`](crate::StaticSegment),
/// [`ParamSegment`](crate::ParamSegment), [`WildcardSegment`](crate::WildcardSegment), and
/// [`OptionalParamSegment`](crate::OptionalParamSegment)).
path: Segments,
/// The view for this route.
view: View,
@@ -374,9 +376,10 @@ where
/// redirects to `redirect_path` instead of displaying its `view`.
#[component(transparent)]
pub fn ProtectedRoute<Segments, ViewFn, View, C, PathFn, P>(
/// The path fragment that this route should match. This can be created using the [`path`]
/// macro, or path segments ([`StaticSegment`], [`ParamSegment`], [`WildcardSegment`], and
/// [`OptionalParamSegment`]).
/// The path fragment that this route should match. This can be created using the
/// [`path`](crate::path) macro, or path segments ([`StaticSegment`](crate::StaticSegment),
/// [`ParamSegment`](crate::ParamSegment), [`WildcardSegment`](crate::WildcardSegment), and
/// [`OptionalParamSegment`](crate::OptionalParamSegment)).
path: Segments,
/// The view for this route.
view: ViewFn,
@@ -386,6 +389,9 @@ pub fn ProtectedRoute<Segments, ViewFn, View, C, PathFn, P>(
condition: C,
/// The path that will be redirected to if the condition is `Some(false)`.
redirect_path: PathFn,
/// Will be displayed while the condition is pending. By default this is the empty view.
#[prop(optional, into)]
fallback: children::ViewFn,
/// The mode that this route prefers during server-side rendering.
/// Defaults to out-of-order streaming.
#[prop(optional)]
@@ -398,23 +404,26 @@ where
PathFn: Fn() -> P + Send + Clone + 'static,
P: Display + 'static,
{
let fallback = move || fallback.run();
let view = move || {
let condition = condition.clone();
let redirect_path = redirect_path.clone();
let view = view.clone();
let fallback = fallback.clone();
(view! {
<Transition>
<Transition fallback=fallback.clone()>
{move || {
let condition = condition();
let view = view.clone();
let redirect_path = redirect_path.clone();
let fallback = fallback.clone();
Unsuspend::new(move || match condition {
Some(true) => Either::Left(view()),
Some(true) => EitherOf3::A(view()),
#[allow(clippy::unit_arg)]
Some(false) => {
Either::Right(view! { <Redirect path=redirect_path()/> }.into_inner())
EitherOf3::B(view! { <Redirect path=redirect_path()/> }.into_inner())
}
None => Either::Right(()),
None => EitherOf3::C(fallback()),
})
}}
@@ -427,9 +436,10 @@ where
#[component(transparent)]
pub fn ProtectedParentRoute<Segments, ViewFn, View, C, PathFn, P, Children>(
/// The path fragment that this route should match. This can be created using the [`path`]
/// macro, or path segments ([`StaticSegment`], [`ParamSegment`], [`WildcardSegment`], and
/// [`OptionalParamSegment`]).
/// The path fragment that this route should match. This can be created using the
/// [`path`](crate::path) macro, or path segments ([`StaticSegment`](crate::StaticSegment),
/// [`ParamSegment`](crate::ParamSegment), [`WildcardSegment`](crate::WildcardSegment), and
/// [`OptionalParamSegment`](crate::OptionalParamSegment)).
path: Segments,
/// The view for this route.
view: ViewFn,
@@ -437,6 +447,9 @@ pub fn ProtectedParentRoute<Segments, ViewFn, View, C, PathFn, P, Children>(
/// the page, `Some(false)` means the user cannot access the page, and `None` means this
/// information is still loading.
condition: C,
/// Will be displayed while the condition is pending. By default this is the empty view.
#[prop(optional, into)]
fallback: children::ViewFn,
/// The path that will be redirected to if the condition is `Some(false)`.
redirect_path: PathFn,
/// Nested child routes.
@@ -453,17 +466,21 @@ where
PathFn: Fn() -> P + Send + Clone + 'static,
P: Display + 'static,
{
let fallback = move || fallback.run();
let children = children.into_inner();
let view = move || {
let condition = condition.clone();
let redirect_path = redirect_path.clone();
let fallback = fallback.clone();
let view = view.clone();
let owner = Owner::current().unwrap();
let view = {
let fallback = fallback.clone();
move || {
let condition = condition();
let view = view.clone();
let redirect_path = redirect_path.clone();
let fallback = fallback.clone();
let owner = owner.clone();
Unsuspend::new(move || match condition {
// reset the owner so that things like providing context work
@@ -472,16 +489,16 @@ where
//
// clippy: not redundant, a FnOnce vs FnMut issue
#[allow(clippy::redundant_closure)]
Some(true) => Either::Left(owner.with(|| view())),
Some(true) => EitherOf3::A(owner.with(|| view())),
#[allow(clippy::unit_arg)]
Some(false) => Either::Right(
Some(false) => EitherOf3::B(
view! { <Redirect path=redirect_path()/> }.into_inner(),
),
None => Either::Right(()),
None => EitherOf3::C(fallback()),
})
}
};
(view! { <Transition>{view}</Transition> }).into_any()
(view! { <Transition fallback>{view}</Transition> }).into_any()
};
NestedRoute::new(path, view).ssr_mode(ssr).child(children)
}

View File

@@ -17,7 +17,8 @@
//! and are rendered by different components. This means you can navigate between siblings
//! in this tree without re-rendering or triggering any change in the parent routes.
//!
//! 3. **Progressive enhancement.** The [`A`] and [`Form`] components resolve any relative
//! 3. **Progressive enhancement.** The [`A`](crate::components::A) and
//! [`Form`](crate::components::Form) components resolve any relative
//! nested routes, render actual `<a>` and `<form>` elements, and (when possible)
//! upgrading them to handle those navigations with client-side routing. If youre using
//! them with server-side rendering (with or without hydration), they just work,

View File

@@ -92,6 +92,9 @@ pub fn A<H>(
/// a trailing slash.
#[prop(optional)]
strict_trailing_slash: bool,
/// If `true`, the router will scroll to the top of the window at the end of navigation. Defaults to `true`.
#[prop(default = true)]
scroll: bool,
/// The nodes or elements to be shown inside the link.
children: Children,
) -> impl IntoView
@@ -104,6 +107,7 @@ where
exact: bool,
children: Children,
strict_trailing_slash: bool,
scroll: bool,
) -> impl IntoView {
let RouterContext { current_url, .. } =
use_context().expect("tried to use <A/> outside a <Router/>.");
@@ -129,6 +133,7 @@ where
href=move || href.get().unwrap_or_default()
target=target
aria-current=move || if is_active() { Some("page") } else { None }
data-noscroll=!scroll
>
{children()}
@@ -137,7 +142,7 @@ where
}
let href = use_resolved_path(move || href.to_href()());
inner(href, target, exact, children, strict_trailing_slash)
inner(href, target, exact, children, strict_trailing_slash, scroll)
}
// Test if `href` is active for `location`. Assumes _both_ `href` and `location` begin with a `'/'`.

View File

@@ -359,7 +359,8 @@ where
let change = LocationChange {
value: to,
replace,
scroll: true,
scroll: !a.has_attribute("noscroll")
&& !a.has_attribute("data-noscroll"),
state: State::new(state),
};

View File

@@ -9,8 +9,7 @@ pub use static_segment::*;
/// or URL segment.
///
/// This is a "horizontal" matching: i.e., it treats a tuple of route segments
/// as subsequent segments of the URL and tries to match them all. For a "vertical"
/// matching that sees a tuple as alternatives to one another, see [`RouteChild`](super::RouteChild).
/// as subsequent segments of the URL and tries to match them all.
pub trait PossibleRouteMatch {
const OPTIONAL: bool = false;

View File

@@ -11,7 +11,7 @@ mod vertical;
use crate::{static_routes::RegenerationFn, Method, SsrMode};
pub use horizontal::*;
pub use nested::*;
use std::{borrow::Cow, collections::HashSet};
use std::{borrow::Cow, collections::HashSet, sync::atomic::Ordering};
pub use vertical::*;
#[derive(Debug)]
@@ -91,6 +91,16 @@ where
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct RouteMatchId(pub(crate) u16);
impl RouteMatchId {
/// Creates a new match ID based on the current route ID used in nested route generation.
///
/// In general, you do not need this; it should only be used for custom route matching behavior
/// in a library that creates its own route types.
pub fn new_from_route_id() -> RouteMatchId {
RouteMatchId(ROUTE_ID.fetch_add(1, Ordering::Relaxed))
}
}
pub trait MatchInterface {
type Child: MatchInterface + MatchParams + 'static;

View File

@@ -13,7 +13,7 @@ use std::{
mod tuples;
static ROUTE_ID: AtomicU16 = AtomicU16::new(1);
pub(crate) static ROUTE_ID: AtomicU16 = AtomicU16::new(1);
#[derive(Debug, PartialEq, Eq)]
pub struct NestedRoute<Segments, Children, Data, View> {

View File

@@ -1,6 +1,6 @@
use crate::location::State;
/// Options that can be used to configure a navigation. Used with [use_navigate](crate::use_navigate).
/// Options that can be used to configure a navigation. Used with [use_navigate](crate::hooks::use_navigate).
#[derive(Clone, Debug)]
pub struct NavigateOptions {
/// Whether the URL being navigated to should be resolved relative to the current route.

View File

@@ -24,7 +24,7 @@ impl ParamsMap {
/// Inserts a value into the map.
///
/// If a value with that key already exists, the new value will be added to it.
/// To replace the value instead, see [`replace`].
/// To replace the value instead, see [`replace`](Self::replace).
pub fn insert(&mut self, key: impl Into<Cow<'static, str>>, value: String) {
let value = Url::unescape(&value);

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_router_macro"
version = "0.7.0-rc1"
version = "0.7.2"
authors = ["Greg Johnston", "Ben Wishovich"]
license = "MIT"
readme = "../README.md"
@@ -19,3 +19,6 @@ quote = "1.0"
[dev-dependencies]
leptos_router = { path = "../router" }
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }

8
server_fn/Cargo.lock generated
View File

@@ -1190,7 +1190,7 @@ dependencies = [
[[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 = [
@@ -1447,7 +1447,7 @@ dependencies = [
[[package]]
name = "radium"
version = "0.7.0"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
@@ -1704,7 +1704,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sct"
version = "0.7.1"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
dependencies = [
@@ -1810,7 +1810,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 = [

View File

@@ -30,7 +30,7 @@ once_cell = "1.20"
actix-web = { version = "4.9", optional = true }
# axum
axum = { version = "0.7.8", optional = true, default-features = false, features = [
axum = { version = "0.7.9", optional = true, default-features = false, features = [
"multipart",
] }
tower = { version = "0.5.1", optional = true }
@@ -49,16 +49,16 @@ http = { version = "1.1" }
ciborium = { version = "0.2.2", optional = true }
postcard = { version = "1", features = ["alloc"], optional = true }
hyper = { version = "1.5", optional = true }
bytes = "1.8"
bytes = "1.9"
http-body-util = { version = "0.1.2", optional = true }
rkyv = { version = "0.8.8", optional = true }
rkyv = { version = "0.8.9", optional = true }
rmp-serde = { version = "1.3.0", optional = true }
# client
gloo-net = { version = "0.6.0", optional = true }
js-sys = { version = "0.3.72", optional = true }
wasm-bindgen = { version = "0.2.95", optional = true }
wasm-bindgen-futures = { version = "0.4.45", optional = true }
js-sys = { version = "0.3.74", optional = true }
wasm-bindgen = { version = "0.2.97", optional = true }
wasm-bindgen-futures = { version = "0.4.47", optional = true }
wasm-streams = { version = "0.4.2", optional = true }
web-sys = { version = "0.3.72", optional = true, features = [
"console",
@@ -229,3 +229,6 @@ skip_feature_sets = [
"rkyv",
],
]
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }

View File

@@ -1,7 +1,7 @@
use crate::{error::ServerFnError, request::Req};
use actix_web::{web::Payload, HttpRequest};
use bytes::Bytes;
use futures::Stream;
use futures::{Stream, StreamExt};
use send_wrapper::SendWrapper;
use std::{borrow::Cow, future::Future};
@@ -91,6 +91,10 @@ where
impl Stream<Item = Result<Bytes, ServerFnError>> + Send,
ServerFnError<CustErr>,
> {
Ok(futures::stream::once(async { todo!() }))
let payload = self.0.take().1;
let stream = payload.map(|res| {
res.map_err(|e| ServerFnError::Deserialization(e.to_string()))
});
Ok(SendWrapper::new(stream))
}
}

Some files were not shown because too many files have changed in this diff Show More