mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 07:34:35 -05:00
feat: resupport From<Fn() -> T> for Signal<T>, ArcSignal<T>, Callback<T, _> and similar (#4273)
This commit is contained in:
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -2924,6 +2924,7 @@ dependencies = [
|
||||
"hydration_context",
|
||||
"indexmap",
|
||||
"or_poisoned",
|
||||
"paste",
|
||||
"pin-project-lite",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc_version",
|
||||
@@ -2935,6 +2936,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-test",
|
||||
"tracing",
|
||||
"typed-builder",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
@@ -4307,18 +4309,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "typed-builder"
|
||||
version = "0.21.2"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fef81aec2ca29576f9f6ae8755108640d0a86dd3161b2e8bca6cfa554e98f77d"
|
||||
checksum = "398a3a3c918c96de527dc11e6e846cd549d4508030b8a33e1da12789c856b81a"
|
||||
dependencies = [
|
||||
"typed-builder-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typed-builder-macro"
|
||||
version = "0.21.2"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ecb9ecf7799210407c14a8cfdfe0173365780968dc57973ed082211958e0b18"
|
||||
checksum = "0e48cea23f68d1f78eb7bc092881b6bb88d3d6b5b7e6234f6f9c911da1ffb221"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -76,7 +76,8 @@ itertools = { default-features = false, version = "0.14.0" }
|
||||
convert_case = { default-features = false, version = "0.8.0" }
|
||||
serde_json = { default-features = false, version = "1.0.143" }
|
||||
trybuild = { default-features = false, version = "1.0.110" }
|
||||
typed-builder = { default-features = false, version = "0.21.2" }
|
||||
typed-builder = { default-features = false, version = "0.22.0" }
|
||||
typed-builder-macro = { default-features = false, version = "0.22.0" }
|
||||
thiserror = { default-features = false, version = "2.0.17" }
|
||||
wasm-bindgen = { default-features = false, version = "0.2.100" }
|
||||
indexmap = { default-features = false, version = "2.11.0" }
|
||||
@@ -120,7 +121,6 @@ serial_test = { default-features = false, version = "3.2.0" }
|
||||
erased = { default-features = false, version = "0.1.2" }
|
||||
glib = { default-features = false, version = "0.20.12" }
|
||||
async-trait = { default-features = false, version = "0.1.89" }
|
||||
typed-builder-macro = { default-features = false, version = "0.21.0" }
|
||||
linear-map = { default-features = false, version = "1.2.0" }
|
||||
anyhow = { default-features = false, version = "1.0.100" }
|
||||
walkdir = { default-features = false, version = "2.5.0" }
|
||||
|
||||
@@ -203,7 +203,7 @@ pub mod prelude {
|
||||
pub mod form;
|
||||
|
||||
/// A standard way to wrap functions and closures to pass them to components.
|
||||
pub mod callback;
|
||||
pub use reactive_graph::callback;
|
||||
|
||||
/// Types that can be passed as the `children` prop of a component.
|
||||
pub mod children;
|
||||
|
||||
@@ -1016,25 +1016,27 @@ struct PropOpt {
|
||||
name: Option<String>,
|
||||
}
|
||||
|
||||
struct TypedBuilderOpts {
|
||||
struct TypedBuilderOpts<'a> {
|
||||
default: bool,
|
||||
default_with_value: Option<syn::Expr>,
|
||||
strip_option: bool,
|
||||
into: bool,
|
||||
ty: &'a Type,
|
||||
}
|
||||
|
||||
impl TypedBuilderOpts {
|
||||
fn from_opts(opts: &PropOpt, is_ty_option: bool) -> Self {
|
||||
impl<'a> TypedBuilderOpts<'a> {
|
||||
fn from_opts(opts: &PropOpt, ty: &'a Type) -> Self {
|
||||
Self {
|
||||
default: opts.optional || opts.optional_no_strip || opts.attrs,
|
||||
default_with_value: opts.default.clone(),
|
||||
strip_option: opts.strip_option || opts.optional && is_ty_option,
|
||||
strip_option: opts.strip_option || opts.optional && is_option(ty),
|
||||
into: opts.into,
|
||||
ty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TypedBuilderOpts {
|
||||
impl TypedBuilderOpts<'_> {
|
||||
fn to_serde_tokens(&self) -> TokenStream {
|
||||
let default = if let Some(v) = &self.default_with_value {
|
||||
let v = v.to_token_stream().to_string();
|
||||
@@ -1053,7 +1055,7 @@ impl TypedBuilderOpts {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for TypedBuilderOpts {
|
||||
impl ToTokens for TypedBuilderOpts<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let default = if let Some(v) = &self.default_with_value {
|
||||
let v = v.to_token_stream().to_string();
|
||||
@@ -1064,14 +1066,29 @@ impl ToTokens for TypedBuilderOpts {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let strip_option = if self.strip_option {
|
||||
// If self.strip_option && self.into, then the strip_option will be represented as part of the transform closure.
|
||||
let strip_option = if self.strip_option && !self.into {
|
||||
quote! { strip_option, }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let into = if self.into {
|
||||
quote! { into, }
|
||||
if !self.strip_option {
|
||||
let ty = &self.ty;
|
||||
quote! {
|
||||
fn transform<__IntoReactiveValueMarker>(value: impl ::leptos::prelude::IntoReactiveValue<#ty, __IntoReactiveValueMarker>) -> #ty {
|
||||
value.into_reactive_value()
|
||||
},
|
||||
}
|
||||
} else {
|
||||
let ty = unwrap_option(self.ty);
|
||||
quote! {
|
||||
fn transform<__IntoReactiveValueMarker>(value: impl ::leptos::prelude::IntoReactiveValue<#ty, __IntoReactiveValueMarker>) -> Option<#ty> {
|
||||
Some(value.into_reactive_value())
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
@@ -1107,8 +1124,7 @@ fn prop_builder_fields(
|
||||
ty,
|
||||
} = prop;
|
||||
|
||||
let builder_attrs =
|
||||
TypedBuilderOpts::from_opts(prop_opts, is_option(ty));
|
||||
let builder_attrs = TypedBuilderOpts::from_opts(prop_opts, ty);
|
||||
|
||||
let builder_docs = prop_to_doc(prop, PropDocStyle::Inline);
|
||||
|
||||
@@ -1153,8 +1169,7 @@ fn prop_serializer_fields(vis: &Visibility, props: &[Prop]) -> TokenStream {
|
||||
ty,
|
||||
} = prop;
|
||||
|
||||
let builder_attrs =
|
||||
TypedBuilderOpts::from_opts(prop_opts, is_option(ty));
|
||||
let builder_attrs = TypedBuilderOpts::from_opts(prop_opts, ty);
|
||||
let serde_attrs = builder_attrs.to_serde_tokens();
|
||||
|
||||
let PatIdent { ident, by_ref, .. } = &name;
|
||||
|
||||
@@ -159,25 +159,27 @@ struct PropOpt {
|
||||
pub attrs: bool,
|
||||
}
|
||||
|
||||
struct TypedBuilderOpts {
|
||||
struct TypedBuilderOpts<'a> {
|
||||
default: bool,
|
||||
default_with_value: Option<syn::Expr>,
|
||||
strip_option: bool,
|
||||
into: bool,
|
||||
ty: &'a Type,
|
||||
}
|
||||
|
||||
impl TypedBuilderOpts {
|
||||
pub fn from_opts(opts: &PropOpt, is_ty_option: bool) -> Self {
|
||||
impl<'a> TypedBuilderOpts<'a> {
|
||||
pub fn from_opts(opts: &PropOpt, ty: &'a Type) -> Self {
|
||||
Self {
|
||||
default: opts.optional || opts.optional_no_strip || opts.attrs,
|
||||
default_with_value: opts.default.clone(),
|
||||
strip_option: opts.strip_option || opts.optional && is_ty_option,
|
||||
strip_option: opts.strip_option || opts.optional && is_option(ty),
|
||||
into: opts.into,
|
||||
ty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for TypedBuilderOpts {
|
||||
impl ToTokens for TypedBuilderOpts<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let default = if let Some(v) = &self.default_with_value {
|
||||
let v = v.to_token_stream().to_string();
|
||||
@@ -188,14 +190,29 @@ impl ToTokens for TypedBuilderOpts {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let strip_option = if self.strip_option {
|
||||
// If self.strip_option && self.into, then the strip_option will be represented as part of the transform closure.
|
||||
let strip_option = if self.strip_option && !self.into {
|
||||
quote! { strip_option, }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let into = if self.into {
|
||||
quote! { into, }
|
||||
if !self.strip_option {
|
||||
let ty = &self.ty;
|
||||
quote! {
|
||||
fn transform<__IntoReactiveValueMarker>(value: impl ::leptos::prelude::IntoReactiveValue<#ty, __IntoReactiveValueMarker>) -> #ty {
|
||||
value.into_reactive_value()
|
||||
},
|
||||
}
|
||||
} else {
|
||||
let ty = unwrap_option(self.ty);
|
||||
quote! {
|
||||
fn transform<__IntoReactiveValueMarker>(value: impl ::leptos::prelude::IntoReactiveValue<#ty, __IntoReactiveValueMarker>) -> Option<#ty> {
|
||||
Some(value.into_reactive_value())
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
@@ -227,8 +244,7 @@ fn prop_builder_fields(vis: &Visibility, props: &[Prop]) -> TokenStream {
|
||||
ty,
|
||||
} = prop;
|
||||
|
||||
let builder_attrs =
|
||||
TypedBuilderOpts::from_opts(prop_opts, is_option(ty));
|
||||
let builder_attrs = TypedBuilderOpts::from_opts(prop_opts, ty);
|
||||
|
||||
let builder_docs = prop_to_doc(prop, PropDocStyle::Inline);
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ pub(crate) fn component_to_tokens(
|
||||
|
||||
if optional {
|
||||
optional_props.push(quote! {
|
||||
props.#name = { #value }.map(Into::into);
|
||||
props.#name = { #value }.map(::leptos::prelude::IntoReactiveValue::into_reactive_value);
|
||||
})
|
||||
} else {
|
||||
required_props.push(quote! {
|
||||
|
||||
@@ -120,6 +120,124 @@ fn returns_static_lifetime() {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
#[component]
|
||||
pub fn IntoReactiveValueTestComponentSignal(
|
||||
#[prop(into)] arg1: Signal<String>,
|
||||
#[prop(into)] arg2: Signal<String>,
|
||||
#[prop(into)] arg3: Signal<String>,
|
||||
#[prop(into)] arg4: Signal<usize>,
|
||||
#[prop(into)] arg5: Signal<usize>,
|
||||
#[prop(into)] arg6: Signal<usize>,
|
||||
#[prop(into)] arg7: Signal<Option<usize>>,
|
||||
#[prop(into)] arg8: ArcSignal<String>,
|
||||
#[prop(into)] arg9: ArcSignal<String>,
|
||||
#[prop(into)] arg10: ArcSignal<String>,
|
||||
#[prop(into)] arg11: ArcSignal<usize>,
|
||||
#[prop(into)] arg12: ArcSignal<usize>,
|
||||
#[prop(into)] arg13: ArcSignal<usize>,
|
||||
#[prop(into)] arg14: ArcSignal<Option<usize>>,
|
||||
// Optionals:
|
||||
#[prop(into, optional)] arg15: Option<Signal<usize>>,
|
||||
#[prop(into, optional)] arg16_purposely_omitted: Option<Signal<usize>>,
|
||||
#[prop(into, optional)] arg17: Option<Signal<usize>>,
|
||||
#[prop(into, strip_option)] arg18: Option<Signal<usize>>,
|
||||
) -> impl IntoView {
|
||||
move || {
|
||||
view! {
|
||||
<div>
|
||||
<p>{arg1.get()}</p>
|
||||
<p>{arg2.get()}</p>
|
||||
<p>{arg3.get()}</p>
|
||||
<p>{arg4.get()}</p>
|
||||
<p>{arg5.get()}</p>
|
||||
<p>{arg6.get()}</p>
|
||||
<p>{arg7.get()}</p>
|
||||
<p>{arg8.get()}</p>
|
||||
<p>{arg9.get()}</p>
|
||||
<p>{arg10.get()}</p>
|
||||
<p>{arg11.get()}</p>
|
||||
<p>{arg12.get()}</p>
|
||||
<p>{arg13.get()}</p>
|
||||
<p>{arg14.get()}</p>
|
||||
<p>{arg15.get()}</p>
|
||||
<p>{arg16_purposely_omitted.get()}</p>
|
||||
<p>{arg17.get()}</p>
|
||||
<p>{arg18.get()}</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn IntoReactiveValueTestComponentCallback(
|
||||
#[prop(into)] arg1: Callback<(), String>,
|
||||
#[prop(into)] arg2: Callback<usize, String>,
|
||||
#[prop(into)] arg3: Callback<(usize,), String>,
|
||||
#[prop(into)] arg4: Callback<(usize, String), String>,
|
||||
#[prop(into)] arg5: UnsyncCallback<(), String>,
|
||||
#[prop(into)] arg6: UnsyncCallback<usize, String>,
|
||||
#[prop(into)] arg7: UnsyncCallback<(usize,), String>,
|
||||
#[prop(into)] arg8: UnsyncCallback<(usize, String), String>,
|
||||
) -> impl IntoView {
|
||||
move || {
|
||||
view! {
|
||||
<div>
|
||||
<p>{arg1.run(())}</p>
|
||||
<p>{arg2.run(1)}</p>
|
||||
<p>{arg3.run((2,))}</p>
|
||||
<p>{arg4.run((3, "three".into()))}</p>
|
||||
<p>{arg5.run(())}</p>
|
||||
<p>{arg6.run(1)}</p>
|
||||
<p>{arg7.run((2,))}</p>
|
||||
<p>{arg8.run((3, "three".into()))}</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
#[test]
|
||||
fn test_into_reactive_value_signal() {
|
||||
let _ = view! {
|
||||
<IntoReactiveValueTestComponentSignal
|
||||
arg1=move || "I was a reactive closure!"
|
||||
arg2="I was a basic str!"
|
||||
arg3=Signal::stored("I was already a signal!")
|
||||
arg4=move || 2
|
||||
arg5=3
|
||||
arg6=Signal::stored(4)
|
||||
arg7=|| 2
|
||||
arg8=move || "I was a reactive closure!"
|
||||
arg9="I was a basic str!"
|
||||
arg10=ArcSignal::stored("I was already a signal!".to_string())
|
||||
arg11=move || 2
|
||||
arg12=3
|
||||
arg13=ArcSignal::stored(4)
|
||||
arg14=|| 2
|
||||
arg15=|| 2
|
||||
nostrip:arg17=Some(|| 2)
|
||||
arg18=|| 2
|
||||
/>
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_reactive_value_callback() {
|
||||
let _ = view! {
|
||||
<IntoReactiveValueTestComponentCallback
|
||||
arg1=|| "I was a callback static str!"
|
||||
arg2=|_n| "I was a callback static str!"
|
||||
arg3=|(_n,)| "I was a callback static str!"
|
||||
arg4=|(_n, _s)| "I was a callback static str!"
|
||||
arg5=|| "I was a callback static str!"
|
||||
arg6=|_n| "I was a callback static str!"
|
||||
arg7=|(_n,)| "I was a callback static str!"
|
||||
arg8=|(_n, _s)| "I was a callback static str!"
|
||||
/>
|
||||
};
|
||||
}
|
||||
|
||||
// an attempt to catch unhygienic macros regression
|
||||
mod macro_hygiene {
|
||||
// To ensure no relative module path to leptos inside macros.
|
||||
@@ -152,12 +270,7 @@ mod macro_hygiene {
|
||||
|
||||
#[component]
|
||||
fn Component() -> impl IntoView {
|
||||
view! {
|
||||
<div>
|
||||
{().into_any()}
|
||||
{()}
|
||||
</div>
|
||||
}
|
||||
view! { <div>{().into_any()} {()}</div> }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ send_wrapper = { features = [
|
||||
], workspace = true, default-features = true }
|
||||
subsecond = { workspace = true, default-features = true, optional = true }
|
||||
indexmap = { workspace = true, default-features = true }
|
||||
paste = { workspace = true, default-features = true }
|
||||
|
||||
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
|
||||
web-sys = { version = "0.3.77", features = ["console"] }
|
||||
@@ -40,6 +41,7 @@ tokio = { features = [
|
||||
], workspace = true, default-features = true }
|
||||
tokio-test = { workspace = true, default-features = true }
|
||||
any_spawner = { workspace = true, features = ["futures-executor", "tokio"] }
|
||||
typed-builder.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
rustc_version = { workspace = true, default-features = true }
|
||||
|
||||
@@ -2,48 +2,19 @@
|
||||
//! for component properties, because they can be used to define optional callback functions,
|
||||
//! which generic props don’t support.
|
||||
//!
|
||||
//! # Usage
|
||||
//! Callbacks can be created manually from any function or closure, but the easiest way
|
||||
//! to create them is to use `#[prop(into)]]` when defining a component.
|
||||
//! ```
|
||||
//! use leptos::prelude::*;
|
||||
//!
|
||||
//! #[component]
|
||||
//! fn MyComponent(
|
||||
//! #[prop(into)] render_number: Callback<(i32,), String>,
|
||||
//! ) -> impl IntoView {
|
||||
//! view! {
|
||||
//! <div>
|
||||
//! {render_number.run((1,))}
|
||||
//! // callbacks can be called multiple times
|
||||
//! {render_number.run((42,))}
|
||||
//! </div>
|
||||
//! }
|
||||
//! }
|
||||
//! // you can pass a closure directly as `render_number`
|
||||
//! fn test() -> impl IntoView {
|
||||
//! view! {
|
||||
//! <MyComponent render_number=|x: i32| x.to_string()/>
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! *Notes*:
|
||||
//! - The `render_number` prop can receive any type that implements `Fn(i32) -> String`.
|
||||
//! - Callbacks are most useful when you want optional generic props.
|
||||
//! - All callbacks implement the [`Callable`](leptos::callback::Callable) trait, and can be invoked with `my_callback.run(input)`.
|
||||
//! - The callback types implement [`Copy`], so they can easily be moved into and out of other closures, just like signals.
|
||||
//! The callback types implement [`Copy`], so they can easily be moved into and out of other closures, just like signals.
|
||||
//!
|
||||
//! # Types
|
||||
//! This modules implements 2 callback types:
|
||||
//! - [`Callback`](leptos::callback::Callback)
|
||||
//! - [`UnsyncCallback`](leptos::callback::UnsyncCallback)
|
||||
//! - [`Callback`](reactive_graph::callback::Callback)
|
||||
//! - [`UnsyncCallback`](reactive_graph::callback::UnsyncCallback)
|
||||
//!
|
||||
//! Use `SyncCallback` if the function is not `Sync` and `Send`.
|
||||
|
||||
use reactive_graph::{
|
||||
use crate::{
|
||||
owner::{LocalStorage, StoredValue},
|
||||
traits::{Dispose, WithValue},
|
||||
IntoReactiveValue,
|
||||
};
|
||||
use std::{fmt, rc::Rc, sync::Arc};
|
||||
|
||||
@@ -60,7 +31,16 @@ pub trait Callable<In: 'static, Out: 'static = ()> {
|
||||
fn run(&self, input: In) -> Out;
|
||||
}
|
||||
|
||||
/// A callback type that is not required to be `Send + Sync`.
|
||||
/// A callback type that is not required to be [`Send`] or [`Sync`].
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*; use reactive_graph::callback::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// let _: UnsyncCallback<()> = UnsyncCallback::new(|_| {});
|
||||
/// let _: UnsyncCallback<(i32, i32)> = (|_x: i32, _y: i32| {}).into();
|
||||
/// let cb: UnsyncCallback<i32, String> = UnsyncCallback::new(|x: i32| x.to_string());
|
||||
/// assert_eq!(cb.run(42), "42".to_string());
|
||||
/// ```
|
||||
pub struct UnsyncCallback<In: 'static, Out: 'static = ()>(
|
||||
StoredValue<Rc<dyn Fn(In) -> Out>, LocalStorage>,
|
||||
);
|
||||
@@ -148,28 +128,15 @@ impl_unsync_callable_from_fn!(
|
||||
P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12
|
||||
);
|
||||
|
||||
/// Callbacks define a standard way to store functions and closures.
|
||||
/// A callback type that is [`Send`] + [`Sync`].
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use leptos::prelude::*;
|
||||
/// # use leptos::callback::{Callable, Callback};
|
||||
/// #[component]
|
||||
/// fn MyComponent(
|
||||
/// #[prop(into)] render_number: Callback<(i32,), String>,
|
||||
/// ) -> impl IntoView {
|
||||
/// view! {
|
||||
/// <div>
|
||||
/// {render_number.run((42,))}
|
||||
/// </div>
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn test() -> impl IntoView {
|
||||
/// view! {
|
||||
/// <MyComponent render_number=move |x: i32| x.to_string()/>
|
||||
/// }
|
||||
/// }
|
||||
/// # use reactive_graph::prelude::*; use reactive_graph::callback::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// let _: Callback<()> = Callback::new(|_| {});
|
||||
/// let _: Callback<(i32, i32)> = (|_x: i32, _y: i32| {}).into();
|
||||
/// let cb: Callback<i32, String> = Callback::new(|x: i32| x.to_string());
|
||||
/// assert_eq!(cb.run(42), "42".to_string());
|
||||
/// ```
|
||||
pub struct Callback<In, Out = ()>(
|
||||
StoredValue<Arc<dyn Fn(In) -> Out + Send + Sync>>,
|
||||
@@ -241,6 +208,7 @@ impl_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12);
|
||||
|
||||
impl<In: 'static, Out: 'static> Callback<In, Out> {
|
||||
/// Creates a new callback from the given function.
|
||||
#[track_caller]
|
||||
pub fn new<F>(fun: F) -> Self
|
||||
where
|
||||
F: Fn(In) -> Out + Send + Sync + 'static,
|
||||
@@ -262,22 +230,94 @@ impl<In: 'static, Out: 'static> Callback<In, Out> {
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct __IntoReactiveValueMarkerCallbackSingleParam;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct __IntoReactiveValueMarkerCallbackStrOutputToString;
|
||||
|
||||
impl<I, O, F>
|
||||
IntoReactiveValue<
|
||||
Callback<I, O>,
|
||||
__IntoReactiveValueMarkerCallbackSingleParam,
|
||||
> for F
|
||||
where
|
||||
F: Fn(I) -> O + Send + Sync + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn into_reactive_value(self) -> Callback<I, O> {
|
||||
Callback::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O, F>
|
||||
IntoReactiveValue<
|
||||
UnsyncCallback<I, O>,
|
||||
__IntoReactiveValueMarkerCallbackSingleParam,
|
||||
> for F
|
||||
where
|
||||
F: Fn(I) -> O + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn into_reactive_value(self) -> UnsyncCallback<I, O> {
|
||||
UnsyncCallback::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, F>
|
||||
IntoReactiveValue<
|
||||
Callback<I, String>,
|
||||
__IntoReactiveValueMarkerCallbackStrOutputToString,
|
||||
> for F
|
||||
where
|
||||
F: Fn(I) -> &'static str + Send + Sync + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn into_reactive_value(self) -> Callback<I, String> {
|
||||
Callback::new(move |i| self(i).to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, F>
|
||||
IntoReactiveValue<
|
||||
UnsyncCallback<I, String>,
|
||||
__IntoReactiveValueMarkerCallbackStrOutputToString,
|
||||
> for F
|
||||
where
|
||||
F: Fn(I) -> &'static str + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn into_reactive_value(self) -> UnsyncCallback<I, String> {
|
||||
UnsyncCallback::new(move |i| self(i).to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Callable;
|
||||
use crate::callback::{Callback, UnsyncCallback};
|
||||
use reactive_graph::traits::Dispose;
|
||||
use crate::{
|
||||
callback::{Callback, UnsyncCallback},
|
||||
owner::Owner,
|
||||
traits::Dispose,
|
||||
IntoReactiveValue,
|
||||
};
|
||||
|
||||
struct NoClone {}
|
||||
|
||||
#[test]
|
||||
fn clone_callback() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let callback = Callback::new(move |_no_clone: NoClone| NoClone {});
|
||||
let _cloned = callback;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clone_unsync_callback() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let callback =
|
||||
UnsyncCallback::new(move |_no_clone: NoClone| NoClone {});
|
||||
let _cloned = callback;
|
||||
@@ -285,20 +325,39 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn runback_from() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let _callback: Callback<(), String> = (|| "test").into();
|
||||
let _callback: Callback<(i32, String), String> =
|
||||
(|num, s| format!("{num} {s}")).into();
|
||||
// Single params should work without needing the (foo,) tuple using IntoReactiveValue:
|
||||
let _callback: Callback<usize, &'static str> =
|
||||
(|_usize| "test").into_reactive_value();
|
||||
let _callback: Callback<usize, String> =
|
||||
(|_usize| "test").into_reactive_value();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_callback_from() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let _callback: UnsyncCallback<(), String> = (|| "test").into();
|
||||
let _callback: UnsyncCallback<(i32, String), String> =
|
||||
(|num, s| format!("{num} {s}")).into();
|
||||
// Single params should work without needing the (foo,) tuple using IntoReactiveValue:
|
||||
let _callback: UnsyncCallback<usize, &'static str> =
|
||||
(|_usize| "test").into_reactive_value();
|
||||
let _callback: UnsyncCallback<usize, String> =
|
||||
(|_usize| "test").into_reactive_value();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_callback_try_run() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let callback = Callback::new(move |arg| arg);
|
||||
assert_eq!(callback.try_run((0,)), Some((0,)));
|
||||
callback.dispose();
|
||||
@@ -307,6 +366,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn unsync_callback_try_run() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let callback = UnsyncCallback::new(move |arg| arg);
|
||||
assert_eq!(callback.try_run((0,)), Some((0,)));
|
||||
callback.dispose();
|
||||
@@ -315,6 +377,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn callback_matches_same() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let callback1 = Callback::new(|x: i32| x * 2);
|
||||
let callback2 = callback1;
|
||||
assert!(callback1.matches(&callback2));
|
||||
@@ -322,6 +387,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn callback_matches_different() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let callback1 = Callback::new(|x: i32| x * 2);
|
||||
let callback2 = Callback::new(|x: i32| x + 1);
|
||||
assert!(!callback1.matches(&callback2));
|
||||
@@ -329,6 +397,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn unsync_callback_matches_same() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let callback1 = UnsyncCallback::new(|x: i32| x * 2);
|
||||
let callback2 = callback1;
|
||||
assert!(callback1.matches(&callback2));
|
||||
@@ -336,6 +407,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn unsync_callback_matches_different() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let callback1 = UnsyncCallback::new(|x: i32| x * 2);
|
||||
let callback2 = UnsyncCallback::new(|x: i32| x + 1);
|
||||
assert!(!callback1.matches(&callback2));
|
||||
67
reactive_graph/src/into_reactive_value.rs
Normal file
67
reactive_graph/src/into_reactive_value.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
#[doc(hidden)]
|
||||
pub struct __IntoReactiveValueMarkerBaseCase;
|
||||
|
||||
/// A helper trait that works like `Into<T>` but uses a marker generic
|
||||
/// to allow more `From` implementations than would be allowed with just `Into<T>`.
|
||||
pub trait IntoReactiveValue<T, M> {
|
||||
/// Converts `self` into a `T`.
|
||||
fn into_reactive_value(self) -> T;
|
||||
}
|
||||
|
||||
// The base case, which allows anything which implements .into() to work:
|
||||
impl<T, I> IntoReactiveValue<T, __IntoReactiveValueMarkerBaseCase> for I
|
||||
where
|
||||
I: Into<T>,
|
||||
{
|
||||
fn into_reactive_value(self) -> T {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
into_reactive_value::IntoReactiveValue,
|
||||
owner::{LocalStorage, Owner},
|
||||
traits::GetUntracked,
|
||||
wrappers::read::Signal,
|
||||
};
|
||||
use typed_builder::TypedBuilder;
|
||||
|
||||
#[test]
|
||||
fn test_into_signal_compiles() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
let _: Signal<usize> = (|| 2).into_reactive_value();
|
||||
let _: Signal<usize, LocalStorage> = 2.into_reactive_value();
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
let _: Signal<usize, LocalStorage> = (|| 2).into_reactive_value();
|
||||
let _: Signal<String> = "str".into_reactive_value();
|
||||
let _: Signal<String, LocalStorage> = "str".into_reactive_value();
|
||||
|
||||
#[derive(TypedBuilder)]
|
||||
struct Foo {
|
||||
#[builder(setter(
|
||||
fn transform<M>(value: impl IntoReactiveValue<Signal<usize>, M>) {
|
||||
value.into_reactive_value()
|
||||
}
|
||||
))]
|
||||
sig: Signal<usize>,
|
||||
}
|
||||
|
||||
assert_eq!(Foo::builder().sig(2).build().sig.get_untracked(), 2);
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
assert_eq!(Foo::builder().sig(|| 2).build().sig.get_untracked(), 2);
|
||||
assert_eq!(
|
||||
Foo::builder()
|
||||
.sig(Signal::stored(2))
|
||||
.build()
|
||||
.sig
|
||||
.get_untracked(),
|
||||
2
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -90,6 +90,12 @@ pub mod traits;
|
||||
pub mod transition;
|
||||
pub mod wrappers;
|
||||
|
||||
mod into_reactive_value;
|
||||
pub use into_reactive_value::*;
|
||||
|
||||
/// A standard way to wrap functions and closures to pass them to components.
|
||||
pub mod callback;
|
||||
|
||||
use computed::ScopedFuture;
|
||||
|
||||
#[cfg(all(feature = "nightly", rustc_nightly))]
|
||||
@@ -97,7 +103,9 @@ mod nightly;
|
||||
|
||||
/// Reexports frequently-used traits.
|
||||
pub mod prelude {
|
||||
pub use crate::{owner::FromLocal, traits::*};
|
||||
pub use crate::{
|
||||
into_reactive_value::IntoReactiveValue, owner::FromLocal, traits::*,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO remove this, it's just useful while developing
|
||||
|
||||
@@ -324,6 +324,22 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> From<&'static str> for ArcSignal<String, S>
|
||||
where
|
||||
S: Storage<&'static str> + Storage<String>,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(value: &'static str) -> Self {
|
||||
Self {
|
||||
inner: SignalTypes::Stored(ArcStoredValue::new(
|
||||
value.to_string(),
|
||||
)),
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> DefinedAt for ArcSignal<T, S>
|
||||
where
|
||||
S: Storage<T>,
|
||||
@@ -1049,6 +1065,13 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Signal<&'static str, LocalStorage>> for Signal<String, LocalStorage> {
|
||||
#[track_caller]
|
||||
fn from(value: Signal<&'static str, LocalStorage>) -> Self {
|
||||
Signal::derive_local(move || value.read().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Signal<&'static str>> for Signal<String, LocalStorage> {
|
||||
#[track_caller]
|
||||
fn from(value: Signal<&'static str>) -> Self {
|
||||
@@ -1077,6 +1100,15 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Signal<Option<&'static str>, LocalStorage>>
|
||||
for Signal<Option<String>, LocalStorage>
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(value: Signal<Option<&'static str>, LocalStorage>) -> Self {
|
||||
Signal::derive_local(move || value.read().map(str::to_string))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Signal<Option<&'static str>>>
|
||||
for Signal<Option<String>, LocalStorage>
|
||||
{
|
||||
@@ -1086,6 +1118,192 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
#[doc(hidden)]
|
||||
pub struct __IntoReactiveValueMarkerSignalFromReactiveClosure;
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
#[doc(hidden)]
|
||||
pub struct __IntoReactiveValueMarkerSignalStrOutputToString;
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
#[doc(hidden)]
|
||||
pub struct __IntoReactiveValueMarkerOptionalSignalFromReactiveClosureAlways;
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<T, F>
|
||||
crate::IntoReactiveValue<
|
||||
Signal<T, SyncStorage>,
|
||||
__IntoReactiveValueMarkerSignalFromReactiveClosure,
|
||||
> for F
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
F: Fn() -> T + Send + Sync + 'static,
|
||||
{
|
||||
fn into_reactive_value(self) -> Signal<T, SyncStorage> {
|
||||
Signal::derive(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<T, F>
|
||||
crate::IntoReactiveValue<
|
||||
ArcSignal<T, SyncStorage>,
|
||||
__IntoReactiveValueMarkerSignalFromReactiveClosure,
|
||||
> for F
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
F: Fn() -> T + Send + Sync + 'static,
|
||||
{
|
||||
fn into_reactive_value(self) -> ArcSignal<T, SyncStorage> {
|
||||
ArcSignal::derive(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<T, F>
|
||||
crate::IntoReactiveValue<
|
||||
Signal<T, LocalStorage>,
|
||||
__IntoReactiveValueMarkerSignalFromReactiveClosure,
|
||||
> for F
|
||||
where
|
||||
T: 'static,
|
||||
F: Fn() -> T + 'static,
|
||||
{
|
||||
fn into_reactive_value(self) -> Signal<T, LocalStorage> {
|
||||
Signal::derive_local(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<T, F>
|
||||
crate::IntoReactiveValue<
|
||||
ArcSignal<T, LocalStorage>,
|
||||
__IntoReactiveValueMarkerSignalFromReactiveClosure,
|
||||
> for F
|
||||
where
|
||||
T: 'static,
|
||||
F: Fn() -> T + 'static,
|
||||
{
|
||||
fn into_reactive_value(self) -> ArcSignal<T, LocalStorage> {
|
||||
ArcSignal::derive_local(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<F>
|
||||
crate::IntoReactiveValue<
|
||||
Signal<String, SyncStorage>,
|
||||
__IntoReactiveValueMarkerSignalStrOutputToString,
|
||||
> for F
|
||||
where
|
||||
F: Fn() -> &'static str + Send + Sync + 'static,
|
||||
{
|
||||
fn into_reactive_value(self) -> Signal<String, SyncStorage> {
|
||||
Signal::derive(move || self().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<F>
|
||||
crate::IntoReactiveValue<
|
||||
ArcSignal<String, SyncStorage>,
|
||||
__IntoReactiveValueMarkerSignalStrOutputToString,
|
||||
> for F
|
||||
where
|
||||
F: Fn() -> &'static str + Send + Sync + 'static,
|
||||
{
|
||||
fn into_reactive_value(self) -> ArcSignal<String, SyncStorage> {
|
||||
ArcSignal::derive(move || self().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<F>
|
||||
crate::IntoReactiveValue<
|
||||
Signal<String, LocalStorage>,
|
||||
__IntoReactiveValueMarkerSignalStrOutputToString,
|
||||
> for F
|
||||
where
|
||||
F: Fn() -> &'static str + 'static,
|
||||
{
|
||||
fn into_reactive_value(self) -> Signal<String, LocalStorage> {
|
||||
Signal::derive_local(move || self().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<F>
|
||||
crate::IntoReactiveValue<
|
||||
ArcSignal<String, LocalStorage>,
|
||||
__IntoReactiveValueMarkerSignalStrOutputToString,
|
||||
> for F
|
||||
where
|
||||
F: Fn() -> &'static str + 'static,
|
||||
{
|
||||
fn into_reactive_value(self) -> ArcSignal<String, LocalStorage> {
|
||||
ArcSignal::derive_local(move || self().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<T, F>
|
||||
crate::IntoReactiveValue<
|
||||
Signal<Option<T>, SyncStorage>,
|
||||
__IntoReactiveValueMarkerOptionalSignalFromReactiveClosureAlways,
|
||||
> for F
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
F: Fn() -> T + Send + Sync + 'static,
|
||||
{
|
||||
fn into_reactive_value(self) -> Signal<Option<T>, SyncStorage> {
|
||||
Signal::derive(move || Some(self()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<T, F>
|
||||
crate::IntoReactiveValue<
|
||||
ArcSignal<Option<T>, SyncStorage>,
|
||||
__IntoReactiveValueMarkerOptionalSignalFromReactiveClosureAlways,
|
||||
> for F
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
F: Fn() -> T + Send + Sync + 'static,
|
||||
{
|
||||
fn into_reactive_value(self) -> ArcSignal<Option<T>, SyncStorage> {
|
||||
ArcSignal::derive(move || Some(self()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<T, F>
|
||||
crate::IntoReactiveValue<
|
||||
Signal<Option<T>, LocalStorage>,
|
||||
__IntoReactiveValueMarkerOptionalSignalFromReactiveClosureAlways,
|
||||
> for F
|
||||
where
|
||||
T: 'static,
|
||||
F: Fn() -> T + 'static,
|
||||
{
|
||||
fn into_reactive_value(self) -> Signal<Option<T>, LocalStorage> {
|
||||
Signal::derive_local(move || Some(self()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<T, F>
|
||||
crate::IntoReactiveValue<
|
||||
ArcSignal<Option<T>, LocalStorage>,
|
||||
__IntoReactiveValueMarkerOptionalSignalFromReactiveClosureAlways,
|
||||
> for F
|
||||
where
|
||||
T: 'static,
|
||||
F: Fn() -> T + 'static,
|
||||
{
|
||||
fn into_reactive_value(self) -> ArcSignal<Option<T>, LocalStorage> {
|
||||
ArcSignal::derive_local(move || Some(self()))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> From<MaybeSignal<T>> for Signal<T>
|
||||
where
|
||||
|
||||
Reference in New Issue
Block a user