diff --git a/Cargo.lock b/Cargo.lock index 2a5c72c982..6f1981a874 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,7 +88,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -205,7 +205,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -230,7 +230,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.2.15", "once_cell", "version_check", "zerocopy", @@ -267,7 +267,7 @@ dependencies = [ "async-executor", "futures", "glib", - "thiserror 2.0.9", + "thiserror 2.0.11", "tokio", "tracing", "wasm-bindgen-futures", @@ -275,9 +275,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "async-executor" @@ -322,7 +322,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -333,13 +333,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.85" +version = "0.1.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" +checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -362,7 +362,7 @@ dependencies = [ "manyhow", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -378,7 +378,7 @@ dependencies = [ "proc-macro2", "quote", "quote-use", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -465,9 +465,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] name = "block-buffer" @@ -491,9 +491,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.1" +version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" +checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -501,9 +501,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bytecheck" @@ -525,7 +525,7 @@ checksum = "523363cbe1df49b68215efdf500b103ac3b0fb4836aed6d15689a076eadb8fff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -536,9 +536,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" [[package]] name = "bytestring" @@ -557,9 +557,9 @@ checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" [[package]] name = "cc" -version = "1.2.5" +version = "1.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" +checksum = "755717a7de9ec452bf7f3f1a3099085deabd7f2962b861dae91ecd7a365903d2" dependencies = [ "jobserver", "libc", @@ -634,7 +634,7 @@ dependencies = [ "serde-lite", "serde-wasm-bindgen", "serde_json", - "thiserror 2.0.9", + "thiserror 2.0.11", "wasm-bindgen", ] @@ -752,9 +752,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -782,9 +782,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto-common" @@ -827,20 +827,20 @@ checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "derive_more" -version = "0.99.18" +version = "0.99.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" dependencies = [ "convert_case 0.4.0", "proc-macro2", "quote", "rustc_version", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -861,7 +861,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -872,9 +872,9 @@ checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408" [[package]] name = "dyn-clone" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +checksum = "feeef44e73baff3a26d371801df019877a9866a8c493d315ab00177843314f35" [[package]] name = "either" @@ -935,9 +935,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.3.1" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" dependencies = [ "concurrent-queue", "parking", @@ -1051,9 +1051,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" dependencies = [ "fastrand", "futures-core", @@ -1070,7 +1070,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -1122,10 +1122,22 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets", +] + [[package]] name = "gimli" version = "0.31.1" @@ -1176,7 +1188,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -1191,9 +1203,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "gloo-net" @@ -1390,9 +1402,9 @@ checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" [[package]] name = "httpdate" @@ -1416,9 +1428,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.2" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", @@ -1602,7 +1614,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -1628,15 +1640,15 @@ dependencies = [ [[package]] name = "impl-more" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae21c3177a27788957044151cc2800043d127acaa460a47ebb9b84dfa2c6aa0" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" [[package]] name = "indexmap" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -1644,13 +1656,14 @@ dependencies = [ [[package]] name = "insta" -version = "1.41.1" +version = "1.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9ffc4d4892617c50a928c52b2961cb5174b6fc6ebf252b2fac9d21955c48b8" +checksum = "71c1b125e30d93896b365e156c33dadfffab45ee8400afcbba4752f59de08a86" dependencies = [ "console", - "lazy_static", "linked-hash-map", + "once_cell", + "pin-project", "similar", ] @@ -1662,18 +1675,18 @@ checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8" [[package]] name = "inventory" -version = "0.3.16" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d80fade88dd420ce0d9ab6f7c58ef2272dde38db874657950f827d4982c817" +checksum = "54b12ebb6799019b044deaf431eadfe23245b259bba5a2c0796acec3943a3cdb" dependencies = [ "rustversion", ] [[package]] name = "ipnet" -version = "2.10.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "itertools" @@ -1701,9 +1714,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -1715,12 +1728,6 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - [[package]] name = "leptos" version = "0.8.0-alpha" @@ -1730,7 +1737,7 @@ dependencies = [ "cfg-if", "either_of", "futures", - "getrandom", + "getrandom 0.2.15", "hydration_context", "leptos-spin-macro", "leptos_config", @@ -1743,7 +1750,7 @@ dependencies = [ "paste", "rand", "reactive_graph", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "send_wrapper", "serde", "serde_json", @@ -1751,7 +1758,7 @@ dependencies = [ "server_fn", "slotmap", "tachys", - "thiserror 2.0.9", + "thiserror 2.0.11", "throw_error", "tracing", "typed-builder", @@ -1769,7 +1776,7 @@ dependencies = [ "http 1.2.0", "proc-macro-error", "server_fn_macro 0.6.15", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -1829,7 +1836,7 @@ dependencies = [ "serde", "temp-env", "tempfile", - "thiserror 2.0.9", + "thiserror 2.0.11", "tokio", "typed-builder", ] @@ -1863,7 +1870,7 @@ dependencies = [ "quote", "rstml", "serde", - "syn 2.0.90", + "syn 2.0.98", "walkdir", ] @@ -1902,7 +1909,7 @@ dependencies = [ "serde", "server_fn", "server_fn_macro 0.8.0-alpha", - "syn 2.0.90", + "syn 2.0.98", "tracing", "trybuild", "typed-builder", @@ -1941,7 +1948,7 @@ dependencies = [ "reactive_graph", "send_wrapper", "tachys", - "thiserror 2.0.9", + "thiserror 2.0.11", "tracing", "url", "wasm-bindgen", @@ -1957,7 +1964,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -2001,9 +2008,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "litemap" @@ -2040,9 +2047,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "manyhow" @@ -2053,7 +2060,7 @@ dependencies = [ "manyhow-macros", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -2103,7 +2110,7 @@ checksum = "07b7f1340a7f0d2f89755aac4096f1c55352bfcb95cb8f351d6fcefa18df474f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -2125,9 +2132,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ "adler2", ] @@ -2140,7 +2147,7 @@ checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -2178,14 +2185,14 @@ checksum = "1bb5c1d8184f13f7d0ccbeeca0def2f9a181bce2624302793005f5ca8aa62e5e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "native-tls" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" dependencies = [ "libc", "log", @@ -2252,20 +2259,20 @@ version = "0.2.0" dependencies = [ "serde", "serde_json", - "thiserror 2.0.9", + "thiserror 2.0.11", ] [[package]] name = "once_cell" -version = "1.20.2" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" dependencies = [ "bitflags", "cfg-if", @@ -2284,20 +2291,20 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" dependencies = [ "cc", "libc", @@ -2358,22 +2365,22 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.7" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" +checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.7" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" +checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -2424,12 +2431,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.25" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" dependencies = [ "proc-macro2", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -2484,7 +2491,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -2500,9 +2507,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -2515,7 +2522,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", "version_check", "yansi", ] @@ -2537,7 +2544,7 @@ checksum = "ca414edb151b4c8d125c12566ab0d74dc9cdba36fb80eb7b848c15f495fd32d1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -2550,10 +2557,10 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "rustls", "socket2", - "thiserror 2.0.9", + "thiserror 2.0.11", "tokio", "tracing", ] @@ -2565,14 +2572,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" dependencies = [ "bytes", - "getrandom", + "getrandom 0.2.15", "rand", "ring", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.9", + "thiserror 2.0.11", "tinyvec", "tracing", "web-time", @@ -2594,9 +2601,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -2620,7 +2627,7 @@ dependencies = [ "proc-macro-utils", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -2659,7 +2666,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", ] [[package]] @@ -2673,11 +2680,11 @@ dependencies = [ "hydration_context", "or_poisoned", "pin-project-lite", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "send_wrapper", "serde", "slotmap", - "thiserror 2.0.9", + "thiserror 2.0.11", "tokio", "tokio-test", "tracing", @@ -2696,7 +2703,7 @@ dependencies = [ "paste", "reactive_graph", "reactive_stores_macro", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "tokio", "tokio-test", ] @@ -2709,7 +2716,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -2767,9 +2774,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.9" +version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" dependencies = [ "base64", "bytes", @@ -2803,6 +2810,7 @@ dependencies = [ "tokio-native-tls", "tokio-rustls", "tokio-util", + "tower", "tower-service", "url", "wasm-bindgen", @@ -2821,7 +2829,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.15", "libc", "spin", "untrusted", @@ -2830,9 +2838,9 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b11a153aec4a6ab60795f8ebe2923c597b16b05bb1504377451e705ef1a45323" +checksum = "1e147371c75553e1e2fcdb483944a8540b8438c31426279553b9a8182a9b7b65" dependencies = [ "bytecheck", "bytes", @@ -2849,13 +2857,13 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beb382a4d9f53bd5c0be86b10d8179c3f8a14c30bf774ff77096ed6581e35981" +checksum = "246b40ac189af6c675d124b802e8ef6d5246c53e17367ce9501f8f66a81abb7a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -2882,17 +2890,17 @@ dependencies = [ [[package]] name = "rstml" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51187e564f12336ef40cd04f6f4d805d6919188001dcf1e0a021898ea0fe28ce" +checksum = "61cf4616de7499fc5164570d40ca4e1b24d231c6833a88bff0fe00725080fd56" dependencies = [ "derive-where", "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.90", + "syn 2.0.98", "syn_derive", - "thiserror 1.0.69", + "thiserror 2.0.11", ] [[package]] @@ -2909,9 +2917,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc_version" @@ -2924,9 +2932,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.42" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags", "errno", @@ -2937,9 +2945,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.20" +version = "0.23.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" +checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7" dependencies = [ "once_cell", "ring", @@ -2960,9 +2968,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" dependencies = [ "web-time", ] @@ -2980,15 +2988,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "same-file" @@ -3029,9 +3037,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -3039,9 +3047,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" [[package]] name = "send_wrapper" @@ -3054,9 +3062,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] @@ -3079,7 +3087,7 @@ checksum = "7ce26a84e3d8d10853301cf6a75c58132b8f5d5e8fee65949ea8dd7758d6760b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -3095,20 +3103,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "serde_json" -version = "1.0.134" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" dependencies = [ "itoa", "memchr", @@ -3190,7 +3198,7 @@ dependencies = [ "serde_json", "serde_qs", "server_fn_macro_default", - "thiserror 2.0.9", + "thiserror 2.0.11", "throw_error", "tower", "tower-layer", @@ -3212,7 +3220,7 @@ dependencies = [ "convert_case 0.6.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", "xxhash-rust", ] @@ -3224,7 +3232,7 @@ dependencies = [ "convert_case 0.6.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", "xxhash-rust", ] @@ -3233,7 +3241,7 @@ name = "server_fn_macro_default" version = "0.8.0-alpha" dependencies = [ "server_fn_macro 0.8.0-alpha", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -3270,9 +3278,9 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "similar" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "slab" @@ -3300,7 +3308,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33a1b4f13e2bbf2f5b29d09dfebc9de69229ffee245aed80e3b70f9b5fd28c06" dependencies = [ "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -3370,9 +3378,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.90" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -3381,14 +3389,14 @@ dependencies = [ [[package]] name = "syn_derive" -version = "0.1.8" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +checksum = "cdb066a04799e45f5d582e8fc6ec8e6d6896040d00898eb4e6a835196815b219" dependencies = [ - "proc-macro-error", + "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -3408,7 +3416,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -3448,7 +3456,7 @@ dependencies = [ "paste", "reactive_graph", "reactive_stores", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "send_wrapper", "sledgehammer_bindgen", "sledgehammer_utils", @@ -3485,12 +3493,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" dependencies = [ "cfg-if", "fastrand", + "getrandom 0.3.1", "once_cell", "rustix", "windows-sys 0.59.0", @@ -3516,11 +3525,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.9" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ - "thiserror-impl 2.0.9", + "thiserror-impl 2.0.11", ] [[package]] @@ -3531,18 +3540,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "thiserror-impl" -version = "2.0.9" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -3610,9 +3619,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.42.0" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", "bytes", @@ -3628,13 +3637,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -3696,9 +3705,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", @@ -3717,9 +3726,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.22.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee" dependencies = [ "indexmap", "serde", @@ -3801,7 +3810,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -3821,9 +3830,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "trybuild" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dcd332a5496c026f1e14b7f3d2b7bd98e509660c04239c58b0ba38a12daded4" +checksum = "b812699e0c4f813b872b373a4471717d9eb550da14b311058a4d9cf4173cbca6" dependencies = [ "glob", "serde", @@ -3851,7 +3860,7 @@ checksum = "560b82d656506509d43abe30e0ba64c56b1953ab3d4fe7ba5902747a7a3cedd5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -3862,15 +3871,15 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicase" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unicode-segmentation" @@ -3921,11 +3930,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.11.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0" dependencies = [ - "getrandom", + "getrandom 0.3.1", ] [[package]] @@ -3977,36 +3986,46 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.49" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", @@ -4017,9 +4036,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4027,22 +4046,25 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-streams" @@ -4059,9 +4081,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -4079,9 +4101,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.7" +version = "0.26.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" dependencies = [ "rustls-pki-types", ] @@ -4209,13 +4231,22 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "86e376c75f4f43f44db463cf729e0d3acbf954d13e22c51e26e4c264b4ab545f" dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags", +] + [[package]] name = "write16" version = "1.0.0" @@ -4230,9 +4261,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "xxhash-rust" -version = "0.8.13" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08fd76779ae1883bbf1e46c2c46a75a0c4e37c445e68a24b01479d438f26ae6" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" [[package]] name = "yansi" @@ -4260,7 +4291,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", "synstructure", ] @@ -4282,7 +4313,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -4302,7 +4333,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", "synstructure", ] @@ -4331,7 +4362,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] diff --git a/examples/counter/tests/web.rs b/examples/counter/tests/web.rs index 2327be35fa..eb9a2ce2e3 100644 --- a/examples/counter/tests/web.rs +++ b/examples/counter/tests/web.rs @@ -19,7 +19,7 @@ async fn clear() { // note that we start at the initial value of 10 let _dispose = mount_to( test_wrapper.clone().unchecked_into(), - || view! { }, + || view! { }, ); // now we extract the buttons by iterating over the DOM @@ -59,9 +59,9 @@ async fn clear() { // .into_view() here is just a convenient way of specifying "use the regular DOM renderer" .into_view() // views are lazy -- they describe a DOM tree but don't create it yet - // calling .build() will actually build the DOM elements - .build() - // .build() returned an ElementState, which is a smart pointer for + // calling .build(None) will actually build the DOM elements + .build(None) + // .build(None) returned an ElementState, which is a smart pointer for // a DOM element. So we can still just call .outer_html(), which access the outerHTML on // the actual DOM element .outer_html() @@ -87,7 +87,7 @@ async fn inc() { let _dispose = mount_to( test_wrapper.clone().unchecked_into(), - || view! { }, + || view! { }, ); // You can do testing with vanilla DOM operations @@ -150,7 +150,7 @@ async fn inc() { } } .into_view() - .build() + .build(None) .outer_html() ); @@ -173,7 +173,7 @@ async fn inc() { } } .into_view() - .build() + .build(None) .outer_html() ); } diff --git a/leptos/src/attribute_interceptor.rs b/leptos/src/attribute_interceptor.rs index 35d5ebf931..154f365c7f 100644 --- a/leptos/src/attribute_interceptor.rs +++ b/leptos/src/attribute_interceptor.rs @@ -3,6 +3,7 @@ use crate::attr::{ Attribute, NextAttribute, }; use leptos::prelude::*; +use tachys::view::any_view::ExtraAttrsMut; /// Function stored to build/rebuild the wrapped children when attributes are added. type ChildBuilder = dyn Fn(AnyAttribute) -> T + Send + Sync + 'static; @@ -43,7 +44,7 @@ pub fn AttributeInterceptor( ) -> impl IntoView where Chil: Fn(AnyAttribute) -> T + Send + Sync + 'static, - T: IntoView, + T: IntoView + 'static, { AttributeInterceptorInner::new(children) } @@ -77,16 +78,20 @@ impl AttributeInterceptorInner { impl Render for AttributeInterceptorInner { type State = ::State; - fn build(self) -> Self::State { - self.children.build() + fn build(self, extra_attrs: Option>) -> Self::State { + self.children.build(extra_attrs) } - fn rebuild(self, state: &mut Self::State) { - self.children.rebuild(state); + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { + self.children.rebuild(state, extra_attrs); } } -impl AddAnyAttr for AttributeInterceptorInner +impl AddAnyAttr for AttributeInterceptorInner where A: Attribute, { @@ -114,19 +119,23 @@ where } } -impl RenderHtml for AttributeInterceptorInner { +impl RenderHtml + for AttributeInterceptorInner +{ type AsyncOutput = T::AsyncOutput; + type Owned = AttributeInterceptorInner; const MIN_LENGTH: usize = T::MIN_LENGTH; - fn dry_resolve(&mut self) { - self.children.dry_resolve() + fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) { + self.children.dry_resolve(extra_attrs) } fn resolve( self, + extra_attrs: ExtraAttrsMut<'_>, ) -> impl std::future::Future + Send { - self.children.resolve() + self.children.resolve(extra_attrs) } fn to_html_with_buf( @@ -135,16 +144,32 @@ impl RenderHtml for AttributeInterceptorInner { position: &mut leptos::tachys::view::Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { - self.children - .to_html_with_buf(buf, position, escape, mark_branches) + self.children.to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs, + ) } fn hydrate( self, cursor: &leptos::tachys::hydration::Cursor, position: &leptos::tachys::view::PositionState, + extra_attrs: Option>, ) -> Self::State { - self.children.hydrate::(cursor, position) + self.children + .hydrate::(cursor, position, extra_attrs) + } + + fn into_owned(self) -> Self::Owned { + AttributeInterceptorInner { + children_builder: self.children_builder, + children: self.children, + attributes: self.attributes.into_cloneable_owned(), + } } } diff --git a/leptos/src/error_boundary.rs b/leptos/src/error_boundary.rs index e4279dc215..c09881bc99 100644 --- a/leptos/src/error_boundary.rs +++ b/leptos/src/error_boundary.rs @@ -11,13 +11,13 @@ use reactive_graph::{ use rustc_hash::FxHashMap; use std::{fmt::Debug, sync::Arc}; use tachys::{ - html::attribute::Attribute, + html::attribute::{any_attribute::AnyAttribute, Attribute}, hydration::Cursor, reactive_graph::OwnedView, ssr::StreamBuilder, view::{ - add_attr::AddAnyAttr, Mountable, Position, PositionState, Render, - RenderHtml, + add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Mountable, Position, + PositionState, Render, RenderHtml, }, }; use throw_error::{Error, ErrorHook, ErrorId}; @@ -173,10 +173,10 @@ where { type State = RenderEffect>; - fn build(mut self) -> Self::State { + fn build(mut self, extra_attrs: Option>) -> Self::State { let hook = Arc::clone(&self.hook); let _hook = throw_error::set_error_hook(Arc::clone(&hook)); - let mut children = Some(self.children.build()); + let mut children = Some(self.children.build(extra_attrs.clone())); RenderEffect::new( move |prev: Option< ErrorBoundaryViewState, @@ -193,7 +193,8 @@ where // yes errors, and was showing children (false, None) => { state.fallback = Some( - (self.fallback)(self.errors.clone()).build(), + (self.fallback)(self.errors.clone()) + .build(extra_attrs.clone()), ); state .children @@ -207,8 +208,10 @@ where } state } else { - let fallback = (!self.errors_empty.get()) - .then(|| (self.fallback)(self.errors.clone()).build()); + let fallback = (!self.errors_empty.get()).then(|| { + (self.fallback)(self.errors.clone()) + .build(extra_attrs.clone()) + }); ErrorBoundaryViewState { children: children.take().unwrap(), fallback, @@ -218,8 +221,12 @@ where ) } - fn rebuild(self, state: &mut Self::State) { - let new = self.build(); + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { + let new = self.build(extra_attrs); let mut old = std::mem::replace(state, new); old.insert_before_this(state); old.unmount(); @@ -268,14 +275,18 @@ where Fal: RenderHtml + Send + 'static, { type AsyncOutput = ErrorBoundaryView; + type Owned = Self; const MIN_LENGTH: usize = Chil::MIN_LENGTH; - fn dry_resolve(&mut self) { - self.children.dry_resolve(); + fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) { + self.children.dry_resolve(extra_attrs); } - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { let ErrorBoundaryView { hook, boundary_id, @@ -289,7 +300,7 @@ where hook, boundary_id, errors_empty, - children: children.resolve().await, + children: children.resolve(extra_attrs).await, fallback, errors, } @@ -301,6 +312,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { // first, attempt to serialize the children to HTML, then check for errors let _hook = throw_error::set_error_hook(self.hook); @@ -311,6 +323,7 @@ where &mut new_pos, escape, mark_branches, + extra_attrs.clone(), ); // any thrown errors would've been caught here @@ -323,6 +336,7 @@ where position, escape, mark_branches, + extra_attrs, ); } } @@ -333,6 +347,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) where Self: Sized, { @@ -345,6 +360,7 @@ where &mut new_pos, escape, mark_branches, + extra_attrs.clone(), ); // any thrown errors would've been caught here @@ -358,6 +374,7 @@ where position, escape, mark_branches, + extra_attrs, ); buf.push_sync(&fallback); } @@ -367,6 +384,7 @@ where mut self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { let mut children = Some(self.children); let hook = Arc::clone(&self.hook); @@ -388,7 +406,8 @@ where // yes errors, and was showing children (false, None) => { state.fallback = Some( - (self.fallback)(self.errors.clone()).build(), + (self.fallback)(self.errors.clone()) + .build(extra_attrs.clone()), ); state .children @@ -405,15 +424,23 @@ where let children = children.take().unwrap(); let (children, fallback) = if self.errors_empty.get() { ( - children.hydrate::(&cursor, &position), + children.hydrate::( + &cursor, + &position, + extra_attrs.clone(), + ), None, ) } else { ( - children.build(), + children.build(extra_attrs.clone()), Some( (self.fallback)(self.errors.clone()) - .hydrate::(&cursor, &position), + .hydrate::( + &cursor, + &position, + extra_attrs.clone(), + ), ), ) }; @@ -423,6 +450,10 @@ where }, ) } + + fn into_owned(self) -> Self::Owned { + self + } } #[derive(Debug)] diff --git a/leptos/src/into_view.rs b/leptos/src/into_view.rs index 20e8fa602a..5c660970de 100644 --- a/leptos/src/into_view.rs +++ b/leptos/src/into_view.rs @@ -1,11 +1,11 @@ use std::borrow::Cow; use tachys::{ - html::attribute::Attribute, + html::attribute::{any_attribute::AnyAttribute, Attribute}, hydration::Cursor, ssr::StreamBuilder, view::{ - add_attr::AddAnyAttr, Position, PositionState, Render, RenderHtml, - ToTemplate, + add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Position, PositionState, + Render, RenderHtml, ToTemplate, }, }; @@ -76,26 +76,34 @@ where impl Render for View { type State = T::State; - fn build(self) -> Self::State { - self.inner.build() + fn build(self, extra_attrs: Option>) -> Self::State { + self.inner.build(extra_attrs) } - fn rebuild(self, state: &mut Self::State) { - self.inner.rebuild(state) + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { + self.inner.rebuild(state, extra_attrs) } } impl RenderHtml for View { type AsyncOutput = T::AsyncOutput; + type Owned = View; const MIN_LENGTH: usize = ::MIN_LENGTH; - async fn resolve(self) -> Self::AsyncOutput { - self.inner.resolve().await + async fn resolve( + self, + extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { + self.inner.resolve(extra_attrs).await } - fn dry_resolve(&mut self) { - self.inner.dry_resolve(); + fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) { + self.inner.dry_resolve(extra_attrs); } fn to_html_with_buf( @@ -104,6 +112,7 @@ impl RenderHtml for View { position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { #[cfg(debug_assertions)] let vm = self.view_marker.to_owned(); @@ -112,8 +121,13 @@ impl RenderHtml for View { buf.push_str(&format!("")); } - self.inner - .to_html_with_buf(buf, position, escape, mark_branches); + self.inner.to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs, + ); #[cfg(debug_assertions)] if let Some(vm) = vm.as_ref() { @@ -127,6 +141,7 @@ impl RenderHtml for View { position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) where Self: Sized, { @@ -142,6 +157,7 @@ impl RenderHtml for View { position, escape, mark_branches, + extra_attrs, ); #[cfg(debug_assertions)] @@ -154,8 +170,18 @@ impl RenderHtml for View { self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { - self.inner.hydrate::(cursor, position) + self.inner + .hydrate::(cursor, position, extra_attrs) + } + + fn into_owned(self) -> Self::Owned { + View { + inner: self.inner.into_owned(), + #[cfg(debug_assertions)] + view_marker: self.view_marker, + } } } diff --git a/leptos/src/mount.rs b/leptos/src/mount.rs index 526bd8d6ac..0ff36f463d 100644 --- a/leptos/src/mount.rs +++ b/leptos/src/mount.rs @@ -71,6 +71,7 @@ where view.hydrate::( &Cursor::new(parent.unchecked_into()), &PositionState::default(), + None, ) }); @@ -124,7 +125,7 @@ where let owner = Owner::new(); let mountable = owner.with(move || { let view = f().into_view(); - let mut mountable = view.build(); + let mut mountable = view.build(None); mountable.mount(&parent, None); mountable }); @@ -152,7 +153,7 @@ where let owner = Owner::new(); let mountable = owner.with(move || { let view = f(); - let mut mountable = view.build(); + let mut mountable = view.build(None); mountable.mount(parent, None); mountable }); diff --git a/leptos/src/suspense_component.rs b/leptos/src/suspense_component.rs index 51c47e5f41..3b677084bb 100644 --- a/leptos/src/suspense_component.rs +++ b/leptos/src/suspense_component.rs @@ -19,12 +19,13 @@ use slotmap::{DefaultKey, SlotMap}; use std::sync::Arc; use tachys::{ either::Either, - html::attribute::Attribute, + html::attribute::{any_attribute::AnyAttribute, Attribute}, hydration::Cursor, reactive_graph::{OwnedView, OwnedViewState}, ssr::StreamBuilder, view::{ add_attr::AddAnyAttr, + any_view::ExtraAttrsMut, either::{EitherKeepAlive, EitherKeepAliveState}, Mountable, Position, PositionState, Render, RenderHtml, }, @@ -162,7 +163,7 @@ where OwnedViewState>, >; - fn build(self) -> Self::State { + fn build(self, extra_attrs: Option>) -> Self::State { let mut children = Some(self.children); let mut fallback = Some(self.fallback); let none_pending = self.none_pending; @@ -187,16 +188,20 @@ where ); if let Some(mut state) = prev { - this.rebuild(&mut state); + this.rebuild(&mut state, extra_attrs.clone()); state } else { - this.build() + this.build(extra_attrs.clone()) } }) } - fn rebuild(self, state: &mut Self::State) { - let new = self.build(); + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { + let new = self.build(extra_attrs); let mut old = std::mem::replace(state, new); old.insert_before_this(state); old.unmount(); @@ -247,12 +252,16 @@ where // i.e., if this is the child of another Suspense during SSR, don't wait for it: it will handle // itself type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = Chil::MIN_LENGTH; - fn dry_resolve(&mut self) {} + fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {} - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + _extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { self } @@ -262,9 +271,15 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { - self.fallback - .to_html_with_buf(buf, position, escape, mark_branches); + self.fallback.to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs, + ); } fn to_html_async_with_buf( @@ -273,6 +288,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + mut extra_attrs: Option>, ) where Self: Sized, { @@ -297,7 +313,8 @@ where provide_context(LocalResourceNotifier::from(local_tx)); // walk over the tree of children once to make sure that all resource loads are registered - self.children.dry_resolve(); + self.children + .dry_resolve(ExtraAttrsMut::from_owned(&mut extra_attrs)); // check the set of tasks to see if it is empty, now or later let eff = reactive_graph::effect::Effect::new_isomorphic({ @@ -313,7 +330,8 @@ where } }); - let mut fut = Box::pin(ScopedFuture::new(ErrorHookFuture::new( + let mut fut = Box::pin(ScopedFuture::new(ErrorHookFuture::new({ + let mut extra_attrs = extra_attrs.clone(); async move { // race the local resource notifier against the set of tasks // @@ -340,7 +358,7 @@ where // but in situations like a we actually // want to be able to 1) synchronously read a resource's value, but still 2) wait // for it to load before we render everything - let mut children = Box::pin(self.children.resolve().fuse()); + let mut children = Box::pin(self.children.resolve(ExtraAttrsMut::from_owned(&mut extra_attrs)).fuse()); // we continue racing the children against the "do we have any local // resources?" Future @@ -359,8 +377,8 @@ where } } } - }, - ))); + } + }))); match fut.as_mut().now_or_never() { Some(Some(resolved)) => { Either::::Right(resolved) @@ -369,6 +387,7 @@ where position, escape, mark_branches, + extra_attrs, ); } Some(None) => { @@ -378,6 +397,7 @@ where position, escape, mark_branches, + extra_attrs, ); } None => { @@ -391,12 +411,14 @@ where self.fallback, &mut fallback_position, mark_branches, + extra_attrs.clone(), ); buf.push_async_out_of_order_with_nonce( fut, position, mark_branches, nonce_or_not(), + extra_attrs, ); } else { buf.push_async({ @@ -412,6 +434,7 @@ where &mut position, escape, mark_branches, + extra_attrs, ); builder.finish().take_chunks() } @@ -426,6 +449,7 @@ where self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { let cursor = cursor.to_owned(); let position = position.to_owned(); @@ -454,13 +478,21 @@ where ); if let Some(mut state) = prev { - this.rebuild(&mut state); + this.rebuild(&mut state, extra_attrs.clone()); state } else { - this.hydrate::(&cursor, &position) + this.hydrate::( + &cursor, + &position, + extra_attrs.clone(), + ) } }) } + + fn into_owned(self) -> Self::Owned { + self + } } /// A wrapper that prevents [`Suspense`] from waiting for any resource reads that happen inside @@ -480,12 +512,16 @@ where { type State = T::State; - fn build(self) -> Self::State { - (self.0)().build() + fn build(self, extra_attrs: Option>) -> Self::State { + (self.0)().build(extra_attrs) } - fn rebuild(self, state: &mut Self::State) { - (self.0)().rebuild(state); + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { + (self.0)().rebuild(state, extra_attrs); } } @@ -513,12 +549,16 @@ where T: RenderHtml + 'static, { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = T::MIN_LENGTH; - fn dry_resolve(&mut self) {} + fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {} - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + _extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { self } @@ -528,8 +568,15 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { - (self.0)().to_html_with_buf(buf, position, escape, mark_branches); + (self.0)().to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs, + ); } fn to_html_async_with_buf( @@ -538,6 +585,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) where Self: Sized, { @@ -546,6 +594,7 @@ where position, escape, mark_branches, + extra_attrs, ); } @@ -553,7 +602,12 @@ where self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { - (self.0)().hydrate::(cursor, position) + (self.0)().hydrate::(cursor, position, extra_attrs) + } + + fn into_owned(self) -> Self::Owned { + self } } diff --git a/leptos_server/src/lib.rs b/leptos_server/src/lib.rs index fa23213b71..ac6b5212c6 100644 --- a/leptos_server/src/lib.rs +++ b/leptos_server/src/lib.rs @@ -79,12 +79,13 @@ mod view_implementations { use reactive_graph::traits::Read; use std::future::Future; use tachys::{ - html::attribute::Attribute, + html::attribute::{any_attribute::AnyAttribute, Attribute}, hydration::Cursor, reactive_graph::{RenderEffectState, Suspend, SuspendState}, ssr::StreamBuilder, view::{ - add_attr::AddAnyAttr, Position, PositionState, Render, RenderHtml, + add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Position, + PositionState, Render, RenderHtml, }, }; @@ -95,12 +96,17 @@ mod view_implementations { { type State = RenderEffectState>; - fn build(self) -> Self::State { - (move || Suspend::new(async move { self.await })).build() + fn build(self, extra_attrs: Option>) -> Self::State { + (move || Suspend::new(async move { self.await })).build(extra_attrs) } - fn rebuild(self, state: &mut Self::State) { - (move || Suspend::new(async move { self.await })).rebuild(state) + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { + (move || Suspend::new(async move { self.await })) + .rebuild(state, extra_attrs) } } @@ -135,15 +141,20 @@ mod view_implementations { Ser: Send + 'static, { type AsyncOutput = Option; + type Owned = Self; const MIN_LENGTH: usize = 0; - fn dry_resolve(&mut self) { + fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) { self.read(); } - fn resolve(self) -> impl Future + Send { - (move || Suspend::new(async move { self.await })).resolve() + fn resolve( + self, + extra_attrs: ExtraAttrsMut<'_>, + ) -> impl Future + Send { + (move || Suspend::new(async move { self.await })) + .resolve(extra_attrs) } fn to_html_with_buf( @@ -152,12 +163,14 @@ mod view_implementations { position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { (move || Suspend::new(async move { self.await })).to_html_with_buf( buf, position, escape, mark_branches, + extra_attrs, ); } @@ -167,6 +180,7 @@ mod view_implementations { position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) where Self: Sized, { @@ -176,6 +190,7 @@ mod view_implementations { position, escape, mark_branches, + extra_attrs, ); } @@ -183,9 +198,14 @@ mod view_implementations { self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { (move || Suspend::new(async move { self.await })) - .hydrate::(cursor, position) + .hydrate::(cursor, position, extra_attrs) + } + + fn into_owned(self) -> Self::Owned { + self } } } diff --git a/meta/src/body.rs b/meta/src/body.rs index b34fd190a8..2e745a7fbe 100644 --- a/meta/src/body.rs +++ b/meta/src/body.rs @@ -1,6 +1,9 @@ use crate::ServerMetaContext; use leptos::{ - attr::NextAttribute, + attr::{ + any_attribute::{AnyAttribute, AnyAttributeState}, + NextAttribute, + }, component, html, reactive::owner::use_context, tachys::{ @@ -8,8 +11,8 @@ use leptos::{ html::attribute::Attribute, hydration::Cursor, view::{ - add_attr::AddAnyAttr, Mountable, Position, PositionState, Render, - RenderHtml, + add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Mountable, Position, + PositionState, Render, RenderHtml, }, }, IntoView, @@ -58,6 +61,7 @@ where At: Attribute, { attributes: At::State, + extra_attrs: Option>, } impl Render for BodyView @@ -66,15 +70,27 @@ where { type State = BodyViewState; - fn build(self) -> Self::State { + fn build(self, extra_attrs: Option>) -> Self::State { let el = document().body().expect("there to be a element"); let attributes = self.attributes.build(&el); - - BodyViewState { attributes } + let extra_attrs = extra_attrs.map(|attrs| attrs.build(&el)); + BodyViewState { + attributes, + extra_attrs, + } } - fn rebuild(self, state: &mut Self::State) { + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { self.attributes.rebuild(&mut state.attributes); + if let (Some(extra_attrs), Some(extra_attr_states)) = + (extra_attrs, &mut state.extra_attrs) + { + extra_attrs.rebuild(extra_attr_states); + } } } @@ -103,17 +119,24 @@ where At: Attribute, { type AsyncOutput = BodyView; + type Owned = BodyView; const MIN_LENGTH: usize = At::MIN_LENGTH; - fn dry_resolve(&mut self) { + fn dry_resolve(&mut self, mut extra_attrs: ExtraAttrsMut<'_>) { self.attributes.dry_resolve(); + extra_attrs.iter_mut().for_each(Attribute::dry_resolve); } - async fn resolve(self) -> Self::AsyncOutput { - BodyView { - attributes: self.attributes.resolve().await, - } + async fn resolve( + self, + extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { + let (attributes, _) = futures::join!( + self.attributes.resolve(), + ExtraAttrsMut::resolve(extra_attrs) + ); + BodyView { attributes } } fn to_html_with_buf( @@ -122,10 +145,15 @@ where _position: &mut Position, _escape: bool, _mark_branches: bool, + extra_attrs: Option>, ) { if let Some(meta) = use_context::() { let mut buf = String::new(); - _ = html::attributes_to_html(self.attributes, &mut buf); + _ = html::attributes_to_html( + self.attributes, + extra_attrs, + &mut buf, + ); if !buf.is_empty() { _ = meta.body.send(buf); } @@ -136,11 +164,23 @@ where self, _cursor: &Cursor, _position: &PositionState, + extra_attrs: Option>, ) -> Self::State { let el = document().body().expect("there to be a element"); let attributes = self.attributes.hydrate::(&el); + let extra_attrs = + extra_attrs.map(|attrs| attrs.hydrate::(&el)); - BodyViewState { attributes } + BodyViewState { + attributes, + extra_attrs, + } + } + + fn into_owned(self) -> Self::Owned { + BodyView { + attributes: self.attributes.into_cloneable_owned(), + } } } diff --git a/meta/src/html.rs b/meta/src/html.rs index 8e45672164..52d5bec63b 100644 --- a/meta/src/html.rs +++ b/meta/src/html.rs @@ -1,6 +1,9 @@ use crate::ServerMetaContext; use leptos::{ - attr::NextAttribute, + attr::{ + any_attribute::{AnyAttribute, AnyAttributeState}, + NextAttribute, + }, component, html, reactive::owner::use_context, tachys::{ @@ -8,8 +11,8 @@ use leptos::{ html::attribute::Attribute, hydration::Cursor, view::{ - add_attr::AddAnyAttr, Mountable, Position, PositionState, Render, - RenderHtml, + add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Mountable, Position, + PositionState, Render, RenderHtml, }, }, IntoView, @@ -55,6 +58,7 @@ where At: Attribute, { attributes: At::State, + extra_attrs: Option>, } impl Render for HtmlView @@ -63,18 +67,33 @@ where { type State = HtmlViewState; - fn build(self) -> Self::State { + fn build(self, extra_attrs: Option>) -> Self::State { let el = document() .document_element() .expect("there to be a element"); let attributes = self.attributes.build(&el); + let extra_attrs = extra_attrs.map(|attrs| { + attrs.into_iter().map(|attr| attr.build(&el)).collect() + }); - HtmlViewState { attributes } + HtmlViewState { + attributes, + extra_attrs, + } } - fn rebuild(self, state: &mut Self::State) { + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { self.attributes.rebuild(&mut state.attributes); + if let (Some(extra_attrs), Some(extra_attr_states)) = + (extra_attrs, &mut state.extra_attrs) + { + extra_attrs.rebuild(extra_attr_states); + } } } @@ -103,17 +122,24 @@ where At: Attribute, { type AsyncOutput = HtmlView; + type Owned = HtmlView; const MIN_LENGTH: usize = At::MIN_LENGTH; - fn dry_resolve(&mut self) { + fn dry_resolve(&mut self, mut extra_attrs: ExtraAttrsMut<'_>) { self.attributes.dry_resolve(); + extra_attrs.iter_mut().for_each(Attribute::dry_resolve); } - async fn resolve(self) -> Self::AsyncOutput { - HtmlView { - attributes: self.attributes.resolve().await, - } + async fn resolve( + self, + extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { + let (attributes, _) = futures::join!( + self.attributes.resolve(), + ExtraAttrsMut::resolve(extra_attrs) + ); + HtmlView { attributes } } fn to_html_with_buf( @@ -122,10 +148,15 @@ where _position: &mut Position, _escape: bool, _mark_branches: bool, + extra_attrs: Option>, ) { if let Some(meta) = use_context::() { let mut buf = String::new(); - _ = html::attributes_to_html(self.attributes, &mut buf); + _ = html::attributes_to_html( + self.attributes, + extra_attrs, + &mut buf, + ); if !buf.is_empty() { _ = meta.html.send(buf); } @@ -136,14 +167,30 @@ where self, _cursor: &Cursor, _position: &PositionState, + extra_attrs: Option>, ) -> Self::State { let el = document() .document_element() .expect("there to be a element"); let attributes = self.attributes.hydrate::(&el); + let extra_attrs = extra_attrs.map(|attrs| { + attrs + .into_iter() + .map(|attr| attr.hydrate::(&el)) + .collect() + }); + + HtmlViewState { + attributes, + extra_attrs, + } + } - HtmlViewState { attributes } + fn into_owned(self) -> Self::Owned { + HtmlView { + attributes: self.attributes.into_cloneable_owned(), + } } } diff --git a/meta/src/lib.rs b/meta/src/lib.rs index 0aed8c463a..9c5b8bfd20 100644 --- a/meta/src/lib.rs +++ b/meta/src/lib.rs @@ -44,7 +44,7 @@ use futures::{Stream, StreamExt}; use leptos::{ - attr::NextAttribute, + attr::{any_attribute::AnyAttribute, NextAttribute}, component, logging::debug_warn, oco::Oco, @@ -57,8 +57,8 @@ use leptos::{ }, hydration::Cursor, view::{ - add_attr::AddAnyAttr, Mountable, Position, PositionState, Render, - RenderHtml, + add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Mountable, Position, + PositionState, Render, RenderHtml, }, }, IntoView, @@ -334,6 +334,7 @@ where &mut Position::NextChild, false, false, + None, ); _ = cx.elements.send(buf); // fails only if the receiver is already dropped } else { @@ -390,13 +391,17 @@ where { type State = RegisteredMetaTagState; - fn build(self) -> Self::State { - let state = self.el.unwrap().build(); + fn build(self, extra_attrs: Option>) -> Self::State { + let state = self.el.unwrap().build(extra_attrs); RegisteredMetaTagState { state } } - fn rebuild(self, state: &mut Self::State) { - self.el.unwrap().rebuild(&mut state.state); + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { + self.el.unwrap().rebuild(&mut state.state, extra_attrs); } } @@ -429,14 +434,18 @@ where Ch: RenderHtml + Send, { type AsyncOutput = Self; + type Owned = RegisteredMetaTag; const MIN_LENGTH: usize = 0; - fn dry_resolve(&mut self) { - self.el.dry_resolve() + fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) { + self.el.dry_resolve(extra_attrs) } - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + _extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { self // TODO? } @@ -446,6 +455,7 @@ where _position: &mut Position, _escape: bool, _mark_branches: bool, + _extra_attrs: Option>, ) { // meta tags are rendered into the buffer stored into the context // the value has already been taken out, when we're on the server @@ -455,6 +465,7 @@ where self, _cursor: &Cursor, _position: &PositionState, + extra_attrs: Option>, ) -> Self::State { let cursor = use_context::() .expect( @@ -465,9 +476,16 @@ where let state = self.el.unwrap().hydrate::( &cursor, &PositionState::new(Position::NextChild), + extra_attrs, ); RegisteredMetaTagState { state } } + + fn into_owned(self) -> Self::Owned { + RegisteredMetaTag { + el: self.el.map(|inner| inner.into_owned()), + } + } } impl Mountable for RegisteredMetaTagState @@ -520,9 +538,14 @@ struct MetaTagsView; impl Render for MetaTagsView { type State = (); - fn build(self) -> Self::State {} + fn build(self, _extra_attrs: Option>) -> Self::State {} - fn rebuild(self, _state: &mut Self::State) {} + fn rebuild( + self, + _state: &mut Self::State, + _extra_attrs: Option>, + ) { + } } impl AddAnyAttr for MetaTagsView { @@ -541,12 +564,16 @@ impl AddAnyAttr for MetaTagsView { impl RenderHtml for MetaTagsView { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = 0; - fn dry_resolve(&mut self) {} + fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {} - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + _extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { self } @@ -556,6 +583,7 @@ impl RenderHtml for MetaTagsView { _position: &mut Position, _escape: bool, _mark_branches: bool, + _extra_attrs: Option>, ) { buf.push_str(""); } @@ -564,8 +592,13 @@ impl RenderHtml for MetaTagsView { self, _cursor: &Cursor, _position: &PositionState, + _extra_attrs: Option>, ) -> Self::State { } + + fn into_owned(self) -> Self::Owned { + self + } } pub(crate) trait OrDefaultNonce { diff --git a/meta/src/title.rs b/meta/src/title.rs index b19776ec89..358f44f4fc 100644 --- a/meta/src/title.rs +++ b/meta/src/title.rs @@ -1,6 +1,6 @@ use crate::{use_head, MetaContext, ServerMetaContext}; use leptos::{ - attr::Attribute, + attr::{any_attribute::AnyAttribute, Attribute}, component, oco::Oco, reactive::{ @@ -11,8 +11,8 @@ use leptos::{ dom::document, hydration::Cursor, view::{ - add_attr::AddAnyAttr, Mountable, Position, PositionState, Render, - RenderHtml, + add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Mountable, Position, + PositionState, Render, RenderHtml, }, }, text_prop::TextProp, @@ -189,7 +189,7 @@ struct TitleViewState { impl Render for TitleView { type State = TitleViewState; - fn build(mut self) -> Self::State { + fn build(mut self, _extra_attrs: Option>) -> Self::State { let el = self.el(); let meta = self.meta; if let Some(formatter) = self.formatter.take() { @@ -213,8 +213,12 @@ impl Render for TitleView { TitleViewState { effect } } - fn rebuild(self, state: &mut Self::State) { - *state = self.build(); + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { + *state = self.build(extra_attrs); } } @@ -234,12 +238,16 @@ impl AddAnyAttr for TitleView { impl RenderHtml for TitleView { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = 0; - fn dry_resolve(&mut self) {} + fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {} - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + _extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { self } @@ -249,6 +257,7 @@ impl RenderHtml for TitleView { _position: &mut Position, _escape: bool, _mark_branches: bool, + _extra_attrs: Option>, ) { // meta tags are rendered into the buffer stored into the context // the value has already been taken out, when we're on the server @@ -258,6 +267,7 @@ impl RenderHtml for TitleView { mut self, _cursor: &Cursor, _position: &PositionState, + _extra_attrs: Option>, ) -> Self::State { let el = self.el(); let meta = self.meta; @@ -282,6 +292,10 @@ impl RenderHtml for TitleView { }); TitleViewState { effect } } + + fn into_owned(self) -> Self::Owned { + self + } } impl Mountable for TitleViewState { diff --git a/router/src/flat_router.rs b/router/src/flat_router.rs index 0f17019e5f..80c7f8a375 100644 --- a/router/src/flat_router.rs +++ b/router/src/flat_router.rs @@ -10,6 +10,7 @@ use crate::{ use any_spawner::Executor; use either_of::Either; use futures::FutureExt; +use leptos::attr::any_attribute::AnyAttribute; use reactive_graph::{ computed::{ArcMemo, ScopedFuture}, owner::{provide_context, Owner}, @@ -25,7 +26,7 @@ use tachys::{ ssr::StreamBuilder, view::{ add_attr::AddAnyAttr, - any_view::{AnyView, AnyViewState, IntoAny}, + any_view::{AnyView, AnyViewState, ExtraAttrsMut, IntoAny}, Mountable, Position, PositionState, Render, RenderHtml, }, }; @@ -79,7 +80,7 @@ where { type State = Rc>; - fn build(self) -> Self::State { + fn build(self, extra_attrs: Option>) -> Self::State { let FlatRoutesView { current_url, routes, @@ -117,7 +118,7 @@ where match new_match { None => Rc::new(RefCell::new(FlatRoutesViewState { - view: fallback().into_any().build(), + view: fallback().into_any().build(extra_attrs), id, owner, params, @@ -150,7 +151,7 @@ where match view.as_mut().now_or_never() { Some(view) => Rc::new(RefCell::new(FlatRoutesViewState { - view: view.into_any().build(), + view: view.into_any().build(extra_attrs), id, owner, params, @@ -161,7 +162,7 @@ where None => { let state = Rc::new(RefCell::new(FlatRoutesViewState { - view: ().into_any().build(), + view: ().into_any().build(extra_attrs.clone()), id, owner, params, @@ -174,8 +175,10 @@ where let state = Rc::clone(&state); async move { let view = view.await; - view.into_any() - .rebuild(&mut state.borrow_mut().view); + view.into_any().rebuild( + &mut state.borrow_mut().view, + extra_attrs, + ); } }); @@ -186,7 +189,11 @@ where } } - fn rebuild(self, state: &mut Self::State) { + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { let FlatRoutesView { current_url, location, @@ -264,7 +271,9 @@ where provide_context(url); provide_context(params_memo); provide_context(Matched(ArcMemo::from(new_matched))); - fallback().into_any().rebuild(&mut state.borrow_mut().view) + fallback() + .into_any() + .rebuild(&mut state.borrow_mut().view, extra_attrs) }); if let Some(location) = location { location.ready_to_complete(); @@ -314,8 +323,10 @@ where == spawned_path { let rebuild = move || { - view.into_any() - .rebuild(&mut state.borrow_mut().view); + view.into_any().rebuild( + &mut state.borrow_mut().view, + extra_attrs, + ); }; if transition { start_view_transition(0, is_back, rebuild); @@ -343,7 +354,7 @@ impl AddAnyAttr for FlatRoutesView where Loc: LocationProvider + Send, Defs: MatchNestedRoutes + Send + 'static, - FalFn: FnOnce() -> Fal + Send, + FalFn: FnOnce() -> Fal + Send + 'static, Fal: RenderHtml + 'static, { type Output = @@ -416,16 +427,20 @@ impl RenderHtml for FlatRoutesView where Loc: LocationProvider + Send, Defs: MatchNestedRoutes + Send + 'static, - FalFn: FnOnce() -> Fal + Send, + FalFn: FnOnce() -> Fal + Send + 'static, Fal: RenderHtml + 'static, { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = as RenderHtml>::MIN_LENGTH; - fn dry_resolve(&mut self) {} + fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {} - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + _extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { self } @@ -435,6 +450,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { // if this is being run on the server for the first time, generating all possible routes if RouteList::is_generating() { @@ -481,7 +497,13 @@ where RouteList::register(RouteList::from(routes)); } else { let view = self.choose_ssr(); - view.to_html_with_buf(buf, position, escape, mark_branches); + view.to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs, + ); } } @@ -491,6 +513,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) where Self: Sized, { @@ -500,6 +523,7 @@ where position, escape, mark_branches, + extra_attrs, ) } @@ -507,6 +531,7 @@ where self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { // this can be mostly the same as the build() implementation, but with hydrate() // @@ -551,9 +576,11 @@ where match new_match { None => Rc::new(RefCell::new(FlatRoutesViewState { - view: fallback() - .into_any() - .hydrate::(cursor, position), + view: fallback().into_any().hydrate::( + cursor, + position, + extra_attrs, + ), id, owner, params, @@ -586,9 +613,11 @@ where match view.as_mut().now_or_never() { Some(view) => Rc::new(RefCell::new(FlatRoutesViewState { - view: view - .into_any() - .hydrate::(cursor, position), + view: view.into_any().hydrate::( + cursor, + position, + extra_attrs, + ), id, owner, params, @@ -604,4 +633,8 @@ where } } } + + fn into_owned(self) -> Self::Owned { + self + } } diff --git a/router/src/nested_router.rs b/router/src/nested_router.rs index 0564055958..70844f4a76 100644 --- a/router/src/nested_router.rs +++ b/router/src/nested_router.rs @@ -10,7 +10,7 @@ use crate::{ use any_spawner::Executor; use either_of::{Either, EitherOf3}; use futures::{channel::oneshot, future::join_all, FutureExt}; -use leptos::{component, oco::Oco}; +use leptos::{attr::any_attribute::AnyAttribute, component, oco::Oco}; use or_poisoned::OrPoisoned; use reactive_graph::{ computed::{ArcMemo, ScopedFuture}, @@ -36,7 +36,7 @@ use tachys::{ ssr::StreamBuilder, view::{ add_attr::AddAnyAttr, - any_view::{AnyView, IntoAny}, + any_view::{AnyView, ExtraAttrsMut, IntoAny}, either::EitherOf3State, Mountable, Position, PositionState, Render, RenderHtml, }, @@ -76,7 +76,7 @@ where // TODO support fallback while loading type State = NestedRouteViewState; - fn build(self) -> Self::State { + fn build(self, extra_attrs: Option>) -> Self::State { let NestedRoutesView { routes, outer_owner, @@ -95,7 +95,7 @@ where let new_match = routes.match_route(url.path()); // start with an empty view because we'll be loading routes async - let view = EitherOf3::A(()).build(); + let view = EitherOf3::A(()).build(extra_attrs.clone()); let view = Rc::new(RefCell::new(view)); let matched_view = match new_match { None => EitherOf3::B(fallback()), @@ -120,7 +120,7 @@ where for trigger in triggers { trigger.notify(); } - matched_view.rebuild(&mut *view.borrow_mut()); + matched_view.rebuild(&mut *view.borrow_mut(), extra_attrs); }) }); @@ -132,7 +132,11 @@ where } } - fn rebuild(self, state: &mut Self::State) { + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { let url_snapshot = self.current_url.get_untracked(); // if the path is the same, we do not need to re-route @@ -154,7 +158,7 @@ where match new_match { None => { EitherOf3::<(), Fal, AnyView>::B((self.fallback)()) - .rebuild(&mut state.view.borrow_mut()); + .rebuild(&mut state.view.borrow_mut(), extra_attrs); state.outlets.clear(); if let Some(loc) = self.location { loc.ready_to_complete(); @@ -213,7 +217,10 @@ where 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()); + .rebuild( + &mut *state.view.borrow_mut(), + extra_attrs, + ); }) } } @@ -228,8 +235,8 @@ where impl AddAnyAttr for NestedRoutesView where Loc: LocationProvider + Send, - Defs: MatchNestedRoutes + Send, - FalFn: FnOnce() -> Fal + Send, + Defs: MatchNestedRoutes + Send + 'static, + FalFn: FnOnce() -> Fal + Send + 'static, Fal: RenderHtml + 'static, { type Output = @@ -249,17 +256,21 @@ where impl RenderHtml for NestedRoutesView where Loc: LocationProvider + Send, - Defs: MatchNestedRoutes + Send, - FalFn: FnOnce() -> Fal + Send, + Defs: MatchNestedRoutes + Send + 'static, + FalFn: FnOnce() -> Fal + Send + 'static, Fal: RenderHtml + 'static, { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = 0; // TODO - fn dry_resolve(&mut self) {} + fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {} - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + _extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { self } @@ -269,6 +280,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { // if this is being run on the server for the first time, generating all possible routes if RouteList::is_generating() { @@ -348,7 +360,13 @@ where outer_owner.with(|| Either::Right(Outlet().into_any())) } }; - view.to_html_with_buf(buf, position, escape, mark_branches); + view.to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs, + ); } } @@ -358,6 +376,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) where Self: Sized, { @@ -400,6 +419,7 @@ where position, escape, mark_branches, + extra_attrs, ); } @@ -407,6 +427,7 @@ where self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { let NestedRoutesView { routes, @@ -446,7 +467,7 @@ where outer_owner.with(|| EitherOf3::C(Outlet().into_any())) } } - .hydrate::(cursor, position), + .hydrate::(cursor, position, extra_attrs), )); NestedRouteViewState { @@ -456,6 +477,10 @@ where view, } } + + fn into_owned(self) -> Self::Owned { + self + } } type OutletViewFn = Box Suspend + Send>; diff --git a/router/src/reactive.rs b/router/src/reactive.rs index ad7435675e..41949c76d6 100644 --- a/router/src/reactive.rs +++ b/router/src/reactive.rs @@ -89,7 +89,7 @@ where type State = ReactiveRouterInnerState; - fn build(self) -> Self::State { + fn build(self, _extra_attrs: Option>) -> Self::State { let (prev_id, inner) = self.inner.fallback_or_view(); let owner = self.owner.with(Owner::new); ReactiveRouterInnerState { @@ -100,7 +100,11 @@ where } } - fn rebuild(self, state: &mut Self::State) { + fn rebuild( + self, + state: &mut Self::State, + _extra_attrs: Option>, + ) { let (new_id, view) = self.inner.fallback_or_view(); if new_id != state.prev_id { state.owner = self.owner.with(Owner::new) @@ -130,6 +134,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + _extra_attrs: Option>, ) { // if this is being run on the server for the first time, generating all possible routes if RouteList::is_generating() { @@ -156,6 +161,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + _extra_attrs: Option>, ) where Self: Sized, { @@ -169,6 +175,7 @@ where self, cursor: &Cursor, position: &PositionState, + _extra_attrs: Option>, ) -> Self::State { let (prev_id, inner) = self.inner.fallback_or_view(); let owner = self.owner.with(Owner::new); @@ -280,7 +287,7 @@ where { type State = ReactiveRouteState; - fn build(self) -> Self::State { + fn build(self, extra_attrs: Option>) -> Self::State { let MatchedRoute { search_params, params, @@ -291,14 +298,19 @@ where params: ArcRwSignal::new(params), matched: ArcRwSignal::new(matched), }; - let view_state = untrack(|| (self.view_fn)(&matched).build()); + let view_state = + untrack(|| (self.view_fn)(&matched).build(extra_attrs.clone())); ReactiveRouteState { matched, view_state, } } - fn rebuild(mut self, state: &mut Self::State) { + fn rebuild( + mut self, + state: &mut Self::State, + _extra_attrs: Option>, + ) { let ReactiveRouteState { matched, .. } = state; matched .search_params @@ -324,6 +336,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { let MatchedRoute { search_params, @@ -336,7 +349,12 @@ where matched: ArcRwSignal::new(matched), }; untrack(|| { - (self.view_fn)(&matched).to_html_with_buf(buf, position, escape) + (self.view_fn)(&matched).to_html_with_buf( + buf, + position, + escape, + extra_attrs.clone(), + ) }); } @@ -346,6 +364,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) where Self: Sized, { @@ -360,8 +379,12 @@ where matched: ArcRwSignal::new(matched), }; untrack(|| { - (self.view_fn)(&matched) - .to_html_async_with_buf::(buf, position, escape) + (self.view_fn)(&matched).to_html_async_with_buf::( + buf, + position, + escape, + extra_attrs.clone(), + ) }); } @@ -369,6 +392,7 @@ where self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { let MatchedRoute { search_params, @@ -381,7 +405,11 @@ where matched: ArcRwSignal::new(matched), }; let view_state = untrack(|| { - (self.view_fn)(&matched).hydrate::(cursor, position) + (self.view_fn)(&matched).hydrate::( + cursor, + position, + extra_attrs.clone(), + ) }); ReactiveRouteState { matched, diff --git a/tachys/src/html/attribute/any_attribute.rs b/tachys/src/html/attribute/any_attribute.rs index d8a8c01cb9..33b58ff43f 100644 --- a/tachys/src/html/attribute/any_attribute.rs +++ b/tachys/src/html/attribute/any_attribute.rs @@ -1,3 +1,4 @@ +#![allow(unused_mut)] use super::{Attribute, NextAttribute}; use dyn_clone::DynClone; use std::{ @@ -35,6 +36,10 @@ pub struct AnyAttribute { type_id: TypeId, html_len: usize, value: Box, + + /// Temporary attribute set during the resolving cycle, to resolve only once. + pub(crate) resolved: bool, + #[cfg(feature = "ssr")] to_html: fn( Box, @@ -197,6 +202,7 @@ where type_id: TypeId::of::(), html_len: value.html_len(), value, + resolved: false, #[cfg(feature = "ssr")] to_html, build, @@ -303,10 +309,13 @@ impl Attribute for AnyAttribute { ); } - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve(mut self) -> Self::AsyncOutput { #[cfg(feature = "ssr")] { - (self.resolve)(self.value).await + let res = (self.resolve)(self.value).await; + // Used by batch_resolve_items_with_extra_attrs() for optimisations. + self.resolved = true; + res } #[cfg(not(feature = "ssr"))] panic!( diff --git a/tachys/src/html/element/element_ext.rs b/tachys/src/html/element/element_ext.rs index b259d7f9a1..230af1d174 100644 --- a/tachys/src/html/element/element_ext.rs +++ b/tachys/src/html/element/element_ext.rs @@ -24,7 +24,7 @@ use web_sys::Element; /// let view = element.on(ev::click, move |_| /* ... */); /// /// // `element` now contains the actual element -/// let element = element.build(); +/// let element = element.build(None); /// let remove = element.on(ev::blur, move |_| /* ... */); /// ``` pub trait ElementExt { diff --git a/tachys/src/html/element/mod.rs b/tachys/src/html/element/mod.rs index d97dd8bb07..23380ab8be 100644 --- a/tachys/src/html/element/mod.rs +++ b/tachys/src/html/element/mod.rs @@ -6,14 +6,14 @@ use crate::{ renderer::{CastFrom, Rndr}, ssr::StreamBuilder, view::{ - add_attr::AddAnyAttr, IntoRender, Mountable, Position, PositionState, - Render, RenderHtml, ToTemplate, + add_attr::AddAnyAttr, any_view::ExtraAttrsMut, IntoRender, Mountable, + Position, PositionState, Render, RenderHtml, ToTemplate, }, }; use const_str_slice_concat::{ const_concat, const_concat_with_prefix, str_from_buffer, }; -use futures::future::join; +use futures::future::join3; use next_tuple::NextTuple; use std::ops::Deref; @@ -21,7 +21,10 @@ mod custom; mod element_ext; mod elements; mod inner_html; -use super::attribute::{escape_attr, NextAttribute}; +use super::attribute::{ + any_attribute::{AnyAttribute, AnyAttributeState}, + escape_attr, NextAttribute, +}; pub use custom::*; pub use element_ext::*; pub use elements::*; @@ -183,31 +186,42 @@ where { type State = ElementState; - fn rebuild(self, state: &mut Self::State) { + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { let ElementState { attrs, children, .. } = state; self.attributes.rebuild(attrs); + if let (Some(extra_attrs), Some(extra_attr_states)) = + (extra_attrs, &mut state.extra_attrs) + { + extra_attrs.rebuild(extra_attr_states); + } if let Some(children) = children { - self.children.rebuild(children); + self.children.rebuild(children, None); } } - fn build(self) -> Self::State { + fn build(self, extra_attrs: Option>) -> Self::State { let el = Rndr::create_element(self.tag.tag(), E::NAMESPACE); let attrs = self.attributes.build(&el); + let extra_attrs = extra_attrs.map(|attrs| attrs.build(&el)); let children = if E::SELF_CLOSING { None } else { - let mut children = self.children.build(); + let mut children = self.children.build(None); children.mount(&el, None); Some(children) }; ElementState { el, - attrs, children, + attrs, + extra_attrs, } } } @@ -219,6 +233,7 @@ where Ch: RenderHtml + Send, { type AsyncOutput = HtmlElement; + type Owned = HtmlElement; const MIN_LENGTH: usize = if E::SELF_CLOSING { 3 // < ... /> @@ -233,14 +248,22 @@ where + E::TAG.len() }; - fn dry_resolve(&mut self) { + fn dry_resolve(&mut self, mut extra_attrs: ExtraAttrsMut<'_>) { self.attributes.dry_resolve(); - self.children.dry_resolve(); + extra_attrs.iter_mut().for_each(Attribute::dry_resolve); + self.children.dry_resolve(ExtraAttrsMut::default()); } - async fn resolve(self) -> Self::AsyncOutput { - let (attributes, children) = - join(self.attributes.resolve(), self.children.resolve()).await; + async fn resolve( + self, + extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { + let (attributes, _, children) = join3( + self.attributes.resolve(), + ExtraAttrsMut::resolve(extra_attrs), + self.children.resolve(ExtraAttrsMut::default()), + ) + .await; HtmlElement { #[cfg(any(debug_assertions, leptos_debuginfo))] defined_at: self.defined_at, @@ -250,7 +273,7 @@ where } } - fn html_len(&self) -> usize { + fn html_len(&self, extra_attrs: Option>) -> usize { if E::SELF_CLOSING { 3 // < ... /> + E::TAG.len() @@ -259,7 +282,10 @@ where 2 // < ... > + E::TAG.len() + self.attributes.html_len() - + self.children.html_len() + + extra_attrs.map(|attrs| { + attrs.into_iter().map(Attribute::html_len).sum::() + }).unwrap_or(0) + + self.children.html_len(None) + 3 // + E::TAG.len() } @@ -271,12 +297,13 @@ where position: &mut Position, _escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { // opening tag buf.push('<'); buf.push_str(self.tag.tag()); - let inner_html = attributes_to_html(self.attributes, buf); + let inner_html = attributes_to_html(self.attributes, extra_attrs, buf); buf.push('>'); @@ -291,6 +318,7 @@ where position, E::ESCAPE_CHILDREN, mark_branches, + None, ); } @@ -308,6 +336,7 @@ where position: &mut Position, _escape: bool, mark_branches: bool, + extra_attrs: Option>, ) where Self: Sized, { @@ -316,7 +345,8 @@ where buf.push('<'); buf.push_str(self.tag.tag()); - let inner_html = attributes_to_html(self.attributes, &mut buf); + let inner_html = + attributes_to_html(self.attributes, extra_attrs, &mut buf); buf.push('>'); buffer.push_sync(&buf); @@ -332,6 +362,7 @@ where position, E::ESCAPE_CHILDREN, mark_branches, + None, ); } @@ -349,6 +380,7 @@ where self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { #[cfg(any(debug_assertions, leptos_debuginfo))] { @@ -373,13 +405,15 @@ where }); let attrs = self.attributes.hydrate::(&el); + let extra_attrs = extra_attrs + .map(|attrs| Attribute::hydrate::(attrs, &el)); // hydrate children let children = if !Ch::EXISTS || !E::ESCAPE_CHILDREN { None } else { position.set(Position::FirstChild); - Some(self.children.hydrate::(cursor, position)) + Some(self.children.hydrate::(cursor, position, None)) }; // go to next sibling @@ -393,14 +427,29 @@ where ElementState { el, - attrs, children, + attrs, + extra_attrs, + } + } + + fn into_owned(self) -> Self::Owned { + HtmlElement { + #[cfg(any(debug_assertions, leptos_debuginfo))] + defined_at: self.defined_at, + tag: self.tag, + attributes: self.attributes.into_cloneable_owned(), + children: self.children.into_owned(), } } } /// Renders an [`Attribute`] (which can be one or more HTML attributes) into an HTML buffer. -pub fn attributes_to_html(attr: At, buf: &mut String) -> String +pub fn attributes_to_html( + attr: At, + extra_attrs: Option>, + buf: &mut String, +) -> String where At: Attribute, { @@ -419,6 +468,11 @@ where // inject regular attributes, and fill class and style attr.to_html(buf, &mut class, &mut style, &mut inner_html); + if let Some(extra_attrs) = extra_attrs { + for attr in extra_attrs { + attr.to_html(buf, &mut class, &mut style, &mut inner_html); + } + } if !class.is_empty() { buf.push(' '); @@ -439,8 +493,10 @@ where /// The retained view state for an HTML element. pub struct ElementState { pub(crate) el: crate::renderer::types::Element, - pub(crate) attrs: At, pub(crate) children: Option, + + attrs: At, + extra_attrs: Option>, } impl Deref for ElementState { @@ -583,7 +639,7 @@ mod tests { fn mock_dom_creates_element() { let el: HtmlElement = main().child(p().id("test").lang("en").child("Hello, world!")); - let el = el.build(); + let el = el.build(None); assert_eq!( el.el.to_debug_html(), "

Hello, world!

" @@ -597,7 +653,7 @@ mod tests { em().child("beautiful"), " world!", ))); - let el = el.build(); + let el = el.build(None); assert_eq!( el.el.to_debug_html(), "

Hello, beautiful world!

" diff --git a/tachys/src/html/islands.rs b/tachys/src/html/islands.rs index 8fa8950ff3..68e264b530 100644 --- a/tachys/src/html/islands.rs +++ b/tachys/src/html/islands.rs @@ -1,9 +1,11 @@ -use super::attribute::Attribute; +use super::attribute::{any_attribute::AnyAttribute, Attribute}; use crate::{ hydration::Cursor, prelude::{Render, RenderHtml}, ssr::StreamBuilder, - view::{add_attr::AddAnyAttr, Position, PositionState}, + view::{ + add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Position, PositionState, + }, }; /// An island of interactivity in an otherwise-inert HTML document. @@ -59,12 +61,16 @@ where { type State = View::State; - fn build(self) -> Self::State { - self.view.build() + fn build(self, extra_attrs: Option>) -> Self::State { + self.view.build(extra_attrs) } - fn rebuild(self, state: &mut Self::State) { - self.view.rebuild(state); + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { + self.view.rebuild(state, extra_attrs); } } @@ -100,6 +106,7 @@ where View: RenderHtml, { type AsyncOutput = Island; + type Owned = Island; const MIN_LENGTH: usize = ISLAND_TAG.len() * 2 + "<>".len() @@ -107,11 +114,14 @@ where + "data-component".len() + View::MIN_LENGTH; - fn dry_resolve(&mut self) { - self.view.dry_resolve() + fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) { + self.view.dry_resolve(extra_attrs) } - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { let Island { component, props_json, @@ -120,7 +130,7 @@ where Island { component, props_json, - view: view.resolve().await, + view: view.resolve(extra_attrs).await, } } @@ -130,10 +140,16 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { Self::open_tag(self.component, &self.props_json, buf); - self.view - .to_html_with_buf(buf, position, escape, mark_branches); + self.view.to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs, + ); Self::close_tag(buf); } @@ -143,6 +159,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) where Self: Sized, { @@ -157,6 +174,7 @@ where position, escape, mark_branches, + extra_attrs, ); // and insert the closing tag synchronously @@ -169,6 +187,7 @@ where self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { if position.get() == Position::FirstChild { cursor.child(); @@ -177,7 +196,16 @@ where } position.set(Position::FirstChild); - self.view.hydrate::(cursor, position) + self.view + .hydrate::(cursor, position, extra_attrs) + } + + fn into_owned(self) -> Self::Owned { + Island { + component: self.component, + props_json: self.props_json, + view: self.view.into_owned(), + } } } @@ -227,9 +255,14 @@ where { type State = (); - fn build(self) -> Self::State {} + fn build(self, _extra_attrs: Option>) -> Self::State {} - fn rebuild(self, _state: &mut Self::State) {} + fn rebuild( + self, + _state: &mut Self::State, + _extra_attrs: Option>, + ) { + } } impl AddAnyAttr for IslandChildren @@ -259,20 +292,24 @@ where View: RenderHtml, { type AsyncOutput = IslandChildren; + type Owned = IslandChildren; const MIN_LENGTH: usize = ISLAND_CHILDREN_TAG.len() * 2 + "<>".len() + "".len() + View::MIN_LENGTH; - fn dry_resolve(&mut self) { - self.view.dry_resolve() + fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) { + self.view.dry_resolve(extra_attrs) } - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { let IslandChildren { view, on_hydrate } = self; IslandChildren { - view: view.resolve().await, + view: view.resolve(extra_attrs).await, on_hydrate, } } @@ -283,10 +320,16 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { Self::open_tag(buf); - self.view - .to_html_with_buf(buf, position, escape, mark_branches); + self.view.to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs, + ); Self::close_tag(buf); } @@ -296,6 +339,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) where Self: Sized, { @@ -310,6 +354,7 @@ where position, escape, mark_branches, + extra_attrs, ); // and insert the closing tag synchronously @@ -322,6 +367,7 @@ where self, cursor: &Cursor, position: &PositionState, + _extra_attrs: Option>, ) -> Self::State { // island children aren't hydrated // we update the walk to pass over them @@ -356,4 +402,11 @@ where ); } } + + fn into_owned(self) -> Self::Owned { + IslandChildren { + view: self.view.into_owned(), + on_hydrate: self.on_hydrate, + } + } } diff --git a/tachys/src/html/mod.rs b/tachys/src/html/mod.rs index 67af1a5bd3..b820d8f5b2 100644 --- a/tachys/src/html/mod.rs +++ b/tachys/src/html/mod.rs @@ -7,8 +7,11 @@ use crate::{ dom::{Element, Node}, CastFrom, Rndr, }, - view::{Position, PositionState, Render, RenderHtml}, + view::{ + any_view::ExtraAttrsMut, Position, PositionState, Render, RenderHtml, + }, }; +use attribute::any_attribute::AnyAttribute; use std::borrow::Cow; /// Types for HTML attributes. @@ -43,21 +46,30 @@ pub fn doctype(value: &'static str) -> Doctype { impl Render for Doctype { type State = (); - fn build(self) -> Self::State {} + fn build(self, _extra_attrs: Option>) -> Self::State {} - fn rebuild(self, _state: &mut Self::State) {} + fn rebuild( + self, + _state: &mut Self::State, + _extra_attrs: Option>, + ) { + } } no_attrs!(Doctype); impl RenderHtml for Doctype { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = "".len(); - fn dry_resolve(&mut self) {} + fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {} - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + _extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { self } @@ -67,6 +79,7 @@ impl RenderHtml for Doctype { _position: &mut Position, _escape: bool, _mark_branches: bool, + _extra_attrs: Option>, ) { buf.push_str(">, ) -> Self::State { } + + fn into_owned(self) -> Self::Owned { + self + } } /// An element that contains no interactivity, and whose contents can be known at compile time. @@ -113,12 +131,16 @@ impl Mountable for InertElementState { impl Render for InertElement { type State = InertElementState; - fn build(self) -> Self::State { + fn build(self, _extra_attrs: Option>) -> Self::State { let el = Rndr::create_element_from_html(&self.html); InertElementState(self.html, el) } - fn rebuild(self, state: &mut Self::State) { + fn rebuild( + self, + state: &mut Self::State, + _extra_attrs: Option>, + ) { let InertElementState(prev, el) = state; if &self.html != prev { let mut new_el = Rndr::create_element_from_html(&self.html); @@ -149,16 +171,17 @@ impl AddAnyAttr for InertElement { impl RenderHtml for InertElement { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = 0; - fn html_len(&self) -> usize { + fn html_len(&self, _extra_attrs: Option>) -> usize { self.html.len() } - fn dry_resolve(&mut self) {} + fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {} - async fn resolve(self) -> Self { + async fn resolve(self, _extra_attrs: ExtraAttrsMut<'_>) -> Self { self } @@ -168,6 +191,7 @@ impl RenderHtml for InertElement { position: &mut Position, _escape: bool, _mark_branches: bool, + _extra_attrs: Option>, ) { buf.push_str(&self.html); *position = Position::NextChild; @@ -177,6 +201,7 @@ impl RenderHtml for InertElement { self, cursor: &Cursor, position: &PositionState, + _extra_attrs: Option>, ) -> Self::State { let curr_position = position.get(); if curr_position == Position::FirstChild { @@ -189,4 +214,8 @@ impl RenderHtml for InertElement { position.set(Position::NextChild); InertElementState(self.html, el) } + + fn into_owned(self) -> Self::Owned { + self + } } diff --git a/tachys/src/oco.rs b/tachys/src/oco.rs index 1a0eb54ba7..a5143e7c8a 100644 --- a/tachys/src/oco.rs +++ b/tachys/src/oco.rs @@ -1,10 +1,17 @@ use crate::{ - html::{attribute::AttributeValue, class::IntoClass, style::IntoStyle}, + html::{ + attribute::{any_attribute::AnyAttribute, AttributeValue}, + class::IntoClass, + style::IntoStyle, + }, hydration::Cursor, no_attrs, prelude::{Mountable, Render, RenderHtml}, renderer::Rndr, - view::{strings::StrState, Position, PositionState, ToTemplate}, + view::{ + any_view::ExtraAttrsMut, strings::StrState, Position, PositionState, + ToTemplate, + }, }; use oco_ref::Oco; @@ -17,12 +24,16 @@ pub struct OcoStrState { impl Render for Oco<'static, str> { type State = OcoStrState; - fn build(self) -> Self::State { + fn build(self, _extra_attrs: Option>) -> Self::State { let node = Rndr::create_text_node(&self); OcoStrState { node, str: self } } - fn rebuild(self, state: &mut Self::State) { + fn rebuild( + self, + state: &mut Self::State, + _extra_attrs: Option>, + ) { let OcoStrState { node, str } = state; if &self != str { Rndr::set_text(node, &self); @@ -35,12 +46,16 @@ no_attrs!(Oco<'static, str>); impl RenderHtml for Oco<'static, str> { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = 0; - fn dry_resolve(&mut self) {} + fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {} - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + _extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { self } @@ -50,6 +65,7 @@ impl RenderHtml for Oco<'static, str> { position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { <&str as RenderHtml>::to_html_with_buf( &self, @@ -57,6 +73,7 @@ impl RenderHtml for Oco<'static, str> { position, escape, mark_branches, + extra_attrs, ) } @@ -64,13 +81,21 @@ impl RenderHtml for Oco<'static, str> { self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { let this: &str = self.as_ref(); let StrState { node, .. } = <&str as RenderHtml>::hydrate::( - this, cursor, position, + this, + cursor, + position, + extra_attrs, ); OcoStrState { node, str: self } } + + fn into_owned(self) -> ::Owned { + self + } } impl ToTemplate for Oco<'static, str> { diff --git a/tachys/src/reactive_graph/class.rs b/tachys/src/reactive_graph/class.rs index 272cf52243..f5e51edd56 100644 --- a/tachys/src/reactive_graph/class.rs +++ b/tachys/src/reactive_graph/class.rs @@ -352,7 +352,7 @@ where }) } - fn rebuild(self, state: &mut Self::State) { + fn rebuild(self, state: &mut Self::State, _extra_attrs: Option>) { let (names, mut f) = self; let prev_value = state.take_value(); @@ -433,7 +433,7 @@ where ::build(self.deref().to_owned(), el) } - fn rebuild(self, state: &mut Self::State) { + fn rebuild(self, state: &mut Self::State, _extra_attrs: Option>) { ::rebuild(self.deref().to_owned(), state) } @@ -447,7 +447,7 @@ where fn dry_resolve(&mut self) {} - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve(self, _extra_attrs: Option>) -> Self::AsyncOutput { self } } @@ -489,7 +489,7 @@ where ) } - fn rebuild(self, state: &mut Self::State) { + fn rebuild(self, state: &mut Self::State, _extra_attrs: Option>) { <(&'static str, bool) as IntoClass>::rebuild( (self.0, *self.1.deref()), state, @@ -506,7 +506,7 @@ where fn dry_resolve(&mut self) {} - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve(self, _extra_attrs: Option>) -> Self::AsyncOutput { self } } @@ -901,7 +901,7 @@ where state } - fn rebuild(self, state: &mut Self::State) { + fn rebuild(self, state: &mut Self::State, _extra_attrs: Option>) { reactive_graph::spawn_local_scoped({ let state = Rc::clone(state); async move { @@ -925,7 +925,7 @@ where fn dry_resolve(&mut self) {} - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve(self, _extra_attrs: Option>) -> Self::AsyncOutput { self.inner.await } } diff --git a/tachys/src/reactive_graph/mod.rs b/tachys/src/reactive_graph/mod.rs index 072ea80f48..39513c44a9 100644 --- a/tachys/src/reactive_graph/mod.rs +++ b/tachys/src/reactive_graph/mod.rs @@ -1,11 +1,11 @@ use crate::{ - html::attribute::{Attribute, AttributeValue}, + html::attribute::{any_attribute::AnyAttribute, Attribute, AttributeValue}, hydration::Cursor, renderer::Rndr, ssr::StreamBuilder, view::{ - add_attr::AddAnyAttr, Mountable, Position, PositionState, Render, - RenderHtml, ToTemplate, + add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Mountable, Position, + PositionState, Render, RenderHtml, ToTemplate, }, }; use reactive_graph::effect::RenderEffect; @@ -57,7 +57,7 @@ where type State = RenderEffectState; #[track_caller] - fn build(mut self) -> Self::State { + fn build(mut self, extra_attrs: Option>) -> Self::State { let hook = throw_error::get_error_hook(); RenderEffect::new(move |prev| { let _guard = hook @@ -65,18 +65,22 @@ where .map(|h| throw_error::set_error_hook(Arc::clone(h))); let value = self.invoke(); if let Some(mut state) = prev { - value.rebuild(&mut state); + value.rebuild(&mut state, extra_attrs.clone()); state } else { - value.build() + value.build(extra_attrs.clone()) } }) .into() } #[track_caller] - fn rebuild(self, state: &mut Self::State) { - let new = self.build(); + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { + let new = self.build(extra_attrs); let mut old = std::mem::replace(state, new); old.insert_before_this(state); old.unmount(); @@ -128,18 +132,22 @@ where V::State: 'static, { type AsyncOutput = V::AsyncOutput; + type Owned = F; const MIN_LENGTH: usize = 0; - fn dry_resolve(&mut self) { - self.invoke().dry_resolve(); + fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) { + self.invoke().dry_resolve(extra_attrs); } - async fn resolve(mut self) -> Self::AsyncOutput { - self.invoke().resolve().await + async fn resolve( + mut self, + extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { + self.invoke().resolve(extra_attrs).await } - fn html_len(&self) -> usize { + fn html_len(&self, _extra_attrs: Option>) -> usize { V::MIN_LENGTH } @@ -149,9 +157,16 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { let value = self.invoke(); - value.to_html_with_buf(buf, position, escape, mark_branches) + value.to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs, + ) } fn to_html_async_with_buf( @@ -160,6 +175,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) where Self: Sized, { @@ -169,6 +185,7 @@ where position, escape, mark_branches, + extra_attrs, ); } @@ -176,6 +193,7 @@ where mut self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { let cursor = cursor.clone(); let position = position.clone(); @@ -186,14 +204,22 @@ where .map(|h| throw_error::set_error_hook(Arc::clone(h))); let value = self.invoke(); if let Some(mut state) = prev { - value.rebuild(&mut state); + value.rebuild(&mut state, extra_attrs.clone()); state } else { - value.hydrate::(&cursor, &position) + value.hydrate::( + &cursor, + &position, + extra_attrs.clone(), + ) } }) .into() } + + fn into_owned(self) -> Self::Owned { + self + } } impl AddAnyAttr for F @@ -507,12 +533,14 @@ where mod stable { use super::RenderEffectState; use crate::{ - html::attribute::{Attribute, AttributeValue}, + html::attribute::{ + any_attribute::AnyAttribute, Attribute, AttributeValue, + }, hydration::Cursor, ssr::StreamBuilder, view::{ - add_attr::AddAnyAttr, Mountable, Position, PositionState, Render, - RenderHtml, + add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Mountable, Position, + PositionState, Render, RenderHtml, }, }; #[allow(deprecated)] @@ -537,13 +565,20 @@ mod stable { type State = RenderEffectState; #[track_caller] - fn build(self) -> Self::State { - (move || self.get()).build() + fn build( + self, + extra_attrs: Option>, + ) -> Self::State { + (move || self.get()).build(extra_attrs) } #[track_caller] - fn rebuild(self, state: &mut Self::State) { - let new = self.build(); + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { + let new = self.build(extra_attrs); let mut old = std::mem::replace(state, new); old.insert_before_this(state); old.unmount(); @@ -576,20 +611,27 @@ mod stable { V::State: 'static, { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = 0; - fn dry_resolve(&mut self) { + fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) { if $dry_resolve { _ = self.get(); } } - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + _extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { self } - fn html_len(&self) -> usize { + fn html_len( + &self, + _extra_attrs: Option>, + ) -> usize { V::MIN_LENGTH } @@ -599,9 +641,16 @@ mod stable { position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { let value = self.get(); - value.to_html_with_buf(buf, position, escape, mark_branches) + value.to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs, + ) } fn to_html_async_with_buf( @@ -610,6 +659,7 @@ mod stable { position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) where Self: Sized, { @@ -619,6 +669,7 @@ mod stable { position, escape, mark_branches, + extra_attrs, ); } @@ -626,9 +677,17 @@ mod stable { self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { - (move || self.get()) - .hydrate::(cursor, position) + (move || self.get()).hydrate::( + cursor, + position, + extra_attrs, + ) + } + + fn into_owned(self) -> Self::Owned { + self } } @@ -705,13 +764,20 @@ mod stable { type State = RenderEffectState; #[track_caller] - fn build(self) -> Self::State { - (move || self.get()).build() + fn build( + self, + extra_attrs: Option>, + ) -> Self::State { + (move || self.get()).build(extra_attrs) } #[track_caller] - fn rebuild(self, state: &mut Self::State) { - let new = self.build(); + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { + let new = self.build(extra_attrs); let mut old = std::mem::replace(state, new); old.insert_before_this(state); old.unmount(); @@ -750,20 +816,27 @@ mod stable { V::State: 'static, { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = 0; - fn dry_resolve(&mut self) { + fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) { if $dry_resolve { _ = self.get(); } } - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + _extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { self } - fn html_len(&self) -> usize { + fn html_len( + &self, + _extra_attrs: Option>, + ) -> usize { V::MIN_LENGTH } @@ -773,9 +846,16 @@ mod stable { position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { let value = self.get(); - value.to_html_with_buf(buf, position, escape, mark_branches) + value.to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs, + ) } fn to_html_async_with_buf( @@ -784,6 +864,7 @@ mod stable { position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) where Self: Sized, { @@ -793,6 +874,7 @@ mod stable { position, escape, mark_branches, + extra_attrs, ); } @@ -800,9 +882,17 @@ mod stable { self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { - (move || self.get()) - .hydrate::(cursor, position) + (move || self.get()).hydrate::( + cursor, + position, + extra_attrs, + ) + } + + fn into_owned(self) -> Self::Owned { + self } } @@ -895,7 +985,7 @@ mod tests { let count = RwSignal::new(0); let app: HtmlElement<_, _, _, MockDom> = button((), move || count.get().to_string()); - let el = app.build(); + let el = app.build(None); assert_eq!(el.el.to_debug_html(), ""); rt.dispose(); } @@ -906,7 +996,7 @@ mod tests { let count = RwSignal::new(0); let app: HtmlElement<_, _, _, MockDom> = button((), move || count.get().to_string()); - let el = app.build(); + let el = app.build(None); assert_eq!(el.el.to_debug_html(), ""); count.set(1); assert_eq!(el.el.to_debug_html(), ""); @@ -924,7 +1014,7 @@ mod tests { ("Hello, my ", move || count.get().to_string(), " friends."), ), ); - let el = app.build(); + let el = app.build(None); assert_eq!( el.el.to_debug_html(), "
" diff --git a/tachys/src/reactive_graph/owned.rs b/tachys/src/reactive_graph/owned.rs index 3de49c67ec..4ec45b7048 100644 --- a/tachys/src/reactive_graph/owned.rs +++ b/tachys/src/reactive_graph/owned.rs @@ -1,9 +1,12 @@ use crate::{ - html::attribute::Attribute, + html::attribute::{any_attribute::AnyAttribute, Attribute}, hydration::Cursor, prelude::Mountable, ssr::StreamBuilder, - view::{add_attr::AddAnyAttr, Position, PositionState, Render, RenderHtml}, + view::{ + add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Position, PositionState, + Render, RenderHtml, + }, }; use reactive_graph::{computed::ScopedFuture, owner::Owner}; @@ -53,14 +56,18 @@ where { type State = OwnedViewState; - fn build(self) -> Self::State { - let state = self.owner.with(|| self.view.build()); + fn build(self, extra_attrs: Option>) -> Self::State { + let state = self.owner.with(|| self.view.build(extra_attrs)); OwnedViewState::new(state, self.owner) } - fn rebuild(self, state: &mut Self::State) { + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { let OwnedView { owner, view, .. } = self; - owner.with(|| view.rebuild(&mut state.state)); + owner.with(|| view.rebuild(&mut state.state, extra_attrs)); state.owner = owner; } } @@ -92,6 +99,7 @@ where { // TODO type AsyncOutput = OwnedView; + type Owned = OwnedView; const MIN_LENGTH: usize = T::MIN_LENGTH; @@ -101,10 +109,16 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { self.owner.with(|| { - self.view - .to_html_with_buf(buf, position, escape, mark_branches) + self.view.to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs, + ) }); } @@ -114,6 +128,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) where Self: Sized, { @@ -123,6 +138,7 @@ where position, escape, mark_branches, + extra_attrs, ) }); @@ -137,23 +153,39 @@ where self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { - let state = self - .owner - .with(|| self.view.hydrate::(cursor, position)); + let state = self.owner.with(|| { + self.view + .hydrate::(cursor, position, extra_attrs) + }); OwnedViewState::new(state, self.owner) } - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { let OwnedView { owner, view } = self; let view = owner - .with(|| ScopedFuture::new(async move { view.resolve().await })) + .with(|| { + ScopedFuture::new( + async move { view.resolve(extra_attrs).await }, + ) + }) .await; OwnedView { owner, view } } - fn dry_resolve(&mut self) { - self.owner.with(|| self.view.dry_resolve()); + fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) { + self.owner.with(|| self.view.dry_resolve(extra_attrs)); + } + + fn into_owned(self) -> Self::Owned { + OwnedView { + owner: self.owner, + view: self.view.into_owned(), + } } } diff --git a/tachys/src/reactive_graph/suspense.rs b/tachys/src/reactive_graph/suspense.rs index 43dc5e538b..b66fb780df 100644 --- a/tachys/src/reactive_graph/suspense.rs +++ b/tachys/src/reactive_graph/suspense.rs @@ -1,10 +1,10 @@ use crate::{ - html::attribute::Attribute, + html::attribute::{any_attribute::AnyAttribute, Attribute}, hydration::Cursor, ssr::StreamBuilder, view::{ - add_attr::AddAnyAttr, iterators::OptionState, Mountable, Position, - PositionState, Render, RenderHtml, + add_attr::AddAnyAttr, any_view::ExtraAttrsMut, iterators::OptionState, + Mountable, Position, PositionState, Render, RenderHtml, }, }; use any_spawner::Executor; @@ -169,7 +169,7 @@ where { type State = SuspendState; - fn build(self) -> Self::State { + fn build(self, extra_attrs: Option>) -> Self::State { let Self { subscriber, inner } = self; // create a Future that will be aborted on on_cleanup @@ -184,7 +184,7 @@ where // otherwise, start with the fallback let initial = fut.as_mut().now_or_never().and_then(Result::ok); let initially_pending = initial.is_none(); - let inner = Rc::new(RefCell::new(initial.build())); + let inner = Rc::new(RefCell::new(initial.build(extra_attrs.clone()))); // get a unique ID if there's a SuspenseContext let id = use_context::().map(|sc| sc.task_id()); @@ -205,7 +205,8 @@ where drop(id); if let Ok(value) = value { - Some(value).rebuild(&mut *state.borrow_mut()); + Some(value) + .rebuild(&mut *state.borrow_mut(), extra_attrs); } subscriber.forward(); @@ -218,7 +219,11 @@ where SuspendState { inner } } - fn rebuild(self, state: &mut Self::State) { + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { let Self { subscriber, inner } = self; // create a Future that will be aborted on on_cleanup @@ -248,7 +253,7 @@ where // has no parent Executor::tick().await; if let Ok(value) = value { - Some(value).rebuild(&mut *state.borrow_mut()); + Some(value).rebuild(&mut *state.borrow_mut(), extra_attrs); } subscriber.forward(); @@ -284,6 +289,7 @@ where T: RenderHtml + Sized + 'static, { type AsyncOutput = Option; + type Owned = Self; const MIN_LENGTH: usize = T::MIN_LENGTH; @@ -293,12 +299,19 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { // TODO wrap this with a Suspense as needed // currently this is just used for Routes, which creates a Suspend but never actually needs // it (because we don't lazy-load routes on the server) if let Some(inner) = self.inner.now_or_never() { - inner.to_html_with_buf(buf, position, escape, mark_branches); + inner.to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs, + ); } } @@ -308,6 +321,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) where Self: Sized, { @@ -318,6 +332,7 @@ where position, escape, mark_branches, + extra_attrs, ), None => { if use_context::().is_none() { @@ -342,6 +357,7 @@ where (), &mut fallback_position, mark_branches, + extra_attrs.clone(), ); // TODO in 0.8: this should include a nonce @@ -353,6 +369,7 @@ where fut, position, mark_branches, + extra_attrs, ); } else { buf.push_async({ @@ -365,6 +382,7 @@ where &mut position, escape, mark_branches, + extra_attrs, ); builder.finish().take_chunks() } @@ -381,6 +399,7 @@ where self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { let Self { subscriber, inner } = self; @@ -396,9 +415,11 @@ where // otherwise, start with the fallback let initial = fut.as_mut().now_or_never().and_then(Result::ok); let initially_pending = initial.is_none(); - let inner = Rc::new(RefCell::new( - initial.hydrate::(cursor, position), - )); + let inner = Rc::new(RefCell::new(initial.hydrate::( + cursor, + position, + extra_attrs.clone(), + ))); // get a unique ID if there's a SuspenseContext let id = use_context::().map(|sc| sc.task_id()); @@ -419,7 +440,8 @@ where drop(id); if let Ok(value) = value { - Some(value).rebuild(&mut *state.borrow_mut()); + Some(value) + .rebuild(&mut *state.borrow_mut(), extra_attrs); } subscriber.forward(); @@ -432,11 +454,14 @@ where SuspendState { inner } } - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + _extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { Some(self.inner.await) } - fn dry_resolve(&mut self) { + fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) { // this is a little crazy, but if a Suspend is immediately available, we end up // with a situation where polling it multiple times (here in dry_resolve and then in // resolve) causes a runtime panic @@ -456,4 +481,8 @@ where as Pin + Send>>; } } + + fn into_owned(self) -> Self::Owned { + self + } } diff --git a/tachys/src/ssr/mod.rs b/tachys/src/ssr/mod.rs index 43c8c5da30..8476ac6bd7 100644 --- a/tachys/src/ssr/mod.rs +++ b/tachys/src/ssr/mod.rs @@ -1,4 +1,7 @@ -use crate::view::{Position, RenderHtml}; +use crate::{ + html::attribute::any_attribute::AnyAttribute, + view::{Position, RenderHtml}, +}; use futures::Stream; use std::{ collections::VecDeque, @@ -103,6 +106,7 @@ impl StreamBuilder { fallback: View, position: &mut Position, mark_branches: bool, + extra_attrs: Option>, ) where View: RenderHtml, { @@ -112,6 +116,7 @@ impl StreamBuilder { position, true, mark_branches, + extra_attrs, ); self.write_chunk_marker(false); *position = Position::NextChild; @@ -160,6 +165,7 @@ impl StreamBuilder { view: impl Future> + Send + 'static, position: &mut Position, mark_branches: bool, + extra_attrs: Option>, ) where View: RenderHtml, { @@ -168,6 +174,7 @@ impl StreamBuilder { position, mark_branches, None, + extra_attrs, ); } @@ -178,6 +185,7 @@ impl StreamBuilder { position: &mut Position, mark_branches: bool, nonce: Option>, + extra_attrs: Option>, ) where View: RenderHtml, { @@ -207,6 +215,7 @@ impl StreamBuilder { &mut position, true, mark_branches, + extra_attrs, ); } let chunks = subbuilder.finish().take_chunks(); diff --git a/tachys/src/view/any_view.rs b/tachys/src/view/any_view.rs index ef54649304..4af052a89a 100644 --- a/tachys/src/view/any_view.rs +++ b/tachys/src/view/any_view.rs @@ -1,3 +1,5 @@ +#![allow(unused_mut)] +#![allow(clippy::type_complexity)] #[cfg(feature = "ssr")] use super::MarkBranch; use super::{ @@ -5,7 +7,12 @@ use super::{ RenderHtml, }; use crate::{ - html::attribute::Attribute, hydration::Cursor, ssr::StreamBuilder, + html::attribute::{ + any_attribute::{AnyAttribute, IntoAnyAttribute}, + Attribute, + }, + hydration::Cursor, + ssr::StreamBuilder, }; use std::{ any::{Any, TypeId}, @@ -27,38 +34,60 @@ use std::{future::Future, pin::Pin}; pub struct AnyView { type_id: TypeId, value: Box, - build: fn(Box) -> AnyViewState, - rebuild: fn(TypeId, Box, &mut AnyViewState), - // Without erasure, tuples of attrs created by default cause too much type explosion to enable. - #[cfg(erase_components)] - add_any_attr: fn( - Box, - crate::html::attribute::any_attribute::AnyAttribute, - ) -> AnyView, + extra_attrs: Vec, + build: fn(Box, Option>) -> AnyViewState, + rebuild: fn(Box, &mut AnyViewState, Option>), // The fields below are cfg-gated so they will not be included in WASM bundles if not needed. // Ordinarily, the compiler can simply omit this dead code because the methods are not called. // With this type-erased wrapper, however, the compiler is not *always* able to correctly // eliminate that code. #[cfg(feature = "ssr")] - html_len: usize, + html_len: fn(&Box, Option>) -> usize, #[cfg(feature = "ssr")] - to_html: fn(Box, &mut String, &mut Position, bool, bool), + to_html: fn( + Box, + &mut String, + &mut Position, + bool, + bool, + Option>, + ), #[cfg(feature = "ssr")] - to_html_async: - fn(Box, &mut StreamBuilder, &mut Position, bool, bool), + to_html_async: fn( + Box, + &mut StreamBuilder, + &mut Position, + bool, + bool, + Option>, + ), #[cfg(feature = "ssr")] - to_html_async_ooo: - fn(Box, &mut StreamBuilder, &mut Position, bool, bool), + to_html_async_ooo: fn( + Box, + &mut StreamBuilder, + &mut Position, + bool, + bool, + Option>, + ), #[cfg(feature = "ssr")] #[allow(clippy::type_complexity)] - resolve: fn(Box) -> Pin + Send>>, + resolve: for<'a> fn( + Box, + ExtraAttrsMut<'a>, + ) + -> Pin + Send + 'a>>, #[cfg(feature = "ssr")] - dry_resolve: fn(&mut Box), + dry_resolve: fn(&mut Box, ExtraAttrsMut<'_>), #[cfg(feature = "hydrate")] #[cfg(feature = "hydrate")] #[allow(clippy::type_complexity)] - hydrate_from_server: - fn(Box, &Cursor, &PositionState) -> AnyViewState, + hydrate_from_server: fn( + Box, + &Cursor, + &PositionState, + Option>, + ) -> AnyViewState, } /// Retained view state for [`AnyView`]. @@ -128,50 +157,64 @@ where state.insert_before_this(child) } -impl IntoAny for T +#[cfg(feature = "ssr")] +fn resolve<'a, T>( + value: Box, + extra_attrs: ExtraAttrsMut<'a>, +) -> Pin + Send + 'a>> where - T: Send, T: RenderHtml + 'static, - T::State: 'static, { - fn into_any(self) -> AnyView { - #[cfg(feature = "ssr")] - let html_len = self.html_len(); + let value = value + .downcast::() + .expect("AnyView::resolve could not be downcast"); + Box::pin(async move { value.resolve(extra_attrs).await.into_any() }) +} - let value = Box::new(self) as Box; +impl IntoAny for T +where + T: RenderHtml, + T::Owned: Send, +{ + fn into_any(self) -> AnyView { + let value = Box::new(self.into_owned()) as Box; match value.downcast::() { // if it's already an AnyView, we don't need to double-wrap it Ok(any_view) => *any_view, Err(value) => { #[cfg(feature = "ssr")] - let dry_resolve = |value: &mut Box| { - let value = value - .downcast_mut::() - .expect("AnyView::resolve could not be downcast"); - value.dry_resolve(); - }; + let html_len = + |value: &Box, extra_attrs: Option>| { + let value = value + .downcast_ref::() + .expect("AnyView::html_len could not be downcast"); + value.html_len(extra_attrs) + }; #[cfg(feature = "ssr")] - let resolve = |value: Box| { - let value = value - .downcast::() - .expect("AnyView::resolve could not be downcast"); - Box::pin(async move { value.resolve().await.into_any() }) - as Pin + Send>> - }; + let dry_resolve = + |value: &mut Box, + extra_attrs: ExtraAttrsMut<'_>| { + let value = value + .downcast_mut::() + .expect("AnyView::resolve could not be downcast"); + value.dry_resolve(extra_attrs); + }; + #[cfg(feature = "ssr")] let to_html = |value: Box, buf: &mut String, position: &mut Position, escape: bool, - mark_branches: bool| { + mark_branches: bool, + extra_attrs: Option>| { let type_id = mark_branches - .then(|| format!("{:?}", TypeId::of::())) + .then(|| format!("{:?}", TypeId::of::())) .unwrap_or_default(); let value = value - .downcast::() + .downcast::() .expect("AnyView::to_html could not be downcast"); if mark_branches { buf.open_branch(&type_id); @@ -181,6 +224,7 @@ where position, escape, mark_branches, + extra_attrs, ); if mark_branches { buf.close_branch(&type_id); @@ -192,12 +236,13 @@ where buf: &mut StreamBuilder, position: &mut Position, escape: bool, - mark_branches: bool| { + mark_branches: bool, + extra_attrs: Option>| { let type_id = mark_branches - .then(|| format!("{:?}", TypeId::of::())) + .then(|| format!("{:?}", TypeId::of::())) .unwrap_or_default(); let value = value - .downcast::() + .downcast::() .expect("AnyView::to_html could not be downcast"); if mark_branches { buf.open_branch(&type_id); @@ -207,107 +252,97 @@ where position, escape, mark_branches, + extra_attrs, ); if mark_branches { buf.close_branch(&type_id); } }; #[cfg(feature = "ssr")] - let to_html_async_ooo = - |value: Box, - buf: &mut StreamBuilder, - position: &mut Position, - escape: bool, - mark_branches: bool| { - let value = value - .downcast::() - .expect("AnyView::to_html could not be downcast"); - value.to_html_async_with_buf::( - buf, - position, - escape, - mark_branches, - ); - }; - let build = |value: Box| { + let to_html_async_ooo = |value: Box, + buf: &mut StreamBuilder, + position: &mut Position, + escape: bool, + mark_branches: bool, + extra_attrs: Option< + Vec, + >| { let value = value - .downcast::() + .downcast::() + .expect("AnyView::to_html could not be downcast"); + value.to_html_async_with_buf::( + buf, + position, + escape, + mark_branches, + extra_attrs, + ); + }; + let build = |value: Box, extra_attrs: Option>| { + let value = value + .downcast::() .expect("AnyView::build couldn't downcast"); - let state = Box::new(value.build()); + let state = Box::new(value.build(extra_attrs)); AnyViewState { - type_id: TypeId::of::(), + type_id: TypeId::of::(), state, - mount: mount_any::, - unmount: unmount_any::, - insert_before_this: insert_before_this::, + mount: mount_any::, + unmount: unmount_any::, + insert_before_this: insert_before_this::, } }; #[cfg(feature = "hydrate")] - let hydrate_from_server = - |value: Box, - cursor: &Cursor, - position: &PositionState| { - let value = value.downcast::().expect( - "AnyView::hydrate_from_server couldn't downcast", - ); - let state = - Box::new(value.hydrate::(cursor, position)); + let hydrate_from_server = |value: Box, + cursor: &Cursor, + position: &PositionState, + extra_attrs: Option< + Vec, + >| { + let value = value.downcast::().expect( + "AnyView::hydrate_from_server couldn't downcast", + ); + let state = Box::new(value.hydrate::( + cursor, + position, + extra_attrs, + )); - AnyViewState { - type_id: TypeId::of::(), - state, + AnyViewState { + type_id: TypeId::of::(), + state, - mount: mount_any::, - unmount: unmount_any::, - insert_before_this: insert_before_this::, - } - }; + mount: mount_any::, + unmount: unmount_any::, + insert_before_this: insert_before_this::, + } + }; let rebuild = - |new_type_id: TypeId, - value: Box, - state: &mut AnyViewState| { + |value: Box, + state: &mut AnyViewState, extra_attrs: Option>| { let value = value - .downcast::() + .downcast::() .expect("AnyView::rebuild couldn't downcast value"); - if new_type_id == state.type_id { - let state = state.state.downcast_mut().expect( - "AnyView::rebuild couldn't downcast state", - ); - value.rebuild(state); - } else { - let mut new = value.into_any().build(); - state.insert_before_this(&mut new); - state.unmount(); - *state = new; - } + let state = state.state.downcast_mut().expect( + "AnyView::rebuild couldn't downcast state", + ); + value.rebuild(state, extra_attrs); }; - // Without erasure, tuples of attrs created by default cause too much type explosion to enable. - #[cfg(erase_components)] - let add_any_attr = |value: Box, attr: crate::html::attribute::any_attribute::AnyAttribute| { - let value = value - .downcast::() - .expect("AnyView::add_any_attr could not be downcast"); - value.add_any_attr(attr).into_any() - }; - AnyView { - type_id: TypeId::of::(), + type_id: TypeId::of::(), value, + extra_attrs: vec![], build, rebuild, - // Without erasure, tuples of attrs created by default cause too much type explosion to enable. - #[cfg(erase_components)] - add_any_attr, #[cfg(feature = "ssr")] - resolve, + html_len, #[cfg(feature = "ssr")] - dry_resolve, + resolve: resolve::, #[cfg(feature = "ssr")] - html_len, + dry_resolve, #[cfg(feature = "ssr")] to_html, #[cfg(feature = "ssr")] @@ -322,15 +357,144 @@ where } } +/// Ignore, this is a hack for pre use<..> syntax. +/// https://github.com/rust-lang/rfcs/blob/master/text/3498-lifetime-capture-rules-2024.md#the-captures-trick +pub trait __Captures {} +impl __Captures for U {} + +/// A mutable view into the extra attributes stored in an [`AnyView`]. +#[derive(Default)] +pub struct ExtraAttrsMut<'a>(Option>>); +impl<'a> ExtraAttrsMut<'a> { + /// Create a new mutable view from owned attributes. + pub fn from_owned(extra_attrs: &'a mut Option>) -> Self { + match extra_attrs { + Some(extra_attrs) => { + if extra_attrs.is_empty() { + Self(None) + } else { + Self(Some(vec![extra_attrs])) + } + } + None => Self(None), + } + } + + #[cfg(feature = "ssr")] + fn add_layer<'b>( + mut self, + extra_attrs: &'b mut Vec, + ) -> ExtraAttrsMut<'b> + where + 'a: 'b, + { + match (self.0, extra_attrs.is_empty()) { + (Some(mut extra), false) => { + extra.push(extra_attrs); + ExtraAttrsMut(Some(extra)) + } + (Some(mut extra), true) => { + self.0 = Some(extra); + self + } + (None, false) => ExtraAttrsMut(Some(vec![extra_attrs])), + (None, true) => ExtraAttrsMut(None), + } + } + + /// Check if there are any extra attributes. + pub fn is_some(&self) -> bool { + match &self.0 { + Some(extra) => extra.is_empty(), + None => true, + } + } + + /// "clone" the mutable view, to allow reuse in e.g. a for loop. + /// The same as .as_deref_mut() on Option<&mut T>. + pub fn as_deref_mut(&mut self) -> ExtraAttrsMut<'_> { + ExtraAttrsMut( + self.0 + .as_mut() + .map(|inner| inner.iter_mut().map(|v| &mut **v).collect()), + ) + } + + /// Iterate over the extra attributes. + pub fn iter_mut( + &mut self, + ) -> impl Iterator + __Captures<&'a ()> + '_ { + match &mut self.0 { + Some(inner) => itertools::Either::Left( + inner.iter_mut().flat_map(|v| v.iter_mut()), + ), + None => itertools::Either::Right(std::iter::empty()), + } + } + + /// Call [`RenderHtml::resolve`] on any extra attributes in parallel. + pub async fn resolve(self) { + if let Some(extra_attr_groups) = self.0 { + futures::future::join_all(extra_attr_groups.into_iter().map( + |extra_attrs| async move { + *extra_attrs = + Attribute::resolve(std::mem::take(extra_attrs)).await; + }, + )) + .await; + } + } +} + +fn combine_owned_extra_attrs( + parent_extra_attrs: Option>, + extra_attrs: Vec, +) -> Option> { + let extra_attrs = if let Some(mut parent_extra_attrs) = parent_extra_attrs { + for attr in extra_attrs { + parent_extra_attrs.push(attr); + } + parent_extra_attrs + } else { + extra_attrs + }; + if extra_attrs.is_empty() { + None + } else { + Some(extra_attrs) + } +} + impl Render for AnyView { type State = AnyViewState; - fn build(self) -> Self::State { - (self.build)(self.value) + fn build(self, extra_attrs: Option>) -> Self::State { + (self.build)( + self.value, + combine_owned_extra_attrs(extra_attrs, self.extra_attrs), + ) } - fn rebuild(self, state: &mut Self::State) { - (self.rebuild)(self.type_id, self.value, state) + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { + if self.type_id == state.type_id { + (self.rebuild)( + self.value, + state, + combine_owned_extra_attrs(extra_attrs, self.extra_attrs), + ) + } else { + let mut new = (self.build)( + self.value, + combine_owned_extra_attrs(extra_attrs, self.extra_attrs), + ); + state.insert_before_this(&mut new); + state.unmount(); + *state = new; + } } } @@ -339,52 +503,60 @@ impl AddAnyAttr for AnyView { #[allow(unused_variables)] fn add_any_attr( - self, + mut self, attr: NewAttr, ) -> Self::Output where Self::Output: RenderHtml, { - // Without erasure, tuples of attrs created by default cause too much type explosion to enable. - #[cfg(erase_components)] - { - use crate::html::attribute::any_attribute::IntoAnyAttribute; - - let attr = attr.into_cloneable_owned(); - (self.add_any_attr)(self.value, attr.into_any_attr()) - } - #[cfg(not(erase_components))] - { - self - } + self.extra_attrs + .push(attr.into_cloneable_owned().into_any_attr()); + self } } impl RenderHtml for AnyView { type AsyncOutput = Self; + type Owned = Self; - fn dry_resolve(&mut self) { + fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) { #[cfg(feature = "ssr")] { - (self.dry_resolve)(&mut self.value) + (self.dry_resolve)( + &mut self.value, + extra_attrs.add_layer(&mut self.extra_attrs), + ); } #[cfg(not(feature = "ssr"))] - panic!( - "You are rendering AnyView to HTML without the `ssr` feature \ - enabled." - ); + { + _ = extra_attrs; + panic!( + "You are rendering AnyView to HTML without the `ssr` feature \ + enabled." + ); + } } - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + mut self, + extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { #[cfg(feature = "ssr")] { - (self.resolve)(self.value).await + (self.resolve)( + self.value, + extra_attrs.add_layer(&mut self.extra_attrs), + ) + .await } #[cfg(not(feature = "ssr"))] - panic!( - "You are rendering AnyView to HTML without the `ssr` feature \ - enabled." - ); + { + _ = extra_attrs; + panic!( + "You are rendering AnyView to HTML without the `ssr` feature \ + enabled." + ); + } } const MIN_LENGTH: usize = 0; @@ -395,15 +567,26 @@ impl RenderHtml for AnyView { position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { #[cfg(feature = "ssr")] - (self.to_html)(self.value, buf, position, escape, mark_branches); + { + (self.to_html)( + self.value, + buf, + position, + escape, + mark_branches, + combine_owned_extra_attrs(extra_attrs, self.extra_attrs), + ); + } #[cfg(not(feature = "ssr"))] { _ = mark_branches; _ = buf; _ = position; _ = escape; + _ = extra_attrs; panic!( "You are rendering AnyView to HTML without the `ssr` feature \ enabled." @@ -417,26 +600,31 @@ impl RenderHtml for AnyView { position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) where Self: Sized, { #[cfg(feature = "ssr")] - if OUT_OF_ORDER { - (self.to_html_async_ooo)( - self.value, - buf, - position, - escape, - mark_branches, - ); - } else { - (self.to_html_async)( - self.value, - buf, - position, - escape, - mark_branches, - ); + { + if OUT_OF_ORDER { + (self.to_html_async_ooo)( + self.value, + buf, + position, + escape, + mark_branches, + combine_owned_extra_attrs(extra_attrs, self.extra_attrs), + ); + } else { + (self.to_html_async)( + self.value, + buf, + position, + escape, + mark_branches, + combine_owned_extra_attrs(extra_attrs, self.extra_attrs), + ); + } } #[cfg(not(feature = "ssr"))] { @@ -444,6 +632,7 @@ impl RenderHtml for AnyView { _ = position; _ = escape; _ = mark_branches; + _ = extra_attrs; panic!( "You are rendering AnyView to HTML without the `ssr` feature \ enabled." @@ -455,20 +644,29 @@ impl RenderHtml for AnyView { self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { #[cfg(feature = "hydrate")] - if FROM_SERVER { - (self.hydrate_from_server)(self.value, cursor, position) - } else { - panic!( - "hydrating AnyView from inside a ViewTemplate is not \ - supported." - ); + { + if FROM_SERVER { + (self.hydrate_from_server)( + self.value, + cursor, + position, + combine_owned_extra_attrs(extra_attrs, self.extra_attrs), + ) + } else { + panic!( + "hydrating AnyView from inside a ViewTemplate is not \ + supported." + ); + } } #[cfg(not(feature = "hydrate"))] { _ = cursor; _ = position; + _ = extra_attrs; panic!( "You are trying to hydrate AnyView without the `hydrate` \ feature enabled." @@ -476,16 +674,34 @@ impl RenderHtml for AnyView { } } - fn html_len(&self) -> usize { + fn html_len(&self, extra_attrs: Option>) -> usize { #[cfg(feature = "ssr")] { - self.html_len + (self.html_len)( + &self.value, + match (extra_attrs, self.extra_attrs.is_empty()) { + (Some(mut extra_attrs), false) => { + for attr in &self.extra_attrs { + extra_attrs.push(attr); + } + Some(extra_attrs) + } + (Some(extra_attrs), true) => Some(extra_attrs), + (None, false) => Some(self.extra_attrs.iter().collect()), + (None, true) => None, + }, + ) } #[cfg(not(feature = "ssr"))] { + _ = extra_attrs; 0 } } + + fn into_owned(self) -> Self::Owned { + self + } } impl Mountable for AnyViewState { diff --git a/tachys/src/view/either.rs b/tachys/src/view/either.rs index 885f2af42b..e4a91ece62 100644 --- a/tachys/src/view/either.rs +++ b/tachys/src/view/either.rs @@ -1,9 +1,11 @@ use super::{ - add_attr::AddAnyAttr, MarkBranch, Mountable, Position, PositionState, - Render, RenderHtml, + add_attr::AddAnyAttr, any_view::ExtraAttrsMut, MarkBranch, Mountable, + Position, PositionState, Render, RenderHtml, }; use crate::{ - html::attribute::Attribute, hydration::Cursor, ssr::StreamBuilder, + html::attribute::{any_attribute::AnyAttribute, Attribute}, + hydration::Cursor, + ssr::StreamBuilder, }; use either_of::*; use futures::future::join; @@ -15,29 +17,33 @@ where { type State = Either; - fn build(self) -> Self::State { + fn build(self, extra_attrs: Option>) -> Self::State { match self { - Either::Left(left) => Either::Left(left.build()), - Either::Right(right) => Either::Right(right.build()), + Either::Left(left) => Either::Left(left.build(extra_attrs)), + Either::Right(right) => Either::Right(right.build(extra_attrs)), } } - fn rebuild(self, state: &mut Self::State) { + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { match (self, &mut *state) { (Either::Left(new), Either::Left(old)) => { - new.rebuild(old); + new.rebuild(old, extra_attrs); } (Either::Right(new), Either::Right(old)) => { - new.rebuild(old); + new.rebuild(old, extra_attrs); } (Either::Right(new), Either::Left(old)) => { - let mut new_state = new.build(); + let mut new_state = new.build(extra_attrs); old.insert_before_this(&mut new_state); old.unmount(); *state = Either::Right(new_state); } (Either::Left(new), Either::Right(old)) => { - let mut new_state = new.build(); + let mut new_state = new.build(extra_attrs); old.insert_before_this(&mut new_state); old.unmount(); *state = Either::Left(new_state); @@ -120,28 +126,34 @@ where B: RenderHtml, { type AsyncOutput = Either; + type Owned = Either; - fn dry_resolve(&mut self) { + fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) { match self { - Either::Left(left) => left.dry_resolve(), - Either::Right(right) => right.dry_resolve(), + Either::Left(left) => left.dry_resolve(extra_attrs), + Either::Right(right) => right.dry_resolve(extra_attrs), } } - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { match self { - Either::Left(left) => Either::Left(left.resolve().await), - Either::Right(right) => Either::Right(right.resolve().await), + Either::Left(left) => Either::Left(left.resolve(extra_attrs).await), + Either::Right(right) => { + Either::Right(right.resolve(extra_attrs).await) + } } } const MIN_LENGTH: usize = max_usize(&[A::MIN_LENGTH, B::MIN_LENGTH]); #[inline(always)] - fn html_len(&self) -> usize { + fn html_len(&self, extra_attrs: Option>) -> usize { match self { - Either::Left(i) => i.html_len(), - Either::Right(i) => i.html_len(), + Either::Left(i) => i.html_len(extra_attrs), + Either::Right(i) => i.html_len(extra_attrs), } } @@ -151,13 +163,20 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { match self { Either::Left(left) => { if mark_branches { buf.open_branch("0"); } - left.to_html_with_buf(buf, position, escape, mark_branches); + left.to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs, + ); if mark_branches { buf.close_branch("0"); } @@ -166,7 +185,13 @@ where if mark_branches { buf.open_branch("1"); } - right.to_html_with_buf(buf, position, escape, mark_branches); + right.to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs, + ); if mark_branches { buf.close_branch("1"); } @@ -180,6 +205,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) where Self: Sized, { @@ -193,6 +219,7 @@ where position, escape, mark_branches, + extra_attrs, ); if mark_branches { buf.close_branch("0"); @@ -207,6 +234,7 @@ where position, escape, mark_branches, + extra_attrs, ); if mark_branches { buf.close_branch("1"); @@ -219,14 +247,24 @@ where self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { match self { - Either::Left(left) => { - Either::Left(left.hydrate::(cursor, position)) - } - Either::Right(right) => { - Either::Right(right.hydrate::(cursor, position)) - } + Either::Left(left) => Either::Left(left.hydrate::( + cursor, + position, + extra_attrs, + )), + Either::Right(right) => Either::Right( + right.hydrate::(cursor, position, extra_attrs), + ), + } + } + + fn into_owned(self) -> Self::Owned { + match self { + Either::Left(left) => Either::Left(left.into_owned()), + Either::Right(right) => Either::Right(right.into_owned()), } } } @@ -255,25 +293,29 @@ where { type State = EitherKeepAliveState; - fn build(self) -> Self::State { + fn build(self, extra_attrs: Option>) -> Self::State { let showing_b = self.show_b; - let a = self.a.map(Render::build); - let b = self.b.map(Render::build); + let a = self.a.map(|val| Render::build(val, extra_attrs.clone())); + let b = self.b.map(|val| Render::build(val, extra_attrs)); EitherKeepAliveState { a, b, showing_b } } - fn rebuild(self, state: &mut Self::State) { + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { // set or update A -- `None` just means "no change" match (self.a, &mut state.a) { - (Some(new), Some(old)) => new.rebuild(old), - (Some(new), None) => state.a = Some(new.build()), + (Some(new), Some(old)) => new.rebuild(old, extra_attrs.clone()), + (Some(new), None) => state.a = Some(new.build(extra_attrs.clone())), _ => {} } // set or update B match (self.b, &mut state.b) { - (Some(new), Some(old)) => new.rebuild(old), - (Some(new), None) => state.b = Some(new.build()), + (Some(new), Some(old)) => new.rebuild(old, extra_attrs), + (Some(new), None) => state.b = Some(new.build(extra_attrs)), _ => {} } @@ -333,35 +375,58 @@ where B: RenderHtml, { type AsyncOutput = EitherKeepAlive; + type Owned = EitherKeepAlive; const MIN_LENGTH: usize = 0; - fn dry_resolve(&mut self) { + fn dry_resolve(&mut self, mut extra_attrs: ExtraAttrsMut<'_>) { if let Some(inner) = &mut self.a { - inner.dry_resolve(); + inner.dry_resolve(extra_attrs.as_deref_mut()); } if let Some(inner) = &mut self.b { - inner.dry_resolve(); + inner.dry_resolve(extra_attrs); } } - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + mut extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { let EitherKeepAlive { a, b, show_b } = self; - let (a, b) = join( - async move { - match a { - Some(a) => Some(a.resolve().await), - None => None, - } - }, - async move { - match b { - Some(b) => Some(b.resolve().await), - None => None, - } - }, - ) - .await; + + // Has to be sequential if extra attrs are present: + let (a, b) = if extra_attrs.is_some() { + let a = match a { + Some(a) => Some(a.resolve(extra_attrs.as_deref_mut()).await), + None => None, + }; + let b = match b { + Some(b) => Some(b.resolve(extra_attrs.as_deref_mut()).await), + None => None, + }; + (a, b) + } else { + join( + async move { + match a { + Some(a) => { + Some(a.resolve(ExtraAttrsMut::default()).await) + } + None => None, + } + }, + async move { + match b { + Some(b) => { + Some(b.resolve(ExtraAttrsMut::default()).await) + } + None => None, + } + }, + ) + .await + }; + EitherKeepAlive { a, b, show_b } } @@ -371,15 +436,28 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { if self.show_b { self.b .expect("rendering B to HTML without filling it") - .to_html_with_buf(buf, position, escape, mark_branches); + .to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs, + ); } else { self.a .expect("rendering A to HTML without filling it") - .to_html_with_buf(buf, position, escape, mark_branches); + .to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs, + ); } } @@ -389,6 +467,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) where Self: Sized, { @@ -400,6 +479,7 @@ where position, escape, mark_branches, + extra_attrs, ); } else { self.a @@ -409,6 +489,7 @@ where position, escape, mark_branches, + extra_attrs, ); } } @@ -417,25 +498,34 @@ where self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { let showing_b = self.show_b; let a = self.a.map(|a| { if showing_b { - a.build() + a.build(extra_attrs.clone()) } else { - a.hydrate::(cursor, position) + a.hydrate::(cursor, position, extra_attrs.clone()) } }); let b = self.b.map(|b| { if showing_b { - b.hydrate::(cursor, position) + b.hydrate::(cursor, position, extra_attrs) } else { - b.build() + b.build(extra_attrs) } }); EitherKeepAliveState { showing_b, a, b } } + + fn into_owned(self) -> Self::Owned { + EitherKeepAlive { + a: self.a.map(|a| a.into_owned()), + b: self.b.map(|b| b.into_owned()), + show_b: self.show_b, + } + } } impl Mountable for EitherKeepAliveState @@ -535,20 +625,20 @@ macro_rules! tuples { type State = []<$($ty,)*>; - fn build(self) -> Self::State { + fn build(self, extra_attrs: Option>) -> Self::State { let state = match self { - $([]::$ty(this) => []::$ty(this.build()),)* + $([]::$ty(this) => []::$ty(this.build(extra_attrs)),)* }; Self::State { state } } - fn rebuild(self, state: &mut Self::State) { + fn rebuild(self, state: &mut Self::State, extra_attrs: Option>) { let new_state = match (self, &mut state.state) { // rebuild same state and return early - $(([]::$ty(new), []::$ty(old)) => { return new.rebuild(old); },)* + $(([]::$ty(new), []::$ty(old)) => { return new.rebuild(old, extra_attrs); },)* // or mount new state $(([]::$ty(new), _) => { - let mut new = new.build(); + let mut new = new.build(extra_attrs); state.insert_before_this(&mut new); []::$ty(new) },)* @@ -592,38 +682,39 @@ macro_rules! tuples { { type AsyncOutput = []<$($ty::AsyncOutput,)*>; + type Owned = []<$($ty::Owned,)*>; const MIN_LENGTH: usize = max_usize(&[$($ty ::MIN_LENGTH,)*]); - fn dry_resolve(&mut self) { + fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) { match self { $([]::$ty(this) => { - this.dry_resolve(); + this.dry_resolve(extra_attrs); })* } } - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve(self, extra_attrs: ExtraAttrsMut<'_>) -> Self::AsyncOutput { match self { - $([]::$ty(this) => []::$ty(this.resolve().await),)* + $([]::$ty(this) => []::$ty(this.resolve(extra_attrs).await),)* } } #[inline(always)] - fn html_len(&self) -> usize { + fn html_len(&self, extra_attrs: Option>) -> usize { match self { - $([]::$ty(i) => i.html_len(),)* + $([]::$ty(i) => i.html_len(extra_attrs),)* } } - fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool, mark_branches: bool) { + fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool, mark_branches: bool, extra_attrs: Option>) { match self { $([]::$ty(this) => { if mark_branches { buf.open_branch(stringify!($ty)); } - this.to_html_with_buf(buf, position, escape, mark_branches); + this.to_html_with_buf(buf, position, escape, mark_branches, extra_attrs); if mark_branches { buf.close_branch(stringify!($ty)); } @@ -633,7 +724,7 @@ macro_rules! tuples { fn to_html_async_with_buf( self, - buf: &mut StreamBuilder, position: &mut Position, escape: bool, mark_branches: bool) where + buf: &mut StreamBuilder, position: &mut Position, escape: bool, mark_branches: bool, extra_attrs: Option>) where Self: Sized, { match self { @@ -641,7 +732,7 @@ macro_rules! tuples { if mark_branches { buf.open_branch(stringify!($ty)); } - this.to_html_async_with_buf::(buf, position, escape, mark_branches); + this.to_html_async_with_buf::(buf, position, escape, mark_branches, extra_attrs); if mark_branches { buf.close_branch(stringify!($ty)); } @@ -653,15 +744,24 @@ macro_rules! tuples { self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { let state = match self { $([]::$ty(this) => { - []::$ty(this.hydrate::(cursor, position)) + []::$ty(this.hydrate::(cursor, position, extra_attrs)) })* }; Self::State { state } } + + fn into_owned(self) -> Self::Owned { + match self { + $([]::$ty(this) => { + []::$ty(this.into_owned()) + })* + } + } } } } diff --git a/tachys/src/view/error_boundary.rs b/tachys/src/view/error_boundary.rs index 2a9b0dd77b..e7ba019be9 100644 --- a/tachys/src/view/error_boundary.rs +++ b/tachys/src/view/error_boundary.rs @@ -1,6 +1,9 @@ -use super::{add_attr::AddAnyAttr, Position, PositionState, RenderHtml}; +use super::{ + add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Position, PositionState, + RenderHtml, +}; use crate::{ - html::attribute::Attribute, + html::attribute::{any_attribute::AnyAttribute, Attribute}, hydration::Cursor, ssr::StreamBuilder, view::{iterators::OptionState, Mountable, Render}, @@ -16,19 +19,23 @@ where { type State = ResultState; - fn build(self) -> Self::State { + fn build(self, extra_attrs: Option>) -> Self::State { let hook = throw_error::get_error_hook(); let (state, error) = match self { - Ok(view) => (Either::Left(view.build()), None), + Ok(view) => (Either::Left(view.build(extra_attrs)), None), Err(e) => ( - Either::Right(Render::build(())), + Either::Right(Render::build((), None)), Some(throw_error::throw(e.into())), ), }; ResultState { state, error, hook } } - fn rebuild(self, state: &mut Self::State) { + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { let _guard = state.hook.clone().map(throw_error::set_error_hook); match (&mut state.state, self) { // both errors: throw the new error and replace @@ -37,11 +44,11 @@ where } // both Ok: need to rebuild child (Either::Left(old), Ok(new)) => { - T::rebuild(new, old); + T::rebuild(new, old, extra_attrs); } // Ok => Err: unmount, replace with marker, and throw (Either::Left(old), Err(err)) => { - let mut new_state = Render::build(()); + let mut new_state = Render::build((), None); old.insert_before_this(&mut new_state); old.unmount(); state.state = Either::Right(new_state); @@ -52,7 +59,7 @@ where if let Some(err) = state.error.take() { throw_error::clear(&err); } - let mut new_state = new.build(); + let mut new_state = new.build(extra_attrs); old.insert_before_this(&mut new_state); old.unmount(); state.state = Either::Left(new_state); @@ -132,25 +139,29 @@ where E: Into + Send + 'static, { type AsyncOutput = Result; + type Owned = Result; const MIN_LENGTH: usize = T::MIN_LENGTH; - fn dry_resolve(&mut self) { + fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) { if let Ok(inner) = self.as_mut() { - inner.dry_resolve() + inner.dry_resolve(extra_attrs) } } - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { match self { - Ok(view) => Ok(view.resolve().await), + Ok(view) => Ok(view.resolve(extra_attrs).await), Err(e) => Err(e), } } - fn html_len(&self) -> usize { + fn html_len(&self, extra_attrs: Option>) -> usize { match self { - Ok(i) => i.html_len() + 3, + Ok(i) => i.html_len(extra_attrs) + 3, Err(_) => 0, } } @@ -161,11 +172,16 @@ where position: &mut super::Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { match self { - Ok(inner) => { - inner.to_html_with_buf(buf, position, escape, mark_branches) - } + Ok(inner) => inner.to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs, + ), Err(e) => { buf.push_str(""); throw_error::throw(e); @@ -179,6 +195,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) where Self: Sized, { @@ -188,6 +205,7 @@ where position, escape, mark_branches, + extra_attrs, ), Err(e) => { buf.push_sync(""); @@ -200,19 +218,35 @@ where self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { let hook = throw_error::get_error_hook(); let (state, error) = match self { Ok(view) => ( - Either::Left(view.hydrate::(cursor, position)), + Either::Left(view.hydrate::( + cursor, + position, + extra_attrs, + )), None, ), Err(e) => { - let state = - RenderHtml::hydrate::((), cursor, position); + let state = RenderHtml::hydrate::( + (), + cursor, + position, + extra_attrs, + ); (Either::Right(state), Some(throw_error::throw(e.into()))) } }; ResultState { state, error, hook } } + + fn into_owned(self) -> Self::Owned { + match self { + Ok(view) => Ok(view.into_owned()), + Err(e) => Err(e), + } + } } diff --git a/tachys/src/view/iterators.rs b/tachys/src/view/iterators.rs index c5041b2f7a..4a38748c6b 100644 --- a/tachys/src/view/iterators.rs +++ b/tachys/src/view/iterators.rs @@ -1,9 +1,12 @@ use super::{ - add_attr::AddAnyAttr, Mountable, Position, PositionState, Render, - RenderHtml, + add_attr::AddAnyAttr, any_view::ExtraAttrsMut, + batch_resolve_items_with_extra_attrs, Mountable, Position, PositionState, + Render, RenderHtml, }; use crate::{ - html::attribute::Attribute, hydration::Cursor, renderer::Rndr, + html::attribute::{any_attribute::AnyAttribute, Attribute}, + hydration::Cursor, + renderer::Rndr, ssr::StreamBuilder, }; use either_of::Either; @@ -18,20 +21,24 @@ where { type State = OptionState; - fn build(self) -> Self::State { + fn build(self, extra_attrs: Option>) -> Self::State { match self { Some(value) => Either::Left(value), None => Either::Right(()), } - .build() + .build(extra_attrs) } - fn rebuild(self, state: &mut Self::State) { + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { match self { Some(value) => Either::Left(value), None => Either::Right(()), } - .rebuild(state) + .rebuild(state, extra_attrs) } } @@ -58,25 +65,29 @@ where T: RenderHtml, { type AsyncOutput = Option; + type Owned = Option; const MIN_LENGTH: usize = T::MIN_LENGTH; - fn dry_resolve(&mut self) { + fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>) { if let Some(inner) = self.as_mut() { - inner.dry_resolve(); + inner.dry_resolve(extra_attrs); } } - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { match self { None => None, - Some(value) => Some(value.resolve().await), + Some(value) => Some(value.resolve(extra_attrs).await), } } - fn html_len(&self) -> usize { + fn html_len(&self, extra_attrs: Option>) -> usize { match self { - Some(i) => i.html_len() + 3, + Some(i) => i.html_len(extra_attrs) + 3, None => 3, } } @@ -87,12 +98,19 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { match self { Some(value) => Either::Left(value), None => Either::Right(()), } - .to_html_with_buf(buf, position, escape, mark_branches) + .to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs, + ) } fn to_html_async_with_buf( @@ -101,6 +119,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) where Self: Sized, { @@ -113,6 +132,7 @@ where position, escape, mark_branches, + extra_attrs, ) } @@ -121,12 +141,17 @@ where self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { match self { Some(value) => Either::Left(value), None => Either::Right(()), } - .hydrate::(cursor, position) + .hydrate::(cursor, position, extra_attrs) + } + + fn into_owned(self) -> Self::Owned { + self.map(RenderHtml::into_owned) } } @@ -136,20 +161,27 @@ where { type State = VecState; - fn build(self) -> Self::State { + fn build(self, extra_attrs: Option>) -> Self::State { let marker = Rndr::create_placeholder(); VecState { - states: self.into_iter().map(T::build).collect(), + states: self + .into_iter() + .map(|val| T::build(val, extra_attrs.clone())) + .collect(), marker, } } - fn rebuild(self, state: &mut Self::State) { + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { let VecState { states, marker } = state; let old = states; // this is an unkeyed diff if old.is_empty() { - let mut new = self.build().states; + let mut new = self.build(extra_attrs).states; for item in new.iter_mut() { Rndr::mount_before(item, marker.as_ref()); } @@ -166,10 +198,10 @@ where for item in self.into_iter().zip_longest(old.iter_mut()) { match item { itertools::EitherOrBoth::Both(new, old) => { - T::rebuild(new, old) + T::rebuild(new, old, extra_attrs.clone()) } itertools::EitherOrBoth::Left(new) => { - let mut new_state = new.build(); + let mut new_state = new.build(extra_attrs.clone()); Rndr::mount_before(&mut new_state, marker.as_ref()); adds.push(new_state); } @@ -255,24 +287,31 @@ where T: RenderHtml, { type AsyncOutput = Vec; + type Owned = Vec; const MIN_LENGTH: usize = 0; - fn dry_resolve(&mut self) { + fn dry_resolve(&mut self, mut extra_attrs: ExtraAttrsMut<'_>) { for inner in self.iter_mut() { - inner.dry_resolve(); + inner.dry_resolve(extra_attrs.as_deref_mut()); } } - async fn resolve(self) -> Self::AsyncOutput { - futures::future::join_all(self.into_iter().map(T::resolve)) + async fn resolve( + self, + extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { + batch_resolve_items_with_extra_attrs(self, extra_attrs) .await .into_iter() .collect() } - fn html_len(&self) -> usize { - self.iter().map(|n| n.html_len()).sum::() + 3 + fn html_len(&self, extra_attrs: Option>) -> usize { + self.iter() + .map(|n| n.html_len(extra_attrs.clone())) + .sum::() + + 3 } fn to_html_with_buf( @@ -281,13 +320,26 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { let mut children = self.into_iter(); if let Some(first) = children.next() { - first.to_html_with_buf(buf, position, escape, mark_branches); + first.to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs.clone(), + ); } for child in children { - child.to_html_with_buf(buf, position, escape, mark_branches); + child.to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs.clone(), + ); } buf.push_str(""); } @@ -298,6 +350,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) where Self: Sized, { @@ -308,6 +361,7 @@ where position, escape, mark_branches, + extra_attrs.clone(), ); } for child in children { @@ -316,6 +370,7 @@ where position, escape, mark_branches, + extra_attrs.clone(), ); } buf.push_sync(""); @@ -325,16 +380,27 @@ where self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { let states = self .into_iter() - .map(|child| child.hydrate::(cursor, position)) + .map(|child| { + child.hydrate::( + cursor, + position, + extra_attrs.clone(), + ) + }) .collect(); let marker = cursor.next_placeholder(position); VecState { states, marker } } + + fn into_owned(self) -> Self::Owned { + self.into_iter().map(RenderHtml::into_owned).collect() + } } impl Render for [T; N] @@ -343,19 +409,23 @@ where { type State = ArrayState; - fn build(self) -> Self::State { + fn build(self, extra_attrs: Option>) -> Self::State { Self::State { - states: self.map(T::build), + states: self.map(|val| T::build(val, extra_attrs.clone())), } } - fn rebuild(self, state: &mut Self::State) { + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { let Self::State { states } = state; let old = states; // this is an unkeyed diff self.into_iter() .zip(old.iter_mut()) - .for_each(|(new, old)| T::rebuild(new, old)); + .for_each(|(new, old)| T::rebuild(new, old, extra_attrs.clone())); } } @@ -417,17 +487,21 @@ where T: RenderHtml, { type AsyncOutput = [T::AsyncOutput; N]; + type Owned = Vec; const MIN_LENGTH: usize = 0; - fn dry_resolve(&mut self) { + fn dry_resolve(&mut self, mut extra_attrs: ExtraAttrsMut<'_>) { for inner in self.iter_mut() { - inner.dry_resolve(); + inner.dry_resolve(extra_attrs.as_deref_mut()); } } - async fn resolve(self) -> Self::AsyncOutput { - futures::future::join_all(self.into_iter().map(T::resolve)) + async fn resolve( + self, + extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { + batch_resolve_items_with_extra_attrs(self, extra_attrs) .await .into_iter() .collect::>() @@ -435,8 +509,10 @@ where .unwrap_or_else(|_| unreachable!()) } - fn html_len(&self) -> usize { - self.iter().map(RenderHtml::html_len).sum::() + fn html_len(&self, extra_attrs: Option>) -> usize { + self.iter() + .map(|val| RenderHtml::html_len(val, extra_attrs.clone())) + .sum::() } fn to_html_with_buf( @@ -445,9 +521,16 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { for child in self.into_iter() { - child.to_html_with_buf(buf, position, escape, mark_branches); + child.to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs.clone(), + ); } } @@ -457,6 +540,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) where Self: Sized, { @@ -466,6 +550,7 @@ where position, escape, mark_branches, + extra_attrs.clone(), ); } } @@ -474,9 +559,17 @@ where self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { - let states = - self.map(|child| child.hydrate::(cursor, position)); + let states = self.map(|child| { + child.hydrate::(cursor, position, extra_attrs.clone()) + }); ArrayState { states } } + + fn into_owned(self) -> Self::Owned { + self.into_iter() + .map(RenderHtml::into_owned) + .collect::>() + } } diff --git a/tachys/src/view/keyed.rs b/tachys/src/view/keyed.rs index 15af71f983..4dc74af2cd 100644 --- a/tachys/src/view/keyed.rs +++ b/tachys/src/view/keyed.rs @@ -1,9 +1,10 @@ use super::{ - add_attr::AddAnyAttr, Mountable, Position, PositionState, Render, - RenderHtml, + add_attr::AddAnyAttr, any_view::ExtraAttrsMut, + batch_resolve_items_with_extra_attrs, Mountable, Position, PositionState, + Render, RenderHtml, }; use crate::{ - html::attribute::Attribute, + html::attribute::{any_attribute::AnyAttribute, Attribute}, hydration::Cursor, renderer::{CastFrom, Rndr}, ssr::StreamBuilder, @@ -75,7 +76,7 @@ where type State = KeyedState; // TODO fallible state and try_build()/try_rebuild() here - fn build(self) -> Self::State { + fn build(self, extra_attrs: Option>) -> Self::State { let items = self.items.into_iter(); let (capacity, _) = items.size_hint(); let mut hashed_items = @@ -84,7 +85,8 @@ where for (index, item) in items.enumerate() { hashed_items.insert((self.key_fn)(&item)); let (set_index, view) = (self.view_fn)(index, item); - rendered_items.push(Some((set_index, view.build()))); + rendered_items + .push(Some((set_index, view.build(extra_attrs.clone())))); } KeyedState { parent: None, @@ -94,7 +96,11 @@ where } } - fn rebuild(self, state: &mut Self::State) { + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ) { let KeyedState { parent, marker, @@ -123,6 +129,7 @@ where rendered_items, &self.view_fn, items, + extra_attrs, ); *hashed_items = new_hashed_items; @@ -131,9 +138,9 @@ where impl AddAnyAttr for Keyed where - I: IntoIterator + Send, + I: IntoIterator + Send + 'static, K: Eq + Hash + 'static, - KF: Fn(&T) -> K + Send, + KF: Fn(&T) -> K + Send + 'static, V: RenderHtml, V: 'static, VF: Fn(usize, T) -> (VFS, V) + Send + 'static, @@ -184,29 +191,38 @@ where impl RenderHtml for Keyed where - I: IntoIterator + Send, + I: IntoIterator + Send + 'static, K: Eq + Hash + 'static, - KF: Fn(&T) -> K + Send, + KF: Fn(&T) -> K + Send + 'static, V: RenderHtml + 'static, VF: Fn(usize, T) -> (VFS, V) + Send + 'static, VFS: Fn(usize) + 'static, T: 'static, { type AsyncOutput = Vec; // TODO + type Owned = Keyed; const MIN_LENGTH: usize = 0; - fn dry_resolve(&mut self) { + fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) { // TODO... } - async fn resolve(self) -> Self::AsyncOutput { - futures::future::join_all(self.items.into_iter().enumerate().map( - |(index, item)| { - let (_, view) = (self.view_fn)(index, item); - view.resolve() - }, - )) + async fn resolve( + self, + extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { + batch_resolve_items_with_extra_attrs( + self.items + .into_iter() + .enumerate() + .map(|(index, item)| { + let (_, view) = (self.view_fn)(index, item); + view + }) + .collect::>(), + extra_attrs, + ) .await .into_iter() .collect::>() @@ -218,10 +234,17 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { for (index, item) in self.items.into_iter().enumerate() { let (_, item) = (self.view_fn)(index, item); - item.to_html_with_buf(buf, position, escape, mark_branches); + item.to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs.clone(), + ); *position = Position::NextChild; } buf.push_str(""); @@ -233,6 +256,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { for (index, item) in self.items.into_iter().enumerate() { let (_, item) = (self.view_fn)(index, item); @@ -241,6 +265,7 @@ where position, escape, mark_branches, + extra_attrs.clone(), ); *position = Position::NextChild; } @@ -251,6 +276,7 @@ where self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { // get parent and position let current = cursor.current(); @@ -272,7 +298,11 @@ where for (index, item) in items.enumerate() { hashed_items.insert((self.key_fn)(&item)); let (set_index, view) = (self.view_fn)(index, item); - let item = view.hydrate::(cursor, position); + let item = view.hydrate::( + cursor, + position, + extra_attrs.clone(), + ); rendered_items.push(Some((set_index, item))); } let marker = cursor.next_placeholder(position); @@ -283,6 +313,10 @@ where rendered_items, } } + + fn into_owned(self) -> Self::Owned { + self + } } impl Mountable for KeyedState @@ -510,6 +544,7 @@ fn apply_diff( children: &mut Vec>, view_fn: impl Fn(usize, T) -> (VFS, V), mut items: Vec>, + extra_attrs: Option>, ) where VFS: Fn(usize), V: Render, @@ -583,7 +618,7 @@ fn apply_diff( for DiffOpAdd { at, mode } in add_cmds { let item = items[at].take().unwrap(); let (set_index, item) = view_fn(at, item); - let mut item = item.build(); + let mut item = item.build(extra_attrs.clone()); match mode { DiffOpAddMode::Normal => { @@ -694,7 +729,7 @@ mod tests { #[test] fn keyed_creates_list() { let el = ul((), keyed(1..=3, |k| *k, item)); - let el_state = el.build(); + let el_state = el.build(None); assert_eq!( el_state.el.to_debug_html(), "
  • 1
  • 2
  • 3
" @@ -704,7 +739,7 @@ mod tests { #[test] fn adding_items_updates_list() { let el = ul((), keyed(1..=3, |k| *k, item)); - let mut el_state = el.build(); + let mut el_state = el.build(None); let el = ul((), keyed(1..=5, |k| *k, item)); el.rebuild(&mut el_state); assert_eq!( @@ -716,7 +751,7 @@ mod tests { #[test] fn removing_items_updates_list() { let el = ul((), keyed(1..=3, |k| *k, item)); - let mut el_state = el.build(); + let mut el_state = el.build(None); let el = ul((), keyed(1..=2, |k| *k, item)); el.rebuild(&mut el_state); assert_eq!( @@ -728,7 +763,7 @@ mod tests { #[test] fn swapping_items_updates_list() { let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item)); - let mut el_state = el.build(); + let mut el_state = el.build(None); let el = ul((), keyed([1, 4, 3, 2, 5], |k| *k, item)); el.rebuild(&mut el_state); assert_eq!( @@ -740,7 +775,7 @@ mod tests { #[test] fn swapping_and_removing_orders_correctly() { let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item)); - let mut el_state = el.build(); + let mut el_state = el.build(None); let el = ul((), keyed([1, 4, 3, 5], |k| *k, item)); el.rebuild(&mut el_state); assert_eq!( @@ -752,7 +787,7 @@ mod tests { #[test] fn arbitrarily_hard_adjustment() { let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item)); - let mut el_state = el.build(); + let mut el_state = el.build(None); let el = ul((), keyed([2, 4, 3], |k| *k, item)); el.rebuild(&mut el_state); assert_eq!( @@ -764,7 +799,7 @@ mod tests { #[test] fn a_series_of_moves() { let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item)); - let mut el_state = el.build(); + let mut el_state = el.build(None); let el = ul((), keyed([2, 4, 3], |k| *k, item)); el.rebuild(&mut el_state); let el = ul((), keyed([1, 7, 5, 11, 13, 17], |k| *k, item)); @@ -784,7 +819,7 @@ mod tests { #[test] fn clearing_works() { let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item)); - let mut el_state = el.build(); + let mut el_state = el.build(None); let el = ul((), keyed([], |k| *k, item)); el.rebuild(&mut el_state); assert_eq!(el_state.el.to_debug_html(), "
    "); diff --git a/tachys/src/view/mod.rs b/tachys/src/view/mod.rs index b4191ecd21..81c18d36c6 100644 --- a/tachys/src/view/mod.rs +++ b/tachys/src/view/mod.rs @@ -1,5 +1,9 @@ use self::add_attr::AddAnyAttr; -use crate::{hydration::Cursor, ssr::StreamBuilder}; +use crate::{ + html::attribute::any_attribute::AnyAttribute, hydration::Cursor, + ssr::StreamBuilder, +}; +use any_view::ExtraAttrsMut; use parking_lot::RwLock; use std::{cell::RefCell, future::Future, rc::Rc, sync::Arc}; @@ -37,10 +41,14 @@ pub trait Render: Sized { type State: Mountable; /// Creates the view for the first time, without hydrating from existing HTML. - fn build(self) -> Self::State; + fn build(self, extra_attrs: Option>) -> Self::State; /// Updates the view with new data. - fn rebuild(self, state: &mut Self::State); + fn rebuild( + self, + state: &mut Self::State, + extra_attrs: Option>, + ); } pub(crate) trait MarkBranch { @@ -96,6 +104,9 @@ where /// The type of the view after waiting for all asynchronous data to load. type AsyncOutput: RenderHtml; + /// A static version of this type. + type Owned: RenderHtml + 'static; + /// The minimum length of HTML created when this view is rendered. const MIN_LENGTH: usize; @@ -105,17 +116,20 @@ where /// “Runs” the view without other side effects. For primitive types, this is a no-op. For /// reactive types, this can be used to gather data about reactivity or about asynchronous data /// that needs to be loaded. - fn dry_resolve(&mut self); + fn dry_resolve(&mut self, extra_attrs: ExtraAttrsMut<'_>); /// Waits for any asynchronous sections of the view to load and returns the output. - fn resolve(self) -> impl Future + Send; + fn resolve( + self, + extra_attrs: ExtraAttrsMut<'_>, + ) -> impl Future + Send; /// An estimated length for this view, when rendered to HTML. /// /// This is used for calculating the string buffer size when rendering HTML. It does not need /// to be precise, but should be an appropriate estimate. The more accurate, the fewer /// reallocations will be required and the faster server-side rendering will be. - fn html_len(&self) -> usize { + fn html_len(&self, _extra_attrs: Option>) -> usize { Self::MIN_LENGTH } @@ -124,8 +138,14 @@ where where Self: Sized, { - let mut buf = String::with_capacity(self.html_len()); - self.to_html_with_buf(&mut buf, &mut Position::FirstChild, true, false); + let mut buf = String::with_capacity(self.html_len(None)); + self.to_html_with_buf( + &mut buf, + &mut Position::FirstChild, + true, + false, + None, + ); buf } @@ -136,8 +156,14 @@ where where Self: Sized, { - let mut buf = String::with_capacity(self.html_len()); - self.to_html_with_buf(&mut buf, &mut Position::FirstChild, true, true); + let mut buf = String::with_capacity(self.html_len(None)); + self.to_html_with_buf( + &mut buf, + &mut Position::FirstChild, + true, + true, + None, + ); buf } @@ -146,12 +172,14 @@ where where Self: Sized, { - let mut builder = StreamBuilder::with_capacity(self.html_len(), None); + let mut builder = + StreamBuilder::with_capacity(self.html_len(None), None); self.to_html_async_with_buf::( &mut builder, &mut Position::FirstChild, true, false, + None, ); builder.finish() } @@ -163,12 +191,14 @@ where where Self: Sized, { - let mut builder = StreamBuilder::with_capacity(self.html_len(), None); + let mut builder = + StreamBuilder::with_capacity(self.html_len(None), None); self.to_html_async_with_buf::( &mut builder, &mut Position::FirstChild, true, true, + None, ); builder.finish() } @@ -180,13 +210,14 @@ where { //let capacity = self.html_len(); let mut builder = - StreamBuilder::with_capacity(self.html_len(), Some(vec![0])); + StreamBuilder::with_capacity(self.html_len(None), Some(vec![0])); self.to_html_async_with_buf::( &mut builder, &mut Position::FirstChild, true, false, + None, ); builder.finish() } @@ -199,13 +230,14 @@ where Self: Sized, { let mut builder = - StreamBuilder::with_capacity(self.html_len(), Some(vec![0])); + StreamBuilder::with_capacity(self.html_len(None), Some(vec![0])); self.to_html_async_with_buf::( &mut builder, &mut Position::FirstChild, true, true, + None, ); builder.finish() } @@ -217,6 +249,7 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ); /// Renders a view into a buffer of (synchronous or asynchronous) HTML chunks. @@ -226,11 +259,18 @@ where position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) where Self: Sized, { buf.with_buf(|buf| { - self.to_html_with_buf(buf, position, escape, mark_branches) + self.to_html_with_buf( + buf, + position, + escape, + mark_branches, + extra_attrs, + ) }); } @@ -245,6 +285,7 @@ where self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State; /// Hydrates using [`RenderHtml::hydrate`], beginning at the given element. @@ -269,8 +310,49 @@ where { let cursor = Cursor::new(el.clone()); let position = PositionState::new(position); - self.hydrate::(&cursor, &position) + self.hydrate::(&cursor, &position, None) + } + + /// Converts this view into a owned/static type. + fn into_owned(self) -> Self::Owned; +} + +/// Resolving multiple children when extra_attrs exist is tricky, because the extra_attrs are potentially shared. +/// +/// The current assumption is: +/// - resolve() should not be run on an AnyAttribute more than once, +/// - any of the children could resolve() the extra_attr_states +/// +/// Therefore, if any extra_attrs exist, resolving must happen sequentially, until any of the children resolves the extra_attrs. +/// After that, the remaining can be done in parallel, like they will be if no extra_attrs exist. +pub(crate) async fn batch_resolve_items_with_extra_attrs( + items: impl IntoIterator, + mut extra_attrs: ExtraAttrsMut<'_>, +) -> impl IntoIterator { + let mut item_iter = items.into_iter(); + let mut preresolved = vec![]; + if extra_attrs.is_some() { + // Reset resolved state to fresh if dirty: + extra_attrs + .as_deref_mut() + .iter_mut() + .for_each(|attr| attr.resolved = false); + for item in item_iter.by_ref() { + preresolved.push(item.resolve(extra_attrs.as_deref_mut()).await); + // Once all resolved, can switch to parallel and not pass in extra_attrs anymore, + // once they've already all resolved: + if extra_attrs.iter_mut().all(|attr| attr.resolved) { + break; + } + } } + preresolved.into_iter().chain( + futures::future::join_all( + item_iter.map(|val| T::resolve(val, ExtraAttrsMut::default())), + ) + .await + .into_iter(), + ) } /// Allows a type to be mounted to the DOM. diff --git a/tachys/src/view/primitives.rs b/tachys/src/view/primitives.rs index 0441af882e..0198a26b39 100644 --- a/tachys/src/view/primitives.rs +++ b/tachys/src/view/primitives.rs @@ -1,5 +1,9 @@ -use super::{Mountable, Position, PositionState, Render, RenderHtml}; +use super::{ + any_view::ExtraAttrsMut, Mountable, Position, PositionState, Render, + RenderHtml, +}; use crate::{ + html::attribute::any_attribute::AnyAttribute, hydration::Cursor, no_attrs, renderer::{CastFrom, Rndr}, @@ -46,12 +50,12 @@ macro_rules! render_primitive { type State = [<$child_type:camel State>]; - fn build(self) -> Self::State { + fn build(self, _extra_attrs: Option>) -> Self::State { let node = Rndr::create_text_node(&self.to_string()); [<$child_type:camel State>](node, self) } - fn rebuild(self, state: &mut Self::State) { + fn rebuild(self, state: &mut Self::State, _extra_attrs: Option>) { let [<$child_type:camel State>](node, this) = state; if &self != this { Rndr::set_text(node, &self.to_string()); @@ -65,16 +69,17 @@ macro_rules! render_primitive { impl RenderHtml for $child_type { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = 0; - fn dry_resolve(&mut self) {} + fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {} - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve(self, _extra_attrs: ExtraAttrsMut<'_>) -> Self::AsyncOutput { self } - fn to_html_with_buf(self, buf: &mut String, position: &mut Position, _escape: bool, _mark_branches: bool) { + fn to_html_with_buf(self, buf: &mut String, position: &mut Position, _escape: bool, _mark_branches: bool, _extra_attrs: Option>) { // add a comment node to separate from previous sibling, if any if matches!(position, Position::NextChildAfterText) { buf.push_str("") @@ -87,6 +92,7 @@ macro_rules! render_primitive { self, cursor: &Cursor, position: &PositionState, + _extra_attrs: Option>, ) -> Self::State { if position.get() == Position::FirstChild { cursor.child(); @@ -110,6 +116,10 @@ macro_rules! render_primitive { [<$child_type:camel State>](node, self) } + + fn into_owned(self) -> Self::Owned { + self + } } impl<'a> ToTemplate for $child_type { diff --git a/tachys/src/view/static_types.rs b/tachys/src/view/static_types.rs index 234b3409a0..e947762362 100644 --- a/tachys/src/view/static_types.rs +++ b/tachys/src/view/static_types.rs @@ -1,9 +1,15 @@ use super::{ - add_attr::AddAnyAttr, Mountable, Position, PositionState, Render, - RenderHtml, ToTemplate, + add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Mountable, Position, + PositionState, Render, RenderHtml, ToTemplate, }; use crate::{ - html::attribute::{Attribute, AttributeKey, AttributeValue, NextAttribute}, + html::attribute::{ + any_attribute::AnyAttribute, + maybe_next_attr_erasure_macros::{ + next_attr_combine, next_attr_output_type, + }, + Attribute, AttributeKey, AttributeValue, NextAttribute, + }, hydration::Cursor, renderer::{CastFrom, Rndr}, }; @@ -145,27 +151,36 @@ where { type State = Option; - fn build(self) -> Self::State { + fn build(self, _extra_attrs: Option>) -> Self::State { // a view state has to be returned so it can be mounted Some(Rndr::create_text_node(V)) } // This type is specified as static, so no rebuilding is done. - fn rebuild(self, _state: &mut Self::State) {} + fn rebuild( + self, + _state: &mut Self::State, + _extra_attrs: Option>, + ) { + } } impl RenderHtml for Static { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = V.len(); - fn dry_resolve(&mut self) {} + fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {} // this won't actually compile because if a weird interaction because the const &'static str and // the RPITIT, so we just refine it to a concrete future type; this will never change in any // case #[allow(refining_impl_trait)] - fn resolve(self) -> std::future::Ready { + fn resolve( + self, + _extra_attrs: ExtraAttrsMut<'_>, + ) -> std::future::Ready { std::future::ready(self) } @@ -175,6 +190,7 @@ impl RenderHtml for Static { position: &mut Position, escape: bool, _mark_branches: bool, + _extra_attrs: Option>, ) { // add a comment node to separate from previous sibling, if any if matches!(position, Position::NextChildAfterText) { @@ -195,6 +211,7 @@ impl RenderHtml for Static { self, cursor: &Cursor, position: &PositionState, + _extra_attrs: Option>, ) -> Self::State { if position.get() == Position::FirstChild { cursor.child(); @@ -217,6 +234,10 @@ impl RenderHtml for Static { Some(node) } + + fn into_owned(self) -> Self::Owned { + self + } } impl AddAnyAttr for Static { diff --git a/tachys/src/view/strings.rs b/tachys/src/view/strings.rs index df4719ed10..cf95f03bf7 100644 --- a/tachys/src/view/strings.rs +++ b/tachys/src/view/strings.rs @@ -1,7 +1,9 @@ use super::{ - Mountable, Position, PositionState, Render, RenderHtml, ToTemplate, + any_view::ExtraAttrsMut, Mountable, Position, PositionState, Render, + RenderHtml, ToTemplate, }; use crate::{ + html::attribute::any_attribute::AnyAttribute, hydration::Cursor, no_attrs, renderer::{CastFrom, Rndr}, @@ -22,12 +24,16 @@ pub struct StrState<'a> { impl<'a> Render for &'a str { type State = StrState<'a>; - fn build(self) -> Self::State { + fn build(self, _extra_attrs: Option>) -> Self::State { let node = Rndr::create_text_node(self); StrState { node, str: self } } - fn rebuild(self, state: &mut Self::State) { + fn rebuild( + self, + state: &mut Self::State, + _extra_attrs: Option>, + ) { let StrState { node, str } = state; if &self != str { Rndr::set_text(node, self); @@ -38,16 +44,20 @@ impl<'a> Render for &'a str { impl RenderHtml for &str { type AsyncOutput = Self; + type Owned = String; const MIN_LENGTH: usize = 0; - fn dry_resolve(&mut self) {} + fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {} - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + _extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { self } - fn html_len(&self) -> usize { + fn html_len(&self, _extra_attrs: Option>) -> usize { self.len() } @@ -57,6 +67,7 @@ impl RenderHtml for &str { position: &mut Position, escape: bool, _mark_branches: bool, + _extra_attrs: Option>, ) { // add a comment node to separate from previous sibling, if any if matches!(position, Position::NextChildAfterText) { @@ -77,6 +88,7 @@ impl RenderHtml for &str { self, cursor: &Cursor, position: &PositionState, + _extra_attrs: Option>, ) -> Self::State { if position.get() == Position::FirstChild { cursor.child(); @@ -102,6 +114,10 @@ impl RenderHtml for &str { StrState { node, str: self } } + + fn into_owned(self) -> Self::Owned { + self.to_string() + } } impl ToTemplate for &str { @@ -149,12 +165,16 @@ pub struct StringState { impl Render for String { type State = StringState; - fn build(self) -> Self::State { + fn build(self, _extra_attrs: Option>) -> Self::State { let node = Rndr::create_text_node(&self); StringState { node, str: self } } - fn rebuild(self, state: &mut Self::State) { + fn rebuild( + self, + state: &mut Self::State, + _extra_attrs: Option>, + ) { let StringState { node, str } = state; if &self != str { Rndr::set_text(node, &self); @@ -166,14 +186,18 @@ impl Render for String { impl RenderHtml for String { const MIN_LENGTH: usize = 0; type AsyncOutput = Self; + type Owned = Self; - fn dry_resolve(&mut self) {} + fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {} - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + _extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { self } - fn html_len(&self) -> usize { + fn html_len(&self, _extra_attrs: Option>) -> usize { self.len() } @@ -183,6 +207,7 @@ impl RenderHtml for String { position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { <&str as RenderHtml>::to_html_with_buf( self.as_str(), @@ -190,6 +215,7 @@ impl RenderHtml for String { position, escape, mark_branches, + extra_attrs, ) } @@ -197,11 +223,17 @@ impl RenderHtml for String { self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { let StrState { node, .. } = - self.as_str().hydrate::(cursor, position); + self.as_str() + .hydrate::(cursor, position, extra_attrs); StringState { node, str: self } } + + fn into_owned(self) -> Self::Owned { + self + } } impl ToTemplate for String { @@ -247,12 +279,16 @@ pub struct RcStrState { impl Render for Rc { type State = RcStrState; - fn build(self) -> Self::State { + fn build(self, _extra_attrs: Option>) -> Self::State { let node = Rndr::create_text_node(&self); RcStrState { node, str: self } } - fn rebuild(self, state: &mut Self::State) { + fn rebuild( + self, + state: &mut Self::State, + _extra_attrs: Option>, + ) { let RcStrState { node, str } = state; if !Rc::ptr_eq(&self, str) { Rndr::set_text(node, &self); @@ -272,11 +308,11 @@ where const MIN_LENGTH: usize = 0; - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve(self, _extra_attrs: Option>) -> Self::AsyncOutput { self } - fn html_len(&self) -> usize { + fn html_len(&self, _extra_attrs: Option>) -> usize { self.len() } @@ -288,6 +324,7 @@ where self, cursor: &Cursor, position: &PositionState, + _extra_attrs: Option>, ) -> Self::State { let this: &str = self.as_ref(); let StrState { node, .. } = @@ -339,12 +376,16 @@ pub struct ArcStrState { impl Render for Arc { type State = ArcStrState; - fn build(self) -> Self::State { + fn build(self, _extra_attrs: Option>) -> Self::State { let node = Rndr::create_text_node(&self); ArcStrState { node, str: self } } - fn rebuild(self, state: &mut Self::State) { + fn rebuild( + self, + state: &mut Self::State, + _extra_attrs: Option>, + ) { let ArcStrState { node, str } = state; if !Arc::ptr_eq(&self, str) { Rndr::set_text(node, &self); @@ -355,16 +396,20 @@ impl Render for Arc { impl RenderHtml for Arc { type AsyncOutput = Self; + type Owned = Arc; const MIN_LENGTH: usize = 0; - fn dry_resolve(&mut self) {} + fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {} - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + _extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { self } - fn html_len(&self) -> usize { + fn html_len(&self, _extra_attrs: Option>) -> usize { self.len() } @@ -374,6 +419,7 @@ impl RenderHtml for Arc { position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { <&str as RenderHtml>::to_html_with_buf( &self, @@ -381,6 +427,7 @@ impl RenderHtml for Arc { position, escape, mark_branches, + extra_attrs, ) } @@ -388,12 +435,17 @@ impl RenderHtml for Arc { self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { let this: &str = self.as_ref(); let StrState { node, .. } = - this.hydrate::(cursor, position); + this.hydrate::(cursor, position, extra_attrs); ArcStrState { node, str: self } } + + fn into_owned(self) -> Self::Owned { + self + } } impl ToTemplate for Arc { @@ -439,12 +491,16 @@ pub struct CowStrState<'a> { impl<'a> Render for Cow<'a, str> { type State = CowStrState<'a>; - fn build(self) -> Self::State { + fn build(self, _extra_attrs: Option>) -> Self::State { let node = Rndr::create_text_node(&self); CowStrState { node, str: self } } - fn rebuild(self, state: &mut Self::State) { + fn rebuild( + self, + state: &mut Self::State, + _extra_attrs: Option>, + ) { let CowStrState { node, str } = state; if self != *str { Rndr::set_text(node, &self); @@ -455,16 +511,20 @@ impl<'a> Render for Cow<'a, str> { impl RenderHtml for Cow<'_, str> { type AsyncOutput = Self; + type Owned = String; const MIN_LENGTH: usize = 0; - fn dry_resolve(&mut self) {} + fn dry_resolve(&mut self, _extra_attrs: ExtraAttrsMut<'_>) {} - async fn resolve(self) -> Self::AsyncOutput { + async fn resolve( + self, + _extra_attrs: ExtraAttrsMut<'_>, + ) -> Self::AsyncOutput { self } - fn html_len(&self) -> usize { + fn html_len(&self, _extra_attrs: Option>) -> usize { self.len() } @@ -474,6 +534,7 @@ impl RenderHtml for Cow<'_, str> { position: &mut Position, escape: bool, mark_branches: bool, + extra_attrs: Option>, ) { <&str as RenderHtml>::to_html_with_buf( &self, @@ -481,6 +542,7 @@ impl RenderHtml for Cow<'_, str> { position, escape, mark_branches, + extra_attrs, ) } @@ -488,12 +550,17 @@ impl RenderHtml for Cow<'_, str> { self, cursor: &Cursor, position: &PositionState, + extra_attrs: Option>, ) -> Self::State { let this: &str = self.as_ref(); let StrState { node, .. } = - this.hydrate::(cursor, position); + this.hydrate::(cursor, position, extra_attrs); CowStrState { node, str: self } } + + fn into_owned(self) -> ::Owned { + self.into_owned() + } } impl ToTemplate for Cow<'_, str> { diff --git a/tachys/src/view/template.rs b/tachys/src/view/template.rs index 44fb2253b4..343d0899bf 100644 --- a/tachys/src/view/template.rs +++ b/tachys/src/view/template.rs @@ -1,8 +1,12 @@ use super::{ - add_attr::AddAnyAttr, Mountable, Position, PositionState, Render, - RenderHtml, ToTemplate, + add_attr::AddAnyAttr, any_view::ExtraAttrsMut, Mountable, Position, + PositionState, Render, RenderHtml, ToTemplate, +}; +use crate::{ + html::attribute::{any_attribute::AnyAttribute, Attribute}, + hydration::Cursor, + renderer::Rndr, }; -use crate::{html::attribute::Attribute, hydration::Cursor, renderer::Rndr}; /// A view wrapper that uses a `