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",
"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/),
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
### 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
### Braking Changes 🛠
- 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.
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
### Braking Changes 🛠
- `use_event_listener` no longer returns a `Box<dyn Fn()>` but a `impl Fn() + Clone`
### Changes 🔥

View file

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

View file

@ -77,14 +77,20 @@ impl From<&str> for MaybeRwSignal<String> {
}
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 {
Self::DynamicRead(s) => {
// TODO : this feels hacky
let (_, w) = create_signal(cx, s.get_untracked());
(*s, w)
let (r, w) = create_signal(cx, s.get_untracked());
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) => {
let (r, w) = create_signal(cx, v.clone());
(Signal::from(r), w)

View file

@ -15,10 +15,10 @@ macro_rules! use_specific_storage {
cx: Scope,
key: &str,
defaults: D,
) -> (ReadSignal<T>, WriteSignal<T>, impl Fn() + Clone)
) -> (Signal<T>, WriteSignal<T>, impl Fn() + Clone)
where
for<'de> T: Serialize + Deserialize<'de> + Clone + 'static,
D: Into<MaybeSignal<T>>,
D: Into<MaybeRwSignal<T>>,
T: Clone,
{
[<use_ $storage_name _storage_with_options>](cx, key, defaults, UseSpecificStorageOptions::default())
@ -34,10 +34,10 @@ macro_rules! use_specific_storage {
key: &str,
defaults: D,
options: UseSpecificStorageOptions<T>,
) -> (ReadSignal<T>, WriteSignal<T>, impl Fn() + Clone)
) -> (Signal<T>, WriteSignal<T>, impl Fn() + Clone)
where
for<'de> T: Serialize + Deserialize<'de> + Clone + 'static,
D: Into<MaybeSignal<T>>,
D: Into<MaybeRwSignal<T>>,
T: Clone,
{
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::{use_storage_with_options, StorageType};
use leptos::*;

View file

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

View file

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

View file

@ -1,4 +1,4 @@
use crate::core::ElementMaybeSignal;
use crate::core::{ElementMaybeSignal, MaybeRwSignal};
#[cfg(feature = "storage")]
use crate::storage::{use_storage_with_options, UseStorageOptions};
#[cfg(feature = "storage")]
@ -248,7 +248,7 @@ where
UseColorModeReturn {
mode,
set_mode: set_store,
store: store.into(),
store,
set_store,
system,
state,
@ -269,20 +269,21 @@ pub enum ColorMode {
cfg_if! { if #[cfg(feature = "storage")] {
fn get_store_signal(
cx: Scope,
initial_value: MaybeSignal<ColorMode>,
initial_value: MaybeRwSignal<ColorMode>,
storage_signal: Option<RwSignal<ColorMode>>,
storage_key: &str,
storage_enabled: bool,
storage: StorageType,
listen_to_storage_changes: bool,
) -> (ReadSignal<ColorMode>, WriteSignal<ColorMode>) {
) -> (Signal<ColorMode>, WriteSignal<ColorMode>) {
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 {
let (store, set_store, _) = use_storage_with_options(
cx,
storage_key,
initial_value.get_untracked(),
initial_value,
UseStorageOptions::default()
.listen_to_storage_changes(listen_to_storage_changes)
.storage_type(storage),
@ -290,23 +291,24 @@ cfg_if! { if #[cfg(feature = "storage")] {
(store, set_store)
} else {
create_signal(cx, initial_value.get_untracked())
initial_value.into_signal(cx)
}
}
} else {
fn get_store_signal(
cx: Scope,
initial_value: MaybeSignal<ColorMode>,
initial_value: MaybeRwSignal<ColorMode>,
storage_signal: Option<RwSignal<ColorMode>>,
_storage_key: &String,
_storage_enabled: bool,
_storage: StorageType,
_listen_to_storage_changes: bool,
) -> (ReadSignal<ColorMode>, WriteSignal<ColorMode>) {
) -> (Signal<ColorMode>, WriteSignal<ColorMode>) {
if let Some(storage_signal) = storage_signal {
storage_signal.split()
let (store, set_store) = storage_signal.split();
(store.into(), set_store)
} 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"`.
#[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: 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 list = list.clone();

View file

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

View file

@ -1,5 +1,6 @@
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
use crate::core::MaybeRwSignal;
use crate::watch;
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder;
@ -59,7 +60,7 @@ use wasm_bindgen::JsCast;
/// ## Server-Side Rendering
///
/// 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())
}
@ -67,18 +68,14 @@ pub fn use_favicon(cx: Scope) -> (ReadSignal<Option<String>>, WriteSignal<Option
pub fn use_favicon_with_options(
cx: Scope,
options: UseFaviconOptions,
) -> (ReadSignal<Option<String>>, WriteSignal<Option<String>>) {
) -> (Signal<Option<String>>, WriteSignal<Option<String>>) {
let UseFaviconOptions {
new_icon,
base_url,
rel,
} = options;
let (favicon, set_favicon) = create_signal(cx, new_icon.get_untracked());
if matches!(&new_icon, MaybeSignal::Dynamic(_)) {
create_effect(cx, move |_| set_favicon.set(new_icon.get()));
}
let (favicon, set_favicon) = new_icon.into_signal(cx);
cfg_if! { if #[cfg(not(feature = "ssr"))] {
let link_selector = format!("link[rel*=\"{rel}\"]");
@ -116,9 +113,9 @@ pub fn use_favicon_with_options(
/// Options for [`use_favicon_with_options`].
#[derive(DefaultBuilder)]
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)]
new_icon: MaybeSignal<Option<String>>,
new_icon: MaybeRwSignal<Option<String>>,
/// Base URL of the favicon. Defaults to "".
#[builder(into)]