Compare commits

..

28 Commits

Author SHA1 Message Date
Greg Johnston
10db2f83eb v0.7.3 2025-01-04 08:55:44 -05:00
Greg Johnston
357f3be393 fix: erase_components with AttributeInterceptor (#3435) 2025-01-04 08:46:17 -05:00
Soso
a1f5d6f79f feat: impl IntoClass for Cow<'_, str> (#3420)
I also thought about making the implementation more generic, for example over
T: AsRef<str>, but that would change the associated types and be a breaking change
it would also conflict with the `Option` impl due to the orphan rules.

Maybe there is something to be done with Borrow or Deref in a future release?
2025-01-03 15:42:42 -05:00
Michael Scofield
34ecd2d541 feat: add Dispose for Store (#3429) 2025-01-03 15:37:59 -05:00
Michael Scofield
df38b075d8 feat: add Default to stores (#3432) 2025-01-03 15:37:41 -05:00
Konstantin Stepanov
46233e3097 fix: avoid failure on guard try_new() functions (#3436) 2025-01-03 15:37:06 -05:00
Greg Johnston
46f6d9ae26 fix: correct ownership in redirect route hook (closes #3425) (#3428) 2024-12-31 11:48:52 -05:00
Chris
8264d478e4 docs: remove islands mention from leptos_axum (#3423) 2024-12-31 10:35:20 -05:00
Greg Johnston
9c54da304e Merge pull request #3424 from leptos-rs/3419
Fix issues with island hydration when nested in a closure, and with context (closes #3419)
2024-12-31 10:35:02 -05:00
Michael Scofield
1c002a2df8 chore: add missing #[track_caller]s (#3422) 2024-12-31 10:34:47 -05:00
Greg Johnston
6e4dac91bf perf: optimize and simplify island walk 2024-12-31 09:02:36 -05:00
Greg Johnston
6493b0b359 fix: a more substantial island context system that restores ownership properly 2024-12-30 19:13:55 -05:00
Greg Johnston
d58042b7ba fix: establish hydration status via ownership and context 2024-12-30 16:49:43 -05:00
Georg Vienna
7a5b27ad20 feat: allow prop destructuring via new #[prop(name = ...)] (#3382) 2024-12-27 16:57:34 -05:00
Spencer Ferris
78d0dbdfa2 fix: provide custom state in file_and_error_handler (#3408)
The `file_and_error_handler` provided by leptos_axum does not provide
the app's custom state in the context. This means that when the fallback
handler tries to render the app, the app will not be able to access the
custom state (and will panic if it uses `expect_context`).

The handle defined by `file_and_error_handler` has access to the custom
state from Axum, so we can pass the custom state from Axum into the
Leptos handler using the `additional_context` parameter of
`handle_response_inner`.
2024-12-27 13:44:39 -05:00
Greg Johnston
1c30a4c131 fix: getrandom needs js feature (used when nonce feature is active) (closes #3409) (#3410) 2024-12-26 07:29:53 -05:00
Greg Johnston
b91429d4f0 fix: correctly track updates to keyed fields in keyed iterator (closes #3401) (#3403) 2024-12-24 08:14:14 -05:00
Alexis Fontaine
6663fddf76 fix: enable console feature of web-sys (#3406) 2024-12-24 08:14:01 -05:00
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
41 changed files with 608 additions and 191 deletions

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,6 +8,8 @@ on:
branches:
- main
- leptos_0.6
env:
DEBIAN_FRONTEND: noninteractive
jobs:
get-leptos-changed:
uses: ./.github/workflows/get-leptos-changed.yml
@@ -17,6 +19,10 @@ jobs:
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

122
Cargo.lock generated
View File

@@ -278,7 +278,7 @@ dependencies = [
"async-executor",
"futures",
"glib",
"thiserror 2.0.7",
"thiserror 2.0.9",
"tokio",
"tracing",
"wasm-bindgen-futures",
@@ -603,9 +603,9 @@ checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3"
[[package]]
name = "cc"
version = "1.2.4"
version = "1.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf"
checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e"
dependencies = [
"jobserver",
"libc",
@@ -918,7 +918,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "either_of"
version = "0.1.2"
version = "0.1.3"
dependencies = [
"pin-project-lite",
]
@@ -1173,9 +1173,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "gio-sys"
version = "0.20.7"
version = "0.20.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c16e55573888c1802b8fd169201cadee4e3fd4c889bfb23c9fa652a7945335da"
checksum = "8446d9b475730ebef81802c1738d972db42fde1c5a36a627ebc4d665fc87db04"
dependencies = [
"glib-sys",
"gobject-sys",
@@ -1484,9 +1484,9 @@ dependencies = [
[[package]]
name = "hyper-rustls"
version = "0.27.3"
version = "0.27.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2"
dependencies = [
"futures-util",
"http 1.2.0",
@@ -1710,9 +1710,12 @@ checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8"
[[package]]
name = "inventory"
version = "0.3.15"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767"
checksum = "e5d80fade88dd420ce0d9ab6f7c58ef2272dde38db874657950f827d4982c817"
dependencies = [
"rustversion",
]
[[package]]
name = "ipnet"
@@ -1768,13 +1771,14 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "leptos"
version = "0.7.1"
version = "0.7.3"
dependencies = [
"any_spawner",
"base64",
"cfg-if",
"either_of",
"futures",
"getrandom",
"hydration_context",
"leptos-spin-macro",
"leptos_config",
@@ -1795,7 +1799,7 @@ dependencies = [
"server_fn",
"slotmap",
"tachys",
"thiserror 2.0.7",
"thiserror 2.0.9",
"throw_error",
"tracing",
"typed-builder",
@@ -1818,7 +1822,7 @@ dependencies = [
[[package]]
name = "leptos_actix"
version = "0.7.1"
version = "0.7.3"
dependencies = [
"actix-files",
"actix-http",
@@ -1843,7 +1847,7 @@ dependencies = [
[[package]]
name = "leptos_axum"
version = "0.7.1"
version = "0.7.3"
dependencies = [
"any_spawner",
"axum",
@@ -1866,21 +1870,21 @@ dependencies = [
[[package]]
name = "leptos_config"
version = "0.7.1"
version = "0.7.3"
dependencies = [
"config",
"regex",
"serde",
"temp-env",
"tempfile",
"thiserror 2.0.7",
"thiserror 2.0.9",
"tokio",
"typed-builder",
]
[[package]]
name = "leptos_dom"
version = "0.7.1"
version = "0.7.3"
dependencies = [
"js-sys",
"leptos",
@@ -1897,7 +1901,7 @@ dependencies = [
[[package]]
name = "leptos_hot_reload"
version = "0.7.1"
version = "0.7.3"
dependencies = [
"anyhow",
"camino",
@@ -1913,7 +1917,7 @@ dependencies = [
[[package]]
name = "leptos_integration_utils"
version = "0.7.1"
version = "0.7.3"
dependencies = [
"futures",
"hydration_context",
@@ -1926,7 +1930,7 @@ dependencies = [
[[package]]
name = "leptos_macro"
version = "0.7.1"
version = "0.7.3"
dependencies = [
"attribute-derive",
"cfg-if",
@@ -1944,7 +1948,7 @@ dependencies = [
"rstml",
"serde",
"server_fn",
"server_fn_macro 0.7.1",
"server_fn_macro 0.7.3",
"syn 2.0.90",
"tracing",
"trybuild",
@@ -1954,7 +1958,7 @@ dependencies = [
[[package]]
name = "leptos_meta"
version = "0.7.1"
version = "0.7.3"
dependencies = [
"futures",
"indexmap",
@@ -1969,7 +1973,7 @@ dependencies = [
[[package]]
name = "leptos_router"
version = "0.7.1"
version = "0.7.3"
dependencies = [
"any_spawner",
"either_of",
@@ -1984,7 +1988,7 @@ dependencies = [
"reactive_graph",
"send_wrapper",
"tachys",
"thiserror 2.0.7",
"thiserror 2.0.9",
"tracing",
"url",
"wasm-bindgen",
@@ -1993,7 +1997,7 @@ dependencies = [
[[package]]
name = "leptos_router_macro"
version = "0.7.1"
version = "0.7.3"
dependencies = [
"leptos_router",
"proc-macro-error2",
@@ -2003,7 +2007,7 @@ dependencies = [
[[package]]
name = "leptos_server"
version = "0.7.1"
version = "0.7.3"
dependencies = [
"any_spawner",
"base64",
@@ -2024,9 +2028,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.168"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "linear-map"
@@ -2166,9 +2170,9 @@ dependencies = [
[[package]]
name = "miniz_oxide"
version = "0.8.0"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394"
dependencies = [
"adler2",
]
@@ -2280,9 +2284,9 @@ dependencies = [
[[package]]
name = "object"
version = "0.36.5"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
"memchr",
]
@@ -2293,7 +2297,7 @@ version = "0.2.0"
dependencies = [
"serde",
"serde_json",
"thiserror 2.0.7",
"thiserror 2.0.9",
]
[[package]]
@@ -2614,7 +2618,7 @@ dependencies = [
"rustc-hash 2.1.0",
"rustls",
"socket2",
"thiserror 2.0.7",
"thiserror 2.0.9",
"tokio",
"tracing",
]
@@ -2633,7 +2637,7 @@ dependencies = [
"rustls",
"rustls-pki-types",
"slab",
"thiserror 2.0.7",
"thiserror 2.0.9",
"tinyvec",
"tracing",
"web-time",
@@ -2641,9 +2645,9 @@ dependencies = [
[[package]]
name = "quinn-udp"
version = "0.5.8"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52cd4b1eff68bf27940dd39811292c49e007f4d0b4c357358dc9b0197be6b527"
checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904"
dependencies = [
"cfg_aliases",
"libc",
@@ -2731,7 +2735,7 @@ dependencies = [
[[package]]
name = "reactive_graph"
version = "0.1.1"
version = "0.1.3"
dependencies = [
"any_spawner",
"async-lock",
@@ -2744,7 +2748,7 @@ dependencies = [
"send_wrapper",
"serde",
"slotmap",
"thiserror 2.0.7",
"thiserror 2.0.9",
"tokio",
"tokio-test",
"tracing",
@@ -2753,7 +2757,7 @@ dependencies = [
[[package]]
name = "reactive_stores"
version = "0.1.1"
version = "0.1.3"
dependencies = [
"any_spawner",
"guardian",
@@ -3139,9 +3143,9 @@ dependencies = [
[[package]]
name = "security-framework-sys"
version = "2.12.1"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2"
checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5"
dependencies = [
"core-foundation-sys",
"libc",
@@ -3216,9 +3220,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.133"
version = "1.0.134"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d"
dependencies = [
"itoa",
"memchr",
@@ -3270,7 +3274,7 @@ dependencies = [
[[package]]
name = "server_fn"
version = "0.7.1"
version = "0.7.3"
dependencies = [
"actix-web",
"axum",
@@ -3298,7 +3302,7 @@ dependencies = [
"serde_json",
"serde_qs",
"server_fn_macro_default",
"thiserror 2.0.7",
"thiserror 2.0.9",
"throw_error",
"tower",
"tower-layer",
@@ -3326,7 +3330,7 @@ dependencies = [
[[package]]
name = "server_fn_macro"
version = "0.7.1"
version = "0.7.3"
dependencies = [
"const_format",
"convert_case 0.6.0",
@@ -3338,9 +3342,9 @@ dependencies = [
[[package]]
name = "server_fn_macro_default"
version = "0.7.1"
version = "0.7.3"
dependencies = [
"server_fn_macro 0.7.1",
"server_fn_macro 0.7.3",
"syn 2.0.90",
]
@@ -3535,7 +3539,7 @@ dependencies = [
[[package]]
name = "tachys"
version = "0.1.1"
version = "0.1.3"
dependencies = [
"any_spawner",
"const_str_slice_concat",
@@ -3629,11 +3633,11 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.7"
version = "2.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93605438cbd668185516ab499d589afb7ee1859ea3d5fc8f6b0755e1c7443767"
checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc"
dependencies = [
"thiserror-impl 2.0.7",
"thiserror-impl 2.0.9",
]
[[package]]
@@ -3649,9 +3653,9 @@ dependencies = [
[[package]]
name = "thiserror-impl"
version = "2.0.7"
version = "2.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d8749b4531af2117677a5fcd12b1348a3fe2b81e36e61ffeac5c4aa3273e36"
checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4"
dependencies = [
"proc-macro2",
"quote",
@@ -3708,9 +3712,9 @@ dependencies = [
[[package]]
name = "tinyvec"
version = "1.8.0"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8"
dependencies = [
"tinyvec_macros",
]
@@ -4352,9 +4356,9 @@ dependencies = [
[[package]]
name = "xxhash-rust"
version = "0.8.12"
version = "0.8.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a5cbf750400958819fb6178eaa83bee5cd9c29a26a40cc241df8c70fdd46984"
checksum = "a08fd76779ae1883bbf1e46c2c46a75a0c4e37c445e68a24b01479d438f26ae6"
[[package]]
name = "yansi"

View File

@@ -40,7 +40,7 @@ members = [
exclude = ["benchmarks", "examples", "projects"]
[workspace.package]
version = "0.7.1"
version = "0.7.3"
edition = "2021"
rust-version = "1.76"
@@ -50,25 +50,25 @@ 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" }
leptos = { path = "./leptos", version = "0.7.1" }
leptos_config = { path = "./leptos_config", version = "0.7.1" }
leptos_dom = { path = "./leptos_dom", version = "0.7.1" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.1" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.1" }
leptos_macro = { path = "./leptos_macro", version = "0.7.1" }
leptos_router = { path = "./router", version = "0.7.1" }
leptos_router_macro = { path = "./router_macro", version = "0.7.1" }
leptos_server = { path = "./leptos_server", version = "0.7.1" }
leptos_meta = { path = "./meta", version = "0.7.1" }
leptos = { path = "./leptos", version = "0.7.3" }
leptos_config = { path = "./leptos_config", version = "0.7.3" }
leptos_dom = { path = "./leptos_dom", version = "0.7.3" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.3" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.3" }
leptos_macro = { path = "./leptos_macro", version = "0.7.3" }
leptos_router = { path = "./router", version = "0.7.3" }
leptos_router_macro = { path = "./router_macro", version = "0.7.3" }
leptos_server = { path = "./leptos_server", version = "0.7.3" }
leptos_meta = { path = "./meta", version = "0.7.3" }
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" }
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.1" }
server_fn_macro = { path = "./server_fn_macro", version = "0.7.1" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.1" }
server_fn = { path = "./server_fn", version = "0.7.3" }
server_fn_macro = { path = "./server_fn_macro", version = "0.7.3" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.3" }
tachys = { path = "./tachys", version = "0.1.0" }
[profile.release]
@@ -80,4 +80,7 @@ opt-level = 'z'
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
[workspace.lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)', 'cfg(erase_components)'] }
unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(leptos_debuginfo)',
'cfg(erase_components)',
] }

View File

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

View File

@@ -16,7 +16,6 @@
//! - `default`: supports running in a typical native Tokio/Axum environment
//! - `wasm`: with `default-features = false`, supports running in a JS Fetch-based
//! environment
//! - `islands`: activates Leptos [islands mode](https://leptos-rs.github.io/leptos/islands.html)
//!
//! ### Important Note
//! Prior to 0.5, using `default-features = false` on `leptos_axum` simply did nothing. Now, it actively
@@ -1994,12 +1993,12 @@ pub fn file_and_error_handler<S, IV>(
+ 'static
where
IV: IntoView + 'static,
S: Send + 'static,
S: Send + Sync + Clone + 'static,
LeptosOptions: FromRef<S>,
{
move |uri: Uri, State(options): State<S>, req: Request<Body>| {
move |uri: Uri, State(state): State<S>, req: Request<Body>| {
Box::pin(async move {
let options = LeptosOptions::from_ref(&options);
let options = LeptosOptions::from_ref(&state);
let res = get_static_file(uri, &options.site_root, req.headers());
let res = res.await.unwrap();
@@ -2007,7 +2006,9 @@ where
res.into_response()
} else {
let mut res = handle_response_inner(
|| {},
move || {
provide_context(state.clone());
},
move || shell(options),
req,
|app, chunks| {

View File

@@ -11,7 +11,10 @@ edition.workspace = true
[dependencies]
throw_error = { workspace = true }
any_spawner = { workspace = true, features = ["wasm-bindgen", "futures-executor"] }
any_spawner = { workspace = true, features = [
"wasm-bindgen",
"futures-executor",
] }
base64 = { version = "0.22.1", optional = true }
cfg-if = "1.0"
hydration_context = { workspace = true }
@@ -28,7 +31,11 @@ paste = "1.0"
rand = { version = "0.8.5", optional = true }
reactive_graph = { workspace = true, features = ["serde"] }
rustc-hash = "2.0"
tachys = { workspace = true, features = ["reactive_graph", "reactive_stores", "oco"] }
tachys = { workspace = true, features = [
"reactive_graph",
"reactive_stores",
"oco",
] }
thiserror = "2.0"
tracing = { version = "0.1.41", optional = true }
typed-builder = "0.20.0"
@@ -50,20 +57,22 @@ serde_qs = "0.13.0"
slotmap = "1.0"
futures = "0.3.31"
send_wrapper = "0.6.0"
getrandom = { version = "0.2", features = ["js"], optional = true }
[features]
hydration = [
"reactive_graph/hydration",
"leptos_server/hydration",
"hydration_context/browser",
"leptos_dom/hydration"
"leptos_dom/hydration",
]
csr = ["leptos_macro/csr", "reactive_graph/effects"]
csr = ["leptos_macro/csr", "reactive_graph/effects", "dep:getrandom"]
hydrate = [
"leptos_macro/hydrate",
"hydration",
"tachys/hydrate",
"reactive_graph/effects",
"dep:getrandom",
]
default-tls = ["server_fn/default-tls"]
rustls = ["server_fn/rustls"]
@@ -89,7 +98,7 @@ spin = ["leptos-spin-macro"]
islands = ["leptos_macro/islands", "dep:serde_json"]
trace-component-props = [
"leptos_macro/trace-component-props",
"leptos_dom/trace-component-props"
"leptos_dom/trace-component-props",
]
delegation = ["tachys/delegation"]
@@ -101,26 +110,56 @@ denylist = [
"rustls",
"default-tls",
"wasm-bindgen",
"rkyv", # was causing clippy issues on nightly
"rkyv", # was causing clippy issues on nightly
"trace-component-props",
"spin",
"islands",
]
skip_feature_sets = [
["csr", "ssr"],
["csr", "hydrate"],
["ssr", "hydrate"],
["serde", "serde-lite"],
["serde-lite", "miniserde"],
["serde", "miniserde"],
["serde", "rkyv"],
["miniserde", "rkyv"],
["serde-lite", "rkyv"],
["default-tls", "rustls"],
[
"csr",
"ssr",
],
[
"csr",
"hydrate",
],
[
"ssr",
"hydrate",
],
[
"serde",
"serde-lite",
],
[
"serde-lite",
"miniserde",
],
[
"serde",
"miniserde",
],
[
"serde",
"rkyv",
],
[
"miniserde",
"rkyv",
],
[
"serde-lite",
"rkyv",
],
[
"default-tls",
"rustls",
],
]
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }

View File

@@ -35,7 +35,7 @@ type ChildBuilder<T> = dyn Fn(AnyAttribute) -> T + Send + Sync + 'static;
/// }
/// }
/// ```
#[component]
#[component(transparent)]
pub fn AttributeInterceptor<Chil, T>(
/// The elements that will be rendered, with the attributes this component received as a
/// parameter.

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

@@ -1,4 +1,6 @@
((root, pkg_path, output_name, wasm_output_name) => {
let MOST_RECENT_CHILDREN_CB;
function idle(c) {
if ("requestIdleCallback" in window) {
window.requestIdleCallback(c);
@@ -6,55 +8,49 @@
c();
}
}
function islandTree(rootNode) {
const tree = [];
function traverse(node, parent) {
function hydrateIslands(rootNode, mod) {
function traverse(node) {
if (node.nodeType === Node.ELEMENT_NODE) {
if(node.tagName.toLowerCase() === 'leptos-island') {
const tag = node.tagName.toLowerCase();
if(tag === 'leptos-island') {
const children = [];
const id = node.dataset.component || null;
const data = { id, node, children };
hydrateIsland(node, id, mod);
for(const child of node.children) {
traverse(child, children);
}
(parent || tree).push(data);
} else {
if(tag === 'leptos-children') {
MOST_RECENT_CHILDREN_CB = node.$$on_hydrate;
}
for(const child of node.children) {
traverse(child, parent);
traverse(child);
};
}
}
}
traverse(rootNode, null);
return { el: null, id: null, children: tree };
traverse(rootNode);
}
function hydrateIsland(el, id, mod) {
const islandFn = mod[id];
if (islandFn) {
if (MOST_RECENT_CHILDREN_CB) {
MOST_RECENT_CHILDREN_CB();
}
islandFn(el);
} else {
console.warn(`Could not find WASM function for the island ${id}.`);
}
}
function hydrateIslands(entry, mod) {
if(entry.node) {
hydrateIsland(entry.node, entry.id, mod);
}
for (const island of entry.children) {
hydrateIslands(island, mod);
}
}
idle(() => {
import(`${root}/${pkg_path}/${output_name}.js`)
.then(mod => {
mod.default(`${root}/${pkg_path}/${wasm_output_name}.wasm`).then(() => {
mod.hydrate();
hydrateIslands(islandTree(document.body, null), mod);
hydrateIslands(document.body, mod);
});
})
});

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

@@ -1,6 +1,6 @@
[package]
name = "leptos_macro"
version = "0.7.1"
version = "0.7.3"
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"
@@ -83,4 +83,7 @@ skip_feature_sets = [
rustdoc-args = ["--generate-link-to-definition"]
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)', 'cfg(erase_components)'] }
unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(leptos_debuginfo)',
'cfg(erase_components)',
] }

View File

@@ -260,8 +260,12 @@ impl ToTokens for Model {
let body_name = unmodified_fn_name_from_fn_name(&body_name);
let body_expr = if is_island {
quote! {
::leptos::reactive::owner::Owner::with_hydration(move || {
#body_name(#prop_names)
::leptos::reactive::owner::Owner::new().with(|| {
::leptos::reactive::owner::Owner::with_hydration(move || {
::leptos::tachys::reactive_graph::OwnedView::new({
#body_name(#prop_names)
})
})
})
}
} else {
@@ -301,8 +305,8 @@ impl ToTokens for Model {
let hydrate_fn_name = hydrate_fn_name.as_ref().unwrap();
quote! {
{
if ::leptos::reactive::owner::Owner::current_shared_context()
.map(|sc| sc.get_is_hydrating())
if ::leptos::context::use_context::<::leptos::reactive::owner::IsHydrating>()
.map(|h| h.0)
.unwrap_or(false) {
::leptos::either::Either::Left(
#component
@@ -339,9 +343,23 @@ impl ToTokens for Model {
let children = Box::new(|| {
let sc = ::leptos::reactive::owner::Owner::current_shared_context().unwrap();
let prev = sc.get_is_hydrating();
let value = ::leptos::reactive::owner::Owner::with_no_hydration(||
::leptos::tachys::html::islands::IslandChildren::new(children()).into_any()
);
let owner = ::leptos::reactive::owner::Owner::new();
let value = owner.clone().with(|| {
::leptos::reactive::owner::Owner::with_no_hydration(move || {
::leptos::tachys::reactive_graph::OwnedView::new({
::leptos::tachys::html::islands::IslandChildren::new_with_on_hydrate(
children(),
{
let owner = owner.clone();
move || {
owner.set()
}
}
)
}).into_any()
})
});
sc.set_is_hydrating(prev);
value
});
@@ -424,20 +442,21 @@ impl ToTokens for Model {
};
let children = if is_island_with_children {
quote! {
.children({Box::new(|| {
.children({
let owner = leptos::reactive::owner::Owner::current();
Box::new(move || {
use leptos::tachys::view::any_view::IntoAny;
::leptos::tachys::html::islands::IslandChildren::new(
// TODO owner restoration for context
()
::leptos::tachys::html::islands::IslandChildren::new_with_on_hydrate(
(),
{
let owner = owner.clone();
move || {
if let Some(owner) = &owner {
owner.set()
}
}
}
).into_any()})})
//.children(children)
/*.children(Box::new(|| {
use leptos::tachys::view::any_view::IntoAny;
::leptos::tachys::html::islands::IslandChildren::new(
// TODO owner restoration for context
()
).into_any()
}))*/
}
} else {
quote! {}
@@ -655,14 +674,44 @@ impl Prop {
abort!(e.span(), e.to_string());
});
let name = if let Pat::Ident(i) = *typed.pat {
i
} else {
abort!(
typed.pat,
"only `prop: bool` style types are allowed within the \
`#[component]` macro"
);
let name = match *typed.pat {
Pat::Ident(i) => {
if let Some(name) = &prop_opts.name {
PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident: Ident::new(name, i.span()),
subpat: None,
}
} else {
i
}
}
Pat::Struct(_) | Pat::Tuple(_) | Pat::TupleStruct(_) => {
if let Some(name) = &prop_opts.name {
PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident: Ident::new(name, typed.pat.span()),
subpat: None,
}
} else {
abort!(
typed.pat,
"destructured props must be given a name e.g. \
#[prop(name = \"data\")]"
);
}
}
_ => {
abort!(
typed.pat,
"only `prop: bool` style types are allowed within the \
`#[component]` macro"
);
}
};
Self {
@@ -865,6 +914,7 @@ struct PropOpt {
default: Option<syn::Expr>,
into: bool,
attrs: bool,
name: Option<String>,
}
struct TypedBuilderOpts {

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

@@ -1,6 +1,15 @@
use core::num::NonZeroUsize;
use leptos::prelude::*;
#[derive(PartialEq, Debug)]
struct UserInfo {
user_id: String,
email: String,
}
#[derive(PartialEq, Debug)]
struct Admin(bool);
#[component]
fn Component(
#[prop(optional)] optional: bool,
@@ -10,6 +19,10 @@ fn Component(
#[prop(default = NonZeroUsize::new(10).unwrap())] default: NonZeroUsize,
#[prop(into)] into: String,
impl_trait: impl Fn() -> i32 + 'static,
#[prop(name = "data")] UserInfo { email, user_id }: UserInfo,
#[prop(name = "tuple")] (name, id): (String, i32),
#[prop(name = "tuple_struct")] Admin(is_admin): Admin,
#[prop(name = "outside_name")] inside_name: i32,
) -> impl IntoView {
_ = optional;
_ = optional_into;
@@ -18,6 +31,12 @@ fn Component(
_ = default;
_ = into;
_ = impl_trait;
_ = email;
_ = user_id;
_ = id;
_ = name;
_ = is_admin;
_ = inside_name;
}
#[test]
@@ -26,6 +45,13 @@ fn component() {
.into("")
.strip_option(9)
.impl_trait(|| 42)
.data(UserInfo {
email: "em@il".into(),
user_id: "1".into(),
})
.tuple(("Joe".into(), 12))
.tuple_struct(Admin(true))
.outside_name(1)
.build();
assert!(!cp.optional);
assert_eq!(cp.optional_into, None);
@@ -34,6 +60,16 @@ fn component() {
assert_eq!(cp.default, NonZeroUsize::new(10).unwrap());
assert_eq!(cp.into, "");
assert_eq!((cp.impl_trait)(), 42);
assert_eq!(
cp.data,
UserInfo {
email: "em@il".into(),
user_id: "1".into(),
}
);
assert_eq!(cp.tuple, ("Joe".into(), 12));
assert_eq!(cp.tuple_struct, Admin(true));
assert_eq!(cp.outside_name, 1);
}
#[test]
@@ -45,12 +81,26 @@ fn component_nostrip() {
strip_option=9
into=""
impl_trait=|| 42
data=UserInfo {
email: "em@il".into(),
user_id: "1".into(),
}
tuple=("Joe".into(), 12)
tuple_struct=Admin(true)
outside_name=1
/>
<Component
nostrip:optional_into=Some("foo")
strip_option=9
into=""
impl_trait=|| 42
data=UserInfo {
email: "em@il".into(),
user_id: "1".into(),
}
tuple=("Joe".into(), 12)
tuple_struct=Admin(true)
outside_name=1
/>
};
}

View File

@@ -44,4 +44,10 @@ fn default_with_invalid_value(
_ = default;
}
#[component]
fn destructure_without_name((default, value): (bool, i32)) -> impl IntoView {
_ = default;
_ = value;
}
fn main() {}

View File

@@ -1,4 +1,4 @@
error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default`, `into` and `attrs`
error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default`, `into`, `attrs` and `name`
--> tests/ui/component.rs:10:31
|
10 | fn unknown_prop_option(#[prop(hello)] test: bool) -> impl IntoView {
@@ -41,3 +41,9 @@ error: unexpected end of input, expected one of: identifier, `::`, `<`, `_`, lit
| ^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `component` (in Nightly builds, run with -Z macro-backtrace for more info)
error: destructured props must be given a name e.g. #[prop(name = "data")]
--> tests/ui/component.rs:48:29
|
48 | fn destructure_without_name((default, value): (bool, i32)) -> impl IntoView {
| ^^^^^^^^^^^^^^^^

View File

@@ -1,4 +1,4 @@
error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default`, `into` and `attrs`
error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default`, `into`, `attrs` and `name`
--> tests/ui/component_absolute.rs:5:31
|
5 | fn unknown_prop_option(#[prop(hello)] test: bool) -> impl ::leptos::IntoView {

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_meta"
version = "0.7.1"
version = "0.7.3"
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"
@@ -34,4 +34,4 @@ rustdoc-args = ["--generate-link-to-definition"]
denylist = ["tracing"]
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_graph"
version = "0.1.1"
version = "0.1.3"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"
@@ -25,7 +25,7 @@ async-lock = "3.4.0"
send_wrapper = { version = "0.6.0", features = ["futures"] }
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
web-sys = "0.3.72"
web-sys = { version = "0.3.72", features = ["console"] }
[dev-dependencies]
tokio = { version = "1.41", features = ["rt-multi-thread", "macros"] }

View File

@@ -292,6 +292,8 @@ impl Owner {
#[cfg(feature = "hydration")]
pub fn with_hydration<T>(fun: impl FnOnce() -> T + 'static) -> T {
fn inner<T>(fun: Box<dyn FnOnce() -> T>) -> T {
provide_context(IsHydrating(true));
let sc = OWNER.with_borrow(|o| {
o.as_ref()
.and_then(|current| current.shared_context.clone())
@@ -315,6 +317,8 @@ impl Owner {
#[cfg(feature = "hydration")]
pub fn with_no_hydration<T>(fun: impl FnOnce() -> T + 'static) -> T {
fn inner<T>(fun: Box<dyn FnOnce() -> T>) -> T {
provide_context(IsHydrating(false));
let sc = OWNER.with_borrow(|o| {
o.as_ref()
.and_then(|current| current.shared_context.clone())
@@ -335,6 +339,10 @@ impl Owner {
}
}
#[doc(hidden)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct IsHydrating(pub bool);
/// Registers a function to be run the next time the current owner is cleaned up.
///
/// Because the ownership model is associated with reactive nodes, each "decision point" in an

View File

@@ -104,7 +104,7 @@ impl<T: 'static> Debug for Plain<T> {
impl<T: 'static> Plain<T> {
/// Takes a reference-counted read guard on the given lock.
pub fn try_new(inner: Arc<RwLock<T>>) -> Option<Self> {
ArcRwLockReadGuardian::take(inner)
ArcRwLockReadGuardian::try_take(inner)?
.ok()
.map(|guard| Plain { guard })
}
@@ -331,7 +331,7 @@ pub struct UntrackedWriteGuard<T: 'static>(ArcRwLockWriteGuardian<T>);
impl<T: 'static> UntrackedWriteGuard<T> {
/// Creates a write guard from the given lock.
pub fn try_new(inner: Arc<RwLock<T>>) -> Option<Self> {
ArcRwLockWriteGuardian::take(inner)
ArcRwLockWriteGuardian::try_take(inner)?
.ok()
.map(UntrackedWriteGuard)
}

View File

@@ -103,6 +103,7 @@ pub trait Dispose {
/// Allows tracking the value of some reactive data.
pub trait Track {
/// Subscribes to this signal in the current reactive scope without doing anything with its value.
#[track_caller]
fn track(&self);
}
@@ -404,6 +405,7 @@ where
/// Notifies subscribers of a change in this signal.
pub trait Notify {
/// Notifies subscribers of a change in this signal.
#[track_caller]
fn notify(&self);
}

View File

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

View File

@@ -450,10 +450,7 @@ where
let inner = self.inner.reader()?;
let inner_path = self.inner.path().into_iter().collect();
let keys = self
.inner
.keys()
.expect("using keys on a store with no keys");
let keys = self.inner.keys()?;
let index = keys
.with_field_keys(
inner_path,
@@ -461,8 +458,7 @@ where
|| self.inner.latest_keys(),
)
.flatten()
.map(|(_, idx)| idx)
.expect("reading from a keyed field that has not yet been created");
.map(|(_, idx)| idx)?;
Some(MappedMutArc::new(
inner,
@@ -654,8 +650,8 @@ where
#[track_caller]
fn into_iter(self) -> StoreFieldKeyedIter<Inner, Prev, K, T> {
// reactively track changes to this field
let trigger = self.get_trigger(self.path().into_iter().collect());
trigger.this.track();
self.update_keys();
self.track_field();
// get the current length of the field by accessing slice
let reader = self

View File

@@ -114,8 +114,8 @@ use reactive_graph::{
ArcTrigger,
},
traits::{
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
Write,
DefinedAt, Dispose, IsDisposed, Notify, ReadUntracked, Track,
UntrackableGuard, Write,
},
};
pub use reactive_stores_macro::{Patch, Store};
@@ -335,6 +335,12 @@ impl<T> ArcStore<T> {
}
}
impl<T: Default> Default for ArcStore<T> {
fn default() -> Self {
Self::new(T::default())
}
}
impl<T: Debug> Debug for ArcStore<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut f = f.debug_struct("ArcStore");
@@ -468,6 +474,24 @@ where
}
}
impl<T> Default for Store<T>
where
T: Default + Send + Sync + 'static,
{
fn default() -> Self {
Self::new(T::default())
}
}
impl<T> Default for Store<T, LocalStorage>
where
T: Default + 'static,
{
fn default() -> Self {
Self::new_local(T::default())
}
}
impl<T: Debug, S> Debug for Store<T, S>
where
S: Debug,
@@ -511,6 +535,15 @@ where
}
}
impl<T, S> Dispose for Store<T, S>
where
T: 'static,
{
fn dispose(self) {
self.inner.dispose();
}
}
impl<T, S> ReadUntracked for Store<T, S>
where
T: 'static,
@@ -569,6 +602,20 @@ where
}
}
impl<T, S> From<ArcStore<T>> for Store<T, S>
where
T: 'static,
S: Storage<ArcStore<T>>,
{
fn from(value: ArcStore<T>) -> Self {
Self {
#[cfg(debug_assertions)]
defined_at: value.defined_at,
inner: ArenaItem::new_with_storage(value),
}
}
}
#[cfg(test)]
mod tests {
use crate::{self as reactive_stores, Patch, Store, StoreFieldIterator};

View File

@@ -24,12 +24,15 @@ pub trait StoreField: Sized {
type Writer: UntrackableGuard<Target = Self::Value>;
/// Returns the trigger that tracks access and updates for this field.
#[track_caller]
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger;
/// The path of this field (see [`StorePath`]).
#[track_caller]
fn path(&self) -> impl IntoIterator<Item = StorePathSegment>;
/// Reactively tracks this field.
#[track_caller]
fn track_field(&self) {
let path = self.path().into_iter().collect();
let trigger = self.get_trigger(path);
@@ -38,12 +41,15 @@ pub trait StoreField: Sized {
}
/// Returns a read guard to access this field.
#[track_caller]
fn reader(&self) -> Option<Self::Reader>;
/// Returns a write guard to update this field.
#[track_caller]
fn writer(&self) -> Option<Self::Writer>;
/// The keys for this field, if it is a keyed field.
#[track_caller]
fn keys(&self) -> Option<KeyMap>;
}
@@ -55,26 +61,31 @@ where
type Reader = Plain<T>;
type Writer = WriteGuard<ArcTrigger, UntrackedWriteGuard<T>>;
#[track_caller]
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
let triggers = &self.signals;
let trigger = triggers.write().or_poisoned().get_or_insert(path);
trigger
}
#[track_caller]
fn path(&self) -> impl IntoIterator<Item = StorePathSegment> {
iter::empty()
}
#[track_caller]
fn reader(&self) -> Option<Self::Reader> {
Plain::try_new(Arc::clone(&self.value))
}
#[track_caller]
fn writer(&self) -> Option<Self::Writer> {
let trigger = self.get_trigger(Default::default());
let guard = UntrackedWriteGuard::try_new(Arc::clone(&self.value))?;
Some(WriteGuard::new(trigger.children, guard))
}
#[track_caller]
fn keys(&self) -> Option<KeyMap> {
Some(self.keys.clone())
}
@@ -89,6 +100,7 @@ where
type Reader = Plain<T>;
type Writer = WriteGuard<ArcTrigger, UntrackedWriteGuard<T>>;
#[track_caller]
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
self.inner
.try_get_value()
@@ -96,6 +108,7 @@ where
.unwrap_or_else(unwrap_signal!(self))
}
#[track_caller]
fn path(&self) -> impl IntoIterator<Item = StorePathSegment> {
self.inner
.try_get_value()
@@ -103,14 +116,17 @@ where
.unwrap_or_else(unwrap_signal!(self))
}
#[track_caller]
fn reader(&self) -> Option<Self::Reader> {
self.inner.try_get_value().and_then(|n| n.reader())
}
#[track_caller]
fn writer(&self) -> Option<Self::Writer> {
self.inner.try_get_value().and_then(|n| n.writer())
}
#[track_caller]
fn keys(&self) -> Option<KeyMap> {
self.inner.try_get_value().and_then(|inner| inner.keys())
}

View File

@@ -103,6 +103,7 @@ where
self.inner.keys()
}
#[track_caller]
fn track_field(&self) {
let inner = self
.inner
@@ -144,6 +145,7 @@ where
Inner: StoreField<Value = Prev>,
Prev: 'static,
{
#[track_caller]
fn notify(&self) {
let trigger = self.get_trigger(self.path().into_iter().collect());
trigger.this.notify();
@@ -157,6 +159,7 @@ where
Prev: 'static,
T: 'static,
{
#[track_caller]
fn track(&self) {
self.track_field();
}

View File

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

View File

@@ -82,13 +82,18 @@ where
#[cfg(not(feature = "ssr"))]
let (location_provider, current_url, redirect_hook) = {
let owner = Owner::current();
let location =
BrowserUrl::new().expect("could not access browser navigation"); // TODO options here
location.init(base.clone());
provide_context(location.clone());
let current_url = location.as_url().clone();
let redirect_hook = Box::new(|loc: &str| BrowserUrl::redirect(loc));
let redirect_hook = Box::new(move |loc: &str| {
if let Some(owner) = &owner {
owner.with(|| BrowserUrl::redirect(loc));
}
});
(Some(location), current_url, redirect_hook)
};

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_router_macro"
version = "0.7.1"
version = "0.7.3"
authors = ["Greg Johnston", "Ben Wishovich"]
license = "MIT"
readme = "../README.md"
@@ -21,4 +21,4 @@ quote = "1.0"
leptos_router = { path = "../router" }
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
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.1"
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

@@ -231,4 +231,4 @@ skip_feature_sets = [
]
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }

View File

@@ -117,7 +117,7 @@ impl<T> JsonStream<T> {
pub fn new(
value: impl Stream<Item = Result<T, ServerFnError>> + Send + 'static,
) -> Self {
Self(Box::pin(value.map(|value| value.map(Into::into))))
Self(Box::pin(value.map(|value| value)))
}
}

View File

@@ -175,7 +175,7 @@ impl TextStream {
pub fn new(
value: impl Stream<Item = Result<String, ServerFnError>> + Send + 'static,
) -> Self {
Self(Box::pin(value.map(|value| value.map(Into::into))))
Self(Box::pin(value.map(|value| value)))
}
}

View File

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

View File

@@ -3,7 +3,7 @@ use crate::{
renderer::Rndr,
view::{Position, ToTemplate},
};
use std::{future::Future, sync::Arc};
use std::{borrow::Cow, future::Future, sync::Arc};
/// Adds a CSS class.
#[inline(always)]
@@ -324,6 +324,63 @@ impl IntoClass for &str {
}
}
impl IntoClass for Cow<'_, str> {
type AsyncOutput = Self;
type State = (crate::renderer::types::Element, Self);
type Cloneable = Arc<str>;
type CloneableOwned = Arc<str>;
fn html_len(&self) -> usize {
self.len()
}
fn to_html(self, class: &mut String) {
IntoClass::to_html(&*self, class);
}
fn hydrate<const FROM_SERVER: bool>(
self,
el: &crate::renderer::types::Element,
) -> Self::State {
if !FROM_SERVER {
Rndr::set_attribute(el, "class", &self);
}
(el.clone(), self)
}
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
Rndr::set_attribute(el, "class", &self);
(el.clone(), self)
}
fn rebuild(self, state: &mut Self::State) {
let (el, prev) = state;
if self != *prev {
Rndr::set_attribute(el, "class", &self);
}
*prev = self;
}
fn into_cloneable(self) -> Self::Cloneable {
self.into()
}
fn into_cloneable_owned(self) -> Self::CloneableOwned {
self.into()
}
fn dry_resolve(&mut self) {}
async fn resolve(self) -> Self::AsyncOutput {
self
}
fn reset(state: &mut Self::State) {
let (el, _prev) = state;
Rndr::remove_attribute(el, "class");
}
}
impl IntoClass for String {
type AsyncOutput = Self;
type State = (crate::renderer::types::Element, Self);

View File

@@ -250,7 +250,7 @@ html_elements! {
/// The `<body>` HTML element represents the content of an HTML document. There can be only one `<body>` element in a document.
body HtmlBodyElement [] true,
/// The `<button>` HTML element represents a clickable button, used to submit forms or anywhere in a document for accessible, standard button functionality.
button HtmlButtonElement [disabled, form, formaction, formenctype, formmethod, formnovalidate, formtarget, name, r#type, value] true,
button HtmlButtonElement [disabled, form, formaction, formenctype, formmethod, formnovalidate, formtarget, name, r#type, value, popovertarget, popovertargetaction] true,
/// Use the HTML `<canvas>` element with either the canvas scripting API or the WebGL API to draw graphics and animations.
canvas HtmlCanvasElement [height, width] true,
/// The `<caption>` HTML element specifies the caption (or title) of a table.

View File

@@ -176,6 +176,7 @@ where
cursor.sibling();
}
position.set(Position::FirstChild);
self.view.hydrate::<FROM_SERVER>(cursor, position)
}
}
@@ -183,12 +184,28 @@ where
/// The children that will be projected into an [`Island`].
pub struct IslandChildren<View> {
view: View,
on_hydrate: Option<Box<dyn Fn() + Send + Sync>>,
}
impl<View> IslandChildren<View> {
/// Creates a new representation of the children.
pub fn new(view: View) -> Self {
IslandChildren { view }
IslandChildren {
view,
on_hydrate: None,
}
}
/// Creates a new representation of the children, with a function to be called whenever
/// a child island hydrates.
pub fn new_with_on_hydrate(
view: View,
on_hydrate: impl Fn() + Send + Sync + 'static,
) -> Self {
IslandChildren {
view,
on_hydrate: Some(Box::new(on_hydrate)),
}
}
fn open_tag(buf: &mut String) {
@@ -229,9 +246,10 @@ where
where
Self::Output<NewAttr>: RenderHtml,
{
let IslandChildren { view } = self;
let IslandChildren { view, on_hydrate } = self;
IslandChildren {
view: view.add_any_attr(attr),
on_hydrate,
}
}
}
@@ -252,9 +270,10 @@ where
}
async fn resolve(self) -> Self::AsyncOutput {
let IslandChildren { view } = self;
let IslandChildren { view, on_hydrate } = self;
IslandChildren {
view: view.resolve().await,
on_hydrate,
}
}
@@ -313,5 +332,28 @@ where
} else if curr_position != Position::Current {
cursor.sibling();
}
if let Some(on_hydrate) = self.on_hydrate {
use crate::{
hydration::failed_to_cast_element, renderer::CastFrom,
};
let el =
crate::renderer::types::Element::cast_from(cursor.current())
.unwrap_or_else(|| {
failed_to_cast_element(
"leptos-children",
cursor.current(),
)
});
let cb = wasm_bindgen::closure::Closure::wrap(
on_hydrate as Box<dyn Fn()>,
);
_ = js_sys::Reflect::set(
&el,
&wasm_bindgen::JsValue::from_str("$$on_hydrate"),
&cb.into_js_value(),
);
}
}
}

View File

@@ -212,6 +212,9 @@ impl<const V: &'static str> RenderHtml for Static<V> {
.unwrap_or_else(|| {
crate::hydration::failed_to_cast_text_node(node)
});
position.set(Position::NextChildAfterText);
Some(node)
}
}