Merge pull request #133 from CorvusPrudens/leptos-0.7-alpha

Progress towards Leptos 0.7.0-alpha
This commit is contained in:
Marc-Stefan Cassola 2024-07-22 20:13:05 +01:00 committed by GitHub
commit 3fdd941f47
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 343 additions and 429 deletions

View file

@ -26,14 +26,15 @@ http1 = { version = "1", optional = true, package = "http" }
http0_2 = { version = "0.2", optional = true, package = "http" }
js-sys = "0.3"
lazy_static = "1"
leptos = { version = "0.7.0-preview2", path = "../leptos/leptos" }
leptos_axum = { version = "0.7.0-preview2", optional = true }
leptos = { version = "0.7.0-alpha" }
leptos_axum = { version = "0.7.0-alpha", optional = true }
leptos_actix = { version = "0.6", optional = true }
leptos-spin = { version = "0.2", optional = true }
num = { version = "0.4", optional = true }
paste = "1"
prost = { version = "0.12", optional = true }
rmp-serde = { version = "1.1", optional = true }
send_wrapper = "0.6.0"
serde = { version = "1", optional = true }
serde_json = { version = "1", optional = true }
thiserror = "1"
@ -133,7 +134,7 @@ features = [
[dev-dependencies]
getrandom = { version = "0.2", features = ["js"] }
leptos_meta = "0.7.0-preview2"
leptos_meta = "0.7.0-alpha"
rand = "0.8"
[features]

View file

@ -44,30 +44,16 @@ where
}
}
impl<T, E> SignalGet for ElementMaybeSignal<T, E>
impl<T, E> DefinedAt for ElementMaybeSignal<T, E>
where
T: Into<E> + Clone + 'static,
{
type Value = Option<T>;
fn get(&self) -> Option<T> {
match self {
Self::Static(t) => t.clone(),
Self::Dynamic(s) => s.get(),
_ => unreachable!(),
}
}
fn try_get(&self) -> Option<Option<T>> {
match self {
Self::Static(t) => Some(t.clone()),
Self::Dynamic(s) => s.try_get(),
_ => unreachable!(),
}
fn defined_at(&self) -> Option<&'static std::panic::Location<'static>> {
None
}
}
impl<T, E> SignalWith for ElementMaybeSignal<T, E>
impl<T, E> With for ElementMaybeSignal<T, E>
where
T: Into<E> + Clone + 'static,
{
@ -90,7 +76,7 @@ where
}
}
impl<T, E> SignalWithUntracked for ElementMaybeSignal<T, E>
impl<T, E> WithUntracked for ElementMaybeSignal<T, E>
where
T: Into<E> + Clone + 'static,
{
@ -113,29 +99,6 @@ where
}
}
impl<T, E> SignalGetUntracked for ElementMaybeSignal<T, E>
where
T: Into<E> + Clone + 'static,
{
type Value = Option<T>;
fn get_untracked(&self) -> Option<T> {
match self {
Self::Static(t) => t.clone(),
Self::Dynamic(s) => s.get_untracked(),
_ => unreachable!(),
}
}
fn try_get_untracked(&self) -> Option<Option<T>> {
match self {
Self::Static(t) => Some(t.clone()),
Self::Dynamic(s) => s.try_get_untracked(),
_ => unreachable!(),
}
}
}
// From static element //////////////////////////////////////////////////////////////
impl<T, E> From<T> for ElementMaybeSignal<T, E>

View file

@ -45,30 +45,16 @@ where
}
}
impl<T, E> SignalGet for ElementsMaybeSignal<T, E>
impl<T, E> DefinedAt for ElementsMaybeSignal<T, E>
where
T: Into<E> + Clone + 'static,
{
type Value = Vec<Option<T>>;
fn get(&self) -> Vec<Option<T>> {
match self {
Self::Static(v) => v.clone(),
Self::Dynamic(s) => s.get(),
Self::_Phantom(_) => unreachable!(),
}
}
fn try_get(&self) -> Option<Vec<Option<T>>> {
match self {
Self::Static(v) => Some(v.clone()),
Self::Dynamic(s) => s.try_get(),
Self::_Phantom(_) => unreachable!(),
}
fn defined_at(&self) -> Option<&'static std::panic::Location<'static>> {
None
}
}
impl<T, E> SignalWith for ElementsMaybeSignal<T, E>
impl<T, E> With for ElementsMaybeSignal<T, E>
where
T: Into<E> + Clone + 'static,
{
@ -91,7 +77,7 @@ where
}
}
impl<T, E> SignalWithUntracked for ElementsMaybeSignal<T, E>
impl<T, E> WithUntracked for ElementsMaybeSignal<T, E>
where
T: Into<E> + Clone + 'static,
{
@ -114,29 +100,6 @@ where
}
}
impl<T, E> SignalGetUntracked for ElementsMaybeSignal<T, E>
where
T: Into<E> + Clone + 'static,
{
type Value = Vec<Option<T>>;
fn get_untracked(&self) -> Vec<Option<T>> {
match self {
Self::Static(t) => t.clone(),
Self::Dynamic(s) => s.get_untracked(),
Self::_Phantom(_) => unreachable!(),
}
}
fn try_get_untracked(&self) -> Option<Vec<Option<T>>> {
match self {
Self::Static(t) => Some(t.clone()),
Self::Dynamic(s) => s.try_get_untracked(),
Self::_Phantom(_) => unreachable!(),
}
}
}
// From single static element //////////////////////////////////////////////////////////////
impl<T, E> From<T> for ElementsMaybeSignal<T, E>

View file

@ -93,7 +93,7 @@ impl<T: Clone> MaybeRwSignal<T> {
Self::DynamicRead(s) => {
let (r, w) = signal(s.get_untracked());
create_effect(move |_| {
Effect::new(move |_| {
w.update(move |w| {
*w = s.get();
});

View file

@ -37,49 +37,17 @@ impl<T> Clone for UseRwSignal<T> {
impl<T> Copy for UseRwSignal<T> {}
impl<T> SignalGet for UseRwSignal<T>
where
T: Clone,
{
type Value = T;
fn get(&self) -> T {
impl<T> DefinedAt for UseRwSignal<T> {
fn defined_at(&self) -> Option<&'static std::panic::Location<'static>> {
match self {
Self::Separate(s, _) => s.get(),
Self::Combined(s) => s.get(),
}
}
fn try_get(&self) -> Option<T> {
match self {
Self::Separate(s, _) => s.try_get(),
Self::Combined(s) => s.try_get(),
Self::Combined(s) => s.defined_at(),
// NOTE: is this sufficient communication?
Self::Separate(_, s) => s.defined_at(),
}
}
}
impl<T> SignalGetUntracked for UseRwSignal<T>
where
T: Clone,
{
type Value = T;
fn get_untracked(&self) -> T {
match self {
Self::Separate(s, _) => s.get_untracked(),
Self::Combined(s) => s.get_untracked(),
}
}
fn try_get_untracked(&self) -> Option<T> {
match self {
Self::Separate(s, _) => s.try_get_untracked(),
Self::Combined(s) => s.try_get_untracked(),
}
}
}
impl<T> SignalWith for UseRwSignal<T> {
impl<T> With for UseRwSignal<T> {
type Value = T;
fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
@ -97,7 +65,7 @@ impl<T> SignalWith for UseRwSignal<T> {
}
}
impl<T> SignalWithUntracked for UseRwSignal<T> {
impl<T> WithUntracked for UseRwSignal<T> {
type Value = T;
fn with_untracked<R>(&self, f: impl FnOnce(&T) -> R) -> R {
@ -115,7 +83,7 @@ impl<T> SignalWithUntracked for UseRwSignal<T> {
}
}
impl<T> SignalSet for UseRwSignal<T> {
impl<T> Set for UseRwSignal<T> {
type Value = T;
fn set(&self, new_value: T) {
@ -133,23 +101,7 @@ impl<T> SignalSet for UseRwSignal<T> {
}
}
impl<T> SignalSetUntracked<T> for UseRwSignal<T> {
fn set_untracked(&self, new_value: T) {
match self {
Self::Separate(_, s) => s.set_untracked(new_value),
Self::Combined(s) => s.set_untracked(new_value),
}
}
fn try_set_untracked(&self, new_value: T) -> Option<T> {
match self {
Self::Separate(_, s) => s.try_set_untracked(new_value),
Self::Combined(s) => s.try_set_untracked(new_value),
}
}
}
impl<T> SignalUpdate for UseRwSignal<T> {
impl<T> Update for UseRwSignal<T> {
type Value = T;
fn update(&self, f: impl FnOnce(&mut T)) {
@ -165,4 +117,18 @@ impl<T> SignalUpdate for UseRwSignal<T> {
Self::Combined(s) => s.try_update(f),
}
}
fn maybe_update(&self, fun: impl FnOnce(&mut Self::Value) -> bool) {
match self {
Self::Separate(_, s) => s.maybe_update(fun),
Self::Combined(s) => s.maybe_update(fun),
}
}
fn try_maybe_update<U>(&self, fun: impl FnOnce(&mut Self::Value) -> (bool, U)) -> Option<U> {
match self {
Self::Separate(_, s) => s.try_maybe_update(fun),
Self::Combined(s) => s.try_maybe_update(fun),
}
}
}

View file

@ -1,7 +1,6 @@
use crate::core::{ElementMaybeSignal, ElementsMaybeSignal};
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder;
use leptos::prelude::diagnostics::SpecialNonReactiveZone;
cfg_if! { if #[cfg(not(feature = "ssr"))] {
use leptos::prelude::*;
@ -115,6 +114,7 @@ where
#[cfg(not(feature = "ssr"))]
{
use leptos::prelude::diagnostics::SpecialNonReactiveZone;
let OnClickOutsideOptions {
ignore,
capture,

View file

@ -15,7 +15,7 @@ pub fn use_local_storage<T, C>(
key: impl AsRef<str>,
) -> (Signal<T>, WriteSignal<T>, impl Fn() + Clone)
where
T: Clone + Default + PartialEq,
T: Clone + Default + PartialEq + Send + Sync,
C: StringCodec<T> + Default,
{
use_storage_with_options(
@ -31,7 +31,7 @@ pub fn use_local_storage_with_options<T, C>(
options: UseStorageOptions<T, C>,
) -> (Signal<T>, WriteSignal<T>, impl Fn() + Clone)
where
T: Clone + PartialEq,
T: Clone + PartialEq + Send + Sync,
C: StringCodec<T> + Default,
{
use_storage_with_options(StorageType::Local, key, options)

View file

@ -15,7 +15,7 @@ pub fn use_session_storage<T, C>(
key: impl AsRef<str>,
) -> (Signal<T>, WriteSignal<T>, impl Fn() + Clone)
where
T: Clone + Default + PartialEq,
T: Clone + Default + PartialEq + Send + Sync,
C: StringCodec<T> + Default,
{
use_storage_with_options(
@ -31,7 +31,7 @@ pub fn use_session_storage_with_options<T, C>(
options: UseStorageOptions<T, C>,
) -> (Signal<T>, WriteSignal<T>, impl Fn() + Clone)
where
T: Clone + PartialEq,
T: Clone + PartialEq + Send + Sync,
C: StringCodec<T> + Default,
{
use_storage_with_options(StorageType::Session, key, options)

View file

@ -5,7 +5,7 @@ use crate::{
use cfg_if::cfg_if;
use leptos::prelude::wrappers::read::Signal;
use leptos::prelude::*;
use std::rc::Rc;
use std::sync::Arc;
use thiserror::Error;
use wasm_bindgen::JsValue;
@ -93,7 +93,7 @@ pub fn use_storage<T, C>(
key: impl AsRef<str>,
) -> (Signal<T>, WriteSignal<T>, impl Fn() + Clone)
where
T: Default + Clone + PartialEq,
T: Default + Clone + PartialEq + Send + Sync,
C: StringCodec<T> + Default,
{
use_storage_with_options::<T, C>(storage_type, key, UseStorageOptions::default())
@ -106,7 +106,7 @@ pub fn use_storage_with_options<T, C>(
options: UseStorageOptions<T, C>,
) -> (Signal<T>, WriteSignal<T>, impl Fn() + Clone)
where
T: Clone + PartialEq,
T: Clone + PartialEq + Send + Sync,
C: StringCodec<T> + Default,
{
let UseStorageOptions {
@ -216,10 +216,10 @@ where
fetch_from_storage();
// Fires when storage needs to be fetched
let notify = create_trigger();
let notify = Trigger::new();
// Refetch from storage. Keeps track of how many times we've been notified. Does not increment for calls to set_data
let notify_id = create_memo::<usize>(move |prev| {
let notify_id = Memo::<usize>::new(move |prev| {
notify.track();
match prev {
None => 1, // Avoid async fetch of initial value
@ -334,7 +334,7 @@ pub struct UseStorageOptions<T: 'static, C: StringCodec<T>> {
// Translates to and from UTF-16 strings
codec: C,
// Callback for when an error occurs
on_error: Rc<dyn Fn(UseStorageError<C::Error>)>,
on_error: Arc<dyn Fn(UseStorageError<C::Error>)>,
// Whether to continuously listen to changes from browser storage
listen_to_storage_changes: bool,
// Initial value to use when the storage key is not set
@ -346,7 +346,7 @@ pub struct UseStorageOptions<T: 'static, C: StringCodec<T>> {
/// Calls the on_error callback with the given error. Removes the error from the Result to avoid double error handling.
#[cfg(not(feature = "ssr"))]
fn handle_error<T, Err>(
on_error: &Rc<dyn Fn(UseStorageError<Err>)>,
on_error: &Arc<dyn Fn(UseStorageError<Err>)>,
result: Result<T, UseStorageError<Err>>,
) -> Result<T, ()> {
result.map_err(|err| (on_error)(err))
@ -356,7 +356,7 @@ impl<T: Default, C: StringCodec<T> + Default> Default for UseStorageOptions<T, C
fn default() -> Self {
Self {
codec: C::default(),
on_error: Rc::new(|_err| ()),
on_error: Arc::new(|_err| ()),
listen_to_storage_changes: true,
initial_value: MaybeRwSignal::default(),
filter: FilterOptions::default(),
@ -376,7 +376,7 @@ impl<T: Default, C: StringCodec<T>> UseStorageOptions<T, C> {
/// Optional callback whenever an error occurs.
pub fn on_error(self, on_error: impl Fn(UseStorageError<C::Error>) + 'static) -> Self {
Self {
on_error: Rc::new(on_error),
on_error: Arc::new(on_error),
..self
}
}

View file

@ -283,7 +283,7 @@ where
on_changed(mode, Rc::new(default_on_changed.clone()));
};
create_effect({
Effect::new({
let on_changed = on_changed.clone();
move |_| {

View file

@ -628,7 +628,7 @@ fn read_cookies_string(
let _ = ssr_cookies_header_getter;
let js_value: wasm_bindgen::JsValue = leptos::document().into();
let js_value: wasm_bindgen::JsValue = document().into();
let document: web_sys::HtmlDocument = js_value.unchecked_into();
cookies = Some(document.cookie().unwrap_or_default());
}

View file

@ -35,13 +35,13 @@ pub fn use_cycle_list<T, L>(
list: L,
) -> UseCycleListReturn<
T,
impl Fn(usize) -> T + Clone,
impl Fn(usize) -> T + Clone + Send + Sync,
impl Fn() + Clone,
impl Fn() + Clone,
impl Fn(i64) -> T + Clone,
>
where
T: Clone + PartialEq + 'static,
T: Clone + PartialEq + Send + Sync + 'static,
L: Into<MaybeSignal<Vec<T>>>,
{
use_cycle_list_with_options(list, UseCycleListOptions::default())
@ -58,7 +58,7 @@ pub fn use_cycle_list_with_options<T, L>(
impl Fn(i64) -> T + Clone,
>
where
T: Clone + PartialEq + 'static,
T: Clone + PartialEq + Send + Sync + 'static,
L: Into<MaybeSignal<Vec<T>>>,
{
let UseCycleListOptions {

View file

@ -1,8 +1,7 @@
pub use crate::utils::DebounceOptions;
use crate::utils::{create_filter_wrapper, create_filter_wrapper_with_arg, debounce_filter};
use leptos::MaybeSignal;
use std::cell::RefCell;
use std::rc::Rc;
use leptos::prelude::MaybeSignal;
use std::sync::{Arc, Mutex};
/// Debounce execution of a function.
///
@ -77,7 +76,7 @@ use std::rc::Rc;
pub fn use_debounce_fn<F, R>(
func: F,
ms: impl Into<MaybeSignal<f64>> + 'static,
) -> impl Fn() -> Rc<RefCell<Option<R>>> + Clone
) -> impl Fn() -> Arc<Mutex<Option<R>>> + Clone
where
F: Fn() -> R + Clone + 'static,
R: 'static,
@ -90,19 +89,19 @@ pub fn use_debounce_fn_with_options<F, R>(
func: F,
ms: impl Into<MaybeSignal<f64>> + 'static,
options: DebounceOptions,
) -> impl Fn() -> Rc<RefCell<Option<R>>> + Clone
) -> impl Fn() -> Arc<Mutex<Option<R>>> + Clone
where
F: Fn() -> R + Clone + 'static,
R: 'static,
{
create_filter_wrapper(Rc::new(debounce_filter(ms, options)), func)
create_filter_wrapper(Arc::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.
pub fn use_debounce_fn_with_arg<F, Arg, R>(
func: F,
ms: impl Into<MaybeSignal<f64>> + 'static,
) -> impl Fn(Arg) -> Rc<RefCell<Option<R>>> + Clone
) -> impl Fn(Arg) -> Arc<Mutex<Option<R>>> + Clone
where
F: Fn(Arg) -> R + Clone + 'static,
Arg: Clone + 'static,
@ -116,11 +115,11 @@ pub fn use_debounce_fn_with_arg_and_options<F, Arg, R>(
func: F,
ms: impl Into<MaybeSignal<f64>> + 'static,
options: DebounceOptions,
) -> impl Fn(Arg) -> Rc<RefCell<Option<R>>> + Clone
) -> impl Fn(Arg) -> Arc<Mutex<Option<R>>> + Clone
where
F: Fn(Arg) -> R + Clone + 'static,
Arg: Clone + 'static,
R: 'static,
{
create_filter_wrapper_with_arg(Rc::new(debounce_filter(ms, options)), func)
create_filter_wrapper_with_arg(Arc::new(debounce_filter(ms, options)), func)
}

View file

@ -1,6 +1,5 @@
use cfg_if::cfg_if;
use leptos::prelude::wrappers::read::Signal;
use leptos::prelude::*;
/// Reactive [DeviceOrientationEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent).
/// Provide web developers with information from the physical orientation of
@ -42,6 +41,7 @@ pub fn use_device_orientation() -> UseDeviceOrientationReturn {
let beta = || None;
let gamma = || None;
} else {
use leptos::prelude::*;
use crate::{use_event_listener_with_options, UseEventListenerOptions, use_supported, js};
use leptos::ev::deviceorientation;
@ -67,7 +67,7 @@ pub fn use_device_orientation() -> UseDeviceOrientationReturn {
.once(false),
);
leptos::on_cleanup(cleanup);
on_cleanup(cleanup);
}
}}

View file

@ -1,6 +1,5 @@
use cfg_if::cfg_if;
use leptos::prelude::wrappers::read::Signal;
use leptos::prelude::*;
/// Reactive [`window.devicePixelRatio`](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio)
///
@ -31,15 +30,17 @@ use leptos::prelude::*;
/// On the server this function returns a Signal that is always `1.0`.
pub fn use_device_pixel_ratio() -> Signal<f64> {
cfg_if! { if #[cfg(feature = "ssr")] {
use leptos::prelude::*;
let pixel_ratio = Signal::derive(|| 1.0);
} else {
use crate::{use_event_listener_with_options, UseEventListenerOptions};
use leptos::prelude::*;
use leptos::ev::change;
let initial_pixel_ratio = window().device_pixel_ratio();
let (pixel_ratio, set_pixel_ratio) = signal(initial_pixel_ratio);
create_effect(move |_| {
Effect::new(move |_| {
let media = window().match_media(
&format!("(resolution: {}dppx)", pixel_ratio.get())
).unwrap();

View file

@ -3,6 +3,7 @@ use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder;
use leptos::prelude::wrappers::read::Signal;
use leptos::prelude::*;
use send_wrapper::SendWrapper;
use wasm_bindgen::{JsCast, JsValue};
/// Reactive [`mediaDevices.getDisplayMedia`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia) streaming.
@ -55,7 +56,8 @@ pub fn use_display_media_with_options(
let (enabled, set_enabled) = enabled.into_signal();
let (stream, set_stream) = signal(None::<Result<web_sys::MediaStream, JsValue>>);
let (stream, set_stream) =
signal(None::<Result<SendWrapper<web_sys::MediaStream>, SendWrapper<JsValue>>>);
let _start = move || async move {
cfg_if! { if #[cfg(not(feature = "ssr"))] {
@ -63,7 +65,10 @@ pub fn use_display_media_with_options(
return;
}
let stream = create_media(audio).await;
let stream = create_media(audio)
.await
.map(SendWrapper::new)
.map_err(SendWrapper::new);
set_stream.update(|s| *s = Some(stream));
} else {
@ -83,7 +88,7 @@ pub fn use_display_media_with_options(
let start = move || {
cfg_if! { if #[cfg(not(feature = "ssr"))] {
spawn_local(async move {
leptos::spawn::spawn_local(async move {
_start().await;
stream.with_untracked(move |stream| {
if let Some(Ok(_)) = stream {
@ -103,7 +108,7 @@ pub fn use_display_media_with_options(
move || enabled.get(),
move |enabled, _, _| {
if *enabled {
spawn_local(async move {
leptos::spawn::spawn_local(async move {
_start().await;
});
} else {
@ -176,7 +181,7 @@ where
/// Initially this is `None` until `start` resolved successfully.
/// In case the stream couldn't be started, for example because the user didn't grant permission,
/// this has the value `Some(Err(...))`.
pub stream: Signal<Option<Result<web_sys::MediaStream, JsValue>>>,
pub stream: Signal<Option<Result<SendWrapper<web_sys::MediaStream>, SendWrapper<JsValue>>>>,
/// Starts the screen streaming. Triggers the ask for permission if not already granted.
pub start: StartFn,

View file

@ -1,7 +1,6 @@
use crate::core::ElementMaybeSignal;
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder;
use leptos::prelude::diagnostics::SpecialNonReactiveZone;
use leptos::prelude::wrappers::read::Signal;
use leptos::prelude::*;
use std::fmt::{Debug, Formatter};
@ -78,6 +77,7 @@ where
#[cfg(not(feature = "ssr"))]
{
use leptos::prelude::diagnostics::SpecialNonReactiveZone;
let UseDropZoneOptions {
on_drop,
on_enter,

View file

@ -2,7 +2,6 @@ use crate::core::ElementMaybeSignal;
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder;
use leptos::ev::EventDescriptor;
use leptos::prelude::diagnostics::SpecialNonReactiveZone;
cfg_if! { if #[cfg(not(feature = "ssr"))] {
use crate::{watch_with_options, WatchOptions};
@ -120,6 +119,8 @@ where
#[cfg(not(feature = "ssr"))]
{
use leptos::prelude::diagnostics::SpecialNonReactiveZone;
use send_wrapper::SendWrapper;
let event_name = event.name();
let closure_js = Closure::wrap(Box::new(move |e| {
#[cfg(debug_assertions)]
@ -187,7 +188,8 @@ where
cleanup_prev_element();
};
on_cleanup(stop.clone());
let cleanup_stop = SendWrapper::new(stop.clone());
on_cleanup(move || cleanup_stop());
stop
}

View file

@ -5,9 +5,10 @@ use default_struct_builder::DefaultBuilder;
use leptos::prelude::diagnostics::SpecialNonReactiveZone;
use leptos::prelude::wrappers::read::Signal;
use leptos::prelude::*;
use send_wrapper::SendWrapper;
use std::cell::Cell;
use std::marker::PhantomData;
use std::rc::Rc;
use std::sync::Arc;
use std::time::Duration;
use thiserror::Error;
@ -114,8 +115,9 @@ pub fn use_event_source<T, C>(
url: &str,
) -> UseEventSourceReturn<T, C::Error, impl Fn() + Clone + 'static, impl Fn() + Clone + 'static>
where
T: Clone + PartialEq + 'static,
T: Clone + PartialEq + Send + Sync + 'static,
C: StringCodec<T> + Default,
C::Error: Send + Sync,
{
use_event_source_with_options(url, UseEventSourceOptions::<T, C>::default())
}
@ -126,8 +128,9 @@ pub fn use_event_source_with_options<T, C>(
options: UseEventSourceOptions<T, C>,
) -> UseEventSourceReturn<T, C::Error, impl Fn() + Clone + 'static, impl Fn() + Clone + 'static>
where
T: Clone + PartialEq + 'static,
T: Clone + PartialEq + Send + Sync + 'static,
C: StringCodec<T> + Default,
C::Error: Send + Sync,
{
let UseEventSourceOptions {
codec,
@ -142,14 +145,14 @@ where
let url = url.to_owned();
let (event, set_event) = signal(None::<web_sys::Event>);
let (event, set_event) = signal(None::<SendWrapper<web_sys::Event>>);
let (data, set_data) = signal(None::<T>);
let (ready_state, set_ready_state) = signal(ConnectionReadyState::Closed);
let (event_source, set_event_source) = signal(None::<web_sys::EventSource>);
let (event_source, set_event_source) = signal(None::<SendWrapper<web_sys::EventSource>>);
let (error, set_error) = signal(None::<UseEventSourceError<C::Error>>);
let explicitly_closed = Rc::new(Cell::new(false));
let retried = Rc::new(Cell::new(0));
let explicitly_closed = Arc::new(Cell::new(false));
let retried = Arc::new(Cell::new(0));
let set_data_from_string = move |data_string: Option<String>| {
if let Some(data_string) = data_string {
@ -161,7 +164,7 @@ where
};
let close = {
let explicitly_closed = Rc::clone(&explicitly_closed);
let explicitly_closed = Arc::clone(&explicitly_closed);
move || {
if let Some(event_source) = event_source.get_untracked() {
@ -173,11 +176,11 @@ where
}
};
let init = StoredValue::new(None::<Rc<dyn Fn()>>);
let init = StoredValue::new(None::<Arc<dyn Fn() + Send + Sync>>);
init.set_value(Some(Rc::new({
let explicitly_closed = Rc::clone(&explicitly_closed);
let retried = Rc::clone(&retried);
init.set_value(Some(Arc::new({
let explicitly_closed = Arc::clone(&explicitly_closed);
let retried = Arc::clone(&retried);
move || {
use wasm_bindgen::prelude::*;
@ -194,7 +197,7 @@ where
set_ready_state.set(ConnectionReadyState::Connecting);
set_event_source.set(Some(es.clone()));
set_event_source.set(Some(SendWrapper::new(es.clone())));
let on_open = Closure::wrap(Box::new(move |_: web_sys::Event| {
set_ready_state.set(ConnectionReadyState::Open);
@ -204,14 +207,14 @@ where
on_open.forget();
let on_error = Closure::wrap(Box::new({
let explicitly_closed = Rc::clone(&explicitly_closed);
let retried = Rc::clone(&retried);
let on_failed = Rc::clone(&on_failed);
let explicitly_closed = Arc::clone(&explicitly_closed);
let retried = Arc::clone(&retried);
let on_failed = Arc::clone(&on_failed);
let es = es.clone();
move |e: web_sys::Event| {
set_ready_state.set(ConnectionReadyState::Closed);
set_error.set(Some(UseEventSourceError::Event(e)));
set_error.set(Some(UseEventSourceError::Event(SendWrapper::new(e))));
// only reconnect if EventSource isn't reconnecting by itself
// this is the case when the connection is closed (readyState is 2)
@ -273,8 +276,8 @@ where
{
open = {
let close = close.clone();
let explicitly_closed = Rc::clone(&explicitly_closed);
let retried = Rc::clone(&retried);
let explicitly_closed = Arc::clone(&explicitly_closed);
let retried = Arc::clone(&retried);
move || {
close();
@ -326,7 +329,7 @@ where
reconnect_interval: u64,
/// On maximum retry times reached.
on_failed: Rc<dyn Fn()>,
on_failed: Arc<dyn Fn()>,
/// If `true` the `EventSource` connection will immediately be opened when calling this function.
/// If `false` you have to manually call the `open` function.
@ -349,7 +352,7 @@ impl<T, C: StringCodec<T> + Default> Default for UseEventSourceOptions<T, C> {
codec: C::default(),
reconnect_limit: 3,
reconnect_interval: 3000,
on_failed: Rc::new(|| {}),
on_failed: Arc::new(|| {}),
immediate: true,
named_events: vec![],
with_credentials: false,
@ -361,8 +364,8 @@ impl<T, C: StringCodec<T> + Default> Default for UseEventSourceOptions<T, C> {
/// Return type of [`use_event_source`].
pub struct UseEventSourceReturn<T, Err, OpenFn, CloseFn>
where
Err: 'static,
T: Clone + 'static,
Err: Send + Sync + 'static,
T: Clone + Send + Sync + 'static,
OpenFn: Fn() + Clone + 'static,
CloseFn: Fn() + Clone + 'static,
{
@ -373,7 +376,7 @@ where
pub ready_state: Signal<ConnectionReadyState>,
/// The latest named event
pub event: Signal<Option<web_sys::Event>>,
pub event: Signal<Option<SendWrapper<web_sys::Event>>>,
/// The current error
pub error: Signal<Option<UseEventSourceError<Err>>>,
@ -386,13 +389,13 @@ where
pub close: CloseFn,
/// The `EventSource` instance
pub event_source: Signal<Option<web_sys::EventSource>>,
pub event_source: Signal<Option<SendWrapper<web_sys::EventSource>>>,
}
#[derive(Error, Debug)]
pub enum UseEventSourceError<Err> {
#[error("Error event: {0:?}")]
Event(web_sys::Event),
Event(SendWrapper<web_sys::Event>),
#[error("Error decoding value")]
Deserialize(Err),

View file

@ -2,6 +2,7 @@ use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder;
use leptos::prelude::wrappers::read::Signal;
use leptos::prelude::*;
use send_wrapper::SendWrapper;
/// Reactive [Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API).
/// It allows the user to provide their location to web applications if they so desire. For privacy reasons,
@ -43,8 +44,8 @@ pub fn use_geolocation_with_options(
options: UseGeolocationOptions,
) -> UseGeolocationReturn<impl Fn() + Clone, impl Fn() + Clone> {
let (located_at, set_located_at) = signal(None::<f64>);
let (error, set_error) = signal(None::<web_sys::PositionError>);
let (coords, set_coords) = signal(None::<web_sys::Coordinates>);
let (error, set_error) = signal(None::<SendWrapper<web_sys::PositionError>>);
let (coords, set_coords) = signal(None::<SendWrapper<web_sys::Coordinates>>);
cfg_if! { if #[cfg(feature = "ssr")] {
let resume = || ();
@ -56,24 +57,23 @@ pub fn use_geolocation_with_options(
let _ = set_coords;
} else {
use crate::use_window;
use std::cell::Cell;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use std::sync::{Arc, Mutex};
let update_position = move |position: web_sys::Position| {
set_located_at.set(Some(position.timestamp()));
set_coords.set(Some(position.coords()));
set_coords.set(Some(SendWrapper::new(position.coords())));
set_error.set(None);
};
let on_error = move |err: web_sys::PositionError| {
set_error.set(Some(err));
set_error.set(Some(SendWrapper::new(err)));
};
let watch_handle = Rc::new(Cell::new(None::<i32>));
let watch_handle = Arc::new(Mutex::new(None::<i32>));
let resume = {
let watch_handle = Rc::clone(&watch_handle);
let watch_handle = Arc::clone(&watch_handle);
let position_options = options.as_position_options();
move || {
@ -85,15 +85,14 @@ pub fn use_geolocation_with_options(
let on_error =
Closure::wrap(Box::new(on_error) as Box<dyn Fn(web_sys::PositionError)>);
watch_handle.replace(
*watch_handle.lock().unwrap() =
geolocation
.watch_position_with_error_callback_and_options(
update_position.as_ref().unchecked_ref(),
Some(on_error.as_ref().unchecked_ref()),
&position_options,
)
.ok(),
);
.ok();
update_position.forget();
on_error.forget();
@ -107,12 +106,12 @@ pub fn use_geolocation_with_options(
}
let pause = {
let watch_handle = Rc::clone(&watch_handle);
let watch_handle = Arc::clone(&watch_handle);
move || {
let navigator = use_window().navigator();
if let Some(navigator) = navigator {
if let Some(handle) = watch_handle.take() {
if let Some(handle) = *watch_handle.lock().unwrap() {
if let Ok(geolocation) = navigator.geolocation() {
geolocation.clear_watch(handle);
}
@ -204,13 +203,13 @@ where
{
/// The coordinates of the current device like latitude and longitude.
/// See [`GeolocationCoordinates`](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates)..
pub coords: Signal<Option<web_sys::Coordinates>>,
pub coords: Signal<Option<SendWrapper<web_sys::Coordinates>>>,
/// The timestamp of the current coordinates.
pub located_at: Signal<Option<f64>>,
/// The last error received from `navigator.geolocation`.
pub error: Signal<Option<web_sys::PositionError>>,
pub error: Signal<Option<SendWrapper<web_sys::PositionError>>>,
/// Resume the geolocation watch.
pub resume: ResumeFn,

View file

@ -158,7 +158,7 @@ where
set_loading.set(true);
let measure = measure.clone();
spawn_local(async move {
leptos::spawn::spawn_local(async move {
#[cfg(debug_assertions)]
let zone = SpecialNonReactiveZone::enter();

View file

@ -102,7 +102,7 @@ where
let closure_js = Closure::<dyn FnMut(js_sys::Array, web_sys::IntersectionObserver)>::new(
move |entries: js_sys::Array, observer| {
#[cfg(debug_assertions)]
let _z = SpecialNonReactiveZone::enter();
let _z = leptos::prelude::diagnostics::SpecialNonReactiveZone::enter();
callback(
entries

View file

@ -91,7 +91,7 @@ pub fn use_media_query(query: impl Into<MaybeSignal<String>>) -> Signal<bool> {
listener.replace(Rc::new(move |_| update()) as Rc<dyn Fn(web_sys::Event)>);
}
create_effect(move |_| update());
Effect::new(move |_| update());
on_cleanup(cleanup);
}}

View file

@ -46,12 +46,12 @@ pub fn use_permission(permission_name: &str) -> Signal<PermissionState> {
}
};
spawn_local({
leptos::spawn::spawn_local({
let permission_name = permission_name.to_owned();
async move {
if let Ok(status) = query_permission(permission_name).await {
let _ = use_event_listener(status.clone(), ev::change, {
let _ = use_event_listener(status.clone(), leptos::ev::change, {
let on_change = on_change.clone();
move |_| on_change()
});

View file

@ -1,5 +1,4 @@
use crate::use_media_query;
use leptos::prelude::wrappers::read::Signal;
use leptos::prelude::*;
/// Reactive [dark theme preference](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme).

View file

@ -138,7 +138,8 @@ pub fn use_raf_fn_with_options(
resume();
}
on_cleanup(pause.clone());
let pause_cleanup = send_wrapper::SendWrapper::new(pause.clone());
on_cleanup(move || pause_cleanup());
Pausable {
resume,

View file

@ -11,6 +11,7 @@ use crate::use_event_listener::use_event_listener_with_options;
use crate::{
use_debounce_fn_with_arg, use_throttle_fn_with_arg_and_options, ThrottleOptions,
};
use leptos::ev;
use leptos::ev::scrollend;
use wasm_bindgen::JsCast;
@ -202,13 +203,13 @@ where
let (is_scrolling, set_is_scrolling) = signal(false);
let arrived_state = create_rw_signal(Directions {
let arrived_state = RwSignal::new(Directions {
left: true,
right: false,
top: true,
bottom: false,
});
let directions = create_rw_signal(Directions {
let directions = RwSignal::new(Directions {
left: false,
right: false,
top: false,

View file

@ -3,7 +3,8 @@ use leptos::prelude::actions::Action;
use leptos::prelude::diagnostics::SpecialNonReactiveZone;
use leptos::prelude::wrappers::read::Signal;
use leptos::prelude::*;
use std::rc::Rc;
use send_wrapper::SendWrapper;
use std::sync::Arc;
use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
use web_sys::ServiceWorkerRegistration;
@ -73,20 +74,21 @@ pub fn use_service_worker_with_options(
create_or_update_registration.dispatch(ServiceWorkerScriptUrl(options.script_url.to_string()));
// And parse the result into individual signals.
let registration: Signal<Result<ServiceWorkerRegistration, ServiceWorkerRegistrationError>> =
Signal::derive(move || {
let a = get_registration.value().get();
let b = create_or_update_registration.value().get();
// We only dispatch create_or_update_registration once.
// Whenever we manually re-fetched the registration, the result of that has precedence!
match a {
Some(res) => res.map_err(ServiceWorkerRegistrationError::Js),
None => match b {
Some(res) => res.map_err(ServiceWorkerRegistrationError::Js),
None => Err(ServiceWorkerRegistrationError::NeverQueried),
},
}
});
let registration: Signal<
Result<SendWrapper<ServiceWorkerRegistration>, ServiceWorkerRegistrationError>,
> = Signal::derive(move || {
let a = get_registration.value().get();
let b = create_or_update_registration.value().get();
// We only dispatch create_or_update_registration once.
// Whenever we manually re-fetched the registration, the result of that has precedence!
match a {
Some(res) => res.map_err(ServiceWorkerRegistrationError::Js),
None => match b {
Some(res) => res.map_err(|e| ServiceWorkerRegistrationError::Js(e)),
None => Err(ServiceWorkerRegistrationError::NeverQueried),
},
}
});
let fetch_registration = Closure::wrap(Box::new(move |_event: JsValue| {
get_registration.dispatch(());
@ -182,7 +184,7 @@ pub struct UseServiceWorkerOptions {
/// What should happen when a new service worker was activated?
/// The default implementation reloads the current page.
on_controller_change: Rc<dyn Fn()>,
on_controller_change: Arc<dyn Fn()>,
}
impl Default for UseServiceWorkerOptions {
@ -190,7 +192,7 @@ impl Default for UseServiceWorkerOptions {
Self {
script_url: "service-worker.js".into(),
skip_waiting_message: "skipWaiting".into(),
on_controller_change: Rc::new(move || {
on_controller_change: Arc::new(move || {
use std::ops::Deref;
if let Some(window) = use_window().deref() {
if let Err(err) = window.location().reload() {
@ -211,7 +213,8 @@ where
SkipFn: Fn() + Clone,
{
/// The current registration state.
pub registration: Signal<Result<ServiceWorkerRegistration, ServiceWorkerRegistrationError>>,
pub registration:
Signal<Result<SendWrapper<ServiceWorkerRegistration>, ServiceWorkerRegistrationError>>,
/// Whether a SW is currently installing.
pub installing: Signal<bool>,
@ -234,29 +237,37 @@ struct ServiceWorkerScriptUrl(pub String);
#[derive(Debug, Clone)]
pub enum ServiceWorkerRegistrationError {
Js(JsValue),
Js(SendWrapper<JsValue>),
NeverQueried,
}
/// A leptos action which asynchronously checks for ServiceWorker updates, given an existing ServiceWorkerRegistration.
fn create_action_update(
) -> Action<ServiceWorkerRegistration, Result<ServiceWorkerRegistration, JsValue>> {
Action::new(move |registration: &ServiceWorkerRegistration| {
let registration = registration.clone();
async move {
match registration.update() {
Ok(promise) => js_fut!(promise)
.await
.and_then(|ok| ok.dyn_into::<ServiceWorkerRegistration>()),
Err(err) => Err(err),
fn create_action_update() -> Action<
SendWrapper<ServiceWorkerRegistration>,
Result<SendWrapper<ServiceWorkerRegistration>, SendWrapper<JsValue>>,
> {
Action::new(
move |registration: &SendWrapper<ServiceWorkerRegistration>| {
let registration = registration.clone();
async move {
match registration.update() {
Ok(promise) => js_fut!(promise)
.await
.and_then(|ok| ok.dyn_into::<ServiceWorkerRegistration>())
.map(SendWrapper::new)
.map_err(SendWrapper::new),
Err(err) => Err(err),
}
}
}
})
},
)
}
/// A leptos action which asynchronously creates or updates and than retrieves the ServiceWorkerRegistration.
fn create_action_create_or_update_registration(
) -> Action<ServiceWorkerScriptUrl, Result<ServiceWorkerRegistration, JsValue>> {
fn create_action_create_or_update_registration() -> Action<
ServiceWorkerScriptUrl,
Result<SendWrapper<ServiceWorkerRegistration>, SendWrapper<JsValue>>,
> {
Action::new(move |script_url: &ServiceWorkerScriptUrl| {
let script_url = script_url.0.to_owned();
async move {
@ -272,14 +283,17 @@ fn create_action_create_or_update_registration(
}
/// A leptos action which asynchronously fetches the current ServiceWorkerRegistration.
fn create_action_get_registration() -> Action<(), Result<ServiceWorkerRegistration, JsValue>> {
fn create_action_get_registration(
) -> Action<(), Result<SendWrapper<ServiceWorkerRegistration>, SendWrapper<JsValue>>> {
Action::new(move |(): &()| async move {
if let Some(navigator) = use_window().navigator() {
js_fut!(navigator.service_worker().get_registration())
.await
.and_then(|ok| ok.dyn_into::<ServiceWorkerRegistration>())
.map(SendWrapper::new)
.map_err(SendWrapper::new)
} else {
Err(JsValue::from_str("no navigator"))
Err(SendWrapper::new(JsValue::from_str("no navigator")))
}
})
}

View file

@ -1,4 +1,3 @@
use leptos::prelude::wrappers::read::Signal;
use leptos::prelude::*;
/// SSR compatibe `is_supported`

View file

@ -1,7 +1,6 @@
use crate::utils::{create_filter_wrapper, create_filter_wrapper_with_arg, throttle_filter};
use leptos::MaybeSignal;
use std::cell::RefCell;
use std::rc::Rc;
use leptos::prelude::MaybeSignal;
use std::sync::{Arc, Mutex};
pub use crate::utils::ThrottleOptions;
@ -73,7 +72,7 @@ pub use crate::utils::ThrottleOptions;
pub fn use_throttle_fn<F, R>(
func: F,
ms: impl Into<MaybeSignal<f64>> + 'static,
) -> impl Fn() -> Rc<RefCell<Option<R>>> + Clone
) -> impl Fn() -> Arc<Mutex<Option<R>>> + Clone
where
F: Fn() -> R + Clone + 'static,
R: 'static,
@ -86,19 +85,19 @@ pub fn use_throttle_fn_with_options<F, R>(
func: F,
ms: impl Into<MaybeSignal<f64>> + 'static,
options: ThrottleOptions,
) -> impl Fn() -> Rc<RefCell<Option<R>>> + Clone
) -> impl Fn() -> Arc<Mutex<Option<R>>> + Clone
where
F: Fn() -> R + Clone + 'static,
R: 'static,
{
create_filter_wrapper(Rc::new(throttle_filter(ms, options)), func)
create_filter_wrapper(Arc::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.
pub fn use_throttle_fn_with_arg<F, Arg, R>(
func: F,
ms: impl Into<MaybeSignal<f64>> + 'static,
) -> impl Fn(Arg) -> Rc<RefCell<Option<R>>> + Clone
) -> impl Fn(Arg) -> Arc<Mutex<Option<R>>> + Clone
where
F: Fn(Arg) -> R + Clone + 'static,
Arg: Clone + 'static,
@ -112,11 +111,11 @@ pub fn use_throttle_fn_with_arg_and_options<F, Arg, R>(
func: F,
ms: impl Into<MaybeSignal<f64>> + 'static,
options: ThrottleOptions,
) -> impl Fn(Arg) -> Rc<RefCell<Option<R>>> + Clone
) -> impl Fn(Arg) -> Arc<Mutex<Option<R>>> + Clone
where
F: Fn(Arg) -> R + Clone + 'static,
Arg: Clone + 'static,
R: 'static,
{
create_filter_wrapper_with_arg(Rc::new(throttle_filter(ms, options)), func)
create_filter_wrapper_with_arg(Arc::new(throttle_filter(ms, options)), func)
}

View file

@ -2,9 +2,8 @@ use leptos::leptos_dom::helpers::TimeoutHandle;
use leptos::prelude::diagnostics::SpecialNonReactiveZone;
use leptos::prelude::wrappers::read::Signal;
use leptos::prelude::*;
use std::cell::Cell;
use std::marker::PhantomData;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use std::time::Duration;
/// Wrapper for `setTimeout` with controls.
@ -46,13 +45,14 @@ where
let (is_pending, set_pending) = signal(false);
let timer = Rc::new(Cell::new(None::<TimeoutHandle>));
let timer = Arc::new(Mutex::new(None::<TimeoutHandle>));
let clear = {
let timer = Rc::clone(&timer);
let timer = Arc::clone(&timer);
move || {
if let Some(timer) = timer.take() {
let timer = timer.lock().unwrap();
if let Some(timer) = *timer {
timer.clear();
}
}
@ -68,7 +68,7 @@ where
};
let start = {
let timer = Rc::clone(&timer);
let timer = Arc::clone(&timer);
let callback = callback.clone();
move |arg: Arg| {
@ -76,12 +76,12 @@ where
let handle = set_timeout_with_handle(
{
let timer = Rc::clone(&timer);
let timer = Arc::clone(&timer);
let callback = callback.clone();
move || {
set_pending.set(false);
timer.set(None);
*timer.lock().unwrap() = None;
#[cfg(debug_assertions)]
let _z = SpecialNonReactiveZone::enter();
@ -93,7 +93,7 @@ where
)
.ok();
timer.set(handle);
*timer.lock().unwrap() = handle;
}
};

View file

@ -1,9 +1,8 @@
use crate::{use_supported, use_window};
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder;
use leptos::prelude::diagnostics::SpecialNonReactiveZone;
use leptos::prelude::wrappers::read::Signal;
use leptos::prelude::*;
use leptos::prelude::{wrappers::read::Signal, *};
use send_wrapper::SendWrapper;
use std::rc::Rc;
/// Reactive [Notification API](https://developer.mozilla.org/en-US/docs/Web/API/Notification).
@ -53,7 +52,7 @@ pub fn use_web_notification_with_options(
) -> UseWebNotificationReturn<impl Fn(ShowOptions) + Clone, impl Fn() + Clone> {
let is_supported = use_supported(browser_supports_notifications);
let (notification, set_notification) = signal(None::<web_sys::Notification>);
let (notification, set_notification) = signal(None::<SendWrapper<web_sys::Notification>>);
let (permission, set_permission) = signal(NotificationPermission::default());
@ -65,6 +64,7 @@ pub fn use_web_notification_with_options(
let show = move |_: ShowOptions| ();
let close = move || ();
} else {
use leptos::{spawn::spawn_local, prelude::diagnostics::SpecialNonReactiveZone};
use crate::use_event_listener;
use leptos::ev::visibilitychange;
use wasm_bindgen::closure::Closure;
@ -462,7 +462,7 @@ where
CloseFn: Fn() + Clone,
{
pub is_supported: Signal<bool>,
pub notification: Signal<Option<web_sys::Notification>>,
pub notification: Signal<Option<SendWrapper<web_sys::Notification>>>,
pub show: ShowFn,
pub close: CloseFn,
pub permission: Signal<NotificationPermission>,

View file

@ -2,8 +2,8 @@
use cfg_if::cfg_if;
use leptos::{leptos_dom::helpers::TimeoutHandle, prelude::*};
use std::cell::Cell;
use std::rc::Rc;
use send_wrapper::SendWrapper;
use std::sync::{atomic::AtomicBool, Arc};
use std::time::Duration;
use crate::core::ConnectionReadyState;
@ -100,16 +100,16 @@ use web_sys::{BinaryType, CloseEvent, Event, MessageEvent, WebSocket};
///
/// ```
/// # use leptos::prelude::*;
/// use std::rc::Rc;
/// use std::sync::Arc;
///
/// #[derive(Clone)]
/// pub struct WebsocketContext {
/// pub message: Signal<Option<String>>,
/// send: Rc<dyn Fn(&str)>, // use Rc to make it easily cloneable
/// send: Arc<dyn Fn(&str)>, // use Arc to make it easily cloneable
/// }
///
/// impl WebsocketContext {
/// pub fn new(message: Signal<Option<String>>, send: Rc<dyn Fn(&str)>) -> Self {
/// pub fn new(message: Signal<Option<String>>, send: Arc<dyn Fn(&str)>) -> Self {
/// Self {
/// message,
/// send,
@ -129,15 +129,15 @@ use web_sys::{BinaryType, CloseEvent, Event, MessageEvent, WebSocket};
/// ```
/// # use leptos::prelude::*;
/// # use leptos_use::{use_websocket, UseWebsocketReturn};
/// # use std::rc::Rc;
/// # use std::sync::Arc;
/// # #[derive(Clone)]
/// # pub struct WebsocketContext {
/// # pub message: Signal<Option<String>>,
/// # send: Rc<dyn Fn(&str)>,
/// # send: Arc<dyn Fn(&str)>,
/// # }
/// #
/// # impl WebsocketContext {
/// # pub fn new(message: Signal<Option<String>>, send: Rc<dyn Fn(&str)>) -> Self {
/// # pub fn new(message: Signal<Option<String>>, send: Arc<dyn Fn(&str)>) -> Self {
/// # Self {
/// # message,
/// # send,
@ -153,7 +153,7 @@ use web_sys::{BinaryType, CloseEvent, Event, MessageEvent, WebSocket};
/// ..
/// } = use_websocket("ws:://some.websocket.io");
///
/// provide_context(WebsocketContext::new(message, Rc::new(send.clone())));
/// provide_context(WebsocketContext::new(message, Arc::new(send.clone())));
/// #
/// # view! {}
/// # }
@ -164,11 +164,11 @@ use web_sys::{BinaryType, CloseEvent, Event, MessageEvent, WebSocket};
/// ```
/// # use leptos::prelude::*;
/// # use leptos_use::{use_websocket, UseWebsocketReturn};
/// # use std::rc::Rc;
/// # use std::sync::Arc;
/// # #[derive(Clone)]
/// # pub struct WebsocketContext {
/// # pub message: Signal<Option<String>>,
/// # send: Rc<dyn Fn(&str)>,
/// # send: Arc<dyn Fn(&str)>,
/// # }
/// #
/// # impl WebsocketContext {
@ -228,26 +228,27 @@ pub fn use_websocket_with_options(
let (ready_state, set_ready_state) = signal(ConnectionReadyState::Closed);
let (message, set_message) = signal(None);
let (message_bytes, set_message_bytes) = signal(None);
let ws_ref: StoredValue<Option<WebSocket>> = StoredValue::new(None);
let ws_ref: StoredValue<Option<SendWrapper<WebSocket>>> = StoredValue::new(None);
let reconnect_timer_ref: StoredValue<Option<TimeoutHandle>> = StoredValue::new(None);
let reconnect_times_ref: StoredValue<u64> = StoredValue::new(0);
let unmounted = Rc::new(Cell::new(false));
let unmounted = Arc::new(AtomicBool::new(false));
let connect_ref: StoredValue<Option<Rc<dyn Fn()>>> = StoredValue::new(None);
let connect_ref: StoredValue<Option<Arc<dyn Fn() + Send + Sync>>> = StoredValue::new(None);
#[cfg(not(feature = "ssr"))]
{
let reconnect_ref: StoredValue<Option<Rc<dyn Fn()>>> = StoredValue::new(None);
let reconnect_ref: StoredValue<Option<Arc<dyn Fn() + Send + Sync>>> =
StoredValue::new(None);
reconnect_ref.set_value({
let ws = ws_ref.get_value();
Some(Rc::new(move || {
Some(Arc::new(move || {
if reconnect_times_ref.get_value() < reconnect_limit
&& ws
.clone()
.map_or(false, |ws: WebSocket| ws.ready_state() != WebSocket::OPEN)
&& ws.clone().map_or(false, |ws: SendWrapper<WebSocket>| {
ws.ready_state() != WebSocket::OPEN
})
{
reconnect_timer_ref.set_value(
set_timeout_with_handle(
@ -267,9 +268,9 @@ pub fn use_websocket_with_options(
connect_ref.set_value({
let ws = ws_ref.get_value();
let unmounted = Rc::clone(&unmounted);
let unmounted = Arc::clone(&unmounted);
Some(Rc::new(move || {
Some(Arc::new(move || {
reconnect_timer_ref.set_value(None);
if let Some(web_socket) = &ws {
@ -294,8 +295,8 @@ pub fn use_websocket_with_options(
// onopen handler
{
let unmounted = Rc::clone(&unmounted);
let on_open = Rc::clone(&on_open);
let unmounted = Arc::clone(&unmounted);
let on_open = Arc::clone(&on_open);
let onopen_closure = Closure::wrap(Box::new(move |e: Event| {
if unmounted.get() {
@ -303,7 +304,7 @@ pub fn use_websocket_with_options(
}
#[cfg(debug_assertions)]
let zone = SpecialNonReactiveZone::enter();
let zone = diagnostics::SpecialNonReactiveZone::enter();
on_open(e);
@ -320,9 +321,9 @@ pub fn use_websocket_with_options(
// onmessage handler
{
let unmounted = Rc::clone(&unmounted);
let on_message = Rc::clone(&on_message);
let on_message_bytes = Rc::clone(&on_message_bytes);
let unmounted = Arc::clone(&unmounted);
let on_message = Arc::clone(&on_message);
let on_message_bytes = Arc::clone(&on_message_bytes);
let onmessage_closure = Closure::wrap(Box::new(move |e: MessageEvent| {
if unmounted.get() {
@ -342,7 +343,7 @@ pub fn use_websocket_with_options(
let txt = String::from(&txt);
#[cfg(debug_assertions)]
let zone = SpecialNonReactiveZone::enter();
let zone = diagnostics::SpecialNonReactiveZone::enter();
on_message(txt.clone());
@ -358,7 +359,7 @@ pub fn use_websocket_with_options(
let array = array.to_vec();
#[cfg(debug_assertions)]
let zone = SpecialNonReactiveZone::enter();
let zone = diagnostics::SpecialNonReactiveZone::enter();
on_message_bytes(array.clone());
@ -376,8 +377,8 @@ pub fn use_websocket_with_options(
// onerror handler
{
let unmounted = Rc::clone(&unmounted);
let on_error = Rc::clone(&on_error);
let unmounted = Arc::clone(&unmounted);
let on_error = Arc::clone(&on_error);
let onerror_closure = Closure::wrap(Box::new(move |e: Event| {
if unmounted.get() {
@ -389,7 +390,7 @@ pub fn use_websocket_with_options(
}
#[cfg(debug_assertions)]
let zone = SpecialNonReactiveZone::enter();
let zone = diagnostics::SpecialNonReactiveZone::enter();
on_error(e);
@ -405,8 +406,8 @@ pub fn use_websocket_with_options(
// onclose handler
{
let unmounted = Rc::clone(&unmounted);
let on_close = Rc::clone(&on_close);
let unmounted = Arc::clone(&unmounted);
let on_close = Arc::clone(&on_close);
let onclose_closure = Closure::wrap(Box::new(move |e: CloseEvent| {
if unmounted.get() {
@ -418,7 +419,7 @@ pub fn use_websocket_with_options(
}
#[cfg(debug_assertions)]
let zone = SpecialNonReactiveZone::enter();
let zone = diagnostics::SpecialNonReactiveZone::enter();
on_close(e);
@ -486,7 +487,7 @@ pub fn use_websocket_with_options(
// clean up (unmount)
on_cleanup(move || {
unmounted.set(true);
unmounted.store(true, std::sync::atomic::Ordering::Relaxed);
close();
});
@ -506,15 +507,15 @@ pub fn use_websocket_with_options(
#[derive(DefaultBuilder)]
pub struct UseWebSocketOptions {
/// `WebSocket` connect callback.
on_open: Rc<dyn Fn(Event)>,
on_open: Arc<dyn Fn(Event) + Send + Sync>,
/// `WebSocket` message callback for text.
on_message: Rc<dyn Fn(String)>,
on_message: Arc<dyn Fn(String) + Send + Sync>,
/// `WebSocket` message callback for binary.
on_message_bytes: Rc<dyn Fn(Vec<u8>)>,
on_message_bytes: Arc<dyn Fn(Vec<u8>) + Send + Sync>,
/// `WebSocket` error callback.
on_error: Rc<dyn Fn(Event)>,
on_error: Arc<dyn Fn(Event) + Send + Sync>,
/// `WebSocket` close callback.
on_close: Rc<dyn Fn(CloseEvent)>,
on_close: Arc<dyn Fn(CloseEvent) + Send + Sync>,
/// Retry times. Defaults to 3.
reconnect_limit: u64,
/// Retry interval in ms. Defaults to 3000.
@ -530,11 +531,11 @@ pub struct UseWebSocketOptions {
impl Default for UseWebSocketOptions {
fn default() -> Self {
Self {
on_open: Rc::new(|_| {}),
on_message: Rc::new(|_| {}),
on_message_bytes: Rc::new(|_| {}),
on_error: Rc::new(|_| {}),
on_close: Rc::new(|_| {}),
on_open: Arc::new(|_| {}),
on_message: Arc::new(|_| {}),
on_message_bytes: Arc::new(|_| {}),
on_error: Arc::new(|_| {}),
on_close: Arc::new(|_| {}),
reconnect_limit: 3,
reconnect_interval: 3000,
immediate: true,
@ -559,7 +560,7 @@ where
/// Latest binary message received from `WebSocket`.
pub message_bytes: Signal<Option<Vec<u8>>>,
/// The `WebSocket` instance.
pub ws: Option<WebSocket>,
pub ws: Option<SendWrapper<WebSocket>>,
/// Opens the `WebSocket` connection
pub open: OpenFn,
/// Closes the `WebSocket` connection

View file

@ -5,8 +5,7 @@ use default_struct_builder::DefaultBuilder;
use leptos::leptos_dom::helpers::TimeoutHandle;
use leptos::prelude::diagnostics::SpecialNonReactiveZone;
use leptos::prelude::*;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use std::time::Duration;
#[derive(Copy, Clone, DefaultBuilder, Default)]
@ -20,23 +19,24 @@ pub struct DebounceOptions {
pub fn debounce_filter<R>(
ms: impl Into<MaybeSignal<f64>>,
options: DebounceOptions,
) -> impl Fn(Rc<dyn Fn() -> R>) -> Rc<RefCell<Option<R>>> + Clone
) -> impl Fn(Arc<dyn Fn() -> R>) -> Arc<Mutex<Option<R>>> + Clone
where
R: 'static,
{
let timer = Rc::new(Cell::new(None::<TimeoutHandle>));
let max_timer = Rc::new(Cell::new(None::<TimeoutHandle>));
let last_return_value: Rc<RefCell<Option<R>>> = Rc::new(RefCell::new(None));
let timer = Arc::new(Mutex::new(None::<TimeoutHandle>));
let max_timer = Arc::new(Mutex::new(None::<TimeoutHandle>));
let last_return_value: Arc<Mutex<Option<R>>> = Arc::new(Mutex::new(None));
let clear_timeout = move |timer: &Rc<Cell<Option<TimeoutHandle>>>| {
if let Some(handle) = timer.get() {
let clear_timeout = move |timer: &Arc<Mutex<Option<TimeoutHandle>>>| {
let mut timer = timer.lock().unwrap();
if let Some(handle) = *timer {
handle.clear();
timer.set(None);
*timer = None;
}
};
on_cleanup({
let timer = Rc::clone(&timer);
let timer = Arc::clone(&timer);
move || {
clear_timeout(&timer);
@ -46,11 +46,11 @@ where
let ms = ms.into();
let max_wait_signal = options.max_wait;
move |_invoke: Rc<dyn Fn() -> R>| {
move |_invoke: Arc<dyn Fn() -> R>| {
let duration = ms.get_untracked();
let max_duration = max_wait_signal.get_untracked();
let last_return_val = Rc::clone(&last_return_value);
let last_return_val = Arc::clone(&last_return_value);
let invoke = move || {
#[cfg(debug_assertions)]
let zone = SpecialNonReactiveZone::enter();
@ -60,7 +60,7 @@ where
#[cfg(debug_assertions)]
drop(zone);
let mut val_mut = last_return_val.borrow_mut();
let mut val_mut = last_return_val.lock().unwrap();
*val_mut = Some(return_value);
};
@ -70,43 +70,41 @@ where
clear_timeout(&max_timer);
invoke();
return Rc::clone(&last_return_value);
return Arc::clone(&last_return_value);
}
cfg_if! { if #[cfg(not(feature = "ssr"))] {
// Create the max_timer. Clears the regular timer on invoke
if let Some(max_duration) = max_duration {
if max_timer.get().is_none() {
let timer = Rc::clone(&timer);
let mut max_timer = max_timer.lock().unwrap();
if max_timer.is_none() {
let timer = Arc::clone(&timer);
let invok = invoke.clone();
max_timer.set(
set_timeout_with_handle(
move || {
clear_timeout(&timer);
invok();
},
Duration::from_millis(max_duration as u64),
)
.ok(),
);
*max_timer = set_timeout_with_handle(
move || {
clear_timeout(&timer);
invok();
},
Duration::from_millis(max_duration as u64),
)
.ok();
}
}
let max_timer = Rc::clone(&max_timer);
let max_timer = Arc::clone(&max_timer);
// Create the regular timer. Clears the max timer on invoke
timer.set(
set_timeout_with_handle(
move || {
clear_timeout(&max_timer);
invoke();
},
Duration::from_millis(duration as u64),
)
.ok(),
);
*timer.lock().unwrap() = set_timeout_with_handle(
move || {
clear_timeout(&max_timer);
invoke();
},
Duration::from_millis(duration as u64),
)
.ok();
}}
Rc::clone(&last_return_value)
Arc::clone(&last_return_value)
}
}

View file

@ -4,31 +4,30 @@ mod throttle;
pub use debounce::*;
pub use throttle::*;
use leptos::MaybeSignal;
use std::cell::RefCell;
use std::rc::Rc;
use leptos::prelude::MaybeSignal;
use std::sync::{Arc, Mutex};
macro_rules! RcFilterFn {
macro_rules! ArcFilterFn {
($R:ident) => {
Rc<dyn Fn(Rc<dyn Fn() -> $R>) -> Rc<RefCell<Option<$R>>>>
Arc<dyn Fn(Arc<dyn Fn() -> $R>) -> Arc<Mutex<Option<$R>>>>
}
}
pub fn create_filter_wrapper<F, R>(
filter: RcFilterFn!(R),
filter: ArcFilterFn!(R),
func: F,
) -> impl Fn() -> Rc<RefCell<Option<R>>> + Clone
) -> impl Fn() -> Arc<Mutex<Option<R>>> + Clone
where
F: Fn() -> R + Clone + 'static,
R: 'static,
{
move || Rc::clone(&filter)(Rc::new(func.clone()))
move || Arc::clone(&filter)(Arc::new(func.clone()))
}
pub fn create_filter_wrapper_with_arg<F, Arg, R>(
filter: RcFilterFn!(R),
filter: ArcFilterFn!(R),
func: F,
) -> impl Fn(Arg) -> Rc<RefCell<Option<R>>> + Clone
) -> impl Fn(Arg) -> Arc<Mutex<Option<R>>> + Clone
where
F: Fn(Arg) -> R + Clone + 'static,
R: 'static,
@ -36,7 +35,7 @@ where
{
move |arg: Arg| {
let func = func.clone();
Rc::clone(&filter)(Rc::new(move || func(arg.clone())))
Arc::clone(&filter)(Arc::new(move || func(arg.clone())))
}
}
@ -70,15 +69,15 @@ impl FilterOptions {
}
}
pub fn filter_fn<R>(&self) -> RcFilterFn!(R)
pub fn filter_fn<R>(&self) -> ArcFilterFn!(R)
where
R: 'static,
{
match self {
FilterOptions::Debounce { ms, options } => Rc::new(debounce_filter(*ms, *options)),
FilterOptions::Throttle { ms, options } => Rc::new(throttle_filter(*ms, *options)),
FilterOptions::Debounce { ms, options } => Arc::new(debounce_filter(*ms, *options)),
FilterOptions::Throttle { ms, options } => Arc::new(throttle_filter(*ms, *options)),
FilterOptions::None => {
Rc::new(|invoke: Rc<dyn Fn() -> R>| Rc::new(RefCell::new(Some(invoke()))))
Arc::new(|invoke: Arc<dyn Fn() -> R>| Arc::new(Mutex::new(Some(invoke()))))
}
}
}

View file

@ -6,9 +6,8 @@ use default_struct_builder::DefaultBuilder;
use leptos::leptos_dom::helpers::TimeoutHandle;
use leptos::prelude::diagnostics::SpecialNonReactiveZone;
use leptos::prelude::*;
use std::cell::{Cell, RefCell};
use std::cmp::max;
use std::rc::Rc;
use std::sync::{atomic::AtomicBool, Arc, Mutex};
use std::time::Duration;
#[derive(Copy, Clone, DefaultBuilder)]
@ -31,20 +30,21 @@ impl Default for ThrottleOptions {
pub fn throttle_filter<R>(
ms: impl Into<MaybeSignal<f64>>,
options: ThrottleOptions,
) -> impl Fn(Rc<dyn Fn() -> R>) -> Rc<RefCell<Option<R>>> + Clone
) -> impl Fn(Arc<dyn Fn() -> R>) -> Arc<Mutex<Option<R>>> + Clone
where
R: 'static,
{
let last_exec = Rc::new(Cell::new(0_f64));
let timer = Rc::new(Cell::new(None::<TimeoutHandle>));
let is_leading = Rc::new(Cell::new(true));
let last_return_value: Rc<RefCell<Option<R>>> = Rc::new(RefCell::new(None));
let last_exec = Arc::new(Mutex::new(0_f64));
let timer = Arc::new(Mutex::new(None::<TimeoutHandle>));
let is_leading = Arc::new(AtomicBool::new(true));
let last_return_value: Arc<Mutex<Option<R>>> = Arc::new(Mutex::new(None));
let t = Rc::clone(&timer);
let t = Arc::clone(&timer);
let clear = move || {
if let Some(handle) = t.get() {
let mut t = t.lock().unwrap();
if let Some(handle) = *t {
handle.clear();
t.set(None);
*t = None;
}
};
@ -52,11 +52,11 @@ where
let ms = ms.into();
move |mut _invoke: Rc<dyn Fn() -> R>| {
move |mut _invoke: Arc<dyn Fn() -> R>| {
let duration = ms.get_untracked();
let elapsed = now() - last_exec.get();
let elapsed = now() - *last_exec.lock().unwrap();
let last_return_val = Rc::clone(&last_return_value);
let last_return_val = Arc::clone(&last_return_value);
let invoke = move || {
#[cfg(debug_assertions)]
let zone = SpecialNonReactiveZone::enter();
@ -66,7 +66,7 @@ where
#[cfg(debug_assertions)]
drop(zone);
let mut val_mut = last_return_val.borrow_mut();
let mut val_mut = last_return_val.lock().unwrap();
*val_mut = Some(return_value);
};
@ -74,50 +74,51 @@ where
clear();
if duration <= 0.0 {
last_exec.set(now());
*last_exec.lock().unwrap() = now();
invoke();
return Rc::clone(&last_return_value);
return Arc::clone(&last_return_value);
}
if elapsed > duration && (options.leading || !is_leading.get()) {
last_exec.set(now());
if elapsed > duration
&& (options.leading || !is_leading.load(std::sync::atomic::Ordering::Relaxed))
{
*last_exec.lock().unwrap() = now();
invoke();
} else if options.trailing {
cfg_if! { if #[cfg(not(feature = "ssr"))] {
let last_exec = Rc::clone(&last_exec);
let is_leading = Rc::clone(&is_leading);
timer.set(
let last_exec = Arc::clone(&last_exec);
let is_leading = Arc::clone(&is_leading);
*timer.lock().unwrap() =
set_timeout_with_handle(
move || {
last_exec.set(now());
is_leading.set(true);
*last_exec.lock().unwrap() = now();
is_leading.store(true, std::sync::atomic::Ordering::Relaxed);
invoke();
clear();
},
Duration::from_millis(max(0, (duration - elapsed) as u64)),
)
.ok(),
);
.ok();
}}
}
cfg_if! { if #[cfg(not(feature = "ssr"))] {
if !options.leading && timer.get().is_none() {
let is_leading = Rc::clone(&is_leading);
timer.set(
set_timeout_with_handle(
let mut timer = timer.lock().unwrap();
if !options.leading && timer.is_none() {
let is_leading = Arc::clone(&is_leading);
*timer = set_timeout_with_handle(
move || {
is_leading.set(true);
is_leading.store(true, std::sync::atomic::Ordering::Relaxed);
},
Duration::from_millis(duration as u64),
)
.ok(),
);
.ok();
}
}}
is_leading.set(false);
is_leading.store(false, std::sync::atomic::Ordering::Relaxed);
Rc::clone(&last_return_value)
Arc::clone(&last_return_value)
}
}

View file

@ -1,7 +1,7 @@
use crate::filter_builder_methods;
use crate::utils::{create_filter_wrapper, DebounceOptions, FilterOptions, ThrottleOptions};
use default_struct_builder::DefaultBuilder;
use leptos::prelude::*;
use leptos::prelude::{diagnostics::SpecialNonReactiveZone, *};
use std::cell::RefCell;
use std::rc::Rc;