use MaybeRwSignal where applicable. Fixes #4

This commit is contained in:
Maccesch 2023-07-15 16:48:29 +01:00
parent 6b9a272b12
commit e55f253e8d
12 changed files with 197 additions and 151 deletions

View file

@ -3,7 +3,13 @@
{ {
"type": "cargo", "type": "cargo",
"name": "Tests", "name": "Tests",
"cargoArgs": ["test", "--all-features"], "cargoArgs": ["test", "--features", "math,storage,docs"],
},
{
"type": "cargo",
"name": "Clippy",
"cargoArgs": ["+nightly", "clippy", "--features", "math,storage,docs", "--tests", "--", "-D", "warnings"],
"workingDir": "./",
}, },
] ]

View file

@ -3,6 +3,29 @@
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] -
### Braking Changes 🛠
- The following functions now accept a `MaybeRwSignal` as their initial/default value which means
you can use a synchronized `RwSignal` in those places.
- `use_color_mode`
- `use_cycle_list`
- `use_favicon`
- `use_storage`
- `use_local_storage`
- `use_session_storage`
- Instead of returning `ReadSignal`, the following functions now return `Signal`.
- `use_color_mode`
- `use_favicon`
- `use_storage`
- `use_local_storage`
- `use_session_storage`
### Fixes 🍕
- `use_drop_zone` now uses `.get_untracked()` in event handlers
## [0.5.0] - 2023-07-15 ## [0.5.0] - 2023-07-15
### New Functions 🚀 ### New Functions 🚀
@ -26,8 +49,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [0.4.0] - 2023-07-03 ## [0.4.0] - 2023-07-03
### Braking Changes 🛠 ### Braking Changes 🛠
- Required `leptos` version is now 0.4 - Required `leptos` version is now 0.4
- Following the changes in `leptos` there is no longer a `stable` crate feature required in order to use this library with a stable toolchain. - Following the changes in `leptos` there is no longer a `stable` crate feature required in order to use this library
with a stable toolchain.
If you want to use it with a nightly toolchain you have to enable the `nightly` feature only on `leptos` directly. If you want to use it with a nightly toolchain you have to enable the `nightly` feature only on `leptos` directly.
No change is required for `leptos-use` itself. No change is required for `leptos-use` itself.
@ -61,6 +86,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [0.3.0] - 2023-06-13 ## [0.3.0] - 2023-06-13
### Braking Changes 🛠 ### Braking Changes 🛠
- `use_event_listener` no longer returns a `Box<dyn Fn()>` but a `impl Fn() + Clone` - `use_event_listener` no longer returns a `Box<dyn Fn()>` but a `impl Fn() + Clone`
### Changes 🔥 ### Changes 🔥

View file

@ -53,7 +53,7 @@ To run all tests run
cargo test --all-features cargo test --all-features
``` ```
## Book ### Book
First you need to install First you need to install

View file

@ -77,14 +77,20 @@ impl From<&str> for MaybeRwSignal<String> {
} }
impl<T: Clone> MaybeRwSignal<T> { impl<T: Clone> MaybeRwSignal<T> {
pub fn to_signal(&self, cx: Scope) -> (Signal<T>, WriteSignal<T>) { pub fn into_signal(self, cx: Scope) -> (Signal<T>, WriteSignal<T>) {
match self { match self {
Self::DynamicRead(s) => { Self::DynamicRead(s) => {
// TODO : this feels hacky let (r, w) = create_signal(cx, s.get_untracked());
let (_, w) = create_signal(cx, s.get_untracked());
(*s, w) create_effect(cx, move |_| {
w.update(move |w| {
*w = s.get();
});
});
(r.into(), w)
} }
Self::DynamicRw(r, w) => (*r, *w), Self::DynamicRw(r, w) => (r, w),
Self::Static(v) => { Self::Static(v) => {
let (r, w) = create_signal(cx, v.clone()); let (r, w) = create_signal(cx, v.clone());
(Signal::from(r), w) (Signal::from(r), w)

View file

@ -15,10 +15,10 @@ macro_rules! use_specific_storage {
cx: Scope, cx: Scope,
key: &str, key: &str,
defaults: D, defaults: D,
) -> (ReadSignal<T>, WriteSignal<T>, impl Fn() + Clone) ) -> (Signal<T>, WriteSignal<T>, impl Fn() + Clone)
where where
for<'de> T: Serialize + Deserialize<'de> + Clone + 'static, for<'de> T: Serialize + Deserialize<'de> + Clone + 'static,
D: Into<MaybeSignal<T>>, D: Into<MaybeRwSignal<T>>,
T: Clone, T: Clone,
{ {
[<use_ $storage_name _storage_with_options>](cx, key, defaults, UseSpecificStorageOptions::default()) [<use_ $storage_name _storage_with_options>](cx, key, defaults, UseSpecificStorageOptions::default())
@ -34,10 +34,10 @@ macro_rules! use_specific_storage {
key: &str, key: &str,
defaults: D, defaults: D,
options: UseSpecificStorageOptions<T>, options: UseSpecificStorageOptions<T>,
) -> (ReadSignal<T>, WriteSignal<T>, impl Fn() + Clone) ) -> (Signal<T>, WriteSignal<T>, impl Fn() + Clone)
where where
for<'de> T: Serialize + Deserialize<'de> + Clone + 'static, for<'de> T: Serialize + Deserialize<'de> + Clone + 'static,
D: Into<MaybeSignal<T>>, D: Into<MaybeRwSignal<T>>,
T: Clone, T: Clone,
{ {
use_storage_with_options(cx, key, defaults, options.into_storage_options(StorageType::[<$storage_name:camel>])) use_storage_with_options(cx, key, defaults, options.into_storage_options(StorageType::[<$storage_name:camel>]))

View file

@ -1,3 +1,4 @@
use crate::core::MaybeRwSignal;
use crate::storage::shared::{use_specific_storage, UseSpecificStorageOptions}; use crate::storage::shared::{use_specific_storage, UseSpecificStorageOptions};
use crate::storage::{use_storage_with_options, StorageType}; use crate::storage::{use_storage_with_options, StorageType};
use leptos::*; use leptos::*;

View file

@ -1,3 +1,4 @@
use crate::core::MaybeRwSignal;
use crate::storage::shared::{use_specific_storage, UseSpecificStorageOptions}; use crate::storage::shared::{use_specific_storage, UseSpecificStorageOptions};
use crate::storage::{use_storage_with_options, StorageType}; use crate::storage::{use_storage_with_options, StorageType};
use leptos::*; use leptos::*;

View file

@ -1,5 +1,6 @@
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports, dead_code))] #![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports, dead_code))]
use crate::core::MaybeRwSignal;
use crate::utils::{CloneableFn, CloneableFnWithArg, FilterOptions}; use crate::utils::{CloneableFn, CloneableFnWithArg, FilterOptions};
use crate::{ use crate::{
filter_builder_methods, use_event_listener, watch_pausable_with_options, DebounceOptions, filter_builder_methods, use_event_listener, watch_pausable_with_options, DebounceOptions,
@ -160,10 +161,10 @@ pub fn use_storage<T, D>(
cx: Scope, cx: Scope,
key: &str, key: &str,
defaults: D, defaults: D,
) -> (ReadSignal<T>, WriteSignal<T>, impl Fn() + Clone) ) -> (Signal<T>, WriteSignal<T>, impl Fn() + Clone)
where where
for<'de> T: Serialize + Deserialize<'de> + Clone + 'static, for<'de> T: Serialize + Deserialize<'de> + Clone + 'static,
D: Into<MaybeSignal<T>>, D: Into<MaybeRwSignal<T>>,
T: Clone, T: Clone,
{ {
use_storage_with_options(cx, key, defaults, UseStorageOptions::default()) use_storage_with_options(cx, key, defaults, UseStorageOptions::default())
@ -176,10 +177,10 @@ pub fn use_storage_with_options<T, D>(
key: &str, key: &str,
defaults: D, defaults: D,
options: UseStorageOptions<T>, options: UseStorageOptions<T>,
) -> (ReadSignal<T>, WriteSignal<T>, impl Fn() + Clone) ) -> (Signal<T>, WriteSignal<T>, impl Fn() + Clone)
where where
for<'de> T: Serialize + Deserialize<'de> + Clone + 'static, for<'de> T: Serialize + Deserialize<'de> + Clone + 'static,
D: Into<MaybeSignal<T>>, D: Into<MaybeRwSignal<T>>,
T: Clone, T: Clone,
{ {
let defaults = defaults.into(); let defaults = defaults.into();
@ -193,7 +194,9 @@ where
filter, filter,
} = options; } = options;
let (data, set_data) = create_signal(cx, defaults.get_untracked()); let (data, set_data) = defaults.into_signal(cx);
let raw_init = data.get();
cfg_if! { if #[cfg(feature = "ssr")] { cfg_if! { if #[cfg(feature = "ssr")] {
let remove: Box<dyn CloneableFn> = Box::new(|| {}); let remove: Box<dyn CloneableFn> = Box::new(|| {});
@ -202,26 +205,26 @@ where
let remove: Box<dyn CloneableFn> = match storage { let remove: Box<dyn CloneableFn> = match storage {
Ok(Some(storage)) => { Ok(Some(storage)) => {
let on_err = on_error.clone(); let write = {
let on_error = on_error.clone();
let storage = storage.clone();
let key = key.to_string();
let store = storage.clone(); move |v: &T| {
let k = key.to_string();
let write = move |v: &T| {
match serde_json::to_string(&v) { match serde_json::to_string(&v) {
Ok(ref serialized) => match store.get_item(&k) { Ok(ref serialized) => match storage.get_item(&key) {
Ok(old_value) => { Ok(old_value) => {
if old_value.as_ref() != Some(serialized) { if old_value.as_ref() != Some(serialized) {
if let Err(e) = store.set_item(&k, serialized) { if let Err(e) = storage.set_item(&key, serialized) {
on_err(UseStorageError::StorageAccessError(e)); on_error(UseStorageError::StorageAccessError(e));
} else { } else {
let mut event_init = web_sys::CustomEventInit::new(); let mut event_init = web_sys::CustomEventInit::new();
event_init.detail( event_init.detail(
&StorageEventDetail { &StorageEventDetail {
key: Some(k.clone()), key: Some(key.clone()),
old_value, old_value,
new_value: Some(serialized.clone()), new_value: Some(serialized.clone()),
storage_area: Some(store.clone()), storage_area: Some(storage.clone()),
} }
.into(), .into(),
); );
@ -239,25 +242,27 @@ where
} }
} }
Err(e) => { Err(e) => {
on_err.clone()(UseStorageError::StorageAccessError(e)); on_error.clone()(UseStorageError::StorageAccessError(e));
} }
}, },
Err(e) => { Err(e) => {
on_err.clone()(UseStorageError::SerializationError(e)); on_error.clone()(UseStorageError::SerializationError(e));
}
} }
} }
}; };
let store = storage.clone(); let read = {
let on_err = on_error.clone(); let storage = storage.clone();
let k = key.to_string(); let on_error = on_error.clone();
let def = defaults.clone(); let key = key.to_string();
let raw_init = raw_init.clone();
let read = move |event_detail: Option<StorageEventDetail>| -> Option<T> { move |event_detail: Option<StorageEventDetail>| -> Option<T> {
let raw_init = match serde_json::to_string(&def.get_untracked()) { let serialized_init = match serde_json::to_string(&raw_init) {
Ok(serialized) => Some(serialized), Ok(serialized) => Some(serialized),
Err(e) => { Err(e) => {
on_err.clone()(UseStorageError::DefaultSerializationError(e)); on_error.clone()(UseStorageError::DefaultSerializationError(e));
None None
} }
}; };
@ -265,15 +270,13 @@ where
let raw_value = if let Some(event_detail) = event_detail { let raw_value = if let Some(event_detail) = event_detail {
event_detail.new_value event_detail.new_value
} else { } else {
match store.get_item(&k) { match storage.get_item(&key) {
Ok(raw_value) => match raw_value { Ok(raw_value) => match raw_value {
Some(raw_value) => { Some(raw_value) => Some(merge_defaults(&raw_value, &raw_init)),
Some(merge_defaults(&raw_value, &def.get_untracked())) None => serialized_init.clone(),
}
None => raw_init.clone(),
}, },
Err(e) => { Err(e) => {
on_err.clone()(UseStorageError::StorageAccessError(e)); on_error.clone()(UseStorageError::StorageAccessError(e));
None None
} }
} }
@ -283,20 +286,21 @@ where
Some(raw_value) => match serde_json::from_str(&raw_value) { Some(raw_value) => match serde_json::from_str(&raw_value) {
Ok(v) => Some(v), Ok(v) => Some(v),
Err(e) => { Err(e) => {
on_err.clone()(UseStorageError::SerializationError(e)); on_error.clone()(UseStorageError::SerializationError(e));
None None
} }
}, },
None => { None => {
if let Some(raw_init) = &raw_init { if let Some(serialized_init) = &serialized_init {
if write_defaults { if write_defaults {
if let Err(e) = store.set_item(&k, raw_init) { if let Err(e) = storage.set_item(&key, serialized_init) {
on_err(UseStorageError::StorageAccessError(e)); on_error(UseStorageError::StorageAccessError(e));
} }
} }
} }
Some(def.get_untracked()) Some(raw_init)
}
} }
} }
}; };
@ -312,22 +316,24 @@ where
WatchOptions::default().filter(filter), WatchOptions::default().filter(filter),
); );
let k = key.to_string(); let update = {
let store = storage.clone(); let key = key.to_string();
let storage = storage.clone();
let raw_init = raw_init.clone();
let update = move |event_detail: Option<StorageEventDetail>| { move |event_detail: Option<StorageEventDetail>| {
if let Some(event_detail) = &event_detail { if let Some(event_detail) = &event_detail {
if event_detail.storage_area != Some(store) { if event_detail.storage_area != Some(storage) {
return; return;
} }
match &event_detail.key { match &event_detail.key {
None => { None => {
set_data.set(defaults.get_untracked()); set_data.set(raw_init);
return; return;
} }
Some(event_key) => { Some(event_key) => {
if event_key != &k { if event_key != &key {
return; return;
} }
} }
@ -347,6 +353,7 @@ where
} else { } else {
resume_watch(); resume_watch();
} }
}
}; };
let upd = update.clone(); let upd = update.clone();

View file

@ -1,4 +1,4 @@
use crate::core::ElementMaybeSignal; use crate::core::{ElementMaybeSignal, MaybeRwSignal};
#[cfg(feature = "storage")] #[cfg(feature = "storage")]
use crate::storage::{use_storage_with_options, UseStorageOptions}; use crate::storage::{use_storage_with_options, UseStorageOptions};
#[cfg(feature = "storage")] #[cfg(feature = "storage")]
@ -248,7 +248,7 @@ where
UseColorModeReturn { UseColorModeReturn {
mode, mode,
set_mode: set_store, set_mode: set_store,
store: store.into(), store,
set_store, set_store,
system, system,
state, state,
@ -269,20 +269,21 @@ pub enum ColorMode {
cfg_if! { if #[cfg(feature = "storage")] { cfg_if! { if #[cfg(feature = "storage")] {
fn get_store_signal( fn get_store_signal(
cx: Scope, cx: Scope,
initial_value: MaybeSignal<ColorMode>, initial_value: MaybeRwSignal<ColorMode>,
storage_signal: Option<RwSignal<ColorMode>>, storage_signal: Option<RwSignal<ColorMode>>,
storage_key: &str, storage_key: &str,
storage_enabled: bool, storage_enabled: bool,
storage: StorageType, storage: StorageType,
listen_to_storage_changes: bool, listen_to_storage_changes: bool,
) -> (ReadSignal<ColorMode>, WriteSignal<ColorMode>) { ) -> (Signal<ColorMode>, WriteSignal<ColorMode>) {
if let Some(storage_signal) = storage_signal { if let Some(storage_signal) = storage_signal {
storage_signal.split() let (store, set_store) = storage_signal.split();
(store.into(), set_store)
} else if storage_enabled { } else if storage_enabled {
let (store, set_store, _) = use_storage_with_options( let (store, set_store, _) = use_storage_with_options(
cx, cx,
storage_key, storage_key,
initial_value.get_untracked(), initial_value,
UseStorageOptions::default() UseStorageOptions::default()
.listen_to_storage_changes(listen_to_storage_changes) .listen_to_storage_changes(listen_to_storage_changes)
.storage_type(storage), .storage_type(storage),
@ -290,23 +291,24 @@ cfg_if! { if #[cfg(feature = "storage")] {
(store, set_store) (store, set_store)
} else { } else {
create_signal(cx, initial_value.get_untracked()) initial_value.into_signal(cx)
} }
} }
} else { } else {
fn get_store_signal( fn get_store_signal(
cx: Scope, cx: Scope,
initial_value: MaybeSignal<ColorMode>, initial_value: MaybeRwSignal<ColorMode>,
storage_signal: Option<RwSignal<ColorMode>>, storage_signal: Option<RwSignal<ColorMode>>,
_storage_key: &String, _storage_key: &String,
_storage_enabled: bool, _storage_enabled: bool,
_storage: StorageType, _storage: StorageType,
_listen_to_storage_changes: bool, _listen_to_storage_changes: bool,
) -> (ReadSignal<ColorMode>, WriteSignal<ColorMode>) { ) -> (Signal<ColorMode>, WriteSignal<ColorMode>) {
if let Some(storage_signal) = storage_signal { if let Some(storage_signal) = storage_signal {
storage_signal.split() let (store, set_store) = storage_signal.split();
(store.into(), set_store)
} else { } else {
create_signal(cx, initial_value.get_untracked()) initial_value.into_signal(cx)
} }
} }
}} }}
@ -368,7 +370,7 @@ where
/// Initial value of the color mode. Defaults to `"Auto"`. /// Initial value of the color mode. Defaults to `"Auto"`.
#[builder(into)] #[builder(into)]
initial_value: MaybeSignal<ColorMode>, initial_value: MaybeRwSignal<ColorMode>,
/// Custom modes that you plan to use as `ColorMode::Custom(x)`. Defaults to `vec![]`. /// Custom modes that you plan to use as `ColorMode::Custom(x)`. Defaults to `vec![]`.
custom_modes: Vec<String>, custom_modes: Vec<String>,

View file

@ -84,7 +84,7 @@ where
} }
}; };
let (state, set_state) = get_initial_value().to_signal(cx); let (state, set_state) = get_initial_value().into_signal(cx);
let index = { let index = {
let list = list.clone(); let list = list.clone();

View file

@ -100,7 +100,7 @@ where
update_files(&event); update_files(&event);
on_enter(UseDropZoneEvent { on_enter(UseDropZoneEvent {
files: files.get(), files: files.get_untracked(),
event, event,
}); });
}); });
@ -109,7 +109,7 @@ where
event.prevent_default(); event.prevent_default();
update_files(&event); update_files(&event);
on_over(UseDropZoneEvent { on_over(UseDropZoneEvent {
files: files.get(), files: files.get_untracked(),
event, event,
}); });
}); });
@ -124,7 +124,7 @@ where
update_files(&event); update_files(&event);
on_leave(UseDropZoneEvent { on_leave(UseDropZoneEvent {
files: files.get(), files: files.get_untracked(),
event, event,
}); });
}); });
@ -137,7 +137,7 @@ where
update_files(&event); update_files(&event);
on_drop(UseDropZoneEvent { on_drop(UseDropZoneEvent {
files: files.get(), files: files.get_untracked(),
event, event,
}); });
}); });

View file

@ -1,5 +1,6 @@
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))] #![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
use crate::core::MaybeRwSignal;
use crate::watch; use crate::watch;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
@ -59,7 +60,7 @@ use wasm_bindgen::JsCast;
/// ## Server-Side Rendering /// ## Server-Side Rendering
/// ///
/// On the server only the signals work but no favicon will be changed obviously. /// On the server only the signals work but no favicon will be changed obviously.
pub fn use_favicon(cx: Scope) -> (ReadSignal<Option<String>>, WriteSignal<Option<String>>) { pub fn use_favicon(cx: Scope) -> (Signal<Option<String>>, WriteSignal<Option<String>>) {
use_favicon_with_options(cx, UseFaviconOptions::default()) use_favicon_with_options(cx, UseFaviconOptions::default())
} }
@ -67,18 +68,14 @@ pub fn use_favicon(cx: Scope) -> (ReadSignal<Option<String>>, WriteSignal<Option
pub fn use_favicon_with_options( pub fn use_favicon_with_options(
cx: Scope, cx: Scope,
options: UseFaviconOptions, options: UseFaviconOptions,
) -> (ReadSignal<Option<String>>, WriteSignal<Option<String>>) { ) -> (Signal<Option<String>>, WriteSignal<Option<String>>) {
let UseFaviconOptions { let UseFaviconOptions {
new_icon, new_icon,
base_url, base_url,
rel, rel,
} = options; } = options;
let (favicon, set_favicon) = create_signal(cx, new_icon.get_untracked()); let (favicon, set_favicon) = new_icon.into_signal(cx);
if matches!(&new_icon, MaybeSignal::Dynamic(_)) {
create_effect(cx, move |_| set_favicon.set(new_icon.get()));
}
cfg_if! { if #[cfg(not(feature = "ssr"))] { cfg_if! { if #[cfg(not(feature = "ssr"))] {
let link_selector = format!("link[rel*=\"{rel}\"]"); let link_selector = format!("link[rel*=\"{rel}\"]");
@ -116,9 +113,9 @@ pub fn use_favicon_with_options(
/// Options for [`use_favicon_with_options`]. /// Options for [`use_favicon_with_options`].
#[derive(DefaultBuilder)] #[derive(DefaultBuilder)]
pub struct UseFaviconOptions { pub struct UseFaviconOptions {
/// New input favicon. Can be a `Signal` in which case updates will change the favicon. Defaults to None. /// New input favicon. Can be a `RwSignal` in which case updates will change the favicon. Defaults to None.
#[builder(into)] #[builder(into)]
new_icon: MaybeSignal<Option<String>>, new_icon: MaybeRwSignal<Option<String>>,
/// Base URL of the favicon. Defaults to "". /// Base URL of the favicon. Defaults to "".
#[builder(into)] #[builder(into)]