Completed MVP port

This commit is contained in:
CorvusPrudens 2024-07-23 21:33:14 -06:00
parent 9866ac6231
commit c414022b23
24 changed files with 263 additions and 188 deletions

View file

@ -171,7 +171,7 @@ where
let _ = target; let _ = target;
Self::Static(None) Self::Static(None)
} else { } else {
Self::Static(document().query_selector(target).unwrap_or_default()) Self::Static(document().query_selector(target).unwrap_or_default().map(SendWrapper::new))
}} }}
} }
} }
@ -197,7 +197,7 @@ macro_rules! impl_from_signal_string {
Self::Dynamic(Signal::derive(|| None)) Self::Dynamic(Signal::derive(|| None))
} else { } else {
Self::Dynamic( Self::Dynamic(
Signal::derive(move || document().query_selector(&signal.get()).unwrap_or_default()), Signal::derive(move || document().query_selector(&signal.get()).unwrap_or_default().map(SendWrapper::new)),
) )
}} }}
} }
@ -212,8 +212,8 @@ impl_from_signal_string!(Memo<String>);
impl_from_signal_string!(Signal<&str>); impl_from_signal_string!(Signal<&str>);
impl_from_signal_string!(ReadSignal<&str>); impl_from_signal_string!(ReadSignal<&str>);
impl_from_signal_string!(RwSignal<&str>); impl_from_signal_string!(RwSignal<&'static str>);
impl_from_signal_string!(Memo<&str>); impl_from_signal_string!(Memo<&'static str>);
// From signal /////////////////////////////////////////////////////////////// // From signal ///////////////////////////////////////////////////////////////

View file

@ -156,7 +156,7 @@ where
let mut list = Vec::with_capacity(node_list.length() as usize); let mut list = Vec::with_capacity(node_list.length() as usize);
for i in 0..node_list.length() { for i in 0..node_list.length() {
let node = node_list.get(i).expect("checked the range"); let node = node_list.get(i).expect("checked the range");
list.push(Some(node)); list.push(Some(SendWrapper::new(node)));
} }
Self::Static(list) Self::Static(list)

View file

@ -68,7 +68,7 @@ mod use_preferred_dark;
mod use_raf_fn; mod use_raf_fn;
mod use_resize_observer; mod use_resize_observer;
mod use_scroll; mod use_scroll;
mod use_service_worker; // mod use_service_worker;
mod use_sorted; mod use_sorted;
mod use_supported; mod use_supported;
mod use_throttle_fn; mod use_throttle_fn;
@ -134,7 +134,7 @@ pub use use_preferred_dark::*;
pub use use_raf_fn::*; pub use use_raf_fn::*;
pub use use_resize_observer::*; pub use use_resize_observer::*;
pub use use_scroll::*; pub use use_scroll::*;
pub use use_service_worker::*; // pub use use_service_worker::*;
pub use use_sorted::*; pub use use_sorted::*;
pub use use_supported::*; pub use use_supported::*;
pub use use_throttle_fn::*; pub use use_throttle_fn::*;

View file

@ -149,7 +149,7 @@ where
let ignore = ignore.get_untracked(); let ignore = ignore.get_untracked();
ignore.into_iter().flatten().any(|element| { ignore.into_iter().flatten().any(|element| {
let element: web_sys::EventTarget = element.into(); let element: web_sys::EventTarget = element.take().into();
event_target::<web_sys::EventTarget>(event) == element event_target::<web_sys::EventTarget>(event) == element
|| event.composed_path().includes(element.as_ref(), 0) || event.composed_path().includes(element.as_ref(), 0)

View file

@ -6,6 +6,7 @@ use codee::{CodecError, Decoder, Encoder};
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::wrappers::read::Signal; use leptos::prelude::wrappers::read::Signal;
use leptos::prelude::*; use leptos::prelude::*;
use send_wrapper::SendWrapper;
use std::sync::Arc; use std::sync::Arc;
use thiserror::Error; use thiserror::Error;
use wasm_bindgen::JsValue; use wasm_bindgen::JsValue;
@ -202,7 +203,7 @@ where
// Get storage API // Get storage API
let storage = storage_type let storage = storage_type
.into_storage() .into_storage()
.map_err(UseStorageError::StorageNotAvailable) .map_err(|e| UseStorageError::StorageNotAvailable(SendWrapper::new(e)))
.and_then(|s| s.ok_or(UseStorageError::StorageReturnedNone)); .and_then(|s| s.ok_or(UseStorageError::StorageReturnedNone));
let storage = handle_error(&on_error, storage); let storage = handle_error(&on_error, storage);
@ -226,7 +227,7 @@ where
) )
.expect("failed to create custom storage event"), .expect("failed to create custom storage event"),
) )
.map_err(UseStorageError::NotifyItemChangedFailed); .map_err(|e| UseStorageError::NotifyItemChangedFailed(SendWrapper::new(e)));
let _ = handle_error(&on_error, result); let _ = handle_error(&on_error, result);
}) })
} }
@ -238,14 +239,14 @@ where
let key = key.as_ref().to_owned(); let key = key.as_ref().to_owned();
let on_error = on_error.to_owned(); let on_error = on_error.to_owned();
move || { SendWrapper::new(move || {
let fetched = storage let fetched = storage
.to_owned() .to_owned()
.and_then(|storage| { .and_then(|storage| {
// Get directly from storage // Get directly from storage
let result = storage let result = storage
.get_item(&key) .get_item(&key)
.map_err(UseStorageError::GetItemFailed); .map_err(|e| UseStorageError::GetItemFailed(SendWrapper::new(e)));
handle_error(&on_error, result) handle_error(&on_error, result)
}) })
.unwrap_or_default() // Drop handled Err(()) .unwrap_or_default() // Drop handled Err(())
@ -270,14 +271,18 @@ where
// Revert to default // Revert to default
None => set_data.set(default.clone()), None => set_data.set(default.clone()),
}; };
} })
}; };
// Fires when storage needs to be fetched // Fires when storage needs to be fetched
let notify = Trigger::new(); let notify = ArcTrigger::new();
// Refetch from storage. Keeps track of how many times we've been notified. Does not increment for calls to set_data // Refetch from storage. Keeps track of how many times we've been notified. Does not increment for calls to set_data
let notify_id = Memo::<usize>::new(move |prev| { let notify_id = Memo::<usize>::new({
let notify = notify.clone();
let fetch_from_storage = fetch_from_storage.clone();
move |prev| {
notify.track(); notify.track();
match prev { match prev {
None => 1, // Avoid async fetch of initial value None => 1, // Avoid async fetch of initial value
@ -286,6 +291,7 @@ where
prev + 1 prev + 1
} }
} }
}
}); });
// Set item on internal (non-event) page changes to the data signal // Set item on internal (non-event) page changes to the data signal
@ -308,9 +314,9 @@ where
.map_err(|e| UseStorageError::ItemCodecError(CodecError::Encode(e))) .map_err(|e| UseStorageError::ItemCodecError(CodecError::Encode(e)))
.and_then(|enc_value| { .and_then(|enc_value| {
// Set storage -- sends a global event // Set storage -- sends a global event
storage storage.set_item(&key, &enc_value).map_err(|e| {
.set_item(&key, &enc_value) UseStorageError::SetItemFailed(SendWrapper::new(e))
.map_err(UseStorageError::SetItemFailed) })
}); });
let result = handle_error(&on_error, result); let result = handle_error(&on_error, result);
// Send internal storage event // Send internal storage event
@ -323,31 +329,42 @@ where
); );
} }
// Fetch initial value // TODO: solve for 0.7
if delay_during_hydration && leptos::leptos_dom::HydrationCtx::is_hydrating() { // // Fetch initial value
request_animation_frame(fetch_from_storage.clone()); // if delay_during_hydration && leptos::leptos_dom::HydrationCtx::is_hydrating() {
} else { // request_animation_frame(fetch_from_storage.clone());
fetch_from_storage(); // } else {
} // fetch_from_storage();
// }
request_animation_frame({
let fetch_from_storage = fetch_from_storage.clone();
#[allow(clippy::redundant_closure)]
move || fetch_from_storage()
});
if listen_to_storage_changes { if listen_to_storage_changes {
let check_key = key.as_ref().to_owned(); let check_key = key.as_ref().to_owned();
let storage_notify = notify.clone();
let custom_notify = notify.clone();
// Listen to global storage events // Listen to global storage events
let _ = use_event_listener(use_window(), leptos::ev::storage, move |ev| { let _ = use_event_listener(use_window(), leptos::ev::storage, move |ev| {
let ev_key = ev.key(); let ev_key = ev.key();
// Key matches or all keys deleted (None) // Key matches or all keys deleted (None)
if ev_key == Some(check_key.clone()) || ev_key.is_none() { if ev_key == Some(check_key.clone()) || ev_key.is_none() {
notify.notify() storage_notify.trigger()
} }
}); });
// Listen to internal storage events // Listen to internal storage events
let check_key = key.as_ref().to_owned(); let check_key = key.as_ref().to_owned();
let _ = use_event_listener( let _ = use_event_listener(
use_window(), use_window(),
ev::Custom::new(INTERNAL_STORAGE_EVENT), leptos::ev::Custom::new(INTERNAL_STORAGE_EVENT),
{
move |ev: web_sys::CustomEvent| { move |ev: web_sys::CustomEvent| {
if Some(check_key.clone()) == ev.detail().as_string() { if Some(check_key.clone()) == ev.detail().as_string() {
notify.notify() custom_notify.trigger()
}
} }
}, },
); );
@ -361,9 +378,9 @@ where
// Delete directly from storage // Delete directly from storage
let result = storage let result = storage
.remove_item(&key) .remove_item(&key)
.map_err(UseStorageError::RemoveItemFailed); .map_err(|e| UseStorageError::RemoveItemFailed(SendWrapper::new(e)));
let _ = handle_error(&on_error, result); let _ = handle_error(&on_error, result);
notify.notify(); notify.trigger();
dispatch_storage_event(); dispatch_storage_event();
}); });
} }
@ -377,17 +394,17 @@ where
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum UseStorageError<E, D> { pub enum UseStorageError<E, D> {
#[error("storage not available")] #[error("storage not available")]
StorageNotAvailable(JsValue), StorageNotAvailable(SendWrapper<JsValue>),
#[error("storage not returned from window")] #[error("storage not returned from window")]
StorageReturnedNone, StorageReturnedNone,
#[error("failed to get item")] #[error("failed to get item")]
GetItemFailed(JsValue), GetItemFailed(SendWrapper<JsValue>),
#[error("failed to set item")] #[error("failed to set item")]
SetItemFailed(JsValue), SetItemFailed(SendWrapper<JsValue>),
#[error("failed to delete item")] #[error("failed to delete item")]
RemoveItemFailed(JsValue), RemoveItemFailed(SendWrapper<JsValue>),
#[error("failed to notify item changed")] #[error("failed to notify item changed")]
NotifyItemChangedFailed(JsValue), NotifyItemChangedFailed(SendWrapper<JsValue>),
#[error("failed to encode / decode item value")] #[error("failed to encode / decode item value")]
ItemCodecError(CodecError<E, D>), ItemCodecError(CodecError<E, D>),
} }
@ -400,7 +417,7 @@ where
{ {
// Callback for when an error occurs // Callback for when an error occurs
#[builder(skip)] #[builder(skip)]
on_error: Arc<dyn Fn(UseStorageError<E, D>)>, on_error: Arc<dyn Fn(UseStorageError<E, D>) + Send + Sync>,
// Whether to continuously listen to changes from browser storage // Whether to continuously listen to changes from browser storage
listen_to_storage_changes: bool, listen_to_storage_changes: bool,
// Initial value to use when the storage key is not set // Initial value to use when the storage key is not set
@ -418,7 +435,7 @@ where
/// Calls the on_error callback with the given error. Removes the error from the Result to avoid double error handling. /// Calls the on_error callback with the given error. Removes the error from the Result to avoid double error handling.
#[cfg(not(feature = "ssr"))] #[cfg(not(feature = "ssr"))]
fn handle_error<T, E, D>( fn handle_error<T, E, D>(
on_error: &Arc<dyn Fn(UseStorageError<E, D>)>, on_error: &Arc<dyn Fn(UseStorageError<E, D>) + Send + Sync>,
result: Result<T, UseStorageError<E, D>>, result: Result<T, UseStorageError<E, D>>,
) -> Result<T, ()> { ) -> Result<T, ()> {
result.map_err(|err| (on_error)(err)) result.map_err(|err| (on_error)(err))
@ -438,7 +455,10 @@ impl<T: Default, E, D> Default for UseStorageOptions<T, E, D> {
impl<T: Default, E, D> UseStorageOptions<T, E, D> { impl<T: Default, E, D> UseStorageOptions<T, E, D> {
/// Optional callback whenever an error occurs. /// Optional callback whenever an error occurs.
pub fn on_error(self, on_error: impl Fn(UseStorageError<E, D>) + 'static) -> Self { pub fn on_error(
self,
on_error: impl Fn(UseStorageError<E, D>) + Send + Sync + 'static,
) -> Self {
Self { Self {
on_error: Arc::new(on_error), on_error: Arc::new(on_error),
..self ..self

View file

@ -9,8 +9,8 @@ use leptos::prelude::wrappers::read::Signal;
use leptos::prelude::*; use leptos::prelude::*;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::rc::Rc;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
/// Reactive color mode (dark / light / customs) with auto data persistence. /// Reactive color mode (dark / light / customs) with auto data persistence.
@ -209,7 +209,7 @@ where
if initial_value_from_url_param_to_storage { if initial_value_from_url_param_to_storage {
set_store.set(value); set_store.set(value);
} else { } else {
set_store.set_untracked(value); *set_store.write_untracked() = value;
} }
} }
@ -279,7 +279,7 @@ where
}; };
let on_changed = move |mode: ColorMode| { let on_changed = move |mode: ColorMode| {
on_changed(mode, Rc::new(default_on_changed.clone())); on_changed(mode, Arc::new(default_on_changed.clone()));
}; };
Effect::new({ Effect::new({
@ -427,7 +427,7 @@ where
/// To get the default behaviour back you can call the provided `default_handler` function. /// To get the default behaviour back you can call the provided `default_handler` function.
/// It takes two parameters: /// It takes two parameters:
/// - `mode: ColorMode`: The color mode to change to. /// - `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. /// -`default_handler: Arc<dyn Fn(ColorMode)>`: The default handler that would have been called if the `on_changed` handler had not been specified.
on_changed: OnChangedFn, on_changed: OnChangedFn,
/// When provided, `useStorage` will be skipped. /// When provided, `useStorage` will be skipped.
@ -476,7 +476,7 @@ where
_marker: PhantomData<T>, _marker: PhantomData<T>,
} }
type OnChangedFn = Rc<dyn Fn(ColorMode, Rc<dyn Fn(ColorMode)>)>; type OnChangedFn = Arc<dyn Fn(ColorMode, Arc<dyn Fn(ColorMode) + Send + Sync>) + Send + Sync>;
impl Default for UseColorModeOptions<&'static str, web_sys::Element> { impl Default for UseColorModeOptions<&'static str, web_sys::Element> {
fn default() -> Self { fn default() -> Self {
@ -487,7 +487,7 @@ impl Default for UseColorModeOptions<&'static str, web_sys::Element> {
initial_value_from_url_param: None, initial_value_from_url_param: None,
initial_value_from_url_param_to_storage: false, initial_value_from_url_param_to_storage: false,
custom_modes: vec![], custom_modes: vec![],
on_changed: Rc::new(move |mode, default_handler| (default_handler)(mode)), on_changed: Arc::new(move |mode, default_handler| (default_handler)(mode)),
storage_signal: None, storage_signal: None,
storage_key: "leptos-use-color-scheme".into(), storage_key: "leptos-use-color-scheme".into(),
storage: StorageType::default(), storage: StorageType::default(),

View file

@ -7,7 +7,7 @@ pub use cookie::SameSite;
use cookie::{Cookie, CookieJar}; use cookie::{Cookie, CookieJar};
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::*; use leptos::prelude::*;
use std::rc::Rc; use std::sync::Arc;
/// SSR-friendly and reactive cookie access. /// SSR-friendly and reactive cookie access.
/// ///
@ -143,7 +143,7 @@ use std::rc::Rc;
pub fn use_cookie<T, C>(cookie_name: &str) -> (Signal<Option<T>>, WriteSignal<Option<T>>) pub fn use_cookie<T, C>(cookie_name: &str) -> (Signal<Option<T>>, WriteSignal<Option<T>>)
where where
C: Encoder<T, Encoded = String> + Decoder<T, Encoded = str>, C: Encoder<T, Encoded = String> + Decoder<T, Encoded = str>,
T: Clone, T: Clone + Send + Sync,
{ {
use_cookie_with_options::<T, C>(cookie_name, UseCookieOptions::default()) use_cookie_with_options::<T, C>(cookie_name, UseCookieOptions::default())
} }
@ -155,7 +155,7 @@ pub fn use_cookie_with_options<T, C>(
) -> (Signal<Option<T>>, WriteSignal<Option<T>>) ) -> (Signal<Option<T>>, WriteSignal<Option<T>>)
where where
C: Encoder<T, Encoded = String> + Decoder<T, Encoded = str>, C: Encoder<T, Encoded = String> + Decoder<T, Encoded = str>,
T: Clone, T: Clone + Send + Sync,
{ {
let UseCookieOptions { let UseCookieOptions {
max_age, max_age,
@ -189,7 +189,7 @@ where
let jar = StoredValue::new(CookieJar::new()); let jar = StoredValue::new(CookieJar::new());
if !has_expired { if !has_expired {
let ssr_cookies_header_getter = Rc::clone(&ssr_cookies_header_getter); let ssr_cookies_header_getter = Arc::clone(&ssr_cookies_header_getter);
jar.update_value(|jar| { jar.update_value(|jar| {
if let Some(new_jar) = load_and_parse_cookie_jar(ssr_cookies_header_getter) { if let Some(new_jar) = load_and_parse_cookie_jar(ssr_cookies_header_getter) {
@ -229,8 +229,8 @@ where
let on_cookie_change = { let on_cookie_change = {
let cookie_name = cookie_name.to_owned(); let cookie_name = cookie_name.to_owned();
let ssr_cookies_header_getter = Rc::clone(&ssr_cookies_header_getter); let ssr_cookies_header_getter = Arc::clone(&ssr_cookies_header_getter);
let on_error = Rc::clone(&on_error); let on_error = Arc::clone(&on_error);
let domain = domain.clone(); let domain = domain.clone();
let path = path.clone(); let path = path.clone();
@ -265,7 +265,7 @@ where
same_site, same_site,
secure, secure,
http_only, http_only,
Rc::clone(&ssr_cookies_header_getter), Arc::clone(&ssr_cookies_header_getter),
); );
}); });
@ -288,7 +288,7 @@ where
// listen to cookie changes from the broadcast channel // listen to cookie changes from the broadcast channel
Effect::new({ Effect::new({
let ssr_cookies_header_getter = Rc::clone(&ssr_cookies_header_getter); let ssr_cookies_header_getter = Arc::clone(&ssr_cookies_header_getter);
let cookie_name = cookie_name.to_owned(); let cookie_name = cookie_name.to_owned();
move |_| { move |_| {
@ -299,7 +299,7 @@ where
match C::decode(&message) { match C::decode(&message) {
Ok(value) => { Ok(value) => {
let ssr_cookies_header_getter = let ssr_cookies_header_getter =
Rc::clone(&ssr_cookies_header_getter); Arc::clone(&ssr_cookies_header_getter);
jar.update_value(|jar| { jar.update_value(|jar| {
update_client_cookie_jar( update_client_cookie_jar(
@ -325,7 +325,7 @@ where
} }
} else { } else {
let cookie_name = cookie_name.clone(); let cookie_name = cookie_name.clone();
let ssr_cookies_header_getter = Rc::clone(&ssr_cookies_header_getter); let ssr_cookies_header_getter = Arc::clone(&ssr_cookies_header_getter);
jar.update_value(|jar| { jar.update_value(|jar| {
update_client_cookie_jar( update_client_cookie_jar(
@ -467,14 +467,14 @@ pub struct UseCookieOptions<T, E, D> {
/// Getter function to return the string value of the cookie header. /// Getter function to return the string value of the cookie header.
/// When you use one of the features `"axum"`, `"actix"` or `"spin"` there's a valid default implementation provided. /// When you use one of the features `"axum"`, `"actix"` or `"spin"` there's a valid default implementation provided.
ssr_cookies_header_getter: Rc<dyn Fn() -> Option<String>>, ssr_cookies_header_getter: Arc<dyn Fn() -> Option<String> + Send + Sync>,
/// Function to add a set cookie header to the response on the server. /// Function to add a set cookie header to the response on the server.
/// When you use one of the features `"axum"`, `"actix"` or `"spin"` there's a valid default implementation provided. /// When you use one of the features `"axum"`, `"actix"` or `"spin"` there's a valid default implementation provided.
ssr_set_cookie: Rc<dyn Fn(&Cookie)>, ssr_set_cookie: Arc<dyn Fn(&Cookie) + Send + Sync>,
/// Callback for encoding/decoding errors. Defaults to logging the error to the console. /// Callback for encoding/decoding errors. Defaults to logging the error to the console.
on_error: Rc<dyn Fn(CodecError<E, D>)>, on_error: Arc<dyn Fn(CodecError<E, D>) + Send + Sync>,
} }
impl<T, E, D> Default for UseCookieOptions<T, E, D> { impl<T, E, D> Default for UseCookieOptions<T, E, D> {
@ -490,7 +490,7 @@ impl<T, E, D> Default for UseCookieOptions<T, E, D> {
domain: None, domain: None,
path: None, path: None,
same_site: None, same_site: None,
ssr_cookies_header_getter: Rc::new(move || { ssr_cookies_header_getter: Arc::new(move || {
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
{ {
#[cfg(all(feature = "actix", feature = "axum"))] #[cfg(all(feature = "actix", feature = "axum"))]
@ -564,7 +564,7 @@ impl<T, E, D> Default for UseCookieOptions<T, E, D> {
#[cfg(not(feature = "ssr"))] #[cfg(not(feature = "ssr"))]
None None
}), }),
ssr_set_cookie: Rc::new(|cookie: &Cookie| { ssr_set_cookie: Arc::new(|cookie: &Cookie| {
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
{ {
#[cfg(feature = "actix")] #[cfg(feature = "actix")]
@ -615,7 +615,7 @@ impl<T, E, D> Default for UseCookieOptions<T, E, D> {
let _ = cookie; let _ = cookie;
}), }),
on_error: Rc::new(|_| { on_error: Arc::new(|_| {
error!("cookie (de-/)serialization error"); error!("cookie (de-/)serialization error");
}), }),
} }
@ -623,7 +623,7 @@ impl<T, E, D> Default for UseCookieOptions<T, E, D> {
} }
fn read_cookies_string( fn read_cookies_string(
ssr_cookies_header_getter: Rc<dyn Fn() -> Option<String>>, ssr_cookies_header_getter: Arc<dyn Fn() -> Option<String> + Send + Sync>,
) -> Option<String> { ) -> Option<String> {
let cookies; let cookies;
@ -646,55 +646,63 @@ fn read_cookies_string(
cookies cookies
} }
fn handle_expiration<T>(delay: Option<i64>, set_cookie: WriteSignal<Option<T>>) { fn handle_expiration<T>(delay: Option<i64>, set_cookie: WriteSignal<Option<T>>)
where
T: Send + Sync + 'static,
{
if let Some(delay) = delay { if let Some(delay) = delay {
#[cfg(not(feature = "ssr"))] #[cfg(not(feature = "ssr"))]
{ {
use leptos::leptos_dom::helpers::TimeoutHandle; use leptos::leptos_dom::helpers::TimeoutHandle;
use std::cell::Cell; use std::sync::{atomic::AtomicI32, Mutex};
use std::cell::RefCell;
// The maximum value allowed on a timeout delay. // The maximum value allowed on a timeout delay.
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#maximum_delay_value // Reference: https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#maximum_delay_value
const MAX_TIMEOUT_DELAY: i64 = 2_147_483_647; const MAX_TIMEOUT_DELAY: i64 = 2_147_483_647;
let timeout = Rc::new(Cell::new(None::<TimeoutHandle>)); let timeout = Arc::new(Mutex::new(None::<TimeoutHandle>));
let elapsed = Rc::new(Cell::new(0)); let elapsed = Arc::new(AtomicI32::new(0));
on_cleanup({ on_cleanup({
let timeout = Rc::clone(&timeout); let timeout = Arc::clone(&timeout);
move || { move || {
if let Some(timeout) = timeout.take() { if let Some(timeout) = timeout.lock().unwrap().take() {
timeout.clear(); timeout.clear();
} }
} }
}); });
let create_expiration_timeout = Rc::new(RefCell::new(None::<Box<dyn Fn()>>)); let create_expiration_timeout =
Arc::new(Mutex::new(None::<Box<dyn Fn() + Send + Sync>>));
create_expiration_timeout.replace(Some(Box::new({ *create_expiration_timeout.lock().unwrap() = Some(Box::new({
let timeout = Rc::clone(&timeout); let timeout = Arc::clone(&timeout);
let elapsed = Rc::clone(&elapsed); let elapsed = Arc::clone(&elapsed);
let create_expiration_timeout = Rc::clone(&create_expiration_timeout); let create_expiration_timeout = Arc::clone(&create_expiration_timeout);
move || { move || {
if let Some(timeout) = timeout.take() { if let Some(timeout) = timeout.lock().unwrap().take() {
timeout.clear(); timeout.clear();
} }
let time_remaining = delay - elapsed.get(); let time_remaining =
delay - elapsed.load(std::sync::atomic::Ordering::Relaxed) as i64;
let timeout_length = time_remaining.min(MAX_TIMEOUT_DELAY); let timeout_length = time_remaining.min(MAX_TIMEOUT_DELAY);
let elapsed = Rc::clone(&elapsed); let elapsed = Arc::clone(&elapsed);
let create_expiration_timeout = Rc::clone(&create_expiration_timeout); let create_expiration_timeout = Arc::clone(&create_expiration_timeout);
timeout.set( *timeout.lock().unwrap() = set_timeout_with_handle(
set_timeout_with_handle(
move || { move || {
elapsed.set(elapsed.get() + timeout_length); let elapsed = elapsed.fetch_add(
if elapsed.get() < delay { timeout_length as i32,
std::sync::atomic::Ordering::Relaxed,
) as i64
+ timeout_length;
if elapsed < delay {
if let Some(create_expiration_timeout) = if let Some(create_expiration_timeout) =
&*create_expiration_timeout.borrow() create_expiration_timeout.lock().unwrap().as_ref()
{ {
create_expiration_timeout(); create_expiration_timeout();
} }
@ -705,12 +713,13 @@ fn handle_expiration<T>(delay: Option<i64>, set_cookie: WriteSignal<Option<T>>)
}, },
std::time::Duration::from_millis(timeout_length as u64), std::time::Duration::from_millis(timeout_length as u64),
) )
.ok(), .ok();
);
} }
}))); }));
if let Some(create_expiration_timeout) = &*create_expiration_timeout.borrow() { if let Some(create_expiration_timeout) =
create_expiration_timeout.lock().unwrap().as_ref()
{
create_expiration_timeout(); create_expiration_timeout();
}; };
} }
@ -735,7 +744,7 @@ fn write_client_cookie(
same_site: Option<SameSite>, same_site: Option<SameSite>,
secure: bool, secure: bool,
http_only: bool, http_only: bool,
ssr_cookies_header_getter: Rc<dyn Fn() -> Option<String>>, ssr_cookies_header_getter: Arc<dyn Fn() -> Option<String> + Send + Sync>,
) { ) {
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
@ -771,7 +780,7 @@ fn update_client_cookie_jar(
same_site: Option<SameSite>, same_site: Option<SameSite>,
secure: bool, secure: bool,
http_only: bool, http_only: bool,
ssr_cookies_header_getter: Rc<dyn Fn() -> Option<String>>, ssr_cookies_header_getter: Arc<dyn Fn() -> Option<String> + Send + Sync>,
) { ) {
if let Some(new_jar) = load_and_parse_cookie_jar(ssr_cookies_header_getter) { if let Some(new_jar) = load_and_parse_cookie_jar(ssr_cookies_header_getter) {
*jar = new_jar; *jar = new_jar;
@ -859,7 +868,7 @@ fn write_server_cookie(
same_site: Option<SameSite>, same_site: Option<SameSite>,
secure: bool, secure: bool,
http_only: bool, http_only: bool,
ssr_set_cookie: Rc<dyn Fn(&Cookie)>, ssr_set_cookie: Arc<dyn Fn(&Cookie) + Send + Sync>,
) { ) {
if let Some(value) = value { if let Some(value) = value {
let cookie: Cookie = build_cookie_from_options( let cookie: Cookie = build_cookie_from_options(
@ -877,7 +886,7 @@ fn write_server_cookie(
} }
fn load_and_parse_cookie_jar( fn load_and_parse_cookie_jar(
ssr_cookies_header_getter: Rc<dyn Fn() -> Option<String>>, ssr_cookies_header_getter: Arc<dyn Fn() -> Option<String> + Send + Sync>,
) -> Option<CookieJar> { ) -> Option<CookieJar> {
read_cookies_string(ssr_cookies_header_getter).map(|cookies| { read_cookies_string(ssr_cookies_header_getter).map(|cookies| {
let mut jar = CookieJar::new(); let mut jar = CookieJar::new();

View file

@ -1,5 +1,6 @@
use cfg_if::cfg_if; use cfg_if::cfg_if;
use leptos::prelude::wrappers::read::Signal; use leptos::prelude::wrappers::read::Signal;
use send_wrapper::SendWrapper;
/// Reactive [DeviceOrientationEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent). /// Reactive [DeviceOrientationEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent).
/// Provide web developers with information from the physical orientation of /// Provide web developers with information from the physical orientation of
@ -67,7 +68,8 @@ pub fn use_device_orientation() -> UseDeviceOrientationReturn {
.once(false), .once(false),
); );
on_cleanup(cleanup); let cleanup = SendWrapper::new(cleanup);
on_cleanup(move || cleanup());
} }
}} }}

View file

@ -90,12 +90,12 @@ where
let update_files = move |event: &web_sys::DragEvent| { let update_files = move |event: &web_sys::DragEvent| {
if let Some(data_transfer) = event.data_transfer() { if let Some(data_transfer) = event.data_transfer() {
let files: Vec<web_sys::File> = data_transfer let files: Vec<_> = data_transfer
.files() .files()
.map(|f| js_sys::Array::from(&f).to_vec()) .map(|f| js_sys::Array::from(&f).to_vec())
.unwrap_or_default() .unwrap_or_default()
.into_iter() .into_iter()
.map(web_sys::File::from) .map(|f| SendWrapper::new(web_sys::File::from(f)))
.collect(); .collect();
set_files.update(move |f| *f = files); set_files.update(move |f| *f = files);
@ -113,7 +113,11 @@ where
let _z = SpecialNonReactiveZone::enter(); let _z = SpecialNonReactiveZone::enter();
on_enter(UseDropZoneEvent { on_enter(UseDropZoneEvent {
files: files.get_untracked(), files: files
.get_untracked()
.into_iter()
.map(SendWrapper::take)
.collect(),
event, event,
}); });
}); });
@ -126,7 +130,11 @@ where
let _z = SpecialNonReactiveZone::enter(); let _z = SpecialNonReactiveZone::enter();
on_over(UseDropZoneEvent { on_over(UseDropZoneEvent {
files: files.get_untracked(), files: files
.get_untracked()
.into_iter()
.map(SendWrapper::take)
.collect(),
event, event,
}); });
}); });
@ -144,7 +152,11 @@ where
let _z = SpecialNonReactiveZone::enter(); let _z = SpecialNonReactiveZone::enter();
on_leave(UseDropZoneEvent { on_leave(UseDropZoneEvent {
files: files.get_untracked(), files: files
.get_untracked()
.into_iter()
.map(SendWrapper::take)
.collect(),
event, event,
}); });
}); });
@ -160,7 +172,11 @@ where
let _z = SpecialNonReactiveZone::enter(); let _z = SpecialNonReactiveZone::enter();
on_drop(UseDropZoneEvent { on_drop(UseDropZoneEvent {
files: files.get_untracked(), files: files
.get_untracked()
.into_iter()
.map(SendWrapper::take)
.collect(),
event, event,
}); });
}); });

View file

@ -5,8 +5,8 @@ use default_struct_builder::DefaultBuilder;
use leptos::prelude::diagnostics::SpecialNonReactiveZone; use leptos::prelude::diagnostics::SpecialNonReactiveZone;
use leptos::prelude::*; use leptos::prelude::*;
use send_wrapper::SendWrapper; use send_wrapper::SendWrapper;
use std::cell::Cell;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::sync::atomic::{AtomicBool, AtomicU32};
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use thiserror::Error; use thiserror::Error;
@ -154,8 +154,8 @@ where
let (event_source, set_event_source) = signal(None::<SendWrapper<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 (error, set_error) = signal(None::<UseEventSourceError<C::Error>>);
let explicitly_closed = Arc::new(Cell::new(false)); let explicitly_closed = Arc::new(AtomicBool::new(false));
let retried = Arc::new(Cell::new(0)); let retried = Arc::new(AtomicU32::new(0));
let set_data_from_string = move |data_string: Option<String>| { let set_data_from_string = move |data_string: Option<String>| {
if let Some(data_string) = data_string { if let Some(data_string) = data_string {
@ -174,7 +174,7 @@ where
event_source.close(); event_source.close();
set_event_source.set(None); set_event_source.set(None);
set_ready_state.set(ConnectionReadyState::Closed); set_ready_state.set(ConnectionReadyState::Closed);
explicitly_closed.set(true); explicitly_closed.store(true, std::sync::atomic::Ordering::Relaxed);
} }
} }
}; };
@ -188,7 +188,7 @@ where
move || { move || {
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
if explicitly_closed.get() { if explicitly_closed.load(std::sync::atomic::Ordering::Relaxed) {
return; return;
} }
@ -222,14 +222,15 @@ where
// only reconnect if EventSource isn't reconnecting by itself // only reconnect if EventSource isn't reconnecting by itself
// this is the case when the connection is closed (readyState is 2) // this is the case when the connection is closed (readyState is 2)
if es.ready_state() == 2 if es.ready_state() == 2
&& !explicitly_closed.get() && !explicitly_closed.load(std::sync::atomic::Ordering::Relaxed)
&& matches!(reconnect_limit, ReconnectLimit::Limited(_)) && matches!(reconnect_limit, ReconnectLimit::Limited(_))
{ {
es.close(); es.close();
retried.set(retried.get() + 1); let retried_value =
retried.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1;
if reconnect_limit.is_exceeded_by(retried.get()) { if reconnect_limit.is_exceeded_by(retried_value as u64) {
set_timeout( set_timeout(
move || { move || {
if let Some(init) = init.get_value() { if let Some(init) = init.get_value() {
@ -281,8 +282,8 @@ where
move || { move || {
close(); close();
explicitly_closed.set(false); explicitly_closed.store(false, std::sync::atomic::Ordering::Relaxed);
retried.set(0); retried.store(0, std::sync::atomic::Ordering::Relaxed);
if let Some(init) = init.get_value() { if let Some(init) = init.get_value() {
init(); init();
} }
@ -326,7 +327,7 @@ where
reconnect_interval: u64, reconnect_interval: u64,
/// On maximum retry times reached. /// On maximum retry times reached.
on_failed: Arc<dyn Fn()>, on_failed: Arc<dyn Fn() + Send + Sync>,
/// If `true` the `EventSource` connection will immediately be opened when calling this function. /// If `true` the `EventSource` connection will immediately be opened when calling this function.
/// If `false` you have to manually call the `open` function. /// If `false` you have to manually call the `open` function.

View file

@ -9,8 +9,9 @@ use gloo_timers::future::sleep;
use leptos::prelude::diagnostics::SpecialNonReactiveZone; use leptos::prelude::diagnostics::SpecialNonReactiveZone;
use leptos::prelude::wrappers::read::Signal; use leptos::prelude::wrappers::read::Signal;
use leptos::prelude::*; use leptos::prelude::*;
use send_wrapper::SendWrapper;
use std::future::Future; use std::future::Future;
use std::rc::Rc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
@ -55,7 +56,7 @@ pub fn use_infinite_scroll<El, T, LFn, LFut>(el: El, on_load_more: LFn) -> Signa
where where
El: Into<ElementMaybeSignal<T, web_sys::Element>> + Clone + 'static, El: Into<ElementMaybeSignal<T, web_sys::Element>> + Clone + 'static,
T: Into<web_sys::Element> + Clone + 'static, T: Into<web_sys::Element> + Clone + 'static,
LFn: Fn(ScrollState) -> LFut + 'static, LFn: Fn(ScrollState) -> LFut + Send + Sync + 'static,
LFut: Future<Output = ()>, LFut: Future<Output = ()>,
{ {
use_infinite_scroll_with_options(el, on_load_more, UseInfiniteScrollOptions::default()) use_infinite_scroll_with_options(el, on_load_more, UseInfiniteScrollOptions::default())
@ -70,7 +71,7 @@ pub fn use_infinite_scroll_with_options<El, T, LFn, LFut>(
where where
El: Into<ElementMaybeSignal<T, web_sys::Element>> + Clone + 'static, El: Into<ElementMaybeSignal<T, web_sys::Element>> + Clone + 'static,
T: Into<web_sys::Element> + Clone + 'static, T: Into<web_sys::Element> + Clone + 'static,
LFn: Fn(ScrollState) -> LFut + 'static, LFn: Fn(ScrollState) -> LFut + Send + Sync + 'static,
LFut: Future<Output = ()>, LFut: Future<Output = ()>,
{ {
let UseInfiniteScrollOptions { let UseInfiniteScrollOptions {
@ -117,20 +118,22 @@ where
let el = el.into(); let el = el.into();
if el.is_instance_of::<web_sys::Window>() || el.is_instance_of::<web_sys::Document>() { if el.is_instance_of::<web_sys::Window>() || el.is_instance_of::<web_sys::Document>() {
SendWrapper::new(
document() document()
.document_element() .document_element()
.expect("document element not found") .expect("document element not found"),
)
} else { } else {
el SendWrapper::new(el)
} }
}) })
}); });
let is_element_visible = use_element_visibility(observed_element); let is_element_visible = use_element_visibility(observed_element);
let check_and_load = StoredValue::new(None::<Rc<dyn Fn()>>); let check_and_load = StoredValue::new(None::<Arc<dyn Fn() + Send + Sync>>);
check_and_load.set_value(Some(Rc::new({ check_and_load.set_value(Some(Arc::new({
let measure = measure.clone(); let measure = measure.clone();
move || { move || {
@ -215,7 +218,7 @@ where
#[derive(DefaultBuilder)] #[derive(DefaultBuilder)]
pub struct UseInfiniteScrollOptions { pub struct UseInfiniteScrollOptions {
/// Callback when scrolling is happening. /// Callback when scrolling is happening.
on_scroll: Rc<dyn Fn(web_sys::Event)>, on_scroll: Arc<dyn Fn(web_sys::Event) + Send + Sync>,
/// Options passed to the `addEventListener("scroll", ...)` call /// Options passed to the `addEventListener("scroll", ...)` call
event_listener_options: UseEventListenerOptions, event_listener_options: UseEventListenerOptions,
@ -233,7 +236,7 @@ pub struct UseInfiniteScrollOptions {
impl Default for UseInfiniteScrollOptions { impl Default for UseInfiniteScrollOptions {
fn default() -> Self { fn default() -> Self {
Self { Self {
on_scroll: Rc::new(|_| {}), on_scroll: Arc::new(|_| {}),
event_listener_options: Default::default(), event_listener_options: Default::default(),
distance: 0.0, distance: 0.0,
direction: Direction::Bottom, direction: Direction::Bottom,

View file

@ -3,12 +3,14 @@ use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::wrappers::read::Signal; use leptos::prelude::wrappers::read::Signal;
use leptos::prelude::*; use leptos::prelude::*;
use send_wrapper::SendWrapper;
use std::marker::PhantomData; use std::marker::PhantomData;
cfg_if! { if #[cfg(not(feature = "ssr"))] { cfg_if! { if #[cfg(not(feature = "ssr"))] {
use crate::{watch_with_options, WatchOptions}; use crate::{watch_with_options, WatchOptions};
use std::cell::RefCell; // use std::cell::RefCell;
use std::rc::Rc; // use std::rc::Rc;
use std::sync::{Arc, Mutex};
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
}} }}
@ -116,14 +118,14 @@ where
) )
.into_js_value(); .into_js_value();
let observer: Rc<RefCell<Option<web_sys::IntersectionObserver>>> = let observer: Arc<Mutex<Option<SendWrapper<web_sys::IntersectionObserver>>>> =
Rc::new(RefCell::new(None)); Arc::new(Mutex::new(None));
let cleanup = { let cleanup = {
let obsserver = Rc::clone(&observer); let observer = Arc::clone(&observer);
move || { move || {
if let Some(o) = obsserver.take() { if let Some(o) = observer.lock().unwrap().take() {
o.disconnect(); o.disconnect();
} }
} }
@ -173,11 +175,11 @@ where
.expect("failed to create IntersectionObserver"); .expect("failed to create IntersectionObserver");
for target in targets.iter().flatten() { for target in targets.iter().flatten() {
let target: web_sys::Element = target.clone().into(); let target: web_sys::Element = target.clone().take().into();
obs.observe(&target); obs.observe(&target);
} }
observer.replace(Some(obs)); *observer.lock().unwrap() = Some(SendWrapper::new(obs));
}, },
WatchOptions::default().immediate(immediate), WatchOptions::default().immediate(immediate),
) )

View file

@ -6,6 +6,7 @@ use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::wrappers::read::Signal; use leptos::prelude::wrappers::read::Signal;
use leptos::prelude::*; use leptos::prelude::*;
use send_wrapper::SendWrapper;
use std::fmt::Display; use std::fmt::Display;
use wasm_bindgen::{JsCast, JsValue}; use wasm_bindgen::{JsCast, JsValue};
@ -167,7 +168,7 @@ pub fn use_intl_number_format(options: UseIntlNumberFormatOptions) -> UseIntlNum
); );
UseIntlNumberFormatReturn { UseIntlNumberFormatReturn {
js_intl_number_format: number_format, js_intl_number_format: SendWrapper::new(number_format),
} }
}} }}
} }
@ -768,7 +769,7 @@ cfg_if! { if #[cfg(feature = "ssr")] {
/// Return type of [`use_intl_number_format`]. /// Return type of [`use_intl_number_format`].
pub struct UseIntlNumberFormatReturn { pub struct UseIntlNumberFormatReturn {
/// The instance of [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat). /// The instance of [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat).
pub js_intl_number_format: js_sys::Intl::NumberFormat, pub js_intl_number_format: SendWrapper<js_sys::Intl::NumberFormat>,
} }
}} }}
@ -777,7 +778,7 @@ impl UseIntlNumberFormatReturn {
/// See [`use_intl_number_format`] for more information. /// See [`use_intl_number_format`] for more information.
pub fn format<N>(&self, number: impl Into<MaybeSignal<N>>) -> Signal<String> pub fn format<N>(&self, number: impl Into<MaybeSignal<N>>) -> Signal<String>
where where
N: Clone + Display + 'static, N: Clone + Display + Send + Sync + 'static,
js_sys::Number: From<N>, js_sys::Number: From<N>,
{ {
let number = number.into(); let number = number.into();
@ -854,8 +855,8 @@ impl UseIntlNumberFormatReturn {
end: impl Into<MaybeSignal<NEnd>>, end: impl Into<MaybeSignal<NEnd>>,
) -> Signal<String> ) -> Signal<String>
where where
NStart: Clone + Display + 'static, NStart: Clone + Display + Send + Sync + 'static,
NEnd: Clone + Display + 'static, NEnd: Clone + Display + Send + Sync + 'static,
js_sys::Number: From<NStart>, js_sys::Number: From<NStart>,
js_sys::Number: From<NEnd>, js_sys::Number: From<NEnd>,
{ {

View file

@ -93,7 +93,10 @@ pub fn use_media_query(query: impl Into<MaybeSignal<String>>) -> Signal<bool> {
Effect::new(move |_| update()); Effect::new(move |_| update());
on_cleanup(cleanup); on_cleanup({
let cleanup = send_wrapper::SendWrapper::new(cleanup);
move || cleanup()
});
}} }}
matches.into() matches.into()

View file

@ -2,6 +2,7 @@ use crate::core::ElementsMaybeSignal;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::wrappers::read::Signal; use leptos::prelude::wrappers::read::Signal;
use send_wrapper::SendWrapper;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
cfg_if! { if #[cfg(not(feature = "ssr"))] { cfg_if! { if #[cfg(not(feature = "ssr"))] {
@ -124,7 +125,7 @@ where
let stop_watch = { let stop_watch = {
let cleanup = cleanup.clone(); let cleanup = cleanup.clone();
leptos::watch( leptos::prelude::watch(
move || targets.get(), move || targets.get(),
move |targets, _, _| { move |targets, _, _| {
cleanup(); cleanup();
@ -135,7 +136,7 @@ where
.expect("failed to create MutationObserver"); .expect("failed to create MutationObserver");
for target in targets.iter().flatten() { for target in targets.iter().flatten() {
let target: web_sys::Element = target.clone().into(); let target: web_sys::Element = target.clone().take().into();
let _ = obs.observe_with_options(&target, &options.clone().into()); let _ = obs.observe_with_options(&target, &options.clone().into());
} }
@ -151,7 +152,10 @@ where
stop_watch(); stop_watch();
}; };
on_cleanup(stop.clone()); on_cleanup({
let stop = SendWrapper::new(stop.clone());
move || stop()
});
UseMutationObserverReturn { is_supported, stop } UseMutationObserverReturn { is_supported, stop }
} }

View file

@ -138,7 +138,7 @@ where
.expect("failed to create ResizeObserver"); .expect("failed to create ResizeObserver");
for target in targets.iter().flatten() { for target in targets.iter().flatten() {
let target: web_sys::Element = target.clone().into(); let target: web_sys::Element = target.clone().take().into();
obs.observe_with_options(&target, &options.clone().into()); obs.observe_with_options(&target, &options.clone().into());
} }
observer.replace(Some(obs)); observer.replace(Some(obs));
@ -153,7 +153,10 @@ where
stop_watch(); stop_watch();
}; };
on_cleanup(stop.clone()); on_cleanup({
let stop = send_wrapper::SendWrapper::new(stop.clone());
move || stop()
});
UseResizeObserverReturn { is_supported, stop } UseResizeObserverReturn { is_supported, stop }
} }

View file

@ -221,6 +221,8 @@ where
let set_y = |_| {}; let set_y = |_| {};
let measure = || {}; let measure = || {};
} else { } else {
use send_wrapper::SendWrapper;
let signal = element.into(); let signal = element.into();
let behavior = options.behavior; let behavior = options.behavior;
@ -372,7 +374,7 @@ where
Signal::derive(move || { Signal::derive(move || {
let element = signal.get(); let element = signal.get();
element.map(|element| element.into().unchecked_into::<web_sys::EventTarget>()) element.map(|element| SendWrapper::new(element.into().unchecked_into::<web_sys::EventTarget>()))
}) })
}; };
@ -392,14 +394,14 @@ where
let _ = use_event_listener_with_options::< let _ = use_event_listener_with_options::<
_, _,
Signal<Option<web_sys::EventTarget>>, Signal<Option<SendWrapper<web_sys::EventTarget>>>,
web_sys::EventTarget, web_sys::EventTarget,
_, _,
>(target, ev::scroll, handler, options.event_listener_options); >(target, ev::scroll, handler, options.event_listener_options);
} else { } else {
let _ = use_event_listener_with_options::< let _ = use_event_listener_with_options::<
_, _,
Signal<Option<web_sys::EventTarget>>, Signal<Option<SendWrapper<web_sys::EventTarget>>>,
web_sys::EventTarget, web_sys::EventTarget,
_, _,
>( >(
@ -412,7 +414,7 @@ where
let _ = use_event_listener_with_options::< let _ = use_event_listener_with_options::<
_, _,
Signal<Option<web_sys::EventTarget>>, Signal<Option<SendWrapper<web_sys::EventTarget>>>,
web_sys::EventTarget, web_sys::EventTarget,
_, _,
>( >(

View file

@ -256,7 +256,7 @@ fn create_action_update() -> Action<
.and_then(|ok| ok.dyn_into::<ServiceWorkerRegistration>()) .and_then(|ok| ok.dyn_into::<ServiceWorkerRegistration>())
.map(SendWrapper::new) .map(SendWrapper::new)
.map_err(SendWrapper::new), .map_err(SendWrapper::new),
Err(err) => Err(err), Err(err) => Err(SendWrapper::new(err)),
} }
} }
}, },
@ -275,8 +275,10 @@ fn create_action_create_or_update_registration() -> Action<
js_fut!(navigator.service_worker().register(script_url.as_str())) js_fut!(navigator.service_worker().register(script_url.as_str()))
.await .await
.and_then(|ok| ok.dyn_into::<ServiceWorkerRegistration>()) .and_then(|ok| ok.dyn_into::<ServiceWorkerRegistration>())
.map(SendWrapper::new)
.map_err(SendWrapper::new)
} else { } else {
Err(JsValue::from_str("no navigator")) Err(SendWrapper::new(JsValue::from_str("no navigator")))
} }
} }
}) })

View file

@ -78,7 +78,7 @@ pub fn use_sorted<S, I, T>(iterable: S) -> Signal<I>
where where
S: Into<MaybeSignal<I>>, S: Into<MaybeSignal<I>>,
T: Ord, T: Ord,
I: DerefMut<Target = [T]> + Clone + PartialEq, I: DerefMut<Target = [T]> + Clone + PartialEq + Send + Sync,
{ {
let iterable = iterable.into(); let iterable = iterable.into();
@ -93,8 +93,8 @@ where
pub fn use_sorted_by<S, I, T, F>(iterable: S, cmp_fn: F) -> Signal<I> pub fn use_sorted_by<S, I, T, F>(iterable: S, cmp_fn: F) -> Signal<I>
where where
S: Into<MaybeSignal<I>>, S: Into<MaybeSignal<I>>,
I: DerefMut<Target = [T]> + Clone + PartialEq, I: DerefMut<Target = [T]> + Clone + PartialEq + Send + Sync,
F: FnMut(&T, &T) -> Ordering + Clone + 'static, F: FnMut(&T, &T) -> Ordering + Clone + Send + Sync + 'static,
{ {
let iterable = iterable.into(); let iterable = iterable.into();
@ -109,9 +109,9 @@ where
pub fn use_sorted_by_key<S, I, T, K, F>(iterable: S, key_fn: F) -> Signal<I> pub fn use_sorted_by_key<S, I, T, K, F>(iterable: S, key_fn: F) -> Signal<I>
where where
S: Into<MaybeSignal<I>>, S: Into<MaybeSignal<I>>,
I: DerefMut<Target = [T]> + Clone + PartialEq, I: DerefMut<Target = [T]> + Clone + PartialEq + Send + Sync,
K: Ord, K: Ord,
F: FnMut(&T) -> K + Clone + 'static, F: FnMut(&T) -> K + Clone + Send + Sync + 'static,
{ {
let iterable = iterable.into(); let iterable = iterable.into();

View file

@ -20,7 +20,7 @@ use leptos::prelude::*;
/// # view! { } /// # view! { }
/// # } /// # }
/// ``` /// ```
pub fn use_supported(callback: impl Fn() -> bool + 'static) -> Signal<bool> { pub fn use_supported(callback: impl Fn() -> bool + Send + Sync + 'static) -> Signal<bool> {
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
{ {
let _ = callback; let _ = callback;

View file

@ -2,6 +2,7 @@ use crate::core::MaybeRwSignal;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::*; use leptos::prelude::*;
use send_wrapper::SendWrapper;
use wasm_bindgen::{JsCast, JsValue}; use wasm_bindgen::{JsCast, JsValue};
/// Reactive [`mediaDevices.getUserMedia`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia) streaming. /// Reactive [`mediaDevices.getUserMedia`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia) streaming.
@ -59,7 +60,8 @@ pub fn use_user_media_with_options(
let (enabled, set_enabled) = enabled.into_signal(); 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 { let _start = move || async move {
cfg_if! { if #[cfg(not(feature = "ssr"))] { cfg_if! { if #[cfg(not(feature = "ssr"))] {
@ -67,7 +69,10 @@ pub fn use_user_media_with_options(
return; return;
} }
let stream = create_media(video, audio).await; let stream = create_media(video, audio)
.await
.map(SendWrapper::new)
.map_err(SendWrapper::new);
set_stream.update(|s| *s = Some(stream)); set_stream.update(|s| *s = Some(stream));
} else { } else {
@ -187,7 +192,7 @@ where
/// Initially this is `None` until `start` resolved successfully. /// 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, /// In case the stream couldn't be started, for example because the user didn't grant permission,
/// this has the value `Some(Err(...))`. /// 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. /// Starts the screen streaming. Triggers the ask for permission if not already granted.
pub start: StartFn, pub start: StartFn,

View file

@ -149,7 +149,7 @@ pub fn use_web_notification_with_options(
notification_value.set_onerror(Some(on_error_closure.unchecked_ref())); notification_value.set_onerror(Some(on_error_closure.unchecked_ref()));
notification_value.set_onshow(Some(on_show_closure.unchecked_ref())); notification_value.set_onshow(Some(on_show_closure.unchecked_ref()));
set_notification.set(Some(notification_value)); set_notification.set(Some(SendWrapper::new(notification_value)));
}); });
} }
}; };

View file

@ -237,7 +237,7 @@ pub fn use_websocket<T, C>(
impl Fn(&T) + Clone + 'static, impl Fn(&T) + Clone + 'static,
> >
where where
T: 'static, T: Send + Sync + 'static,
C: Encoder<T> + Decoder<T>, C: Encoder<T> + Decoder<T>,
C: IsBinary<T, <C as Decoder<T>>::Encoded>, C: IsBinary<T, <C as Decoder<T>>::Encoded>,
C: HybridDecoder<T, <C as Decoder<T>>::Encoded, Error = <C as Decoder<T>>::Error>, C: HybridDecoder<T, <C as Decoder<T>>::Encoded, Error = <C as Decoder<T>>::Error>,
@ -261,7 +261,7 @@ pub fn use_websocket_with_options<T, C>(
impl Fn(&T) + Clone + 'static, impl Fn(&T) + Clone + 'static,
> >
where where
T: 'static, T: Send + Sync + 'static,
C: Encoder<T> + Decoder<T>, C: Encoder<T> + Decoder<T>,
C: IsBinary<T, <C as Decoder<T>>::Encoded>, C: IsBinary<T, <C as Decoder<T>>::Encoded>,
C: HybridDecoder<T, <C as Decoder<T>>::Encoded, Error = <C as Decoder<T>>::Error>, C: HybridDecoder<T, <C as Decoder<T>>::Encoded, Error = <C as Decoder<T>>::Error>,
@ -358,7 +358,7 @@ where
let on_open = Arc::clone(&on_open); let on_open = Arc::clone(&on_open);
let onopen_closure = Closure::wrap(Box::new(move |e: Event| { let onopen_closure = Closure::wrap(Box::new(move |e: Event| {
if unmounted.get() { if unmounted.load(std::sync::atomic::Ordering::Relaxed) {
return; return;
} }
@ -387,7 +387,7 @@ where
let on_error = Arc::clone(&on_error); let on_error = Arc::clone(&on_error);
let onmessage_closure = Closure::wrap(Box::new(move |e: MessageEvent| { let onmessage_closure = Closure::wrap(Box::new(move |e: MessageEvent| {
if unmounted.get() { if unmounted.load(std::sync::atomic::Ordering::Relaxed) {
return; return;
} }
@ -409,17 +409,18 @@ where
on_message_raw(&txt); on_message_raw(&txt);
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
SpecialNonReactiveZone::exit(prev); drop(zone);
match C::decode_str(&txt) { match C::decode_str(&txt) {
Ok(val) => { Ok(val) => {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let prev = SpecialNonReactiveZone::enter(); let prev =
diagnostics::SpecialNonReactiveZone::enter();
on_message(&val); on_message(&val);
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
drop(zone); drop(prev);
set_message.set(Some(val)); set_message.set(Some(val));
} }
@ -445,12 +446,12 @@ where
match C::decode_bin(array.as_slice()) { match C::decode_bin(array.as_slice()) {
Ok(val) => { Ok(val) => {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let prev = SpecialNonReactiveZone::enter(); let prev = diagnostics::SpecialNonReactiveZone::enter();
on_message(&val); on_message(&val);
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
SpecialNonReactiveZone::exit(prev); drop(prev);
set_message.set(Some(val)); set_message.set(Some(val));
} }
@ -472,7 +473,7 @@ where
let on_error = Arc::clone(&on_error); let on_error = Arc::clone(&on_error);
let onerror_closure = Closure::wrap(Box::new(move |e: Event| { let onerror_closure = Closure::wrap(Box::new(move |e: Event| {
if unmounted.get() { if unmounted.load(std::sync::atomic::Ordering::Relaxed) {
return; return;
} }
@ -501,7 +502,7 @@ where
let on_close = Arc::clone(&on_close); let on_close = Arc::clone(&on_close);
let onclose_closure = Closure::wrap(Box::new(move |e: CloseEvent| { let onclose_closure = Closure::wrap(Box::new(move |e: CloseEvent| {
if unmounted.get() { if unmounted.load(std::sync::atomic::Ordering::Relaxed) {
return; return;
} }
@ -524,7 +525,7 @@ where
onclose_closure.forget(); onclose_closure.forget();
} }
ws_ref.set_value(Some(web_socket)); ws_ref.set_value(Some(SendWrapper::new(web_socket)));
})) }))
}); });
} }
@ -642,7 +643,7 @@ where
on_open: Arc<dyn Fn(Event) + Send + Sync>, on_open: Arc<dyn Fn(Event) + Send + Sync>,
/// `WebSocket` message callback for typed message decoded by codec. /// `WebSocket` message callback for typed message decoded by codec.
#[builder(skip)] #[builder(skip)]
on_message: Arc<dyn Fn(&T)>, on_message: Arc<dyn Fn(&T) + Send + Sync>,
/// `WebSocket` message callback for text. /// `WebSocket` message callback for text.
on_message_raw: Arc<dyn Fn(&str) + Send + Sync>, on_message_raw: Arc<dyn Fn(&str) + Send + Sync>,
/// `WebSocket` message callback for binary. /// `WebSocket` message callback for binary.
@ -669,7 +670,7 @@ impl<T: ?Sized, E, D> UseWebSocketOptions<T, E, D> {
/// `WebSocket` error callback. /// `WebSocket` error callback.
pub fn on_error<F>(self, handler: F) -> Self pub fn on_error<F>(self, handler: F) -> Self
where where
F: Fn(UseWebSocketError<E, D>) + 'static, F: Fn(UseWebSocketError<E, D>) + Send + Sync + 'static,
{ {
Self { Self {
on_error: Arc::new(handler), on_error: Arc::new(handler),
@ -680,7 +681,7 @@ impl<T: ?Sized, E, D> UseWebSocketOptions<T, E, D> {
/// `WebSocket` message callback for typed message decoded by codec. /// `WebSocket` message callback for typed message decoded by codec.
pub fn on_message<F>(self, handler: F) -> Self pub fn on_message<F>(self, handler: F) -> Self
where where
F: Fn(&T) + 'static, F: Fn(&T) + Send + Sync + 'static,
{ {
Self { Self {
on_message: Arc::new(handler), on_message: Arc::new(handler),
@ -710,7 +711,7 @@ impl<T: ?Sized, E, D> Default for UseWebSocketOptions<T, E, D> {
#[derive(Clone)] #[derive(Clone)]
pub struct UseWebSocketReturn<T, OpenFn, CloseFn, SendFn> pub struct UseWebSocketReturn<T, OpenFn, CloseFn, SendFn>
where where
T: 'static, T: Send + Sync + 'static,
OpenFn: Fn() + Clone + 'static, OpenFn: Fn() + Clone + 'static,
CloseFn: Fn() + Clone + 'static, CloseFn: Fn() + Clone + 'static,
SendFn: Fn(&T) + Clone + 'static, SendFn: Fn(&T) + Clone + 'static,

View file

@ -11,6 +11,7 @@ macro_rules! use_derive_signal {
$(#[$outer])* $(#[$outer])*
pub fn $name<V $(, $( $type_param ),* )? >(value: V) -> Signal<$return_type> pub fn $name<V $(, $( $type_param ),* )? >(value: V) -> Signal<$return_type>
where where
$inner_signal_type $(< $( $inner_type_param ),+ >)?: Send + Sync,
V: Into<MaybeSignal<$inner_signal_type $(< $( $inner_type_param ),+ >)?>> $(, $( $type_param $( : $first_bound $(+ $rest_bound)* )? ),+ )? V: Into<MaybeSignal<$inner_signal_type $(< $( $inner_type_param ),+ >)?>> $(, $( $type_param $( : $first_bound $(+ $rest_bound)* )? ),+ )?
{ {
let value = value.into(); let value = value.into();