diff --git a/leptos_dom/src/callback.rs b/leptos_dom/src/callback.rs deleted file mode 100644 index d03ab1fe4e..0000000000 --- a/leptos_dom/src/callback.rs +++ /dev/null @@ -1,412 +0,0 @@ -//! Callbacks define a standard way to store functions and closures, -//! in particular for component properties. -//! -//! # How to use them -//! You can always create a callback from a closure, but the prefered way is to use `prop(into)` -//! when you define your component: -//! ``` -//! # use leptos::*; -//! # use leptos::leptos_dom::{Callback, Callable}; -//! #[component] -//! fn MyComponent( -//! #[prop(into)] render_number: Callback, -//! ) -> impl IntoView { -//! view! { -//!
-//! {render_number.call(42)} -//!
-//! } -//! } -//! // now you can use it from a closure directly: -//! fn test() -> impl IntoView { -//! view! { -//! -//! } -//! } -//! ``` -//! -//! *Notes*: -//! - in this example, you should use a generic type that implements `Fn(i32) -> String`. -//! Callbacks are more usefull when you want optional generic props. -//! - All callbacks implement the `Callable` trait. You have to write `my_callback.call(input)` -//! -//! -//! # Types -//! This modules defines: -//! - [Callback], the most basic callback type -//! - [SyncCallback] for scenarios when you need `Send` and `Sync` -//! - [HtmlCallback] for a function that returns a [HtmlElement] -//! - [ViewCallback] for a function that returns some kind of [view][IntoView] -//! -//! # Copying vs cloning -//! All callbacks type defined in this module are [Clone] but not [Copy]. -//! To solve this issue, use [StoredValue]; see [StoredCallback] for more -//! ``` -//! # use leptos::*; -//! # use leptos::leptos_dom::{Callback, Callable}; -//! fn test() -> impl IntoView { -//! let callback: Callback = -//! Callback::new(|x: i32| x.to_string()); -//! let stored_callback = store_value(callback); -//! -//! view! { -//!
-//! // `stored_callback` can be moved multiple times -//! {move || stored_callback.call(1)} -//! {move || stored_callback.call(42)} -//!
-//! } -//! } -//! ``` -//! -//! Note that for each callback type `T`, `StoredValue` implements `Call`, so you can call them -//! without even thinking about it. - -use crate::{AnyElement, ElementDescriptor, HtmlElement, IntoView, View}; -use leptos_reactive::StoredValue; -use std::{fmt, rc::Rc, sync::Arc}; - -/// A wrapper trait for calling callbacks. -pub trait Callable { - /// calls the callback with the specified argument. - fn call(&self, input: In) -> Out; -} - -/// The most basic leptos callback type. -/// For how to use callbacks, see [here][crate::callback] -/// -/// # Example -/// ``` -/// # use leptos::*; -/// # use leptos::leptos_dom::{Callable, Callback}; -/// #[component] -/// fn MyComponent( -/// #[prop(into)] render_number: Callback, -/// ) -> impl IntoView { -/// view! { -///
-/// {render_number.call(42)} -///
-/// } -/// } -/// -/// fn test() -> impl IntoView { -/// view! { -/// -/// } -/// } -/// ``` -/// -/// # Cloning -/// See [StoredCallback] - -pub struct Callback(Rc Out>); - -impl fmt::Debug for Callback { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - fmt.write_str("Callback") - } -} - -impl Clone for Callback { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl Callback { - /// creates a new callback from the function or closure - pub fn new(f: F) -> Callback - where - F: Fn(In) -> Out + 'static, - { - Self(Rc::new(f)) - } -} - -impl Callable for Callback { - fn call(&self, input: In) -> Out { - (self.0)(input) - } -} - -impl From for Callback -where - F: Fn(In) -> Out + 'static, -{ - fn from(f: F) -> Callback { - Callback::new(f) - } -} - -/// A callback type that implements `Copy`. -/// `StoredCallback` is an alias for `StoredValue>`. -/// -/// # Example -/// ``` -/// # use leptos::*; -/// # use leptos::leptos_dom::{Callback, StoredCallback, Callable}; -/// fn test() -> impl IntoView { -/// let callback: Callback = -/// Callback::new(|x: i32| x.to_string()); -/// let stored_callback: StoredCallback = -/// store_value(callback); -/// view! { -///
-/// {move || stored_callback.call(1)} -/// {move || stored_callback.call(42)} -///
-/// } -/// } -/// ``` -/// -/// Note that in this example, you can replace `Callback` by `SyncCallback` or `ViewCallback`, and -/// it will work in the same way. -/// -/// -/// Note that a prop should never be a [StoredCallback]: -/// you have to call [store_value][leptos_reactive::store_value] inside your component code. -pub type StoredCallback = StoredValue>; - -impl Callable for StoredValue -where - F: Callable, -{ - fn call(&self, input: In) -> Out { - self.with_value(|cb| cb.call(input)) - } -} - -/// a callback type that is `Send` and `Sync` if the input type is -pub struct SyncCallback(Arc Out>); - -impl fmt::Debug for SyncCallback { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - fmt.write_str("SyncCallback") - } -} - -impl Clone for SyncCallback { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl SyncCallback { - /// creates a new callback from the function or closure - pub fn new(fun: F) -> Self - where - F: Fn(In) -> Out + 'static, - { - Self(Arc::new(fun)) - } -} - -/// A special callback type that returns any Html element. -/// You can use it exactly the same way as a classic callback. -/// -/// For how to use callbacks, see [here][crate::callback] -/// -/// # Example -/// -/// ``` -/// # use leptos::*; -/// # use leptos::leptos_dom::{Callable, HtmlCallback}; -/// #[component] -/// fn MyComponent( -/// #[prop(into)] render_number: HtmlCallback, -/// ) -> impl IntoView { -/// view! { -///
-/// {render_number.call(42)} -///
-/// } -/// } -/// fn test() -> impl IntoView { -/// view! { -/// {x}}/> -/// } -/// } -/// ``` -/// -/// # `HtmlCallback` with empty input type. -/// Note that when `my_html_callback` is `HtmlCallback<()>`, you can use it more easily because it -/// implements [IntoView] -/// -/// view!{ -///
-/// {render_number} -///
-/// } -pub struct HtmlCallback(Rc HtmlElement>); - -impl fmt::Debug for HtmlCallback { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - fmt.write_str("HtmlCallback") - } -} - -impl Clone for HtmlCallback { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl HtmlCallback { - /// creates a new callback from the function or closure - pub fn new(f: F) -> Self - where - F: Fn(In) -> HtmlElement + 'static, - H: ElementDescriptor + 'static, - { - Self(Rc::new(move |x| f(x).into_any())) - } -} - -impl Callable> for HtmlCallback { - fn call(&self, input: In) -> HtmlElement { - (self.0)(input) - } -} - -impl From for HtmlCallback -where - F: Fn(In) -> HtmlElement + 'static, - H: ElementDescriptor + 'static, -{ - fn from(f: F) -> Self { - HtmlCallback(Rc::new(move |x| f(x).into_any())) - } -} - -impl IntoView for HtmlCallback<()> { - fn into_view(self) -> View { - self.call(()).into_view() - } -} - -/// A special callback type that returns any [`View`]. -/// -/// You can use it exactly the same way as a classic callback. -/// For how to use callbacks, see [here][crate::callback] -/// -/// ``` -/// # use leptos::*; -/// # use leptos::leptos_dom::{ViewCallback, Callable}; -/// #[component] -/// fn MyComponent( -/// #[prop(into)] render_number: ViewCallback, -/// ) -> impl IntoView { -/// view! { -///
-/// {render_number.call(42)} -///
-/// } -/// } -/// fn test() -> impl IntoView { -/// view! { -/// {x}}/> -/// } -/// } -/// ``` -/// -/// # `ViewCallback` with empty input type. -/// Note that when `my_view_callback` is `ViewCallback<()>`, you can use it more easily because it -/// implements [IntoView] -/// -/// view!{ -///
-/// {render_number} -///
-/// } -pub struct ViewCallback(Rc View>); - -impl fmt::Debug for ViewCallback { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - fmt.write_str("ViewCallback") - } -} - -impl Clone for ViewCallback { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl ViewCallback { - /// creates a new callback from the function or closure - pub fn new(f: F) -> Self - where - F: Fn(In) -> V + 'static, - V: IntoView + 'static, - { - ViewCallback(Rc::new(move |x| f(x).into_view())) - } -} - -impl Callable for ViewCallback { - fn call(&self, input: In) -> View { - (self.0)(input) - } -} - -impl From for ViewCallback -where - F: Fn(In) -> V + 'static, - V: IntoView + 'static, -{ - fn from(f: F) -> Self { - Self::new(f) - } -} - -impl IntoView for ViewCallback<()> { - fn into_view(self) -> View { - self.call(()).into_view() - } -} - -#[cfg(test)] -mod tests { - use crate::{Callback, HtmlCallback, SyncCallback, ViewCallback}; - - struct NoClone {} - - #[test] - fn clone_callback() { - let callback = Callback::new(move |_no_clone: NoClone| NoClone {}); - let _cloned = callback.clone(); - } - - #[test] - fn clone_sync_callback() { - let callback = SyncCallback::new(move |_no_clone: NoClone| NoClone {}); - let _cloned = callback.clone(); - } - - #[test] - fn clone_html_callback() { - #[derive(Debug)] - struct TestElem; - impl crate::ElementDescriptor for TestElem { - fn name(&self) -> leptos_reactive::Oco<'static, str> { - leptos_reactive::Oco::Borrowed("test-elem") - } - fn hydration_id(&self) -> &Option { - &None - } - } - - let callback = HtmlCallback::new(move |_no_clone: NoClone| { - crate::HtmlElement::new(TestElem {}) - }); - let _cloned = callback.clone(); - } - - #[test] - fn clone_view_callback() { - let callback = - ViewCallback::new(move |_no_clone: NoClone| crate::View::default()); - let _cloned = callback.clone(); - } -} diff --git a/leptos_dom/src/lib.rs b/leptos_dom/src/lib.rs index bff1c996bb..e0aa7a2ec6 100644 --- a/leptos_dom/src/lib.rs +++ b/leptos_dom/src/lib.rs @@ -9,7 +9,6 @@ #[cfg_attr(any(debug_assertions, feature = "ssr"), macro_use)] pub extern crate tracing; -pub mod callback; mod components; mod events; pub mod helpers; @@ -26,7 +25,6 @@ pub mod ssr; pub mod ssr_in_order; pub mod svg; mod transparent; -pub use callback::*; use cfg_if::cfg_if; pub use components::*; #[cfg(all(target_arch = "wasm32", feature = "web"))] diff --git a/leptos_reactive/src/callback.rs b/leptos_reactive/src/callback.rs new file mode 100644 index 0000000000..80a20265f2 --- /dev/null +++ b/leptos_reactive/src/callback.rs @@ -0,0 +1,316 @@ +//! Callbacks define a standard way to store functions and closures. They are useful +//! 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::*; +//! #[component] +//! fn MyComponent( +//! #[prop(into)] render_number: Callback, +//! ) -> impl IntoView { +//! view! { +//!
+//! {render_number.call(1)} +//! // callbacks can be called multiple times +//! {render_number.call(42)} +//!
+//! } +//! } +//! // you can pass a closure directly as `render_number` +//! fn test() -> impl IntoView { +//! view! { +//! +//! } +//! } +//! ``` +//! +//! *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`] trait, and can be invoked with `my_callback.call(input)`. On nightly, you can even do `my_callback(input)` +//! - 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`] +//! - [`SyncCallback`] +//! +//! Use `SyncCallback` when you want the function to be `Sync` and `Send`. + +use crate::{store_value, StoredValue}; +use std::{fmt, sync::Arc}; + +/// A wrapper trait for calling callbacks. +pub trait Callable { + /// calls the callback with the specified argument. + fn call(&self, input: In) -> Out; +} + +/// Callbacks define a standard way to store functions and closures. +/// +/// # Example +/// ``` +/// # use leptos::*; +/// # use leptos::{Callable, Callback}; +/// #[component] +/// fn MyComponent( +/// #[prop(into)] render_number: Callback, +/// ) -> impl IntoView { +/// view! { +///
+/// {render_number.call(42)} +///
+/// } +/// } +/// +/// fn test() -> impl IntoView { +/// view! { +/// +/// } +/// } +/// ``` + +pub struct Callback( + StoredValue Out>>, +); + +impl fmt::Debug for Callback { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + fmt.write_str("Callback") + } +} + +impl Clone for Callback { + fn clone(&self) -> Self { + Self(self.0) + } +} + +impl Callback { + /// Creates a new callback from the given function. + pub fn new(f: F) -> Callback + where + F: Fn(In) -> Out + 'static, + { + Self(store_value(Box::new(f))) + } +} + +impl Callable for Callback { + fn call(&self, input: In) -> Out { + self.0.with_value(|f| f(input)) + } +} + +#[cfg(not(feature = "nightly"))] +impl From for Callback +where + F: Fn(In) -> T + 'static, + T: Into + 'static, +{ + fn from(f: F) -> Callback { + Callback::new(move |x| f(x).into()) + } +} + +#[cfg(feature = "nightly")] +auto trait NotRawCallback {} +#[cfg(feature = "nightly")] +impl !NotRawCallback for Callback {} +#[cfg(feature = "nightly")] +impl From for Callback +where + F: Fn(In) -> T + NotRawCallback + 'static, + T: Into + 'static, +{ + fn from(f: F) -> Callback { + Callback::new(move |x| f(x).into()) + } +} + +#[cfg(feature = "nightly")] +impl FnOnce<(In,)> for Callback { + type Output = Out; + + extern "rust-call" fn call_once(self, args: (In,)) -> Self::Output { + Callable::call(&self, args.0) + } +} + +#[cfg(feature = "nightly")] +impl FnMut<(In,)> for Callback { + extern "rust-call" fn call_mut(&mut self, args: (In,)) -> Self::Output { + Callable::call(&*self, args.0) + } +} + +#[cfg(feature = "nightly")] +impl Fn<(In,)> for Callback { + extern "rust-call" fn call(&self, args: (In,)) -> Self::Output { + Callable::call(self, args.0) + } +} + +/// A callback type that is `Send` and `Sync` if its input type is `Send` and `Sync`. +/// Otherwise, you can use exactly the way you use [`Callback`]. +pub struct SyncCallback( + StoredValue Out>>, +); + +impl fmt::Debug for SyncCallback { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + fmt.write_str("SyncCallback") + } +} + +impl Callable for SyncCallback { + fn call(&self, input: In) -> Out { + self.0.with_value(|f| f(input)) + } +} + +impl Clone for SyncCallback { + fn clone(&self) -> Self { + Self(self.0) + } +} + +impl SyncCallback { + /// Creates a new callback from the given function. + pub fn new(fun: F) -> Self + where + F: Fn(In) -> Out + 'static, + { + Self(store_value(Arc::new(fun))) + } +} + +#[cfg(not(feature = "nightly"))] +impl From for SyncCallback +where + F: Fn(In) -> T + 'static, + T: Into + 'static, +{ + fn from(f: F) -> SyncCallback { + SyncCallback::new(move |x| f(x).into()) + } +} + +#[cfg(feature = "nightly")] +auto trait NotRawSyncCallback {} +#[cfg(feature = "nightly")] +impl !NotRawSyncCallback for SyncCallback {} +#[cfg(feature = "nightly")] +impl From for SyncCallback +where + F: Fn(In) -> T + NotRawSyncCallback + 'static, + T: Into + 'static, +{ + fn from(f: F) -> SyncCallback { + SyncCallback::new(move |x| f(x).into()) + } +} + +#[cfg(feature = "nightly")] +impl FnOnce<(In,)> for SyncCallback { + type Output = Out; + + extern "rust-call" fn call_once(self, args: (In,)) -> Self::Output { + Callable::call(&self, args.0) + } +} + +#[cfg(feature = "nightly")] +impl FnMut<(In,)> for SyncCallback { + extern "rust-call" fn call_mut(&mut self, args: (In,)) -> Self::Output { + Callable::call(&*self, args.0) + } +} + +#[cfg(feature = "nightly")] +impl Fn<(In,)> for SyncCallback { + extern "rust-call" fn call(&self, args: (In,)) -> Self::Output { + Callable::call(self, args.0) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + callback::{Callback, SyncCallback}, + create_runtime, + }; + + struct NoClone {} + + #[test] + fn clone_callback() { + let rt = create_runtime(); + let callback = Callback::new(move |_no_clone: NoClone| NoClone {}); + let _cloned = callback.clone(); + rt.dispose(); + } + + #[test] + fn clone_sync_callback() { + let rt = create_runtime(); + let callback = SyncCallback::new(move |_no_clone: NoClone| NoClone {}); + let _cloned = callback.clone(); + rt.dispose(); + } + + #[test] + fn callback_from() { + let rt = create_runtime(); + let _callback: Callback<(), String> = (|()| "test").into(); + rt.dispose(); + } + + #[test] + fn callback_from_html() { + let rt = create_runtime(); + use leptos::{ + html::{AnyElement, HtmlElement}, + *, + }; + + let _callback: Callback> = + (|x: String| { + view! { +

{x}

+ } + }) + .into(); + rt.dispose(); + } + + #[test] + fn sync_callback_from() { + let rt = create_runtime(); + let _callback: SyncCallback<(), String> = (|()| "test").into(); + rt.dispose(); + } + + #[test] + fn sync_callback_from_html() { + use leptos::{ + html::{AnyElement, HtmlElement}, + *, + }; + + let rt = create_runtime(); + + let _callback: SyncCallback> = + (|x: String| { + view! { +

{x}

+ } + }) + .into(); + + rt.dispose(); + } +} diff --git a/leptos_reactive/src/lib.rs b/leptos_reactive/src/lib.rs index cec4ad7ee7..fcd0191855 100644 --- a/leptos_reactive/src/lib.rs +++ b/leptos_reactive/src/lib.rs @@ -80,6 +80,7 @@ extern crate tracing; #[macro_use] mod signal; +mod callback; mod context; #[macro_use] mod diagnostics; @@ -105,6 +106,7 @@ pub mod suspense; mod trigger; mod watch; +pub use callback::*; pub use context::*; pub use diagnostics::SpecialNonReactiveZone; pub use effect::*;