Compare commits

..

15 Commits
4492 ... 4512

Author SHA1 Message Date
Greg Johnston
0aafcc3947 chore: warning 2025-12-28 21:45:03 -05:00
Greg Johnston
5c2482fadc fix: preserve existing attributes when rebuilding AnyViewWithAttrs
(closes #4512)
2025-12-28 20:26:08 -05:00
dependabot[bot]
81cff63455 chore(deps): bump actions/cache from 4 to 5 (#4489)
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-19 15:48:06 -05:00
dependabot[bot]
61186c2432 chore(deps): bump the rust-dependencies group across 1 directory with 19 updates (#4499)
Bumps the rust-dependencies group with 14 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [tracing](https://github.com/tokio-rs/tracing) | `0.1.43` | `0.1.44` |
| [bitcode](https://github.com/SoftbearStudios/bitcode) | `0.6.7` | `0.6.9` |
| [insta](https://github.com/mitsuhiko/insta) | `1.44.3` | `1.45.0` |
| [bumpalo](https://github.com/fitzgen/bumpalo) | `3.19.0` | `3.19.1` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.49` | `1.2.50` |
| [glam](https://github.com/bitshifter/glam-rs) | `0.30.8` | `0.30.9` |
| [half](https://github.com/VoidStarKat/half-rs) | `2.6.0` | `2.7.1` |
| [minicov](https://github.com/Amanieu/minicov) | `0.3.7` | `0.3.8` |
| [rustls-pki-types](https://github.com/rustls/pki-types) | `1.13.1` | `1.13.2` |
| [system-deps](https://github.com/gdesmott/system-deps) | `7.0.5` | `7.0.7` |
| [toml_parser](https://github.com/toml-rs/toml) | `1.0.4` | `1.0.6+spec-1.1.0` |
| [toml_writer](https://github.com/toml-rs/toml) | `1.0.4` | `1.0.6+spec-1.1.0` |
| [utf8-width](https://github.com/magiclen/utf8-width) | `0.1.7` | `0.1.8` |
| [version-compare](https://gitlab.com/timvisee/version-compare) | `0.2.0` | `0.2.1` |



Updates `tracing` from 0.1.43 to 0.1.44
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.43...tracing-0.1.44)

Updates `bitcode` from 0.6.7 to 0.6.9
- [Commits](https://github.com/SoftbearStudios/bitcode/commits)

Updates `insta` from 1.44.3 to 1.45.0
- [Release notes](https://github.com/mitsuhiko/insta/releases)
- [Changelog](https://github.com/mitsuhiko/insta/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mitsuhiko/insta/compare/1.44.3...1.45.0)

Updates `bitcode_derive` from 0.6.7 to 0.6.9
- [Commits](https://github.com/SoftbearStudios/bitcode/commits)

Updates `bumpalo` from 3.19.0 to 3.19.1
- [Changelog](https://github.com/fitzgen/bumpalo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fitzgen/bumpalo/compare/v3.19.0...v3.19.1)

Updates `cc` from 1.2.49 to 1.2.50
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.49...cc-v1.2.50)

Updates `glam` from 0.30.8 to 0.30.9
- [Changelog](https://github.com/bitshifter/glam-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bitshifter/glam-rs/compare/0.30.8...0.30.9)

Updates `half` from 2.6.0 to 2.7.1
- [Release notes](https://github.com/VoidStarKat/half-rs/releases)
- [Changelog](https://github.com/VoidStarKat/half-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/VoidStarKat/half-rs/compare/v2.6.0...v2.7.1)

Updates `minicov` from 0.3.7 to 0.3.8
- [Changelog](https://github.com/Amanieu/minicov/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Amanieu/minicov/compare/v0.3.7...v0.3.8)

Updates `rustls-pki-types` from 1.13.1 to 1.13.2
- [Release notes](https://github.com/rustls/pki-types/releases)
- [Commits](https://github.com/rustls/pki-types/compare/v/1.13.1...v/1.13.2)

Updates `system-deps` from 7.0.5 to 7.0.7
- [Release notes](https://github.com/gdesmott/system-deps/releases)
- [Changelog](https://github.com/gdesmott/system-deps/blob/main/CHANGELOG.md)
- [Commits](https://github.com/gdesmott/system-deps/compare/v7.0.5...v7.0.7)

Updates `toml` from 0.8.23 to 0.9.8
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.23...toml-v0.9.8)

Updates `toml_datetime` from 0.6.11 to 0.7.3
- [Commits](https://github.com/toml-rs/toml/compare/toml_datetime-v0.6.11...toml_datetime-v0.7.3)

Updates `toml_edit` from 0.22.27 to 0.23.7
- [Commits](https://github.com/toml-rs/toml/compare/v0.22.27...v0.23.7)

Updates `toml_parser` from 1.0.4 to 1.0.6+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/toml_parser-v1.0.4...toml_parser-v1.0.6)

Updates `toml_writer` from 1.0.4 to 1.0.6+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/toml_writer-v1.0.4...toml_writer-v1.0.6)

Updates `tracing-core` from 0.1.35 to 0.1.36
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-core-0.1.35...tracing-core-0.1.36)

Updates `utf8-width` from 0.1.7 to 0.1.8
- [Commits](https://github.com/magiclen/utf8-width/compare/v0.1.7...v0.1.8)

Updates `version-compare` from 0.2.0 to 0.2.1
- [Changelog](https://gitlab.com/timvisee/version-compare/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/timvisee/version-compare/compare/v0.2.0...v0.2.1)

---
updated-dependencies:
- dependency-name: tracing
  dependency-version: 0.1.44
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: bitcode
  dependency-version: 0.6.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: insta
  dependency-version: 1.45.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: bitcode_derive
  dependency-version: 0.6.9
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: bumpalo
  dependency-version: 3.19.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: cc
  dependency-version: 1.2.50
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: glam
  dependency-version: 0.30.9
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: half
  dependency-version: 2.7.1
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: minicov
  dependency-version: 0.3.8
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: rustls-pki-types
  dependency-version: 1.13.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: system-deps
  dependency-version: 7.0.7
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: toml
  dependency-version: 0.9.8
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: toml_datetime
  dependency-version: 0.7.3
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: toml_edit
  dependency-version: 0.23.7
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: toml_parser
  dependency-version: 1.0.6+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: toml_writer
  dependency-version: 1.0.6+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: tracing-core
  dependency-version: 0.1.36
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: utf8-width
  dependency-version: 0.1.8
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: version-compare
  dependency-version: 0.2.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-19 15:47:50 -05:00
Greg Johnston
75e42ccea5 Merge pull request #4501 from leptos-rs/4488v2
Fix untracked writes on keyed store fields
2025-12-19 15:47:29 -05:00
Greg Johnston
85c7cc94ad Merge pull request #4500 from leptos-rs/fix-4481
fix: wrong path for `IntoAnyAttribute` in macro expansion from #4481
2025-12-19 15:46:56 -05:00
Greg Johnston
270536adb1 fix: prevent untracked writes on keyed subfields from notifying parent
(closes #4488)
2025-12-19 11:41:13 -05:00
Greg Johnston
cec0fb8d85 chore: add regression test to make sure untracked writes on store fields
don't notify anything
2025-12-19 11:40:44 -05:00
Greg Johnston
764b9cd57d leptos_macro-v0.8.14 2025-12-19 11:32:43 -05:00
Greg Johnston
6de2b4006a fix: wrong path for IntoAnyAttribute in macro expansion from #4481 2025-12-19 11:31:25 -05:00
Greg Johnston
65940cbefa fix: do not show Transition fallback on 2nd change if 1st change resolved synchronously (closes #4492, closes #3868) (#4495) 2025-12-19 10:42:44 -05:00
Greg Johnston
8f5c34de8a chore: publish patch versions 2025-12-19 10:22:10 -05:00
Saber Haj Rabiee
4faa340ba8 chore: upgrade dependencies (#4497) 2025-12-19 09:29:04 -05:00
Merlijn
5af5fdeeed chore: add missing import for IntoAnyAttribute when using erase_components (#4481) 2025-12-15 14:57:32 -05:00
Greg Johnston
9dd52e6c15 fix: use unkeyed paths for all store patching (closes #4486) (#4487) 2025-12-14 21:13:08 -05:00
20 changed files with 706 additions and 614 deletions

View File

@@ -103,7 +103,7 @@ jobs:
id: pnpm-cache
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v4
- uses: actions/cache@v5
if: contains(inputs.directory, 'examples')
name: Setup pnpm cache
with:

977
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -49,45 +49,45 @@ any_spawner = { path = "./any_spawner/", version = "0.3.0" }
const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1" }
either_of = { path = "./either_of/", version = "0.1.6" }
hydration_context = { path = "./hydration_context", version = "0.3.0" }
leptos = { path = "./leptos", version = "0.8.14" }
leptos = { path = "./leptos", version = "0.8.15" }
leptos_config = { path = "./leptos_config", version = "0.8.8" }
leptos_dom = { path = "./leptos_dom", version = "0.8.7" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.8.5" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.8.7" }
leptos_macro = { path = "./leptos_macro", version = "0.8.12" }
leptos_router = { path = "./router", version = "0.8.10" }
leptos_macro = { path = "./leptos_macro", version = "0.8.13" }
leptos_router = { path = "./router", version = "0.8.11" }
leptos_router_macro = { path = "./router_macro", version = "0.8.6" }
leptos_server = { path = "./leptos_server", version = "0.8.6" }
leptos_meta = { path = "./meta", version = "0.8.5" }
next_tuple = { path = "./next_tuple", version = "0.1.0" }
oco_ref = { path = "./oco", version = "0.2.1" }
or_poisoned = { path = "./or_poisoned", version = "0.1.0" }
reactive_graph = { path = "./reactive_graph", version = "0.2.11" }
reactive_stores = { path = "./reactive_stores", version = "0.3.0" }
reactive_graph = { path = "./reactive_graph", version = "0.2.12" }
reactive_stores = { path = "./reactive_stores", version = "0.3.1" }
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.2.6" }
server_fn = { path = "./server_fn", version = "0.8.8" }
server_fn = { path = "./server_fn", version = "0.8.9" }
server_fn_macro = { path = "./server_fn_macro", version = "0.8.8" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.8.5" }
tachys = { path = "./tachys", version = "0.2.11" }
# members deps
async-once-cell = { default-features = false, version = "0.5.3" }
async-once-cell = { default-features = false, version = "0.5.4" }
itertools = { default-features = false, version = "0.14.0" }
convert_case = { default-features = false, version = "0.8.0" }
serde_json = { default-features = false, version = "1.0.143" }
trybuild = { default-features = false, version = "1.0.110" }
typed-builder = { default-features = false, version = "0.22.0" }
typed-builder-macro = { default-features = false, version = "0.22.0" }
convert_case = { default-features = false, version = "0.10.0" }
serde_json = { default-features = false, version = "1.0.145" }
trybuild = { default-features = false, version = "1.0.114" }
typed-builder = { default-features = false, version = "0.23.2" }
typed-builder-macro = { default-features = false, version = "0.23.2" }
thiserror = { default-features = false, version = "2.0.17" }
wasm-bindgen = { default-features = false, version = "0.2.100" }
indexmap = { default-features = false, version = "2.11.0" }
wasm-bindgen = { default-features = false, version = "0.2.106" }
indexmap = { default-features = false, version = "2.12.1" }
rstml = { default-features = false, version = "0.12.1" }
rustc_version = { default-features = false, version = "0.4.1" }
guardian = { default-features = false, version = "1.3.0" }
rustc-hash = { default-features = false, version = "2.1.1" }
actix-web = { default-features = false, version = "4.11.0" }
tracing = { default-features = false, version = "0.1.41" }
slotmap = { default-features = false, version = "1.0.7" }
actix-web = { default-features = false, version = "4.12.1" }
tracing = { default-features = false, version = "0.1.44" }
slotmap = { default-features = false, version = "1.1.1" }
futures = { default-features = false, version = "0.3.31" }
dashmap = { default-features = false, version = "6.1.0" }
pin-project-lite = { default-features = false, version = "0.2.16" }
@@ -97,41 +97,41 @@ html-escape = { default-features = false, version = "0.2.13" }
proc-macro-error2 = { default-features = false, version = "2.0.1" }
const_format = { default-features = false, version = "0.2.35" }
gloo-net = { default-features = false, version = "0.6.0" }
url = { default-features = false, version = "2.5.4" }
tokio = { default-features = false, version = "1.47.1" }
url = { default-features = false, version = "2.5.7" }
tokio = { default-features = false, version = "1.48.0" }
base64 = { default-features = false, version = "0.22.1" }
cfg-if = { default-features = false, version = "1.0.3" }
wasm-bindgen-futures = { default-features = false, version = "0.4.50" }
cfg-if = { default-features = false, version = "1.0.4" }
wasm-bindgen-futures = { default-features = false, version = "0.4.56" }
tower = { default-features = false, version = "0.5.2" }
proc-macro2 = { default-features = false, version = "1.0.101" }
serde = { default-features = false, version = "1.0.219" }
proc-macro2 = { default-features = false, version = "1.0.103" }
serde = { default-features = false, version = "1.0.228" }
parking_lot = { default-features = false, version = "0.12.5" }
axum = { default-features = false, version = "0.8.6" }
axum = { default-features = false, version = "0.8.7" }
serde_qs = { default-features = false, version = "0.15.0" }
syn = { default-features = false, version = "2.0.106" }
syn = { default-features = false, version = "2.0.111" }
xxhash-rust = { default-features = false, version = "0.8.15" }
paste = { default-features = false, version = "1.0.15" }
quote = { default-features = false, version = "1.0.41" }
web-sys = { default-features = false, version = "0.3.77" }
js-sys = { default-features = false, version = "0.3.77" }
rand = { default-features = false, version = "0.9.1" }
serde-lite = { default-features = false, version = "0.5.0" }
quote = { default-features = false, version = "1.0.42" }
web-sys = { default-features = false, version = "0.3.83" }
js-sys = { default-features = false, version = "0.3.83" }
rand = { default-features = false, version = "0.9.2" }
serde-lite = { default-features = false, version = "0.5.1" }
tokio-tungstenite = { default-features = false, version = "0.28.0" }
serial_test = { default-features = false, version = "3.2.0" }
erased = { default-features = false, version = "0.1.2" }
glib = { default-features = false, version = "0.20.12" }
glib = { default-features = false, version = "0.21.5" }
async-trait = { default-features = false, version = "0.1.89" }
linear-map = { default-features = false, version = "1.2.0" }
anyhow = { default-features = false, version = "1.0.100" }
walkdir = { default-features = false, version = "2.5.0" }
actix-ws = { default-features = false, version = "0.3.0" }
tower-http = { default-features = false, version = "0.6.4" }
tower-http = { default-features = false, version = "0.6.8" }
prettyplease = { default-features = false, version = "0.2.37" }
inventory = { default-features = false, version = "0.3.21" }
config = { default-features = false, version = "0.15.14" }
camino = { default-features = false, version = "1.2.1" }
config = { default-features = false, version = "0.15.19" }
camino = { default-features = false, version = "1.2.2" }
ciborium = { default-features = false, version = "0.2.2" }
bitcode = { default-features = false, version = "0.6.6" }
bitcode = { default-features = false, version = "0.6.9" }
multer = { default-features = false, version = "3.1.0" }
leptos-spin-macro = { default-features = false, version = "0.2.0" }
sledgehammer_utils = { default-features = false, version = "0.3.1" }
@@ -139,38 +139,38 @@ sledgehammer_bindgen = { default-features = false, version = "0.6.0" }
wasm-streams = { default-features = false, version = "0.4.2" }
rkyv = { default-features = false, version = "0.8.12" }
temp-env = { default-features = false, version = "0.3.6" }
uuid = { default-features = false, version = "1.18.0" }
bytes = { default-features = false, version = "1.10.1" }
http = { default-features = false, version = "1.3.1" }
regex = { default-features = false, version = "1.11.3" }
uuid = { default-features = false, version = "1.19.0" }
bytes = { default-features = false, version = "1.11.0" }
http = { default-features = false, version = "1.4.0" }
regex = { default-features = false, version = "1.12.2" }
drain_filter_polyfill = { default-features = false, version = "0.1.3" }
tempfile = { default-features = false, version = "3.23.0" }
futures-lite = { default-features = false, version = "2.6.1" }
log = { default-features = false, version = "0.4.27" }
log = { default-features = false, version = "0.4.29" }
percent-encoding = { default-features = false, version = "2.3.2" }
async-executor = { default-features = false, version = "1.13.2" }
const-str = { default-features = false, version = "0.6.4" }
async-executor = { default-features = false, version = "1.13.3" }
const-str = { default-features = false, version = "0.7.1" }
http-body-util = { default-features = false, version = "0.1.3" }
hyper = { default-features = false, version = "1.7.0" }
hyper = { default-features = false, version = "1.8.1" }
postcard = { default-features = false, version = "1.1.3" }
rmp-serde = { default-features = false, version = "1.3.0" }
reqwest = { default-features = false, version = "0.12.23" }
reqwest = { default-features = false, version = "0.12.26" }
tower-layer = { default-features = false, version = "0.3.3" }
attribute-derive = { default-features = false, version = "0.10.5" }
insta = { default-features = false, version = "1.43.1" }
codee = { default-features = false, version = "0.3.0" }
insta = { default-features = false, version = "1.45.0" }
codee = { default-features = false, version = "0.3.5" }
actix-http = { default-features = false, version = "3.11.2" }
wasm-bindgen-test = { default-features = false, version = "0.3.50" }
wasm-bindgen-test = { default-features = false, version = "0.3.56" }
rustversion = { default-features = false, version = "1.0.22" }
getrandom = { default-features = false, version = "0.3.3" }
actix-files = { default-features = false, version = "0.6.6" }
getrandom = { default-features = false, version = "0.3.4" }
actix-files = { default-features = false, version = "0.6.9" }
async-lock = { default-features = false, version = "3.4.1" }
base16 = { default-features = false, version = "0.2.1" }
digest = { default-features = false, version = "0.10.7" }
sha2 = { default-features = false, version = "0.10.8" }
subsecond = { default-features = false, version = "0.7.0-rc.0" }
dioxus-cli-config = { default-features = false, version = "0.7.0-rc.0" }
dioxus-devtools = { default-features = false, version = "0.7.0-rc.0" }
sha2 = { default-features = false, version = "0.10.9" }
subsecond = { default-features = false, version = "0.7.2" }
dioxus-cli-config = { default-features = false, version = "0.7.2" }
dioxus-devtools = { default-features = false, version = "0.7.2" }
wasm_split_helpers = { default-features = false, version = "0.2.0" }
[profile.release]

View File

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

View File

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

View File

@@ -176,7 +176,9 @@ pub(crate) fn component_to_tokens(
let spreads = (!(spreads.is_empty())).then(|| {
if cfg!(feature = "__internal_erase_components") {
quote! {
.add_any_attr(vec![#(#spreads.into_any_attr(),)*])
.add_any_attr({
vec![#(::leptos::attr::any_attribute::IntoAnyAttribute::into_any_attr(#spreads),)*]
})
}
} else {
quote! {

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_graph"
version = "0.2.11"
version = "0.2.12"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"
@@ -32,7 +32,7 @@ indexmap = { workspace = true, default-features = true }
paste = { workspace = true, default-features = true }
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
web-sys = { version = "0.3.77", features = ["console"] }
web-sys = { version = "0.3.83", features = ["console"] }
[dev-dependencies]
tokio = { features = [

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_stores"
version = "0.3.0"
version = "0.3.1"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -29,6 +29,7 @@ where
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: &'static Location<'static>,
path: Arc<dyn Fn() -> StorePath + Send + Sync>,
path_unkeyed: Arc<dyn Fn() -> StorePath + Send + Sync>,
get_trigger: Arc<dyn Fn(StorePath) -> StoreFieldTrigger + Send + Sync>,
get_trigger_unkeyed:
Arc<dyn Fn(StorePath) -> StoreFieldTrigger + Send + Sync>,
@@ -113,6 +114,10 @@ impl<T> StoreField for ArcField<T> {
(self.path)()
}
fn path_unkeyed(&self) -> impl IntoIterator<Item = StorePathSegment> {
(self.path_unkeyed)()
}
fn reader(&self) -> Option<Self::Reader> {
(self.read)().map(StoreFieldReader::new)
}
@@ -137,6 +142,9 @@ where
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: Location::caller(),
path: Arc::new(move || value.path().into_iter().collect()),
path_unkeyed: Arc::new(move || {
value.path_unkeyed().into_iter().collect()
}),
get_trigger: Arc::new(move |path| value.get_trigger(path)),
get_trigger_unkeyed: Arc::new(move |path| {
value.get_trigger_unkeyed(path)
@@ -163,6 +171,10 @@ where
let value = value.clone();
move || value.path().into_iter().collect()
}),
path_unkeyed: Arc::new({
let value = value.clone();
move || value.path_unkeyed().into_iter().collect()
}),
get_trigger: Arc::new({
let value = value.clone();
move |path| value.get_trigger(path)
@@ -211,6 +223,10 @@ where
let value = value.clone();
move || value.path().into_iter().collect()
}),
path_unkeyed: Arc::new({
let value = value.clone();
move || value.path_unkeyed().into_iter().collect()
}),
get_trigger: Arc::new({
let value = value.clone();
move |path| value.get_trigger(path)
@@ -258,6 +274,10 @@ where
let value = value.clone();
move || value.path().into_iter().collect()
}),
path_unkeyed: Arc::new({
let value = value.clone();
move || value.path_unkeyed().into_iter().collect()
}),
get_trigger: Arc::new({
let value = value.clone();
move |path| value.get_trigger(path)
@@ -306,6 +326,10 @@ where
let value = value.clone();
move || value.path().into_iter().collect()
}),
path_unkeyed: Arc::new({
let value = value.clone();
move || value.path_unkeyed().into_iter().collect()
}),
get_trigger: Arc::new({
let value = value.clone();
move |path| value.get_trigger(path)
@@ -358,6 +382,10 @@ where
let value = value.clone();
move || value.path().into_iter().collect()
}),
path_unkeyed: Arc::new({
let value = value.clone();
move || value.path_unkeyed().into_iter().collect()
}),
get_trigger: Arc::new({
let value = value.clone();
move |path| value.get_trigger(path)
@@ -396,6 +424,7 @@ impl<T> Clone for ArcField<T> {
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: self.defined_at,
path: self.path.clone(),
path_unkeyed: self.path_unkeyed.clone(),
get_trigger: Arc::clone(&self.get_trigger),
get_trigger_unkeyed: Arc::clone(&self.get_trigger_unkeyed),
read: Arc::clone(&self.read),

View File

@@ -76,6 +76,11 @@ where
fn path(&self) -> impl IntoIterator<Item = StorePathSegment> {
self.inner.path()
}
fn path_unkeyed(&self) -> impl IntoIterator<Item = StorePathSegment> {
self.inner.path_unkeyed()
}
fn reader(&self) -> Option<Self::Reader> {
let inner = self.inner.reader()?;
Some(Mapped::new_with_guard(inner, |n| n.deref()))

View File

@@ -73,6 +73,13 @@ where
.unwrap_or_default()
}
fn path_unkeyed(&self) -> impl IntoIterator<Item = StorePathSegment> {
self.inner
.try_get_value()
.map(|inner| inner.path_unkeyed().into_iter().collect::<Vec<_>>())
.unwrap_or_default()
}
fn reader(&self) -> Option<Self::Reader> {
self.inner.try_get_value().and_then(|inner| inner.reader())
}

View File

@@ -80,6 +80,13 @@ where
.chain(iter::once(self.index.into()))
}
fn path_unkeyed(&self) -> impl IntoIterator<Item = StorePathSegment> {
self.inner
.path_unkeyed()
.into_iter()
.chain(iter::once(self.index.into()))
}
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
self.inner.get_trigger(path)
}

View File

@@ -106,6 +106,13 @@ where
.chain(iter::once(self.path_segment))
}
fn path_unkeyed(&self) -> impl IntoIterator<Item = StorePathSegment> {
self.inner
.path_unkeyed()
.into_iter()
.chain(iter::once(self.path_segment))
}
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
self.inner.get_trigger(path)
}
@@ -169,6 +176,7 @@ where
{
inner: KeyedSubfield<Inner, Prev, K, T>,
guard: Option<Guard>,
untracked: bool,
}
impl<Inner, Prev, K, T, Guard> Deref
@@ -220,6 +228,7 @@ where
K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
{
fn untrack(&mut self) {
self.untracked = true;
if let Some(inner) = self.guard.as_mut() {
inner.untrack();
}
@@ -244,7 +253,10 @@ where
// now that the write lock is release, we can get a read lock to refresh this keyed field
// based on the new value
self.inner.update_keys();
self.inner.notify();
if !self.untracked {
self.inner.notify();
}
// reactive updates happen on the next tick
}
@@ -337,6 +349,7 @@ where
Some(KeyedSubfieldWriteGuard {
inner: self.clone(),
guard: Some(guard),
untracked: false,
})
}
@@ -348,6 +361,7 @@ where
Some(KeyedSubfieldWriteGuard {
inner: self.clone(),
guard: Some(guard),
untracked: true,
})
}
}
@@ -444,6 +458,24 @@ where
inner.into_iter().chain(this)
}
fn path_unkeyed(&self) -> impl IntoIterator<Item = StorePathSegment> {
let inner =
self.inner.path_unkeyed().into_iter().collect::<StorePath>();
let keys = self
.inner
.keys()
.expect("using keys on a store with no keys");
let this = keys
.with_field_keys(
inner.clone(),
|keys| (keys.get(&self.key), vec![]),
|| self.inner.latest_keys(),
)
.flatten()
.map(|(_, idx)| StorePathSegment(idx));
inner.into_iter().chain(this)
}
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
self.inner.get_trigger(path)
}
@@ -721,18 +753,19 @@ mod tests {
effect::Effect,
traits::{GetUntracked, ReadUntracked, Set, Track, Write},
};
use reactive_stores::Patch;
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
#[derive(Debug, Store, Default)]
#[derive(Debug, Store, Default, Patch)]
struct Todos {
#[store(key: usize = |todo| todo.id)]
todos: Vec<Todo>,
}
#[derive(Debug, Store, Default, Clone, PartialEq, Eq)]
#[derive(Debug, Store, Default, Clone, PartialEq, Eq, Patch)]
struct Todo {
id: usize,
label: String,
@@ -853,4 +886,41 @@ mod tests {
assert_eq!(b_count.load(Ordering::Relaxed), 1);
assert_eq!(c_count.load(Ordering::Relaxed), 1);
}
#[tokio::test]
async fn untracked_write_on_keyed_subfield_shouldnt_notify() {
_ = any_spawner::Executor::init_tokio();
let store = Store::new(data());
assert_eq!(store.read_untracked().todos.len(), 3);
// create an effect to read from the keyed subfield
let todos_count = Arc::new(AtomicUsize::new(0));
Effect::new_sync({
let todos_count = Arc::clone(&todos_count);
move || {
store.todos().track();
todos_count.fetch_add(1, Ordering::Relaxed);
}
});
tick().await;
assert_eq!(todos_count.load(Ordering::Relaxed), 1);
// writing to keyed subfield notifies the iterator
store.todos().write().push(Todo {
id: 13,
label: "D".into(),
});
tick().await;
assert_eq!(todos_count.load(Ordering::Relaxed), 2);
// but an untracked write doesn't
store.todos().write_untracked().push(Todo {
id: 14,
label: "E".into(),
});
tick().await;
assert_eq!(todos_count.load(Ordering::Relaxed), 2);
}
}

View File

@@ -833,7 +833,7 @@ mod tests {
use reactive_graph::{
effect::Effect,
owner::StoredValue,
traits::{Read, ReadUntracked, Set, Update, Write},
traits::{Read, ReadUntracked, Set, Track, Update, Write},
};
use std::sync::{
atomic::{AtomicUsize, Ordering},
@@ -1375,4 +1375,34 @@ mod tests {
assert_eq!(combined_count.load(Ordering::Relaxed), 3);
}
#[tokio::test]
async fn untracked_write_on_subfield_shouldnt_notify() {
_ = any_spawner::Executor::init_tokio();
let name_count = Arc::new(AtomicUsize::new(0));
let store = Store::new(data());
let tracked_field = store.user();
Effect::new_sync({
let name_count = Arc::clone(&name_count);
move |_| {
tracked_field.track();
name_count.fetch_add(1, Ordering::Relaxed);
}
});
tick().await;
assert_eq!(name_count.load(Ordering::Relaxed), 1);
tracked_field.write().push('!');
tick().await;
assert_eq!(name_count.load(Ordering::Relaxed), 2);
tracked_field.write_untracked().push('!');
tick().await;
assert_eq!(name_count.load(Ordering::Relaxed), 2);
}
}

View File

@@ -30,7 +30,7 @@ where
type Value = T::Value;
fn patch(&self, new: Self::Value) {
let path = self.path().into_iter().collect::<StorePath>();
let path = self.path_unkeyed().into_iter().collect::<StorePath>();
if let Some(mut writer) = self.writer() {
// don't track the writer for the whole store
writer.untrack();

View File

@@ -38,6 +38,13 @@ pub trait StoreField: Sized {
#[track_caller]
fn path(&self) -> impl IntoIterator<Item = StorePathSegment>;
/// The path of this field (see [`StorePath`]). Uses unkeyed indices for any keyed fields.
#[track_caller]
fn path_unkeyed(&self) -> impl IntoIterator<Item = StorePathSegment> {
// TODO remove default impl next time we do a breaking release
self.path()
}
/// Reactively tracks this field.
#[track_caller]
fn track_field(&self) {
@@ -129,7 +136,9 @@ where
trigger
}
#[track_caller]
fn get_trigger_unkeyed(&self, path: StorePath) -> StoreFieldTrigger {
let caller = std::panic::Location::caller();
let orig_path = path.clone();
let mut path = StorePath::with_capacity(orig_path.len());
@@ -140,7 +149,13 @@ where
let key = self
.keys
.get_key_for_index(&(path.clone(), segment.0))
.expect("could not find key for index");
.unwrap_or_else(|| {
panic!(
"could not find key for index {:?} at {}",
&(path.clone(), segment.0),
caller
)
});
path.push(key);
} else {
path.push(*segment);
@@ -154,6 +169,11 @@ where
iter::empty()
}
#[track_caller]
fn path_unkeyed(&self) -> impl IntoIterator<Item = StorePathSegment> {
iter::empty()
}
#[track_caller]
fn reader(&self) -> Option<Self::Reader> {
Plain::try_new(Arc::clone(&self.value))
@@ -205,6 +225,14 @@ where
.unwrap_or_default()
}
#[track_caller]
fn path_unkeyed(&self) -> impl IntoIterator<Item = StorePathSegment> {
self.inner
.try_get_value()
.map(|n| n.path_unkeyed().into_iter().collect::<Vec<_>>())
.unwrap_or_default()
}
#[track_caller]
fn reader(&self) -> Option<Self::Reader> {
self.inner.try_get_value().and_then(|n| n.reader())

View File

@@ -84,6 +84,13 @@ where
.chain(iter::once(self.path_segment))
}
fn path_unkeyed(&self) -> impl IntoIterator<Item = StorePathSegment> {
self.inner
.path_unkeyed()
.into_iter()
.chain(iter::once(self.path_segment))
}
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
self.inner.get_trigger(path)
}

View File

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

View File

@@ -5,7 +5,7 @@ license = "MIT"
repository = "https://github.com/leptos-rs/leptos"
description = "RPC for any web framework."
readme = "../README.md"
version = "0.8.8"
version = "0.8.9"
rust-version.workspace = true
edition.workspace = true
@@ -32,7 +32,7 @@ dashmap = { workspace = true, default-features = true }
## servers
# actix
actix-web = { optional = true, workspace = true, default-features = false }
actix-web = { optional = true, workspace = true, default-features = false, features = ["ws"] }
actix-ws = { optional = true, workspace = true, default-features = true }
# axum

View File

@@ -693,13 +693,18 @@ impl Render for AnyViewWithAttrs {
fn rebuild(self, state: &mut Self::State) {
self.view.rebuild(&mut state.view);
let elements = state.elements();
// FIXME this seems wrong but I think the previous version was also broken!
if let Some(element) = elements.first() {
self.attrs.rebuild(&mut (
element.clone(),
std::mem::take(&mut state.attrs),
));
// at this point, we have rebuilt the inner view
// now we need to update attributes that were spread onto this
// this approach is not ideal, but it avoids two edge cases:
// 1) merging attributes from two unrelated views (https://github.com/leptos-rs/leptos/issues/4268)
// 2) failing to re-create attributes from the same view (https://github.com/leptos-rs/leptos/issues/4512)
for element in state.elements() {
// first, remove the previous set of attributes
self.attrs
.clone()
.rebuild(&mut (element.clone(), Vec::new()));
// then, add the new set of attributes
self.attrs.clone().build(&element);
}
}
}
@@ -824,6 +829,7 @@ impl AddAnyAttr for AnyViewWithAttrs {
/// State for any view with attributes spread onto it.
pub struct AnyViewWithAttrsState {
view: AnyViewState,
#[allow(dead_code)] // keeps attribute states alive until dropped
attrs: Vec<AnyAttributeState>,
}