mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 15:44:42 -05:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdd5671fe1 | ||
|
|
293149eeb2 | ||
|
|
a9ce608433 | ||
|
|
62196ff638 | ||
|
|
db9cabb365 | ||
|
|
5293afddb7 | ||
|
|
eeb01e2d52 | ||
|
|
7d9cd43c0e | ||
|
|
723685c370 | ||
|
|
6d19e1a921 | ||
|
|
165911b2e6 | ||
|
|
575186349c | ||
|
|
10db2f83eb | ||
|
|
357f3be393 | ||
|
|
a1f5d6f79f | ||
|
|
34ecd2d541 | ||
|
|
df38b075d8 | ||
|
|
46233e3097 | ||
|
|
46f6d9ae26 | ||
|
|
8264d478e4 | ||
|
|
9c54da304e | ||
|
|
1c002a2df8 | ||
|
|
6e4dac91bf | ||
|
|
6493b0b359 | ||
|
|
d58042b7ba | ||
|
|
7a5b27ad20 | ||
|
|
78d0dbdfa2 | ||
|
|
1c30a4c131 | ||
|
|
b91429d4f0 | ||
|
|
6663fddf76 |
43
Cargo.lock
generated
43
Cargo.lock
generated
@@ -918,7 +918,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "either_of"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
@@ -1771,13 +1771,14 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "leptos"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"base64",
|
||||
"cfg-if",
|
||||
"either_of",
|
||||
"futures",
|
||||
"getrandom",
|
||||
"hydration_context",
|
||||
"leptos-spin-macro",
|
||||
"leptos_config",
|
||||
@@ -1821,7 +1822,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_actix"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"actix-files",
|
||||
"actix-http",
|
||||
@@ -1846,7 +1847,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_axum"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"axum",
|
||||
@@ -1869,7 +1870,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_config"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"config",
|
||||
"regex",
|
||||
@@ -1883,7 +1884,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_dom"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"leptos",
|
||||
@@ -1900,7 +1901,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_hot_reload"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
@@ -1916,7 +1917,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_integration_utils"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"hydration_context",
|
||||
@@ -1929,7 +1930,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_macro"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"attribute-derive",
|
||||
"cfg-if",
|
||||
@@ -1947,7 +1948,7 @@ dependencies = [
|
||||
"rstml",
|
||||
"serde",
|
||||
"server_fn",
|
||||
"server_fn_macro 0.7.2",
|
||||
"server_fn_macro 0.7.4",
|
||||
"syn 2.0.90",
|
||||
"tracing",
|
||||
"trybuild",
|
||||
@@ -1957,7 +1958,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_meta"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"indexmap",
|
||||
@@ -1972,7 +1973,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_router"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"either_of",
|
||||
@@ -1996,7 +1997,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_router_macro"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"leptos_router",
|
||||
"proc-macro-error2",
|
||||
@@ -2006,7 +2007,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_server"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"base64",
|
||||
@@ -2734,7 +2735,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reactive_graph"
|
||||
version = "0.1.1"
|
||||
version = "0.1.4"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"async-lock",
|
||||
@@ -2756,7 +2757,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reactive_stores"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"guardian",
|
||||
@@ -3273,7 +3274,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "server_fn"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"axum",
|
||||
@@ -3329,7 +3330,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "server_fn_macro"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"const_format",
|
||||
"convert_case 0.6.0",
|
||||
@@ -3341,9 +3342,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "server_fn_macro_default"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"server_fn_macro 0.7.2",
|
||||
"server_fn_macro 0.7.4",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
@@ -3538,7 +3539,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tachys"
|
||||
version = "0.1.2"
|
||||
version = "0.1.4"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"const_str_slice_concat",
|
||||
|
||||
28
Cargo.toml
28
Cargo.toml
@@ -40,7 +40,7 @@ members = [
|
||||
exclude = ["benchmarks", "examples", "projects"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
edition = "2021"
|
||||
rust-version = "1.76"
|
||||
|
||||
@@ -50,25 +50,25 @@ any_spawner = { path = "./any_spawner/", version = "0.2.0" }
|
||||
const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1.0" }
|
||||
either_of = { path = "./either_of/", version = "0.1.0" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.2.0" }
|
||||
leptos = { path = "./leptos", version = "0.7.2" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.2" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.2" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.2" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.2" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.2" }
|
||||
leptos_router = { path = "./router", version = "0.7.2" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.2" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.2" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.2" }
|
||||
leptos = { path = "./leptos", version = "0.7.4" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.4" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.4" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.4" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.4" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.4" }
|
||||
leptos_router = { path = "./router", version = "0.7.4" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.4" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.4" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.4" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0" }
|
||||
oco_ref = { path = "./oco", version = "0.2.0" }
|
||||
or_poisoned = { path = "./or_poisoned", version = "0.1.0" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.1.0" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.1.0" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.0" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.2" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.2" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.2" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.4" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.4" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.4" }
|
||||
tachys = { path = "./tachys", version = "0.1.0" }
|
||||
|
||||
[profile.release]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "either_of"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -18,7 +18,7 @@ hydration_context = { workspace = true }
|
||||
leptos = { workspace = true, features = ["nonce", "ssr"] }
|
||||
leptos_integration_utils = { workspace = true }
|
||||
leptos_macro = { workspace = true, features = ["actix"] }
|
||||
leptos_meta = { workspace = true }
|
||||
leptos_meta = { workspace = true, features = ["nonce"] }
|
||||
leptos_router = { workspace = true, features = ["ssr"] }
|
||||
server_fn = { workspace = true, features = ["actix"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
@@ -19,7 +19,7 @@ futures = "0.3.31"
|
||||
leptos = { workspace = true, features = ["nonce", "ssr"] }
|
||||
server_fn = { workspace = true, features = ["axum-no-default"] }
|
||||
leptos_macro = { workspace = true, features = ["axum"] }
|
||||
leptos_meta = { workspace = true, features = ["ssr"] }
|
||||
leptos_meta = { workspace = true, features = ["ssr", "nonce"] }
|
||||
leptos_router = { workspace = true, features = ["ssr"] }
|
||||
leptos_integration_utils = { workspace = true }
|
||||
once_cell = "1"
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
//! - `default`: supports running in a typical native Tokio/Axum environment
|
||||
//! - `wasm`: with `default-features = false`, supports running in a JS Fetch-based
|
||||
//! environment
|
||||
//! - `islands`: activates Leptos [islands mode](https://leptos-rs.github.io/leptos/islands.html)
|
||||
//!
|
||||
//! ### Important Note
|
||||
//! Prior to 0.5, using `default-features = false` on `leptos_axum` simply did nothing. Now, it actively
|
||||
@@ -1994,12 +1993,12 @@ pub fn file_and_error_handler<S, IV>(
|
||||
+ 'static
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
S: Send + 'static,
|
||||
S: Send + Sync + Clone + 'static,
|
||||
LeptosOptions: FromRef<S>,
|
||||
{
|
||||
move |uri: Uri, State(options): State<S>, req: Request<Body>| {
|
||||
move |uri: Uri, State(state): State<S>, req: Request<Body>| {
|
||||
Box::pin(async move {
|
||||
let options = LeptosOptions::from_ref(&options);
|
||||
let options = LeptosOptions::from_ref(&state);
|
||||
let res = get_static_file(uri, &options.site_root, req.headers());
|
||||
let res = res.await.unwrap();
|
||||
|
||||
@@ -2007,7 +2006,9 @@ where
|
||||
res.into_response()
|
||||
} else {
|
||||
let mut res = handle_response_inner(
|
||||
|| {},
|
||||
move || {
|
||||
provide_context(state.clone());
|
||||
},
|
||||
move || shell(options),
|
||||
req,
|
||||
|app, chunks| {
|
||||
|
||||
@@ -11,7 +11,10 @@ edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
throw_error = { workspace = true }
|
||||
any_spawner = { workspace = true, features = ["wasm-bindgen", "futures-executor"] }
|
||||
any_spawner = { workspace = true, features = [
|
||||
"wasm-bindgen",
|
||||
"futures-executor",
|
||||
] }
|
||||
base64 = { version = "0.22.1", optional = true }
|
||||
cfg-if = "1.0"
|
||||
hydration_context = { workspace = true }
|
||||
@@ -28,7 +31,11 @@ paste = "1.0"
|
||||
rand = { version = "0.8.5", optional = true }
|
||||
reactive_graph = { workspace = true, features = ["serde"] }
|
||||
rustc-hash = "2.0"
|
||||
tachys = { workspace = true, features = ["reactive_graph", "reactive_stores", "oco"] }
|
||||
tachys = { workspace = true, features = [
|
||||
"reactive_graph",
|
||||
"reactive_stores",
|
||||
"oco",
|
||||
] }
|
||||
thiserror = "2.0"
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
typed-builder = "0.20.0"
|
||||
@@ -50,20 +57,22 @@ serde_qs = "0.13.0"
|
||||
slotmap = "1.0"
|
||||
futures = "0.3.31"
|
||||
send_wrapper = "0.6.0"
|
||||
getrandom = { version = "0.2", features = ["js"], optional = true }
|
||||
|
||||
[features]
|
||||
hydration = [
|
||||
"reactive_graph/hydration",
|
||||
"leptos_server/hydration",
|
||||
"hydration_context/browser",
|
||||
"leptos_dom/hydration"
|
||||
"leptos_dom/hydration",
|
||||
]
|
||||
csr = ["leptos_macro/csr", "reactive_graph/effects"]
|
||||
csr = ["leptos_macro/csr", "reactive_graph/effects", "dep:getrandom"]
|
||||
hydrate = [
|
||||
"leptos_macro/hydrate",
|
||||
"hydration",
|
||||
"tachys/hydrate",
|
||||
"reactive_graph/effects",
|
||||
"dep:getrandom",
|
||||
]
|
||||
default-tls = ["server_fn/default-tls"]
|
||||
rustls = ["server_fn/rustls"]
|
||||
@@ -75,7 +84,10 @@ ssr = [
|
||||
"tachys/ssr",
|
||||
]
|
||||
nightly = ["leptos_macro/nightly", "reactive_graph/nightly", "tachys/nightly"]
|
||||
rkyv = ["server_fn/rkyv"]
|
||||
rkyv = [
|
||||
"server_fn/rkyv",
|
||||
"leptos_server/rkyv"
|
||||
]
|
||||
tracing = [
|
||||
"dep:tracing",
|
||||
"reactive_graph/tracing",
|
||||
@@ -89,7 +101,7 @@ spin = ["leptos-spin-macro"]
|
||||
islands = ["leptos_macro/islands", "dep:serde_json"]
|
||||
trace-component-props = [
|
||||
"leptos_macro/trace-component-props",
|
||||
"leptos_dom/trace-component-props"
|
||||
"leptos_dom/trace-component-props",
|
||||
]
|
||||
delegation = ["tachys/delegation"]
|
||||
|
||||
@@ -101,26 +113,56 @@ denylist = [
|
||||
"rustls",
|
||||
"default-tls",
|
||||
"wasm-bindgen",
|
||||
"rkyv", # was causing clippy issues on nightly
|
||||
"rkyv", # was causing clippy issues on nightly
|
||||
"trace-component-props",
|
||||
"spin",
|
||||
"islands",
|
||||
]
|
||||
skip_feature_sets = [
|
||||
["csr", "ssr"],
|
||||
["csr", "hydrate"],
|
||||
["ssr", "hydrate"],
|
||||
["serde", "serde-lite"],
|
||||
["serde-lite", "miniserde"],
|
||||
["serde", "miniserde"],
|
||||
["serde", "rkyv"],
|
||||
["miniserde", "rkyv"],
|
||||
["serde-lite", "rkyv"],
|
||||
["default-tls", "rustls"],
|
||||
[
|
||||
"csr",
|
||||
"ssr",
|
||||
],
|
||||
[
|
||||
"csr",
|
||||
"hydrate",
|
||||
],
|
||||
[
|
||||
"ssr",
|
||||
"hydrate",
|
||||
],
|
||||
[
|
||||
"serde",
|
||||
"serde-lite",
|
||||
],
|
||||
[
|
||||
"serde-lite",
|
||||
"miniserde",
|
||||
],
|
||||
[
|
||||
"serde",
|
||||
"miniserde",
|
||||
],
|
||||
[
|
||||
"serde",
|
||||
"rkyv",
|
||||
],
|
||||
[
|
||||
"miniserde",
|
||||
"rkyv",
|
||||
],
|
||||
[
|
||||
"serde-lite",
|
||||
"rkyv",
|
||||
],
|
||||
[
|
||||
"default-tls",
|
||||
"rustls",
|
||||
],
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
|
||||
|
||||
@@ -35,7 +35,7 @@ type ChildBuilder<T> = dyn Fn(AnyAttribute) -> T + Send + Sync + 'static;
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[component]
|
||||
#[component(transparent)]
|
||||
pub fn AttributeInterceptor<Chil, T>(
|
||||
/// The elements that will be rendered, with the attributes this component received as a
|
||||
/// parameter.
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
((root, pkg_path, output_name, wasm_output_name) => {
|
||||
let MOST_RECENT_CHILDREN_CB;
|
||||
|
||||
function idle(c) {
|
||||
if ("requestIdleCallback" in window) {
|
||||
window.requestIdleCallback(c);
|
||||
@@ -6,55 +8,49 @@
|
||||
c();
|
||||
}
|
||||
}
|
||||
function islandTree(rootNode) {
|
||||
const tree = [];
|
||||
|
||||
function traverse(node, parent) {
|
||||
function hydrateIslands(rootNode, mod) {
|
||||
function traverse(node) {
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
if(node.tagName.toLowerCase() === 'leptos-island') {
|
||||
const tag = node.tagName.toLowerCase();
|
||||
if(tag === 'leptos-island') {
|
||||
const children = [];
|
||||
const id = node.dataset.component || null;
|
||||
const data = { id, node, children };
|
||||
|
||||
hydrateIsland(node, id, mod);
|
||||
|
||||
for(const child of node.children) {
|
||||
traverse(child, children);
|
||||
}
|
||||
|
||||
(parent || tree).push(data);
|
||||
} else {
|
||||
if(tag === 'leptos-children') {
|
||||
MOST_RECENT_CHILDREN_CB = node.$$on_hydrate;
|
||||
}
|
||||
for(const child of node.children) {
|
||||
traverse(child, parent);
|
||||
traverse(child);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
traverse(rootNode, null);
|
||||
|
||||
return { el: null, id: null, children: tree };
|
||||
traverse(rootNode);
|
||||
}
|
||||
function hydrateIsland(el, id, mod) {
|
||||
const islandFn = mod[id];
|
||||
if (islandFn) {
|
||||
if (MOST_RECENT_CHILDREN_CB) {
|
||||
MOST_RECENT_CHILDREN_CB();
|
||||
}
|
||||
islandFn(el);
|
||||
} else {
|
||||
console.warn(`Could not find WASM function for the island ${id}.`);
|
||||
}
|
||||
}
|
||||
function hydrateIslands(entry, mod) {
|
||||
if(entry.node) {
|
||||
hydrateIsland(entry.node, entry.id, mod);
|
||||
}
|
||||
for (const island of entry.children) {
|
||||
hydrateIslands(island, mod);
|
||||
}
|
||||
}
|
||||
idle(() => {
|
||||
import(`${root}/${pkg_path}/${output_name}.js`)
|
||||
.then(mod => {
|
||||
mod.default(`${root}/${pkg_path}/${wasm_output_name}.wasm`).then(() => {
|
||||
mod.hydrate();
|
||||
hydrateIslands(islandTree(document.body, null), mod);
|
||||
hydrateIslands(document.body, mod);
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
@@ -51,6 +51,13 @@ use tachys::html::attribute::AttributeValue;
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Nonce(pub(crate) Arc<str>);
|
||||
|
||||
impl Nonce {
|
||||
/// Returns a reference to the inner reference-counted string slice representing the nonce.
|
||||
pub fn as_inner(&self) -> &Arc<str> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Nonce {
|
||||
type Target = str;
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ use reactive_graph::{
|
||||
traits::{Dispose, Get, Read, Track, With},
|
||||
};
|
||||
use slotmap::{DefaultKey, SlotMap};
|
||||
use std::sync::Arc;
|
||||
use tachys::{
|
||||
either::Either,
|
||||
html::attribute::Attribute,
|
||||
@@ -132,6 +133,18 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
fn nonce_or_not() -> Option<Arc<str>> {
|
||||
#[cfg(feature = "nonce")]
|
||||
{
|
||||
use crate::nonce::Nonce;
|
||||
use_context::<Nonce>().map(|n| n.0)
|
||||
}
|
||||
#[cfg(not(feature = "nonce"))]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SuspenseBoundary<const TRANSITION: bool, Fal, Chil> {
|
||||
pub id: SerializedDataId,
|
||||
pub none_pending: ArcMemo<bool>,
|
||||
@@ -379,7 +392,12 @@ where
|
||||
&mut fallback_position,
|
||||
mark_branches,
|
||||
);
|
||||
buf.push_async_out_of_order(fut, position, mark_branches);
|
||||
buf.push_async_out_of_order_with_nonce(
|
||||
fut,
|
||||
position,
|
||||
mark_branches,
|
||||
nonce_or_not(),
|
||||
);
|
||||
} else {
|
||||
buf.push_async({
|
||||
let mut position = *position;
|
||||
|
||||
@@ -85,41 +85,44 @@ pub fn Transition<Chil>(
|
||||
where
|
||||
Chil: IntoView + Send + 'static,
|
||||
{
|
||||
let (starts_local, id) = {
|
||||
Owner::current_shared_context()
|
||||
.map(|sc| {
|
||||
let id = sc.next_id();
|
||||
(sc.get_incomplete_chunk(&id), id)
|
||||
})
|
||||
.unwrap_or_else(|| (false, Default::default()))
|
||||
};
|
||||
let fallback = fallback.run();
|
||||
let children = children.into_inner()();
|
||||
let tasks = ArcRwSignal::new(SlotMap::<DefaultKey, ()>::new());
|
||||
provide_context(SuspenseContext {
|
||||
tasks: tasks.clone(),
|
||||
});
|
||||
let none_pending = ArcMemo::new(move |prev: Option<&bool>| {
|
||||
tasks.track();
|
||||
if prev.is_none() && starts_local {
|
||||
false
|
||||
} else {
|
||||
tasks.with(SlotMap::is_empty)
|
||||
}
|
||||
});
|
||||
if let Some(set_pending) = set_pending {
|
||||
Effect::new_isomorphic({
|
||||
let none_pending = none_pending.clone();
|
||||
move |_| {
|
||||
set_pending.set(!none_pending.get());
|
||||
let owner = Owner::new();
|
||||
owner.with(|| {
|
||||
let (starts_local, id) = {
|
||||
Owner::current_shared_context()
|
||||
.map(|sc| {
|
||||
let id = sc.next_id();
|
||||
(sc.get_incomplete_chunk(&id), id)
|
||||
})
|
||||
.unwrap_or_else(|| (false, Default::default()))
|
||||
};
|
||||
let fallback = fallback.run();
|
||||
let children = children.into_inner()();
|
||||
let tasks = ArcRwSignal::new(SlotMap::<DefaultKey, ()>::new());
|
||||
provide_context(SuspenseContext {
|
||||
tasks: tasks.clone(),
|
||||
});
|
||||
let none_pending = ArcMemo::new(move |prev: Option<&bool>| {
|
||||
tasks.track();
|
||||
if prev.is_none() && starts_local {
|
||||
false
|
||||
} else {
|
||||
tasks.with(SlotMap::is_empty)
|
||||
}
|
||||
});
|
||||
}
|
||||
if let Some(set_pending) = set_pending {
|
||||
Effect::new_isomorphic({
|
||||
let none_pending = none_pending.clone();
|
||||
move |_| {
|
||||
set_pending.set(!none_pending.get());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
OwnedView::new(SuspenseBoundary::<true, _, _> {
|
||||
id,
|
||||
none_pending,
|
||||
fallback,
|
||||
children,
|
||||
OwnedView::new(SuspenseBoundary::<true, _, _> {
|
||||
id,
|
||||
none_pending,
|
||||
fallback,
|
||||
children,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -131,6 +131,10 @@ impl AnimationFrameRequestHandle {
|
||||
|
||||
/// Runs the given function between the next repaint using
|
||||
/// [`Window.requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame).
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(feature = "tracing", instrument(level = "trace", skip_all))]
|
||||
#[inline(always)]
|
||||
pub fn request_animation_frame(cb: impl FnOnce() + 'static) {
|
||||
@@ -159,6 +163,10 @@ fn closure_once(cb: impl FnOnce() + 'static) -> JsValue {
|
||||
/// Runs the given function between the next repaint using
|
||||
/// [`Window.requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame),
|
||||
/// returning a cancelable handle.
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(feature = "tracing", instrument(level = "trace", skip_all))]
|
||||
#[inline(always)]
|
||||
pub fn request_animation_frame_with_handle(
|
||||
@@ -197,6 +205,10 @@ impl IdleCallbackHandle {
|
||||
|
||||
/// Queues the given function during an idle period using
|
||||
/// [`Window.requestIdleCallback`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestIdleCallback).
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(feature = "tracing", instrument(level = "trace", skip_all))]
|
||||
#[inline(always)]
|
||||
pub fn request_idle_callback(cb: impl Fn() + 'static) {
|
||||
@@ -206,6 +218,10 @@ pub fn request_idle_callback(cb: impl Fn() + 'static) {
|
||||
/// Queues the given function during an idle period using
|
||||
/// [`Window.requestIdleCallback`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestIdleCallback),
|
||||
/// returning a cancelable handle.
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(feature = "tracing", instrument(level = "trace", skip_all))]
|
||||
#[inline(always)]
|
||||
pub fn request_idle_callback_with_handle(
|
||||
@@ -239,6 +255,8 @@ pub fn request_idle_callback_with_handle(
|
||||
/// to perform final cleanup or other just-before-rendering tasks.
|
||||
///
|
||||
/// [MDN queueMicrotask](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask)
|
||||
///
|
||||
/// <div class="warning">The task is called outside of the ownership tree, this means that if you want to access for example the context you need to reestablish the owner.</div>
|
||||
pub fn queue_microtask(task: impl FnOnce() + 'static) {
|
||||
use js_sys::{Function, Reflect};
|
||||
|
||||
@@ -265,6 +283,10 @@ impl TimeoutHandle {
|
||||
|
||||
/// Executes the given function after the given duration of time has passed.
|
||||
/// [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout).
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
instrument(level = "trace", skip_all, fields(duration = ?duration))
|
||||
@@ -275,6 +297,10 @@ pub fn set_timeout(cb: impl FnOnce() + 'static, duration: Duration) {
|
||||
|
||||
/// Executes the given function after the given duration of time has passed, returning a cancelable handle.
|
||||
/// [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout).
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
instrument(level = "trace", skip_all, fields(duration = ?duration))
|
||||
@@ -331,6 +357,10 @@ pub fn set_timeout_with_handle(
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
pub fn debounce<T: 'static>(
|
||||
delay: Duration,
|
||||
mut cb: impl FnMut(T) + 'static,
|
||||
@@ -398,6 +428,10 @@ impl IntervalHandle {
|
||||
|
||||
/// Repeatedly calls the given function, with a delay of the given duration between calls.
|
||||
/// See [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval).
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
instrument(level = "trace", skip_all, fields(duration = ?duration))
|
||||
@@ -409,6 +443,10 @@ pub fn set_interval(cb: impl Fn() + 'static, duration: Duration) {
|
||||
/// Repeatedly calls the given function, with a delay of the given duration between calls,
|
||||
/// returning a cancelable handle.
|
||||
/// See [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval).
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
instrument(level = "trace", skip_all, fields(duration = ?duration))
|
||||
@@ -451,6 +489,10 @@ pub fn set_interval_with_handle(
|
||||
|
||||
/// Adds an event listener to the `Window`, typed as a generic `Event`,
|
||||
/// returning a cancelable handle.
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
instrument(level = "trace", skip_all, fields(event_name = %event_name))
|
||||
@@ -519,6 +561,10 @@ pub fn window_event_listener_untyped(
|
||||
/// on_cleanup(move || handle.remove());
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Note about Context
|
||||
///
|
||||
/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
|
||||
pub fn window_event_listener<E: EventDescriptor + 'static>(
|
||||
event: E,
|
||||
cb: impl Fn(E::EventType) + 'static,
|
||||
|
||||
@@ -29,14 +29,9 @@ macro_rules! error {
|
||||
macro_rules! debug_warn {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
if cfg!(debug_assertions) {
|
||||
$crate::warn!($($x)*)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
($($x)*)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_macro"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
@@ -45,6 +45,7 @@ ssr = ["server_fn_macro/ssr", "leptos/ssr"]
|
||||
nightly = ["server_fn_macro/nightly"]
|
||||
tracing = ["dep:tracing"]
|
||||
islands = []
|
||||
trace-components = []
|
||||
trace-component-props = []
|
||||
actix = ["server_fn_macro/actix"]
|
||||
axum = ["server_fn_macro/axum"]
|
||||
|
||||
@@ -201,13 +201,35 @@ impl ToTokens for Model {
|
||||
) = {
|
||||
#[cfg(feature = "tracing")]
|
||||
{
|
||||
/* TODO for 0.8: fix this
|
||||
*
|
||||
* The problem is that cargo now warns about an expected "tracing" cfg if
|
||||
* you don't have a "tracing" feature in your actual crate
|
||||
*
|
||||
* However, until https://github.com/tokio-rs/tracing/pull/1819 is merged
|
||||
* (?), you can't provide an alternate path for `tracing` (for example,
|
||||
* ::leptos::tracing), which means that if you're going to use the macro
|
||||
* you *must* have `tracing` in your Cargo.toml.
|
||||
*
|
||||
* Including the feature-check here causes cargo warnings on
|
||||
* previously-working projects.
|
||||
*
|
||||
* Removing the feature-check here breaks any project that uses leptos with
|
||||
* the tracing feature turned on, but without a tracing dependency in its
|
||||
* Cargo.toml.
|
||||
* /
|
||||
*/
|
||||
let instrument = cfg!(feature = "trace-components").then(|| quote! {
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
::leptos::tracing::instrument(level = "info", name = #trace_name, skip_all)
|
||||
)]
|
||||
});
|
||||
|
||||
(
|
||||
quote! {
|
||||
#[allow(clippy::let_with_type_underscore)]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
::leptos::tracing::instrument(level = "info", name = #trace_name, skip_all)
|
||||
)]
|
||||
#instrument
|
||||
},
|
||||
quote! {
|
||||
let __span = ::leptos::tracing::Span::current();
|
||||
@@ -260,8 +282,12 @@ impl ToTokens for Model {
|
||||
let body_name = unmodified_fn_name_from_fn_name(&body_name);
|
||||
let body_expr = if is_island {
|
||||
quote! {
|
||||
::leptos::reactive::owner::Owner::with_hydration(move || {
|
||||
#body_name(#prop_names)
|
||||
::leptos::reactive::owner::Owner::new().with(|| {
|
||||
::leptos::reactive::owner::Owner::with_hydration(move || {
|
||||
::leptos::tachys::reactive_graph::OwnedView::new({
|
||||
#body_name(#prop_names)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
} else {
|
||||
@@ -301,8 +327,8 @@ impl ToTokens for Model {
|
||||
let hydrate_fn_name = hydrate_fn_name.as_ref().unwrap();
|
||||
quote! {
|
||||
{
|
||||
if ::leptos::reactive::owner::Owner::current_shared_context()
|
||||
.map(|sc| sc.get_is_hydrating())
|
||||
if ::leptos::context::use_context::<::leptos::reactive::owner::IsHydrating>()
|
||||
.map(|h| h.0)
|
||||
.unwrap_or(false) {
|
||||
::leptos::either::Either::Left(
|
||||
#component
|
||||
@@ -339,9 +365,23 @@ impl ToTokens for Model {
|
||||
let children = Box::new(|| {
|
||||
let sc = ::leptos::reactive::owner::Owner::current_shared_context().unwrap();
|
||||
let prev = sc.get_is_hydrating();
|
||||
let value = ::leptos::reactive::owner::Owner::with_no_hydration(||
|
||||
::leptos::tachys::html::islands::IslandChildren::new(children()).into_any()
|
||||
);
|
||||
let owner = ::leptos::reactive::owner::Owner::new();
|
||||
let value = owner.clone().with(|| {
|
||||
::leptos::reactive::owner::Owner::with_no_hydration(move || {
|
||||
::leptos::tachys::reactive_graph::OwnedView::new({
|
||||
::leptos::tachys::html::islands::IslandChildren::new_with_on_hydrate(
|
||||
children(),
|
||||
{
|
||||
let owner = owner.clone();
|
||||
move || {
|
||||
owner.set()
|
||||
}
|
||||
}
|
||||
|
||||
)
|
||||
}).into_any()
|
||||
})
|
||||
});
|
||||
sc.set_is_hydrating(prev);
|
||||
value
|
||||
});
|
||||
@@ -424,20 +464,21 @@ impl ToTokens for Model {
|
||||
};
|
||||
let children = if is_island_with_children {
|
||||
quote! {
|
||||
.children({Box::new(|| {
|
||||
.children({
|
||||
let owner = leptos::reactive::owner::Owner::current();
|
||||
Box::new(move || {
|
||||
use leptos::tachys::view::any_view::IntoAny;
|
||||
::leptos::tachys::html::islands::IslandChildren::new(
|
||||
// TODO owner restoration for context
|
||||
()
|
||||
::leptos::tachys::html::islands::IslandChildren::new_with_on_hydrate(
|
||||
(),
|
||||
{
|
||||
let owner = owner.clone();
|
||||
move || {
|
||||
if let Some(owner) = &owner {
|
||||
owner.set()
|
||||
}
|
||||
}
|
||||
}
|
||||
).into_any()})})
|
||||
//.children(children)
|
||||
/*.children(Box::new(|| {
|
||||
use leptos::tachys::view::any_view::IntoAny;
|
||||
::leptos::tachys::html::islands::IslandChildren::new(
|
||||
// TODO owner restoration for context
|
||||
()
|
||||
).into_any()
|
||||
}))*/
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
@@ -655,14 +696,44 @@ impl Prop {
|
||||
abort!(e.span(), e.to_string());
|
||||
});
|
||||
|
||||
let name = if let Pat::Ident(i) = *typed.pat {
|
||||
i
|
||||
} else {
|
||||
abort!(
|
||||
typed.pat,
|
||||
"only `prop: bool` style types are allowed within the \
|
||||
`#[component]` macro"
|
||||
);
|
||||
let name = match *typed.pat {
|
||||
Pat::Ident(i) => {
|
||||
if let Some(name) = &prop_opts.name {
|
||||
PatIdent {
|
||||
attrs: vec![],
|
||||
by_ref: None,
|
||||
mutability: None,
|
||||
ident: Ident::new(name, i.span()),
|
||||
subpat: None,
|
||||
}
|
||||
} else {
|
||||
i
|
||||
}
|
||||
}
|
||||
Pat::Struct(_) | Pat::Tuple(_) | Pat::TupleStruct(_) => {
|
||||
if let Some(name) = &prop_opts.name {
|
||||
PatIdent {
|
||||
attrs: vec![],
|
||||
by_ref: None,
|
||||
mutability: None,
|
||||
ident: Ident::new(name, typed.pat.span()),
|
||||
subpat: None,
|
||||
}
|
||||
} else {
|
||||
abort!(
|
||||
typed.pat,
|
||||
"destructured props must be given a name e.g. \
|
||||
#[prop(name = \"data\")]"
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
abort!(
|
||||
typed.pat,
|
||||
"only `prop: bool` style types are allowed within the \
|
||||
`#[component]` macro"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Self {
|
||||
@@ -865,6 +936,7 @@ struct PropOpt {
|
||||
default: Option<syn::Expr>,
|
||||
into: bool,
|
||||
attrs: bool,
|
||||
name: Option<String>,
|
||||
}
|
||||
|
||||
struct TypedBuilderOpts {
|
||||
|
||||
@@ -1651,7 +1651,7 @@ pub(crate) fn ident_from_tag_name(tag_name: &NodeName) -> Ident {
|
||||
.path
|
||||
.segments
|
||||
.iter()
|
||||
.last()
|
||||
.next_back()
|
||||
.map(|segment| segment.ident.clone())
|
||||
.expect("element needs to have a name"),
|
||||
NodeName::Block(_) => {
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
use core::num::NonZeroUsize;
|
||||
use leptos::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct UserInfo {
|
||||
user_id: String,
|
||||
email: String,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct Admin(bool);
|
||||
|
||||
#[component]
|
||||
fn Component(
|
||||
#[prop(optional)] optional: bool,
|
||||
@@ -10,6 +19,10 @@ fn Component(
|
||||
#[prop(default = NonZeroUsize::new(10).unwrap())] default: NonZeroUsize,
|
||||
#[prop(into)] into: String,
|
||||
impl_trait: impl Fn() -> i32 + 'static,
|
||||
#[prop(name = "data")] UserInfo { email, user_id }: UserInfo,
|
||||
#[prop(name = "tuple")] (name, id): (String, i32),
|
||||
#[prop(name = "tuple_struct")] Admin(is_admin): Admin,
|
||||
#[prop(name = "outside_name")] inside_name: i32,
|
||||
) -> impl IntoView {
|
||||
_ = optional;
|
||||
_ = optional_into;
|
||||
@@ -18,6 +31,12 @@ fn Component(
|
||||
_ = default;
|
||||
_ = into;
|
||||
_ = impl_trait;
|
||||
_ = email;
|
||||
_ = user_id;
|
||||
_ = id;
|
||||
_ = name;
|
||||
_ = is_admin;
|
||||
_ = inside_name;
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -26,6 +45,13 @@ fn component() {
|
||||
.into("")
|
||||
.strip_option(9)
|
||||
.impl_trait(|| 42)
|
||||
.data(UserInfo {
|
||||
email: "em@il".into(),
|
||||
user_id: "1".into(),
|
||||
})
|
||||
.tuple(("Joe".into(), 12))
|
||||
.tuple_struct(Admin(true))
|
||||
.outside_name(1)
|
||||
.build();
|
||||
assert!(!cp.optional);
|
||||
assert_eq!(cp.optional_into, None);
|
||||
@@ -34,6 +60,16 @@ fn component() {
|
||||
assert_eq!(cp.default, NonZeroUsize::new(10).unwrap());
|
||||
assert_eq!(cp.into, "");
|
||||
assert_eq!((cp.impl_trait)(), 42);
|
||||
assert_eq!(
|
||||
cp.data,
|
||||
UserInfo {
|
||||
email: "em@il".into(),
|
||||
user_id: "1".into(),
|
||||
}
|
||||
);
|
||||
assert_eq!(cp.tuple, ("Joe".into(), 12));
|
||||
assert_eq!(cp.tuple_struct, Admin(true));
|
||||
assert_eq!(cp.outside_name, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -45,12 +81,26 @@ fn component_nostrip() {
|
||||
strip_option=9
|
||||
into=""
|
||||
impl_trait=|| 42
|
||||
data=UserInfo {
|
||||
email: "em@il".into(),
|
||||
user_id: "1".into(),
|
||||
}
|
||||
tuple=("Joe".into(), 12)
|
||||
tuple_struct=Admin(true)
|
||||
outside_name=1
|
||||
/>
|
||||
<Component
|
||||
nostrip:optional_into=Some("foo")
|
||||
strip_option=9
|
||||
into=""
|
||||
impl_trait=|| 42
|
||||
data=UserInfo {
|
||||
email: "em@il".into(),
|
||||
user_id: "1".into(),
|
||||
}
|
||||
tuple=("Joe".into(), 12)
|
||||
tuple_struct=Admin(true)
|
||||
outside_name=1
|
||||
/>
|
||||
};
|
||||
}
|
||||
|
||||
@@ -44,4 +44,10 @@ fn default_with_invalid_value(
|
||||
_ = default;
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn destructure_without_name((default, value): (bool, i32)) -> impl IntoView {
|
||||
_ = default;
|
||||
_ = value;
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default`, `into` and `attrs`
|
||||
error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default`, `into`, `attrs` and `name`
|
||||
--> tests/ui/component.rs:10:31
|
||||
|
|
||||
10 | fn unknown_prop_option(#[prop(hello)] test: bool) -> impl IntoView {
|
||||
@@ -41,3 +41,9 @@ error: unexpected end of input, expected one of: identifier, `::`, `<`, `_`, lit
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the attribute macro `component` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: destructured props must be given a name e.g. #[prop(name = "data")]
|
||||
--> tests/ui/component.rs:48:29
|
||||
|
|
||||
48 | fn destructure_without_name((default, value): (bool, i32)) -> impl IntoView {
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default`, `into` and `attrs`
|
||||
error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default`, `into`, `attrs` and `name`
|
||||
--> tests/ui/component_absolute.rs:5:31
|
||||
|
|
||||
5 | fn unknown_prop_option(#[prop(hello)] test: bool) -> impl ::leptos::IntoView {
|
||||
|
||||
@@ -8,8 +8,11 @@ use reactive_graph::{
|
||||
ToAnySource, ToAnySubscriber,
|
||||
},
|
||||
owner::use_context,
|
||||
signal::guards::{AsyncPlain, ReadGuard},
|
||||
traits::{DefinedAt, IsDisposed, ReadUntracked},
|
||||
signal::{
|
||||
guards::{AsyncPlain, ReadGuard},
|
||||
ArcRwSignal, RwSignal,
|
||||
},
|
||||
traits::{DefinedAt, IsDisposed, ReadUntracked, Track, Update, Write},
|
||||
};
|
||||
use send_wrapper::SendWrapper;
|
||||
use std::{
|
||||
@@ -20,6 +23,7 @@ use std::{
|
||||
/// A reference-counted resource that only loads its data locally on the client.
|
||||
pub struct ArcLocalResource<T> {
|
||||
data: ArcAsyncDerived<SendWrapper<T>>,
|
||||
refetch: ArcRwSignal<usize>,
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
@@ -28,6 +32,7 @@ impl<T> Clone for ArcLocalResource<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
data: self.data.clone(),
|
||||
refetch: self.refetch.clone(),
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
}
|
||||
@@ -65,15 +70,27 @@ impl<T> ArcLocalResource<T> {
|
||||
}
|
||||
};
|
||||
let fetcher = SendWrapper::new(fetcher);
|
||||
Self {
|
||||
data: ArcAsyncDerived::new(move || {
|
||||
let refetch = ArcRwSignal::new(0);
|
||||
let data = {
|
||||
let refetch = refetch.clone();
|
||||
ArcAsyncDerived::new(move || {
|
||||
refetch.track();
|
||||
let fut = fetcher();
|
||||
SendWrapper::new(async move { SendWrapper::new(fut.await) })
|
||||
}),
|
||||
})
|
||||
};
|
||||
Self {
|
||||
data,
|
||||
refetch,
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Re-runs the async function.
|
||||
pub fn refetch(&self) {
|
||||
*self.refetch.write() += 1;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoFuture for ArcLocalResource<T>
|
||||
@@ -200,6 +217,7 @@ impl<T> Subscriber for ArcLocalResource<T> {
|
||||
/// A resource that only loads its data locally on the client.
|
||||
pub struct LocalResource<T> {
|
||||
data: AsyncDerived<SendWrapper<T>>,
|
||||
refetch: RwSignal<usize>,
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
@@ -242,6 +260,7 @@ impl<T> LocalResource<T> {
|
||||
}
|
||||
}
|
||||
};
|
||||
let refetch = RwSignal::new(0);
|
||||
|
||||
Self {
|
||||
data: if cfg!(feature = "ssr") {
|
||||
@@ -249,14 +268,21 @@ impl<T> LocalResource<T> {
|
||||
} else {
|
||||
let fetcher = SendWrapper::new(fetcher);
|
||||
AsyncDerived::new(move || {
|
||||
refetch.track();
|
||||
let fut = fetcher();
|
||||
SendWrapper::new(async move { SendWrapper::new(fut.await) })
|
||||
})
|
||||
},
|
||||
refetch,
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Re-runs the async function.
|
||||
pub fn refetch(&self) {
|
||||
self.refetch.try_update(|n| *n += 1);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoFuture for LocalResource<T>
|
||||
@@ -398,6 +424,7 @@ impl<T: 'static> From<ArcLocalResource<T>> for LocalResource<T> {
|
||||
fn from(arc: ArcLocalResource<T>) -> Self {
|
||||
Self {
|
||||
data: arc.data.into(),
|
||||
refetch: arc.refetch.into(),
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: arc.defined_at,
|
||||
}
|
||||
@@ -408,6 +435,7 @@ impl<T: 'static> From<LocalResource<T>> for ArcLocalResource<T> {
|
||||
fn from(local: LocalResource<T>) -> Self {
|
||||
Self {
|
||||
data: local.data.into(),
|
||||
refetch: local.refetch.into(),
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: local.defined_at,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_meta"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
@@ -26,6 +26,7 @@ features = ["HtmlLinkElement", "HtmlMetaElement", "HtmlTitleElement"]
|
||||
default = []
|
||||
ssr = []
|
||||
tracing = ["dep:tracing"]
|
||||
nonce = ["leptos/nonce"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
@@ -47,6 +47,7 @@ use leptos::{
|
||||
attr::NextAttribute,
|
||||
component,
|
||||
logging::debug_warn,
|
||||
oco::Oco,
|
||||
reactive::owner::{provide_context, use_context},
|
||||
tachys::{
|
||||
dom::document,
|
||||
@@ -566,3 +567,25 @@ impl RenderHtml for MetaTagsView {
|
||||
) -> Self::State {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait OrDefaultNonce {
|
||||
fn or_default_nonce(self) -> Option<Oco<'static, str>>;
|
||||
}
|
||||
|
||||
impl OrDefaultNonce for Option<Oco<'static, str>> {
|
||||
fn or_default_nonce(self) -> Option<Oco<'static, str>> {
|
||||
#[cfg(feature = "nonce")]
|
||||
{
|
||||
use leptos::nonce::use_nonce;
|
||||
|
||||
match self {
|
||||
Some(nonce) => Some(nonce),
|
||||
None => use_nonce().map(|n| Arc::clone(n.as_inner()).into()),
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "nonce"))]
|
||||
{
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::register;
|
||||
use crate::{register, OrDefaultNonce};
|
||||
use leptos::{
|
||||
component, oco::Oco, prelude::*, tachys::html::element::script, IntoView,
|
||||
};
|
||||
@@ -74,7 +74,7 @@ pub fn Script(
|
||||
.fetchpriority(fetchpriority)
|
||||
.integrity(integrity)
|
||||
.nomodule(nomodule)
|
||||
.nonce(nonce)
|
||||
.nonce(nonce.or_default_nonce())
|
||||
.referrerpolicy(referrerpolicy)
|
||||
.src(src)
|
||||
.r#type(type_)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::register;
|
||||
use crate::{register, OrDefaultNonce};
|
||||
use leptos::{
|
||||
component, oco::Oco, prelude::*, tachys::html::element::style, IntoView,
|
||||
};
|
||||
@@ -48,7 +48,7 @@ pub fn Style(
|
||||
style()
|
||||
.id(id)
|
||||
.media(media)
|
||||
.nonce(nonce)
|
||||
.nonce(nonce.or_default_nonce())
|
||||
.title(title)
|
||||
.blocking(blocking)
|
||||
.child(children.map(|c| c())),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_graph"
|
||||
version = "0.1.1"
|
||||
version = "0.1.4"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
@@ -25,7 +25,7 @@ async-lock = "3.4.0"
|
||||
send_wrapper = { version = "0.6.0", features = ["futures"] }
|
||||
|
||||
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
|
||||
web-sys = "0.3.72"
|
||||
web-sys = { version = "0.3.72", features = ["console"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.41", features = ["rt-multi-thread", "macros"] }
|
||||
|
||||
@@ -272,7 +272,10 @@ macro_rules! spawn_derived {
|
||||
// so that the correct value is set synchronously
|
||||
let initial = initial_fut.as_mut().now_or_never();
|
||||
match initial {
|
||||
None => (false, Some(initial_fut)),
|
||||
None => {
|
||||
inner.write().or_poisoned().notifier.notify();
|
||||
(false, Some(initial_fut))
|
||||
}
|
||||
Some(orig_value) => {
|
||||
let mut guard = this.inner.write().or_poisoned();
|
||||
|
||||
@@ -296,10 +299,6 @@ macro_rules! spawn_derived {
|
||||
if was_ready {
|
||||
first_run.take();
|
||||
}
|
||||
// begin loading eagerly but asynchronously, if not already loaded
|
||||
if !was_ready {
|
||||
any_subscriber.mark_dirty();
|
||||
}
|
||||
|
||||
if let Some(source) = $source {
|
||||
any_subscriber.with_observer(|| source.track());
|
||||
@@ -312,6 +311,18 @@ macro_rules! spawn_derived {
|
||||
let wakers = Arc::downgrade(&this.wakers);
|
||||
let loading = Arc::downgrade(&this.loading);
|
||||
let fut = async move {
|
||||
// if the AsyncDerived has *already* been marked dirty (i.e., one of its
|
||||
// sources has changed after creation), we should throw out the Future
|
||||
// we already created, because its values might be stale
|
||||
let already_dirty = inner.upgrade()
|
||||
.as_ref()
|
||||
.and_then(|inner| inner.read().ok())
|
||||
.map(|inner| inner.state == AsyncDerivedState::Dirty)
|
||||
.unwrap_or(false);
|
||||
if already_dirty {
|
||||
initial_fut.take();
|
||||
}
|
||||
|
||||
while rx.next().await.is_some() {
|
||||
let update_if_necessary = if $should_track {
|
||||
any_subscriber
|
||||
@@ -413,7 +424,10 @@ impl<T: 'static> ArcAsyncDerived<T> {
|
||||
) {
|
||||
loading.store(false, Ordering::Relaxed);
|
||||
|
||||
inner.write().or_poisoned().state = AsyncDerivedState::Notifying;
|
||||
let prev_state = mem::replace(
|
||||
&mut inner.write().or_poisoned().state,
|
||||
AsyncDerivedState::Notifying,
|
||||
);
|
||||
|
||||
if let Some(ready_tx) = ready_tx {
|
||||
// if it's an Err, that just means the Receiver was dropped
|
||||
@@ -433,7 +447,10 @@ impl<T: 'static> ArcAsyncDerived<T> {
|
||||
waker.wake();
|
||||
}
|
||||
|
||||
inner.write().or_poisoned().state = AsyncDerivedState::Clean;
|
||||
// if this was marked dirty before notifications began, this means it
|
||||
// had been notified while loading; marking it clean will cause it not to
|
||||
// run on the next tick of the async loop, so here it should be left dirty
|
||||
inner.write().or_poisoned().state = prev_state;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -292,6 +292,8 @@ impl Owner {
|
||||
#[cfg(feature = "hydration")]
|
||||
pub fn with_hydration<T>(fun: impl FnOnce() -> T + 'static) -> T {
|
||||
fn inner<T>(fun: Box<dyn FnOnce() -> T>) -> T {
|
||||
provide_context(IsHydrating(true));
|
||||
|
||||
let sc = OWNER.with_borrow(|o| {
|
||||
o.as_ref()
|
||||
.and_then(|current| current.shared_context.clone())
|
||||
@@ -315,6 +317,8 @@ impl Owner {
|
||||
#[cfg(feature = "hydration")]
|
||||
pub fn with_no_hydration<T>(fun: impl FnOnce() -> T + 'static) -> T {
|
||||
fn inner<T>(fun: Box<dyn FnOnce() -> T>) -> T {
|
||||
provide_context(IsHydrating(false));
|
||||
|
||||
let sc = OWNER.with_borrow(|o| {
|
||||
o.as_ref()
|
||||
.and_then(|current| current.shared_context.clone())
|
||||
@@ -335,6 +339,10 @@ impl Owner {
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct IsHydrating(pub bool);
|
||||
|
||||
/// Registers a function to be run the next time the current owner is cleaned up.
|
||||
///
|
||||
/// Because the ownership model is associated with reactive nodes, each "decision point" in an
|
||||
|
||||
@@ -104,7 +104,7 @@ impl<T: 'static> Debug for Plain<T> {
|
||||
impl<T: 'static> Plain<T> {
|
||||
/// Takes a reference-counted read guard on the given lock.
|
||||
pub fn try_new(inner: Arc<RwLock<T>>) -> Option<Self> {
|
||||
ArcRwLockReadGuardian::take(inner)
|
||||
ArcRwLockReadGuardian::try_take(inner)?
|
||||
.ok()
|
||||
.map(|guard| Plain { guard })
|
||||
}
|
||||
@@ -331,7 +331,7 @@ pub struct UntrackedWriteGuard<T: 'static>(ArcRwLockWriteGuardian<T>);
|
||||
impl<T: 'static> UntrackedWriteGuard<T> {
|
||||
/// Creates a write guard from the given lock.
|
||||
pub fn try_new(inner: Arc<RwLock<T>>) -> Option<Self> {
|
||||
ArcRwLockWriteGuardian::take(inner)
|
||||
ArcRwLockWriteGuardian::try_take(inner)?
|
||||
.ok()
|
||||
.map(UntrackedWriteGuard)
|
||||
}
|
||||
|
||||
@@ -103,6 +103,7 @@ pub trait Dispose {
|
||||
/// Allows tracking the value of some reactive data.
|
||||
pub trait Track {
|
||||
/// Subscribes to this signal in the current reactive scope without doing anything with its value.
|
||||
#[track_caller]
|
||||
fn track(&self);
|
||||
}
|
||||
|
||||
@@ -404,6 +405,7 @@ where
|
||||
/// Notifies subscribers of a change in this signal.
|
||||
pub trait Notify {
|
||||
/// Notifies subscribers of a change in this signal.
|
||||
#[track_caller]
|
||||
fn notify(&self);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_stores"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -450,10 +450,7 @@ where
|
||||
let inner = self.inner.reader()?;
|
||||
|
||||
let inner_path = self.inner.path().into_iter().collect();
|
||||
let keys = self
|
||||
.inner
|
||||
.keys()
|
||||
.expect("using keys on a store with no keys");
|
||||
let keys = self.inner.keys()?;
|
||||
let index = keys
|
||||
.with_field_keys(
|
||||
inner_path,
|
||||
@@ -461,8 +458,7 @@ where
|
||||
|| self.inner.latest_keys(),
|
||||
)
|
||||
.flatten()
|
||||
.map(|(_, idx)| idx)
|
||||
.expect("reading from a keyed field that has not yet been created");
|
||||
.map(|(_, idx)| idx)?;
|
||||
|
||||
Some(MappedMutArc::new(
|
||||
inner,
|
||||
@@ -654,8 +650,8 @@ where
|
||||
#[track_caller]
|
||||
fn into_iter(self) -> StoreFieldKeyedIter<Inner, Prev, K, T> {
|
||||
// reactively track changes to this field
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.this.track();
|
||||
self.update_keys();
|
||||
self.track_field();
|
||||
|
||||
// get the current length of the field by accessing slice
|
||||
let reader = self
|
||||
|
||||
@@ -114,8 +114,8 @@ use reactive_graph::{
|
||||
ArcTrigger,
|
||||
},
|
||||
traits::{
|
||||
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
|
||||
Write,
|
||||
DefinedAt, Dispose, IsDisposed, Notify, ReadUntracked, Track,
|
||||
UntrackableGuard, Write,
|
||||
},
|
||||
};
|
||||
pub use reactive_stores_macro::{Patch, Store};
|
||||
@@ -335,6 +335,12 @@ impl<T> ArcStore<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default> Default for ArcStore<T> {
|
||||
fn default() -> Self {
|
||||
Self::new(T::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for ArcStore<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut f = f.debug_struct("ArcStore");
|
||||
@@ -468,6 +474,24 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Store<T>
|
||||
where
|
||||
T: Default + Send + Sync + 'static,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::new(T::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Store<T, LocalStorage>
|
||||
where
|
||||
T: Default + 'static,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::new_local(T::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug, S> Debug for Store<T, S>
|
||||
where
|
||||
S: Debug,
|
||||
@@ -511,6 +535,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> Dispose for Store<T, S>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
fn dispose(self) {
|
||||
self.inner.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> ReadUntracked for Store<T, S>
|
||||
where
|
||||
T: 'static,
|
||||
|
||||
@@ -24,12 +24,15 @@ pub trait StoreField: Sized {
|
||||
type Writer: UntrackableGuard<Target = Self::Value>;
|
||||
|
||||
/// Returns the trigger that tracks access and updates for this field.
|
||||
#[track_caller]
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger;
|
||||
|
||||
/// The path of this field (see [`StorePath`]).
|
||||
#[track_caller]
|
||||
fn path(&self) -> impl IntoIterator<Item = StorePathSegment>;
|
||||
|
||||
/// Reactively tracks this field.
|
||||
#[track_caller]
|
||||
fn track_field(&self) {
|
||||
let path = self.path().into_iter().collect();
|
||||
let trigger = self.get_trigger(path);
|
||||
@@ -38,12 +41,15 @@ pub trait StoreField: Sized {
|
||||
}
|
||||
|
||||
/// Returns a read guard to access this field.
|
||||
#[track_caller]
|
||||
fn reader(&self) -> Option<Self::Reader>;
|
||||
|
||||
/// Returns a write guard to update this field.
|
||||
#[track_caller]
|
||||
fn writer(&self) -> Option<Self::Writer>;
|
||||
|
||||
/// The keys for this field, if it is a keyed field.
|
||||
#[track_caller]
|
||||
fn keys(&self) -> Option<KeyMap>;
|
||||
}
|
||||
|
||||
@@ -55,26 +61,31 @@ where
|
||||
type Reader = Plain<T>;
|
||||
type Writer = WriteGuard<ArcTrigger, UntrackedWriteGuard<T>>;
|
||||
|
||||
#[track_caller]
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
|
||||
let triggers = &self.signals;
|
||||
let trigger = triggers.write().or_poisoned().get_or_insert(path);
|
||||
trigger
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn path(&self) -> impl IntoIterator<Item = StorePathSegment> {
|
||||
iter::empty()
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn reader(&self) -> Option<Self::Reader> {
|
||||
Plain::try_new(Arc::clone(&self.value))
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn writer(&self) -> Option<Self::Writer> {
|
||||
let trigger = self.get_trigger(Default::default());
|
||||
let guard = UntrackedWriteGuard::try_new(Arc::clone(&self.value))?;
|
||||
Some(WriteGuard::new(trigger.children, guard))
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn keys(&self) -> Option<KeyMap> {
|
||||
Some(self.keys.clone())
|
||||
}
|
||||
@@ -89,6 +100,7 @@ where
|
||||
type Reader = Plain<T>;
|
||||
type Writer = WriteGuard<ArcTrigger, UntrackedWriteGuard<T>>;
|
||||
|
||||
#[track_caller]
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
|
||||
self.inner
|
||||
.try_get_value()
|
||||
@@ -96,6 +108,7 @@ where
|
||||
.unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn path(&self) -> impl IntoIterator<Item = StorePathSegment> {
|
||||
self.inner
|
||||
.try_get_value()
|
||||
@@ -103,14 +116,17 @@ where
|
||||
.unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn reader(&self) -> Option<Self::Reader> {
|
||||
self.inner.try_get_value().and_then(|n| n.reader())
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn writer(&self) -> Option<Self::Writer> {
|
||||
self.inner.try_get_value().and_then(|n| n.writer())
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn keys(&self) -> Option<KeyMap> {
|
||||
self.inner.try_get_value().and_then(|inner| inner.keys())
|
||||
}
|
||||
|
||||
@@ -103,6 +103,7 @@ where
|
||||
self.inner.keys()
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn track_field(&self) {
|
||||
let inner = self
|
||||
.inner
|
||||
@@ -144,6 +145,7 @@ where
|
||||
Inner: StoreField<Value = Prev>,
|
||||
Prev: 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn notify(&self) {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.this.notify();
|
||||
@@ -157,6 +159,7 @@ where
|
||||
Prev: 'static,
|
||||
T: 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn track(&self) {
|
||||
self.track_field();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_router"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
authors = ["Greg Johnston", "Ben Wishovich"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -32,7 +32,7 @@ use tachys::view::any_view::AnyView;
|
||||
|
||||
/// A wrapper that allows passing route definitions as children to a component like [`Routes`],
|
||||
/// [`FlatRoutes`], [`ParentRoute`], or [`ProtectedParentRoute`].
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RouteChildren<Children>(Children);
|
||||
|
||||
impl<Children> RouteChildren<Children> {
|
||||
@@ -82,13 +82,18 @@ where
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
let (location_provider, current_url, redirect_hook) = {
|
||||
let owner = Owner::current();
|
||||
let location =
|
||||
BrowserUrl::new().expect("could not access browser navigation"); // TODO options here
|
||||
location.init(base.clone());
|
||||
provide_context(location.clone());
|
||||
let current_url = location.as_url().clone();
|
||||
|
||||
let redirect_hook = Box::new(|loc: &str| BrowserUrl::redirect(loc));
|
||||
let redirect_hook = Box::new(move |loc: &str| {
|
||||
if let Some(owner) = &owner {
|
||||
owner.with(|| BrowserUrl::redirect(loc));
|
||||
}
|
||||
});
|
||||
|
||||
(Some(location), current_url, redirect_hook)
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_router_macro"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
authors = ["Greg Johnston", "Ben Wishovich"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -231,4 +231,4 @@ skip_feature_sets = [
|
||||
]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
|
||||
|
||||
@@ -117,7 +117,7 @@ impl<T> JsonStream<T> {
|
||||
pub fn new(
|
||||
value: impl Stream<Item = Result<T, ServerFnError>> + Send + 'static,
|
||||
) -> Self {
|
||||
Self(Box::pin(value.map(|value| value.map(Into::into))))
|
||||
Self(Box::pin(value.map(|value| value)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ impl TextStream {
|
||||
pub fn new(
|
||||
value: impl Stream<Item = Result<String, ServerFnError>> + Send + 'static,
|
||||
) -> Self {
|
||||
Self(Box::pin(value.map(|value| value.map(Into::into))))
|
||||
Self(Box::pin(value.map(|value| value)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tachys"
|
||||
version = "0.1.2"
|
||||
version = "0.1.4"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
renderer::Rndr,
|
||||
view::{Position, ToTemplate},
|
||||
};
|
||||
use std::{future::Future, sync::Arc};
|
||||
use std::{borrow::Cow, future::Future, sync::Arc};
|
||||
|
||||
/// Adds a CSS class.
|
||||
#[inline(always)]
|
||||
@@ -324,6 +324,63 @@ impl IntoClass for &str {
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoClass for Cow<'_, str> {
|
||||
type AsyncOutput = Self;
|
||||
type State = (crate::renderer::types::Element, Self);
|
||||
type Cloneable = Arc<str>;
|
||||
type CloneableOwned = Arc<str>;
|
||||
|
||||
fn html_len(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn to_html(self, class: &mut String) {
|
||||
IntoClass::to_html(&*self, class);
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
el: &crate::renderer::types::Element,
|
||||
) -> Self::State {
|
||||
if !FROM_SERVER {
|
||||
Rndr::set_attribute(el, "class", &self);
|
||||
}
|
||||
(el.clone(), self)
|
||||
}
|
||||
|
||||
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
|
||||
Rndr::set_attribute(el, "class", &self);
|
||||
(el.clone(), self)
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
let (el, prev) = state;
|
||||
if self != *prev {
|
||||
Rndr::set_attribute(el, "class", &self);
|
||||
}
|
||||
*prev = self;
|
||||
}
|
||||
|
||||
fn into_cloneable(self) -> Self::Cloneable {
|
||||
self.into()
|
||||
}
|
||||
|
||||
fn into_cloneable_owned(self) -> Self::CloneableOwned {
|
||||
self.into()
|
||||
}
|
||||
|
||||
fn dry_resolve(&mut self) {}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
|
||||
fn reset(state: &mut Self::State) {
|
||||
let (el, _prev) = state;
|
||||
Rndr::remove_attribute(el, "class");
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoClass for String {
|
||||
type AsyncOutput = Self;
|
||||
type State = (crate::renderer::types::Element, Self);
|
||||
|
||||
@@ -176,6 +176,7 @@ where
|
||||
cursor.sibling();
|
||||
}
|
||||
position.set(Position::FirstChild);
|
||||
|
||||
self.view.hydrate::<FROM_SERVER>(cursor, position)
|
||||
}
|
||||
}
|
||||
@@ -183,12 +184,28 @@ where
|
||||
/// The children that will be projected into an [`Island`].
|
||||
pub struct IslandChildren<View> {
|
||||
view: View,
|
||||
on_hydrate: Option<Box<dyn Fn() + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl<View> IslandChildren<View> {
|
||||
/// Creates a new representation of the children.
|
||||
pub fn new(view: View) -> Self {
|
||||
IslandChildren { view }
|
||||
IslandChildren {
|
||||
view,
|
||||
on_hydrate: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new representation of the children, with a function to be called whenever
|
||||
/// a child island hydrates.
|
||||
pub fn new_with_on_hydrate(
|
||||
view: View,
|
||||
on_hydrate: impl Fn() + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
IslandChildren {
|
||||
view,
|
||||
on_hydrate: Some(Box::new(on_hydrate)),
|
||||
}
|
||||
}
|
||||
|
||||
fn open_tag(buf: &mut String) {
|
||||
@@ -229,9 +246,10 @@ where
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml,
|
||||
{
|
||||
let IslandChildren { view } = self;
|
||||
let IslandChildren { view, on_hydrate } = self;
|
||||
IslandChildren {
|
||||
view: view.add_any_attr(attr),
|
||||
on_hydrate,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -252,9 +270,10 @@ where
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
let IslandChildren { view } = self;
|
||||
let IslandChildren { view, on_hydrate } = self;
|
||||
IslandChildren {
|
||||
view: view.resolve().await,
|
||||
on_hydrate,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,5 +332,28 @@ where
|
||||
} else if curr_position != Position::Current {
|
||||
cursor.sibling();
|
||||
}
|
||||
|
||||
if let Some(on_hydrate) = self.on_hydrate {
|
||||
use crate::{
|
||||
hydration::failed_to_cast_element, renderer::CastFrom,
|
||||
};
|
||||
|
||||
let el =
|
||||
crate::renderer::types::Element::cast_from(cursor.current())
|
||||
.unwrap_or_else(|| {
|
||||
failed_to_cast_element(
|
||||
"leptos-children",
|
||||
cursor.current(),
|
||||
)
|
||||
});
|
||||
let cb = wasm_bindgen::closure::Closure::wrap(
|
||||
on_hydrate as Box<dyn Fn()>,
|
||||
);
|
||||
_ = js_sys::Reflect::set(
|
||||
&el,
|
||||
&wasm_bindgen::JsValue::from_str("$$on_hydrate"),
|
||||
&cb.into_js_value(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,6 +339,12 @@ where
|
||||
&mut fallback_position,
|
||||
mark_branches,
|
||||
);
|
||||
|
||||
// TODO in 0.8: this should include a nonce
|
||||
// we do have access to nonces via context (because this is the `reactive_graph` module)
|
||||
// but unfortunately the Nonce type is defined in `leptos`, not in `tachys`
|
||||
//
|
||||
// missing it here only affects top-level Suspend, not Suspense components
|
||||
buf.push_async_out_of_order(
|
||||
fut,
|
||||
position,
|
||||
|
||||
@@ -6,6 +6,7 @@ use std::{
|
||||
future::Future,
|
||||
mem,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
@@ -161,6 +162,24 @@ impl StreamBuilder {
|
||||
mark_branches: bool,
|
||||
) where
|
||||
View: RenderHtml,
|
||||
{
|
||||
self.push_async_out_of_order_with_nonce(
|
||||
view,
|
||||
position,
|
||||
mark_branches,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
/// Injects an out-of-order chunk into the stream, using the given nonce for `<script>` tags.
|
||||
pub fn push_async_out_of_order_with_nonce<View>(
|
||||
&mut self,
|
||||
view: impl Future<Output = Option<View>> + Send + 'static,
|
||||
position: &mut Position,
|
||||
mark_branches: bool,
|
||||
nonce: Option<Arc<str>>,
|
||||
) where
|
||||
View: RenderHtml,
|
||||
{
|
||||
let id = self.clone_id();
|
||||
// copy so it's not updated by additional iterations
|
||||
@@ -196,6 +215,7 @@ impl StreamBuilder {
|
||||
id,
|
||||
chunks,
|
||||
replace,
|
||||
nonce,
|
||||
}
|
||||
}),
|
||||
});
|
||||
@@ -234,6 +254,7 @@ pub struct OooChunk {
|
||||
id: String,
|
||||
chunks: VecDeque<StreamChunk>,
|
||||
replace: bool,
|
||||
nonce: Option<Arc<str>>,
|
||||
}
|
||||
|
||||
impl OooChunk {
|
||||
@@ -247,11 +268,25 @@ impl OooChunk {
|
||||
|
||||
/// Pushes a closing `</template>` and update script into the buffer.
|
||||
pub fn push_end(replace: bool, id: &str, buf: &mut String) {
|
||||
Self::push_end_with_nonce(replace, id, buf, None);
|
||||
}
|
||||
|
||||
/// Pushes a closing `</template>` and update script with the given nonce into the buffer.
|
||||
pub fn push_end_with_nonce(
|
||||
replace: bool,
|
||||
id: &str,
|
||||
buf: &mut String,
|
||||
nonce: Option<&str>,
|
||||
) {
|
||||
buf.push_str("</template>");
|
||||
|
||||
// TODO nonce
|
||||
buf.push_str("<script");
|
||||
buf.push_str(r#">(function() { let id = ""#);
|
||||
if let Some(nonce) = nonce {
|
||||
buf.push_str("<script nonce=\"");
|
||||
buf.push_str(nonce);
|
||||
buf.push_str(r#"">(function() { let id = ""#);
|
||||
} else {
|
||||
buf.push_str(r#"<script>(function() { let id = ""#);
|
||||
}
|
||||
buf.push_str(id);
|
||||
buf.push_str(
|
||||
"\";let open = undefined;let close = undefined;let walker = \
|
||||
@@ -324,6 +359,7 @@ impl Stream for StreamBuilder {
|
||||
id,
|
||||
chunks,
|
||||
replace,
|
||||
nonce,
|
||||
}) => {
|
||||
let opening = format!("<!--s-{id}o-->");
|
||||
let placeholder_at =
|
||||
@@ -369,10 +405,11 @@ impl Stream for StreamBuilder {
|
||||
this.chunks.push_front(chunk);
|
||||
}
|
||||
}
|
||||
OooChunk::push_end(
|
||||
OooChunk::push_end_with_nonce(
|
||||
replace,
|
||||
&id,
|
||||
&mut this.sync_buf,
|
||||
nonce.as_deref(),
|
||||
);
|
||||
}
|
||||
self.poll_next(cx)
|
||||
|
||||
@@ -220,7 +220,7 @@ impl<const V: &'static str> RenderHtml for Static<V> {
|
||||
}
|
||||
|
||||
impl<const V: &'static str> AddAnyAttr for Static<V> {
|
||||
type Output<SomeNewAttr: Attribute> = Static<V>;
|
||||
type Output<NewAttr: Attribute> = Static<V>;
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
@@ -229,7 +229,15 @@ impl<const V: &'static str> AddAnyAttr for Static<V> {
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml,
|
||||
{
|
||||
todo!()
|
||||
// inline helper function to assist the compiler with type inference
|
||||
#[inline(always)]
|
||||
const fn create_static<const S: &'static str, A: Attribute>(
|
||||
) -> <Static<S> as AddAnyAttr>::Output<A> {
|
||||
Static
|
||||
}
|
||||
|
||||
// call the helper function with the current const value and new attribute type
|
||||
create_static::<V, NewAttr>()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user