migrated away from CloneableFn... traits

This commit is contained in:
Maccesch 2023-08-04 15:58:03 +01:00
parent 19c4f00c5b
commit 58c54bb3e7
23 changed files with 210 additions and 415 deletions

View file

@ -25,9 +25,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- The options `reconnect_limit` and `reconnect_interval` now take a `u64` instead of `Option<u64>` to improve DX.
- The option `manual` has been renamed to `immediate` to make it more consistent with other functions.
To port please note that `immediate` is the inverse of `manual` (`immediate` = `!manual`).
- `use_color_mode`:
- The optional `on_changed` handler parameters have changed slightly. Please refer to the docs for more details.
- Throttled or debounced functions cannot be `FnOnce` anymore.
- All traits `ClonableFn...` have been removed.
### Other Changes 🔥
- Callbacks in options don't require to be cloneable anymore
- Callback in `use_raf_fn` doesn't require to be cloneable anymore
## [0.6.2] - 2023-08-03

View file

@ -16,7 +16,7 @@ homepage = "https://leptos-use.rs"
leptos = "0.5.0-alpha"
wasm-bindgen = "0.2"
js-sys = "0.3"
default-struct-builder = "0.4"
default-struct-builder = "0.5"
num = { version = "0.4", optional = true }
serde = { version = "1", optional = true }
serde_json = { version = "1", optional = true }

View file

@ -1,8 +1,9 @@
use crate::filter_builder_methods;
use crate::storage::{StorageType, UseStorageError, UseStorageOptions};
use crate::utils::{CloneableFnWithArg, DebounceOptions, FilterOptions, ThrottleOptions};
use crate::utils::{DebounceOptions, FilterOptions, ThrottleOptions};
use default_struct_builder::DefaultBuilder;
use leptos::*;
use std::rc::Rc;
macro_rules! use_specific_storage {
($(#[$outer:meta])*
@ -58,8 +59,7 @@ pub struct UseSpecificStorageOptions<T> {
/// Defaults to simply returning the stored value.
merge_defaults: fn(&str, &T) -> String,
/// Optional callback whenever an error occurs. The callback takes an argument of type [`UseStorageError`].
#[builder(into)]
on_error: Box<dyn CloneableFnWithArg<UseStorageError>>,
on_error: Rc<dyn Fn(UseStorageError)>,
/// Debounce or throttle the writing to storage whenever the value changes.
filter: FilterOptions,
@ -71,7 +71,7 @@ impl<T> Default for UseSpecificStorageOptions<T> {
listen_to_storage_changes: true,
write_defaults: true,
merge_defaults: |stored_value, _default_value| stored_value.to_string(),
on_error: Box::new(|_| ()),
on_error: Rc::new(|_| ()),
filter: Default::default(),
}
}

View file

@ -1,7 +1,7 @@
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports, dead_code))]
use crate::core::MaybeRwSignal;
use crate::utils::{CloneableFn, CloneableFnWithArg, FilterOptions};
use crate::utils::FilterOptions;
use crate::{
filter_builder_methods, use_event_listener, watch_pausable_with_options, DebounceOptions,
ThrottleOptions, WatchOptions, WatchPausableReturn,
@ -12,6 +12,7 @@ use js_sys::Reflect;
use leptos::*;
use serde::{Deserialize, Serialize};
use serde_json::Error;
use std::rc::Rc;
use std::time::Duration;
use wasm_bindgen::{JsCast, JsValue};
@ -190,18 +191,18 @@ where
let raw_init = data.get_untracked();
cfg_if! { if #[cfg(feature = "ssr")] {
let remove: Box<dyn CloneableFn> = Box::new(|| {});
let remove: Rc<dyn Fn()> = Rc::new(|| {});
} else {
let storage = storage_type.into_storage();
let remove: Box<dyn CloneableFn> = match storage {
let remove: Rc<dyn Fn()> = match storage {
Ok(Some(storage)) => {
let write = {
let on_error = on_error.clone();
let storage = storage.clone();
let key = key.to_string();
move |v: &T| {
Rc::new(move |v: &T| {
match serde_json::to_string(&v) {
Ok(ref serialized) => match storage.get_item(&key) {
Ok(old_value) => {
@ -240,7 +241,7 @@ where
on_error.clone()(UseStorageError::SerializationError(e));
}
}
}
})
};
let read = {
@ -249,6 +250,7 @@ where
let key = key.to_string();
let raw_init = raw_init.clone();
Rc::new(
move |event_detail: Option<StorageEventDetail>| -> Option<T> {
let serialized_init = match serde_json::to_string(&raw_init) {
Ok(serialized) => Some(serialized),
@ -290,10 +292,11 @@ where
}
}
Some(raw_init)
}
Some(raw_init.clone())
}
}
},
)
};
let WatchPausableReturn {
@ -311,15 +314,15 @@ where
let storage = storage.clone();
let raw_init = raw_init.clone();
move |event_detail: Option<StorageEventDetail>| {
Rc::new(move |event_detail: Option<StorageEventDetail>| {
if let Some(event_detail) = &event_detail {
if event_detail.storage_area != Some(storage) {
if event_detail.storage_area != Some(storage.clone()) {
return;
}
match &event_detail.key {
None => {
set_data.set(raw_init);
set_data.set(raw_init.clone());
return;
}
Some(event_key) => {
@ -343,16 +346,20 @@ where
} else {
resume_watch();
}
}
})
};
let upd = update.clone();
let update_from_custom_event =
move |event: web_sys::CustomEvent| upd.clone()(Some(event.into()));
let update_from_custom_event = {
let update = Rc::clone(&update);
let upd = update.clone();
let update_from_storage_event =
move |event: web_sys::StorageEvent| upd.clone()(Some(event.into()));
move |event: web_sys::CustomEvent| update(Some(event.into()))
};
let update_from_storage_event = {
let update = Rc::clone(&update);
move |event: web_sys::StorageEvent| update(Some(event.into()))
};
if listen_to_storage_changes {
let _ = use_event_listener(window(), ev::storage, update_from_storage_event);
@ -367,22 +374,22 @@ where
let k = key.to_string();
Box::new(move || {
Rc::new(move || {
let _ = storage.remove_item(&k);
})
}
Err(e) => {
on_error(UseStorageError::NoStorage(e));
Box::new(move || {})
Rc::new(move || {})
}
_ => {
// do nothing
Box::new(move || {})
Rc::new(move || {})
}
};
}}
(data, set_data, move || remove.clone()())
(data, set_data, move || remove())
}
#[derive(Clone)]
@ -461,7 +468,7 @@ pub struct UseStorageOptions<T> {
/// Defaults to simply returning the stored value.
pub(crate) merge_defaults: fn(&str, &T) -> String,
/// Optional callback whenever an error occurs. The callback takes an argument of type [`UseStorageError`].
pub(crate) on_error: Box<dyn CloneableFnWithArg<UseStorageError>>,
pub(crate) on_error: Rc<dyn Fn(UseStorageError)>,
/// Debounce or throttle the writing to storage whenever the value changes.
pub(crate) filter: FilterOptions,
@ -474,7 +481,7 @@ impl<T> Default for UseStorageOptions<T> {
listen_to_storage_changes: true,
write_defaults: true,
merge_defaults: |stored_value, _default_value| stored_value.to_string(),
on_error: Box::new(|_| ()),
on_error: Rc::new(|_| ()),
filter: Default::default(),
}
}

View file

@ -7,11 +7,11 @@ use std::fmt::{Display, Formatter};
use crate::core::StorageType;
use crate::use_preferred_dark;
use crate::utils::CloneableFnWithArg;
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder;
use leptos::*;
use std::marker::PhantomData;
use std::rc::Rc;
use wasm_bindgen::JsCast;
/// Reactive color mode (dark / light / customs) with auto data persistence.
@ -217,10 +217,7 @@ where
};
let on_changed = move |mode: ColorMode| {
on_changed(UseColorModeOnChangeArgs {
mode,
default_handler: Box::new(default_on_changed.clone()),
});
on_changed(mode, Rc::new(default_on_changed.clone()));
};
create_effect({
@ -333,16 +330,6 @@ impl From<String> for ColorMode {
}
}
/// Arguments to [`UseColorModeOptions::on_changed`]
#[derive(Clone)]
pub struct UseColorModeOnChangeArgs {
/// The color mode to change to.
pub mode: ColorMode,
/// The default handler that would have been called if the `on_changed` handler had not been specified.
pub default_handler: Box<dyn CloneableFnWithArg<ColorMode>>,
}
#[derive(DefaultBuilder)]
pub struct UseColorModeOptions<El, T>
where
@ -367,7 +354,10 @@ where
/// Custom handler that is called on updates.
/// If specified this will override the default behavior.
/// To get the default behaviour back you can call the provided `default_handler` function.
on_changed: Box<dyn CloneableFnWithArg<UseColorModeOnChangeArgs>>,
/// It takes two parameters:
/// - `mode: ColorMode`: The color mode to change to.
/// -`default_handler: Rc<dyn Fn(ColorMode)>`: The default handler that would have been called if the `on_changed` handler had not been specified.
on_changed: OnChangedFn,
/// When provided, `useStorage` will be skipped.
/// Storage requires the *create feature* **`storage`** to be enabled.
@ -411,6 +401,8 @@ where
_marker: PhantomData<T>,
}
type OnChangedFn = Rc<dyn Fn(ColorMode, Rc<dyn Fn(ColorMode)>)>;
impl Default for UseColorModeOptions<&'static str, web_sys::Element> {
fn default() -> Self {
Self {
@ -418,9 +410,7 @@ impl Default for UseColorModeOptions<&'static str, web_sys::Element> {
attribute: "class".into(),
initial_value: ColorMode::Auto.into(),
custom_modes: vec![],
on_changed: Box::new(move |args: UseColorModeOnChangeArgs| {
(args.default_handler)(args.mode)
}),
on_changed: Rc::new(move |mode, default_handler| (default_handler)(mode)),
storage_signal: None,
storage_key: "leptos-use-color-scheme".into(),
storage: StorageType::default(),

View file

@ -77,7 +77,7 @@ pub fn use_debounce_fn<F, R>(
ms: impl Into<MaybeSignal<f64>> + 'static,
) -> impl Fn() -> Rc<RefCell<Option<R>>> + Clone
where
F: FnOnce() -> R + Clone + 'static,
F: Fn() -> R + Clone + 'static,
R: 'static,
{
use_debounce_fn_with_options(func, ms, DebounceOptions::default())
@ -90,10 +90,10 @@ pub fn use_debounce_fn_with_options<F, R>(
options: DebounceOptions,
) -> impl Fn() -> Rc<RefCell<Option<R>>> + Clone
where
F: FnOnce() -> R + Clone + 'static,
F: Fn() -> R + Clone + 'static,
R: 'static,
{
create_filter_wrapper(Box::new(debounce_filter(ms, options)), func)
create_filter_wrapper(Rc::new(debounce_filter(ms, options)), func)
}
/// Version of [`use_debounce_fn`] with an argument for the debounced function. See the docs for [`use_debounce_fn`] for how to use.
@ -102,7 +102,7 @@ pub fn use_debounce_fn_with_arg<F, Arg, R>(
ms: impl Into<MaybeSignal<f64>> + 'static,
) -> impl Fn(Arg) -> Rc<RefCell<Option<R>>> + Clone
where
F: FnOnce(Arg) -> R + Clone + 'static,
F: Fn(Arg) -> R + Clone + 'static,
Arg: Clone + 'static,
R: 'static,
{
@ -116,9 +116,9 @@ pub fn use_debounce_fn_with_arg_and_options<F, Arg, R>(
options: DebounceOptions,
) -> impl Fn(Arg) -> Rc<RefCell<Option<R>>> + Clone
where
F: FnOnce(Arg) -> R + Clone + 'static,
F: Fn(Arg) -> R + Clone + 'static,
Arg: Clone + 'static,
R: 'static,
{
create_filter_wrapper_with_arg(Box::new(debounce_filter(ms, options)), func)
create_filter_wrapper_with_arg(Rc::new(debounce_filter(ms, options)), func)
}

View file

@ -1,10 +1,10 @@
use crate::core::{ElementMaybeSignal, MaybeRwSignal, PointerType, Position};
use crate::use_event_listener_with_options;
use crate::utils::{CloneableFnMutWithArg, CloneableFnWithArgAndReturn};
use default_struct_builder::DefaultBuilder;
use leptos::ev::{pointerdown, pointermove, pointerup};
use leptos::*;
use std::marker::PhantomData;
use std::rc::Rc;
use wasm_bindgen::JsCast;
use web_sys::PointerEvent;
@ -134,7 +134,7 @@ where
x: event.client_x() as f64 - rect.left(),
y: event.client_y() as f64 - rect.top(),
};
if !on_start.clone()(UseDraggableCallbackArgs {
if !on_start(UseDraggableCallbackArgs {
position,
event: event.clone(),
}) {
@ -159,7 +159,7 @@ where
y: event.client_y() as f64 - start_position.y,
};
set_position.set(position);
on_move.clone()(UseDraggableCallbackArgs {
on_move(UseDraggableCallbackArgs {
position,
event: event.clone(),
});
@ -176,7 +176,7 @@ where
return;
}
set_start_position.set(None);
on_end.clone()(UseDraggableCallbackArgs {
on_end(UseDraggableCallbackArgs {
position: position.get_untracked(),
event: event.clone(),
});
@ -253,13 +253,13 @@ where
initial_value: MaybeRwSignal<Position>,
/// Callback when the dragging starts. Return `false` to prevent dragging.
on_start: Box<dyn CloneableFnWithArgAndReturn<UseDraggableCallbackArgs, bool>>,
on_start: Rc<dyn Fn(UseDraggableCallbackArgs) -> bool>,
/// Callback during dragging.
on_move: Box<dyn CloneableFnMutWithArg<UseDraggableCallbackArgs>>,
on_move: Rc<dyn Fn(UseDraggableCallbackArgs)>,
/// Callback when dragging end.
on_end: Box<dyn CloneableFnMutWithArg<UseDraggableCallbackArgs>>,
on_end: Rc<dyn Fn(UseDraggableCallbackArgs)>,
#[builder(skip)]
_marker1: PhantomData<DragT>,
@ -284,9 +284,9 @@ impl Default
handle: None,
pointer_types: vec![PointerType::Mouse, PointerType::Touch, PointerType::Pen],
initial_value: MaybeRwSignal::default(),
on_start: Box::new(|_| true),
on_move: Box::new(|_| {}),
on_end: Box::new(|_| {}),
on_start: Rc::new(|_| true),
on_move: Rc::new(|_| {}),
on_end: Rc::new(|_| {}),
_marker1: PhantomData,
_marker2: PhantomData,
}

View file

@ -1,9 +1,10 @@
use crate::core::ElementMaybeSignal;
use crate::use_event_listener;
use crate::utils::CloneableFnMutWithArg;
use default_struct_builder::DefaultBuilder;
use leptos::ev::{dragenter, dragleave, dragover, drop};
use leptos::*;
use std::fmt::{Debug, Formatter};
use std::rc::Rc;
/// Create a zone where files can be dropped.
///
@ -65,10 +66,10 @@ where
T: Into<web_sys::EventTarget> + Clone + 'static,
{
let UseDropZoneOptions {
mut on_drop,
mut on_enter,
mut on_leave,
mut on_over,
on_drop,
on_enter,
on_leave,
on_over,
} = options;
let (is_over_drop_zone, set_over_drop_zone) = create_signal(false);
@ -147,16 +148,33 @@ where
}
/// Options for [`use_drop_zone_with_options`].
#[derive(DefaultBuilder, Default, Clone, Debug)]
#[derive(DefaultBuilder, Clone)]
pub struct UseDropZoneOptions {
/// Event handler for the [`drop`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/drop_event) event
on_drop: Box<dyn CloneableFnMutWithArg<UseDropZoneEvent>>,
on_drop: Rc<dyn Fn(UseDropZoneEvent)>,
/// Event handler for the [`dragenter`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dragenter_event) event
on_enter: Box<dyn CloneableFnMutWithArg<UseDropZoneEvent>>,
on_enter: Rc<dyn Fn(UseDropZoneEvent)>,
/// Event handler for the [`dragleave`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dragleave_event) event
on_leave: Box<dyn CloneableFnMutWithArg<UseDropZoneEvent>>,
on_leave: Rc<dyn Fn(UseDropZoneEvent)>,
/// Event handler for the [`dragover`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dragover_event) event
on_over: Box<dyn CloneableFnMutWithArg<UseDropZoneEvent>>,
on_over: Rc<dyn Fn(UseDropZoneEvent)>,
}
impl Default for UseDropZoneOptions {
fn default() -> Self {
Self {
on_drop: Rc::new(|_| {}),
on_enter: Rc::new(|_| {}),
on_leave: Rc::new(|_| {}),
on_over: Rc::new(|_| {}),
}
}
}
impl Debug for UseDropZoneOptions {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "UseDropZoneOptions")
}
}
/// Event passed as argument to the event handler functions of `UseDropZoneOptions`.

View file

@ -1,6 +1,7 @@
use crate::utils::{CloneableFnWithArg, Pausable};
use crate::utils::Pausable;
use crate::{use_interval_fn_with_options, UseIntervalFnOptions};
use default_struct_builder::DefaultBuilder;
use std::rc::Rc;
use leptos::*;
@ -61,7 +62,7 @@ where
let cb = move || {
update();
callback.clone()(counter.get());
callback(counter.get());
};
let Pausable {
@ -93,14 +94,14 @@ pub struct UseIntervalOptions {
immediate: bool,
/// Callback on every interval.
callback: Box<dyn CloneableFnWithArg<u64>>,
callback: Rc<dyn Fn(u64)>,
}
impl Default for UseIntervalOptions {
fn default() -> Self {
Self {
immediate: true,
callback: Box::new(|_: u64| {}),
callback: Rc::new(|_: u64| {}),
}
}
}

View file

@ -1,7 +1,6 @@
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports, dead_code))]
use crate::use_event_listener;
use crate::utils::CloneableFnMutWithArg;
use cfg_if::cfg_if;
use leptos::ev::change;
use leptos::*;
@ -48,8 +47,7 @@ pub fn use_media_query(query: impl Into<MaybeSignal<String>>) -> Signal<bool> {
let media_query: Rc<RefCell<Option<web_sys::MediaQueryList>>> = Rc::new(RefCell::new(None));
let remove_listener: RemoveListener = Rc::new(RefCell::new(None));
let listener: Rc<RefCell<Box<dyn CloneableFnMutWithArg<web_sys::Event>>>> =
Rc::new(RefCell::new(Box::new(|_| {})));
let listener = Rc::new(RefCell::new(Rc::new(|_| {}) as Rc<dyn Fn(web_sys::Event)>));
let cleanup = {
let remove_listener = Rc::clone(&remove_listener);
@ -65,7 +63,7 @@ pub fn use_media_query(query: impl Into<MaybeSignal<String>>) -> Signal<bool> {
let cleanup = cleanup.clone();
let listener = Rc::clone(&listener);
move || {
Rc::new(move || {
cleanup();
let mut media_query = media_query.borrow_mut();
@ -74,21 +72,22 @@ pub fn use_media_query(query: impl Into<MaybeSignal<String>>) -> Signal<bool> {
if let Some(media_query) = media_query.as_ref() {
set_matches.set(media_query.matches());
let listener = Rc::clone(&*listener.borrow());
remove_listener.replace(Some(Box::new(use_event_listener(
media_query.clone(),
change,
listener.borrow().clone(),
move |e| listener(e),
))));
} else {
set_matches.set(false);
}
}
})
};
{
let update = update.clone();
listener
.replace(Box::new(move |_| update()) as Box<dyn CloneableFnMutWithArg<web_sys::Event>>);
let update = Rc::clone(&update);
listener.replace(Rc::new(move |_| update()) as Rc<dyn Fn(web_sys::Event)>);
}
create_effect(move |_| update());

View file

@ -1,11 +1,11 @@
use crate::core::ElementMaybeSignal;
use crate::use_event_listener::use_event_listener_with_options;
use crate::utils::CloneableFnWithArg;
use crate::{use_debounce_fn_with_arg, use_throttle_fn_with_arg_and_options, ThrottleOptions};
use default_struct_builder::DefaultBuilder;
use leptos::ev::EventDescriptor;
use leptos::*;
use std::borrow::Cow;
use std::rc::Rc;
use wasm_bindgen::JsCast;
/// Reactive scroll position and state.
@ -232,7 +232,7 @@ where
});
let on_scroll_end = {
let on_stop = options.on_stop.clone();
let on_stop = Rc::clone(&options.on_stop);
move |e| {
if !is_scrolling.get_untracked() {
@ -328,7 +328,7 @@ where
};
let on_scroll_handler = {
let on_scroll = options.on_scroll.clone();
let on_scroll = Rc::clone(&options.on_scroll);
move |e: web_sys::Event| {
let target: web_sys::Element = event_target(&e);
@ -441,10 +441,10 @@ pub struct UseScrollOptions {
offset: ScrollOffset,
/// Callback when scrolling is happening.
on_scroll: Box<dyn CloneableFnWithArg<web_sys::Event>>,
on_scroll: Rc<dyn Fn(web_sys::Event)>,
/// Callback when scrolling stops (after `idle` + `throttle` milliseconds have passed).
on_stop: Box<dyn CloneableFnWithArg<web_sys::Event>>,
on_stop: Rc<dyn Fn(web_sys::Event)>,
/// Options passed to the `addEventListener("scroll", ...)` call
event_listener_options: web_sys::AddEventListenerOptions,
@ -461,8 +461,8 @@ impl Default for UseScrollOptions {
throttle: 0.0,
idle: 200.0,
offset: ScrollOffset::default(),
on_scroll: Box::new(|_| {}),
on_stop: Box::new(|_| {}),
on_scroll: Rc::new(|_| {}),
on_stop: Rc::new(|_| {}),
event_listener_options: Default::default(),
behavior: Default::default(),
}

View file

@ -73,7 +73,7 @@ pub fn use_throttle_fn<F, R>(
ms: impl Into<MaybeSignal<f64>> + 'static,
) -> impl Fn() -> Rc<RefCell<Option<R>>> + Clone
where
F: FnOnce() -> R + Clone + 'static,
F: Fn() -> R + Clone + 'static,
R: 'static,
{
use_throttle_fn_with_options(func, ms, Default::default())
@ -86,10 +86,10 @@ pub fn use_throttle_fn_with_options<F, R>(
options: ThrottleOptions,
) -> impl Fn() -> Rc<RefCell<Option<R>>> + Clone
where
F: FnOnce() -> R + Clone + 'static,
F: Fn() -> R + Clone + 'static,
R: 'static,
{
create_filter_wrapper(Box::new(throttle_filter(ms, options)), func)
create_filter_wrapper(Rc::new(throttle_filter(ms, options)), func)
}
/// Version of [`use_throttle_fn`] with an argument for the throttled function. See the docs for [`use_throttle_fn`] for how to use.
@ -98,7 +98,7 @@ pub fn use_throttle_fn_with_arg<F, Arg, R>(
ms: impl Into<MaybeSignal<f64>> + 'static,
) -> impl Fn(Arg) -> Rc<RefCell<Option<R>>> + Clone
where
F: FnOnce(Arg) -> R + Clone + 'static,
F: Fn(Arg) -> R + Clone + 'static,
Arg: Clone + 'static,
R: 'static,
{
@ -112,9 +112,9 @@ pub fn use_throttle_fn_with_arg_and_options<F, Arg, R>(
options: ThrottleOptions,
) -> impl Fn(Arg) -> Rc<RefCell<Option<R>>> + Clone
where
F: FnOnce(Arg) -> R + Clone + 'static,
F: Fn(Arg) -> R + Clone + 'static,
Arg: Clone + 'static,
R: 'static,
{
create_filter_wrapper_with_arg(Box::new(throttle_filter(ms, options)), func)
create_filter_wrapper_with_arg(Rc::new(throttle_filter(ms, options)), func)
}

View file

@ -1,7 +1,6 @@
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports, dead_code))]
use cfg_if::cfg_if;
use core::fmt;
use leptos::{leptos_dom::helpers::TimeoutHandle, *};
use std::rc::Rc;
use std::time::Duration;
@ -12,8 +11,6 @@ use js_sys::Array;
use wasm_bindgen::{prelude::*, JsCast, JsValue};
use web_sys::{BinaryType, CloseEvent, Event, MessageEvent, WebSocket};
use crate::utils::CloneableFnWithArg;
/// Creating and managing a [Websocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) connection.
///
/// ## Demo
@ -102,15 +99,24 @@ pub fn use_websocket_with_options(
> {
let url = url.to_string();
let UseWebSocketOptions {
on_open,
on_message,
on_message_bytes,
on_error,
on_close,
reconnect_limit,
reconnect_interval,
immediate,
protocols,
} = options;
let (ready_state, set_ready_state) = create_signal(ConnectionReadyState::Closed);
let (message, set_message) = create_signal(None);
let (message_bytes, set_message_bytes) = create_signal(None);
let ws_ref: StoredValue<Option<WebSocket>> = store_value(None);
let reconnect_limit = options.reconnect_limit;
let reconnect_timer_ref: StoredValue<Option<TimeoutHandle>> = store_value(None);
let immediate = options.immediate;
let reconnect_times_ref: StoredValue<u64> = store_value(0);
let unmounted_ref = store_value(false);
@ -118,14 +124,6 @@ pub fn use_websocket_with_options(
let connect_ref: StoredValue<Option<Rc<dyn Fn()>>> = store_value(None);
cfg_if! { if #[cfg(not(feature = "ssr"))] {
let on_open_ref = store_value(options.on_open);
let on_message_ref = store_value(options.on_message);
let on_message_bytes_ref = store_value(options.on_message_bytes);
let on_error_ref = store_value(options.on_error);
let on_close_ref = store_value(options.on_close);
let reconnect_interval = options.reconnect_interval;
let protocols = options.protocols;
let reconnect_ref: StoredValue<Option<Rc<dyn Fn()>>> = store_value(None);
reconnect_ref.set_value({
@ -181,13 +179,14 @@ pub fn use_websocket_with_options(
// onopen handler
{
let on_open = Rc::clone(&on_open);
let onopen_closure = Closure::wrap(Box::new(move |e: Event| {
if unmounted_ref.get_value() {
return;
}
let callback = on_open_ref.get_value();
callback(e);
on_open(e);
set_ready_state.set(ConnectionReadyState::Open);
}) as Box<dyn FnMut(Event)>);
@ -198,6 +197,9 @@ pub fn use_websocket_with_options(
// onmessage handler
{
let on_message = Rc::clone(&on_message);
let on_message_bytes = Rc::clone(&on_message_bytes);
let onmessage_closure = Closure::wrap(Box::new(move |e: MessageEvent| {
if unmounted_ref.get_value() {
return;
@ -211,8 +213,7 @@ pub fn use_websocket_with_options(
},
|txt| {
let txt = String::from(&txt);
let callback = on_message_ref.get_value();
callback(txt.clone());
on_message(txt.clone());
set_message.set(Some(txt));
},
@ -221,8 +222,7 @@ pub fn use_websocket_with_options(
|array_buffer| {
let array = js_sys::Uint8Array::new(&array_buffer);
let array = array.to_vec();
let callback = on_message_bytes_ref.get_value();
callback(array.clone());
on_message_bytes(array.clone());
set_message_bytes.set(Some(array));
},
@ -235,6 +235,8 @@ pub fn use_websocket_with_options(
// onerror handler
{
let on_error = Rc::clone(&on_error);
let onerror_closure = Closure::wrap(Box::new(move |e: Event| {
if unmounted_ref.get_value() {
return;
@ -244,8 +246,7 @@ pub fn use_websocket_with_options(
reconnect();
}
let callback = on_error_ref.get_value();
callback(e);
on_error(e);
set_ready_state.set(ConnectionReadyState::Closed);
}) as Box<dyn FnMut(Event)>);
@ -255,6 +256,8 @@ pub fn use_websocket_with_options(
// onclose handler
{
let on_close = Rc::clone(&on_close);
let onclose_closure = Closure::wrap(Box::new(move |e: CloseEvent| {
if unmounted_ref.get_value() {
return;
@ -264,8 +267,7 @@ pub fn use_websocket_with_options(
reconnect();
}
let callback = on_close_ref.get_value();
callback(e);
on_close(e);
set_ready_state.set(ConnectionReadyState::Closed);
})
@ -348,15 +350,15 @@ pub fn use_websocket_with_options(
#[derive(DefaultBuilder)]
pub struct UseWebSocketOptions {
/// `WebSocket` connect callback.
on_open: Box<dyn CloneableFnWithArg<Event>>,
on_open: Rc<dyn Fn(Event)>,
/// `WebSocket` message callback for text.
on_message: Box<dyn CloneableFnWithArg<String>>,
on_message: Rc<dyn Fn(String)>,
/// `WebSocket` message callback for binary.
on_message_bytes: Box<dyn CloneableFnWithArg<Vec<u8>>>,
on_message_bytes: Rc<dyn Fn(Vec<u8>)>,
/// `WebSocket` error callback.
on_error: Box<dyn CloneableFnWithArg<Event>>,
on_error: Rc<dyn Fn(Event)>,
/// `WebSocket` close callback.
on_close: Box<dyn CloneableFnWithArg<CloseEvent>>,
on_close: Rc<dyn Fn(CloseEvent)>,
/// Retry times. Defaults to 3.
reconnect_limit: u64,
/// Retry interval in ms. Defaults to 3000.
@ -372,11 +374,11 @@ pub struct UseWebSocketOptions {
impl Default for UseWebSocketOptions {
fn default() -> Self {
Self {
on_open: Box::new(|_| {}),
on_message: Box::new(|_| {}),
on_message_bytes: Box::new(|_| {}),
on_error: Box::new(|_| {}),
on_close: Box::new(|_| {}),
on_open: Rc::new(|_| {}),
on_message: Rc::new(|_| {}),
on_message_bytes: Rc::new(|_| {}),
on_error: Rc::new(|_| {}),
on_close: Rc::new(|_| {}),
reconnect_limit: 3,
reconnect_interval: 3000,
immediate: true,

View file

@ -1,43 +0,0 @@
mod mut_;
mod mut_with_arg;
mod with_arg;
mod with_arg_and_return;
mod with_return;
pub use mut_::*;
pub use mut_with_arg::*;
use std::fmt::Debug;
pub use with_arg::*;
pub use with_arg_and_return::*;
pub use with_return::*;
pub trait CloneableFn: FnOnce() {
fn clone_box(&self) -> Box<dyn CloneableFn>;
}
impl<F> CloneableFn for F
where
F: FnOnce() + Clone + 'static,
{
fn clone_box(&self) -> Box<dyn CloneableFn> {
Box::new(self.clone())
}
}
impl Clone for Box<dyn CloneableFn> {
fn clone(&self) -> Self {
(**self).clone_box()
}
}
impl Default for Box<dyn CloneableFn> {
fn default() -> Self {
Box::new(|| {})
}
}
impl Debug for Box<dyn CloneableFn> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Box<dyn CloneableFn>",)
}
}

View file

@ -1,32 +0,0 @@
use std::fmt::Debug;
pub trait CloneableFnMut: FnMut() {
fn clone_box(&self) -> Box<dyn CloneableFnMut>;
}
impl<F> CloneableFnMut for F
where
F: FnMut() + Clone + 'static,
{
fn clone_box(&self) -> Box<dyn CloneableFnMut> {
Box::new(self.clone())
}
}
impl Clone for Box<dyn CloneableFnMut> {
fn clone(&self) -> Self {
(**self).clone_box()
}
}
impl Default for Box<dyn CloneableFnMut> {
fn default() -> Self {
Box::new(|| {})
}
}
impl Debug for Box<dyn CloneableFnMut> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Box<dyn CloneableFnMut>",)
}
}

View file

@ -1,36 +0,0 @@
use std::fmt::Debug;
pub trait CloneableFnMutWithArg<Arg>: FnMut(Arg) {
fn clone_box(&self) -> Box<dyn CloneableFnMutWithArg<Arg>>;
}
impl<F, Arg> CloneableFnMutWithArg<Arg> for F
where
F: FnMut(Arg) + Clone + 'static,
{
fn clone_box(&self) -> Box<dyn CloneableFnMutWithArg<Arg>> {
Box::new(self.clone())
}
}
impl<Arg> Clone for Box<dyn CloneableFnMutWithArg<Arg>> {
fn clone(&self) -> Self {
(**self).clone_box()
}
}
impl<Arg> Default for Box<dyn CloneableFnMutWithArg<Arg>> {
fn default() -> Self {
Box::new(|_| {})
}
}
impl<Arg> Debug for Box<dyn CloneableFnMutWithArg<Arg>> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Box<dyn CloneableFnMutWithArg<{}>>",
std::any::type_name::<Arg>()
)
}
}

View file

@ -1,36 +0,0 @@
use std::fmt::Debug;
pub trait CloneableFnWithArg<Arg>: FnOnce(Arg) {
fn clone_box(&self) -> Box<dyn CloneableFnWithArg<Arg>>;
}
impl<F, Arg> CloneableFnWithArg<Arg> for F
where
F: FnOnce(Arg) + Clone + 'static,
{
fn clone_box(&self) -> Box<dyn CloneableFnWithArg<Arg>> {
Box::new(self.clone())
}
}
impl<Arg> Clone for Box<dyn CloneableFnWithArg<Arg>> {
fn clone(&self) -> Self {
(**self).clone_box()
}
}
impl<Arg> Default for Box<dyn CloneableFnWithArg<Arg>> {
fn default() -> Self {
Box::new(|_| {})
}
}
impl<Arg> Debug for Box<dyn CloneableFnWithArg<Arg>> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Box<dyn CloneableFnWithArg<{}>>",
std::any::type_name::<Arg>()
)
}
}

View file

@ -1,38 +0,0 @@
use std::fmt::Debug;
pub trait CloneableFnWithArgAndReturn<Arg, R>: FnOnce(Arg) -> R {
fn clone_box(&self) -> Box<dyn CloneableFnWithArgAndReturn<Arg, R>>;
}
impl<F, R, Arg> CloneableFnWithArgAndReturn<Arg, R> for F
where
F: FnMut(Arg) -> R + Clone + 'static,
R: 'static,
{
fn clone_box(&self) -> Box<dyn CloneableFnWithArgAndReturn<Arg, R>> {
Box::new(self.clone())
}
}
impl<R, Arg> Clone for Box<dyn CloneableFnWithArgAndReturn<Arg, R>> {
fn clone(&self) -> Self {
(**self).clone_box()
}
}
impl<R: Default + 'static, Arg> Default for Box<dyn CloneableFnWithArgAndReturn<Arg, R>> {
fn default() -> Self {
Box::new(|_| Default::default())
}
}
impl<R, Arg> Debug for Box<dyn CloneableFnWithArgAndReturn<Arg, R>> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Box<dyn CloneableFnWithArgAndReturn<{}, {}>>",
std::any::type_name::<Arg>(),
std::any::type_name::<R>()
)
}
}

View file

@ -1,37 +0,0 @@
use std::fmt::Debug;
pub trait CloneableFnWithReturn<R>: FnOnce() -> R {
fn clone_box(&self) -> Box<dyn CloneableFnWithReturn<R>>;
}
impl<F, R> CloneableFnWithReturn<R> for F
where
F: FnOnce() -> R + Clone + 'static,
R: 'static,
{
fn clone_box(&self) -> Box<dyn CloneableFnWithReturn<R>> {
Box::new(self.clone())
}
}
impl<R> Clone for Box<dyn CloneableFnWithReturn<R>> {
fn clone(&self) -> Self {
(**self).clone_box()
}
}
impl<R: Default + 'static> Default for Box<dyn CloneableFnWithReturn<R>> {
fn default() -> Self {
Box::new(|| Default::default())
}
}
impl<R> Debug for Box<dyn CloneableFnWithReturn<R>> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Box<dyn CloneableFnWithReturn<{}>>",
std::any::type_name::<R>()
)
}
}

View file

@ -1,6 +1,5 @@
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
use crate::utils::CloneableFnWithReturn;
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder;
use leptos::leptos_dom::helpers::TimeoutHandle;
@ -20,7 +19,7 @@ pub struct DebounceOptions {
pub fn debounce_filter<R>(
ms: impl Into<MaybeSignal<f64>>,
options: DebounceOptions,
) -> impl Fn(Box<dyn CloneableFnWithReturn<R>>) -> Rc<RefCell<Option<R>>> + Clone
) -> impl Fn(Rc<dyn Fn() -> R>) -> Rc<RefCell<Option<R>>> + Clone
where
R: 'static,
{
@ -38,7 +37,7 @@ where
let ms = ms.into();
let max_wait_signal = options.max_wait;
move |_invoke: Box<dyn CloneableFnWithReturn<R>>| {
move |_invoke: Rc<dyn Fn() -> R>| {
let duration = ms.get_untracked();
let max_duration = max_wait_signal.get_untracked();

View file

@ -4,40 +4,39 @@ mod throttle;
pub use debounce::*;
pub use throttle::*;
use crate::utils::{CloneableFnWithArgAndReturn, CloneableFnWithReturn};
use leptos::MaybeSignal;
use std::cell::RefCell;
use std::rc::Rc;
macro_rules! BoxFilterFn {
macro_rules! RcFilterFn {
($R:ident) => {
Box<dyn CloneableFnWithArgAndReturn<Box<dyn CloneableFnWithReturn<$R>>, Rc<RefCell<Option<$R>>>>>
Rc<dyn Fn(Rc<dyn Fn() -> $R>) -> Rc<RefCell<Option<$R>>>>
}
}
pub fn create_filter_wrapper<F, R>(
filter: BoxFilterFn!(R),
filter: RcFilterFn!(R),
func: F,
) -> impl Fn() -> Rc<RefCell<Option<R>>> + Clone
where
F: FnOnce() -> R + Clone + 'static,
F: Fn() -> R + Clone + 'static,
R: 'static,
{
move || filter.clone()(Box::new(func.clone()))
move || Rc::clone(&filter)(Rc::new(func.clone()))
}
pub fn create_filter_wrapper_with_arg<F, Arg, R>(
filter: BoxFilterFn!(R),
filter: RcFilterFn!(R),
func: F,
) -> impl Fn(Arg) -> Rc<RefCell<Option<R>>> + Clone
where
F: FnOnce(Arg) -> R + Clone + 'static,
F: Fn(Arg) -> R + Clone + 'static,
R: 'static,
Arg: Clone + 'static,
{
move |arg: Arg| {
let func = func.clone();
filter.clone()(Box::new(move || func(arg)))
Rc::clone(&filter)(Rc::new(move || func(arg.clone())))
}
}
@ -57,16 +56,16 @@ pub enum FilterOptions {
}
impl FilterOptions {
pub fn filter_fn<R>(&self) -> BoxFilterFn!(R)
pub fn filter_fn<R>(&self) -> RcFilterFn!(R)
where
R: 'static,
{
match self {
FilterOptions::Debounce { ms, options } => Box::new(debounce_filter(*ms, *options)),
FilterOptions::Throttle { ms, options } => Box::new(throttle_filter(*ms, *options)),
FilterOptions::None => Box::new(|invoke: Box<dyn CloneableFnWithReturn<R>>| {
Rc::new(RefCell::new(Some(invoke())))
}),
FilterOptions::Debounce { ms, options } => Rc::new(debounce_filter(*ms, *options)),
FilterOptions::Throttle { ms, options } => Rc::new(throttle_filter(*ms, *options)),
FilterOptions::None => {
Rc::new(|invoke: Rc<dyn Fn() -> R>| Rc::new(RefCell::new(Some(invoke()))))
}
}
}
}

View file

@ -1,6 +1,5 @@
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
use crate::utils::CloneableFnWithReturn;
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder;
use js_sys::Date;
@ -31,7 +30,7 @@ impl Default for ThrottleOptions {
pub fn throttle_filter<R>(
ms: impl Into<MaybeSignal<f64>>,
options: ThrottleOptions,
) -> impl Fn(Box<dyn CloneableFnWithReturn<R>>) -> Rc<RefCell<Option<R>>> + Clone
) -> impl Fn(Rc<dyn Fn() -> R>) -> Rc<RefCell<Option<R>>> + Clone
where
R: 'static,
{
@ -50,7 +49,7 @@ where
let ms = ms.into();
move |mut _invoke: Box<dyn CloneableFnWithReturn<R>>| {
move |mut _invoke: Rc<dyn Fn() -> R>| {
let duration = ms.get_untracked();
let elapsed = Date::now() - last_exec.get();

View file

@ -1,4 +1,3 @@
mod clonable_fn;
mod filters;
mod is;
mod js_value_from_to_string;
@ -6,7 +5,6 @@ mod pausable;
mod signal_filtered;
mod use_derive_signal;
pub use clonable_fn::*;
pub use filters::*;
pub use is::*;
pub(crate) use js_value_from_to_string::*;