Compare commits

...

30 Commits
4079 ... 4088

Author SHA1 Message Date
Greg Johnston
54764261b1 fix memory leak! 2025-06-25 10:02:41 -04:00
Greg Johnston
acd8fd52c3 fix ownership for root 2025-06-24 22:01:26 -04:00
Greg Johnston
51bd63a91a fix context providers (for <A> etc) 2025-06-23 21:50:21 -04:00
Greg Johnston
1d34ea7133 Merge remote-tracking branch 'origin' into 4088 2025-06-23 20:05:40 -04:00
Gabriel Lopes Veiga
f51c676e0d feat: add method take for BrowserFormData (#4102) 2025-06-22 13:55:48 -04:00
bicarlsen
cf0aa0e4d7 fix: create svg::InertElement templates in SVG namespace. (#4104) 2025-06-22 13:53:37 -04:00
Greg Johnston
a8894460e2 Merge remote-tracking branch 'origin' into 4088 2025-06-20 20:40:11 -04:00
bicarlsen
df09d4a7f6 fix: update svg::InertElement for dom cache. (#4100)
#4099 introduced caching for inert elements, changing the API for [`dom::Dom::create_element_from_html`](30b0a579ca/tachys/src/renderer/dom.rs (L495)). This updates `svg::InertElement` to match the new API.
2025-06-20 20:39:55 -04:00
Greg Johnston
403d6ed84e chore: clippy 2025-06-20 17:42:56 -04:00
TERRORW0LF
30b0a579ca enhance: handle ../ in aria-current for <A/> component (#4051) 2025-06-20 17:09:07 -04:00
dependabot[bot]
50a4c3b0d9 chore(deps): bump autofix-ci/action from 1.3.1 to 1.3.2 (#4072)
Bumps [autofix-ci/action](https://github.com/autofix-ci/action) from 1.3.1 to 1.3.2.
- [Release notes](https://github.com/autofix-ci/action/releases)
- [Commits](https://github.com/autofix-ci/action/compare/v1.3.1...v1.3.2)

---
updated-dependencies:
- dependency-name: autofix-ci/action
  dependency-version: 1.3.2
  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>
2025-06-20 17:02:54 -04:00
bicarlsen
c76649d77b feat: add inert SVG elements. (#4085) 2025-06-20 17:02:38 -04:00
bicarlsen
911be5007e perf: add template cache for InertElement (#4099) 2025-06-20 17:01:19 -04:00
Greg Johnston
b37900ec55 update rebuild 2025-06-19 14:40:19 -04:00
Greg Johnston
fda2a1348c fix overwritten Outlet 2025-06-19 14:23:08 -04:00
Greg Johnston
c2ea59ca96 correct top-level Outlet rendering again 2025-06-18 15:38:09 -04:00
Greg Johnston
7af2a31e21 waypoint 2025-06-18 15:24:03 -04:00
Greg Johnston
0e45694c6e correctly render top-level Outlet once 2025-06-17 19:44:42 -04:00
Greg Johnston
ca5c179cb1 fix: correctly provide context via nested outlets (closes #4088) 2025-06-16 21:12:42 -04:00
Greg Johnston
5227221c96 docs: add document on adding class and other attributes to <A/> component (#4086) 2025-06-16 20:00:45 -04:00
Greg Johnston
3f48b77256 feat: impl IntoFragment for AnyView (#4087) 2025-06-16 20:00:32 -04:00
foldedwave
99117f496f fix: correctly remove :capture listeners (closes #4081) (#4082)
Co-authored-by: foldedwave <anon@foldedwave.com>
2025-06-16 20:00:15 -04:00
Moritz Hedtke
cf12ea3404 fix: conflicting changes between #4035 and #4074 (#4090) 2025-06-16 19:59:30 -04:00
martin frances
d555c1e0ce Removed crate once_cell (#4083)
* Removed crate once_cell

As of rust_version 1.80.0 there are now equivalent options in std.

Async and sync changes are as follows.

-use once_cell::sync::Lazy;
+use std::sync::LazyLock;

-use once_cell::sync::Lazy;
+use std::sync::LazyLock;

* ran cargo fmt.

* fixed server_fn errors.

* cargo fmt fixes.

* "use srd::sync" becomes "use std::sync".

* fixed formatting issue.

* formatting issues.

* Fixed error in examples/server_fns_axum

* more formatting issues.

* more formatting issues.

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-06-16 10:38:32 -07:00
Moritz Hedtke
40ea20057f Remove unnecessary Option wrapping (#4035)
* Remove unnecessary Option wrapping.

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-06-16 10:37:26 -07:00
Greg Johnston
5587ccd1eb fix: don't render a comment node for () attributes in template (closes #4079) (#4080) 2025-06-14 15:47:48 -04:00
Greg Johnston
50a9df9eea Merge pull request #4078 from leptos-rs/4066
Fix updates to static class names
2025-06-13 22:49:51 -04:00
Greg Johnston
c46b1c4e25 feat: allow dereferencing LocalResource to an AsyncDerived (see #4063) (#4077) 2025-06-13 18:03:18 -04:00
Greg Johnston
e6f86408a1 fix: ensure that dynamic classes are replaced if they have changed 2025-06-13 18:01:42 -04:00
Greg Johnston
aa13ed9431 fix: ensure that classes are replaced if they have changed (closes #4066) 2025-06-13 18:01:27 -04:00
39 changed files with 849 additions and 377 deletions

View File

@@ -34,7 +34,7 @@ jobs:
run: cargo fmt --all
- name: Clippy the workspace
run: cargo all-features clippy --allow-dirty --fix --lib --no-deps
- uses: autofix-ci/action@v1.3.1
- uses: autofix-ci/action@v1.3.2
if: ${{ always() }}
with:
fail-fast: false

249
Cargo.lock generated
View File

@@ -88,7 +88,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb"
dependencies = [
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -205,7 +205,7 @@ dependencies = [
"actix-router",
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -233,9 +233,9 @@ dependencies = [
[[package]]
name = "adler2"
version = "2.0.0"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aho-corasick"
@@ -327,7 +327,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -344,7 +344,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -367,7 +367,7 @@ dependencies = [
"manyhow",
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -383,7 +383,7 @@ dependencies = [
"proc-macro2",
"quote",
"quote-use",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -509,9 +509,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.17.0"
version = "3.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee"
[[package]]
name = "bytecheck"
@@ -533,7 +533,7 @@ checksum = "efb7846e0cb180355c2dec69e721edafa36919850f1a9f52ffba4ebc0393cb71"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -559,15 +559,15 @@ dependencies = [
[[package]]
name = "camino"
version = "1.1.9"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3"
checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab"
[[package]]
name = "cc"
version = "1.2.24"
version = "1.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7"
checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
dependencies = [
"jobserver",
"libc",
@@ -586,9 +586,9 @@ dependencies = [
[[package]]
name = "cfg-if"
version = "1.0.0"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "cfg_aliases"
@@ -850,7 +850,7 @@ checksum = "e73f2692d4bd3cac41dca28934a39894200c9fabf49586d77d0e5954af1d7902"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -863,7 +863,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustc_version",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -883,7 +883,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
"unicode-xid",
]
@@ -905,7 +905,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -1006,9 +1006,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "flate2"
version = "1.1.1"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
dependencies = [
"crc32fast",
"miniz_oxide",
@@ -1120,7 +1120,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -1172,7 +1172,7 @@ dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasi 0.11.1+wasi-snapshot-preview1",
"wasm-bindgen",
]
@@ -1240,7 +1240,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -1356,9 +1356,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hashbrown"
version = "0.15.3"
version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
[[package]]
name = "heapless"
@@ -1382,9 +1382,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.3.9"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]]
name = "html-escape"
@@ -1470,7 +1470,6 @@ version = "0.3.0"
dependencies = [
"futures",
"js-sys",
"once_cell",
"or_poisoned",
"pin-project-lite",
"serde",
@@ -1500,9 +1499,9 @@ dependencies = [
[[package]]
name = "hyper-rustls"
version = "0.27.6"
version = "0.27.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03a01595e11bdcec50946522c32dde3fc6914743000a68b93000965f2f02406d"
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
dependencies = [
"http 1.3.1",
"hyper",
@@ -1533,9 +1532,9 @@ dependencies = [
[[package]]
name = "hyper-util"
version = "0.1.13"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8"
checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb"
dependencies = [
"base64",
"bytes",
@@ -1675,7 +1674,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
dependencies = [
"equivalent",
"hashbrown 0.15.3",
"hashbrown 0.15.4",
]
[[package]]
@@ -1812,7 +1811,7 @@ dependencies = [
"http 1.3.1",
"proc-macro-error",
"server_fn_macro 0.6.15",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -1831,7 +1830,6 @@ dependencies = [
"leptos_macro",
"leptos_meta",
"leptos_router",
"once_cell",
"parking_lot",
"send_wrapper",
"serde_json",
@@ -1855,7 +1853,6 @@ dependencies = [
"leptos_macro",
"leptos_meta",
"leptos_router",
"once_cell",
"parking_lot",
"server_fn",
"tachys",
@@ -1908,7 +1905,7 @@ dependencies = [
"quote",
"rstml",
"serde",
"syn 2.0.101",
"syn 2.0.103",
"walkdir",
]
@@ -1948,7 +1945,7 @@ dependencies = [
"serde",
"server_fn",
"server_fn_macro 0.8.2",
"syn 2.0.101",
"syn 2.0.103",
"tracing",
"trybuild",
"typed-builder",
@@ -1962,7 +1959,6 @@ dependencies = [
"futures",
"indexmap",
"leptos",
"once_cell",
"or_poisoned",
"send_wrapper",
"tracing",
@@ -1981,7 +1977,6 @@ dependencies = [
"js-sys",
"leptos",
"leptos_router_macro",
"once_cell",
"or_poisoned",
"percent-encoding",
"reactive_graph",
@@ -2004,7 +1999,7 @@ dependencies = [
"proc-macro-error2",
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -2030,9 +2025,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.172"
version = "0.2.173"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb"
[[package]]
name = "linear-map"
@@ -2100,7 +2095,7 @@ dependencies = [
"manyhow-macros",
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -2122,9 +2117,9 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]]
name = "memchr"
version = "2.7.4"
version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "mime"
@@ -2150,7 +2145,7 @@ checksum = "d6c74ab4f1a0c0ab045260ee4727b23c00cc17e5eff5095262d08eef8c3c8d49"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -2176,9 +2171,9 @@ dependencies = [
[[package]]
name = "miniz_oxide"
version = "0.8.8"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
]
@@ -2191,7 +2186,7 @@ checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
dependencies = [
"libc",
"log",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasi 0.11.1+wasi-snapshot-preview1",
"windows-sys 0.59.0",
]
@@ -2229,7 +2224,7 @@ checksum = "0ac7d860b767c6398e88fe93db73ce53eb496057aa6895ffa4d60cb02e1d1c6b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -2270,9 +2265,9 @@ dependencies = [
[[package]]
name = "num_cpus"
version = "1.16.0"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
dependencies = [
"hermit-abi",
"libc",
@@ -2304,9 +2299,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "openssl"
version = "0.10.72"
version = "0.10.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da"
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
dependencies = [
"bitflags",
"cfg-if",
@@ -2325,7 +2320,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -2336,9 +2331,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-sys"
version = "0.9.108"
version = "0.9.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847"
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
dependencies = [
"cc",
"libc",
@@ -2414,7 +2409,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -2474,12 +2469,12 @@ dependencies = [
[[package]]
name = "prettyplease"
version = "0.2.33"
version = "0.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dee91521343f4c5c6a63edd65e54f31f5c92fe8978c40a4282f8372194c6a7d"
checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55"
dependencies = [
"proc-macro2",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -2534,7 +2529,7 @@ dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -2565,7 +2560,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
"version_check",
"yansi",
]
@@ -2587,7 +2582,7 @@ checksum = "ca414edb151b4c8d125c12566ab0d74dc9cdba36fb80eb7b848c15f495fd32d1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -2673,7 +2668,7 @@ dependencies = [
"proc-macro-utils",
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -2770,14 +2765,14 @@ dependencies = [
"proc-macro-error2",
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
name = "redox_syscall"
version = "0.5.12"
version = "0.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6"
dependencies = [
"bitflags",
]
@@ -2828,9 +2823,9 @@ dependencies = [
[[package]]
name = "reqwest"
version = "0.12.18"
version = "0.12.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e98ff6b0dbbe4d5a37318f433d4fc82babd21631f194d370409ceb2e40b2f0b5"
checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813"
dependencies = [
"base64",
"bytes",
@@ -2843,13 +2838,10 @@ dependencies = [
"hyper-rustls",
"hyper-tls",
"hyper-util",
"ipnet",
"js-sys",
"log",
"mime",
"mime_guess",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
"quinn",
@@ -2896,7 +2888,7 @@ checksum = "1e147371c75553e1e2fcdb483944a8540b8438c31426279553b9a8182a9b7b65"
dependencies = [
"bytecheck",
"bytes",
"hashbrown 0.15.3",
"hashbrown 0.15.4",
"indexmap",
"munge",
"ptr_meta",
@@ -2915,7 +2907,7 @@ checksum = "246b40ac189af6c675d124b802e8ef6d5246c53e17367ce9501f8f66a81abb7a"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -2950,16 +2942,16 @@ dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn 2.0.101",
"syn 2.0.103",
"syn_derive",
"thiserror 2.0.12",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
[[package]]
name = "rustc-hash"
@@ -3146,7 +3138,7 @@ checksum = "7ce26a84e3d8d10853301cf6a75c58132b8f5d5e8fee65949ea8dd7758d6760b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -3168,7 +3160,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -3206,9 +3198,9 @@ dependencies = [
[[package]]
name = "serde_spanned"
version = "0.6.8"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
dependencies = [
"serde",
]
@@ -3247,7 +3239,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -3271,7 +3263,6 @@ dependencies = [
"inventory",
"js-sys",
"multer",
"once_cell",
"pin-project-lite",
"postcard",
"reqwest",
@@ -3310,7 +3301,7 @@ dependencies = [
"convert_case 0.6.0",
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
"xxhash-rust",
]
@@ -3323,7 +3314,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustc_version",
"syn 2.0.101",
"syn 2.0.103",
"xxhash-rust",
]
@@ -3332,7 +3323,7 @@ name = "server_fn_macro_default"
version = "0.8.2"
dependencies = [
"server_fn_macro 0.8.2",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -3399,7 +3390,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f62f06db0370222f7f498ef478fce9f8df5828848d1d3517e3331936d7074f55"
dependencies = [
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -3422,9 +3413,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.15.0"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
@@ -3469,9 +3460,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.101"
version = "2.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8"
dependencies = [
"proc-macro2",
"quote",
@@ -3487,7 +3478,7 @@ dependencies = [
"proc-macro-error2",
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -3507,7 +3498,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -3541,7 +3532,6 @@ dependencies = [
"linear-map",
"next_tuple",
"oco_ref",
"once_cell",
"or_poisoned",
"parking_lot",
"paste",
@@ -3633,7 +3623,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -3644,7 +3634,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -3736,7 +3726,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -3811,9 +3801,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.22"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
dependencies = [
"serde",
"serde_spanned",
@@ -3823,18 +3813,18 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.6.9"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.26"
version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap",
"serde",
@@ -3846,9 +3836,9 @@ dependencies = [
[[package]]
name = "toml_write"
version = "0.1.1"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]]
name = "tower"
@@ -3868,12 +3858,13 @@ dependencies = [
[[package]]
name = "tower-http"
version = "0.6.4"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fdb0c213ca27a9f57ab69ddb290fd80d970922355b83ae380b395d3986b8a2e"
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
dependencies = [
"bitflags",
"bytes",
"futures-core",
"futures-util",
"http 1.3.1",
"http-body",
@@ -3919,20 +3910,20 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.28"
version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
name = "tracing-core"
version = "0.1.33"
version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
dependencies = [
"once_cell",
]
@@ -3992,7 +3983,7 @@ checksum = "60d8d828da2a3d759d3519cdf29a5bac49c77d039ad36d0782edadbf9cd5415b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -4116,9 +4107,9 @@ dependencies = [
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasi"
@@ -4151,7 +4142,7 @@ dependencies = [
"log",
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
"wasm-bindgen-shared",
]
@@ -4186,7 +4177,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -4221,7 +4212,7 @@ checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -4359,9 +4350,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.7.10"
version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec"
checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd"
dependencies = [
"memchr",
]
@@ -4413,7 +4404,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
"synstructure",
]
@@ -4434,7 +4425,7 @@ checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]
@@ -4454,7 +4445,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
"synstructure",
]
@@ -4494,7 +4485,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.103",
]
[[package]]

View File

@@ -42,7 +42,7 @@ exclude = ["benchmarks", "examples", "projects"]
[workspace.package]
version = "0.8.2"
edition = "2021"
rust-version = "1.76"
rust-version = "1.80"
[workspace.dependencies]
# members
@@ -85,7 +85,6 @@ 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" }
once_cell = { default-features = false, version = "1.21.3" }
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" }

View File

@@ -23,7 +23,6 @@ leptos = { path = "../../leptos" }
leptos_actix = { path = "../../integrations/actix", optional = true }
leptos_router = { path = "../../router" }
log = "0.4.22"
once_cell = "1.19"
gloo-net = { version = "0.6.0" }
wasm-bindgen = "0.2.93"
serde = { version = "1.0", features = ["derive"] }

View File

@@ -38,7 +38,6 @@ strum = { version = "0.27.1", features = ["strum_macros", "derive"] }
notify = { version = "8.0", optional = true }
pin-project-lite = "0.2.14"
dashmap = { version = "6.0", optional = true }
once_cell = { version = "1.19", optional = true }
async-broadcast = { version = "0.7.1", optional = true }
bytecheck = "0.8.0"
rkyv = { version = "0.8.8" }
@@ -54,7 +53,6 @@ ssr = [
"dep:leptos_axum",
"dep:notify",
"dep:dashmap",
"dep:once_cell",
"dep:async-broadcast",
]

View File

@@ -424,7 +424,7 @@ pub fn FileUploadWithProgress() -> impl IntoView {
use async_broadcast::{broadcast, Receiver, Sender};
use dashmap::DashMap;
use futures::Stream;
use once_cell::sync::Lazy;
use std::sync::LazyLock;
struct File {
total: usize,
@@ -432,7 +432,8 @@ pub fn FileUploadWithProgress() -> impl IntoView {
rx: Receiver<usize>,
}
static FILES: Lazy<DashMap<String, File>> = Lazy::new(DashMap::new);
static FILES: LazyLock<DashMap<String, File>> =
LazyLock::new(DashMap::new);
pub async fn add_chunk(filename: &str, len: usize) {
println!("[{filename}]\tadding {len}");

View File

@@ -16,7 +16,6 @@ futures = { workspace = true, default-features = true }
serde = { features = ["derive"] , workspace = true, default-features = true }
wasm-bindgen = { workspace = true, optional = true , default-features = true }
js-sys = { optional = true , workspace = true, default-features = true }
once_cell = { workspace = true, default-features = true }
pin-project-lite = { workspace = true, default-features = true }
[features]

View File

@@ -7,10 +7,12 @@ use super::{SerializedDataId, SharedContext};
use crate::{PinnedFuture, PinnedStream};
use core::fmt::Debug;
use js_sys::Array;
use once_cell::sync::Lazy;
use std::{
fmt::Display,
sync::atomic::{AtomicBool, AtomicUsize, Ordering},
sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
LazyLock,
},
};
use throw_error::{Error, ErrorId};
use wasm_bindgen::{prelude::wasm_bindgen, JsCast};
@@ -79,8 +81,8 @@ pub struct HydrateSharedContext {
id: AtomicUsize,
is_hydrating: AtomicBool,
during_hydration: AtomicBool,
errors: Lazy<Vec<(SerializedDataId, ErrorId, Error)>>,
incomplete: Lazy<Vec<SerializedDataId>>,
errors: LazyLock<Vec<(SerializedDataId, ErrorId, Error)>>,
incomplete: LazyLock<Vec<SerializedDataId>>,
}
impl HydrateSharedContext {
@@ -90,8 +92,8 @@ impl HydrateSharedContext {
id: AtomicUsize::new(0),
is_hydrating: AtomicBool::new(true),
during_hydration: AtomicBool::new(true),
errors: Lazy::new(serialized_errors),
incomplete: Lazy::new(incomplete_chunks),
errors: LazyLock::new(serialized_errors),
incomplete: LazyLock::new(incomplete_chunks),
}
}
@@ -104,8 +106,8 @@ impl HydrateSharedContext {
id: AtomicUsize::new(0),
is_hydrating: AtomicBool::new(false),
during_hydration: AtomicBool::new(true),
errors: Lazy::new(serialized_errors),
incomplete: Lazy::new(incomplete_chunks),
errors: LazyLock::new(serialized_errors),
incomplete: LazyLock::new(incomplete_chunks),
}
}
}

View File

@@ -28,7 +28,6 @@ tracing = { optional = true , workspace = true, default-features = true }
tokio = { features = ["rt", "fs"] , workspace = true, default-features = true }
send_wrapper = { workspace = true, default-features = true }
dashmap = { workspace = true, default-features = true }
once_cell = { workspace = true, default-features = true }
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

View File

@@ -38,7 +38,6 @@ use leptos_router::{
static_routes::{RegenerationFn, ResolvedStaticPath},
ExpandOptionals, Method, PathSegment, RouteList, RouteListing, SsrMode,
};
use once_cell::sync::Lazy;
use parking_lot::RwLock;
use send_wrapper::SendWrapper;
use server_fn::{
@@ -51,7 +50,7 @@ use std::{
future::Future,
ops::{Deref, DerefMut},
path::Path,
sync::Arc,
sync::{Arc, LazyLock},
};
/// This struct lets you define headers and override the status of the Response from an Element or a Server Function
@@ -1210,8 +1209,8 @@ impl StaticRouteGenerator {
}
}
static STATIC_HEADERS: Lazy<DashMap<String, ResponseOptions>> =
Lazy::new(DashMap::new);
static STATIC_HEADERS: LazyLock<DashMap<String, ResponseOptions>> =
LazyLock::new(DashMap::new);
fn was_404(owner: &Owner) -> bool {
let resp = owner.with(|| expect_context::<ResponseOptions>());

View File

@@ -23,7 +23,6 @@ leptos_meta = { workspace = true, features = ["ssr", "nonce"] }
leptos_router = { workspace = true, features = ["ssr"] }
leptos_integration_utils = { workspace = true }
tachys = { workspace = true }
once_cell = { workspace = true, default-features = true }
parking_lot = { workspace = true, default-features = true }
tokio = { default-features = false , workspace = true }
tower = { features = ["util"] , workspace = true, default-features = true }

View File

@@ -69,12 +69,12 @@ use leptos_router::{
static_routes::RegenerationFn, ExpandOptionals, PathSegment, RouteList,
RouteListing, SsrMode,
};
#[cfg(feature = "default")]
use once_cell::sync::Lazy;
use parking_lot::RwLock;
use server_fn::{error::ServerFnErrorErr, redirect::REDIRECT_HEADER};
#[cfg(feature = "default")]
use std::path::Path;
#[cfg(feature = "default")]
use std::sync::LazyLock;
use std::{collections::HashSet, fmt::Debug, io, pin::Pin, sync::Arc};
#[cfg(feature = "default")]
use tower::util::ServiceExt;
@@ -1522,8 +1522,8 @@ impl StaticRouteGenerator {
}
#[cfg(feature = "default")]
static STATIC_HEADERS: Lazy<DashMap<String, ResponseOptions>> =
Lazy::new(DashMap::new);
static STATIC_HEADERS: LazyLock<DashMap<String, ResponseOptions>> =
LazyLock::new(DashMap::new);
#[cfg(feature = "default")]
fn was_404(owner: &Owner) -> bool {

View File

@@ -112,9 +112,9 @@ fn is_inert_element(orig_node: &Node<impl CustomNode>) -> bool {
return false;
}
// also doesn't work if the top-level element is an SVG/MathML element
// also doesn't work if the top-level element is a MathML element
let el_name = el.name().to_string();
if is_svg_element(&el_name) || is_math_ml_element(&el_name) {
if is_math_ml_element(&el_name) {
return false;
}
}
@@ -300,7 +300,7 @@ fn inert_element_to_tokens(
node: &Node<impl CustomNode>,
escape_text: bool,
global_class: Option<&TokenTree>,
) -> Option<TokenStream> {
) -> TokenStream {
let mut html = InertElementBuilder::new(global_class);
let mut nodes = VecDeque::from([Item::Node(node, escape_text)]);
@@ -396,9 +396,114 @@ fn inert_element_to_tokens(
html.finish();
Some(quote! {
quote! {
::leptos::tachys::html::InertElement::new(#html)
})
}
}
fn inert_svg_element_to_tokens(
node: &Node<impl CustomNode>,
escape_text: bool,
global_class: Option<&TokenTree>,
) -> TokenStream {
let mut html = InertElementBuilder::new(global_class);
let mut nodes = VecDeque::from([Item::Node(node, escape_text)]);
while let Some(current) = nodes.pop_front() {
match current {
Item::ClosingTag(tag) => {
// closing tag
html.push_str("</");
html.push_str(&tag);
html.push('>');
}
Item::Node(current, escape) => {
match current {
Node::RawText(raw) => {
let text = raw.to_string_best();
let text = if escape {
html_escape::encode_text(&text)
} else {
text.into()
};
html.push_str(&text);
}
Node::Text(text) => {
let text = text.value_string();
let text = if escape {
html_escape::encode_text(&text)
} else {
text.into()
};
html.push_str(&text);
}
Node::Element(node) => {
let self_closing = is_self_closing(node);
let el_name = node.name().to_string();
let escape = el_name != "script"
&& el_name != "style"
&& el_name != "textarea";
// opening tag
html.push('<');
html.push_str(&el_name);
for attr in node.attributes() {
if let NodeAttribute::Attribute(attr) = attr {
let attr_name = attr.key.to_string();
// trim r# from raw identifiers like r#as
let attr_name =
attr_name.trim_start_matches("r#");
if attr_name != "class" {
html.push(' ');
html.push_str(attr_name);
}
if let Some(value) =
attr.possible_value.to_value()
{
if let KVAttributeValue::Expr(Expr::Lit(
lit,
)) = &value.value
{
if let Lit::Str(txt) = &lit.lit {
let value = txt.value();
let value = html_escape::encode_double_quoted_attribute(&value);
if attr_name == "class" {
html.push_class(&value);
} else {
html.push_str("=\"");
html.push_str(&value);
html.push('"');
}
}
}
};
}
}
html.push('>');
// render all children
if !self_closing {
nodes.push_front(Item::ClosingTag(el_name));
let children = node.children.iter().rev();
for child in children {
nodes.push_front(Item::Node(child, escape));
}
}
}
_ => {}
}
}
}
}
html.finish();
quote! {
::leptos::tachys::svg::InertElement::new(#html)
}
}
fn element_children_to_tokens(
@@ -597,7 +702,17 @@ fn node_to_tokens(
let escape = el_name != "script"
&& el_name != "style"
&& el_name != "textarea";
inert_element_to_tokens(node, escape, global_class)
let el_name = el_node.name().to_string();
if is_svg_element(&el_name) {
Some(inert_svg_element_to_tokens(
node,
escape,
global_class,
))
} else {
Some(inert_element_to_tokens(node, escape, global_class))
}
} else {
element_to_tokens(
el_node,

View File

@@ -20,7 +20,7 @@ use reactive_graph::{
};
use std::{
future::{pending, Future, IntoFuture},
ops::DerefMut,
ops::{Deref, DerefMut},
panic::Location,
};
@@ -43,6 +43,14 @@ impl<T> Clone for ArcLocalResource<T> {
}
}
impl<T> Deref for ArcLocalResource<T> {
type Target = ArcAsyncDerived<T>;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<T> ArcLocalResource<T> {
/// Creates the resource.
///
@@ -269,6 +277,14 @@ pub struct LocalResource<T> {
defined_at: &'static Location<'static>,
}
impl<T> Deref for LocalResource<T> {
type Target = AsyncDerived<T>;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<T> Clone for LocalResource<T> {
fn clone(&self) -> Self {
*self

View File

@@ -10,7 +10,6 @@ edition.workspace = true
[dependencies]
leptos = { workspace = true }
once_cell = { workspace = true, default-features = true }
or_poisoned = { workspace = true }
indexmap = { workspace = true, default-features = true }
send_wrapper = { workspace = true, default-features = true }

View File

@@ -63,13 +63,12 @@ use leptos::{
},
IntoView,
};
use once_cell::sync::Lazy;
use send_wrapper::SendWrapper;
use std::{
fmt::Debug,
sync::{
mpsc::{channel, Receiver, Sender},
Arc,
Arc, LazyLock,
},
};
use wasm_bindgen::JsCast;
@@ -101,7 +100,7 @@ pub struct MetaContext {
/// Metadata associated with the `<title>` element.
pub(crate) title: TitleContext,
/// The hydration cursor for the location in the `<head>` for arbitrary tags will be rendered.
pub(crate) cursor: Arc<Lazy<SendWrapper<Cursor>>>,
pub(crate) cursor: Arc<LazyLock<SendWrapper<Cursor>>>,
}
impl MetaContext {
@@ -143,7 +142,7 @@ impl Default for MetaContext {
))
};
let cursor = Arc::new(Lazy::new(build_cursor));
let cursor = Arc::new(LazyLock::new(build_cursor));
Self {
title: Default::default(),
cursor,

View File

@@ -21,7 +21,6 @@ fake = "2.9"
tokio-tungstenite = "0.23.1"
futures-util = "0.3.30"
uuid = { version = "1.10", features = ["serde"] }
once_cell = "1.19"
futures = "0.3.30"
[[test]]
@@ -33,6 +32,5 @@ harness = false # Allow Cucumber to print output instead of libtest
ssr = []
[dependencies]
once_cell = "1.19.0"
regex = "1.10.6"
serde.workspace = true

View File

@@ -18,14 +18,14 @@ use chromiumoxide::{
use cucumber::World;
use futures::channel::mpsc::Sender;
use futures_util::stream::StreamExt;
use once_cell::sync::Lazy;
use std::sync::LazyLock;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, sync::Arc, time::Duration};
use tokio::sync::RwLock;
use tokio_tungstenite::connect_async;
use uuid::Uuid;
static EMAIL_ID_MAP: Lazy<RwLock<HashMap<String, String>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
static EMAIL_ID_MAP: LazyLock<RwLock<HashMap<String, String>>> =
LazyLock::new(|| RwLock::new(HashMap::new()));
#[derive(Clone, Debug, PartialEq)]
pub struct RequestPair {
@@ -93,7 +93,7 @@ impl RequestPair {
async fn main() -> Result<()> {
// create a thread and store a
// tokio-tungstenite client that connectsto http://127.0.0.1:1080/ws
// and then stores the recieved messages in a once_cell::Lazy<RwLock<Vec<MailCrabMsg>>>
// and then stores the recieved messages in a std::sync::LazyLock<RwLock<Vec<MailCrabMsg>>>
// or a custom struct that matches the body or has specific impls for verify codes, links etc.
let _ = tokio::spawn(async move {
let (mut socket, _) = connect_async(
@@ -152,7 +152,7 @@ async fn main() -> Result<()> {
tokio::task::spawn(async move {
while let Some(event) = log_events.next().await {
if let Some(EventEntryAdded { entry }) =
if let Some(EventEntryAdded { entry }) =
Arc::<EventEntryAdded>::into_inner(event) {
console_logs.write().await.push(format!(" {entry:#?} "));
} else {
@@ -171,7 +171,7 @@ async fn main() -> Result<()> {
} else {
tracing::error!("tried to into inner but none")
}
}
});
@@ -208,7 +208,7 @@ async fn main() -> Result<()> {
thing.cookies_before_request = cookies;
}
}
CookieEnum::AfterResp(req_id) => {
let cookies = page
@@ -293,8 +293,8 @@ async fn main() -> Result<()> {
} else {
tracing::error!(" uhh err here")
}
}
});
// We don't need to join on our join handles, they will run detached and clean up whenever.

View File

@@ -22,7 +22,6 @@ url = { workspace = true, default-features = true }
js-sys = { workspace = true, default-features = true }
wasm-bindgen = { workspace = true , default-features = true }
tracing = { optional = true , workspace = true, default-features = true }
once_cell = { workspace = true, default-features = true }
send_wrapper = { workspace = true, default-features = true }
thiserror = { workspace = true , default-features = true }
percent-encoding = { optional = true , workspace = true, default-features = true }

View File

@@ -144,16 +144,12 @@ impl RouterContext {
resolve_path("", path, None)
};
let mut url = match resolved_to.map(|to| BrowserUrl::parse(&to)) {
Some(Ok(url)) => url,
Some(Err(e)) => {
let mut url = match BrowserUrl::parse(&resolved_to) {
Ok(url) => url,
Err(e) => {
leptos::logging::error!("Error parsing URL: {e:?}");
return;
}
None => {
leptos::logging::error!("Error resolving relative URL.");
return;
}
};
let query_mutations =
mem::take(&mut *self.query_mutations.write_value());
@@ -203,7 +199,7 @@ impl RouterContext {
&'a self,
path: &'a str,
from: Option<&'a str>,
) -> Option<Cow<'a, str>> {
) -> Cow<'a, str> {
let base = self.base.as_deref().unwrap_or_default();
resolve_path(base, path, from)
}
@@ -580,18 +576,11 @@ pub fn Redirect<P>(
// redirect on the server
if let Some(redirect_fn) = use_context::<ServerRedirectFunction>() {
match resolve_path("", &path, Some(&use_matched().get_untracked())) {
Some(path) => (redirect_fn.f)(&path),
None => {
if cfg!(feature = "ssr") {
#[cfg(feature = "tracing")]
tracing::warn!("Error resolving relative URL.");
#[cfg(not(feature = "tracing"))]
eprintln!("Error resolving relative URL.");
}
}
}
(redirect_fn.f)(&resolve_path(
"",
&path,
Some(&use_matched().get_untracked()),
));
}
// redirect on the client
else {

View File

@@ -87,7 +87,7 @@ where
fn inner(
has_router: bool,
method: Option<&'static str>,
action: ArcMemo<Option<String>>,
action: ArcMemo<String>,
enctype: Option<String>,
version: Option<RwSignal<usize>>,
error: Option<RwSignal<Option<Box<dyn Error + Send + Sync>>>>,
@@ -311,7 +311,7 @@ where
let action = if has_router {
use_resolved_path(move || action.to_href()())
} else {
ArcMemo::new(move |_| Some(action.to_href()()))
ArcMemo::new(move |_| action.to_href()())
};
inner(
has_router,

View File

@@ -240,7 +240,7 @@ pub(crate) struct Matched(pub ArcMemo<String>);
#[track_caller]
pub(crate) fn use_resolved_path(
path: impl Fn() -> String + Send + Sync + 'static,
) -> ArcMemo<Option<String>> {
) -> ArcMemo<String> {
let router = use_context::<RouterContext>()
.expect("called use_resolved_path outside a <Router>");
// TODO make this work with flat routes too?
@@ -248,14 +248,14 @@ pub(crate) fn use_resolved_path(
ArcMemo::new(move |_| {
let path = path();
if path.starts_with('/') {
Some(path)
path
} else {
router
.resolve_path(
&path,
matched.as_ref().map(|n| n.get()).as_deref(),
)
.map(|n| n.to_string())
.to_string()
}
})
}

View File

@@ -66,6 +66,25 @@ where
/// This is helpful for accessibility and for styling. For example, maybe you want to set the link a
/// different color if its a link to the page youre currently on.
///
/// ### Additional Attributes
///
/// You can add additional HTML attributes to the `<a>` element created by this component using the attribute
/// spreading syntax for components. For example, to add a class, you can use `attr:class="my-link"`.
/// Alternately, you can add any number of HTML attributes (include `class`) after a `{..}` marker.
///
/// ```rust
/// # use leptos::prelude::*; use leptos_router::components::A;
/// # fn spread_example() -> impl IntoView {
/// view! {
/// <A href="/about" attr:class="my-link" {..} id="foo">"Some link"</A>
/// <A href="/about" {..} class="my-link" id="bar">"Another link"</A>
/// <A href="/about" {..} class:my-link=true id="baz">"One more"</A>
/// }
/// # }
/// ```
///
/// For more information on this attribute spreading syntax, [see here](https://book.leptos.dev/view/03_components.html#spreading-attributes-onto-components).
///
/// ### DOM Properties
///
/// `<a>` elements can take several additional DOM properties with special meanings.
@@ -102,7 +121,7 @@ where
H: ToHref + Send + Sync + 'static,
{
fn inner(
href: ArcMemo<Option<String>>,
href: ArcMemo<String>,
target: Option<Oco<'static, str>>,
exact: bool,
children: Children,
@@ -114,23 +133,21 @@ where
let is_active = {
let href = href.clone();
move || {
href.read().as_deref().is_some_and(|to| {
let path = to.split(['?', '#']).next().unwrap_or_default();
current_url.with(|loc| {
let loc = loc.path();
if exact {
loc == path
} else {
is_active_for(path, loc, strict_trailing_slash)
}
})
let path = normalize_path(&href.read());
current_url.with(|loc| {
let loc = loc.path();
if exact {
loc == path
} else {
is_active_for(&path, loc, strict_trailing_slash)
}
})
}
};
view! {
<a
href=move || href.get().unwrap_or_default()
href=move || href.get()
target=target
aria-current=move || if is_active() { Some("page") } else { None }
data-noscroll=!scroll
@@ -172,9 +189,62 @@ fn is_active_for(
}
}
// Resolve `".."` segments in the path. Assume path is either empty or starts with a `'/'``.
fn normalize_path(path: &str) -> String {
// Return only on the only condition where leading slash
// is allowed to be missing.
if path.is_empty() {
return String::new();
}
let mut del = 0;
let mut it = path
.split(['?', '#'])
.next()
.unwrap_or_default()
.split(['/'])
.rev()
.peekable();
let init = if it.peek() == Some(&"..") {
String::from("/")
} else {
String::new()
};
let mut path = it
.filter(|v| {
if *v == ".." {
del += 1;
false
} else if *v == "." {
false
} else if del > 0 {
del -= 1;
false
} else {
true
}
})
// We cannot reverse before the fold again bc the filter
// would be forwards again.
.fold(init, |mut p, v| {
p.reserve(v.len() + 1);
p.insert(0, '/');
p.insert_str(0, v);
p
});
path.truncate(path.len().saturating_sub(1));
// Path starts with '/' giving it an extra empty segment after the split
// Which should not be removed.
if !path.starts_with('/') {
path.insert(0, '/');
}
path
}
#[cfg(test)]
mod tests {
use super::is_active_for;
use super::{is_active_for, normalize_path};
#[test]
fn is_active_for_matched() {
@@ -393,4 +463,37 @@ mod tests {
// assert!(!is_same_level("/some/", "/some/level/"))
// assert!(!is_same_level("/some/", "/some/level/deeper"))
}
#[test]
fn normalize_path_test() {
// Make sure it doesn't touch already normalized urls.
assert!(normalize_path("") == "".to_string());
assert!(normalize_path("/") == "/".to_string());
assert!(normalize_path("/some") == "/some".to_string());
assert!(normalize_path("/some/") == "/some/".to_string());
// Correctly removes ".." segments.
assert!(normalize_path("/some/../another") == "/another".to_string());
assert!(
normalize_path("/one/two/../three/../../four")
== "/four".to_string()
);
// Correctly sets trailing slash if last segement is "..".
assert!(normalize_path("/one/two/..") == "/one/".to_string());
assert!(normalize_path("/one/two/../") == "/one/".to_string());
// Level outside of the url.
assert!(normalize_path("/..") == "/".to_string());
assert!(normalize_path("/../") == "/".to_string());
// Going into negative levels and coming back into the positives.
assert!(
normalize_path("/one/../../two/three") == "/two/three".to_string()
);
assert!(
normalize_path("/one/../../two/three/")
== "/two/three/".to_string()
);
}
}

View File

@@ -4,9 +4,9 @@ pub fn resolve_path<'a>(
base: &'a str,
path: &'a str,
from: Option<&'a str>,
) -> Option<Cow<'a, str>> {
) -> Cow<'a, str> {
if has_scheme(path) {
Some(path.into())
path.into()
} else {
let base_path = normalize(base, false);
let from_path = from.map(|from| normalize(from, false));
@@ -25,7 +25,7 @@ pub fn resolve_path<'a>(
let result_empty = result.is_empty();
let prefix = if result_empty { "/".into() } else { result };
Some(prefix + normalize(path, result_empty))
prefix + normalize(path, result_empty)
}
}

View File

@@ -65,6 +65,9 @@ where
// TODO loading fallback
#[allow(clippy::type_complexity)]
view: Rc<RefCell<EitherOf3State<(), Fal, AnyView>>>,
// held to keep the Owner alive until the router is dropped
#[allow(unused)]
outer_owner: Owner,
}
impl<Loc, Defs, FalFn, Fal> Render for NestedRoutesView<Loc, Defs, FalFn>
@@ -109,7 +112,8 @@ where
&outer_owner,
);
drop(url);
outer_owner.with(|| EitherOf3::C(Outlet().into_any()))
EitherOf3::C(top_level_outlet(&outlets, &outer_owner))
}
};
@@ -130,6 +134,7 @@ where
current_url,
outlets,
view,
outer_owner,
}
}
@@ -212,17 +217,14 @@ where
// if it was on the fallback, show the view instead
if matches!(state.view.borrow().state, EitherOf3::B(_)) {
self.outer_owner.with(|| {
EitherOf3::<(), Fal, AnyView>::C(Outlet().into_any())
.rebuild(&mut *state.view.borrow_mut());
})
EitherOf3::<(), Fal, AnyView>::C(top_level_outlet(
&state.outlets,
&self.outer_owner,
))
.rebuild(&mut *state.view.borrow_mut());
}
}
}
if let Some(outlet) = state.outlets.first() {
self.outer_owner.with(|| outlet.provide_contexts());
}
}
}
@@ -348,7 +350,7 @@ where
.now_or_never()
.expect("async routes not supported in SSR");
outer_owner.with(|| Either::Right(Outlet().into_any()))
Either::Right(top_level_outlet(&outlets, &outer_owner))
}
};
view.to_html_with_buf(
@@ -402,7 +404,7 @@ where
.now_or_never()
.expect("async routes not supported in SSR");
outer_owner.with(|| Either::Right(Outlet().into_any()))
Either::Right(top_level_outlet(&outlets, &outer_owner))
}
};
view.to_html_async_with_buf::<OUT_OF_ORDER>(
@@ -454,7 +456,7 @@ where
join_all(mem::take(&mut loaders))
.now_or_never()
.expect("async routes not supported in SSR");
outer_owner.with(|| EitherOf3::C(Outlet().into_any()))
EitherOf3::C(top_level_outlet(&outlets, &outer_owner))
}
}
.hydrate::<FROM_SERVER>(cursor, position),
@@ -465,6 +467,7 @@ where
current_url,
outlets,
view,
outer_owner,
}
}
@@ -484,8 +487,12 @@ pub(crate) struct RouteContext {
pub matched: ArcRwSignal<String>,
base: Option<Oco<'static, str>>,
view_fn: Arc<Mutex<OutletViewFn>>,
child: ChildRoute,
}
#[derive(Clone)]
pub(crate) struct ChildRoute(Arc<Mutex<Option<RouteContext>>>);
impl Debug for RouteContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RouteContext")
@@ -500,12 +507,6 @@ impl Debug for RouteContext {
}
}
impl RouteContext {
fn provide_contexts(&self) {
provide_context(self.clone());
}
}
impl Clone for RouteContext {
fn clone(&self) -> Self {
Self {
@@ -517,6 +518,7 @@ impl Clone for RouteContext {
matched: self.matched.clone(),
base: self.base.clone(),
view_fn: Arc::clone(&self.view_fn),
child: self.child.clone(),
}
}
}
@@ -628,59 +630,67 @@ where
Suspend::new(Box::pin(async { ().into_any() }))
}))),
base: base.clone(),
child: ChildRoute(Arc::new(Mutex::new(None))),
};
if !outlets.is_empty() {
let prev_index = outlets.len().saturating_sub(1);
*outlets[prev_index].child.0.lock().or_poisoned() =
Some(outlet.clone());
}
outlets.push(outlet.clone());
// send the initial view through the channel, and recurse through the children
let (view, child) = self.into_view_and_child();
loaders.push(Box::pin(owner.with(|| {
ScopedFuture::new({
let owner = outlet.owner.clone();
let url = outlet.url.clone();
let matched = Matched(matched_including_parents);
let view_fn = Arc::clone(&outlet.view_fn);
async move {
provide_context(params_including_parents);
provide_context(url);
provide_context(matched.clone());
view.preload().await;
*view_fn.lock().or_poisoned() =
Box::new(move |owner_where_used| {
owner.join_contexts(&owner_where_used);
let view = view.clone();
owner.with({
let matched = matched.clone();
move || {
Suspend::new(Box::pin(async move {
let view = SendWrapper::new(
ScopedFuture::new(view.choose()),
);
let view = view.await;
let view = MatchedRoute(
matched.0.get_untracked(),
view,
);
OwnedView::new(view).into_any()
})
as Pin<
Box<
dyn Future<Output = AnyView>
+ Send,
>,
>)
}
})
});
trigger
}
})
})));
loaders.push(Box::pin(ScopedFuture::new({
let url = outlet.url.clone();
let matched = Matched(matched_including_parents);
let view_fn = Arc::clone(&outlet.view_fn);
let outlet = outlet.clone();
let params = params_including_parents.clone();
let url = url.clone();
let matched = matched.clone();
async move {
view.preload().await;
let child = outlet.child.clone();
*view_fn.lock().or_poisoned() =
Box::new(move |owner_where_used| {
let view = view.clone();
let child = child.clone();
let params = params.clone();
let url = url.clone();
let matched = matched.clone();
owner_where_used.with({
let matched = matched.clone();
move || {
let child = child.clone();
Suspend::new(Box::pin(async move {
provide_context(child.clone());
provide_context(params.clone());
provide_context(url.clone());
provide_context(matched.clone());
let view = SendWrapper::new(
ScopedFuture::new(view.choose()),
);
let view = view.await;
let view = MatchedRoute(
matched.0.get_untracked(),
view,
);
// and share the outlet with the parent via context
// we share it with the *parent* because the <Outlet/> is rendered in or below the parent
// wherever it appears, <Outlet/> will look for the closest RouteContext
parent.with(|| outlet.provide_contexts());
OwnedView::new(view).into_any()
})
as Pin<
Box<
dyn Future<Output = AnyView> + Send,
>,
>)
}
})
});
trigger
}
})));
// recursively continue building the tree
// this is important because to build the view, we need access to the outlet
@@ -785,64 +795,71 @@ where
let (full_tx, full_rx) = oneshot::channel();
let full_tx = Mutex::new(Some(full_tx));
full_loaders.push(full_rx);
let outlet = current.clone();
// send the new view, with the new owner, through the channel to the Outlet,
// and notify the trigger so that the reactive view inside the Outlet tracking
// the trigger runs again
preloaders.push(Box::pin(owner.with(|| {
ScopedFuture::new({
let owner = owner.clone();
let trigger = current.trigger.clone();
let url = current.url.clone();
let matched = Matched(matched_including_parents);
let view_fn = Arc::clone(&current.view_fn);
async move {
provide_context(params_including_parents);
provide_context(url);
provide_context(matched);
view.preload().await;
*view_fn.lock().or_poisoned() =
Box::new(move |owner_where_used| {
owner.join_contexts(&owner_where_used);
let owner = owner.clone();
let view = view.clone();
let full_tx =
full_tx.lock().or_poisoned().take();
let old_owner = old_owner.take();
Suspend::new(Box::pin(async move {
let view = SendWrapper::new(
owner.with(|| {
ScopedFuture::new(
async move {
if set_is_routing {
AsyncTransition::run(|| view.choose()).await
} else {
view.choose().await
}
}
)
}),
);
let view = view.await;
if let Some(old_owner) = old_owner {
old_owner.cleanup();
}
preloaders.push(Box::pin(ScopedFuture::new({
let owner = owner.clone();
let trigger = current.trigger.clone();
let url = current.url.clone();
let matched = Matched(matched_including_parents);
let view_fn = Arc::clone(&current.view_fn);
let child = outlet.child.clone();
async move {
view.preload().await;
let child = child.clone();
*view_fn.lock().or_poisoned() =
Box::new(move |owner_where_used| {
let owner = owner.clone();
let view = view.clone();
let full_tx =
full_tx.lock().or_poisoned().take();
let old_owner = old_owner.take();
let child = child.clone();
let params =
params_including_parents.clone();
let url = url.clone();
let matched = matched.clone();
Suspend::new(Box::pin(async move {
let view = SendWrapper::new(
owner_where_used.with(|| {
provide_context(child.clone());
provide_context(params);
provide_context(url);
provide_context(matched);
ScopedFuture::new(async move {
if set_is_routing {
AsyncTransition::run(
|| view.choose(),
)
.await
} else {
view.choose().await
}
})
}),
);
let view = view.await;
if let Some(old_owner) = old_owner {
old_owner.cleanup();
}
if let Some(tx) = full_tx {
_ = tx.send(());
}
owner.with(|| {
OwnedView::new(view).into_any()
})
}))
});
if let Some(tx) = full_tx {
_ = tx.send(());
}
owner.with(|| {
OwnedView::new(view).into_any()
})
}))
});
drop(old_params);
drop(old_url);
drop(old_matched);
trigger
}
})
drop(old_params);
drop(old_url);
drop(old_matched);
trigger
}
})));
// remove all the items lower in the tree
@@ -911,20 +928,38 @@ where
}
}
fn top_level_outlet(outlets: &[RouteContext], outer_owner: &Owner) -> AnyView {
let outlet = outlets.first().unwrap();
let child = outlet.child.clone();
let view_fn = outlet.view_fn.clone();
let trigger = outlet.trigger.clone();
let owner = outer_owner.child();
outer_owner.clone().with(|| {
provide_context(child.clone());
(move || {
trigger.track();
let mut view_fn = view_fn.lock().or_poisoned();
view_fn(owner.clone())
})
.into_any()
})
}
/// Displays the child route nested in a parent route, allowing you to control exactly where
/// that child route is displayed. Renders nothing if there is no nested child.
#[component]
pub fn Outlet() -> impl RenderHtml
where
{
move || {
let ctx = use_context::<RouteContext>()
.expect("<Outlet/> used without RouteContext being provided.");
let RouteContext {
trigger, view_fn, ..
} = ctx;
trigger.track();
let mut view_fn = view_fn.lock().or_poisoned();
view_fn(Owner::current().unwrap())
}
let ChildRoute(child) = use_context()
.expect("<Outlet/> used without RouteContext being provided.");
let owner = Owner::new();
let child = child.lock().or_poisoned().clone();
child.map(|child| {
move || {
child.trigger.track();
let mut view_fn = child.view_fn.lock().or_poisoned();
view_fn(owner.clone())
}
})
}

View File

@@ -29,7 +29,6 @@ thiserror = { workspace = true, default-features = true }
# registration system
inventory = { optional = true, workspace = true, default-features = true }
dashmap = { workspace = true, default-features = true }
once_cell = { workspace = true, default-features = true }
## servers
# actix

View File

@@ -151,7 +151,6 @@ use error::{FromServerFnError, ServerFnErrorErr};
use futures::{pin_mut, SinkExt, Stream, StreamExt};
use http::Method;
use middleware::{BoxedService, Layer, Service};
use once_cell::sync::Lazy;
use redirect::call_redirect_hook;
use request::Req;
use response::{ClientRes, Res, TryRes};
@@ -169,7 +168,7 @@ use std::{
marker::PhantomData,
ops::{Deref, DerefMut},
pin::Pin,
sync::Arc,
sync::{Arc, LazyLock},
};
#[doc(hidden)]
pub use xxhash_rust;
@@ -862,7 +861,7 @@ pub use inventory;
#[macro_export]
macro_rules! initialize_server_fn_map {
($req:ty, $res:ty) => {
once_cell::sync::Lazy::new(|| {
std::sync::LazyLock::new(|| {
$crate::inventory::iter::<ServerFnTraitObj<$req, $res>>
.into_iter()
.map(|obj| {
@@ -981,7 +980,7 @@ impl<Req, Res> Clone for ServerFnTraitObj<Req, Res> {
#[allow(unused)] // used by server integrations
type LazyServerFnMap<Req, Res> =
Lazy<DashMap<(String, Method), ServerFnTraitObj<Req, Res>>>;
LazyLock<DashMap<(String, Method), ServerFnTraitObj<Req, Res>>>;
#[cfg(feature = "ssr")]
impl<Req: 'static, Res: 'static> inventory::Collect

View File

@@ -75,6 +75,13 @@ impl DerefMut for BrowserRequest {
#[derive(Debug)]
pub struct BrowserFormData(pub(crate) SendWrapper<FormData>);
impl BrowserFormData {
/// Returns the raw `web_sys::FormData` struct.
pub fn take(self) -> FormData {
self.0.take()
}
}
impl From<FormData> for BrowserFormData {
fn from(value: FormData) -> Self {
Self(SendWrapper::new(value))

View File

@@ -5,14 +5,14 @@ use crate::{
};
use bytes::Bytes;
use futures::{Stream, StreamExt};
use once_cell::sync::Lazy;
use reqwest::{
header::{ACCEPT, CONTENT_TYPE},
Body,
};
pub use reqwest::{multipart::Form, Client, Method, Request, Url};
use std::sync::LazyLock;
pub(crate) static CLIENT: Lazy<Client> = Lazy::new(Client::new);
pub(crate) static CLIENT: LazyLock<Client> = LazyLock::new(Client::new);
impl<E> ClientReq<E> for Request
where

View File

@@ -21,7 +21,6 @@ reactive_stores = { workspace = true, optional = true }
slotmap = { optional = true, workspace = true, default-features = true }
oco_ref = { workspace = true, optional = true }
async-trait = { workspace = true, default-features = true }
once_cell = { workspace = true, default-features = true }
paste = { workspace = true, default-features = true }
erased = { workspace = true, default-features = true }
wasm-bindgen = { workspace = true, default-features = true }

View File

@@ -540,11 +540,20 @@ impl IntoClass for (&'static str, bool) {
fn rebuild(self, state: &mut Self::State) {
let (name, include) = self;
let (class_list, prev_include, prev_name) = state;
if include != *prev_include {
if name == *prev_name {
if include != *prev_include {
if include {
Rndr::add_class(class_list, name);
} else {
Rndr::remove_class(class_list, name);
}
}
} else {
if *prev_include {
Rndr::remove_class(class_list, prev_name);
}
if include {
Rndr::add_class(class_list, name);
} else {
Rndr::remove_class(class_list, name);
}
}
*prev_include = include;

View File

@@ -709,7 +709,7 @@ where
buf.push('<');
buf.push_str(E::TAG);
<At as ToTemplate>::to_template(
<At as ToTemplate>::to_template_attribute(
buf,
&mut class,
&mut style,

View File

@@ -125,14 +125,14 @@ impl Render for InertElement {
type State = InertElementState;
fn build(self) -> Self::State {
let el = Rndr::create_element_from_html(&self.html);
let el = Rndr::create_element_from_html(self.html.clone());
InertElementState(self.html, el)
}
fn rebuild(self, state: &mut Self::State) {
let InertElementState(prev, el) = state;
if &self.html != prev {
let mut new_el = Rndr::create_element_from_html(&self.html);
let mut new_el = Rndr::create_element_from_html(self.html.clone());
el.insert_before_this(&mut new_el);
el.unmount();
*el = new_el;

View File

@@ -209,6 +209,15 @@ where
fn rebuild(self, state: &mut Self::State) {
let (name, mut f) = self;
let prev_name = state.name;
let prev_state = state.effect.take_value();
if let Some((list, prev_include)) = &prev_state {
if prev_name != name && *prev_include {
Rndr::remove_class(list, prev_name);
}
}
// Name might've updated:
state.name = name;
let mut first_run = true;
@@ -232,7 +241,7 @@ where
}
}
},
state.effect.take_value(),
prev_state,
);
}

View File

@@ -9,9 +9,12 @@ use crate::{
view::{Mountable, ToTemplate},
};
use linear_map::LinearMap;
use once_cell::unsync::Lazy;
use rustc_hash::FxHashSet;
use std::{any::TypeId, borrow::Cow, cell::RefCell};
use std::{
any::TypeId,
borrow::Cow,
cell::{LazyCell, RefCell},
};
use wasm_bindgen::{intern, prelude::Closure, JsCast, JsValue};
use web_sys::{AddEventListenerOptions, Comment, HtmlTemplateElement};
@@ -21,6 +24,7 @@ pub struct Dom;
thread_local! {
pub(crate) static GLOBAL_EVENTS: RefCell<FxHashSet<Cow<'static, str>>> = Default::default();
pub static TEMPLATE_CACHE: RefCell<Vec<(Cow<'static, str>, web_sys::Element)>> = Default::default();
}
pub type Node = web_sys::Node;
@@ -57,7 +61,7 @@ impl Dom {
pub fn create_placeholder() -> Placeholder {
thread_local! {
static COMMENT: Lazy<Comment> = Lazy::new(|| {
static COMMENT: LazyCell<Comment> = LazyCell::new(|| {
document().create_comment("")
});
}
@@ -281,9 +285,10 @@ impl Dom {
let cb = send_wrapper::SendWrapper::new(cb);
move |el: &Element| {
or_debug!(
el.remove_event_listener_with_callback(
el.remove_event_listener_with_callback_and_bool(
intern(&name),
cb.as_ref().unchecked_ref()
cb.as_ref().unchecked_ref(),
true
),
el,
"removeEventListener"
@@ -451,8 +456,8 @@ impl Dom {
V: ToTemplate + 'static,
{
thread_local! {
static TEMPLATE_ELEMENT: Lazy<HtmlTemplateElement> =
Lazy::new(|| document().create_element("template").unwrap().unchecked_into());
static TEMPLATE_ELEMENT: LazyCell<HtmlTemplateElement> =
LazyCell::new(|| document().create_element(Dom::intern("template")).unwrap().unchecked_into());
static TEMPLATES: RefCell<LinearMap<TypeId, HtmlTemplateElement>> = Default::default();
}
@@ -487,13 +492,66 @@ impl Dom {
.unchecked_into()
}
pub fn create_element_from_html(html: &str) -> Element {
// TODO can be optimized to cache HTML strings or cache <template>?
let tpl = document().create_element("template").unwrap();
tpl.set_inner_html(html);
let tpl = Self::clone_template(tpl.unchecked_ref());
pub fn create_element_from_html(html: Cow<'static, str>) -> Element {
let tpl = TEMPLATE_CACHE.with(|cache| {
let mut cache = cache.borrow_mut();
if let Some(tpl_content) = cache.iter().find_map(|(key, tpl)| {
(html == *key)
.then_some(Self::clone_template(tpl.unchecked_ref()))
}) {
tpl_content
} else {
let tpl = document()
.create_element(Self::intern("template"))
.unwrap();
tpl.set_inner_html(&html);
let tpl_content = Self::clone_template(tpl.unchecked_ref());
cache.push((html, tpl));
tpl_content
}
});
tpl.first_element_child().unwrap_or(tpl)
}
pub fn create_svg_element_from_html(html: Cow<'static, str>) -> Element {
let tpl = TEMPLATE_CACHE.with(|cache| {
let mut cache = cache.borrow_mut();
if let Some(tpl_content) = cache.iter().find_map(|(key, tpl)| {
(html == *key)
.then_some(Self::clone_template(tpl.unchecked_ref()))
}) {
tpl_content
} else {
let tpl = document()
.create_element(Self::intern("template"))
.unwrap();
let svg = document()
.create_element_ns(
Some(Self::intern("http://www.w3.org/2000/svg")),
Self::intern("svg"),
)
.unwrap();
let g = document()
.create_element_ns(
Some(Self::intern("http://www.w3.org/2000/svg")),
Self::intern("g"),
)
.unwrap();
g.set_inner_html(&html);
svg.append_child(&g).unwrap();
tpl.unchecked_ref::<TemplateElement>()
.content()
.append_child(&svg)
.unwrap();
let tpl_content = Self::clone_template(tpl.unchecked_ref());
cache.push((html, tpl));
tpl_content
}
});
let svg = tpl.first_element_child().unwrap();
svg.first_element_child().unwrap_or(svg)
}
}
impl Mountable for Node {

View File

@@ -1,11 +1,17 @@
use crate::{
html::{
attribute::Attribute,
attribute::{any_attribute::AnyAttribute, Attribute},
element::{ElementType, ElementWithChildren, HtmlElement},
},
view::Render,
hydration::Cursor,
prelude::{AddAnyAttr, Mountable},
renderer::{
dom::{Element, Node},
CastFrom, Rndr,
},
view::{Position, PositionState, Render, RenderHtml},
};
use std::fmt::Debug;
use std::{borrow::Cow, fmt::Debug};
macro_rules! svg_elements {
($($tag:ident [$($attr:ty),*]),* $(,)?) => {
@@ -185,3 +191,124 @@ impl ElementType for Use {
}
impl ElementWithChildren for Use {}
/// An element that contains no interactivity, and whose contents can be known at compile time.
pub struct InertElement {
html: Cow<'static, str>,
}
impl InertElement {
/// Creates a new inert svg element.
pub fn new(html: impl Into<Cow<'static, str>>) -> Self {
Self { html: html.into() }
}
}
/// Retained view state for [`InertElement`].
pub struct InertElementState(Cow<'static, str>, Element);
impl Mountable for InertElementState {
fn unmount(&mut self) {
self.1.unmount();
}
fn mount(&mut self, parent: &Element, marker: Option<&Node>) {
self.1.mount(parent, marker)
}
fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
self.1.insert_before_this(child)
}
fn elements(&self) -> Vec<crate::renderer::types::Element> {
vec![self.1.clone()]
}
}
impl Render for InertElement {
type State = InertElementState;
fn build(self) -> Self::State {
let el = Rndr::create_svg_element_from_html(self.html.clone());
InertElementState(self.html, el)
}
fn rebuild(self, state: &mut Self::State) {
let InertElementState(prev, el) = state;
if &self.html != prev {
let mut new_el =
Rndr::create_svg_element_from_html(self.html.clone());
el.insert_before_this(&mut new_el);
el.unmount();
*el = new_el;
*prev = self.html;
}
}
}
impl AddAnyAttr for InertElement {
type Output<SomeNewAttr: Attribute> = Self;
fn add_any_attr<NewAttr: Attribute>(
self,
_attr: NewAttr,
) -> Self::Output<NewAttr>
where
Self::Output<NewAttr>: RenderHtml,
{
panic!(
"InertElement does not support adding attributes. It should only \
be used as a child, and not returned at the top level."
)
}
}
impl RenderHtml for InertElement {
type AsyncOutput = Self;
type Owned = Self;
const MIN_LENGTH: usize = 0;
fn html_len(&self) -> usize {
self.html.len()
}
fn dry_resolve(&mut self) {}
async fn resolve(self) -> Self {
self
}
fn to_html_with_buf(
self,
buf: &mut String,
position: &mut Position,
_escape: bool,
_mark_branches: bool,
_extra_attrs: Vec<AnyAttribute>,
) {
buf.push_str(&self.html);
*position = Position::NextChild;
}
fn hydrate<const FROM_SERVER: bool>(
self,
cursor: &Cursor,
position: &PositionState,
) -> Self::State {
let curr_position = position.get();
if curr_position == Position::FirstChild {
cursor.child();
} else if curr_position != Position::Current {
cursor.sibling();
}
let el = crate::renderer::types::Element::cast_from(cursor.current())
.unwrap();
position.set(Position::NextChild);
InertElementState(self.html, el)
}
fn into_owned(self) -> Self::Owned {
self
}
}

View File

@@ -53,6 +53,12 @@ where
}
}
impl IntoFragment for AnyView {
fn into_fragment(self) -> Fragment {
Fragment::new(vec![self])
}
}
impl<T> IntoFragment for Vec<T>
where
T: IntoAny,

View File

@@ -437,6 +437,17 @@ pub trait ToTemplate {
inner_html: &mut String,
position: &mut Position,
);
/// Renders a view type to a template in attribute position.
fn to_template_attribute(
buf: &mut String,
class: &mut String,
style: &mut String,
inner_html: &mut String,
position: &mut Position,
) {
Self::to_template(buf, class, style, inner_html, position);
}
}
/// Keeps track of what position the item currently being hydrated is in, relative to its siblings

View File

@@ -103,6 +103,15 @@ impl ToTemplate for () {
) {
buf.push_str("<!>");
}
fn to_template_attribute(
_buf: &mut String,
_class: &mut String,
_style: &mut String,
_inner_html: &mut String,
_position: &mut Position,
) {
}
}
impl<A: Render> Render for (A,) {