mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 13:43:01 -05:00
Compare commits
21 Commits
cargo-lept
...
pause-effe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d733b54b1 | ||
|
|
8284f303d5 | ||
|
|
1dab980b02 | ||
|
|
4612125e5e | ||
|
|
53853ab105 | ||
|
|
7be6a9da86 | ||
|
|
c51e07b5a4 | ||
|
|
b17a4c92c7 | ||
|
|
03f9c6cb6d | ||
|
|
9e8b8886da | ||
|
|
6a6b3dee15 | ||
|
|
5d71913523 | ||
|
|
706617ab0a | ||
|
|
cd64bb9d67 | ||
|
|
f881c1877d | ||
|
|
8783f6a478 | ||
|
|
2add714c65 | ||
|
|
88b1b2d882 | ||
|
|
9d3a743d33 | ||
|
|
c6de7c714e | ||
|
|
6154199850 |
2
.github/workflows/run-cargo-make-task.yml
vendored
2
.github/workflows/run-cargo-make-task.yml
vendored
@@ -55,7 +55,7 @@ jobs:
|
||||
- name: Install wasm-bindgen
|
||||
run: cargo binstall wasm-bindgen-cli --no-confirm
|
||||
- name: Install cargo-leptos
|
||||
run: cargo binstall cargo-leptos --no-confirm
|
||||
run: cargo binstall cargo-leptos --locked --no-confirm
|
||||
- name: Install Trunk
|
||||
uses: jetli/trunk-action@v0.5.0
|
||||
with:
|
||||
|
||||
42
Cargo.lock
generated
42
Cargo.lock
generated
@@ -1718,7 +1718,7 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "leptos"
|
||||
version = "0.7.5"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"base64",
|
||||
@@ -1769,7 +1769,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_actix"
|
||||
version = "0.7.5"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"actix-files",
|
||||
"actix-http",
|
||||
@@ -1794,7 +1794,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_axum"
|
||||
version = "0.7.5"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"axum",
|
||||
@@ -1817,7 +1817,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_config"
|
||||
version = "0.7.5"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"config",
|
||||
"regex",
|
||||
@@ -1831,7 +1831,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_dom"
|
||||
version = "0.7.5"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"leptos",
|
||||
@@ -1848,7 +1848,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_hot_reload"
|
||||
version = "0.7.5"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
@@ -1864,7 +1864,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_integration_utils"
|
||||
version = "0.7.5"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"hydration_context",
|
||||
@@ -1877,7 +1877,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_macro"
|
||||
version = "0.7.5"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"attribute-derive",
|
||||
"cfg-if",
|
||||
@@ -1896,7 +1896,7 @@ dependencies = [
|
||||
"rstml",
|
||||
"serde",
|
||||
"server_fn",
|
||||
"server_fn_macro 0.7.5",
|
||||
"server_fn_macro 0.7.7",
|
||||
"syn 2.0.90",
|
||||
"tracing",
|
||||
"trybuild",
|
||||
@@ -1906,7 +1906,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_meta"
|
||||
version = "0.7.5"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"indexmap",
|
||||
@@ -1921,7 +1921,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_router"
|
||||
version = "0.7.5"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"either_of",
|
||||
@@ -1945,7 +1945,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_router_macro"
|
||||
version = "0.7.5"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"leptos_macro",
|
||||
"leptos_router",
|
||||
@@ -1957,7 +1957,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_server"
|
||||
version = "0.7.5"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"base64",
|
||||
@@ -2659,7 +2659,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reactive_graph"
|
||||
version = "0.1.5"
|
||||
version = "0.1.7"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"async-lock",
|
||||
@@ -2681,7 +2681,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reactive_stores"
|
||||
version = "0.1.5"
|
||||
version = "0.1.7"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"guardian",
|
||||
@@ -2698,7 +2698,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reactive_stores_macro"
|
||||
version = "0.1.5"
|
||||
version = "0.1.7"
|
||||
dependencies = [
|
||||
"convert_case 0.6.0",
|
||||
"proc-macro-error2",
|
||||
@@ -3155,7 +3155,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "server_fn"
|
||||
version = "0.7.5"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"axum",
|
||||
@@ -3211,7 +3211,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "server_fn_macro"
|
||||
version = "0.7.5"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"const_format",
|
||||
"convert_case 0.6.0",
|
||||
@@ -3223,9 +3223,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "server_fn_macro_default"
|
||||
version = "0.7.5"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"server_fn_macro 0.7.5",
|
||||
"server_fn_macro 0.7.7",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
@@ -3419,7 +3419,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tachys"
|
||||
version = "0.1.5"
|
||||
version = "0.1.7"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"async-trait",
|
||||
|
||||
36
Cargo.toml
36
Cargo.toml
@@ -40,7 +40,7 @@ members = [
|
||||
exclude = ["benchmarks", "examples", "projects"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.7.5"
|
||||
version = "0.7.7"
|
||||
edition = "2021"
|
||||
rust-version = "1.76"
|
||||
|
||||
@@ -50,26 +50,26 @@ any_spawner = { path = "./any_spawner/", version = "0.2.0" }
|
||||
const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1" }
|
||||
either_of = { path = "./either_of/", version = "0.1.0" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.2.0" }
|
||||
leptos = { path = "./leptos", version = "0.7.5" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.5" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.5" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.5" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.5" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.5" }
|
||||
leptos_router = { path = "./router", version = "0.7.5" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.5" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.5" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.5" }
|
||||
leptos = { path = "./leptos", version = "0.7.7" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.7" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.7" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.7" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.7" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.7" }
|
||||
leptos_router = { path = "./router", version = "0.7.7" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.7" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.7" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.7" }
|
||||
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.5" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.1.3" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.0" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.5" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.5" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.5" }
|
||||
tachys = { path = "./tachys", version = "0.1.5" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.1.7" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.1.7" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.7" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.7" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.7" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.7" }
|
||||
tachys = { path = "./tachys", version = "0.1.7" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_macro"
|
||||
version = "0.7.5"
|
||||
version = { workspace = true }
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
@@ -34,7 +34,7 @@ log = "0.4.22"
|
||||
typed-builder = "0.20.0"
|
||||
trybuild = "1.0"
|
||||
leptos = { path = "../leptos" }
|
||||
leptos_router = { path = "../router", features= ["ssr"] }
|
||||
leptos_router = { path = "../router", features = ["ssr"] }
|
||||
server_fn = { path = "../server_fn", features = ["cbor"] }
|
||||
insta = "1.41"
|
||||
serde = "1.0"
|
||||
|
||||
@@ -144,8 +144,6 @@ impl ToTokens for Model {
|
||||
let (impl_generics, generics, where_clause) =
|
||||
body.sig.generics.split_for_impl();
|
||||
|
||||
let lifetimes = body.sig.generics.lifetimes();
|
||||
|
||||
let props_name = format_ident!("{name}Props");
|
||||
let props_builder_name = format_ident!("{name}PropsBuilder");
|
||||
let props_serialized_name = format_ident!("{name}PropsSerialized");
|
||||
@@ -570,7 +568,7 @@ impl ToTokens for Model {
|
||||
#tracing_instrument_attr
|
||||
#vis fn #name #impl_generics (
|
||||
#props_arg
|
||||
) #ret #(+ #lifetimes)*
|
||||
) #ret
|
||||
#where_clause
|
||||
{
|
||||
#body
|
||||
|
||||
@@ -677,17 +677,21 @@ fn component_macro(
|
||||
#[allow(non_snake_case, dead_code, clippy::too_many_arguments, clippy::needless_lifetimes)]
|
||||
#unexpanded
|
||||
}
|
||||
} else if let Ok(mut dummy) = dummy {
|
||||
dummy.sig.ident = unmodified_fn_name_from_fn_name(&dummy.sig.ident);
|
||||
quote! {
|
||||
#[doc(hidden)]
|
||||
#[allow(non_snake_case, dead_code, clippy::too_many_arguments, clippy::needless_lifetimes)]
|
||||
#dummy
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
}
|
||||
.into()
|
||||
match dummy {
|
||||
Ok(mut dummy) => {
|
||||
dummy.sig.ident = unmodified_fn_name_from_fn_name(&dummy.sig.ident);
|
||||
quote! {
|
||||
#[doc(hidden)]
|
||||
#[allow(non_snake_case, dead_code, clippy::too_many_arguments, clippy::needless_lifetimes)]
|
||||
#dummy
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
proc_macro_error2::abort!(e.span(), e);
|
||||
}
|
||||
}
|
||||
}.into()
|
||||
}
|
||||
|
||||
/// Annotates a struct so that it can be used with your Component as a `slot`.
|
||||
|
||||
@@ -154,7 +154,12 @@ fn is_inert_element(orig_node: &Node<impl CustomNode>) -> bool {
|
||||
Some(value) => {
|
||||
matches!(&value.value, KVAttributeValue::Expr(expr) if {
|
||||
if let Expr::Lit(lit) = expr {
|
||||
matches!(&lit.lit, Lit::Str(_))
|
||||
let key = attr.key.to_string();
|
||||
if key.starts_with("style:") || key.starts_with("prop:") || key.starts_with("on:") || key.starts_with("use:") || key.starts_with("bind") {
|
||||
false
|
||||
} else {
|
||||
matches!(&lit.lit, Lit::Str(_))
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -1174,8 +1179,7 @@ pub(crate) fn event_type_and_handler(
|
||||
) -> (TokenStream, TokenStream, TokenStream) {
|
||||
let handler = attribute_value(node, false);
|
||||
|
||||
let (event_type, is_custom, is_force_undelegated, is_targeted) =
|
||||
parse_event_name(name);
|
||||
let (event_type, is_custom, options) = parse_event_name(name);
|
||||
|
||||
let event_name_ident = match &node.key {
|
||||
NodeName::Punctuated(parts) => {
|
||||
@@ -1193,11 +1197,17 @@ pub(crate) fn event_type_and_handler(
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let capture_ident = match &node.key {
|
||||
NodeName::Punctuated(parts) => {
|
||||
parts.iter().find(|part| part.to_string() == "capture")
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let on = match &node.key {
|
||||
NodeName::Punctuated(parts) => &parts[0],
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let on = if is_targeted {
|
||||
let on = if options.targeted {
|
||||
Ident::new("on_target", on.span()).to_token_stream()
|
||||
} else {
|
||||
on.to_token_stream()
|
||||
@@ -1210,15 +1220,29 @@ pub(crate) fn event_type_and_handler(
|
||||
event_type
|
||||
};
|
||||
|
||||
let event_type = if is_force_undelegated {
|
||||
let event_type = quote! {
|
||||
::leptos::tachys::html::event::#event_type
|
||||
};
|
||||
let event_type = if options.captured {
|
||||
let capture = if let Some(capture) = capture_ident {
|
||||
quote! { #capture }
|
||||
} else {
|
||||
quote! { capture }
|
||||
};
|
||||
quote! { ::leptos::tachys::html::event::#capture(#event_type) }
|
||||
} else {
|
||||
event_type
|
||||
};
|
||||
|
||||
let event_type = if options.undelegated {
|
||||
let undelegated = if let Some(undelegated) = undelegated_ident {
|
||||
quote! { #undelegated }
|
||||
} else {
|
||||
quote! { undelegated }
|
||||
};
|
||||
quote! { ::leptos::tachys::html::event::#undelegated(::leptos::tachys::html::event::#event_type) }
|
||||
quote! { ::leptos::tachys::html::event::#undelegated(#event_type) }
|
||||
} else {
|
||||
quote! { ::leptos::tachys::html::event::#event_type }
|
||||
event_type
|
||||
};
|
||||
|
||||
(on, event_type, handler)
|
||||
@@ -1424,13 +1448,22 @@ fn is_ambiguous_element(tag: &str) -> bool {
|
||||
tag == "a" || tag == "script" || tag == "title"
|
||||
}
|
||||
|
||||
fn parse_event(event_name: &str) -> (String, bool, bool) {
|
||||
let is_undelegated = event_name.contains(":undelegated");
|
||||
let is_targeted = event_name.contains(":target");
|
||||
fn parse_event(event_name: &str) -> (String, EventNameOptions) {
|
||||
let undelegated = event_name.contains(":undelegated");
|
||||
let targeted = event_name.contains(":target");
|
||||
let captured = event_name.contains(":capture");
|
||||
let event_name = event_name
|
||||
.replace(":undelegated", "")
|
||||
.replace(":target", "");
|
||||
(event_name, is_undelegated, is_targeted)
|
||||
.replace(":target", "")
|
||||
.replace(":capture", "");
|
||||
(
|
||||
event_name,
|
||||
EventNameOptions {
|
||||
undelegated,
|
||||
targeted,
|
||||
captured,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Escapes Rust keywords that are also HTML attribute names
|
||||
@@ -1622,8 +1655,17 @@ const TYPED_EVENTS: [&str; 126] = [
|
||||
|
||||
const CUSTOM_EVENT: &str = "Custom";
|
||||
|
||||
pub(crate) fn parse_event_name(name: &str) -> (TokenStream, bool, bool, bool) {
|
||||
let (name, is_force_undelegated, is_targeted) = parse_event(name);
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct EventNameOptions {
|
||||
undelegated: bool,
|
||||
targeted: bool,
|
||||
captured: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn parse_event_name(
|
||||
name: &str,
|
||||
) -> (TokenStream, bool, EventNameOptions) {
|
||||
let (name, options) = parse_event(name);
|
||||
|
||||
let (event_type, is_custom) = TYPED_EVENTS
|
||||
.binary_search(&name.as_str())
|
||||
@@ -1639,7 +1681,7 @@ pub(crate) fn parse_event_name(name: &str) -> (TokenStream, bool, bool, bool) {
|
||||
} else {
|
||||
event_type
|
||||
};
|
||||
(event_type, is_custom, is_force_undelegated, is_targeted)
|
||||
(event_type, is_custom, options)
|
||||
}
|
||||
|
||||
fn convert_to_snake_case(name: String) -> String {
|
||||
|
||||
@@ -104,3 +104,18 @@ fn component_nostrip() {
|
||||
/>
|
||||
};
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn WithLifetime<'a>(data: &'a str) -> impl IntoView {
|
||||
_ = data;
|
||||
"static lifetime"
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_static_lifetime() {
|
||||
#[allow(unused)]
|
||||
fn can_return_impl_intoview_from_body() -> impl IntoView {
|
||||
let val = String::from("non_static_lifetime");
|
||||
WithLifetime(WithLifetimeProps::builder().data(&val).build())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_meta"
|
||||
version = "0.7.5"
|
||||
version = "0.7.7"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_graph"
|
||||
version = "0.1.5"
|
||||
version = "0.1.7"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -939,7 +939,8 @@ where
|
||||
#[track_caller]
|
||||
pub fn dispatch(&self, input: I) -> ActionAbortHandle {
|
||||
self.inner
|
||||
.try_with_value(|inner| inner.dispatch(input))
|
||||
.try_get_value()
|
||||
.map(|inner| inner.dispatch(input))
|
||||
.unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
}
|
||||
@@ -954,7 +955,8 @@ where
|
||||
#[track_caller]
|
||||
pub fn dispatch_local(&self, input: I) -> ActionAbortHandle {
|
||||
self.inner
|
||||
.try_with_value(|inner| inner.dispatch_local(input))
|
||||
.try_get_value()
|
||||
.map(|inner| inner.dispatch_local(input))
|
||||
.unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,7 +324,7 @@ macro_rules! spawn_derived {
|
||||
}
|
||||
|
||||
while rx.next().await.is_some() {
|
||||
let update_if_necessary = if $should_track {
|
||||
let update_if_necessary = !owner.paused() && if $should_track {
|
||||
any_subscriber
|
||||
.with_observer(|| any_subscriber.update_if_necessary())
|
||||
} else {
|
||||
|
||||
@@ -170,9 +170,10 @@ impl Effect<LocalStorage> {
|
||||
|
||||
async move {
|
||||
while rx.next().await.is_some() {
|
||||
if subscriber
|
||||
.with_observer(|| subscriber.update_if_necessary())
|
||||
|| first_run
|
||||
if !owner.paused()
|
||||
&& (subscriber.with_observer(|| {
|
||||
subscriber.update_if_necessary()
|
||||
}) || first_run)
|
||||
{
|
||||
first_run = false;
|
||||
subscriber.clear_sources(&subscriber);
|
||||
@@ -321,9 +322,10 @@ impl Effect<LocalStorage> {
|
||||
|
||||
async move {
|
||||
while rx.next().await.is_some() {
|
||||
if subscriber
|
||||
.with_observer(|| subscriber.update_if_necessary())
|
||||
|| first_run
|
||||
if !owner.paused()
|
||||
&& (subscriber.with_observer(|| {
|
||||
subscriber.update_if_necessary()
|
||||
}) || first_run)
|
||||
{
|
||||
subscriber.clear_sources(&subscriber);
|
||||
|
||||
@@ -388,9 +390,10 @@ impl Effect<SyncStorage> {
|
||||
|
||||
async move {
|
||||
while rx.next().await.is_some() {
|
||||
if subscriber
|
||||
.with_observer(|| subscriber.update_if_necessary())
|
||||
|| first_run
|
||||
if !owner.paused()
|
||||
&& (subscriber.with_observer(|| {
|
||||
subscriber.update_if_necessary()
|
||||
}) || first_run)
|
||||
{
|
||||
first_run = false;
|
||||
subscriber.clear_sources(&subscriber);
|
||||
@@ -434,9 +437,10 @@ impl Effect<SyncStorage> {
|
||||
|
||||
async move {
|
||||
while rx.next().await.is_some() {
|
||||
if subscriber
|
||||
.with_observer(|| subscriber.update_if_necessary())
|
||||
|| first_run
|
||||
if !owner.paused()
|
||||
&& (subscriber
|
||||
.with_observer(|| subscriber.update_if_necessary())
|
||||
|| first_run)
|
||||
{
|
||||
first_run = false;
|
||||
subscriber.clear_sources(&subscriber);
|
||||
@@ -487,9 +491,10 @@ impl Effect<SyncStorage> {
|
||||
|
||||
async move {
|
||||
while rx.next().await.is_some() {
|
||||
if subscriber
|
||||
.with_observer(|| subscriber.update_if_necessary())
|
||||
|| first_run
|
||||
if !owner.paused()
|
||||
&& (subscriber.with_observer(|| {
|
||||
subscriber.update_if_necessary()
|
||||
}) || first_run)
|
||||
{
|
||||
subscriber.clear_sources(&subscriber);
|
||||
|
||||
|
||||
@@ -91,9 +91,11 @@ where
|
||||
|
||||
async move {
|
||||
while rx.next().await.is_some() {
|
||||
if subscriber.with_observer(|| {
|
||||
subscriber.update_if_necessary()
|
||||
}) {
|
||||
if !owner.paused()
|
||||
&& subscriber.with_observer(|| {
|
||||
subscriber.update_if_necessary()
|
||||
})
|
||||
{
|
||||
subscriber.clear_sources(&subscriber);
|
||||
|
||||
let old_value = mem::take(
|
||||
@@ -159,8 +161,10 @@ where
|
||||
|
||||
async move {
|
||||
while rx.next().await.is_some() {
|
||||
if subscriber
|
||||
.with_observer(|| subscriber.update_if_necessary())
|
||||
if !owner.paused()
|
||||
&& subscriber.with_observer(|| {
|
||||
subscriber.update_if_necessary()
|
||||
})
|
||||
{
|
||||
subscriber.clear_sources(&subscriber);
|
||||
|
||||
|
||||
@@ -130,6 +130,7 @@ impl Owner {
|
||||
.and_then(|parent| parent.upgrade())
|
||||
.map(|parent| parent.read().or_poisoned().arena.clone())
|
||||
.unwrap_or_default(),
|
||||
paused: false,
|
||||
})),
|
||||
#[cfg(feature = "hydration")]
|
||||
shared_context,
|
||||
@@ -163,6 +164,7 @@ impl Owner {
|
||||
children: Default::default(),
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
arena: Default::default(),
|
||||
paused: false,
|
||||
})),
|
||||
#[cfg(feature = "hydration")]
|
||||
shared_context,
|
||||
@@ -174,8 +176,10 @@ impl Owner {
|
||||
/// Creates a new `Owner` that is the child of the current `Owner`, if any.
|
||||
pub fn child(&self) -> Self {
|
||||
let parent = Some(Arc::downgrade(&self.inner));
|
||||
let mut inner = self.inner.write().or_poisoned();
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
let arena = self.inner.read().or_poisoned().arena.clone();
|
||||
let arena = inner.arena.clone();
|
||||
let paused = inner.paused;
|
||||
let child = Self {
|
||||
inner: Arc::new(RwLock::new(OwnerInner {
|
||||
parent,
|
||||
@@ -185,15 +189,12 @@ impl Owner {
|
||||
children: Default::default(),
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
arena,
|
||||
paused,
|
||||
})),
|
||||
#[cfg(feature = "hydration")]
|
||||
shared_context: self.shared_context.clone(),
|
||||
};
|
||||
self.inner
|
||||
.write()
|
||||
.or_poisoned()
|
||||
.children
|
||||
.push(Arc::downgrade(&child.inner));
|
||||
inner.children.push(Arc::downgrade(&child.inner));
|
||||
child
|
||||
}
|
||||
|
||||
@@ -337,6 +338,47 @@ impl Owner {
|
||||
|
||||
inner(Box::new(fun))
|
||||
}
|
||||
|
||||
/// Pauses the execution of side effects for this owner, and any of its descendants.
|
||||
///
|
||||
/// If this owner is the owner for an [`Effect`](crate::effect::Effect) or [`RenderEffect`](crate::effect::RenderEffect), this effect will not run until [`Owner::resume`] is called. All children of this effects are also paused.
|
||||
///
|
||||
/// Any notifications will be ignored; effects that are notified will paused will not run when
|
||||
/// resumed, until they are notified again by a source after being resumed.
|
||||
pub fn pause(&self) {
|
||||
let mut stack = Vec::with_capacity(16);
|
||||
stack.push(Arc::downgrade(&self.inner));
|
||||
while let Some(curr) = stack.pop() {
|
||||
if let Some(curr) = curr.upgrade() {
|
||||
let mut curr = curr.write().or_poisoned();
|
||||
curr.paused = true;
|
||||
stack.extend(curr.children.iter().map(Weak::clone));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this owner has been paused by [`Owner::pause`].
|
||||
pub fn paused(&self) -> bool {
|
||||
self.inner.read().or_poisoned().paused
|
||||
}
|
||||
|
||||
/// Resumes side effects that have been paused by [`Owner::pause`].
|
||||
///
|
||||
/// All children will also be resumed.
|
||||
///
|
||||
/// This will *not* cause side effects that were notified while paused to run, until they are
|
||||
/// notified again by a source after being resumed.
|
||||
pub fn resume(&self) {
|
||||
let mut stack = Vec::with_capacity(16);
|
||||
stack.push(Arc::downgrade(&self.inner));
|
||||
while let Some(curr) = stack.pop() {
|
||||
if let Some(curr) = curr.upgrade() {
|
||||
let mut curr = curr.write().or_poisoned();
|
||||
curr.paused = false;
|
||||
stack.extend(curr.children.iter().map(Weak::clone));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
@@ -363,6 +405,7 @@ pub(crate) struct OwnerInner {
|
||||
pub children: Vec<Weak<RwLock<OwnerInner>>>,
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
arena: Arc<RwLock<ArenaMap>>,
|
||||
paused: bool,
|
||||
}
|
||||
|
||||
impl Debug for OwnerInner {
|
||||
|
||||
@@ -55,7 +55,7 @@ impl<T: AsSubscriberSet + DefinedAt> ReactiveNode for T {
|
||||
|
||||
fn mark_subscribers_check(&self) {
|
||||
if let Some(inner) = self.as_subscriber_set() {
|
||||
let subs = inner.borrow().write().unwrap().take();
|
||||
let subs = inner.borrow().read().unwrap().clone();
|
||||
for sub in subs {
|
||||
sub.mark_dirty();
|
||||
}
|
||||
|
||||
@@ -196,3 +196,62 @@ async fn recursive_effect_runs_recursively() {
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "effects")]
|
||||
#[tokio::test]
|
||||
async fn paused_effect_pauses() {
|
||||
use imports::*;
|
||||
use reactive_graph::owner::StoredValue;
|
||||
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
task::LocalSet::new()
|
||||
.run_until(async {
|
||||
let a = RwSignal::new(-1);
|
||||
|
||||
// simulate an arbitrary side effect
|
||||
let runs = StoredValue::new(0);
|
||||
|
||||
let owner = StoredValue::new(None);
|
||||
|
||||
Effect::new({
|
||||
move || {
|
||||
*owner.write_value() = Owner::current();
|
||||
|
||||
let _ = a.get();
|
||||
*runs.write_value() += 1;
|
||||
}
|
||||
});
|
||||
|
||||
Executor::tick().await;
|
||||
assert_eq!(runs.get_value(), 1);
|
||||
|
||||
println!("setting to 1");
|
||||
a.set(1);
|
||||
|
||||
Executor::tick().await;
|
||||
assert_eq!(runs.get_value(), 2);
|
||||
|
||||
println!("pausing");
|
||||
owner.get_value().unwrap().pause();
|
||||
|
||||
println!("setting to 2");
|
||||
a.set(2);
|
||||
|
||||
Executor::tick().await;
|
||||
assert_eq!(runs.get_value(), 2);
|
||||
|
||||
println!("resuming");
|
||||
owner.get_value().unwrap().resume();
|
||||
|
||||
println!("setting to 3");
|
||||
a.set(3);
|
||||
|
||||
Executor::tick().await;
|
||||
println!("checking value");
|
||||
assert_eq!(runs.get_value(), 3);
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_stores"
|
||||
version = "0.1.5"
|
||||
version = "0.1.7"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -10,7 +10,6 @@ use reactive_graph::{
|
||||
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
|
||||
Write,
|
||||
},
|
||||
unwrap_signal,
|
||||
};
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
@@ -44,14 +43,14 @@ where
|
||||
self.inner
|
||||
.try_get_value()
|
||||
.map(|inner| inner.get_trigger(path))
|
||||
.unwrap_or_else(unwrap_signal!(self))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn path(&self) -> impl IntoIterator<Item = StorePathSegment> {
|
||||
self.inner
|
||||
.try_get_value()
|
||||
.map(|inner| inner.path().into_iter().collect::<Vec<_>>())
|
||||
.unwrap_or_else(unwrap_signal!(self))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn reader(&self) -> Option<Self::Reader> {
|
||||
|
||||
@@ -148,11 +148,8 @@ where
|
||||
{
|
||||
fn latest_keys(&self) -> Vec<K> {
|
||||
self.reader()
|
||||
.expect("trying to update keys")
|
||||
.deref()
|
||||
.into_iter()
|
||||
.map(|n| (self.key_fn)(n))
|
||||
.collect()
|
||||
.map(|r| r.deref().into_iter().map(|n| (self.key_fn)(n)).collect())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,8 +480,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(WriteGuard::new(
|
||||
trigger.children,
|
||||
@@ -654,13 +650,15 @@ where
|
||||
self.track_field();
|
||||
|
||||
// get the current length of the field by accessing slice
|
||||
let reader = self
|
||||
.reader()
|
||||
.expect("creating iterator from unavailable store field");
|
||||
let reader = self.reader();
|
||||
|
||||
let keys = reader
|
||||
.into_iter()
|
||||
.map(|item| (self.key_fn)(item))
|
||||
.collect::<VecDeque<_>>();
|
||||
.map(|r| {
|
||||
r.into_iter()
|
||||
.map(|item| (self.key_fn)(item))
|
||||
.collect::<VecDeque<_>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
// return the iterator
|
||||
StoreFieldKeyedIter { inner: self, keys }
|
||||
|
||||
@@ -9,8 +9,7 @@ use reactive_graph::{
|
||||
guards::{Plain, UntrackedWriteGuard, WriteGuard},
|
||||
ArcTrigger,
|
||||
},
|
||||
traits::{DefinedAt, Track, UntrackableGuard},
|
||||
unwrap_signal,
|
||||
traits::{Track, UntrackableGuard},
|
||||
};
|
||||
use std::{iter, ops::Deref, sync::Arc};
|
||||
|
||||
@@ -105,7 +104,7 @@ where
|
||||
self.inner
|
||||
.try_get_value()
|
||||
.map(|n| n.get_trigger(path))
|
||||
.unwrap_or_else(unwrap_signal!(self))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
@@ -113,7 +112,7 @@ where
|
||||
self.inner
|
||||
.try_get_value()
|
||||
.map(|n| n.path().into_iter().collect::<Vec<_>>())
|
||||
.unwrap_or_else(unwrap_signal!(self))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_stores_macro"
|
||||
version = "0.1.5"
|
||||
version = "0.1.7"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -87,15 +87,15 @@ impl Parse for SubfieldMode {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let mode: Ident = input.parse()?;
|
||||
if mode == "key" {
|
||||
let _col: Token!(:) = input.parse()?;
|
||||
let _col: Token![:] = input.parse()?;
|
||||
let ty: Type = input.parse()?;
|
||||
let _eq: Token!(=) = input.parse()?;
|
||||
let ident: ExprClosure = input.parse()?;
|
||||
Ok(SubfieldMode::Keyed(ident, ty))
|
||||
let _eq: Token![=] = input.parse()?;
|
||||
let closure: ExprClosure = input.parse()?;
|
||||
Ok(SubfieldMode::Keyed(closure, ty))
|
||||
} else if mode == "skip" {
|
||||
Ok(SubfieldMode::Skip)
|
||||
} else {
|
||||
Err(input.error("expected `key = <ident>: <Type>`"))
|
||||
Err(input.error("expected `key: <Type> = <closure>`"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_router"
|
||||
version = "0.7.5"
|
||||
version = "0.7.7"
|
||||
authors = ["Greg Johnston", "Ben Wishovich"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_router_macro"
|
||||
version = "0.7.5"
|
||||
version = "0.7.7"
|
||||
authors = ["Greg Johnston", "Ben Wishovich"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tachys"
|
||||
version = "0.1.5"
|
||||
version = "0.1.7"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -283,7 +283,7 @@ html_elements! {
|
||||
/// The `<em>` HTML element marks text that has stress emphasis. The `<em>` element can be nested, with each level of nesting indicating a greater degree of emphasis.
|
||||
em HtmlElement [] true,
|
||||
/// The `<fieldset>` HTML element is used to group several controls as well as labels (label) within a web form.
|
||||
fieldset HtmlFieldSetElement [] true,
|
||||
fieldset HtmlFieldSetElement [disabled, form, name] true,
|
||||
/// The `<figcaption>` HTML element represents a caption or legend describing the rest of the contents of its parent figure element.
|
||||
figcaption HtmlElement [] true,
|
||||
/// The `<figure>` HTML element represents self-contained content, potentially with an optional caption, which is specified using the figcaption element. The figure, its caption, and its contents are referenced as a single unit.
|
||||
|
||||
@@ -167,6 +167,9 @@ where
|
||||
el: &crate::renderer::types::Element,
|
||||
cb: Box<dyn FnMut(crate::renderer::types::Event)>,
|
||||
name: Cow<'static, str>,
|
||||
// TODO investigate: does passing this as an option
|
||||
// (rather than, say, having a const DELEGATED: bool)
|
||||
// add to binary size?
|
||||
delegation_key: Option<Cow<'static, str>>,
|
||||
) -> RemoveEventHandler<crate::renderer::types::Element> {
|
||||
match delegation_key {
|
||||
@@ -201,6 +204,39 @@ where
|
||||
.then(|| self.event.event_delegation_key()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Attaches the event listener to the element as a listener that is triggered during the capture phase,
|
||||
/// meaning it will fire before any event listeners further down in the DOM.
|
||||
pub fn attach_capture(
|
||||
self,
|
||||
el: &crate::renderer::types::Element,
|
||||
) -> RemoveEventHandler<crate::renderer::types::Element> {
|
||||
fn attach_inner(
|
||||
el: &crate::renderer::types::Element,
|
||||
cb: Box<dyn FnMut(crate::renderer::types::Event)>,
|
||||
name: Cow<'static, str>,
|
||||
) -> RemoveEventHandler<crate::renderer::types::Element> {
|
||||
Rndr::add_event_listener_use_capture(el, &name, cb)
|
||||
}
|
||||
|
||||
let mut cb = self.cb.expect("callback removed before attaching").take();
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
let span = tracing::Span::current();
|
||||
|
||||
let cb = Box::new(move |ev: crate::renderer::types::Event| {
|
||||
#[cfg(all(debug_assertions, feature = "reactive_graph"))]
|
||||
let _rx_guard =
|
||||
reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
#[cfg(feature = "tracing")]
|
||||
let _tracing_guard = span.enter();
|
||||
|
||||
let ev = E::EventType::from(ev);
|
||||
cb.invoke(ev);
|
||||
}) as Box<dyn FnMut(crate::renderer::types::Event)>;
|
||||
|
||||
attach_inner(el, cb, self.event.name())
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, F> Debug for On<E, F>
|
||||
@@ -250,13 +286,21 @@ where
|
||||
self,
|
||||
el: &crate::renderer::types::Element,
|
||||
) -> Self::State {
|
||||
let cleanup = self.attach(el);
|
||||
let cleanup = if E::CAPTURE {
|
||||
self.attach_capture(el)
|
||||
} else {
|
||||
self.attach(el)
|
||||
};
|
||||
(el.clone(), Some(cleanup))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
|
||||
let cleanup = self.attach(el);
|
||||
let cleanup = if E::CAPTURE {
|
||||
self.attach_capture(el)
|
||||
} else {
|
||||
self.attach(el)
|
||||
};
|
||||
(el.clone(), Some(cleanup))
|
||||
}
|
||||
|
||||
@@ -266,7 +310,11 @@ where
|
||||
if let Some(prev) = prev_cleanup.take() {
|
||||
(prev.into_inner())(el);
|
||||
}
|
||||
*prev_cleanup = Some(self.attach(el));
|
||||
*prev_cleanup = Some(if E::CAPTURE {
|
||||
self.attach_capture(el)
|
||||
} else {
|
||||
self.attach(el)
|
||||
});
|
||||
}
|
||||
|
||||
fn into_cloneable(self) -> Self::Cloneable {
|
||||
@@ -334,10 +382,13 @@ pub trait EventDescriptor: Clone {
|
||||
/// Indicates if this event bubbles. For example, `click` bubbles,
|
||||
/// but `focus` does not.
|
||||
///
|
||||
/// If this is true, then the event will be delegated globally,
|
||||
/// otherwise, event listeners will be directly attached to the element.
|
||||
/// If this is true, then the event will be delegated globally if the `delegation`
|
||||
/// feature is enabled. Otherwise, event listeners will be directly attached to the element.
|
||||
const BUBBLES: bool;
|
||||
|
||||
/// Indicates if this event should be handled during the capture phase.
|
||||
const CAPTURE: bool = false;
|
||||
|
||||
/// The name of the event, such as `click` or `mouseover`.
|
||||
fn name(&self) -> Cow<'static, str>;
|
||||
|
||||
@@ -352,6 +403,32 @@ pub trait EventDescriptor: Clone {
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper that tells the framework to handle an event during the capture phase.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Capture<E> {
|
||||
inner: E,
|
||||
}
|
||||
|
||||
/// Wraps an event to indicate that it should be handled during the capture phase.
|
||||
pub fn capture<E>(event: E) -> Capture<E> {
|
||||
Capture { inner: event }
|
||||
}
|
||||
|
||||
impl<E: EventDescriptor> EventDescriptor for Capture<E> {
|
||||
type EventType = E::EventType;
|
||||
|
||||
const CAPTURE: bool = true;
|
||||
const BUBBLES: bool = E::BUBBLES;
|
||||
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
self.inner.name()
|
||||
}
|
||||
|
||||
fn event_delegation_key(&self) -> Cow<'static, str> {
|
||||
self.inner.event_delegation_key()
|
||||
}
|
||||
}
|
||||
|
||||
/// A custom event.
|
||||
#[derive(Debug)]
|
||||
pub struct Custom<E: FromWasmAbi = web_sys::Event> {
|
||||
|
||||
@@ -13,7 +13,7 @@ use once_cell::unsync::Lazy;
|
||||
use rustc_hash::FxHashSet;
|
||||
use std::{any::TypeId, borrow::Cow, cell::RefCell};
|
||||
use wasm_bindgen::{intern, prelude::Closure, JsCast, JsValue};
|
||||
use web_sys::{Comment, HtmlTemplateElement};
|
||||
use web_sys::{AddEventListenerOptions, Comment, HtmlTemplateElement};
|
||||
|
||||
/// A [`Renderer`](crate::renderer::Renderer) that uses `web-sys` to manipulate DOM elements in the browser.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
@@ -245,6 +245,44 @@ impl Dom {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_event_listener_use_capture(
|
||||
el: &Element,
|
||||
name: &str,
|
||||
cb: Box<dyn FnMut(Event)>,
|
||||
) -> RemoveEventHandler<Element> {
|
||||
let cb = wasm_bindgen::closure::Closure::wrap(cb);
|
||||
let name = intern(name);
|
||||
let options = AddEventListenerOptions::new();
|
||||
options.set_capture(true);
|
||||
or_debug!(
|
||||
el.add_event_listener_with_callback_and_add_event_listener_options(
|
||||
name,
|
||||
cb.as_ref().unchecked_ref(),
|
||||
&options
|
||||
),
|
||||
el,
|
||||
"addEventListenerUseCapture"
|
||||
);
|
||||
|
||||
// return the remover
|
||||
RemoveEventHandler::new({
|
||||
let name = name.to_owned();
|
||||
// safe to construct this here, because it will only run in the browser
|
||||
// so it will always be accessed or dropped from the main thread
|
||||
let cb = send_wrapper::SendWrapper::new(cb);
|
||||
move |el: &Element| {
|
||||
or_debug!(
|
||||
el.remove_event_listener_with_callback(
|
||||
intern(&name),
|
||||
cb.as_ref().unchecked_ref()
|
||||
),
|
||||
el,
|
||||
"removeEventListener"
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn event_target<T>(ev: &Event) -> T
|
||||
where
|
||||
T: CastFrom<Element>,
|
||||
|
||||
@@ -3,7 +3,9 @@ use super::{
|
||||
Render, RenderHtml,
|
||||
};
|
||||
use crate::{
|
||||
html::attribute::Attribute, hydration::Cursor, ssr::StreamBuilder,
|
||||
html::attribute::{Attribute, NextAttribute},
|
||||
hydration::Cursor,
|
||||
ssr::StreamBuilder,
|
||||
};
|
||||
use either_of::*;
|
||||
use futures::future::join;
|
||||
@@ -114,6 +116,150 @@ const fn max_usize(vals: &[usize]) -> usize {
|
||||
max
|
||||
}
|
||||
|
||||
#[cfg(not(erase_components))]
|
||||
impl<A, B> NextAttribute for Either<A, B>
|
||||
where
|
||||
B: NextAttribute,
|
||||
A: NextAttribute,
|
||||
{
|
||||
type Output<NewAttr: Attribute> = Either<
|
||||
<A as NextAttribute>::Output<NewAttr>,
|
||||
<B as NextAttribute>::Output<NewAttr>,
|
||||
>;
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
new_attr: NewAttr,
|
||||
) -> Self::Output<NewAttr> {
|
||||
match self {
|
||||
Either::Left(left) => Either::Left(left.add_any_attr(new_attr)),
|
||||
Either::Right(right) => Either::Right(right.add_any_attr(new_attr)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(erase_components)]
|
||||
use crate::html::attribute::any_attribute::{AnyAttribute, IntoAnyAttribute};
|
||||
|
||||
#[cfg(erase_components)]
|
||||
impl<A, B> NextAttribute for Either<A, B>
|
||||
where
|
||||
B: IntoAnyAttribute,
|
||||
A: IntoAnyAttribute,
|
||||
{
|
||||
type Output<NewAttr: Attribute> = Vec<AnyAttribute>;
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
new_attr: NewAttr,
|
||||
) -> Self::Output<NewAttr> {
|
||||
vec![
|
||||
match self {
|
||||
Either::Left(left) => left.into_any_attr(),
|
||||
Either::Right(right) => right.into_any_attr(),
|
||||
},
|
||||
new_attr.into_any_attr(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B> Attribute for Either<A, B>
|
||||
where
|
||||
B: Attribute,
|
||||
A: Attribute,
|
||||
{
|
||||
const MIN_LENGTH: usize = max_usize(&[A::MIN_LENGTH, B::MIN_LENGTH]);
|
||||
|
||||
type AsyncOutput = Either<A::AsyncOutput, B::AsyncOutput>;
|
||||
type State = Either<A::State, B::State>;
|
||||
type Cloneable = Either<A::Cloneable, B::Cloneable>;
|
||||
type CloneableOwned = Either<A::CloneableOwned, B::CloneableOwned>;
|
||||
|
||||
fn html_len(&self) -> usize {
|
||||
match self {
|
||||
Either::Left(left) => left.html_len(),
|
||||
Either::Right(right) => right.html_len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_html(
|
||||
self,
|
||||
buf: &mut String,
|
||||
class: &mut String,
|
||||
style: &mut String,
|
||||
inner_html: &mut String,
|
||||
) {
|
||||
match self {
|
||||
Either::Left(left) => left.to_html(buf, class, style, inner_html),
|
||||
Either::Right(right) => {
|
||||
right.to_html(buf, class, style, inner_html)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
el: &crate::renderer::types::Element,
|
||||
) -> Self::State {
|
||||
match self {
|
||||
Either::Left(left) => Either::Left(left.hydrate::<FROM_SERVER>(el)),
|
||||
Either::Right(right) => {
|
||||
Either::Right(right.hydrate::<FROM_SERVER>(el))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
|
||||
match self {
|
||||
Either::Left(left) => Either::Left(left.build(el)),
|
||||
Either::Right(right) => Either::Right(right.build(el)),
|
||||
}
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
match self {
|
||||
Either::Left(left) => {
|
||||
if let Some(state) = state.as_left_mut() {
|
||||
left.rebuild(state)
|
||||
}
|
||||
}
|
||||
Either::Right(right) => {
|
||||
if let Some(state) = state.as_right_mut() {
|
||||
right.rebuild(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn into_cloneable(self) -> Self::Cloneable {
|
||||
match self {
|
||||
Either::Left(left) => Either::Left(left.into_cloneable()),
|
||||
Either::Right(right) => Either::Right(right.into_cloneable()),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_cloneable_owned(self) -> Self::CloneableOwned {
|
||||
match self {
|
||||
Either::Left(left) => Either::Left(left.into_cloneable_owned()),
|
||||
Either::Right(right) => Either::Right(right.into_cloneable_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
match self {
|
||||
Either::Left(left) => left.dry_resolve(),
|
||||
Either::Right(right) => right.dry_resolve(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
match self {
|
||||
Either::Left(left) => Either::Left(left.resolve().await),
|
||||
Either::Right(right) => Either::Right(right.resolve().await),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B> RenderHtml for Either<A, B>
|
||||
where
|
||||
A: RenderHtml,
|
||||
|
||||
@@ -3,7 +3,12 @@ use super::{
|
||||
RenderHtml, ToTemplate,
|
||||
};
|
||||
use crate::{
|
||||
html::attribute::{Attribute, AttributeKey, AttributeValue, NextAttribute},
|
||||
html::attribute::{
|
||||
maybe_next_attr_erasure_macros::{
|
||||
next_attr_combine, next_attr_output_type,
|
||||
},
|
||||
Attribute, AttributeKey, AttributeValue, NextAttribute,
|
||||
},
|
||||
hydration::Cursor,
|
||||
renderer::{CastFrom, Rndr},
|
||||
};
|
||||
@@ -111,13 +116,13 @@ impl<K, const V: &'static str> NextAttribute for StaticAttr<K, V>
|
||||
where
|
||||
K: AttributeKey,
|
||||
{
|
||||
type Output<NewAttr: Attribute> = (Self, NewAttr);
|
||||
next_attr_output_type!(Self, NewAttr);
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
new_attr: NewAttr,
|
||||
) -> Self::Output<NewAttr> {
|
||||
(StaticAttr::<K, V> { ty: PhantomData }, new_attr)
|
||||
next_attr_combine!(StaticAttr::<K, V> { ty: PhantomData }, new_attr)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user